iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🐧

Remapping Keys at a Lower Level than X on Linux

に公開

Summary

To use the Half-width/Full-width key as Esc and CapsLock as Ctrl on a USB-connected ThinkPad TrackPoint keyboard, create a file named /etc/udev/hwdb.d/10-tmtms.hwdb (the filename can be anything) with the following content:

evdev:name:Lenovo ThinkPad Compact USB Keyboard with TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*
 KEYBOARD_KEY_70035=esc
 KEYBOARD_KEY_70039=leftctrl

Then run the following commands:

% sudo udevadm hwdb --update
% sudo udevadm trigger

The following is research and background, so you don't need to read it

I had been using Emacs for about 30 years, but I decided it was finally time to try out VSCode.
By default, cursor movement seems to be limited to the arrow keys with no shortcuts provided. I thought about using it with the default settings at first, but using the arrow keys was just too difficult, so I ended up installing the "Awesome Emacs Keymap".

https://qiita.com/kurun_pan/items/507517c3f569b080abe2

By enabling the Use Meta Prefix Escape setting, you can also use the Esc key for M-.

However, it didn't work well. While the original physical Esc key worked, the Esc key configured via xmodmap or XKB did not (I had changed the Half-width/Full-width key to the left of 1 to Esc).
https://tmtms.hatenablog.com/entry/2013/07/07/keymap

Checking with xev shows that the keysym is correctly set to Escape:

KeyPress event, serial 37, synthetic NO, window 0x1800001,
    root 0x7c7, subw 0x0, time 906346, (72,68), root:(3823,1069),
    state 0x0, keycode 49 (keysym 0xff1b, Escape), same_screen YES,
    XKeysymToKeycode returns keycode: 9
    XLookupString gives 1 bytes: (1b) "
mbLookupString gives 1 bytes: (1b) "
FilterEvent returns: False

This suggested that it might be using the keycode instead of the keysym.

Since there seemed to be no way to handle this at the X level, I Googled to see if I could change the keycode at a lower layer and found that it could be done with setkeycodes.
It apparently allows you to change the map that converts physical codes (scan codes) from the keyboard into keycodes.

It seems this can't be done on X and needs to be performed in the console, so I'll switch to the console with something like Ctrl-Alt-F1 before executing it (you can return to X with something like Alt-F7).

Check the scan code for the Half-width/Full-width key with showkey -s:

% showkey -s
kb mode was UNICODE
[ if you are trying this under X, it might not work
since the X server is also reading /dev/console ]

press any key (program terminates 10s after last keypress)...
0x29 0xa9

It doesn't stop with Ctrl-C. It terminates if left alone for 10 seconds.

Check the keycode for Esc with showkey -k:

% showkey -s
kb mode was UNICODE
[ if you are trying this under X, it might not work
since the X server is also reading /dev/console ]

press any key (program terminates 10s after last keypress)...
keycode   1 press
keycode   1 release

Since it seems I just need to map scan code 0x29 to keycode 1, I'll execute setkeycodes:

% sudo setkeycodes 0x29 1

This successfully changed the mapping for the laptop's built-in keyboard, but it didn't work for the USB keyboard.

The man page states the following (this description was missing from the Ubuntu man setkeycodes):

setkeycodes affects only the "first" input device that has modifiable scancode-to-keycode mapping. If there is more than one such device, setkeycodes cannot change the mapping of other devices than the "first" one.

Consequently, as someone using a USB ThinkPad TrackPoint keyboard, I needed to find another way.

I Googled again and found a page like this:
https://wiki.archlinux.jp/index.php/スキャンコードをキーコードにマップ

Apparently, it can be done using udev.

evdev:name:<input device name>:dmi:bvn*:bvr*:bd*:svn<vendor>:pn*
 KEYBOARD_KEY_<scancode>=<keycode>

It seems I can specify it in this format.

For <input device name>, I can probably use the name from xinput:

% xinput
⎡ Virtual core pointer                    	id=2	[master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer              	id=4	[slave  pointer  (2)]
⎜   ↳ TPPS/2 IBM TrackPoint                   	id=16	[slave  pointer  (2)]
⎜   ↳ Lenovo ThinkPad Compact USB Keyboard with TrackPoint	id=10	[slave  pointer  (2)]
⎜   ↳ Synaptics TM3145-003                    	id=15	[slave  pointer  (2)]
⎣ Virtual core keyboard                   	id=3	[master keyboard (2)]
    ↳ Virtual core XTEST keyboard             	id=5	[slave  keyboard (3)]
    ↳ Power Button                            	id=6	[slave  keyboard (3)]
    ↳ Video Bus                               	id=7	[slave  keyboard (3)]
    ↳ Sleep Button                            	id=8	[slave  keyboard (3)]
    ↳ Generic USB2.0 Microphone               	id=11	[slave  keyboard (3)]
    ↳ Integrated Camera: Integrated C         	id=12	[slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard            	id=13	[slave  keyboard (3)]
    ↳ ThinkPad Extra Buttons                  	id=14	[slave  keyboard (3)]
    ↳ Lenovo ThinkPad Compact USB Keyboard with TrackPoint	id=9	[slave  keyboard (3)]
    ↳ Lenovo ThinkPad Compact USB Keyboard with TrackPoint	id=17	[slave  keyboard (3)]

It seems the name is Lenovo ThinkPad Compact USB Keyboard with TrackPoint.

I wasn't sure about <vendor>, but it's probably LENOVO. Using * might also work.

Apparently, you can use evtest to find out the <scancode>.
https://wiki.archlinux.jp/index.php/特別なキーボーードキー

It wasn't installed, so I installed it with sudo apt install evtest.

I found the /dev/input/eventXX argument like this:

% ls -l /dev/input/by-path/*usb*kbd
lrwxrwxrwx 1 root root 9  9月 24 12:15 /dev/input/by-path/pci-0000:00:14.0-usb-0:3:1.0-event-kbd -> ../event5

Start evtest and press the Half-width/Full-width key and the Esc key:

% sudo evtest /dev/input/event5
...
Testing ... (interrupt to exit)
Event: time 1632465959.559163, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70035
Event: time 1632465959.559163, type 1 (EV_KEY), code 41 (KEY_GRAVE), value 1
Event: time 1632465959.559163, -------------- SYN_REPORT ------------
Event: time 1632465959.606785, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70035
Event: time 1632465959.606785, type 1 (EV_KEY), code 41 (KEY_GRAVE), value 0
Event: time 1632465959.606785, -------------- SYN_REPORT ------------

Event: time 1632465965.087102, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70029
Event: time 1632465965.087102, type 1 (EV_KEY), code 1 (KEY_ESC), value 1
Event: time 1632465965.087102, -------------- SYN_REPORT ------------
Event: time 1632465965.126598, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70029
Event: time 1632465965.126598, type 1 (EV_KEY), code 1 (KEY_ESC), value 0
Event: time 1632465965.126598, -------------- SYN_REPORT ------------

Half-width/Full-width has a scan code of 70035, and the keycode for Esc appears to be ESC.
(Don't worry about it being displayed as GRAVE even though it's the Half-width/Full-width key. That is because I have selected an English layout despite using a Japanese keyboard.)

In addition to Esc, I also investigated the method to change CapsLock to left Ctrl in the same way.
The scan code for CapsLock was 70039, and the keycode for left Ctrl was LEFTCTRL.

Create a file named /etc/udev/hwdb.d/10-tmtms.hwdb (the filename can be anything):

evdev:name:Lenovo ThinkPad Compact USB Keyboard with TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*
 KEYBOARD_KEY_70035=esc
 KEYBOARD_KEY_70039=leftctrl

Apparently, the keycodes must be written in lowercase.

Update the database file:

% sudo udevadm hwdb --update

Apply changes:

% sudo udevadm trigger

Verify:

% udevadm info /dev/input/event5 | grep KEYBOARD_KEY
E: KEYBOARD_KEY_70035=esc
E: KEYBOARD_KEY_70039=leftctrl

As a result, I can now properly use the Half-width/Full-width key as Esc even in VSCode. I also tried writing this article in VSCode. (I'm still getting used to it.)

Discussion