Skip to main content

smart_keymap/
lib.rs

1#![warn(missing_docs)]
2#![warn(clippy::unwrap_used)]
3#![warn(clippy::expect_used)]
4
5//! Smart Keymap library.
6//!
7//! A "smart keyboard" is a keyboard where the keys can perform
8//!  multiple actions, depending on the context.
9//! Features such as layering, tap-hold, and tap-dance are
10//!  examples of smart keyboard functionality.
11//!
12//! The smart keymap library provides an interface for the "smart keymap"
13//!  part of smart keyboard firmware.
14//! i.e. the part that takes key presses and releases as input,
15//!  and outputs HID keyboard reports (or other smart keyboard outputs).
16//!
17//! This crate can be used directly with Rust, or built as a C library.
18//!
19//! The engine implementation lives in [`smart_keymap_core`]; this package
20//! re-exports it and adds the per-build [`init`] keymap shell (dummy or
21//! codegen via `SMART_KEYMAP_CUSTOM_KEYMAP`).
22//!
23//! # Usage as a C library
24//!
25//! ## Custom Keymap
26//!
27//! When used as a C library, the library should be built by setting
28//!  the environment variable `SMART_KEYMAP_CUSTOM_KEYMAP` to the path
29//!  of a custom keymap file.
30//!
31//! `SMART_KEYMAP_CUSTOM_KEYMAP` can be set either to a `.ncl` file,
32//!  or to a `.rs` file (generated using the scripts under `ncl/`).
33//!
34//! ## Keyboard Firmware Implementation
35//!
36//! When used as a C library, the firmware should call to
37//!  `keymap_init`, `keymap_register_input_keypress`, `keymap_register_input_keyrelease`,
38//!  and `keymap_tick` functions.
39//! The `keymap_tick` function should be called every ms, and should copy the
40//!  HID keyboard report to the given buffer.
41//!
42//! # Implementation Overview
43//!
44//! The heart of the library is the [key] module,
45//! and its [key::System] trait.
46//!
47//! Per-keymap aggregation is produced by Nickel codegen
48//!  as `init::key_system` (custom keymap / `keymap!`).
49//! Without a custom keymap,
50//!  [init] provides a trivial keyboard-only shell
51//!  so [`new_keymap`] still type-checks.
52//!
53//! The full-profile, Vec-backed composite shell used by Cucumber and other
54//! std harnesses lives in the separate `smart-keymap-full-system-std` package
55//! so default `smart-keymap` builds (including `cargo doc`) do not require
56//! Nickel for the universal shell (custom keymap codegen still needs Nickel
57//! when pointing at a `.ncl` file).
58
59#![cfg_attr(not(feature = "std"), no_std)]
60
61// Re-export core engine modules so `smart_keymap::key::…` works for call sites
62// and for generated `init` / `key_system` (which use the crate name, not `crate::`).
63#[doc(inline)]
64pub use smart_keymap_core::input;
65#[doc(inline)]
66pub use smart_keymap_core::key;
67#[doc(inline)]
68pub use smart_keymap_core::keymap;
69#[doc(inline)]
70pub use smart_keymap_core::slice;
71#[doc(inline)]
72pub use smart_keymap_core::split;
73
74// Generated modules and the default `init` shell refer to engine paths as
75// `smart_keymap::…`. Inside this package that name is this crate.
76extern crate self as smart_keymap;
77
78/// Types and initial data used for constructing a [keymap::Keymap].
79///
80/// Without `SMART_KEYMAP_CUSTOM_KEYMAP`, this is a **keyboard-only** dummy map
81/// (letter `A`) plus generous size constants used by composite shells (also
82/// re-exported by `smart-keymap-full-system-std`). With a custom keymap, build
83/// codegen replaces this module.
84/// cbindgen:ignore
85#[cfg(not(custom_keymap))]
86pub mod init {
87    /// Number of instructions used by the [crate::key::automation] implementation.
88    pub const AUTOMATION_INSTRUCTION_COUNT: usize = 1024;
89
90    /// Number of alternate-repeat rules for the [crate::key::history] implementation.
91    ///
92    /// Generous default for the full-system / cucumber shell; per-keymap codegen
93    /// uses the exact rule count from the keymap.
94    pub const HISTORY_ALT_REPEAT_RULE_COUNT: usize = 64;
95
96    /// Number of layers supported by the [crate::key::layered] implementation.
97    pub const LAYERED_LAYER_COUNT: usize = 8;
98
99    /// Number of conditional layer rules for the [crate::key::layered] implementation.
100    ///
101    /// Generous default for the full-system / cucumber shell; per-keymap codegen
102    /// uses the exact rule count from the keymap.
103    pub const CONDITIONAL_LAYER_COUNT: usize = 4;
104
105    /// The maximum number of keys in a chord.
106    pub const CHORDED_MAX_CHORD_SIZE: usize = 16;
107
108    /// The maximum number of chords.
109    pub const CHORDED_MAX_CHORDS: usize = 4;
110
111    /// The maximum number of overlapping chords for a chorded key.
112    pub const CHORDED_MAX_OVERLAPPING_CHORD_SIZE: usize = 16;
113
114    /// The maximum number of steps in a sequence.
115    pub const SEQUENCE_MAX_SEQUENCE_LEN: usize = 8;
116
117    /// The maximum number of sequences.
118    pub const SEQUENCE_MAX_SEQUENCES: usize = 16;
119
120    /// The maximum number of sequences sharing a primary key.
121    pub const SEQUENCE_MAX_OVERLAPPING: usize = 4;
122
123    /// The tap-dance definitions.
124    pub const TAP_DANCE_MAX_DEFINITIONS: usize = 3;
125
126    /// Trivial composite shell: keyboard family only (matches codegen shape).
127    pub mod key_system {
128        use smart_keymap::key;
129        use smart_keymap::keymap;
130
131        /// Aggregate key reference.
132        #[derive(serde::Deserialize, Debug, Clone, Copy, PartialEq)]
133        pub enum Ref {
134            /// [smart_keymap::key::keyboard] variant.
135            Keyboard(smart_keymap::key::keyboard::Ref),
136        }
137
138        /// Aggregate config (no configurable families in this default map).
139        #[derive(serde::Deserialize, Debug, Clone, Copy, PartialEq)]
140        pub struct Config {}
141
142        impl Default for Config {
143            fn default() -> Self {
144                Self::new()
145            }
146        }
147
148        impl Config {
149            /// Constructs a new [Config] with defaults.
150            pub const fn new() -> Self {
151                Self {}
152            }
153        }
154
155        /// Aggregate context.
156        #[derive(Debug, Clone, Copy)]
157        pub struct Context {
158            keymap_context: smart_keymap::keymap::KeymapContext,
159            keyboard: smart_keymap::key::keyboard::Context,
160        }
161
162        impl Context {
163            /// Constructs a [Context] from the given [Config].
164            pub const fn from_config(config: Config) -> Self {
165                let _ = &config;
166                Self {
167                    keymap_context: smart_keymap::keymap::KeymapContext::new(),
168                    keyboard: smart_keymap::key::keyboard::Context,
169                }
170            }
171        }
172
173        impl Default for Context {
174            fn default() -> Self {
175                Self::from_config(Config::new())
176            }
177        }
178
179        impl key::Context for Context {
180            type Event = Event;
181
182            fn handle_event(
183                &mut self,
184                _event: key::Event<Self::Event>,
185            ) -> key::KeyEvents<Self::Event> {
186                key::KeyEvents::no_events()
187            }
188
189            fn reset(&mut self) {
190                self.keymap_context = smart_keymap::keymap::KeymapContext::new();
191                self.keyboard.reset();
192            }
193        }
194
195        impl keymap::SetKeymapContext for Context {
196            fn set_keymap_context(&mut self, context: keymap::KeymapContext) {
197                self.keymap_context = context;
198            }
199        }
200
201        impl keymap::ReportHints for Context {}
202
203        /// Aggregate event.
204        #[derive(Debug, Clone, Copy, PartialEq)]
205        pub enum Event {
206            /// [smart_keymap::key::keyboard] variant.
207            Keyboard(smart_keymap::key::keyboard::Event),
208        }
209
210        impl From<smart_keymap::key::keyboard::Event> for Event {
211            fn from(v: smart_keymap::key::keyboard::Event) -> Self {
212                Event::Keyboard(v)
213            }
214        }
215
216        impl TryFrom<Event> for smart_keymap::key::keyboard::Event {
217            type Error = smart_keymap::key::EventError;
218
219            fn try_from(v: Event) -> Result<Self, Self::Error> {
220                match v {
221                    Event::Keyboard(v) => Ok(v),
222                }
223            }
224        }
225
226        /// Aggregate pending key state.
227        #[derive(Debug, Clone, PartialEq)]
228        #[allow(clippy::large_enum_variant)]
229        pub enum PendingKeyState {
230            /// [smart_keymap::key::keyboard] variant.
231            Keyboard(smart_keymap::key::keyboard::PendingKeyState),
232        }
233
234        impl From<smart_keymap::key::keyboard::PendingKeyState> for PendingKeyState {
235            fn from(pks: smart_keymap::key::keyboard::PendingKeyState) -> Self {
236                PendingKeyState::Keyboard(pks)
237            }
238        }
239
240        /// Aggregate key state.
241        #[derive(Debug, Clone, Copy, PartialEq)]
242        pub enum KeyState {
243            /// No-op key state (e.g. auxiliary chorded keys).
244            NoOp,
245            /// [smart_keymap::key::keyboard] key state.
246            Keyboard(smart_keymap::key::keyboard::KeyState),
247        }
248
249        impl From<key::NoOpKeyState> for KeyState {
250            fn from(_: key::NoOpKeyState) -> Self {
251                KeyState::NoOp
252            }
253        }
254
255        impl From<smart_keymap::key::keyboard::KeyState> for KeyState {
256            fn from(ks: smart_keymap::key::keyboard::KeyState) -> Self {
257                KeyState::Keyboard(ks)
258            }
259        }
260
261        /// Aggregate [key::System] for the default keyboard-only map.
262        #[derive(Debug, Clone, Copy, PartialEq)]
263        pub struct System {
264            keyboard:
265                smart_keymap::key::keyboard::System<Ref, [smart_keymap::key::keyboard::Key; 0]>,
266        }
267
268        impl System {
269            /// Constructs the system from the keyboard subsystem.
270            pub const fn new(
271                keyboard: smart_keymap::key::keyboard::System<
272                    Ref,
273                    [smart_keymap::key::keyboard::Key; 0],
274                >,
275            ) -> Self {
276                Self { keyboard }
277            }
278        }
279
280        impl key::System<Ref> for System {
281            type Ref = Ref;
282            type Context = Context;
283            type Event = Event;
284            type PendingKeyState = PendingKeyState;
285            type KeyState = KeyState;
286
287            fn new_pressed_key(
288                &self,
289                keymap_index: u16,
290                context: &Self::Context,
291                key_ref: Ref,
292            ) -> (
293                key::PressedKeyResult<Ref, Self::PendingKeyState, Self::KeyState>,
294                key::KeyEvents<Self::Event>,
295            ) {
296                match key_ref {
297                    Ref::Keyboard(key_ref) => {
298                        let (pkr, pke) =
299                            self.keyboard
300                                .new_pressed_key(keymap_index, &context.keyboard, key_ref);
301                        (pkr.into_result(), pke.into_events())
302                    }
303                }
304            }
305
306            fn update_pending_state(
307                &self,
308                pending_state: &mut Self::PendingKeyState,
309                keymap_index: u16,
310                context: &Self::Context,
311                key_ref: Ref,
312                event: key::Event<Self::Event>,
313            ) -> (Option<key::NewPressedKey<Ref>>, key::KeyEvents<Self::Event>) {
314                let _ = (pending_state, keymap_index, context, key_ref, event);
315                panic!("no pending key systems in this key_system")
316            }
317
318            fn update_state(
319                &self,
320                key_state: &mut Self::KeyState,
321                key_ref: &Self::Ref,
322                context: &Self::Context,
323                keymap_index: u16,
324                event: key::Event<Self::Event>,
325            ) -> key::KeyEvents<Self::Event> {
326                match (key_ref, key_state) {
327                    (Ref::Keyboard(key_ref), KeyState::Keyboard(key_state)) => {
328                        if let Ok(event) = event.try_into_key_event() {
329                            self.keyboard
330                                .update_state(
331                                    key_state,
332                                    key_ref,
333                                    &context.keyboard,
334                                    keymap_index,
335                                    event,
336                                )
337                                .into_events()
338                        } else {
339                            key::KeyEvents::no_events()
340                        }
341                    }
342                    (_, _) => key::KeyEvents::no_events(),
343                }
344            }
345
346            fn key_output(
347                &self,
348                key_ref: &Self::Ref,
349                key_state: &Self::KeyState,
350            ) -> Option<key::KeyOutput> {
351                match (key_ref, key_state) {
352                    (Ref::Keyboard(r), KeyState::Keyboard(ks)) => self.keyboard.key_output(r, ks),
353                    (_, _) => None,
354                }
355            }
356        }
357    }
358
359    pub use key_system::Context;
360    pub use key_system::Event;
361    pub use key_system::KeyState;
362    pub use key_system::PendingKeyState;
363    pub use key_system::Ref;
364    pub use key_system::System;
365
366    /// The number of keys in the keymap.
367    pub const KEY_COUNT: usize = 1;
368
369    /// Without a custom keymap, just the letter 'A'.
370    pub const KEY_REFS: [Ref; KEY_COUNT] = [Ref::Keyboard(
371        smart_keymap::key::keyboard::Ref::KeyCode(0x04),
372    )];
373
374    /// Config used to construct initial context.
375    pub const CONFIG: key_system::Config = key_system::Config::new();
376
377    /// Initial [Context] value.
378    pub const CONTEXT: Context = key_system::Context::from_config(CONFIG);
379
380    /// Initial [System] value.
381    pub const SYSTEM: System =
382        key_system::System::new(smart_keymap::key::keyboard::System::new([]));
383
384    /// Alias for the [crate::keymap::Keymap] type.
385    pub type Keymap = smart_keymap::keymap::Keymap<
386        [Ref; KEY_COUNT],
387        Ref,
388        Context,
389        Event,
390        PendingKeyState,
391        KeyState,
392        System,
393    >;
394}
395
396#[cfg(custom_keymap)]
397include!(concat!(env!("OUT_DIR"), "/keymap.rs"));
398
399pub use init::{Keymap, CONTEXT, KEY_REFS, SYSTEM};
400
401/// Constructs a new keymap.
402pub const fn new_keymap() -> Keymap {
403    Keymap::new(KEY_REFS, CONTEXT, SYSTEM)
404}