From 0d840adfca532345ceaa7be6ebd4ec69c134e9e6 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 28 May 2026 17:36:19 +0300 Subject: [PATCH] fix(ctypes): share wintypes.MSG with platform_detector to avoid argtype races MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WindowsShutdownGuard was binding user32.GetMessageW.argtypes with POINTER(_MSG) (project-local struct), while PlatformDetector's display- power monitor binds it with POINTER(wintypes.MSG). argtypes is a mutable global on the cached WinDLL handle, so whichever module imported last won, and the other module's byref() then tripped Python 3.13's strict argtype check with "expected LP_MSG instance instead of pointer to _MSG". The two structs are byte-identical (same field types in the same order, just pt vs pt_x/pt_y naming) and we never touch the pt field, so aliasing _MSG to wintypes.MSG eliminates the conflict — both modules now bind the same POINTER class, the writes become idempotent, and the full test suite passes regardless of import order. CI runs on Linux so this never fired in release builds, but it broke the local Windows test run. --- server/src/ledgrab/utils/win_shutdown.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/server/src/ledgrab/utils/win_shutdown.py b/server/src/ledgrab/utils/win_shutdown.py index 75def70..34e1906 100644 --- a/server/src/ledgrab/utils/win_shutdown.py +++ b/server/src/ledgrab/utils/win_shutdown.py @@ -101,16 +101,15 @@ class _WNDCLASS(ctypes.Structure): ] -class _MSG(ctypes.Structure): - _fields_ = [ - ("hwnd", wintypes.HWND), - ("message", wintypes.UINT), - ("wParam", wintypes.WPARAM), - ("lParam", wintypes.LPARAM), - ("time", wintypes.DWORD), - ("pt_x", wintypes.LONG), - ("pt_y", wintypes.LONG), - ] +# Use the stdlib wintypes.MSG (rather than a project-local _MSG) so the +# POINTER(MSG) type is shared with any other module that binds +# user32.GetMessageW.argtypes — argtypes is a global on the cached DLL +# handle, and two modules binding it with different POINTER classes for +# the same C function fight each other (last writer wins, the other one's +# byref() then trips Python 3.13's strict argtype check). PlatformDetector's +# display-power monitor binds with POINTER(wintypes.MSG); aligning here +# means whichever loads last produces the same binding. +_MSG = wintypes.MSG def _bind_winapi() -> None: