hidpp/feature/fn_inversion/
mod.rs1use std::sync::Arc;
4
5use num_enum::{IntoPrimitive, TryFromPrimitive};
6
7use crate::{
8 channel::HidppChannel,
9 feature::{CreatableFeature, Feature, FeatureEndpoint, hosts_info::HostIndex},
10 protocol::v20::Hidpp20Error,
11};
12
13bitflags::bitflags! {
14 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
17 pub struct FnInversionCapabilities: u8 {
18 const MANUAL_FN_LOCK = 1 << 0;
20 }
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26#[non_exhaustive]
27#[repr(u8)]
28pub enum FnInversionState {
29 Off = 0,
31 On = 1,
33}
34
35impl From<bool> for FnInversionState {
36 fn from(value: bool) -> Self {
37 if value { Self::On } else { Self::Off }
38 }
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44#[non_exhaustive]
45pub struct FnInversionInfo {
46 pub host_index: HostIndex,
48 pub state: FnInversionState,
50 pub default_state: FnInversionState,
52 pub capabilities: FnInversionCapabilities,
54}
55
56#[derive(Clone)]
58pub struct FnInversionMultiHostFeature {
59 endpoint: FeatureEndpoint,
61}
62
63impl CreatableFeature for FnInversionMultiHostFeature {
64 const ID: u16 = 0x40a3;
65 const STARTING_VERSION: u8 = 0;
66
67 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
68 Self {
69 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
70 }
71 }
72}
73
74impl Feature for FnInversionMultiHostFeature {}
75
76impl FnInversionMultiHostFeature {
77 pub async fn get_global_fn_inversion(
79 &self,
80 host: HostIndex,
81 ) -> Result<FnInversionInfo, Hidpp20Error> {
82 let payload = self
83 .endpoint
84 .call(0, [u8::from(host), 0, 0])
85 .await?
86 .extend_payload();
87 FnInversionInfo::from_payload(payload)
88 }
89
90 pub async fn set_global_fn_inversion(
94 &self,
95 host: HostIndex,
96 state: FnInversionState,
97 ) -> Result<FnInversionInfo, Hidpp20Error> {
98 let payload = self
99 .endpoint
100 .call(1, set_multi_host_fn_inversion_args(host, state))
101 .await?
102 .extend_payload();
103 FnInversionInfo::from_payload(payload)
104 }
105}
106
107fn set_multi_host_fn_inversion_args(host: HostIndex, state: FnInversionState) -> [u8; 3] {
108 [u8::from(host), u8::from(state), 0]
109}
110
111impl FnInversionInfo {
112 fn from_payload(payload: [u8; 16]) -> Result<Self, Hidpp20Error> {
113 Ok(Self {
114 host_index: HostIndex::from(payload[0]),
115 state: FnInversionState::try_from(payload[1])
116 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
117 default_state: FnInversionState::try_from(payload[2])
118 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
119 capabilities: FnInversionCapabilities::from_bits_retain(payload[3]),
120 })
121 }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize))]
127#[non_exhaustive]
128pub struct GlobalFnInversion {
129 pub state: FnInversionState,
131 pub default_state: FnInversionState,
133}
134
135impl GlobalFnInversion {
136 fn from_payload(payload: [u8; 16]) -> Result<Self, Hidpp20Error> {
137 Ok(Self {
138 state: FnInversionState::try_from(payload[0])
139 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
140 default_state: FnInversionState::try_from(payload[1])
141 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
142 })
143 }
144}
145
146#[derive(Clone)]
152pub struct FnInversionWithDefaultStateFeature {
153 endpoint: FeatureEndpoint,
155}
156
157impl CreatableFeature for FnInversionWithDefaultStateFeature {
158 const ID: u16 = 0x40a2;
159 const STARTING_VERSION: u8 = 0;
160
161 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
162 Self {
163 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
164 }
165 }
166}
167
168impl Feature for FnInversionWithDefaultStateFeature {}
169
170impl FnInversionWithDefaultStateFeature {
171 pub async fn get_global_fn_inversion(&self) -> Result<GlobalFnInversion, Hidpp20Error> {
173 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
174 GlobalFnInversion::from_payload(payload)
175 }
176
177 pub async fn set_global_fn_inversion(
179 &self,
180 state: FnInversionState,
181 ) -> Result<GlobalFnInversion, Hidpp20Error> {
182 let payload = self
183 .endpoint
184 .call(1, [u8::from(state), 0, 0])
185 .await?
186 .extend_payload();
187 GlobalFnInversion::from_payload(payload)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::{
194 FnInversionInfo, FnInversionState, GlobalFnInversion, set_multi_host_fn_inversion_args,
195 };
196 use crate::feature::hosts_info::HostIndex;
197
198 #[test]
199 fn parses_fn_inversion_info() {
200 let mut payload = [0; 16];
201 payload[0] = 1;
202 payload[1] = 1;
203 payload[2] = 0;
204 payload[3] = 1;
205
206 let info = FnInversionInfo::from_payload(payload).unwrap();
207
208 assert_eq!(info.host_index, HostIndex::Slot(1));
209 assert_eq!(info.state, FnInversionState::On);
210 assert_eq!(info.default_state, FnInversionState::Off);
211 }
212
213 #[test]
214 fn parses_global_fn_inversion() {
215 let mut payload = [0; 16];
216 payload[0] = 1;
217 payload[1] = 0;
218
219 let global = GlobalFnInversion::from_payload(payload).unwrap();
220
221 assert_eq!(global.state, FnInversionState::On);
222 assert_eq!(global.default_state, FnInversionState::Off);
223 }
224
225 #[test]
226 fn encodes_multi_host_set_args_as_host_then_state() {
227 assert_eq!(
228 set_multi_host_fn_inversion_args(HostIndex::Slot(2), FnInversionState::On),
229 [2, 1, 0]
230 );
231 }
232}