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 `PersistentRemappableAction` feature (ID `0x1c00`) that
//! persistently remaps a device control to a different HID action.
//!
//! Controls are identified by the same [`ControlId`]s as
//! [`ReprogControls`](super::reprog_controls) (`0x1b04`); when both features are
//! present and `0x1b04` diverts a control, that takes precedence over a
//! persistent remap here.
#[cfg(test)]
mod tests;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use openlogi_hidpp_derive::Feature;
use crate::{
feature::{FeatureEndpoint, hosts_info::HostIndex, reprog_controls::ControlId},
protocol::v20::Hidpp20Error,
};
bitflags::bitflags! {
/// What HID outputs a device's persistent remapping can produce, from
/// [`get_feature_info`](PersistentRemappableActionFeature::get_feature_info).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct RemappableCapabilities: u16 {
/// Can send keyboard/keypad keys.
const KEYBOARD_REPORT = 1 << 0;
/// Can send mouse buttons.
const MOUSE_BUTTONS = 1 << 1;
/// Can send mouse X displacement.
const X_DISPLACEMENT = 1 << 2;
/// Can send mouse Y displacement.
const Y_DISPLACEMENT = 1 << 3;
/// Can send vertical roller increments.
const VERTICAL_ROLLER = 1 << 4;
/// Can send horizontal roller (AC pan) increments.
const HORIZONTAL_ROLLER = 1 << 5;
/// Can send consumer-control codes.
const CONSUMER_CONTROL = 1 << 6;
/// Can execute internal functions.
const INTERNAL_FUNCTION = 1 << 7;
/// Can send power keys.
const POWER_KEY = 1 << 8;
}
}
bitflags::bitflags! {
/// Standard keyboard modifier keys for a remapped keyboard action.
///
/// Modifiers only apply to keyboard reports.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ModifierMask: u8 {
/// Left Control.
const LEFT_CTRL = 1 << 0;
/// Left Shift.
const LEFT_SHIFT = 1 << 1;
/// Left Alt.
const LEFT_ALT = 1 << 2;
/// Left GUI (Win/Command).
const LEFT_GUI = 1 << 3;
/// Right Control.
const RIGHT_CTRL = 1 << 4;
/// Right Shift.
const RIGHT_SHIFT = 1 << 5;
/// Right Alt.
const RIGHT_ALT = 1 << 6;
/// Right GUI (Win/Command).
const RIGHT_GUI = 1 << 7;
}
}
bitflags::bitflags! {
/// A set of host slots for
/// [`reset_to_factory_settings`](PersistentRemappableActionFeature::reset_to_factory_settings).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct HostMask: u8 {
/// Host 1.
const HOST_1 = 1 << 0;
/// Host 2.
const HOST_2 = 1 << 1;
/// Host 3.
const HOST_3 = 1 << 2;
}
}
/// The action a control performs when triggered.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ActionId {
/// Send a keyboard/keypad report (HID usage page 7).
SendKeyboard = 0x01,
/// Send a mouse-button report (usage page 9).
SendMouseButton = 0x02,
/// Send mouse X displacement (usage page 1, code 0x30).
SendXDisplacement = 0x03,
/// Send mouse Y displacement (usage page 1, code 0x31).
SendYDisplacement = 0x04,
/// Send vertical roller/wheel displacement (usage page 1, code 0x38).
SendVerticalRoller = 0x05,
/// Send horizontal roller / AC pan displacement (usage page 12, code 0x0238).
SendHorizontalRoller = 0x06,
/// Send a consumer-control report (usage page 12).
SendConsumerControl = 0x07,
/// Execute an internal function (the value is the function index).
ExecuteInternalFunction = 0x08,
/// Send a power-key report (usage page 1).
SendPowerKey = 0x09,
}
/// Control-table sizing from
/// [`get_count`](PersistentRemappableActionFeature::get_count).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct RemappableInfo {
/// Number of control IDs in the table.
pub count: u8,
/// Number of hosts the device supports.
pub host_count: u8,
}
/// The action mapped to a control, from
/// [`get_persistent_action`](PersistentRemappableActionFeature::get_persistent_action).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct PersistentAction {
/// The control the action belongs to.
pub cid: ControlId,
/// The host slot the mapping applies to.
pub host: HostIndex,
/// The action performed when triggered.
pub action_id: ActionId,
/// The HID usage code, displacement, or internal-function index sent.
pub value: u16,
/// Keyboard modifiers applied (keyboard actions only).
pub modifier_mask: ModifierMask,
/// Whether the control is remapped away from its default behaviour.
pub remapped: bool,
}
impl PersistentAction {
fn from_payload(payload: &[u8; 16]) -> Result<Self, Hidpp20Error> {
Ok(Self {
cid: ControlId::from(u16::from_be_bytes([payload[0], payload[1]])),
host: HostIndex::from(payload[2]),
action_id: ActionId::try_from(payload[3])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
value: u16::from_be_bytes([payload[4], payload[5]]),
modifier_mask: ModifierMask::from_bits_retain(payload[6]),
remapped: payload[7] & 1 != 0,
})
}
}
/// The action to assign with
/// [`set_persistent_action`](PersistentRemappableActionFeature::set_persistent_action).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PersistentActionConfig {
/// The action to perform when triggered.
pub action_id: ActionId,
/// The HID usage code, displacement, or internal-function index to send.
pub value: u16,
/// Keyboard modifiers to apply (keyboard actions only).
pub modifier_mask: ModifierMask,
}
/// Implements the `PersistentRemappableAction` / `0x1c00` feature.
#[derive(Clone, Feature)]
#[creatable(id = 0x1c00, version = 0)]
pub struct PersistentRemappableActionFeature {
/// The endpoint this feature talks to.
endpoint: FeatureEndpoint,
}
impl PersistentRemappableActionFeature {
/// Retrieves which HID outputs the device's remapping can produce.
pub async fn get_feature_info(&self) -> Result<RemappableCapabilities, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(RemappableCapabilities::from_bits_retain(
u16::from_be_bytes([payload[0], payload[1]]),
))
}
/// Retrieves the control-ID count and host count.
pub async fn get_count(&self) -> Result<RemappableInfo, Hidpp20Error> {
let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
Ok(RemappableInfo {
count: payload[0],
host_count: payload[1],
})
}
/// Retrieves the control ID at table `index` for `host`.
pub async fn get_cid_info(
&self,
index: u8,
host: HostIndex,
) -> Result<ControlId, Hidpp20Error> {
let payload = self
.endpoint
.call(2, [index, u8::from(host), 0])
.await?
.extend_payload();
Ok(ControlId::from(u16::from_be_bytes([
payload[0], payload[1],
])))
}
/// Retrieves the persistent action mapped to `cid` on `host`.
pub async fn get_persistent_action(
&self,
cid: ControlId,
host: HostIndex,
) -> Result<PersistentAction, Hidpp20Error> {
let [cid_hi, cid_lo] = u16::from(cid).to_be_bytes();
let payload = self
.endpoint
.call(3, [cid_hi, cid_lo, u8::from(host)])
.await?
.extend_payload();
PersistentAction::from_payload(&payload)
}
/// Persistently remaps `cid` on `host` to `config`.
///
/// This writes to the device's non-volatile memory and changes the control's
/// behaviour until reset (see [`Self::reset_persistent_action`]).
pub async fn set_persistent_action(
&self,
cid: ControlId,
host: HostIndex,
config: PersistentActionConfig,
) -> Result<(), Hidpp20Error> {
let [cid_hi, cid_lo] = u16::from(cid).to_be_bytes();
let [value_hi, value_lo] = config.value.to_be_bytes();
let mut args = [0; 16];
args[..7].copy_from_slice(&[
cid_hi,
cid_lo,
u8::from(host),
config.action_id.into(),
value_hi,
value_lo,
config.modifier_mask.bits(),
]);
self.endpoint.call_long(4, args).await?;
Ok(())
}
/// Resets `cid` on `host` to its factory default action.
pub async fn reset_persistent_action(
&self,
cid: ControlId,
host: HostIndex,
) -> Result<(), Hidpp20Error> {
let [cid_hi, cid_lo] = u16::from(cid).to_be_bytes();
self.endpoint
.call(5, [cid_hi, cid_lo, u8::from(host)])
.await?;
Ok(())
}
/// Resets every control to its factory default for the hosts in `hosts`.
pub async fn reset_to_factory_settings(&self, hosts: HostMask) -> Result<(), Hidpp20Error> {
self.endpoint.call(6, [hosts.bits(), 0, 0]).await?;
Ok(())
}
}