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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user