diff --git a/99-HyperHeadset.rules b/99-HyperHeadset.rules index e506d82..6790f44 100644 --- a/99-HyperHeadset.rules +++ b/99-HyperHeadset.rules @@ -13,6 +13,7 @@ SUBSYSTEMS=="usb", ATTRS{idProduct}=="1743", ATTRS{idVendor}=="03f0", MODE="0666 SUBSYSTEMS=="usb", ATTRS{idProduct}=="069f", ATTRS{idVendor}=="03f0", MODE="0666" SUBSYSTEMS=="usb", ATTRS{idProduct}=="0995", ATTRS{idVendor}=="03f0", MODE="0666" SUBSYSTEMS=="usb", ATTRS{idProduct}=="02cc", ATTRS{idVendor}=="03f0", MODE="0666" +SUBSYSTEMS=="usb", ATTRS{idProduct}=="0e90", ATTRS{idVendor}=="03f0", MODE="0666" KERNEL=="hidraw*", ATTRS{idProduct}=="0d93", ATTRS{idVendor}=="03f0", MODE="0666" @@ -30,3 +31,4 @@ KERNEL=="hidraw*", ATTRS{idProduct}=="1743", ATTRS{idVendor}=="03f0", MODE="0666 KERNEL=="hidraw*", ATTRS{idProduct}=="069f", ATTRS{idVendor}=="03f0", MODE="0666" KERNEL=="hidraw*", ATTRS{idProduct}=="0995", ATTRS{idVendor}=="03f0", MODE="0666" KERNEL=="hidraw*", ATTRS{idProduct}=="02cc", ATTRS{idVendor}=="03f0", MODE="0666" +KERNEL=="hidraw*", ATTRS{idProduct}=="0e90", ATTRS{idVendor}=="03f0", MODE="0666" diff --git a/README.md b/README.md index a23dd42..8a3e6ea 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Both the CLI and tray applications are compatible with Linux, MacOS, and Windows - HyperX Cloud III S Wireless - HyperX Cloud Stinger 2 Wireless - HyperX Cloud Flight S +- HyperX Cloud Flight Wireless - HyperX Cloud Alpha Wireless If your headset is not supported, feel free to open an issue; be sure to include the name, product ID, and vendor ID. @@ -37,7 +38,7 @@ No manual setup required (dependencies and udev rules are handled automatically) ```bash yay -S hyper-headset-git ``` -or +or ```bash yay -S hyper-headset-bin ``` @@ -106,6 +107,7 @@ SUBSYSTEMS=="usb", ATTRS{idProduct}=="1743", ATTRS{idVendor}=="03f0", MODE="0666 SUBSYSTEMS=="usb", ATTRS{idProduct}=="069f", ATTRS{idVendor}=="03f0", MODE="0666" SUBSYSTEMS=="usb", ATTRS{idProduct}=="0995", ATTRS{idVendor}=="03f0", MODE="0666" SUBSYSTEMS=="usb", ATTRS{idProduct}=="02cc", ATTRS{idVendor}=="03f0", MODE="0666" +SUBSYSTEMS=="usb", ATTRS{idProduct}=="0e90", ATTRS{idVendor}=="03f0", MODE="0666" KERNEL=="hidraw*", ATTRS{idProduct}=="0d93", ATTRS{idVendor}=="03f0", MODE="0666" KERNEL=="hidraw*", ATTRS{idProduct}=="018b", ATTRS{idVendor}=="03f0", MODE="0666" @@ -122,6 +124,7 @@ KERNEL=="hidraw*", ATTRS{idProduct}=="1743", ATTRS{idVendor}=="03f0", MODE="0666 KERNEL=="hidraw*", ATTRS{idProduct}=="069f", ATTRS{idVendor}=="03f0", MODE="0666" KERNEL=="hidraw*", ATTRS{idProduct}=="0995", ATTRS{idVendor}=="03f0", MODE="0666" KERNEL=="hidraw*", ATTRS{idProduct}=="02cc", ATTRS{idVendor}=="03f0", MODE="0666" +KERNEL=="hidraw*", ATTRS{idProduct}=="0e90", ATTRS{idVendor}=="03f0", MODE="0666" ``` Once created, replug the wireless dongle. diff --git a/src/devices/cloud_flight_wireless.rs b/src/devices/cloud_flight_wireless.rs new file mode 100644 index 0000000..8cef004 --- /dev/null +++ b/src/devices/cloud_flight_wireless.rs @@ -0,0 +1,174 @@ +use crate::{ + debug_println, + devices::{ChargingStatus, Device, DeviceEvent, DeviceState}, +}; +use std::time::Duration; + +const HP: u16 = 0x03F0; +pub const VENDOR_IDS: [u16; 1] = [HP]; +pub const PRODUCT_IDS: [u16; 1] = [0x0e90]; + +const BASE_PACKET: [u8; 64] = { + let mut packet = [0; 64]; + packet[0] = 33; + packet[1] = 255; + packet +}; + +const GET_BATTERY_CMD_ID: u8 = 5; + +pub struct CloudFlightWireless { + state: DeviceState, +} + +impl CloudFlightWireless { + pub fn new_from_state(state: DeviceState) -> Self { + let mut state = state; + state.device_properties.connected = Some(true); + CloudFlightWireless { state } + } +} + +const THRESHOLDS: [u16; 20] = [ + 3328, 3584, 3674, 3704, 3732, 3744, 3754, 3764, 3774, 3784, 3794, 3804, 3824, 3840, 3860, 3890, + 3910, 3940, 3960, 3970, +]; + +const PERCENTAGES: [u8; 20] = [ + 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, +]; + +impl Device for CloudFlightWireless { + fn get_battery_packet(&self) -> Option> { + let mut tmp = BASE_PACKET.to_vec(); + tmp[2] = GET_BATTERY_CMD_ID; + Some(tmp) + } + + fn get_event_from_device_response(&self, response: &[u8]) -> Option> { + debug_println!("Read packet: {:?}", response); + if response[0] != BASE_PACKET[0] || response[1] != BASE_PACKET[1] { + return None; + } + match response[2] { + GET_BATTERY_CMD_ID => { + let upper = response[3]; + let lower = response[4]; + let mut events = Vec::new(); + if (upper == 16 && lower >= 20) || upper >= 17 { + events.push(DeviceEvent::Charging(ChargingStatus::Charging)); + } else { + let index = + match THRESHOLDS.binary_search(&(((upper as u16) << 8) | (lower as u16))) { + Ok(i) => i, + Err(0) => 0, + Err(i) => i - 1, + }; + events.push(DeviceEvent::BatterLevel(PERCENTAGES[index])); + events.push(DeviceEvent::Charging(ChargingStatus::NotCharging)); + } + Some(events) + } + _ => { + debug_println!("Unknown device event: {:?}", response); + None + } + } + } + + fn get_device_state(&self) -> &DeviceState { + &self.state + } + + fn get_device_state_mut(&mut self) -> &mut DeviceState { + &mut self.state + } + + fn allow_passive_refresh(&mut self) -> bool { + true + } + + fn get_charging_packet(&self) -> Option> { + None + } + + fn set_automatic_shut_down_packet(&self, _shutdown_after: Duration) -> Option> { + None + } + + fn get_automatic_shut_down_packet(&self) -> Option> { + None + } + + fn get_mute_packet(&self) -> Option> { + None + } + + fn set_mute_packet(&self, _mute: bool) -> Option> { + None + } + + fn get_surround_sound_packet(&self) -> Option> { + None + } + + fn set_surround_sound_packet(&self, _surround_sound: bool) -> Option> { + None + } + + fn get_mic_connected_packet(&self) -> Option> { + None + } + + fn get_pairing_info_packet(&self) -> Option> { + None + } + + fn get_product_color_packet(&self) -> Option> { + None + } + + fn get_side_tone_packet(&self) -> Option> { + None + } + + fn set_side_tone_packet(&self, _side_tone_on: bool) -> Option> { + None + } + + fn get_side_tone_volume_packet(&self) -> Option> { + None + } + + fn set_side_tone_volume_packet(&self, _volume: u8) -> Option> { + None + } + + fn get_voice_prompt_packet(&self) -> Option> { + None + } + + fn set_voice_prompt_packet(&self, _enable: bool) -> Option> { + None + } + + fn get_wireless_connected_status_packet(&self) -> Option> { + None + } + + fn get_sirk_packet(&self) -> Option> { + None + } + + fn reset_sirk_packet(&self) -> Option> { + None + } + + fn get_silent_mode_packet(&self) -> Option> { + None + } + + fn set_silent_mode_packet(&self, _silence: bool) -> Option> { + None + } +} diff --git a/src/devices/mod.rs b/src/devices/mod.rs index 8749ff3..11b37ef 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,4 +1,5 @@ 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; @@ -8,9 +9,10 @@ pub mod cloud_iii_wireless; use crate::{ debug_println, devices::{ - cloud_alpha_wireless::CloudAlphaWireless, cloud_ii_core_wireless::CloudIICoreWireless, - cloud_ii_wireless::CloudIIWireless, cloud_ii_wireless_dts::CloudIIWirelessDTS, - cloud_iii_s_wireless::CloudIIISWireless, cloud_iii_wireless::CloudIIIWireless, + 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}; @@ -62,6 +64,11 @@ const DEVICE_REGISTER: &[DeviceEntry] = &[ 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;