Skip to main content

openlogi_hid/write/
diagnostics.rs

1use std::sync::Arc;
2
3use hidpp::{device::Device, feature::CreatableFeature, feature::feature_set::FeatureSetFeature};
4
5use crate::reprog_controls::{self, CidFlags, CidInfo, ReprogControlsV4};
6use crate::route::DeviceRoute;
7use crate::write::{HidppOperation, WriteError, classify_hidpp_error, with_route};
8
9/// Snapshot of one HID++ feature exposed by a device: protocol ID +
10/// version. Returned by [`dump_features`] for diagnostics.
11#[derive(Debug, Clone, Copy)]
12pub struct FeatureEntry {
13    /// HID++ feature ID.
14    pub id: u16,
15    /// Feature version reported by the device.
16    pub version: u8,
17}
18
19/// Snapshot of one HID++ `0x1b04` reprogrammable control. Returned by
20/// [`dump_reprog_controls`] for diagnostics so new device controls can be
21/// identified before OpenLogi maps them to a first-class button.
22#[derive(Debug, Clone, Copy)]
23pub struct ReprogControlEntry {
24    /// HID++ control ID.
25    pub cid: u16,
26    /// Default task ID assigned to the control.
27    pub task_id: u16,
28    /// Capability and classification flags for the control.
29    pub flags: CidFlags,
30}
31
32impl From<CidInfo> for ReprogControlEntry {
33    fn from(info: CidInfo) -> Self {
34        Self {
35            cid: info.cid.into(),
36            task_id: info.task_id.0,
37            flags: info.flags,
38        }
39    }
40}
41
42/// Enumerate every HID++ feature the device on `route` reports — used by
43/// `openlogi diag features` to confirm which DPI / SmartShift / etc.
44/// feature IDs a given peripheral actually exposes (e.g. some mice use
45/// `0x2202 ExtendedAdjustableDpi` instead of `0x2201 AdjustableDpi`).
46pub async fn dump_features(route: &DeviceRoute) -> Result<Vec<FeatureEntry>, WriteError> {
47    let index = route.device_index();
48    with_route(route, move |channel| async move {
49        let mut device = Device::new(Arc::clone(&channel), index)
50            .await
51            .map_err(|_| WriteError::DeviceUnreachable { index })?;
52        // The root feature exposes the FeatureSet (0x0001) at a fixed
53        // address; we look it up directly rather than going through
54        // `enumerate_features` so the iteration is observable.
55        let feature_set_info = device
56            .root()
57            .get_feature(FeatureSetFeature::ID)
58            .await
59            .map_err(|e| {
60                classify_hidpp_error(e, HidppOperation::DumpFeatures, FeatureSetFeature::ID)
61            })?
62            .ok_or(WriteError::FeatureUnsupported {
63                feature_hex: FeatureSetFeature::ID,
64            })?;
65        let feature_set = device.add_feature::<FeatureSetFeature>(feature_set_info.index);
66        let count = feature_set.count().await.map_err(|e| {
67            classify_hidpp_error(e, HidppOperation::DumpFeatures, FeatureSetFeature::ID)
68        })?;
69        let mut entries = Vec::with_capacity(usize::from(count));
70        for i in 0..=count {
71            let info = feature_set.get_feature(i).await.map_err(|e| {
72                classify_hidpp_error(e, HidppOperation::DumpFeatures, FeatureSetFeature::ID)
73            })?;
74            entries.push(FeatureEntry {
75                id: info.id,
76                version: info.version,
77            });
78        }
79        Ok(entries)
80    })
81    .await
82}
83
84/// Enumerate the device's HID++ `0x1b04` reprogrammable controls. This is a
85/// diagnostics-only probe used to discover controls for newly released devices.
86/// For example, MX Master 4 has both a Gesture Button and a separate Haptic
87/// Sense Panel in the thumb area; this probe lets us identify the panel's CID
88/// and capabilities before wiring it into the capture/remapping model.
89pub async fn dump_reprog_controls(
90    route: &DeviceRoute,
91) -> Result<Vec<ReprogControlEntry>, WriteError> {
92    let index = route.device_index();
93    with_route(route, move |channel| async move {
94        let device = Device::new(Arc::clone(&channel), index)
95            .await
96            .map_err(|_| WriteError::DeviceUnreachable { index })?;
97        let info = device
98            .root()
99            .get_feature(reprog_controls::FEATURE_ID)
100            .await
101            .map_err(|e| {
102                classify_hidpp_error(e, HidppOperation::DumpFeatures, reprog_controls::FEATURE_ID)
103            })?
104            .ok_or(WriteError::FeatureUnsupported {
105                feature_hex: reprog_controls::FEATURE_ID,
106            })?;
107        let rc = ReprogControlsV4::new(Arc::clone(&channel), index, info.index);
108        let count = rc.get_count().await.map_err(|e| {
109            classify_hidpp_error(e, HidppOperation::DumpFeatures, reprog_controls::FEATURE_ID)
110        })?;
111        let mut entries = Vec::with_capacity(usize::from(count));
112        for i in 0..count {
113            let control = rc.get_cid_info(i).await.map_err(|e| {
114                classify_hidpp_error(e, HidppOperation::DumpFeatures, reprog_controls::FEATURE_ID)
115            })?;
116            entries.push(control.into());
117        }
118        Ok(entries)
119    })
120    .await
121}