1use hidpp_transport::HidapiChannel;
7use tracing::{debug, trace};
8
9use crate::error::{HidppErrorCode, ProtocolError, Result};
10use crate::protocol::{build_long_request, get_error_code, is_error_response};
11
12pub struct AdjustableDpiFeature {
14 device_index: u8,
15 feature_index: u8,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct SensorInfo {
21 pub index: u8,
23 pub dpi_min: u16,
25 pub dpi_max: u16,
27 pub dpi_step: u16,
29 pub default_dpi: u16,
31 pub dpi_list: Vec<u16>,
33}
34
35impl AdjustableDpiFeature {
36 #[must_use]
42 pub fn new(device_index: u8, feature_index: u8) -> Self {
43 Self {
44 device_index,
45 feature_index,
46 }
47 }
48
49 pub async fn get_sensor_count(&self, channel: &HidapiChannel) -> Result<u8> {
56 let request = build_long_request(self.device_index, self.feature_index, 0x00, &[]);
58
59 trace!("getting sensor count");
60 let response = channel.request(&request, 5).await?;
61
62 if is_error_response(&response) {
63 let code = get_error_code(&response).unwrap_or(0);
64 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
65 }
66
67 if response.len() < 5 {
68 return Err(ProtocolError::InvalidResponse(
69 "sensor count response too short".to_string(),
70 ));
71 }
72
73 let count = response[4];
74 debug!(count, "got sensor count");
75 Ok(count)
76 }
77
78 pub async fn get_sensor_dpi_info(
87 &self,
88 channel: &HidapiChannel,
89 sensor_index: u8,
90 ) -> Result<SensorInfo> {
91 let request =
93 build_long_request(self.device_index, self.feature_index, 0x01, &[sensor_index]);
94
95 trace!(sensor_index, "getting sensor DPI info");
96 let response = channel.request(&request, 5).await?;
97
98 if is_error_response(&response) {
99 let code = get_error_code(&response).unwrap_or(0);
100 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
101 }
102
103 if response.len() < 9 {
104 return Err(ProtocolError::InvalidResponse(
105 "sensor DPI info response too short".to_string(),
106 ));
107 }
108
109 let byte4 = response[4];
112 let byte5 = response[5];
113 let byte6 = response[6];
114 let byte7 = response.get(7).copied().unwrap_or(0);
115 let byte8 = response.get(8).copied().unwrap_or(0);
116
117 let dpi_step = u16::from_be_bytes([byte4, byte5]);
120
121 let (dpi_min, dpi_max, default_dpi, dpi_list) = if dpi_step > 0 {
122 let dpi_min = u16::from_be_bytes([byte6, byte7]);
124 let dpi_max = u16::from_be_bytes([byte8, response.get(9).copied().unwrap_or(0)]);
125 (dpi_min, dpi_max, dpi_min, Vec::new())
126 } else {
127 let mut dpi_list = Vec::new();
129 let mut i = 4;
130 while i + 1 < response.len() {
131 let dpi = u16::from_be_bytes([response[i], response[i + 1]]);
132 if dpi == 0 {
133 break;
134 }
135 let actual_dpi = dpi & 0x7FFF;
137 if actual_dpi > 0 {
138 dpi_list.push(actual_dpi);
139 }
140 i += 2;
141 }
142
143 let min = dpi_list.iter().copied().min().unwrap_or(400);
144 let max = dpi_list.iter().copied().max().unwrap_or(3200);
145 let default = dpi_list.first().copied().unwrap_or(1000);
146 (min, max, default, dpi_list)
147 };
148
149 let info = SensorInfo {
150 index: sensor_index,
151 dpi_min,
152 dpi_max,
153 dpi_step,
154 default_dpi,
155 dpi_list,
156 };
157
158 debug!(
159 sensor = sensor_index,
160 min = dpi_min,
161 max = dpi_max,
162 step = dpi_step,
163 "got sensor DPI info"
164 );
165
166 Ok(info)
167 }
168
169 pub async fn get_sensor_dpi(&self, channel: &HidapiChannel, sensor_index: u8) -> Result<u16> {
178 let request =
180 build_long_request(self.device_index, self.feature_index, 0x02, &[sensor_index]);
181
182 trace!(sensor_index, "getting sensor DPI");
183 let response = channel.request(&request, 5).await?;
184
185 if is_error_response(&response) {
186 let code = get_error_code(&response).unwrap_or(0);
187 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
188 }
189
190 if response.len() < 7 {
191 return Err(ProtocolError::InvalidResponse(
192 "sensor DPI response too short".to_string(),
193 ));
194 }
195
196 let dpi = u16::from_be_bytes([response[5], response[6]]);
198 debug!(sensor = sensor_index, dpi, "got sensor DPI");
199
200 Ok(dpi)
201 }
202
203 pub async fn set_sensor_dpi(
213 &self,
214 channel: &HidapiChannel,
215 sensor_index: u8,
216 dpi: u16,
217 ) -> Result<()> {
218 let dpi_bytes = dpi.to_be_bytes();
220 let request = build_long_request(
221 self.device_index,
222 self.feature_index,
223 0x03,
224 &[sensor_index, dpi_bytes[0], dpi_bytes[1]],
225 );
226
227 trace!(sensor_index, dpi, "setting sensor DPI");
228 let response = channel.request(&request, 5).await?;
229
230 if is_error_response(&response) {
231 let code = get_error_code(&response).unwrap_or(0);
232 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
233 }
234
235 debug!(sensor = sensor_index, dpi, "set sensor DPI");
236 Ok(())
237 }
238
239 pub async fn get_default_dpi(&self, channel: &HidapiChannel, sensor_index: u8) -> Result<u16> {
248 let request =
250 build_long_request(self.device_index, self.feature_index, 0x04, &[sensor_index]);
251
252 trace!(sensor_index, "getting default DPI");
253 let response = channel.request(&request, 5).await?;
254
255 if is_error_response(&response) {
256 let code = get_error_code(&response).unwrap_or(0);
257 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
258 }
259
260 if response.len() < 7 {
261 return Err(ProtocolError::InvalidResponse(
262 "default DPI response too short".to_string(),
263 ));
264 }
265
266 let dpi = u16::from_be_bytes([response[5], response[6]]);
267 debug!(sensor = sensor_index, dpi, "got default DPI");
268
269 Ok(dpi)
270 }
271}
272
273impl AdjustableDpiFeature {
275 pub async fn get_dpi(&self, channel: &HidapiChannel) -> Result<u16> {
280 self.get_sensor_dpi(channel, 0).await
281 }
282
283 pub async fn set_dpi(&self, channel: &HidapiChannel, dpi: u16) -> Result<()> {
288 self.set_sensor_dpi(channel, 0, dpi).await
289 }
290
291 pub async fn get_dpi_info(&self, channel: &HidapiChannel) -> Result<SensorInfo> {
296 self.get_sensor_dpi_info(channel, 0).await
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_sensor_info() {
306 let info = SensorInfo {
307 index: 0,
308 dpi_min: 400,
309 dpi_max: 8000,
310 dpi_step: 50,
311 default_dpi: 1000,
312 dpi_list: vec![],
313 };
314
315 assert_eq!(info.dpi_min, 400);
316 assert_eq!(info.dpi_max, 8000);
317 }
318}