Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.
transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
-> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
as immutable audit trail). Includes architecture-map references
to magnotia_core::*, magnotia_storage::*, etc. now pointing at
lumotia_*; dev-setup.md tracing output examples (lumotia_startup
target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
("lumotia_task_sync"); magnotia_locale i18n localStorage key
renamed + migration shim added; CSS keyframe names
magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
to "lumotia era" earlier — restored).
Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
legacy detection strings in legacy_and_target_paths() so the
migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
— migration call sites reference the legacy magnotia keys
deliberately.
- docs/handovers/ — historical audit trail.
cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 map → Tauri runtime → Commands → 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). Lumotia's bespoke evdev backend reads /dev/input/event* directly, parses key combos, and emits lumotia:hotkey-pressed / lumotia: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 ininputgroup, 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:
lumotia:hotkey-pressed(no payload),lumotia:hotkey-released(no payload). Fired from the forwarder task instart_evdev_hotkey(src-tauri/src/commands/hotkey.rs:67,:70). - Depends on:
lumotia_hotkey::{EvdevHotkeyListener, HotkeyCombo, HotkeyEvent, check_evdev_access}. Plustokio::sync::{mpsc, Mutex}. - Called from frontend at: Settings → Hotkey on Linux. Other platforms call the
tauri-plugin-global-shortcutplugin'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 lumotia_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)
- Parse the hotkey string (e.g.
"Shift+Cmd+Space") viaHotkeyCombo::from_tauri_str. Errors become"Cannot parse hotkey: ...". - Lock the listener mutex; if a listener is already running, stop it and await termination.
- Build a 64-deep
tokio::sync::mpscchannel forHotkeyEvents. - Call
EvdevHotkeyListener::start(combo, event_tx), stash the listener. - Spawn a
tokio::spawnforwarder that reads from the event_rx and emitslumotia:hotkey-pressed/lumotia:hotkey-releasedper 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 'lumotia: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_windowguard. 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-shortcutdirectly — 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 cratelumotia_hotkeyhas the platform shim). /dev/input/event*access requires either: (a) the user is in theinputgroup, or (b) a udev rule grants Lumotia explicit access. The Settings UI walks the user through option (a) on first run; option (b) is documented indocs/dev-setup.mdfor 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().awaitreturns 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 lifecycle —
HotkeyState::new()is stashed in setup. - Diagnostics —
OsInfo.custom_hotkey_backendflags 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.