pub mod cloud_alpha_wireless; pub mod cloud_flight_wireless; pub mod cloud_ii_core_wireless; pub mod cloud_ii_wireless; pub mod cloud_ii_wireless_dts; pub mod cloud_iii_s_wireless; pub mod cloud_iii_wireless; use crate::{ debug_println, devices::{ cloud_alpha_wireless::CloudAlphaWireless, cloud_flight_wireless::CloudFlightWireless, cloud_ii_core_wireless::CloudIICoreWireless, cloud_ii_wireless::CloudIIWireless, cloud_ii_wireless_dts::CloudIIWirelessDTS, cloud_iii_s_wireless::CloudIIISWireless, cloud_iii_wireless::CloudIIIWireless, }, }; use hidapi::{HidApi, HidDevice, HidError}; use std::{ collections::HashSet, fmt::{Debug, Display}, time::Duration, }; use thistermination::TerminationFull; const PASSIVE_REFRESH_TIME_OUT: Duration = Duration::from_secs(2); type DeviceFactory = fn(DeviceState) -> Box; struct DeviceEntry { vendor_ids: &'static [u16], product_ids: &'static [u16], factory: DeviceFactory, } const DEVICE_REGISTER: &[DeviceEntry] = &[ DeviceEntry { vendor_ids: &cloud_ii_wireless::VENDOR_IDS, product_ids: &cloud_ii_wireless::PRODUCT_IDS, factory: |s| Box::new(CloudIIWireless::new_from_state(s)), }, DeviceEntry { vendor_ids: &cloud_ii_wireless_dts::VENDOR_IDS, product_ids: &cloud_ii_wireless_dts::PRODUCT_IDS, factory: |s| Box::new(CloudIIWirelessDTS::new_from_state(s)), }, DeviceEntry { vendor_ids: &cloud_iii_s_wireless::VENDOR_IDS, product_ids: &cloud_iii_s_wireless::PRODUCT_IDS, factory: |s| Box::new(CloudIIISWireless::new_from_state(s)), }, DeviceEntry { vendor_ids: &cloud_iii_wireless::VENDOR_IDS, product_ids: &cloud_iii_wireless::PRODUCT_IDS, factory: |s| Box::new(CloudIIIWireless::new_from_state(s)), }, DeviceEntry { vendor_ids: &cloud_alpha_wireless::VENDOR_IDS, product_ids: &cloud_alpha_wireless::PRODUCT_IDS, factory: |s| Box::new(CloudAlphaWireless::new_from_state(s)), }, DeviceEntry { vendor_ids: &cloud_ii_core_wireless::VENDOR_IDS, product_ids: &cloud_ii_core_wireless::PRODUCT_IDS, factory: |s| Box::new(CloudIICoreWireless::new_from_state(s)), }, DeviceEntry { vendor_ids: &cloud_flight_wireless::VENDOR_IDS, product_ids: &cloud_flight_wireless::PRODUCT_IDS, factory: |s| Box::new(CloudFlightWireless::new_from_state(s)), }, ]; const RESPONSE_BUFFER_SIZE: usize = 256; pub const RESPONSE_DELAY: Duration = Duration::from_millis(50); pub fn connect_compatible_device() -> Result, DeviceError> { let all_product_ids: Vec = DEVICE_REGISTER .iter() .flat_map(|e| e.product_ids.iter().copied()) .collect(); let all_vendor_ids: Vec = DEVICE_REGISTER .iter() .flat_map(|e| e.vendor_ids.iter().copied()) .collect(); let states = DeviceState::new(&all_product_ids, &all_vendor_ids)?; debug_println!("Found device selecting handler"); // On Linux and MacOS we can just take the first #[cfg(not(target_os = "windows"))] { let state = states .into_iter() .next() .ok_or(DeviceError::NoDeviceFound())?; eprintln!( "Connecting to {}", state .device_properties .device_name .clone() .unwrap_or("???".to_string()) ); let entry = DEVICE_REGISTER .iter() .find(|e| { e.vendor_ids.contains(&state.device_properties.vendor_id) && e.product_ids.contains(&state.device_properties.product_id) }) .ok_or(DeviceError::NoDeviceFound())?; let mut device = (entry.factory)(state); device.init_capabilities(); Ok(device) } // On Windows we have to check which interface can be used #[cfg(target_os = "windows")] { let mut device = None; for state in states { eprintln!( "Try to connecting to {}", state .device_properties .device_name .clone() .unwrap_or("???".to_string()) ); let entry = DEVICE_REGISTER .iter() .find(|e| { e.vendor_ids.contains(&state.device_properties.vendor_id) && e.product_ids.contains(&state.device_properties.product_id) }) .ok_or(DeviceError::NoDeviceFound())?; let mut test_device = (entry.factory)(state); test_device.init_capabilities(); let probe_packet = test_device .get_query_packets() .into_iter() .next() .expect("Why is there a device without packets ???"); test_device.prepare_write(); if let Err(_e) = test_device .get_device_state() .write_hid_report(&probe_packet) { debug_println!("Failed to open: {_e:?}"); continue; } else { device = Some(test_device); break; } } device.ok_or(DeviceError::NoDeviceFound()) } } #[derive(Debug)] pub struct DeviceState { pub hid_device: HidDevice, pub device_properties: DeviceProperties, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct DeviceProperties { pub product_id: u16, pub vendor_id: u16, pub device_name: Option, pub battery_level: Option, pub charging: Option, pub muted: Option, pub mic_connected: Option, pub automatic_shutdown_after: Option, pub pairing_info: Option, pub product_color: Option, pub side_tone_on: Option, pub side_tone_volume: Option, pub surround_sound: Option, pub voice_prompt_on: Option, pub connected: Option, pub silent: Option, pub noise_gate_active: Option, // Capability flags - set once during device initialization pub can_set_mute: bool, pub can_set_surround_sound: bool, pub can_set_side_tone: bool, pub can_set_automatic_shutdown: bool, pub can_set_side_tone_volume: bool, pub can_set_voice_prompt: bool, pub can_set_silent_mode: bool, pub can_set_equalizer: bool, pub can_set_noise_gate: bool, } impl Display for DeviceProperties { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.to_string_with_readonly_info(25)) } } impl DeviceState { pub fn new(product_ids: &[u16], vendor_ids: &[u16]) -> Result, DeviceError> { let hid_api = HidApi::new()?; let mut potential_devices = HashSet::new(); let mut error = Ok(()); debug_println!( "Devices: {:?}", hid_api .device_list() .by_ref() .map(|d| { (d.vendor_id(), d.product_id(), d.product_string()) }) .collect::)>>() ); let device_candidates: Vec<(HidDevice, u16, u16)> = hid_api .device_list() .filter_map(|info| { if product_ids.contains(&info.product_id()) && vendor_ids.contains(&info.vendor_id()) { debug_println!( "Selecting: {:x}:{:x} {:?}", info.vendor_id(), info.product_id(), info.product_string() ); match info.open_device(&hid_api) { Ok(device) => Some((device, info.product_id(), info.vendor_id())), Err(e) => { debug_println!( "Failed to open: {:x}:{:x} {:?}: {:?}", info.vendor_id(), info.product_id(), info.product_string(), e ); error = Err(e); None } } } else { if let Some(name) = info.product_string() { if name.contains("HyperX") { potential_devices.insert(( info.vendor_id(), info.product_id(), info.product_string(), )); } } None } }) .collect(); if device_candidates.is_empty() { if !potential_devices.is_empty() { let names = potential_devices .iter() .map(|e| { format!( " vendorID: 0x{:04X} productID: 0x{:04X} name: {}", e.0, e.1, e.2.unwrap_or("Unknown") ) }) .collect::>() .join(",\n"); //TODO: show as message in tray app eprintln!( "Found the following HyperX device{}: [\n{}\n]\nHowever, either {} not supported or the product ID is not yet known.", if potential_devices.len() > 1 { "s" } else { "" }, names, if potential_devices.len() > 1 { "they are" } else { "it is" } ); } error?; return Err(DeviceError::NoDeviceFound()); } Ok(device_candidates .into_iter() .map(|(hid_device, product_id, vendor_id)| { let device_name = hid_device.get_product_string().ok().flatten(); DeviceState { hid_device, device_properties: DeviceProperties::new(product_id, vendor_id, device_name), } }) .collect()) } /// Write a HID report to the device. /// /// On Windows, some HyperX dongles expose commands as **Feature reports** only. /// In that case, `hidapi::HidDevice::write()` fails with: /// `WriteFile: (0x00000001) Incorrect function.` /// /// Linux/macOS hidraw paths often accept the same bytes via output reports, so this can look /// "Windows-exclusive". We transparently fall back to `send_feature_report` when we detect /// this specific failure. /// Adapted from PR #20 by @navrozashvili /// Source: https://github.com/LennardKittner/HyperHeadset/pull/20 pub fn write_hid_report(&self, packet: &[u8]) -> Result<(), HidError> { match self.hid_device.write(packet) { Ok(_) => Ok(()), Err(write_err) => { #[cfg(target_os = "windows")] { if let HidError::HidApiError { message } = &write_err { // Windows HID stack returns ERROR_INVALID_FUNCTION (0x1) when the device // doesn't support output reports / interrupt OUT. if message.contains("Incorrect function") || message.contains("(0x00000001)") { // If the feature report also fails, prefer returning the original // write() error since that's what callers attempted. if let Err(_feature_err) = self.hid_device.send_feature_report(packet) { return Err(write_err); } return Ok(()); } } } Err(write_err) } } } fn update_self_with_event(&mut self, event: &DeviceEvent) { match event { DeviceEvent::BatterLevel(level) => self.device_properties.battery_level = Some(*level), DeviceEvent::Charging(status) => self.device_properties.charging = Some(*status), DeviceEvent::Muted(status) => self.device_properties.muted = Some(*status), DeviceEvent::MicConnected(status) => { self.device_properties.mic_connected = Some(*status) } DeviceEvent::AutomaticShutdownAfter(duration) => { self.device_properties.automatic_shutdown_after = Some(*duration) } DeviceEvent::PairingInfo(info) => self.device_properties.pairing_info = Some(*info), DeviceEvent::ProductColor(color) => self.device_properties.product_color = Some(*color), DeviceEvent::SideToneOn(side) => self.device_properties.side_tone_on = Some(*side), DeviceEvent::SideToneVolume(volume) => { self.device_properties.side_tone_volume = Some(*volume) } DeviceEvent::SurroundSound(status) => { self.device_properties.surround_sound = Some(*status) } DeviceEvent::VoicePrompt(on) => self.device_properties.voice_prompt_on = Some(*on), DeviceEvent::WirelessConnected(connected) => { self.device_properties.connected = Some(*connected) } DeviceEvent::Silent(silent) => self.device_properties.silent = Some(*silent), DeviceEvent::RequireSIRKReset(_reset) => { debug_println!("requested SIRK reset {_reset}"); } DeviceEvent::NoiseGateActive(on) => { self.device_properties.noise_gate_active = Some(*on) } }; } } #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum PropertyType { ReadOnly, AlwaysReadOnly, ReadWrite, } #[derive(Debug)] pub enum PropertyDescriptorWrapper { Int(PropertyDescriptor, &'static [u8]), Bool(PropertyDescriptor), String(PropertyDescriptor), } pub struct PropertyDescriptor { pub prefix: &'static str, pub data: Option, pub suffix: &'static str, pub property_type: PropertyType, pub create_event: &'static (dyn Fn(T) -> Option + Send + Sync), } impl Debug for PropertyDescriptor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PropertyDescriptor") .field("prefix", &self.prefix) .field("data", &self.data) .field("suffix", &self.suffix) .field("property_type", &self.property_type) .finish() } } impl DeviceProperties { pub fn new(product_id: u16, vendor_id: u16, device_name: Option) -> DeviceProperties { DeviceProperties { product_id, vendor_id, device_name, battery_level: None, charging: None, muted: None, mic_connected: None, automatic_shutdown_after: None, pairing_info: None, product_color: None, side_tone_on: None, side_tone_volume: None, surround_sound: None, voice_prompt_on: None, connected: None, silent: None, noise_gate_active: None, can_set_mute: false, can_set_surround_sound: false, can_set_side_tone: false, can_set_automatic_shutdown: false, can_set_side_tone_volume: false, can_set_voice_prompt: false, can_set_silent_mode: false, can_set_equalizer: false, can_set_noise_gate: false, } } pub fn get_properties(&self) -> Vec { vec![ PropertyDescriptorWrapper::String(PropertyDescriptor { prefix: "Charging status:", data: self.charging.map(|c| c.to_string()), suffix: "", property_type: PropertyType::AlwaysReadOnly, create_event: &|_| None, }), PropertyDescriptorWrapper::Int( PropertyDescriptor { prefix: "Battery level:", data: self.battery_level, suffix: "%", property_type: PropertyType::AlwaysReadOnly, create_event: &|_| None, }, &[], ), PropertyDescriptorWrapper::Bool(PropertyDescriptor { prefix: "Muted:", data: self.muted, suffix: "", property_type: if self.can_set_mute { PropertyType::ReadWrite } else { PropertyType::ReadOnly }, create_event: &move |mute| Some(DeviceEvent::Muted(mute)), }), PropertyDescriptorWrapper::Bool(PropertyDescriptor { prefix: "Mic connected:", data: self.mic_connected, suffix: "", property_type: PropertyType::AlwaysReadOnly, create_event: &|_| None, }), PropertyDescriptorWrapper::Int( PropertyDescriptor { prefix: "Automatic shutdown after:", data: self .automatic_shutdown_after .map(|t| (t.as_secs() / 60) as u8), suffix: "min", property_type: if self.can_set_mute { PropertyType::ReadWrite } else { PropertyType::ReadOnly }, create_event: &|t| { Some(DeviceEvent::AutomaticShutdownAfter(Duration::from_secs( t as u64 * 60, ))) }, }, &[0, 5, 10, 15, 20, 30, 40, 60], ), PropertyDescriptorWrapper::Int( PropertyDescriptor { prefix: "Pairing info:", data: self.pairing_info, suffix: "", property_type: PropertyType::AlwaysReadOnly, create_event: &|_| None, }, &[], ), PropertyDescriptorWrapper::String(PropertyDescriptor { prefix: "Product color:", data: self.product_color.map(|c| c.to_string()), suffix: "", property_type: PropertyType::AlwaysReadOnly, create_event: &|_| None, }), PropertyDescriptorWrapper::Bool(PropertyDescriptor { prefix: "Side tone:", data: self.side_tone_on, suffix: "", property_type: if self.can_set_side_tone { PropertyType::ReadWrite } else { PropertyType::ReadOnly }, create_event: &move |enable| Some(DeviceEvent::SideToneOn(enable)), }), PropertyDescriptorWrapper::Int( PropertyDescriptor { prefix: "Side tone volume:", data: self.side_tone_volume, suffix: "", property_type: if self.can_set_side_tone_volume { PropertyType::ReadWrite } else { PropertyType::ReadOnly }, create_event: &|v| Some(DeviceEvent::SideToneVolume(v)), }, &[0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250], ), PropertyDescriptorWrapper::Bool(PropertyDescriptor { prefix: "Surround sound:", data: self.surround_sound, suffix: "", property_type: if self.can_set_surround_sound { PropertyType::ReadWrite } else { PropertyType::ReadOnly }, create_event: &move |enable| Some(DeviceEvent::SurroundSound(enable)), }), PropertyDescriptorWrapper::Bool(PropertyDescriptor { prefix: "Voice prompt:", data: self.voice_prompt_on, suffix: "", property_type: if self.can_set_voice_prompt { PropertyType::ReadWrite } else { PropertyType::ReadOnly }, create_event: &move |enable| Some(DeviceEvent::VoicePrompt(enable)), }), PropertyDescriptorWrapper::Bool(PropertyDescriptor { prefix: "Playback muted:", data: self.silent, suffix: "", property_type: if self.can_set_silent_mode { PropertyType::ReadWrite } else { PropertyType::ReadOnly }, create_event: &move |enable| Some(DeviceEvent::Silent(enable)), }), PropertyDescriptorWrapper::Bool(PropertyDescriptor { prefix: "Noise gate active:", data: self.noise_gate_active, suffix: "", property_type: if self.can_set_noise_gate { PropertyType::ReadWrite } else { PropertyType::ReadOnly }, create_event: &move |enable| Some(DeviceEvent::NoiseGateActive(enable)), }), PropertyDescriptorWrapper::Bool(PropertyDescriptor { prefix: "Connected:", data: self.connected, suffix: "", property_type: PropertyType::AlwaysReadOnly, create_event: &|_| None, }), ] } pub fn to_string_with_padding(&self, padding: usize) -> String { self.get_properties() .iter() .filter_map(|prop| { let (prefix, data, suffix) = match prop { PropertyDescriptorWrapper::Int(property_descriptor, _) => ( property_descriptor.prefix, &property_descriptor.data.map(|v| v.to_string()), property_descriptor.suffix, ), PropertyDescriptorWrapper::Bool(property_descriptor) => ( property_descriptor.prefix, &property_descriptor.data.map(|v| v.to_string()), property_descriptor.suffix, ), PropertyDescriptorWrapper::String(property_descriptor) => ( property_descriptor.prefix, &property_descriptor.data, property_descriptor.suffix, ), }; data.as_ref() .map(|data| format!("{:>() .join("\n") } pub fn to_string_with_readonly_info(&self, padding: usize) -> String { self.get_properties() .iter() .filter_map(|prop| { let (prefix, data, suffix, property_type) = match prop { PropertyDescriptorWrapper::Int(property_descriptor, _) => ( property_descriptor.prefix, &property_descriptor.data.map(|v| v.to_string()), property_descriptor.suffix, property_descriptor.property_type, ), PropertyDescriptorWrapper::Bool(property_descriptor) => ( property_descriptor.prefix, &property_descriptor.data.map(|v| v.to_string()), property_descriptor.suffix, property_descriptor.property_type, ), PropertyDescriptorWrapper::String(property_descriptor) => ( property_descriptor.prefix, &property_descriptor.data, property_descriptor.suffix, property_descriptor.property_type, ), }; data.as_ref().map(|data| { let readonly_marker = if property_type == PropertyType::ReadOnly { " (read-only)" } else { "" }; format!("{:>() .join("\n") } } #[derive(TerminationFull)] pub enum DeviceError { #[termination(msg("{0:?}"))] HidError(#[from] HidError), #[termination(msg("No device found."))] NoDeviceFound(), #[termination(msg("No response. Is the headset turned on?"))] HeadSetOff(), #[termination(msg("No response."))] NoResponse(), #[termination(msg("Unknown response: {0:?} with length: {1:?}"))] UnknownResponse([u8; 8], usize), } #[derive(Debug, Copy, Clone)] pub enum DeviceEvent { BatterLevel(u8), Muted(bool), MicConnected(bool), Charging(ChargingStatus), AutomaticShutdownAfter(Duration), PairingInfo(u8), ProductColor(Color), SideToneOn(bool), SideToneVolume(u8), VoicePrompt(bool), WirelessConnected(bool), SurroundSound(bool), Silent(bool), RequireSIRKReset(bool), NoiseGateActive(bool), } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Color { BlackBlack, WhiteWhite, BlackRed, UnknownColor(u8), } impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Color::BlackBlack => "Black".to_string(), Color::WhiteWhite => "White".to_string(), Color::BlackRed => "Red".to_string(), Color::UnknownColor(n) => format!("Unknown color {}", n), } ) } } impl From for Color { fn from(color: u8) -> Self { match color { 0 => Color::BlackBlack, 1 => Color::WhiteWhite, 2 => Color::BlackRed, _ => Color::UnknownColor(color), } } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ChargingStatus { NotCharging, Charging, FullyCharged, ChargeError, } impl Display for ChargingStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { ChargingStatus::NotCharging => "Not charging", ChargingStatus::Charging => "Charging", ChargingStatus::FullyCharged => "Fully charged", ChargingStatus::ChargeError => "Charging error!", } ) } } impl From for ChargingStatus { fn from(value: u8) -> ChargingStatus { match value { 0 => ChargingStatus::NotCharging, 1 => ChargingStatus::Charging, 2 => ChargingStatus::FullyCharged, _ => ChargingStatus::ChargeError, } } } pub trait Device { fn get_response_buffer(&self) -> Vec { [0u8; RESPONSE_BUFFER_SIZE].to_vec() } fn get_charging_packet(&self) -> Option>; fn get_battery_packet(&self) -> Option>; fn set_automatic_shut_down_packet(&self, shutdown_after: Duration) -> Option>; fn get_automatic_shut_down_packet(&self) -> Option>; fn get_mute_packet(&self) -> Option>; fn set_mute_packet(&self, mute: bool) -> Option>; fn get_surround_sound_packet(&self) -> Option>; fn set_surround_sound_packet(&self, surround_sound: bool) -> Option>; fn get_mic_connected_packet(&self) -> Option>; fn get_pairing_info_packet(&self) -> Option>; fn get_product_color_packet(&self) -> Option>; fn get_side_tone_packet(&self) -> Option>; fn set_side_tone_packet(&self, side_tone_on: bool) -> Option>; fn get_side_tone_volume_packet(&self) -> Option>; fn set_side_tone_volume_packet(&self, volume: u8) -> Option>; fn get_voice_prompt_packet(&self) -> Option>; fn set_voice_prompt_packet(&self, enable: bool) -> Option>; fn get_wireless_connected_status_packet(&self) -> Option>; fn get_sirk_packet(&self) -> Option>; fn reset_sirk_packet(&self) -> Option>; fn get_silent_mode_packet(&self) -> Option>; fn set_silent_mode_packet(&self, silence: bool) -> Option>; /// Set equalizer band (0-9) to dB value (-12.0 to +12.0) /// Bands: 0=32Hz, 1=64Hz, 2=125Hz, 3=250Hz, 4=500Hz, 5=1kHz, 6=2kHz, 7=4kHz, 8=8kHz, 9=16kHz fn set_equalizer_band_packet(&self, _band_index: u8, _db_value: f32) -> Option> { None } fn get_noise_gate_packet(&self) -> Option> { None } fn set_noise_gate_packet(&self, _enable: bool) -> Option> { None } fn get_event_from_device_response(&self, response: &[u8]) -> Option>; fn get_device_state(&self) -> &DeviceState; fn get_device_state_mut(&mut self) -> &mut DeviceState; fn prepare_write(&mut self) {} /// whether the app should periodically listen for packets from the headsets fn allow_passive_refresh(&mut self) -> bool; // Helper methods to check if features are writable fn can_set_mute(&self) -> bool { self.set_mute_packet(false).is_some() } fn can_set_surround_sound(&self) -> bool { self.set_surround_sound_packet(false).is_some() } fn can_set_side_tone(&self) -> bool { self.set_side_tone_packet(false).is_some() } fn can_set_automatic_shutdown(&self) -> bool { self.set_automatic_shut_down_packet(Duration::from_secs(0)) .is_some() } fn can_set_side_tone_volume(&self) -> bool { self.set_side_tone_volume_packet(0).is_some() } fn can_set_voice_prompt(&self) -> bool { self.set_voice_prompt_packet(false).is_some() } fn can_set_silent_mode(&self) -> bool { self.set_silent_mode_packet(false).is_some() } fn can_set_equalizer(&self) -> bool { self.set_equalizer_band_packet(0, 0.0).is_some() } fn can_set_noise_gate(&self) -> bool { self.set_noise_gate_packet(true).is_some() } // Initialize capability flags in device state fn init_capabilities(&mut self) { // Collect capabilities first to avoid borrowing conflicts let can_set_mute = self.can_set_mute(); let can_set_surround_sound = self.can_set_surround_sound(); let can_set_side_tone = self.can_set_side_tone(); let can_set_automatic_shutdown = self.can_set_automatic_shutdown(); let can_set_side_tone_volume = self.can_set_side_tone_volume(); let can_set_voice_prompt = self.can_set_voice_prompt(); let can_set_silent_mode = self.can_set_silent_mode(); let can_set_equalizer = self.can_set_equalizer(); let can_set_noise_gate = self.can_set_noise_gate(); // Now set them in device state let state = self.get_device_state_mut(); state.device_properties.can_set_mute = can_set_mute; state.device_properties.can_set_surround_sound = can_set_surround_sound; state.device_properties.can_set_side_tone = can_set_side_tone; state.device_properties.can_set_automatic_shutdown = can_set_automatic_shutdown; state.device_properties.can_set_side_tone_volume = can_set_side_tone_volume; state.device_properties.can_set_voice_prompt = can_set_voice_prompt; state.device_properties.can_set_silent_mode = can_set_silent_mode; state.device_properties.can_set_equalizer = can_set_equalizer; state.device_properties.can_set_noise_gate = can_set_noise_gate; } fn execute_headset_specific_functionality(&mut self) -> Result<(), DeviceError> { Ok(()) } fn wait_for_updates(&mut self, duration: Duration) -> Option> { let mut buf = self.get_response_buffer(); let res = self .get_device_state() .hid_device .read_timeout(&mut buf[..], duration.as_millis() as i32) .ok()?; if res == 0 { return None; } self.get_event_from_device_response(&buf) } fn get_query_packets(&self) -> Vec> { vec![ self.get_wireless_connected_status_packet(), self.get_charging_packet(), self.get_battery_packet(), self.get_automatic_shut_down_packet(), self.get_mute_packet(), self.get_surround_sound_packet(), self.get_mic_connected_packet(), self.get_pairing_info_packet(), self.get_product_color_packet(), self.get_side_tone_packet(), self.get_side_tone_volume_packet(), self.get_voice_prompt_packet(), self.get_sirk_packet(), self.get_silent_mode_packet(), self.get_noise_gate_packet(), ] .into_iter() .flatten() .collect() } /// Refreshes the state by querying all available information fn active_refresh_state(&mut self) -> Result<(), DeviceError> { let packets = self.get_query_packets(); self.execute_headset_specific_functionality()?; let mut responded = false; for packet in packets.into_iter() { self.prepare_write(); debug_println!("Write packet: {packet:?}"); self.get_device_state().write_hid_report(&packet)?; std::thread::sleep(RESPONSE_DELAY); if let Some(events) = self.wait_for_updates(Duration::from_secs(1)) { for event in events { self.get_device_state_mut().update_self_with_event(&event); } responded = true; } if !matches!( self.get_device_state().device_properties.connected, Some(true) ) { break; } } if responded { Ok(()) } else { Err(DeviceError::NoResponse()) } } /// Refreshes the state by listening for events /// Only the battery level is actively queried because it is not communicated by the device on its own fn passive_refresh_state(&mut self) -> Result<(), DeviceError> { let mut request_active_refresh = false; if self.allow_passive_refresh() { if let Some(events) = self.wait_for_updates(PASSIVE_REFRESH_TIME_OUT) { for event in events { // Some headsets send this if they just turned on so we should refresh the // state if matches!(event, DeviceEvent::WirelessConnected(true)) { request_active_refresh = true; } self.get_device_state_mut().update_self_with_event(&event); } } } if let Some(batter_packet) = self.get_battery_packet() { self.prepare_write(); self.get_device_state().write_hid_report(&batter_packet)?; std::thread::sleep(RESPONSE_DELAY); if let Some(events) = self.wait_for_updates(Duration::from_secs(1)) { for event in events { // Some headsets send this if they just turned on so we should refresh the // state if matches!(event, DeviceEvent::WirelessConnected(true)) { request_active_refresh = true; } self.get_device_state_mut().update_self_with_event(&event); } } } if request_active_refresh { self.active_refresh_state()?; } Ok(()) } fn try_apply(&mut self, command: DeviceEvent) -> Result<(), String> { match command { DeviceEvent::AutomaticShutdownAfter(delay) => { if let Some(packet) = self.set_automatic_shut_down_packet(delay) { self.prepare_write(); if let Err(err) = self.get_device_state().hid_device.write(&packet) { Err(format!( "Failed to set automatic shutdown with error: {:?}", err ))?; } } else { Err("ERROR: Automatic shutdown is not supported on this device".to_string())?; } } DeviceEvent::Muted(mute) => { if let Some(packet) = self.set_mute_packet(mute) { self.prepare_write(); if let Err(err) = self.get_device_state().hid_device.write(&packet) { Err(format!("Failed to mute with error: {:?}", err))?; } } else { Err("ERROR: Microphone mute control is not supported on this device (hardware button only)")?; } } DeviceEvent::SideToneOn(enable) => { if let Some(packet) = self.set_side_tone_packet(enable) { self.prepare_write(); if let Err(err) = self.get_device_state().hid_device.write(&packet) { Err(format!("Failed to enable side tone with error: {:?}", err))?; } } else { Err("ERROR: Side tone control is not supported on this device".to_string())?; } } DeviceEvent::SideToneVolume(volume) => { if let Some(packet) = self.set_side_tone_volume_packet(volume) { self.prepare_write(); if let Err(err) = self.get_device_state().hid_device.write(&packet) { Err(format!( "Failed to set side tone volume with error: {:?}", err ))?; } } else { Err( "ERROR: Side tone volume control is not supported on this device" .to_string(), )?; } } DeviceEvent::VoicePrompt(enable) => { if let Some(packet) = self.set_voice_prompt_packet(enable) { self.prepare_write(); if let Err(err) = self.get_device_state().hid_device.write(&packet) { Err(format!( "Failed to enable voice prompt with error: {:?}", err ))?; } } else { Err("ERROR: Voice prompt control is not supported on this device")?; } } DeviceEvent::SurroundSound(surround_sound) => { if let Some(packet) = self.set_surround_sound_packet(surround_sound) { self.prepare_write(); if let Err(err) = self.get_device_state().hid_device.write(&packet) { Err(format!( "Failed to set surround sound with error: {:?}", err ))?; } } else { Err("ERROR: Surround sound control is not supported on this device")?; } } DeviceEvent::Silent(mute_playback) => { if let Some(packet) = self.set_silent_mode_packet(mute_playback) { self.prepare_write(); if let Err(err) = self.get_device_state().hid_device.write(&packet) { Err(format!("Failed to mute playback with error: {:?}", err))?; } } else { Err("ERROR: Playback mute control is not supported on this device")?; } } DeviceEvent::NoiseGateActive(activate) => { if let Some(packet) = self.set_noise_gate_packet(activate) { self.prepare_write(); if let Err(err) = self.get_device_state().hid_device.write(&packet) { Err(format!( "Failed to activate noise gate with error: {:?}", err ))?; } } else { Err("ERROR: Activating noise gate is not supported on this device")?; } } _ => (), } Ok(()) } fn clear_state(&mut self) { let product_id = self.get_device_state().device_properties.product_id; let vendor_id = self.get_device_state().device_properties.vendor_id; let device_name = self .get_device_state() .device_properties .device_name .clone(); self.get_device_state_mut().device_properties = DeviceProperties::new(product_id, vendor_id, device_name) } }