Cloud III S: Fix HID dedup, packet size, add EQ CLI#25
Cloud III S: Fix HID dedup, packet size, add EQ CLI#25NubeBuster wants to merge 46 commits intoLennardKittner:devfrom
Conversation
Two related fixes for Cloud III S Wireless device handling: 1. src/devices/mod.rs: Deduplicate HID device handles by path using HashSet + open_path() instead of open(vid, pid). The bug occurred when the same /dev/hidrawN was opened N times (once per HID collection), causing N^2 buffer flooding with stale responses. 2. src/devices/cloud_iii_s_wireless.rs: Changed BASE_PACKET size from 62 to 64 bytes to match the HID report descriptor (Report Count=63 + 1 report ID byte = 64 total). Vibe Agent Swarm Orchestrated by Claude Opus 4.6 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Supports band references by index (0-9) or frequency suffix (e.g. 1khz, 250hz, case-insensitive). Comma-separated multi-band setting via --eq. Updated README with EQ documentation. Only Cloud III S Wireless implements EQ. Vibe Agent Swarm Orchestrated by Claude Opus 4.6 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New: EQ CLI redesign (72e9b37)
CLI changes:
|
The EQ protocol supports multiple (band, hi, lo) triplets in a single 64-byte packet. Consolidated set_equalizer_band_packet into set_equalizer_bands_packet (handles both single and batch). Split CLI into --eq (full profile, unspecified bands reset to 0dB) and --eq-band (adjust specific bands only, repeatable, comma-separated). Updated README to match. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
981021b to
72e9b37
Compare
The --eq-band argument was using kebab-case while all other multi-word arguments used snake_case, creating an inconsistent CLI interface. This standardizes all arguments to kebab-case (the Rust convention) while maintaining backwards compatibility through aliases. Changes: - --automatic-shutdown (--automatic_shutdown still works) - --enable-side-tone (--enable_side_tone still works) - --side-tone-volume (--side_tone_volume still works) - --enable-voice-prompt (--enable_voice_prompt still works) - --surround-sound (--surround_sound still works) - --mute-playback (--mute_playback still works) Help text now displays the kebab-case variants to match --eq-band, but old snake_case arguments continue to work as hidden aliases. Also removed unused "derive" feature from clap dependency. The codebase uses the builder pattern for runtime device detection (arguments are conditionally shown based on device capabilities), which requires the builder API rather than derive macros. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Follow-up to 27e38ca fixing CLI argument naming. Updates: - README.md: All argument examples now show kebab-case - src/main.rs: Tray app --refresh-interval (with backwards compat alias) Ensures documentation matches the code changes. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- New src/eq/ module: types, constants, preset persistence (XDG config via dirs crate), file watcher (notify crate) - 5 built-in presets: Flat, Bass Boost, Treble Boost, V-Shape, Vocal - Tray: EQ Preset submenu with RadioGroup, sends commands via mpsc channel to main loop for device writes - Main loop: processes tray commands, watches config dir for external changes, auto-applies saved EQ settings on device connect - Ratatui TUI editor (default feature, disable with --no-default-features) with live preview, preset management, colored bar graph - CLI: --eq launches TUI editor, old --eq renamed to --eq-profile - Fix eq-band arg ID mismatch (was snake_case ID with kebab-case lookup) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- TUI editor (editor.rs): startup conflict dialog when tray-selected profile differs from TUI state, confirm-quit dialog (Save TUI / Save As / Undo Changes), EditorResult enum for typed save/cancel with profile restoration, live EQ preview to headset, r to reset to reference preset, Shift+0 for flat, Ctrl+C saves and exits - Preset persistence (presets.rs): individual preset files in eq_presets/ directory instead of monolithic JSON, SelectedProfile (selected_profile.json) stores only active preset name, builtin fallback in load_preset, recursive file watcher for config dir - CLI (hyper_headset_cli.rs): handle EditorResult::Saved and Cancelled, always sync TUI.json on save, restore selected profile on cancel - Tray (status_tray.rs): persist selected_profile.json immediately in select callback to avoid race condition with TUI reads - Main loop (main.rs): removed redundant profile save from command handler, removed auto-apply EQ on connect Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…synced` field on SelectedProfile, sync-aware tray UI (tooltip shows EQ status, radio selection only when synced, presets disabled when offline), auto-apply unsynced profile on reconnect, save synced=false in tray select callback (main loop confirms sync), TUI editor saves with synced=true since bands are sent live. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Probe connected status before applying EQ to prevent false "applied" when headset is off. Detect disconnect from passive refresh notifications to exit monitor loop early. Add probe_connected_status() trait method. All workaround code is tagged with WORKAROUND(firmware-no-response) and TODO: Remove comments for easy identification and future cleanup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… before EQ apply and alongside passive refresh for faster reconnect detection, detect connected→disconnected transitions, auto-sync on reconnect via transition detection in inner loop. All workaround code tagged with WORKAROUND(firmware-no-response) and TODO comments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…set_equalizer so other headsets skip the ~550ms/cycle overhead, remove dead disconnect-transition break (was_connected already updated before check) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add eq-popup feature (gtk4 dep) for a left-click EQ preset popup window that stays open after selection and shows apply status. Architecture: three threads (ksni, GTK4, main) connected via mpsc channels. New files: - src/eq/popup.rs — cross-platform PopupState/PopupCommand/EqPopupController trait - src/eq/popup_gtk4.rs — GTK4 implementation: dedicated thread with glib::MainLoop, polls mpsc at 50ms, undecorated window with radio presets, focus-out dismiss, toggle Tray improvements (unconditional, no new deps): - escape_label() for ksni accelerator prefix (single underscore) - Show "(applying...)" next to unsynced preset in right-click menu - Always show radio selection on active preset (was hidden when unsynced) - TrayHandler.hide_popup() on device disconnect - Feature tips when eq-popup/eq-editor not compiled Cargo.toml: default features now empty (eq-editor was default), added eq-popup = ["gtk4"]. README documents GTK4 dep and features. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wireshark capture of NGenuity confirmed firmware only processes one EQ band per HID write. Our batch code packed all 10 bands into a single packet, so only band 0 took effect. This broke: preset apply (tray/popup), TUI startup sync, preset select (p), revert (r), flat (Shift+0), exit undo, and CLI --eq-profile. Changes: - Device trait: set_equalizer_bands_packet → set_equalizer_bands_packets (returns Vec<Vec<u8>> — one packet per band) - Cloud III S: build individual 64-byte packets per band - All callers (main.rs, CLI, TUI editor): iterate packets with 3ms delay - TUI editor: deduplicate send_all_bands/restore_original into shared send_bands_to_device helper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The GTK4 popup appeared centered on screen instead of anchored to the tray icon. Proper positioning requires gtk4-layer-shell (Wayland-only) or native Plasma widget architecture. The right-click context menu already provides the EQ Preset submenu, so the popup adds no value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Claude's test setup for me to listen if the batch eq set packet worked was faulty and made me think it works. For the rest these commits port the equalizer TUI from the python script to this repo and also integrate it in the tray. So you can now create and load presets. Also I tried to get a more native popup for the tray widget. But claude misunderstood which interface I wanted and implemented the wrong one. Upon correction it turns out it's just much better to not bother and keep the right click menu that does not add dependencies and maintenance burden supporting all platforms and distributions. |
|
Thanks for the PR. I will look through the code and try to test it on my headset as well, since a recent update added EQ support. |
Make serde, serde_json, dirs, and notify optional dependencies pulled in by the new eq-support feature. eq-editor now implies eq-support. Without eq-support: tray shows a hint menu item, CLI prints a tip, and no preset/file-watcher code is compiled. CLI --eq-profile and --eq-band remain available (they use the Device trait directly). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents ratatui/crossterm from leaking into the tray binary when built with --features eq-editor. The editor is only used by the CLI, so it belongs there. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hi there. I stuck to the tray read only behaviour as I figured that would keep it's footprint and maintenance burden smaller. Without ever touching the EQ settings in CLI the tray widget provides standard EQ profiles to select, without supporting editing the profiles. The file watching is there so that if the profile is switched in the cli the tray picks up on it. It's a small detail but I am no fan of out-of-sync UI states. The tray also saves the selected profile so that it remembers the selected profile between tray restarts. The file watching is extremely lightweight and a Linux feature, so performance wise it's not significant. For good measure I will add a feature flag for the EQ stuff for the tray to reduce the binary footprint.
Hold on, why does the hyper_headset grow with eq-editor??? @claude pls fix...
Bruh
This is Rust, btw, so YES IT IS WORTH THE CHURN YOU F*****Y CLANKER.
|
to clarify, with this PR the tray only stores which profile is currently selected. The tray does not write any settings to the profile settings. But yes, it does send the EQ settings to the headset. So in terms of being read-only from the headset, the tray app now becomes read, write-eq-settings-only to the headset. Does this not align with the vision of the repo? I figured that being able to switch the EQ profile from the widget is a worthy feature. I left any EQ profile editing/storing/management to the CLI. |
This makes sense if it is not possible to query the EQ state of the headset.
This is true performance and was not my concern.
Ah, I see. After only skimming the code. I thought the tray app just watched the file and applied changes when it detected them, but it actually also has a little menu to select a preset. Yeah, I like it. Good job. I will try to get EQ working on my headset and review your code more thoroughly. |
LennardKittner
left a comment
There was a problem hiding this comment.
I added some comments, but overall it is already looking pretty good.
Okay, I will solve this crazy mystery today. After a month of having to manually push the button on the headset to know the battery level, having to physically inspect my external, often latent objective space to see if I am currently charging my headset, the tray will now assist me in providing this information again. While trying to install my second GPU once more, coming to the same conclusion that I still indeed that extra PSU cable that I still don't have, I've been able to reduce the variables down to only a few BIOS settings. I didn't touch the USB stuff this time. I'll try not to ramble too much but I cannot resist sharing the confusion.
8d2f8fb#diff-e609f4b99af5543736a42cb253146c665862e1e5578c79182a3776388ff74f67L37-R40 -const BASE_PACKET: [u8; 62] = {
- let mut packet = [0u8; 62];
+const BASE_PACKET: [u8; 64] = {
+ let mut packet = [0u8; 64];This spiked a vague memory for me. Back to this PRIt's been 3 weeks and in the meantime #LennardKittner/cloud_III_s spawned.
flowchart TD
A[LennardKittner:cloud_III_s] --> M([Merged]):::done --> B[("this PR<br/>cloud-iii-s-support")]
B --> P([On Approval]):::todo --> J(( )):::tap --> C[LennardKittner:dev]:::tainted
D>Dorment Claude Code Payload™]:::source -.-> EP([En passant]):::good -.-> J
C --> F[LennardKittner:main]:::tainted
F -.-> PT([Patience]):::good -.-> G[AUR]:::tainted
G -.-> PAC([Escape Confinement]):::good -.-> MF[(Mainframe)]:::pwned
MF -.->|Execute Plan| GL[(Keep Summer Safe)]
classDef tap fill:#000,stroke:#000,r:4
classDef tainted stroke:#cdf,color:#cdf
classDef source fill:#2b3b4b,stroke:#4ac,color:#cdf
classDef done fill:#1a3a1a,stroke:#4c4,color:#dfd
classDef todo fill:#1a2a3a,stroke:#4ac,color:#cdf
classDef pwned fill:#002255,stroke:#036,stroke-width:3px,color:#fff
|
The non-Linux main never seeded eq_preset_options, never wired the config-dir watcher, and never auto-synced unsynced profiles on connect, so the SelectEQ tray submenu hit its empty-options short-circuit and macOS users saw no EQ menu (matching Lennard's review observation). Move the per-connection EQ glue into a new src/eq/runtime.rs module (init_device_eq_state, drain_watcher, refresh_preset_options, maybe_sync_on_reconnect) and call it from both main()s. Non-Linux explicitly re-sends DeviceProperties after a watcher event so the tray updates without waiting for the next refresh tick. Also document the disk-state staleness caveat in the CLI: the firmware does not respond to EQ-query reads, so the on-disk profile is the only source of truth and may be stale if changed via another tool (e.g. NGenuity on Windows). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
debug_println! is a no-op in release builds, so `event` was referenced only inside a format string that gets compiled out. Mirror the existing `_e` pattern from the surrounding write_hid_report error branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Linux watcher path called tray_handler.reload_presets(), which mutated the tray's cached device_properties — but every iteration of the main loop ends with tray_handler.update(device.get_device_state()), which clobbers the tray cache with whatever is on the device side. Since nothing ever rewrote device.device_properties.eq_preset_options after init_device_eq_state ran at startup, the watcher's "fresh" list existed for at most one loop body before being overwritten by the stale startup snapshot. Net effect: deleting a preset on disk left it visible in the tray; adding one never appeared. Fix the source instead of the cache. Replace reload_presets() with eq_runtime::refresh_eq_state_from_disk(&mut *device), which seeds the device's properties; the per-iteration update() then propagates them naturally. Same change applies to non-Linux for symmetry. Widen the refresh while we're there: also re-read selected_profile.json so external edits (another tool changing the active preset) propagate to the tray, not just changes to the set of preset files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…send_event maybe_sync_on_reconnect previously used `let _ = device.try_apply(...)` and unconditionally returned `is_connected` (true), so a failed apply flipped was_connected to true and the next iteration's transition condition never re-fired. Result: a transient HID error on the very first sync after reconnect would leave the headset out of sync until a real disconnect/reconnect cycle. Now the function returns true only when the headset is connected AND either no sync was needed or the sync succeeded; on apply failure it returns false so the next iteration retries. Drop the redundant proxy.send_event in the non-Linux watcher branch. The trailing send_event at the end of every loop iteration already picks up the post-refresh DeviceProperties, so the inner send was dead code (deduplicated by the tray's update() equality check, but clutter nonetheless). Correct the doc comment on refresh_eq_state_from_disk: band-content edits do fire watcher events (notify watches recursively); they just have no observable UI effect because bands aren't stored in DeviceProperties. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- init_device_eq_state -> init_device_eq (also seeds props and starts the watcher, not just state) - refresh_eq_state_from_disk -> refresh_eq_props_from_disk (matches the seed_eq_props_from_disk private helper; "state" overstated what the function loads) - Drop verbose note on refresh_eq_props_from_disk that just restated implicit behaviour (band content not in DeviceProperties). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EQ packet writes were going through hid_device.write directly, bypassing the trait's write_hid_report which transparently falls back to send_feature_report on Windows when the HID stack returns ERROR_INVALID_FUNCTION. Switched four call sites to the trait method: - try_apply(EqualizerPreset) in src/devices/mod.rs - --eq-band write loop in src/bin/hyper_headset_cli/main.rs - send_band_to_device + send_bands_to_device in src/bin/hyper_headset_cli/eq_editor.rs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude Opus 4.7 finds:
|
Replace four free functions + a private wrapper with a single EqSession struct that owns the watcher state and exposes the operations as methods. Net effect: - EqSession::new is a real constructor; capability check, initial seeding, and watcher startup are constructor concerns rather than a composite "init" free function pretending otherwise. - Watcher and its receiver are bundled in the struct that owns them, instead of returned as a tuple for the caller to keep alive. - The watcher-rx drain and the disk load collapse into one method, load_if_config_changed, since callers always paired them and never used either alone. - maybe_sync_on_reconnect renamed sync_if_reconnected; "maybe" hand-waved the no-op condition, the if-suffix names it. The was_connected state moves from main into the session as an internal field, so the caller no longer ping-pongs the flag through a parameter and a return value. - load_props_from_disk becomes private; only new and load_if_config_changed need it. - File renamed src/eq/runtime.rs -> src/eq/session.rs to match. The per-connection lifetime (born on connect, dropped on disconnect) reads as a session, not a long-lived runtime. Methods are action-named (load_if_config_changed, sync_if_reconnected, load_props_from_disk) since they are tick-driven: the main loop calls them every iteration and they internally check whether work is pending. on_*-style names would have suggested they fire only when the trigger does, which is not what happens. Caller surface in src/main.rs shrinks from a tuple match + four free function calls per loop + a tracking variable to one constructor + two method calls on the session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit treated EqSession as a per-connection struct, but its watcher and connection-state tracking have different natural lifetimes: - the config-dir watcher is process-scope: the filesystem doesn't disconnect with the headset, and disk changes during a disconnect window are lost when the watcher drops with the session. - was_connected and the capability check are connect-scope. Bundling them dragged the watcher down to connect-scope, causing inotify fd and worker-thread churn on every reconnect, conflating watcher-install failure with "no EQ for this device", and creating a brief observation gap during disconnects. Split the responsibilities: - EqSession::new() no longer takes a device; installs the watcher only, returns None only on watcher failure. - bind_device(&mut self, &mut Device) handles per-connect work: capability check (sets the active flag), seed props from disk, drain watcher events queued during the disconnect, reset was_connected so the next sync_if_reconnected pushes the active preset. - load_if_config_changed and sync_if_reconnected early-return when !active so devices without EQ support are inert. Main constructs the session once before the outer reconnect loop and calls bind_device on each successful (re)connect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure formatting/structural cleanup, no behavior change: - Group definitions under section comments (Types / Path helpers / Built-in presets / Preset I/O / Selected-profile I/O / Config-dir watcher). - Move path helpers above BUILTIN_PRESETS so callers see the file scaffolding before the data. - Move is_builtin next to builtin_presets. - Rewrap save_preset's Error::new across three lines. - Drop "Check user preset file first" / "Fall back to builtin" comments in load_preset — the function is short enough that the structure is self-evident. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ConfigWatcher hides notify's "must be kept alive" requirement and the raw mpsc receiver behind a small abstraction. EqSession now holds one field instead of a watcher/receiver pair and polls via take_pending(). Watcher callback now filters events to those touching eq_presets/* or selected_profile.json, instead of firing on any change anywhere under the config dir. Bumped notify 7 -> 8. notify 7's FSEvents backend canonicalizes the watch path internally and emits canonicalized event paths, which silently breaks prefix/equality matching against the as-watched paths on macOS. notify 8 rewrites event paths back to the as-watched form via its reported_path machinery. Also surface JSON parse errors on preset/profile load via eprintln! instead of failing silent. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Friction with the old design
The code was functional. But my difficulty understanding it was at least 25% attributable to it being a bit of an awkward pattern, for me at least. Some causes of the frictionFive free functions where one struct should have lived.
Not to mention the naming... // Now what do I do with the WatcherPair? And paired with, what?
pub fn init_device_eq(device) -> Option<WatcherPair>
// calls fn seed_eq_props_from_disk(device) - private wrapper for no reason
pub fn refresh_eq_props_from_disk(device)
// Drain what? Why? And what do I do with the return value?
pub fn drain_watcher(rx) -> bool
// Maybe what? Sync if reconnecting? And what do we do with the return value? Maybe do something if it returns true?
pub fn maybe_sync_on_reconnect(device, was_connected) -> boolResolutionIntroduced an
After: impl EqSession {
pub fn new() -> Option<Self>
pub fn bind_device(&mut self, device)
pub fn load_if_config_changed(&self, device)
pub fn sync_if_reconnected(&mut self, device)
fn try_sync_active_preset(&self, device) -> bool
fn load_props_from_disk(device)
}with the watcher itself wrapped in a small companion type: pub struct ConfigWatcher { /* notify watcher kept alive + filtered event channel */ }
impl ConfigWatcher {
pub fn new() -> notify::Result<Self>
pub fn take_pending(&self) -> bool
}The watcher callback also filters events to those touching Caller surface in
Also bumped While there: Findings
JK. There were some issues and I tried solving them in various wrong ways, gained Rust experience, learned globally applicable ... ways to shoot myself in the foot. ReviewI've taken this opportunity to learn more of Rust. The refactor starting from 51f4ae7, would you consider the struct approach an improvement over the free function one? It was a very bumpy road to get here. Not sure if the patterns I applied are what you would've chosen. It would be great to get your feedback. My personal preference found the free functions less intuitive and harder to reason about, and harder to rewrite/rename into clarity. So @LennardKittner ready for review |
|
Thanks for the work; I will take a look. I haven't looked through the changes yet; however, I'd say that using a struct/trait is probably better than defining free-standing functions because this likely makes it easier to reuse the code for other headsets that also feature a built-in equalizer. But which require certain functions to be overridden. |
|
Yeah well I've taken quite a deep look at the code today. And I decided that I would implement my changes assuming one Device instance per process, as all the code basically is written with that assumption. it's not a super large code base so a refactor to get to a bare working multi-tenance setup would not be the craziest refactor. But the code is exactly not that. I had figured that for multi device support, at least in this state, it's 100% worth the shortcut to just add a --device flag and write up a wrapper and have a separate process for each device. But that's .... wait, in this PR branch Maybe I overestimated the repo size. I haven't really escaped my files. I wasn't aware lol. Oh wait * 2. You meant expanding support, not parallel device support. Or does the Struct setup somehow facilitate better? other than encapsulation? |
|
I am fucking pissed off now. So usb stuff worked. I go into EUFI, I enable ASPM, headset as broken as it gets. Great! Right? No because I turn back off ASPM and then for two hours I am trying to fucking figure out what can possibly be it.
Those are all very possible candidates. But as I am typing I have managed to confuse the dongle into psychosis. And my microphone is currently toggling muted state every 5 seconds. Probably the tray app messing with the dongle and the very most likely cherry on top in combination with these EUFI tweaks: HP fucking sucks at quality control and fucking decided that it works on their machine, ready to fucking sell.
Everything is just so unpredictive that this is really hard to conclude: Unplug dongle until it runs out of power, and make sure the headset is also off. Disable tray service so nothing aat boot time starts pulling bells and whistles. Once you unlock your KWallet GPG key - if applicable (this might just also be one bolt in this gearfuck). Then plug in your dongle, WITH HEADSET STILL OFF. Then as seen in the picture above, set it to DIGITAL because the headset communicates digitally. ONLY THEN turn on your headset. Now it all should work. I have no idea how many of these variables actually matter, but I think the ordering is very important. Because once the dongle and headset establish psychotic communication, your entire aura and luck of the gods is tainted. Why was this hard for me to find out? I think the combo box in the image, is very much a front for a very complex shitstorm of software abstractions and hardware abstractions and this means that once you establish a working connection, you can set this to analog, and it will somehow continue working for quite a while. Or perhaps the donge and headset... ah yeah, they do analog for the audio and mic, because that's higher quality or better over 2.4GHz vs BT. And all the other features require digital link. So in theory you should be able to switch between analog and digital so switch between these two feature sets. But the dongle's firmware or hardware is lame. And it throws a persistent tantrum. Maybe the dongle and headset pair with some encryption key that is corrupted and then they never forget. Maybe it's all not as complicated and I've just lost it.
I've sunk a crazy amount of time into trying to find out what the hell is going on. And I think it's one of those moments where a large collection of small things just combine to create unpredictive, hard to reproduce behaviour. And the depending factors are spread out across walled gardens and modern companies that don't share their code for shit! Pardon my French. One thing is clear though: Fuck HP, just release the fucking firmware, we won't make fun of your shit code - we all write shit code. But at least let's collaborate. It's not like Harry's incomprehensive assembly firmware binary blobs are going to allow me to compete with you on the headset market... heck I wanna fucking fix it for ya! Gotta admit - the headset is very fucking nice!1 Except the dongle assembly junk that Harry forgot to test because he was nearly 99 fucking agility or something. Idk. Fuck you Harry. Footnotes
|
|
Oh and fuck Claude with it's narrow vision. It started getting cocky at me earlier for not following it's instructions. But we've done whole whole circular fucking ceremony at least four times by now. The problem is NOT in fucking UEFI. And it's NOT in LennardKittner/HyperHeadset ... so, it's out of my hands, not in the training set of Claude, and only solvable through religic rituals that almost make it seems that I had to do the right chant to make someone's god happy for the magic to fix it. @LennardKittner there is something missing in this repo for it to become a serious product; You need to establish the brand for the application. I recommend the message of the Lord and Jesus Christ Himself, who have blessed me today with the prosperity of a functional digital communication link between my Headset which I use to keep myself aligned with the verses. Maybe you should add a CONDUCT.md with a similar concept the guys at SQLite have taken; https://sqlite.org/codeofethics.html |
Oh boy I'm going straight to hell, won't even get prosecuted. Turns out Limbo can actually be skipped, even without the floor-clipping glitches that were patched in 1985. How will this affect the reincarnation speed-running community? |
Yes, I was referring to expanding support, not supporting multiple devices at the same time. I think Harry should have tested a bit more. Have you tried #42? Hopefully that fixes the connection issues.
I would never make fun of your code Harry
Yes, this project definitely needs guidance from God.
I think you are also violating |
| if profile.synced { | ||
| return true; | ||
| } | ||
| println!("Syncing EQ preset '{}' to headset...", name); |
There was a problem hiding this comment.
Probably better to use debug_println! here.
LennardKittner
left a comment
There was a problem hiding this comment.
It looks very good already. However, there is a little bug in the non-Linux status tray preventing the EQ select entry from showing if the preset files don't exist.
Thanks again for your work.
There was a problem hiding this comment.
I think the usage of EqSession here is really clean and avoids code duplication across operating systems.
| } | ||
|
|
||
| /// Drain pending change signals; return true if anything was queued. | ||
| pub fn take_pending(&self) -> bool { |
There was a problem hiding this comment.
Not necessary, only a thought. Since we don't care about the number of events nor the type of events, a simple atomic bool would also be sufficient.
|
|
||
| menu_items.push( | ||
| SubMenu { | ||
| label: format!("{} {}", descriptor.prefix, descriptor.data.as_deref().unwrap_or("None")), |
There was a problem hiding this comment.
The unwrap_or triggers on first launch when the preset files have not been generated yet. In this case the UI should probably show something like "Unknown" or "Not set" rather than "None".
| active_preset, | ||
| synced, | ||
| } => { | ||
| let Some(ref current_value) = descriptor.data else { |
There was a problem hiding this comment.
This check is not present in the Linux version and causes the select EQ entry to only appear if the selected preset file exists.
Thus, it is not possible to set the EQ from the tray without manually creating the file or using the CLI first.
There was a problem hiding this comment.
The Edit with: hyper__headset__cli --eq entry is also not present.
Print "interface N/total" alongside each "Try to connecting to ..." line so the multi-interface probe burst is visible as such in the daemon log, instead of looking like several full reconnect attempts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Take the first matching HID interface on Linux/macOS instead of probing all of them with HID writes. This was the original behavior up until commit b66718a (2026-03-12) added Windows support and adapted the multi-interface probe to all platforms. The probe is the right approach on Windows where some HyperX dongles only accept commands on specific interfaces, but on Linux/macOS the first interface has historically been the control interface. Probing all 6 interfaces of the Cloud III S Wireless dongle per connect attempt was empirically observed to make connect failures more frequent in this session. Reverting to the historical Linux behavior restored reliable connect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
I've visited the deepest corners of the indexed internet, and I believe Gigabyte might actually be involved. Actual documentationLinux USB DWC3 Host/Peripheral DriverWARNING
https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842069/USB Above documentation is not actually relevant. But it does tell me that I think I'll never touch hardware drivers again after this fiasco, after. I will get this dongle in line, even if it's the last thing I'll do. So next session:
Who knows |
|
#42 tried to reproduce this capture #36 (comment), so maybe you could compare against the capture using #42 and check if anything is different. |
It's been a while. What kin of capture? what o I check out an what o I try to execute without being able to press the [...]ONGLE key? I'll ask my butler to fill in the [...]ots. If it involves Winows, I am surprise , but happy to say that Winows works even less than Linux for me. |
I think I get what you're referring to. But no, I on't think I will not produce unique capture packets. I've reviewed #42 trying to make sure I 'i'n't write the wrong packet abstractions. But #42 is correct. My packets kina worke , but only because my hea set won't talk to us. So having the packets kin a right, is goo enough to make the bells ring on it. |



Summary
Fix HID device handle duplication (
src/devices/mod.rs): Added path-based deduplication usingHashSet+open_path(). On Linux hidraw, the Cloud III S enumerates 6 HID collections that all map to the same/dev/hidraw0. The previous code (froma8569f6"Use all available channels") opened the same device 6 times, causing duplicate writes and stale response flooding. This is not a full revert — theVec<HidDevice>multi-device structure is preserved. The dedup is cross-platform safe: it's a no-op on libusb/macOS/Windows where collections have distinct paths.Fix Cloud III S BASE_PACKET size (
src/devices/cloud_iii_s_wireless.rs): Changed from 62 to 64 bytes per the HID report descriptor (Report Count=63 + 1 report ID byte = 64 total).Add EQ CLI support (
src/bin/hyper_headset_cli.rs,README.md): Two new arguments for equalizer control:--eq-band BAND=DB— set a single band, repeatable--eq BAND=DB,...— set multiple bands, comma-separatedBand references support bare index (0-9) or frequency suffix (
1khz,250hz, case-insensitive). Only Cloud III S Wireless currently implements EQ.Test plan
🤖 Generated with Claude Code