logiops_core/features/
device_name.rs1use hidpp_transport::HidapiChannel;
4use tracing::{debug, trace};
5
6use crate::error::{HidppErrorCode, ProtocolError, Result};
7use crate::protocol::{build_long_request, get_error_code, is_error_response};
8
9pub struct DeviceNameFeature {
11 device_index: u8,
12 feature_index: u8,
13}
14
15impl DeviceNameFeature {
16 #[must_use]
22 pub fn new(device_index: u8, feature_index: u8) -> Self {
23 Self {
24 device_index,
25 feature_index,
26 }
27 }
28
29 pub async fn get_name_length(&self, channel: &HidapiChannel) -> Result<u8> {
34 let request = build_long_request(self.device_index, self.feature_index, 0x00, &[]);
36
37 trace!("getting device name length");
38 let response = channel.request(&request, 5).await?;
39
40 if is_error_response(&response) {
41 let code = get_error_code(&response).unwrap_or(0);
42 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
43 }
44
45 if response.len() < 5 {
46 return Err(ProtocolError::InvalidResponse(
47 "name length response too short".to_string(),
48 ));
49 }
50
51 Ok(response[4])
52 }
53
54 pub async fn get_name(&self, channel: &HidapiChannel) -> Result<String> {
61 let length = self.get_name_length(channel).await?;
62 let mut name = String::with_capacity(length as usize);
63
64 let mut offset = 0u8;
67 while offset < length {
68 let request =
70 build_long_request(self.device_index, self.feature_index, 0x01, &[offset]);
71
72 trace!(offset, "getting device name chunk");
73 let response = channel.request(&request, 5).await?;
74
75 if is_error_response(&response) {
76 let code = get_error_code(&response).unwrap_or(0);
77 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
78 }
79
80 for &byte in response.iter().skip(4) {
82 if byte == 0 || offset >= length {
83 break;
84 }
85 if byte.is_ascii() {
86 name.push(byte as char);
87 }
88 offset += 1;
89 }
90 }
91
92 debug!(name = %name, "got device name");
93 Ok(name)
94 }
95
96 pub async fn get_device_type(&self, channel: &HidapiChannel) -> Result<DeviceKind> {
103 let request = build_long_request(self.device_index, self.feature_index, 0x02, &[]);
105
106 trace!("getting device type");
107 let response = channel.request(&request, 5).await?;
108
109 if is_error_response(&response) {
110 let code = get_error_code(&response).unwrap_or(0);
111 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
112 }
113
114 if response.len() < 5 {
115 return Err(ProtocolError::InvalidResponse(
116 "device type response too short".to_string(),
117 ));
118 }
119
120 let device_type = DeviceKind::from_byte(response[4]);
121 debug!(device_type = ?device_type, "got device type");
122 Ok(device_type)
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum DeviceKind {
129 Keyboard,
131 RemoteControl,
133 Numpad,
135 Mouse,
137 Touchpad,
139 Trackball,
141 Presenter,
143 Receiver,
145 Headset,
147 Unknown(u8),
149}
150
151impl DeviceKind {
152 #[must_use]
154 pub fn from_byte(code: u8) -> Self {
155 match code {
156 0x00 => Self::Keyboard,
157 0x01 => Self::RemoteControl,
158 0x02 => Self::Numpad,
159 0x03 => Self::Mouse,
160 0x04 => Self::Touchpad,
161 0x05 => Self::Trackball,
162 0x06 => Self::Presenter,
163 0x07 => Self::Receiver,
164 0x08 => Self::Headset,
165 code => Self::Unknown(code),
166 }
167 }
168
169 #[must_use]
171 pub fn is_pointing_device(&self) -> bool {
172 matches!(self, Self::Mouse | Self::Trackball | Self::Touchpad)
173 }
174}
175
176impl std::fmt::Display for DeviceKind {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 match self {
179 Self::Keyboard => write!(f, "Keyboard"),
180 Self::RemoteControl => write!(f, "Remote Control"),
181 Self::Numpad => write!(f, "Numpad"),
182 Self::Mouse => write!(f, "Mouse"),
183 Self::Touchpad => write!(f, "Touchpad"),
184 Self::Trackball => write!(f, "Trackball"),
185 Self::Presenter => write!(f, "Presenter"),
186 Self::Receiver => write!(f, "Receiver"),
187 Self::Headset => write!(f, "Headset"),
188 Self::Unknown(code) => write!(f, "Unknown(0x{code:02X})"),
189 }
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_device_kind() {
199 assert!(DeviceKind::Mouse.is_pointing_device());
200 assert!(DeviceKind::Trackball.is_pointing_device());
201 assert!(!DeviceKind::Keyboard.is_pointing_device());
202 }
203}