1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
//! Implements the `Thumbwheel` feature (ID `0x2150`) that allows configuration
//! and diversion of thumbwheel events.
use num_enum::{IntoPrimitive, TryFromPrimitive};
use openlogi_hidpp_derive::Feature;
use crate::{
feature::{DecodeEvent, EventSource, FeatureEndpoint},
protocol::v20::Hidpp20Error,
};
/// Implements the `Thumbwheel` / `0x2150` feature.
#[derive(Feature)]
#[creatable(id = 0x2150, version = 0)]
pub struct ThumbwheelFeature {
/// The endpoint this feature talks to.
endpoint: FeatureEndpoint,
/// Publishes decoded events to listeners.
events: EventSource<ThumbwheelEvent>,
}
impl DecodeEvent for ThumbwheelEvent {
fn decode(sub_id: u8, payload: &[u8; 16]) -> Option<Self> {
// The status update is the only event and carries sub-id 0.
if sub_id != 0 {
return None;
}
let rotation_status = ThumbwheelRotationStatus::try_from(payload[4]).ok()?;
Some(ThumbwheelEvent::StatusUpdate(ThumbwheelStatusUpdate {
rotation: i16::from_be_bytes([payload[0], payload[1]]),
time_elapsed: u16::from_be_bytes([payload[2], payload[3]]),
rotation_status,
touch: payload[5] & (1 << 1) != 0,
proxy: payload[5] & (1 << 2) != 0,
single_tap: payload[5] & (1 << 3) != 0,
}))
}
}
impl ThumbwheelFeature {
/// Retrieves some information about the thumbwheel.
pub async fn get_thumbwheel_info(&self) -> Result<ThumbwheelInfo, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(ThumbwheelInfo {
native_resolution: u16::from_be_bytes([payload[0], payload[1]]),
diverted_resolution: u16::from_be_bytes([payload[2], payload[3]]),
time_unit: u16::from_be_bytes([payload[6], payload[7]]),
default_direction: ThumbwheelDirection::try_from(payload[4] & 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
capabilities: ThumbwheelCapabilities::from_bits_retain(payload[5]),
})
}
/// Retrieves the custom status of the thumbwheel.
pub async fn get_thumbwheel_status(&self) -> Result<ThumbwheelStatus, Hidpp20Error> {
let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
Ok(ThumbwheelStatus {
reporting_mode: ThumbwheelReportingMode::try_from(payload[0])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
direction_inverted: payload[1] & 1 != 0,
touch: payload[1] & (1 << 1) != 0,
proxy: payload[1] & (1 << 2) != 0,
})
}
/// Sets the reporting mode of the thumbwheel.
///
/// This can be used to divert the thumbwheel notifications to HID++.
///
/// If `invert_direction` is set, the [`ThumbwheelStatusUpdate::rotation`]
/// field will be the inverse of that would be expected if following
/// [`ThumbwheelInfo::default_direction`].
pub async fn set_thumbwheel_reporting(
&self,
mode: ThumbwheelReportingMode,
invert_direction: bool,
) -> Result<(), Hidpp20Error> {
self.endpoint
.call(2, [mode.into(), u8::from(invert_direction), 0x00])
.await?;
Ok(())
}
}
/// Represents information about the thumbwheel as reported by
/// [`ThumbwheelFeature::get_thumbwheel_info`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct ThumbwheelInfo {
/// The number of ratchets generated by revolution when in native (HID)
/// mode.
pub native_resolution: u16,
/// The number of rotation increments generated by revolution when in
/// diverted (HID++) mode
pub diverted_resolution: u16,
/// If [`ThumbwheelCapabilities::TIME_STAMP`] is set, this is set to the
/// timestamp unit used for [`ThumbwheelStatusUpdate::time_elapsed`] in
/// microseconds. If the capability is not supported, this will always be
/// `0`.
pub time_unit: u16,
/// The default rotation direction. This determines which rotation direction
/// corresponds to which number range (positive or negative) for the
/// [`ThumbwheelStatusUpdate::rotation`] value.
pub default_direction: ThumbwheelDirection,
/// The capabilites of the thumbwheel.
pub capabilities: ThumbwheelCapabilities,
}
/// Determines which thumbwheel rotation corresponds to which number range
/// (positive or negative) for the [`ThumbwheelStatusUpdate::rotation`] value.
///
/// The direction descriptors (`LeftOrBack`, `RightOrFront`) are
/// specific to the device orientation.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ThumbwheelDirection {
/// Positive rotation means left/back for this device orientation.
PositiveWhenLeftOrBack = 0,
/// Positive rotation means right/front for this device orientation.
PositiveWhenRightOrFront = 1,
}
bitflags::bitflags! {
/// Represents the capabilities the thumbwheel may support.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ThumbwheelCapabilities: u8 {
/// The thumbwheel emits the elapsed time between two events via
/// [`ThumbwheelStatusUpdate::time_elapsed`].
const TIME_STAMP = 1 << 0;
/// The thumbwheel is equipped with a touch sensor, so
/// [`ThumbwheelStatusUpdate::touch`] reports whether the user is
/// touching it.
const TOUCH = 1 << 1;
/// The thumbwheel is equipped with a proximity sensor, so
/// [`ThumbwheelStatusUpdate::proxy`] reports whether the user is close
/// to it.
const PROXY = 1 << 2;
/// The thumbwheel detects single taps, so
/// [`ThumbwheelStatusUpdate::single_tap`] reports whether the user
/// tapped it.
const SINGLE_TAP = 1 << 3;
}
}
/// Represents information about the thumbwheel status as reported by
/// [`ThumbwheelFeature::get_thumbwheel_status`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct ThumbwheelStatus {
/// The mode how thumbwheel events are reported (native/HID or
/// diverted/HID++).
pub reporting_mode: ThumbwheelReportingMode,
/// Whether the default direction as reported by
/// [`ThumbwheelInfo::default_direction`] is inverted.
pub direction_inverted: bool,
/// Whether the user touches the thumbwheel.
///
/// This is only set if the device supports touch detection as reported by
/// [`ThumbwheelCapabilities::TOUCH`].
pub touch: bool,
/// Whether the user is close to the thumbwheel.
///
/// This is only set if the device supports proximity detection as reported
/// by [`ThumbwheelCapabilities::PROXY`].
pub proxy: bool,
}
/// Represents the mode how the thumbwheel reports its events.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ThumbwheelReportingMode {
/// Thumbwheel events are reported only to the native HID channel.
Native = 0,
/// Thumbwheel events are reported only to the diverted HID++ channel.
///
/// This mode is required for
/// [`ThumbwheelFeature::listen`](crate::feature::EmittingFeature::listen) to
/// report any events.
Diverted = 1,
}
/// Represents an event emitted by the [`ThumbwheelFeature`] feature.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ThumbwheelEvent {
/// Is emitted whenever the thumbwheel status updates.
///
/// Requires the thumbwheel to be in diverted reporting mode.
StatusUpdate(ThumbwheelStatusUpdate),
}
/// Represents the data of the [`ThumbwheelEvent::StatusUpdate`] event.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct ThumbwheelStatusUpdate {
/// The rotation in relation to [`ThumbwheelInfo::native_resolution`] or
/// [`ThumbwheelInfo::diverted_resolution`].
pub rotation: i16,
/// The time elapsed since the last event.
///
/// The unit of this value is reported in [`ThumbwheelInfo::time_unit`].
///
/// If [`ThumbwheelCapabilities::TIME_STAMP`] is not supported, this value
/// will be `0`.
pub time_elapsed: u16,
/// The status of the current rotation.
pub rotation_status: ThumbwheelRotationStatus,
/// Whether the user touches the thumbwheel.
///
/// This is only set if the device supports touch detection as reported by
/// [`ThumbwheelCapabilities::TOUCH`].
pub touch: bool,
/// Whether the user is close to the thumbwheel.
///
/// This is only set if the device supports proximity detection as reported
/// by [`ThumbwheelCapabilities::PROXY`].
pub proxy: bool,
/// Whether the user single-tapped the thumbwheel.
///
/// This is only set if the device supports single-tap detection as reported
/// by [`ThumbwheelCapabilities::SINGLE_TAP`].
pub single_tap: bool,
}
/// Represents a thumbwheel rotation status as reported in
/// [`ThumbwheelStatusUpdate::rotation_status`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ThumbwheelRotationStatus {
/// The thumbwheel was not rotated.
Inactive = 0,
/// The thumbwheel rotation was started.
Start = 1,
/// The thumbwheel rotation is ongoing.
Active = 2,
/// The thumbwheel was released.
Stop = 3,
}