Skip to main content

rmk_types/
morse.rs

1//! Morse key types shared between firmware and protocol layers.
2//!
3//! This module contains all morse-related types:
4//! - [`MorseMode`] / [`MorseProfile`] — timing and behavior configuration
5//! - [`MorsePattern`] — tap/hold pattern encoding (up to 15 steps in a u16)
6//! - [`Morse`] — full morse key definition (profile + pattern→action map)
7
8use heapless::LinearMap;
9use postcard::experimental::max_size::MaxSize;
10use serde::{Deserialize, Serialize};
11
12use crate::action::Action;
13use crate::constants::MORSE_SIZE;
14
15// ---------------------------------------------------------------------------
16// MorseMode & MorseProfile — timing/behavior configuration
17// ---------------------------------------------------------------------------
18
19/// Mode for morse key behavior
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
21#[cfg_attr(feature = "defmt", derive(defmt::Format))]
22#[repr(u8)]
23#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
24#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
25pub enum MorseMode {
26    /// Same as QMK's permissive hold: <https://docs.qmk.fm/tap_hold#tap-or-hold-decision-modes>
27    /// When another key is pressed and released during the current morse key is held,
28    /// the hold action of current morse key will be triggered
29    PermissiveHold,
30    /// Trigger hold immediately if any other non-morse key is pressed when the current morse key is held
31    HoldOnOtherPress,
32    /// Normal mode, the decision is made when timeout
33    Normal,
34}
35
36/// Configuration for morse, tap dance and tap-hold.
37/// Manually packed into 64 bits to save RAM.
38///
39/// Bit layout of the inner `u64`:
40/// ```text
41/// 63        46 | 45      | 44           32 | 31  30 | 29       17 | 16  15 | 14  13   | 12       0
42/// reserved     | qt_set  | quick_tap_tm    | mode   | gap_timeout | uni_tap| flow_tap | hold_timeout
43///   (18b)      |  (1b)   |   (13b ms)      |  (2b)  |   (13b ms)  |  (2b)  |   (2b)   |  (13b ms)
44/// ```
45///
46/// - `qt_set` (bit 45): when set, `quick_tap_timeout` is explicitly configured
47///   (even if 0, which means "disabled"). When clear, the field is unset and
48///   callers should fall back to the global default.
49/// - `quick_tap_timeout` (bits 44-32): quick-tap timeout in ms (max 8191).
50/// - `mode` (bits 31-30): `00` = None, `01` = PermissiveHold, `10` = HoldOnOtherPress, `11` = Normal
51/// - `flow_tap` (bits 14, 13): `00`/`01` = None, `10` = Some(false), `11` = Some(true)
52/// - `gap_timeout` (bits 29-17): gap timeout in ms (0 = None, max 8191)
53/// - `uni_tap` (bits 16-15): `00`/`01` = None, `10` = Some(false), `11` = Some(true)
54/// - `hold_timeout` (bits 12-0): hold timeout in ms (0 = None, max 8191)
55#[derive(PartialEq, Eq, Clone, Copy, Debug, MaxSize)]
56#[cfg_attr(feature = "defmt", derive(defmt::Format))]
57pub struct MorseProfile(u64);
58
59const TIMEOUT_MASK: u64 = 0x1FFF;
60const TIMEOUT_MAX_MS: u16 = TIMEOUT_MASK as u16;
61const GAP_TIMEOUT_SHIFT: u32 = 17;
62const HOLD_TIMEOUT_MASK: u64 = TIMEOUT_MASK;
63const GAP_TIMEOUT_MASK: u64 = TIMEOUT_MASK << GAP_TIMEOUT_SHIFT;
64const UNI_TAP_LOW_BIT: u64 = 0x0000_8000;
65const UNI_TAP_HIGH_BIT: u64 = 0x0001_0000;
66const UNI_TAP_MASK: u64 = UNI_TAP_LOW_BIT | UNI_TAP_HIGH_BIT;
67const FLOW_TAP_LOW_BIT: u64 = 0x0000_2000;
68const FLOW_TAP_HIGH_BIT: u64 = 0x0000_4000;
69const FLOW_TAP_MASK: u64 = FLOW_TAP_LOW_BIT | FLOW_TAP_HIGH_BIT;
70const MODE_MASK: u64 = 0xC000_0000;
71const QT_VALUE_MASK: u64 = TIMEOUT_MASK << 32;
72const QT_SET_BIT: u64 = 1 << 45;
73
74const fn encode_timeout_ms(t: u16) -> u64 {
75    if t > TIMEOUT_MAX_MS {
76        TIMEOUT_MAX_MS as u64
77    } else {
78        t as u64
79    }
80}
81
82impl MorseProfile {
83    pub const fn const_default() -> Self {
84        Self(0)
85    }
86
87    /// If the previous key is on the same "hand", the current key will be determined as a tap
88    pub fn unilateral_tap(self) -> Option<bool> {
89        match (self.0 & UNI_TAP_MASK) >> 15 {
90            3 => Some(true),
91            2 => Some(false),
92            _ => None,
93        }
94    }
95
96    pub const fn with_unilateral_tap(self, b: Option<bool>) -> Self {
97        Self(
98            (self.0 & !UNI_TAP_MASK)
99                | match b {
100                    Some(true) => UNI_TAP_MASK,
101                    Some(false) => UNI_TAP_HIGH_BIT,
102                    None => 0,
103                },
104        )
105    }
106
107    /// Per-profile override for flow tap. `None` inherits the global morse setting.
108    pub fn enable_flow_tap(self) -> Option<bool> {
109        match (self.0 & FLOW_TAP_MASK) >> 13 {
110            3 => Some(true),
111            2 => Some(false),
112            _ => None,
113        }
114    }
115
116    pub const fn with_enable_flow_tap(self, b: Option<bool>) -> Self {
117        Self(
118            (self.0 & !FLOW_TAP_MASK)
119                | match b {
120                    Some(true) => FLOW_TAP_MASK,
121                    Some(false) => FLOW_TAP_HIGH_BIT,
122                    None => 0,
123                },
124        )
125    }
126
127    /// The decision mode of the morse/tap-hold key
128    pub fn mode(self) -> Option<MorseMode> {
129        match self.0 & MODE_MASK {
130            MODE_MASK => Some(MorseMode::Normal),
131            0x8000_0000 => Some(MorseMode::HoldOnOtherPress),
132            0x4000_0000 => Some(MorseMode::PermissiveHold),
133            _ => None,
134        }
135    }
136
137    pub const fn with_mode(self, m: Option<MorseMode>) -> Self {
138        Self(
139            (self.0 & !MODE_MASK)
140                | match m {
141                    Some(MorseMode::Normal) => MODE_MASK,
142                    Some(MorseMode::HoldOnOtherPress) => 0x8000_0000,
143                    Some(MorseMode::PermissiveHold) => 0x4000_0000,
144                    None => 0,
145                },
146        )
147    }
148
149    /// If the key is pressed longer than this, it is accepted as `hold` (in milliseconds)
150    pub fn hold_timeout_ms(self) -> Option<u16> {
151        let t = (self.0 & HOLD_TIMEOUT_MASK) as u16;
152        if t == 0 { None } else { Some(t) }
153    }
154
155    pub const fn with_hold_timeout_ms(self, t: Option<u16>) -> Self {
156        if let Some(t) = t {
157            Self((self.0 & !HOLD_TIMEOUT_MASK) | encode_timeout_ms(t))
158        } else {
159            Self(self.0 & !HOLD_TIMEOUT_MASK)
160        }
161    }
162
163    pub const fn set_hold_timeout_ms(&mut self, t: u16) {
164        self.0 = (self.0 & !HOLD_TIMEOUT_MASK) | encode_timeout_ms(t)
165    }
166
167    pub const fn set_gap_timeout_ms(&mut self, t: u16) {
168        self.0 = (self.0 & !GAP_TIMEOUT_MASK) | (encode_timeout_ms(t) << GAP_TIMEOUT_SHIFT)
169    }
170
171    /// The time elapsed from the last release of a key is longer than this, it will break the morse pattern (in milliseconds)
172    pub fn gap_timeout_ms(self) -> Option<u16> {
173        let t = ((self.0 & GAP_TIMEOUT_MASK) >> GAP_TIMEOUT_SHIFT) as u16;
174        if t == 0 { None } else { Some(t) }
175    }
176
177    pub const fn with_gap_timeout_ms(self, t: Option<u16>) -> Self {
178        if let Some(t) = t {
179            Self((self.0 & !GAP_TIMEOUT_MASK) | (encode_timeout_ms(t) << GAP_TIMEOUT_SHIFT))
180        } else {
181            Self(self.0 & !GAP_TIMEOUT_MASK)
182        }
183    }
184
185    pub const fn quick_tap_timeout_ms(self) -> Option<u16> {
186        if self.0 & QT_SET_BIT != 0 {
187            Some(((self.0 >> 32) & TIMEOUT_MASK) as u16)
188        } else {
189            None
190        }
191    }
192
193    pub const fn with_quick_tap_timeout_ms(self, t: Option<u16>) -> Self {
194        if let Some(t) = t {
195            Self((self.0 & !(QT_VALUE_MASK | QT_SET_BIT)) | (encode_timeout_ms(t) << 32) | QT_SET_BIT)
196        } else {
197            Self(self.0 & !(QT_VALUE_MASK | QT_SET_BIT))
198        }
199    }
200
201    pub const fn set_quick_tap_timeout_ms(&mut self, t: u16) {
202        self.0 = (self.0 & !(QT_VALUE_MASK | QT_SET_BIT)) | (encode_timeout_ms(t) << 32) | QT_SET_BIT;
203    }
204
205    pub const fn new(
206        unilateral_tap: Option<bool>,
207        mode: Option<MorseMode>,
208        hold_timeout_ms: Option<u16>,
209        gap_timeout_ms: Option<u16>,
210    ) -> Self {
211        let mut v = 0u64;
212        if let Some(t) = hold_timeout_ms {
213            v = encode_timeout_ms(t);
214        }
215        if let Some(t) = gap_timeout_ms {
216            v |= encode_timeout_ms(t) << GAP_TIMEOUT_SHIFT;
217        }
218        if let Some(b) = unilateral_tap {
219            v |= if b { UNI_TAP_MASK } else { UNI_TAP_HIGH_BIT };
220        }
221        if let Some(m) = mode {
222            v |= match m {
223                MorseMode::Normal => MODE_MASK,
224                MorseMode::HoldOnOtherPress => 0x8000_0000,
225                MorseMode::PermissiveHold => 0x4000_0000,
226            };
227        }
228        MorseProfile(v)
229    }
230}
231
232impl Default for MorseProfile {
233    fn default() -> Self {
234        MorseProfile::const_default()
235    }
236}
237
238impl From<u64> for MorseProfile {
239    fn from(v: u64) -> Self {
240        MorseProfile(v)
241    }
242}
243
244impl From<MorseProfile> for u64 {
245    fn from(val: MorseProfile) -> Self {
246        val.0
247    }
248}
249
250// Wire stays packed; human-readable serializers expose named fields.
251impl Serialize for MorseProfile {
252    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
253        if serializer.is_human_readable() {
254            #[derive(Serialize)]
255            struct Repr {
256                unilateral_tap: Option<bool>,
257                enable_flow_tap: Option<bool>,
258                mode: Option<MorseMode>,
259                hold_timeout_ms: Option<u16>,
260                gap_timeout_ms: Option<u16>,
261                quick_tap_timeout_ms: Option<u16>,
262            }
263            Repr {
264                unilateral_tap: self.unilateral_tap(),
265                enable_flow_tap: self.enable_flow_tap(),
266                mode: self.mode(),
267                hold_timeout_ms: self.hold_timeout_ms(),
268                gap_timeout_ms: self.gap_timeout_ms(),
269                quick_tap_timeout_ms: self.quick_tap_timeout_ms(),
270            }
271            .serialize(serializer)
272        } else {
273            serializer.serialize_u64(self.0)
274        }
275    }
276}
277
278impl<'de> Deserialize<'de> for MorseProfile {
279    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
280        if deserializer.is_human_readable() {
281            #[derive(Deserialize)]
282            struct Repr {
283                unilateral_tap: Option<bool>,
284                enable_flow_tap: Option<bool>,
285                mode: Option<MorseMode>,
286                hold_timeout_ms: Option<u16>,
287                gap_timeout_ms: Option<u16>,
288                quick_tap_timeout_ms: Option<u16>,
289            }
290            let r = Repr::deserialize(deserializer)?;
291            Ok(
292                MorseProfile::new(r.unilateral_tap, r.mode, r.hold_timeout_ms, r.gap_timeout_ms)
293                    .with_enable_flow_tap(r.enable_flow_tap)
294                    .with_quick_tap_timeout_ms(r.quick_tap_timeout_ms),
295            )
296        } else {
297            Ok(MorseProfile(u64::deserialize(deserializer)?))
298        }
299    }
300}
301
302// TS shape mirrors the `Repr` above; `Option<T>` renders as `T | undefined`.
303#[cfg(feature = "wasm")]
304const _: () = {
305    #[::wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
306    const TS_APPEND_CONTENT: &'static str = "export type MorseProfile = { unilateral_tap: boolean | undefined; enable_flow_tap: boolean | undefined; mode: MorseMode | undefined; hold_timeout_ms: number | undefined; gap_timeout_ms: number | undefined; quick_tap_timeout_ms: number | undefined; };";
307};
308crate::wasm_object_abi!(MorseProfile, "MorseProfile");
309
310// ---------------------------------------------------------------------------
311// MorsePattern & Morse — pattern encoding and key definition
312// ---------------------------------------------------------------------------
313
314/// MorsePattern is a sequence of maximum 15 taps or holds that can be encoded into an u16:
315/// 0x1 when empty, then 0 for tap or 1 for hold shifted from the right
316#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
317#[cfg_attr(feature = "defmt", derive(defmt::Format))]
318#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
319#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
320pub struct MorsePattern(u16);
321
322pub const TAP: MorsePattern = MorsePattern(0b10);
323pub const HOLD: MorsePattern = MorsePattern(0b11);
324pub const DOUBLE_TAP: MorsePattern = MorsePattern(0b100);
325pub const HOLD_AFTER_TAP: MorsePattern = MorsePattern(0b101);
326
327impl Default for MorsePattern {
328    fn default() -> Self {
329        MorsePattern(0b1) // 0b1 means empty
330    }
331}
332
333impl MorsePattern {
334    pub fn max_taps() -> usize {
335        15 // 15 taps can be encoded on u16 bits (1 bit used to mark the start position)
336    }
337
338    /// Creates a `MorsePattern` from a raw `u16`.
339    ///
340    /// # Panics (debug only)
341    /// Panics if `value` is 0, which is not a valid encoding
342    /// (the empty pattern is `0b1`).
343    pub fn from_u16(value: u16) -> Self {
344        debug_assert!(value != 0, "MorsePattern 0 is invalid; the empty pattern is 0b1");
345        MorsePattern(value)
346    }
347
348    pub fn to_u16(&self) -> u16 {
349        self.0
350    }
351
352    pub fn is_empty(&self) -> bool {
353        self.0 == 0b1
354    }
355
356    pub fn is_full(&self) -> bool {
357        (self.0 & 0b1000_0000_0000_0000) != 0
358    }
359
360    pub fn pattern_length(&self) -> usize {
361        // leading_zeros() is 16 for 0, which would underflow.
362        // Saturate to 0 for the (invalid) zero case.
363        15usize.saturating_sub(self.0.leading_zeros() as usize)
364    }
365
366    /// Checks if this pattern starts with the given one
367    pub fn starts_with(&self, pattern_start: MorsePattern) -> bool {
368        let n = pattern_start.0.leading_zeros();
369        let m = self.0.leading_zeros();
370        m <= n && (self.0 >> (n - m) == pattern_start.0)
371    }
372
373    /// Returns `true` if the last step in the pattern is a hold.
374    /// Returns `false` for empty patterns.
375    pub fn last_is_hold(&self) -> bool {
376        !self.is_empty() && self.0 & 0b1 == 0b1
377    }
378
379    pub fn followed_by_tap(&self) -> Self {
380        // Shift the bits to the left and set the last bit to 0 (tap)
381        MorsePattern(self.0 << 1)
382    }
383
384    pub fn followed_by_hold(&self) -> Self {
385        // Shift the bits to the left and set the last bit to 1 (hold)
386        MorsePattern((self.0 << 1) | 0b1)
387    }
388
389    /// `true` when the pattern consists only of tap steps (no holds).
390    /// Returns `false` for the empty pattern (encoding `0b1`).
391    pub fn is_all_taps(&self) -> bool {
392        self.0 > 0b1 && self.0 & (self.0 - 1) == 0
393    }
394}
395
396/// Definition of a morse key.
397///
398/// A morse key is a key that behaves differently according to the pattern of a tap/hold sequence.
399/// The maximum number of taps is limited to 15 by the internal u16 representation of MorsePattern.
400/// There is a list of (pattern, corresponding action) pairs for each morse key:
401/// The number of pairs is limited by `MORSE_SIZE` (from `constants.rs`, generated at build time).
402///
403/// Note: `MORSE_SIZE` is a **wire-format** capacity — on firmware it equals
404/// `MAX_PATTERNS_PER_KEY` (from `keyboard.toml`), on host it's a fixed upper bound.
405#[derive(Debug, Clone, Default, Serialize, Deserialize)]
406#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
407#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
408pub struct Morse {
409    /// The profile of this morse key, which defines the timing parameters, etc.
410    /// If some of its fields are filled with None, the global default value will be used.
411    pub profile: MorseProfile,
412    /// The list of pattern -> action pairs, which can be triggered
413    #[serde(with = "morse_actions_serde")]
414    #[cfg_attr(feature = "wasm", tsify(type = "[number, Action][]"))]
415    pub actions: LinearMap<MorsePattern, Action, MORSE_SIZE>,
416}
417
418impl MaxSize for Morse {
419    // The custom serializer in `morse_actions_serde` (below) emits the
420    // `LinearMap` as `Vec<(u16, Action), MORSE_SIZE>` on the wire — keep that
421    // shape in sync with the helper type parameter here.
422    const POSTCARD_MAX_SIZE: usize =
423        MorseProfile::POSTCARD_MAX_SIZE + crate::heapless_vec_max_size::<(u16, Action), MORSE_SIZE>();
424}
425
426#[cfg(feature = "defmt")]
427impl defmt::Format for Morse {
428    fn format(&self, f: defmt::Formatter<'_>) {
429        defmt::write!(f, "profile: MorseProfile({:?}), ", self.profile);
430        defmt::write!(f, "actions: [");
431        for item in self.actions.iter() {
432            defmt::write!(f, "{:?},", item);
433        }
434        defmt::write!(f, "]");
435    }
436}
437
438impl PartialEq for Morse {
439    fn eq(&self, other: &Self) -> bool {
440        if self.profile != other.profile || self.actions.len() != other.actions.len() {
441            return false;
442        }
443        self.actions.iter().all(|(k, v)| other.actions.get(k) == Some(v))
444    }
445}
446
447impl Eq for Morse {}
448
449/// Wire format note: `Morse` uses a custom serde impl for the `LinearMap`
450/// of actions. The on-wire shape is `(MorseProfile, Vec<(u16, Action)>)`.
451/// The `morse_wire_format` test below pins this contract.
452impl Morse {
453    pub fn new_from_vial(
454        tap: Action,
455        hold: Action,
456        hold_after_tap: Action,
457        double_tap: Action,
458        profile: MorseProfile,
459    ) -> Self {
460        let mut result = Self {
461            profile,
462            ..Default::default()
463        };
464
465        if tap != Action::No {
466            _ = result.actions.insert(TAP, tap);
467        }
468        if hold != Action::No {
469            _ = result.actions.insert(HOLD, hold);
470        }
471        if double_tap != Action::No {
472            _ = result.actions.insert(DOUBLE_TAP, double_tap);
473        }
474        if hold_after_tap != Action::No {
475            _ = result.actions.insert(HOLD_AFTER_TAP, hold_after_tap);
476        }
477        result
478    }
479
480    pub fn new_with_actions(
481        tap_actions: heapless::Vec<Action, MORSE_SIZE>,
482        hold_actions: heapless::Vec<Action, MORSE_SIZE>,
483        profile: MorseProfile,
484    ) -> Self {
485        let mut result = Self {
486            profile,
487            ..Default::default()
488        };
489
490        let mut pattern = 0b1u16;
491        for item in tap_actions.iter() {
492            pattern <<= 1;
493            let _ = result.put(MorsePattern::from_u16(pattern), *item);
494        }
495
496        let mut pattern = 0b1u16;
497        for item in hold_actions.iter() {
498            pattern <<= 1;
499            let _ = result.put(MorsePattern::from_u16(pattern | 0b1), *item);
500        }
501
502        result
503    }
504
505    pub fn max_pattern_length(&self) -> usize {
506        let mut max_length = 0;
507        for pair in self.actions.iter() {
508            max_length = max_length.max(pair.0.pattern_length());
509        }
510        max_length
511    }
512
513    pub fn try_predict_final_action(&self, pattern_start: MorsePattern) -> Option<Action> {
514        if !self.actions.contains_key(&pattern_start) {
515            return None;
516        }
517        for (pattern, _) in self.actions.iter() {
518            if *pattern != pattern_start && pattern.starts_with(pattern_start) {
519                return None;
520            }
521        }
522        self.actions.get(&pattern_start).copied()
523    }
524
525    pub fn can_fire_early(&self, pattern: MorsePattern) -> bool {
526        let Some(current_action) = self.actions.get(&pattern) else {
527            return false;
528        };
529        if self.actions.contains_key(&pattern.followed_by_tap()) {
530            return false;
531        }
532        self.actions
533            .get(&pattern.followed_by_hold())
534            .is_some_and(|a| *a == *current_action)
535    }
536
537    pub fn has_pattern_or_continuation(&self, pattern: MorsePattern) -> bool {
538        self.actions.iter().any(|(p, _)| p.starts_with(pattern))
539    }
540
541    pub fn get(&self, pattern: MorsePattern) -> Option<Action> {
542        self.actions.get(&pattern).copied()
543    }
544
545    /// Insert or update an action for the given pattern.
546    ///
547    /// An `Action::No` removes the pattern. Returns `Err((pattern, action))` if the map is full.
548    pub fn put(&mut self, pattern: MorsePattern, action: Action) -> Result<(), (MorsePattern, Action)> {
549        if action != Action::No {
550            self.actions.insert(pattern, action).map(|_| ())
551        } else {
552            let _ = self.actions.remove(&pattern);
553            Ok(())
554        }
555    }
556}
557
558// Custom serde module for LinearMap
559mod morse_actions_serde {
560    use serde::de::Error;
561    use serde::{Deserializer, Serializer};
562
563    use super::*;
564
565    pub fn serialize<S>(map: &LinearMap<MorsePattern, Action, MORSE_SIZE>, serializer: S) -> Result<S::Ok, S::Error>
566    where
567        S: Serializer,
568    {
569        // Convert to Vec for serialization
570        let vec: heapless::Vec<(u16, Action), MORSE_SIZE> = map.iter().map(|(k, v)| (k.to_u16(), *v)).collect();
571        vec.serialize(serializer)
572    }
573
574    pub fn deserialize<'de, D>(deserializer: D) -> Result<LinearMap<MorsePattern, Action, MORSE_SIZE>, D::Error>
575    where
576        D: Deserializer<'de>,
577    {
578        use core::fmt;
579
580        use serde::de::{SeqAccess, Visitor};
581
582        struct VecVisitor;
583
584        impl<'de> Visitor<'de> for VecVisitor {
585            type Value = heapless::Vec<(u16, Action), MORSE_SIZE>;
586
587            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
588                write!(formatter, "a sequence of (u16, Action) tuples")
589            }
590
591            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
592            where
593                A: SeqAccess<'de>,
594            {
595                let mut vec = heapless::Vec::new();
596                while let Some(elem) = seq.next_element::<(u16, Action)>()? {
597                    vec.push(elem)
598                        .map_err(|_| serde::de::Error::custom("Vec capacity exceeded"))?;
599                }
600                Ok(vec)
601            }
602        }
603
604        let vec = deserializer.deserialize_seq(VecVisitor)?;
605        let mut map = LinearMap::new();
606        for (pattern, action) in vec {
607            if pattern == 0 {
608                return Err(D::Error::custom("MorsePattern 0 is invalid; the empty pattern is 0b1"));
609            }
610            map.insert(MorsePattern::from_u16(pattern), action)
611                .map_err(|_| D::Error::custom("Failed to insert into LinearMap"))?;
612        }
613        Ok(map)
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    extern crate alloc;
620
621    use super::*;
622    use crate::action::Action;
623    use crate::keycode::{HidKeyCode, KeyCode};
624
625    #[test]
626    fn test_linear_map_serde_empty() {
627        let morse = Morse::default();
628
629        let mut buffer = [0u8; 128];
630        let serialized = postcard::to_slice(&morse, &mut buffer).unwrap();
631        let deserialized: Morse = postcard::from_bytes(serialized).unwrap();
632
633        assert_eq!(morse.actions.len(), deserialized.actions.len());
634        assert_eq!(morse.actions.len(), 0);
635    }
636
637    #[test]
638    fn test_linear_map_serde_single_entry() {
639        let mut morse = Morse::default();
640        morse.actions.insert(TAP, Action::Key(KeyCode::Hid(HidKeyCode::A))).ok();
641
642        let mut buffer = [0u8; 128];
643        let serialized = postcard::to_slice(&morse, &mut buffer).unwrap();
644        let deserialized: Morse = postcard::from_bytes(serialized).unwrap();
645
646        assert_eq!(morse.actions.len(), deserialized.actions.len());
647        assert_eq!(
648            deserialized.actions.get(&TAP),
649            Some(&Action::Key(KeyCode::Hid(HidKeyCode::A)))
650        );
651    }
652
653    #[test]
654    fn test_linear_map_serde_multiple_entries() {
655        let mut morse = Morse::default();
656        morse.actions.insert(TAP, Action::Key(KeyCode::Hid(HidKeyCode::A))).ok();
657        morse
658            .actions
659            .insert(HOLD, Action::Key(KeyCode::Hid(HidKeyCode::B)))
660            .ok();
661        morse
662            .actions
663            .insert(DOUBLE_TAP, Action::Key(KeyCode::Hid(HidKeyCode::C)))
664            .ok();
665        morse
666            .actions
667            .insert(HOLD_AFTER_TAP, Action::Key(KeyCode::Hid(HidKeyCode::D)))
668            .ok();
669
670        let mut buffer = [0u8; 128];
671        let serialized = postcard::to_slice(&morse, &mut buffer).unwrap();
672        let deserialized: Morse = postcard::from_bytes(serialized).unwrap();
673
674        assert_eq!(morse.actions.len(), deserialized.actions.len());
675        assert_eq!(morse.actions.len(), 4);
676
677        assert_eq!(
678            deserialized.actions.get(&TAP),
679            Some(&Action::Key(KeyCode::Hid(HidKeyCode::A)))
680        );
681        assert_eq!(
682            deserialized.actions.get(&HOLD),
683            Some(&Action::Key(KeyCode::Hid(HidKeyCode::B)))
684        );
685        assert_eq!(
686            deserialized.actions.get(&DOUBLE_TAP),
687            Some(&Action::Key(KeyCode::Hid(HidKeyCode::C)))
688        );
689        assert_eq!(
690            deserialized.actions.get(&HOLD_AFTER_TAP),
691            Some(&Action::Key(KeyCode::Hid(HidKeyCode::D)))
692        );
693    }
694
695    #[test]
696    fn test_linear_map_serde_with_profile() {
697        let mut morse = Morse {
698            profile: MorseProfile::new(Some(true), Some(MorseMode::PermissiveHold), Some(200), Some(150)),
699            ..Default::default()
700        };
701        morse.actions.insert(TAP, Action::Key(KeyCode::Hid(HidKeyCode::H))).ok();
702        morse
703            .actions
704            .insert(HOLD, Action::Key(KeyCode::Hid(HidKeyCode::I)))
705            .ok();
706
707        let mut buffer = [0u8; 128];
708        let serialized = postcard::to_slice(&morse, &mut buffer).unwrap();
709        let deserialized: Morse = postcard::from_bytes(serialized).unwrap();
710
711        assert_eq!(morse.profile, deserialized.profile);
712        assert_eq!(morse.actions.len(), deserialized.actions.len());
713    }
714
715    #[test]
716    fn morse_pattern_max_size_matches_u16() {
717        // Morse actions serialize MorsePattern as u16 on the wire.
718        // If MorsePattern's MaxSize ever diverges from u16, the manual
719        // MaxSize impl on Morse would be wrong.
720        assert_eq!(MorsePattern::POSTCARD_MAX_SIZE, u16::POSTCARD_MAX_SIZE,);
721    }
722
723    #[test]
724    fn test_morse_profile_timeout_setters() {
725        let mut profile = MorseProfile::new(Some(true), Some(MorseMode::PermissiveHold), Some(1000), Some(2000));
726
727        assert_eq!(profile.hold_timeout_ms(), Some(1000));
728        assert_eq!(profile.gap_timeout_ms(), Some(2000));
729        assert_eq!(profile.unilateral_tap(), Some(true));
730        assert_eq!(profile.mode(), Some(MorseMode::PermissiveHold));
731
732        profile.set_hold_timeout_ms(1500);
733        assert_eq!(profile.hold_timeout_ms(), Some(1500));
734        assert_eq!(profile.gap_timeout_ms(), Some(2000));
735        assert_eq!(profile.unilateral_tap(), Some(true));
736        assert_eq!(profile.mode(), Some(MorseMode::PermissiveHold));
737
738        profile.set_gap_timeout_ms(2500);
739        assert_eq!(profile.hold_timeout_ms(), Some(1500));
740        assert_eq!(profile.gap_timeout_ms(), Some(2500));
741        assert_eq!(profile.unilateral_tap(), Some(true));
742        assert_eq!(profile.mode(), Some(MorseMode::PermissiveHold));
743
744        profile.set_hold_timeout_ms(0xFFFF);
745        profile.set_gap_timeout_ms(0xFFFF);
746        assert_eq!(profile.hold_timeout_ms(), Some(TIMEOUT_MAX_MS));
747        assert_eq!(profile.gap_timeout_ms(), Some(TIMEOUT_MAX_MS));
748
749        profile.set_hold_timeout_ms(0);
750        profile.set_gap_timeout_ms(0);
751        assert_eq!(profile.hold_timeout_ms(), None);
752        assert_eq!(profile.gap_timeout_ms(), None);
753
754        let p = MorseProfile::const_default().with_quick_tap_timeout_ms(Some(300));
755        assert_eq!(p.quick_tap_timeout_ms(), Some(300));
756        assert_eq!(p.hold_timeout_ms(), MorseProfile::const_default().hold_timeout_ms());
757        assert_eq!(p.gap_timeout_ms(), MorseProfile::const_default().gap_timeout_ms());
758
759        let mut p2 = p;
760        p2.set_quick_tap_timeout_ms(0);
761        assert_eq!(p2.quick_tap_timeout_ms(), Some(0), "set_*_ms(0) is explicit");
762
763        p2.set_quick_tap_timeout_ms(0xFFFF);
764        assert_eq!(p2.quick_tap_timeout_ms(), Some(TIMEOUT_MAX_MS));
765
766        let p3 = p.with_quick_tap_timeout_ms(None);
767        assert_eq!(p3.quick_tap_timeout_ms(), None, "with_*(None) clears the field");
768
769        let p4 = MorseProfile::const_default().with_quick_tap_timeout_ms(Some(0));
770        assert_eq!(p4.quick_tap_timeout_ms(), Some(0), "Some(0) is explicitly disabled");
771    }
772
773    #[test]
774    fn is_all_taps_encoding_invariant() {
775        let tap = MorsePattern::from_u16(0b10);
776        let tap_tap = MorsePattern::from_u16(0b100);
777        let tap_tap_tap = MorsePattern::from_u16(0b1000);
778        let hold = MorsePattern::from_u16(0b11);
779        let tap_hold = MorsePattern::from_u16(0b101);
780        let hold_tap = MorsePattern::from_u16(0b110);
781        let empty = MorsePattern::default();
782
783        assert!(tap.is_all_taps());
784        assert!(tap_tap.is_all_taps());
785        assert!(tap_tap_tap.is_all_taps());
786        assert!(!hold.is_all_taps());
787        assert!(!tap_hold.is_all_taps());
788        assert!(!hold_tap.is_all_taps());
789        assert!(!empty.is_all_taps());
790
791        assert_eq!(tap, MorsePattern::default().followed_by_tap());
792        assert_eq!(tap_tap, tap.followed_by_tap());
793        assert_eq!(hold, MorsePattern::default().followed_by_hold());
794    }
795
796    #[test]
797    fn test_morse_profile_packed_layout_matches_docs() {
798        let profile = MorseProfile::new(
799            Some(false),
800            Some(MorseMode::HoldOnOtherPress),
801            Some(0x0123),
802            Some(0x0456),
803        )
804        .with_enable_flow_tap(Some(false));
805        assert_eq!(
806            u64::from(profile),
807            0x8000_0000 | (0x0456u64 << 17) | 0x0001_0000 | 0x0000_4000 | 0x0123
808        );
809        assert_eq!(profile.unilateral_tap(), Some(false));
810        assert_eq!(profile.enable_flow_tap(), Some(false));
811
812        let profile = MorseProfile::new(Some(true), Some(MorseMode::Normal), Some(0x0123), Some(0x0456))
813            .with_enable_flow_tap(Some(true));
814        assert_eq!(
815            u64::from(profile),
816            0xC000_0000 | (0x0456u64 << 17) | 0x0001_8000 | 0x0000_6000 | 0x0123
817        );
818        assert_eq!(profile.unilateral_tap(), Some(true));
819        assert_eq!(profile.enable_flow_tap(), Some(true));
820    }
821
822    #[test]
823    fn test_morse_profile_enable_flow_tap_accessors_preserve_packed_fields() {
824        assert_eq!(core::mem::size_of::<MorseProfile>(), 8);
825        assert_eq!(MorseProfile::POSTCARD_MAX_SIZE, u64::POSTCARD_MAX_SIZE);
826        assert_eq!(MorseProfile::const_default().enable_flow_tap(), None);
827
828        let profile = MorseProfile::new(Some(true), Some(MorseMode::PermissiveHold), Some(1000), Some(2000));
829        let profile = profile.with_enable_flow_tap(Some(true));
830        assert_eq!(profile.enable_flow_tap(), Some(true));
831        assert_eq!(profile.hold_timeout_ms(), Some(1000));
832        assert_eq!(profile.gap_timeout_ms(), Some(2000));
833        assert_eq!(profile.unilateral_tap(), Some(true));
834        assert_eq!(profile.mode(), Some(MorseMode::PermissiveHold));
835
836        let profile = profile.with_enable_flow_tap(Some(false));
837        assert_eq!(profile.enable_flow_tap(), Some(false));
838        assert_eq!(profile.hold_timeout_ms(), Some(1000));
839        assert_eq!(profile.gap_timeout_ms(), Some(2000));
840        assert_eq!(profile.unilateral_tap(), Some(true));
841        assert_eq!(profile.mode(), Some(MorseMode::PermissiveHold));
842
843        let profile = profile.with_enable_flow_tap(None);
844        assert_eq!(profile.enable_flow_tap(), None);
845        assert_eq!(profile.hold_timeout_ms(), Some(1000));
846        assert_eq!(profile.gap_timeout_ms(), Some(2000));
847        assert_eq!(profile.unilateral_tap(), Some(true));
848        assert_eq!(profile.mode(), Some(MorseMode::PermissiveHold));
849    }
850
851    /// The human-readable serde goes `MorseProfile` -> decoded parts -> `new()`.
852    /// All 32 bits are covered by the five fields, so that path must be lossless.
853    #[test]
854    fn morse_profile_parts_roundtrip() {
855        for p in [
856            MorseProfile::new(
857                Some(false),
858                Some(MorseMode::HoldOnOtherPress),
859                Some(TIMEOUT_MAX_MS),
860                Some(1),
861            )
862            .with_enable_flow_tap(Some(false)),
863            MorseProfile::new(Some(true), Some(MorseMode::Normal), Some(200), Some(150))
864                .with_enable_flow_tap(Some(true)),
865            MorseProfile::const_default(),
866        ] {
867            let parts = MorseProfile::new(p.unilateral_tap(), p.mode(), p.hold_timeout_ms(), p.gap_timeout_ms())
868                .with_enable_flow_tap(p.enable_flow_tap());
869            assert_eq!(p, parts);
870        }
871    }
872
873    /// Pins the on-wire shape of `Morse`:
874    ///   `(MorseProfile, Vec<(u16, Action)>)`
875    ///
876    /// `Morse` uses a custom serde impl for the `LinearMap` of actions; this
877    /// test verifies a Morse value can be reconstructed by manually
878    /// deserializing those two fields from the same byte stream.
879    #[test]
880    fn morse_wire_format() {
881        use postcard::to_slice;
882
883        // Build a Morse with known data
884        let mut morse = Morse::default();
885        morse.actions.insert(MorsePattern::from_u16(0b11), Action::No).unwrap();
886
887        // Serialize the whole Morse
888        let mut buf = [0u8; 256];
889        let bytes = to_slice(&morse, &mut buf).unwrap();
890
891        // Now manually deserialize field-by-field in the order the Schema declares:
892        // 1. profile: MorseProfile (a newtype around u64)
893        let (profile, rest): (MorseProfile, &[u8]) =
894            postcard::take_from_bytes(bytes).expect("should deserialize MorseProfile first");
895        assert_eq!(profile, MorseProfile::const_default());
896
897        // 2. actions: Vec<(u16, Action)> — which is what the custom serde produces
898        let (actions, rest): (heapless::Vec<(u16, Action), MORSE_SIZE>, &[u8]) =
899            postcard::take_from_bytes(rest).expect("should deserialize actions vec second");
900        assert!(rest.is_empty(), "no trailing bytes should remain");
901
902        assert_eq!(actions.len(), 1);
903        assert_eq!(actions[0], (0b11u16, Action::No));
904    }
905}