dictatd 3 - the days i lost to one hotkey
The plan was simple. The hardest part of dictation isn't the ai. It's the hotkey.
I started with libx11. Global hotkey, grab the keysym, done. Failed. On my window manager, some combinations are already claimed before my program sees them. Grab shift+s, nothing happens, the compositor got there first. X11 has no way around that from inside X11.
So I went a level deeper. Past the window manager, past X11, down to the kernel. evdev.
evdev is how the kernel exposes input devices. Every physical keyboard is a device file. Open it, call EVIOCGRAB, and the kernel stops delivering that keyboard's events to anyone else. Not just me first, everyone else stops seeing them. I'm the only thing between the keyboard and the system.
Which means I now owe the system every keystroke back. So I re-emit each one through uinput, a fake keyboard the kernel treats as real.
physical keys -> my daemon reads them -> re-emits through uinput -> the focused app sees normal typing
Except shift+s. That one I read and don't re-emit. Swallowed. Mic starts instead.
physical keys to daemon to virtual keyboard to app, shift+s takes a detourHard constraint here: I cannot drop a key. Owning the keyboard means every other keystroke has to survive the trip through my process untouched. So key re-emission runs on its own thread, separate from audio decoding. Whisper can chew on a chunk for 3 seconds, doesn't matter, the key thread never waits on it.
main thread busy on a whisper chunk while the key thread keeps emitting ticksThen the paste bug. I paste by synthesizing ctrl+v. But shift+s means shift was physically down when recording started, and it's often still down by the time the correction lands. Ctrl+v with shift still held becomes ctrl+shift+v, wrong shortcut entirely. So the daemon lifts shift for a few milliseconds, pastes, puts it back if it's still physically held.
(if this sounds insane, that's the job.)
Win condition: hold shift+s, speak, release, clean text. No stuck keys, no swallowed letters, no paste turning into something else.
Took a few days and one qemu session that had me questioning my life choices. Works now. Don't notice it running anymore, which is the point.