Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scroll Not Recognized on ASUS Precision Touchpad Driver #615

Open
naman108 opened this issue Oct 21, 2024 · 0 comments
Open

Scroll Not Recognized on ASUS Precision Touchpad Driver #615

naman108 opened this issue Oct 21, 2024 · 0 comments

Comments

@naman108
Copy link

Description
pynput does not register scroll actions from a touchpad using the ASUS Precision Touchpad Driver. I've confirmed this and the following POC does register the scroll events.

Platform and pynput version
Windows 11, version 23H2, pynput version 1.7.7

To Reproduce

from pynput import mouse

def on_move(x, y):
    print('Pointer moved to {0}'.format(
        (x, y)))

def on_click(x, y, button, pressed):
    print('{0} at {1}'.format(
        'Pressed' if pressed else 'Released',
        (x, y)))
    if not pressed:
        # Stop listener
        return False

def on_scroll(x, y, dx, dy):
    print('Scrolled {0} at {1}'.format(
        'down' if dy < 0 else 'up',
        (x, y)))

# Collect events until released
with mouse.Listener(
        on_move=on_move,
        on_click=on_click,
        on_scroll=on_scroll) as listener:
    listener.join()

On ASUS Zenbook with ASUS Precision Touchpad v16.0.0.11

Functional POC Code to register scroll events

import win32api
import win32gui
import win32con
import ctypes
from ctypes import wintypes

# Define WM_INPUT manually
WM_INPUT = 0x00FF

class TouchpadListener:
    def __init__(self):
        self.hInstance = win32api.GetModuleHandle(None)
        self.className = 'TouchpadEventListener'

        wndClass = win32gui.WNDCLASS()
        wndClass.style = win32con.CS_HREDRAW | win32con.CS_VREDRAW
        wndClass.lpfnWndProc = self.wndProc
        wndClass.hInstance = self.hInstance
        wndClass.hCursor = win32gui.LoadCursor(None, win32con.IDC_ARROW)
        wndClass.hbrBackground = win32con.COLOR_WINDOW
        wndClass.lpszClassName = self.className

        try:
            win32gui.RegisterClass(wndClass)
        except win32gui.error:
            pass  # The window class is already registered

        self.hWnd = win32gui.CreateWindow(
            self.className,
            'ASUS Precision Touchpad Listener',
            0,
            0, 0,
            win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
            None, None,
            self.hInstance,
            None
        )

        # Register for raw input from the touchpad
        self.register_raw_input_devices()

    def register_raw_input_devices(self):
        class RAWINPUTDEVICE(ctypes.Structure):
            _fields_ = [
                ('usUsagePage', ctypes.c_ushort),
                ('usUsage', ctypes.c_ushort),
                ('dwFlags', ctypes.c_ulong),
                ('hwndTarget', ctypes.c_void_p),
            ]

        RIDEV_INPUTSINK = 0x00000100
        HID_USAGE_PAGE_DIGITIZER = 0x0D
        HID_USAGE_DIGITIZER_TOUCHPAD = 0x05

        rid = RAWINPUTDEVICE()
        rid.usUsagePage = HID_USAGE_PAGE_DIGITIZER
        rid.usUsage = HID_USAGE_DIGITIZER_TOUCHPAD
        rid.dwFlags = RIDEV_INPUTSINK
        rid.hwndTarget = self.hWnd

        user32 = ctypes.WinDLL('user32', use_last_error=True)
        RegisterRawInputDevices = user32.RegisterRawInputDevices
        RegisterRawInputDevices.argtypes = [ctypes.POINTER(RAWINPUTDEVICE), ctypes.c_uint, ctypes.c_uint]
        RegisterRawInputDevices.restype = ctypes.c_bool

        if not RegisterRawInputDevices(ctypes.byref(rid), 1, ctypes.sizeof(rid)):
            raise ctypes.WinError(ctypes.get_last_error())

    def wndProc(self, hWnd, msg, wParam, lParam):
        if msg == WM_INPUT:
            self.process_raw_input(lParam)
            return 0
        elif msg == win32con.WM_DESTROY:
            win32gui.PostQuitMessage(0)
            return 0
        return win32gui.DefWindowProc(hWnd, msg, wParam, lParam)

    def process_raw_input(self, lParam):
        user32 = ctypes.WinDLL('user32', use_last_error=True)

        # Define missing types
        HRAWINPUT = ctypes.c_void_p
        UINT = ctypes.c_uint

        class RAWINPUTHEADER(ctypes.Structure):
            _fields_ = [
                ('dwType', UINT),
                ('dwSize', UINT),
                ('hDevice', ctypes.c_void_p),
                ('wParam', ctypes.c_ulong),
            ]

        class RAWMOUSE(ctypes.Structure):
            _fields_ = [
                ('usFlags', ctypes.c_ushort),
                ('ulButtons', ctypes.c_ulong),
                ('usButtonFlags', ctypes.c_ushort),
                ('usButtonData', ctypes.c_ushort),
                ('ulRawButtons', ctypes.c_ulong),
                ('lLastX', ctypes.c_long),
                ('lLastY', ctypes.c_long),
                ('ulExtraInformation', ctypes.c_ulong),
            ]

        class RAWKEYBOARD(ctypes.Structure):
            _fields_ = [
                ('MakeCode', ctypes.c_ushort),
                ('Flags', ctypes.c_ushort),
                ('Reserved', ctypes.c_ushort),
                ('VKey', ctypes.c_ushort),
                ('Message', ctypes.c_uint),
                ('ExtraInformation', ctypes.c_ulong),
            ]

        class RAWHID(ctypes.Structure):
            _fields_ = [
                ('dwSizeHid', ctypes.c_uint),
                ('dwCount', ctypes.c_uint),
                ('bRawData', ctypes.c_byte * 1),  # Placeholder
            ]

        class RAWINPUT_UNION(ctypes.Union):
            _fields_ = [
                ('mouse', RAWMOUSE),
                ('keyboard', RAWKEYBOARD),
                ('hid', RAWHID),
            ]

        class RAWINPUT(ctypes.Structure):
            _fields_ = [
                ('header', RAWINPUTHEADER),
                ('data', RAWINPUT_UNION),
            ]

        GetRawInputData = user32.GetRawInputData
        GetRawInputData.argtypes = [
            HRAWINPUT,        # hRawInput
            UINT,             # uiCommand
            ctypes.c_void_p,  # pData
            ctypes.POINTER(UINT),  # pcbSize
            UINT              # cbSizeHeader
        ]
        GetRawInputData.restype = UINT

        RID_INPUT = 0x10000003

        data_size = UINT(0)
        header_size = ctypes.sizeof(RAWINPUTHEADER)

        # First call to get the size of the raw input data
        res = GetRawInputData(
            HRAWINPUT(lParam),
            RID_INPUT,
            None,
            ctypes.byref(data_size),
            header_size
        )
        if res == -1 or data_size.value == 0:
            print("Failed to get raw input data size")
            return

        # Allocate buffer for raw input data
        buffer = ctypes.create_string_buffer(data_size.value)

        # Second call to get the raw input data
        res = GetRawInputData(
            HRAWINPUT(lParam),
            RID_INPUT,
            buffer,
            ctypes.byref(data_size),
            header_size
        )
        if res == -1 or res == 0:
            print("Failed to get raw input data")
            return

        # Cast buffer to RAWINPUT structure
        raw_input = ctypes.cast(buffer, ctypes.POINTER(RAWINPUT)).contents

        # Check if the input is from a HID device
        if raw_input.header.dwType == 2:  # RIM_TYPEHID
            # Access the HID data
            hid = raw_input.data.hid
            # Calculate the size of HID data
            hid_data_size = hid.dwSizeHid * hid.dwCount
            # Access the raw HID data
            hid_data = ctypes.cast(ctypes.byref(hid.bRawData), ctypes.POINTER(ctypes.c_ubyte * hid_data_size)).contents
            # Process the HID data here
            print("Received touchpad HID event")
        else:
            print("Received non-HID input")

    def run(self):
        win32gui.PumpMessages()

if __name__ == '__main__':
    listener = TouchpadListener()
    listener.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant