Send key press events to slint
This commit is contained in:
parent
26c213043f
commit
24b4aa449b
3
firmware2/Cargo.lock
generated
3
firmware2/Cargo.lock
generated
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CriticalSectionRawMutex, KeyMessage, 16> = Channel::new();
|
||||
|
||||
pub struct KeyMessage {
|
||||
pub keysym: Keysym,
|
||||
pub string: Option<String>,
|
||||
}
|
||||
|
||||
#[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<Output = ()> {
|
||||
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::<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();
|
||||
|
||||
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<String>)> = 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<Self>;
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
match k {
|
||||
$(
|
||||
$(Keysym::$xkb => Some(Key::$name),)*
|
||||
)*
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
i_slint_common::for_each_special_keys!(declare_consts_for_special_keys);
|
||||
|
|
|
|||
|
|
@ -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<CriticalSectionRawMutex, Report, 16> = 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<CriticalSectionRawMutex, ()> = 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<CriticalSectionRawMutex, Report, 16> = 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::<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;
|
||||
#[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<String>)> = 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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue