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
8const 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
28pub mod protocol_limits {
38 pub const MAX_COMBO_SIZE: usize = 16;
40 pub const MAX_MORSE_SIZE: usize = 32;
42 pub const MAX_MACRO_DATA_SIZE: usize = 256;
44 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#[derive(Clone, Debug, Deserialize)]
78#[serde(deny_unknown_fields)]
79#[allow(unused)]
80pub struct KeyboardTomlConfig {
81 keyboard: Option<KeyboardInfo>,
83 matrix: Option<MatrixConfig>,
85 aliases: Option<HashMap<String, String>>,
87 keymap: Option<KeymapTomlConfig>,
89 layout: Option<LayoutTomlConfig>,
92 behavior: Option<BehaviorConfig>,
94 light: Option<LightConfig>,
96 storage: Option<StorageConfig>,
98 dfu: Option<DfuTomlConfig>,
100 pub(crate) ble: Option<BleConfig>,
102 chip: Option<HashMap<String, ChipConfig>>,
104 dependency: Option<DependencyConfig>,
106 split: Option<SplitConfig>,
108 input_device: Option<InputDeviceConfig>,
110 display: Option<DisplayConfig>,
112 output: Option<Vec<OutputConfig>>,
114 pub(crate) host: Option<HostConfig>,
116 #[serde(default)]
118 pub(crate) rmk: RmkConstantsConfig,
119 #[serde(default)]
123 pub(crate) event: EventConfig,
124 #[serde(skip)]
126 pub(crate) storage_user_set: bool,
127 #[serde(skip)]
130 pub(crate) storage_start_addr_user_set: bool,
131 #[serde(skip)]
132 pub(crate) storage_num_sectors_user_set: bool,
133 #[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 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 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 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 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 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 pub(crate) fn auto_calculate_parameters(&mut self) {
229 if let Some(split) = &self.split
231 && split.peripheral.len() > self.rmk.split_peripherals_num
232 {
233 self.rmk.split_peripherals_num = split.peripheral.len();
239 }
240
241 if let Some(behavior) = &self.behavior {
242 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 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#[serde_inline_default]
278#[derive(Clone, Debug, Deserialize)]
279#[serde(deny_unknown_fields)]
280pub(crate) struct RmkConstantsConfig {
281 #[serde_inline_default(20)]
283 pub mouse_key_interval: u16,
284 #[serde_inline_default(80)]
286 pub mouse_wheel_interval: u16,
287 #[serde_inline_default(8)]
289 #[serde(deserialize_with = "check_combo_max_num")]
290 pub combo_max_num: usize,
291 #[serde_inline_default(4)]
293 pub combo_max_length: usize,
294 #[serde_inline_default(8)]
296 #[serde(deserialize_with = "check_fork_max_num")]
297 pub fork_max_num: usize,
298 #[serde_inline_default(8)]
300 #[serde(deserialize_with = "check_morse_max_num")]
301 pub morse_max_num: usize,
302 #[serde_inline_default(16)]
304 #[serde(deserialize_with = "check_morse_profile_max_num")]
305 pub morse_profile_max_num: usize,
306 #[serde_inline_default(8)]
308 #[serde(deserialize_with = "check_max_patterns_per_key")]
309 pub max_patterns_per_key: usize,
310 #[serde_inline_default(256)]
312 pub macro_space_size: usize,
313 #[serde_inline_default(20)]
315 pub debounce_time: u16,
316 #[serde_inline_default(16)]
318 pub report_channel_size: usize,
319 #[serde_inline_default(4)]
321 pub vial_channel_size: usize,
322 #[serde_inline_default(4)]
324 pub flash_channel_size: usize,
325 #[serde_inline_default(0)]
327 pub split_peripherals_num: usize,
328 #[serde_inline_default(3)]
330 pub ble_profiles_num: usize,
331 #[serde_inline_default(0)]
333 pub split_central_sleep_timeout_seconds: u32,
334 #[serde_inline_default(64)]
337 pub protocol_macro_chunk_size: usize,
338 #[serde(default)]
340 pub auto_mouse_layer_max_num: Option<usize>,
341 #[serde_inline_default(488)]
344 pub rynk_buffer_size: usize,
345 #[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
377fn 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
417impl 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#[derive(Clone, Debug, Deserialize)]
447#[serde(deny_unknown_fields)]
448pub(crate) struct EventChannelConfig {
449 pub channel_size: usize,
451 pub pubs: usize,
453 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
467macro_rules! define_event_config {
469 ($($field:ident),* $(,)?) => {
470 #[derive(Clone, Debug, Deserialize)]
473 #[serde(deny_unknown_fields, default)]
474 pub(crate) struct EventConfig {
475 $(pub $field: EventChannelConfig,)*
476 }
477
478 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_status_change,
499 modifier,
501 keyboard,
502 layer_change,
504 wpm_update,
505 led_indicator,
506 sleep_state,
507 battery_status,
509 battery_adc,
510 charging_state,
511 pointing,
513 peripheral_connected,
515 central_connected,
516 peripheral_battery,
517 clear_peer,
518 dfu_status,
520 action,
522);
523
524#[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 pub map: Option<String>,
535 pub default_variant: Option<String>,
537 pub shapes: Option<HashMap<String, ShapeToml>>,
538 pub variant: Option<Vec<VariantToml>>,
539}
540
541#[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#[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#[derive(Clone, Debug, Default, Deserialize)]
569#[serde(deny_unknown_fields)]
570#[allow(unused)]
571pub(crate) struct KeymapTomlConfig {
572 pub layers: Option<u8>,
575 #[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#[derive(Clone, Debug, Default, Deserialize)]
591#[serde(deny_unknown_fields)]
592pub(crate) struct KeyboardInfo {
593 pub name: String,
595 pub vendor_id: u16,
597 pub product_id: u16,
599 pub manufacturer: Option<String>,
601 pub product_name: Option<String>,
603 pub serial_number: Option<String>,
605 pub board: Option<String>,
607 pub chip: Option<String>,
609 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#[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#[derive(Clone, Copy, Debug, Default, Deserialize)]
656#[serde(deny_unknown_fields)]
657pub(crate) struct StorageConfig {
658 pub start_addr: Option<usize>,
661 pub num_sectors: Option<u8>,
663 #[serde(default = "default_true")]
664 pub enabled: bool,
665 pub clear_storage: Option<bool>,
667 pub clear_layout: Option<bool>,
669}
670
671#[derive(Clone, Debug, Default, Deserialize)]
676#[serde(deny_unknown_fields)]
677pub(crate) struct DfuTomlConfig {
678 pub page_size: Option<u32>,
680 pub led: Option<String>,
683 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
702pub const DEFAULT_PASSKEY_ENTRY_TIMEOUT_SECS: u32 = 120;
704
705pub const MIN_PASSKEY_ENTRY_TIMEOUT_SECS: u32 = 30;
707
708#[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#[derive(Clone, Default, Debug, Deserialize)]
719#[serde(deny_unknown_fields)]
720pub struct ChipConfig {
721 pub dcdc_reg0: Option<bool>,
723 pub dcdc_reg1: Option<bool>,
725 pub dcdc_reg0_voltage: Option<DcdcReg0Voltage>,
727}
728
729#[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#[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#[derive(Clone, Debug, Deserialize)]
748#[serde(deny_unknown_fields)]
749pub struct DependencyConfig {
750 #[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
761pub(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]>>, }
770
771#[derive(Clone, Debug, Default, Deserialize)]
772#[serde(deny_unknown_fields)]
773pub struct KeyInfo {
774 pub hand: char, }
776
777#[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#[derive(Clone, Debug, Deserialize)]
798#[serde(deny_unknown_fields)]
799pub(crate) struct AutoMouseLayerConfig {
800 pub device_id: Option<u8>,
803 pub target_layer: u8,
805 pub timeout: Option<DurationMillis>,
808 pub threshold: Option<u16>,
811 pub deactivate_on_key: Option<bool>,
814 pub extra_mouse_keys: Option<Vec<String>>,
817 pub reset_timeout_on_key: Option<bool>,
820}
821
822#[derive(Clone, Debug, Deserialize, Default)]
825#[serde(deny_unknown_fields)]
826pub(crate) struct MorseProfile {
827 pub enable_flow_tap: Option<bool>,
828
829 pub unilateral_tap: Option<bool>,
831
832 pub permissive_hold: Option<bool>,
835 pub hold_on_other_press: Option<bool>,
836 pub normal_mode: Option<bool>,
837
838 pub hold_timeout: Option<DurationMillis>,
840
841 pub gap_timeout: Option<DurationMillis>,
843
844 pub quick_tap_timeout: Option<DurationMillis>,
845}
846
847#[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#[derive(Clone, Debug, Deserialize)]
858#[serde(deny_unknown_fields)]
859pub(crate) struct OneShotConfig {
860 pub timeout: Option<DurationMillis>,
861}
862
863#[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#[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#[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#[derive(Clone, Debug, Deserialize)]
892#[serde(deny_unknown_fields)]
893pub(crate) struct MacrosConfig {
894 pub macros: Vec<MacroConfig>,
895}
896
897#[derive(Clone, Debug, Deserialize)]
899#[serde(deny_unknown_fields)]
900pub(crate) struct MacroConfig {
901 pub operations: Vec<MacroOperation>,
902}
903
904#[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#[derive(Clone, Debug, Deserialize)]
917#[serde(deny_unknown_fields)]
918pub(crate) struct ForksConfig {
919 pub forks: Vec<ForkConfig>,
920}
921
922#[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#[derive(Clone, Debug, Deserialize)]
937#[serde(deny_unknown_fields)]
938pub(crate) struct MorsesConfig {
939 pub enable_flow_tap: Option<bool>, pub prior_idle_time: Option<DurationMillis>,
942
943 pub unilateral_tap: Option<bool>,
945
946 pub permissive_hold: Option<bool>,
949 pub hold_on_other_press: Option<bool>,
950 pub normal_mode: Option<bool>,
951
952 pub hold_timeout: Option<DurationMillis>,
954
955 pub gap_timeout: Option<DurationMillis>,
957
958 pub quick_tap_timeout: Option<DurationMillis>,
959
960 pub profiles: Option<HashMap<String, MorseProfile>>,
962
963 pub morses: Option<Vec<MorseConfig>>,
965}
966
967#[derive(Clone, Debug, Deserialize)]
969#[serde(deny_unknown_fields)]
970pub(crate) struct MorseConfig {
971 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 pub tap_actions: Option<Vec<String>>,
980 pub hold_actions: Option<Vec<String>>,
982 pub morse_actions: Option<Vec<MorseActionPair>>,
984}
985
986#[derive(Clone, Debug, Deserialize)]
988#[serde(deny_unknown_fields)]
989pub(crate) struct MorseActionPair {
990 pub pattern: String, pub action: String, }
993
994#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq)]
996#[serde(rename_all = "lowercase")]
997pub enum SplitConnection {
998 #[default]
999 Ble,
1000 Serial,
1001}
1002
1003#[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#[derive(Clone, Debug, Default, Deserialize)]
1018#[serde(deny_unknown_fields)]
1019pub struct SplitBoardConfig {
1020 pub rows: usize,
1022 pub cols: usize,
1024 pub row_offset: usize,
1026 pub col_offset: usize,
1028 pub ble_addr: Option<[u8; 6]>,
1030 pub serial: Option<Vec<SerialConfig>>,
1032 pub matrix: MatrixConfig,
1034 pub input_device: Option<InputDeviceConfig>,
1036 pub display: Option<DisplayConfig>,
1038 pub battery_adc_pin: Option<String>,
1040 pub adc_divider_measured: Option<u32>,
1042 pub adc_divider_total: Option<u32>,
1044 pub output: Option<Vec<OutputConfig>>,
1046 pub firmware: Option<String>,
1051 pub update_policy: Option<String>,
1054}
1055
1056#[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#[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#[serde_inline_default]
1102#[derive(Clone, Debug, Deserialize)]
1103#[serde(deny_unknown_fields)]
1104pub(crate) struct HostConfig {
1105 #[serde_inline_default(true)]
1107 pub vial_enabled: bool,
1108 #[serde_inline_default(false)]
1111 pub rynk_enabled: bool,
1112 pub unlock_keys: Option<Vec<[u8; 2]>>,
1115 #[serde(alias = "vial_insecure")]
1118 #[serde_inline_default(false)]
1119 pub insecure: bool,
1120 #[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#[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 pub name: String,
1156 pub id: Option<u8>,
1159 pub pin_x: String,
1161 pub pin_y: String,
1163 pub pin_z: String,
1165 pub transform: Vec<Vec<i16>>,
1166 pub bias: Vec<i16>,
1167 pub resolution: u16,
1168}
1169
1170#[derive(Clone, Debug, Default, Deserialize)]
1172#[serde(deny_unknown_fields)]
1173pub struct Pmw3610Config {
1174 pub name: String,
1176 pub id: Option<u8>,
1178 pub spi: SpiConfig,
1180 pub motion: Option<String>,
1182 pub cpi: Option<u16>,
1184 #[serde(default)]
1186 pub invert_x: bool,
1187 #[serde(default)]
1189 pub invert_y: bool,
1190 #[serde(default)]
1192 pub swap_xy: bool,
1193 #[serde(default)]
1195 pub force_awake: bool,
1196 #[serde(default)]
1198 pub smart_mode: bool,
1199 #[serde(default = "default_pointing_report_hz")]
1201 pub report_hz: u16,
1202 #[serde(default)]
1203 pub proc_invert_x: bool,
1204 #[serde(default)]
1206 pub proc_invert_y: bool,
1207 #[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 pub name: String,
1225 pub id: Option<u8>,
1227 pub sensor_type: Pmw33xxType,
1229 pub spi: SpiConfig,
1231 pub motion: Option<String>,
1233 pub cpi: Option<u16>,
1235 pub rot_trans_angle: Option<i8>,
1237 pub liftoff_dist: Option<u8>,
1239 #[serde(default)]
1241 pub proc_invert_x: bool,
1242 #[serde(default)]
1244 pub proc_invert_y: bool,
1245 #[serde(default)]
1247 pub proc_swap_xy: bool,
1248 #[serde(default = "default_pointing_report_hz")]
1250 pub report_hz: u16,
1251}
1252
1253#[derive(Clone, Debug, Default, Deserialize)]
1255#[serde(deny_unknown_fields)]
1256pub struct Iqs5xxConfig {
1257 pub name: String,
1259 pub id: Option<u8>,
1261 pub i2c: Iqs5xxI2cConfig,
1265 pub rdy: Option<String>,
1268 #[serde(default)]
1270 pub proc_invert_x: bool,
1271 #[serde(default)]
1273 pub proc_invert_y: bool,
1274 #[serde(default)]
1276 pub proc_swap_xy: bool,
1277}
1278
1279#[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 pub pin_a: String,
1295 pub pin_b: String,
1297 #[serde(default)]
1305 pub phase: EncoderPhase,
1306 pub resolution: Option<EncoderResolution>,
1308 pub detent: Option<u8>,
1310 pub pulse: Option<u8>,
1312 pub reverse: Option<bool>,
1314 #[serde(default = "default_false")]
1316 pub internal_pullup: bool,
1317 pub debounce_ms: Option<u16>,
1320}
1321
1322#[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#[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#[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#[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 #[serde(default = "default_i2c_address")]
1382 pub address: u8,
1383}
1384
1385const fn default_i2c_address() -> u8 {
1386 0x3C
1387}
1388
1389#[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#[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 pub render_interval: Option<u64>,
1413 pub min_render_interval: Option<u64>,
1416}
1417
1418#[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 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 let user_toml = r#"
1482[event.keyboard]
1483channel_size = 32
1484"#;
1485 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 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 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}