Simon Lederhilger PROJECTS

altshiftkeys

I have a separate computer I use for computer programming than the one I use for my daily reading and writing. A computer without needless software is better for tasks requiring more attention, I imagine. And so this other computer is a Thinkpad T490 with a barebones installation of Arch Linux:

$ fastfetch
                  -`                     simon@T490-arch
                 .o+`                    ---------------
                `ooo/                    OS: Arch Linux x86_64
               `+oooo:                   Host: ThinkPad T490
              `+oooooo:                  Kernel: Linux 7.1.9-arch1-2
              -+oooooo+:                 Uptime: 2 mins
            `/:-:++oooo+:                Packages: x (pacman)
           `/++++/+++++++:               Shell: bash 5.3.15
          `/++++++++++++++:              Display: 1920x1080 in 14", 60 Hz [Built-in]
         `/+++ooooooooooooo/`            Terminal: /dev/tty1
        ./ooosssso++osssssso+`           Terminal Font: VGA default kernel font 8x16x256
       .oossssso-````/ossssss+`          CPU: Intel(R) Core(TM) i5-8265U (8) @ 3.90 GHz
      -osssssso.      :ssssssso.         GPU: Intel UHD Graphics 620 @ 1.10 GHz [Integrated]
     :osssssss/        osssso+++.        Memory: 867.87 MiB / 38.83 GiB (2%)
    /ossssssss/        +ssssooo/-        Swap: 0 B / 4.00 GiB (0%)
  `/ossssso+/:-        -:/+osssso+-      Disk (/): 14.33 GiB / 48.91 GiB (29%) - ext4
 `+sso+:-`                 `.-/+oso:     Disk (/home): 5.91 GiB / 865.61 GiB (1%) - ext4
`++:.                           `-/+/    Battery: 99% [Discharging]
.`                                 `/    Locale: en_US.UTF-8                                         

I do have $\LaTeX$ installed for some reason, but I ought to get rid of it, as I don't have a display server on the computer. That is, the entire graphical user interface is a black terminal with white text. Nevermind that, for the issue that led me to create the altshiftkeys repository is something entirely different.

I use Emacs for writing code, which, for those unfamiliar with the text editing software, is an acronym for editor macros. As the name suggests, it uses macros, or keybindings, to navigate and edit text. As you can see from the fastfetch above, my locale is US English, meaning that the system displays English text. My computer, however, has a Norwegian keyboard layout:

$ localectl status
System Locale: LANG=en_US.UTF-8
    VC Keymap: no
   X11 Layout: (unset)

The VC Keymap parameter here means virtual console keymap, corresponding to the teletypewriter (/dev/tty1) terminal. Telling my computer what these keymaps are, essentially acting as an interpreter between the keyboard presses and the resulting action on the computer's end, is the program kbd. Now, the problem is that for a lot of keybindings in emacs relies on the meta button M, which my keyboard does not have. The equivalent on my keyboard is the Alt button (which one can configure in kbd by declaring alt_is_meta). This shouldn't really be a problem, as looking at the keymap modifiers:

$ dumpkeys -l | tail
Recognized modifier names and their column numbers:
shift             1
altgr             2
control           4
alt               8
shiftl           16
shiftr           32
ctrll            64
ctrlr           128
capsshift       256

What this means is that the keypress combination Ctrl+Alt+Shift+1 corresponds to the 4+8+1 = 13th column of the "lookup table" for keycode 2 (the button that says "1"). I'll show you exactly what I mean. We can dump the entirety of the Norwegian keymap table into a file named 2bfixd.map by running

$ dumpkeys --full-table > 2dfixd.map

Grabbing the first line shows us what columns are defined:

$ awk 'NR==1' 2bfixd.map
keymaps 0-2,4,6,8,12

Notably, the combination Alt+Shift, column nine, is not defined. So if I want to search and replace by typing Alt+Shift+5, or run code by typing Ctrl+u Alt+Shift+1, I am literally not able to do so, since the ninth column isn't defined. Indeed, we can grab line 7, and see that this is indeed the case:

$ awk 'NR==7' 2bfixd.map
keycode 6 = five    percent    VoidSymbol    Control_bracketright    VoidSymbol    Meta_five    VoidSymbol

The defined presses here are 5, Shift+5, AltGr+5, Ctrl+5, Ctrl+AltGr+5, Alt+5, and Ctrl+Alt+5. VoidSymbol means that there isn't a defined output, which is slightly different from the keypress not being defined at all. We can see this by running showkey -k, which will echo back the keypresses.

$ showkey -k
keycode   6 press
keycode   6 release

keycode  42 press
keycode   6 press
keycode   6 release
keycode  42 release

keycode 100 press
keycode   6 press
keycode   6 release
keycode 100 release

keycode  29 press
keycode   6 press
keycode   6 release
keycode  29 release

keycode  29 press
keycode 100 press
keycode   6 press
keycode   6 release
keycode  29 release
keycode 100 release

keycode  56 press
keycode   6 press
keycode   6 release
keycode  56 press

keycode  29 press
keycode  56 press
keycode   6 press
keycode   6 release
keycode  56 release
keycode  29 release

We see that the computer registers the keystrokes perfectly well. Keycodes 42, 100, 29, and 56 are respectively Shift, AltGr, Control, and Alt. This can be double checked by running awk 'NR==X{print $4}' 2bfixd.map, where X is the number in question, plus one. We can see the resulting keymaps by running the following command.

$ showkey -a
5    53 0065 0x35
%    37 0045 0x25
^]   29 0035 0x1d
^[5  27 0065 0x1b
     53 0065 0x35

I'll walk through each of the presses in the order I showed above. Just pressing 5 does indeed output 5. The second column is the ASCII value of the input—5 has ASCII number 53, for example. Similarly, Shify+5 does indeed print the percent symbol, but AltGr+5 outputs nothing, as the map is void. The caret (^) represents control, and ASCII value 29 corresponds to the "group separator," whatever that is. But it is congruent to what the map indicates, which is Control_bracketright. Ctrl+AltGr+5 outputs nothing. Pressing Alt+5 results in the equivalent of Ctrl+], whose ASCII code corresponds to Esc, followed by 5. So the escape button also functions as the meta key! Ctrl+Alt+5 outputs nothing.

Still, if I want to search and replace, being M-% or Alt+Shift+5, I am not able to because the ninth column is not available in the keymap. So my project altshiftkeys fixes this issue by patching the map. Having dumped the keys like earlier into the file 2bfixd.map, the following Perl script fixes the issue.

while (<>) {
    if (/^keymaps\b/) { s/8,12/8-9,12; print; next }
    if (/^keycode\s+(\d+)\s*=\s*(.*)$/) {
    my ($n,@v)=($1, split /\s+/,$2);
    $v[3]='Control_backslash' if $n==13;
    my $new=$v[1]=~/^[a-z]/?"Meta_$v[1]":'VoidSymbol';
    $new=$v[0] if $new eq 'VoidSymbol' && @v
        && $v[0]=~/^(?:Shift|AltGr|Control|Alt|ShiftL|ShiftR|CtrlL|CtrlR|CapsShift)$/
        && !grep { $_ ne $v[0] } @v;
    splice @v,6,0,$new;
    printf "keycode %3d = %s\n",$n,join(' ', map {sprintf '%-16s', $_ } @v);
    next;
    }
    print;
}

Perl is so neat—I love the way it looks. I don't intend for this to be a Perl tutorial, so I'll just explain what the code does instead of working through the syntax. The code first replaces 8,12 in the defined keymaps with 8-9,12 so that the combination Alt+Shift is actually recognized as a valid input. Then, for each line that starts like keycode x =, then determines what the new column should be. My logic here was that all symbols I do want to be defined as some meta combination has the same kind of naming convention. For example, for M-! the exclamation mark is denited exclam on line 3, and for M-% the percent sign is defined percent on line 7. That is, all symbols and glyphs I do want Alt+Shift to be defined for are all lowercase words. So the Perl script makes it so that all such words are appended to the phrase Meta_, so that Meta_exclam, Meta_percent, &c. are the only entries in that column that are not VoidSymbol. The only entries where I ensure that is not the case is for the modifier buttons Shift, Alt, Esc, &c. I fear those buttons may become locked in some situations. I also set column three of line 14 to be Control_backslash, since I have backslash mapped to keycode 13. If you're confused about the line numbers, it's because the keymaps declaration is on the first line, and Perl and awk start counting from zero and one respectively (and the Perl script is reading the keycodes directly).

So now, by executing the following command, the keymaps update to have Alt+Shift defined.

$ perl fix.pl 2bfixd.map > fixd.map && loadkeys fixd.map

In my experience, executing loadkeys requires root access, which is good, because when I wrote this script I mangled my keymap a couple of times and I had to reboot. This overwrite of the keymap is fortunately only temporary. Having fixed the kaymaps now, though, I wondered why it hadn't been addressed before.

I ended up making a pull request to the kbd repository, which was accepted :^) So I am now an official contributor to Linux software!

$ git -P diff no.map
@@ -1,4 +1,5 @@
- keymaps 0-2,4,6,8,12
+ keymaps 0-2,4,6,8-9,12
+ alt_is_meta

This was my contribution, by the way.