Skip to content

feat(vfo): accent ADSP launcher when a client NR module is active (#3800)#3822

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/3800-nr-indicator
Jun 26, 2026
Merged

feat(vfo): accent ADSP launcher when a client NR module is active (#3800)#3822
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/3800-nr-indicator

Conversation

@jensenpat

Copy link
Copy Markdown
Collaborator

Problem (#3800)

"if you turn on NR4, there is no visual indication even if you select DSP. You need to dig deeper and click on ADSP to see which one is active … at least HIGHLIGHT the DSP button if any DSP NR function is activated."

AetherSDR's client-side NR modules (NR2 / NR4 / MNR / BNR / DFNR / RN2) are global AudioEngine state that lives behind the ADSP launcher. That button is created setCheckable(false) with a fixed kDspToggle stylesheet, so it never changes appearance — enabling NR4 left no cue anywhere on the main surface. The radio-side toggles (NR/NRL/NRS/…) at least look pressed; the client modules had nothing.

Fix

Accent the ADSP launcher whenever any of the six client NR modules is on:

  • New kDspToggleActive stylesheet — the same green "active" look the radio-side toggles get via :checked — applied to m_aetherDspBtn when active, reverted to kDspToggle when all are off.
  • The active state is folded into the button's accessibleName ("AetherDSP Settings (NR active)"), so screen readers — and the agent automation bridge — can read the cue without a screenshot. A stable objectName (aetherDspBtn) keeps programmatic targeting independent of that now-dynamic name.
  • MainWindow::wireVfoWidget() connects the six AudioEngine::*EnabledChanged signals (the modules are global engine state) to every slice's VfoWidget and syncs the current state when a slice is created. Connections are bound to the widget so they drop when the slice closes.

Minimal and self-contained: VfoWidget.{h,cpp} + MainWindow_Wiring.cpp, 57 insertions, no behavior change to the launcher's click action. The "ideal" top-right on-screen NR badge from the issue remains a follow-up (it's a two-render-path SpectrumWidget change).

How the agent automation bridge proved it

Built on a FLEX-8400M (KI6BCJ) and driven through the bridge (tools/automation_probe.py) — RX-only, no TX:

State read (dumpTree of aetherDspBtn.accessibleName):

Step bridge action ADSP accessibleName
baseline AetherDSP Settings
NR4 on invoke NR4 clickchecked AetherDSP Settings (NR active)
NR4 off invoke NR4 clickunchecked AetherDSP Settings

OR-of-modules (RN2): RN2 on → active; NR2 toggled while RN2 still on → stays active; RN2 off → inactive. Confirms the indicator tracks the OR, not a single module.

Visual (grab aetherDspBtn): active = green accent, inactive = default navy.

inactive active (NR4 on)
navy kDspToggle green kDspToggleActive

Final state verified clean: indicator inactive (all client NR off), transmitting == false.

The bridge fidelity work used to drive this proof (per-PID socket discovery, grab of a specific widget) is not included in this PR.

Closes #3800

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat

…thersdr#3800)

Enabling a client-side NR module (NR2 / NR4 / MNR / BNR / DFNR / RN2) left
no cue anywhere on the main surface — the only way to tell one was on was to
reopen the AetherDSP applet. The ADSP launcher is created non-checkable with a
fixed kDspToggle stylesheet, so it never changed appearance.

Add a green "active" accent (mirroring the radio-side toggles' :checked look)
applied to the ADSP button whenever any of the six client NR modules is on.
The state is folded into the button's accessibleName ("AetherDSP Settings
(NR active)") so screen readers — and the agent automation bridge — can read
it without a screenshot, and a stable objectName ("aetherDspBtn") keeps bridge
targeting independent of that dynamic name.

MainWindow wires the six AudioEngine *EnabledChanged signals (the modules are
global engine state) to every slice's VfoWidget and syncs current state on
slice creation. Closes the reporter's minimum ask; the top-right on-screen
NR badge remains a follow-up.

Closes aethersdr#3800

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat
@jensenpat jensenpat marked this pull request as ready for review June 26, 2026 00:37
@jensenpat jensenpat requested a review from a team as a code owner June 26, 2026 00:37

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff against the surrounding code — this is clean, minimal, and correct. Thanks @jensenpat for the well-documented change and the bridge proof.

Verified against the codebase:

  • All six AudioEngine accessors and *EnabledChanged signals exist with the exact names wired here (AudioEngine.h:168-215, :502-509), so the OR and the six connect()s resolve correctly.
  • The connections use w as the receiver/context object, so they auto-drop when the slice's VfoWidget is destroyed — correct RAII, no dangling slot into a freed widget. The if (!m_audio) return; guard inside the lambda covers engine teardown too.
  • setAetherDspActive's early-return on the inactive baseline is safe: the "AetherDSP Settings" accessibleName is already set at construction (VfoWidget.cpp:1644), so the a11y baseline is never skipped. And buildTabContent() runs in the constructor (:1209), so m_aetherDspBtn always exists before wireVfoWidget calls syncAetherDsp() — no ordering hazard where the button gets built after the state was already latched.
  • rn2TxEnabledChanged (the TX mic pre-amp path, :506) is correctly excluded from the OR — this cue is about RX-side client NR only.

One thing worth noting (not a blocker): because these modules are global AudioEngine state, every open slice's ADSP button lights up green when any one is on, not just the slice whose applet enabled it. That's consistent with the modules being global and your PR body calls it out, so it's the right behavior — just flagging it for whoever merges, in case a future "per-slice NR" notion ever lands.

Conventions (AppSettings, RAII, C++20) all good; no new settings keys, no system-boundary error handling needed. Nice use of objectName to keep programmatic targeting stable independent of the now-dynamic accessibleName. LGTM.


🤖 aethersdr-agent · cost: $3.7223 · model: claude-opus-4-8

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, correct UI/a11y fix. Verified: the OR-of-modules logic re-queries all six engine flags on every *EnabledChanged so toggling one off while another is on correctly stays active (the RN2 case); all six connect()s use w as receiver so they auto-drop on slice close (no dangling fire), with an m_audio null-guard; the trailing syncAetherDsp() applies current global state to a freshly-wired slice; setAetherDspActive is idempotent + null-guarded and swaps kDspToggle↔kDspToggleActive with no change to the click action. Good a11y (accessibleName '(NR active)') + stable objectName for the bridge. No TX/audio-behavior surface. Thanks @jensenpat.

@ten9876 ten9876 merged commit 8919b54 into aethersdr:main Jun 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UI to show what NR function is in use

2 participants