privacy(hid): gate Ulanzi Dial + StreamDeck+ behind opt-in toggles to stop the unconditional macOS Input Monitoring prompt (#3257). Principle III.#3258
Conversation
… stop the unconditional macOS Input Monitoring prompt (#3257). Principle III. `MainWindow` started `UlanziDialBackend` and `HidEncoderManager` on every launch. The Ulanzi macOS backend calls `IOHIDManagerOpen(..., kIOHIDOptionsTypeSeizeDevice)` and HIDAPI's macOS backend opens with the same flag internally — both trip the TCC Input Monitoring prompt on first launch even on machines with no supported hardware. Existing reproduction: K5PTB's screenshot of AetherSDR appearing in System Settings → Privacy & Security → Input Monitoring with no peripheral ever connected. Fix: - New `UlanziDialEnabled` / `HidEncoderEnabled` AppSettings keys, default "False". `MainWindow` ctor gates the `start()` / `loadSettings()` calls on them. - New "USB Control Surfaces" group at the top of the Serial tab in `RadioSetupDialog`, with one checkbox per backend. Flipping a checkbox on emits `serialSettingsChanged()`, which `MainWindow:: wireRadioSetupDialogSignals` now handles by kicking `start()` / `loadSettings()` — so the TCC prompt fires the moment the user affirmatively asks for the feature, with context, instead of on every launch. - `wireRadioSetupDialogSignals` `connect()` moved out of the `#ifdef HAVE_SERIALPORT` wrap so the Ulanzi/HID branch runs even in builds without serial-port support (the dial/encoder backends exist regardless). - `NSInputMonitoringUsageDescription` added to `packaging/macos/ Info.plist.in` so the prompt — when it does fire — carries an app- supplied rationale explaining it is only for optional USB control surfaces. `start()` and `loadSettings()` are idempotent (each guards re-open), so re-firing on unrelated settings changes is harmless. Principle III is the load-bearing citation: the new user-facing labels ("Enable Ulanzi Dial", "Enable HID encoders / StreamDeck+ (RC-28, PowerMate, ShuttleXpress, …)") match the visible peripheral names users recognize, and the Info.plist rationale points to "Preferences → Serial" — the exact tab name registered in `RadioSetupDialog.cpp:472`. Blast radius: HidEncoderManager::loadSettings risk_score=0.306, 3 high-risk affected (top: MainWindow::MainWindow, AppSettings::instance, AppSettings::value); RadioSetupDialog edit risk_score=0.183, 3 high-risk affected (top: MainWindow::MainWindow, MainWindow::wirePanadapter, MainWindow::buildMenuBar). All callers are structural bridges to MainWindow's ctor and the AppSettings singleton; edits are purely additive (new if-gate, new connect branch, new QGroupBox), no symbol contracts change. Verification: - `cmake` is not installed on this host so no local build was possible — relying on CI for compile + link. - No cross-TU symbol motion, no new project-header includes, no Q_LOGGING_CATEGORY changes — test-target link audit not applicable. - Manual UX path to verify post-merge: fresh macOS install, build, launch — confirm no Input Monitoring prompt; open Radio Setup → Serial, tick "Enable Ulanzi Dial" — confirm prompt fires now with rationale text. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Without this, the new HidEncoderEnabled=False default in this PR silently turns off existing users' working HID encoders (RC-28, PowerMate, ShuttleXpress, StreamDeck+) until they discover the Radio Setup → Serial checkbox. Linux + Windows hidraw paths have no TCC concern, so honouring the prior intent on first launch is the right default for them. Mechanism: at MainWindow ctor, if HidEncoderEnabled isn't set at all (first launch after upgrade), copy HidEncoderAutoDetect's value into it. Subsequent toggles in the dialog own the key. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Pushed a 15-line migration fixup on top so existing The privacy fix in the original commit is unchanged — macOS Input Monitoring still only prompts when the user explicitly opts in. 73, Jeremy KK7GWY & Claude (AI dev partner) |
There was a problem hiding this comment.
Thanks for the privacy-first framing — NSInputMonitoringUsageDescription (matching kIOHIDOptionsTypeSeizeDevice), opt-in defaults, and re-firing start() / loadSettings() from the existing serialSettingsChanged channel are all the right moves. The conditional refactor in wireRadioSetupDialogSignals (moving connect() out of the HAVE_SERIALPORT guard so the lambda still reaches the Ulanzi/HID re-evaluation, while keeping the serial-port and setFlexControlConnectionStatus bodies guarded) reads correctly.
One issue worth fixing before merge, plus a couple of smaller notes.
HidEncoderEnabled migration is effectively always-off (regression)
if (!s.contains("HidEncoderEnabled")) {
const bool hadAutodetect =
s.value("HidEncoderAutoDetect", "False").toString() == "True";
s.setValue("HidEncoderEnabled", hadAutodetect ? "True" : "False");
}HidEncoderAutoDetect is never written anywhere in the tree — grep only finds two readers (HidEncoderManager.cpp:262 and MainWindow.cpp:7076), both with default "True". No UI writes it, and HidEncoderManager::loadSettings() reads it without ever calling setValue. So in practice the key is absent from AppSettings for essentially every existing user, the migration falls through to the "False" fallback, and every user who had a working RC-28 / PowerMate / Shuttle / StreamDeck+ on the prior build silently loses it on upgrade until they discover the new checkbox.
The stated intent ("Honour that prior intent on first launch") only fires if someone manually edited AppSettings. Two ways to fix:
- Match the actual observed default:
s.value("HidEncoderAutoDetect", "True")— this preserves prior behavior for the autodetect-by-default world and is a one-character change. - Or use a different evidence-of-prior-use signal: e.g. presence of
HidEncoderInvertDir, or any key under a HID prefix, as proof the user has been here before.
Without one of these, the "migration" block is dead code and the PR is a silent regression for anyone currently using a HID encoder — which is a meaningfully larger user base than the Ulanzi case below.
Ulanzi has no migration (acceptable but worth a release-note callout)
There's no prior UlanziDialEnabled key to migrate from, so the same silent-breakage problem applies but is genuinely unavoidable. Probably fine, but worth flagging in release notes so existing Ulanzi users know to re-enable in Preferences → Serial. Consider also a one-shot "we changed this" toast on first launch if other privacy-gated features land later.
Minor: comment on serialSettingsChanged
The new comment ("the 'external-device settings changed' signal in practice") captures the de-facto semantics nicely. If you ever revisit this area, a rename to externalDeviceSettingsChanged would make it self-documenting — not for this PR.
Everything else (AppSettings usage, m_dialBackend / m_hidEncoder null guards, idempotent re-fire of start() / loadSettings(), the Info.plist copy with its "only after you enable" qualifier) looks good. Just the HID default-flip needs sorting and this is ready.
🤖 aethersdr-agent · cost: $8.9725 · model: claude-opus-4-7
…-enables on upgrade (#3493) Two root causes for StreamDeck+ going dark after the v26.6 opt-in gate (#3258): 1. packaging/linux/60-hid-encoders.rules was missing the StreamDeck+ entry (0fd9:0084). Without it hidraw is root-only on Linux, so hid_open() fails silently and the device never initialises. 2. The v1 migration in MainWindow read HidEncoderAutoDetect with default "False", but the old code treated the key's *absence* as True (implicit default on line 8029). Users who never touched that setting had HID implicitly on, yet the migration wrote HidEncoderEnabled=False for them. V2 migration detects and corrects installs that were already written with the wrong default. Squashed-from: #3493 Co-authored-by: Tony KX3H <276457091+motoham88@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: jensenpat <patjensen@gmail.com>
… stop the unconditional macOS Input Monitoring prompt (aethersdr#3257). Principle III. (aethersdr#3258) ## Summary Fixes aethersdr#3257 ### What was changed privacy(hid): gate Ulanzi Dial + StreamDeck+ behind opt-in toggles to stop the unconditional macOS Input Monitoring prompt (aethersdr#3257). Principle III. ### Files modified - `packaging/macos/Info.plist.in` - `src/gui/MainWindow.cpp` - `src/gui/RadioSetupDialog.cpp` ``` packaging/macos/Info.plist.in | 2 ++ src/gui/MainWindow.cpp | 53 +++++++++++++++++++++++++++++++++++++------ src/gui/RadioSetupDialog.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 7 deletions(-) ``` --- Generated by AetherClaude (automated agent for AetherSDR) --- <sub>🤖 aethersdr-agent · cost: $18.1449 · model: claude-opus-4-7</sub> --------- Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Jeremy Fielder <kk7gwy@aethersdr.com>
…-enables on upgrade (aethersdr#3493) Two root causes for StreamDeck+ going dark after the v26.6 opt-in gate (aethersdr#3258): 1. packaging/linux/60-hid-encoders.rules was missing the StreamDeck+ entry (0fd9:0084). Without it hidraw is root-only on Linux, so hid_open() fails silently and the device never initialises. 2. The v1 migration in MainWindow read HidEncoderAutoDetect with default "False", but the old code treated the key's *absence* as True (implicit default on line 8029). Users who never touched that setting had HID implicitly on, yet the migration wrote HidEncoderEnabled=False for them. V2 migration detects and corrects installs that were already written with the wrong default. Squashed-from: aethersdr#3493 Co-authored-by: Tony KX3H <276457091+motoham88@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: jensenpat <patjensen@gmail.com>
Summary
Fixes #3257
What was changed
privacy(hid): gate Ulanzi Dial + StreamDeck+ behind opt-in toggles to stop the unconditional macOS Input Monitoring prompt (#3257). Principle III.
Files modified
packaging/macos/Info.plist.insrc/gui/MainWindow.cppsrc/gui/RadioSetupDialog.cppGenerated by AetherClaude (automated agent for AetherSDR)
🤖 aethersdr-agent · cost: $18.1449 · model: claude-opus-4-7