Skip to main content

openlogi_device/
write.rs

1//! HID++ reads and writes per feature — DPI, SmartShift, wheel modes,
2//! lighting, backlight, and diagnostics.
3//!
4//! Each entry point takes a [`DeviceRoute`] and resolves it to an open channel
5//! through `open_route_channel`, so the same call works whether the device is
6//! behind a Bolt receiver or attached directly (USB cable / Bluetooth). Each
7//! route-addressed call re-enumerates and re-opens, while the corresponding
8//! `_on` entry points reuse a [`crate::SharedChannel`] already owned by
9//! inventory or a standalone capture session.
10
11use std::sync::Arc;
12
13use hidpp::{channel::HidppChannel, device::Device, feature::CreatableFeature};
14
15use crate::backend::HidBackend;
16use crate::channel::route::{DeviceRoute, open_route_channel};
17
18mod backlight;
19mod diagnostics;
20mod dpi;
21mod error;
22mod fn_lock;
23mod haptic;
24mod hires_wheel;
25mod lighting;
26mod litra;
27mod pointer_scaling;
28mod smartshift;
29
30pub use backlight::{get_backlight, get_backlight_on, set_backlight_enabled};
31pub use diagnostics::{
32    FeatureEntry, FirmwareEntity, FirmwareEntityInfo, ReprogControlEntry, dump_features,
33    dump_firmware_entities, dump_reprog_controls, read_battery_raw,
34};
35pub use dpi::{
36    Dpi, DpiCapabilities, DpiInfo, get_dpi, get_dpi_info, get_dpi_info_on, set_dpi, set_dpi_on,
37};
38pub use error::{HidppFeatureErrorKind, HidppOperation, WriteError};
39pub use fn_lock::{get_fn_lock, get_fn_lock_on, set_fn_lock, set_fn_lock_on};
40pub use haptic::{ensure_haptics_armed_on, play_haptic, play_haptic_on};
41pub use hidpp::feature::haptic_feedback::HapticWaveform;
42pub use hires_wheel::{
43    ScrollReportingTarget, ScrollResolution, ScrollWheelMode, get_scroll_wheel_mode,
44    get_scroll_wheel_mode_on, set_scroll_inversion, set_scroll_inversion_on, set_scroll_resolution,
45    set_scroll_resolution_on, set_scroll_wheel_mode, set_scroll_wheel_mode_on,
46};
47pub use lighting::{
48    LightingMethod, LightingWrite, set_keyboard_color, set_keyboard_color_on,
49    set_keyboard_color_with, set_keyboard_color_with_on,
50};
51pub(crate) use litra::litra_capabilities;
52pub use litra::{
53    LITRA_BEAM_PRODUCT_ID, LITRA_GLOW_PRODUCT_ID, LightCommand, LitraDescriptor, LitraModel,
54    apply as apply_litra, encode_command as encode_litra_command, find_litra,
55    litra_model_for_route, matches_litra,
56};
57pub use pointer_scaling::{PointerScaling, get_pointer_scaling, set_pointer_scaling};
58pub use smartshift::{
59    get_smartshift_status, get_smartshift_status_on, set_smartshift, set_smartshift_on,
60    set_smartshift_sensitivity, toggle_smartshift, toggle_smartshift_on,
61};
62
63// commands_for_light_settings operates purely on openlogi_core config/device
64// types with no HID++ I/O, so it lives in `openlogi_core::hid::light`; this
65// module's callers name it through here, next to the writes it feeds.
66pub use openlogi_core::hid::light::commands_for_light_settings;
67
68pub(crate) use error::classify_hidpp_error;
69
70/// Look up `F` on a device by HID++ feature ID, register it with
71/// [`Device::add_feature`], and return the typed wrapper.
72///
73/// The direct lookup via `root().get_feature(id)` returns the assigned index
74/// unconditionally; `add_feature` then attaches our wrapper to that index. This
75/// keeps route-based write/read paths independent from full feature-table
76/// enumeration and also works for feature wrappers that are not in the central
77/// registry yet.
78pub(crate) async fn open_feature<F: CreatableFeature + 'static>(
79    device: &mut Device,
80) -> Result<Arc<F>, WriteError> {
81    let info = device
82        .root()
83        .get_feature(F::ID)
84        .await
85        .map_err(|e| classify_hidpp_error(e, HidppOperation::ResolveFeature, F::ID))?
86        .ok_or(WriteError::FeatureUnsupported { feature_hex: F::ID })?;
87    Ok(device.add_feature::<F>(info.index))
88}
89
90/// Boilerplate-eater: open the channel that reaches `route`, then run `f` once
91/// with it. The caller addresses features at [`DeviceRoute::device_index`].
92pub(crate) async fn with_route<F, Fut, T>(
93    backend: &dyn HidBackend,
94    route: &DeviceRoute,
95    f: F,
96) -> Result<T, WriteError>
97where
98    F: FnOnce(Arc<HidppChannel>) -> Fut,
99    Fut: std::future::Future<Output = Result<T, WriteError>>,
100{
101    match open_route_channel(backend, route).await? {
102        Some(channel) => f(channel).await,
103        None => Err(WriteError::DeviceNotFound),
104    }
105}
106
107#[cfg(test)]
108mod tests;