iTranslated by AI
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".
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).
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:
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>.
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