Fixing My Wacom Tablet
A quick warning: this isn't one of these "here's the problem, here's the solution, bam bam bam" type of write-ups.This was written while I tried to fix my tablet, and with very little editing after the fact. Don't go in expecting a well though out story arc, as this is meant to reflect how I was working and what I was thinking. I think generally there's way too little material online on how people work day to day, and too many write-ups of just the good parts, so this is me helping pushing the ratio a little in the right direction.
With that out of the way, let's start with the background.
Over a year ago I bought a Wacom drawing tablet, having been increasingly annoyed with my handwritten notes and doodles. I figured if I got a tablet I could draw digitally which would simplify erasing, colors, and layout. And, of course, I would be able to access it digitally. Since then I've mainly used XournalPP for this, and I think it's been working okay.
Not great though; there are definitely quirks with both XournalPP and the wacom driver, and it took some tinkering before I had a setup that was usable. Still, one thing that never worked is button presses on the drawing pad. The buttons on the stylus works fine, and the on/off button on the pad works, but none of the four remaining pad buttons do anything.
Worse yet, I'm using Wayland on my home computer, which I suspect will make things tougher.
Still, I figured after so long I'd try to properly fix this, whatever it takes. Just1 getting some button event presses shouldn't be that hard, right?
libinput
libinput is a library to handle input devices in Wayland.
My system also has the libinput
tool for interfacing with this library, and the tool
has, among other things, the command debug-events
:
$ sudo libinput debug-events
-event1 DEVICE_ADDED Power Button seat0 default group1 cap:k
-event0 DEVICE_ADDED Power Button seat0 default group2 cap:k
-event20 DEVICE_ADDED Logitech Performance MX seat0 default group3 cap:p left scroll-nat scroll-button
-event3 DEVICE_ADDED HDA ATI HDMI HDMI/DP,pcm=3 seat0 default group4 cap:
-event4 DEVICE_ADDED HDA ATI HDMI HDMI/DP,pcm=7 seat0 default group4 cap:
-event5 DEVICE_ADDED HDA ATI HDMI HDMI/DP,pcm=8 seat0 default group4 cap:
-event6 DEVICE_ADDED HDA ATI HDMI HDMI/DP,pcm=9 seat0 default group4 cap:
-event7 DEVICE_ADDED HDA ATI HDMI HDMI/DP,pcm=10 seat0 default group4 cap:
-event8 DEVICE_ADDED HDA ATI HDMI HDMI/DP,pcm=11 seat0 default group4 cap:
-event21 DEVICE_ADDED Kingsis Peripherals Evoluent VerticalMouse 4 seat0 default group5 cap:p left scroll-nat scroll-button
-event26 DEVICE_ADDED HD Pro Webcam C920 seat0 default group6 cap:k
-event24 DEVICE_ADDED Wacom Intuos BT M Pen seat0 default group7 cap:T size 216x135mm
-event25 DEVICE_ADDED Wacom Intuos BT M Pad seat0 default group7 cap:P buttons:4 strips:0 rings:0 mode groups:1
-event17 DEVICE_ADDED ZSA Ergodox EZ seat0 default group8 cap:k
-event18 DEVICE_ADDED ZSA Ergodox EZ Mouse seat0 default group8 cap:p left scroll-nat scroll-button
-event19 DEVICE_ADDED ZSA Ergodox EZ System Control seat0 default group8 cap:k
-event22 DEVICE_ADDED ZSA Ergodox EZ Consumer Control seat0 default group8 cap:kp scroll-nat
-event23 DEVICE_ADDED ZSA Ergodox EZ Keyboard seat0 default group8 cap:k
-event10 DEVICE_ADDED HD-Audio Generic Rear Mic seat0 default group4 cap:
-event11 DEVICE_ADDED HD-Audio Generic Line seat0 default group4 cap:
-event12 DEVICE_ADDED HD-Audio Generic Line Out Front seat0 default group4 cap:
-event13 DEVICE_ADDED HD-Audio Generic Line Out Surround seat0 default group4 cap:
-event14 DEVICE_ADDED HD-Audio Generic Line Out CLFE seat0 default group4 cap:
-event15 DEVICE_ADDED HD-Audio Generic Line Out Side seat0 default group4 cap:
-event16 DEVICE_ADDED HD-Audio Generic Front Headphone seat0 default group4 cap:
-event9 DEVICE_ADDED HD-Audio Generic Front Mic seat0 default group4 cap:
two of which looks pretty interesting:
-event24 DEVICE_ADDED Wacom Intuos BT M Pen seat0 default group7 cap:T size 216x135mm
-event25 DEVICE_ADDED Wacom Intuos BT M Pad seat0 default group7 cap:P buttons:4 strips:0 rings:0 mode groups:1
The pad with its four buttons seems to be properly detected.
Drawing on the pad while libinput debug-events
is running spits out a bunch of lines of the form
event24 TABLET_TOOL_AXIS +1.666s 121.71*/69.45* distance: 0.94*
event24 TABLET_TOOL_AXIS +1.674s 121.76*/69.46* distance: 0.94
event24 TABLET_TOOL_AXIS +1.682s 121.83*/69.47* distance: 0.87*
event24 TABLET_TOOL_AXIS +2.552s 106.09*/43.80* pressure: 0.34*
event24 TABLET_TOOL_AXIS +2.558s 106.06*/43.79* pressure: 0.35*
event24 TABLET_TOOL_AXIS +2.566s 106.05*/43.79 pressure: 0.35*
that is, events from the pen. We can see that we're getting events that the pen is near, but not touching, the pad with the lines saying distance
,
and that the events corresponding to when we're actually touching2 the pad have pressure
.
The numbers in the middle are positional coordinates, ranging from 0/0
at the top left
to 216/135
at the bottom right, which I suspect are in millimeters3.
So far this seems to be working rather well. But uh oh, what happens when we try to press the pad buttons? Nothing. And thus begins the adventure.
libwacom
I figure that since the pen is working well, it might be the driver for the pad that's lacking.
This is slightly supported by the fact that the only apparent usage of the pad
is to detect when the pen is near (see this2 footnote).
Assuming this is handled across other pads in the same way, maybe the specifics of my pad,
like the buttons, are problematic in the driver.
The output from libinput
did state correctly that it has 4 button though, but ... uuuh, let's just check anyways.
Let's see what relevant kernel modules and packages we have:
$ lsmod | grep wacom
wacom 126976 0
usbhid 65536 2 wacom,hid_logitech_dj
hid 143360 5 wacom,usbhid,hid_generic,hid_logitech_dj,hid_logitech_hidpp
$ pacman -Q | grep wacom
libwacom 1.3-1
Okay. A quick search reveals that libwacom
is on Github,
and the README contains the following helpful note:
Use the
libwacom-list-local-devices
tool to list all local devices recognized by libwacom. If your device is not listed, but it is available as an event device in the kernel (see /proc/bus/input/devices) and in the X session (see xinput list), the device is missing from libwacom's database.
Again, we are using Wayland, and since the README assumes an X system we might run into trouble. Let's give it a shot:
$ libwacom-list-local-devices
# Device node: /dev/input/event25
[Device]
Name=Wacom Intuos BT M
ModelName=CTL-6100WL
DeviceMatch=usb:056a:0378;bluetooth:056a:0379;
Class=Bamboo
Width=9
Height=5
IntegratedIn=
Layout=intuos-m-p3.svg
Styli=0x862;
[Features]
Reversible=false
Stylus=true
Ring=false
Ring2=false
Touch=false
TouchSwitch=false
# StatusLEDs=
NumStrips=0
Buttons=4
[Buttons]
# Left=
# Right=
Top=A;B;C;D;
# Bottom=
# Touchstrip=
# Touchstrip2=
# OLEDs=
# Ring=
# Ring2=
EvdevCodes=0x110;0x111;0x115;0x116;
RingNumModes=0
Ring2NumModes=0
StripsNumModes=0
---------------------------------------------------------------
# Device node: /dev/input/event24
[Device]
Name=Wacom Intuos BT M
ModelName=CTL-6100WL
DeviceMatch=usb:056a:0378;bluetooth:056a:0379;
Class=Bamboo
Width=9
Height=5
IntegratedIn=
Layout=intuos-m-p3.svg
Styli=0x862;
[Features]
Reversible=false
Stylus=true
Ring=false
Ring2=false
Touch=false
TouchSwitch=false
# StatusLEDs=
NumStrips=0
Buttons=4
[Buttons]
# Left=
# Right=
Top=A;B;C;D;
# Bottom=
# Touchstrip=
# Touchstrip2=
# OLEDs=
# Ring=
# Ring2=
EvdevCodes=0x110;0x111;0x115;0x116;
RingNumModes=0
Ring2NumModes=0
StripsNumModes=0
---------------------------------------------------------------
Recall from above that event24
is the pen and event25
is the pad.
The driver seems to be confused as to the difference between the pad and the pen, as both devices have the exact same output;
Maybe this is due to to the fact that they share a device id, or maybe it makes things simpler in the driver.
For instance, having the pen be Width=9
and Height=5
is obviously not true, but those
are the limits of the pen pressure events that you'd get since you always would use the pen together
with the pad. I'll assume that's not a problem.
The output also states, once again, that we do have four buttons, but now they also correctly state that the buttons are on the top of the pad.
They are labeled A-D.
Since there are a listing of four numbers in EvdevCodes
, I think those are the "scan codes", so to speak,
that are sent when the buttons are pressed.
In case of confusion, let's write those down in hex and decimal:
0x110 = 272
0x111 = 273
0x115 = 277
0x116 = 278
Just cat it
Come to think of it, why don't we just cat
the right event file?
If there are any events coming through we would at least know that the hardware is recognizing that we're
using it and sending something into the driver.
Then we would have narrowed down slightly more where in the stack the problems are.
$ sudo cat /dev/input/event25
�`��`�(�`��*D �*D (�*D �y
�y
(�y
����(���)�)(�)^C⏎
That looks about right? Slightly unreadable tough;
here are the output after having pressed the buttons one at a time, piped through hexdump
with a newline
in between each event:
$ sudo cat /dev/input/event25 | hexdump
0000000 04c1 5eee 0000 0000 ffb0 0005 0000 0000
0000010 0001 0100 0001 0000 04c1 5eee 0000 0000
0000020 ffb0 0005 0000 0000 0003 0028 000f 0000
0000030 04c1 5eee 0000 0000 ffb0 0005 0000 0000
0000040 0000 0000 0000 0000 04c1 5eee 0000 0000
0000050 39ff 0008 0000 0000 0001 0100 0000 0000
0000060 04c1 5eee 0000 0000 39ff 0008 0000 0000
0000070 0003 0028 0000 0000 04c1 5eee 0000 0000
0000080 39ff 0008 0000 0000 0000 0000 0000 0000
0000090 04c4 5eee 0000 0000 4288 0004 0000 0000
00000a0 0001 0101 0001 0000 04c4 5eee 0000 0000
00000b0 4288 0004 0000 0000 0003 0028 000f 0000
00000c0 04c4 5eee 0000 0000 4288 0004 0000 0000
00000d0 0000 0000 0000 0000 04c4 5eee 0000 0000
00000e0 d49a 0007 0000 0000 0001 0101 0000 0000
00000f0 04c4 5eee 0000 0000 d49a 0007 0000 0000
0000100 0003 0028 0000 0000 04c4 5eee 0000 0000
0000110 d49a 0007 0000 0000 0000 0000 0000 0000
0000120 04c4 5eee 0000 0000 d983 000e 0000 0000
0000130 0001 0102 0001 0000 04c4 5eee 0000 0000
0000140 d983 000e 0000 0000 0003 0028 000f 0000
0000150 04c4 5eee 0000 0000 d983 000e 0000 0000
0000160 0000 0000 0000 0000 04c5 5eee 0000 0000
0000170 0a14 0003 0000 0000 0001 0102 0000 0000
0000180 04c5 5eee 0000 0000 0a14 0003 0000 0000
0000190 0003 0028 0000 0000 04c5 5eee 0000 0000
00001a0 0a14 0003 0000 0000 0000 0000 0000 0000
00001b0 04c5 5eee 0000 0000 7e2c 000b 0000 0000
00001c0 0001 0103 0001 0000 04c5 5eee 0000 0000
00001d0 7e2c 000b 0000 0000 0003 0028 000f 0000
00001e0 04c5 5eee 0000 0000 7e2c 000b 0000 0000
00001f0 0000 0000 0000 0000 04c5 5eee 0000 0000
0000200 3f1e 000f 0000 0000 0001 0103 0000 0000
0000210 04c5 5eee 0000 0000 3f1e 000f 0000 0000
0000220 0003 0028 0000 0000 04c5 5eee 0000 0000
0000230 3f1e 000f 0000 0000 0000 0000 0000 0000
Somehow, the the down presses are less data than the releases.
This might be true, but another explanation is that hexdump
is buffering up the data
so that it can output each line as 0x10
bytes.
After carefully reading the man
page of hexdump
, and with some trial and error4,
the following does the job:
$ sudo cat /dev/input/event25 | hexdump -ve "1/1 \"%02x\n\""
# A down ** **
0a 0a ee 5e 00 00 00 00 9c 82 06 00 00 00 00 00 01 00 00 01 01 00 00 00 0a 0a ee 5e 00 00 00 00 9c 82 06 00 00 00 00 00 03 00 28 00 0f 00 00 00 0a 0a ee 5e 00 00 00 00 9c 82 06 00 00 00 00 00 00 00 00 00 00 00 00 00
# A up
0b 0a ee 5e 00 00 00 00 3b fa 03 00 00 00 00 00 01 00 00 01 00 00 00 00 0b 0a ee 5e 00 00 00 00 3b fa 03 00 00 00 00 00 03 00 28 00 00 00 00 00 0b 0a ee 5e 00 00 00 00 3b fa 03 00 00 00 00 00 00 00 00 00 00 00 00 00
# B down
0c 0a ee 5e 00 00 00 00 a9 ea 03 00 00 00 00 00 01 00 01 01 01 00 00 00 0c 0a ee 5e 00 00 00 00 a9 ea 03 00 00 00 00 00 03 00 28 00 0f 00 00 00 0c 0a ee 5e 00 00 00 00 a9 ea 03 00 00 00 00 00 00 00 00 00 00 00 00 00
# B up
0c 0a ee 5e 00 00 00 00 42 0e 0f 00 00 00 00 00 01 00 01 01 00 00 00 00 0c 0a ee 5e 00 00 00 00 42 0e 0f 00 00 00 00 00 03 00 28 00 00 00 00 00 0c 0a ee 5e 00 00 00 00 42 0e 0f 00 00 00 00 00 00 00 00 00 00 00 00 00
# C down
0e 0a ee 5e 00 00 00 00 f4 ee 01 00 00 00 00 00 01 00 02 01 01 00 00 00 0e 0a ee 5e 00 00 00 00 f4 ee 01 00 00 00 00 00 03 00 28 00 0f 00 00 00 0e 0a ee 5e 00 00 00 00 f4 ee 01 00 00 00 00 00 00 00 00 00 00 00 00 00
# C up
0e 0a ee 5e 00 00 00 00 4c 76 0c 00 00 00 00 00 01 00 02 01 00 00 00 00 0e 0a ee 5e 00 00 00 00 4c 76 0c 00 00 00 00 00 03 00 28 00 00 00 00 00 0e 0a ee 5e 00 00 00 00 4c 76 0c 00 00 00 00 00 00 00 00 00 00 00 00 00
# D down
0f 0a ee 5e 00 00 00 00 ee 8f 08 00 00 00 00 00 01 00 03 01 01 00 00 00 0f 0a ee 5e 00 00 00 00 ee 8f 08 00 00 00 00 00 03 00 28 00 0f 00 00 00 0f 0a ee 5e 00 00 00 00 ee 8f 08 00 00 00 00 00 00 00 00 00 00 00 00 00
# D up
10 0a ee 5e 00 00 00 00 c2 96 04 00 00 00 00 00 01 00 03 01 00 00 00 00 10 0a ee 5e 00 00 00 00 c2 96 04 00 00 00 00 00 03 00 28 00 00 00 00 00 10 0a ee 5e 00 00 00 00 c2 96 04 00 00 00 00 00 00 00 00 00 00 00 00 00
We can even see some signs of what data is sent through here.
For instance, in the columns as marked by **
we see 00
through 03
, likely the button number,
and 01
for press and 00
for release.
In other words, there seems to be reasonable data sent from the pad that we can read from /dev/input/event25
.
Next, we need to find out on which side libwacom
is;
is that doing the mapping from whatever goes to over the wire and to what we just read,
or is that supposed to read from event25
to the events that we did not get from libinput
?
A Closer Look At That Data
kernel.org has some documentation for the Linux input subsystem, but I think it's written in such a way that it's not very helpful unless you already have a pretty good idea of what's going on. However, Section 1.5 has the following info:
You can use blocking and nonblocking reads, and also select() on the /dev/input/eventX devices, and you’ll always get a whole number of input events on a read. Their layout is:
struct input_event {
struct timeval time;
unsigned short type;
unsigned short code;
unsigned int value;
};
This doesn't seem right, since the size of input_event
is way less than the data we read above.
Unless, of course, we didn't get only one event. Since the first member is the time we can assume
that all events should start with more or less the same.
In addition, we suspect that an event is about 8 + 2 + 2 + 4 = 16
bytes long.
Or maybe struct timeval
is 16
bytes big? That seems to align much better with the data we have read:
0a 0a ee 5e 00 00 00 00 9c 82 06 00 00 00 00 00 01 00 00 01 01 00 00 00
0a 0a ee 5e 00 00 00 00 9c 82 06 00 00 00 00 00 03 00 28 00 0f 00 00 00
0a 0a ee 5e 00 00 00 00 9c 82 06 00 00 00 00 00 00 00 00 00 00 00 00 00
man 3 timeval
says that struct timeval
has two members, a time_t
and a suseconds_t
;
in addition, we're on a big-endian machine, so our struct
members should look like this5:
struct input_event {
struct timeval {
time_t tv_sec = 0x000000005eee0a0a;
suseconds_t tv_usec = 0x000000000006829c;
} // and for the other two events:
unsigned short type = 0x0001; // 0003 / 0000
unsigned short code = 0x0100; // 0028 / 0000
unsigned int value = 0x00000001; // 0000000f / 00000000
}
The time certainly looks reasonable:
$ printf "%016x\n" (date +"%s")
000000005eee1baa
The docs says that the types are defined in include/uapi/linux/input-event-codes.h
, which I found on my system in /usr/include/linux/input-event-codes.h
6.
Looking through it we can infer that the events we're reading are;
The difficulty is that how to interpret the code
or value
of an event depends on the type
of the event.
§2.2.1 is helpful here.
As far as I can tell, this is what's going on:
Type | Code | Value | Meaning |
---|---|---|---|
EV_KEY | BTN_0 | 1 | pressed |
EV_ABS | ABS_MISC | 15 | ? |
EV_SYN | SYN_REPORT | 0 | undef |
Note that in §2.2.1 they say
EV_SYN event values are undefined. Their usage is defined only by when they are sent in the evdev event stream.
Here are all of the events from earlier, but this time one per line and annotated on the right:
# A down
0a 0a ee 5e 00 00 00 00 9c 82 06 00 00 00 00 00 01 00 00 01 01 00 00 00 # KEY/BTN_0 Press
0a 0a ee 5e 00 00 00 00 9c 82 06 00 00 00 00 00 03 00 28 00 0f 00 00 00 # ABS/MISC 15
0a 0a ee 5e 00 00 00 00 9c 82 06 00 00 00 00 00 00 00 00 00 00 00 00 00 # SYN/REPORT
# A up
0b 0a ee 5e 00 00 00 00 3b fa 03 00 00 00 00 00 01 00 00 01 00 00 00 00 # KEY/BTN_0 Release
0b 0a ee 5e 00 00 00 00 3b fa 03 00 00 00 00 00 03 00 28 00 00 00 00 00 # ABS/MISC 0
0b 0a ee 5e 00 00 00 00 3b fa 03 00 00 00 00 00 00 00 00 00 00 00 00 00 # SYN/REPORT
# B down
0c 0a ee 5e 00 00 00 00 a9 ea 03 00 00 00 00 00 01 00 01 01 01 00 00 00 # KEY/BTN_1 Press
0c 0a ee 5e 00 00 00 00 a9 ea 03 00 00 00 00 00 03 00 28 00 0f 00 00 00 # ABS/MISC 15
0c 0a ee 5e 00 00 00 00 a9 ea 03 00 00 00 00 00 00 00 00 00 00 00 00 00 # SYN/REPORT
# B up
0c 0a ee 5e 00 00 00 00 42 0e 0f 00 00 00 00 00 01 00 01 01 00 00 00 00 # KEY/BTN_1 Release
0c 0a ee 5e 00 00 00 00 42 0e 0f 00 00 00 00 00 03 00 28 00 00 00 00 00 # ABS/MISC 0
0c 0a ee 5e 00 00 00 00 42 0e 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 # SYN/REPORT
# C down
0e 0a ee 5e 00 00 00 00 f4 ee 01 00 00 00 00 00 01 00 02 01 01 00 00 00 # KEY/BTN_2 Press
0e 0a ee 5e 00 00 00 00 f4 ee 01 00 00 00 00 00 03 00 28 00 0f 00 00 00 # ABS/MISC 15
0e 0a ee 5e 00 00 00 00 f4 ee 01 00 00 00 00 00 00 00 00 00 00 00 00 00 # SYN/REPORT
# C up
0e 0a ee 5e 00 00 00 00 4c 76 0c 00 00 00 00 00 01 00 02 01 00 00 00 00 # KEY/BTN_2 Release
0e 0a ee 5e 00 00 00 00 4c 76 0c 00 00 00 00 00 03 00 28 00 00 00 00 00 # ABS/MISC 0
0e 0a ee 5e 00 00 00 00 4c 76 0c 00 00 00 00 00 00 00 00 00 00 00 00 00 # SYN/REPORT
# D down
0f 0a ee 5e 00 00 00 00 ee 8f 08 00 00 00 00 00 01 00 03 01 01 00 00 00 # KEY/BTN_3 Press
0f 0a ee 5e 00 00 00 00 ee 8f 08 00 00 00 00 00 03 00 28 00 0f 00 00 00 # ABS/MISC 15
0f 0a ee 5e 00 00 00 00 ee 8f 08 00 00 00 00 00 00 00 00 00 00 00 00 00 # SYN/REPORT
# D up
10 0a ee 5e 00 00 00 00 c2 96 04 00 00 00 00 00 01 00 03 01 00 00 00 00 # KEY/BTN_3 Release
10 0a ee 5e 00 00 00 00 c2 96 04 00 00 00 00 00 03 00 28 00 00 00 00 00 # ABS/MISC 0
10 0a ee 5e 00 00 00 00 c2 96 04 00 00 00 00 00 00 00 00 00 00 00 00 00 # SYN/REPORT
In trying to find out more info about ABS_MISC
I found a wiki page on the linuxwacom/input-wacom repository named
Kernel Input Event Overview,
which explains pretty well how the wacom driver works.
They state the following:
In addition to the
BTN_TOOL_*
events for informing user land what tool the current events being sent belong to, there is aMSC_SERIAL
event that contains a serial # to aid in tracking current tool as well as aABS_MISC
which is a hard code device ID. Of these two, theMSC_SERIAL
is the most useful to user land.
So, uuh.. maybe I won't worry too much about the ABS/MISC
events.
But this is good; we have confirmed that the data we're reading from /dev/input/event25
is of the type input_event
,
and that it makes sense, more or less. From reading the wiki page it really does sound like the driver is mapping
whatever goes over the wire to the Linux input subsystem format, which is input_event
.
At this point I realize that linuxwacom
has three primary components:
input-wacom
which is the kernel driver, which presumably does the mapping just mentioned;
xf86-input-wacom
the X driver, which I suppose makes kernel driver events into X events?
and libwacom
, which really just seems to be a utility for simpler querying of state and button mapping and so on.
Going back, we can see that libwacom-list-local-devices
seems to work just fine,
which I think means that the pad is properly detected.
In addition, we know that we get "good" events to /dev/input/event25
so presumably the kernel driver also works fine.
However, libinput debug-events
did not list the button presses, so libinput
doesn't get those events, although it does get the stylus events.
Back to libinput
Next, we got back to libinput
; looking through some of the docs it seems there's another command,
libinput record
.
According to man 1 libinput-record
,
The libinput record tool records kernel events from a device and prints them in a format that can later be replayed with the libinput replay(1) tool.
Running it, and selecting our device, actually shows that the buttons are detected:
$ sudo libinput record
Available devices:
/dev/input/event0: Power Button
/dev/input/event1: Power Button
/dev/input/event2: PC Speaker
/dev/input/event3: HDA ATI HDMI HDMI/DP,pcm=3
/dev/input/event4: HDA ATI HDMI HDMI/DP,pcm=7
/dev/input/event5: HDA ATI HDMI HDMI/DP,pcm=8
/dev/input/event6: HDA ATI HDMI HDMI/DP,pcm=9
/dev/input/event7: HDA ATI HDMI HDMI/DP,pcm=10
/dev/input/event8: HDA ATI HDMI HDMI/DP,pcm=11
/dev/input/event9: HD-Audio Generic Front Mic
/dev/input/event10: HD-Audio Generic Rear Mic
/dev/input/event11: HD-Audio Generic Line
/dev/input/event12: HD-Audio Generic Line Out Front
/dev/input/event13: HD-Audio Generic Line Out Surround
/dev/input/event14: HD-Audio Generic Line Out CLFE
/dev/input/event15: HD-Audio Generic Line Out Side
/dev/input/event16: HD-Audio Generic Front Headphone
/dev/input/event17: ZSA Ergodox EZ
/dev/input/event18: ZSA Ergodox EZ Mouse
/dev/input/event19: ZSA Ergodox EZ System Control
/dev/input/event20: Logitech Performance MX
/dev/input/event21: Kingsis Peripherals Evoluent VerticalMouse 4
/dev/input/event22: ZSA Ergodox EZ Consumer Control
/dev/input/event23: ZSA Ergodox EZ Keyboard
/dev/input/event24: Wacom Intuos BT M Pen
/dev/input/event25: Wacom Intuos BT M Pad
/dev/input/event26: HD Pro Webcam C920
Select the device event number: 25
Recording to 'stdout'.
version: 1
ndevices: 1
libinput:
version: "1.15.5"
git: "unknown"
system:
kernel: "5.7.2-arch1-1"
dmi: "dmi:bvnAmericanMegatrendsInc.:bvr3.D0:bd07/11/2018:svnMicro-StarInternationalCo.,Ltd.:pnMS-7A33:pvr2.0:rvnMSI:rnX370SLIPLUS(MS-7A33):rvr2.0:cvnMicro-StarInternationalCo.,Ltd.:ct3:cvr2.0:"
devices:
- node: /dev/input/event25
evdev:
# Name: Wacom Intuos BT M Pad
# ID: bus 0x3 vendor 0x56a product 0x378 version 0x110
# Size in mm: unknown, missing resolution
# Supported Events:
# Event type 0 (EV_SYN)
# Event type 1 (EV_KEY)
# Event code 256 (BTN_0)
# Event code 257 (BTN_1)
# Event code 258 (BTN_2)
# Event code 259 (BTN_3)
# Event code 331 (BTN_STYLUS)
# Event type 3 (EV_ABS)
# Event code 0 (ABS_X)
# Value 0
# Min 0
# Max 1
# Fuzz 0
# Flat 0
# Resolution 0
# Event code 1 (ABS_Y)
# Value 0
# Min 0
# Max 1
# Fuzz 0
# Flat 0
# Resolution 0
# Event code 40 (ABS_MISC)
# Value 0
# Min 0
# Max 0
# Fuzz 0
# Flat 0
# Resolution 0
# Properties:
name: "Wacom Intuos BT M Pad"
id: [3, 1386, 888, 272]
codes:
0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] # EV_SYN
1: [256, 257, 258, 259, 331] # EV_KEY
3: [0, 1, 40] # EV_ABS
absinfo:
0: [0, 1, 0, 0, 0]
1: [0, 1, 0, 0, 0]
40: [0, 0, 0, 0, 0]
properties: []
hid: [6, 13, 255, 9, 1, 161, 1, 133, 16, 9, 32, 53, 0, 69, 0, 21, 0, 37, 1, 161, 0, 9, 66, 9, 68, 9, 90, 37, 1, 117, 1, 149, 3, 129, 2, 149, 2, 129, 3, 9, 50, 9, 54, 149, 2, 129, 2, 149, 1, 129, 3, 10, 48, 1, 101, 17, 85, 13, 71, 96, 84, 0, 0, 39, 96, 84, 0, 0, 117, 24, 149, 1, 129, 2, 10, 49, 1, 71, 188, 52, 0, 0, 39, 188, 52, 0, 0, 129, 2, 9, 48, 85, 0, 101, 0, 38, 255, 15, 117, 16, 129, 2, 117, 8, 149, 6, 129, 3, 10, 50, 1, 37, 63, 117, 8, 149, 1, 129, 2, 9, 91, 9, 92, 23, 0, 0, 0, 128, 39, 255, 255, 255, 127, 117, 32, 149, 2, 129, 2, 9, 119, 21, 0, 38, 255, 15, 117, 16, 149, 1, 129, 2, 192, 133, 17, 101, 0, 85, 0, 53, 0, 69, 0, 9, 57, 161, 0, 10, 16, 9, 10, 17, 9, 10, 18, 9, 10, 19, 9, 21, 0, 37, 1, 117, 1, 149, 4, 129, 2, 149, 4, 129, 3, 117, 8, 149, 7, 129, 3, 192, 133, 19, 101, 0, 85, 0, 53, 0, 69, 0, 10, 19, 16, 161, 0, 10, 59, 4, 21, 0, 37, 100, 117, 7, 149, 1, 129, 2, 10, 4, 4, 37, 1, 117, 1, 129, 2, 9, 0, 38, 255, 0, 117, 8, 129, 2, 117, 8, 149, 6, 129, 3, 192, 9, 14, 161, 2, 133, 2, 10, 2, 16, 21, 2, 37, 2, 117, 8, 149, 1, 177, 2, 133, 3, 10, 3, 16, 21, 0, 38, 255, 0, 149, 1, 177, 2, 133, 4, 10, 4, 16, 21, 1, 37, 1, 149, 1, 177, 2, 133, 7, 10, 9, 16, 21, 0, 38, 255, 0, 149, 1, 177, 2, 177, 3, 10, 7, 16, 9, 0, 39, 255, 255, 0, 0, 117, 16, 149, 2, 177, 2, 117, 8, 149, 9, 177, 3, 133, 12, 10, 48, 13, 10, 49, 13, 10, 50, 13, 10, 51, 13, 101, 17, 85, 13, 53, 0, 70, 200, 0, 21, 0, 38, 144, 1, 117, 16, 149, 4, 177, 2, 133, 13, 10, 13, 16, 101, 0, 85, 0, 69, 0, 37, 1, 117, 8, 149, 1, 177, 2, 133, 20, 10, 20, 16, 38, 255, 0, 149, 13, 177, 2, 133, 204, 10, 204, 16, 149, 2, 177, 2, 133, 49, 10, 49, 16, 37, 100, 149, 3, 177, 2, 149, 2, 177, 3, 192, 10, 172, 16, 161, 2, 21, 0, 38, 255, 0, 117, 8, 133, 172, 9, 0, 150, 191, 0, 129, 2, 133, 21, 9, 0, 149, 14, 177, 2, 133, 51, 9, 0, 149, 18, 177, 2, 133, 68, 9, 0, 149, 4, 177, 2, 133, 69, 9, 0, 149, 32, 177, 2, 133, 96, 9, 0, 149, 63, 177, 2, 133, 97, 9, 0, 149, 62, 177, 2, 133, 98, 9, 0, 149, 62, 177, 2, 133, 101, 9, 0, 149, 4, 177, 2, 133, 102, 9, 0, 149, 4, 177, 2, 133, 103, 9, 0, 149, 4, 177, 2, 133, 104, 9, 0, 149, 17, 177, 2, 133, 111, 9, 0, 149, 62, 177, 2, 133, 205, 9, 0, 149, 2, 177, 2, 133, 22, 9, 0, 149, 14, 177, 2, 133, 53, 9, 0, 149, 10, 177, 2, 192, 133, 208, 9, 1, 150, 8, 0, 177, 2, 133, 209, 9, 1, 150, 4, 1, 177, 2, 133, 210, 9, 1, 150, 4, 1, 177, 2, 133, 211, 9, 1, 150, 4, 0, 177, 2, 133, 212, 9, 1, 150, 4, 0, 177, 2, 133, 213, 9, 1, 150, 4, 0, 177, 2, 133, 214, 9, 1, 150, 4, 0, 177, 2, 133, 215, 9, 1, 150, 8, 0, 177, 2, 133, 216, 9, 1, 150, 12, 0, 177, 2, 133, 217, 9, 1, 150, 0, 5, 177, 2, 133, 218, 9, 1, 150, 4, 2, 177, 2, 133, 219, 9, 1, 150, 6, 0, 177, 2, 133, 220, 9, 1, 150, 2, 0, 177, 2, 133, 221, 9, 1, 150, 4, 0, 177, 2, 133, 222, 9, 1, 150, 4, 0, 177, 2, 133, 223, 9, 1, 150, 34, 0, 177, 2, 133, 224, 9, 1, 150, 1, 0, 177, 2, 133, 225, 9, 1, 150, 2, 0, 177, 2, 133, 226, 9, 1, 150, 2, 0, 177, 2, 133, 227, 9, 1, 150, 2, 0, 177, 2, 133, 228, 9, 1, 150, 255, 1, 177, 2, 192 ]
udev:
properties:
- ID_INPUT=1
- ID_INPUT_TABLET=1
- ID_INPUT_TABLET_PAD=1
- LIBINPUT_DEVICE_GROUP=3/56a/378:usb-0000:29:00.3-2
quirks:
events:
- evdev:
- [ 0, 0, 1, 256, 1] # EV_KEY / BTN_0 1
- evdev:
- [ 0, 0, 3, 40, 15] # EV_ABS / ABS_MISC 15 (+15)
- [ 0, 0, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +0ms
- evdev:
- [ 0, 214000, 1, 256, 0] # EV_KEY / BTN_0 0
- [ 0, 214000, 3, 40, 0] # EV_ABS / ABS_MISC 0 (-15)
- [ 0, 214000, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +214ms
These are exactly the same events as the ones we reverse engineered above, which is a good sign;
libinput
gets the same events as we are, but it seems to decide that they aren't worth sending further.
Maybe if we dig a bit into libinput
we can find out how devices and events are treated, set a breakpoint
somewhere when libinput
is reading the button press event and see what happens.
The libinput docs
has an overview over libinput
.
evdev_device_create
calls evdev_configure_device
with the device
as a parameter;
device->devname
contains the name of the device and will contain Wacom
for the devices we're interested in.
(gdb) break evdev_configure_device if ((int) strstr(device->devname, "acom"))
(gdb) c
Continuing.
Breakpoint 3, evdev_configure_device (device=0x55555566f000) at ../src/evdev.c:1775
1775 struct libevdev *evdev = device->evdev;
(gdb) p device->devname
$7 = 0x5555556365f0 "Wacom Intuos BT M Pad"
(gdb)
We're stepping through the function to see whether anything strange is happening. We would like it to be recognized as a tablet pad so that the correct dispatch methods are set up.
(gdb) p udev_tags
$8 = (EVDEV_UDEV_TAG_INPUT | EVDEV_UDEV_TAG_TABLET | EVDEV_UDEV_TAG_TABLET_PAD)
So far so good. Continuing down it does correctly go into the if
on line 1852,
and calls evdev_tablet_pad_create
.
The pad is thus identified as a tablet pad.
Now we need to find out how events are read in libinput
.
Apparently, libinput
uses libevdev
which is a wrapper library for evdev devices.
So instead of reading the files in /dev/input
like we did above, we can
get the events from handles that we get through libevdev
.
The function libevdev_next_event
is called in four places in src/evdev.c
, but the most
promising one is in evdev_device_dispatch
.
We can set a conditional breakpoint here for when our event is coming through7:
(gdb) break evdev.c:1061 if (ev->type == 1 && ev->code == 0x100)
No source file named evdev.c.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (evdev.c:1061 if (ev->type == 1 && ev->code == 0x100)) pending.
(gdb) run debug-events
Starting program: /home/mht/src/libinput/build/libinput debug-events
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
process 48478 is executing new program: /home/mht/src/libinput/build/libinput-debug-events
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
-event1 DEVICE_ADDED Power Button seat0 default group1 cap:k
-event0 DEVICE_ADDED Power Button seat0 default group2 cap:k
-event20 DEVICE_ADDED Logitech Performance MX seat0 default group3 cap:p left scroll-nat scroll-button
-event21 DEVICE_ADDED Kingsis Peripherals Evoluent VerticalMouse 4 seat0 default group4 cap:p left scroll-nat scroll-button
-event26 DEVICE_ADDED HD Pro Webcam C920 seat0 default group5 cap:k
-event24 DEVICE_ADDED Wacom Intuos BT M Pen seat0 default group6 cap:T size 216x135mm
-event25 DEVICE_ADDED Wacom Intuos BT M Pad seat0 default group6 cap:P buttons:4 strips:0 rings:0 mode groups:1
-event17 DEVICE_ADDED ZSA Ergodox EZ seat0 default group7 cap:k
-event18 DEVICE_ADDED ZSA Ergodox EZ Mouse seat0 default group7 cap:p left scroll-nat scroll-button
-event19 DEVICE_ADDED ZSA Ergodox EZ System Control seat0 default group7 cap:k
-event22 DEVICE_ADDED ZSA Ergodox EZ Consumer Control seat0 default group7 cap:kp scroll-nat
-event23 DEVICE_ADDED ZSA Ergodox EZ Keyboard seat0 default group7 cap:k
Breakpoint 1, evdev_device_dispatch (data=0x5555556718e0) at ../src/evdev.c:1061
1061 if (rc == LIBEVDEV_READ_STATUS_SYNC) {
(gdb) n
1075 } else if (rc == LIBEVDEV_READ_STATUS_SUCCESS) {
(gdb) n
1076 if (!once) {
(gdb) n
1077 evdev_note_time_delay(device, &ev);
(gdb) n
1078 once = true;
(gdb) n
1080 evdev_device_dispatch_one(device, &ev);
(gdb) s
evdev_device_dispatch_one (device=0x5555556718e0, ev=0x7fffffffdf60) at ../src/evdev.c:989
989 {
(gdb) n
990 if (!device->mtdev) {
(gdb) n
991 evdev_process_event(device, ev);
(gdb) s
evdev_process_event (device=0x5555556718e0, e=0x7fffffffdf60) at ../src/evdev.c:974
974 struct evdev_dispatch *dispatch = device->dispatch;
(gdb) n
975 uint64_t time = input_event_time(e);
(gdb) n
981 libinput_timer_flush(evdev_libinput_context(device), time);
(gdb) n
983 dispatch->interface->process(dispatch, device, e, time);
(gdb) s
pad_process (dispatch=0x555555674c00, device=0x5555556718e0, e=0x7fffffffdf60, time=33217800469) at ../src/evdev-tablet-pad.c:483
483 struct pad_dispatch *pad = pad_dispatch(dispatch);
(gdb) bt
#0 pad_process (dispatch=0x555555674c00, device=0x5555556718e0, e=0x7fffffffdf60, time=33217800469)
at ../src/evdev-tablet-pad.c:483
#1 0x00007ffff7f7bd06 in evdev_process_event (device=0x5555556718e0, e=0x7fffffffdf60) at ../src/evdev.c:983
#2 0x00007ffff7f7bd4b in evdev_device_dispatch_one (device=0x5555556718e0, ev=0x7fffffffdf60) at ../src/evdev.c:991
#3 0x00007ffff7f7bfef in evdev_device_dispatch (data=0x5555556718e0) at ../src/evdev.c:1080
#4 0x00007ffff7f74f06 in libinput_dispatch (libinput=0x5555555773b0) at ../src/libinput.c:2125
#5 0x000055555555d1e0 in handle_and_print_events (li=0x5555555773b0) at ../tools/libinput-debug-events.c:827
#6 0x000055555555d6df in mainloop (li=0x5555555773b0) at ../tools/libinput-debug-events.c:953
#7 0x000055555555db1e in main (argc=1, argv=0x7fffffffe588) at ../tools/libinput-debug-events.c:1091
(gdb) list
478 pad_process(struct evdev_dispatch *dispatch,
479 struct evdev_device *device,
480 struct input_event *e,
481 uint64_t time)
482 {
483 struct pad_dispatch *pad = pad_dispatch(dispatch);
484
485 switch (e->type) {
486 case EV_ABS:
487 pad_process_absolute(pad, device, e, time);
(gdb) n
485 switch (e->type) {
(gdb) n
490 pad_process_key(pad, device, e, time);
(gdb) s
pad_process_key (pad=0x555555674c00, device=0x5555556718e0, e=0x7fffffffdf60, time=33217800469) at ../src/evdev-tablet-pad.c:332
332 uint32_t button = e->code;
(gdb) p e
$1 = (struct input_event *) 0x7fffffffdf60
(gdb) p *e
$2 = {time = {tv_sec = 33217, tv_usec = 800469}, type = 1, code = 256, value = 1}
(gdb) n
333 uint32_t is_press = e->value != 0;
(gdb) n
336 if (e->value == 2)
(gdb) n
339 pad_button_set_down(pad, button, is_press);
(gdb) s
pad_button_set_down (pad=0x555555674c00, button=256, is_down=true) at ../src/evdev-tablet-pad.c:88
88 struct button_state *state = &pad->button_state;
(gdb) list
83 static inline void
84 pad_button_set_down(struct pad_dispatch *pad,
85 uint32_t button,
86 bool is_down)
87 {
88 struct button_state *state = &pad->button_state;
89
90 if (is_down) {
91 set_bit(state->bits, button);
92 pad_set_status(pad, PAD_BUTTONS_PRESSED);
(gdb) n
90 if (is_down) {
(gdb) n
91 set_bit(state->bits, button);
(gdb) n
92 pad_set_status(pad, PAD_BUTTONS_PRESSED);
(gdb) n
97 }
At this point it's apparent that the event is going through the code successfully.
That is, the pad is recognized as a pad, and events from libevdev
are correctly setting the state of
the evdev_device
in libinput
. But, we're not getting any events, so where are events in libinput
made?
Events in libinput
In order to find out how events in libinput
works we can take a look in the only place so far that we know we've seen them:
in libinput-debug-events
8.
libinput-debug-events.c
has a main
function that does some argument parsing and initialization, and then calls mainloop
,
which contains a do while
loop, which poll
s a (the?) fd from libinput
, and calls handle_and_print_events
.
This function gets all events with libinput_get_event
, and has a giant switch
to dispatch how the event should be printed.
But, there is no default
branch so, while unlikely, we might already hit a dead end.
We jump back into gdb
to test this (running with permissions; otherwise we get nothing!).
Oh, that's right, my keyboard is also sending events, and so instead of c
ing though the initialization events until I get to
press the button on the pad and see what happens, I'm getting swamped with events for me pressing c
9!
Okay; we'll just add a default
case to the switch
, put a printf
there, and break on it.
But oh, there's nothing coming through.
Okay, so let's see where libinput_get_event
get its events.
It's here,
from the circular buffer libinput->events
.
Let's see where this is written to:
$ rg "events\[.*\]\s*=" src/
src/libinput.c
2971: events[libinput->events_in] = event;
src/evdev-tablet.c
2000: struct input_event events[2] = {
Oh, that's five lines over the function we just looked at.
The magical function in question is libinput_post_event
, and so, presumably,
all events we're getting from libevdev
should end up being sent to post_event
,
and our precious button click isn't.
This function is also sparingly called:
$ rg "libinput_post_event" src/
src/libinput.c
335:libinput_post_event(struct libinput *libinput,
2217: libinput_post_event(libinput, event);
2244: libinput_post_event(device->seat->libinput, event);
2923:libinput_post_event(struct libinput *libinput,
The first occurrence is the prototype, the second is in post_base_event
,
the third is in post_device_event
which sounds promising, and the fourth is the function itself.
The problem is that both of these are static
, and so they have quite a few callers in the file.
We want to get closer to where the mapping from libevdev
events to struct libinput_event
s are,
so maybe it makes sense to go hunting for where the libinput_event
s are initialized.
There's even a struct libinput_event_tablet_pad
.
Looking further, we find the tablet_pad_notify_button
function which creates a
libinput_event_tablet_pad
, and sends to to post_device_event
.
This is probably where the button click should en up.
$ rg "tablet_pad_notify_button" src/
src/libinput.c
2700:tablet_pad_notify_button(struct libinput_device *device,
src/libinput-private.h
662:tablet_pad_notify_button(struct libinput_device *device,
src/evdev-tablet-pad.c
394: tablet_pad_notify_button(base,
The call in evdev-tablet-pad.c
comes from the function pad_notify_button_mask
,
which we'll breakpoint.
/h/m/s/l/build$ sudo gdb ./libinput-debug-events
[sudo] password for mht:
GNU gdb (GDB) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./libinput-debug-events...
(gdb) break pad_notify_button_mask
Function "pad_notify_button_mask" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (pad_notify_button_mask) pending.
(gdb) run
Starting program: /home/mht/src/libinput/build/libinput-debug-events
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
-event1 DEVICE_ADDED Power Button seat0 default group1 cap:k
-event0 DEVICE_ADDED Power Button seat0 default group2 cap:k
-event20 DEVICE_ADDED Logitech Performance MX seat0 default group3 cap:p left scroll-nat scroll-button
-event21 DEVICE_ADDED Kingsis Peripherals Evoluent VerticalMouse 4 seat0 default group4 cap:p left scroll-nat scroll-button
-event26 DEVICE_ADDED HD Pro Webcam C920 seat0 default group5 cap:k
-event24 DEVICE_ADDED Wacom Intuos BT M Pen seat0 default group6 cap:T size 216x135mm
-event25 DEVICE_ADDED Wacom Intuos BT M Pad seat0 default group6 cap:P buttons:4 strips:0 rings:0 mode groups:1
-event17 DEVICE_ADDED ZSA Ergodox EZ seat0 default group7 cap:k
-event18 DEVICE_ADDED ZSA Ergodox EZ Mouse seat0 default group7 cap:p left scroll-nat scroll-button
-event19 DEVICE_ADDED ZSA Ergodox EZ System Control seat0 default group7 cap:k
-event22 DEVICE_ADDED ZSA Ergodox EZ Consumer Control seat0 default group7 cap:kp scroll-nat
-event23 DEVICE_ADDED ZSA Ergodox EZ Keyboard seat0 default group7 cap:k
Breakpoint 1, pad_notify_button_mask (pad=0x555555672b30, device=0x55555566f400, time=42251078117, buttons=0x7fffffffddb0,
state=LIBINPUT_BUTTON_STATE_PRESSED) at ../src/evdev-tablet-pad.c:365
365 struct libinput_device *base = &device->base;
And it fires! We're now here,
and we would like to get to 394
, or 402
, which leads to tablet_pad_notify_key
, which seems to be basically the same but different.
We can set breakpoints and run, just in case we do end up in either:
(gdb) break 394
Breakpoint 2 at 0x7ffff7fa3b13: file ../src/evdev-tablet-pad.c, line 394.
(gdb) break 402
Breakpoint 3 at 0x7ffff7fa3b49: file ../src/evdev-tablet-pad.c, line 402.
(gdb) c
Continuing.
Breakpoint 1, pad_notify_button_mask (pad=0x555555672b30, device=0x55555566f400, time=42251378122, buttons=0x7fffffffddb0,
state=LIBINPUT_BUTTON_STATE_RELEASED) at ../src/evdev-tablet-pad.c:365
365 struct libinput_device *base = &device->base;
(gdb) c
Continuing.
but we don't. We just get the second event, which is the key release. This is good, because we know exactly where our event gets lost.
The Last Missing Piece?
Now it's time to make sense of what's going on in that for
loop.
A quick gdb
p
of buttons->bits
shows that they're mostly 0
,
so we'll put another breakpoint on line 378
, just inside the while
loop, which we also hit.
Here are the local variables at that time:
(gdb) info locals
enabled = 21845
map = {value = 1432824624}
buttons_slice = 1 '\001'
base = 0x55555566f400
group = 0x555555672bd8
code = 256
i = 32
Note that enabled
and map
are garbage values so far.
Stepping down to the first if
changes things a little:
(gdb) info locals
enabled = 1
map = {value = 1432824624}
buttons_slice = 0 '\000'
base = 0x55555566f400
group = 0x555555672bd8
code = 257
i = 32
which means we're enabled. Good. Then we step past the map
assignment
(gdb) p map
$4 = {value = 4294967295}
Now, since we know that we didn't get to either tablet_pad_notify
function, or the abort
call,
map_is_unmapped
will be true
, which it is:
(gdb) n
387 continue;
How does one know whether a map is unmapped? Well,
#define map_is_unmapped(x_) ((x_).value == (uint32_t)-1)
and (uint32_t) -1 == 4294967295
, which means that we need to rewind, and look at the line
map = pad->button_map[code - 1];
Looking further at what's in this map gives us a very important clue:
(gdb) p pad->button_map
$7 = {{value = 4294967295} <repeats 272 times>, {value = 0}, {value = 1}, {value = 4294967295}, {value = 4294967295}, {
value = 4294967295}, {value = 2}, {value = 3}, {value = 4294967295} <repeats 489 times>}
(gdb) p pad->button_map[272]
$8 = {value = 0}
(gdb) p pad->button_map[273]
$9 = {value = 1}
(gdb) p pad->button_map[277]
$10 = {value = 2}
(gdb) p pad->button_map[278]
$11 = {value = 3}
These are exactly10 the numbers that we saw in the output from libwacom-list-local-devices
, and
that we bothered translating from hex to decimal!
So the map is here, but we're skipping the iteration with the button click because we're mistaken in which index to look at.
At this point, I really hoped this was a off-by-one thing and that code - 1
was 271
. But,
(gdb) p code
$19 = 257
This is when clicking the first button, and
clicking the second yields code == 258
and so on.
In other words, it looks like we're off by 16 bits.
Let's get the overview:
buttons->bits
is a byte array of 96 bytes, and we're looking at which bits are set.
To do this, we look at each byte (this is the for
loop), and look at each bit in that byte until buttons_slice
, the current byte, is 0
(this is the while
loop).
Our problem is that code
, which is the bit offset in the whole byte array, is off by 16, i.e. two bytes.
In other words, we need to find out where buttons->bits
are set.
For at least one caller of the function, pad_notify_buttons
, the buttons are set in pad_get_buttons_{pressed,released}
.
Looking at the stack trace (with bt
in gdb
) we see this is indeed the place where we come from.
But the logic there is very simple, and leaves no room for errors such as this.
In addition, pad->button_state
has the same error:
(gdb) p pad->button_state
$16 = {bits = '\000' <repeats 32 times>, "\001", '\000' <repeats 62 times>}
We know this is wrong, since we are supposed to end up at 272
.
Well, according to libwacom
anyways.
Back to libwacom
At this point I'm getting suspicious. How certain are we really that the mapping isn't set up wrong?
After all, in the evdev
events we read out from /dev/input/event25
was 0x0100 == BTN_0
, and not
272 == 0x110 == BTN_LEFT
, which I think is strangely well fitting for out problem.
This would also make sense with libinput
, since it presumably queries either the pad itself or
libwacom
to get the mapping, but there's a mismatch between the mapping and what's really being sent.
Let's push our current bug hunt onto our mental stack, and try to look at this map instead.
Okay, so where does libwacom-list-local-devices
get those numbers from?
tools/list-local-devices.c
contains a call to libwacom_print_device_description()
in libwacom.c
, which again calls
print_buttons_for_device
, which again calls print_button_evdev_codes
, which calls
libwacom_get_button_evdev_code
.
This function basically indexes into device->button_codes
, which we now assume are wrong.
The button codes are set in this file,
but by simple inspection it's not clear what's wrong, so we clone the repository, and build the tool ourselves
with the build instructions from the wiki.
We compile, start up gdb
, set the breakpoints, but uh oh, SIGSEGV
.
I revert to the libwacom-1.3
tag, and now we don't segfault any more, but we get
Failed to initialize device database
which we solve by passing --database ../data
when running. All is well, and the evdev
codes are still 0x110
and counting.
We run it in gdb:
(gdb) set args --database ../data
(gdb) break set_button_codes_from_heuristics if (device->model_name && ((int) strcmp(device->model_name, "CTL-6100WL")) == 0)
Breakpoint 5 at 0x7ffff7fc215e: file ../libwacom/libwacom-database.c, line 418.
(gdb) break set_button_codes_from_string if (device->model_name && ((int) strcmp(device->model_name, "CTL-6100WL")) == 0)
Breakpoint 6 at 0x7ffff7fc2024: file ../libwacom/libwacom-database.c, line 391.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/mht/src/libwacom/builddir/libwacom-list-local-devices --database ../data
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Breakpoint 5, set_button_codes_from_heuristics (device=0x5555555acdd0) at ../libwacom/libwacom-database.c:418
418 for (i = 0; i < device->num_buttons; i++) {
(gdb) p *device
$16 = {name = 0x5555555a30c0 "Wacom Intuos BT M", model_name = 0x5555555acc90 "CTL-6100WL", width = 9, height = 5, match = 0,
matches = 0x5555555acbb0, nmatches = 2, paired = 0x0, cls = WCLASS_BAMBOO, num_strips = 0, features = 1, integration_flags = 0,
strips_num_modes = 0, ring_num_modes = 0, ring2_num_modes = 0, num_styli = 1, supported_styli = 0x5555555abbe0, num_buttons = 4,
buttons = 0x5555555ad100, button_codes = 0x5555555ad090, num_leds = 0, status_leds = 0x0,
layout = 0x555555586690 "../data/layouts/intuos-m-p3.svg", refcnt = 1}
(gdb) list
413
414 static inline void
415 set_button_codes_from_heuristics(WacomDevice *device)
416 {
417 gint i;
418 for (i = 0; i < device->num_buttons; i++) {
419 if (device->cls == WCLASS_BAMBOO ||
420 device->cls == WCLASS_GRAPHIRE) {
421 switch (i) {
422 case 0:
(gdb)
So we're in set_button_codes_from_heuristics
, and since our device class is BAMBOO
, although I don't know why that is,
we default to BTN_LEFT
as the first button, which is 0x110
.
The Fix
I'm not really sure what the Class
field does in this config, apart from heuristically setting key codes, but the fix that made it
all word was simple: set the class to something else.
I changed it on my system (the file was in /usr/share/libwacom/intuos-m-p3-wl.tablet
), and submitted a PR upstream.
All in all, this adventure took my entire Saturday, and the fix was one line,
but I'm finally getting events when I'm pressing the buttons.
Now, how do I make these buttons to anything useful?
Thanks for reading.
Footnotes
-
Never mind that XournalPP doesn't have good (or decent, or any?) support for key rebinding. ↩
-
Interestingly, we don't even have to touch the pad itself; it seems to be sufficient for the tip of the pen to be pushed in for the action to be interpreted as drawing. ↩ ↩2
-
This is supported by the output of
debug-events
, which humorously states that the size of the pen is 216x135mm. ↩ -
two things were confusing: the fact that in the format string you need a space in between the byte count and the format, which was not explicitly stated, and that "squeezing" is by default on, which completely messes up the output if you are defining your own format. ↩
-
It would probably be easier to just write a small program and cast a pointer to an array with the data we read to the struct we suspect. ↩
-
I guess
uapi
is for user space, and that the directory is superfluous when you're not doing kernel dev? ↩ -
This represents most of my workflow in
gdb
: set breakpoints,n
ors
down wherever,list
unless I have the source code right by, andp
expressions; sometimes I'll alsopt
for when I don't know the types of things. It's... not great? But it's alright. I would like to have better integration in my text editor, that is, I don't really want to leave my text editor when debugging, since mentally I'm doing the same in both programs, but I haven't actually bothered seeing what's out there. My experience from trying outgdb
integration invim
was pretty bad, and if it doesn't work well invim
, I don't see how semi-obscure editors stand a chance. ↩ -
At least this is a place where we know that some of the
libevdev
events are coming through and some are not. ↩ -
I'm sure there's a way of breaking conditionally based on the event type, and I browsed through the types a little bit with
gdb
, but couldn't find anything that seemed useful. When the alternative, adding adefault
branch to aswitch
in a codebase I already hadclone
d and build, was so simple, it makes sense to do, despite not really being what I wanted to do. ↩ -
Had this been in a textbook I would think "yeah sure, that's reeeaaallly convenient how that minor thing we did way back when turned out to be useful.", but I promise, I did not go back and added the conversion after the fact! ↩
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License