openlogi-hidpp 0.7.1

OpenLogi's hard fork of the `hidpp` crate (Logitech HID++ protocol).
Documentation
//! Implements the `Crown` feature (ID `0x4600`) for the MX Master's rotary
//! crown: reading its capabilities, configuring its mode (HID vs diverted,
//! free vs ratchet, timeouts), and receiving diverted rotation/touch/button
//! events.

pub mod event;

#[cfg(test)]
mod tests;

use num_enum::{IntoPrimitive, TryFromPrimitive};
use openlogi_hidpp_derive::Feature;

pub use event::{ActivityState, ButtonState, CrownEvent, CrownGesture, CrownUpdate, RotationState};

use crate::{
    feature::{EventSource, FeatureEndpoint},
    protocol::v20::Hidpp20Error,
};

bitflags::bitflags! {
    /// Crown control capabilities, from [`get_info`](CrownFeature::get_info).
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
    pub struct CrownControlCapabilities: u8 {
        /// The crown has a button.
        const BUTTON = 1 << 0;
        /// The button reports long presses.
        const BUTTON_LONG_PRESS = 1 << 1;
        /// The ratchet is mechanized (no manual control).
        const MECHANIZED_RATCHET = 1 << 2;
        /// The rotation timeout is configurable.
        const ROTATION_TIMEOUT_CONFIGURABLE = 1 << 3;
        /// The short-long timeout is configurable.
        const SHORT_LONG_TIMEOUT_CONFIGURABLE = 1 << 4;
        /// The double-tap speed is configurable.
        const DOUBLE_TAP_SPEED_CONFIGURABLE = 1 << 5;
    }
}

bitflags::bitflags! {
    /// Crown sensor capabilities, from [`get_info`](CrownFeature::get_info).
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
    pub struct CrownSensorCapabilities: u8 {
        /// The crown has a proximity sensor.
        const PROXIMITY = 1 << 0;
        /// The crown has a touch sensor.
        const TOUCH = 1 << 1;
        /// The crown detects tap gestures.
        const TAP_GESTURE = 1 << 2;
        /// The crown detects double-tap gestures.
        const DOUBLE_TAP_GESTURE = 1 << 3;
    }
}

/// How crown events are reported.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ReportingMode {
    /// Leave the setting unchanged (write-only sentinel).
    NoChange = 0,
    /// Events go to the native HID channel.
    Hid = 1,
    /// Events are diverted to HID++ (required for [`CrownEvent`]).
    Diverted = 2,
}

/// The crown's ratchet mode.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum RatchetMode {
    /// Leave the setting unchanged (write-only sentinel).
    NoChange = 0,
    /// Free-spinning mode.
    Free = 1,
    /// Ratchet (detented) mode.
    Ratchet = 2,
}

/// Crown info constants from [`get_info`](CrownFeature::get_info).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct CrownInfo {
    /// Control capabilities.
    pub controls: CrownControlCapabilities,
    /// Sensor capabilities.
    pub sensors: CrownSensorCapabilities,
    /// Number of slots per revolution.
    pub slots: u16,
    /// Number of ratchets per revolution.
    pub ratchets: u16,
}

/// The crown's mode, from [`get_mode`](CrownFeature::get_mode) and echoed by
/// [`set_mode`](CrownFeature::set_mode).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct CrownMode {
    /// How events are reported.
    pub diverting: ReportingMode,
    /// Ratchet mode.
    pub ratchet_mode: RatchetMode,
    /// Rotation timeout, in 10 ms steps.
    pub rotation_timeout: u8,
    /// Short-long press timeout, in 10 ms steps.
    pub short_long_timeout: u8,
    /// Double-tap speed, in 10 ms steps.
    pub double_tap_speed: u8,
}

impl CrownMode {
    fn from_payload(payload: &[u8; 16]) -> Result<Self, Hidpp20Error> {
        Ok(Self {
            diverting: ReportingMode::try_from(payload[0])
                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
            ratchet_mode: RatchetMode::try_from(payload[1])
                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
            rotation_timeout: payload[2],
            short_long_timeout: payload[3],
            double_tap_speed: payload[4],
        })
    }
}

/// Mode settings to write with [`set_mode`](CrownFeature::set_mode).
///
/// Every field uses `0` / [`ReportingMode::NoChange`] / [`RatchetMode::NoChange`]
/// as a "leave unchanged" sentinel. The rotation timeout is clipped to `0x40`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SetCrownMode {
    /// How events are reported, or [`ReportingMode::NoChange`].
    pub diverting: ReportingMode,
    /// Ratchet mode, or [`RatchetMode::NoChange`].
    pub ratchet_mode: RatchetMode,
    /// Rotation timeout in 10 ms steps, or `0` to leave unchanged.
    pub rotation_timeout: u8,
    /// Short-long timeout in 10 ms steps, or `0` to leave unchanged.
    pub short_long_timeout: u8,
    /// Double-tap speed in 10 ms steps, or `0` to leave unchanged.
    pub double_tap_speed: u8,
}

/// Implements the `Crown` / `0x4600` feature.
#[derive(Feature)]
#[creatable(id = 0x4600, version = 0)]
pub struct CrownFeature {
    /// The endpoint this feature talks to.
    endpoint: FeatureEndpoint,

    /// Publishes decoded events to listeners.
    events: EventSource<CrownEvent>,
}

impl CrownFeature {
    /// Retrieves the crown's capabilities and slot/ratchet counts.
    pub async fn get_info(&self) -> Result<CrownInfo, Hidpp20Error> {
        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
        Ok(CrownInfo {
            controls: CrownControlCapabilities::from_bits_retain(payload[0]),
            sensors: CrownSensorCapabilities::from_bits_retain(payload[1]),
            slots: u16::from_be_bytes([payload[2], payload[3]]),
            ratchets: u16::from_be_bytes([payload[4], payload[5]]),
        })
    }

    /// Retrieves the crown's current mode.
    pub async fn get_mode(&self) -> Result<CrownMode, Hidpp20Error> {
        let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
        CrownMode::from_payload(&payload)
    }

    /// Sets the crown's mode and returns the resulting mode echoed by the device.
    ///
    /// Divert the crown ([`ReportingMode::Diverted`]) for [`CrownEvent`]s to be
    /// emitted.
    pub async fn set_mode(&self, mode: SetCrownMode) -> Result<CrownMode, Hidpp20Error> {
        let mut args = [0; 16];
        args[..5].copy_from_slice(&[
            mode.diverting.into(),
            mode.ratchet_mode.into(),
            mode.rotation_timeout,
            mode.short_long_timeout,
            mode.double_tap_speed,
        ]);
        let payload = self.endpoint.call_long(2, args).await?.extend_payload();
        CrownMode::from_payload(&payload)
    }
}