TeXitoi / keyberon

A rust crate to create a pure rust keyboard firmware.
MIT License
1.08k stars 79 forks source link

Example of layer tied to NumLock? #123

Closed Meptl closed 1 year ago

Meptl commented 1 year ago

Is there an example of having a layer that is tied to the numlock state?

I'm creating a keypad and would like to customize the behaviour of keys whilst NumLock is disabled. e.g. Kp2 behaves like the down arrow key whilst NumLock is disabled and I'd like it to behave like the up arrow key.

TeXitoi commented 1 year ago

There is no such example to my knowledge.

You can do this in several ways. I can imagine a few:

Meptl commented 1 year ago

I implemented your 2nd option as follows (with cortex-m-rtic 1.1.4). Click the bullet points for code sample.

Add a flag in the Leds struct to track num_lock state. We can't directly simulate keys here because we can't get a reference to the internal shared resource of rtic ```rust pub struct Leds { num_lock: PC13>, pub status: bool, } impl keyberon::keyboard::Leds for Leds { fn num_lock(&mut self, status: bool) { if status { self.num_lock.set_low(); } else { self.num_lock.set_high(); } self.status = status; } } ```
Add an rtic resource to track layout num_lock state. ```rust #[local] struct Local { num_lock_state: Option, } #[init] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { ... Local { num_lock_state: None, }, init::Monotonics(mono) ) } ```
Add an auxiliary layer in your keyboard layout to support layer switching. ```rust #[rustfmt::skip] pub static LAYERS: keyberon::layout::Layers<4, 5, 2, ()> = keyberon::layout::layout! { { [Kp1 Kp2 Kp3 NumLock], [Kp4 Kp5 Kp6 KpAsterisk], [Kp7 Kp8 Kp9 KpPlus], [BSpace Kp0 KpDot KpEnter], [{l(1)} t t t], } { [Home Up End NumLock], [Left Down Right KpSlash], [PgDown Down PgUp KpMinus], [Delete X KpEqual KpEnter], [{l(1)} t t t], } }; ```
In the usb tick loop, grab the Leds class num_lock state and simulate the layer button as needed. ```rust #[task(local = [ num_lock_state ])] fn tick(mut cx: tick::Context) { ... // Grab canonical num lock state let mut num_lock = false; cx.shared.usb_class.lock(|k| { num_lock = k.device_mut().leds_mut().status }); // Simulate the layer switch button as needed. This assumes the num-lock enabled layer is // not the default. if cx.local.num_lock_state.is_some() { if cx.local.num_lock_state.unwrap() != num_lock { *cx.local.num_lock_state = Some(num_lock); if num_lock { let _ = layout.event(Event::Release(4, 0)); } else { let _ = layout.event(Event::Press(4, 0)); } } } else { // First run. if !num_lock { let _ = layout.event(Event::Press(4, 0)); } *cx.local.num_lock_state = Some(num_lock); } // Send keyboard report. let report: KbHidReport = layout.keycodes().collect(); if cx.shared.usb_class.lock(|k| k.device_mut().set_keyboard_report(report.clone())) { while let Ok(0) = cx.shared.usb_class.lock(|k| k.write(report.as_bytes())) {} } } ```

I was finding calling layout.event(Event::Release(4,0)); or Event::Press every tick to not work, hence the need for the num_lock_state Local variable. But maybe I just had a bug.