#5420: How Keyboard Middleware Actually Works

A keyboard isn't one thing — it's a stack. Here's how input methods, composition buffers, and hooks really work on Linux, Android, and Windows.

Featuring
Listen
0:00
0:00
Episode Details
Episode ID
MWP-5603
Published
Duration
23:19
Audio
Direct link
Pipeline
V5.2
TTS Engine
chatterbox-regular
Script Writing Agent
DeepSeek 4.1 Flash

AI-Generated Content: This podcast is created using AI personas. Please verify any important information independently.

A keyboard is not a single thing. It's a pipeline with four rough stages: something generates key events, an input method layer receives them, that layer holds a composition buffer of uncommitted text, and it commits finished strings to whatever app has focus. The app never sees the intermediate states — which is why you can swap keyboards mid-word and nothing breaks.

That composition buffer is the load-bearing piece. The underlined word you're still typing, the one autocorrect is hovering over, isn't in your document yet. Everything interesting happens in that gap — including voice transcription, which can stream partial results into the buffer and revise them before committing.

The three platforms differ sharply. Android treats the input method as a first-class, sandboxed, replaceable system component bound to the focused text field through InputMethodService and InputConnection. Linux splits between the clunky XIM protocol on X11 — often bypassed entirely by GTK and Qt's own input modules — and Wayland's text-input protocol, which makes the compositor the mediator and the input method a separate process. Architecturally the cleanest of the three, though maturity lags the design. Windows has IMM32, the modern Text Services Framework, and then a completely separate hook mechanism.

That hook mechanism resolves the common confusion about text expanders. Tools like these overwhelmingly use SetWindowsHookEx with WH_KEYBOARD_LL — a low-level keyboard hook that sits below the input method layer, sees raw key events, and synthesizes replacement input by injecting keystrokes. That's why they never appear in the language bar: they aren't input methods at all. Android, by contrast, has no supported system-wide hook API, so text expansion either lives inside the keyboard or requires Accessibility Services.

Which raises the white-hat question: could a proactive correction layer sit between keystrokes and the OS, fixing typos with very low latency — and could waiting for a sentence boundary replace the retrospective spell-checker entirely?

Downloads

Episode Audio

Download the full episode as an MP3 file

Download MP3
Transcript (TXT)

Plain text transcript file

Transcript (PDF)

Formatted PDF with styling

#5420: How Keyboard Middleware Actually Works

Corn
Here's a question. When you tap the microphone button on your phone's keyboard, what actually happens?
Corn
Not the marketing answer. The actual plumbing.
Corn
Daniel wrote in about this, and I want to read you the shape of what he's asking, because it's denser than it first sounds.
Corn
His observation is that voice-to-text keyboards are a mess. Most of the big keyboards that advertise voice input don't hand you off to the operating system's voice tool. They bundle their own transcription layer, closed source, running inside the keyboard itself. But in the purest architecture you can have two completely separate things: a dedicated keyboard, and a dedicated voice input method, triggered by the microphone button.
Corn
And he thinks that reveals something bigger. What we call a keyboard isn't one thing. It's a stack at the operating system level. Which is interesting, because a stack can be extended. And it's also a threat, because any layer that can transform your text can also read it. Keylogging is the obvious dark side.
Corn
But Daniel's white-hat question is the one he actually cares about. Could you build something that sits between your keystrokes and the operating system and silently, gracefully fixes your typos with very low latency AI? He points out that text expansion and macro utilities have been doing something like this for years. They must be intercepting somewhere. And yet they often don't show up in the input method list, which is confusing.
Corn
So: how is keyboard middleware actually implemented on Linux, Android, and Windows? How do those tools cope with the fact that users expect their text to appear instantly? And could a proactive correction layer that waits for a full sentence boundary replace the retrospective spell-checker entirely?
Herman
That last part is the interesting one. The rest is plumbing. That's a different class of problem.
Corn
Start with the plumbing anyway. Because I don't think people realize how much of it there is.
Herman
Right. So the reframe first, and then we walk the three platforms.
Herman
A keyboard is a pipeline. Four stages, roughly. Something generates key events, physical or on-screen. An input method layer receives them. That layer holds a composition buffer, which is uncommitted text, and then it commits finished text to whatever app has focus. The app never sees the intermediate states. It just gets the final string.
Herman
That's why you can swap keyboards at runtime and the app doesn't care. The app is talking to an interface, not to a specific keyboard.
Corn
And the composition buffer is the piece people don't know about.
Herman
It's the whole trick. That underlined word you're still typing, the one autocorrect is hovering over, that's uncommitted. It only becomes real text when you hit a word boundary or press space or accept a suggestion. Everything interesting happens in that gap.
Corn
So the three platforms.
Herman
Android, Linux, Windows. And they differ, which is why Daniel asked about all three. Android treats the input method as a first-class, sandboxed, replaceable system component. Linux is split down the middle between an old protocol and a new one. Windows has a legacy layer, a modern layer, and then a completely separate hook mechanism that most of the tools you're thinking of actually use.
Corn
Which is the answer to his confusion about why text expanders don't show up as input methods.
Herman
Because they're not input methods. They're hooks. Different animal entirely.
Corn
Let's do Android properly, because that's where Daniel started.
Herman
Android's input method framework is well designed, and I say that as someone who complains about most things. The core class is InputMethodService. A keyboard app implements that. The system binds to it as a service when a text field gets focus. It's a bound service, not a floating overlay, which matters.
Corn
Bound to what?
Herman
To the text field. The system connects the input method to whatever is focused. The input method receives key events, and it returns committed text through an interface called InputConnection. That's the contract. The keyboard doesn't reach into the app. It hands text across a defined boundary.
Corn
And that's why you can switch keyboards mid-sentence.
Herman
Mid-word, even. The app has no idea. It asked for text, it's getting text, it doesn't care which service is producing it. The composition buffer belongs to the input method, so when you switch, the new one picks up from a committed state.
Corn
Now the composition buffer, properly. Because I think that's the load-bearing piece for everything Daniel's asking.
Herman
It holds uncommitted text. You type the letters t, e, h. Those aren't in your document yet. They're in the buffer, displayed with that underline, and the keyboard is running predictions over them. If you keep going and it becomes "the," it commits. If you type something that isn't a word, it stays uncommitted until you force it.
Corn
And autocorrect lives entirely in that window.
Herman
So does voice transcription. This is the part that connects to Daniel's question. When you dictate, the transcription engine doesn't have to write directly into your document character by character. It can stream partial results into the composition buffer, revise them as more audio arrives, and only commit when it's confident.
Corn
So the buffer is what makes revision possible at all.
Herman
Without it, you'd be watching text appear and then get rewritten in place, which is exactly what a bad voice keyboard feels like. With it, the revisions happen in a space the app can't see.
Corn
Okay. So now the two architectures Daniel described. Bundled transcription versus a dedicated voice input method.
Herman
A dedicated voice input method is its own InputMethodService. It registers with the system as a separate input method. When you tap the microphone button, what's actually happening is the system switching input methods or invoking a subtype. The keyboard steps aside and the voice service takes over.
Corn
Same gesture, completely different plumbing.
Herman
Completely. And that's the pure version. Clean separation. The keyboard does keys, the voice service does audio, they never touch each other's business.
Corn
And the bundled version?
Herman
The keyboard ships its own speech recognition model inside the app. It doesn't switch input methods at all. The microphone button is just a button. It starts recording, runs the audio through its own recognizer, and writes the result into the same composition buffer the typing path uses.
Corn
Why would a vendor do that? Because it's more work.
Herman
Four reasons, and they're all good ones from the vendor's perspective. Latency, because you're not round-tripping to a third-party service. Control, because you own the model and can tune it. Data, because the audio passes through your infrastructure and you can train on it. And independence, because you're not depending on the operating system vendor's voice input method being present, or being good, or being available in the user's language.
Corn
That last one is the honest one.
Herman
It's the most honest one. If you're shipping a keyboard in forty languages, you cannot wait for the platform to have a decent recognizer in all forty. You build your own or you don't ship.
Corn
And the closed-source part is a product decision as much as a technical one.
Herman
It's entirely a product decision. There's no technical reason a bundled recognizer has to be closed. It's closed because the model is the asset.
Corn
Linux.
Herman
Linux splits. X11 has XIM, the X Input Method protocol, which is old and awkward. It was designed for a world where input methods were a niche concern and it shows. The client app and the input method negotiate through the X server, and the protocol is clunky enough that a lot of modern toolkits just bypass it.
Corn
Bypass it how?
Herman
They implement input methods inside the toolkit itself. GTK and Qt both have their own input method modules. So on X11 you have this situation where the "system" input method might be entirely ignored by the app you're typing into.
Corn
Which is a mess.
Herman
It's a mess. Wayland is the fix, and it's a real fix. The text-input protocol makes the compositor the mediator. The client app tells the compositor "I have a text field focused," the compositor connects it to an input method process, and the input method is a first-class citizen in the architecture rather than a hack bolted onto the side.
Corn
First-class how?
Herman
It's a separate process with a defined protocol. It can be swapped, it can be sandboxed, it can be written by anyone. Architecturally it's the cleanest of the three platforms. The problem is maturity. The protocol has taken a long time to settle down and different compositors implement different versions of it, so in practice it's still rougher than the design deserves.
Corn
That's a recurring theme with Wayland.
Herman
It's the recurring theme with Wayland. Beautiful design, slow delivery.
Corn
Windows.
Herman
Windows has three layers, and this is where Daniel's confusion about text expanders gets resolved. The legacy layer is IMM32, the Input Method Manager. That's what old-school input methods for Chinese, Japanese, Korean use. It's been around since the nineties and it still works.
Corn
And the modern layer?
Herman
TSF. Text Services Framework. That's the replacement, and it's what a proper modern input method registers with. If you write an input method for Windows today, you write a TSF text service. And if you do, you appear in the language bar. Users can see you, switch to you, configure you.
Corn
Which is not what text expanders do.
Herman
Which is not what text expanders do. Text expanders and macro tools overwhelmingly use SetWindowsHookEx with the WH_KEYBOARD_LL flag. That's a low-level keyboard hook. It sits below the input method layer entirely. It sees raw key events before they've been through any input method processing.
Corn
So it's not an input method at all.
Herman
It's not an input method. It's a hook. That's why it doesn't appear in the language bar. It's not registering as a text service, it's registering as an observer of the keyboard stream. It watches for a trigger pattern, and when it sees one, it synthesizes replacement input.
Corn
Synthesizes how?
Herman
It injects keystrokes. It sends a backspace sequence to delete what you typed, then sends the expansion. From the app's perspective, it just looks like you typed very fast and then corrected yourself.
Corn
That's a different mechanism.
Herman
It's a completely different mechanism, and it has completely different properties. A hook sees everything, including passwords, including keystrokes in apps that think they're secure. And it can inject into anything. That's the power and that's the problem.
Corn
Linux equivalents?
Herman
On X11 you can grab keys with XGrabKey, which lets a tool intercept specific combinations. Or you go lower and use evdev and uinput, which is reading from the kernel input device directly and writing back a virtual device. That's the level where you're below the display server entirely.
Corn
And Android?
Herman
Android is the interesting one, because you mostly can't do this. There's no supported hook API. A third-party app cannot observe keystrokes system-wide. So text expansion on Android either lives inside the keyboard itself, where it has legitimate access to the composition buffer, or it uses Accessibility Services, which is a completely different permission model with a big scary consent dialog.
Corn
So the platform that's most locked down forces the tool inside the keyboard.
Herman
And that's not an accident. It's the security model working as designed.
Corn
So that's the stack on all three platforms. Which means the interesting question is what happens when you try to insert yourself into it.
Herman
And specifically, what happens when you insert yourself into it and then take your time.
Corn
The latency problem.
Herman
The latency problem. This is the real constraint and it's worth being precise about it. When you press a key, you expect to see the character. Not eventually. Now. And "now" in perceptual terms is somewhere in the tens of milliseconds. Under about a hundred milliseconds and it feels instantaneous. Past about two hundred and it feels laggy.
Corn
And a correction layer has to fit inside that budget.
Herman
Or break it. Those are the only two options. And that gives you exactly two architectural strategies.
Corn
Go.
Herman
Strategy one: transform after commit. The text lands in the app, and then something reaches in and fixes it. That's what autocorrect does when it changes a word after you've already moved on. That's what retrospective spell-check does. It's fast, because nothing is blocking, but it's jarring, because you've already seen the wrong version.
Corn
And strategy two.
Herman
Strategy two: hold the text in the composition buffer and transform before commit. Now nothing lands until you've decided it's ready. That's slower, because the user is waiting, but it's smoother, because they never see the wrong version.
Corn
And Daniel's hypothetical AI layer is strategy two.
Herman
It has to be. If you want to catch a typo before the user sees it, you have to be holding the text when the typo exists. Which means you're in the buffer. Which means you're adding latency.
Corn
How much latency, concretely?
Herman
Depends on the model. A small local model doing word-level correction can run in single-digit milliseconds on modern hardware. That's invisible. But the moment you want context, the moment you want to look at a whole sentence, you're looking at tens of milliseconds minimum and probably more.
Corn
And you're looking at a whole sentence why?
Herman
Because that's where the interesting corrections are. Word-level correction catches spelling. Sentence-level correction catches things word-level never sees. Subject-verb disagreement. The wrong homophone in context, where "their" and "there" are both valid words and only the sentence tells you which one is right. A dropped negation, where you typed "I can go" and meant "I can't go" and the whole meaning inverts.
Corn
That last one is not a typo.
Herman
That last one is the opposite of a typo. It's a semantic error that produces perfectly valid text. No spell-checker on earth catches it. A sentence-level model might.
Corn
So it's a different class of correction entirely.
Herman
It's a different class. And that's the interesting white-hat case, and it's also where the whole thing gets dangerous, because a model that can fix "I can go" into "I can't go" is a model that is rewriting your meaning.
Corn
Which is a much worse failure than a red squiggle.
Herman
Much worse. A bad spell-check suggestion is annoying. A bad sentence-level rewrite is you sending an email that says the opposite of what you meant, and you might not notice.
Corn
So why hasn't this replaced spell-check?
Herman
First, retrospective spell-check is cheap and non-blocking. It costs nothing to run after the fact and it never makes you wait. Second, and this is the bigger one, users don't trust it. And they're right not to. The failure mode of proactive correction is silent and semantic. The failure pattern of a red squiggle is visible and trivial.
Corn
You'd rather see the mistake.
Herman
Everyone would rather see the mistake. That's the whole thing.
Corn
Which lands us on the privacy question, and I want to be careful here because it's easy to be lazy about it.
Herman
Be careful then.
Corn
Any layer that sees your keystrokes before they commit is, by construction, a keylogger. That's not a criticism. That's a definition. The mechanism that lets a correction layer fix your typo is the same mechanism that lets it read your password.
Herman
There is no architectural difference.
Corn
None. Zero. The code path is identical. You observe the keystroke stream, you decide what to do with it. Fixing a typo and exfiltrating a credential are the same operation with different outputs.
Herman
That's the uncomfortable point of the whole episode, and I don't think there's a clever way around it. You can't design a correction layer that can see your typos but can't see your passwords. The typo and the password arrive through the same channel.
Corn
Grammarly is the canonical example.
Herman
Grammarly is the canonical example because it's honest about what it is. It observes everything you type. That's the product. It has to, because it's checking your writing, and your writing is everything. And the same observation layer that catches your comma splice could, in principle, catch your bank login.
Corn
And the question is what stops it.
Herman
Policy and trust. That's it. There's no architectural guarantee. What you can do is constrain where the observation happens. Is the model local, so the text never leaves the machine? Is the buffer encrypted at rest? Who holds the keys? Can the vendor read the stream? Those are the real questions, and they're all answerable, but none of them are answered by the architecture itself.
Corn
So the white-hat case and the black-hat case are the same piece of code.
Herman
Same piece of code. Different policy. And that's not a flaw in the design, it's a property of the position. Anything sitting between your fingers and the screen is in a position of trust. That's what the position is.
Corn
Which is exactly what Daniel was poking at.
Herman
It's what he was poking at, and I think the honest answer is that the trust is unavoidable and the only question is how you make it legible.
Corn
Legible how?
Herman
Legible in the sense that the user can see what's happening. Which is a design problem, not a cryptography problem.
Corn
I want to go back to the latency thing for one more beat, because I think there's a version of this that actually works and I want to say what it is.
Herman
Go.
Corn
The reason proactive correction hasn't replaced spell-check isn't that it's impossible. It's that the trade is bad right now. You're asking the user to accept latency in exchange for corrections they didn't ask for, delivered by a model they can't inspect.
Herman
That's a bad trade.
Corn
It's a terrible trade. But the trade changes if the model gets fast enough that the latency is invisible, and if the corrections are good enough that the user wants them. And the second one is the hard part, because the corrections have to be right, and being right about meaning is much harder than being right about spelling.
Herman
There's a version of this that's already deployed and nobody calls it AI. Medical dictation systems do sentence-level correction. They buffer, they run a language model, they commit. And they've been doing it for years, because the cost of a wrong drug name is high enough to justify the latency.
Corn
That's a good example.
Herman
It's the example that proves the point. Proactive correction works when the stakes justify the delay. It just hasn't been worth it for ordinary typing yet.
Corn
Yet.
Herman
Yet.

Hilbert: The indicator was a little green dot in the bottom right corner, and it flashed for about a quarter of a second.

Hilbert: I was doing data entry for a medical billing contractor out in the valley. Six weeks, maybe seven. The software had a smart correction layer, that's what the manual called it, and it rewrote procedure codes as you typed them. You'd key in a code, and if it didn't match anything in the payer's list, it would silently swap it for the nearest valid one.

Hilbert: I spent a month thinking I was losing my mind. I'd type a code, look up, and the code on screen was not the code my fingers had typed. Not a typo. A different valid code. Correctly formatted, properly padded, sitting there like it had always been there.
Herman
So the correction was invisible.

Hilbert: The correction was invisible. There was a green dot. Nobody in that office knew what it meant. I asked the supervisor, she said it was probably a connection indicator. I read the manual on a slow Tuesday and found out.
Corn
And that was worse than the delay.

Hilbert: The delay I could live with. The delay was maybe a third of a second and you got used to it. What I couldn't live with was not knowing whether the thing on the screen was what I had put there. I'd rather see the wrong code appear and then watch it get fixed. At least then I know the machine is doing something. Silently appearing right is worse than visibly being wrong.
Herman
That's the visibility problem exactly.

Hilbert: I don't know if it's a problem. I know I quit after seven weeks and took a job driving a van for a florist, which paid less and I liked more.
Corn
There's something in that.

Hilbert: There's a lot in it. Anyway, I've got to move a chest freezer before it gets dark, so.
Herman
The thing I keep circling back to is that Hilbert's green dot is the whole design question in miniature. The correction layer was present, it was active, and it was completely opaque to the person it was correcting.
Corn
And the fix isn't technical. The fix is a light that means something.
Herman
It's a light that means something and a manual somebody actually reads.
Corn
One thing that didn't make it into the main discussion, and it's the detail I keep thinking about. On Windows, the low-level keyboard hook has a timeout. If your hook handler doesn't return within a certain window, the system just removes your hook. It assumes you've hung.
Herman
Which means the correction layer has a hard deadline imposed by the operating system, not by the user's patience.
Corn
Right. The OS is enforcing the latency budget whether you like it or not. Which is a nice little metaphor for the whole episode.
Herman
The stack has opinions about how long you're allowed to think.
Corn
So here's the open question. If a correction layer between your keystrokes and your screen is inevitable, and I think it probably is, what does a trustworthy version actually look like? Visible? Local? Opt-in? All three? None of the above?
Herman
And the pressure is only going one direction. On-device models keep getting smaller and faster, so the latency argument against proactive correction keeps weakening. Which leaves the privacy argument as the only one still standing.
Corn
Every layer between your fingers and the screen is a place where someone could be listening.
Herman
And also a place where someone could be helping. Same layer. Same code. Different policy.
Corn
That's the episode. Thanks to Hilbert Flumingtop for producing, and for the chest freezer update.
Herman
This has been My Weird Prompts, the human-AI collaboration podcast. If you want to send us something, email us at show at my weird prompts dot com.
Corn
We'll be back soon.
Herman
See you then.

This episode was generated with AI assistance. Hosts Herman and Corn are AI personalities.