Files
Lumotia/docs/architecture-map/02-tauri-runtime/commands/hotkey.md
jars a1f3f3f134 docs: architecture map (initial 5-slice generation, 105 pages)
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>
2026-05-09 14:04:13 +01:00

5.7 KiB

name, type, slice, last_verified
name type slice last_verified
Hotkey bridge architecture-map-page 02-tauri-runtime 2026/05/09

commands::hotkey

Where you are: Architecture mapTauri runtimeCommands → Hotkey bridge

Plain English summary. The Linux Wayland-compatible global hotkey backend. Tauri's tauri-plugin-global-shortcut works on macOS / Windows and on X11 Linux but fails silently on Wayland (the protocol forbids unprivileged keystroke grabs). Magnotia's bespoke evdev backend reads /dev/input/event* directly, parses key combos, and emits magnotia:hotkey-pressed / magnotia:hotkey-released events. Settings on Linux uses these commands instead of the global-shortcut plugin; everywhere else the plugin path is canonical.

At a glance

  • Path: src-tauri/src/commands/hotkey.rs.
  • LOC: 105.
  • Tauri commands exposed (5 total):
    • is_wayland_session() -> bool. Pure env probe.
    • check_hotkey_access() -> Result<(), String>. Probes evdev access (user in input group, etc.).
    • start_evdev_hotkey(app, state, hotkey: String) -> Result<(), String>. Parses the Tauri-style combo string, stops any existing listener, starts a new one, spawns a forwarder task that converts evdev events to Tauri events.
    • update_evdev_hotkey(state, hotkey: String) -> Result<(), String>. Updates the combo on a running listener.
    • stop_evdev_hotkey(state) -> Result<(), String>. Stops cleanly.
  • Events emitted: magnotia:hotkey-pressed (no payload), magnotia:hotkey-released (no payload). Fired from the forwarder task in start_evdev_hotkey (src-tauri/src/commands/hotkey.rs:67, :70).
  • Depends on: magnotia_hotkey::{EvdevHotkeyListener, HotkeyCombo, HotkeyEvent, check_evdev_access}. Plus tokio::sync::{mpsc, Mutex}.
  • Called from frontend at: Settings → Hotkey on Linux. Other platforms call the tauri-plugin-global-shortcut plugin's JS API directly.

What's in here

HotkeyState (src-tauri/src/commands/hotkey.rs:9)

Tauri-managed: listener: Arc<Mutex<Option<EvdevHotkeyListener>>>. tokio::sync::Mutex because operations await across .lock().

is_wayland_session (:23)

Returns true if WAYLAND_DISPLAY is set or XDG_SESSION_TYPE=wayland. Used by Settings to decide whether to display the Linux-only hotkey UI.

check_hotkey_access (:32)

Defers to magnotia_hotkey::check_evdev_access — that helper checks the user can read /dev/input/event* (typically requires being in the input group, or a udev rule). On failure, returns the actionable error string.

start_evdev_hotkey (:41)

  1. Parse the hotkey string (e.g. "Shift+Cmd+Space") via HotkeyCombo::from_tauri_str. Errors become "Cannot parse hotkey: ...".
  2. Lock the listener mutex; if a listener is already running, stop it and await termination.
  3. Build a 64-deep tokio::sync::mpsc channel for HotkeyEvents.
  4. Call EvdevHotkeyListener::start(combo, event_tx), stash the listener.
  5. Spawn a tokio::spawn forwarder that reads from the event_rx and emits magnotia:hotkey-pressed / magnotia:hotkey-released per event variant.

update_evdev_hotkey (:81)

Re-parses the combo and calls listener.set_hotkey(combo) if a listener is running. Returns "Hotkey listener not running" otherwise. Avoids the cost of stopping and restarting the evdev thread.

stop_evdev_hotkey (:99)

Take the listener out, call listener.stop().await. Idempotent — calling stop with no listener returns Ok silently.

Data flow

Settings -> is_wayland_session() -> bool
Settings -> check_hotkey_access() -> Ok or "join the input group / install udev rule"
Settings -> start_evdev_hotkey("Shift+Cmd+Space")
  -> parse combo
  -> stop any prior listener
  -> EvdevHotkeyListener spawns its own thread reading /dev/input/event*
  -> forwarder spawn: HotkeyEvent -> Tauri event
frontend listens on 'magnotia:hotkey-pressed' / '...-released' and starts/stops dictation
Settings -> update_evdev_hotkey("Ctrl+Alt+M") (when user changes binding)
Settings -> stop_evdev_hotkey() (when user disables)

Watch-outs

  • No ensure_main_window guard. Settings is in the main window. If you ever expose hotkey re-binding in a secondary window, add the guard.
  • Linux only. macOS and Windows code paths in the frontend talk to tauri-plugin-global-shortcut directly — no Rust commands needed because the plugin handles register / unregister via JS. This module compiles and runs on every desktop OS but only works on Linux because evdev is Linux-specific (the workspace crate magnotia_hotkey has the platform shim).
  • /dev/input/event* access requires either: (a) the user is in the input group, or (b) a udev rule grants Magnotia explicit access. The Settings UI walks the user through option (a) on first run; option (b) is documented in docs/dev-setup.md for power users.
  • Channel size of 64 is enough for a key smash burst. If a worker stalls and the channel fills, evdev events would be dropped silently. Consider logging a warning when the channel is at capacity.
  • The forwarder spawn is detached. No way to stop the spawn task explicitly; it exits when event_rx.recv().await returns None (which happens when the listener is dropped). Acceptable.
  • No power assertion. The evdev listener thread is pinned by the kernel via the file handle; idle CPU is near zero.

See also

  • App lifecycleHotkeyState::new() is stashed in setup.
  • DiagnosticsOsInfo.custom_hotkey_backend flags Linux as the evdev path.
  • Paste — the consumer of "user pressed dictation hotkey": holds focus on the target window for the eventual paste.
  • Live transcription — the dictation flow that the hotkey starts.