Skip to main content

zmk_studio_api/
hid_usage.rs

1use std::fmt;
2
3use crate::keycode::Keycode;
4
5pub const HID_USAGE_KEYBOARD: u16 = 0x07;
6
7pub const MOD_LCTL: u8 = 0x01;
8pub const MOD_LSFT: u8 = 0x02;
9pub const MOD_LALT: u8 = 0x04;
10pub const MOD_LGUI: u8 = 0x08;
11pub const MOD_RCTL: u8 = 0x10;
12pub const MOD_RSFT: u8 = 0x20;
13pub const MOD_RALT: u8 = 0x40;
14pub const MOD_RGUI: u8 = 0x80;
15
16/// Lossless decoded ZMK HID usage value (base usage + modifiers).
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct HidUsage {
19    page: u16,
20    id: u16,
21    modifiers: u8,
22}
23
24impl HidUsage {
25    /// Decode from ZMK's encoded usage format.
26    ///
27    /// ZMK encodes as:
28    /// - bits 31:24: modifiers
29    /// - bits 23:16: usage page
30    /// - bits 15:00: usage id
31    ///
32    /// If page is 0, ZMK treats it as keyboard page (`0x07`).
33    pub fn from_encoded(encoded: u32) -> Self {
34        let mut page = ((encoded >> 16) & 0xFF) as u16;
35        if page == 0 {
36            page = HID_USAGE_KEYBOARD;
37        }
38
39        Self {
40            page,
41            id: (encoded & 0xFFFF) as u16,
42            modifiers: (encoded >> 24) as u8,
43        }
44    }
45
46    pub fn from_parts(page: u16, id: u16, modifiers: u8) -> Self {
47        Self {
48            page,
49            id,
50            modifiers,
51        }
52    }
53
54    pub fn to_hid_usage(self) -> u32 {
55        ((self.modifiers as u32) << 24) | ((self.page as u32) << 16) | self.id as u32
56    }
57
58    pub fn page(self) -> u16 {
59        self.page
60    }
61
62    pub fn id(self) -> u16 {
63        self.id
64    }
65
66    pub fn modifiers(self) -> u8 {
67        self.modifiers
68    }
69
70    pub fn base(self) -> Self {
71        Self {
72            page: self.page,
73            id: self.id,
74            modifiers: 0,
75        }
76    }
77
78    pub fn known_keycode(self) -> Option<Keycode> {
79        Keycode::from_hid_usage(self.to_hid_usage())
80    }
81
82    pub fn known_base_keycode(self) -> Option<Keycode> {
83        Keycode::from_hid_usage(self.base().to_hid_usage())
84    }
85
86    pub fn modifier_labels(self) -> Vec<&'static str> {
87        let mut labels = Vec::new();
88        let mods = self.modifiers;
89        if mods & MOD_LCTL != 0 {
90            labels.push("LCTL");
91        }
92        if mods & MOD_LSFT != 0 {
93            labels.push("LSFT");
94        }
95        if mods & MOD_LALT != 0 {
96            labels.push("LALT");
97        }
98        if mods & MOD_LGUI != 0 {
99            labels.push("LGUI");
100        }
101        if mods & MOD_RCTL != 0 {
102            labels.push("RCTL");
103        }
104        if mods & MOD_RSFT != 0 {
105            labels.push("RSFT");
106        }
107        if mods & MOD_RALT != 0 {
108            labels.push("RALT");
109        }
110        if mods & MOD_RGUI != 0 {
111            labels.push("RGUI");
112        }
113        labels
114    }
115}
116
117impl fmt::Display for HidUsage {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        if let Some(keycode) = self.known_keycode() {
120            return f.write_str(keycode.to_name());
121        }
122
123        write!(
124            f,
125            "0x{:02X}{:02X}{:02X}{:02X}",
126            self.modifiers,
127            self.page,
128            (self.id >> 8) as u8,
129            self.id as u8
130        )
131    }
132}