Skip to main content

smart_keymap_core/key/
mod_conditioned.rs

1//! Mod-conditioned keys: dual binding gated on held modifiers,
2//!  with optional report-level suppression of those modifiers
3//! (ZMK mod-morph / QMK key-override style).
4//!
5//! At press time, if any of [Key::trigger_mods](crate::key::mod_conditioned::Key::trigger_mods)
6//!  is held (from
7//!  [KeymapContext::pressed_modifiers](crate::keymap::KeymapContext::pressed_modifiers)),
8//!  the key resolves to [Key::morphed](crate::key::mod_conditioned::Key::morphed);
9//!  otherwise to [Key::base](crate::key::mod_conditioned::Key::base).
10//! On the morph path, trigger mods except
11//!  [Key::keep_mods](crate::key::mod_conditioned::Key::keep_mods) are suppressed
12//!  from the HID report while the key is held.
13
14use core::fmt::Debug;
15use core::ops::Index;
16
17use serde::Deserialize;
18
19use crate::input;
20use crate::key;
21use crate::keymap;
22
23/// Maximum simultaneous morph suppress entries.
24const MAX_ACTIVE_SUPPRESS: usize = 4;
25
26/// Reference for a mod-conditioned key.
27#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
28pub struct Ref(pub u8);
29
30/// A key that picks a nested binding based on held modifiers.
31#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
32pub struct Key<R> {
33    /// Binding used when none of [`Self::trigger_mods`] are held.
34    pub base: R,
35    /// Binding used when any of [`Self::trigger_mods`] is held.
36    pub morphed: R,
37    /// Modifiers that activate the morphed binding (any-of).
38    #[serde(rename = "mods")]
39    pub trigger_mods: key::KeyboardModifiers,
40    /// Subset of [`Self::trigger_mods`] to keep in the report on the morph path.
41    #[serde(default = "default_keep_mods")]
42    pub keep_mods: key::KeyboardModifiers,
43}
44
45fn default_keep_mods() -> key::KeyboardModifiers {
46    key::KeyboardModifiers::NONE
47}
48
49impl<R> Key<R> {
50    /// Constructs a mod-conditioned key.
51    pub const fn new(
52        base: R,
53        morphed: R,
54        trigger_mods: key::KeyboardModifiers,
55        keep_mods: key::KeyboardModifiers,
56    ) -> Self {
57        Self {
58            base,
59            morphed,
60            trigger_mods,
61            keep_mods,
62        }
63    }
64
65    /// Mods to suppress on the morph path (`trigger \ keep`).
66    pub const fn suppress_mods(&self) -> key::KeyboardModifiers {
67        self.trigger_mods.difference(&self.keep_mods)
68    }
69}
70
71/// Events for mod-conditioned keys.
72#[derive(Debug, Clone, Copy, PartialEq)]
73pub enum Event {
74    /// Morph path taken: suppress these mods for this keymap index while held.
75    ActivateSuppress {
76        /// Physical keymap index of the conditioned key.
77        keymap_index: u16,
78        /// Modifier bits to hide from the host report.
79        mask: key::KeyboardModifiers,
80    },
81}
82
83/// Active suppress entry for one held morph.
84#[derive(Debug, Clone, Copy, PartialEq)]
85struct ActiveSuppress {
86    keymap_index: u16,
87    mask: key::KeyboardModifiers,
88}
89
90/// Context for mod-conditioned keys.
91#[derive(Debug, Clone, Copy, PartialEq)]
92pub struct Context {
93    pressed_modifiers: key::KeyboardModifiers,
94    active: [ActiveSuppress; MAX_ACTIVE_SUPPRESS],
95    active_count: u8,
96}
97
98impl Default for Context {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104impl Context {
105    /// Constructs a new [Context].
106    pub const fn new() -> Self {
107        Context {
108            pressed_modifiers: key::KeyboardModifiers::NONE,
109            active: [ActiveSuppress {
110                keymap_index: 0,
111                mask: key::KeyboardModifiers::NONE,
112            }; MAX_ACTIVE_SUPPRESS],
113            active_count: 0,
114        }
115    }
116
117    /// Clear runtime state.
118    pub fn reset(&mut self) {
119        *self = Self::new();
120    }
121
122    /// Currently held keyboard modifiers (engine snapshot).
123    pub const fn pressed_modifiers(&self) -> key::KeyboardModifiers {
124        self.pressed_modifiers
125    }
126
127    /// Union of active suppress masks for the HID report.
128    pub fn suppressed_modifiers(&self) -> key::KeyboardModifiers {
129        self.active[..self.active_count as usize]
130            .iter()
131            .fold(key::KeyboardModifiers::NONE, |acc, e| acc.union(&e.mask))
132    }
133
134    /// Updates from the engine keymap context snapshot.
135    pub fn update_keymap_context(
136        &mut self,
137        keymap::KeymapContext {
138            pressed_modifiers, ..
139        }: &keymap::KeymapContext,
140    ) {
141        self.pressed_modifiers = *pressed_modifiers;
142    }
143
144    fn push_suppress(&mut self, keymap_index: u16, mask: key::KeyboardModifiers) {
145        if mask == key::KeyboardModifiers::NONE {
146            return;
147        }
148        // Replace existing entry for the same index, if any.
149        if let Some(slot) = self.active[..self.active_count as usize]
150            .iter_mut()
151            .find(|e| e.keymap_index == keymap_index)
152        {
153            slot.mask = mask;
154            return;
155        }
156        if (self.active_count as usize) < MAX_ACTIVE_SUPPRESS {
157            self.active[self.active_count as usize] = ActiveSuppress { keymap_index, mask };
158            self.active_count += 1;
159        }
160    }
161
162    fn clear_suppress(&mut self, keymap_index: u16) {
163        let count = self.active_count as usize;
164        if let Some(i) = self.active[..count]
165            .iter()
166            .position(|e| e.keymap_index == keymap_index)
167        {
168            for j in i..(count - 1) {
169                self.active[j] = self.active[j + 1];
170            }
171            self.active_count -= 1;
172        }
173    }
174
175    fn handle_event(&mut self, event: key::Event<Event>) -> key::KeyEvents<Event> {
176        match event {
177            key::Event::Key {
178                key_event: Event::ActivateSuppress { keymap_index, mask },
179                ..
180            } => {
181                self.push_suppress(keymap_index, mask);
182                key::KeyEvents::no_events()
183            }
184            key::Event::Input(input::Event::Release { keymap_index }) => {
185                self.clear_suppress(keymap_index);
186                key::KeyEvents::no_events()
187            }
188            _ => key::KeyEvents::no_events(),
189        }
190    }
191}
192
193impl key::Context for Context {
194    type Event = Event;
195
196    fn handle_event(&mut self, event: key::Event<Self::Event>) -> key::KeyEvents<Self::Event> {
197        self.handle_event(event)
198    }
199
200    fn reset(&mut self) {
201        Context::reset(self);
202    }
203}
204
205/// Pending key state (none — resolves immediately).
206#[derive(Debug, Clone, Copy, PartialEq)]
207pub struct PendingKeyState;
208
209/// Pressed key state (none — always `NewPressedKey`).
210#[derive(Debug, Clone, Copy, PartialEq)]
211pub struct KeyState;
212
213/// The [key::System] implementation for mod-conditioned keys.
214#[derive(Debug, Clone, Copy, PartialEq)]
215pub struct System<R, Keys: Index<usize, Output = Key<R>>> {
216    keys: Keys,
217}
218
219impl<R, Keys: Index<usize, Output = Key<R>>> System<R, Keys> {
220    /// Constructs a new [System] with the given key data.
221    pub const fn new(keys: Keys) -> Self {
222        Self { keys }
223    }
224}
225
226impl<R: Copy + Debug, Keys: Debug + Index<usize, Output = Key<R>>> key::System<R>
227    for System<R, Keys>
228{
229    type Ref = Ref;
230    type Context = Context;
231    type Event = Event;
232    type PendingKeyState = PendingKeyState;
233    type KeyState = KeyState;
234
235    fn new_pressed_key(
236        &self,
237        keymap_index: u16,
238        context: &Self::Context,
239        Ref(key_index): Ref,
240    ) -> (
241        key::PressedKeyResult<R, Self::PendingKeyState, Self::KeyState>,
242        key::KeyEvents<Self::Event>,
243    ) {
244        let key = &self.keys[key_index as usize];
245        let morph = context.pressed_modifiers().has_modifiers(&key.trigger_mods);
246
247        if morph {
248            let suppress = key.suppress_mods();
249            let pke = if suppress != key::KeyboardModifiers::NONE {
250                key::KeyEvents::event(key::Event::key_event(
251                    keymap_index,
252                    Event::ActivateSuppress {
253                        keymap_index,
254                        mask: suppress,
255                    },
256                ))
257            } else {
258                key::KeyEvents::no_events()
259            };
260            (
261                key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(key.morphed)),
262                pke,
263            )
264        } else {
265            (
266                key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(key.base)),
267                key::KeyEvents::no_events(),
268            )
269        }
270    }
271
272    fn update_pending_state(
273        &self,
274        _pending_state: &mut Self::PendingKeyState,
275        _keymap_index: u16,
276        _context: &Self::Context,
277        _key_ref: Ref,
278        _event: key::Event<Self::Event>,
279    ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Self::Event>) {
280        panic!()
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287    use crate::key::System as _;
288
289    #[test]
290    fn test_sizeof_ref() {
291        assert_eq!(1, core::mem::size_of::<Ref>());
292    }
293
294    #[test]
295    fn morph_when_trigger_mod_held() {
296        // Assemble
297        let keys = [Key::new(
298            0u8, // base
299            1u8, // morphed
300            key::KeyboardModifiers::LEFT_SHIFT,
301            key::KeyboardModifiers::NONE,
302        )];
303        let system = System::new(keys);
304        let mut ctx = Context::new();
305        ctx.pressed_modifiers = key::KeyboardModifiers::LEFT_SHIFT;
306
307        // Act
308        let (pkr, pke) = system.new_pressed_key(0, &ctx, Ref(0));
309
310        // Assert: morphed binding, and ActivateSuppress for the trigger mod
311        assert_eq!(
312            key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(1u8)),
313            pkr
314        );
315        assert_eq!(
316            key::KeyEvents::event(key::Event::key_event(
317                0,
318                Event::ActivateSuppress {
319                    keymap_index: 0,
320                    mask: key::KeyboardModifiers::LEFT_SHIFT,
321                },
322            )),
323            pke
324        );
325    }
326
327    #[test]
328    fn base_when_no_trigger_mod() {
329        // Assemble
330        let keys = [Key::new(
331            0u8,
332            1u8,
333            key::KeyboardModifiers::LEFT_SHIFT,
334            key::KeyboardModifiers::NONE,
335        )];
336        let system = System::new(keys);
337        let ctx = Context::new();
338
339        // Act
340        let (pkr, pke) = system.new_pressed_key(0, &ctx, Ref(0));
341
342        // Assert: base binding, no suppress event
343        assert_eq!(
344            key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(0u8)),
345            pkr
346        );
347        assert_eq!(key::KeyEvents::no_events(), pke);
348    }
349
350    #[test]
351    fn keep_mods_reduces_suppress_mask() {
352        let key = Key::new(
353            0u8,
354            1u8,
355            key::KeyboardModifiers::LEFT_SHIFT.union(&key::KeyboardModifiers::RIGHT_SHIFT),
356            key::KeyboardModifiers::RIGHT_SHIFT,
357        );
358        assert_eq!(key::KeyboardModifiers::LEFT_SHIFT, key.suppress_mods());
359    }
360
361    #[test]
362    fn morph_with_keep_mods_emits_reduced_suppress() {
363        // Assemble: trigger LShift|RShift, keep RShift → suppress only LShift
364        let keys = [Key::new(
365            0u8,
366            1u8,
367            key::KeyboardModifiers::LEFT_SHIFT.union(&key::KeyboardModifiers::RIGHT_SHIFT),
368            key::KeyboardModifiers::RIGHT_SHIFT,
369        )];
370        let system = System::new(keys);
371        let mut ctx = Context::new();
372        // Either trigger mod is enough to morph; hold LeftShift
373        ctx.pressed_modifiers = key::KeyboardModifiers::LEFT_SHIFT;
374
375        // Act
376        let (pkr, pke) = system.new_pressed_key(0, &ctx, Ref(0));
377
378        // Assert: morphed, ActivateSuppress only for the non-kept trigger mod
379        assert_eq!(
380            key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(1u8)),
381            pkr
382        );
383        assert_eq!(
384            key::KeyEvents::event(key::Event::key_event(
385                0,
386                Event::ActivateSuppress {
387                    keymap_index: 0,
388                    mask: key::KeyboardModifiers::LEFT_SHIFT,
389                },
390            )),
391            pke
392        );
393    }
394
395    #[test]
396    fn morph_with_all_keep_mods_emits_no_suppress() {
397        // Assemble: keep_mods == trigger_mods → morph with no report suppress
398        let keys = [Key::new(
399            0u8,
400            1u8,
401            key::KeyboardModifiers::LEFT_SHIFT,
402            key::KeyboardModifiers::LEFT_SHIFT,
403        )];
404        let system = System::new(keys);
405        let mut ctx = Context::new();
406        ctx.pressed_modifiers = key::KeyboardModifiers::LEFT_SHIFT;
407
408        // Act
409        let (pkr, pke) = system.new_pressed_key(0, &ctx, Ref(0));
410
411        // Assert: still morphed, but no ActivateSuppress event
412        assert_eq!(
413            key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(1u8)),
414            pkr
415        );
416        assert_eq!(key::KeyEvents::no_events(), pke);
417    }
418
419    #[test]
420    fn context_suppress_clears_on_release() {
421        // Assemble
422        let mut ctx = Context::new();
423
424        // Act: activate suppress for keymap index 3
425        let _ = key::Context::handle_event(
426            &mut ctx,
427            key::Event::key_event(
428                3,
429                Event::ActivateSuppress {
430                    keymap_index: 3,
431                    mask: key::KeyboardModifiers::LEFT_SHIFT,
432                },
433            ),
434        );
435
436        // Assert: suppress active while held
437        assert_eq!(
438            key::KeyboardModifiers::LEFT_SHIFT,
439            ctx.suppressed_modifiers()
440        );
441
442        // Act: release the conditioned key
443        let _ = key::Context::handle_event(
444            &mut ctx,
445            key::Event::Input(input::Event::Release { keymap_index: 3 }),
446        );
447
448        // Assert: suppress cleared
449        assert_eq!(key::KeyboardModifiers::NONE, ctx.suppressed_modifiers());
450    }
451}