Architecture · 26 Aug 2026

Jarvis Linux hold-to-talk: production pattern

The current approach (uiohook on Wayland, quiet timeouts, forced X11 grabs) is not how production voice apps do push-to-talk. This note compares Discord / Electron / portal reality and the architecture Jarvis should ship.

Verdict

Use org.freedesktop.portal.GlobalShortcuts with Activated + Deactivated. That is the intentional Wayland hold-to-talk API. Discord’s Linux hotkey work, Chromium’s GlobalShortcutsPortal, and Electron 41’s Wayland path all converge here. Evdev/uinput helpers are community workarounds for apps that never implemented the portal.

Portal = prod Quiet timeout = reject uiohook on GNOME Wayland = broken

What production apps actually do

Surface Hold mechanism Press/release Notes
XDG Desktop Portal GlobalShortcuts D-Bus Activated / Deactivated Designed for PTT. GNOME consent dialog once; bindings persist by app id.
Electron 41+ on Wayland Chromium GlobalShortcutsPortal Press only in JS API globalShortcut.register only implements OnKeyPressed. Portal still emits Deactivated — Electron does not expose it.
Discord Linux (2025–26) Portal / compositor hotkeys Session-wide “Year of the Linux Desktop” hotkey work. Older Flatpak/XWayland builds still needed workarounds.
Community Discord PTT fixes evdev read + uinput inject Real key edges Requires input group. Explicitly a bypass of Wayland security — not an app-platform pattern.
KDE Plasma (upcoming) Compositor-level mic mute PTT OS-owned System mutes mic while held; app-agnostic. Complements portal, does not replace app UX.
Windows / macOS prod OS hooks (RegisterHotKey / CGEvent) Hold via hooks uiohook-style listeners are legitimate there. Keep for those platforms.

Sources: xdg-desktop-portal GlobalShortcuts docs; Electron v41.5 electron_api_global_shortcut.cc (OnKeyPressed only); Electron PR #45171 / docs Linux portal section; PPQ Whisper Wayland issue; Rush/nikolai-in wayland-push-to-talk-fix; GNOME Shell GrabAccelerator AccessDenied for normal apps on this host.

What Jarvis is doing today (and why it fails)

HackIntentFailure mode on this machine
Force --ozone-platform=x11 Absolute overlay positioning + GPU workaround Opts out of Chromium’s portal accelerator path; XWayland grabs are focus/XKB fragile.
uiohook-napi Native press/release XkbGetKeyboard failed under GNOME Wayland — starts but sees zero keys.
1.2s quiet-timeout “hold” Fake hold from Electron press-only API GNOME does not repeat global shortcuts → auto-release at ~1200ms. Matches production traces exactly.
GNOME GrabAccelerator Shell-owned press/release AccessDenied for normal apps (extension-only).
Tap-to-talk fallback Honest degradation Correct as fallback; wrong as the primary “hold” story.

Recommended Jarvis architecture

  1. Primary (Linux): talk to org.freedesktop.portal.GlobalShortcuts ourselves over the session bus. Bind Ctrl+Shift+J with a human description. Activated → startTalk, Deactivated → releaseTalk.
  2. Why not only Electron globalShortcut: Electron 41’s JS API still only fires on press even when the portal is used. Hold requires the Deactivated signal — so a thin portal client is the production cut.
  3. Identity: portal binds are keyed by app id. Ship/use com.abstergo.jarvis with a matching .desktop and app.setDesktopName (already partially present).
  4. Overlay vs input: absolute overlay positioning can stay on XWayland if needed; the portal is D-Bus and does not require ozone=wayland. Do not couple GPU/compositor flags to the PTT strategy.
  5. Fallbacks: if portal missing/denied → explicit tap-to-talk (no timers). Windows/macOS → keep native hold hooks.
  6. Reject permanently: quiet-timeout holds, synthetic key-repeat modeling, and claiming hold while only press is observed.

Implementation slice

  1. DesktopPortalGlobalShortcuts.ts — CreateSession → BindShortcuts → subscribe Activated/Deactivated; typed + unit-tested against a fake bus.
  2. Wire into DesktopJarvisShell ahead of Electron globalShortcut / uiohook on Linux.
  3. First-run UX: GNOME may show a consent dialog; Control Center should surface “Hold shortcut authorized” / “Tap fallback”.
  4. Keep ozone/GPU decisions independent; only change them if overlay positioning is re-solved for native Wayland later.
  5. Acceptance: physical hold ≥5s stays capturing; release ends once; no 1200ms auto-release in traces.

Local environment facts