mirror of
https://github.com/feigeCode/navop.git
synced 2026-09-22 00:01:25 +00:00
排查文档记录的结论是:RDP 关闭超时时会把整个 adapter 连同 COM 资源一起 Box::leak,SSH 文件编辑器的分离加载/保存任务持有窗口强引用,两者都会让 已关闭的会话残留内存。本次按文档建议分三档落地,并刻意保持关闭/释放语义 不变——先建立可观测性,再改所有权。 第 1 档:低频诊断,不改任何语义 - 新增 windows_rdp_host::lifecycle_stats:进程级原子计数,明确区分 created / destroyed / quarantined,覆盖 hosts_*、wrong_thread_drops、 overlays_* 与 adapters_retired / retired_destroyed / retired_leaked_at_exit, 并派生 live_hosts() / live_overlays() / quarantined_adapters()。 - close 超时、overlay abandon、wrong-thread drop 的日志补 close_mode / retry_count / elapsed_ms / last_stage / last_error 关联字段。 - C++ 侧原先 DestroyWindow(host_window) 调用后不看结果,现挪进新的 destroy_host_window() noexcept:先取 GetWindowThreadProcessId(窗口销毁 之后 Win32 已不再报线程 id),SetLastError 后调用,再回读 GetLastError() 与 IsWindow()。失败行始终打印,成功行受 NAVOP_REMOTE_DESKTOP_DIAGNOSTICS 约束。刻意不做重试:失败只可能是跨线程调用或句柄已失效,而重试存在句柄 被复用后二次释放的风险。 第 2 档:编辑器分离任务改用弱引用 - reload_tab / save_tab 的分离任务由 cx.entity().clone() 改为 downgrade()。 根因就在这里:慢的 SFTP 往返会把已关闭的窗口连同其全部 tab 一起钉住。 - save 语义保持不变:写操作照常执行完毕,只有 UI 更新退化为 no-op。 - 新增 impl Drop for RemoteFileEditorWindow。窗口被移除时仍开着的 tab 不走 close_clean_tab,视图与 tab 计数必须在这里归还,否则计数会永久漂移。 - 结果被丢弃时打 log_task_result_discarded,用于区分「任务干净排空」与 「任务仍挂在后台」。 第 3 档:Box::leak 兜底改为 owner 线程 retirement 队列 - 三处 deadline 分支(close 超时、初始化清理超时、drain 的 poll_view_owner) 不再 Box::leak,改为交给 windows_native_retirement:owner 线程按 RETIREMENT_RETRY_INTERVAL=1s 重试 force_close,沿用已有的 borrow-free 三段式(Phase B 释放借用之后才泵 COM 消息,避免重入 App 借用), RETIREMENT_MAX_AGE=60s 有界宽限到期、或 on_app_quit 时才兜底 leak 并计数。 - WindowsNativeCloseOperation::into_leaked_adapter 更名 into_adapter,语义 仍是无 COM 调用的所有权移交,只是不再隐含「泄漏」。 - 队列本身(RetirementPolicy / Retired / RetirementQueue)是纯数据且跨平台 单测覆盖,只有 GPUI/native 接线是 Windows 专属。 验证: - cargo test -p windows_rdp_host:153 lib + 39 contract passed / 0 failed - cargo test -p remote_desktop_view --features windows-native-rdp: 280 passed / 0 failed - cargo test -p remote_file_editor:62 passed / 0 failed - 同步更新三处源文本契约测试:删去 Box::leak / into_leaked_adapter 断言, 新增「三处 deadline 路径都不得再出现 Box::leak」「retirement driver 不得 在 update_global 借用内 spawn」「销毁必须观测 DestroyWindow 结果」等不变量 未验证:真实 RDP 会话卡住 COM 回调、真实 SFTP 断连等真机路径仍是 #[ignore] + NAVOP_LIVE_* 环境变量用例,需要真实服务器。另有报告的 RDP 终端卡死(#118)根因仍未定位,本次未触碰该链路。
1713 lines
61 KiB
C++
1713 lines
61 KiB
C++
#include "host_internal.h"
|
|
|
|
#include <windows.h>
|
|
|
|
#include <atlbase.h>
|
|
#include <atlhost.h>
|
|
#include <ocidl.h>
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4471)
|
|
#include "mstscax.tlh"
|
|
#pragma warning(pop)
|
|
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <new>
|
|
|
|
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<CAxHostWindow>.
|
|
// 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> {};
|
|
|
|
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<IUnknown> container;
|
|
CComPtr<IUnknown> control;
|
|
CComPtr<IOleInPlaceObject> in_place_object;
|
|
CComPtr<IMsRdpClient9> client;
|
|
CComPtr<IMsRdpClientNonScriptable2> non_scriptable;
|
|
CComPtr<IMsRdpClientNonScriptable3> non_scriptable3;
|
|
CComPtr<IMsRdpClientNonScriptable5> 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<uintptr_t>(window),
|
|
reinterpret_cast<uintptr_t>(parent_window),
|
|
host_generation,
|
|
window_thread_id,
|
|
destroyed ? INT32_C(1) : INT32_C(0),
|
|
static_cast<uint32_t>(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<uintptr_t>(ATL::_pAtlModule));
|
|
trace_native_stage("create.atl_container_instance.before");
|
|
HRESULT result = CAxHostWindow::_CreatorClass::CreateInstance(
|
|
nullptr,
|
|
__uuidof(IUnknown),
|
|
reinterpret_cast<void**>(&resources.container));
|
|
trace_native_hresult(
|
|
"create.atl_container_instance.after",
|
|
static_cast<int32_t>(result));
|
|
if (FAILED(result) || resources.container == nullptr) {
|
|
return FAILED(result) ? result : E_UNEXPECTED;
|
|
}
|
|
|
|
CComPtr<IAxWinHostWindow> host;
|
|
trace_native_stage("create.query_ax_host.before");
|
|
result = resources.container->QueryInterface(&host);
|
|
trace_native_hresult(
|
|
"create.query_ax_host.after",
|
|
static_cast<int32_t>(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<int32_t>(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<uintptr_t>(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<POINT*>(&mapped_rect),
|
|
2);
|
|
const DWORD map_error = GetLastError();
|
|
trace_native_win32(
|
|
"presentation.position_control_window.map_points",
|
|
static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(error.code));
|
|
return error.result;
|
|
}
|
|
trace_native_rect(
|
|
"presentation.position_control_window.descendant_mapped",
|
|
static_cast<int32_t>(mapped_rect.left),
|
|
static_cast<int32_t>(mapped_rect.top),
|
|
static_cast<int32_t>(mapped_rect.right),
|
|
static_cast<int32_t>(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<uint32_t>(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<DescendantTraceContext*>(
|
|
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<int>(
|
|
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<uint16_t>(
|
|
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<uint32_t>(
|
|
last_error == ERROR_SUCCESS
|
|
? ERROR_INVALID_WINDOW_HANDLE
|
|
: last_error));
|
|
}
|
|
trace_native_window(
|
|
"presentation.control_descendant",
|
|
context->count,
|
|
reinterpret_cast<uintptr_t>(window),
|
|
reinterpret_cast<uintptr_t>(GetParent(window)),
|
|
IsWindowVisible(window) ? 1U : 0U,
|
|
static_cast<uintptr_t>(GetWindowLongPtrW(window, GWL_STYLE)),
|
|
static_cast<uintptr_t>(GetWindowLongPtrW(window, GWL_EXSTYLE)),
|
|
static_cast<int32_t>(rect.left),
|
|
static_cast<int32_t>(rect.top),
|
|
static_cast<int32_t>(rect.right),
|
|
static_cast<int32_t>(rect.bottom),
|
|
class_name,
|
|
class_name_len > 0
|
|
? static_cast<uint32_t>(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<LPARAM>(&context))) {
|
|
const DWORD error = GetLastError();
|
|
if (error != ERROR_SUCCESS) {
|
|
trace_native_win32(
|
|
"presentation.enumerate_control_descendants.failed",
|
|
static_cast<uint32_t>(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<uintptr_t>(GetParent(resources.host_window)));
|
|
trace_native_pointer(
|
|
"presentation.control_root",
|
|
reinterpret_cast<uintptr_t>(
|
|
GetAncestor(control_window, GA_ROOT)));
|
|
trace_native_pointer(
|
|
"presentation.control_root_owner",
|
|
reinterpret_cast<uintptr_t>(
|
|
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<uint32_t>(GetDpiForWindow(owner)));
|
|
}
|
|
trace_native_win32(
|
|
"presentation.host_dpi",
|
|
static_cast<uint32_t>(GetDpiForWindow(resources.host_window)));
|
|
trace_native_win32(
|
|
"presentation.control_dpi",
|
|
static_cast<uint32_t>(GetDpiForWindow(control_window)));
|
|
|
|
RECT host_rect{};
|
|
if (GetWindowRect(resources.host_window, &host_rect)) {
|
|
trace_native_rect(
|
|
"presentation.host_window_rect",
|
|
static_cast<int32_t>(host_rect.left),
|
|
static_cast<int32_t>(host_rect.top),
|
|
static_cast<int32_t>(host_rect.right),
|
|
static_cast<int32_t>(host_rect.bottom));
|
|
}
|
|
RECT control_rect{};
|
|
if (GetWindowRect(control_window, &control_rect)) {
|
|
trace_native_rect(
|
|
"presentation.control_window_rect",
|
|
static_cast<int32_t>(control_rect.left),
|
|
static_cast<int32_t>(control_rect.top),
|
|
static_cast<int32_t>(control_rect.right),
|
|
static_cast<int32_t>(control_rect.bottom));
|
|
}
|
|
trace_native_win32(
|
|
"presentation.control_window_style",
|
|
static_cast<uint32_t>(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<uint32_t>(win32_code));
|
|
return HRESULT_FROM_WIN32(win32_code);
|
|
}
|
|
trace_native_rect(
|
|
"presentation.host_client_rect",
|
|
static_cast<int32_t>(client_rect.left),
|
|
static_cast<int32_t>(client_rect.top),
|
|
static_cast<int32_t>(client_rect.right),
|
|
static_cast<int32_t>(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<int32_t>(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<int32_t>(window_result));
|
|
trace_native_pointer(
|
|
"presentation.control_window",
|
|
reinterpret_cast<uintptr_t>(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<uint32_t>(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<const OLECHAR*>(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<size_t>(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<HWND>(parent_hwnd);
|
|
if (parent == nullptr || !IsWindow(parent)) {
|
|
trace_native_win32(
|
|
"create.invalid_parent",
|
|
static_cast<uint32_t>(GetLastError()));
|
|
return NAVOP_RDP_RESULT_INVALID_ARGUMENT;
|
|
}
|
|
trace_native_pointer(
|
|
"create.parent_valid",
|
|
reinterpret_cast<uintptr_t>(parent));
|
|
|
|
trace_native_stage("create.allocate.before");
|
|
auto resources = std::unique_ptr<NativeRdpActiveXResources>(
|
|
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<int32_t>(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<int32_t>(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<uint32_t>(win32_code));
|
|
return record_last_stage_win32(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
NAVOP_RDP_CREATE_STAGE_ATL_AX_WIN_INIT,
|
|
static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uintptr_t>(resources->state.host_window));
|
|
if (resources->state.host_window == nullptr) {
|
|
const DWORD win32_code = GetLastError();
|
|
trace_native_win32(
|
|
"create.host_window.failed",
|
|
static_cast<uint32_t>(win32_code));
|
|
return record_last_stage_win32(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
NAVOP_RDP_CREATE_STAGE_CREATE_WINDOW,
|
|
static_cast<uint32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<IPersistStreamInit> 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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<wireHWND>(parent));
|
|
trace_native_hresult(
|
|
"create.set_ui_parent.after",
|
|
static_cast<int32_t>(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<int32_t>(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<int32_t>(result));
|
|
trace_native_win32(
|
|
"display.connected_state",
|
|
static_cast<uint32_t>(static_cast<uint16_t>(connected)));
|
|
if (FAILED(result)) {
|
|
return record_last_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
static_cast<int32_t>(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<ULONG>(settings.desktop_width),
|
|
static_cast<ULONG>(settings.desktop_height),
|
|
static_cast<ULONG>(settings.physical_width),
|
|
static_cast<ULONG>(settings.physical_height),
|
|
static_cast<ULONG>(settings.orientation),
|
|
static_cast<ULONG>(settings.desktop_scale_factor),
|
|
static_cast<ULONG>(settings.device_scale_factor));
|
|
trace_native_hresult(
|
|
"display.update_session_display_settings.after",
|
|
static_cast<int32_t>(result));
|
|
|
|
if (FAILED(result)) {
|
|
return record_last_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
static_cast<int32_t>(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<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_stage_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
NAVOP_RDP_STAGE_CONNECT_GET_CONNECTED,
|
|
static_cast<int32_t>(result));
|
|
}
|
|
if (connected != 0) {
|
|
return record_last_error(owner, NAVOP_RDP_RESULT_INVALID_STATE);
|
|
}
|
|
|
|
CComBSTR server(
|
|
static_cast<int>(options.host.len),
|
|
reinterpret_cast<LPCOLESTR>(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<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_stage_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
NAVOP_RDP_STAGE_CONNECT_SET_SERVER,
|
|
static_cast<int32_t>(result));
|
|
}
|
|
|
|
CComPtr<IMsRdpClientAdvancedSettings> 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<int32_t>(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<int32_t>(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<LONG>(options.port));
|
|
trace_native_hresult(
|
|
"connect.rdp_port.after",
|
|
static_cast<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_stage_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
NAVOP_RDP_STAGE_CONNECT_SET_RDP_PORT,
|
|
static_cast<int32_t>(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<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_stage_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
NAVOP_RDP_STAGE_CONNECT_SET_DESKTOP_WIDTH,
|
|
static_cast<int32_t>(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<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_stage_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
NAVOP_RDP_STAGE_CONNECT_SET_DESKTOP_HEIGHT,
|
|
static_cast<int32_t>(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<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_stage_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
NAVOP_RDP_STAGE_CONNECT_SET_COLOR_DEPTH,
|
|
static_cast<int32_t>(result));
|
|
}
|
|
trace_native_stage("connect.invoke.before");
|
|
result = resources->state.client->Connect();
|
|
trace_native_hresult(
|
|
"connect.invoke.after",
|
|
static_cast<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_stage_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
NAVOP_RDP_STAGE_CONNECT_INVOKE,
|
|
static_cast<int32_t>(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<int32_t>(result));
|
|
}
|
|
if (connected != 0) {
|
|
return record_last_error(owner, NAVOP_RDP_RESULT_INVALID_STATE);
|
|
}
|
|
|
|
if (username.len > static_cast<uint32_t>((std::numeric_limits<int>::max)()) ||
|
|
domain.len > static_cast<uint32_t>((std::numeric_limits<int>::max)()) ||
|
|
gateway_username.len >
|
|
static_cast<uint32_t>((std::numeric_limits<int>::max)()) ||
|
|
gateway_domain.len >
|
|
static_cast<uint32_t>((std::numeric_limits<int>::max)())) {
|
|
return record_last_error(owner, NAVOP_RDP_RESULT_INVALID_ARGUMENT);
|
|
}
|
|
|
|
if (username.len != UINT32_C(0)) {
|
|
CComBSTR username_bstr(
|
|
static_cast<int>(username.len),
|
|
reinterpret_cast<LPCOLESTR>(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<int32_t>(result));
|
|
}
|
|
}
|
|
|
|
if (domain.len != UINT32_C(0)) {
|
|
CComBSTR domain_bstr(
|
|
static_cast<int>(domain.len),
|
|
reinterpret_cast<LPCOLESTR>(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<int32_t>(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<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
static_cast<int32_t>(result));
|
|
}
|
|
}
|
|
|
|
if (gateway_username.len != UINT32_C(0) ||
|
|
gateway_domain.len != UINT32_C(0) ||
|
|
gateway_password.len != UINT32_C(0)) {
|
|
CComQIPtr<IMsRdpClient7> client7(resources->state.client);
|
|
if (client7 == nullptr) {
|
|
return record_last_error(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR);
|
|
}
|
|
|
|
CComPtr<IMsRdpClientTransportSettings2> 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<int32_t>(result));
|
|
if (FAILED(result) || transport == nullptr) {
|
|
if (FAILED(result)) {
|
|
return record_last_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
static_cast<int32_t>(result));
|
|
}
|
|
return record_last_error(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR);
|
|
}
|
|
|
|
if (gateway_username.len != UINT32_C(0)) {
|
|
CComBSTR gateway_username_bstr(
|
|
static_cast<int>(gateway_username.len),
|
|
reinterpret_cast<LPCOLESTR>(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<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
static_cast<int32_t>(result));
|
|
}
|
|
}
|
|
|
|
if (gateway_domain.len != UINT32_C(0)) {
|
|
CComBSTR gateway_domain_bstr(
|
|
static_cast<int>(gateway_domain.len),
|
|
reinterpret_cast<LPCOLESTR>(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<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
static_cast<int32_t>(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<int32_t>(result));
|
|
if (FAILED(result)) {
|
|
return record_last_hresult(
|
|
owner,
|
|
NAVOP_RDP_RESULT_INTERNAL_ERROR,
|
|
static_cast<int32_t>(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<int32_t>(result));
|
|
}
|
|
if (connected < 0 || connected > 2) {
|
|
return record_last_error(owner, NAVOP_RDP_RESULT_INTERNAL_ERROR);
|
|
}
|
|
*out_state = static_cast<uint32_t>(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<uint32_t>(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<int32_t>(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<UINT>(disconnect_code),
|
|
static_cast<UINT>(extended_code),
|
|
&description);
|
|
trace_native_utf16(
|
|
"disconnect.error_description",
|
|
static_cast<int32_t>(result),
|
|
reinterpret_cast<const uint16_t*>(
|
|
static_cast<BSTR>(description)),
|
|
SUCCEEDED(result) && description.m_str != nullptr
|
|
? static_cast<uint32_t>(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<int32_t>(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<int32_t>(result));
|
|
}
|
|
if (status != controlCloseCanProceed &&
|
|
status != controlCloseWaitForEvents) {
|
|
return record_last_error(owner, NAVOP_RDP_RESULT_INTERNAL_ERROR);
|
|
}
|
|
*out_status = static_cast<uint32_t>(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<int32_t>(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<int32_t>(result))
|
|
: NAVOP_RDP_RESULT_OK;
|
|
}
|