Making CapsLock Useful... on Solus
I thought I had this figured out…
I previously wrote about fixing the caps lock key in Ubuntu. This more or less worked fine, but it still felt pretty hacky. I wasn’t happy with it, but at the same time I mostly use Windows or macOS as my daily driver, and didn’t spend much time looking for a better solution.
The other day I got fed up (again) with Windows 10, and set up a dual-boot install with Linux. After a bit of searching around, I settled on Solus Budgie as my distro of choice this time. It seems that everyone who uses it vows to never switch to anything else, which isn’t super surprising given the Linux community, but… it looked intriguing. I’ve spent most of my Linux time on Ubuntu or other Debian variants, with a bit of dabbling with Arch when I’m feeling particularly masochistic. Solus isn’t based on either, and it felt like a good time to learn something new so I dove in.
For the most part, it’s been smooth sailing so far, and the majority of the packages I needed were available
through eopkg
with names similar to what I’d expect from apt-get
. When it came time to get caps lock working, though,
I found a new solution that looked a bit more stable, in the form of the caps2esc
plugin for a tool called interception
. Hat tip to Danny Guo’s post that
led me to this more reliable alternative to xcape
.
caps2esc
is a plugin for a tool by the same author called interception
,
which self-describes as “A minimal composable infrastructure on top of libudev
and libevdev
”. Anyway, whatever. It looks like
it can do what I want, namely to remap caps lock into something useful, and also able to do it without
barfing whenever the computer goes to sleep, and without a bunch of hacks and workarounds.
caps2esc
has nicely-built packages… for Ubuntu and Arch. Not so much for Solus.
Building from source
Install prerequisites
OK, no problem - just build it myself. This should have been way easier than it was, and I blame myself, mostly.
The documentation could’ve been a little more explicit, but I’m also not super familiar with
C++ development on Linux, and while I have a general idea of what make
does, it’s not something I use
regularly. But, in the spirit of not needing to look things up across the internet in the future, here’s
what it took for me to get this thing built:
$ sudo eopkg install -c system.devel
This gives you things like cmake
& make
, gcc
& g++
, and a bunch of other useful utilities for building
other software.
$ sudo eopkg install libevdev-devel libconfig-devel yaml-cpp-devel libboost-devel
This gives you the rest of the libraries needed by both caps2esc
and the underlying interception
.
Clone the repos and build
caps2esc
$ cd ~/src
$ git clone git@gitlab.com:interception/linux/plugins/caps2esc.git
$ cd caps2esc
$ cmake -B build -DCMAKE_BUILD_TYPE=Release
$ cmake --build build
interception
$ cd ~/src
$ git clone git@gitlab.com:interception/linux/tools.git interception-tools
$ cd interception-tools
$ cmake -B build -DCMAKE_BUILD_TYPE=Release
$ cmake --build build
Copy binaries
You can probably put these wherever you like, as long as it’s on your path. I chose
/usr/bin/
:
$ sudo cp ~/src/caps2esc/build/caps2esc /usr/bin/
$ cd ~/src/interception-tools/build
$ cp udevmon intercept mux uinput /usr/bin/
Set up a systemd
unit file to run intercept
$ cd ~/src/interception-tools
$ sudo cp ./udevmon.service /etc/systemd/system/
If you take a look at the unit file, you’ll see that udevmon
will look for a YAML
config file at /etc/interception/udevmon.yaml
, so let’s create that.
$ sudo mkdir -p /etc/interception
$ sudo vim /etc/interception/udevmon.yaml
Create a config file
The GitLab page has an example
config file to use caps2esc
correctly with interception
:
- JOB: intercept -g $DEVNODE | caps2esc | uinput -d $DEVNODE
DEVICE:
EVENTS:
EV_KEY: [KEY_CAPSLOCK, KEY_ESC]
Start the service
$ sudo systemctl enable udevmon --now
$ sudo systemctl status udevmon
● udevmon.service - Monitor input devices for launching tasks
Loaded: loaded (/etc/systemd/system/udevmon.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2021-05-25 17:18:54 PDT; 3h 43min ago
Docs: man:udev(7)
Main PID: 30691 (udevmon)
Tasks: 17 (limit: 77059)
Memory: 5.5M
CPU: 10.870s
CGroup: /system.slice/udevmon.service
├─30691 /usr/bin/udevmon -c /etc/interception/udevmon.yaml
├─30705 sh -c intercept -g $DEVNODE | caps2esc | uinput -d $DEVNODE
├─30706 sh -c intercept -g $DEVNODE | caps2esc | uinput -d $DEVNODE
├─30707 sh -c intercept -g $DEVNODE | caps2esc | uinput -d $DEVNODE
├─30708 intercept -g /dev/input/event1
├─30709 caps2esc
├─30710 sh -c intercept -g $DEVNODE | caps2esc | uinput -d $DEVNODE
├─30711 uinput -d /dev/input/event1
├─30712 intercept -g /dev/input/event2
├─30713 caps2esc
├─30714 uinput -d /dev/input/event2
├─30715 intercept -g /dev/input/event3
├─30716 caps2esc
├─30717 uinput -d /dev/input/event3
├─30718 intercept -g /dev/input/event0
├─30719 caps2esc
└─30720 uinput -d /dev/input/event0
OK, but…
So this is great, and it indeed makes CapsLock behave the way I want it to - Escape when tapped, and Control when held down in combination with another key. All well and good, right?… right?…
Of course it can’t be that simple. Everything worked fine right up until the point where
I remapped a few other keys on the keyboard using gnome-tweaks
. Although I frequently
switch between Windows and macOS, I’m most comfortable with macOS-ish (sort of) modifier
keys. I prefer the left-most key on the bottom row to act like a Windows key (i.e., to open
up the Start menu or launcher), then the Fn key (which I don’t use), then the Alt/Opt key,
then the Command/Control key. gnome-tweaks
has this exact swapperoo that I want (called
“Left Alt as Ctrl, Left Ctrl as Win, Left Win as Left Alt”, under the “Ctrl position”
setting. Great. Enable that, re-login to enable it, and… whoops. Now CapsLock still works
as Escape, but no longer works as Ctrl.
A bit of digging around shows that caps2esc
doesn’t play well with anything else that
modifies the keyboard, particularly if the other mapping has lower priority. Hmm… After
trying out a different ways of remapping the keys I wanted, and getting the same results,
I finally found another plugin (unofficial) for interception
that worked, with
a bit of playing around.
interception-k2k
This plugin does quite a few different
things, but what I really need it for is pretty simple: add another pipeline step
in the interception
chain that will remap the modifier keys before caps2esc gets
hold of them. Instead of having a one-size-fits-all scope, this plugin lets you define
your own keymappings in a way that interception
can pipeline them.
Here’s what I did:
Clone the repo
$ cd ~/src
$ git clone git@github.com:zsugabubus/interception-k2k
Create a new keymapping configuration
$ mkdir ~/src/interception-k2k/default/swapsies
$ nvim ~/src/interception-k2k/default/swapsies/map-rules.h.in
Add the following to this file:
{ .from_key = KEY_LEFTCTRL, .to_key = KEY_LEFTMETA },
{ .from_key = KEY_LEFTMETA, .to_key = KEY_LEFTALT },
{ .from_key = KEY_LEFTALT, .to_key = KEY_LEFTCTRL }
Build the pipeline step
$ cd ~/src/interception-k2k
$ make
touch default/swapsies/tap-rules.h.in
touch default/swapsies/multi-rules.h.in
cc -std=c99 -O3 -g -Wall -Wextra -Werror -Wno-type-limits -Idefault -Idefault/swapsies k2k.c -o out/swapsies
rm default/swapsies/multi-rules.h.in default/swapsies/tap-rules.h.in
This builds an executable from the rules I just specified, and puts it
in the ./out/
folder.
Move the pipeline executable somewhere sane
$ sudo mkdir -p /opt/interception
$ sudo cp ~/src/interception-k2k/out/swapsies /opt/interception/
Update udevmon
to add this pipeline step
IMPORTANT: this step MUST come before the
caps2esc
step, otherwise it doesn’t fix the problem at all
$ sudo nvim /etc/interception/udevmon.yaml
Add the new pipeline step in the JOB
section as the first pipeline. The file
should look like this afterwards:
- JOB: intercept -g $DEVNODE | /opt/interception/swapsies | caps2esc | uinput -d $DEVNODE
DEVICE:
EVENTS:
EV_KEY: [KEY_CAPSLOCK, KEY_ESC]
Restart udevmon
$ sudo systemctl restart udevmon
Profit
It took a re-login after this to get things working, after initially finding that my key-repeat and delay settings had been clobbered. After logging out and back in again, though everything seems to be working properly, and I’ve finally got what I wanted.