Skip to main content

hidpp/feature/
multi_platform.rs

1//! Implements `MultiPlatform` (feature `0x4531`).
2
3use num_enum::TryFromPrimitive;
4use openlogi_hidpp_derive::Feature;
5
6use crate::{
7    feature::{FeatureEndpoint, hosts_info::HostIndex},
8    protocol::v20::Hidpp20Error,
9};
10
11bitflags::bitflags! {
12    /// Capabilities reported by `MultiPlatform`.
13    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
15    pub struct MultiPlatformCapabilities: u16 {
16        /// The device can detect the host OS automatically.
17        const OS_DETECTION = 1 << 0;
18        /// Software can set the host platform.
19        const SET_HOST_PLATFORM = 1 << 1;
20    }
21}
22
23bitflags::bitflags! {
24    /// Operating systems covered by a platform descriptor.
25    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
27    pub struct OsMask: u16 {
28        /// Microsoft Windows.
29        const WINDOWS = 1 << 0;
30        /// Windows Embedded.
31        const WINDOWS_EMBEDDED = 1 << 1;
32        /// Linux.
33        const LINUX = 1 << 2;
34        /// ChromeOS.
35        const CHROME = 1 << 3;
36        /// Android.
37        const ANDROID = 1 << 4;
38        /// macOS.
39        const MACOS = 1 << 5;
40        /// iOS.
41        const IOS = 1 << 6;
42        /// webOS.
43        const WEBOS = 1 << 7;
44        /// Tizen.
45        const TIZEN = 1 << 8;
46    }
47}
48
49/// Source of a host-platform selection.
50#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TryFromPrimitive)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize))]
52#[non_exhaustive]
53#[repr(u8)]
54pub enum PlatformSource {
55    /// Device default.
56    Default = 0,
57    /// Automatically detected by the device.
58    Auto = 1,
59    /// Manually selected on the device.
60    Manual = 2,
61    /// Set by host software.
62    Software = 3,
63}
64
65/// Static `MultiPlatform` feature information.
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize))]
68#[non_exhaustive]
69pub struct MultiPlatformInfo {
70    /// Feature capabilities.
71    pub capabilities: MultiPlatformCapabilities,
72    /// Number of platform IDs.
73    pub platform_count: u8,
74    /// Number of platform descriptor rows.
75    pub descriptor_count: u8,
76    /// Number of host slots.
77    pub host_count: u8,
78    /// Current host slot.
79    pub current_host: HostIndex,
80    /// Platform index selected for the current host.
81    pub current_host_platform: Option<u8>,
82}
83
84/// A platform descriptor row.
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize))]
87#[non_exhaustive]
88pub struct PlatformDescriptor {
89    /// Platform index this descriptor belongs to.
90    pub platform_index: u8,
91    /// Descriptor row index.
92    pub descriptor_index: u8,
93    /// Covered operating systems.
94    pub os_mask: OsMask,
95    /// First supported OS major version.
96    pub from_version: u8,
97    /// First supported OS revision.
98    pub from_revision: u8,
99    /// Last supported OS major version.
100    pub to_version: u8,
101    /// Last supported OS revision.
102    pub to_revision: u8,
103}
104
105/// Platform selection for a host slot.
106#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize))]
108#[non_exhaustive]
109pub struct HostPlatform {
110    /// Host slot index returned by the device.
111    pub host_index: HostIndex,
112    /// Raw host status byte.
113    pub status: u8,
114    /// Selected platform, or `None` when undefined.
115    pub platform_index: Option<u8>,
116    /// Source of the platform selection.
117    pub source: PlatformSource,
118    /// Automatically detected platform, if available.
119    pub auto_platform_index: Option<u8>,
120    /// Automatically matched platform descriptor, if available.
121    pub auto_descriptor_index: Option<u8>,
122}
123
124/// Implements the `MultiPlatform` / `0x4531` feature.
125#[derive(Clone, Feature)]
126#[creatable(id = 0x4531, version = 1)]
127pub struct MultiPlatformFeature {
128    /// The endpoint this feature talks to.
129    endpoint: FeatureEndpoint,
130}
131
132impl MultiPlatformFeature {
133    /// Retrieves feature capabilities and platform counts.
134    pub async fn get_feature_infos(&self) -> Result<MultiPlatformInfo, Hidpp20Error> {
135        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
136        Ok(MultiPlatformInfo {
137            capabilities: MultiPlatformCapabilities::from_bits_retain(u16::from_be_bytes([
138                payload[0], payload[1],
139            ])),
140            platform_count: payload[2],
141            descriptor_count: payload[3],
142            host_count: payload[4],
143            current_host: HostIndex::from(payload[5]),
144            current_host_platform: optional_index(payload[6]),
145        })
146    }
147
148    /// Retrieves a platform descriptor row.
149    pub async fn get_platform_descriptor(
150        &self,
151        descriptor_index: u8,
152    ) -> Result<PlatformDescriptor, Hidpp20Error> {
153        let payload = self
154            .endpoint
155            .call(1, [descriptor_index, 0, 0])
156            .await?
157            .extend_payload();
158        Ok(PlatformDescriptor {
159            platform_index: payload[0],
160            descriptor_index: payload[1],
161            os_mask: OsMask::from_bits_retain(u16::from_be_bytes([payload[2], payload[3]])),
162            from_version: payload[4],
163            from_revision: payload[5],
164            to_version: payload[6],
165            to_revision: payload[7],
166        })
167    }
168
169    /// Retrieves the platform selected for `host`.
170    pub async fn get_host_platform(&self, host: HostIndex) -> Result<HostPlatform, Hidpp20Error> {
171        let payload = self
172            .endpoint
173            .call(2, [u8::from(host), 0, 0])
174            .await?
175            .extend_payload();
176        Ok(HostPlatform {
177            host_index: HostIndex::from(payload[0]),
178            status: payload[1],
179            platform_index: optional_index(payload[2]),
180            source: PlatformSource::try_from(payload[3])
181                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
182            auto_platform_index: optional_index(payload[4]),
183            auto_descriptor_index: optional_index(payload[5]),
184        })
185    }
186}
187
188fn optional_index(value: u8) -> Option<u8> {
189    (value != 0xff).then_some(value)
190}