@@ -33,8 +33,8 @@ def type_text(text: str, post_key: str | None = None) -> None:
3333 import ctypes .wintypes
3434
3535 # --- SendInput struct definitions ---
36- # Copied from pynput/_util/win32.py (MIT-licensed) for correct alignment .
37- # See: https://github.com/moses-palmer/pynput
36+ # SendInput requires cbSize to be the exact size of the Win32 INPUT union .
37+ # Defining only KEYBDINPUT makes INPUT too small on 64-bit Windows.
3838
3939 INPUT_KEYBOARD = 1
4040 KEYEVENTF_KEYUP = 0x0002
@@ -46,39 +46,73 @@ class KEYBDINPUT(ctypes.Structure):
4646 ("wScan" , ctypes .wintypes .WORD ),
4747 ("dwFlags" , ctypes .wintypes .DWORD ),
4848 ("time" , ctypes .wintypes .DWORD ),
49- ("dwExtraInfo" , ctypes .POINTER (ctypes .c_ulong )),
49+ ("dwExtraInfo" , ctypes .c_size_t ),
50+ ]
51+
52+ class MOUSEINPUT (ctypes .Structure ):
53+ _fields_ = [
54+ ("dx" , ctypes .wintypes .LONG ),
55+ ("dy" , ctypes .wintypes .LONG ),
56+ ("mouseData" , ctypes .wintypes .DWORD ),
57+ ("dwFlags" , ctypes .wintypes .DWORD ),
58+ ("time" , ctypes .wintypes .DWORD ),
59+ ("dwExtraInfo" , ctypes .c_size_t ),
60+ ]
61+
62+ class HARDWAREINPUT (ctypes .Structure ):
63+ _fields_ = [
64+ ("uMsg" , ctypes .wintypes .DWORD ),
65+ ("wParamL" , ctypes .wintypes .WORD ),
66+ ("wParamH" , ctypes .wintypes .WORD ),
5067 ]
5168
5269 class _INPUT_UNION (ctypes .Union ):
53- _fields_ = [("ki" , KEYBDINPUT )]
70+ _fields_ = [
71+ ("mi" , MOUSEINPUT ),
72+ ("ki" , KEYBDINPUT ),
73+ ("hi" , HARDWAREINPUT ),
74+ ]
5475
5576 class INPUT (ctypes .Structure ):
77+ _anonymous_ = ("union" ,)
5678 _fields_ = [
5779 ("type" , ctypes .wintypes .DWORD ),
5880 ("union" , _INPUT_UNION ),
5981 ]
6082
61- user32 = ctypes .windll .user32 # type: ignore[attr-defined]
83+ user32 = ctypes .WinDLL ("user32" , use_last_error = True ) # type: ignore[attr-defined]
84+ user32 .SendInput .argtypes = (ctypes .wintypes .UINT , ctypes .POINTER (INPUT ), ctypes .c_int )
85+ user32 .SendInput .restype = ctypes .wintypes .UINT
86+
87+ def _raise_sendinput_failed (detail : str ) -> None :
88+ err = ctypes .get_last_error ()
89+ if err :
90+ detail = f"{ detail } (WinError { err } )"
91+ raise ScreamerError (AppError .INJECTION_FAILED , detail )
6292
6393 def _send_unicode (char : str , key_up : bool = False ) -> None :
6494 inp = INPUT ()
6595 inp .type = INPUT_KEYBOARD
66- inp .union . ki .wScan = ord (char )
67- inp .union . ki .dwFlags = KEYEVENTF_UNICODE | (KEYEVENTF_KEYUP if key_up else 0 )
68- if user32 .SendInput (1 , ctypes .byref (inp ), ctypes .sizeof (INPUT )) == 0 :
69- raise ScreamerError ( AppError . INJECTION_FAILED , f"SendInput failed for U+{ ord (char ):04X} " )
96+ inp .ki .wScan = ord (char )
97+ inp .ki .dwFlags = KEYEVENTF_UNICODE | (KEYEVENTF_KEYUP if key_up else 0 )
98+ if user32 .SendInput (1 , ctypes .byref (inp ), ctypes .sizeof (INPUT )) != 1 :
99+ _raise_sendinput_failed ( f"SendInput failed for U+{ ord (char ):04X} " )
70100
71101 def _send_vk (vk : int , key_up : bool = False ) -> None :
72102 inp = INPUT ()
73103 inp .type = INPUT_KEYBOARD
74- inp .union .ki .wVk = vk
75- inp .union .ki .dwFlags = KEYEVENTF_KEYUP if key_up else 0
76- if user32 .SendInput (1 , ctypes .byref (inp ), ctypes .sizeof (INPUT )) == 0 :
77- raise ScreamerError (AppError .INJECTION_FAILED , f"SendInput failed for VK 0x{ vk :02X} " )
104+ inp .ki .wVk = vk
105+ inp .ki .dwFlags = KEYEVENTF_KEYUP if key_up else 0
106+ if user32 .SendInput (1 , ctypes .byref (inp ), ctypes .sizeof (INPUT )) != 1 :
107+ _raise_sendinput_failed (f"SendInput failed for VK 0x{ vk :02X} " )
108+
109+ def _utf16_units (value : str ) -> list [str ]:
110+ encoded = value .encode ("utf-16-le" , errors = "surrogatepass" )
111+ return [chr (int .from_bytes (encoded [i : i + 2 ], "little" )) for i in range (0 , len (encoded ), 2 )]
78112
79113 try :
80114 log .info ("Typing %d characters" , len (text ))
81- for ch in text :
115+ for ch in _utf16_units ( text ) :
82116 _send_unicode (ch )
83117 _send_unicode (ch , key_up = True )
84118
0 commit comments