v1.8.32: fix embedded RDP — AttachThreadInput for cross-process SetParent

SetParent returned 0 (failed) because mstsc runs in a different thread.
Added AttachThreadInput() before SetParent to link input queues.
Also strip WS_EX_APPWINDOW to remove mstsc from taskbar when embedded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chrome-storm-c442
2026-02-24 12:07:49 -05:00
parent 91bc1c0345
commit 68e94856f6
3 changed files with 37 additions and 7 deletions

View File

@@ -508,9 +508,11 @@ class EmbeddedRDP:
import ctypes.wintypes
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
hwnd = self._mstsc_hwnd
GWL_STYLE = -16
GWL_EXSTYLE = -20
WS_CHILD = 0x40000000
WS_VISIBLE = 0x10000000
WS_CAPTION = 0x00C00000
@@ -519,24 +521,48 @@ class EmbeddedRDP:
WS_SYSMENU = 0x00080000
WS_MINIMIZEBOX = 0x00020000
WS_MAXIMIZEBOX = 0x00010000
WS_EX_APPWINDOW = 0x00040000
WS_EX_WINDOWEDGE = 0x00000100
SWP_FRAMECHANGED = 0x0020
SWP_NOZORDER = 0x0004
try:
# Attach thread input queues — required for cross-process SetParent
target_tid = user32.GetWindowThreadProcessId(hwnd, None)
our_tid = kernel32.GetCurrentThreadId()
attached = False
if target_tid != our_tid:
attached = bool(user32.AttachThreadInput(our_tid, target_tid, True))
log.info(f"AttachThreadInput({our_tid}, {target_tid}): {attached}")
# Remove decorations, make child
# Use unsigned 32-bit mask to avoid ctypes overflow
style = user32.GetWindowLongW(hwnd, GWL_STYLE) & 0xFFFFFFFF
style = (style | WS_CHILD | WS_VISIBLE) & ~(
new_style = (style | WS_CHILD | WS_VISIBLE) & ~(
WS_POPUP | WS_CAPTION | WS_THICKFRAME |
WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
) & 0xFFFFFFFF
user32.SetWindowLongW(hwnd, GWL_STYLE, ctypes.c_long(style).value)
user32.SetWindowLongW(hwnd, GWL_STYLE, ctypes.c_long(new_style).value)
# Also strip extended styles that keep it on taskbar
ex_style = user32.GetWindowLongW(hwnd, GWL_EXSTYLE) & 0xFFFFFFFF
new_ex = (ex_style & ~(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE)) & 0xFFFFFFFF
user32.SetWindowLongW(hwnd, GWL_EXSTYLE, ctypes.c_long(new_ex).value)
# Reparent
user32.SetParent(hwnd, parent_hwnd)
result = user32.SetParent(hwnd, parent_hwnd)
log.info(f"SetParent(hwnd={hwnd}, parent={parent_hwnd}) = {result}")
# Resize to fill parent
user32.MoveWindow(hwnd, 0, 0, width, height, True)
if not result:
# SetParent failed — try alternative: set owner instead
error = kernel32.GetLastError()
log.warning(f"SetParent failed, GetLastError={error}. Trying ShowWindow approach.")
# Force the window into position over our frame anyway
user32.SetWindowPos(hwnd, 0, 0, 0, width, height,
SWP_FRAMECHANGED | SWP_NOZORDER)
else:
# Resize to fill parent
user32.MoveWindow(hwnd, 0, 0, width, height, True)
# Apply style change
user32.SetWindowPos(hwnd, 0, 0, 0, 0, 0,
@@ -545,8 +571,12 @@ class EmbeddedRDP:
# Focus
user32.SetFocus(hwnd)
# Detach thread input
if attached:
user32.AttachThreadInput(our_tid, target_tid, False)
self._connected = True
log.info(f"mstsc embedded: HWND={hwnd} into parent={parent_hwnd}")
log.info(f"mstsc embedded: HWND={hwnd} into parent={parent_hwnd} (SetParent={result})")
if self.on_embedded:
self.on_embedded()