#include "host_internal.h" #include #include #include #include #pragma warning(push) #pragma warning(disable : 4471) #include "mstscax.tlh" #pragma warning(pop) #include #include #include namespace { constexpr wchar_t kNativeHostWindowClassName[] = L"Navop.WindowsRdpHost.Container"; // 1Remote deliberately hosts the broadly deployed // MsRdpClient9NotSafeForScripting control instead of the newest RDP control. // Keep the native smoke host on the same conservative control generation // until the GPUI/ActiveX hosting path is proven on real Windows machines. constexpr CLSID kMsRdpClient9NotSafeForScriptingClsid = { 0x8b918b82, 0x7985, 0x4c24, {0x89, 0xdf, 0xc3, 0x3a, 0xd2, 0xbb, 0xfb, 0xcd}, }; // CAxHostWindow::_CreatorClass constructs a CComObject. // CComObject takes an ATL module lock in its constructor, so an executable // which only links this native code through a Rust static library must still // provide a live CAtlModule. Without it, ATL dereferences a null _pAtlModule // inside AtlAxAttachControl before that inline helper can return an HRESULT. class WindowsRdpAtlModule final : public CAtlModuleT {}; WindowsRdpAtlModule windows_rdp_atl_module; // Distinguishable failure for "the ActiveX drawing window is not inside the // host subtree yet". The connection may still complete; callers re-synchronize // bounds after the next LoginComplete/Reconnected instead of treating this as a // terminal error. 'NA' is the Navop facility tag. constexpr HRESULT kPresentationIncompleteHresult = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x4E41); LRESULT CALLBACK native_host_window_procedure( HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept { return DefWindowProcW(window, message, wparam, lparam); } DWORD ensure_native_host_window_class(HINSTANCE instance) noexcept { WNDCLASSEXW window_class{}; window_class.cbSize = sizeof(window_class); window_class.lpfnWndProc = native_host_window_procedure; window_class.hInstance = instance; window_class.lpszClassName = kNativeHostWindowClassName; SetLastError(ERROR_SUCCESS); if (RegisterClassExW(&window_class) != 0) { return ERROR_SUCCESS; } const DWORD error = GetLastError(); return error == ERROR_CLASS_ALREADY_EXISTS ? ERROR_SUCCESS : error; } struct ActiveXCleanup { bool ole_initialized = false; bool atl_initialized = false; HWND parent_window = nullptr; HWND host_window = nullptr; // Navop-side generation of the owning NativeRdpHost. HWNDs are recycled by // Win32, so a destroy log without the generation cannot be correlated with // one specific RDP session. uint64_t host_generation = 0; CComPtr container; CComPtr control; CComPtr in_place_object; CComPtr client; CComPtr non_scriptable; CComPtr non_scriptable3; CComPtr non_scriptable5; NativeRdpEventSubscription* event_subscription = nullptr; ~ActiveXCleanup() noexcept { destroy_event_subscription(event_subscription); event_subscription = nullptr; non_scriptable5.Release(); non_scriptable3.Release(); non_scriptable.Release(); client.Release(); in_place_object.Release(); control.Release(); container.Release(); destroy_host_window(); if (atl_initialized) { AtlAxWinTerm(); } if (ole_initialized) { OleUninitialize(); } } // Destroys the ATL host window and records whether Win32 accepted the call. // // Returns nothing and never throws: the surrounding destructor is `noexcept` // and there is no recovery for a failed `DestroyWindow`. What matters for // diagnosis is that a failure is *visible* — previously the call site // ignored both the return value and `GetLastError`, so a leaked host HWND // looked identical to a clean teardown from the Rust side. // // Must run on the thread that created the window; Win32 fails the call when // it does not, and that case is exactly what the thread fields expose. void destroy_host_window() noexcept { if (host_window == nullptr) { return; } const HWND window = host_window; // Win32 stops reporting a thread id once the window is gone, so the // owner thread has to be captured first. const uint32_t window_thread_id = GetWindowThreadProcessId(window, nullptr); trace_native_stage("destroy.host_window.before"); SetLastError(ERROR_SUCCESS); const BOOL destroyed = DestroyWindow(window); const DWORD destroy_error = destroyed ? ERROR_SUCCESS : GetLastError(); const uint32_t still_alive = IsWindow(window) != 0 ? UINT32_C(1) : UINT32_C(0); log_native_host_window_destroy( destroyed ? "destroy.host_window.after" : "destroy.host_window.failed", reinterpret_cast(window), reinterpret_cast(parent_window), host_generation, window_thread_id, destroyed ? INT32_C(1) : INT32_C(0), static_cast(destroy_error), still_alive); // Never retry: a failed DestroyWindow means the window belongs to // another thread or the handle is no longer valid. Retrying would only // risk a double release through a recycled handle. host_window = nullptr; } }; HRESULT attach_control_with_traces(ActiveXCleanup& resources) noexcept { // AtlAxAttachControl is an inline wrapper around three distinct ATL // operations. Keep them explicit here so a Windows crash identifies // whether ATL fails while allocating its host object, exposing // IAxWinHostWindow, or activating the RDP control. trace_native_pointer( "create.atl_module", reinterpret_cast(ATL::_pAtlModule)); trace_native_stage("create.atl_container_instance.before"); HRESULT result = CAxHostWindow::_CreatorClass::CreateInstance( nullptr, __uuidof(IUnknown), reinterpret_cast(&resources.container)); trace_native_hresult( "create.atl_container_instance.after", static_cast(result)); if (FAILED(result) || resources.container == nullptr) { return FAILED(result) ? result : E_UNEXPECTED; } CComPtr host; trace_native_stage("create.query_ax_host.before"); result = resources.container->QueryInterface(&host); trace_native_hresult( "create.query_ax_host.after", static_cast(result)); if (FAILED(result) || host == nullptr) { return FAILED(result) ? result : E_NOINTERFACE; } trace_native_stage("create.ax_host_attach.before"); result = host->AttachControl( resources.control, resources.host_window); trace_native_hresult( "create.ax_host_attach.after", static_cast(result)); return result; } struct PresentationWindowError { DWORD code; HRESULT result; }; PresentationWindowError presentation_window_error(DWORD error) noexcept { const DWORD code = error == ERROR_SUCCESS ? ERROR_INVALID_WINDOW_HANDLE : error; return PresentationWindowError{ code, code == ERROR_INVALID_WINDOW_HANDLE ? kPresentationIncompleteHresult : HRESULT_FROM_WIN32(code), }; } HRESULT position_direct_control_window( ActiveXCleanup& resources, HWND control_window, const RECT& client_rect) noexcept { const HWND control_parent = GetParent(control_window); trace_native_pointer( "presentation.control_parent", reinterpret_cast(control_parent)); if (control_parent != resources.host_window) { // The control window is not a direct child of the native host. The // real drawing surface may live deeper in the subtree; never report // silent success when it cannot be positioned. if (!IsChild(resources.host_window, control_window)) { trace_native_stage( "presentation.position_control_window.control_not_descendant"); return kPresentationIncompleteHresult; } trace_native_stage( "presentation.position_control_window.non_direct_descendant"); RECT mapped_rect = client_rect; SetLastError(ERROR_SUCCESS); const int mapped_points = MapWindowPoints( resources.host_window, control_parent, reinterpret_cast(&mapped_rect), 2); const DWORD map_error = GetLastError(); trace_native_win32( "presentation.position_control_window.map_points", static_cast(mapped_points)); if (mapped_points == 0 && map_error != ERROR_SUCCESS) { const PresentationWindowError error = presentation_window_error(map_error); trace_native_win32( "presentation.position_control_window.map_failed", static_cast(error.code)); return error.result; } UINT descendant_flags = SWP_NOZORDER | SWP_NOACTIVATE; if (IsWindowVisible(resources.host_window)) { descendant_flags |= SWP_SHOWWINDOW; } SetLastError(ERROR_SUCCESS); if (!SetWindowPos( control_window, nullptr, mapped_rect.left, mapped_rect.top, mapped_rect.right - mapped_rect.left, mapped_rect.bottom - mapped_rect.top, descendant_flags)) { const PresentationWindowError error = presentation_window_error(GetLastError()); trace_native_win32( "presentation.position_control_window.descendant_failed", static_cast(error.code)); return error.result; } trace_native_rect( "presentation.position_control_window.descendant_mapped", static_cast(mapped_rect.left), static_cast(mapped_rect.top), static_cast(mapped_rect.right), static_cast(mapped_rect.bottom)); return S_OK; } UINT position_flags = SWP_NOZORDER | SWP_NOACTIVATE; if (IsWindowVisible(resources.host_window)) { position_flags |= SWP_SHOWWINDOW; } trace_native_stage("presentation.position_control_window.before"); SetLastError(ERROR_SUCCESS); if (SetWindowPos( control_window, nullptr, 0, 0, client_rect.right - client_rect.left, client_rect.bottom - client_rect.top, position_flags)) { trace_native_stage("presentation.position_control_window.after"); return S_OK; } const PresentationWindowError error = presentation_window_error(GetLastError()); trace_native_win32( "presentation.position_control_window.failed", static_cast(error.code)); return error.result; } struct DescendantTraceContext { uint32_t count = 0; }; BOOL CALLBACK trace_descendant_window( HWND window, LPARAM context_pointer) noexcept { auto* context = reinterpret_cast( context_pointer); if (context == nullptr) { return FALSE; } wchar_t native_class_name[256]{}; const int class_name_len = GetClassNameW( window, native_class_name, static_cast( sizeof(native_class_name) / sizeof(native_class_name[0]))); uint16_t class_name[256]{}; for (int index = 0; index < class_name_len; ++index) { class_name[index] = static_cast( native_class_name[index]); } RECT rect{}; SetLastError(ERROR_SUCCESS); if (!GetWindowRect(window, &rect)) { const DWORD last_error = GetLastError(); trace_native_win32( "presentation.control_descendant_rect.failed", static_cast( last_error == ERROR_SUCCESS ? ERROR_INVALID_WINDOW_HANDLE : last_error)); } trace_native_window( "presentation.control_descendant", context->count, reinterpret_cast(window), reinterpret_cast(GetParent(window)), IsWindowVisible(window) ? 1U : 0U, static_cast(GetWindowLongPtrW(window, GWL_STYLE)), static_cast(GetWindowLongPtrW(window, GWL_EXSTYLE)), static_cast(rect.left), static_cast(rect.top), static_cast(rect.right), static_cast(rect.bottom), class_name, class_name_len > 0 ? static_cast(class_name_len) : UINT32_C(0)); ++context->count; return TRUE; } void trace_control_descendants(HWND control_window) noexcept { DescendantTraceContext context; SetLastError(ERROR_SUCCESS); if (!EnumChildWindows( control_window, trace_descendant_window, reinterpret_cast(&context))) { const DWORD error = GetLastError(); if (error != ERROR_SUCCESS) { trace_native_win32( "presentation.enumerate_control_descendants.failed", static_cast(error)); } } trace_native_win32( "presentation.control_descendant_count", context.count); } void trace_presentation_window_state( const ActiveXCleanup& resources, HWND control_window) noexcept { trace_native_pointer( "presentation.host_parent", reinterpret_cast(GetParent(resources.host_window))); trace_native_pointer( "presentation.control_root", reinterpret_cast( GetAncestor(control_window, GA_ROOT))); trace_native_pointer( "presentation.control_root_owner", reinterpret_cast( GetAncestor(control_window, GA_ROOTOWNER))); trace_native_win32( "presentation.control_is_host_descendant", IsChild(resources.host_window, control_window) ? 1U : 0U); trace_native_win32( "presentation.host_visible", IsWindowVisible(resources.host_window) ? 1U : 0U); trace_native_win32( "presentation.control_visible", IsWindowVisible(control_window) ? 1U : 0U); const HWND owner = GetParent(resources.host_window); if (owner != nullptr) { trace_native_win32( "presentation.owner_visible", IsWindowVisible(owner) ? 1U : 0U); trace_native_win32( "presentation.owner_iconic", IsIconic(owner) ? 1U : 0U); trace_native_win32( "presentation.owner_dpi", static_cast(GetDpiForWindow(owner))); } trace_native_win32( "presentation.host_dpi", static_cast(GetDpiForWindow(resources.host_window))); trace_native_win32( "presentation.control_dpi", static_cast(GetDpiForWindow(control_window))); RECT host_rect{}; if (GetWindowRect(resources.host_window, &host_rect)) { trace_native_rect( "presentation.host_window_rect", static_cast(host_rect.left), static_cast(host_rect.top), static_cast(host_rect.right), static_cast(host_rect.bottom)); } RECT control_rect{}; if (GetWindowRect(control_window, &control_rect)) { trace_native_rect( "presentation.control_window_rect", static_cast(control_rect.left), static_cast(control_rect.top), static_cast(control_rect.right), static_cast(control_rect.bottom)); } trace_native_win32( "presentation.control_window_style", static_cast(GetWindowLongPtrW( control_window, GWL_STYLE))); trace_control_descendants(control_window); } HRESULT synchronize_control_bounds(ActiveXCleanup& resources) noexcept { if (resources.in_place_object == nullptr || resources.host_window == nullptr) { return E_UNEXPECTED; } RECT client_rect{}; SetLastError(ERROR_SUCCESS); trace_native_stage("presentation.get_client_rect.before"); if (!GetClientRect(resources.host_window, &client_rect)) { const DWORD last_error = GetLastError(); const DWORD win32_code = last_error == ERROR_SUCCESS ? ERROR_INVALID_WINDOW_HANDLE : last_error; trace_native_win32( "presentation.get_client_rect.failed", static_cast(win32_code)); return HRESULT_FROM_WIN32(win32_code); } trace_native_rect( "presentation.host_client_rect", static_cast(client_rect.left), static_cast(client_rect.top), static_cast(client_rect.right), static_cast(client_rect.bottom)); trace_native_stage("presentation.set_object_rects.before"); const HRESULT layout_result = resources.in_place_object->SetObjectRects( &client_rect, &client_rect); trace_native_hresult( "presentation.set_object_rects.after", static_cast(layout_result)); if (FAILED(layout_result)) { return layout_result; } HWND control_window = nullptr; trace_native_stage("presentation.get_control_window.before"); const HRESULT window_result = resources.in_place_object->GetWindow(&control_window); trace_native_hresult( "presentation.get_control_window.after", static_cast(window_result)); trace_native_pointer( "presentation.control_window", reinterpret_cast(control_window)); if (SUCCEEDED(window_result) && control_window != nullptr && IsWindow(control_window)) { const HRESULT position_result = position_direct_control_window( resources, control_window, client_rect); if (FAILED(position_result)) { return position_result; } trace_presentation_window_state(resources, control_window); } trace_native_stage("presentation.redraw.before"); SetLastError(ERROR_SUCCESS); if (!RedrawWindow( resources.host_window, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN)) { trace_native_win32( "presentation.redraw.failed", static_cast(GetLastError())); } else { trace_native_stage("presentation.redraw.after"); } return S_OK; } bool window_or_descendant_has_focus(HWND window) noexcept { const HWND focused = GetFocus(); return focused != nullptr && (focused == window || IsChild(window, focused)); } class SensitiveBstr { public: SensitiveBstr() noexcept = default; ~SensitiveBstr() noexcept { reset(); } SensitiveBstr(const SensitiveBstr&) = delete; SensitiveBstr& operator=(const SensitiveBstr&) = delete; SensitiveBstr(SensitiveBstr&&) = delete; SensitiveBstr& operator=(SensitiveBstr&&) = delete; NavopRdpResult assign(NavopRdpBorrowedSecret secret) noexcept { reset(); if (secret.len == UINT32_C(0)) { return NAVOP_RDP_RESULT_OK; } value_ = SysAllocStringLen( reinterpret_cast(secret.data), secret.len); return value_ == nullptr ? NAVOP_RDP_RESULT_ALLOCATION_FAILED : NAVOP_RDP_RESULT_OK; } BSTR get() const noexcept { return value_; } private: void reset() noexcept { if (value_ == nullptr) { return; } SecureZeroMemory( value_, static_cast(SysStringByteLen(value_)) + sizeof(OLECHAR)); SysFreeString(value_); value_ = nullptr; } BSTR value_ = nullptr; }; NavopRdpResult validate_resources( NativeRdpActiveXResources* resources) noexcept; } // namespace struct NativeRdpActiveXResources { ActiveXCleanup state; }; namespace { NavopRdpResult validate_resources( NativeRdpActiveXResources* resources) noexcept { if (resources == nullptr || resources->state.parent_window == nullptr || resources->state.host_window == nullptr) { return NAVOP_RDP_RESULT_UNAVAILABLE; } if (!IsWindow(resources->state.parent_window) || !IsWindow(resources->state.host_window)) { return NAVOP_RDP_RESULT_UNAVAILABLE; } if (resources->state.control == nullptr || resources->state.client == nullptr) { return NAVOP_RDP_RESULT_UNAVAILABLE; } return NAVOP_RDP_RESULT_OK; } } // namespace NavopRdpResult create_active_x_resources( NativeRdpHost* owner, uintptr_t parent_hwnd, NativeRdpActiveXResources** out_resources) noexcept { trace_native_stage("create.begin"); if (owner == nullptr || out_resources == nullptr) { trace_native_stage("create.invalid_output"); return NAVOP_RDP_RESULT_INVALID_ARGUMENT; } *out_resources = nullptr; const HWND parent = reinterpret_cast(parent_hwnd); if (parent == nullptr || !IsWindow(parent)) { trace_native_win32( "create.invalid_parent", static_cast(GetLastError())); return NAVOP_RDP_RESULT_INVALID_ARGUMENT; } trace_native_pointer( "create.parent_valid", reinterpret_cast(parent)); trace_native_stage("create.allocate.before"); auto resources = std::unique_ptr( new (std::nothrow) NativeRdpActiveXResources()); if (!resources) { trace_native_stage("create.allocate.failed"); return record_last_error(owner, NAVOP_RDP_RESULT_ALLOCATION_FAILED); } trace_native_stage("create.allocate.after"); resources->state.parent_window = parent; // Correlate this ActiveX host with one Navop RDP session in the destroy // trace; the HWND alone is ambiguous because Win32 recycles handles. resources->state.host_generation = owner->generation; trace_native_stage("create.ole_initialize.before"); const HRESULT ole_result = OleInitialize(nullptr); trace_native_hresult( "create.ole_initialize.after", static_cast(ole_result)); if (FAILED(ole_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_OLE_INITIALIZE, static_cast(ole_result)); } resources->state.ole_initialized = true; SetLastError(ERROR_SUCCESS); trace_native_stage("create.atl_ax_win_init.before"); if (!AtlAxWinInit()) { const DWORD win32_code = GetLastError(); trace_native_win32( "create.atl_ax_win_init.failed", static_cast(win32_code)); return record_last_stage_win32( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_ATL_AX_WIN_INIT, static_cast(win32_code)); } trace_native_stage("create.atl_ax_win_init.after"); resources->state.atl_initialized = true; const HINSTANCE instance = GetModuleHandleW(nullptr); trace_native_stage("create.host_class.before"); const DWORD host_class_error = ensure_native_host_window_class(instance); trace_native_win32( "create.host_class.after", static_cast(host_class_error)); if (host_class_error != ERROR_SUCCESS) { return record_last_stage_win32( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_WINDOW, static_cast(host_class_error)); } // GPUI's HWND owns Rust window state. Keep ATL and ActiveX window traffic // below a dedicated native child so synchronous parent notifications // cannot enter GPUI's Rust window procedure directly. AtlAxAttachControl // subclasses this window into the ActiveX host after the RDP control has // been explicitly constructed and initialized. SetLastError(ERROR_SUCCESS); trace_native_stage("create.host_window.before"); resources->state.host_window = CreateWindowExW( WS_EX_NOPARENTNOTIFY, kNativeHostWindowClassName, L"", WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, 1, 1, parent, nullptr, instance, nullptr); trace_native_pointer( "create.host_window.after", reinterpret_cast(resources->state.host_window)); if (resources->state.host_window == nullptr) { const DWORD win32_code = GetLastError(); trace_native_win32( "create.host_window.failed", static_cast(win32_code)); return record_last_stage_win32( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_WINDOW, static_cast(win32_code)); } trace_native_stage("create.control_instance.before"); const HRESULT control_result = CoCreateInstance( kMsRdpClient9NotSafeForScriptingClsid, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&resources->state.control)); trace_native_hresult( "create.control_instance.after", static_cast(control_result)); if (FAILED(control_result) || resources->state.control == nullptr) { if (FAILED(control_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL, static_cast(control_result)); } return record_last_stage_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL); } trace_native_stage("create.query_client.before"); const HRESULT query_result = resources->state.control->QueryInterface( IID_PPV_ARGS(&resources->state.client)); trace_native_hresult( "create.query_client.after", static_cast(query_result)); if (FAILED(query_result) || resources->state.client == nullptr) { if (FAILED(query_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_QUERY_CLIENT, static_cast(query_result)); } return record_last_stage_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_QUERY_CLIENT); } trace_native_stage("create.query_non_scriptable.before"); const HRESULT non_scriptable_result = resources->state.control->QueryInterface( IID_PPV_ARGS(&resources->state.non_scriptable)); trace_native_hresult( "create.query_non_scriptable.after", static_cast(non_scriptable_result)); if (FAILED(non_scriptable_result) || resources->state.non_scriptable == nullptr) { if (FAILED(non_scriptable_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_QUERY_NON_SCRIPTABLE, static_cast(non_scriptable_result)); } return record_last_stage_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_QUERY_NON_SCRIPTABLE); } trace_native_stage("create.query_non_scriptable3.before"); const HRESULT non_scriptable3_result = resources->state.control->QueryInterface( IID_PPV_ARGS(&resources->state.non_scriptable3)); trace_native_hresult( "create.query_non_scriptable3.after", static_cast(non_scriptable3_result)); if (FAILED(non_scriptable3_result) || resources->state.non_scriptable3 == nullptr) { if (FAILED(non_scriptable3_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_QUERY_NON_SCRIPTABLE, static_cast(non_scriptable3_result)); } return record_last_stage_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_QUERY_NON_SCRIPTABLE); } trace_native_stage("create.query_non_scriptable5.before"); const HRESULT non_scriptable5_result = resources->state.control->QueryInterface( IID_PPV_ARGS(&resources->state.non_scriptable5)); trace_native_hresult( "create.query_non_scriptable5.after", static_cast(non_scriptable5_result)); if (FAILED(non_scriptable5_result) || resources->state.non_scriptable5 == nullptr) { if (FAILED(non_scriptable5_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_QUERY_NON_SCRIPTABLE, static_cast(non_scriptable5_result)); } return record_last_stage_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_QUERY_NON_SCRIPTABLE); } // AtlAxAttachControl treats the supplied object as already initialized and // deliberately skips IPersistStreamInit::InitNew. WinForms AxHost, which // 1Remote uses, initializes a newly created control before it starts the // event sinks and activates the control. Reproduce that required step // explicitly before handing the object to ATL. CComPtr persist_stream_init; trace_native_stage("create.query_persist_stream_init.before"); const HRESULT persist_query_result = resources->state.control->QueryInterface( IID_PPV_ARGS(&persist_stream_init)); trace_native_hresult( "create.query_persist_stream_init.after", static_cast(persist_query_result)); if (FAILED(persist_query_result) || persist_stream_init == nullptr) { if (FAILED(persist_query_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL, static_cast(persist_query_result)); } return record_last_stage_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL); } trace_native_stage("create.initialize_control.before"); const HRESULT initialize_result = persist_stream_init->InitNew(); trace_native_hresult( "create.initialize_control.after", static_cast(initialize_result)); if (FAILED(initialize_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL, static_cast(initialize_result)); } // 1Remote registers managed event handlers before EndInit/CreateControl. // AxHost turns those handlers into COM event sinks after initialization. // Advise after InitNew but before ATL's in-place activation. trace_native_stage("create.event_subscription.before"); const NavopRdpResult subscription_result = create_event_subscription( owner, resources->state.control, &resources->state.event_subscription); trace_native_result( "create.event_subscription.after", subscription_result); if (subscription_result != NAVOP_RDP_RESULT_OK) { return record_last_diagnostic( owner, subscription_result, NAVOP_RDP_CREATE_STAGE_EVENT_SUBSCRIPTION, owner->last_hresult, owner->has_last_hresult, owner->last_win32_code, owner->has_last_win32_code); } const HRESULT attach_result = attach_control_with_traces(resources->state); if (FAILED(attach_result) || resources->state.container == nullptr) { if (FAILED(attach_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL, static_cast(attach_result)); } return record_last_stage_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL); } trace_native_stage("create.query_in_place_object.before"); const HRESULT in_place_result = resources->state.control->QueryInterface( IID_PPV_ARGS(&resources->state.in_place_object)); trace_native_hresult( "create.query_in_place_object.after", static_cast(in_place_result)); if (FAILED(in_place_result) || resources->state.in_place_object == nullptr) { if (FAILED(in_place_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL, static_cast(in_place_result)); } return record_last_stage_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL); } trace_native_stage("create.synchronize_bounds.before"); const HRESULT initial_layout_result = synchronize_control_bounds(resources->state); trace_native_hresult( "create.synchronize_bounds.after", static_cast(initial_layout_result)); if (FAILED(initial_layout_result) && initial_layout_result != kPresentationIncompleteHresult) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_CREATE_CONTROL, static_cast(initial_layout_result)); } if (initial_layout_result == kPresentationIncompleteHresult) { // The ActiveX control creates its drawing window during in-place // activation, so a 1x1 create-time bounds sync is expected to be // incomplete. Never fail the create here: the Rust presentation // re-synchronizes bounds after LoginComplete/Reconnected and every // set_bounds call re-runs synchronize_control_bounds. trace_native_stage("create.synchronize_bounds.deferred"); } trace_native_stage("create.set_ui_parent.before"); const HRESULT ui_parent_result = resources->state.non_scriptable->put_UIParentWindowHandle( reinterpret_cast(parent)); trace_native_hresult( "create.set_ui_parent.after", static_cast(ui_parent_result)); if (FAILED(ui_parent_result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_CREATE_STAGE_SET_PARENT, static_cast(ui_parent_result)); } *out_resources = resources.release(); trace_native_stage("create.complete"); return NAVOP_RDP_RESULT_OK; } void destroy_active_x_resources( NativeRdpActiveXResources* resources) noexcept { delete resources; } NavopRdpResult set_active_x_bounds( NativeRdpActiveXResources* resources, const NavopRdpBounds& bounds) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return resource_result; } if (bounds.width < 0 || bounds.height < 0) { return NAVOP_RDP_RESULT_INVALID_ARGUMENT; } if (!SetWindowPos( resources->state.host_window, nullptr, bounds.x, bounds.y, bounds.width, bounds.height, SWP_NOZORDER | SWP_NOACTIVATE)) { return NAVOP_RDP_RESULT_INTERNAL_ERROR; } const HRESULT layout_result = synchronize_control_bounds(resources->state); if (FAILED(layout_result)) { if (layout_result == kPresentationIncompleteHresult) { return NAVOP_RDP_RESULT_PRESENTATION_INCOMPLETE; } return NAVOP_RDP_RESULT_INTERNAL_ERROR; } return NAVOP_RDP_RESULT_OK; } NavopRdpResult update_active_x_session_display_settings( NativeRdpHost* owner, NativeRdpActiveXResources* resources, const NavopRdpSessionDisplaySettings& settings) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return record_last_error(owner, resource_result); } short connected = 0; HRESULT result = resources->state.client->get_Connected(&connected); trace_native_hresult( "display.get_connected.after", static_cast(result)); trace_native_win32( "display.connected_state", static_cast(static_cast(connected))); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } if (connected != 1) { return record_last_error(owner, NAVOP_RDP_RESULT_INVALID_STATE); } trace_native_win32( "display.desktop_width", settings.desktop_width); trace_native_win32( "display.desktop_height", settings.desktop_height); trace_native_win32( "display.physical_width", settings.physical_width); trace_native_win32( "display.physical_height", settings.physical_height); trace_native_win32( "display.orientation", settings.orientation); trace_native_win32( "display.desktop_scale_factor", settings.desktop_scale_factor); trace_native_win32( "display.device_scale_factor", settings.device_scale_factor); trace_native_stage( "display.update_session_display_settings.before"); result = resources->state.client->UpdateSessionDisplaySettings( static_cast(settings.desktop_width), static_cast(settings.desktop_height), static_cast(settings.physical_width), static_cast(settings.physical_height), static_cast(settings.orientation), static_cast(settings.desktop_scale_factor), static_cast(settings.device_scale_factor)); trace_native_hresult( "display.update_session_display_settings.after", static_cast(result)); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } return NAVOP_RDP_RESULT_OK; } NavopRdpResult set_active_x_visible( NativeRdpActiveXResources* resources, bool visible) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return resource_result; } if (!visible && window_or_descendant_has_focus(resources->state.host_window)) { SetFocus(resources->state.parent_window); } ShowWindow( resources->state.host_window, visible ? SW_SHOWNA : SW_HIDE); if (visible) { const HRESULT layout_result = synchronize_control_bounds(resources->state); if (FAILED(layout_result)) { if (layout_result == kPresentationIncompleteHresult) { return NAVOP_RDP_RESULT_PRESENTATION_INCOMPLETE; } return NAVOP_RDP_RESULT_INTERNAL_ERROR; } } const LONG_PTR host_style = GetWindowLongPtrW( resources->state.host_window, GWL_STYLE); const bool host_is_visible = (host_style & WS_VISIBLE) != 0; if (host_is_visible != visible) { return NAVOP_RDP_RESULT_INTERNAL_ERROR; } return NAVOP_RDP_RESULT_OK; } NavopRdpResult focus_active_x( NativeRdpActiveXResources* resources) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return resource_result; } if ((GetWindowLongPtrW( resources->state.host_window, GWL_STYLE) & WS_VISIBLE) == 0) { return NAVOP_RDP_RESULT_INVALID_ARGUMENT; } SetFocus(resources->state.host_window); if (!window_or_descendant_has_focus(resources->state.host_window)) { return NAVOP_RDP_RESULT_INTERNAL_ERROR; } return NAVOP_RDP_RESULT_OK; } NavopRdpResult connect_active_x( NativeRdpHost* owner, NativeRdpActiveXResources* resources, const NavopRdpConnectionOptions& options) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return record_last_error(owner, resource_result); } short connected = 0; trace_native_stage("connect.get_connected.before"); HRESULT result = resources->state.client->get_Connected(&connected); trace_native_hresult( "connect.get_connected.after", static_cast(result)); if (FAILED(result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_STAGE_CONNECT_GET_CONNECTED, static_cast(result)); } if (connected != 0) { return record_last_error(owner, NAVOP_RDP_RESULT_INVALID_STATE); } CComBSTR server( static_cast(options.host.len), reinterpret_cast(options.host.data)); if (server.m_str == nullptr) { return record_last_error(owner, NAVOP_RDP_RESULT_ALLOCATION_FAILED); } trace_native_stage("connect.server.before"); result = resources->state.client->put_Server(server); trace_native_hresult( "connect.server.after", static_cast(result)); if (FAILED(result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_STAGE_CONNECT_SET_SERVER, static_cast(result)); } CComPtr advanced_settings; trace_native_stage("connect.get_advanced_settings.before"); result = resources->state.client->get_AdvancedSettings2( &advanced_settings); trace_native_hresult( "connect.get_advanced_settings.after", static_cast(result)); if (FAILED(result) || advanced_settings == nullptr) { if (FAILED(result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_STAGE_CONNECT_GET_ADVANCED_SETTINGS, static_cast(result)); } return record_last_error(owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } trace_native_stage("connect.rdp_port.before"); result = advanced_settings->put_RDPPort( static_cast(options.port)); trace_native_hresult( "connect.rdp_port.after", static_cast(result)); if (FAILED(result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_STAGE_CONNECT_SET_RDP_PORT, static_cast(result)); } // All connection policy sections run before Connect with fail-fast // semantics: security, reconnect, input, resource, audio, display, // performance, gateway (connection_policy.cpp). const NativeRdpConnectionPolicyContext policy_context{ owner, resources->state.control, resources->state.client, resources->state.non_scriptable3, resources->state.non_scriptable5, }; const NavopRdpResult policy_result = configure_active_x_connection_policy(policy_context, options); if (policy_result != NAVOP_RDP_RESULT_OK) { return policy_result; } trace_native_stage("connect.desktop_width.before"); result = resources->state.client->put_DesktopWidth( options.desktop_width); trace_native_hresult( "connect.desktop_width.after", static_cast(result)); if (FAILED(result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_STAGE_CONNECT_SET_DESKTOP_WIDTH, static_cast(result)); } trace_native_stage("connect.desktop_height.before"); result = resources->state.client->put_DesktopHeight( options.desktop_height); trace_native_hresult( "connect.desktop_height.after", static_cast(result)); if (FAILED(result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_STAGE_CONNECT_SET_DESKTOP_HEIGHT, static_cast(result)); } trace_native_stage("connect.color_depth.before"); result = resources->state.client->put_ColorDepth(options.color_depth); trace_native_hresult( "connect.color_depth.after", static_cast(result)); if (FAILED(result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_STAGE_CONNECT_SET_COLOR_DEPTH, static_cast(result)); } trace_native_stage("connect.invoke.before"); result = resources->state.client->Connect(); trace_native_hresult( "connect.invoke.after", static_cast(result)); if (FAILED(result)) { return record_last_stage_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, NAVOP_RDP_STAGE_CONNECT_INVOKE, static_cast(result)); } return NAVOP_RDP_RESULT_OK; } NavopRdpResult apply_active_x_credentials( NativeRdpHost* owner, NativeRdpActiveXResources* resources, NavopRdpBorrowedUtf16 username, NavopRdpBorrowedUtf16 domain, NavopRdpBorrowedSecret server_password, NavopRdpBorrowedUtf16 gateway_username, NavopRdpBorrowedUtf16 gateway_domain, NavopRdpBorrowedSecret gateway_password) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return record_last_error(owner, resource_result); } short connected = 0; HRESULT result = resources->state.client->get_Connected(&connected); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } if (connected != 0) { return record_last_error(owner, NAVOP_RDP_RESULT_INVALID_STATE); } if (username.len > static_cast((std::numeric_limits::max)()) || domain.len > static_cast((std::numeric_limits::max)()) || gateway_username.len > static_cast((std::numeric_limits::max)()) || gateway_domain.len > static_cast((std::numeric_limits::max)())) { return record_last_error(owner, NAVOP_RDP_RESULT_INVALID_ARGUMENT); } if (username.len != UINT32_C(0)) { CComBSTR username_bstr( static_cast(username.len), reinterpret_cast(username.data)); if (username_bstr.m_str == nullptr) { return record_last_error( owner, NAVOP_RDP_RESULT_ALLOCATION_FAILED); } result = resources->state.client->put_UserName(username_bstr); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } } if (domain.len != UINT32_C(0)) { CComBSTR domain_bstr( static_cast(domain.len), reinterpret_cast(domain.data)); if (domain_bstr.m_str == nullptr) { return record_last_error( owner, NAVOP_RDP_RESULT_ALLOCATION_FAILED); } result = resources->state.client->put_Domain(domain_bstr); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } } if (server_password.len != UINT32_C(0)) { SensitiveBstr password_bstr; const NavopRdpResult password_result = password_bstr.assign(server_password); if (password_result != NAVOP_RDP_RESULT_OK) { return record_last_error(owner, password_result); } trace_native_stage("credentials.set_password.before"); result = resources->state.non_scriptable->put_ClearTextPassword( password_bstr.get()); trace_native_hresult( "credentials.set_password.after", static_cast(result)); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } } if (gateway_username.len != UINT32_C(0) || gateway_domain.len != UINT32_C(0) || gateway_password.len != UINT32_C(0)) { CComQIPtr client7(resources->state.client); if (client7 == nullptr) { return record_last_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } CComPtr transport; trace_native_stage("credentials.gateway.get_transport_settings2.before"); result = client7->get_TransportSettings2(&transport); trace_native_hresult( "credentials.gateway.get_transport_settings2.after", static_cast(result)); if (FAILED(result) || transport == nullptr) { if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } return record_last_error( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } if (gateway_username.len != UINT32_C(0)) { CComBSTR gateway_username_bstr( static_cast(gateway_username.len), reinterpret_cast(gateway_username.data)); if (gateway_username_bstr.m_str == nullptr) { return record_last_error( owner, NAVOP_RDP_RESULT_ALLOCATION_FAILED); } trace_native_stage("credentials.gateway.username.before"); result = transport->put_GatewayUsername(gateway_username_bstr); trace_native_hresult( "credentials.gateway.username.after", static_cast(result)); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } } if (gateway_domain.len != UINT32_C(0)) { CComBSTR gateway_domain_bstr( static_cast(gateway_domain.len), reinterpret_cast(gateway_domain.data)); if (gateway_domain_bstr.m_str == nullptr) { return record_last_error( owner, NAVOP_RDP_RESULT_ALLOCATION_FAILED); } trace_native_stage("credentials.gateway.domain.before"); result = transport->put_GatewayDomain(gateway_domain_bstr); trace_native_hresult( "credentials.gateway.domain.after", static_cast(result)); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } } if (gateway_password.len != UINT32_C(0)) { SensitiveBstr gateway_password_bstr; const NavopRdpResult gateway_password_result = gateway_password_bstr.assign(gateway_password); if (gateway_password_result != NAVOP_RDP_RESULT_OK) { return record_last_error(owner, gateway_password_result); } trace_native_stage("credentials.gateway.password.before"); result = transport->put_GatewayPassword( gateway_password_bstr.get()); trace_native_hresult( "credentials.gateway.password.after", static_cast(result)); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } } } return NAVOP_RDP_RESULT_OK; } NavopRdpResult get_active_x_connection_state( NativeRdpHost* owner, NativeRdpActiveXResources* resources, uint32_t* out_state) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return record_last_error(owner, resource_result); } if (out_state == nullptr) { return record_last_error(owner, NAVOP_RDP_RESULT_INVALID_ARGUMENT); } short connected = 0; const HRESULT result = resources->state.client->get_Connected(&connected); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } if (connected < 0 || connected > 2) { return record_last_error(owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } *out_state = static_cast(connected); return NAVOP_RDP_RESULT_OK; } NavopRdpResult get_active_x_presentation_state( NativeRdpActiveXResources* resources, NavopRdpPresentationState* out_state) noexcept { if (out_state == nullptr) { return NAVOP_RDP_RESULT_INVALID_ARGUMENT; } if (out_state->struct_size < static_cast(sizeof(NavopRdpPresentationState))) { return NAVOP_RDP_RESULT_INVALID_ARGUMENT; } const uint32_t caller_size = out_state->struct_size; const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { // A live host always produces a snapshot: unavailable resources simply // report every flag as zero so the caller can treat the presentation // as not ready without failing the session. *out_state = NavopRdpPresentationState{ caller_size, NAVOP_RDP_PRESENTATION_STATE_ABI_VERSION, UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0)}; return NAVOP_RDP_RESULT_OK; } HWND control_window = nullptr; const HRESULT window_result = resources->state.in_place_object->GetWindow(&control_window); const bool control_valid = SUCCEEDED(window_result) && control_window != nullptr && IsWindow(control_window); RECT host_rect{}; const bool host_rect_nonzero = GetWindowRect(resources->state.host_window, &host_rect) && host_rect.right > host_rect.left && host_rect.bottom > host_rect.top; RECT control_rect{}; const bool control_rect_nonzero = control_valid && GetWindowRect(control_window, &control_rect) && control_rect.right > control_rect.left && control_rect.bottom > control_rect.top; *out_state = NavopRdpPresentationState{ caller_size, NAVOP_RDP_PRESENTATION_STATE_ABI_VERSION, control_valid ? UINT32_C(1) : UINT32_C(0), host_rect_nonzero ? UINT32_C(1) : UINT32_C(0), control_rect_nonzero ? UINT32_C(1) : UINT32_C(0), control_valid && IsWindowVisible(control_window) ? UINT32_C(1) : UINT32_C(0), control_valid && IsChild(resources->state.host_window, control_window) ? UINT32_C(1) : UINT32_C(0), IsWindowVisible(resources->state.host_window) ? UINT32_C(1) : UINT32_C(0)}; trace_native_win32( "presentation.state.control_hwnd_valid", out_state->control_hwnd_valid); trace_native_win32( "presentation.state.host_rect_nonzero", out_state->host_rect_nonzero); trace_native_win32( "presentation.state.control_rect_nonzero", out_state->control_rect_nonzero); trace_native_win32( "presentation.state.control_visible", out_state->control_visible); trace_native_win32( "presentation.state.control_is_host_descendant", out_state->control_is_host_descendant); trace_native_win32( "presentation.state.host_visible", out_state->host_visible); return NAVOP_RDP_RESULT_OK; } NavopRdpResult get_active_x_extended_disconnect_reason( NativeRdpActiveXResources* resources, int32_t* out_extended_code) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return resource_result; } if (out_extended_code == nullptr) { return NAVOP_RDP_RESULT_INVALID_ARGUMENT; } ExtendedDisconnectReasonCode extended_reason{}; const HRESULT result = resources->state.client->get_ExtendedDisconnectReason( &extended_reason); if (FAILED(result)) { return NAVOP_RDP_RESULT_INTERNAL_ERROR; } *out_extended_code = static_cast(extended_reason); return NAVOP_RDP_RESULT_OK; } void trace_active_x_disconnect_description( NativeRdpActiveXResources* resources, int32_t disconnect_code, int32_t extended_code) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { trace_native_result( "disconnect.error_description.unavailable", resource_result); return; } CComBSTR description; const HRESULT result = resources->state.client->GetErrorDescription( static_cast(disconnect_code), static_cast(extended_code), &description); trace_native_utf16( "disconnect.error_description", static_cast(result), reinterpret_cast( static_cast(description)), SUCCEEDED(result) && description.m_str != nullptr ? static_cast(description.Length()) : UINT32_C(0)); } NavopRdpResult request_close_active_x( NativeRdpHost* owner, NativeRdpActiveXResources* resources, uint32_t* out_status) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return record_last_error(owner, resource_result); } if (out_status == nullptr) { return record_last_error(owner, NAVOP_RDP_RESULT_INVALID_ARGUMENT); } short connected = 0; HRESULT result = resources->state.client->get_Connected(&connected); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } if (connected == 0) { *out_status = 0; return NAVOP_RDP_RESULT_OK; } if (connected != 1 && connected != 2) { return record_last_error(owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } ControlCloseStatus status = controlCloseCanProceed; result = resources->state.client->RequestClose(&status); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } if (status != controlCloseCanProceed && status != controlCloseWaitForEvents) { return record_last_error(owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } *out_status = static_cast(status); return NAVOP_RDP_RESULT_OK; } NavopRdpResult disconnect_active_x( NativeRdpHost* owner, NativeRdpActiveXResources* resources) noexcept { const NavopRdpResult resource_result = validate_resources(resources); if (resource_result != NAVOP_RDP_RESULT_OK) { return record_last_error(owner, resource_result); } short connected = 0; HRESULT result = resources->state.client->get_Connected(&connected); if (FAILED(result)) { return record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)); } if (connected == 0) { return NAVOP_RDP_RESULT_OK; } if (connected != 1 && connected != 2) { return record_last_error(owner, NAVOP_RDP_RESULT_INTERNAL_ERROR); } result = resources->state.client->Disconnect(); return FAILED(result) ? record_last_hresult( owner, NAVOP_RDP_RESULT_INTERNAL_ERROR, static_cast(result)) : NAVOP_RDP_RESULT_OK; }