From c1110ac81f87ba374696e419f3807f48a3a06d6d Mon Sep 17 00:00:00 2001 From: jamie Date: Sat, 29 Aug 2026 21:58:43 +0100 Subject: feat(daemon): link-state hooks for headset connect/disconnect --- src/hid.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'src/hid.rs') diff --git a/src/hid.rs b/src/hid.rs index da70d81..6b09e98 100644 --- a/src/hid.rs +++ b/src/hid.rs @@ -10,6 +10,7 @@ use std::fs; use std::io; use std::os::fd::AsRawFd; use std::path::{Path, PathBuf}; +use std::time::{Duration, Instant}; /// beyerdynamic USB vendor ID. pub const BEYERDYNAMIC_VID: u16 = 0x2e50; @@ -198,6 +199,51 @@ impl Hidraw { self.file.read(buf) } + /// Wait up to `timeout` for one input report on the interrupt IN + /// endpoint. Returns `Ok(None)` if none arrived in time. + pub fn read_with_timeout(&mut self, timeout: Duration) -> io::Result>> { + let deadline = Instant::now() + timeout; + loop { + let now = Instant::now(); + if now >= deadline { + return Ok(None); + } + let mut set = unsafe { std::mem::zeroed::() }; + unsafe { libc::FD_ZERO(&mut set) }; + unsafe { libc::FD_SET(self.file.as_raw_fd(), &mut set) }; + let remaining = deadline.saturating_duration_since(now); + let mut tv = libc::timeval { + tv_sec: remaining.as_secs() as libc::time_t, + tv_usec: remaining.subsec_micros() as libc::suseconds_t, + }; + let n = unsafe { + libc::select( + self.file.as_raw_fd() + 1, + &mut set, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut tv, + ) + }; + if n < 0 { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue; + } + return Err(err); + } + if n == 0 { + return Ok(None); + } + let mut buf = [0u8; 256]; + match self.read(&mut buf) { + Ok(len) => return Ok(Some(buf[..len].to_vec())), + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + } + } + } + /// Write to the interrupt OUT endpoint. pub fn write(&mut self, buf: &[u8]) -> io::Result { use std::io::Write; -- cgit v1.3.1