Skip to main content

rmk_config/
lib.rs

1use std::collections::HashMap;
2use std::path::Path;
3
4use config::{Config, File, FileFormat};
5use serde::{Deserialize, de};
6use serde_inline_default::serde_inline_default;
7
8/// Event channel default configuration
9const EVENT_DEFAULT_CONFIG: &str = include_str!("default_config/event_default.toml");
10
11pub(crate) mod chip;
12pub(crate) mod communication;
13pub mod resolved;
14#[rustfmt::skip]
15pub mod usb_interrupt_map;
16pub(crate) mod behavior;
17pub(crate) mod board;
18pub(crate) mod dfu;
19pub(crate) mod display;
20pub(crate) mod host;
21pub(crate) mod keycode_alias;
22pub(crate) mod keymap;
23pub mod layout;
24pub use layout::{STOCK_WIDTHS, layout_blob_from_toml, layout_info_from_toml};
25pub(crate) mod light;
26pub(crate) mod storage;
27
28/// Protocol-level capacity ceilings for wire-format Vec sizes.
29///
30/// These define the maximum values any firmware may use for protocol
31/// Vec capacities (`COMBO_SIZE`, `MORSE_SIZE`, etc.). The host tool compiles
32/// against these as upper bounds. Any firmware with `rynk` enabled
33/// must satisfy `value <= ceiling` at compile time.
34///
35/// Constant names mirror the generated constants with a `MAX_` prefix:
36/// `COMBO_SIZE` is bounded by `MAX_COMBO_SIZE`, etc.
37pub mod protocol_limits {
38    /// Max keys in a combo trigger — ceiling for `COMBO_SIZE`
39    pub const MAX_COMBO_SIZE: usize = 16;
40    /// Max pattern entries per morse key — ceiling for `MORSE_SIZE`
41    pub const MAX_MORSE_SIZE: usize = 32;
42    /// Max bytes per macro data chunk — ceiling for `MACRO_DATA_SIZE`
43    pub const MAX_MACRO_DATA_SIZE: usize = 256;
44    /// Max key positions in an unlock challenge.
45    pub const MAX_UNLOCK_KEYS_SIZE: usize = 4;
46}
47
48pub(crate) fn validate_unlock_keys(
49    section: &str,
50    unlock_keys: &[[u8; 2]],
51    layout: Option<&LayoutTomlConfig>,
52) -> Result<(), String> {
53    if unlock_keys.len() > protocol_limits::MAX_UNLOCK_KEYS_SIZE {
54        return Err(format!(
55            "{section}.unlock_keys has {} entries, the max is {}",
56            unlock_keys.len(),
57            protocol_limits::MAX_UNLOCK_KEYS_SIZE
58        ));
59    }
60
61    if let Some(layout) = layout {
62        for key in unlock_keys {
63            let (row, col) = (key[0], key[1]);
64            if row >= layout.rows || col >= layout.cols {
65                return Err(format!(
66                    "{section}.unlock_keys position ({row}, {col}) is outside the {}x{} matrix",
67                    layout.rows, layout.cols
68                ));
69            }
70        }
71    }
72
73    Ok(())
74}
75
76/// Configurations for RMK keyboard.
77#[derive(Clone, Debug, Deserialize)]
78#[serde(deny_unknown_fields)]
79#[allow(unused)]
80pub struct KeyboardTomlConfig {
81    /// Basic keyboard info
82    keyboard: Option<KeyboardInfo>,
83    /// Matrix of the keyboard, only for non-split keyboards
84    matrix: Option<MatrixConfig>,
85    // Aliases for key maps
86    aliases: Option<HashMap<String, String>>,
87    /// Keymap config: layer count and the per-layer key actions (`[[keymap.layer]]`).
88    keymap: Option<KeymapTomlConfig>,
89    /// Layout config: the physical key arrangement (`map`) plus the rendered layout.
90    /// For split keyboards, the total row/col is defined in this section.
91    layout: Option<LayoutTomlConfig>,
92    /// Behavior config
93    behavior: Option<BehaviorConfig>,
94    /// Light config
95    light: Option<LightConfig>,
96    /// Storage config
97    storage: Option<StorageConfig>,
98    /// DFU partition config (embassy-boot)
99    dfu: Option<DfuTomlConfig>,
100    /// Ble config
101    pub(crate) ble: Option<BleConfig>,
102    /// Chip-specific configs (e.g., [chip.nrf52840])
103    chip: Option<HashMap<String, ChipConfig>>,
104    /// Dependency config
105    dependency: Option<DependencyConfig>,
106    /// Split config
107    split: Option<SplitConfig>,
108    /// Input device config
109    input_device: Option<InputDeviceConfig>,
110    /// Display config
111    display: Option<DisplayConfig>,
112    /// Output Pin config
113    output: Option<Vec<OutputConfig>>,
114    /// Set host configurations
115    pub(crate) host: Option<HostConfig>,
116    /// RMK config constants
117    #[serde(default)]
118    pub(crate) rmk: RmkConstantsConfig,
119    /// Event channel configuration
120    /// Default values are loaded from event_default.toml in new_from_toml_path()
121    /// build.rs also loads event defaults via new_from_toml_path_with_event_defaults()
122    #[serde(default)]
123    pub(crate) event: EventConfig,
124    /// Whether the user explicitly set a [storage] section in keyboard.toml.
125    #[serde(skip)]
126    pub(crate) storage_user_set: bool,
127    /// Whether the user explicitly set `[storage]` `start_addr`/`num_sectors`
128    /// in keyboard.toml (chip defaults don't count).
129    #[serde(skip)]
130    pub(crate) storage_start_addr_user_set: bool,
131    #[serde(skip)]
132    pub(crate) storage_num_sectors_user_set: bool,
133    /// Whether the user explicitly wrote a [dfu] section in keyboard.toml
134    /// (chip defaults contain an empty [dfu] too).
135    #[serde(skip)]
136    pub(crate) dfu_user_set: bool,
137}
138
139impl KeyboardTomlConfig {
140    fn parse_from_toml_path<P: AsRef<Path>>(config_toml_path: P, chip_default_config: Option<&str>) -> Self {
141        let path = config_toml_path.as_ref();
142        let path_str = path
143            .to_str()
144            .unwrap_or_else(|| panic!("Config path is not valid UTF-8: {:?}", path));
145
146        let mut builder = Config::builder().add_source(File::from_str(EVENT_DEFAULT_CONFIG, FileFormat::Toml));
147        if let Some(default_config) = chip_default_config {
148            builder = builder.add_source(File::from_str(default_config, FileFormat::Toml));
149        }
150        builder
151            .add_source(File::with_name(path_str))
152            .build()
153            .unwrap_or_else(|e| panic!("Parse {:?} error: {}", path, e))
154            .try_deserialize()
155            .unwrap_or_else(|e| panic!("Deserialize {:?} error: {}", path, e))
156    }
157
158    /// Load keyboard.toml with event defaults only.
159    ///
160    /// This is used in build.rs where we only need [rmk] and [event] constants,
161    /// and should not require `[keyboard.board]`/`[keyboard.chip]`.
162    pub fn new_from_toml_path_with_event_defaults<P: AsRef<Path>>(config_toml_path: P) -> Self {
163        let mut config = Self::parse_from_toml_path(config_toml_path, None);
164        let storage = config.storage;
165        config.set_storage_user_flags(storage.as_ref());
166        config.dfu_user_set = config.dfu.is_some();
167        config.auto_calculate_parameters();
168        config
169    }
170
171    pub fn new_from_toml_path<P: AsRef<Path>>(config_toml_path: P) -> Self {
172        let path = config_toml_path.as_ref();
173
174        // First pass: load user config with event defaults to get chip model.
175        // This allows user's keyboard.toml to omit [event] section.
176        let user_config = Self::parse_from_toml_path(path, None);
177
178        let default_config_str = user_config
179            .get_chip_model()
180            .and_then(|chip| chip.get_default_config_str())
181            .unwrap_or_else(|e| panic!("❌ keyboard.toml error: {e}"));
182
183        // Second pass: load with all three config sources
184        // Config priority (later sources override earlier ones):
185        // 1. Event default config (lowest priority)
186        // 2. Chip-specific default config
187        // 3. User config (highest priority)
188        let mut config = Self::parse_from_toml_path(path, Some(default_config_str));
189        config.set_storage_user_flags(user_config.storage.as_ref());
190        config.dfu_user_set = user_config.dfu.is_some();
191
192        config.auto_calculate_parameters();
193
194        config
195    }
196
197    /// Record which `[storage]` keys the user explicitly set, so DFU-related
198    /// checks can distinguish them from chip default values.
199    fn set_storage_user_flags(&mut self, user_storage: Option<&StorageConfig>) {
200        self.storage_user_set = user_storage.is_some_and(|s| s.start_addr.is_some() || s.num_sectors.is_some());
201        self.storage_start_addr_user_set = user_storage.is_some_and(|s| s.start_addr.is_some());
202        self.storage_num_sectors_user_set = user_storage.is_some_and(|s| s.num_sectors.is_some());
203    }
204
205    /// Detect a `[storage]`/`[dfu]` conflict in the user's keyboard.toml.
206    ///
207    /// While DFU is enabled, the storage region is a partition fixed by the
208    /// bootloader's linker script (`rmk-memory.x`, generated by rmk-boot's
209    /// build.rs): `start_addr` is overridden to the partition start and
210    /// `num_sectors` must match the partition size. Explicit `[storage]`
211    /// values have no effect there, so return which keys the user set.
212    pub fn dfu_storage_conflict(&self) -> Option<DfuStorageConflict> {
213        if !self.dfu_user_set || !self.storage_user_set {
214            return None;
215        }
216        let conflict = DfuStorageConflict {
217            start_addr_set: self.storage_start_addr_user_set,
218            num_sectors_set: self.storage_num_sectors_user_set,
219        };
220        (conflict.start_addr_set || conflict.num_sectors_set).then_some(conflict)
221    }
222
223    /// Auto calculate some parameters in toml:
224    /// - Update morse_max_num to fit all configured morses
225    /// - Update max_patterns_per_key to fit the max number of configured (pattern, action) pairs per morse key
226    /// - Update peripheral number based on the number of split boards
227    /// - TODO: Update controller number based on the number of split boards
228    pub(crate) fn auto_calculate_parameters(&mut self) {
229        // Update the number of peripherals
230        if let Some(split) = &self.split
231            && split.peripheral.len() > self.rmk.split_peripherals_num
232        {
233            // eprintln!(
234            //     "The number of split peripherals is updated to {} from {}",
235            //     split.peripheral.len(),
236            //     self.rmk.split_peripherals_num
237            // );
238            self.rmk.split_peripherals_num = split.peripheral.len();
239        }
240
241        if let Some(behavior) = &self.behavior {
242            // Update the max_patterns_per_key
243            if let Some(morse) = &behavior.morse
244                && let Some(morses) = &morse.morses
245            {
246                let mut max_required_patterns = self.rmk.max_patterns_per_key;
247
248                for morse in morses {
249                    let tap_actions_len = morse.tap_actions.as_ref().map(|v| v.len()).unwrap_or(0);
250                    let hold_actions_len = morse.hold_actions.as_ref().map(|v| v.len()).unwrap_or(0);
251
252                    let n = tap_actions_len.max(hold_actions_len);
253                    if n > 15 {
254                        panic!("The number of taps per morse is too large, the max number of taps is 15, got {n}");
255                    }
256
257                    let morse_actions_len = morse.morse_actions.as_ref().map(|v| v.len()).unwrap_or(0);
258
259                    max_required_patterns =
260                        max_required_patterns.max(tap_actions_len + hold_actions_len + morse_actions_len);
261                }
262                self.rmk.max_patterns_per_key = max_required_patterns;
263
264                // Update the morse_max_num
265                self.rmk.morse_max_num = self.rmk.morse_max_num.max(morses.len());
266            }
267
268            let auto_mouse_layers = behavior.auto_mouse_layer.as_deref().unwrap_or_default();
269            self.rmk.auto_mouse_layer_max_num.get_or_insert(auto_mouse_layers.len());
270        } else {
271            self.rmk.auto_mouse_layer_max_num.get_or_insert(0);
272        }
273    }
274}
275
276/// Keyboard constants configuration for performance and hardware limits
277#[serde_inline_default]
278#[derive(Clone, Debug, Deserialize)]
279#[serde(deny_unknown_fields)]
280pub(crate) struct RmkConstantsConfig {
281    /// Mouse key interval (ms) - controls mouse movement speed
282    #[serde_inline_default(20)]
283    pub mouse_key_interval: u16,
284    /// Mouse wheel interval (ms) - controls scrolling speed
285    #[serde_inline_default(80)]
286    pub mouse_wheel_interval: u16,
287    /// Maximum number of combos keyboard can store
288    #[serde_inline_default(8)]
289    #[serde(deserialize_with = "check_combo_max_num")]
290    pub combo_max_num: usize,
291    /// Maximum number of keys pressed simultaneously in a combo
292    #[serde_inline_default(4)]
293    pub combo_max_length: usize,
294    /// Maximum number of forks for conditional key actions
295    #[serde_inline_default(8)]
296    #[serde(deserialize_with = "check_fork_max_num")]
297    pub fork_max_num: usize,
298    /// Maximum number of morses keyboard can store
299    #[serde_inline_default(8)]
300    #[serde(deserialize_with = "check_morse_max_num")]
301    pub morse_max_num: usize,
302    /// Capacity of the morse profile table (named profiles in `[behavior.morse.profiles]`)
303    #[serde_inline_default(16)]
304    #[serde(deserialize_with = "check_morse_profile_max_num")]
305    pub morse_profile_max_num: usize,
306    /// Maximum number of patterns a morse key can handle
307    #[serde_inline_default(8)]
308    #[serde(deserialize_with = "check_max_patterns_per_key")]
309    pub max_patterns_per_key: usize,
310    /// Macro space size in bytes for storing sequences
311    #[serde_inline_default(256)]
312    pub macro_space_size: usize,
313    /// Default debounce time in ms
314    #[serde_inline_default(20)]
315    pub debounce_time: u16,
316    /// Report channel size
317    #[serde_inline_default(16)]
318    pub report_channel_size: usize,
319    /// Vial channel size
320    #[serde_inline_default(4)]
321    pub vial_channel_size: usize,
322    /// Flash channel size
323    #[serde_inline_default(4)]
324    pub flash_channel_size: usize,
325    /// The number of the split peripherals
326    #[serde_inline_default(0)]
327    pub split_peripherals_num: usize,
328    /// The number of available BLE profiles
329    #[serde_inline_default(3)]
330    pub ble_profiles_num: usize,
331    /// BLE Split Central sleep timeout in seconds (0 = disabled)
332    #[serde_inline_default(0)]
333    pub split_central_sleep_timeout_seconds: u32,
334    /// Maximum macro data chunk size for protocol transfers (bytes).
335    /// Smaller values reduce firmware RAM usage but require more round-trips.
336    #[serde_inline_default(64)]
337    pub protocol_macro_chunk_size: usize,
338    /// Maximum number of auto mouse layer entries; auto-derived from `[[behavior.auto_mouse_layer]]` if unset.
339    #[serde(default)]
340    pub auto_mouse_layer_max_num: Option<usize>,
341    /// Exact RAM of each Rynk RX/TX frame buffer (bytes), payload capacity and bulk counts derive from it.
342    /// Default 488 fills exactly two BLE notifications.
343    #[serde_inline_default(488)]
344    pub rynk_buffer_size: usize,
345    /// Length of one dongle pairing window in seconds: repeated while no
346    /// keyboard is bonded, opened once at power-on otherwise (dongle firmware only)
347    #[serde_inline_default(30)]
348    pub dongle_pairing_window_secs: u32,
349}
350
351fn check_combo_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
352where
353    D: de::Deserializer<'de>,
354{
355    let value = Deserialize::deserialize(deserializer)?;
356    if value > u8::MAX as usize {
357        return Err(de::Error::custom(format!(
358            "combo_max_num must be between 0 and 255, got {value}"
359        )));
360    }
361    Ok(value)
362}
363
364fn check_morse_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
365where
366    D: de::Deserializer<'de>,
367{
368    let value = Deserialize::deserialize(deserializer)?;
369    if value > u8::MAX as usize {
370        return Err(de::Error::custom(format!(
371            "morse_max_num must be between 0 and 255, got {value}"
372        )));
373    }
374    Ok(value)
375}
376
377/// The profile index is a `u8` in `KeyAction::TapHold` and an index with no
378/// table entry means "use the default profile", so the table may never cover
379/// the full `u8` range: capacity ≤ 255 keeps at least one index always vacant.
380fn check_morse_profile_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
381where
382    D: de::Deserializer<'de>,
383{
384    let value = Deserialize::deserialize(deserializer)?;
385    if value > 255 {
386        panic!("❌ Parse `keyboard.toml` error: morse_profile_max_num must be between 0 and 255, got {value}");
387    }
388    Ok(value)
389}
390
391fn check_max_patterns_per_key<'de, D>(deserializer: D) -> Result<usize, D::Error>
392where
393    D: de::Deserializer<'de>,
394{
395    let value = Deserialize::deserialize(deserializer)?;
396    if !(4..=65536).contains(&value) {
397        return Err(de::Error::custom(format!(
398            "max_patterns_per_key must be between 4 and 65536, got {value}"
399        )));
400    }
401    Ok(value)
402}
403
404fn check_fork_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
405where
406    D: de::Deserializer<'de>,
407{
408    let value = Deserialize::deserialize(deserializer)?;
409    if value > u8::MAX as usize {
410        return Err(de::Error::custom(format!(
411            "fork_max_num must be between 0 and 255, got {value}"
412        )));
413    }
414    Ok(value)
415}
416
417/// This separate Default impl is needed when `[rmk]` section is not set in keyboard.toml
418impl Default for RmkConstantsConfig {
419    fn default() -> Self {
420        Self {
421            mouse_key_interval: 20,
422            mouse_wheel_interval: 80,
423            combo_max_num: 8,
424            combo_max_length: 4,
425            fork_max_num: 8,
426            morse_max_num: 8,
427            morse_profile_max_num: 16,
428            max_patterns_per_key: 8,
429            macro_space_size: 256,
430            debounce_time: 20,
431            report_channel_size: 16,
432            vial_channel_size: 4,
433            flash_channel_size: 4,
434            split_peripherals_num: 0,
435            ble_profiles_num: 3,
436            split_central_sleep_timeout_seconds: 0,
437            protocol_macro_chunk_size: 64,
438            auto_mouse_layer_max_num: None,
439            rynk_buffer_size: 488,
440            dongle_pairing_window_secs: 30,
441        }
442    }
443}
444
445/// Event channel configuration for a single event type
446#[derive(Clone, Debug, Deserialize)]
447#[serde(deny_unknown_fields)]
448pub(crate) struct EventChannelConfig {
449    /// Channel buffer size
450    pub channel_size: usize,
451    /// Number of publishers
452    pub pubs: usize,
453    /// Number of subscribers
454    pub subs: usize,
455}
456
457impl Default for EventChannelConfig {
458    fn default() -> Self {
459        Self {
460            channel_size: 1,
461            pubs: 1,
462            subs: 1,
463        }
464    }
465}
466
467/// Macro to define EventConfig and related code without repetition
468macro_rules! define_event_config {
469    ($($field:ident),* $(,)?) => {
470        /// Event configuration for all controller events
471        /// Default values are loaded from event_default.toml
472        #[derive(Clone, Debug, Deserialize)]
473        #[serde(deny_unknown_fields, default)]
474        pub(crate) struct EventConfig {
475            $(pub $field: EventChannelConfig,)*
476        }
477
478        /// Cached default EventConfig parsed from event_default.toml
479        static EVENT_CONFIG_DEFAULTS: std::sync::LazyLock<EventConfig> = std::sync::LazyLock::new(|| {
480            #[derive(Deserialize)]
481            struct Inner { $($field: EventChannelConfig,)* }
482            #[derive(Deserialize)]
483            struct Wrapper { event: Inner }
484            let w: Wrapper = toml::from_str(EVENT_DEFAULT_CONFIG).expect("Failed to parse event_default.toml");
485            EventConfig { $($field: w.event.$field,)* }
486        });
487
488        impl Default for EventConfig {
489            fn default() -> Self {
490                EVENT_CONFIG_DEFAULTS.clone()
491            }
492        }
493    };
494}
495
496define_event_config!(
497    // Connection events
498    connection_status_change,
499    // Input events
500    modifier,
501    keyboard,
502    // Keyboard state events
503    layer_change,
504    wpm_update,
505    led_indicator,
506    sleep_state,
507    // Power events
508    battery_status,
509    battery_adc,
510    charging_state,
511    // Pointing device events
512    pointing,
513    // Split events
514    peripheral_connected,
515    central_connected,
516    peripheral_battery,
517    clear_peer,
518    // DFU events
519    dfu_status,
520    // Action events
521    action,
522);
523
524/// The `[layout]` section: the physical key arrangement plus the rendered layout.
525#[derive(Clone, Debug, Deserialize)]
526#[serde(deny_unknown_fields)]
527#[allow(unused)]
528pub(crate) struct LayoutTomlConfig {
529    pub rows: u8,
530    pub cols: u8,
531    /// The physical arrangement: an ordered map of `(row,col)` positions with
532    /// optional hand, shape (`@2u`), gaps (`[1.5]`), row-steps (`[y=]`), and
533    /// encoders (`(e,0)`). Its order also defines the order of `[[keymap.layer]]`.
534    pub map: Option<String>,
535    // Rendered-layout fields.
536    pub default_variant: Option<String>,
537    pub shapes: Option<HashMap<String, ShapeToml>>,
538    pub variant: Option<Vec<VariantToml>>,
539}
540
541/// A named shape from `[layout.shapes]`. Every field optional; widths/
542/// heights default to 1u, nudges/rotation to 0, and `w2/h2/x2/y2` are an
543/// optional second rectangle for L-shaped caps.
544#[derive(Clone, Debug, Default, Deserialize)]
545#[serde(deny_unknown_fields)]
546pub(crate) struct ShapeToml {
547    pub w: Option<f32>,
548    pub h: Option<f32>,
549    pub x: Option<f32>,
550    pub y: Option<f32>,
551    pub r: Option<f32>,
552    pub w2: Option<f32>,
553    pub h2: Option<f32>,
554    pub x2: Option<f32>,
555    pub y2: Option<f32>,
556}
557
558/// One `[[layout.variant]]` render overlay: reshape some keys, hide others.
559#[derive(Clone, Debug, Deserialize)]
560#[serde(deny_unknown_fields)]
561pub(crate) struct VariantToml {
562    pub name: String,
563    pub shapes: Option<HashMap<String, String>>,
564    pub hidden: Option<Vec<String>>,
565}
566
567/// The `[keymap]` section: layer count plus the per-layer key actions.
568#[derive(Clone, Debug, Default, Deserialize)]
569#[serde(deny_unknown_fields)]
570#[allow(unused)]
571pub(crate) struct KeymapTomlConfig {
572    /// Total layer count. Optional — defaults to the number of `[[keymap.layer]]`
573    /// blocks; set it larger to reserve extra empty layers (e.g. for Vial/Rynk).
574    pub layers: Option<u8>,
575    /// Per-layer key actions: `[[keymap.layer]]`.
576    #[serde(default)]
577    pub layer: Vec<LayerTomlConfig>,
578}
579
580#[derive(Clone, Debug, Deserialize)]
581#[serde(deny_unknown_fields)]
582#[allow(unused)]
583pub(crate) struct LayerTomlConfig {
584    pub name: Option<String>,
585    pub keys: String,
586    pub encoders: Option<Vec<[String; 2]>>,
587}
588
589/// Configurations for keyboard info
590#[derive(Clone, Debug, Default, Deserialize)]
591#[serde(deny_unknown_fields)]
592pub(crate) struct KeyboardInfo {
593    /// Keyboard name
594    pub name: String,
595    /// Vender id
596    pub vendor_id: u16,
597    /// Product id
598    pub product_id: u16,
599    /// Manufacturer
600    pub manufacturer: Option<String>,
601    /// Product name, if not set, it will use `name` as default
602    pub product_name: Option<String>,
603    /// Serial number
604    pub serial_number: Option<String>,
605    /// Board name(if a supported board is used)
606    pub board: Option<String>,
607    /// Chip model
608    pub chip: Option<String>,
609    /// enable usb
610    pub usb_enable: Option<bool>,
611}
612
613#[derive(Clone, Debug, Default, Deserialize)]
614pub enum MatrixType {
615    #[default]
616    #[serde(rename = "normal")]
617    Normal,
618    #[serde(rename = "direct_pin")]
619    DirectPin,
620}
621
622#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq)]
623#[serde(rename_all = "lowercase")]
624pub enum DebouncerType {
625    #[default]
626    Default,
627    Fast,
628}
629
630#[derive(Clone, Debug, Default, Deserialize)]
631#[serde(deny_unknown_fields)]
632pub struct MatrixConfig {
633    #[serde(default)]
634    pub matrix_type: MatrixType,
635    pub row_pins: Option<Vec<String>>,
636    pub col_pins: Option<Vec<String>>,
637    pub direct_pins: Option<Vec<Vec<String>>>,
638    #[serde(default = "default_true")]
639    pub direct_pin_low_active: bool,
640    #[serde(default = "default_false")]
641    pub row2col: bool,
642    #[serde(default)]
643    pub debouncer: DebouncerType,
644    pub bootmagic: Option<(u8, u8)>,
645}
646
647/// Which `[storage]` keys the user explicitly set while `[dfu]` is enabled.
648#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
649pub struct DfuStorageConflict {
650    pub start_addr_set: bool,
651    pub num_sectors_set: bool,
652}
653
654/// Config for storage
655#[derive(Clone, Copy, Debug, Default, Deserialize)]
656#[serde(deny_unknown_fields)]
657pub(crate) struct StorageConfig {
658    /// Start address of local storage, MUST BE start of a sector.
659    /// If start_addr is set to 0(this is the default value), the last `num_sectors` sectors will be used.
660    pub start_addr: Option<usize>,
661    // Number of sectors used for storage, >= 2.
662    pub num_sectors: Option<u8>,
663    #[serde(default = "default_true")]
664    pub enabled: bool,
665    // Clear on the storage at reboot, set this to true if you want to reset the keymap
666    pub clear_storage: Option<bool>,
667    // Clear on the layout at reboot, set this to true if you want to reset the layout
668    pub clear_layout: Option<bool>,
669}
670
671/// Config for DFU (embassy-boot).
672///
673/// Offsets come from `rmk-memory.x` linker symbols. This section only
674/// configures DFU behaviour (LED, unlock keys, page size).
675#[derive(Clone, Debug, Default, Deserialize)]
676#[serde(deny_unknown_fields)]
677pub(crate) struct DfuTomlConfig {
678    /// Flash page size in bytes (e.g. 4096 for RP2040).
679    pub page_size: Option<u32>,
680    /// Optional DFU activity LED pin, e.g. `"PIN_16"`. When set, the LED
681    /// is lit while a DFU download is in progress.
682    pub led: Option<String>,
683    /// Unlock keys for DFU lock (optional)
684    pub unlock_keys: Option<Vec<[u8; 2]>>,
685}
686
687#[derive(Clone, Default, Debug, Deserialize)]
688#[serde(deny_unknown_fields)]
689pub struct BleConfig {
690    pub enabled: bool,
691    pub battery_adc_pin: Option<String>,
692    pub charge_state: Option<PinConfig>,
693    pub charge_led: Option<PinConfig>,
694    pub adc_divider_measured: Option<u32>,
695    pub adc_divider_total: Option<u32>,
696    pub default_tx_power: Option<i8>,
697    pub use_2m_phy: Option<bool>,
698    pub passkey_entry: Option<bool>,
699    pub passkey_entry_timeout: Option<u32>,
700}
701
702/// Default passkey entry timeout in seconds.
703pub const DEFAULT_PASSKEY_ENTRY_TIMEOUT_SECS: u32 = 120;
704
705/// Minimum passkey entry timeout in seconds.
706pub const MIN_PASSKEY_ENTRY_TIMEOUT_SECS: u32 = 30;
707
708/// nRF52840 DCDC REG0 output voltage
709#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
710pub enum DcdcReg0Voltage {
711    #[serde(rename = "3V3")]
712    V3_3,
713    #[serde(rename = "1V8")]
714    V1_8,
715}
716
717/// Config for chip-specific settings
718#[derive(Clone, Default, Debug, Deserialize)]
719#[serde(deny_unknown_fields)]
720pub struct ChipConfig {
721    /// DCDC regulator 0 enabled (for nrf52840)
722    pub dcdc_reg0: Option<bool>,
723    /// DCDC regulator 1 enabled (for nrf52840, nrf52833)
724    pub dcdc_reg1: Option<bool>,
725    /// DCDC regulator 0 voltage (for nrf52840)
726    pub dcdc_reg0_voltage: Option<DcdcReg0Voltage>,
727}
728
729/// Config for lights
730#[derive(Clone, Default, Debug, Deserialize)]
731#[serde(deny_unknown_fields)]
732pub struct LightConfig {
733    pub capslock: Option<PinConfig>,
734    pub scrolllock: Option<PinConfig>,
735    pub numslock: Option<PinConfig>,
736}
737
738/// Config for a single pin
739#[derive(Clone, Default, Debug, Deserialize)]
740#[serde(deny_unknown_fields)]
741pub struct PinConfig {
742    pub pin: String,
743    pub low_active: bool,
744}
745
746/// Configurations for dependencies
747#[derive(Clone, Debug, Deserialize)]
748#[serde(deny_unknown_fields)]
749pub struct DependencyConfig {
750    /// Enable defmt log or not
751    #[serde(default = "default_true")]
752    pub defmt_log: bool,
753}
754
755impl Default for DependencyConfig {
756    fn default() -> Self {
757        Self { defmt_log: true }
758    }
759}
760
761/// Intermediate resolved keymap grid (rows/cols/layers + per-layer actions).
762/// Built once by `get_keymap_config` and unpacked into `Keymap`; never (de)serialized.
763pub(crate) struct KeymapConfig {
764    pub rows: u8,
765    pub cols: u8,
766    pub layers: u8,
767    pub keymap: Vec<Vec<Vec<String>>>,
768    pub encoder_map: Vec<Vec<[String; 2]>>, // Empty if there are no encoders or not configured
769}
770
771#[derive(Clone, Debug, Default, Deserialize)]
772#[serde(deny_unknown_fields)]
773pub struct KeyInfo {
774    pub hand: char, // 'L' or 'R' or other chars
775}
776
777/// Configurations for actions behavior
778#[derive(Clone, Debug, Default, Deserialize)]
779#[serde(deny_unknown_fields)]
780pub(crate) struct BehaviorConfig {
781    pub tri_layer: Option<TriLayerConfig>,
782    pub one_shot: Option<OneShotConfig>,
783    pub one_shot_modifiers: Option<OneShotModifiersConfig>,
784    pub combo: Option<CombosConfig>,
785    #[serde(alias = "macro")]
786    pub macros: Option<MacrosConfig>,
787    pub fork: Option<ForksConfig>,
788    pub morse: Option<MorsesConfig>,
789    pub auto_mouse_layer: Option<Vec<AutoMouseLayerConfig>>,
790}
791
792/// Configurations for auto mouse layer
793///
794/// When motion is detected from a pointing device (e.g. PMW3610), the
795/// specified `target_layer` is activated. The layer stays active until
796/// `timeout` has elapsed without further motion, then it is deactivated.
797#[derive(Clone, Debug, Deserialize)]
798#[serde(deny_unknown_fields)]
799pub(crate) struct AutoMouseLayerConfig {
800    /// Pointing device id this entry applies to. When omitted, the entry acts as
801    /// a fallback for events whose `device_id` matches no other entry.
802    pub device_id: Option<u8>,
803    /// Layer index to activate on cursor motion
804    pub target_layer: u8,
805    /// Idle time after the last cursor motion before the layer is deactivated
806    /// (e.g. `"500ms"` or `"2s"`).
807    pub timeout: Option<DurationMillis>,
808    /// Minimum absolute axis delta required to be considered as motion.
809    /// Defaults to `1` (any motion). Helpful to filter out sensor noise.
810    pub threshold: Option<u16>,
811    /// When `true`, non-mouse key presses deactivate `target_layer` immediately (mouse HID keys and `extra_mouse_keys` excepted).
812    /// Macro-emitted keycodes, `Again`/`Repeat`, and `GraveEscape` cannot be classified and never deactivate the layer.
813    pub deactivate_on_key: Option<bool>,
814    /// Extra keycodes (e.g. modifiers) that do not trigger deactivation when `deactivate_on_key` is set.
815    /// Modifier keycodes listed here also exempt modifier-only actions containing them.
816    pub extra_mouse_keys: Option<Vec<String>>,
817    /// When `true`, key presses that do NOT deactivate `target_layer` extend the timeout deadline
818    /// (i.e. reset it to now + `timeout`) at the moment the key's action resolves.
819    pub reset_timeout_on_key: Option<bool>,
820}
821
822/// Per Key configurations profiles for morse, tap-hold, etc.
823/// overrides the defaults given in TapHoldConfig
824#[derive(Clone, Debug, Deserialize, Default)]
825#[serde(deny_unknown_fields)]
826pub(crate) struct MorseProfile {
827    pub enable_flow_tap: Option<bool>,
828
829    /// if true, tap-hold key will always send tap action when tapped with the same hand only
830    pub unilateral_tap: Option<bool>,
831
832    /// The decision mode of the morse/tap-hold key (only one of permissive_hold, hold_on_other_press and normal_mode can be true)
833    /// /// if none of them is given, normal mode will be the default
834    pub permissive_hold: Option<bool>,
835    pub hold_on_other_press: Option<bool>,
836    pub normal_mode: Option<bool>,
837
838    /// If the key is pressed longer than this, it is accepted as `hold` (in milliseconds)
839    pub hold_timeout: Option<DurationMillis>,
840
841    /// The time elapsed from the last release of a key is longer than this, it will break the morse pattern (in milliseconds)
842    pub gap_timeout: Option<DurationMillis>,
843
844    pub quick_tap_timeout: Option<DurationMillis>,
845}
846
847/// Configurations for tri layer
848#[derive(Clone, Debug, Deserialize)]
849#[serde(deny_unknown_fields)]
850pub(crate) struct TriLayerConfig {
851    pub upper: u8,
852    pub lower: u8,
853    pub adjust: u8,
854}
855
856/// Configurations for oneshot modifiers/layers
857#[derive(Clone, Debug, Deserialize)]
858#[serde(deny_unknown_fields)]
859pub(crate) struct OneShotConfig {
860    pub timeout: Option<DurationMillis>,
861}
862
863/// Configurations for oneshot modifiers
864#[derive(Clone, Debug, Deserialize)]
865#[serde(deny_unknown_fields)]
866pub struct OneShotModifiersConfig {
867    pub activate_on_keypress: Option<bool>,
868    pub quick_release: Option<bool>,
869}
870
871/// Configurations for combos
872#[derive(Clone, Debug, Deserialize)]
873#[serde(deny_unknown_fields)]
874pub(crate) struct CombosConfig {
875    #[serde(default)]
876    pub combos: Vec<ComboConfig>,
877    pub timeout: Option<DurationMillis>,
878    pub prior_idle_time: Option<DurationMillis>,
879}
880
881/// Configurations for combo
882#[derive(Clone, Debug, Deserialize)]
883#[serde(deny_unknown_fields)]
884pub(crate) struct ComboConfig {
885    pub actions: Vec<String>,
886    pub output: String,
887    pub layer: Option<u8>,
888}
889
890/// Configurations for macros
891#[derive(Clone, Debug, Deserialize)]
892#[serde(deny_unknown_fields)]
893pub(crate) struct MacrosConfig {
894    pub macros: Vec<MacroConfig>,
895}
896
897/// Configurations for macro
898#[derive(Clone, Debug, Deserialize)]
899#[serde(deny_unknown_fields)]
900pub(crate) struct MacroConfig {
901    pub operations: Vec<MacroOperation>,
902}
903
904/// Macro operations (TOML deserialization type — resolved equivalent is in `resolved::behavior`)
905#[derive(Clone, Debug, Deserialize)]
906#[serde(tag = "operation", rename_all = "lowercase")]
907pub(crate) enum MacroOperation {
908    Tap { keycode: String },
909    Down { keycode: String },
910    Up { keycode: String },
911    Delay { duration: DurationMillis },
912    Text { text: String },
913}
914
915/// Configurations for forks
916#[derive(Clone, Debug, Deserialize)]
917#[serde(deny_unknown_fields)]
918pub(crate) struct ForksConfig {
919    pub forks: Vec<ForkConfig>,
920}
921
922/// Configurations for fork
923#[derive(Clone, Debug, Deserialize)]
924#[serde(deny_unknown_fields)]
925pub(crate) struct ForkConfig {
926    pub trigger: String,
927    pub negative_output: String,
928    pub positive_output: String,
929    pub match_any: Option<String>,
930    pub match_none: Option<String>,
931    pub kept_modifiers: Option<String>,
932    pub bindable: Option<bool>,
933}
934
935/// Configurations for morse keys
936#[derive(Clone, Debug, Deserialize)]
937#[serde(deny_unknown_fields)]
938pub(crate) struct MorsesConfig {
939    pub enable_flow_tap: Option<bool>, //default: false
940    /// used in permissive_hold mode
941    pub prior_idle_time: Option<DurationMillis>,
942
943    /// if true, tap-hold key will always send tap action when tapped with the same hand only
944    pub unilateral_tap: Option<bool>,
945
946    /// The decision mode of the morse/tap-hold key (only one of permissive_hold, hold_on_other_press and normal_mode can be true)
947    /// if none of them is given, normal mode will be the default
948    pub permissive_hold: Option<bool>,
949    pub hold_on_other_press: Option<bool>,
950    pub normal_mode: Option<bool>,
951
952    /// If the key is pressed longer than this, it is accepted as `hold` (in milliseconds)
953    pub hold_timeout: Option<DurationMillis>,
954
955    /// The time elapsed from the last release of a key is longer than this, it will break the morse pattern (in milliseconds)
956    pub gap_timeout: Option<DurationMillis>,
957
958    pub quick_tap_timeout: Option<DurationMillis>,
959
960    /// these can be used to overrides the defaults given above
961    pub profiles: Option<HashMap<String, MorseProfile>>,
962
963    /// the definition of morse / tap dance keys
964    pub morses: Option<Vec<MorseConfig>>,
965}
966
967/// Configurations for morse
968#[derive(Clone, Debug, Deserialize)]
969#[serde(deny_unknown_fields)]
970pub(crate) struct MorseConfig {
971    // name of morse profile (to address BehaviorConfig::morse.profiles[self.profile])
972    pub profile: Option<String>,
973
974    pub tap: Option<String>,
975    pub hold: Option<String>,
976    pub hold_after_tap: Option<String>,
977    pub double_tap: Option<String>,
978    /// Array of tap actions for each tap count (0-indexed)
979    pub tap_actions: Option<Vec<String>>,
980    /// Array of hold actions for each tap count (0-indexed)
981    pub hold_actions: Option<Vec<String>>,
982    /// Array of morse patter->action pairs  count (0-indexed)
983    pub morse_actions: Option<Vec<MorseActionPair>>,
984}
985
986/// Configurations for morse action pairs
987#[derive(Clone, Debug, Deserialize)]
988#[serde(deny_unknown_fields)]
989pub(crate) struct MorseActionPair {
990    pub pattern: String, // for example morse code of "B": "-..." or "_..." or "1000"
991    pub action: String,  // "B"
992}
993
994/// Split connection transport
995#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq)]
996#[serde(rename_all = "lowercase")]
997pub enum SplitConnection {
998    #[default]
999    Ble,
1000    Serial,
1001}
1002
1003/// Configurations for split keyboards
1004#[derive(Clone, Debug, Default, Deserialize)]
1005#[serde(deny_unknown_fields)]
1006pub struct SplitConfig {
1007    pub connection: SplitConnection,
1008    pub central: SplitBoardConfig,
1009    pub peripheral: Vec<SplitBoardConfig>,
1010}
1011
1012/// Configurations for each split board
1013///
1014/// The transport field must match `split.connection`: `serial` is required for
1015/// serial splits and forbidden for BLE splits; `ble_addr` is optional for BLE
1016/// splits (dongle setups omit it) and forbidden for serial splits.
1017#[derive(Clone, Debug, Default, Deserialize)]
1018#[serde(deny_unknown_fields)]
1019pub struct SplitBoardConfig {
1020    /// Row number of the split board
1021    pub rows: usize,
1022    /// Col number of the split board
1023    pub cols: usize,
1024    /// Row offset of the split board
1025    pub row_offset: usize,
1026    /// Col offset of the split board
1027    pub col_offset: usize,
1028    /// Ble address
1029    pub ble_addr: Option<[u8; 6]>,
1030    /// Serial config, the vector length should be 1 for peripheral
1031    pub serial: Option<Vec<SerialConfig>>,
1032    /// Matrix config for the split
1033    pub matrix: MatrixConfig,
1034    /// Input device config for the split
1035    pub input_device: Option<InputDeviceConfig>,
1036    /// Display config for the split board
1037    pub display: Option<DisplayConfig>,
1038    /// Battery ADC pin for this split board
1039    pub battery_adc_pin: Option<String>,
1040    /// ADC divider measured value for battery
1041    pub adc_divider_measured: Option<u32>,
1042    /// ADC divider total value for battery
1043    pub adc_divider_total: Option<u32>,
1044    /// Output Pin config for the split
1045    pub output: Option<Vec<OutputConfig>>,
1046    /// Path to the peripheral firmware binary for automatic dfu_split update.
1047    /// Relative to the project's `Cargo.toml`.  When set, the generated code
1048    /// includes the binary with `include_bytes!` and registers it via
1049    /// [`set_firmware_update_data`](crate::set_firmware_update_data).
1050    pub firmware: Option<String>,
1051    /// DFU update policy for this peripheral. "MatchHash" (default) only
1052    /// flashes when the firmware differs; "force" always flashes.
1053    pub update_policy: Option<String>,
1054}
1055
1056/// Serial port config
1057#[derive(Clone, Debug, Default, Deserialize)]
1058#[serde(deny_unknown_fields)]
1059pub struct SerialConfig {
1060    pub instance: String,
1061    pub tx_pin: String,
1062    pub rx_pin: String,
1063}
1064
1065/// Duration in milliseconds
1066#[derive(Clone, Debug, Deserialize)]
1067pub(crate) struct DurationMillis(#[serde(deserialize_with = "parse_duration_millis")] pub u64);
1068
1069const fn default_true() -> bool {
1070    true
1071}
1072
1073const fn default_false() -> bool {
1074    false
1075}
1076
1077const fn default_pointing_report_hz() -> u16 {
1078    125
1079}
1080
1081fn parse_duration_millis<'de, D: de::Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
1082    let input: String = de::Deserialize::deserialize(deserializer)?;
1083    let num = input.trim_end_matches(|c: char| !c.is_numeric());
1084    let unit = &input[num.len()..];
1085    let num: u64 = num.parse().map_err(|_| {
1086        de::Error::custom(format!(
1087            "Invalid number \"{num}\" in duration: number part must be a u64"
1088        ))
1089    })?;
1090
1091    match unit {
1092        "s" => Ok(num * 1000),
1093        "ms" => Ok(num),
1094        other => Err(de::Error::custom(format!(
1095            "Invalid duration unit \"{other}\": unit part must be either \"s\" or \"ms\""
1096        ))),
1097    }
1098}
1099
1100/// Configuration for host tools
1101#[serde_inline_default]
1102#[derive(Clone, Debug, Deserialize)]
1103#[serde(deny_unknown_fields)]
1104pub(crate) struct HostConfig {
1105    /// Whether Vial is enabled
1106    #[serde_inline_default(true)]
1107    pub vial_enabled: bool,
1108    /// Whether the RMK-native Rynk protocol is enabled. Mutually exclusive
1109    /// with `vial_enabled` (the underlying Cargo features conflict).
1110    #[serde_inline_default(false)]
1111    pub rynk_enabled: bool,
1112    /// Physical keys (row, col) held simultaneously to unlock (optional).
1113    /// Shared by the Vial lock and the Rynk lock gate.
1114    pub unlock_keys: Option<Vec<[u8; 2]>>,
1115    /// Start (and stay) unlocked, bypassing the unlock-key combo (default:
1116    /// false). Renamed from `vial_insecure`; the old name still parses.
1117    #[serde(alias = "vial_insecure")]
1118    #[serde_inline_default(false)]
1119    pub insecure: bool,
1120    /// Move the Rynk config-write tier (`SetKeyAction`, `SetMacro`, …) into the
1121    /// locked set, so writes also require unlock (default: false).
1122    #[serde_inline_default(false)]
1123    pub write_requires_unlock: bool,
1124}
1125
1126impl Default for HostConfig {
1127    fn default() -> Self {
1128        Self {
1129            vial_enabled: true,
1130            rynk_enabled: false,
1131            unlock_keys: None,
1132            insecure: false,
1133            write_requires_unlock: false,
1134        }
1135    }
1136}
1137
1138/// Configurations for input devices
1139///
1140#[derive(Clone, Debug, Default, Deserialize)]
1141#[serde(deny_unknown_fields)]
1142pub struct InputDeviceConfig {
1143    pub encoder: Option<Vec<EncoderConfig>>,
1144    pub pointing: Option<Vec<PointingDeviceConfig>>,
1145    pub joystick: Option<Vec<JoystickConfig>>,
1146    pub pmw3610: Option<Vec<Pmw3610Config>>,
1147    pub pmw33xx: Option<Vec<Pmw33xxConfig>>,
1148    pub iqs5xx: Option<Vec<Iqs5xxConfig>>,
1149}
1150
1151#[derive(Clone, Debug, Default, Deserialize)]
1152#[serde(deny_unknown_fields)]
1153pub struct JoystickConfig {
1154    // Name of the joystick
1155    pub name: String,
1156    /// Device id used to match this joystick with its JoystickProcessor.
1157    /// If omitted, ids are assigned sequentially starting from 0.
1158    pub id: Option<u8>,
1159    // Pin a of the joystick
1160    pub pin_x: String,
1161    // Pin b of the joystick
1162    pub pin_y: String,
1163    // Pin z of the joystick
1164    pub pin_z: String,
1165    pub transform: Vec<Vec<i16>>,
1166    pub bias: Vec<i16>,
1167    pub resolution: u16,
1168}
1169
1170/// PMW3610 optical mouse sensor configuration
1171#[derive(Clone, Debug, Default, Deserialize)]
1172#[serde(deny_unknown_fields)]
1173pub struct Pmw3610Config {
1174    /// Name of the sensor (used for variable naming)
1175    pub name: String,
1176    /// id of the device
1177    pub id: Option<u8>,
1178    /// SPI pins
1179    pub spi: SpiConfig,
1180    /// Optional motion interrupt pin
1181    pub motion: Option<String>,
1182    /// CPI resolution (200-3200, step 200). Optional, uses sensor default if not set.
1183    pub cpi: Option<u16>,
1184    /// Invert X axis
1185    #[serde(default)]
1186    pub invert_x: bool,
1187    /// Invert Y axis
1188    #[serde(default)]
1189    pub invert_y: bool,
1190    /// Swap X and Y axes
1191    #[serde(default)]
1192    pub swap_xy: bool,
1193    /// Force awake mode (disable power saving)
1194    #[serde(default)]
1195    pub force_awake: bool,
1196    /// Enable smart mode for better tracking on shiny surfaces
1197    #[serde(default)]
1198    pub smart_mode: bool,
1199    /// Report rate (Hz). Motion will be accumulated and emitted at this rate.
1200    #[serde(default = "default_pointing_report_hz")]
1201    pub report_hz: u16,
1202    #[serde(default)]
1203    pub proc_invert_x: bool,
1204    /// Invert Y axis
1205    #[serde(default)]
1206    pub proc_invert_y: bool,
1207    /// Swap X and Y axes
1208    #[serde(default)]
1209    pub proc_swap_xy: bool,
1210}
1211
1212#[derive(Clone, Debug, Default, Deserialize)]
1213#[serde(deny_unknown_fields)]
1214pub enum Pmw33xxType {
1215    #[default]
1216    PMW3360,
1217    PMW3389,
1218}
1219
1220#[derive(Clone, Debug, Default, Deserialize)]
1221#[serde(deny_unknown_fields)]
1222pub struct Pmw33xxConfig {
1223    // Name of the sensor (used for variable naming)
1224    pub name: String,
1225    // id of the device
1226    pub id: Option<u8>,
1227    // Sensor Type (3360 or 3389)
1228    pub sensor_type: Pmw33xxType,
1229    // SPI pins
1230    pub spi: SpiConfig,
1231    // Optional motion interrupt pin
1232    pub motion: Option<String>,
1233    // CPI resolution (100-12000, step 100).Optional, uses sensor default 1600 if not set.
1234    pub cpi: Option<u16>,
1235    // Rotational transform angle (-127 to 127) Optional, uses sensor default 0 if not set.
1236    pub rot_trans_angle: Option<i8>,
1237    // liftoff distance. Optional, uses sensor default 0 if not set.
1238    pub liftoff_dist: Option<u8>,
1239    // Invert X axis
1240    #[serde(default)]
1241    pub proc_invert_x: bool,
1242    // Invert Y axis
1243    #[serde(default)]
1244    pub proc_invert_y: bool,
1245    // Swap X and Y axes
1246    #[serde(default)]
1247    pub proc_swap_xy: bool,
1248    /// Report rate (Hz). Motion will be accumulated and emitted at this rate.
1249    #[serde(default = "default_pointing_report_hz")]
1250    pub report_hz: u16,
1251}
1252
1253/// Azoteq IQS5xx trackpad configuration.
1254#[derive(Clone, Debug, Default, Deserialize)]
1255#[serde(deny_unknown_fields)]
1256pub struct Iqs5xxConfig {
1257    /// Name of the trackpad (used for variable naming).
1258    pub name: String,
1259    /// RMK pointing-device id (0-255). Defaults to 0.
1260    pub id: Option<u8>,
1261    /// I²C bus the trackpad is connected to. The bus is dedicated to this
1262    /// device — sharing with other I²C peripherals (e.g. an OLED) is not yet
1263    /// supported via TOML.
1264    pub i2c: Iqs5xxI2cConfig,
1265    /// Optional `RDY` pin. Strongly recommended; without it the driver falls
1266    /// back to timed polling and may stall the bus through clock-stretching.
1267    pub rdy: Option<String>,
1268    /// Invert X in the PointingProcessor.
1269    #[serde(default)]
1270    pub proc_invert_x: bool,
1271    /// Invert Y in the PointingProcessor.
1272    #[serde(default)]
1273    pub proc_invert_y: bool,
1274    /// Swap X and Y in the PointingProcessor.
1275    #[serde(default)]
1276    pub proc_swap_xy: bool,
1277}
1278
1279/// I²C bus configuration for the IQS5xx. Distinct from the generic `I2cConfig`
1280/// because the IQS5xx address is fixed (`0x74` by default; can be reprogrammed
1281/// at the IC, but not at runtime — exposing it would be misleading).
1282#[derive(Clone, Debug, Default, Deserialize)]
1283#[serde(deny_unknown_fields)]
1284pub struct Iqs5xxI2cConfig {
1285    pub instance: String,
1286    pub sda: String,
1287    pub scl: String,
1288}
1289
1290#[derive(Clone, Debug, Default, Deserialize)]
1291#[serde(deny_unknown_fields)]
1292pub struct EncoderConfig {
1293    // Pin a of the encoder
1294    pub pin_a: String,
1295    // Pin b of the encoder
1296    pub pin_b: String,
1297    // Phase is the working mode of the rotary encoders.
1298    // Available mode:
1299    // - default: resolution = 1
1300    // - e8h7: phase table tuned for E8H7 encoders
1301    // - resolution: customized resolution, the resolution value and reverse should be specified
1302    //   A typical [EC11 encoder](https://tech.alpsalpine.com/cms.media/product_catalog_ec_01_ec11e_en_611f078659.pdf)'s resolution is 2
1303    //   In resolution mode, you can also specify the number of detent and pulses, the resolution will be calculated by `pulse * 4 / detent`
1304    #[serde(default)]
1305    pub phase: EncoderPhase,
1306    // Resolution
1307    pub resolution: Option<EncoderResolution>,
1308    // The number of detent
1309    pub detent: Option<u8>,
1310    // The number of pulse
1311    pub pulse: Option<u8>,
1312    // Whether the direction of the rotary encoder is reversed.
1313    pub reverse: Option<bool>,
1314    // Use MCU's internal pull-up resistor or not, defaults to false, the external pull-up resistor is needed
1315    #[serde(default = "default_false")]
1316    pub internal_pullup: bool,
1317    // Debounce interval in milliseconds. Suppresses spurious events from mechanical contact bounce.
1318    // Defaults to 0 (disabled) if not specified.
1319    pub debounce_ms: Option<u16>,
1320}
1321
1322/// Rotary encoder phase (decoding) mode
1323#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq)]
1324#[serde(rename_all = "lowercase")]
1325pub enum EncoderPhase {
1326    #[default]
1327    Default,
1328    E8h7,
1329    Resolution,
1330}
1331
1332#[derive(Clone, Debug, Deserialize)]
1333#[serde(deny_unknown_fields, untagged)]
1334pub enum EncoderResolution {
1335    Value(u8),
1336    Derived { detent: u8, pulse: u8 },
1337}
1338
1339impl Default for EncoderResolution {
1340    fn default() -> Self {
1341        Self::Value(4)
1342    }
1343}
1344
1345/// Pointing device config
1346#[derive(Clone, Debug, Default, Deserialize)]
1347#[serde(deny_unknown_fields)]
1348pub struct PointingDeviceConfig {
1349    pub interface: Option<CommunicationProtocol>,
1350}
1351
1352#[derive(Clone, Debug, Deserialize)]
1353#[serde(rename_all = "snake_case")]
1354pub enum CommunicationProtocol {
1355    I2c(I2cConfig),
1356    Spi(SpiConfig),
1357}
1358
1359/// SPI config
1360#[derive(Clone, Debug, Default, Deserialize)]
1361#[serde(deny_unknown_fields)]
1362pub struct SpiConfig {
1363    pub instance: String,
1364    pub sck: String,
1365    pub mosi: String,
1366    pub miso: String,
1367    pub cs: Option<String>,
1368    pub cpi: Option<u32>,
1369    pub tx_dma: Option<String>,
1370    pub rx_dma: Option<String>,
1371}
1372
1373/// I2C config
1374#[derive(Clone, Debug, Default, Deserialize)]
1375#[serde(deny_unknown_fields)]
1376pub struct I2cConfig {
1377    pub instance: String,
1378    pub sda: String,
1379    pub scl: String,
1380    /// 7-bit I2C address. Defaults to 0x3C when omitted.
1381    #[serde(default = "default_i2c_address")]
1382    pub address: u8,
1383}
1384
1385const fn default_i2c_address() -> u8 {
1386    0x3C
1387}
1388
1389/// Display driver type
1390#[derive(Clone, Debug, Deserialize)]
1391#[serde(rename_all = "snake_case")]
1392pub enum DisplayDriver {
1393    Ssd1306,
1394    Sh1106,
1395    Sh1107,
1396    Sh1108,
1397    Ssd1309,
1398}
1399
1400/// Display configuration
1401#[derive(Clone, Debug, Deserialize)]
1402#[serde(deny_unknown_fields)]
1403pub struct DisplayConfig {
1404    pub driver: DisplayDriver,
1405    pub protocol: CommunicationProtocol,
1406    pub size: String,
1407    #[serde(default)]
1408    pub rotation: u16,
1409    pub renderer: Option<String>,
1410    /// Poll interval in milliseconds for periodic redraws (animations).
1411    /// When absent, polling is disabled — the display only redraws on events.
1412    pub render_interval: Option<u64>,
1413    /// Minimum time in milliseconds between event-driven renders.
1414    /// Prevents the display from being hammered by rapid events. Default: 10 ms.
1415    pub min_render_interval: Option<u64>,
1416}
1417
1418/// Configuration for an output pin
1419#[derive(Clone, Debug, Default, Deserialize)]
1420#[serde(deny_unknown_fields)]
1421pub struct OutputConfig {
1422    pub pin: String,
1423    #[serde(default)]
1424    pub low_active: bool,
1425    #[serde(default)]
1426    pub initial_state_active: bool,
1427}
1428
1429impl KeyboardTomlConfig {
1430    pub(crate) fn get_output_config(&self) -> Result<Vec<OutputConfig>, String> {
1431        let output_config = self.output.clone();
1432        let split = self.split.clone();
1433        match (output_config, split) {
1434            (None, Some(s)) => Ok(s.central.output.unwrap_or_default()),
1435            (Some(c), None) => Ok(c),
1436            (None, None) => Ok(Default::default()),
1437            _ => Err("Use [[split.output]] to define outputs for split in your keyboard.toml!".to_string()),
1438        }
1439    }
1440
1441    pub(crate) fn get_dependency_config(&self) -> DependencyConfig {
1442        self.dependency.clone().unwrap_or_default()
1443    }
1444}
1445
1446#[cfg(test)]
1447mod tests {
1448    use super::*;
1449
1450    #[test]
1451    fn test_event_config_default_values() {
1452        let config = EventConfig::default();
1453
1454        // Check some key default values from event_default.toml
1455        assert_eq!(config.keyboard.channel_size, 16);
1456        assert_eq!(config.keyboard.pubs, 2);
1457        assert_eq!(config.keyboard.subs, 3);
1458
1459        assert_eq!(config.modifier.channel_size, 8);
1460        assert_eq!(config.modifier.pubs, 1);
1461        assert_eq!(config.modifier.subs, 2);
1462
1463        assert_eq!(config.layer_change.channel_size, 1);
1464        assert_eq!(config.layer_change.subs, 1);
1465
1466        assert_eq!(config.led_indicator.channel_size, 2);
1467        assert_eq!(config.led_indicator.pubs, 2);
1468        assert_eq!(config.led_indicator.subs, 3);
1469
1470        assert_eq!(config.pointing.channel_size, 8);
1471        assert_eq!(config.pointing.subs, 2);
1472
1473        assert_eq!(config.action.channel_size, 16);
1474        assert_eq!(config.action.pubs, 1);
1475        assert_eq!(config.action.subs, 0);
1476    }
1477
1478    #[test]
1479    fn test_event_config_user_override() {
1480        // Simulate user config that overrides some event settings
1481        let user_toml = r#"
1482[event.keyboard]
1483channel_size = 32
1484"#;
1485        // Parse with event defaults first, then user config
1486        let config: KeyboardTomlConfig = Config::builder()
1487            .add_source(File::from_str(EVENT_DEFAULT_CONFIG, FileFormat::Toml))
1488            .add_source(File::from_str(user_toml, FileFormat::Toml))
1489            .build()
1490            .unwrap()
1491            .try_deserialize()
1492            .unwrap();
1493
1494        // User-overridden values
1495        assert_eq!(config.event.keyboard.channel_size, 32);
1496        assert_eq!(config.event.keyboard.pubs, 2);
1497        assert_eq!(config.event.keyboard.subs, 3);
1498
1499        // Non-overridden values should use defaults
1500        assert_eq!(config.event.modifier.channel_size, 8);
1501        assert_eq!(config.event.modifier.subs, 2);
1502        assert_eq!(config.event.layer_change.subs, 1);
1503    }
1504
1505    #[test]
1506    fn rmk_count_limits_fit_u8_capability_fields() {
1507        let ok: KeyboardTomlConfig = toml::from_str(
1508            r#"
1509[rmk]
1510combo_max_num = 255
1511morse_max_num = 255
1512fork_max_num = 255
1513"#,
1514        )
1515        .unwrap();
1516        assert_eq!(ok.rmk.combo_max_num, 255);
1517        assert_eq!(ok.rmk.morse_max_num, 255);
1518        assert_eq!(ok.rmk.fork_max_num, 255);
1519
1520        for (field, message) in [
1521            ("combo_max_num", "combo_max_num must be between 0 and 255"),
1522            ("morse_max_num", "morse_max_num must be between 0 and 255"),
1523            ("fork_max_num", "fork_max_num must be between 0 and 255"),
1524        ] {
1525            let toml = format!("[rmk]\n{field} = 256\n");
1526            let err = toml::from_str::<KeyboardTomlConfig>(&toml).unwrap_err();
1527            assert!(err.to_string().contains(message), "{err}");
1528        }
1529    }
1530
1531    #[test]
1532    fn test_event_config_partial_override_with_event_defaults_loader() {
1533        let user_toml = r#"
1534[event.layer_change]
1535subs = 2
1536"#;
1537        let path = std::env::temp_dir().join(format!(
1538            "rmk-event-defaults-loader-{}-{}.toml",
1539            std::process::id(),
1540            std::time::SystemTime::now()
1541                .duration_since(std::time::UNIX_EPOCH)
1542                .unwrap()
1543                .as_nanos()
1544        ));
1545        std::fs::write(&path, user_toml).unwrap();
1546
1547        let config = KeyboardTomlConfig::new_from_toml_path_with_event_defaults(&path);
1548        std::fs::remove_file(path).unwrap();
1549
1550        assert_eq!(config.event.layer_change.channel_size, 1);
1551        assert_eq!(config.event.layer_change.pubs, 2);
1552        assert_eq!(config.event.layer_change.subs, 2);
1553    }
1554}