Нашёл решение:
import ctypes, time, win32con, win32api, win32gui
PBYTE256 = ctypes.c_ubyte * 256
_user32 = ctypes.WinDLL("user32")
GetKeyboardState = _user32.GetKeyboardState
SetKeyboardState = _user32.SetKeyboardState
PostMessage = win32api.PostMessage
SendMessage = win32gui.SendMessage
FindWindow = win32gui.FindWindow
IsWindow = win32gui.IsWindow
GetCurrentThreadId = win32api.GetCurrentThreadId
GetWindowThreadProcessId = _user32.GetWindowThreadProcessId #очень важно брать функцию из dll, т.к. питоновский враппер (win32process.GetWindowThreadProcessId) выдаёт неправильные значения
AttachThreadInput = _user32.AttachThreadInput
MapVirtualKeyA = _user32.MapVirtualKeyA
MapVirtualKeyW = _user32.MapVirtualKeyW
MakeLong = win32api.MAKELONG
w = win32con #так короче запись
def PostKeyEx( hwnd, key, shift, specialkey):
if IsWindow(hwnd):
ThreadId = GetWindowThreadProcessId(hwnd, None)
lparam = MakeLong(0, MapVirtualKeyA(key, 0))
msg_down=w.WM_KEYDOWN
msg_up=w.WM_KEYUP
if specialkey:
lparam = lparam | 0x1000000
if len(shift) > 0: #Если есть модификаторы - используем PostMessage и AttachThreadInput
pKeyBuffers = PBYTE256()
pKeyBuffers_old = PBYTE256()
SendMessage(hwnd, w.WM_ACTIVATE, w.WA_ACTIVE, 0)
AttachThreadInput(GetCurrentThreadId(), ThreadId, True)
GetKeyboardState( ctypes.byref(pKeyBuffers_old ))
for modkey in shift:
if modkey == w.VK_MENU:
lparam = lparam | 0x20000000
msg_down=w.WM_SYSKEYDOWN
msg_up=w.WM_SYSKEYUP
pKeyBuffers[modkey] |= 128
SetKeyboardState( ctypes.byref(pKeyBuffers) )
time.sleep(0.01)
PostMessage( hwnd, msg_down, key, lparam)
time.sleep(0.01)
PostMessage( hwnd, msg_up, key, lparam | 0xC0000000)
time.sleep(0.01)
SetKeyboardState( ctypes.byref(pKeyBuffers_old) )
time.sleep(0.01)
AttachThreadInput(GetCurrentThreadId(), ThreadId, False)
else: #Если нету модификаторов - используем SendMessage
SendMessage( hwnd, msg_down, key, lparam)
SendMessage( hwnd, msg_up, key, lparam | 0xC0000000)
hwnd=FindWindow("Notepad", None)
PostKeyEx(hwnd,ord('A'),[w.VK_CONTROL],False)