diff --git a/src/devices/mod.rs b/src/devices/mod.rs index c55c43d..3af4b40 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -13,63 +13,72 @@ use crate::{ }, }; use hidapi::{HidApi, HidDevice, HidError}; -use std::{fmt::Display, time::Duration}; +use std::{collections::HashSet, fmt::Display, time::Duration}; use thistermination::TerminationFull; -// Possible vendor IDs [HyperX, HP] -const VENDOR_IDS: [u16; 2] = [0x0951, 0x03F0]; -// All supported product IDs -const PRODUCT_IDS: [u16; 13] = [ - 0x1718, 0x018B, 0x0D93, 0x0696, 0x0b92, 0x05B7, 0x16EA, 0x16EB, 0x0c9d, 0x06BE, 0x1743, 0x1765, - 0x098D, +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)), + }, ]; const RESPONSE_BUFFER_SIZE: usize = 256; const RESPONSE_DELAY: Duration = Duration::from_millis(50); pub fn connect_compatible_device() -> Result, DeviceError> { - let state = DeviceState::new(&PRODUCT_IDS, &VENDOR_IDS)?; + 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 state = DeviceState::new(&all_product_ids, &all_vendor_ids)?; debug_println!("Found device selecting handler"); let name = state .hid_device .get_product_string()? .ok_or(DeviceError::NoDeviceFound())?; println!("Connecting to {}", name); - let mut device: Box = match (state.vendor_id, state.product_id) { - (v, p) - if cloud_ii_wireless::VENDOR_IDS.contains(&v) - && cloud_ii_wireless::PRODUCT_IDS.contains(&p) => - { - Box::new(CloudIIWireless::new_from_state(state)) - } - (v, p) - if cloud_ii_wireless_dts::VENDOR_IDS.contains(&v) - && cloud_ii_wireless_dts::PRODUCT_IDS.contains(&p) => - { - Box::new(CloudIIWirelessDTS::new_from_state(state)) - } - (v, p) - if cloud_iii_s_wireless::VENDOR_IDS.contains(&v) - && cloud_iii_s_wireless::PRODUCT_IDS.contains(&p) => - { - Box::new(CloudIIISWireless::new_from_state(state)) - } - (v, p) - if cloud_iii_wireless::VENDOR_IDS.contains(&v) - && cloud_iii_wireless::PRODUCT_IDS.contains(&p) => - { - Box::new(CloudIIIWireless::new_from_state(state)) - } - (v, p) - if cloud_alpha_wireless::VENDOR_IDS.contains(&v) - && cloud_alpha_wireless::PRODUCT_IDS.contains(&p) => - { - Box::new(CloudAlphaWireless::new_from_state(state)) - } - (_, _) => return Err(DeviceError::NoDeviceFound()), - }; + let entry = DEVICE_REGISTER + .iter() + .find(|e| { + e.vendor_ids.contains(&state.vendor_id) && e.product_ids.contains(&state.product_id) + }) + .ok_or(DeviceError::NoDeviceFound())?; - // Initialize capability flags + let mut device = (entry.factory)(state); device.init_capabilities(); Ok(device) @@ -114,6 +123,7 @@ impl Display for DeviceState { impl DeviceState { pub fn new(product_ids: &[u16], vendor_ids: &[u16]) -> Result { let hid_api = HidApi::new()?; + let mut potential_devices = HashSet::new(); debug_println!( "Devices: {:?}", hid_api @@ -122,7 +132,7 @@ impl DeviceState { .map(|d| { (d.vendor_id(), d.product_id(), d.product_string()) }) .collect::)>>() ); - let (hid_device, product_id, vendor_id) = hid_api + let device_lookup_result = hid_api .device_list() .find_map(|info| { if product_ids.contains(&info.product_id()) @@ -140,10 +150,44 @@ impl DeviceState { info.vendor_id(), )) } 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 } }) - .ok_or(DeviceError::NoDeviceFound())?; + .ok_or(DeviceError::NoDeviceFound()); + let (hid_device, product_id, vendor_id) = match device_lookup_result { + Ok(value) => value, + Err(DeviceError::NoDeviceFound()) => { + 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"); + println!( + "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" } + ) + } + return Err(DeviceError::NoDeviceFound()); + } + Err(e) => return Err(e), + }; let hid_device = hid_device?; let device_name = hid_device.get_product_string()?; Ok(DeviceState {