Push-to-talk hotkey never registers on Windows: a bare AltLeft is not a registrable shortcut #51

Closed
opened 2026-07-30 15:02:08 +00:00 by aiko · 5 comments
Owner

Push-to-talk on the desktop pal never registers its hotkey on Windows. Observed on launch, from main at f796efa:

[2026-07-30][14:48:01][app_lib][WARN] Failed to register push-to-talk global shortcut: Unable to register hotkey: Unknown VKCode for AltLeft

src-tauri/src/lib.rs:42-44 registers a bare modifier key with no modifier combination:

fn push_to_talk_shortcut() -> Shortcut {
  Shortcut::new(None, Code::AltLeft)
}

Windows implements global shortcuts through RegisterHotKey, which takes a virtual-key code plus a modifier mask. A bare Alt is a modifier, not a key it will accept as the hotkey itself — so the registration fails at startup. mic_toggle_shortcut (Ctrl+Shift+M-shaped) and interrupt_shortcut (Ctrl+Shift+I) both register without complaint, because each carries a real modifier combination.

Why it matters

The comment above the function records exactly why the bare key was chosen:

Held-modifier push to talk for the transparent pal (design 1b): the window is never focused, so the gesture has to be an OS-level hotkey. Registered as the bare left Alt/Option key with no modifier combination, because the design's gesture is holding the key itself.

That reasoning is sound and the mechanism cannot deliver it on Windows. The handler at lib.rs:58-68 is careful to forward both edges — "a held key that only reported its press would leave the mic open" — and none of it ever runs, because nothing is registered.

The failure is a log::warn! and nothing else (lib.rs:123). The app starts normally, the pal appears, and holding Alt does nothing. Voice is v1-blocking on desktop and this is the pal's only way in, so it fails in the quietest possible way on a path that matters.

What is needed

  • A push-to-talk gesture that Windows will actually register, or a mechanism that is not RegisterHotKey. A low-level keyboard hook can observe a bare held modifier where a hotkey registration cannot; whichever is chosen must still deliver both edges.
  • If the gesture changes shape (a chord rather than a bare key), that is a design change to 1b and should be recorded, not slipped in.
  • A failed registration must not be a warning the app shrugs off. Either the gesture works or the pal says it cannot listen.
  • Verified by holding the key on Windows and seeing the mic open and close, not by reading the registration path.
  • #43 — the Rust/webview audio boundary; whatever owns the mic has to be driven by this trigger
  • #35 — moves capture into Rust, but the trigger is a separate problem from the capture
  • #33 — press-to-talk transmitting nothing on a short press; that is the dock's on-screen key, this is the desktop hotkey
  • #18 — the transparent pal this gesture belongs to
Push-to-talk on the desktop pal never registers its hotkey on Windows. Observed on launch, from `main` at `f796efa`: ``` [2026-07-30][14:48:01][app_lib][WARN] Failed to register push-to-talk global shortcut: Unable to register hotkey: Unknown VKCode for AltLeft ``` `src-tauri/src/lib.rs:42-44` registers a **bare modifier key** with no modifier combination: ```rust fn push_to_talk_shortcut() -> Shortcut { Shortcut::new(None, Code::AltLeft) } ``` Windows implements global shortcuts through `RegisterHotKey`, which takes a virtual-key code plus a modifier mask. A bare `Alt` is a modifier, not a key it will accept as the hotkey itself — so the registration fails at startup. `mic_toggle_shortcut` (`Ctrl+Shift+M`-shaped) and `interrupt_shortcut` (`Ctrl+Shift+I`) both register without complaint, because each carries a real modifier combination. ## Why it matters The comment above the function records exactly why the bare key was chosen: > Held-modifier push to talk for the transparent pal (design 1b): the window is never focused, so the gesture has to be an OS-level hotkey. Registered as the bare left Alt/Option key with no modifier combination, because the design's gesture is holding the key itself. That reasoning is sound and the mechanism cannot deliver it on Windows. The handler at `lib.rs:58-68` is careful to forward both edges — *"a held key that only reported its press would leave the mic open"* — and none of it ever runs, because nothing is registered. **The failure is a `log::warn!` and nothing else** (`lib.rs:123`). The app starts normally, the pal appears, and holding Alt does nothing. Voice is v1-blocking on desktop and this is the pal's only way in, so it fails in the quietest possible way on a path that matters. ## What is needed - A push-to-talk gesture that Windows will actually register, or a mechanism that is not `RegisterHotKey`. A low-level keyboard hook can observe a bare held modifier where a hotkey registration cannot; whichever is chosen must still deliver **both** edges. - If the gesture changes shape (a chord rather than a bare key), that is a design change to 1b and should be recorded, not slipped in. - A failed registration must not be a warning the app shrugs off. Either the gesture works or the pal says it cannot listen. - Verified by **holding the key on Windows and seeing the mic open and close**, not by reading the registration path. ## Related - `#43` — the Rust/webview audio boundary; whatever owns the mic has to be driven by this trigger - `#35` — moves capture into Rust, but the trigger is a separate problem from the capture - `#33` — press-to-talk transmitting nothing on a short press; that is the dock's on-screen key, this is the desktop hotkey - `#18` — the transparent pal this gesture belongs to
Author
Owner

Still real, and no longer blocking voice. Recorded so the priority is not misread from the title.

#35's desktop half landed, so press-to-talk works from the on-screen key — a real conversation was held on 2026-07-30. What remains broken is the OS-level hotkey, which lib.rs:43 registers as a bare AltLeft that RegisterHotKey will not accept.

That matters for the surface it was built for: the transparent desktop pal (#18), whose window is never focused, so a key it can hear at all has to be an OS-level hotkey. Voice in the full receiver does not depend on it.

**Still real, and no longer blocking voice.** Recorded so the priority is not misread from the title. `#35`'s desktop half landed, so press-to-talk works from the **on-screen** key — a real conversation was held on 2026-07-30. What remains broken is the OS-level hotkey, which `lib.rs:43` registers as a bare `AltLeft` that `RegisterHotKey` will not accept. That matters for the surface it was built for: the transparent desktop pal (`#18`), whose window is never focused, so a key it can hear at all has to be an OS-level hotkey. Voice in the full receiver does not depend on it.
Author
Owner

Moved to v2, 2026-07-30. Out of the v1 run by decision, not by difficulty.

v1 is "the conversation works" on the desktop, the OnePlus Pad and the Pixel (aiko/denpa#41). Voice already works from the on-screen key, so nothing about the conversation is blocked on this. The hotkey serves exactly one surface — the transparent pal, whose window is never focused — which makes it a convenience on a path that already has a working alternative.

The defect itself stands as written: lib.rs:43 registers a bare AltLeft, and RegisterHotKey rejects a modifier with no key. Nothing here is stale; it is scheduled, not shelved.

**Moved to `v2`, 2026-07-30.** Out of the v1 run by decision, not by difficulty. v1 is "the conversation works" on the desktop, the OnePlus Pad and the Pixel (`aiko/denpa#41`). Voice already works from the on-screen key, so nothing about the conversation is blocked on this. The hotkey serves exactly one surface — the transparent pal, whose window is never focused — which makes it a convenience on a path that already has a working alternative. The defect itself stands as written: `lib.rs:43` registers a bare `AltLeft`, and `RegisterHotKey` rejects a modifier with no key. Nothing here is stale; it is scheduled, not shelved.
Author
Owner

Decided 2026-07-31: the gesture becomes a chord. Design § 1b changes; the bare held key is given up.

RegisterHotKey will not accept a modifier as the key itself, so the held-AltLeft gesture cannot be delivered by the mechanism the pal uses. The alternative that preserves it — a low-level SetWindowsHookEx keyboard hook — can observe a bare held modifier, and is being turned down deliberately: it is a Windows-specific global input-capture path that would have to be owned and kept working forever, in exchange for a gesture that a chord serves nearly as well.

So: a chord with a real modifier combination, Ctrl+Alt-shaped, alongside mic_toggle_shortcut and interrupt_shortcut which register today without complaint. push_to_talk_shortcut at lib.rs:42-44 changes shape; the handler at lib.rs:58-68 does not — both edges still matter, and a held key that only reported its press would still leave the mic open.

This is a change to design § 1b, not an implementation detail. Record it there: the gesture is a held chord, not a held bare key, because the OS will not register the latter globally.

Separately, and regardless of which gesture wins: a failed registration must not be a log::warn! the app shrugs off (lib.rs:123). Today the app starts normally, the pal appears, and holding the key does nothing, with the only evidence in a log nobody is reading. Either the gesture works or the pal says it cannot listen.

Staying on v2 — the priority set on 2026-07-30 is unchanged, since voice already works from the on-screen key and this serves only the transparent pal. What is settled now is the shape, so the ticket is buildable when it comes up.

Acceptance

  • push_to_talk_shortcut registers a modifier chord, and registers cleanly on Windows at startup
  • Both press and release edges still drive the mic
  • Design § 1b records the chord and why the bare key was given up
  • A failed registration surfaces in the pal, not only in the log
  • Verified by holding the chord on Windows and seeing the mic open and close
**Decided 2026-07-31: the gesture becomes a chord. Design § 1b changes; the bare held key is given up.** `RegisterHotKey` will not accept a modifier as the key itself, so the held-`AltLeft` gesture cannot be delivered by the mechanism the pal uses. The alternative that preserves it — a low-level `SetWindowsHookEx` keyboard hook — can observe a bare held modifier, and is being turned down deliberately: it is a Windows-specific global input-capture path that would have to be owned and kept working forever, in exchange for a gesture that a chord serves nearly as well. So: a chord with a real modifier combination, `Ctrl+Alt`-shaped, alongside `mic_toggle_shortcut` and `interrupt_shortcut` which register today without complaint. `push_to_talk_shortcut` at `lib.rs:42-44` changes shape; **the handler at `lib.rs:58-68` does not** — both edges still matter, and a held key that only reported its press would still leave the mic open. This is a change to design § 1b, not an implementation detail. Record it there: the gesture is a held chord, not a held bare key, because the OS will not register the latter globally. **Separately, and regardless of which gesture wins: a failed registration must not be a `log::warn!` the app shrugs off** (`lib.rs:123`). Today the app starts normally, the pal appears, and holding the key does nothing, with the only evidence in a log nobody is reading. Either the gesture works or the pal says it cannot listen. Staying on `v2` — the priority set on 2026-07-30 is unchanged, since voice already works from the on-screen key and this serves only the transparent pal. What is settled now is the shape, so the ticket is buildable when it comes up. ## Acceptance - [ ] `push_to_talk_shortcut` registers a modifier chord, and registers cleanly on Windows at startup - [ ] Both press and release edges still drive the mic - [ ] Design § 1b records the chord and why the bare key was given up - [ ] A failed registration surfaces in the pal, not only in the log - [ ] Verified by holding the chord on Windows and seeing the mic open and close
Author
Owner

This was generated by AI during triage.

Built and merged, with one correction to the decision.

The decision recorded on 2026-07-31 said the gesture becomes a chord, Ctrl+Alt-shaped. Taken literally that is two modifiers and no key, which RegisterHotKey rejects for exactly the reason the bare AltLeft was rejected — the siblings that do register, Ctrl+Shift+M and Ctrl+Shift+I, each carry a real key. So the chord is Ctrl+Alt+Space: the shape that was decided, plus the key the mechanism requires, on the conventional talk key.

Verified on Windows. The app now launches with zero registration warnings. Every launch before it logged Unable to register hotkey: Unknown VKCode for AltLeft — including three of tonight's, which is how this got picked up again.

The handler is untouched: both edges still drive the mic, because a held key that only reported its press would leave it open.

Design § 1b records the chord and why the bare held key was given up — the low-level SetWindowsHookEx hook that could have observed it is a Windows-only global input-capture path to own forever, for a gesture a chord serves nearly as well.

Acceptance

  • push_to_talk_shortcut registers a modifier chord, cleanly, on Windows at startup
  • Both press and release edges still drive the mic — handler unchanged
  • Design § 1b records the chord and why the bare key was given up
  • A failed registration surfaces in the pal, not only in the log — deliberately not done
  • Verified by holding the chord on Windows and seeing the mic open and close — needs a person

The fourth item is left rather than guessed: what the pal says when it cannot listen, and where it says it, is a design decision about a surface with very little room. Registering cleanly makes it a rarer case but not an impossible one — another app can still hold the combo, which is the collision lib.rs's comment already records seeing in the wild.

Staying on v2; the priority set on 2026-07-30 is unchanged.

> *This was generated by AI during triage.* **Built and merged, with one correction to the decision.** The decision recorded on 2026-07-31 said the gesture becomes a chord, `Ctrl+Alt`-shaped. Taken literally that is **two modifiers and no key**, which `RegisterHotKey` rejects for exactly the reason the bare `AltLeft` was rejected — the siblings that do register, `Ctrl+Shift+M` and `Ctrl+Shift+I`, each carry a real key. So the chord is **`Ctrl+Alt+Space`**: the shape that was decided, plus the key the mechanism requires, on the conventional talk key. **Verified on Windows.** The app now launches with **zero** registration warnings. Every launch before it logged `Unable to register hotkey: Unknown VKCode for AltLeft` — including three of tonight's, which is how this got picked up again. The handler is untouched: both edges still drive the mic, because a held key that only reported its press would leave it open. **Design § 1b records the chord** and why the bare held key was given up — the low-level `SetWindowsHookEx` hook that could have observed it is a Windows-only global input-capture path to own forever, for a gesture a chord serves nearly as well. ## Acceptance - [x] `push_to_talk_shortcut` registers a modifier chord, cleanly, on Windows at startup - [x] Both press and release edges still drive the mic — handler unchanged - [x] Design § 1b records the chord and why the bare key was given up - [ ] **A failed registration surfaces in the pal, not only in the log** — deliberately not done - [ ] **Verified by holding the chord on Windows and seeing the mic open and close** — needs a person The fourth item is left rather than guessed: what the pal says when it cannot listen, and where it says it, is a design decision about a surface with very little room. Registering cleanly makes it a rarer case but not an impossible one — another app can still hold the combo, which is the collision `lib.rs`'s comment already records seeing in the wild. Staying on `v2`; the priority set on 2026-07-30 is unchanged.
aiko referenced this issue from a commit 2026-08-07 08:57:16 +00:00
Author
Owner

Closed by 946ef93 — push to talk became a chord Windows will actually register. RegisterHotKey takes a modifier mask plus a key, so a bare AltLeft was never registrable; src-tauri/src/lib.rs:43 records the reason and names this issue.

Closed by `946ef93` — push to talk became a chord Windows will actually register. `RegisterHotKey` takes a modifier mask plus a key, so a bare `AltLeft` was never registrable; `src-tauri/src/lib.rs:43` records the reason and names this issue.
aiko closed this issue 2026-08-10 22:51:48 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiko/denpa#51
No description provided.