Why global keyboard remapping can be bad
In an earlier post, I blogged about remapping keys on a keyboard for better typing comfort. The change was to swap the Control and Windows keys on a CM Storm Stealth USB keyboard.
Using the "partial fist" method I described in my prior post, the pinky ends up hitting the Windows key, both with left and right pinkies. And because I use vim heavily, I want the control key at this position. The CM Storm keyboard is nice because its placement of the Control and Window keys are symmetrical in relation to the hands on the home row of the keyboard.
The xmodmap mappings described in my earlier post works fine to swap the Control and Windows keys, but because it changes the mappings globally, it remaps the Control and Windows keys on all attached keyboards. I use a Thinkpad X201 notebook, and swapping its Control and Windows keys is not desirable.
The position of the Control keys on the Thinkpad keyboard is already ideal according to my preference. So what I really want is a method to change the mappings for only one keyboard device attached to the computer, and not all of them.
How to implement device specific remapping
xkb allows per-device mapping
The solution is to ditch the xmodmap solution presented before and instead use the capabilities of xkb. Using the Xorg evdev input driver, it is possible to configure different settings for different input devices. For keyboards, we can therefore select different xkb options.
The configuration changes describe here are available in the cmstorm repository.
Step 1 -- Create a new xkb option
I first created a new xkb option. New file /usr/share/X11/xkb/symbols/ctrlwin
contains the option named ctrlwin:swap_ctrl_win
:
// Swap the Ctrl and Win keys.
partial modifier_keys
xkb_symbols "swap_ctrl_win" {
key <LWIN> { [ Control_L ] };
key <LCTL> { [ Super_L ] };
key <RWIN> { [ Control_R ] };
key <RCTL> { [ Super_R ] };
};
Step 2 -- Reference the new option so it can be used
The new option should show up in the proper reference files to be usable.
Specifically, the evdev and evdev.lst files, found in the
/usr/share/X11/xkb/rules
directory, at least on Ubuntu 14.04.
Here's the patch for evdev.
--- evdev.orig 2014-01-15 07:42:33.000000000 -0700
+++ evdev 2015-01-30 20:20:08.708389769 -0700
@@ -971,6 +971,7 @@
altwin:hyper_win = +altwin(hyper_win)
altwin:alt_super_win = +altwin(alt_super_win)
altwin:swap_alt_win = +altwin(swap_alt_win)
+ ctrlwin:swap_ctrl_win = +ctrlwin(swap_ctrl_win)
grp:switch = +group(switch)
grp:lswitch = +group(lswitch)
grp:win_switch = +group(win_switch)
Here is the patch for evdev.lst.
--- evdev.lst.orig 2014-01-15 07:42:33.000000000 -0700
+++ evdev.lst 2015-01-30 20:20:20.976390083 -0700
@@ -800,6 +800,8 @@
altwin:hyper_win Hyper is mapped to Win-keys
altwin:alt_super_win Alt is mapped to Right Win, Super to Menu
altwin:swap_alt_win Alt is swapped with Win
+ ctrlwin Ctrl/Win key behavior
+ ctrlwin:swap_ctrl_win Ctrl and Win keys are swapped
Compose key Position of Compose key
compose:ralt Right Alt
compose:lwin Left Win
With these changes, one can activate the option on a given X keyboard input device to swap the Control and Windows keys. Something like this:
setxkbmap -device 10 -option ctrlwin:swap_ctrl_win
where 10
is the id of the device. One can see all the X input devices by
running at a shell prompt:
xinput -list
To de-activate the option, issue the command again but with ""
as the option
argument. Or, since this device is a USB keyboard, simply unplug it and then
plug it back in. At this point, we have shown the option works correctly (or
not!) but the change is not persistent.
Step 3 -- Set the new option for the appropriate devices
In Ubuntu 14.04, /usr/share/X11/xorg.conf.d/
contains a set of files used for
the configuration of X. Add the new file
/usr/share/X11/xorg.conf.d/90-evdev-CM-Storm.conf
:
Section "InputClass"
Identifier "CM Storm keyboard"
MatchIsKeyboard "on"
MatchDevicePath "/dev/input/event*"
Driver "evdev"
MatchUSBID "2516:0017"
Option "XkbOptions" "ctrlwin:swap_ctrl_win"
EndSection
This file defines an InputClass that overrides the default for keyboards defined
in 10-evdev.conf
. It is applicable for devices that are keyboards, whose
device path starts with /dev/input/event
, and shows USB VID:PID is
2516:0017
. The CM Storm keyboard has this VID:PID, although it's hard to
track it down via lsusb
. Here is the lsusb
output on my Thinkpad:
Bus 002 Device 002: ID 8087:0020 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 080: ID 17ef:4816 Lenovo
Bus 001 Device 088: ID 1366:0101 SEGGER J-Link ARM
Bus 001 Device 087: ID 046d:c52f Logitech, Inc. Unifying Receiver
Bus 001 Device 086: ID 1a40:0101 Terminus Technology Inc. 4-Port HUB
Bus 001 Device 085: ID 1fc9:0083 NXP Semiconductors
Bus 001 Device 084: ID 1058:1110 Western Digital Technologies, Inc.
Bus 001 Device 083: ID 1a40:0201 Terminus Technology Inc. FE 2.1 7-port Hub
Bus 001 Device 082: ID 046d:0825 Logitech, Inc. Webcam C270
Bus 001 Device 092: ID 2516:0017
Bus 001 Device 079: ID 17ef:1005 Lenovo
Bus 001 Device 002: ID 8087:0020 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
But the Xorg log file has the information
$ grep "CM Storm.*Vendor" /var/log/Xorg.0.log.old | tail -1
[100514.151] (--) evdev: CM Storm Side print: Vendor 0x2516 Product 0x17
Update 2015-09-17: I purchased another one of these keyboards for a second
location. I couldn't find a Stealth model with Cherry MX Brown key switches, so
purchased instead the Rapid model. The only difference is the latter has the
keycaps printed on the top face, as do most keyboards. It's USB VID:PID is also
different: 2516:0004
. So I've changed the MatchUSBID
value in
/usr/share/X11/xord.conf.d/90-evdev-CM-Storm.conf
to use a wildcard entry to
capture both keyboards: 2516:*
.
Step 4 -- Log out and back in
X only reads the configurations in /usr/share/X11/xorg.conf.d on startup, so the X session needs to be restarted. The simplest way to do this is to log out and back in again. Now, upon detecting the CM Storm keyboard, its Control and Windows keys should be swapped without affecting any other keyboards attached to the system -- in my case the Thinkpad's built-in keyboard. Here's an Xorg snippet that shows the more specific InputClass definition working:
[101104.743] (II) config/udev: Adding input device CM Storm Side print (/dev/input/event14)
[101104.743] (**) CM Storm Side print: Applying InputClass "evdev keyboard catchall"
[101104.743] (**) CM Storm Side print: Applying InputClass "CM Storm keyboard"
[101104.743] (II) Using input driver 'evdev' for 'CM Storm Side print'
[101104.743] (**) CM Storm Side print: always reports core events
[101104.743] (**) evdev: CM Storm Side print: Device: "/dev/input/event14"
[101104.743] (--) evdev: CM Storm Side print: Vendor 0x2516 Product 0x17
[101104.743] (--) evdev: CM Storm Side print: Found keys
[101104.743] (II) evdev: CM Storm Side print: Configuring as keyboard
[101104.743] (**) Option "config_info" "udev:/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5.2/1-1.5.2:1.1/input/input60/event14"
[101104.743] (II) XINPUT: Adding extended input device "CM Storm Side print" (type: KEYBOARD, id 9)
[101104.744] (**) Option "xkb_rules" "evdev"
[101104.744] (**) Option "xkb_model" "pc105"
[101104.744] (**) Option "xkb_layout" "us"
[101104.744] (**) Option "xkb_options" "ctrlwin:swap_ctrl_win"
Line three of the output shows use of the new InputClass, and the last line of the output above shows the xkb option applied by that class.
Step 5 -- Swap the key caps on the keyboard
The CM Storm comes with a key cap puller, so it's trivial to swap the Control and Windows keys and have the key legends match with the new keyboard mapping.