1use std::collections::HashMap;
4
5use hidpp_transport::HidapiChannel;
6use tracing::{debug, info, warn};
7
8use crate::error::Result;
9use crate::features::device_name::DeviceKind;
10use crate::features::{DeviceNameFeature, RootFeature};
11use crate::protocol::{feature_id, ProtocolVersion, DEVICE_INDEX_DIRECT};
12
13#[derive(Debug, Clone)]
15pub struct DeviceInfo {
16 pub name: String,
18 pub protocol_version: ProtocolVersion,
20 pub device_kind: DeviceKind,
22 pub features: HashMap<u16, u8>,
24}
25
26pub struct HidppDevice {
28 channel: HidapiChannel,
29 device_index: u8,
30 info: Option<DeviceInfo>,
31}
32
33impl HidppDevice {
34 #[must_use]
40 pub fn new(channel: HidapiChannel, device_index: u8) -> Self {
41 Self {
42 channel,
43 device_index,
44 info: None,
45 }
46 }
47
48 #[must_use]
50 pub fn direct(channel: HidapiChannel) -> Self {
51 Self::new(channel, DEVICE_INDEX_DIRECT)
52 }
53
54 #[must_use]
56 pub fn channel(&self) -> &HidapiChannel {
57 &self.channel
58 }
59
60 #[must_use]
62 pub fn device_index(&self) -> u8 {
63 self.device_index
64 }
65
66 #[must_use]
68 pub fn info(&self) -> Option<&DeviceInfo> {
69 self.info.as_ref()
70 }
71
72 pub async fn initialize(&mut self) -> Result<&DeviceInfo> {
82 info!(
83 path = %self.channel.path(),
84 device_index = self.device_index,
85 "initializing HID++ device"
86 );
87
88 let root = RootFeature::new(self.device_index);
89
90 let protocol_version = root.ping(&self.channel).await?;
92
93 if !protocol_version.supports_hidpp2() {
94 warn!(
95 version = %protocol_version,
96 "device uses HID++ 1.0, limited support available"
97 );
98 }
99
100 let mut features = HashMap::new();
102
103 features.insert(feature_id::IROOT, 0);
105
106 let feature_ids = [
108 feature_id::IFEATURE_SET,
109 feature_id::DEVICE_NAME,
110 feature_id::FIRMWARE_INFO,
111 feature_id::UNIFIED_BATTERY,
112 feature_id::BATTERY_STATUS,
113 feature_id::REPROG_CONTROLS,
114 feature_id::SMART_SHIFT,
115 feature_id::HIRES_SCROLLING,
116 feature_id::THUMB_WHEEL,
117 feature_id::ADJUSTABLE_DPI,
118 feature_id::ONBOARD_PROFILES,
119 ];
120
121 for &fid in &feature_ids {
122 match root.get_feature_index(&self.channel, fid).await {
123 Ok(Some(idx)) => {
124 features.insert(fid, idx);
125 }
126 Ok(None) => {
127 debug!(feature_id = format!("0x{fid:04X}"), "feature not supported");
128 }
129 Err(e) => {
130 warn!(
131 feature_id = format!("0x{fid:04X}"),
132 error = %e,
133 "error querying feature"
134 );
135 }
136 }
137 }
138
139 let name = if let Some(&name_idx) = features.get(&feature_id::DEVICE_NAME) {
141 let name_feature = DeviceNameFeature::new(self.device_index, name_idx);
142 match name_feature.get_name(&self.channel).await {
143 Ok(name) => name,
144 Err(e) => {
145 warn!(error = %e, "failed to get device name");
146 "Unknown Device".to_string()
147 }
148 }
149 } else {
150 "Unknown Device".to_string()
151 };
152
153 let device_kind = if let Some(&name_idx) = features.get(&feature_id::DEVICE_NAME) {
155 let name_feature = DeviceNameFeature::new(self.device_index, name_idx);
156 match name_feature.get_device_type(&self.channel).await {
157 Ok(kind) => kind,
158 Err(e) => {
159 warn!(error = %e, "failed to get device type");
160 DeviceKind::Unknown(0xFF)
161 }
162 }
163 } else {
164 DeviceKind::Unknown(0xFF)
165 };
166
167 let info = DeviceInfo {
168 name,
169 protocol_version,
170 device_kind,
171 features,
172 };
173
174 info!(
175 name = %info.name,
176 version = %info.protocol_version,
177 device_type = %info.device_kind,
178 feature_count = info.features.len(),
179 "device initialized"
180 );
181
182 self.info = Some(info);
183 Ok(self.info.as_ref().unwrap())
184 }
185
186 #[must_use]
188 pub fn has_feature(&self, feature_id: u16) -> bool {
189 self.info
190 .as_ref()
191 .is_some_and(|i| i.features.contains_key(&feature_id))
192 }
193
194 #[must_use]
196 pub fn get_feature_index(&self, feature_id: u16) -> Option<u8> {
197 self.info.as_ref()?.features.get(&feature_id).copied()
198 }
199}
200
201impl std::fmt::Debug for HidppDevice {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 f.debug_struct("HidppDevice")
204 .field("path", &self.channel.path())
205 .field("device_index", &self.device_index)
206 .field("info", &self.info)
207 .finish()
208 }
209}