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 layers supported by the [crate::key::layered] implementation.
91    pub const LAYERED_LAYER_COUNT: usize = 8;
92
93    /// Number of conditional layer rules for the [crate::key::layered] implementation.
94    ///
95    /// Generous default for the full-system / cucumber shell; per-keymap codegen
96    /// uses the exact rule count from the keymap.
97    pub const CONDITIONAL_LAYER_COUNT: usize = 4;
98
99    /// The maximum number of keys in a chord.
100    pub const CHORDED_MAX_CHORD_SIZE: usize = 16;
101
102    /// The maximum number of chords.
103    pub const CHORDED_MAX_CHORDS: usize = 4;
104
105    /// The maximum number of overlapping chords for a chorded key.
106    pub const CHORDED_MAX_OVERLAPPING_CHORD_SIZE: usize = 16;
107
108    /// The maximum number of steps in a sequence.
109    pub const SEQUENCE_MAX_SEQUENCE_LEN: usize = 8;
110
111    /// The maximum number of sequences.
112    pub const SEQUENCE_MAX_SEQUENCES: usize = 16;
113
114    /// The maximum number of sequences sharing a primary key.
115    pub const SEQUENCE_MAX_OVERLAPPING: usize = 4;
116
117    /// The tap-dance definitions.
118    pub const TAP_DANCE_MAX_DEFINITIONS: usize = 3;
119
120    /// Trivial composite shell: keyboard family only (matches codegen shape).
121    pub mod key_system {
122        use smart_keymap::key;
123        use smart_keymap::keymap;
124
125        /// Aggregate key reference.
126        #[derive(serde::Deserialize, Debug, Clone, Copy, PartialEq)]
127        pub enum Ref {
128            /// [smart_keymap::key::keyboard] variant.
129            Keyboard(smart_keymap::key::keyboard::Ref),
130        }
131
132        /// Aggregate config (no configurable families in this default map).
133        #[derive(serde::Deserialize, Debug, Clone, Copy, PartialEq)]
134        pub struct Config {}
135
136        impl Default for Config {
137            fn default() -> Self {
138                Self::new()
139            }
140        }
141
142        impl Config {
143            /// Constructs a new [Config] with defaults.
144            pub const fn new() -> Self {
145                Self {}
146            }
147        }
148
149        /// Aggregate context.
150        #[derive(Debug, Clone, Copy)]
151        pub struct Context {
152            keymap_context: smart_keymap::keymap::KeymapContext,
153            keyboard: smart_keymap::key::keyboard::Context,
154        }
155
156        impl Context {
157            /// Constructs a [Context] from the given [Config].
158            pub const fn from_config(config: Config) -> Self {
159                let _ = &config;
160                Self {
161                    keymap_context: smart_keymap::keymap::KeymapContext::new(),
162                    keyboard: smart_keymap::key::keyboard::Context,
163                }
164            }
165        }
166
167        impl Default for Context {
168            fn default() -> Self {
169                Self::from_config(Config::new())
170            }
171        }
172
173        impl key::Context for Context {
174            type Event = Event;
175
176            fn handle_event(
177                &mut self,
178                _event: key::Event<Self::Event>,
179            ) -> key::KeyEvents<Self::Event> {
180                key::KeyEvents::no_events()
181            }
182
183            fn reset(&mut self) {
184                self.keymap_context = smart_keymap::keymap::KeymapContext::new();
185                self.keyboard.reset();
186            }
187        }
188
189        impl keymap::SetKeymapContext for Context {
190            fn set_keymap_context(&mut self, context: keymap::KeymapContext) {
191                self.keymap_context = context;
192            }
193        }
194
195        impl keymap::ReportHints for Context {}
196
197        /// Aggregate event.
198        #[derive(Debug, Clone, Copy, PartialEq)]
199        pub enum Event {
200            /// [smart_keymap::key::keyboard] variant.
201            Keyboard(smart_keymap::key::keyboard::Event),
202        }
203
204        impl From<smart_keymap::key::keyboard::Event> for Event {
205            fn from(v: smart_keymap::key::keyboard::Event) -> Self {
206                Event::Keyboard(v)
207            }
208        }
209
210        impl TryFrom<Event> for smart_keymap::key::keyboard::Event {
211            type Error = smart_keymap::key::EventError;
212
213            fn try_from(v: Event) -> Result<Self, Self::Error> {
214                match v {
215                    Event::Keyboard(v) => Ok(v),
216                }
217            }
218        }
219
220        /// Aggregate pending key state.
221        #[derive(Debug, Clone, PartialEq)]
222        #[allow(clippy::large_enum_variant)]
223        pub enum PendingKeyState {
224            /// [smart_keymap::key::keyboard] variant.
225            Keyboard(smart_keymap::key::keyboard::PendingKeyState),
226        }
227
228        impl From<smart_keymap::key::keyboard::PendingKeyState> for PendingKeyState {
229            fn from(pks: smart_keymap::key::keyboard::PendingKeyState) -> Self {
230                PendingKeyState::Keyboard(pks)
231            }
232        }
233
234        /// Aggregate key state.
235        #[derive(Debug, Clone, Copy, PartialEq)]
236        pub enum KeyState {
237            /// No-op key state (e.g. auxiliary chorded keys).
238            NoOp,
239            /// [smart_keymap::key::keyboard] key state.
240            Keyboard(smart_keymap::key::keyboard::KeyState),
241        }
242
243        impl From<key::NoOpKeyState> for KeyState {
244            fn from(_: key::NoOpKeyState) -> Self {
245                KeyState::NoOp
246            }
247        }
248
249        impl From<smart_keymap::key::keyboard::KeyState> for KeyState {
250            fn from(ks: smart_keymap::key::keyboard::KeyState) -> Self {
251                KeyState::Keyboard(ks)
252            }
253        }
254
255        /// Aggregate [key::System] for the default keyboard-only map.
256        #[derive(Debug, Clone, Copy, PartialEq)]
257        pub struct System {
258            keyboard:
259                smart_keymap::key::keyboard::System<Ref, [smart_keymap::key::keyboard::Key; 0]>,
260        }
261
262        impl System {
263            /// Constructs the system from the keyboard subsystem.
264            pub const fn new(
265                keyboard: smart_keymap::key::keyboard::System<
266                    Ref,
267                    [smart_keymap::key::keyboard::Key; 0],
268                >,
269            ) -> Self {
270                Self { keyboard }
271            }
272        }
273
274        impl key::System<Ref> for System {
275            type Ref = Ref;
276            type Context = Context;
277            type Event = Event;
278            type PendingKeyState = PendingKeyState;
279            type KeyState = KeyState;
280
281            fn new_pressed_key(
282                &self,
283                keymap_index: u16,
284                context: &Self::Context,
285                key_ref: Ref,
286            ) -> (
287                key::PressedKeyResult<Ref, Self::PendingKeyState, Self::KeyState>,
288                key::KeyEvents<Self::Event>,
289            ) {
290                match key_ref {
291                    Ref::Keyboard(key_ref) => {
292                        let (pkr, pke) =
293                            self.keyboard
294                                .new_pressed_key(keymap_index, &context.keyboard, key_ref);
295                        (pkr.into_result(), pke.into_events())
296                    }
297                }
298            }
299
300            fn update_pending_state(
301                &self,
302                pending_state: &mut Self::PendingKeyState,
303                keymap_index: u16,
304                context: &Self::Context,
305                key_ref: Ref,
306                event: key::Event<Self::Event>,
307            ) -> (Option<key::NewPressedKey<Ref>>, key::KeyEvents<Self::Event>) {
308                let _ = (pending_state, keymap_index, context, key_ref, event);
309                panic!("no pending key systems in this key_system")
310            }
311
312            fn update_state(
313                &self,
314                key_state: &mut Self::KeyState,
315                key_ref: &Self::Ref,
316                context: &Self::Context,
317                keymap_index: u16,
318                event: key::Event<Self::Event>,
319            ) -> key::KeyEvents<Self::Event> {
320                match (key_ref, key_state) {
321                    (Ref::Keyboard(key_ref), KeyState::Keyboard(key_state)) => {
322                        if let Ok(event) = event.try_into_key_event() {
323                            self.keyboard
324                                .update_state(
325                                    key_state,
326                                    key_ref,
327                                    &context.keyboard,
328                                    keymap_index,
329                                    event,
330                                )
331                                .into_events()
332                        } else {
333                            key::KeyEvents::no_events()
334                        }
335                    }
336                    (_, _) => key::KeyEvents::no_events(),
337                }
338            }
339
340            fn key_output(
341                &self,
342                key_ref: &Self::Ref,
343                key_state: &Self::KeyState,
344            ) -> Option<key::KeyOutput> {
345                match (key_ref, key_state) {
346                    (Ref::Keyboard(r), KeyState::Keyboard(ks)) => self.keyboard.key_output(r, ks),
347                    (_, _) => None,
348                }
349            }
350        }
351    }
352
353    pub use key_system::Context;
354    pub use key_system::Event;
355    pub use key_system::KeyState;
356    pub use key_system::PendingKeyState;
357    pub use key_system::Ref;
358    pub use key_system::System;
359
360    /// The number of keys in the keymap.
361    pub const KEY_COUNT: usize = 1;
362
363    /// Without a custom keymap, just the letter 'A'.
364    pub const KEY_REFS: [Ref; KEY_COUNT] = [Ref::Keyboard(
365        smart_keymap::key::keyboard::Ref::KeyCode(0x04),
366    )];
367
368    /// Config used to construct initial context.
369    pub const CONFIG: key_system::Config = key_system::Config::new();
370
371    /// Initial [Context] value.
372    pub const CONTEXT: Context = key_system::Context::from_config(CONFIG);
373
374    /// Initial [System] value.
375    pub const SYSTEM: System =
376        key_system::System::new(smart_keymap::key::keyboard::System::new([]));
377
378    /// Alias for the [crate::keymap::Keymap] type.
379    pub type Keymap = smart_keymap::keymap::Keymap<
380        [Ref; KEY_COUNT],
381        Ref,
382        Context,
383        Event,
384        PendingKeyState,
385        KeyState,
386        System,
387    >;
388}
389
390#[cfg(custom_keymap)]
391include!(concat!(env!("OUT_DIR"), "/keymap.rs"));
392
393pub use init::{Keymap, CONTEXT, KEY_REFS, SYSTEM};
394
395/// Constructs a new keymap.
396pub const fn new_keymap() -> Keymap {
397    Keymap::new(KEY_REFS, CONTEXT, SYSTEM)
398}