Skip to main content

hidpp/feature/persistent_remappable_action/
mod.rs

1//! Implements the `PersistentRemappableAction` feature (ID `0x1c00`) that
2//! persistently remaps a device control to a different HID action.
3//!
4//! Controls are identified by the same [`ControlId`]s as
5//! [`ReprogControls`](super::reprog_controls) (`0x1b04`); when both features are
6//! present and `0x1b04` diverts a control, that takes precedence over a
7//! persistent remap here.
8
9#[cfg(test)]
10mod tests;
11
12use std::sync::Arc;
13
14use num_enum::{IntoPrimitive, TryFromPrimitive};
15
16use crate::{
17    channel::HidppChannel,
18    feature::{
19        CreatableFeature, Feature, FeatureEndpoint, hosts_info::HostIndex,
20        reprog_controls::ControlId,
21    },
22    protocol::v20::Hidpp20Error,
23};
24
25bitflags::bitflags! {
26    /// What HID outputs a device's persistent remapping can produce, from
27    /// [`get_feature_info`](PersistentRemappableActionFeature::get_feature_info).
28    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
30    pub struct RemappableCapabilities: u16 {
31        /// Can send keyboard/keypad keys.
32        const KEYBOARD_REPORT = 1 << 0;
33        /// Can send mouse buttons.
34        const MOUSE_BUTTONS = 1 << 1;
35        /// Can send mouse X displacement.
36        const X_DISPLACEMENT = 1 << 2;
37        /// Can send mouse Y displacement.
38        const Y_DISPLACEMENT = 1 << 3;
39        /// Can send vertical roller increments.
40        const VERTICAL_ROLLER = 1 << 4;
41        /// Can send horizontal roller (AC pan) increments.
42        const HORIZONTAL_ROLLER = 1 << 5;
43        /// Can send consumer-control codes.
44        const CONSUMER_CONTROL = 1 << 6;
45        /// Can execute internal functions.
46        const INTERNAL_FUNCTION = 1 << 7;
47        /// Can send power keys.
48        const POWER_KEY = 1 << 8;
49    }
50}
51
52bitflags::bitflags! {
53    /// Standard keyboard modifier keys for a remapped keyboard action.
54    ///
55    /// Modifiers only apply to keyboard reports.
56    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
58    pub struct ModifierMask: u8 {
59        /// Left Control.
60        const LEFT_CTRL = 1 << 0;
61        /// Left Shift.
62        const LEFT_SHIFT = 1 << 1;
63        /// Left Alt.
64        const LEFT_ALT = 1 << 2;
65        /// Left GUI (Win/Command).
66        const LEFT_GUI = 1 << 3;
67        /// Right Control.
68        const RIGHT_CTRL = 1 << 4;
69        /// Right Shift.
70        const RIGHT_SHIFT = 1 << 5;
71        /// Right Alt.
72        const RIGHT_ALT = 1 << 6;
73        /// Right GUI (Win/Command).
74        const RIGHT_GUI = 1 << 7;
75    }
76}
77
78bitflags::bitflags! {
79    /// A set of host slots for
80    /// [`reset_to_factory_settings`](PersistentRemappableActionFeature::reset_to_factory_settings).
81    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
82    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
83    pub struct HostMask: u8 {
84        /// Host 1.
85        const HOST_1 = 1 << 0;
86        /// Host 2.
87        const HOST_2 = 1 << 1;
88        /// Host 3.
89        const HOST_3 = 1 << 2;
90    }
91}
92
93/// The action a control performs when triggered.
94#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize))]
96#[non_exhaustive]
97#[repr(u8)]
98pub enum ActionId {
99    /// Send a keyboard/keypad report (HID usage page 7).
100    SendKeyboard = 0x01,
101    /// Send a mouse-button report (usage page 9).
102    SendMouseButton = 0x02,
103    /// Send mouse X displacement (usage page 1, code 0x30).
104    SendXDisplacement = 0x03,
105    /// Send mouse Y displacement (usage page 1, code 0x31).
106    SendYDisplacement = 0x04,
107    /// Send vertical roller/wheel displacement (usage page 1, code 0x38).
108    SendVerticalRoller = 0x05,
109    /// Send horizontal roller / AC pan displacement (usage page 12, code 0x0238).
110    SendHorizontalRoller = 0x06,
111    /// Send a consumer-control report (usage page 12).
112    SendConsumerControl = 0x07,
113    /// Execute an internal function (the value is the function index).
114    ExecuteInternalFunction = 0x08,
115    /// Send a power-key report (usage page 1).
116    SendPowerKey = 0x09,
117}
118
119/// Control-table sizing from
120/// [`get_count`](PersistentRemappableActionFeature::get_count).
121#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize))]
123#[non_exhaustive]
124pub struct RemappableInfo {
125    /// Number of control IDs in the table.
126    pub count: u8,
127    /// Number of hosts the device supports.
128    pub host_count: u8,
129}
130
131/// The action mapped to a control, from
132/// [`get_persistent_action`](PersistentRemappableActionFeature::get_persistent_action).
133#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize))]
135#[non_exhaustive]
136pub struct PersistentAction {
137    /// The control the action belongs to.
138    pub cid: ControlId,
139    /// The host slot the mapping applies to.
140    pub host: HostIndex,
141    /// The action performed when triggered.
142    pub action_id: ActionId,
143    /// The HID usage code, displacement, or internal-function index sent.
144    pub value: u16,
145    /// Keyboard modifiers applied (keyboard actions only).
146    pub modifier_mask: ModifierMask,
147    /// Whether the control is remapped away from its default behaviour.
148    pub remapped: bool,
149}
150
151impl PersistentAction {
152    fn from_payload(payload: &[u8; 16]) -> Result<Self, Hidpp20Error> {
153        Ok(Self {
154            cid: ControlId::from(u16::from_be_bytes([payload[0], payload[1]])),
155            host: HostIndex::from(payload[2]),
156            action_id: ActionId::try_from(payload[3])
157                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
158            value: u16::from_be_bytes([payload[4], payload[5]]),
159            modifier_mask: ModifierMask::from_bits_retain(payload[6]),
160            remapped: payload[7] & 1 != 0,
161        })
162    }
163}
164
165/// The action to assign with
166/// [`set_persistent_action`](PersistentRemappableActionFeature::set_persistent_action).
167#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize))]
169pub struct PersistentActionConfig {
170    /// The action to perform when triggered.
171    pub action_id: ActionId,
172    /// The HID usage code, displacement, or internal-function index to send.
173    pub value: u16,
174    /// Keyboard modifiers to apply (keyboard actions only).
175    pub modifier_mask: ModifierMask,
176}
177
178/// Implements the `PersistentRemappableAction` / `0x1c00` feature.
179#[derive(Clone)]
180pub struct PersistentRemappableActionFeature {
181    /// The endpoint this feature talks to.
182    endpoint: FeatureEndpoint,
183}
184
185impl CreatableFeature for PersistentRemappableActionFeature {
186    const ID: u16 = 0x1c00;
187    const STARTING_VERSION: u8 = 0;
188
189    fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
190        Self {
191            endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
192        }
193    }
194}
195
196impl Feature for PersistentRemappableActionFeature {}
197
198impl PersistentRemappableActionFeature {
199    /// Retrieves which HID outputs the device's remapping can produce.
200    pub async fn get_feature_info(&self) -> Result<RemappableCapabilities, Hidpp20Error> {
201        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
202        Ok(RemappableCapabilities::from_bits_retain(
203            u16::from_be_bytes([payload[0], payload[1]]),
204        ))
205    }
206
207    /// Retrieves the control-ID count and host count.
208    pub async fn get_count(&self) -> Result<RemappableInfo, Hidpp20Error> {
209        let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
210        Ok(RemappableInfo {
211            count: payload[0],
212            host_count: payload[1],
213        })
214    }
215
216    /// Retrieves the control ID at table `index` for `host`.
217    pub async fn get_cid_info(
218        &self,
219        index: u8,
220        host: HostIndex,
221    ) -> Result<ControlId, Hidpp20Error> {
222        let payload = self
223            .endpoint
224            .call(2, [index, u8::from(host), 0])
225            .await?
226            .extend_payload();
227        Ok(ControlId::from(u16::from_be_bytes([
228            payload[0], payload[1],
229        ])))
230    }
231
232    /// Retrieves the persistent action mapped to `cid` on `host`.
233    pub async fn get_persistent_action(
234        &self,
235        cid: ControlId,
236        host: HostIndex,
237    ) -> Result<PersistentAction, Hidpp20Error> {
238        let [cid_hi, cid_lo] = u16::from(cid).to_be_bytes();
239        let payload = self
240            .endpoint
241            .call(3, [cid_hi, cid_lo, u8::from(host)])
242            .await?
243            .extend_payload();
244        PersistentAction::from_payload(&payload)
245    }
246
247    /// Persistently remaps `cid` on `host` to `config`.
248    ///
249    /// This writes to the device's non-volatile memory and changes the control's
250    /// behaviour until reset (see [`Self::reset_persistent_action`]).
251    pub async fn set_persistent_action(
252        &self,
253        cid: ControlId,
254        host: HostIndex,
255        config: PersistentActionConfig,
256    ) -> Result<(), Hidpp20Error> {
257        let [cid_hi, cid_lo] = u16::from(cid).to_be_bytes();
258        let [value_hi, value_lo] = config.value.to_be_bytes();
259        let mut args = [0; 16];
260        args[..7].copy_from_slice(&[
261            cid_hi,
262            cid_lo,
263            u8::from(host),
264            config.action_id.into(),
265            value_hi,
266            value_lo,
267            config.modifier_mask.bits(),
268        ]);
269        self.endpoint.call_long(4, args).await?;
270        Ok(())
271    }
272
273    /// Resets `cid` on `host` to its factory default action.
274    pub async fn reset_persistent_action(
275        &self,
276        cid: ControlId,
277        host: HostIndex,
278    ) -> Result<(), Hidpp20Error> {
279        let [cid_hi, cid_lo] = u16::from(cid).to_be_bytes();
280        self.endpoint
281            .call(5, [cid_hi, cid_lo, u8::from(host)])
282            .await?;
283        Ok(())
284    }
285
286    /// Resets every control to its factory default for the hosts in `hosts`.
287    pub async fn reset_to_factory_settings(&self, hosts: HostMask) -> Result<(), Hidpp20Error> {
288        self.endpoint.call(6, [hosts.bits(), 0, 0]).await?;
289        Ok(())
290    }
291}