use std::collections::HashMap;
use std::path::Path;
use config::{Config, File, FileFormat};
use serde::{Deserialize, de};
use serde_inline_default::serde_inline_default;
const EVENT_DEFAULT_CONFIG: &str = include_str!("default_config/event_default.toml");
pub(crate) mod chip;
pub(crate) mod communication;
pub mod resolved;
#[rustfmt::skip]
pub mod usb_interrupt_map;
pub(crate) mod behavior;
pub(crate) mod board;
pub(crate) mod dfu;
pub(crate) mod display;
pub(crate) mod host;
pub(crate) mod keycode_alias;
pub(crate) mod keymap;
pub mod layout;
pub use layout::{STOCK_WIDTHS, layout_blob_from_toml, layout_info_from_toml};
pub(crate) mod light;
pub(crate) mod storage;
pub mod protocol_limits {
pub const MAX_COMBO_SIZE: usize = 16;
pub const MAX_MORSE_SIZE: usize = 32;
pub const MAX_MACRO_DATA_SIZE: usize = 256;
pub const MAX_UNLOCK_KEYS_SIZE: usize = 4;
}
pub(crate) fn validate_unlock_keys(
section: &str,
unlock_keys: &[[u8; 2]],
layout: Option<&LayoutTomlConfig>,
) -> Result<(), String> {
if unlock_keys.len() > protocol_limits::MAX_UNLOCK_KEYS_SIZE {
return Err(format!(
"{section}.unlock_keys has {} entries, the max is {}",
unlock_keys.len(),
protocol_limits::MAX_UNLOCK_KEYS_SIZE
));
}
if let Some(layout) = layout {
for key in unlock_keys {
let (row, col) = (key[0], key[1]);
if row >= layout.rows || col >= layout.cols {
return Err(format!(
"{section}.unlock_keys position ({row}, {col}) is outside the {}x{} matrix",
layout.rows, layout.cols
));
}
}
}
Ok(())
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
#[allow(unused)]
pub struct KeyboardTomlConfig {
keyboard: Option<KeyboardInfo>,
matrix: Option<MatrixConfig>,
aliases: Option<HashMap<String, String>>,
keymap: Option<KeymapTomlConfig>,
layout: Option<LayoutTomlConfig>,
behavior: Option<BehaviorConfig>,
light: Option<LightConfig>,
storage: Option<StorageConfig>,
dfu: Option<DfuTomlConfig>,
pub(crate) ble: Option<BleConfig>,
chip: Option<HashMap<String, ChipConfig>>,
dependency: Option<DependencyConfig>,
split: Option<SplitConfig>,
input_device: Option<InputDeviceConfig>,
display: Option<DisplayConfig>,
output: Option<Vec<OutputConfig>>,
pub(crate) host: Option<HostConfig>,
#[serde(default)]
pub(crate) rmk: RmkConstantsConfig,
#[serde(default)]
pub(crate) event: EventConfig,
#[serde(skip)]
pub(crate) storage_user_set: bool,
#[serde(skip)]
pub(crate) storage_start_addr_user_set: bool,
#[serde(skip)]
pub(crate) storage_num_sectors_user_set: bool,
#[serde(skip)]
pub(crate) dfu_user_set: bool,
}
impl KeyboardTomlConfig {
fn parse_from_toml_path<P: AsRef<Path>>(config_toml_path: P, chip_default_config: Option<&str>) -> Self {
let path = config_toml_path.as_ref();
let path_str = path
.to_str()
.unwrap_or_else(|| panic!("Config path is not valid UTF-8: {:?}", path));
let mut builder = Config::builder().add_source(File::from_str(EVENT_DEFAULT_CONFIG, FileFormat::Toml));
if let Some(default_config) = chip_default_config {
builder = builder.add_source(File::from_str(default_config, FileFormat::Toml));
}
builder
.add_source(File::with_name(path_str))
.build()
.unwrap_or_else(|e| panic!("Parse {:?} error: {}", path, e))
.try_deserialize()
.unwrap_or_else(|e| panic!("Deserialize {:?} error: {}", path, e))
}
pub fn new_from_toml_path_with_event_defaults<P: AsRef<Path>>(config_toml_path: P) -> Self {
let mut config = Self::parse_from_toml_path(config_toml_path, None);
let storage = config.storage;
config.set_storage_user_flags(storage.as_ref());
config.dfu_user_set = config.dfu.is_some();
config.auto_calculate_parameters();
config
}
pub fn new_from_toml_path<P: AsRef<Path>>(config_toml_path: P) -> Self {
let path = config_toml_path.as_ref();
let user_config = Self::parse_from_toml_path(path, None);
let default_config_str = user_config
.get_chip_model()
.and_then(|chip| chip.get_default_config_str())
.unwrap_or_else(|e| panic!("❌ keyboard.toml error: {e}"));
let mut config = Self::parse_from_toml_path(path, Some(default_config_str));
config.set_storage_user_flags(user_config.storage.as_ref());
config.dfu_user_set = user_config.dfu.is_some();
config.auto_calculate_parameters();
config
}
fn set_storage_user_flags(&mut self, user_storage: Option<&StorageConfig>) {
self.storage_user_set = user_storage.is_some_and(|s| s.start_addr.is_some() || s.num_sectors.is_some());
self.storage_start_addr_user_set = user_storage.is_some_and(|s| s.start_addr.is_some());
self.storage_num_sectors_user_set = user_storage.is_some_and(|s| s.num_sectors.is_some());
}
pub fn dfu_storage_conflict(&self) -> Option<DfuStorageConflict> {
if !self.dfu_user_set || !self.storage_user_set {
return None;
}
let conflict = DfuStorageConflict {
start_addr_set: self.storage_start_addr_user_set,
num_sectors_set: self.storage_num_sectors_user_set,
};
(conflict.start_addr_set || conflict.num_sectors_set).then_some(conflict)
}
pub(crate) fn auto_calculate_parameters(&mut self) {
if let Some(split) = &self.split
&& split.peripheral.len() > self.rmk.split_peripherals_num
{
self.rmk.split_peripherals_num = split.peripheral.len();
}
if let Some(behavior) = &self.behavior {
if let Some(morse) = &behavior.morse
&& let Some(morses) = &morse.morses
{
let mut max_required_patterns = self.rmk.max_patterns_per_key;
for morse in morses {
let tap_actions_len = morse.tap_actions.as_ref().map(|v| v.len()).unwrap_or(0);
let hold_actions_len = morse.hold_actions.as_ref().map(|v| v.len()).unwrap_or(0);
let n = tap_actions_len.max(hold_actions_len);
if n > 15 {
panic!("The number of taps per morse is too large, the max number of taps is 15, got {n}");
}
let morse_actions_len = morse.morse_actions.as_ref().map(|v| v.len()).unwrap_or(0);
max_required_patterns =
max_required_patterns.max(tap_actions_len + hold_actions_len + morse_actions_len);
}
self.rmk.max_patterns_per_key = max_required_patterns;
self.rmk.morse_max_num = self.rmk.morse_max_num.max(morses.len());
}
let auto_mouse_layers = behavior.auto_mouse_layer.as_deref().unwrap_or_default();
self.rmk.auto_mouse_layer_max_num.get_or_insert(auto_mouse_layers.len());
} else {
self.rmk.auto_mouse_layer_max_num.get_or_insert(0);
}
}
}
#[serde_inline_default]
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct RmkConstantsConfig {
#[serde_inline_default(20)]
pub mouse_key_interval: u16,
#[serde_inline_default(80)]
pub mouse_wheel_interval: u16,
#[serde_inline_default(8)]
#[serde(deserialize_with = "check_combo_max_num")]
pub combo_max_num: usize,
#[serde_inline_default(4)]
pub combo_max_length: usize,
#[serde_inline_default(8)]
#[serde(deserialize_with = "check_fork_max_num")]
pub fork_max_num: usize,
#[serde_inline_default(8)]
#[serde(deserialize_with = "check_morse_max_num")]
pub morse_max_num: usize,
#[serde_inline_default(16)]
#[serde(deserialize_with = "check_morse_profile_max_num")]
pub morse_profile_max_num: usize,
#[serde_inline_default(8)]
#[serde(deserialize_with = "check_max_patterns_per_key")]
pub max_patterns_per_key: usize,
#[serde_inline_default(256)]
pub macro_space_size: usize,
#[serde_inline_default(20)]
pub debounce_time: u16,
#[serde_inline_default(16)]
pub report_channel_size: usize,
#[serde_inline_default(4)]
pub vial_channel_size: usize,
#[serde_inline_default(4)]
pub flash_channel_size: usize,
#[serde_inline_default(0)]
pub split_peripherals_num: usize,
#[serde_inline_default(3)]
pub ble_profiles_num: usize,
#[serde_inline_default(0)]
pub split_central_sleep_timeout_seconds: u32,
#[serde_inline_default(64)]
pub protocol_macro_chunk_size: usize,
#[serde(default)]
pub auto_mouse_layer_max_num: Option<usize>,
#[serde_inline_default(488)]
pub rynk_buffer_size: usize,
#[serde_inline_default(30)]
pub dongle_pairing_window_secs: u32,
}
fn check_combo_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: de::Deserializer<'de>,
{
let value = Deserialize::deserialize(deserializer)?;
if value > u8::MAX as usize {
return Err(de::Error::custom(format!(
"combo_max_num must be between 0 and 255, got {value}"
)));
}
Ok(value)
}
fn check_morse_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: de::Deserializer<'de>,
{
let value = Deserialize::deserialize(deserializer)?;
if value > u8::MAX as usize {
return Err(de::Error::custom(format!(
"morse_max_num must be between 0 and 255, got {value}"
)));
}
Ok(value)
}
fn check_morse_profile_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: de::Deserializer<'de>,
{
let value = Deserialize::deserialize(deserializer)?;
if value > 255 {
panic!("❌ Parse `keyboard.toml` error: morse_profile_max_num must be between 0 and 255, got {value}");
}
Ok(value)
}
fn check_max_patterns_per_key<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: de::Deserializer<'de>,
{
let value = Deserialize::deserialize(deserializer)?;
if !(4..=65536).contains(&value) {
return Err(de::Error::custom(format!(
"max_patterns_per_key must be between 4 and 65536, got {value}"
)));
}
Ok(value)
}
fn check_fork_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: de::Deserializer<'de>,
{
let value = Deserialize::deserialize(deserializer)?;
if value > u8::MAX as usize {
return Err(de::Error::custom(format!(
"fork_max_num must be between 0 and 255, got {value}"
)));
}
Ok(value)
}
impl Default for RmkConstantsConfig {
fn default() -> Self {
Self {
mouse_key_interval: 20,
mouse_wheel_interval: 80,
combo_max_num: 8,
combo_max_length: 4,
fork_max_num: 8,
morse_max_num: 8,
morse_profile_max_num: 16,
max_patterns_per_key: 8,
macro_space_size: 256,
debounce_time: 20,
report_channel_size: 16,
vial_channel_size: 4,
flash_channel_size: 4,
split_peripherals_num: 0,
ble_profiles_num: 3,
split_central_sleep_timeout_seconds: 0,
protocol_macro_chunk_size: 64,
auto_mouse_layer_max_num: None,
rynk_buffer_size: 488,
dongle_pairing_window_secs: 30,
}
}
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct EventChannelConfig {
pub channel_size: usize,
pub pubs: usize,
pub subs: usize,
}
impl Default for EventChannelConfig {
fn default() -> Self {
Self {
channel_size: 1,
pubs: 1,
subs: 1,
}
}
}
macro_rules! define_event_config {
($($field:ident),* $(,)?) => {
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub(crate) struct EventConfig {
$(pub $field: EventChannelConfig,)*
}
static EVENT_CONFIG_DEFAULTS: std::sync::LazyLock<EventConfig> = std::sync::LazyLock::new(|| {
#[derive(Deserialize)]
struct Inner { $($field: EventChannelConfig,)* }
#[derive(Deserialize)]
struct Wrapper { event: Inner }
let w: Wrapper = toml::from_str(EVENT_DEFAULT_CONFIG).expect("Failed to parse event_default.toml");
EventConfig { $($field: w.event.$field,)* }
});
impl Default for EventConfig {
fn default() -> Self {
EVENT_CONFIG_DEFAULTS.clone()
}
}
};
}
define_event_config!(
connection_status_change,
modifier,
keyboard,
layer_change,
wpm_update,
led_indicator,
sleep_state,
battery_status,
battery_adc,
charging_state,
pointing,
peripheral_connected,
central_connected,
peripheral_battery,
clear_peer,
dfu_status,
action,
);
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
#[allow(unused)]
pub(crate) struct LayoutTomlConfig {
pub rows: u8,
pub cols: u8,
pub map: Option<String>,
pub default_variant: Option<String>,
pub shapes: Option<HashMap<String, ShapeToml>>,
pub variant: Option<Vec<VariantToml>>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ShapeToml {
pub w: Option<f32>,
pub h: Option<f32>,
pub x: Option<f32>,
pub y: Option<f32>,
pub r: Option<f32>,
pub w2: Option<f32>,
pub h2: Option<f32>,
pub x2: Option<f32>,
pub y2: Option<f32>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct VariantToml {
pub name: String,
pub shapes: Option<HashMap<String, String>>,
pub hidden: Option<Vec<String>>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
#[allow(unused)]
pub(crate) struct KeymapTomlConfig {
pub layers: Option<u8>,
#[serde(default)]
pub layer: Vec<LayerTomlConfig>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
#[allow(unused)]
pub(crate) struct LayerTomlConfig {
pub name: Option<String>,
pub keys: String,
pub encoders: Option<Vec<[String; 2]>>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct KeyboardInfo {
pub name: String,
pub vendor_id: u16,
pub product_id: u16,
pub manufacturer: Option<String>,
pub product_name: Option<String>,
pub serial_number: Option<String>,
pub board: Option<String>,
pub chip: Option<String>,
pub usb_enable: Option<bool>,
}
#[derive(Clone, Debug, Default, Deserialize)]
pub enum MatrixType {
#[default]
#[serde(rename = "normal")]
Normal,
#[serde(rename = "direct_pin")]
DirectPin,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum DebouncerType {
#[default]
Default,
Fast,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MatrixConfig {
#[serde(default)]
pub matrix_type: MatrixType,
pub row_pins: Option<Vec<String>>,
pub col_pins: Option<Vec<String>>,
pub direct_pins: Option<Vec<Vec<String>>>,
#[serde(default = "default_true")]
pub direct_pin_low_active: bool,
#[serde(default = "default_false")]
pub row2col: bool,
#[serde(default)]
pub debouncer: DebouncerType,
pub bootmagic: Option<(u8, u8)>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DfuStorageConflict {
pub start_addr_set: bool,
pub num_sectors_set: bool,
}
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct StorageConfig {
pub start_addr: Option<usize>,
pub num_sectors: Option<u8>,
#[serde(default = "default_true")]
pub enabled: bool,
pub clear_storage: Option<bool>,
pub clear_layout: Option<bool>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct DfuTomlConfig {
pub page_size: Option<u32>,
pub led: Option<String>,
pub unlock_keys: Option<Vec<[u8; 2]>>,
}
#[derive(Clone, Default, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BleConfig {
pub enabled: bool,
pub battery_adc_pin: Option<String>,
pub charge_state: Option<PinConfig>,
pub charge_led: Option<PinConfig>,
pub adc_divider_measured: Option<u32>,
pub adc_divider_total: Option<u32>,
pub default_tx_power: Option<i8>,
pub use_2m_phy: Option<bool>,
pub passkey_entry: Option<bool>,
pub passkey_entry_timeout: Option<u32>,
}
pub const DEFAULT_PASSKEY_ENTRY_TIMEOUT_SECS: u32 = 120;
pub const MIN_PASSKEY_ENTRY_TIMEOUT_SECS: u32 = 30;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub enum DcdcReg0Voltage {
#[serde(rename = "3V3")]
V3_3,
#[serde(rename = "1V8")]
V1_8,
}
#[derive(Clone, Default, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChipConfig {
pub dcdc_reg0: Option<bool>,
pub dcdc_reg1: Option<bool>,
pub dcdc_reg0_voltage: Option<DcdcReg0Voltage>,
}
#[derive(Clone, Default, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LightConfig {
pub capslock: Option<PinConfig>,
pub scrolllock: Option<PinConfig>,
pub numslock: Option<PinConfig>,
}
#[derive(Clone, Default, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PinConfig {
pub pin: String,
pub low_active: bool,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DependencyConfig {
#[serde(default = "default_true")]
pub defmt_log: bool,
}
impl Default for DependencyConfig {
fn default() -> Self {
Self { defmt_log: true }
}
}
pub(crate) struct KeymapConfig {
pub rows: u8,
pub cols: u8,
pub layers: u8,
pub keymap: Vec<Vec<Vec<String>>>,
pub encoder_map: Vec<Vec<[String; 2]>>, }
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KeyInfo {
pub hand: char, }
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct BehaviorConfig {
pub tri_layer: Option<TriLayerConfig>,
pub one_shot: Option<OneShotConfig>,
pub one_shot_modifiers: Option<OneShotModifiersConfig>,
pub combo: Option<CombosConfig>,
#[serde(alias = "macro")]
pub macros: Option<MacrosConfig>,
pub fork: Option<ForksConfig>,
pub morse: Option<MorsesConfig>,
pub auto_mouse_layer: Option<Vec<AutoMouseLayerConfig>>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct AutoMouseLayerConfig {
pub device_id: Option<u8>,
pub target_layer: u8,
pub timeout: Option<DurationMillis>,
pub threshold: Option<u16>,
pub deactivate_on_key: Option<bool>,
pub extra_mouse_keys: Option<Vec<String>>,
pub reset_timeout_on_key: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub(crate) struct MorseProfile {
pub enable_flow_tap: Option<bool>,
pub unilateral_tap: Option<bool>,
pub permissive_hold: Option<bool>,
pub hold_on_other_press: Option<bool>,
pub normal_mode: Option<bool>,
pub hold_timeout: Option<DurationMillis>,
pub gap_timeout: Option<DurationMillis>,
pub quick_tap_timeout: Option<DurationMillis>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct TriLayerConfig {
pub upper: u8,
pub lower: u8,
pub adjust: u8,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct OneShotConfig {
pub timeout: Option<DurationMillis>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OneShotModifiersConfig {
pub activate_on_keypress: Option<bool>,
pub quick_release: Option<bool>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct CombosConfig {
#[serde(default)]
pub combos: Vec<ComboConfig>,
pub timeout: Option<DurationMillis>,
pub prior_idle_time: Option<DurationMillis>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ComboConfig {
pub actions: Vec<String>,
pub output: String,
pub layer: Option<u8>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct MacrosConfig {
pub macros: Vec<MacroConfig>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct MacroConfig {
pub operations: Vec<MacroOperation>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "operation", rename_all = "lowercase")]
pub(crate) enum MacroOperation {
Tap { keycode: String },
Down { keycode: String },
Up { keycode: String },
Delay { duration: DurationMillis },
Text { text: String },
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ForksConfig {
pub forks: Vec<ForkConfig>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ForkConfig {
pub trigger: String,
pub negative_output: String,
pub positive_output: String,
pub match_any: Option<String>,
pub match_none: Option<String>,
pub kept_modifiers: Option<String>,
pub bindable: Option<bool>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct MorsesConfig {
pub enable_flow_tap: Option<bool>, pub prior_idle_time: Option<DurationMillis>,
pub unilateral_tap: Option<bool>,
pub permissive_hold: Option<bool>,
pub hold_on_other_press: Option<bool>,
pub normal_mode: Option<bool>,
pub hold_timeout: Option<DurationMillis>,
pub gap_timeout: Option<DurationMillis>,
pub quick_tap_timeout: Option<DurationMillis>,
pub profiles: Option<HashMap<String, MorseProfile>>,
pub morses: Option<Vec<MorseConfig>>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct MorseConfig {
pub profile: Option<String>,
pub tap: Option<String>,
pub hold: Option<String>,
pub hold_after_tap: Option<String>,
pub double_tap: Option<String>,
pub tap_actions: Option<Vec<String>>,
pub hold_actions: Option<Vec<String>>,
pub morse_actions: Option<Vec<MorseActionPair>>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct MorseActionPair {
pub pattern: String, pub action: String, }
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SplitConnection {
#[default]
Ble,
Serial,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SplitConfig {
pub connection: SplitConnection,
pub central: SplitBoardConfig,
pub peripheral: Vec<SplitBoardConfig>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SplitBoardConfig {
pub rows: usize,
pub cols: usize,
pub row_offset: usize,
pub col_offset: usize,
pub ble_addr: Option<[u8; 6]>,
pub serial: Option<Vec<SerialConfig>>,
pub matrix: MatrixConfig,
pub input_device: Option<InputDeviceConfig>,
pub display: Option<DisplayConfig>,
pub battery_adc_pin: Option<String>,
pub adc_divider_measured: Option<u32>,
pub adc_divider_total: Option<u32>,
pub output: Option<Vec<OutputConfig>>,
pub firmware: Option<String>,
pub update_policy: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SerialConfig {
pub instance: String,
pub tx_pin: String,
pub rx_pin: String,
}
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct DurationMillis(#[serde(deserialize_with = "parse_duration_millis")] pub u64);
const fn default_true() -> bool {
true
}
const fn default_false() -> bool {
false
}
const fn default_pointing_report_hz() -> u16 {
125
}
fn parse_duration_millis<'de, D: de::Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
let input: String = de::Deserialize::deserialize(deserializer)?;
let num = input.trim_end_matches(|c: char| !c.is_numeric());
let unit = &input[num.len()..];
let num: u64 = num.parse().map_err(|_| {
de::Error::custom(format!(
"Invalid number \"{num}\" in duration: number part must be a u64"
))
})?;
match unit {
"s" => Ok(num * 1000),
"ms" => Ok(num),
other => Err(de::Error::custom(format!(
"Invalid duration unit \"{other}\": unit part must be either \"s\" or \"ms\""
))),
}
}
#[serde_inline_default]
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HostConfig {
#[serde_inline_default(true)]
pub vial_enabled: bool,
#[serde_inline_default(false)]
pub rynk_enabled: bool,
pub unlock_keys: Option<Vec<[u8; 2]>>,
#[serde(alias = "vial_insecure")]
#[serde_inline_default(false)]
pub insecure: bool,
#[serde_inline_default(false)]
pub write_requires_unlock: bool,
}
impl Default for HostConfig {
fn default() -> Self {
Self {
vial_enabled: true,
rynk_enabled: false,
unlock_keys: None,
insecure: false,
write_requires_unlock: false,
}
}
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct InputDeviceConfig {
pub encoder: Option<Vec<EncoderConfig>>,
pub pointing: Option<Vec<PointingDeviceConfig>>,
pub joystick: Option<Vec<JoystickConfig>>,
pub pmw3610: Option<Vec<Pmw3610Config>>,
pub pmw33xx: Option<Vec<Pmw33xxConfig>>,
pub iqs5xx: Option<Vec<Iqs5xxConfig>>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct JoystickConfig {
pub name: String,
pub id: Option<u8>,
pub pin_x: String,
pub pin_y: String,
pub pin_z: String,
pub transform: Vec<Vec<i16>>,
pub bias: Vec<i16>,
pub resolution: u16,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Pmw3610Config {
pub name: String,
pub id: Option<u8>,
pub spi: SpiConfig,
pub motion: Option<String>,
pub cpi: Option<u16>,
#[serde(default)]
pub invert_x: bool,
#[serde(default)]
pub invert_y: bool,
#[serde(default)]
pub swap_xy: bool,
#[serde(default)]
pub force_awake: bool,
#[serde(default)]
pub smart_mode: bool,
#[serde(default = "default_pointing_report_hz")]
pub report_hz: u16,
#[serde(default)]
pub proc_invert_x: bool,
#[serde(default)]
pub proc_invert_y: bool,
#[serde(default)]
pub proc_swap_xy: bool,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum Pmw33xxType {
#[default]
PMW3360,
PMW3389,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Pmw33xxConfig {
pub name: String,
pub id: Option<u8>,
pub sensor_type: Pmw33xxType,
pub spi: SpiConfig,
pub motion: Option<String>,
pub cpi: Option<u16>,
pub rot_trans_angle: Option<i8>,
pub liftoff_dist: Option<u8>,
#[serde(default)]
pub proc_invert_x: bool,
#[serde(default)]
pub proc_invert_y: bool,
#[serde(default)]
pub proc_swap_xy: bool,
#[serde(default = "default_pointing_report_hz")]
pub report_hz: u16,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Iqs5xxConfig {
pub name: String,
pub id: Option<u8>,
pub i2c: Iqs5xxI2cConfig,
pub rdy: Option<String>,
#[serde(default)]
pub proc_invert_x: bool,
#[serde(default)]
pub proc_invert_y: bool,
#[serde(default)]
pub proc_swap_xy: bool,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Iqs5xxI2cConfig {
pub instance: String,
pub sda: String,
pub scl: String,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EncoderConfig {
pub pin_a: String,
pub pin_b: String,
#[serde(default)]
pub phase: EncoderPhase,
pub resolution: Option<EncoderResolution>,
pub detent: Option<u8>,
pub pulse: Option<u8>,
pub reverse: Option<bool>,
#[serde(default = "default_false")]
pub internal_pullup: bool,
pub debounce_ms: Option<u16>,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum EncoderPhase {
#[default]
Default,
E8h7,
Resolution,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields, untagged)]
pub enum EncoderResolution {
Value(u8),
Derived { detent: u8, pulse: u8 },
}
impl Default for EncoderResolution {
fn default() -> Self {
Self::Value(4)
}
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PointingDeviceConfig {
pub interface: Option<CommunicationProtocol>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommunicationProtocol {
I2c(I2cConfig),
Spi(SpiConfig),
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SpiConfig {
pub instance: String,
pub sck: String,
pub mosi: String,
pub miso: String,
pub cs: Option<String>,
pub cpi: Option<u32>,
pub tx_dma: Option<String>,
pub rx_dma: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct I2cConfig {
pub instance: String,
pub sda: String,
pub scl: String,
#[serde(default = "default_i2c_address")]
pub address: u8,
}
const fn default_i2c_address() -> u8 {
0x3C
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DisplayDriver {
Ssd1306,
Sh1106,
Sh1107,
Sh1108,
Ssd1309,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DisplayConfig {
pub driver: DisplayDriver,
pub protocol: CommunicationProtocol,
pub size: String,
#[serde(default)]
pub rotation: u16,
pub renderer: Option<String>,
pub render_interval: Option<u64>,
pub min_render_interval: Option<u64>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OutputConfig {
pub pin: String,
#[serde(default)]
pub low_active: bool,
#[serde(default)]
pub initial_state_active: bool,
}
impl KeyboardTomlConfig {
pub(crate) fn get_output_config(&self) -> Result<Vec<OutputConfig>, String> {
let output_config = self.output.clone();
let split = self.split.clone();
match (output_config, split) {
(None, Some(s)) => Ok(s.central.output.unwrap_or_default()),
(Some(c), None) => Ok(c),
(None, None) => Ok(Default::default()),
_ => Err("Use [[split.output]] to define outputs for split in your keyboard.toml!".to_string()),
}
}
pub(crate) fn get_dependency_config(&self) -> DependencyConfig {
self.dependency.clone().unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_config_default_values() {
let config = EventConfig::default();
assert_eq!(config.keyboard.channel_size, 16);
assert_eq!(config.keyboard.pubs, 2);
assert_eq!(config.keyboard.subs, 3);
assert_eq!(config.modifier.channel_size, 8);
assert_eq!(config.modifier.pubs, 1);
assert_eq!(config.modifier.subs, 2);
assert_eq!(config.layer_change.channel_size, 1);
assert_eq!(config.layer_change.subs, 1);
assert_eq!(config.led_indicator.channel_size, 2);
assert_eq!(config.led_indicator.pubs, 2);
assert_eq!(config.led_indicator.subs, 3);
assert_eq!(config.pointing.channel_size, 8);
assert_eq!(config.pointing.subs, 2);
assert_eq!(config.action.channel_size, 16);
assert_eq!(config.action.pubs, 1);
assert_eq!(config.action.subs, 0);
}
#[test]
fn test_event_config_user_override() {
let user_toml = r#"
[event.keyboard]
channel_size = 32
"#;
let config: KeyboardTomlConfig = Config::builder()
.add_source(File::from_str(EVENT_DEFAULT_CONFIG, FileFormat::Toml))
.add_source(File::from_str(user_toml, FileFormat::Toml))
.build()
.unwrap()
.try_deserialize()
.unwrap();
assert_eq!(config.event.keyboard.channel_size, 32);
assert_eq!(config.event.keyboard.pubs, 2);
assert_eq!(config.event.keyboard.subs, 3);
assert_eq!(config.event.modifier.channel_size, 8);
assert_eq!(config.event.modifier.subs, 2);
assert_eq!(config.event.layer_change.subs, 1);
}
#[test]
fn rmk_count_limits_fit_u8_capability_fields() {
let ok: KeyboardTomlConfig = toml::from_str(
r#"
[rmk]
combo_max_num = 255
morse_max_num = 255
fork_max_num = 255
"#,
)
.unwrap();
assert_eq!(ok.rmk.combo_max_num, 255);
assert_eq!(ok.rmk.morse_max_num, 255);
assert_eq!(ok.rmk.fork_max_num, 255);
for (field, message) in [
("combo_max_num", "combo_max_num must be between 0 and 255"),
("morse_max_num", "morse_max_num must be between 0 and 255"),
("fork_max_num", "fork_max_num must be between 0 and 255"),
] {
let toml = format!("[rmk]\n{field} = 256\n");
let err = toml::from_str::<KeyboardTomlConfig>(&toml).unwrap_err();
assert!(err.to_string().contains(message), "{err}");
}
}
#[test]
fn test_event_config_partial_override_with_event_defaults_loader() {
let user_toml = r#"
[event.layer_change]
subs = 2
"#;
let path = std::env::temp_dir().join(format!(
"rmk-event-defaults-loader-{}-{}.toml",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::write(&path, user_toml).unwrap();
let config = KeyboardTomlConfig::new_from_toml_path_with_event_defaults(&path);
std::fs::remove_file(path).unwrap();
assert_eq!(config.event.layer_change.channel_size, 1);
assert_eq!(config.event.layer_change.pubs, 2);
assert_eq!(config.event.layer_change.subs, 2);
}
}