hidpp/lib.rs
1//! An implementation of Logitech's HID++ protocol.
2//!
3//! Many of Logitech's more modern peripheral devices (mice, keyboards etc.)
4//! support advanced features improving the user experience. These include, but
5//! are not limited to, things like:
6//!
7//! - scroll wheels dynamically switching between ratchet and freespin mode ([SmartShift](https://support.logi.com/hc/en-us/articles/360052340194-What-is-SmartShift-on-MX-Anywhere-3))
8//! - [mouse gestures](https://support.logi.com/hc/en-us/articles/360023359813-How-to-customize-mouse-buttons-with-Logitech-Options#gesture)
9//! - custom actions for specific mouse buttons
10//! - several customizability options for keyboards, audio devices and touchpads
11//!
12//! All of these features can be managed using their (more or less) proprietary
13//! HID++-protocol which extends standard [HID](https://en.wikipedia.org/wiki/Human_interface_device).
14//!
15//! Logitech kindly provided a [public Google Drive folder](https://drive.google.com/drive/folders/0BxbRzx7vEV7eWmgwazJ3NUFfQ28)
16//! with a lot of documentation on HID++ and several device features. These
17//! documents were heavily used during the development of this crate.
18//!
19//! I also made use of the excellent work already done by the
20//! [Solaar](https://github.com/pwr-Solaar/Solaar) team to grow my
21//! understanding of how things work. It's a great project perfectly usable to
22//! configure Logitech devices on Linux, so definitely check it out if you are
23//! looking for something like this.
24//!
25//! # Quickstart
26//!
27//! ## Establish HID communication
28//!
29//! This crate implements the HID++ protocol, not the underlying [HID](https://en.wikipedia.org/wiki/Human_interface_device)
30//! communication, which is left to an external crate of your choice.
31//! The trait used for bridging your HID implementation to this crate is
32//! [`channel::RawHidChannel`], so make sure to provide an implementation for
33//! it. The trait defines async methods using [`mod@async_trait`], which is
34//! re-exported for annotating your implementing type.
35//!
36//! The crate primarily used while testing and developing is [`async-hid`](https://crates.io/crates/async-hid).
37//! Providing an implementation for this crate behind a feature gate is
38//! planned and will be implemented once [retrieving the raw report descriptor](https://github.com/sidit77/async-hid/issues/17)
39//! is supported.
40//!
41//! ## Initialize HID++ communication
42//!
43//! Once you have a working implementation of [`channel::RawHidChannel`], you
44//! can start by creating a [`channel::HidppChannel`]:
45//!
46//! ```ignore
47//! use std::sync::Arc;
48//!
49//! use hidpp::{
50//! channel::HidppChannel,
51//! device::Device,
52//! feature::{
53//! CreatableFeature,
54//! EmittingFeature,
55//! feature_set::v0::FeatureSetFeatureV0,
56//! thumbwheel::v0::{ThumbwheelEvent, ThumbwheelFeatureV0, ThumbwheelReportingMode},
57//! },
58//! nibble::U4,
59//! receiver::{self, Receiver, bolt::BoltEvent},
60//! };
61//!
62//! // First, we will create the HID++ channel.
63//! // This function will return `ChannelError::HidppNotSupported`
64//! // if the passed HID channel does not support HID++.
65//! let channel = Arc::new(
66//! HidppChannel::from_raw_channel(my_hid_channel)
67//! .await
68//! .expect("could not establish HID++ communication"),
69//! );
70//!
71//! // HID++2.0 includes an arbitrary "software ID" in every message.
72//! // This ID is meant to differentiate messages of different
73//! // softwares, but it can also be used to ease the mapping of
74//! // incoming messages to previously sent outgoing messages by
75//! // rotating it after every sent message.
76//! // By default, the software ID is `0x01` and will not rotate.
77//! channel.set_rotating_sw_id(true);
78//!
79//! // You can also set a custom software ID.
80//! channel.set_sw_id(U4::from_lo(0xa));
81//!
82//! // If a wireless receiver is handling the HID++ communication,
83//! // we can detect it.
84//! let receiver = receiver::detect(Arc::clone(&channel)).expect("no receiver
85//! was found");
86//!
87//! // Assuming we have a Bolt receiver, we will now detect all connected
88//! devices. let Receiver::Bolt(bolt) = receiver else {
89//! panic!("no Bolt receiver");
90//! };
91//! tokio::spawn({
92//! let rx = bolt.listen();
93//!
94//! async move {
95//! while let Ok(BoltEvent::DeviceConnection(event)) = rx.recv() {
96//! println!("Paired device found: {:x?}", event);
97//! }
98//! }
99//! });
100//! bolt.trigger_device_arrival()
101//! .await
102//! .expect("could not trigger device arrival notification");
103//!
104//! // Let's say we found a device with the index 0x02 using this enumeration.
105//! We can now initialize it:
106//! let mut device = Device::new(Arc::clone(&channel), 0x02)
107//! .await
108//! .expect("could not initialize device");
109//!
110//! // The device is a HID++2.0 one, meaning it supports so-called HID++2.0
111//! // features. Every device supports the standardized `IRoot` feature, which
112//! // we can access like this:
113//! let root = device.root();
114//! assert_eq!(0x2, root.ping(0x2).await.unwrap());
115//!
116//! // Additional features are accessed by their feature index in the
117//! // device-internal feature map, not by their globally unique feature ID.
118//! // The root feature also supports looking up a specific feature by its ID.
119//! // The resulting value will contain some information about the feature,
120//! // including its index:
121//! let info = root
122//! .get_feature(FeatureSetFeatureV0::ID)
123//! .await
124//! .expect("could not look up feature")
125//! .expect("FeatureSet feature is not supported");
126//!
127//! // As there are a lot of possible features and a given device only supports
128//! // a small subset of these, looking up every single feature ID using this
129//! // technique is not practicable. That's why the `IFeatureSet` feature can be
130//! // used to enumerate over all supported features, but only if this feature
131//! // itself is supported by the device.
132//! let infos = device
133//! .enumerate_features()
134//! .await
135//! .expect("could not look up features")
136//! .expect("FeatureSet feature is not supported");
137//!
138//! // This crate provides Rust implementations for many HID++2.0 features. A
139//! // registry in the `hidpp::feature::registry` module maintains a list of all
140//! // known features and, if provided, a link to its implementation. The
141//! // `enumerate_features` function we just called automatically registers
142//! // these implementations for our device and we can now access them like this:
143//! let thumbwheel = device
144//! .get_feature::<ThumbwheelFeatureV0>()
145//! .expect("Thumbwheel feature is not supported");
146//! thumbwheel
147//! .set_thumbwheel_reporting(ThumbwheelReportingMode::Diverted, false)
148//! .await
149//! .expect("could not divert thumbwheel");
150//! ```
151
152pub use async_trait::async_trait;
153
154mod bcd;
155pub mod channel;
156pub mod device;
157mod event;
158pub mod feature;
159pub mod nibble;
160pub mod protocol;
161pub mod receiver;