I followed the steps here to try and read some input from a device. I've been trying for a couple hours now to figure out why GetMessage
doesn't return anything. Originally I was trying to read from a certain device, but seeing as that wasn't working, I wanted to just try reading keyboard or mouse inputs. However, I've had no luck in doing so.
Edit: Some more info. I'm on Windows 10. I'm running the code in cmder (not sure if that makes any difference) with python main.py
. There are no error messages and the output is Successfully registered input device!
before the program just waits to receive a message from GetMessage
.
Here's the running code:
main.py:
from ctypes import windll, sizeof, WinDLL, pointer, c_uint, create_string_buffer, POINTER
from ctypes.wintypes import *
from structures import *
from constants import * # I put a comment specifying the value for each variable used from here
k32 = WinDLL('kernel32')
GetRawInputDeviceInfo = windll.user32.GetRawInputDeviceInfoA
GetRawInputDeviceInfo.argtypes = HANDLE, UINT, LPVOID, PUINT
RegisterRawInputDevices = windll.user32.RegisterRawInputDevices
RegisterRawInputDevices.argtypes = (RawInputDevice * 7), UINT, UINT
GetMessage = windll.user32.GetMessageA
GetMessage.argtypes = POINTER(Message), HWND, UINT, UINT
def print_error(code=None):
print(f"Error code {k32.GetLastError() if code is None else code}")
def register_devices(hwnd_target=None):
# Here I added all usages just to try and get any kind of response from GetMessage
page = 0x01
# DW_FLAGS is 0
devices = (RawInputDevice * 7)(
RawInputDevice(page, 0x01, DW_FLAGS, hwnd_target),
RawInputDevice(page, 0x02, DW_FLAGS, hwnd_target),
RawInputDevice(page, 0x04, DW_FLAGS, hwnd_target),
RawInputDevice(page, 0x05, DW_FLAGS, hwnd_target),
RawInputDevice(page, 0x06, DW_FLAGS, hwnd_target),
RawInputDevice(page, 0x07, DW_FLAGS, hwnd_target),
RawInputDevice(page, 0x08, DW_FLAGS, hwnd_target),
)
if not RegisterRawInputDevices(devices, len(devices), sizeof(devices[0])):
print_error()
else:
print("Successfully registered input device!")
def get_message(h_wnd=None):
msg = pointer(Message())
# WM_INPUT is 0
return_value = GetMessage(msg, h_wnd, WM_INPUT, WM_INPUT)
if return_value == -1:
print_error()
elif return_value == 0:
print("WM_QUIT message received.")
else:
print("Successfully got message!")
return msg
register_devices()
print(get_message().contents.message)
structures.py:
from ctypes import Structure
from ctypes.wintypes import *
class RawInputDevice(Structure):
_fields_ = [
("usUsagePage", USHORT),
("usUsage", USHORT),
("dwFlags", DWORD),
("hwndTarget", HWND),
]
class Message(Structure):
_fields_ = [
("hwnd", HWND),
("message", UINT),
("wParam", WPARAM),
("lParam", LPARAM),
("time", DWORD),
("pt", POINT),
("lPrivate", DWORD)
]
I'd appreciate it if anyone helped me figure out what's going wrong, or I'd also be fine if someone can point out an alternative to reading input from an HID device on Windows.
CodePudding user response:
I'm going to start with the (main) resources:
[SO]: Is it possible to use Windows Raw Input API without a window (ie from a console application)?
[Python.Docs]: ctypes - A foreign function library for Python
I prepared an example.
ctypes_wrappers.py:
import ctypes as ct
import ctypes.wintypes as wt
HCURSOR = ct.c_void_p
LRESULT = ct.c_ssize_t
wndproc_args = (wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM)
WNDPROC = ct.CFUNCTYPE(LRESULT, *wndproc_args)
kernel32 = ct.WinDLL("Kernel32")
user32 = ct.WinDLL("User32")
def structure_to_string_method(self):
ret = [f"{self.__class__.__name__} (size: {ct.sizeof(self.__class__)}) instance at 0x{id(self):016X}:"]
for fn, _ in self._fields_:
ret.append(f" {fn}: {getattr(self, fn)}")
return "\n".join(ret) "\n"
union_to_string_method = structure_to_string_method
class Struct(ct.Structure):
to_string = structure_to_string_method
class Uni(ct.Union):
to_string = union_to_string_method
class WNDCLASSEXW(Struct):
_fields_ = (
("cbSize", wt.UINT),
("style", wt.UINT),
#("lpfnWndProc", ct.c_void_p),
("lpfnWndProc", WNDPROC),
("cbClsExtra", ct.c_int),
("cbWndExtra", ct.c_int),
("hInstance", wt.HINSTANCE),
("hIcon", wt.HICON),
("hCursor", HCURSOR),
("hbrBackground", wt.HBRUSH),
("lpszMenuName", wt.LPCWSTR),
("lpszClassName", wt.LPCWSTR),
("hIconSm", wt.HICON),
)
WNDCLASSEX = WNDCLASSEXW
class RawInputDevice(Struct):
_fields_ = (
("usUsagePage", wt.USHORT),
("usUsage", wt.USHORT),
("dwFlags", wt.DWORD),
("hwndTarget", wt.HWND),
)
PRawInputDevice = ct.POINTER(RawInputDevice)
class RAWINPUTHEADER(Struct):
_fields_ = (
("dwType", wt.DWORD),
("dwSize", wt.DWORD),
("hDevice", wt.HANDLE),
("wParam", wt.WPARAM),
)
class RAWMOUSE(Struct):
_fields_ = (
("usFlags", wt.USHORT),
("ulButtons", wt.ULONG), # unnamed union: 2 USHORTS: flags, data
("ulRawButtons", wt.ULONG),
("lLastX", wt.LONG),
("lLastY", wt.LONG),
("ulExtraInformation", wt.ULONG),
)
class RAWKEYBOARD(Struct):
_fields_ = (
("MakeCode", wt.USHORT),
("Flags", wt.USHORT),
("Reserved", wt.USHORT),
("VKey", wt.USHORT),
("Message", wt.UINT),
("ExtraInformation", wt.ULONG),
)
class RAWHID(Struct):
_fields_ = (
("dwSizeHid", wt.DWORD),
("dwCount", wt.DWORD),
("bRawData", wt.BYTE * 1), # @TODO - cfati: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawhid, but not very usable via CTypes
)
class RAWINPUT_U0(Uni):
_fields_ = (
("mouse", RAWMOUSE),
("keyboard", RAWKEYBOARD),
("hid", RAWHID),
)
class RAWINPUT(Struct):
_fields_ = (
("header", RAWINPUTHEADER),
("data", RAWINPUT_U0),
)
PRAWINPUT = ct.POINTER(RAWINPUT)
GetLastError = kernel32.GetLastError
GetLastError.argtypes = ()
GetLastError.restype = wt.DWORD
GetModuleHandle = kernel32.GetModuleHandleW
GetModuleHandle.argtypes = (wt.LPWSTR,)
GetModuleHandle.restype = wt.HMODULE
DefWindowProc = user32.DefWindowProcW
DefWindowProc.argtypes = wndproc_args
DefWindowProc.restype = LRESULT
RegisterClassEx = user32.RegisterClassExW
RegisterClassEx.argtypes = (ct.POINTER(WNDCLASSEX),)
RegisterClassEx.restype = wt.ATOM
CreateWindowEx = user32.CreateWindowExW
CreateWindowEx.argtypes = (wt.DWORD, wt.LPCWSTR, wt.LPCWSTR, wt.DWORD, ct.c_int, ct.c_int, ct.c_int, ct.c_int, wt.HWND, wt.HMENU, wt.HINSTANCE, wt.LPVOID)
CreateWindowEx.restype = wt.HWND
RegisterRawInputDevices = user32.RegisterRawInputDevices
RegisterRawInputDevices.argtypes = (PRawInputDevice, wt.UINT, wt.UINT)
RegisterRawInputDevices.restype = wt.BOOL
GetRawInputData = user32.GetRawInputData
GetRawInputData.argtypes = (PRAWINPUT, wt.UINT, wt.LPVOID, wt.PUINT, wt.UINT)
GetRawInputData.restype = wt.UINT
GetMessage = user32.GetMessageW
GetMessage.argtypes = (wt.LPMSG, wt.HWND, wt.UINT, wt.UINT)
GetMessage.restype = wt.BOOL
PeekMessage = user32.PeekMessageW
PeekMessage.argtypes = (wt.LPMSG, wt.HWND, wt.UINT, wt.UINT, wt.UINT)
PeekMessage.restype = wt.BOOL
TranslateMessage = user32.TranslateMessage
TranslateMessage.argtypes = (wt.LPMSG,)
TranslateMessage.restype = wt.BOOL
DispatchMessage = user32.DispatchMessageW
DispatchMessage.argtypes = (wt.LPMSG,)
DispatchMessage.restype = LRESULT
PostQuitMessage = user32.PostQuitMessage
PostQuitMessage.argtypes = (ct.c_int,)
PostQuitMessage.restype = None
code00.py:
#!/usr/bin/env python
import ctypes as ct
import ctypes.wintypes as wt
import sys
import time
import ctypes_wrappers as cw
HWND_MESSAGE = -3
WM_QUIT = 0x0012
WM_INPUT = 0x00FF
WM_KEYUP = 0x0101
WM_CHAR = 0x0102
HID_USAGE_PAGE_GENERIC = 0x01
RIDEV_NOLEGACY = 0x00000030
RIDEV_INPUTSINK = 0x00000100
RIDEV_CAPTUREMOUSE = 0x00000200
RID_HEADER = 0x10000005
RID_INPUT = 0x10000003
RIM_TYPEMOUSE = 0
RIM_TYPEKEYBOARD = 1
RIM_TYPEHID = 2
PM_NOREMOVE = 0x0000
def wnd_proc(hwnd, msg, wparam, lparam):
print(f"Handle message - hwnd: 0x{hwnd:016X} msg: 0x{msg:08X} wp: 0x{wparam:016X} lp: 0x{lparam:016X}")
if msg == WM_INPUT:
size = wt.UINT(0)
res = cw.GetRawInputData(ct.cast(lparam, cw.PRAWINPUT), RID_INPUT, None, ct.byref(size), ct.sizeof(cw.RAWINPUTHEADER))
if res == wt.UINT(-1) or size == 0:
print_error(text="GetRawInputData 0")
return 0
buf = ct.create_string_buffer(size.value)
res = cw.GetRawInputData(ct.cast(lparam, cw.PRAWINPUT), RID_INPUT, buf, ct.byref(size), ct.sizeof(cw.RAWINPUTHEADER))
if res != size.value:
print_error(text="GetRawInputData 1")
return 0
#print("kkt: ", ct.cast(lparam, cw.PRAWINPUT).contents.to_string())
ri = ct.cast(buf, cw.PRAWINPUT).contents
#print(ri.to_string())
head = ri.header
print(head.to_string())
#print(ri.data.mouse.to_string())
#print(ri.data.keyboard.to_string())
#print(ri.data.hid.to_string())
if head.dwType == RIM_TYPEMOUSE:
data = ri.data.mouse
elif head.dwType == RIM_TYPEKEYBOARD:
data = ri.data.keyboard
if data.VKey == 0x1B:
cw.PostQuitMessage(0)
elif head.dwType == RIM_TYPEHID:
data = ri.data.hid
else:
print("Wrong raw input type!!!")
return 0
print(data.to_string())
return cw.DefWindowProc(hwnd, msg, wparam, lparam)
def print_error(code=None, text=None):
text = text " - e" if text else "E"
code = cw.GetLastError() if code is None else code
print(f"{text}rror code: {code}")
def register_devices(hwnd=None):
flags = RIDEV_INPUTSINK # @TODO - cfati: If setting to 0, GetMessage hangs
generic_usage_ids = (0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08)
devices = (cw.RawInputDevice * len(generic_usage_ids))(
*(cw.RawInputDevice(HID_USAGE_PAGE_GENERIC, uid, flags, hwnd) for uid in generic_usage_ids)
)
#for d in devices: print(d.usUsagePage, d.usUsage, d.dwFlags, d.hwndTarget)
if cw.RegisterRawInputDevices(devices, len(generic_usage_ids), ct.sizeof(cw.RawInputDevice)):
print("Successfully registered input device(s)!")
return True
else:
print_error(text="RegisterRawInputDevices")
return False
def main(*argv):
wnd_cls = "SO049572093_RawInputWndClass"
wcx = cw.WNDCLASSEX()
wcx.cbSize = ct.sizeof(cw.WNDCLASSEX)
#wcx.lpfnWndProc = ct.cast(cw.DefWindowProc, ct.c_void_p)
wcx.lpfnWndProc = cw.WNDPROC(wnd_proc)
wcx.hInstance = cw.GetModuleHandle(None)
wcx.lpszClassName = wnd_cls
#print(dir(wcx))
res = cw.RegisterClassEx(ct.byref(wcx))
if not res:
print_error(text="RegisterClass")
return 0
hwnd = cw.CreateWindowEx(0, wnd_cls, None, 0, 0, 0, 0, 0, 0, None, wcx.hInstance, None)
if not hwnd:
print_error(text="CreateWindowEx")
return 0
#print("hwnd:", hwnd)
if not register_devices(hwnd):
return 0
msg = wt.MSG()
pmsg = ct.byref(msg)
print("Start loop (press <ESC> to exit)...")
while res := cw.GetMessage(pmsg, None, 0, 0):
if res < 0:
print_error(text="GetMessage")
break
cw.TranslateMessage(pmsg)
cw.DispatchMessage(pmsg)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
Output:
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q071994439]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32 Handle message - hwnd: 0x00000000002F0606 msg: 0x00000024 wp: 0x0000000000000000 lp: 0x000000F5E0BEDDE0 Handle message - hwnd: 0x00000000002F0606 msg: 0x00000081 wp: 0x0000000000000000 lp: 0x000000F5E0BEDD70 Handle message - hwnd: 0x00000000002F0606 msg: 0x00000083 wp: 0x0000000000000000 lp: 0x000000F5E0BEDE00 Handle message - hwnd: 0x00000000002F0606 msg: 0x00000001 wp: 0x0000000000000000 lp: 0x000000F5E0BEDD70 Successfully registered input device(s)! Start loop (press <ESC> to exit)... Handle message - hwnd: 0x00000000002F0606 msg: 0x0000031F wp: 0x0000000000000001 lp: 0x0000000000000000 Handle message - hwnd: 0x00000000002F0606 msg: 0x000000FF wp: 0x0000000000000001 lp: 0x-00000003849FCDF RAWINPUTHEADER (size: 24) instance at 0x00000296313BBBC0: dwType: 1 dwSize: 40 hDevice: 843780541 wParam: 1 RAWKEYBOARD (size: 16) instance at 0x00000296313BBCC0: MakeCode: 30 Flags: 0 Reserved: 0 VKey: 65 Message: 256 ExtraInformation: 0 Handle message - hwnd: 0x00000000002F0606 msg: 0x000000FF wp: 0x0000000000000001 lp: 0x0000000031AE1619 RAWINPUTHEADER (size: 24) instance at 0x00000296313BBBC0: dwType: 1 dwSize: 40 hDevice: 843780541 wParam: 1 RAWKEYBOARD (size: 16) instance at 0x00000296313BBD40: MakeCode: 30 Flags: 1 Reserved: 0 VKey: 65 Message: 257 ExtraInformation: 0 Handle message - hwnd: 0x00000000002F0606 msg: 0x000000FF wp: 0x0000000000000001 lp: 0x000000007C851501 RAWINPUTHEADER (size: 24) instance at 0x00000296313BBBC0: dwType: 0 dwSize: 48 hDevice: 4461491 wParam: 1 RAWMOUSE (size: 24) instance at 0x00000296313BBDC0: usFlags: 0 ulButtons: 1 ulRawButtons: 0 lLastX: 0 lLastY: 0 ulExtraInformation: 0 Handle message - hwnd: 0x00000000002F0606 msg: 0x000000FF wp: 0x0000000000000001 lp: 0x0000000031B41619 RAWINPUTHEADER (size: 24) instance at 0x00000296313BBBC0: dwType: 0 dwSize: 48 hDevice: 4461491 wParam: 1 RAWMOUSE (size: 24) instance at 0x00000296313BBE40: usFlags: 0 ulButtons: 2 ulRawButtons: 0 lLastX: 0 lLastY: 0 ulExtraInformation: 0 Handle message - hwnd: 0x00000000002F0606 msg: 0x000000FF wp: 0x0000000000000001 lp: 0x0000000052D10665 RAWINPUTHEADER (size: 24) instance at 0x00000296313BBBC0: dwType: 1 dwSize: 40 hDevice: 843780541 wParam: 1 RAWKEYBOARD (size: 16) instance at 0x00000296313BBEC0: MakeCode: 1 Flags: 0 Reserved: 0 VKey: 27 Message: 256 ExtraInformation: 0 Done.
Notes:
The (above) output was generated by the following actions: a, LClick, ESC
I gave up on PyWin32, as it doesn't wrap the functions that we need (none of the Raw Input family), but it might be used (at least) for the constants from win32con (to avoid defining them)
I think things can be simplified, by moving functionality from wnd_proc to the while loop from main (and thus all the window class stuff (constants, structures, functions) could be dropped), but I started this way and I don't feel like changing it
A major breakthrough was RIDEV_INPUTSINK (since then, GetMessage stopped hanging)
RAWHID structure (@TODO) is wrapped "by the book", but it won't work OOTB (you mentioned working with other type of devices). That (1 sized) array at the end is just a way of stating that some additional data (dwSizeHid sized) will follow, which obviously won't fit in one byte. A "trick" is required there: the structure must be dynamically defined, based on the size of the data (example: [SO]: Setting _fields_ dynamically in ctypes.Structure (@CristiFati's answer) - I remember that I wrote a newer answer on that topic, but I can't find or remember it), and all that behavior propagated (recursively) in all structures (unions) that encapsulate it
CodePudding user response:
GetMessage is for use in a message pump, see https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage. I do not know what you should use but GetMessage definitely is not meant for such use.