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}