Five-slice navigable map of the entire codebase under
docs/architecture-map/. Each slice is a self-contained
breadcrumbed sub-tree:
01-frontend (16) Svelte/SvelteKit UI
02-tauri-runtime (26) src-tauri commands + lifecycle
03-audio-transcription (16) audio + transcription crates
04-llm-formatting-mcp (19) llm, ai-formatting, mcp, cloud
05-core-storage-hotkey-build core, storage, hotkey, workspace,
(26) CI, dev glue
Plus master README.md and data-flow-end-to-end.md tracing
audio bytes from microphone to FTS5 search to MCP read.
Generated by 5 parallel subagents on 2026/05/09 against
HEAD 3c47000. Each page has YAML frontmatter, file:line code
refs, sibling cross-links, plain-English summaries.
Aggregated debt surfaced (full lists in master README):
RB-08 macOS power assertion, schema head drift v14 vs v15,
VAD blocked on ort version conflict, streaming primitives
not wired into live.rs, no prompt versioning, MCP has no
auth, cloud-providers in-memory keystore, SettingsPage
2 484 LOC, commands/live.rs 1 737 LOC, dual theme system,
brand rename to Lumenote pending across the codebase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.2 KiB
name, type, slice, last_verified
| name | type | slice | last_verified |
|---|---|---|---|
| Hotkey crate (Linux evdev) | architecture-map-page | 05-core-storage-hotkey-build | 2026/05/09 |
Hotkey crate (Linux evdev)
Where you are: Architecture map → Core, Storage, Hotkey, Build → Hotkey crate (Linux evdev)
Plain English summary. A global hotkey listener for Linux that reads keypresses straight from /dev/input/event* rather than going through a display server. That makes it work on both X11 and Wayland, with no compositor-specific protocol negotiation. macOS and Windows fall back to Tauri's global-shortcut plugin; this crate compiles to a no-op on those platforms.
At a glance
- Crate:
magnotia-hotkey. - LOC: 632 (
lib.rs177,linux.rs426,stub.rs29). - External deps:
tokio 1(rt + sync + macros + time),serde 1,log 0.4, plus Linux-only:evdev 0.12(withtokiofeature),notify 7(default features off,macos_fseventonly),nix 0.29(fsfeature). - Public surface:
HotkeyCombo,HotkeyEvent,EvdevHotkeyListener,check_evdev_access. Plus the parserHotkeyCombo::from_tauri_str. - Consumers: slice 2 (
src-tauri/src/commands/hotkey.rs) is the only caller in production. The Tauri command holds anEvdevHotkeyListenerintauri::Stateand forwardsHotkeyEventovermpscto the live-session command.
Public surface
HotkeyCombo — crates/hotkey/src/lib.rs:33
Cross-platform shape. Lives in lib.rs so both linux and stub modules can use it.
pub struct HotkeyCombo {
pub ctrl: bool,
pub shift: bool,
pub alt: bool,
pub super_key: bool,
pub key_code: u16, // evdev key code
pub label: String, // user-facing, eg "Ctrl+Shift+R"
}
impl HotkeyCombo {
pub fn from_tauri_str(s: &str) -> Option<Self>;
}
from_tauri_str parser — crates/hotkey/src/lib.rs:50
Splits on +, accepts ctrl|control, shift, alt, super|meta|cmd|command, plus a single trigger key. The key-name → evdev-code mapping at lib.rs:79 covers A-Z, 0-9, F1-F12, plus arrows, modifiers, and the standard punctuation set. Returns None on unmappable input.
HotkeyEvent — crates/hotkey/src/linux.rs:23 / stub.rs:11
Defined in both backends:
pub enum HotkeyEvent { Pressed, Released }
Released matters for push-to-talk.
EvdevHotkeyListener — crates/hotkey/src/linux.rs:32
pub struct EvdevHotkeyListener { /* hotkey_tx, shutdown_tx */ }
impl EvdevHotkeyListener {
pub fn start(combo: HotkeyCombo, event_tx: mpsc::Sender<HotkeyEvent>) -> Self;
pub fn set_hotkey(&self, combo: HotkeyCombo);
pub async fn stop(&self);
}
start spawns:
- One async task per input device that supports the configured trigger key (initial scan).
- A watcher task that listens on
notifyfor new files in/dev/input/and attaches per-device listeners with a 5-attempt 1-second-backoff loop (udev permissions propagate asynchronously afterinotify CREATE).
set_hotkey updates a tokio::sync::watch::Sender<Option<HotkeyCombo>>. All listener tasks pick up the change; future devices use the new combo.
stop sends None on the watch channel and signals the shutdown channel. Per-device tasks exit on the next hotkey_rx.changed() poll.
check_evdev_access() — crates/hotkey/src/lib.rs:166
Probes whether the current user can read evdev devices. On permission denied returns:
Permission denied reading /dev/input/eventN. Add your user to the 'input' group:
sudo usermod -aG input $USER (then log out and back in)
The hotkey command in slice 2 calls this at startup; the message becomes a non-modal toast on first run.
Linux backend internals
Device hotplug — crates/hotkey/src/linux.rs:64-136
Uses notify::recommended_watcher against /dev/input/. On inotify Create(_) events with a path that starts event*, it spawns a 5-attempt retry to attach a per-device listener. Retries are 1-second sleeps; udev permissions propagate over a window of a few hundred milliseconds after device creation.
Failure modes:
recommended_watcherreturnsErr(rare; minimal containers, BSD pretending to be Linux). Logs and degrades to "no hotplug detection" — the initial scan still picks up devices that exist at startup. Non-fatal.watcher.watch("/dev/input")returnsErr(eg/dev/inputitself missing). Same fallback.
Per-device listener — crates/hotkey/src/linux.rs:274
device.into_event_stream() gives an async stream of evdev::InputEvent. The listener tracks four modifier flags (ctrl_held, shift_held, alt_held, super_held) by watching KEY_LEFT* and KEY_RIGHT* press / release. When the trigger key matches and modifier state matches, sends HotkeyEvent::Pressed or HotkeyEvent::Released on event_tx.
device_supports_combo — crates/hotkey/src/linux.rs:368
Filters out devices whose reported EV_KEY capability does not include the configured trigger. Replaces the RB-12 hard-coded KEY_A || KEY_R filter from the original whisper-overlay port. See Existing in-repo docs.
fn device_supports_combo(supported: Option<&AttributeSetRef<Key>>, combo: &HotkeyCombo) -> bool {
supported.is_some_and(|keys| keys.contains(Key::new(combo.key_code)))
}
Channel close handling — crates/hotkey/src/linux.rs:328
When event_tx.send(...) fails, the listener exits cleanly: receiver was dropped, no point spinning. Logged at WARN once per device.
Tests
crates/hotkey/src/linux.rs:372-426:
attaches_when_device_supports_configured_trigger— happy path.rejects_when_device_lacks_configured_trigger— wrong key.rejects_when_device_reports_no_keys—Nonecapability set.attaches_for_non_a_non_r_trigger— RB-12 regression: aCtrl+Shift+Dbinding now attaches to a device that reports KEY_D and rejects a device that only reports KEY_R.
Watch-outs
- Linux only. The crate compiles to a no-op (
stub.rs) on macOS and Windows where Tauri'splugin-global-shortcuthandles hotkeys natively. Feature parity is maintained across platforms but by two distinct mechanisms — including, for example, hotplug detection (Linux only). - Requires user in the
inputgroup. Documented in thecheck_evdev_accesserror message. Without group membership, no devices open. - Modifier tracking is per-device. A user pressing
Ctrlon the physical keyboard and the trigger on the laptop's built-in keyboard would not match — modifier state is local to one device's stream. In practice users press all keys on the same keyboard so this is fine, but worth knowing for split keyboard rigs. notifylistens on/dev/inputnon-recursively. Sub-directories would be missed. None exist today.- Hotplug retry attempts (5 × 1 s) can stack. A USB hub that announces 10 keyboards in a burst spawns 10 retry tasks; benign but worth knowing.
Existing in-repo docs
docs/issues/hotkey-linux-device-filter.md— RB-12. The hard-codedKEY_A || KEY_Rfilter the currentdevice_supports_comboreplaced.
See also
- Slice 2 hotkey command — the only caller.
- Slice 1 preferences (hotkey configuration) — the UI that produces a Tauri-style hotkey string.