use std::collections::HashMap;
use std::path::Path;
use config::{Config, File, FileFormat};
use serde::{Deserialize as SerdeDeserialize, de};
use serde_derive::Deserialize;
use serde_inline_default::serde_inline_default;
pub mod chip;
pub mod communication;
pub mod keyboard;
#[rustfmt::skip]
pub mod usb_interrupt_map;
pub mod behavior;
pub mod board;
pub mod host;
pub mod keycode_alias;
pub mod layout;
pub mod light;
pub mod storage;
pub use board::{BoardConfig, UniBodyConfig};
pub use chip::{ChipModel, ChipSeries};
pub use communication::{CommunicationConfig, UsbInfo};
pub use keyboard::Basic;
pub use keycode_alias::KEYCODE_ALIAS;
#[derive(Clone, Debug, Deserialize)]
#[allow(unused)]
pub struct KeyboardTomlConfig {
keyboard: Option<KeyboardInfo>,
matrix: Option<MatrixConfig>,
aliases: Option<HashMap<String, String>>,
layer: Option<Vec<LayerTomlConfig>>,
layout: Option<LayoutTomlConfig>,
behavior: Option<BehaviorConfig>,
light: Option<LightConfig>,
storage: Option<StorageConfig>,
ble: Option<BleConfig>,
chip: Option<HashMap<String, ChipConfig>>,
dependency: Option<DependencyConfig>,
split: Option<SplitConfig>,
input_device: Option<InputDeviceConfig>,
output: Option<Vec<OutputConfig>>,
pub host: Option<HostConfig>,
#[serde(default)]
pub rmk: RmkConstantsConfig,
}
impl KeyboardTomlConfig {
pub fn new_from_toml_path<P: AsRef<Path>>(config_toml_path: P) -> Self {
let user_config = match std::fs::read_to_string(config_toml_path.as_ref()) {
Ok(s) => match toml::from_str::<KeyboardTomlConfig>(&s) {
Ok(c) => c,
Err(e) => panic!("Parse {:?} error: {}", config_toml_path.as_ref(), e.message()),
},
Err(e) => panic!("Read keyboard config file {:?} error: {}", config_toml_path.as_ref(), e),
};
let default_config_str = user_config.get_chip_model().unwrap().get_default_config_str().unwrap();
let mut config: KeyboardTomlConfig = Config::builder()
.add_source(File::from_str(default_config_str, FileFormat::Toml))
.add_source(File::with_name(config_toml_path.as_ref().to_str().unwrap()))
.build()
.unwrap()
.try_deserialize()
.unwrap();
config.auto_calculate_parameters();
config
}
pub 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());
}
}
}
}
#[serde_inline_default]
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RmkConstantsConfig {
#[serde_inline_default(20)]
pub mouse_key_interval: u32,
#[serde_inline_default(80)]
pub mouse_wheel_interval: u32,
#[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(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 event_channel_size: usize,
#[serde_inline_default(16)]
pub controller_channel_size: usize,
#[serde_inline_default(12)]
pub controller_channel_pubs: usize,
#[serde_inline_default(8)]
pub controller_channel_subs: usize,
#[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(1)]
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,
}
fn check_combo_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: de::Deserializer<'de>,
{
let value = SerdeDeserialize::deserialize(deserializer)?;
if value > 256 {
panic!("❌ Parse `keyboard.toml` error: combo_max_num must be between 0 and 256, got {value}");
}
Ok(value)
}
fn check_morse_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: de::Deserializer<'de>,
{
let value = SerdeDeserialize::deserialize(deserializer)?;
if value > 256 {
panic!("❌ Parse `keyboard.toml` error: morse_max_num must be between 0 and 256, 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 = SerdeDeserialize::deserialize(deserializer)?;
if !(4..=65536).contains(&value) {
panic!("❌ Parse `keyboard.toml` error: max_patterns_per_key must be between 4 and 65566, got {value}");
}
Ok(value)
}
fn check_fork_max_num<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: de::Deserializer<'de>,
{
let value = SerdeDeserialize::deserialize(deserializer)?;
if value > 256 {
panic!("❌ Parse `keyboard.toml` error: fork_max_num must be between 0 and 256, 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,
max_patterns_per_key: 8,
macro_space_size: 256,
debounce_time: 20,
event_channel_size: 16,
controller_channel_size: 16,
controller_channel_pubs: 12,
controller_channel_subs: 8,
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,
}
}
}
#[derive(Clone, Debug, Deserialize)]
#[allow(unused)]
pub struct LayoutTomlConfig {
pub rows: u8,
pub cols: u8,
pub layers: u8,
pub keymap: Option<Vec<Vec<Vec<String>>>>, pub matrix_map: Option<String>, pub encoder_map: Option<Vec<Vec<[String; 2]>>>, }
#[derive(Clone, Debug, Deserialize)]
#[allow(unused)]
pub struct LayerTomlConfig {
pub name: Option<String>,
pub keys: String,
pub encoders: Option<Vec<[String; 2]>>,
}
#[derive(Clone, Debug, Default, Deserialize)]
pub 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)]
#[allow(non_camel_case_types)]
pub enum MatrixType {
#[default]
normal,
direct_pin,
}
#[derive(Clone, Debug, Default, Deserialize)]
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,
pub debouncer: Option<String>,
}
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub 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, 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 ble_use_2m_phy: Option<bool>,
}
#[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<String>,
}
#[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 }
}
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LayoutConfig {
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 struct BehaviorConfig {
pub tri_layer: Option<TriLayerConfig>,
pub one_shot: Option<OneShotConfig>,
pub combo: Option<CombosConfig>,
#[serde(alias = "macro")]
pub macros: Option<MacrosConfig>,
pub fork: Option<ForksConfig>,
pub morse: Option<MorsesConfig>,
}
#[derive(Clone, Debug, Deserialize, Default)]
pub struct MorseProfile {
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>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TriLayerConfig {
pub upper: u8,
pub lower: u8,
pub adjust: u8,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OneShotConfig {
pub timeout: Option<DurationMillis>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CombosConfig {
pub combos: Vec<ComboConfig>,
pub timeout: Option<DurationMillis>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct ComboConfig {
pub actions: Vec<String>,
pub output: String,
pub layer: Option<u8>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MacrosConfig {
pub macros: Vec<MacroConfig>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct MacroConfig {
pub operations: Vec<MacroOperation>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "operation", rename_all = "lowercase")]
pub 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 struct ForksConfig {
pub forks: Vec<ForkConfig>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub 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 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 profiles: Option<HashMap<String, MorseProfile>>,
pub morses: Option<Vec<MorseConfig>>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub 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 struct MorseActionPair {
pub pattern: String, pub action: String, }
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SplitConfig {
pub connection: String,
pub central: SplitBoardConfig,
pub peripheral: Vec<SplitBoardConfig>,
}
#[allow(unused)]
#[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 output: Option<Vec<OutputConfig>>,
}
#[derive(Clone, Debug, Default, Deserialize)]
pub struct SerialConfig {
pub instance: String,
pub tx_pin: String,
pub rx_pin: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct DurationMillis(#[serde(deserialize_with = "parse_duration_millis")] pub u64);
const fn default_true() -> bool {
true
}
const fn default_false() -> bool {
false
}
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 struct HostConfig {
#[serde_inline_default(true)]
pub vial_enabled: bool,
pub unlock_keys: Option<Vec<[u8; 2]>>,
}
impl Default for HostConfig {
fn default() -> Self {
Self {
vial_enabled: true,
unlock_keys: None,
}
}
}
#[derive(Clone, Debug, Default, Deserialize)]
#[allow(unused)]
#[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>>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[allow(unused)]
#[serde(deny_unknown_fields)]
pub struct JoystickConfig {
pub name: String,
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)]
#[allow(unused)]
#[serde(deny_unknown_fields)]
pub struct Pmw3610Config {
pub name: String,
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,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[allow(unused)]
#[serde(deny_unknown_fields)]
pub struct EncoderConfig {
pub pin_a: String,
pub pin_b: String,
pub phase: Option<String>,
pub resolution: Option<EncoderResolution>,
pub detent: Option<u8>,
pub pulse: Option<u8>,
pub reverse: Option<bool>,
#[serde(default = "default_false")]
pub internal_pullup: bool,
}
#[derive(Clone, Debug, Deserialize)]
#[allow(unused)]
#[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)]
#[allow(unused)]
#[serde(deny_unknown_fields)]
pub struct PointingDeviceConfig {
pub interface: Option<CommunicationProtocol>,
}
#[derive(Clone, Debug, Deserialize)]
#[allow(unused)]
pub enum CommunicationProtocol {
I2c(I2cConfig),
Spi(SpiConfig),
}
#[derive(Clone, Debug, Default, Deserialize)]
#[allow(unused)]
pub struct SpiConfig {
pub instance: String,
pub sck: String,
pub mosi: String,
pub miso: String,
pub cs: Option<String>,
pub cpi: Option<u32>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[allow(unused)]
pub struct I2cConfig {
pub instance: String,
pub sda: String,
pub scl: String,
pub address: u8,
}
#[allow(unused)]
#[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 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()),
}
}
}