The input broker
inputd reads the keyboard below the compositor, where Left Ctrl and Right
Ctrl are still different keys. A key the person has bound fires a Bus verb
and never reaches an app. Everything else is passed on unchanged.
It is the successor of AmigaOS's input.device: keys become verbs, and
bindings are data that a person, an agent or a Bus message can change.
The user keys
These strokes belong to the person, never to an app or a system default:
- bare F1 to F12;
- Right Ctrl, Right Alt or Right Shift with an arrow (any of the three right-hand modifiers, and no other modifier).
inputd swallows every user key on its press, repeats and release unless the
person has bound it to a verb or marked it passthrough. The shipped keymap
binds none of them. Ctrl+F3, bare arrows and Left-hand modifier arrows
stay with the apps.
The one shipped binding is the system-reserved recovery chord Ctrl+Alt+P
(Left or Right Ctrl with Left Alt), which toggles the scene editor.
How a key is decided
Each key is decided once, at its press, and keeps that decision until it is released:
- fire: a binding matched, so its verb is sent and the key is
swallowed. A row with
repeat: "allow"fires again on each repeat. - swallow: an unbound user key.
- pass: anything else, or a passthrough row. The key is re-emitted on
the
inputd virtual keyboarddevice.
Releasing Right Ctrl before → still swallows the → release. A binding changed while a key is held applies from that key's next press.
Grab and VTs
With --grab DEVICE or --grab-name NAME, inputd grabs the keyboard
(EVIOCGRAB) and re-emits what it passes. With --vt N[,M...] it holds the
grab only while one of those VTs is the active console:
- On switch-away, it releases on the re-emitter every key it had passed and is still held, then drops the grab. The next VT's compositor reads the keyboard directly.
- On switch-to, it takes the grab again, but only once no key is down. A key held across the grab would stay down in the compositor, which saw its press but would never see its release. So while the switch chord (Ctrl+Alt+Fn) is still held, inputd waits ungrabbed and checks again at each report from the keyboard, and also on a timed retry with a backoff from 50 ms up to 2 s (in case another holder takes the last release). While another holder still has the device, it retries on the same backoff.
- If the kernel drops events (
SYN_DROPPED), inputd re-reads which keys are down, releases every key it had passed on, and swallows any key still held until it is released.
It watches /sys/class/tty/tty0/active with poll(2), so a switch wakes it
at once: the active VT is never polled on a timer. The only timers are the
bounded grab retries above. The re-emitter is made once, at
start, and kept: a compositor in a container cannot see an input device that
appears after it started.
Known limits
All of these happen only while inputd takes the grab (at startup or on switching to its VT) with keys in motion, and none can be closed from inputd. They close where the compositor enforces the user keys itself: compd's user-key layer.
- A key can stay down in the compositor. If a key is pressed before the
grab and released in the brief window while inputd checks, grabs and
discards queued events, the release is consumed under the grab. The
compositor saw the press on the original keyboard and never sees its
release;
EVIOCGRABoffers no way to hand it back on that device. - A key tapped as the grab is taken can be lost. A press and release that both land between the grab and the discard of queued events reach neither the apps nor inputd's bindings. Nothing is left held.
- User keys pass while inputd waits. While a key is held at switch-to, inputd is not grabbed, so a user key pressed in that time reaches the apps unclassified.
Running it
| flag | what it does |
|---|---|
| (none) | the Bus service only; no keyboard |
--observe DEVICE | read a keyboard without grabbing it, and log what each key would do |
--grab DEVICE | grab this event node |
--grab-name NAME | grab the node whose name in /proc/bus/input/devices is NAME |
--vt N[,M...] | hold the grab only on these VTs |
--grab-timeout SECONDS | exit after this long, which releases the grab |
--keymap PATH | the keymap file (default: keymap.json in the state directory) |
The unit inputd.service runs the Bus service only. An operator turns the
grab on with a drop-in, after a supervised run with --grab-timeout from a
shell that can kill it. Any exit of inputd, SIGKILL included, releases the
grab.
The keymap file
/var/lib/mixos/inputd/keymap.json (the unit's state directory), in the
keymap.v1 format:
{"version": 1, "physical": [
{"stroke": {"code": 106, "modifiers": {"right_ctrl": true}},
"action": "comp.workspace.switch", "args": {"index": "next"},
"service": "comp", "repeat": "allow"}
]}
code is the Linux key code. service names the Bus service the verb goes
to; without it, the verb's first dot-segment does. args is the verb's
body. A row with "passthrough": true re-emits its stroke instead.
Verbs
| verb | what it does |
|---|---|
input.query | the rows, the mode and the generation |
input.bind | add or replace a row ({"layer":"physical", ...row}) |
input.unbind | remove the row on a stroke ({"code":..., "modifiers":{...}}) |
input.mode | {"mode":"transparent"} passes every key unchanged; normal; empty toggles |
input.reload | re-read the keymap file |
input.key | inject a key press, release or tap |
input.pointer.move, .button, .scroll | inject pointer input |
Bindings are written through to the keymap file. MIXOS_MESH_OPEN=0
limits the keymap changes to node-local callers.