1#[cfg(test)]
4mod tests;
5
6use num_enum::TryFromPrimitive;
7use openlogi_hidpp_derive::Feature;
8
9use crate::{
10 feature::{FeatureEndpoint, hosts_info::HostIndex},
11 protocol::v20::Hidpp20Error,
12};
13
14bitflags::bitflags! {
15 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
26 pub struct MultiPlatformCapabilities: u16 {
27 const OS_DETECTION = 1 << 8;
29 const SET_HOST_PLATFORM = 1 << 9;
31 }
32}
33
34bitflags::bitflags! {
35 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
45 pub struct OsMask: u16 {
46 const TIZEN = 1 << 0;
48 const WINDOWS = 1 << 8;
50 const WINDOWS_EMBEDDED = 1 << 9;
52 const LINUX = 1 << 10;
54 const CHROME = 1 << 11;
56 const ANDROID = 1 << 12;
58 const MACOS = 1 << 13;
60 const IOS = 1 << 14;
62 const WEBOS = 1 << 15;
64 }
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TryFromPrimitive)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize))]
70#[non_exhaustive]
71#[repr(u8)]
72pub enum PlatformSource {
73 Default = 0,
75 Auto = 1,
77 Manual = 2,
79 Software = 3,
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize))]
86#[non_exhaustive]
87pub struct MultiPlatformInfo {
88 pub capabilities: MultiPlatformCapabilities,
90 pub platform_count: u8,
92 pub descriptor_count: u8,
94 pub host_count: u8,
96 pub current_host: HostIndex,
98 pub current_host_platform: Option<u8>,
100}
101
102impl MultiPlatformInfo {
103 fn from_payload(payload: &[u8; 16]) -> Self {
104 Self {
105 capabilities: MultiPlatformCapabilities::from_bits_retain(u16::from_be_bytes([
106 payload[0], payload[1],
107 ])),
108 platform_count: payload[2],
109 descriptor_count: payload[3],
110 host_count: payload[4],
111 current_host: HostIndex::from(payload[5]),
112 current_host_platform: optional_index(payload[6]),
113 }
114 }
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
119#[cfg_attr(feature = "serde", derive(serde::Serialize))]
120#[non_exhaustive]
121pub struct PlatformDescriptor {
122 pub platform_index: u8,
124 pub descriptor_index: u8,
126 pub os_mask: OsMask,
128 pub from_version: u8,
130 pub from_revision: u8,
132 pub to_version: u8,
134 pub to_revision: u8,
136}
137
138impl PlatformDescriptor {
139 fn from_payload(payload: &[u8; 16]) -> Self {
140 Self {
141 platform_index: payload[0],
142 descriptor_index: payload[1],
143 os_mask: OsMask::from_bits_retain(u16::from_be_bytes([payload[2], payload[3]])),
144 from_version: payload[4],
145 from_revision: payload[5],
146 to_version: payload[6],
147 to_revision: payload[7],
148 }
149 }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
154#[cfg_attr(feature = "serde", derive(serde::Serialize))]
155#[non_exhaustive]
156pub struct HostPlatform {
157 pub host_index: HostIndex,
159 pub status: u8,
161 pub platform_index: Option<u8>,
163 pub source: PlatformSource,
165 pub auto_platform_index: Option<u8>,
167 pub auto_descriptor_index: Option<u8>,
169}
170
171#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
174#[cfg_attr(feature = "serde", derive(serde::Serialize))]
175#[non_exhaustive]
176pub struct PlatformChange {
177 pub host_index: HostIndex,
179 pub platform_index: Option<u8>,
181 pub source: Option<PlatformSource>,
184}
185
186impl PlatformChange {
187 #[must_use]
194 pub fn decode(payload: &[u8]) -> Option<Self> {
195 let [host, platform, source, ..] = *payload else {
196 return None;
197 };
198 Some(Self {
199 host_index: HostIndex::from(host),
200 platform_index: optional_index(platform),
201 source: PlatformSource::try_from(source).ok(),
202 })
203 }
204}
205
206#[derive(Clone, Feature)]
208#[creatable(id = 0x4531, version = 1)]
209pub struct MultiPlatformFeature {
210 endpoint: FeatureEndpoint,
212}
213
214impl MultiPlatformFeature {
215 pub async fn get_feature_infos(&self) -> Result<MultiPlatformInfo, Hidpp20Error> {
217 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
218 Ok(MultiPlatformInfo::from_payload(&payload))
219 }
220
221 pub async fn get_platform_descriptor(
223 &self,
224 descriptor_index: u8,
225 ) -> Result<PlatformDescriptor, Hidpp20Error> {
226 let payload = self
227 .endpoint
228 .call(1, [descriptor_index, 0, 0])
229 .await?
230 .extend_payload();
231 Ok(PlatformDescriptor::from_payload(&payload))
232 }
233
234 pub async fn get_host_platform(&self, host: HostIndex) -> Result<HostPlatform, Hidpp20Error> {
236 let payload = self
237 .endpoint
238 .call(2, [u8::from(host), 0, 0])
239 .await?
240 .extend_payload();
241 Ok(HostPlatform {
242 host_index: HostIndex::from(payload[0]),
243 status: payload[1],
244 platform_index: optional_index(payload[2]),
245 source: PlatformSource::try_from(payload[3])
246 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
247 auto_platform_index: optional_index(payload[4]),
248 auto_descriptor_index: optional_index(payload[5]),
249 })
250 }
251
252 pub async fn set_host_platform(
258 &self,
259 host: HostIndex,
260 platform_index: u8,
261 ) -> Result<(), Hidpp20Error> {
262 self.endpoint
263 .call(3, [u8::from(host), platform_index, 0])
264 .await?;
265 Ok(())
266 }
267}
268
269fn optional_index(value: u8) -> Option<u8> {
270 (value != 0xff).then_some(value)
271}