From 24b4aa449baeaac7e293082d6c184438112d9208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Sat, 10 Jan 2026 19:21:13 +0100 Subject: [PATCH] Send key press events to slint --- firmware2/Cargo.lock | 3 + firmware2/Cargo.toml | 1 + firmware2/build.rs | 7 +- firmware2/src/keymap.rs | 242 +++++++++++++++++++++++++++++++++- firmware2/src/main.rs | 253 ++++-------------------------------- firmware2/src/ui/backend.rs | 27 +++- firmware2/src/ui/mod.rs | 35 ++++- 7 files changed, 328 insertions(+), 240 deletions(-) diff --git a/firmware2/Cargo.lock b/firmware2/Cargo.lock index 60d4dca..c366752 100644 --- a/firmware2/Cargo.lock +++ b/firmware2/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ "esp-rtos", "esp-storage", "gix", + "i-slint-common", "itertools 0.14.0", "json", "lazy_static", @@ -6063,8 +6064,10 @@ dependencies = [ "proc-macro2", "quote", "rmk-config", + "rmk-types", "serde", "serde_derive", + "strum", "syn 2.0.114", "toml", ] diff --git a/firmware2/Cargo.toml b/firmware2/Cargo.toml index a87d01a..3522ec4 100644 --- a/firmware2/Cargo.toml +++ b/firmware2/Cargo.toml @@ -69,6 +69,7 @@ paste = "1.0.15" itertools = { version = "0.14.0", default-features = false } bytemuck = "1.24.0" slint = { version = "1.14.1", default-features = false, features = ["compat-1-2", "libm", "log", "unsafe-single-threaded", "renderer-software"]} +i-slint-common = "1.14.1" critical-section = "1.2.0" cfg-if = "1.0.4" xkbcommon = { git = "https://github.com/Limeth/xkbcommon-rs", branch = "esp32s3", default-features = false, features = ["c-lib-wrap"] } diff --git a/firmware2/build.rs b/firmware2/build.rs index c72083d..d1425a6 100644 --- a/firmware2/build.rs +++ b/firmware2/build.rs @@ -98,9 +98,9 @@ fn generate_vial_config() { writeln!(out_file, "{}", const_declarations).unwrap(); - writeln!(out_file, "#[repr(u16)] pub enum CustomKeycodes {{").unwrap(); + writeln!(out_file, "#[repr(u8)] pub enum CustomKeycodes {{").unwrap(); - const CUSTOM_KEYCODE_FIRST: u16 = 0x840; + // const CUSTOM_KEYCODE_FIRST: u16 = 0x840; #[allow(clippy::collapsible_if)] if let JsonValue::Object(vial_cfg) = vial_cfg { @@ -116,7 +116,8 @@ fn generate_vial_config() { out_file, " {} = {},", name, - CUSTOM_KEYCODE_FIRST + index as u16 + // CUSTOM_KEYCODE_FIRST + index as u16 + index as u8 ) .unwrap(); } diff --git a/firmware2/src/keymap.rs b/firmware2/src/keymap.rs index f19ea5a..a6e67e1 100644 --- a/firmware2/src/keymap.rs +++ b/firmware2/src/keymap.rs @@ -1,10 +1,35 @@ -use rmk::types::action::KeyAction; +use core::alloc::Layout; +use core::convert::TryFrom; +use core::slice; + +use alloc::boxed::Box; +use alloc::string::String; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::Instant; +use esp_alloc::MemoryCapability; +use log::{debug, info, warn}; +use rmk::descriptor::KeyboardReport; +use rmk::futures::FutureExt; +use rmk::hid::Report; +use rmk::types::action::{Action, KeyAction}; use rmk::{a, k, layer}; +use slint::platform::Key; +use xkbcommon::xkb::{self, FeedResult, KeyDirection, Keysym, Status}; use crate::matrix::{MATRIX_COLS, MATRIX_ROWS}; +use crate::vial::CustomKeycodes; +use crate::{KEYBOARD_REPORT_PROXY, PSRAM_ALLOCATOR}; pub const NUM_LAYER: usize = 1; +pub static KEY_MESSAGE_CHANNEL: Channel = Channel::new(); + +pub struct KeyMessage { + pub keysym: Keysym, + pub string: Option, +} + #[rustfmt::skip] pub const fn get_default_keymap() -> [[[KeyAction; MATRIX_COLS]; MATRIX_ROWS]; NUM_LAYER] { [ @@ -12,9 +37,218 @@ pub const fn get_default_keymap() -> [[[KeyAction; MATRIX_COLS]; MATRIX_ROWS]; N [k!(Escape), k!(Kc1), k!(Kc2), k!(Kc3), k!(Kc4), k!(Kc5), k!(Kc6), k!(Kc7), k!(Kc8), k!(Kc9), k!(Kc0), k!(Backspace)], [k!(Tab), k!(Q), k!(W), k!(E), k!(R), k!(T), k!(Z), k!(U), k!(I), k!(O), k!(P), k!(Delete)], [k!(LCtrl), k!(A), k!(S), k!(D), k!(F), k!(G), k!(H), k!(J), k!(K), k!(L), k!(Comma), k!(Enter)], - [k!(LShift), k!(Y), k!(X), k!(C), k!(V), k!(B), k!(N), k!(M), a!(No), a!(No), k!(Up), k!(RShift)], - //[a!(No), a!(No), k!(LGui), k!(LAlt), KeyAction::Single(Action::TriLayerLower), k!(Space), k!(Space), KeyAction::Single(Action::TriLayerLower), k!(RAlt), k!(Left), k!(Down), k!(Right)] - [a!(No), a!(No), k!(LGui), k!(LAlt), k!(TriLayerLower), k!(Space), k!(Space), k!(TriLayerLower), k!(RAlt), k!(Left), k!(Down), k!(Right)] + [k!(LShift), k!(Y), k!(X), k!(C), k!(V), k!(B), k!(N), k!(M), a!(No), a!(No), k!(Up), KeyAction::Single(Action::User(CustomKeycodes::FOCUS_LCD as u8))], + [a!(No), a!(No), k!(LGui), k!(LAlt), KeyAction::Single(Action::TriLayerLower), k!(Space), k!(Space), KeyAction::Single(Action::TriLayerLower), k!(RAlt), k!(Left), k!(Down), k!(Right)] + // [a!(No), a!(No), k!(LGui), k!(LAlt), k!(TriLayerLower), k!(Space), k!(Space), k!(TriLayerLower), k!(RAlt), k!(Left), k!(Down), k!(Right)] ]) ] } + +pub fn create_hid_report_interceptor() -> impl Future { + const KEYMAP_STRING: &str = include_str!("../keymaps/cz.xkb"); + const COMPOSE_MAP_STRING: &str = include_str!("../compose/cs_CZ Compose.txt"); + const COMPOSE_MAP_LOCALE: &str = "cs_CZ.UTF-8"; + + let keymap_string_buffer = unsafe { + let allocation = PSRAM_ALLOCATOR.alloc_caps( + MemoryCapability::External.into(), + Layout::from_size_align(KEYMAP_STRING.len(), 32).unwrap(), + ); + let slice = str::from_utf8_unchecked_mut(slice::from_raw_parts_mut( + allocation, + KEYMAP_STRING.len(), + )); + + slice + .as_bytes_mut() + .copy_from_slice(KEYMAP_STRING.as_bytes()); + + Box::from_raw_in(slice as *mut str, &PSRAM_ALLOCATOR) + }; + let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + info!("Loading XKB keymap..."); + let instant_start = Instant::now(); + let keymap = xkb::Keymap::new_from_string( + &context, + keymap_string_buffer, + xkb::KEYMAP_FORMAT_TEXT_V1, + xkb::KEYMAP_COMPILE_NO_FLAGS, + ) + .unwrap(); + let duration = Instant::now().duration_since(instant_start); + info!( + "XKB keymap loaded successfully! Took {seconds}.{millis:03} seconds.", + seconds = duration.as_secs(), + millis = duration.as_millis() % 1_000 + ); + info!("Loading XKB compose map..."); + let instant_start = Instant::now(); + let compose_table = xkb::compose::Table::new_from_buffer( + &context, + COMPOSE_MAP_STRING, + COMPOSE_MAP_LOCALE, + xkb::compose::FORMAT_TEXT_V1, + xkb::compose::COMPILE_NO_FLAGS, + ) + .unwrap(); + let duration = Instant::now().duration_since(instant_start); + info!( + "XKB compose map loaded successfully! Took {seconds}.{millis:03} seconds.", + seconds = duration.as_secs(), + millis = duration.as_millis() % 1_000 + ); + let mut state = xkb::State::new(&keymap); + let mut previous_state = KeyboardReport::default(); + let mut compose_state = xkb::compose::State::new(&compose_table, xkb::compose::STATE_NO_FLAGS); + + async move { + loop { + let report = KEYBOARD_REPORT_PROXY.receive().await; + + if let Report::KeyboardReport(report) = &report { + const KEYCODES_LEN_MODIFIERS: usize = 8; + const KEYCODES_LEN_REGULAR: usize = 6; + const KEYCODES_LEN: usize = KEYCODES_LEN_MODIFIERS + KEYCODES_LEN_REGULAR; + + let mut pressed_keys = rmk::heapless::Vec::::new(); + let mut released_keys = rmk::heapless::Vec::::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(); + + for keycode in released_keys { + debug!("Release: 0x{:02x} ({})", keycode, keycode); + state.update_key(into_xkb_keycode(keycode), KeyDirection::Up); + } + + for keycode in pressed_keys { + debug!("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)> = match compose_state.feed(sym) { + FeedResult::Ignored => { + let string = state.key_get_utf8(keycode_xkb); + Some((sym, Some(string))) + } + FeedResult::Accepted => { + let status = compose_state.status(); + debug!("Compose status: {status:?}"); + match status { + Status::Nothing => { + let string = state.key_get_utf8(keycode_xkb); + Some((sym, Some(string))) + } + 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, + } + } + }; + + if let Some((keysym, string)) = result { + // Change `Some("")` into `None`. + let string = string.filter(|string| !string.is_empty()); + + info!("Keysym: {keysym:?}"); + info!("String: {:?}", string.as_ref()); + + KEY_MESSAGE_CHANNEL + .send(KeyMessage { keysym, string }) + .await; + state.update_key(keycode_xkb, KeyDirection::Down); + } + } + } + } + } +} + +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; + #[rustfmt::skip] + const HID_KEYBOARD: [u8; 256] = [ + 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, + 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, + 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 87, 88, 99, 70, 119, 110, 102, 104, 111, 107, 109, 106, + 105, 108, 103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, + 72, 73, 82, 83, 86, 127, 116, 117, 183, 184, 185, 186, 187, 188, 189, 190, + 191, 192, 193, 194, 134, 138, 130, 132, 128, 129, 131, 137, 133, 135, 136, 113, + 115, 114, UNK, UNK, UNK, 121, UNK, 89, 93, 124, 92, 94, 95, UNK, UNK, UNK, + 122, 123, 90, 91, 85, UNK, UNK, UNK, UNK, UNK, UNK, UNK, 111, UNK, UNK, UNK, + UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, + UNK, UNK, UNK, UNK, UNK, UNK, 179, 180, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, + UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, + UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, 111, UNK, UNK, UNK, UNK, UNK, UNK, UNK, + 29, 42, 56, 125, 97, 54, 100, 126, 164, 166, 165, 163, 161, 115, 114, 113, + 150, 158, 159, 128, 136, 177, 178, 176, 142, 152, 173, 140, UNK, UNK, UNK, UNK, + ]; + // https://cgit.freedesktop.org/xorg/driver/xf86-input-evdev/tree/src/evdev.c#n73 + const MIN_KEYCODE: u8 = 8; + + // 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) +} + +pub trait TryFromKeysym: Sized { + fn try_from_keysym(k: Keysym) -> Option; +} + +macro_rules! declare_consts_for_special_keys { + ($($_char:literal # $name:ident # $($_qt:ident)|* # $($_winit:ident $(($_pos:ident))?)|* # $($xkb:ident)|*;)*) => { + impl TryFromKeysym for Key { + fn try_from_keysym(k: Keysym) -> Option { + match k { + $( + $(Keysym::$xkb => Some(Key::$name),)* + )* + _ => None + } + } + } + }; +} + +i_slint_common::for_each_special_keys!(declare_consts_for_special_keys); diff --git a/firmware2/src/main.rs b/firmware2/src/main.rs index 0a21255..c40b1da 100644 --- a/firmware2/src/main.rs +++ b/firmware2/src/main.rs @@ -18,6 +18,7 @@ extern crate alloc; use core::alloc::Layout; use core::cell::RefCell; use core::slice; +use core::sync::atomic::{AtomicBool, Ordering}; use alloc::boxed::Box; use alloc::string::String; @@ -56,7 +57,7 @@ use rmk::join_all; use rmk::keyboard::Keyboard; use rmk::storage::async_flash_wrapper; use rmk::types::action::{Action, KeyAction}; -use rmk::types::keycode::KeyCode; +use rmk::types::keycode::{HidKeyCode, KeyCode}; use rmk::{initialize_keymap_and_storage, run_devices, run_rmk}; use slint::ComponentHandle; use slint::platform::software_renderer::Rgb565Pixel; @@ -65,6 +66,7 @@ use ui::AppWindow; use xkbcommon::xkb::{self, FeedResult, KeyDirection, Keysym, Status}; use {esp_alloc as _, esp_backtrace as _}; +use crate::keymap::{KEY_MESSAGE_CHANNEL, hid_report, run_hid_report_interceptor}; use crate::matrix::IoeMatrix; use crate::peripherals::st7701s::St7701s; use crate::ui::backend::{FramebufferPtr, SlintBackend}; @@ -108,6 +110,9 @@ const FRAME_DURATION_MIN: Duration = Duration::from_millis(100); // 10 FPS pub static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); +static KEYBOARD_REPORT_PROXY: Channel = Channel::new(); +static LCD_ENABLED: AtomicBool = AtomicBool::new(false); + /// Used to signal that MCU is ready to submit the framebuffer to the LCD. static SIGNAL_LCD_SUBMIT: Signal = Signal::new(); @@ -387,207 +392,13 @@ async fn main(_spawner: Spawner) { window: RefCell::new(None), framebuffer: framebuffer_ptr, }; - spawner.must_spawn(run_renderer_task(slint_backend)); + spawner.must_spawn(ui::run_renderer_task(slint_backend)); }); }, ); info!("Second core started!"); - let hid_report_proxy_task = { - static KEYBOARD_REPORT_PROXY: Channel = Channel::new(); - - { - *rmk::channel::KEYBOARD_REPORT_SENDER.write().await = &KEYBOARD_REPORT_PROXY; - } - - const KEYMAP_STRING: &str = include_str!("../keymaps/cz.xkb"); - let keymap_string_buffer = unsafe { - let allocation = PSRAM_ALLOCATOR.alloc_caps( - MemoryCapability::External.into(), - Layout::from_size_align(KEYMAP_STRING.len(), 32).unwrap(), - ); - let slice = str::from_utf8_unchecked_mut(slice::from_raw_parts_mut( - allocation, - KEYMAP_STRING.len(), - )); - - slice - .as_bytes_mut() - .copy_from_slice(KEYMAP_STRING.as_bytes()); - - Box::from_raw_in(slice as *mut str, &PSRAM_ALLOCATOR) - }; - let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); - info!("Loading XKB keymap..."); - let instant_start = Instant::now(); - let keymap = xkb::Keymap::new_from_string( - &context, - keymap_string_buffer, - xkb::KEYMAP_FORMAT_TEXT_V1, - xkb::KEYMAP_COMPILE_NO_FLAGS, - ) - .unwrap(); - let duration = Instant::now().duration_since(instant_start); - info!( - "XKB keymap loaded successfully! Took {seconds}.{millis:03} seconds.", - seconds = duration.as_secs(), - millis = duration.as_millis() % 1_000 - ); - info!("Loading XKB compose map..."); - let instant_start = Instant::now(); - const COMPOSE_MAP_STRING: &str = include_str!("../compose/cs_CZ Compose.txt"); - const COMPOSE_MAP_LOCALE: &str = "cs_CZ.UTF-8"; - let compose_table = xkb::compose::Table::new_from_buffer( - &context, - COMPOSE_MAP_STRING, - COMPOSE_MAP_LOCALE, - xkb::compose::FORMAT_TEXT_V1, - xkb::compose::COMPILE_NO_FLAGS, - ) - .unwrap(); - let duration = Instant::now().duration_since(instant_start); - info!( - "XKB compose map loaded successfully! Took {seconds}.{millis:03} seconds.", - seconds = duration.as_secs(), - millis = duration.as_millis() % 1_000 - ); - let mut state = xkb::State::new(&keymap); - let mut previous_state = KeyboardReport::default(); - let mut compose_state = - xkb::compose::State::new(&compose_table, xkb::compose::STATE_NO_FLAGS); - - async move { - loop { - let report = KEYBOARD_REPORT_PROXY.receive().await; - - if let Report::KeyboardReport(report) = &report { - const KEYCODES_LEN_MODIFIERS: usize = 8; - const KEYCODES_LEN_REGULAR: usize = 6; - const KEYCODES_LEN: usize = KEYCODES_LEN_MODIFIERS + KEYCODES_LEN_REGULAR; - - let mut pressed_keys = rmk::heapless::Vec::::new(); - let mut released_keys = rmk::heapless::Vec::::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; - #[rustfmt::skip] - const HID_KEYBOARD: [u8; 256] = [ - 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, - 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, - 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, - 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 87, 88, 99, 70, 119, 110, 102, 104, 111, 107, 109, 106, - 105, 108, 103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, - 72, 73, 82, 83, 86, 127, 116, 117, 183, 184, 185, 186, 187, 188, 189, 190, - 191, 192, 193, 194, 134, 138, 130, 132, 128, 129, 131, 137, 133, 135, 136, 113, - 115, 114, UNK, UNK, UNK, 121, UNK, 89, 93, 124, 92, 94, 95, UNK, UNK, UNK, - 122, 123, 90, 91, 85, UNK, UNK, UNK, UNK, UNK, UNK, UNK, 111, UNK, UNK, UNK, - UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, - UNK, UNK, UNK, UNK, UNK, UNK, 179, 180, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, - UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, - UNK, UNK, UNK, UNK, UNK, UNK, UNK, UNK, 111, UNK, UNK, UNK, UNK, UNK, UNK, UNK, - 29, 42, 56, 125, 97, 54, 100, 126, 164, 166, 165, 163, 161, 115, 114, 113, - 150, 158, 159, 128, 136, 177, 178, 176, 142, 152, 173, 140, UNK, UNK, UNK, UNK, - ]; - // https://cgit.freedesktop.org/xorg/driver/xf86-input-evdev/tree/src/evdev.c#n73 - const MIN_KEYCODE: u8 = 8; - - // 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) - } - - for keycode in released_keys { - warn!("Release: 0x{:02x} ({})", keycode, keycode); - state.update_key(into_xkb_keycode(keycode), KeyDirection::Up); - } - - 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)> = match compose_state.feed(sym) - { - FeedResult::Ignored => { - let string = state.key_get_utf8(keycode_xkb); - Some((sym, Some(string))) - } - 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))) - } - 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, - } - } - }; - - 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); - } - } - } - - rmk::channel::KEYBOARD_REPORT_RECEIVER.send(report).await; - } - } - }; - let mut user_controller = UserController::new(); info!("Awaiting on all tasks..."); @@ -610,7 +421,7 @@ async fn main(_spawner: Spawner) { &mut storage, rmk_config, ), - hid_report_proxy_task, + hid_report(), user_controller.event_loop(), alt_uart_rx_task ] @@ -633,13 +444,26 @@ impl Controller for UserController { type Event = ControllerEvent; async fn process_event(&mut self, event: Self::Event) { - if let ControllerEvent::Key(keyboard_event, KeyAction::Single(Action::Key(keycode))) = event - && (KeyCode::Kb0..=KeyCode::User31).contains(&keycode) + if let ControllerEvent::Key( + keyboard_event, + KeyAction::Single(Action::User(user_key_index)), + ) = event { - warn!("{keycode:?} pressed."); + if user_key_index == CustomKeycodes::FOCUS_LCD as u8 && keyboard_event.pressed { + let enabled = !LCD_ENABLED.fetch_xor(true, Ordering::SeqCst); - if keycode as u16 == CustomKeycodes::FOCUS_LCD as u16 { - warn!("Focus LCD pressed."); + match enabled { + false => { + info!("Disabling LCD."); + *rmk::channel::KEYBOARD_REPORT_SENDER.write().await = + &rmk::channel::KEYBOARD_REPORT_RECEIVER; + } + true => { + info!("Enabling LCD."); + *rmk::channel::KEYBOARD_REPORT_SENDER.write().await = + &KEYBOARD_REPORT_PROXY; + } + } } } } @@ -649,31 +473,6 @@ impl Controller for UserController { } } -#[embassy_executor::task] -async fn run_renderer_task(backend: SlintBackend) { - slint::platform::set_platform(Box::new(backend)).expect("backend already initialized"); - - let main = AppWindow::new().unwrap(); - - // Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve - // async by having only one iteration of the loop run, and `await`ing here. - // The following block is analogous to `main.run()`. - { - main.show().unwrap(); - - loop { - slint::run_event_loop().unwrap(); - SIGNAL_LCD_SUBMIT.signal(()); - #[cfg(feature = "limit-fps")] - embassy_time::Timer::after(FRAME_DURATION_MIN).await; - SIGNAL_UI_RENDER.wait().await; - } - - #[expect(unreachable_code)] - main.hide().unwrap(); - } -} - struct Framebuffer { width: u32, height: u32, diff --git a/firmware2/src/ui/backend.rs b/firmware2/src/ui/backend.rs index 757ead3..36e1242 100644 --- a/firmware2/src/ui/backend.rs +++ b/firmware2/src/ui/backend.rs @@ -4,11 +4,15 @@ use alloc::rc::Rc; use esp_hal::time::Instant; use log::info; use slint::{ - PhysicalSize, WindowSize, - platform::software_renderer::{ - RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer, + PhysicalSize, SharedString, WindowSize, + platform::{ + Key, WindowEvent, + software_renderer::{RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer}, }, }; +use xkbcommon::xkb; + +use crate::keymap::{KEY_MESSAGE_CHANNEL, TryFromKeysym}; use super::window_adapter::SoftwareWindowAdapter; @@ -54,8 +58,21 @@ impl slint::platform::Platform for SlintBackend { /* loop */ { if let Some(window) = self.window.borrow().clone() { - // TODO: Event dispatch. Here or in `run_renderer_task`? - // window.try_dispatch_event(todo!())?; + // Handle key presses + while let Ok(key_message) = KEY_MESSAGE_CHANNEL.try_receive() { + let text = key_message.string.map(SharedString::from).or_else(|| { + Key::try_from_keysym(key_message.keysym).map(SharedString::from) + }); + + if let Some(text) = text { + window + .try_dispatch_event(WindowEvent::KeyPressed { text: text.clone() }) + .unwrap(); + window + .try_dispatch_event(WindowEvent::KeyPressed { text }) + .unwrap(); + } + } window.draw_if_needed(|renderer| { // TODO: Proper synchronization. diff --git a/firmware2/src/ui/mod.rs b/firmware2/src/ui/mod.rs index 06ad418..08abcb3 100644 --- a/firmware2/src/ui/mod.rs +++ b/firmware2/src/ui/mod.rs @@ -1,6 +1,39 @@ // #![cfg_attr(not(feature = "simulator"), no_main)] -pub mod window_adapter; +use alloc::boxed::Box; + +#[cfg(feature = "limit-fps")] +use crate::FRAME_DURATION_MIN; +use crate::{ + SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, keymap::KEY_MESSAGE_CHANNEL, ui::backend::SlintBackend, +}; + pub mod backend; +pub mod window_adapter; slint::include_modules!(); + +#[embassy_executor::task] +pub async fn run_renderer_task(backend: SlintBackend) { + slint::platform::set_platform(Box::new(backend)).expect("backend already initialized"); + + let main = AppWindow::new().unwrap(); + + // Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve + // async by having only one iteration of the loop run, and `await`ing here. + // The following block is analogous to `main.run()`. + { + main.show().unwrap(); + + loop { + slint::run_event_loop().unwrap(); + SIGNAL_LCD_SUBMIT.signal(()); + #[cfg(feature = "limit-fps")] + embassy_time::Timer::after(FRAME_DURATION_MIN).await; + SIGNAL_UI_RENDER.wait().await; + } + + #[expect(unreachable_code)] + main.hide().unwrap(); + } +}