hid-types 0.2.1

Rust types for working with USB HID report descriptors
Documentation
//! Types used in Input/Output/Feature items.

use bitfield_struct::bitfield;

/// Flags that can be used with Input, Output, or Feature items.
///
/// # Example
///
/// ```rust
/// # use hid_types::id::IoFlags;
/// let flags = IoFlags::data().variable().null_state();
/// assert_eq!(u32::from(flags), 0x42);
/// ```
#[bitfield(u32, debug = false, new = false)]
#[derive(PartialEq)]
pub struct IoFlags {
    #[bits(1)]
    data_or_constant: bool,
    #[bits(1)]
    array_or_variable: bool,
    #[bits(1)]
    absolute_or_relative: bool,
    #[bits(1)]
    no_wrap_or_wrap: bool,
    #[bits(1)]
    linear_or_nonlinear: bool,
    #[bits(1)]
    preferred_or_no_preferred: bool,
    #[bits(1)]
    no_null_position_or_null_state: bool,
    // Note: `volatile` is only valid for Output and Feature (not Input)
    #[bits(1)]
    non_volatile_or_volatile: bool,
    #[bits(1)]
    bitfield_or_buffered: bool,
    #[bits(23)]
    _reserved: u32,
}

impl IoFlags {
    const fn new() -> Self {
        Self(0)
    }

    /// Construct a new `IoFlags` for a data field.
    pub const fn data() -> Self {
        Self::new()
    }

    /// Construct a new `IoFlags` for a constant field.
    pub const fn constant() -> Self {
        Self::new().with_data_or_constant(true)
    }

    /// Test if this is a constant field.
    pub const fn is_constant(self) -> bool {
        self.data_or_constant()
    }
    /// Test if this is a data field.
    pub const fn is_data(self) -> bool {
        !self.is_constant()
    }

    /// The field stores a variable.
    ///
    /// By default fields store an array.
    pub const fn variable(self) -> Self {
        self.with_array_or_variable(true)
    }

    /// Test if this is a variable field.
    pub const fn is_variable(self) -> bool {
        self.array_or_variable()
    }
    /// Test if this is an array field.
    pub const fn is_array(self) -> bool {
        !self.is_variable()
    }

    /// The field holds a relative value.
    ///
    /// By default values are absolute.
    pub const fn relative(self) -> Self {
        self.with_absolute_or_relative(true)
    }

    /// Test if this field is relative.
    pub const fn is_relative(self) -> bool {
        self.absolute_or_relative()
    }
    /// Test if this field is absolute.
    pub const fn is_absolute(self) -> bool {
        !self.is_relative()
    }

    /// The value wraps around in the positive and negative directions.
    ///
    /// By default values do not wrap.
    pub const fn wrap(self) -> Self {
        self.with_no_wrap_or_wrap(true)
    }

    /// Test if this field wraps.
    pub const fn wraps(self) -> bool {
        self.no_wrap_or_wrap()
    }

    /// The value is non-linear.
    ///
    /// By default values are linear.
    pub const fn non_linear(self) -> Self {
        self.with_linear_or_nonlinear(true)
    }

    /// Test if this field is linear.
    pub const fn is_linear(self) -> bool {
        !self.linear_or_nonlinear()
    }

    /// The value has no preferred state.
    ///
    /// Preferred state means that a control will return to some state
    /// when it's not being used. Buttons or joysticks are examples of
    /// controls with a preferred state.
    ///
    /// By default values have a preferred state.
    pub const fn no_preferred_state(self) -> Self {
        self.with_preferred_or_no_preferred(true)
    }

    /// Test if this field has a preferred state.
    pub const fn has_preferred_state(self) -> bool {
        !self.preferred_or_no_preferred()
    }

    /// The control has a null state.
    ///
    /// A null state is a value outside the logical minimum-maximum range,
    /// used to indicate that the control is not active (such as a hat switch).
    ///
    /// By default controls have no null position.
    pub const fn null_state(self) -> Self {
        self.with_no_null_position_or_null_state(true)
    }

    /// Test if this field has a null state.
    pub const fn has_null_state(self) -> bool {
        !self.no_null_position_or_null_state()
    }

    /// The value is volatile.
    ///
    /// A volatile value is one that can change without host interaction.
    ///
    /// By default a value is non-volatile.
    ///
    /// This flag is only used for Output or Feature items.
    pub const fn volatile(self) -> Self {
        self.with_non_volatile_or_volatile(true)
    }

    /// Test if this field is volatile.
    pub const fn is_volatile(self) -> bool {
        self.non_volatile_or_volatile()
    }

    /// The value is buffered.
    ///
    /// Buffered data is not a single numeric quantity, but instead
    /// part of a stream of bytes (for example data from a barcode reader).
    ///
    /// By default a value is a bit field.
    pub const fn buffered(self) -> Self {
        self.with_bitfield_or_buffered(true)
    }

    /// Test if this field is buffered.
    pub const fn is_buffered(self) -> bool {
        self.bitfield_or_buffered()
    }
    /// Test if this field is a bit field.
    pub const fn is_bitfield(self) -> bool {
        !self.is_buffered()
    }
}

/// A collection of shorthand types for specifying input/output/feature item flags.
///
/// These are not required ([`IoFlags`] may be used instead) but because it may not
/// be clear what the default flags are, these values may improve readability.
pub mod ioflags {
    use super::IoFlags;

    /// Input/Output/Feature flags, using the terminology common in USB HID standards.
    ///
    /// This is shorthand for `IoFlags::data().variable()`.
    pub const DATA_VARIABLE_ABSOLUTE: IoFlags = IoFlags::data().variable();

    /// Input/Output/Feature flags for a constant, using the terminology common in USB HID standards.
    ///
    /// This is shorthand for `IoFlags::constant()`. It is commonly used to declare padding bits or bytes.
    pub const CONSTANT: IoFlags = IoFlags::constant();

    /// Input/Output/Feature flags for a data array, using the terminology common in USB HID standards.
    ///
    /// This is shorthand for `IoFlags::data()` (Note: the flag defaults to "array").
    pub const ARRAY: IoFlags = IoFlags::data();
}

/// A wrapper indicating `IoFlags` that should be interpreted as Input flags.
#[derive(Clone, Copy, PartialEq)]
pub struct InputFlags(pub IoFlags);

impl core::ops::Deref for InputFlags {
    type Target = IoFlags;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl InputFlags {
    /// Mark some flags as input flags, without checking if the bit pattern is sensible for an Input item.
    pub fn from_unchecked(flags: IoFlags) -> Self {
        Self(flags)
    }

    #[doc(hidden)]
    #[deprecated = "`InputFlags` should not use the volatile bit"]
    /// Attempt to prevent users from calling .volatile() on InputFlags, since that bit shouldn't be used.
    pub fn is_volatile(&self) -> bool {
        self.0.is_volatile()
    }
}

/// A wrapper indicating `IoFlags` that should be interpreted as Output or Feature flags.
#[derive(Clone, Copy, PartialEq)]
pub struct OutputFeatureFlags(pub IoFlags);

impl core::ops::Deref for OutputFeatureFlags {
    type Target = IoFlags;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl From<IoFlags> for OutputFeatureFlags {
    /// Convert an `IoFlags` value.
    ///
    /// Note that unline [`InputFlags`], all `IoFlags` values are valid Output or Feature flags.
    fn from(flags: IoFlags) -> Self {
        Self(flags)
    }
}

#[cfg(feature = "std")]
mod std_impls {
    use super::*;

    use std::fmt::{self, Debug};

    fn debug_flags(is_input: bool, flags: IoFlags, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if flags.data_or_constant() {
            f.write_str("Constant ")?;
        } else {
            f.write_str("Data ")?;
        }
        if flags.array_or_variable() {
            f.write_str("Variable ")?;
        } else {
            f.write_str("Array ")?;
        }
        if flags.absolute_or_relative() {
            f.write_str("Relative ")?;
        } else {
            f.write_str("Absolute ")?;
        }
        if flags.no_wrap_or_wrap() {
            f.write_str("Wrap ")?;
        } else {
            f.write_str("No-Wrap ")?;
        }
        if flags.linear_or_nonlinear() {
            f.write_str("Non-Linear ")?;
        } else {
            f.write_str("Linear ")?;
        }
        if flags.preferred_or_no_preferred() {
            f.write_str("No-Preferred ")?;
        } else {
            f.write_str("Preferred-State ")?;
        }
        if flags.no_null_position_or_null_state() {
            f.write_str("Null-State ")?
        } else {
            f.write_str("No-Null-Position ")?;
        }

        // The `volatile` field is only valid for Output and Feature tags.
        if flags.non_volatile_or_volatile() {
            f.write_str("Volatile ")?;
        } else {
            if !is_input {
                f.write_str("Non-Volatile ")?;
            }
        }

        if flags.bitfield_or_buffered() {
            f.write_str("Buffered-Bytes")?;
        } else {
            f.write_str("Bit-Field")?;
        }
        Ok(())
    }

    impl Debug for InputFlags {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            debug_flags(true, self.0, f)
        }
    }

    impl Debug for OutputFeatureFlags {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            debug_flags(false, self.0, f)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_ioflags() {
        const DATA: IoFlags = IoFlags::data();
        const CONSTANT: IoFlags = IoFlags::constant();

        assert!(DATA.is_data());
        assert!(!DATA.is_constant());
        assert!(!CONSTANT.is_data());
        assert!(CONSTANT.is_constant());

        assert!(DATA.is_array());
        assert!(!DATA.is_variable());
        assert!(!DATA.variable().is_array());
        assert!(DATA.variable().is_variable());

        assert!(DATA.is_absolute());
        assert!(!DATA.is_relative());
        assert!(!DATA.relative().is_absolute());
        assert!(DATA.relative().is_relative());

        assert!(!DATA.wraps());
        assert!(DATA.wrap().wraps());

        assert!(DATA.is_linear());
        assert!(!DATA.non_linear().is_linear());

        assert!(DATA.has_preferred_state());
        assert!(!DATA.no_preferred_state().has_preferred_state());

        assert!(DATA.has_null_state());
        assert!(!DATA.null_state().has_null_state());

        assert!(!DATA.is_volatile());
        assert!(DATA.volatile().is_volatile());

        assert!(DATA.is_bitfield());
        assert!(!DATA.is_buffered());
        assert!(!DATA.buffered().is_bitfield());
        assert!(DATA.buffered().is_buffered());
    }
}