Skip to main content

smart_keymap_core/key/
key_lock.rs

1//! Key Lock: hold the next key until it is pressed again.
2//!
3//! After arming with `Key::KeyLock`, the next resolved keyboard key output is
4//! kept held via a virtual key press. Pressing a key that produces the same
5//! output again releases the lock.
6//!
7//! Only one output is locked at a time (QMK-style). Arming again and locking a
8//! different key replaces the previous lock. Simultaneous multi-lock can be
9//! added later if needed.
10
11use core::fmt::Debug;
12use core::marker::PhantomData;
13
14use serde::Deserialize;
15
16use crate::input;
17use crate::key;
18use crate::keymap;
19
20/// Reference for a key lock key.
21#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
22pub struct Ref(pub Key);
23
24/// Whether a resolved [key::KeyOutput] can be locked.
25///
26/// Only non-empty keyboard usage outputs are lockable.
27pub fn is_lockable(key_output: &key::KeyOutput) -> bool {
28    *key_output != key::KeyOutput::NO_OUTPUT
29        && matches!(key_output.key_code(), key::KeyUsage::Keyboard(_))
30}
31
32/// Key awaiting physical release before its virtual lock is applied.
33#[derive(Debug, Clone, Copy, PartialEq)]
34struct PendingLock {
35    keymap_index: u16,
36    key_output: key::KeyOutput,
37}
38
39/// Key Lock context: watching arm and at most one active virtual lock.
40#[derive(Debug, Clone, Copy, PartialEq)]
41pub struct Context {
42    watching: bool,
43    /// Next key to lock once its physical key is released (avoids duplicate HID codes).
44    pending_lock: Option<PendingLock>,
45    locked: Option<key::KeyOutput>,
46}
47
48impl Default for Context {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl Context {
55    /// Constructs a new [Context].
56    pub const fn new() -> Self {
57        Context {
58            watching: false,
59            pending_lock: None,
60            locked: None,
61        }
62    }
63
64    /// Clear watching and the locked key (does not emit virtual releases).
65    pub fn reset(&mut self) {
66        *self = Self::new();
67    }
68
69    /// Whether key lock is watching for the next key to lock.
70    pub fn is_watching(&self) -> bool {
71        self.watching
72    }
73
74    /// Whether `key_output` is currently locked.
75    pub fn is_locked(&self, key_output: &key::KeyOutput) -> bool {
76        self.locked.as_ref() == Some(key_output)
77    }
78
79    /// Apply a pending lock after physical release.
80    ///
81    /// Replaces any previously locked output (releasing it virtually first).
82    fn commit_lock(&mut self, key_output: key::KeyOutput) -> key::KeyEvents<Event> {
83        match self.locked {
84            Some(existing) if existing == key_output => key::KeyEvents::no_events(),
85            Some(existing) => {
86                self.locked = Some(key_output);
87                let mut pke =
88                    key::KeyEvents::event(key::Event::Input(input::Event::VirtualKeyRelease {
89                        key_output: existing,
90                    }));
91                pke.add_event(key::Event::Input(input::Event::VirtualKeyPress {
92                    key_output,
93                }));
94                pke
95            }
96            None => {
97                self.locked = Some(key_output);
98                key::KeyEvents::event(key::Event::Input(input::Event::VirtualKeyPress {
99                    key_output,
100                }))
101            }
102        }
103    }
104
105    /// Clear the lock if it matches `key_output`; returns whether it was unlocked.
106    fn unlock_if_matches(&mut self, key_output: &key::KeyOutput) -> bool {
107        match self.locked {
108            Some(locked) if locked == *key_output => {
109                self.locked = None;
110                true
111            }
112            _ => false,
113        }
114    }
115
116    fn handle_event(&mut self, event: key::Event<Event>) -> key::KeyEvents<Event> {
117        match event {
118            key::Event::Key {
119                key_event: Event::ToggleWatching,
120                ..
121            } => {
122                self.watching = !self.watching;
123                // Cancelling watch also drops a pending lock that was never committed.
124                if !self.watching {
125                    self.pending_lock = None;
126                }
127                key::KeyEvents::no_events()
128            }
129            key::Event::Keymap(keymap::KeymapEvent::ResolvedKeyOutput {
130                keymap_index,
131                key_output,
132            }) => match (self.watching, is_lockable(&key_output)) {
133                (true, true) => {
134                    // Defer virtual press until physical release so the HID
135                    // report does not carry a duplicate key code.
136                    self.watching = false;
137                    self.pending_lock = Some(PendingLock {
138                        keymap_index,
139                        key_output,
140                    });
141                    key::KeyEvents::no_events()
142                }
143                (true, false) => {
144                    // Non-lockable keys cancel watching without locking.
145                    self.watching = false;
146                    key::KeyEvents::no_events()
147                }
148                (false, true) if self.unlock_if_matches(&key_output) => {
149                    key::KeyEvents::event(key::Event::Input(input::Event::VirtualKeyRelease {
150                        key_output,
151                    }))
152                }
153                (false, _) => key::KeyEvents::no_events(),
154            },
155            key::Event::Input(input::Event::Release { keymap_index }) => match self.pending_lock {
156                Some(PendingLock {
157                    keymap_index: pending_index,
158                    key_output,
159                }) if pending_index == keymap_index => {
160                    self.pending_lock = None;
161                    self.commit_lock(key_output)
162                }
163                _ => key::KeyEvents::no_events(),
164            },
165            _ => key::KeyEvents::no_events(),
166        }
167    }
168}
169
170impl key::Context for Context {
171    type Event = Event;
172
173    fn handle_event(&mut self, event: key::Event<Self::Event>) -> key::KeyEvents<Self::Event> {
174        self.handle_event(event)
175    }
176
177    fn reset(&mut self) {
178        Context::reset(self);
179    }
180}
181
182/// Key Lock events.
183#[derive(Debug, Clone, Copy, PartialEq)]
184pub enum Event {
185    /// Toggle whether the next resolved key should be locked.
186    ToggleWatching,
187}
188
189/// A key that arms/disarms key lock watching.
190#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
191pub enum Key {
192    /// Enter/exit watching mode for the next key to lock.
193    KeyLock,
194}
195
196impl Key {
197    /// Constructs a [Key::KeyLock].
198    pub const fn new() -> Self {
199        Key::KeyLock
200    }
201
202    /// Constructs pressed-key events for this key.
203    pub fn new_pressed_key(&self, keymap_index: u16) -> key::KeyEvents<Event> {
204        match self {
205            Key::KeyLock => {
206                key::KeyEvents::event(key::Event::key_event(keymap_index, Event::ToggleWatching))
207            }
208        }
209    }
210}
211
212impl Default for Key {
213    fn default() -> Self {
214        Self::new()
215    }
216}
217
218/// Pending key state type for key lock keys. (No pending state.)
219#[derive(Debug, Clone, Copy, PartialEq)]
220pub struct PendingKeyState;
221
222/// Key state used by [System]. (No per-key state; behaviour is on [Context].)
223#[derive(Debug, Clone, Copy, PartialEq)]
224pub struct KeyState;
225
226/// The [key::System] implementation for key lock keys.
227#[derive(Debug, Clone, Copy, PartialEq)]
228pub struct System<R>(PhantomData<R>);
229
230impl<R> System<R> {
231    /// Constructs a new [System].
232    pub const fn new() -> Self {
233        Self(PhantomData)
234    }
235}
236
237impl<R> Default for System<R> {
238    fn default() -> Self {
239        Self::new()
240    }
241}
242
243impl<R: Debug> key::System<R> for System<R> {
244    type Ref = Ref;
245    type Context = Context;
246    type Event = Event;
247    type PendingKeyState = PendingKeyState;
248    type KeyState = KeyState;
249
250    fn new_pressed_key(
251        &self,
252        keymap_index: u16,
253        _context: &Self::Context,
254        Ref(key): Ref,
255    ) -> (
256        key::PressedKeyResult<R, Self::PendingKeyState, Self::KeyState>,
257        key::KeyEvents<Self::Event>,
258    ) {
259        let pke = key.new_pressed_key(keymap_index);
260        let pkr = key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp);
261        (pkr, pke.into_events())
262    }
263
264    fn update_pending_state(
265        &self,
266        _pending_state: &mut Self::PendingKeyState,
267        _keymap_index: u16,
268        _context: &Self::Context,
269        _key_ref: Ref,
270        _event: key::Event<Self::Event>,
271    ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Self::Event>) {
272        panic!()
273    }
274
275    fn update_state(
276        &self,
277        _key_state: &mut Self::KeyState,
278        _ref: &Self::Ref,
279        _context: &Self::Context,
280        _keymap_index: u16,
281        _event: key::Event<Self::Event>,
282    ) -> key::KeyEvents<Self::Event> {
283        panic!()
284    }
285
286    fn key_output(
287        &self,
288        _key_ref: &Self::Ref,
289        _key_state: &Self::KeyState,
290    ) -> Option<key::KeyOutput> {
291        panic!()
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    #[test]
300    fn test_sizeof_ref() {
301        assert_eq!(0, core::mem::size_of::<Ref>());
302    }
303
304    #[test]
305    fn test_sizeof_event() {
306        assert_eq!(0, core::mem::size_of::<Event>());
307    }
308
309    #[test]
310    fn is_lockable_accepts_keyboard_keycode() {
311        assert!(is_lockable(&key::KeyOutput::from_key_code(0x04)));
312    }
313
314    #[test]
315    fn is_lockable_accepts_modifier() {
316        assert!(is_lockable(&key::KeyOutput::from_key_code(0xE1)));
317    }
318
319    #[test]
320    fn is_lockable_rejects_no_output() {
321        assert!(!is_lockable(&key::KeyOutput::NO_OUTPUT));
322    }
323
324    #[test]
325    fn is_lockable_rejects_consumer() {
326        assert!(!is_lockable(&key::KeyOutput::from_consumer_code(1)));
327    }
328
329    #[test]
330    fn toggle_watching_arms() {
331        let mut ctx = Context::new();
332        let _ =
333            key::Context::handle_event(&mut ctx, key::Event::key_event(0, Event::ToggleWatching));
334        assert!(ctx.is_watching());
335    }
336
337    #[test]
338    fn toggle_watching_twice_disarms() {
339        let mut ctx = Context::new();
340        let _ =
341            key::Context::handle_event(&mut ctx, key::Event::key_event(0, Event::ToggleWatching));
342        let _ =
343            key::Context::handle_event(&mut ctx, key::Event::key_event(0, Event::ToggleWatching));
344        assert!(!ctx.is_watching());
345    }
346
347    #[test]
348    fn non_lockable_cancels_watching() {
349        let mut ctx = Context::new();
350        let _ =
351            key::Context::handle_event(&mut ctx, key::Event::key_event(0, Event::ToggleWatching));
352        let _ = key::Context::handle_event(
353            &mut ctx,
354            key::Event::Keymap(keymap::KeymapEvent::ResolvedKeyOutput {
355                keymap_index: 1,
356                key_output: key::KeyOutput::from_consumer_code(0x01),
357            }),
358        );
359        assert!(!ctx.is_watching());
360    }
361}