/*++ Copyright (c) 1999-2000 Microsoft Corporation Module Name: RemoteDesktopClient Abstract: The CRemoteDesktopClient class is the parent class for the Remote Desktop class hierarchy on the server-side. It helps the CRemoteDesktopClientHost class to implement the ISAFRemoteDesktopClient interface. The Remote Desktop class hierarchy provides a pluggable C++ interface for remote desktop access, by abstracting the implementation specific details of remote desktop access for the server-side. Author: Tad Brockway 02/00 Revision History: --*/ #include "stdafx.h" #ifdef TRC_FILE #undef TRC_FILE #endif #define TRC_FILE "_rdclnt" #include "RDCHost.h" #include "RemoteDesktopClient.h" #include #include "ClientDataChannelMgr.h" #include using namespace std; /////////////////////////////////////////////////////// // // CRemoteDesktopClientEventSink Methods // void __stdcall CRemoteDesktopClientEventSink::OnConnected() { m_Obj->OnConnected(); } void __stdcall CRemoteDesktopClientEventSink::OnDisconnected(long reason) { m_Obj->OnDisconnected(reason); } void __stdcall CRemoteDesktopClientEventSink::OnConnectRemoteDesktopComplete(long status) { m_Obj->OnConnectRemoteDesktopComplete(status); } void __stdcall CRemoteDesktopClientEventSink::OnListenConnect(long status) { m_Obj->OnListenConnect(status); } void __stdcall CRemoteDesktopClientEventSink::OnBeginConnect() { m_Obj->OnBeginConnect(); } /////////////////////////////////////////////////////// // // CRemoteDesktopClient Methods // HRESULT CRemoteDesktopClient::FinalConstruct() /*++ Routine Description: Final Constructor Arguments: Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CRemoteDesktopClient::FinalConstruct"); // // Register with ActiveX // HRESULT hr = S_OK; if (!AtlAxWinInit()) { TRC_ERR((TB, L"AtlAxWinInit failed.")); hr = E_FAIL; } // // Create the Data Channel Manager // m_ChannelMgr = new CComObject(); m_ChannelMgr->AddRef(); // // Initialize the channel mnager // hr = m_ChannelMgr->Initialize(); DC_END_FN(); return hr; } CRemoteDesktopClient::~CRemoteDesktopClient() /*++ Routine Description: Destructor Arguments: Return Value: --*/ { DC_BEGIN_FN("CRemoteDesktopClient::~CRemoteDesktopClient"); DisconnectFromServer(); // // !!!!NOTE!!!! // Cleaning up the contained m_Client control is being done in the destructor // for Windows XP to make sure it and the MSTSCAX control are not destroyed // in a callback to PC Health via a DisconnectFromServer invokation. Cleaning // up here removes late-binding of the protocol in that once we connect one time // to a particular protocol type (RDP only for XP), we can't later connect using // some other protocol. // // If we are to support other protocol types in the future, then the clean up // should be done in the DisconnectFromServer so we can rebind to a different protocol // on each ConnectToServer call. To make this work, we will need to clean up MSTCAX // and the TSRDP Salem control so they can be destroyed in a callback. I (TadB) actually // had this working for the TSRDP Salem control in an afternoon. // // Zero out the IO interface ptr for the channel manager, since it is // going away. // if (m_ChannelMgr != NULL) { m_ChannelMgr->SetIOInterface(NULL); } if (m_Client != NULL) { m_Client = NULL; } if (m_ClientWnd != NULL) { m_ClientAxView.DestroyWindow(); m_ClientWnd = NULL; } // // Release the data channel manager. // m_ChannelMgr->Release(); if ( NULL != m_ExtDllName ) SysFreeString( m_ExtDllName ); if ( NULL != m_ExtParams ) SysFreeString( m_ExtParams ); DC_END_FN(); } STDMETHODIMP CRemoteDesktopClient::get_IsServerConnected( BOOL *pVal ) /*++ Routine Description: Indicates whether the client-side Remote Desktop Host ActiveX Control is connected to the server, independent of whether the remote user's desktop is currently remote controlled. Arguments: pVal - Set to TRUE if the client is currently connected to the server. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CRemoteDesktopClient::get_IsServerConnected"); HRESULT hr; if (pVal == NULL) { ASSERT(FALSE); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto CLEANUPANDEXIT; } if (m_Client != NULL) { hr = m_Client->get_IsServerConnected(pVal); } else { *pVal = FALSE; hr = S_OK; } CLEANUPANDEXIT: DC_END_FN(); return hr; } STDMETHODIMP CRemoteDesktopClient::get_IsRemoteDesktopConnected( BOOL *pVal ) /*++ Routine Description: Indicates whether the control is currently connected to the server machine. Arguments: pVal - Sets to TRUE if the control is currently connected to the server. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CRemoteDesktopClient::get_IsRemoteDesktopConnected"); HRESULT hr; if (pVal == NULL) { ASSERT(FALSE); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto CLEANUPANDEXIT; } if (m_Client != NULL) { hr = m_Client->get_IsRemoteDesktopConnected(pVal); } else { hr = HRESULT_FROM_WIN32(ERROR_NOT_CONNECTED); } CLEANUPANDEXIT: DC_END_FN(); return hr; } STDMETHODIMP CRemoteDesktopClient::get_ExtendedErrorInfo( LONG *error ) /*++ Routine Description: Arguments: Return Value: --*/ { DC_BEGIN_FN("CRemoteDesktopClient::get_ExtendedErrorInfo"); HRESULT hr; if (error == NULL) { ASSERT(FALSE); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto CLEANUPANDEXIT; } if (m_Client != NULL) { hr = m_Client->get_ExtendedErrorInfo(error); } else { hr = S_OK; *error = SAFERROR_NOERROR; } CLEANUPANDEXIT: DC_END_FN(); return hr; } STDMETHODIMP CRemoteDesktopClient::DisconnectRemoteDesktop() /*++ Routine Description: Arguments: Return Value: --*/ { DC_BEGIN_FN("CRemoteDesktopClient::DisconnectRemoteDesktop"); HRESULT hr; // // Hide our window. // //ShowWindow(SW_HIDE); m_RemoteControlEnabled = FALSE; if (m_Client != NULL) { hr = m_Client->DisconnectRemoteDesktop(); } else { hr = HRESULT_FROM_WIN32(ERROR_NOT_CONNECTED); } DC_END_FN(); return hr; } STDMETHODIMP CRemoteDesktopClient::ConnectRemoteDesktop() /*++ Routine Description: Arguments: Return Value: --*/ { DC_BEGIN_FN("CRemoteDesktopClient::ConnectRemoteDesktop"); HRESULT hr; if (m_Client != NULL) { hr = m_Client->ConnectRemoteDesktop(); } else { hr = HRESULT_FROM_WIN32(ERROR_NOT_CONNECTED); } DC_END_FN(); return hr; } HRESULT CRemoteDesktopClient::InitializeRemoteDesktopClientObject() /*++ Routine Description: Routine to initialize window for actvie x control and setups our channel manager Parameters: None. Returns: S_OK or error code. Notes: put_EnableSmartSizing() is not invoked in here because on listen mode, we create/initialize object first then goes into actual connection, two seperate calls, and so it is possible for caller to invoke smartsizeing in-between and we will never pick it up, both ConnectToServer() and AcceptListenConnection() need to make the call. --*/ { DC_BEGIN_FN("CRemoteDesktopClient::InitializeRemoteDesktopClientObject"); HRESULT hr = S_OK; IUnknown *pUnk = NULL; RECT ourWindowRect; RECT rcClient; CComPtr ioInterface; // // Get the dimensions of our window. // if (!::GetWindowRect(m_hWnd, &ourWindowRect)) { hr = HRESULT_FROM_WIN32(GetLastError()); TRC_ERR((TB, L"GetWindowRect: %08X", hr)); goto CLEANUPANDEXIT; } // // Create the client Window. // rcClient.top = 0; rcClient.left = 0; rcClient.right = ourWindowRect.right - ourWindowRect.left; rcClient.bottom = ourWindowRect.bottom - ourWindowRect.top; m_ClientWnd = m_ClientAxView.Create( m_hWnd, rcClient, REMOTEDESKTOPRDPCLIENT_TEXTGUID, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0 ); if (m_ClientWnd == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); TRC_ERR((TB, L"Window Create: %08X", GetLastError())); goto CLEANUPANDEXIT; } ASSERT(::IsWindow(m_ClientWnd)); // // Get IUnknown // hr = AtlAxGetControl(m_ClientWnd, &pUnk); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"AtlAxGetControl: %08X", hr)); pUnk = NULL; goto CLEANUPANDEXIT; } // // Get the control. // hr = pUnk->QueryInterface(__uuidof(ISAFRemoteDesktopClient), (void**)&m_Client); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"QueryInterface: %08X", hr)); goto CLEANUPANDEXIT; } // // Initialize the event sink. // m_ClientEventSink.m_Obj = this; // // Add the event sink. // hr = m_ClientEventSink.DispEventAdvise(pUnk); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"DispEventAdvise: %08X", hr)); goto CLEANUPANDEXIT; } // // Get the Data IO interface from the control so we can talk // over an OOB data channel. // hr = pUnk->QueryInterface(__uuidof(IDataChannelIO), (void**)&ioInterface); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"QueryInterface: %08X", hr)); goto CLEANUPANDEXIT; } // // Pass the channel manager to the control. // ioInterface->put_ChannelMgr(m_ChannelMgr); // // Indicate the current data io provider to the channel manager // because it just changed. // m_ChannelMgr->SetIOInterface(ioInterface); CLEANUPANDEXIT: // // m_Client keeps our reference to the client object until // it ref counts to zero. // if (pUnk != NULL) { pUnk->Release(); } return hr; } STDMETHODIMP CRemoteDesktopClient::ConnectToServer(BSTR bstrExpertBlob) /*++ Routine Description: Arguments: bstrExpertBlob : Optional blob to be transmitted over to user side, this is used only in the case of SAF resolver. Return Value: --*/ { DC_BEGIN_FN("CRemoteDesktopClient::ConnectToServer"); HRESULT hr; DWORD protocolType; CComBSTR tmp; CComBSTR helpSessionId; DWORD result; CComBSTR channelInfo; ChannelsType::iterator element; DWORD dwConnParmVersion; WCHAR buf[MAX_PATH]; // // Check the connection parameters. // if (m_ConnectParms.Length() == 0) { ASSERT(FALSE); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto CLEANUPANDEXIT; } // // Parse the connection parms to get the type of server // to which we are connecting. // result = ParseConnectParmsString( m_ConnectParms, &dwConnParmVersion, &protocolType, tmp, tmp, tmp, helpSessionId, tmp, tmp, tmp ); if (result != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(result); goto CLEANUPANDEXIT; } // // Right now, we only support the TSRDP client. // TODO: We should make this pluggable for Whistler timeframe // via registry defined CLSID's. // if (protocolType != REMOTEDESKTOP_TSRDP_PROTOCOL) { TRC_ERR((TB, L"Unsupported protocol: %ld", protocolType)); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto CLEANUPANDEXIT; } if( m_Client == NULL) { hr = InitializeRemoteDesktopClientObject(); if( FAILED(hr) ) { TRC_ERR((TB, L"InitializeRemoteDesktopClientObject() failed with : %x", hr)); goto CLEANUPANDEXIT; } } // // Enable/disable smart sizing. // hr = m_Client->put_EnableSmartSizing(m_EnableSmartSizing); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } hr = m_Client->put_ColorDepth(m_ColorDepth); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } // // setup the test extension // _PutExtParams(); // // Connect. // m_Client->put_ConnectParms(m_ConnectParms); hr = m_Client->ConnectToServer(bstrExpertBlob); CLEANUPANDEXIT: DC_END_FN(); return hr; } STDMETHODIMP CRemoteDesktopClient::DisconnectFromServer() /*++ Routine Description: Arguments: Return Value: --*/ { DC_BEGIN_FN("CRemoteDesktopClient::DisconnectFromServer"); // // Hide our window. // //ShowWindow(SW_HIDE); // // Notify the contained client object. // if (m_Client != NULL) { m_Client->DisconnectFromServer(); } DC_END_FN(); return S_OK; } // // send parameters to ISAFRemoteDesktopTestExtension // HRESULT CRemoteDesktopClient::_PutExtParams( VOID ) { ISAFRemoteDesktopTestExtension *pExt = NULL; HRESULT hr = E_NOTIMPL; DC_BEGIN_FN("CRemoteDesktopClient::_PutExtParams"); if ( NULL == m_ExtDllName ) { hr = S_OK; goto CLEANUPANDEXIT; } if (m_Client == NULL) { hr = HRESULT_FROM_WIN32(ERROR_NOT_CONNECTED); goto CLEANUPANDEXIT; } hr = m_Client->QueryInterface( __uuidof( ISAFRemoteDesktopTestExtension ), (void **)&pExt ); if (FAILED( hr )) { TRC_ERR((TB, L"QueryInterface( ISAFRemoteDesktopTestExtension ), failed %08X", hr)); goto CLEANUPANDEXIT; } hr = pExt->put_TestExtDllName( m_ExtDllName ); if (FAILED( hr )) { TRC_ERR((TB, L"put_TestExtDllName failed %08X", hr )); goto CLEANUPANDEXIT; } if ( NULL != m_ExtParams ) hr = pExt->put_TestExtParams( m_ExtParams ); CLEANUPANDEXIT: if ( NULL != pExt ) pExt->Release(); DC_END_FN(); return hr; } STDMETHODIMP CRemoteDesktopClient::StartListen( /*[in]*/ LONG timeout ) /*++ Description: Put client (expert) in listening on socket port listening_port and wait for TS server to connect. Parameters: listening_port : Port to listen on, 0 for dynamic port. timeout : Listen timeout. pConnectParm : Return Salem specific connection parameter for ISAFRemoteDesktopServerHost object to connect to this client (expert). returns: S_OK or error code. Notes: Function is async, return code, if error, is for listening thread set up, caller is notified of successful or error in network connection via ListenConnect event. --*/ { HRESULT hr; if( m_Client != NULL ) { hr = m_Client->StartListen( timeout ); } else { hr = E_FAIL; } CLEANUPANDEXIT: return hr; } STDMETHODIMP CRemoteDesktopClient::CreateListenEndpoint( /*[in]*/ LONG listening_port, /*[out, retval]*/ BSTR* pConnectParm ) /*++ Description: Put client (expert) in listening on socket port listening_port and wait for TS server to connect. Parameters: listening_port : Port to listen on, 0 for dynamic port. pConnectParm : Return Salem specific connection parameter for ISAFRemoteDesktopServerHost object to connect to this client (expert). returns: S_OK or error code. Notes: Function is async, return code, if error, is for listening thread set up, caller is notified of successful or error in network connection via ListenConnect event. --*/ { HRESULT hr; if( NULL == pConnectParm ) { hr = E_INVALIDARG; } else { if( m_Client == NULL ) { hr = InitializeRemoteDesktopClientObject(); if( FAILED(hr) ) { goto CLEANUPANDEXIT; } } hr = m_Client->CreateListenEndpoint( listening_port, pConnectParm ); } CLEANUPANDEXIT: return hr; } STDMETHODIMP CRemoteDesktopClient::StopListen() /*++ Description: Stop listening waiting for TS server (helpee, user) to connect. Parameters: None. Returns: S_OK or error code. --*/ { HRESULT hr; if( m_Client != NULL ) { hr = m_Client->StopListen(); } else { hr = E_FAIL; } return hr; } STDMETHODIMP CRemoteDesktopClient::AcceptListenConnection( /*[in]*/ BSTR expertBlob ) /*++ Description: Stop listening waiting for TS server (helpee, user) to connect. Parameters: None. Returns: S_OK or error code. --*/ { HRESULT hr = S_OK; DWORD protocolType; CComBSTR tmp; CComBSTR helpSessionId; DWORD result; CComBSTR channelInfo; ChannelsType::iterator element; DWORD dwConnParmVersion; WCHAR buf[MAX_PATH]; DC_BEGIN_FN("CRemoteDesktopClient::AcceptListenConnection"); // // Check the connection parameters. // if (m_ConnectParms.Length() == 0 || m_Client == NULL) { ASSERT(FALSE); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto CLEANUPANDEXIT; } // // Parse the connection parms to get the type of server // to which we are connecting. // result = ParseConnectParmsString( m_ConnectParms, &dwConnParmVersion, &protocolType, tmp, tmp, tmp, helpSessionId, tmp, tmp, tmp ); if (result != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(result); goto CLEANUPANDEXIT; } // // Right now, we only support the TSRDP client. // TODO: We should make this pluggable for Whistler timeframe // via registry defined CLSID's. // if (protocolType != REMOTEDESKTOP_TSRDP_PROTOCOL) { TRC_ERR((TB, L"Unsupported protocol: %ld", protocolType)); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto CLEANUPANDEXIT; } // // Enable/disable smart sizing. // hr = m_Client->put_EnableSmartSizing(m_EnableSmartSizing); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } hr = m_Client->put_ColorDepth(m_ColorDepth); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } // // setup the test extension // _PutExtParams(); // // Connect. // m_Client->put_ConnectParms(m_ConnectParms); hr = m_Client->AcceptListenConnection(expertBlob); CLEANUPANDEXIT: DC_END_FN(); return hr; }