I want to send keyboard input to a game running in the background (game: Knight Online) but the win32api.SendMessage, PostMessage methods are not working. how can I do that
code:
from time import sleep
import win32gui
import win32con
def callback(handle, param):
s = win32gui.GetClassName(handle)
try:
print(f'Sending key to {handle}, {s}')
win32gui.SendMessage(handle, 0x0102, 0x5A, 0)
sleep(.5)
except Exception:
print('Exception sending to {handle}, {s}')
window_id = win32gui.FindWindow(None, "Knight OnLine Client")
win32gui.EnumChildWindows(window_id, callback, 0)
output:
Sending key to 23004754, Edit
Sending key to 1639850, Edit
Sending key to 10421696, Edit
the input does not work in the game
Why it doesn't work
There are many stops along the pipeline from a keypress to an application. And applications can choose where along that pipeline to receive keyboard input.
You're sending
0x0102
which isWM_CHAR
. That's about as far to the end of the pipeline as you can go. It's likely the game is tapping the pipe earlier.When you press a physical key, the keyboard driver places an input event in an input queue.
When the OS pulls that event from the input queue, it places a
WM_KEYDOWN
message in the message queue for the thread that owns the window with the input focus.When the application's GUI thread pulls the message from queue, it may choose to route it through an API called TranslateMessage, which watches for low level keyboard messages like
WM_KEYDOWN
andWM_KEYUP
.TranslateMessage synthesizes
WM_CHAR
(and/orWM_UNICHAR
) and sends them to the window (just before allowing the keyboard message to be processed). These messages tell the program that the user has typed a character (for example, a capital E with an acute accent) which can be done only with a series of keyboard messages.Meanwhile, part of the system is tracking the state of the entire keyboard at different points in time.
One of those is the asynchronous keyboard state, which watches that input queue so that it knows what's happening on the keyboard right now. Games can query this with GetAsyncKeyState (and maybe with the legacy DirectInput API). A fast video game might rely on this (after checking that they are the "active" window).
There's also a synchronous keyboard state, which is tracked per GUI thread as the threads pull keyboard messages from their queues. Imagine if a thread fell behind and a lot of keyboard messages were still queued up. The synchronous keyboard state (from GetKeyState or GetKeyboardState) would indicate the keyboard state at the time the most recently processed window message was posted, which may be different that the asynchronous state which would already reflect all of the messages still in the queue.
The character input messages, like
WM_CHAR
, are useful for text editors, word processors, etc. But even they must tap the pipeline earlier for keystrokes like Page Up and Page Down.Many apps work primarily with the
WM_KEYDOWN
andWM_KEYUP
messages.Fast video games likely use the asynchronous keyboard state or perhaps the synchronous one. And they may purposely not process any keyboard input when they are in the background.
What you can try
The easiest thing to try is to send (or better, post)
WM_KEYDOWN
andWM_KEYUP
messages instead ofWM_CHAR
. That won't guarantee success.Windows has the SendInput API for putting events in the input queue (the way the keyboard driver does). I don't know if there's a Python library that covers that. Even if there is, it's not likely to help, since the system won't send your injected keyboard input events to a background window.