Summary
UlanziDialMacOSManager::start() is called unconditionally at every AetherSDR startup on macOS, regardless of whether the user has ever connected or configured a Ulanzi Dial. This causes macOS to present the Input Monitoring privacy permission dialog to every macOS user on first launch of any build that includes #3236.
Root cause
MainWindow constructs m_dialBackend (aliased to UlanziDialMacOSManager on macOS) and calls start() with no guard:
// MainWindow.cpp ~line 4073
m_dialBackend = new UlanziDialBackend; // always created
// ...
QMetaObject::invokeMethod(m_dialBackend, &UlanziDialBackend::start, ...); // always started
Inside start():
IOHIDManagerOpen(mgr, kIOHIDOptionsTypeSeizeDevice); // triggers Input Monitoring prompt
kIOHIDOptionsTypeSeizeDevice is what macOS uses as the trigger for the Input Monitoring permission gate — it means "take exclusive control of this device, preventing the OS from seeing its events." macOS correctly treats any app that requests this flag as potentially capable of intercepting user input, and requires the user to explicitly grant permission in System Settings → Privacy & Security → Input Monitoring.
Why this matters
Input Monitoring is one of macOS's highest-privilege privacy gates — it allows an application to capture input from HID devices even while the app is in the background. The makeMatchDict() function does correctly narrow the device matching to product name "Ulanzi Dial", which limits which device is seized at the IOKit level. However:
- The OS-level permission is app-wide, not device-scoped. Once a user grants Input Monitoring to AetherSDR, the permission cannot be restricted to only the Ulanzi Dial code path by the OS.
- The vast majority of AetherSDR users do not own a Ulanzi Dial and will never benefit from this feature. Prompting them for a sensitive privacy permission they don't need erodes trust and encourages cargo-cult permission grants.
- A user who grants the permission expecting it to be necessary for normal radio operation has no way to know that it is only required for an optional peripheral.
The intent of the code is entirely benign — kIOHIDOptionsTypeSeizeDevice is the correct pattern for preventing dial events from leaking into other apps as spurious keystrokes. The problem is that it fires unconditionally.
Proposed fix
Add a user-visible setting (e.g. Enable Ulanzi Dial, default off) and gate the start() call on it:
if (AppSettings::instance().ulanziDialEnabled())
QMetaObject::invokeMethod(m_dialBackend, &UlanziDialBackend::start, ...);
Users who enable the feature will see the Input Monitoring prompt exactly once, understand why it is needed, and can grant it knowingly. Users who never enable it will never see it.
The same audit should be applied to HidEncoderManager (StreamDeck+ support), which also calls hid_open() — HIDAPI uses kIOHIDOptionsTypeSeizeDevice internally on macOS when opening a device.
Reproduction
Build and run AetherSDR from any commit after #3236 on macOS. On first launch, macOS presents the Input Monitoring permission dialog for AetherSDR.app. No Ulanzi Dial hardware is required to trigger the prompt.
Workaround
Deny the permission. AetherSDR functions normally without it unless a Ulanzi Dial is connected and in use.
Regression introduced by #3236.
Summary
UlanziDialMacOSManager::start()is called unconditionally at every AetherSDR startup on macOS, regardless of whether the user has ever connected or configured a Ulanzi Dial. This causes macOS to present the Input Monitoring privacy permission dialog to every macOS user on first launch of any build that includes #3236.Root cause
MainWindowconstructsm_dialBackend(aliased toUlanziDialMacOSManageron macOS) and callsstart()with no guard:Inside
start():kIOHIDOptionsTypeSeizeDeviceis what macOS uses as the trigger for the Input Monitoring permission gate — it means "take exclusive control of this device, preventing the OS from seeing its events." macOS correctly treats any app that requests this flag as potentially capable of intercepting user input, and requires the user to explicitly grant permission in System Settings → Privacy & Security → Input Monitoring.Why this matters
Input Monitoring is one of macOS's highest-privilege privacy gates — it allows an application to capture input from HID devices even while the app is in the background. The
makeMatchDict()function does correctly narrow the device matching to product name"Ulanzi Dial", which limits which device is seized at the IOKit level. However:The intent of the code is entirely benign —
kIOHIDOptionsTypeSeizeDeviceis the correct pattern for preventing dial events from leaking into other apps as spurious keystrokes. The problem is that it fires unconditionally.Proposed fix
Add a user-visible setting (e.g. Enable Ulanzi Dial, default off) and gate the
start()call on it:Users who enable the feature will see the Input Monitoring prompt exactly once, understand why it is needed, and can grant it knowingly. Users who never enable it will never see it.
The same audit should be applied to
HidEncoderManager(StreamDeck+ support), which also callshid_open()— HIDAPI useskIOHIDOptionsTypeSeizeDeviceinternally on macOS when opening a device.Reproduction
Build and run AetherSDR from any commit after #3236 on macOS. On first launch, macOS presents the Input Monitoring permission dialog for
AetherSDR.app. No Ulanzi Dial hardware is required to trigger the prompt.Workaround
Deny the permission. AetherSDR functions normally without it unless a Ulanzi Dial is connected and in use.
Regression introduced by #3236.