Skip to main content

openlogi_hid/
write.rs

1//! HID++ writes back to the device — DPI, SmartShift, lighting, and diagnostics.
2//!
3//! Each entry point takes a [`DeviceRoute`] and resolves it to an open channel
4//! through `open_route_channel`, so the same call works whether the device is
5//! behind a Bolt receiver or attached directly (USB cable / Bluetooth). Each
6//! call re-enumerates and re-opens — fine at the frequency this is invoked
7//! (once per slider release) — unless a [`SharedChannel`] from the capture
8//! session is reused.
9
10use std::sync::Arc;
11
12use hidpp::{channel::HidppChannel, device::Device, feature::CreatableFeature};
13
14use crate::route::{DeviceRoute, open_route_channel};
15
16mod diagnostics;
17mod dpi;
18mod error;
19mod lighting;
20mod shared;
21mod smartshift;
22
23pub use diagnostics::{FeatureEntry, ReprogControlEntry, dump_features, dump_reprog_controls};
24pub use dpi::{DpiCapabilities, DpiInfo, get_dpi, get_dpi_info, set_dpi};
25pub use error::{HidppFeatureErrorKind, HidppOperation, WriteError};
26pub use lighting::{LightingMethod, set_keyboard_color, set_keyboard_color_with};
27pub use shared::{SharedChannel, set_dpi_on, set_smartshift_on, toggle_smartshift_on};
28pub use smartshift::{
29    get_smartshift_status, set_smartshift, set_smartshift_sensitivity, toggle_smartshift,
30};
31
32pub(crate) use error::classify_hidpp_error;
33
34/// Look up `F` on a device by HID++ feature ID, register it with
35/// [`Device::add_feature`], and return the typed wrapper.
36///
37/// The direct lookup via `root().get_feature(id)` returns the assigned index
38/// unconditionally; `add_feature` then attaches our wrapper to that index. This
39/// keeps route-based write/read paths independent from full feature-table
40/// enumeration and also works for feature wrappers that are not in the central
41/// registry yet.
42pub(crate) async fn open_feature<F: CreatableFeature + 'static>(
43    device: &mut Device,
44) -> Result<Arc<F>, WriteError> {
45    let info = device
46        .root()
47        .get_feature(F::ID)
48        .await
49        .map_err(|e| classify_hidpp_error(e, HidppOperation::ResolveFeature, F::ID))?
50        .ok_or(WriteError::FeatureUnsupported { feature_hex: F::ID })?;
51    Ok(device.add_feature::<F>(info.index))
52}
53
54/// Boilerplate-eater: open the channel that reaches `route`, then run `f` once
55/// with it. The caller addresses features at [`DeviceRoute::device_index`].
56pub(crate) async fn with_route<F, Fut, T>(route: &DeviceRoute, f: F) -> Result<T, WriteError>
57where
58    F: FnOnce(Arc<HidppChannel>) -> Fut,
59    Fut: std::future::Future<Output = Result<T, WriteError>>,
60{
61    match open_route_channel(route).await? {
62        Some(channel) => f(channel).await,
63        None => Err(WriteError::DeviceNotFound),
64    }
65}
66
67#[cfg(test)]
68mod tests;