Using macro keyboard as app control board

209 Views Asked by At

A while back, I bought a small 12-key (+2 rotary toggles) macro keyboard that can be programmed with various keys (https://github.com/jonnytest1/minikeyboard)

Now I want to use that keyboard to control random apps, but without interfering with anything a "normal keyboard" does (by using any of the keys that could do something on Windows or other apps) to achieve that, my idea was to:

  1. overwrite the "keyboard drivers" in the device manager to generic HID devices
  2. Write my own keyboard driver in-app
  3. Handle whatever stuff I want it to do

my initial attempt (which sort of worked): use pywinusb to open the device(s) for

VENDOR_ID = 0x1189
PRODUCT_ID = 0x8890

And then set a

device.set_raw_data_handler(devicebbound_handler(device))

which semi-worked :

  • it works for the media keys (I get events for those keys, but they also have a Windows effect, so it may not have worked)
  • if I set it to any "normal keys" like A B C or 1 2 3, I don't get any raw_data_handler - events

My current intuition is that I have to either

  • A) manually query for key presses

or

  • B) Send a report of some sort to start the keyboard to send interrupt signals

Unfortunately, the HID documentation is quite sparse, so I don't know what kind of report I have to send to get either

(I'm pretty sure I have to use a device with usagePage: 12 for the keyboard, which seems to exist at least)

current state of the app:

deviceList: Any = hid.HidDeviceFilter(vendor_id=VENDOR_ID,
                                      product_id=PRODUCT_ID,).get_devices()
devices: list["hid.HidDevice"] = deviceList

def devicebbound_handler(device: hid.HidDevice):

    def sample_handler(data):
        print(device.product_name+" "+str(device.product_id))
        print(data)
        pass

    return sample_handler

def isPLugged():
    pl = False
    for device in devices:
        pl = pl | device.is_plugged()
    return pl
for device in devices:
   device.open()
   device.set_raw_data_handler(devicebbound_handler(device))

while isPLugged():
   sleep(0.01)
1

There are 1 best solutions below

4
On

To want to use your macro keyboard as an app control board without interfering with the normal keyboard functions.

You can try, instead of overwriting drivers, to intercept keyboard events at the application level: that would allow handling macro keyboard inputs separately from the normal keyboard.

When you receive an event from the macro keyboard, check if it is a media key or a regular key. If it is a regular key, handle it within your application and prevent it from being sent to other applications.

Pressing a key on the macro keyboard should trigger a predefined action in your app: for that, define a mapping for each macro key to a specific function in your application.

import hid
from time import sleep

VENDOR_ID = 0x1189
PRODUCT_ID = 0x8890

# Function to handle device events
def device_bound_handler(device):
    def sample_handler(data):
        # Define your key mappings and app-specific actions here
        if data[2] == 1:  # Example: If the first key is pressed
            # Perform specific action for key 1
            print("Key 1 pressed on", device.product_name)

        # Add more conditions for other keys

    return sample_handler

# Check if device is plugged in
def is_plugged(devices):
    return any(device.is_plugged() for device in devices)

# Main function to start the app
def start_app():
    device_list = hid.HidDeviceFilter(vendor_id=VENDOR_ID, product_id=PRODUCT_ID).get_devices()
    devices = [device for device in device_list]

    for device in devices:
        device.open()
        device.set_raw_data_handler(device_bound_handler(device))

    while is_plugged(devices):
        sleep(0.01)

# Run the app
start_app()

That would intercept keyboard events from your macro keyboard and handles them within your application. You will need to define the specific actions for each key based on your app's requirements.

 Macro Keyboard ┌────────────────────────────────────────────────────┐
 (12 keys, 2    │  - Python app to intercept and handle events       │
 rotary toggles)│  - Vendor ID: 0x1189, Product ID: 0x8890           │
                │  - Mapping macro keys to app-specific functions    │
                └────────────────────────────────────────────────────┘
                   │
                   │ Intercepts and handles
                   │ macro keyboard events
                   ▼
           ┌────────────────────┐
           │ Application Logic  │
           │ - Custom actions   │
           │   for macro keys   │
           └────────────────────┘

How would I prevent it from being sent to other applications?

Maybe more importantly: when I reset it to the keyboard drivers, I do get the input event in, for example, a text editor - but not in the raw_data_handler

If you reset the macro keyboard to standard keyboard drivers and see inputs in text editors but not in your raw_data_handler, that should mean the OS is capturing these inputs as regular keyboard events, and they are not being directed to your handler.

Make sure the keyboard is using a driver that allows for raw input capture. Standard keyboard drivers will typically cause inputs to be interpreted directly by the OS, bypassing your handler.

And double-check that your application is correctly identifying and registering the macro keyboard. If the device ID or configuration has changed after resetting to keyboard drivers, your application may not recognize it as the intended device.

But I do not think pywinusb would support the ability to "grab" a device exclusively in the context of handling HID (Human Interface Device) inputs. It is a low-level library that provides access to the Windows HID API, which is not designed to provide the level of control you need to isolate the input from your macro keyboard.

On Windows, you might need to delve into the Windows Raw Input API, which allows applications to register for raw input from specific devices, including keyboards. That way, you can register for raw input from your macro keyboard and process it separately.

Consider using other Python libraries that might offer a more straightforward way to capture and isolate input from specific HID devices on Windows. Direct usage of Windows APIs through ctypes might be necessary. See also "How To Use The win32api with python3" from Karim / cpu0x00.