Skip to main content

hidpp/feature/
dual_platform.rs

1//! Implements the `DualPlatform` feature (ID `0x4530`) that selects which of two
2//! OS platforms a device sends HID codes for.
3//!
4//! This is the predecessor of [`MultiPlatform`](super::multi_platform)
5//! (`0x4531`); a device exposing `0x4531` should be driven through that feature
6//! instead.
7
8use num_enum::{IntoPrimitive, TryFromPrimitive};
9use openlogi_hidpp_derive::Feature;
10
11use crate::{
12    feature::{DecodeEvent, EventSource, FeatureEndpoint},
13    protocol::v20::Hidpp20Error,
14};
15
16/// The platform a [`DualPlatformFeature`] device is configured for.
17///
18/// The selection is persistent and chosen by the user during pairing or by short
19/// pressing an OS-selection button; there is no default.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22#[non_exhaustive]
23#[repr(u8)]
24pub enum DualPlatformSelection {
25    /// iOS or macOS.
26    IosOrMac = 0,
27    /// Android or Windows.
28    AndroidOrWindows = 1,
29}
30
31/// An event emitted by [`DualPlatformFeature`].
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize))]
34#[non_exhaustive]
35pub enum DualPlatformEvent {
36    /// The user changed the platform via an OS-selection button.
37    PlatformChanged(DualPlatformSelection),
38}
39
40/// Implements the `DualPlatform` / `0x4530` feature.
41#[derive(Feature)]
42#[creatable(id = 0x4530, version = 0)]
43pub struct DualPlatformFeature {
44    /// The endpoint this feature talks to.
45    endpoint: FeatureEndpoint,
46
47    /// Publishes decoded events to listeners.
48    events: EventSource<DualPlatformEvent>,
49}
50
51impl DecodeEvent for DualPlatformEvent {
52    fn decode(sub_id: u8, payload: &[u8; 16]) -> Option<Self> {
53        // PlatformChange is the only event and carries sub-id 0.
54        if sub_id != 0 {
55            return None;
56        }
57        DualPlatformSelection::try_from(payload[0])
58            .ok()
59            .map(DualPlatformEvent::PlatformChanged)
60    }
61}
62
63impl DualPlatformFeature {
64    /// Retrieves the current platform setting.
65    pub async fn get_platform(&self) -> Result<DualPlatformSelection, Hidpp20Error> {
66        // `getPlatform` is function 1 in this feature, not the usual 0.
67        let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
68        DualPlatformSelection::try_from(payload[0]).map_err(|_| Hidpp20Error::UnsupportedResponse)
69    }
70
71    /// Sets the platform and returns the device's echo of the new setting.
72    ///
73    /// This does not trigger a [`DualPlatformEvent::PlatformChanged`] event.
74    pub async fn set_platform(
75        &self,
76        platform: DualPlatformSelection,
77    ) -> Result<DualPlatformSelection, Hidpp20Error> {
78        let payload = self
79            .endpoint
80            .call(2, [platform.into(), 0, 0])
81            .await?
82            .extend_payload();
83        DualPlatformSelection::try_from(payload[0]).map_err(|_| Hidpp20Error::UnsupportedResponse)
84    }
85}
86
87#[cfg(test)]
88#[allow(clippy::unwrap_used, reason = "expect/unwrap are idiomatic in tests")]
89mod tests {
90    use super::DualPlatformSelection;
91
92    #[test]
93    fn maps_platform_wire_values() {
94        assert_eq!(
95            DualPlatformSelection::try_from(0).unwrap(),
96            DualPlatformSelection::IosOrMac
97        );
98        assert_eq!(
99            DualPlatformSelection::try_from(1).unwrap(),
100            DualPlatformSelection::AndroidOrWindows
101        );
102        DualPlatformSelection::try_from(2).unwrap_err();
103        assert_eq!(u8::from(DualPlatformSelection::AndroidOrWindows), 1);
104    }
105}