Having trouble using winapi to read input from a device

Question:

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.

Asked By: Sheepposu

||

Answers:

GetMessage is for use in a message pump, see https://learn.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.

Answered By: guest

I’m going to start with the (main) resources:

I prepared an example.

ctypes_wrappers.py:


import ctypes as cts
import ctypes.wintypes as wts


HCURSOR = cts.c_void_p
LRESULT = cts.c_ssize_t

wndproc_args = (wts.HWND, wts.UINT, wts.WPARAM, wts.LPARAM)

WNDPROC = cts.CFUNCTYPE(LRESULT, *wndproc_args)

kernel32 = cts.WinDLL("Kernel32")
user32 = cts.WinDLL("User32")


def structure_to_string_method(self):
    ret = [f"{self.__class__.__name__} (size: {cts.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(cts.Structure):
    to_string = structure_to_string_method


class Uni(cts.Union):
    to_string = union_to_string_method


class WNDCLASSEXW(Struct):
    _fields_ = (
        ("cbSize", wts.UINT),
        ("style", wts.UINT),
        #("lpfnWndProc", cts.c_void_p),
        ("lpfnWndProc", WNDPROC),
        ("cbClsExtra", cts.c_int),
        ("cbWndExtra", cts.c_int),
        ("hInstance", wts.HINSTANCE),
        ("hIcon", wts.HICON),
        ("hCursor", HCURSOR),
        ("hbrBackground", wts.HBRUSH),
        ("lpszMenuName", wts.LPCWSTR),
        ("lpszClassName", wts.LPCWSTR),
        ("hIconSm", wts.HICON),
    )

WNDCLASSEX = WNDCLASSEXW


class RawInputDevice(Struct):
    _fields_ = (
        ("usUsagePage", wts.USHORT),
        ("usUsage", wts.USHORT),
        ("dwFlags", wts.DWORD),
        ("hwndTarget", wts.HWND),
    )

PRawInputDevice = cts.POINTER(RawInputDevice)


class RAWINPUTHEADER(Struct):
    _fields_ = (
        ("dwType", wts.DWORD),
        ("dwSize", wts.DWORD),
        ("hDevice", wts.HANDLE),
        ("wParam", wts.WPARAM),
    )


class RAWMOUSE(Struct):
    _fields_ = (
        ("usFlags", wts.USHORT),
        ("ulButtons", wts.ULONG),  # unnamed union: 2 USHORTS: flags, data
        ("ulRawButtons", wts.ULONG),
        ("lLastX", wts.LONG),
        ("lLastY", wts.LONG),
        ("ulExtraInformation", wts.ULONG),
    )


class RAWKEYBOARD(Struct):
    _fields_ = (
        ("MakeCode", wts.USHORT),
        ("Flags", wts.USHORT),
        ("Reserved", wts.USHORT),
        ("VKey", wts.USHORT),
        ("Message", wts.UINT),
        ("ExtraInformation", wts.ULONG),
    )


class RAWHID(Struct):
    _fields_ = (
        ("dwSizeHid", wts.DWORD),
        ("dwCount", wts.DWORD),
        ("bRawData", wts.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 = cts.POINTER(RAWINPUT)


GetLastError = kernel32.GetLastError
GetLastError.argtypes = ()
GetLastError.restype = wts.DWORD

GetModuleHandle = kernel32.GetModuleHandleW
GetModuleHandle.argtypes = (wts.LPWSTR,)
GetModuleHandle.restype = wts.HMODULE


DefWindowProc = user32.DefWindowProcW
DefWindowProc.argtypes = wndproc_args
DefWindowProc.restype = LRESULT

RegisterClassEx = user32.RegisterClassExW
RegisterClassEx.argtypes = (cts.POINTER(WNDCLASSEX),)
RegisterClassEx.restype = wts.ATOM

CreateWindowEx = user32.CreateWindowExW
CreateWindowEx.argtypes = (wts.DWORD, wts.LPCWSTR, wts.LPCWSTR, wts.DWORD, cts.c_int, cts.c_int, cts.c_int, cts.c_int, wts.HWND, wts.HMENU, wts.HINSTANCE, wts.LPVOID)
CreateWindowEx.restype = wts.HWND

RegisterRawInputDevices = user32.RegisterRawInputDevices
RegisterRawInputDevices.argtypes = (PRawInputDevice, wts.UINT, wts.UINT)
RegisterRawInputDevices.restype = wts.BOOL

GetRawInputData = user32.GetRawInputData
GetRawInputData.argtypes = (PRAWINPUT, wts.UINT, wts.LPVOID, wts.PUINT, wts.UINT)
GetRawInputData.restype = wts.UINT

GetMessage = user32.GetMessageW
GetMessage.argtypes = (wts.LPMSG, wts.HWND, wts.UINT, wts.UINT)
GetMessage.restype = wts.BOOL

PeekMessage = user32.PeekMessageW
PeekMessage.argtypes = (wts.LPMSG, wts.HWND, wts.UINT, wts.UINT, wts.UINT)
PeekMessage.restype = wts.BOOL

TranslateMessage = user32.TranslateMessage
TranslateMessage.argtypes = (wts.LPMSG,)
TranslateMessage.restype = wts.BOOL

DispatchMessage = user32.DispatchMessageW
DispatchMessage.argtypes = (wts.LPMSG,)
DispatchMessage.restype = LRESULT

PostQuitMessage = user32.PostQuitMessage
PostQuitMessage.argtypes = (cts.c_int,)
PostQuitMessage.restype = None

code00.py:

#!/usr/bin/env python

import ctypes as cts
import ctypes.wintypes as wts
import sys
import time

import ctypes_wrappers as cws


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 = wts.UINT(0)
        res = cws.GetRawInputData(cts.cast(lparam, cws.PRAWINPUT), RID_INPUT, None, cts.byref(size), cts.sizeof(cws.RAWINPUTHEADER))
        if res == wts.UINT(-1) or size == 0:
            print_error(text="GetRawInputData 0")
            return 0
        buf = cts.create_string_buffer(size.value)
        res = cws.GetRawInputData(cts.cast(lparam, cws.PRAWINPUT), RID_INPUT, buf, cts.byref(size), cts.sizeof(cws.RAWINPUTHEADER))
        if res != size.value:
            print_error(text="GetRawInputData 1")
            return 0
        #print("kkt: ", cts.cast(lparam, cws.PRAWINPUT).contents.to_string())
        ri = cts.cast(buf, cws.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:
                cws.PostQuitMessage(0)
        elif head.dwType == RIM_TYPEHID:
            data = ri.data.hid
        else:
            print("Wrong raw input type!!!")
            return 0
        print(data.to_string())
    return cws.DefWindowProc(hwnd, msg, wparam, lparam)


def print_error(code=None, text=None):
    text = text + " - e" if text else "E"
    code = cws.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 = (cws.RawInputDevice * len(generic_usage_ids))(
        *(cws.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 cws.RegisterRawInputDevices(devices, len(generic_usage_ids), cts.sizeof(cws.RawInputDevice)):
        print("Successfully registered input device(s)!")
        return True
    else:
        print_error(text="RegisterRawInputDevices")
        return False


def main(*argv):
    wnd_cls = "SO049572093_RawInputWndClass"
    wcx = cws.WNDCLASSEX()
    wcx.cbSize = cts.sizeof(cws.WNDCLASSEX)
    #wcx.lpfnWndProc = cts.cast(cws.DefWindowProc, cts.c_void_p)
    wcx.lpfnWndProc = cws.WNDPROC(wnd_proc)
    wcx.hInstance = cws.GetModuleHandle(None)
    wcx.lpszClassName = wnd_cls
    #print(dir(wcx))
    res = cws.RegisterClassEx(cts.byref(wcx))
    if not res:
        print_error(text="RegisterClass")
        return 0
    hwnd = cws.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 = wts.MSG()
    pmsg = cts.byref(msg)
    print("Start loop (press <ESC> to exit)...")
    while res := cws.GetMessage(pmsg, None, 0, 0):
        if res < 0:
            print_error(text="GetMessage")
            break
        cws.TranslateMessage(pmsg)
        cws.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.n")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:WorkDevStackOverflowq071994439]> "e:WorkDevVEnvspy_pc064_03.09_test0Scriptspython.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

Answered By: CristiFati
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.