Skip to main content

openlogi_hid/write/
error.rs

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