Skip to main content

hidpp/feature/crown/
mod.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 std::sync::Arc;
12
13use num_enum::{IntoPrimitive, TryFromPrimitive};
14
15pub use event::{ActivityState, ButtonState, CrownEvent, CrownGesture, CrownUpdate, RotationState};
16
17use crate::{
18    channel::{HidppChannel, MessageListenerGuard},
19    event::EventEmitter,
20    feature::{CreatableFeature, EmittingFeature, Feature, FeatureEndpoint, event_payload},
21    protocol::v20::Hidpp20Error,
22};
23
24bitflags::bitflags! {
25    /// Crown control capabilities, from [`get_info`](CrownFeature::get_info).
26    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
28    pub struct CrownControlCapabilities: u8 {
29        /// The crown has a button.
30        const BUTTON = 1 << 0;
31        /// The button reports long presses.
32        const BUTTON_LONG_PRESS = 1 << 1;
33        /// The ratchet is mechanized (no manual control).
34        const MECHANIZED_RATCHET = 1 << 2;
35        /// The rotation timeout is configurable.
36        const ROTATION_TIMEOUT_CONFIGURABLE = 1 << 3;
37        /// The short-long timeout is configurable.
38        const SHORT_LONG_TIMEOUT_CONFIGURABLE = 1 << 4;
39        /// The double-tap speed is configurable.
40        const DOUBLE_TAP_SPEED_CONFIGURABLE = 1 << 5;
41    }
42}
43
44bitflags::bitflags! {
45    /// Crown sensor capabilities, from [`get_info`](CrownFeature::get_info).
46    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
48    pub struct CrownSensorCapabilities: u8 {
49        /// The crown has a proximity sensor.
50        const PROXIMITY = 1 << 0;
51        /// The crown has a touch sensor.
52        const TOUCH = 1 << 1;
53        /// The crown detects tap gestures.
54        const TAP_GESTURE = 1 << 2;
55        /// The crown detects double-tap gestures.
56        const DOUBLE_TAP_GESTURE = 1 << 3;
57    }
58}
59
60/// How crown events are reported.
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize))]
63#[non_exhaustive]
64#[repr(u8)]
65pub enum ReportingMode {
66    /// Leave the setting unchanged (write-only sentinel).
67    NoChange = 0,
68    /// Events go to the native HID channel.
69    Hid = 1,
70    /// Events are diverted to HID++ (required for [`CrownEvent`]).
71    Diverted = 2,
72}
73
74/// The crown's ratchet mode.
75#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize))]
77#[non_exhaustive]
78#[repr(u8)]
79pub enum RatchetMode {
80    /// Leave the setting unchanged (write-only sentinel).
81    NoChange = 0,
82    /// Free-spinning mode.
83    Free = 1,
84    /// Ratchet (detented) mode.
85    Ratchet = 2,
86}
87
88/// Crown info constants from [`get_info`](CrownFeature::get_info).
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize))]
91#[non_exhaustive]
92pub struct CrownInfo {
93    /// Control capabilities.
94    pub controls: CrownControlCapabilities,
95    /// Sensor capabilities.
96    pub sensors: CrownSensorCapabilities,
97    /// Number of slots per revolution.
98    pub slots: u16,
99    /// Number of ratchets per revolution.
100    pub ratchets: u16,
101}
102
103/// The crown's mode, from [`get_mode`](CrownFeature::get_mode) and echoed by
104/// [`set_mode`](CrownFeature::set_mode).
105#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize))]
107#[non_exhaustive]
108pub struct CrownMode {
109    /// How events are reported.
110    pub diverting: ReportingMode,
111    /// Ratchet mode.
112    pub ratchet_mode: RatchetMode,
113    /// Rotation timeout, in 10 ms steps.
114    pub rotation_timeout: u8,
115    /// Short-long press timeout, in 10 ms steps.
116    pub short_long_timeout: u8,
117    /// Double-tap speed, in 10 ms steps.
118    pub double_tap_speed: u8,
119}
120
121impl CrownMode {
122    fn from_payload(payload: &[u8; 16]) -> Result<Self, Hidpp20Error> {
123        Ok(Self {
124            diverting: ReportingMode::try_from(payload[0])
125                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
126            ratchet_mode: RatchetMode::try_from(payload[1])
127                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
128            rotation_timeout: payload[2],
129            short_long_timeout: payload[3],
130            double_tap_speed: payload[4],
131        })
132    }
133}
134
135/// Mode settings to write with [`set_mode`](CrownFeature::set_mode).
136///
137/// Every field uses `0` / [`ReportingMode::NoChange`] / [`RatchetMode::NoChange`]
138/// as a "leave unchanged" sentinel. The rotation timeout is clipped to `0x40`.
139#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize))]
141pub struct SetCrownMode {
142    /// How events are reported, or [`ReportingMode::NoChange`].
143    pub diverting: ReportingMode,
144    /// Ratchet mode, or [`RatchetMode::NoChange`].
145    pub ratchet_mode: RatchetMode,
146    /// Rotation timeout in 10 ms steps, or `0` to leave unchanged.
147    pub rotation_timeout: u8,
148    /// Short-long timeout in 10 ms steps, or `0` to leave unchanged.
149    pub short_long_timeout: u8,
150    /// Double-tap speed in 10 ms steps, or `0` to leave unchanged.
151    pub double_tap_speed: u8,
152}
153
154/// Implements the `Crown` / `0x4600` feature.
155pub struct CrownFeature {
156    /// The endpoint this feature talks to.
157    endpoint: FeatureEndpoint,
158
159    /// The emitter used to publish decoded events.
160    emitter: Arc<EventEmitter<CrownEvent>>,
161
162    /// Removes the message listener when the feature is dropped.
163    _msg_listener: MessageListenerGuard,
164}
165
166impl CreatableFeature for CrownFeature {
167    const ID: u16 = 0x4600;
168    const STARTING_VERSION: u8 = 0;
169
170    fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
171        let emitter = Arc::new(EventEmitter::new());
172
173        let listener = chan.add_msg_listener_guarded({
174            let emitter = Arc::clone(&emitter);
175
176            move |raw, matched| {
177                let Some((func, payload)) =
178                    event_payload(raw, matched, device_index, feature_index)
179                else {
180                    return;
181                };
182                if let Some(event) = event::decode_event(func.to_lo(), &payload) {
183                    emitter.emit(event);
184                }
185            }
186        });
187
188        Self {
189            endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
190            emitter,
191            _msg_listener: listener,
192        }
193    }
194}
195
196impl Feature for CrownFeature {}
197
198impl EmittingFeature<CrownEvent> for CrownFeature {
199    fn listen(&self) -> async_channel::Receiver<CrownEvent> {
200        self.emitter.create_receiver()
201    }
202}
203
204impl CrownFeature {
205    /// Retrieves the crown's capabilities and slot/ratchet counts.
206    pub async fn get_info(&self) -> Result<CrownInfo, Hidpp20Error> {
207        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
208        Ok(CrownInfo {
209            controls: CrownControlCapabilities::from_bits_retain(payload[0]),
210            sensors: CrownSensorCapabilities::from_bits_retain(payload[1]),
211            slots: u16::from_be_bytes([payload[2], payload[3]]),
212            ratchets: u16::from_be_bytes([payload[4], payload[5]]),
213        })
214    }
215
216    /// Retrieves the crown's current mode.
217    pub async fn get_mode(&self) -> Result<CrownMode, Hidpp20Error> {
218        let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
219        CrownMode::from_payload(&payload)
220    }
221
222    /// Sets the crown's mode and returns the resulting mode echoed by the device.
223    ///
224    /// Divert the crown ([`ReportingMode::Diverted`]) for [`CrownEvent`]s to be
225    /// emitted.
226    pub async fn set_mode(&self, mode: SetCrownMode) -> Result<CrownMode, Hidpp20Error> {
227        let mut args = [0; 16];
228        args[..5].copy_from_slice(&[
229            mode.diverting.into(),
230            mode.ratchet_mode.into(),
231            mode.rotation_timeout,
232            mode.short_long_timeout,
233            mode.double_tap_speed,
234        ]);
235        let payload = self.endpoint.call_long(2, args).await?.extend_payload();
236        CrownMode::from_payload(&payload)
237    }
238}