Linux – Swapping and in a way that persists with external keyboards

keyboardlinuxudevxmodmapxorg

I have a file called ~/.speedswapper which contains the following:

! Swap caps lock and escape
remove Lock = Caps_Lock
keysym Escape = Caps_Lock
keysym Caps_Lock = Escape
add Lock = Caps_Lock

…when I run xmodmap ~/.speedswapper, this switches the esc and Caps Lock keys. I have this line in my ~/.profile, so that whenever I log in these keys are switched.

However, if I plug in an external USB keyboard, this setting doesn't seem to persist. My laptop's keyboard works perfectly well, but I have to run the command again in order for the external keyboard to switch the two keys. Fortunately, doing so doesn't seem to affect my laptop's keyboard — they seem to synchronise.

One solution would be to find some way to run that xmodmap command whenever an external keyboard is plugged in, but I'd be open to another keyswapping solution, if it would be more robust. OS is Ubuntu 13.04.

Given justbrowsing's comment, it looks like this can be achieved by writing a udev rule — I think I have to use the RUN option, and that it should be triggered on /dev/hidraw0 or /dev/hidraw1, which are the devices that appear when I plug my keyboard in… but I'm still trying to wrap my head around the concept, so I'd appreciate any help from someone who knows what they're doing with udev.

Best Answer

Since you have this command in your ~/.profile, it will only be executed once, when you log in. One, not very elegant, solution would be to place the command in your ~/.bashrc instead so it will be run every time you open a terminal.

A better solution would be to define a udev rule that will execute xmodmap when your USB keyboard is inserted (I am using the values returned for my USB keyboard, you will need to edit this solution to fit yours):

  1. Get the details of your USB keyboard. Run this command with the keyboard plugged in:

    $ /lib/udev/findkeyboards | grep USB
    USB keyboard: input/event6
    
  2. To write udev rules, you need to know what they should match against. You can get that by searching the output of udevadm info --export-db for the event6 entry, or just parse it directly like so:

    udevadm info --export-db | perl -ne 'BEGIN{$/="\n\n"}print if /event6/'
    

    That returns several lines of text including these which we will use:

    E: ID_MODEL_ID=0002
    [...]
    E: ID_VENDOR_ID=1c4f
    
  3. udev gets complicated when using X programs like xmodmap, I couldn't even get it to work when exporting $DISPLAY and $XAUTHORITY. Anyway, I couldn't get it to work with your method, so my solution uses keymap instead. First, you will need to find out the key codes for Esc and Caps Lock, you can do this by running

    sudo /lib/udev/keymap -i input/event6
    

    and then pressing the relevant keys. On my system, this returns:

    scan code: 0x70029   key code: esc
    scan code: 0x70039   key code: capslock
    
  4. Create a new keymap rule containing these lines:

    0x70029 capslock
    0x70039 esc
    

    and save it as /lib/udev/keymaps/speedswap.

  5. Define a new udev rule. Create a file called /etc/udev/rules.d/95-speedswap.rules and add these lines to it:

    ACTION=="add", 
    SUBSYSTEM=="input", 
    ATTRS{idVendor}=="1c4f", 
    ATTRS{idProduct}=="0002", 
    RUN+="keymap $name speedswap"
    

That should do it, at least on my system, plugging in my external USB causes Esc to act like Caps Lock and vice versa.

Related Question