Compose key handling

This commit is contained in:
Jakub Hlusička 2026-01-07 05:11:58 +01:00
parent 1f21d29bd2
commit 97f330c7b9

View file

@ -20,6 +20,7 @@ use core::cell::RefCell;
use core::slice;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec;
use cfg_if::cfg_if;
use embassy_executor::Spawner;
@ -61,7 +62,7 @@ use slint::ComponentHandle;
use slint::platform::software_renderer::Rgb565Pixel;
use static_cell::StaticCell;
use ui::AppWindow;
use xkbcommon::xkb::{self, KeyDirection, ModIndex};
use xkbcommon::xkb::{self, FeedResult, KeyDirection, Keysym, Status};
use {esp_alloc as _, esp_backtrace as _};
use crate::matrix::IoeMatrix;
@ -461,13 +462,50 @@ async fn main(_spawner: Spawner) {
let report = KEYBOARD_REPORT_PROXY.receive().await;
if let Report::KeyboardReport(report) = &report {
warn!("MODIFIERS = {:02x}", report.modifier);
const RMK_ALT: u8 = 0xE2;
// TODO: Process modifiers
const KEYCODES_LEN_MODIFIERS: usize = 8;
const KEYCODES_LEN_REGULAR: usize = 6;
const KEYCODES_LEN: usize = KEYCODES_LEN_MODIFIERS + KEYCODES_LEN_REGULAR;
for (keycode_old, &keycode_new) in
core::iter::zip(&mut previous_state.keycodes, &report.keycodes)
let mut pressed_keys = rmk::heapless::Vec::<u8, KEYCODES_LEN>::new();
let mut released_keys = rmk::heapless::Vec::<u8, KEYCODES_LEN>::new();
let pressed_mods_bits = !previous_state.modifier & report.modifier;
let released_mods_bits = previous_state.modifier & !report.modifier;
for index in 0..KEYCODES_LEN_MODIFIERS {
const USB_HID_LEFT_CTRL: u8 = 0xE0;
let mod_bit = 1_u8 << index;
let mod_keycode = USB_HID_LEFT_CTRL + index as u8;
if pressed_mods_bits & mod_bit != 0 {
pressed_keys.push(mod_keycode).unwrap();
}
if released_mods_bits & mod_bit != 0 {
released_keys.push(mod_keycode).unwrap();
}
}
// TODO: This currently depends on pressed keys not changing position in the array.
// Should be made independent of that.
for (&keycode_old, &keycode_new) in
core::iter::zip(&previous_state.keycodes, &report.keycodes)
{
if keycode_old == keycode_new {
continue;
}
if keycode_old != 0 {
released_keys.push(keycode_old).unwrap();
}
if keycode_new != 0 {
pressed_keys.push(keycode_new).unwrap();
}
}
previous_state = report.clone();
fn into_xkb_keycode(rmk_keycode: u8) -> xkb::Keycode {
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/hid/hid-input.c?id=refs/tags/v6.18#n27
const UNK: u8 = 240;
@ -495,40 +533,53 @@ async fn main(_spawner: Spawner) {
// TODO: The combination of these two operations should be precomputed
// in a const expr into a single look-up table.
xkb::Keycode::new(
(HID_KEYBOARD[rmk_keycode as usize] + MIN_KEYCODE) as u32,
)
xkb::Keycode::new((HID_KEYBOARD[rmk_keycode as usize] + MIN_KEYCODE) as u32)
}
if *keycode_old == 0 && keycode_new == 0 {
continue;
for keycode in released_keys {
warn!("Release: 0x{:02x} ({})", keycode, keycode);
state.update_key(into_xkb_keycode(keycode), KeyDirection::Up);
}
if keycode_new == 0 || ((*keycode_old != 0) && *keycode_old != keycode_new)
for keycode in pressed_keys {
warn!("Pressed: 0x{:02x} ({})", keycode, keycode);
let keycode_xkb = into_xkb_keycode(keycode);
let sym = state.key_get_one_sym(keycode_xkb);
let result: Option<(Keysym, Option<String>)> = match compose_state.feed(sym)
{
warn!("Release: 0x{:02x} ({})", *keycode_old, *keycode_old);
state.update_key(into_xkb_keycode(*keycode_old), KeyDirection::Up);
FeedResult::Ignored => {
let string = state.key_get_utf8(keycode_xkb);
Some((sym, Some(string)))
}
if *keycode_old == 0 || ((keycode_new != 0) && *keycode_old != keycode_new)
{
let keycode_new_xkb = into_xkb_keycode(keycode_new);
let syms = state.key_get_syms(keycode_new_xkb);
for sym in syms {
compose_state.feed(*sym);
FeedResult::Accepted => {
let status = compose_state.status();
warn!("Compose status: {status:?}");
match status {
Status::Nothing => {
let string = state.key_get_utf8(keycode_xkb);
Some((sym, Some(string)))
}
let string = compose_state.utf8();
warn!("Pressed: 0x{:02x} ({})", keycode_new, keycode_new);
warn!("Syms: {syms:?}");
warn!("String: {string:?}");
state.update_key(keycode_new_xkb, KeyDirection::Down);
Status::Composing => None,
Status::Composed => {
let sym = compose_state.keysym().unwrap_or_default();
let string = compose_state.utf8().unwrap_or_default();
Some((sym, Some(string)))
}
Status::Cancelled => None,
}
}
};
*keycode_old = keycode_new;
if let Some((sym, string)) = result {
// Change `Some("")` into `None`.
let string = string.filter(|string| !string.is_empty());
warn!("Sym: {sym:?}");
warn!("String: {:?}", string.as_ref());
state.update_key(keycode_xkb, KeyDirection::Down);
}
}
}