feat(calls): system-wide PTT/deafen on desktop via the native key poll (cinny-desktop #2)
CI / Build & Quality Checks (push) Successful in 1m37s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 8s
CI / Trigger Desktop Build (push) Successful in 8s
CI / Playwright smoke (e2e) (push) Successful in 2m20s

PTT and deafen were DOM key handlers and only fired while Lotus (or the EC
iframe) had focus — alt-tab into a fullscreen game and the voice controls
stopped working.

- useCallHotkeys: while a call is joined and the new device-local
  `globalCallHotkeys` setting is on, register {ptt, deafen} bindings with
  the desktop (`set_global_hotkeys`, cleared on leave) and act on its
  `lotus-global-hotkey` press/release events. Events are ignored while
  `document.hasFocus()` so the DOM handlers keep owning the in-focus case
  (editable-field and interactive-element checks, no double toggles). PTT
  press engages the mic exactly like the DOM path (pttActive set before
  unmute), release restores; the existing blur/focus release covers a hold
  that spans a focus change. Same modifier rules as the DOM path
  (`shouldActOnGlobalHotkey`, tested).
- Settings → Calls: "Hotkeys Work Outside the Window" toggle, Tauri only.
- settingsSync: `globalCallHotkeys` is device-local (never synced).
- LOTUS_FEATURES: desktop section entry.

Native side lands in cinny-desktop (src-tauri/src/native/hotkeys.rs).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-17 13:39:51 -04:00
co-authored by Claude Opus 5
parent bd1e61e8cd
commit 00584d7809
6 changed files with 105 additions and 1 deletions
@@ -1644,6 +1644,7 @@ function Calls() {
const [pttMode, setPttMode] = useSetting(settingsAtom, 'pttMode');
const [pttKey, setPttKey] = useSetting(settingsAtom, 'pttKey');
const [deafenKey, setDeafenKey] = useSetting(settingsAtom, 'deafenKey');
const [globalCallHotkeys, setGlobalCallHotkeys] = useSetting(settingsAtom, 'globalCallHotkeys');
const [afkAutoMute, setAfkAutoMute] = useSetting(settingsAtom, 'afkAutoMute');
const [afkTimeoutMinutes, setAfkTimeoutMinutes] = useSetting(settingsAtom, 'afkTimeoutMinutes');
const [callJoinLeaveSound, setCallJoinLeaveSound] = useSetting(
@@ -1977,6 +1978,15 @@ function Calls() {
</Button>
}
/>
{isTauriEnv() && (
<SettingTile
title="Hotkeys Work Outside the Window"
description="Keep Push to Talk and Push to Deafen working while a game or another app has focus. The keys are only watched during a call and are never blocked from other apps. Windows only; some anti-cheat systems may prevent it."
after={
<Switch variant="Primary" value={globalCallHotkeys} onChange={setGlobalCallHotkeys} />
}
/>
)}
</SequenceCard>
<SequenceCard
className={SequenceCardStyle}