rmk 0.8.3

Keyboard firmware written in Rust
Documentation
#[cfg(feature = "_ble")]
mod ble_config;
pub mod macro_config;

#[cfg(feature = "_ble")]
pub use ble_config::BleBatteryConfig;
use embassy_time::Duration;
use heapless::Vec;
use macro_config::KeyboardMacrosConfig;
use rmk_types::action::{MorseMode, MorseProfile};

use crate::combo::Combo;
use crate::fork::Fork;
use crate::morse::Morse;
use crate::{COMBO_MAX_NUM, FORK_MAX_NUM, MORSE_MAX_NUM};

/// Internal configurations for RMK keyboard.
#[derive(Default)]
pub struct RmkConfig<'a> {
    pub device_config: DeviceConfig<'a>,
    #[cfg(feature = "vial")]
    pub vial_config: VialConfig<'a>,
    #[cfg(feature = "storage")]
    pub storage_config: StorageConfig,
    #[cfg(feature = "_ble")]
    pub ble_battery_config: BleBatteryConfig<'a>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum Hand {
    Unknown,
    Left,
    Right,
}

impl Default for Hand {
    fn default() -> Self {
        Self::Unknown
    }
}

/// Config for configurable action behavior
#[derive(Debug, Default)]
pub struct BehaviorConfig {
    pub tri_layer: Option<[u8; 3]>,
    pub tap: TapConfig,
    pub one_shot: OneShotConfig,
    pub combo: CombosConfig,
    pub fork: ForksConfig,
    pub morse: MorsesConfig,
    pub keyboard_macros: KeyboardMacrosConfig,
    pub mouse_key: MouseKeyConfig,
}

/// Configurations for morse behavior
#[derive(Clone, Copy, Debug)]
pub struct TapConfig {
    // TODO: Use `Duration` instead?
    pub tap_interval: u16,
    pub tap_capslock_interval: u16,
}

impl Default for TapConfig {
    fn default() -> Self {
        Self {
            tap_interval: 20,
            tap_capslock_interval: 20,
        }
    }
}

/// Configuration for morse, tap dance, tap-hold and home row mods
#[derive(Clone, Debug)]
pub struct MorsesConfig {
    pub enable_flow_tap: bool,
    pub prior_idle_time: Duration, //used only when flow tap is enabled
    pub default_profile: MorseProfile,

    pub morses: Vec<Morse, MORSE_MAX_NUM>,
}

impl Default for MorsesConfig {
    fn default() -> Self {
        Self {
            enable_flow_tap: false,
            prior_idle_time: Duration::from_millis(120),
            default_profile: MorseProfile::new(Some(false), Some(MorseMode::Normal), Some(250u16), Some(250u16)),
            morses: Vec::new(),
        }
    }
}

/// Configuration that's only related to the key's position.
///
/// Now only the hand information is included.
/// In the future more fields can be added here for the future configurator GUI, such as
/// - physical key position and orientation
/// - key size,
/// - key shape,
/// - backlight sequence number, etc.
///
/// IDEA: For Keyboards with low memory, these should be compile time constants to save RAM?
#[derive(Debug)]
pub struct PositionalConfig<const ROW: usize, const COL: usize> {
    pub hand: [[Hand; COL]; ROW],
}

impl<const ROW: usize, const COL: usize> Default for PositionalConfig<ROW, COL> {
    fn default() -> Self {
        Self {
            hand: [[Hand::default(); COL]; ROW],
        }
    }
}

impl<const ROW: usize, const COL: usize> PositionalConfig<ROW, COL> {
    pub fn new(hand: [[Hand; COL]; ROW]) -> Self {
        Self { hand }
    }
}

/// Config for one shot behavior
#[derive(Clone, Copy, Debug)]
pub struct OneShotConfig {
    pub timeout: Duration,
}

impl Default for OneShotConfig {
    fn default() -> Self {
        Self {
            timeout: Duration::from_secs(1),
        }
    }
}

/// Config for combo behavior
#[derive(Clone, Debug)]
pub struct CombosConfig {
    pub combos: [Option<Combo>; COMBO_MAX_NUM],
    pub timeout: Duration,
}

impl Default for CombosConfig {
    fn default() -> Self {
        Self {
            timeout: Duration::from_millis(50),
            combos: [None; COMBO_MAX_NUM],
        }
    }
}

/// Config for fork behavior
#[derive(Clone, Debug)]
pub struct ForksConfig {
    pub forks: Vec<Fork, FORK_MAX_NUM>,
}

impl Default for ForksConfig {
    fn default() -> Self {
        Self { forks: Vec::new() }
    }
}

/// Config for storage
#[derive(Clone, Copy, Debug)]
pub struct StorageConfig {
    /// Start address of local storage, MUST BE start of a sector.
    /// If start_addr is set to 0(this is the default value), the last `num_sectors` sectors will be used.
    pub start_addr: usize,
    // Number of sectors used for storage, >= 2.
    pub num_sectors: u8,
    pub clear_storage: bool,
    pub clear_layout: bool,
}

impl Default for StorageConfig {
    fn default() -> Self {
        Self {
            start_addr: 0,
            num_sectors: 2,
            clear_storage: false,
            clear_layout: false,
        }
    }
}

/// Config for [vial](https://get.vial.today/).
///
/// You can generate automatically using [`build.rs`](https://github.com/HaoboGu/rmk/blob/main/examples/use_rust/stm32h7/build.rs).
#[derive(Clone, Copy, Debug, Default)]
pub struct VialConfig<'a> {
    pub vial_keyboard_id: &'a [u8],
    pub vial_keyboard_def: &'a [u8],
    pub unlock_keys: &'a [(u8, u8)],
}

impl<'a> VialConfig<'a> {
    pub fn new(vial_keyboard_id: &'a [u8], vial_keyboard_def: &'a [u8], unlock_keys: &'a [(u8, u8)]) -> Self {
        Self {
            vial_keyboard_id,
            vial_keyboard_def,
            unlock_keys,
        }
    }
}

/// Configurations for usb
#[derive(Clone, Copy, Debug)]
pub struct DeviceConfig<'a> {
    /// Vender id
    pub vid: u16,
    /// Product id
    pub pid: u16,
    /// Manufacturer
    pub manufacturer: &'a str,
    /// Product name
    pub product_name: &'a str,
    /// Serial number
    pub serial_number: &'a str,
}

impl Default for DeviceConfig<'_> {
    fn default() -> Self {
        Self {
            vid: 0x4c4b,
            pid: 0x4643,
            manufacturer: "RMK",
            product_name: "RMK Keyboard",
            serial_number: "vial:f64c2b3c:000001",
        }
    }
}

/// Config for mouse key behavior
#[derive(Clone, Copy, Debug)]
pub struct MouseKeyConfig {
    // Accelerated mode parameters
    /// Initial delay between pressing a movement key and first cursor movement (in milliseconds)
    pub initial_delay_ms: u16,
    /// Time between subsequent cursor movements in milliseconds
    pub repeat_interval_ms: u16,
    /// Step size for each movement
    pub move_delta: u8,
    /// Maximum cursor speed at which acceleration stops
    pub max_speed: u8,
    /// Number of repeated movements until maximum cursor speed is reached
    pub time_to_max: u8,
    /// Initial delay between pressing a wheel key and first wheel movement (in milliseconds)
    pub wheel_initial_delay_ms: u16,
    /// Time between subsequent wheel movements in milliseconds
    pub wheel_repeat_interval_ms: u16,
    /// Wheel movement step size
    pub wheel_delta: u8,
    /// Maximum wheel speed
    pub wheel_max_speed_multiplier: u8,
    /// Number of repeated movements until maximum wheel speed is reached
    pub wheel_time_to_max: u8,
    /// Maximum movement distance per report
    pub move_max: u8,
    /// Maximum wheel distance per report
    pub wheel_max: u8,
}

impl Default for MouseKeyConfig {
    fn default() -> Self {
        Self {
            // Optimized values for comfortable and responsive mouse movement
            initial_delay_ms: 100,         // 100ms initial delay
            repeat_interval_ms: 20,        // 20ms between movements
            move_delta: 6,                 // 6 pixels per movement (~300 px/sec)
            max_speed: 3,                  // Conservative max speed multiplier (300 -> 900 px/sec)
            time_to_max: 50,               // 1.0 second to max
            wheel_initial_delay_ms: 100,   // 100ms initial wheel delay
            wheel_repeat_interval_ms: 80,  // 80ms between wheel movements
            wheel_delta: 1,                // 1 wheel unit per movement
            wheel_max_speed_multiplier: 3, // Conservative wheel max speed
            wheel_time_to_max: 40,         // 0.5 second to max
            move_max: 20,                  // Maximum movement per report
            wheel_max: 4,                  // Maximum wheel movement per report
        }
    }
}

impl MouseKeyConfig {
    /// Get the appropriate delay for cursor movement based on repeat count
    pub fn get_movement_delay(&self, repeat_count: u8) -> u16 {
        if repeat_count == 0 {
            self.initial_delay_ms
        } else {
            self.repeat_interval_ms
        }
    }

    /// Get the appropriate delay for wheel movement based on repeat count
    pub fn get_wheel_delay(&self, repeat_count: u8) -> u16 {
        if repeat_count == 0 {
            self.wheel_initial_delay_ms
        } else {
            self.wheel_repeat_interval_ms
        }
    }
}