In Praise of XCompose
arbitrary unicode input, anywhere (on linux)
I find myself writing Unicode a lot. I’m a mathematics and linguistics student, which means I very frequently work with non-ASCII characters – indirectly through LaTeX, or directly through input support in whatever editor I happen to be using. But Unicode support in editors, broadly… sucks? It’s sometimes painful and always inconsistent and often does not exist at all. This is annoying! I can’t always rely on LaTeX sequence recognition to be available, so in the past I’ve had to turn to Googling names + copy-paste. An input method doesn’t get any worse than that.
But recently, I’ve stumbled upon a system that works pretty well for this stuff (and other things): X11’s XCompose. It Just Works, across all Linux applications and across both X11 and Wayland desktops (!! despite the name).1 It reads a file at ~/.XCompose containing lines of esoteric incantations invoking sequences of character inputs and strings of Unicode to remap them to. When such sequences are recognized as being input, a preview / visual indicator can be shown,2 and upon completion the Unicode character – or string – will be inserted, replacing the preview.
XCompose is very powerful. I primarily use it for two things:
- Unicode input, through a designated Compose key + some combinations of keys following.
- Arbitrary key remappings, rebinding layer-1 and layer-2 inputs any way I so desire.
There are other ways to accomplish the latter task. Keyboard layouts themselves may be modified and introspected and switched between, on Linux. This is complicated, somewhat, and so I used to take the easier (seriously) of MITM’ing /dev/uinput to replace keycalls directly using a nice tool by the name of [kmonad]. But XCompose accomplishes this, in a simpler fashion, while supporting Unicode, so I use it now instead.
Anyway – onto XCompose itself. The syntax of the ~/.XCompose file itself is a little arcane, if decently well-documented. A piece of my configuration is below as an example.
# Swap : and ;
<semicolon> : ":"
<colon> : ";"
# (This means anything binding to these
# physical keys will have to swap, too.)
# The Multi_key is the key I have bound as
# Compose on my computer. This may vary.
<Multi_key> <a> <l> <l> : "∀"
<Multi_key> <l> <a> <m> : "λ"
<Multi_key> <s> <q> <r> <t> : "√"
<Multi_key> <a> : "α"
<Multi_key> <a> <l> <p> <h> <a> : "α"An XCompose file is composed of a list of rules, split into two parts by :. On the left-hand side is a key sequence. Keys are placed within <...> angle brackets. Both upper and lowercase letters are represented verbatium. Special characters are named: grave, asciitilde, exclam, at, numbersign, dollar, percent, asciicircum (^), ampersand, asterisk, parenleft, parenright, underscore, equal, minus, plus, bracketleft, bracketright, braceleft, braceright, backslash, bar, semicolon, colon, apostrophe, quotedbl ("), comma, period, slash, less, greater, question, space. On the right hand side is a string of arbitrary Unicode, to be inserted.
Overlapping (and redundant) sequences are allowed. For example, α is inserted by both Compose+a and Compose+alpha. The insertion itself happens upon completion of the sequence + there no longer being any future valid compose bindings: for instance, Compose followed by a alone will not insert α, but Compose followed by a followed by space (or in this example – any other key sans <l>) will.3
Further documentation is available by running man 5 compose.
While I think XCompose is great, the syntax is definitely not great. It’s very overwhelmingly verbose and there’s no sort of “default” bindings you can inherit from (though there is inheritance syntax), aside that provided by your distro. And if you remap individual keys, you have remap all bindings that rely on it. That’s why I wrote a little Guile Scheme script to take in a more-compact representation of bindings and output them to an XCompose file automatically, resolving any remappings. Guile compiles to the web with its Hoot backend – so here it is!
input
output
(This helper script was mostly a nice excuse to learn how Guile Hoot actually works, and is not very feature-complete. I might update it in the future. Maybe. But for now, it does work!)
See also:
- https://github.com/kragen/xcompose/
- https://github.com/rrthomas/pointless-xcompose
- https://2π.com/11/compose-key/
- https://tylercipriani.com/blog/2015/01/23/toward-a-useful-keyboard-in-x/
- https://wiki.archlinux.org/title/Xorg/Keyboard_configuration
- http://canonical.org/~kragen/setting-up-keyboard.html
- https://vi.stackexchange.com/a/2256
Though, it doesn’t come “for-free” on Wayland like it does on X11. KDE explicitly implements support. I am not sure what other compositors do.↩︎
This depends on the toolkit. On GTK, a nice “ghost” preview is shown. On Qt and libxkbcommon, nothing is shown. On libadwaita, it’s completely and utterly broken (as, perhaps, is expected wrt. that software stack).↩︎
Unfortunately, at the time of writing – this behavior is not consistent. Qt and libxkbcommon only work properly with unambiguous bindings. There are murmurings of fixing this, but I haven’t sent PRs and I think the only other person who cares is busy atm.↩︎