logiops_core/features/
feature_set.rs1use 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 FeatureSetFeature {
14 device_index: u8,
15 feature_index: u8,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct FeatureInfo {
21 pub feature_id: u16,
23 pub index: u8,
25 pub feature_type: FeatureType,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
31pub struct FeatureType {
32 pub obsolete: bool,
34 pub hidden: bool,
36 pub software_hidden: bool,
38}
39
40impl FeatureType {
41 #[must_use]
43 pub fn from_byte(byte: u8) -> Self {
44 Self {
45 obsolete: (byte & 0x80) != 0,
46 hidden: (byte & 0x40) != 0,
47 software_hidden: (byte & 0x20) != 0,
48 }
49 }
50
51 #[must_use]
53 pub fn is_visible(&self) -> bool {
54 !self.hidden && !self.software_hidden
55 }
56}
57
58impl FeatureSetFeature {
59 #[must_use]
65 pub fn new(device_index: u8, feature_index: u8) -> Self {
66 Self {
67 device_index,
68 feature_index,
69 }
70 }
71
72 pub async fn get_count(&self, channel: &HidapiChannel) -> Result<u8> {
77 let request = build_long_request(self.device_index, self.feature_index, 0x00, &[]);
79
80 trace!("getting feature count");
81 let response = channel.request(&request, 5).await?;
82
83 if is_error_response(&response) {
84 let code = get_error_code(&response).unwrap_or(0);
85 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
86 }
87
88 if response.len() < 5 {
89 return Err(ProtocolError::InvalidResponse(
90 "feature count response too short".to_string(),
91 ));
92 }
93
94 let count = response[4];
95 debug!(count, "got feature count");
96 Ok(count)
97 }
98
99 pub async fn get_feature_id(&self, channel: &HidapiChannel, index: u8) -> Result<FeatureInfo> {
108 let request = build_long_request(self.device_index, self.feature_index, 0x01, &[index]);
110
111 trace!(index, "getting feature ID");
112 let response = channel.request(&request, 5).await?;
113
114 if is_error_response(&response) {
115 let code = get_error_code(&response).unwrap_or(0);
116 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
117 }
118
119 if response.len() < 7 {
120 return Err(ProtocolError::InvalidResponse(
121 "feature ID response too short".to_string(),
122 ));
123 }
124
125 let feature_id = u16::from_be_bytes([response[4], response[5]]);
127 let feature_type = FeatureType::from_byte(response[6]);
128
129 debug!(
130 index,
131 feature_id = format!("0x{feature_id:04X}"),
132 obsolete = feature_type.obsolete,
133 hidden = feature_type.hidden,
134 "got feature ID"
135 );
136
137 Ok(FeatureInfo {
138 feature_id,
139 index,
140 feature_type,
141 })
142 }
143
144 pub async fn enumerate_all(&self, channel: &HidapiChannel) -> Result<Vec<FeatureInfo>> {
151 let count = self.get_count(channel).await?;
152 let mut features = Vec::with_capacity(count as usize);
153
154 for i in 0..count {
155 match self.get_feature_id(channel, i).await {
156 Ok(info) => features.push(info),
157 Err(e) => {
158 debug!(index = i, error = %e, "failed to get feature, skipping");
159 }
160 }
161 }
162
163 debug!(total = features.len(), "enumerated all features");
164 Ok(features)
165 }
166
167 pub async fn enumerate_visible(&self, channel: &HidapiChannel) -> Result<Vec<FeatureInfo>> {
172 let all = self.enumerate_all(channel).await?;
173 Ok(all
174 .into_iter()
175 .filter(|f| f.feature_type.is_visible())
176 .collect())
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_feature_type_parsing() {
186 let normal = FeatureType::from_byte(0x00);
187 assert!(!normal.obsolete);
188 assert!(!normal.hidden);
189 assert!(!normal.software_hidden);
190 assert!(normal.is_visible());
191
192 let obsolete = FeatureType::from_byte(0x80);
193 assert!(obsolete.obsolete);
194 assert!(!obsolete.hidden);
195 assert!(obsolete.is_visible());
196
197 let hidden = FeatureType::from_byte(0x40);
198 assert!(hidden.hidden);
199 assert!(!hidden.is_visible());
200
201 let sw_hidden = FeatureType::from_byte(0x20);
202 assert!(sw_hidden.software_hidden);
203 assert!(!sw_hidden.is_visible());
204 }
205}