Skip to content

Blog

Auto-correcting wrong-layout typing on Wayland is nearly impossible. We did it anyway

 · 8 min read

If you type in more than one keyboard layout, you know the moment: you look up at the screen and the last word reads Espa;a. The keyboard was still in US English, and Spanish ñ lives on that key. So you delete the word, switch the layout, and type it again — for the thirtieth time today. The further apart the alphabets — Greek, Hebrew, Cyrillic — the worse the mess.

Utilities that fix this automatically are an old category. Windows has had them since the 2000s (Punto Switcher and its descendants), X11 had xneur: watch what is being typed, notice that it is gibberish in the current layout but a real word in the other one, switch the layout and retype the word. Then Linux desktops moved to Wayland, and the category quietly died there — not because nobody cared, but because Wayland is deliberately designed to make this class of application impossible.

PolterType is an open-source (MIT) tray app, pure Rust, that does it anyway — on Windows, macOS and Linux. On Linux that includes Wayland, which was the hard part, and which is what this post is about.

Three doors, all locked

To fix a word typed in the wrong layout you need three capabilities:

  1. hear keystrokes globally, in every application;
  2. inject keystrokes, to erase the wrong word and retype it;
  3. draw a small suggestion next to the text being edited.

X11 hands all three to any client that can open the display: raw XInput2 events on the root window, XTest for injection, override-redirect windows for the popup. Which is exactly why X11 is also a keylogger’s paradise — and exactly why Wayland’s designers refused to carry those capabilities over. There is no protocol for global key events. There is no protocol for placing a window at screen coordinates. Synthetic input exists only behind portals that most compositors didn’t implement for years.

From a security standpoint, all of this is correct. From the standpoint of a layout corrector, all three doors are locked. Here is the key we found for each one.

Door 1: hearing keystrokes — go under the display server, not over it

If the compositor won’t relay input, read it where the compositor itself does: evdev. PolterType’s listener reads /dev/input/event* directly, below the display server, so it works identically on every compositor — Hyprland, Sway, KDE, GNOME, whatever comes next.

The honest cost: that needs membership in the input group plus a udev rule, which is one sudo between installing the app and it working (scripts/setup-linux.sh does both; the Setup pane inside the app probes your machine and tells you which half is missing, including the classic trap where usermod -aG input can’t touch a login session that already exists). On X11 the same app needs no permission at all — that contrast is Wayland’s security model working as intended, and we state it rather than hide it.

Reading evdev means you get keycodes, not characters. PolterType carries its own layout mappings — TOML files generated from xkeyboard-config rather than transcribed from keyboard pictures — for the fifteen layouts it currently bundles, and only the layouts your OS actually has enabled are loaded.

There was one alternative we refused only after measuring it: AT-SPI, the accessibility bus, has a keystroke-listener API on paper. Registering it returns false on wlroots compositors and delivers nothing even with keys injected at the kernel level, because at-spi2-registryd has no keyboard of its own — on Wayland it relays what the compositor hands it, and only Mutter does. Where it would work (X11), the existing listener already needs no permissions. So: no AT-SPI listener, and that is now a decision with measurements behind it, not an open plan item.

Door 2: typing it back

Corrections go out through a uinput virtual keyboard: erase the word, switch the layout, retype it. The same setup script covers /dev/uinput, so it is one permission story, not two.

Since 0.10.0 there is also a fallback for sessions where uinput cannot be opened: the RemoteDesktop portal, the standard, permissioned way to ask a compositor to synthesise input. It is tried only when uinput is closed, so nobody who ran the setup script ever sees a consent dialog. Full disclosure: that path is written from the specification and has not yet run on a real GNOME or KDE session — it is labelled that way in the code, and if it misbehaves we will assume PolterType is wrong before the compositor.

And here is the part nobody warns you about: switching the layout has no universal Wayland API either. Every desktop owns layouts its own way, so PolterType probes a chain of backends in priority order — hyprctl switchxkblayout, KDE’s org.kde.keyboard D-Bus interface, GNOME-family gsettings input sources, IBus, Fcitx5, and X11 XKB group locking as the bare-window-manager fallback. If none of them answers, the app refuses to start rather than sit in the tray detecting mistakes it cannot fix.

Door 3: a tooltip next to your caret

When PolterType is not sure enough to auto-correct, it shows a small suggestion you can accept with a chord. On Wayland that innocent feature is two locked doors disguised as one.

The surface. Wayland clients cannot position their own windows. The escape hatch is zwlr_layer_shell_v1 — the protocol panels and notification daemons use — which gives an overlay surface with its own coordinate space. wlroots compositors (Hyprland, Sway) have it, and KWin has implemented it for years, something we ourselves documented wrong until we tested KWin 6.7.3 and watched the surface map exactly as on Hyprland. Mutter has no layer-shell — there the tooltip maps as an X11 override-redirect window through XWayland. The genuinely uncovered case is a GNOME-like session with XWayland disabled.

The position. No protocol tells you where the text caret is. But the accessibility bus does — the readable half of AT-SPI that actually works: subscribe to object:text-caret-moved, ask the focused widget for GetCharacterExtents, and you get the caret’s rectangle. Coordinates only; the tooltip code never requests text content. One subtlety worth knowing: toolkits keep their accessibility bridges dormant until something raises the session’s org.a11y.Status.IsEnabled flag — the same flag screen readers raise — so PolterType raises it at startup and deliberately never unsets it, because a real screen reader might start later. Applications launched before the flag went up stay silent until restarted; then the tooltip falls back to anchoring on the focused window or the pointer. Terminals mostly ship no accessibility bridge at all — an honest gap, and developers live in terminals.

The door that bit back: corrections versus your own typing

A correction is a burst of injected keystrokes. If you keep typing while it is on the wire — and fast typists do — your keys land in the middle of the burst and the word comes out interleaved. On Linux, PolterType closes that race by holding the keyboard for the length of the burst (EVIOCGRAB), then typing the held keystrokes out itself, in order, once the correction is down.

Then it met keyd. Input remappers hold every keyboard exclusively — including PolterType’s own virtual one — and re-emit everything through a single virtual device. Grab that proxy and you have blocked your own corrections along with the user’s typing. So the gate checks whether it can grab its own emitter, and if the emitter turns out to be proxied, stands down for the rest of the run; corrections still work, they just repair the raced keystroke instead of preventing it.

That check used to run once, at startup. That was a bug, and it cost us a whole session once: a remapper grabs a freshly created device asynchronously, so the startup probe can win the race, arm the gate — and the first correction funnels the entire session’s input into a queue nobody drains. Since 0.6.3 the gate re-verifies before every single hold. The lesson generalises to the whole stack this app sits on (kernel → remapper → compositor → XWayland → toolkit): probed at startup proves nothing at the moment of use.

What it costs, honestly

Yes, it sees your keystrokes. Here is the entire surface

There is no way around it: anything in this category must see keys — that is the category. What an open-source tool can offer is a surface small enough to audit:

The long version of all of this is the privacy policy — and the code, which is the version that counts.

Coda

PolterType bundles fifteen layouts today; adding a language is a TOML file plus a wordlist, documented in docs/ADDING_A_LANGUAGE.md — it is data, not code, and it is the friendliest first contribution the project has. Things we would genuinely love help with: validating macOS on Apple Silicon (#3) and the macOS keystroke hold-back (#8).

Code: github.com/Just-Code-NET/PolterType · Download: poltertype.com

If you type in two layouts, the ghost is friendly.