If you’re like me and absolutely abhor the Caps Lock key, you’ve probably figured out some way to replace it with a more suitable function. I myself have settled on the following command to make it a duplicate
$ setxkbmap -option ctrl:nocaps
This works great when placed in
~/.xinitrc and run as X starts, but what about USB keyboards which are plugged in later? Perhaps your pair-programming, or moving your laptop between work and home. This happens frequently enough that I thought it’d be nice to use a udev rule to trigger the command auto-magically whenever a keyboard is plugged in.
The setup is fairly simple in the end, but I found enough minor traps that I thought it was appropriate to document things once I got it working.
It has come to my attention that configuring this via
xorg.conf.d actually does affect hot-plugged keyboards.
Section "InputClass" Identifier "Keyboard Defaults" MatchIsKeyboard "yes" Option "XkbOptions" "ctrl:nocaps" EndSection
While this renders the rest of this post fairly pointless, it is a much cleaner approach.
You can’t just place the
setxkbmap command directly in a udev rule (that’d be too easy!) since you’ll need enough decoration that a one-liner gets a bit cumbersome. Instead, create a simple script to add this decoration; then we can call it from the udev rule.
Create the file wherever you like, just note the full path since it will be needed later:
#!/bin/bash ( sleep 1 DISPLAY=:0.0 setxkbmap -option ctrl:nocaps ) &
And make it executable:
$ chmod +x ~/.bin/fix-caps
Important things to note:
sleep 1in order to give time for udev to finish initializing the keyboard before we attempt to tweak things.
- We set the
DISPLAYenvironment variable since the context in which the udev rule will trigger has no knowledge of X (also, the
:0.0value is an assumption, you may need to tweak it).
- We background the whole command with
&so that the script returns control back to udev immediately while we (wait a second and) do our thing in the background.
Now that we have a single callable script, we just need to run it (as our normal user) when a particular event occurs.
SUBSYSTEM=="input", ACTION=="add", RUN+="/bin/su patrick -c /home/patrick/.bin/fix-caps"
Be sure to change the places I’m using my username (patrick) to yours. I had considered putting the
su in the script itself, but eventually decided I might use it outside of udev when I’m already a normal user. The additional line-noise in the udev rule is the better trade-off to me.
And again, a few things to note:
- I don’t get any more specific than the subsystem and action. I don’t care that this runs more often then actually needed.
- We need to use the full path to
su, since udev has no
There’s no need to reload anything (that happens automatically). To execute a dry run via
udevadm test, you’ll need the path to an input device. This can be copied out of
dmesg from when one was connected or you could take an educated guess.
Once that’s known, execute:
# udevadm test --action=add /dev/path/to/whatever/input0 ... ... run: '/bin/su patrick -c /home/patrick/.bin/fix-caps' ...
As long as you see the
run bit towards the bottom, you should be all set. At this point, you could unplug and re-plug your keyboard, or tell udev to re-process events for currently plugged in devices:
# udevadm trigger
This command doesn’t need a device path (though I think you can give it one); without it, it triggers events for all devices.