Recently, I got a rotary encoder. You might have seen one of these on your favourite microblogging platform, showcased by someone doing something cool looking with it. I mean, at least some of the use cases look kind of useful: media controls are an obvious case, undo/redo or scrolling as well. Probably you can think of many more if the idea of such a device sounds appealing to you.
Use evsieve
.
It is feature-rich and well documented and can be used even for rather complex mappings.
It is pretty much a large knob. You can turn it both left and right and you can press it like a big key. So in theory there should be three keys to configure. I wondered whether there might be a limit to the rotation in each direction, potentially adding two more events.
What I want to achieve with this device is the following:
As a long-time user of Linux, I have seen quite some ups and downs with hardware, leading to generally low expectations when connecting a new device to my machine. To my pleasant surprise, the rotary encoder worked out of the box. It acted as a volume control with the large button muting the sound. Given the device looks a lot like a large volume knob, this seems like a very sane default. I like sane defaults.
Of course using the device solely as a large volume knob is not where I want to
stop.
Trying to figure out is there a straightforward way to configure the device
using Gnome’s control centre turned out to be futile.
The Gnome desktop seems to assume that there is only ever one keyboard and one
mouse present.
At least the device did not show up in either of these configuration screens and
there is no way to select a device to configure.
Trying my luck with setting up keyboard shortcuts did not turn out to be
worthwhile either, because while I could map the events triggered by the rotary
encoder to commands, this interfered with the media keys on my keyboard.
Of course, if I remap VOLUME_DOWN
to UNDO
then all VOLUME_DOWN
s will be
UNDO
s.
So, it was time to dig a bit deeper.
I have to admit, though that the last time I configured specific keys to do
specific things on a Linux system was ca. 2009 when configuring media keys on a
mediocre laptop.
In 2009, things were probably a bit different than today, but nonetheless my
experience from 2009 (yup, that was 15 years ago) at least provided me with some
pointers.
The basic idea back then was that keys have codes which are mapped to all sorts
of things like letters and events (such as VOLUME_DOWN
).
These things are not set in stone, but you can tell your system to do different
things than the defaults.
Great.
Which leads us to xev
.
xev
is an utility of the X window system allowing you to get information
about inputs.
Pressing a key it provides you with something like this:
KeyPress event, serial 37, synthetic NO, window 0x2c00001,
root 0x1d3, subw 0x2c00002, time 9199222, (37,31), root:(37,933),
state 0x0, keycode 111 (keysym 0xff52, Up), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False
The interesting things here are mainly the keycode and the things in the
parentheses after the keycode, where you can see that keycode 111 corresponds to
the up
key.
However, in case of media keys things might look a bit less useful.
KeymapNotify event, serial 38, synthetic NO, window 0x0,
keys: 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
This does not tell us too many useful things.
Now, the approach of solving this with something like .Xmodmap
does not seem
too promising.
On the one hand we can not get useful events for these keys, on the other hand
re-assigning VOLUME_UP
to something else will most certainly cause the same
issues as above.
We need something else.
evtest
Querying my preferred search engine with the right words pointed me towards using
evtest
to see which events are triggered and udev
to assign rules to what
these events should do.
Running evtest
as root provides you with a long list of input devices, in my
case a total of 23.
The list dose include audio devices, keyboards and four entries for the rotary
encoder.
Finding the correct entry for the rotary encoder took four tries, but when asked
to read the correct device, evtest
does provide much saner output than xev
does (at least for my purposes).
Event: time 1713014408.647600, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e2
Event: time 1713014408.647600, type 1 (EV_KEY), code 113 (KEY_MUTE), value 1
Event: time 1713014408.647600, -------------- SYN_REPORT ------------
Event: time 1713014408.954602, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e2
Event: time 1713014408.954602, type 1 (EV_KEY), code 113 (KEY_MUTE), value 0
Event: time 1713014408.954602, -------------- SYN_REPORT ------------
Above the logged events evtest
also provides a list of supported events, like
so:
Event code 1 (KEY_ESC)
Event code 28 (KEY_ENTER)
Event code 74 (KEY_KPMINUS)
Event code 78 (KEY_KPPLUS)
Event code 103 (KEY_UP)
Event code 105 (KEY_LEFT)
Event code 106 (KEY_RIGHT)
Event code 108 (KEY_DOWN)
Of interest here are the actual event names, as well as the values in the
output of evtest
, which identify the key.
This information can then be used to craft udev
rules.
We need one more thing, though.
This is an unique identifier for the device, which we can find looking at
/sys/class/input/eventXX/device/modalias
, where XX
is the number of the
device as reported by evtest
.
Putting everything together I created a file in /etc/udev/hwdb.d
called
10-bnr1.hwdb
with the content:
# Configure rotary encoder BNR-1
evdev:input:b0003v4249p4241e0111*
KEYBOARD_KEY_c00ea=key_down
KEYBOARD_KEY_c00e9=key_up
The spaces before the rules are really important, so is the lower-case spelling of the event names. Then calling
sudo systemd-hwdb reload
sudo udevadm trigger
turned the rotary encoder into a fancy scroll wheel. Two problems quickly become apparent:
udev
rules causes a very noticeable lag.The approach is probably too low level to achieve the desired result.
The Arch Linux wiki comes in quite handy rather often, despite me not using Arch,
but Debian.
Nonetheless pointing out options and basic documentation should work across all
distributions more or less equally.
Luckily, there is a page on input remap utilities.
After briefly evaluating the options here, evsieve
seemed like a decent
starting point.
Building evsieve
was done rather quickly and easily and the documentations is
quite decent.
Doing the first steps I came up with something like:
sudo ./evsieve \
--input /dev/input/by-id/usb-device grab \ # abbreviated for readability
--map key:up key:a \
--output
This is of course a rather absurd set-up remapping the up key to the a key, turning our fancy scroll wheel into a weird contraption. But it proves a point and we can work from here. At this point we should also probably get rid of our udev rules we set up before, because this seems like something that would creep up on me in several years time causing headaches trying to find where the weird behaviour of the device comes from.
Overall, this looks like something we can work with.
Furthermore, reading the documentation of evsieve
it looks very much like you can create several modes with it.
Which is exactly what I want.
sudo ./evsieve \
--input /dev/input/by-id/usb-device grab \
--hook key:mute toggle \
--block key:mute \
--toggle "" @e0 @e1 \
--map yield key:volumeup@e1 key:nextsong \
--map yield key:volumedown@e1 key:previoussong \
--output
This is still a bit of a toy example, but pretty much what I want.
Pressing down on the rotary encoder, i.e. pressing what is originally the mute key, toggles between the layouts e0
and e1
, where e0
is the default.
All other events are blocked, so switching layouts does not cause the music to go silent.
The layer e0
keeps the original association of volume control, but the e1
layer is here remapped to previous and next song.
Of course this setup is still not exactly what I want to achieve, but it is close enough that I am confident I can do what I want with my new device.
Given that modes without clear indication are bad UX, I should probably also create some sort of indication which layout is currently in use.
From the evsieve
documentation it seems like this can also be achieved with the software.
As I recently switched back to using a tiling window manager (after many many years with Gnome), displaying the mode somewhere in the UI should be easy enough.