Hardware → IOKit
You press a key. IOKit lifts it into CoreGraphics as a CGEventRef carrying keycode, modifier flags, and the layout-aware unicode the OS already computed.
BM Keyboard is a tiny macOS overlay that turns plain romaja keystrokes into Hangul jamo in real time. Bold, unmistakably an input app.
BM Keyboard is a small Rust app that sits quietly above your workspace. You type jang on a regular QWERTY keyboard. The overlay catches every keystroke, converts it into Korean jamo, and shows the result above whatever app you happen to be in.
No IME juggling, no system-wide input source switching. One window, one buffer, one canonical translation table. Built on iced and the macOS CGEventTap APIs — the same primitive Karabiner uses.
From hardware to pixels in under a millisecond. The pipeline is short on purpose — every step is a function you could read in a sitting.
You press a key. IOKit lifts it into CoreGraphics as a CGEventRef carrying keycode, modifier flags, and the layout-aware unicode the OS already computed.
A tap registered at HID location, HeadInsert placement. We let Cmd/Ctrl/Opt shortcuts pass through untouched. Special keys (return, delete, escape) short-circuit. Everything else hands back its unicode string.
The tap thread sends typed events across an iced::futures::mpsc channel — no shared mutable state. In Grab mode the original keystroke is nulled so the focused app never sees it.
A tiny state machine on a String. Push on Char, pop on Backspace, commit on Enter, clear on Escape. The overlay window resizes itself to fit.
Greedy left-to-right scan. At each byte, look up the longest matching romaja prefix in the token table. Push the matched jamo. Advance by the canonical length. babo → ㅂㅏㅂㅗ.
Both agents emit window::resize tasks; the two overlay windows snap to their new content size. CFRunLoop keeps spinning. Next keypress, repeat from step one.
guides/pipeline.md · the canonical trace
Rust toolchain required. macOS 12 or newer. The build produces a single binary; the overlay runs as a normal user process — no kernel extensions, no privileged daemons.
git clone https://github.com/you/bm_keyboard cd bm_keyboard
cargo build --release --example overlay
System Settings → Privacy & Security → grant the launching process Accessibility and Input Monitoring. Terminal if you run from terminal, your IDE if you run from there.
cargo run --release --example overlay
Type romaja anywhere — a browser, Notes, your terminal. The keystrokes flow into the overlay, not the focused app.
The bottom window mirrors the raw romaja buffer. The top window shows the jamo guess, updated on every keystroke.
Commits the current buffer (printed to stdout for now). Clears it.
Clears without committing.
Pops the last char.