barrettford / Octoprint-Usb_keyboard

Octoprint plugin to enable controlling a connected 3D printer with a USB keyboard
GNU General Public License v3.0
18 stars 5 forks source link

Listen to multiple input devices at once #35

Open Mazvy opened 2 years ago

Mazvy commented 2 years ago

I'm facing an issue with a USB numpad keyboard that has extra media buttons.

The keyboard appears as one device as expected

mazvydas@octopi:~$ ls /dev/input/by-path/
platform-3f980000.usb-usb-0:1.3:1.0-event-kbd  platform-3f980000.usb-usb-0:1.3:1.1-event

However it creates 3 event entries in /dev/input

mazvydas@octopi:~$ ls /dev/input/
by-id  by-path  event0  event1  event2  mice

From the plugins device configurator window:

Device device /dev/input/event2, name "SEM USB Keyboard System Control", phys "usb-3f980000.usb-1.3/input1"
  Info bus: 0003, vendor 1a2c, product 0e24, version 0110
  Physical usb-3f980000.usb-1.3/input1
Device device /dev/input/event1, name "SEM USB Keyboard Consumer Control", phys "usb-3f980000.usb-1.3/input1"
  Info bus: 0003, vendor 1a2c, product 0e24, version 0110
  Physical usb-3f980000.usb-1.3/input1
Device device /dev/input/event0, name "SEM USB Keyboard", phys "usb-3f980000.usb-1.3/input0"
  Info bus: 0003, vendor 1a2c, product 0e24, version 0110
  Physical usb-3f980000.usb-1.3/input0

/dev/input/event0 handles all "regular" keys but not the media keys (KEY_HOMEPAGE, KEY_MAIL, KEY_CALC). Media keys are handled exclusively by /dev/input/event1 but only those and not the regular keys. I have no idea what /dev/input/event2 does or why it exists, it appears to handle these events but those keys don't exist on the keypad itself

Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 116 (KEY_POWER)
    Event code 142 (KEY_SLEEP)
    Event code 143 (KEY_WAKEUP)
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)

As it stands I'm loosing 3 keys (KEY_HOMEPAGE, KEY_MAIL, KEY_CALC) by using /dev/input/event0 that would be very useful considering I only have a numpad to work with. Or I use /dev/input/event1 and only have access to 3 keys :)

It would be great if we could listen for more than just one /dev/input/eventX or perhaps some other means of listening to keystrokes? I don't have enough expertise with input devices in linux to by of any help in terms of suggestions, perhaps there is some workaround for this?

barrettford commented 2 years ago

I made a decision early on to only listen to one keyboard, but I suppose I can revisit the idea of having more than one. It's not gonna be soon, though, since I'm busy on other things at the moment.

barrettford commented 2 years ago

You gotta understand, this codebase is pretty hackish, with no unit tests and bad project structure. I made it quickly so that I could get my usb keypad working for my printer, and once it worked I pretty much stopped touching it.

Mazvy commented 2 years ago

You gotta understand, this codebase is pretty hackish, with no unit tests and bad project structure. I made it quickly so that I could get my usb keypad working for my printer, and once it worked I pretty much stopped touching it.

You did an amazing job, please don't perceive this as an attack on that. I greatly appreciate what you've done and if only I knew Python at a comfortable level I would join in and make PRs for this.

I made a decision early on to only listen to one keyboard, but I suppose I can revisit the idea of having more than one. It's not gonna be soon, though, since I'm busy on other things at the moment.

The issue is that linux can create multiple /dev/input/event's for a single keyboard, after some Google-fu I found people reporting a dozen or more events created for one keyboard. Presumably those keyboards had various media, macro, etc. keys. Something I didn't know before this.

I don't have enough knowledge on the matter, but maybe there is an event monitor in linux that provides a combined keyboard input stream? Perhaps a workaround would be for the plugin to listen to a file if listening to multiple /dev/input/event's if it is too cumbersome to implement? It would require the user to create their own middle-ware for intercepting key strokes, but ultimately that also opens the door the really wacky and cool stuff like remote operation via HTTP and whatnot.

Regardless, I hope you find the time for this, if not - this plugin is still greatly appreciated.

Mazvy commented 2 years ago

Also this https://python-evdev.readthedocs.io/en/latest/tutorial.html#reading-events-from-multiple-devices-using-select

NaviRob commented 2 years ago

Hi, this code will work. You just need to add it in start up of system and modify eventX number. The other thing is about multiple codes from listener, so you have two possibilities, add some code in this or modify your fantastic project.

kbd.py

import asyncio from evdev import UInput, InputDevice, categorize, ecodes as e

mouse = InputDevice('/dev/input/event3') keybd = InputDevice('/dev/input/event4')

ui = from_device(mouse, keybd, name='keyboard-pad-device')

async def print_events(device): async for event in device.async_read_loop(): print(device.path,e.categorize(event), sep=': ') ui.write_event(key_event) for device in mouse, keybd: asyncio.ensure_future(print_events(device))

loop = asyncio.get_event_loop() loop.run_forever()

Mazvy commented 2 years ago

@NaviRob Thanks for the example code! I got it working! Now my entire numpad keyboard is functional.

I also had to remap numpad enter code 96 (KEY_KPENTER) and space code 57 (KEY_SPACE) to different keys (F1 and F2). For whatever reason pressing either of them resulted in a full system crash. The crash has nothing to do with Octoprint or this plugin as it happened when Octoprint wasn't running. I have no idea why that was happening.

Here's what I'm currently running that I've also added to system startup:

import asyncio, evdev
from evdev import UInput, InputDevice, categorize, ecodes as e

event0 = evdev.InputDevice('/dev/input/event0')
event1 = evdev.InputDevice('/dev/input/event1')

# grab so enter and space don't crash the os
event0.grab()
event1.grab()

ui = UInput.from_device(event0, event1, name='Custom numpad controls')

async def print_events(device):
    async for event in device.async_read_loop():        
        # remap enter, space to f1, f2
        if event.code == 96:
            event.code = 59
        elif event.code == 57:
            event.code = 60

        ui.write_event(event)

for device in event0, event1:
    asyncio.ensure_future(print_events(device))

loop = asyncio.get_event_loop()
loop.run_forever()