A few years ago, the user spiceBar proposed the SpaceFn keyboard layout on geekhack. The idea of SpaceFn is to let the space key work as both a regular space and a modifier. If you just press space normally, then a space appears. If you hold down space and press another key, then something different happens. For instance, you can configure Space+h/j/k/l to work as arrow keys so it's no longer necessary to move the hands away from home row in order to use the arrow keys. The spacebar is always right under your thumb, so no advanced pinky gymnastics are needed in order to reach an out-of-the-way Fn key.
I have implemented SpaceFn for Linux by modifying the Xorg input driver: https://github.com/ljosa/xf86-input-evdev-spacefn. This page describe the implementation and how to build, install, and configure it.
The SpaceFn implementation intecepts and buffers some keypresses until it knows what to do with them. As an example, suppose you press down space and then, soon after, press down H. The driver does not yet know whether you intended to (1) type a space followed by an H or (2) press the left arrow key (Space + H), so it marks that space has been pressed down and buffers the H.
There are several ways in which the driver can reach a decision about your intention and release the keypresses in the buffer:
These rules seem to work well, but they are not perfect:
This algorithm seems to work very well for me, but people type in different ways, so if you try it and find that it causes mistakes when you type, I'd love to hear from you. We may have to adjust the timeouts or revise the algorithm.
The Xorg input driver is perhaps not the ideal place to implement SpaceFn.
I tried to write a Linux kernel module, but modifying and changing keyboard events in the keyboard notifier chain turned out to affect only the virtual terminals and not evdev, so it didn't work for X. And I didn't find anywhere else in the Linux kernel that looked like it might work and be accessible to a kernel module.
In Xorg, perhaps xkb would be a more logical place for this functionality, but I didn't immediately see where it would go. So for now it's in the input driver.
Some people have tried to implement SpaceFn with xcape, a program that is typically used to make left control act as escape when pressed and released on its own. The program is just an X client, so that is simpler than installing a driver: all you have to do is run it. Xcape works by eavesdropping on X events with X's Record extension. If it sees that the modifier has been pressed and then released without any other keypresses in-between, then xcape synthesizes a keypress with Xtest.
That works fairly well for the left-control-as-escape case because the key is normally used as a modifier; when it is pressed alone, it's a fairly unusual situation, and it's not too much of a chore to release the key before pressing something else. But it doesn't work well at all for space because one uses the space bar all the time when typing quickly. When typing quickly, one often "rolls over," pressing the next key before releasing the previous key. If Space+h is left arrow, then typing "come here" quickly comes out as "come←ere" and you see "comereh".
I tried to improve xcape by making it send the synthetic space event unless another key has been both pressed and released while space is held. That worked in the sense that it generated the missing space in the use case we discussed, but the keypress event for "h" still had the modifier bit set, so it ended up as a "←". And "come ←ere" is not much better than "come←ere".
The fundamental problem is that the SpaceFn implementation needs to be able to intercept and modify and/or prevent keyboard events, and Xrecord/Xtest can't do that. There used to be an extension called Xevie for intercepting events, but it has been removed years ago.
I build the modified input driver for Ubuntu 16.04 under Docker so I don't have to clutter my system with all the dependencies. Here is the Dockerfile: https://gist.github.com/ljosa/b626f9fdd86424ce9923530ab561fbaf
Build the container with
docker build -t spacefn-build .
where .
refers to the directory where the Dockerfile is located. You can then check out the xf86-input-evdev-spacefn
repo and build it:
git clone git@github.com:ljosa/xf86-input-evdev-spacefn.git
docker run --rm=true -v `pwd`:/src spacefn-build
You should end up with a file called evdev_drv.so
in the current directory. Copy this file to the right place to install it:
sudo cp evdev_drv.so /usr/lib/xorg/modules/input/evdev_drv.so
Note that copying the file will kill the X server. It will restart with the modified driver.
The implementation will send keycode 135 ("menu") when space is being used as a modifier. You probably already have that key as Mod5, so you can just use Xmodmap to bind the keycode to Mode_switch and then add whatever you want as the second pair of keysyms on the letter keys:
keycode 135 = Mode_switch Mode_switch Mode_switch Mode_switch keycode 56 = b B space space keycode 43 = h H Left Left keycode 44 = j J Down Down keycode 45 = k K Up Up keycode 46 = l L Right Right keycode 57 = n N Next Next keycode 58 = m M Prior Prior keycode 30 = u U Home Home keycode 32 = o O End End keycode 10 = 1 exclam F1 F1 keycode 11 = 2 at F2 F2 keycode 12 = 3 numbersign F3 F3 keycode 13 = 4 dollar F4 F4 keycode 14 = 5 percent F5 F5 keycode 15 = 6 asciicircum F6 F6 keycode 16 = 7 ampersand F7 F7 keycode 17 = 8 asterisk F8 F8 keycode 18 = 9 parenleft F9 F9 keycode 19 = 0 parenright F10 F10 keycode 20 = minus underscore F11 F11 keycode 21 = equal plus F12 F12
If you put this in your \~/.Xmodmap
, then it should be read when you log in. You can read it immediately with xmodmap - < ~/.Xmodmap
.
Last updated: 2017-12-29