Skip to main content

rmk_types/
modifier.rs

1//! Modifier keys and their operations.
2//!
3//! This module provides efficient handling of keyboard modifier states using
4//! bitfield structures. It supports both left and right variants of all
5//! standard modifiers (Ctrl, Shift, Alt, GUI).
6use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
7
8use bitfield_struct::bitfield;
9use postcard::experimental::max_size::MaxSize;
10
11use crate::keycode::HidKeyCode;
12
13/// The bit representation of the modifier combination.
14#[bitfield(u8, order = Lsb, debug = false)]
15#[derive(MaxSize, Eq, PartialEq)]
16pub struct ModifierCombination {
17    #[bits(1)]
18    pub left_ctrl: bool,
19    #[bits(1)]
20    pub left_shift: bool,
21    #[bits(1)]
22    pub left_alt: bool,
23    #[bits(1)]
24    pub left_gui: bool,
25    #[bits(1)]
26    pub right_ctrl: bool,
27    #[bits(1)]
28    pub right_shift: bool,
29    #[bits(1)]
30    pub right_alt: bool,
31    #[bits(1)]
32    pub right_gui: bool,
33}
34
35crate::impl_debug_list!(ModifierCombination, |self| [
36    (self.left_ctrl(), HidKeyCode::LCtrl),
37    (self.left_shift(), HidKeyCode::LShift),
38    (self.left_alt(), HidKeyCode::LAlt),
39    (self.left_gui(), HidKeyCode::LGui),
40    (self.right_ctrl(), HidKeyCode::RCtrl),
41    (self.right_shift(), HidKeyCode::RShift),
42    (self.right_alt(), HidKeyCode::RAlt),
43    (self.right_gui(), HidKeyCode::RGui),
44]
45.into_iter()
46.filter_map(|(state, label)| state.then_some(label)));
47
48// u8 on the wire (postcard); named bools on serde-wasm-bindgen / serde_json (TS).
49crate::bitfield_named_serde!(ModifierCombination, "ModifierCombination", {
50    left_ctrl = with_left_ctrl,
51    left_shift = with_left_shift,
52    left_alt = with_left_alt,
53    left_gui = with_left_gui,
54    right_ctrl = with_right_ctrl,
55    right_shift = with_right_shift,
56    right_alt = with_right_alt,
57    right_gui = with_right_gui,
58});
59
60impl BitOr for ModifierCombination {
61    type Output = Self;
62
63    fn bitor(self, rhs: Self) -> Self::Output {
64        Self::from_bits(self.into_bits() | rhs.into_bits())
65    }
66}
67
68impl BitAnd for ModifierCombination {
69    type Output = Self;
70
71    fn bitand(self, rhs: Self) -> Self::Output {
72        Self::from_bits(self.into_bits() & rhs.into_bits())
73    }
74}
75
76impl BitAndAssign for ModifierCombination {
77    fn bitand_assign(&mut self, rhs: Self) {
78        *self = *self & rhs;
79    }
80}
81
82impl BitOrAssign for ModifierCombination {
83    fn bitor_assign(&mut self, rhs: Self) {
84        *self = *self | rhs;
85    }
86}
87
88impl Not for ModifierCombination {
89    type Output = Self;
90
91    fn not(self) -> Self::Output {
92        Self::from_bits(!self.into_bits())
93    }
94}
95
96impl ModifierCombination {
97    pub const LCTRL: Self = Self::new().with_left_ctrl(true);
98    pub const LSHIFT: Self = Self::new().with_left_shift(true);
99    pub const LALT: Self = Self::new().with_left_alt(true);
100    pub const LGUI: Self = Self::new().with_left_gui(true);
101
102    pub const RCTRL: Self = Self::new().with_right_ctrl(true);
103    pub const RSHIFT: Self = Self::new().with_right_shift(true);
104    pub const RALT: Self = Self::new().with_right_alt(true);
105    pub const RGUI: Self = Self::new().with_right_gui(true);
106
107    pub const fn new_from(right: bool, gui: bool, alt: bool, shift: bool, ctrl: bool) -> Self {
108        if right {
109            ModifierCombination::new()
110                .with_right_gui(gui)
111                .with_right_alt(alt)
112                .with_right_shift(shift)
113                .with_right_ctrl(ctrl)
114        } else {
115            ModifierCombination::new()
116                .with_left_gui(gui)
117                .with_left_alt(alt)
118                .with_left_shift(shift)
119                .with_left_ctrl(ctrl)
120        }
121    }
122
123    #[allow(clippy::too_many_arguments)]
124    pub const fn new_from_vals(
125        left_ctrl: bool,
126        left_shift: bool,
127        left_alt: bool,
128        left_gui: bool,
129        right_ctrl: bool,
130        right_shift: bool,
131        right_alt: bool,
132        right_gui: bool,
133    ) -> Self {
134        ModifierCombination::new()
135            .with_left_ctrl(left_ctrl)
136            .with_left_shift(left_shift)
137            .with_left_alt(left_alt)
138            .with_left_gui(left_gui)
139            .with_right_ctrl(right_ctrl)
140            .with_right_shift(right_shift)
141            .with_right_alt(right_alt)
142            .with_right_gui(right_gui)
143    }
144
145    /// Convert current modifier into packed bits:
146    ///
147    /// | bit4 | bit3 | bit2 | bit1 | bit0 |
148    /// | --- | --- | --- | --- | --- |
149    /// | L/R | GUI | ALT |SHIFT| CTRL|
150    ///
151    /// WARN: Since the packed version cannot represent the state that BOTH left and right modifier is present,
152    /// the left side has higher priority
153    pub const fn into_packed_bits(self) -> u8 {
154        let bits = self.into_bits();
155        if bits == 0 {
156            return 0;
157        }
158        let left_bits = bits & 0x0F; // Extract left side modifiers (bits 0-3)
159        let right_bits = bits >> 4; // Extract right side modifiers (bits 4-7)
160
161        // If left side has any modifiers, use left; otherwise use right with bit 4 set
162        if left_bits != 0 {
163            left_bits
164        } else {
165            right_bits | 0x10 // Set bit 4 to indicate right side
166        }
167    }
168
169    /// Convert packed bits back into ModifierCombination:
170    ///
171    /// | bit4 | bit3 | bit2 | bit1 | bit0 |
172    /// | --- | --- | --- | --- | --- |
173    /// | L/R | GUI | ALT |SHIFT| CTRL|
174    ///
175    /// If bit4 is 0, modifiers are applied to left side, otherwise right side
176    pub const fn from_packed_bits(bits: u8) -> Self {
177        let modifier_bits = bits & 0x0F; // Extract modifier bits (0-3)
178        let is_right = (bits & 0x10) != 0; // Check if bit 4 is set (right side)
179
180        if is_right {
181            Self::from_bits(modifier_bits << 4) // Shift to right side position
182        } else {
183            Self::from_bits(modifier_bits) // Use as left side
184        }
185    }
186}