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 keyboard device.

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; EVIOCGRAB offers 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

flagwhat it does
(none)the Bus service only; no keyboard
--observe DEVICEread a keyboard without grabbing it, and log what each key would do
--grab DEVICEgrab this event node
--grab-name NAMEgrab the node whose name in /proc/bus/input/devices is NAME
--vt N[,M...]hold the grab only on these VTs
--grab-timeout SECONDSexit after this long, which releases the grab
--keymap PATHthe 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

verbwhat it does
input.querythe rows, the mode and the generation
input.bindadd or replace a row ({"layer":"physical", ...row})
input.unbindremove the row on a stroke ({"code":..., "modifiers":{...}})
input.mode{"mode":"transparent"} passes every key unchanged; normal; empty toggles
input.reloadre-read the keymap file
input.keyinject a key press, release or tap
input.pointer.move, .button, .scrollinject pointer input

Bindings are written through to the keymap file. MIXOS_MESH_OPEN=0 limits the keymap changes to node-local callers.

This page as Markdown · This page describes the current main branch of MixOS.