From 5941ecf767d0bab4aea63d0b58ba94783be34eaf Mon Sep 17 00:00:00 2001 From: AdrianKuriata Date: Wed, 29 Jul 2026 15:56:07 +0200 Subject: [PATCH 1/3] perf(cli): don't force info reads when the invocation sets something Structured output marks every info capability as requested, even when the command line only asked to set a value. On an Audeze Maxwell 2 that turns a 0.07 s write into 2.90 s, because the status read sends 21 packets at 60 ms intervals. Explicitly requested info still works, -b -s 20 -o json reports the battery. --- cli/main.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cli/main.cpp b/cli/main.cpp index 7255980..61425a3 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -1031,10 +1031,26 @@ std::vector toLegacyDeviceList(std::vector& device return legacy; } +// Whether this invocation was asked to set something. +bool hasRequestedAction(const std::vector& devices) +{ + for (const auto& dev : devices) { + for (const auto& req : dev.feature_requests) { + if (req.type == CAPABILITYTYPE_ACTION && req.should_process) { + return true; + } + } + } + + return false; +} + // Enable info requests for extended output formats (JSON, YAML, ENV) +// Only for a pure query: the extra reads can cost far more than the action +// itself (Maxwell 2: -s 20 is 0.07 s, -s 20 -o json is 2.90 s). void enableExtendedInfoRequests(std::vector& devices, bool extended) { - if (!extended) + if (!extended || hasRequestedAction(devices)) return; for (auto& dev : devices) { From 2f3c5fa667467a7c4afdc3c5b88c8049f98641a6 Mon Sep 17 00:00:00 2001 From: AdrianKuriata Date: Fri, 14 Aug 2026 14:18:33 +0200 Subject: [PATCH 2/3] perf(cli): check for actions per device, after the multi-device guard Review feedback on three points. enableExtendedInfoRequests ran before handleMultiDeviceActions, so it judged action requests that were neutralized one line later. It now runs after, and the ordering is spelled out at the call site so it does not get swapped back. hasRequestedAction looked at every device at once, so an action on one headset switched off the info reads of another that never had one. It now takes a single device and the caller skips per device. The output shape changes, so API_VERSION goes to 1.5. Updated the sample response in docs/LIBRARY_USAGE.md to match. --- cli/main.cpp | 26 ++++++++++++++++---------- cli/output/output.cpp | 4 +++- docs/LIBRARY_USAGE.md | 2 +- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/cli/main.cpp b/cli/main.cpp index 61425a3..d0be489 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -1031,14 +1031,13 @@ std::vector toLegacyDeviceList(std::vector& device return legacy; } -// Whether this invocation was asked to set something. -bool hasRequestedAction(const std::vector& devices) +// Whether this device was asked to set something. Only counts requests that are +// still live, so it has to run after handleMultiDeviceActions has had its say. +bool hasRequestedAction(const DiscoveredDevice& device) { - for (const auto& dev : devices) { - for (const auto& req : dev.feature_requests) { - if (req.type == CAPABILITYTYPE_ACTION && req.should_process) { - return true; - } + for (const auto& req : device.feature_requests) { + if (req.type == CAPABILITYTYPE_ACTION && req.should_process) { + return true; } } @@ -1047,13 +1046,17 @@ bool hasRequestedAction(const std::vector& devices) // Enable info requests for extended output formats (JSON, YAML, ENV) // Only for a pure query: the extra reads can cost far more than the action -// itself (Maxwell 2: -s 20 is 0.07 s, -s 20 -o json is 2.90 s). +// itself (Maxwell 2: -s 20 is 0.07 s, -s 20 -o json is 2.90 s). Per device, so +// an action on one headset does not silence the info reads of another. void enableExtendedInfoRequests(std::vector& devices, bool extended) { - if (!extended || hasRequestedAction(devices)) + if (!extended) return; for (auto& dev : devices) { + if (hasRequestedAction(dev)) + continue; + for (auto& req : dev.feature_requests) { if (req.type == CAPABILITYTYPE_INFO && !req.should_process && dev.hasCapability(req.cap)) { req.should_process = true; @@ -1217,8 +1220,11 @@ int main(int argc, char* argv[]) // Initialize and configure feature requests initializeFeatureRequests(devices, opts); bool extended = opts.output_format == OUTPUT_YAML || opts.output_format == OUTPUT_JSON || opts.output_format == OUTPUT_ENV; - enableExtendedInfoRequests(devices, extended); + // Order matters: handleMultiDeviceActions neutralizes action requests, and + // enableExtendedInfoRequests has to see the result of that, not the requests + // as they were parsed. handleMultiDeviceActions(devices, opts); + enableExtendedInfoRequests(devices, extended); // Main loop do { diff --git a/cli/output/output.cpp b/cli/output/output.cpp index d193cf0..1ee16bd 100644 --- a/cli/output/output.cpp +++ b/cli/output/output.cpp @@ -23,7 +23,9 @@ using namespace headsetcontrol::serializers; // Constants // ============================================================================ -constexpr std::string_view API_VERSION = "1.4"; +// 1.5: an invocation that performs an action no longer reports info it was not +// explicitly asked for. +constexpr std::string_view API_VERSION = "1.5"; constexpr std::string_view APP_NAME = "HeadsetControl"; // ============================================================================ diff --git a/docs/LIBRARY_USAGE.md b/docs/LIBRARY_USAGE.md index cf8afc0..419dd80 100644 --- a/docs/LIBRARY_USAGE.md +++ b/docs/LIBRARY_USAGE.md @@ -28,7 +28,7 @@ headsetcontrol -o env { "name": "HeadsetControl", "version": "3.0.0", - "api_version": "1.4", + "api_version": "1.5", "device_count": 1, "devices": [ { From ab84bf1d688408b8a7167a985a0335997584b86a Mon Sep 17 00:00:00 2001 From: AdrianKuriata Date: Fri, 14 Aug 2026 14:25:05 +0200 Subject: [PATCH 3/3] style: satisfy clang-format 18 in the files this PR touches cpp-linter checks whole files, not changed lines, and cli/main.cpp and cli/output/output.cpp both carry formatting violations that predate this PR. They are not reachable from master's own CI, which only runs clang-format on pull requests, so they went unnoticed. clang-format 18.1.3 output, nothing hand-written. --- cli/main.cpp | 6 +++--- cli/output/output.cpp | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cli/main.cpp b/cli/main.cpp index d0be489..007cff8 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -37,8 +37,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -420,7 +420,7 @@ class HIDEnumeration { if (devices_) hid_free_enumeration(devices_); } - HIDEnumeration(const HIDEnumeration&) = delete; + HIDEnumeration(const HIDEnumeration&) = delete; HIDEnumeration& operator=(const HIDEnumeration&) = delete; hid_device_info* get() const { return devices_; } @@ -1003,7 +1003,7 @@ void setupSignalHandler() #ifdef _WIN32 signal(SIGINT, signalHandler); #else - struct sigaction act {}; + struct sigaction act { }; act.sa_handler = signalHandler; sigaction(SIGINT, &act, nullptr); #endif diff --git a/cli/output/output.cpp b/cli/output/output.cpp index 1ee16bd..68b2d56 100644 --- a/cli/output/output.cpp +++ b/cli/output/output.cpp @@ -181,8 +181,7 @@ void processFeatureRequest(const FeatureRequest& req, DeviceData& dev, std::stri for (const auto& preset : presets->presets) { preset_data.push_back(EqualizerPresetData { .name = preset.name, - .values = preset.values - }); + .values = preset.values }); } dev.equalizer_presets = std::move(preset_data); }