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
|
import ctypes.wintypes
|
||||||
|
|
||||||
user32 = ctypes.windll.user32
|
user32 = ctypes.windll.user32
|
||||||
|
kernel32 = ctypes.windll.kernel32
|
||||||
hwnd = self._mstsc_hwnd
|
hwnd = self._mstsc_hwnd
|
||||||
|
|
||||||
GWL_STYLE = -16
|
GWL_STYLE = -16
|
||||||
|
GWL_EXSTYLE = -20
|
||||||
WS_CHILD = 0x40000000
|
WS_CHILD = 0x40000000
|
||||||
WS_VISIBLE = 0x10000000
|
WS_VISIBLE = 0x10000000
|
||||||
WS_CAPTION = 0x00C00000
|
WS_CAPTION = 0x00C00000
|
||||||
@@ -519,24 +521,48 @@ class EmbeddedRDP:
|
|||||||
WS_SYSMENU = 0x00080000
|
WS_SYSMENU = 0x00080000
|
||||||
WS_MINIMIZEBOX = 0x00020000
|
WS_MINIMIZEBOX = 0x00020000
|
||||||
WS_MAXIMIZEBOX = 0x00010000
|
WS_MAXIMIZEBOX = 0x00010000
|
||||||
|
WS_EX_APPWINDOW = 0x00040000
|
||||||
|
WS_EX_WINDOWEDGE = 0x00000100
|
||||||
SWP_FRAMECHANGED = 0x0020
|
SWP_FRAMECHANGED = 0x0020
|
||||||
SWP_NOZORDER = 0x0004
|
SWP_NOZORDER = 0x0004
|
||||||
|
|
||||||
try:
|
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
|
# Remove decorations, make child
|
||||||
# Use unsigned 32-bit mask to avoid ctypes overflow
|
# Use unsigned 32-bit mask to avoid ctypes overflow
|
||||||
style = user32.GetWindowLongW(hwnd, GWL_STYLE) & 0xFFFFFFFF
|
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_POPUP | WS_CAPTION | WS_THICKFRAME |
|
||||||
WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
|
WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
|
||||||
) & 0xFFFFFFFF
|
) & 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
|
# 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
|
if not result:
|
||||||
user32.MoveWindow(hwnd, 0, 0, width, height, True)
|
# 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
|
# Apply style change
|
||||||
user32.SetWindowPos(hwnd, 0, 0, 0, 0, 0,
|
user32.SetWindowPos(hwnd, 0, 0, 0, 0, 0,
|
||||||
@@ -545,8 +571,12 @@ class EmbeddedRDP:
|
|||||||
# Focus
|
# Focus
|
||||||
user32.SetFocus(hwnd)
|
user32.SetFocus(hwnd)
|
||||||
|
|
||||||
|
# Detach thread input
|
||||||
|
if attached:
|
||||||
|
user32.AttachThreadInput(our_tid, target_tid, False)
|
||||||
|
|
||||||
self._connected = True
|
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:
|
if self.on_embedded:
|
||||||
self.on_embedded()
|
self.on_embedded()
|
||||||
|
|||||||
BIN
releases/ServerManager-v1.8.32-win-x64.exe
Normal file
BIN
releases/ServerManager-v1.8.32-win-x64.exe
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
"""Version info for ServerManager."""
|
"""Version info for ServerManager."""
|
||||||
|
|
||||||
__version__ = "1.8.31"
|
__version__ = "1.8.32"
|
||||||
__app_name__ = "ServerManager"
|
__app_name__ = "ServerManager"
|
||||||
__author__ = "aibot777"
|
__author__ = "aibot777"
|
||||||
__description__ = "Desktop GUI for managing remote servers"
|
__description__ = "Desktop GUI for managing remote servers"
|
||||||
|
|||||||
Reference in New Issue
Block a user