Auto-unkey momentary keyboard PTT/CW on focus loss + modifier-release (#3888). Principle VI.#3890
Conversation
…#3888). Principle VI. Closes two pre-existing stuck-TX windows in the momentary keyboard-keying family (PTT (Hold), CW straight key / paddles), surfaced by #3884's rebindable PTT-hold key. 1. Focus loss / deactivation while held — the KeyRelease that would un-key goes to whatever app gains focus and never reaches our event filter, so the flag stayed set and TX stayed keyed. Add failSafeMomentaryKeyingToRx(): on QEvent::ActivationChange (window deactivated) and, as a macOS belt-and-suspenders, QEvent::ApplicationStateChange (app backgrounded), clear every momentary flag and issue the matching un-key. It reuses the existing requestPttOff / setCw*State paths so the interlock gating and Quindar outro still run, and no-ops when nothing is keyed. 2. Modifier-combo binding released modifier-first — a binding like Ctrl+T released Ctrl-first delivers a KeyRelease whose QKeySequence(modifiers|key) is now plain T and no longer matches Ctrl+T, so the un-key never fired. While a momentary action is active, also accept a release matched by the bound base key alone (keyEventMatchesActionBaseKey), so releasing any part of the combo fails safe to RX. Client-side only; the un-key terminates in the existing xmit / sendCwKey paths with their interlock gating intact. A held transmit key must never strand the transmitter — fail-safe-to-RX is the Principle VI expectation. Blast radius: risk_score=0.044 on handlePttHoldShortcut, 3 high-risk affected (top: MainWindow::eventFilter, RadioModel::transmitModel, RadioModel::isConnected). All are the existing caller/un-key path this change reuses; no signatures change and the new changeEvent branch is a separate ActivationChange early-path that never touches the minimal-mode AppSettings logic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Reviewed the full diff against #3888 and the surrounding shortcut code. This is a clean, well-scoped implementation of both stuck-TX windows the issue describes, and it consistently fails safe to RX. Nice work, and thank you for the thorough comments tying each branch back to the failure mode it closes.
What I verified
- All referenced APIs exist and are used correctly:
ShortcutManager::action(id)(returnsAction*, called from a non-const method ✓),Action::currentKey, and the existingsetCw*State/requestPttOff(PttSource::Mox)/cwTraceNowMs/nextCwTraceIdpaths. The failsafe reuses the same un-key paths as the normal KeyRelease branch, so the interlock gating and Quindar outro still run — good call-out in the comment. keyEventMatchesActionBaseKeycomparescurrentKey[0].key()(key half only) againstev->key(), so a combo released modifier-first still un-keys on the base key's release. The branch only ever clears state on KeyRelease, so it can never spuriously key — strictly the safe direction.failSafeMomentaryKeyingToRxearly-outs when nothing is keyed, and the threesetCw*State(false…)calls each no-op when already released, so calling all of them (plus the ActivationChange + ApplicationStateChange belt-and-suspenders) is idempotent. The double-trigger just no-ops the second time.- CI is green on Linux/macOS/Windows + CodeQL on the head commit.
One non-blocking observation
changeEvent(ActivationChange)will also fire when an in-app modal (e.g. aQDialog::exec()or message box) deactivates the main window, which would un-key a held momentary key. In practice that's both unlikely (PTT held = actively transmitting) and the correct fail-safe direction, so I'd leave it as-is — just flagging it so it's a known, intended behavior rather than a surprise.
No changes requested. Looks good to merge.
🤖 aethersdr-agent · cost: $2.1970 · model: claude-opus-4-8
ten9876
left a comment
There was a problem hiding this comment.
Independently traced every new branch against #3888. All three fail-safe paths (window-deactivate, app-deactivate, modifier-first combo release) only ever move toward RX — none can key the radio. PTT un-key reuses requestPttOff(Mox) so the interlock + Quindar outro still run; the CW setCw*State(false) calls are idempotent; the modifier-tolerant release fallback is triple-guarded (KeyRelease only, only while active, base-key match) so it can't spuriously key. CI green across Linux/macOS/Windows + CodeQL. Closes the stuck-TX windows in #3888. Principle VI verified.
#3961) ## Release documentation prep for **v26.7.1** (2026-07-02) 29 commits since v26.6.5. Docs-only + version bump — no code behavior change. ### Version bump 26.6.5 → 26.7.1 The three canonical locations (per `AGENTS.md`): - `CMakeLists.txt:2` — `project(AetherSDR VERSION 26.7.1)` → `AETHERSDR_VERSION` (the app's version string) - `README.md` — Current version line - `AGENTS.md` — Current version line ### `CHANGELOG.md` Promoted `[Unreleased]` → **`## [v26.7.1] — 2026-07-02`** in house style (v-prefix, em-dash, "N commits since…" opener + headline), authored from all merged PRs and categorized: - **Added:** 3D stacked-trace spectrum (#3899), 3D dBm scale (#3937), in-process NVIDIA BNR + AFX download-on-demand (#3902), TX meter mouse-over readouts (#3936), SWR manual sweep range (#3885), CW sidetone capture (#3895), KiwiSDR metadata scaffolding (#3898), bridge verbs (#3920) - **Changed:** BNR container/NIM backend removed, 60 fps + per-pixel GPU FFT trace (#3958), FlexLib-sourced model capabilities (#3954/#2177), RX speaker-latency cut (#3897), Display pane sections (#3935) - **Fixed:** #3922, #3926, #3941, #3940, #3942, #3921, #3903, #3892, #3924, #3891, #3890, #3900, #3947 - Fresh empty `[Unreleased]` restored above the release block. Internal/CI/docs/self-fix PRs (#3931/#3916/#3934/#3929) omitted per house style. ### `ROADMAP.md` - Cycle header → "post-v26.7.1" - NVIDIA BNR removed from **In flight** (shipped this cycle) - Five v26.7.1 highlights prepended to **Recently shipped** ### `README.md` - Highlights: GPU spectrum bullet now notes the **per-pixel FFT trace at up to 60 fps** and the **3D stacked-trace** spectrum mode ### Reviewer notes - Touches protected paths (`*.md` + `AGENTS.md` Tier-1 + `CMakeLists.txt`) — needs `@aethersdr/infrastructure` sign-off. - Tagging `v26.7.1` and cutting the release build are **not** part of this PR (docs prep only). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Fixes #3888
What was changed
Auto-unkey momentary keyboard PTT/CW on focus loss + modifier-release (#3888). Principle VI.
Files modified
src/gui/MainWindow.cppsrc/gui/MainWindow.hsrc/gui/MainWindow_Shortcuts.cppGenerated by AetherClaude (automated agent for AetherSDR)
🤖 aethersdr-agent · cost: $9.3030 · model: claude-opus-4-8