Skip to main content

hidpp/feature/
crown.rs

1//! Implements the `Crown` feature (ID `0x4600`) for the MX Master's rotary
2//! crown: reading its capabilities, configuring its mode (HID vs diverted,
3//! free vs ratchet, timeouts), and receiving diverted rotation/touch/button
4//! events.
5
6pub mod event;
7
8#[cfg(test)]
9mod tests;
10
11use num_enum::{IntoPrimitive, TryFromPrimitive};
12use openlogi_hidpp_derive::Feature;
13
14pub use event::{ActivityState, ButtonState, CrownEvent, CrownGesture, CrownUpdate, RotationState};
15
16use crate::{
17    feature::{EventSource, FeatureEndpoint},
18    protocol::v20::Hidpp20Error,
19};
20
21bitflags::bitflags! {
22    /// Crown control capabilities, from [`get_info`](CrownFeature::get_info).
23    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
24    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
25    pub struct CrownControlCapabilities: u8 {
26        /// The crown has a button.
27        const BUTTON = 1 << 0;
28        /// The button reports long presses.
29        const BUTTON_LONG_PRESS = 1 << 1;
30        /// The ratchet is mechanized (no manual control).
31        const MECHANIZED_RATCHET = 1 << 2;
32        /// The rotation timeout is configurable.
33        const ROTATION_TIMEOUT_CONFIGURABLE = 1 << 3;
34        /// The short-long timeout is configurable.
35        const SHORT_LONG_TIMEOUT_CONFIGURABLE = 1 << 4;
36        /// The double-tap speed is configurable.
37        const DOUBLE_TAP_SPEED_CONFIGURABLE = 1 << 5;
38    }
39}
40
41bitflags::bitflags! {
42    /// Crown sensor capabilities, from [`get_info`](CrownFeature::get_info).
43    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
45    pub struct CrownSensorCapabilities: u8 {
46        /// The crown has a proximity sensor.
47        const PROXIMITY = 1 << 0;
48        /// The crown has a touch sensor.
49        const TOUCH = 1 << 1;
50        /// The crown detects tap gestures.
51        const TAP_GESTURE = 1 << 2;
52        /// The crown detects double-tap gestures.
53        const DOUBLE_TAP_GESTURE = 1 << 3;
54    }
55}
56
57/// How crown events are reported.
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize))]
60#[non_exhaustive]
61#[repr(u8)]
62pub enum ReportingMode {
63    /// Leave the setting unchanged (write-only sentinel).
64    NoChange = 0,
65    /// Events go to the native HID channel.
66    Hid = 1,
67    /// Events are diverted to HID++ (required for [`CrownEvent`]).
68    Diverted = 2,
69}
70
71/// The crown's ratchet mode.
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74#[non_exhaustive]
75#[repr(u8)]
76pub enum RatchetMode {
77    /// Leave the setting unchanged (write-only sentinel).
78    NoChange = 0,
79    /// Free-spinning mode.
80    Free = 1,
81    /// Ratchet (detented) mode.
82    Ratchet = 2,
83}
84
85/// Crown info constants from [`get_info`](CrownFeature::get_info).
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
87#[cfg_attr(feature = "serde", derive(serde::Serialize))]
88#[non_exhaustive]
89pub struct CrownInfo {
90    /// Control capabilities.
91    pub controls: CrownControlCapabilities,
92    /// Sensor capabilities.
93    pub sensors: CrownSensorCapabilities,
94    /// Number of slots per revolution.
95    pub slots: u16,
96    /// Number of ratchets per revolution.
97    pub ratchets: u16,
98}
99
100/// The crown's mode, from [`get_mode`](CrownFeature::get_mode) and echoed by
101/// [`set_mode`](CrownFeature::set_mode).
102#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize))]
104#[non_exhaustive]
105pub struct CrownMode {
106    /// How events are reported.
107    pub diverting: ReportingMode,
108    /// Ratchet mode.
109    pub ratchet_mode: RatchetMode,
110    /// Rotation timeout, in 10 ms steps.
111    pub rotation_timeout: u8,
112    /// Short-long press timeout, in 10 ms steps.
113    pub short_long_timeout: u8,
114    /// Double-tap speed, in 10 ms steps.
115    pub double_tap_speed: u8,
116}
117
118impl CrownMode {
119    fn from_payload(payload: &[u8; 16]) -> Result<Self, Hidpp20Error> {
120        Ok(Self {
121            diverting: ReportingMode::try_from(payload[0])
122                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
123            ratchet_mode: RatchetMode::try_from(payload[1])
124                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
125            rotation_timeout: payload[2],
126            short_long_timeout: payload[3],
127            double_tap_speed: payload[4],
128        })
129    }
130}
131
132/// Mode settings to write with [`set_mode`](CrownFeature::set_mode).
133///
134/// Every field uses `0` / [`ReportingMode::NoChange`] / [`RatchetMode::NoChange`]
135/// as a "leave unchanged" sentinel. The rotation timeout is clipped to `0x40`.
136#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize))]
138pub struct SetCrownMode {
139    /// How events are reported, or [`ReportingMode::NoChange`].
140    pub diverting: ReportingMode,
141    /// Ratchet mode, or [`RatchetMode::NoChange`].
142    pub ratchet_mode: RatchetMode,
143    /// Rotation timeout in 10 ms steps, or `0` to leave unchanged.
144    pub rotation_timeout: u8,
145    /// Short-long timeout in 10 ms steps, or `0` to leave unchanged.
146    pub short_long_timeout: u8,
147    /// Double-tap speed in 10 ms steps, or `0` to leave unchanged.
148    pub double_tap_speed: u8,
149}
150
151/// Implements the `Crown` / `0x4600` feature.
152#[derive(Feature)]
153#[creatable(id = 0x4600, version = 0)]
154pub struct CrownFeature {
155    /// The endpoint this feature talks to.
156    endpoint: FeatureEndpoint,
157
158    /// Publishes decoded events to listeners.
159    events: EventSource<CrownEvent>,
160}
161
162impl CrownFeature {
163    /// Retrieves the crown's capabilities and slot/ratchet counts.
164    pub async fn get_info(&self) -> Result<CrownInfo, Hidpp20Error> {
165        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
166        Ok(CrownInfo {
167            controls: CrownControlCapabilities::from_bits_retain(payload[0]),
168            sensors: CrownSensorCapabilities::from_bits_retain(payload[1]),
169            slots: u16::from_be_bytes([payload[2], payload[3]]),
170            ratchets: u16::from_be_bytes([payload[4], payload[5]]),
171        })
172    }
173
174    /// Retrieves the crown's current mode.
175    pub async fn get_mode(&self) -> Result<CrownMode, Hidpp20Error> {
176        let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
177        CrownMode::from_payload(&payload)
178    }
179
180    /// Sets the crown's mode and returns the resulting mode echoed by the device.
181    ///
182    /// Divert the crown ([`ReportingMode::Diverted`]) for [`CrownEvent`]s to be
183    /// emitted.
184    pub async fn set_mode(&self, mode: SetCrownMode) -> Result<CrownMode, Hidpp20Error> {
185        let mut args = [0; 16];
186        args[..5].copy_from_slice(&[
187            mode.diverting.into(),
188            mode.ratchet_mode.into(),
189            mode.rotation_timeout,
190            mode.short_long_timeout,
191            mode.double_tap_speed,
192        ]);
193        let payload = self.endpoint.call_long(2, args).await?.extend_payload();
194        CrownMode::from_payload(&payload)
195    }
196}