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}