Skip to content

Commit

Permalink
feat!(rpt-any): repeat one-shotted chord (#641)
Browse files Browse the repository at this point in the history
Implements #596. This commit adds unsafe code to deal with
lifetime/reference shenanigans in keyberon.
  • Loading branch information
jtroo authored Nov 24, 2023
1 parent a870431 commit 0764b79
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 8 deletions.
75 changes: 67 additions & 8 deletions keyberon/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
/// to do when not using a macro.
pub use kanata_keyberon_macros::*;

use crate::action::*;
use crate::key_code::KeyCode;
use crate::{action::*, multikey_buffer::MultiKeyBuffer};
use arraydeque::ArrayDeque;
use heapless::Vec;

Expand Down Expand Up @@ -84,6 +84,7 @@ where
pub action_queue: ActionQueue<'a, T>,
pub rpt_action: Option<&'a Action<'a, T>>,
pub historical_keys: ArrayDeque<[KeyCode; 8], arraydeque::behavior::Wrapping>,
rpt_multikey_key_buffer: MultiKeyBuffer<'a, T>,
}

/// An event on the key matrix.
Expand Down Expand Up @@ -222,6 +223,18 @@ impl<'a, T: 'a> State<'a, T> {
_ => None,
}
}
fn keycode_in_coords(&self, coords: &OneShotCoords) -> Option<KeyCode> {
match self {
NormalKey { keycode, coord, .. } => {
if coords.contains(coord) {
Some(*keycode)
} else {
None
}
}
_ => None,
}
}
fn tick(&self) -> Option<Self> {
Some(*self)
}
Expand Down Expand Up @@ -680,6 +693,8 @@ impl<'a, T: std::fmt::Debug> WaitingState<'a, T> {
}
}

type OneShotCoords = ArrayDeque<[KCoord; ONE_SHOT_MAX_ACTIVE], arraydeque::behavior::Wrapping>;

#[derive(Debug, Copy, Clone)]
pub struct SequenceState<'a, T: 'a> {
cur_event: Option<SequenceEvent<'a, T>>,
Expand Down Expand Up @@ -730,9 +745,10 @@ impl OneShotState {
}
}

fn handle_press(&mut self, key: OneShotHandlePressKey) {
fn handle_press(&mut self, key: OneShotHandlePressKey) -> OneShotCoords {
let mut oneshot_coords = ArrayDeque::new();
if self.keys.is_empty() {
return;
return oneshot_coords;
}
match key {
OneShotHandlePressKey::OneShotKey(pressed_coord) => {
Expand All @@ -743,6 +759,7 @@ impl OneShotState {
) && self.keys.contains(&pressed_coord)
{
self.release_on_next_tick = true;
oneshot_coords.extend(self.keys.iter().copied());
}
self.released_keys.retain(|coord| *coord != pressed_coord);
}
Expand All @@ -755,8 +772,10 @@ impl OneShotState {
} else {
let _ = self.other_pressed_keys.push_back(pressed_coord);
}
oneshot_coords.extend(self.keys.iter().copied());
}
}
};
oneshot_coords
}

/// Returns true if the caller should handle the release normally and false otherwise.
Expand Down Expand Up @@ -857,6 +876,7 @@ impl<'a, const C: usize, const R: usize, const L: usize, T: 'a + Copy + std::fmt
action_queue: ArrayDeque::new(),
rpt_action: None,
historical_keys: ArrayDeque::new(),
rpt_multikey_key_buffer: unsafe { MultiKeyBuffer::new() },
}
}
/// Iterates on the key codes of the current state.
Expand Down Expand Up @@ -1331,11 +1351,29 @@ impl<'a, const C: usize, const R: usize, const L: usize, T: 'a + Copy + std::fmt
keycode,
flags: NormalKeyFlags(0),
});
let mut oneshot_coords = ArrayDeque::new();
if !is_oneshot {
self.oneshot
oneshot_coords = self
.oneshot
.handle_press(OneShotHandlePressKey::Other(coord));
}
self.rpt_action = Some(action);
if oneshot_coords.is_empty() {
self.rpt_action = Some(action);
} else {
self.rpt_action = None;
unsafe {
self.rpt_multikey_key_buffer.clear();
for kc in self
.states
.iter()
.filter_map(|kc| State::keycode_in_coords(kc, &oneshot_coords))
{
self.rpt_multikey_key_buffer.push(kc);
}
self.rpt_multikey_key_buffer.push(keycode);
self.rpt_action = Some(self.rpt_multikey_key_buffer.get_ref());
}
}
}
&MultipleKeyCodes(v) => {
self.last_press_tracker.coord = coord;
Expand All @@ -1359,11 +1397,32 @@ impl<'a, const C: usize, const R: usize, const L: usize, T: 'a + Copy + std::fmt
}),
});
}

let mut oneshot_coords = ArrayDeque::new();
if !is_oneshot {
self.oneshot
oneshot_coords = self
.oneshot
.handle_press(OneShotHandlePressKey::Other(coord));
}
self.rpt_action = Some(action);
if oneshot_coords.is_empty() {
self.rpt_action = Some(action);
} else {
self.rpt_action = None;
unsafe {
self.rpt_multikey_key_buffer.clear();
for kc in self
.states
.iter()
.filter_map(|s| s.keycode_in_coords(&oneshot_coords))
{
self.rpt_multikey_key_buffer.push(kc);
}
for &keycode in *v {
self.rpt_multikey_key_buffer.push(keycode);
}
self.rpt_action = Some(&*self.rpt_multikey_key_buffer.get_ref());
}
}
}
&MultipleActions(v) => {
self.last_press_tracker.coord = coord;
Expand Down
1 change: 1 addition & 0 deletions keyberon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
pub mod action;
pub mod key_code;
pub mod layout;
mod multikey_buffer;
84 changes: 84 additions & 0 deletions keyberon/src/multikey_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! Module for `MultiKeyBuffer`.
use std::ptr::null_mut;
use std::{array, slice};

use crate::action::{Action, ONE_SHOT_MAX_ACTIVE};
use crate::key_code::KeyCode;

// Presumably this should be plenty.
// ONE_SHOT_MAX_ACTIVE is already likely unreasonably large enough.
// This buffer capacity adds more onto that,
// just in case somebody finds a way to use all of the one-shot capacity.
const BUFCAP: usize = ONE_SHOT_MAX_ACTIVE + 4;

/// This is an unsafe container that enables a mutable Action::MultipleKeyCodes.
pub(crate) struct MultiKeyBuffer<'a, T> {
buf: [KeyCode; BUFCAP],
size: usize,
ptr: *mut &'static [KeyCode],
ac: *mut Action<'a, T>,
}

unsafe impl<'a, T> Send for MultiKeyBuffer<'a, T> {}

impl<'a, T> MultiKeyBuffer<'a, T> {
/// Create a new instance of `MultiKeyBuffer`.
///
/// # Safety
///
/// The program should not have any references to the inner buffer when the struct is dropped.
pub(crate) unsafe fn new() -> Self {
Self {
buf: array::from_fn(|_| KeyCode::Escape),
size: 0,
ptr: Box::leak(Box::new(slice::from_raw_parts(null_mut(), 0))),
ac: Box::leak(Box::new(Action::NoOp)),
}
}

/// Set the current size of the buffer to zero.
///
/// # Safety
///
/// The program should not have any references to the inner buffer.
pub(crate) unsafe fn clear(&mut self) {
self.size = 0;
}

/// Push to the end of the buffer. If the buffer is full, this silently fails.
///
/// # Safety
///
/// The program should not have any references to the inner buffer.
pub(crate) unsafe fn push(&mut self, kc: KeyCode) {
if self.size < BUFCAP {
self.buf[self.size] = kc;
self.size += 1;
}
}

/// Get a reference to the inner buffer in the form of an `Action`.
/// The `Action` will be the variant `MultipleKeyCodes`,
/// containing all keys that have been pushed.
///
/// # Safety
///
/// The program should not have any references to the inner buffer before calling.
/// The program should not mutate the buffer after calling this function until after the returned reference is dropped.
pub(crate) unsafe fn get_ref(&self) -> &'a Action<'a, T> {
*self.ac = Action::NoOp;
*self.ptr = slice::from_raw_parts(self.buf.as_ptr(), self.size);
*self.ac = Action::MultipleKeyCodes(&*self.ptr);
&*self.ac
}
}

impl<'a, T> Drop for MultiKeyBuffer<'a, T> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.ac));
drop(Box::from_raw(self.ptr));
}
}
}

0 comments on commit 0764b79

Please sign in to comment.