Skip to content

Add libusb transport for Cloud III S Wireless#42

Open
vxel wants to merge 5 commits intoLennardKittner:cloud_III_sfrom
vxel:cloud_III_s
Open

Add libusb transport for Cloud III S Wireless#42
vxel wants to merge 5 commits intoLennardKittner:cloud_III_sfrom
vxel:cloud_III_s

Conversation

@vxel
Copy link
Copy Markdown

@vxel vxel commented Apr 30, 2026

Cloud III S Wireless: libusb transport, generic refactor, mute fix

Problem

Restores reliable headset state queries (battery, charge, side tone, voice prompt, color, auto-shutdown, mute) on Cloud III S Wireless under Linux, and lifts the libusb-based workaround into a clean transport abstraction so other devices can opt into it without polluting mod.rs.

Disclaimer : made with the help of AI (Claude Code)

Diagnosis

  • I believe Linux hidraw drops RF-forwarded responses on this dongle. Once /dev/hidrawN has been opened, query responses that travel over RF (battery, charge, side tone, …) silently never reach user space. NGenuity uses raw USB on Windows; I replicate that on Linux via libusb, hence completely bypassing hidraw.
  • The previous write_hid_report override sent SET_REPORT type Feature, which the firmware ignores on Report ID 0x0c. NGenuity always uses type Output. The override is removed; the default hid_device.write() produces the correct request.
  • Mute was sent via Report ID 0x05 (05 02 / 05 00) which did not seem to work. Replaced with the 0x0c-protocol cmd 0x01 (0c 02 03 00 00 01 <0|1>), confirmed by USB capture of NGenuity.

Changes

  • Added dependency to rusb (already a transitive dependency).
  • New generic HidTransport { Hidapi(...), Libusb(...) } and LibusbTransport in mod.rs. The libusb variant detaches the kernel HID driver, claims the interface, and drives raw SET_REPORT control transfers + INT IN reads through a background reader thread.
  • DeviceEntry gained a custom_open: Option<CustomOpener> hook so devices can register a non-hidapi opener. The hidapi enumeration generically skips VID/PID pairs owned by a custom opener.
  • Cloud III S-specific code (USB topology constants, libusb opener, Consumer Control → media key forwarding) lives in cloud_iii_s_wireless.rs, gated #[cfg(target_os = "linux")].
  • Mic mute now uses the 0x0c-protocol cmd 0x01. The notification path (0d 02 03 00 03 <val>) was already handled.

Notes

  • On Wayland, the Consumer Control button forwarding (vol up/down, play/pause) depends on the compositor accepting libei input emulation; works out of the box on X11.

Copy link
Copy Markdown
Owner

@LennardKittner LennardKittner left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. I like the new HidTransport abstraction.

}
};
let want_vid = VENDOR_IDS[0];
let want_pid = PRODUCT_IDS[0];
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The code should not assume that there is only a single product ID. Many headsets have multiple.

///
/// Linux-only: the firmware quirk has only been observed on Linux's hidraw
/// stack, and the kernel-driver-detach pattern is meaningful only there.
#[cfg(target_os = "linux")]
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This probably shouldn't be Linux-only, so it also works on Windows and macOS.

Comment thread src/devices/mod.rs
if let Some(events) = test_device.wait_for_updates(Duration::from_secs(1)) {
for event in events {
debug_println!("got response {event:?}");
for _event in events {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The "_" is intentional to avoid warnings when compiling in release mode.

Comment thread src/devices/mod.rs
}
}

fn rusb_to_hid(err: rusb::Error) -> HidError {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Is this necessary? Why not add a new error type to DeviceError?

Comment thread src/devices/mod.rs
.collect();
// Custom-opened devices come first so the connection probe tries them
// before falling back to any hidapi candidate.
for state in custom_states.into_iter().rev() {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Why not append states to custom_states?

Comment thread src/devices/mod.rs
if now >= deadline {
return Ok(0);
}
let (g, _) = cvar.wait_timeout(q, deadline - now).unwrap();
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Do I understand correctly that we are waiting here while still holding the lock?
If so, the reader thread will also get blocked and cannot listen for new events while the main thread waits.

@NubeBuster
Copy link
Copy Markdown
Contributor

NubeBuster commented May 4, 2026

@vxel I'm trying to get this to work but it's unfortunately not out of the box working.

My junior assistant developer has reported

Diagnosis: PR #42's libusb transport itself is working — open_via_libusb succeeded, claim_interface succeeded, the probe write went out. The connect bails because the dongle didn't reply to the probe within 1s. 1

That's the way she goes — the firmware is wedged in its query-deaf state, no amount of Linux-side workaround will fix it from this layer.

  • Claude
1a78ae5fb476d07d2fb01bf36bf6d9f1 499x499x1

So, @vxel Which 'profile' are you using? IEC958? Analog? Smoke signal?
Mobo? I have an B650 EAGLE AX
6.17.0-23-generic

I'll just keep it running and do some voodoo rituals at some point to make it co-operate perhaps. But right now the dongle is at its heightened spiritual tantrum state I think.


SIKE

It's connected now. But no responses from the headset.

Notably, after running this branch, if I kill the hyper_headset process, I can no longer control the volume with the slider. There is a SIGINT exit trap missing to restore the thinggamabob.

CLAUDE FINDS THE PROBLEM - again.

Claude finds out for the fifth time in 30 minutes that the dongle is the problem
● Actually it tells us something important: the PR's central premise looks shaky for your dongle.

  PR #42's claim: "Linux hidraw drops RF-forwarded responses on this dongle." — that's the whole reason for the libusb rewrite.                                                                                      
   
  But your observation contradicts it:                                                                                                                                                                               
  - Volume buttons (RF-forwarded HID reports) worked fine on kernel hidraw before libusb intervened. Always worked. Never broken.                                                                                  
  - They only broke after libusb left intf 3 unbound.                                                                                                                                                                
                                                     
  So the kernel HID path isn't dropping arbitrary RF-forwarded reports. The PR author may have been wrong about why their queries weren't working — and "fixing" it with libusb worked for them but for an unknown   
  reason.                                                                                                                                                                                                            
                                                                                                                                                                                                                     
  What's actually going on with your dongle:                                                                                                                                                                         
  - Dongle is alive: button events flow (one-way, headset→dongle→host) on both kernel HID and libusb paths                                                                                                         
  - Mute writes get acknowledged (mic actually mutes)                                                                                                                                                                
  - But query responses never come — same on libusb as on hidraw
                                                                                                                                                                                                                     
  That points away from the OS layer and toward something else:                                                                                                                                                      
  - Dongle→headset RF path may be wedged: queries reach the dongle but never get RF-forwarded to the headset (so no response is generated). Button events work because they're one-way headset→dongle.               
  - Or the headset's BT/RF state is wedged: it pairs enough to relay buttons but won't reply to query packets.                                                                                                       
                                                                                                                                                                                                                     
  Either way, it's not Linux-side. Switching transports (hidraw ↔ libusb) won't change anything for query reads on your dongle. Same story we've been seeing all session — firmware-state, host can't fix it.        
                                                                                                                                                                                                                     
  Want me to still patch the kernel-driver-rebind on exit? It's a real bug in the PR, but unrelated to your query problem.                                                                                           
                                                                                                                                                                                                                     
✻ Cooked for 1m 35s                                                                                                                                                                                                  
                                                                                                                                                                                                                   yes . but for the record, you're saying that the dongle is bugged, right?                                                                                                                                          
                                                                             
● Yes — that's the most consistent explanation with what we've observed:                                                                                                                                             
                                                                                                                                                                                                                   
  - Both transports (hidraw and libusb) deliver every byte the dongle sends                                                                                                                                          
  - The dongle sends one-way data fine (button events on both paths)
  - It accepts and acts on writes (mute LED toggles)                                                                                                                                                                 
  - It just doesn't reply to query packets                                                                                                                                                                           
                                                                                                                                                                                                                     
  Nothing in the host stack can manufacture a reply that the dongle doesn't send. Yes, dongle (or headset) firmware state is the most likely culprit. Caveat: we haven't ruled out a USB capture / Wireshark-side    
  investigation that could prove writes actually leave the host and reach the dongle — but every other signal we have points firmware-side.                                                                          
                                                                                                                                                                                                                     
  Now patching the kernel-rebind bug.                                                                                                                                                                                

Oh man Claude, for god's sake, I've told you a hundred times the dongle is bugged!1

Footnotes

  1. The ongle is bugge an my keyboar is broken 2

@NubeBuster
Copy link
Copy Markdown
Contributor

NubeBuster commented May 4, 2026

but, did we learn something new? Or is it the dongle, after all?
so, in conclusion tho. you've been ambivalent on the possible causes, but, did we learn something new?
❯
❯  not new:- the firmware is the problem- the dongle and the headset are in a divorce- ...etc

● Yes, a few genuinely new things:

  1. The PR's central premise doesn't hold for your dongle.
  PR #42 says hidraw drops RF-forwarded responses, justifying the libusb rewrite. But on your hardware:
  - Volume buttons (RF-forwarded HID reports) work fine via kernel hidraw
  - Even via libusb, query responses still don't come
  
  So hidraw isn't the problem — at least not the whole problem. The query-deafness happens at a layer libusb doesn't reach. The PR's libusb transport is solving for a different failure mode than yours.

  2. The "wedge" is selective, not total.
  Before this session it was vaguely "writes work, reads don't." We now have a sharper picture: [^note1]

1

  - Host→dongle writes: ack and act (mute LED toggles)
  - Headset→dongle one-way RF: works (button events flow on both transports)
  - Host→dongle→headset→dongle→host round-trip: silently fails (queries)

  That points at the dongle→headset direction or the response-forwarding step specifically. Not generic firmware death.

  3. PR #42 mute protocol vs yours is genuinely different bytes — 0x0c-cmd-0x01 (64-byte) vs your 0x05 (20-byte). Both work for muting. Useful to know if your branch ever wants to converge with PR #42's protocol.

  4. Two latent bugs in PR #42, now fixed in your PR:
  - Connect couldn't survive query-deafness (gates on a probe response that never comes)
  - Ctrl+C left the dongle's HID interface unbound, breaking volume keys system-wide

  5. Practical takeaway: even when query reads fail, the tray/CLI is now usable for writes-only (mute, EQ presets, auto-shutdown). That's a usability floor we didn't have before.[^note2]

2

  What we didn't learn: an actual fix for the query-deafness. Still a firmware/RF-state problem on the device side.

✻ Churned for 25s

3

Footnotes

  1. No we don't. We've known this for months. I could not possibly have explained this clearer or more often, I had sent only 20 messages, and at least 10 of those were that you are working towards an already solved problem 2, and the other 10 were that it's the DONGLE. Opus 4.7, you're a mood.

  2. We've solved this months ago, and I've told it about 10 times this session. 2

  3. Anyone got Harry's email address? He hasn't been responding to my calls.

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.

3 participants