erjiang / huion-keys

Linux program to create custom bindings for Huion tablet hotkeys
GNU General Public License v3.0
13 stars 4 forks source link

get_tablet_hidraw() needs more robust device detection #11

Open NeoTheFox opened 3 years ago

NeoTheFox commented 3 years ago

The actual issue is not related to GNOME but instead has to do with hidraw device detection misfiring. Jump to

The following Is no longer relevat.

I have limited knowledge about the inner working of GNOME, but since the recent update, it supports mapping keys on Huion tablets and recognizes their pens/pressure settings. As a consequence of it handling these events, huion-keys receives nothing from the hidraw. A workaround I found is to just prevent the X11 from using the "Pad" part of the device by creating a file /etc/X11/xorg.conf.d/50-huion.conf


Section "InputClass"
    Identifier "HUION Huion Tablet_Q620M Pad pad"
    MatchProduct "HUION Huion Tablet_Q620M Pad pad"
    Option "Ignore" "true"
EndSection
erjiang commented 3 years ago

I have limited knowledge about the inner working of GNOME, but since the recent update, it supports mapping keys on Huion tablets and recognizes their pens/pressure settings.

Interesting... do you know what components of gnome handle this? Does it include a GUI for configuring the tablet?

NeoTheFox commented 3 years ago

Yes, the GUI is a part of gnome-settings gnome-settings-screenshot

I think mutter handles input right now, since this is the intended behavior for Wayland, where the compositor handles input. They just started exposing these settings, even though the buttons and some other stuff had been in libinput for some time now, you can look at its events with libinput debug-gui. I can't say for sure but my guess is libinput gets exclusive rights over the device node and captures events before they manage to reach hidraw. Now there is python-libinput that should give an ability to tap into that in theory, but their support is incomplete - for example, they lack the dial support, and of course no mode switching. But it does support button holding/modifiers if you map them through the GNOME GUI.

I don't really know if something should be done about this, other than just mentioning the device blacklisting in the README.

NeoTheFox commented 3 years ago

I've been investigating this issue further and it looks like I got it completely wrong. The root issue is not with GNOME at all, although I suspect running GNOME as opposed to some other lighter options contributes to the race condition somewhere in the udev or the kernel.

The problem lies in get_tablet_hidraw() function. More than 2 hidraw devices are being created for the tablet, and it turns out the function is, indeed, is too fragile. The previous issues I've been having had been caused by a race condition that would name them in a different order every time I would plug the tablet in, hence the inconsistency in when it would work and when it wouldn't. But it looks like that in more recent kernel/digimend versions the race either stopped happening or is extremely rare because I've been unable to use the mapping and almost drove myself insane by various means, including trying to figure out more ways to blacklist the tablet from libinput and the system and even rewriting huion-keys.py in Rust from scratch.

Everything works perfectly once again if I just manually select the appropriate hidraw device. I'll try and refactor the function to be more robust if I'll find a way to do so, but so far it looks like there isn't a good way to tell if it's the appropriate hidraw device without opening it first to test it or knowing its name beforehand.

erjiang commented 3 years ago

The get_tablet_hidraw() function is supposed to only select the device that has device/input in a subdirectory (huion_keys.py:113). Does your tablet have multiple hidraw with device/input? My tablet shows up with two hidraw devices, but only one of them has device/input.

Can you post what your /sys/devices/hidraw*/device/ directories contain for your tablet?

If that doesn't work, I was thinking we could check the HID report descriptor for each device which should list each device's capabilities.

erjiang commented 3 years ago

For reference, here's mine:

/sys/class/hidraw$ readlink -f hidraw3 && ls hidraw3/device/
/sys/devices/pci0000:00/0000:00:14.0/usb1/1-6/1-6:1.0/0003:256C:006E.0004/hidraw/hidraw3
country  hidraw  modalias  report_descriptor  uevent
driver   input   power     subsystem
/sys/class/hidraw$ readlink -f hidraw3 && ls hidraw4/device/
/sys/devices/pci0000:00/0000:00:14.0/usb1/1-6/1-6:1.0/0003:256C:006E.0004/hidraw/hidraw3
country  driver  hidraw  modalias  power  report_descriptor  subsystem  uevent

Here's a section of the report descriptor for the device that reports the buttons:

    Field(2)                                                           
      Physical(Digitizers.TabletFunctionKey)                           
      Application(GenericDesktop.Keypad)                               
      Usage(10)                                                        
        Button.0001                                                    
        Button.0002                                                    
        Button.0003                                                    
        Button.0004                                                    
        Button.0005                                                    
        Button.0006                                                    
        Button.0007                                                    
        Button.0008                                                    
        Button.0009                                                    
        Button.000a                                                    
      Logical Minimum(0)                                               
      Logical Maximum(1)                                               
      Report Size(1)                                                   
      Report Count(10)                                                 
      Report Offset(24)                                                
      Flags( Variable Absolute )                                       
    Field(3)                                                           
      Physical(GenericDesktop.GamePad)                                 
      Application(GenericDesktop.Keypad)                               
      Usage(2)                                                         
        Button.0001                                                    
        Button.0002                                                    
      Logical Minimum(0)                                               
      Logical Maximum(1)                                               
      Report Size(1)                                                   
      Report Count(2)                                                  
      Report Offset(34)                                                
      Flags( Variable Absolute )

You can get the report descriptor by looking in /sys/kernel/debug/hid/[DEVICE_ID]/rdesc as root.

NeoTheFox commented 3 years ago

Sure, here are the two devices '/dev/hidraw21', '/dev/hidraw22' /sys/devices/pci0000:00/0000:00:01.3/0000:02:00.2/0000:03:03.0/0000:07:00.0/usb3/3-1/3-1.3/3-1.3:1.0/0003:256C:006D.0066/hidraw/hidraw21 country driver hidraw input modalias power report_descriptor subsystem uevent

/sys/devices/pci0000:00/0000:00:01.3/0000:02:00.2/0000:03:03.0/0000:07:00.0/usb3/3-1/3-1.3/3-1.3:1.1/0003:256C:006D.0067/hidraw/hidraw22 country driver hidraw input modalias power report_descriptor subsystem uevent

The rdesc reports on all 3 would be too long so I made a gist of it here https://gist.github.com/NeoTheFox/905b64d35830e14d0583fd1d00db91dc

erjiang commented 3 years ago

I see why there's a problem - your tablet simulates a keyboard on one device for some reason, and it's not safe to look for device/input. Since only one device reports TabletFunctionKey, maybe we can just look for that in get_tablet_hidraw?

I'm not entirely sure how to get the report descriptor though... it seems that on my machine, the device's driver needs to be unbound first (e.g. libusb_detach_kernel_driver) but that would cause the device's other features to stop working.

NeoTheFox commented 3 years ago

The threaded approach I've tried in the WIP branch works around that and also offers multi-device support on top, there seem to be no downsides to opening both devices. Or a "dumber" of doing that would be to ask the user what device to use interactively on start and add a cli flag so that could be set in stone (if the race condition that used to be the case on my PC is truly eliminated then we can somewhat rely on the order these appear in). So, for example, if started with no argument it could do this:

./huion_keys.py
Found Q620M with devices ['hidraw19', 'hidraw20']. Please select the appropriate device:
1: hidraw19
2: hidraw20

and then on next runs you do

./huion_keys.py -d 1

to always go with device 1, if there is only one device - default to it. If run in a non-interactive shell and no arguments - default to the first one.