Skip to main content

openlogi_core/hid/
error.rs

1//! HID++ read/write error vocabulary — pure data, no I/O.
2//!
3//! The conversions from `hidpp`/`async_hid` error types into [`WriteError`]
4//! live in `openlogi_hid::write::error`, which this crate must never depend
5//! on.
6
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10/// Error returned by HID++ read/write operations.
11///
12/// Serializable + Clone so it can cross the agent↔GUI IPC unchanged: the GUI
13/// classifies a device read/write error as permanent (FeatureUnsupported /
14/// EmptyDpiList) vs transient, so the discriminating variant must survive the
15/// wire — stringifying it would collapse every case to "transient" and a device
16/// that genuinely lacks a feature would be re-probed forever. Variant order is
17/// therefore wire format: changes require a `PROTOCOL_VERSION` bump (guarded
18/// by `openlogi-ipc/tests/wire_format.rs`).
19#[derive(Debug, Clone, Error, Serialize, Deserialize)]
20pub enum WriteError {
21    /// HID transport error serialized as text.
22    ///
23    /// `openlogi_hid::BackendError` isn't `Serialize`, so carry its message as
24    /// text; the typed error is never matched on (only constructed +
25    /// displayed).
26    #[error("HID transport error: {0}")]
27    Hid(String),
28    /// No currently connected device matched the requested route.
29    #[error("no connected device matched the route")]
30    DeviceNotFound,
31    /// The HID node opened, but the target HID++ device index did not answer.
32    #[error("device at index {index:#04x} did not respond to HID++")]
33    DeviceUnreachable {
34        /// HID++ device index that failed to answer.
35        index: u8,
36    },
37    /// Device does not expose the requested HID++ feature.
38    #[error("device does not expose HID++ feature {feature_hex:#06x}")]
39    FeatureUnsupported {
40        /// HID++ feature ID that was not present.
41        feature_hex: u16,
42    },
43    /// Device reported no valid DPI values.
44    #[error("device returned no supported DPI values")]
45    EmptyDpiList,
46    /// Generic HID++ protocol error serialized as text.
47    #[error("HID++ protocol error: {0}")]
48    Hidpp(String),
49    /// HID++ feature error response.
50    #[error("HID++ feature error during {operation:?} for feature {feature_hex:#06x}: {kind:?}")]
51    HidppFeature {
52        /// Operation being performed.
53        operation: HidppOperation,
54        /// HID++ feature ID involved in the operation.
55        feature_hex: u16,
56        /// HID++ feature error kind.
57        kind: HidppFeatureErrorKind,
58    },
59    /// Device returned a structurally unsupported response.
60    #[error("HID++ unsupported response during {operation:?} for feature {feature_hex:#06x}")]
61    UnsupportedResponse {
62        /// Operation being performed.
63        operation: HidppOperation,
64        /// HID++ feature ID involved in the operation.
65        feature_hex: u16,
66    },
67    /// HID++ request timed out.
68    #[error("HID++ request timed out during {operation:?}")]
69    RequestTimedOut {
70        /// Operation that timed out.
71        operation: HidppOperation,
72    },
73    /// Tokio runtime could not be initialized for a sync caller.
74    #[error("tokio runtime init failed: {message}")]
75    RuntimeInit {
76        /// Runtime initialization error message.
77        message: String,
78    },
79    /// Background agent write path is unavailable.
80    #[error("background agent is unavailable")]
81    AgentUnavailable,
82    /// A standalone light value is outside the driver's supported range.
83    #[error("invalid light value for {control}: {value}")]
84    InvalidLightValue {
85        /// Semantic control name.
86        control: String,
87        /// Rejected value in the semantic/native unit supplied by the caller.
88        value: u16,
89    },
90    /// The selected light driver does not implement a requested control.
91    #[error("light control is unsupported: {control}")]
92    LightUnsupported {
93        /// Semantic control name.
94        control: String,
95    },
96    /// Multiple raw HID nodes matched one physical route.
97    #[error("multiple raw HID devices matched the route")]
98    AmbiguousRawDevice,
99}
100
101/// HID++ operation being performed when a device write/read failed.
102///
103/// Variant order is wire format because this travels inside [`WriteError`].
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
105pub enum HidppOperation {
106    /// Resolve a feature ID to its runtime feature index.
107    ResolveFeature,
108    /// Enumerate the device feature table.
109    DumpFeatures,
110    /// Read current DPI.
111    ReadDpi,
112    /// Read DPI capabilities.
113    ReadDpiCapabilities,
114    /// Write DPI.
115    WriteDpi,
116    /// Read SmartShift status.
117    ReadSmartShift,
118    /// Write SmartShift status.
119    WriteSmartShift,
120    /// Write keyboard lighting.
121    Lighting,
122    /// Read HiResWheel capabilities or the current wheel mode.
123    ReadWheelMode,
124    /// Write and verify the native HiResWheel mode.
125    WriteWheelMode,
126    /// Read the keyboard backlight config or info.
127    ReadBacklight,
128    /// Write the keyboard backlight config.
129    WriteBacklight,
130    /// Write keyboard Fn-lock (fn inversion).
131    WriteFnLock,
132    /// Write a standalone-light command. Appended last — variant order is wire format.
133    Light,
134    /// Play one haptic waveform. Appended last — variant order is wire format.
135    PlayHaptic,
136}
137
138/// HID++ feature error kind in a serializable wire-safe form.
139///
140/// Variant order is wire format because this travels inside [`WriteError`].
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
142pub enum HidppFeatureErrorKind {
143    /// HID++ `NoError` code.
144    NoError,
145    /// Unknown HID++ error.
146    Unknown,
147    /// Invalid argument.
148    InvalidArgument,
149    /// Argument out of range.
150    OutOfRange,
151    /// Hardware error.
152    HwError,
153    /// Logitech-internal firmware error.
154    LogitechInternal,
155    /// Invalid feature index.
156    InvalidFeatureIndex,
157    /// Invalid function ID.
158    InvalidFunctionId,
159    /// Device is busy.
160    Busy,
161    /// Operation is unsupported.
162    Unsupported,
163    /// Error code not modeled by OpenLogi.
164    Unrecognized,
165}