From f5e04355d7c520a83f1dc088aa6eee29548ef1b5 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 7 Sep 2025 15:34:38 +0200 Subject: [PATCH] kbvm: use indicators to determine the active LEDs --- src/ei/ei_ifs/ei_keyboard.rs | 4 +- src/ifs/wl_seat.rs | 47 ++++++++---------- .../zwp_input_method_keyboard_grab_v2.rs | 2 +- .../zwp_virtual_keyboard_manager_v1.rs | 6 +-- src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs | 11 ++--- src/kbvm.rs | 36 ++++++++++++-- src/keyboard.rs | 48 ++++++++++++++----- src/state.rs | 4 +- 8 files changed, 101 insertions(+), 57 deletions(-) diff --git a/src/ei/ei_ifs/ei_keyboard.rs b/src/ei/ei_ifs/ei_keyboard.rs index b57d1581..bd2ab4a6 100644 --- a/src/ei/ei_ifs/ei_keyboard.rs +++ b/src/ei/ei_ifs/ei_keyboard.rs @@ -36,8 +36,8 @@ impl EiKeyboard { self.client.event(Keymap { self_id: self.id, keymap_type: KEYMAP_TYPE_XKB, - size: state.map.len as _, - keymap: state.map.map.clone(), + size: state.map.map.len as _, + keymap: state.map.map.map.clone(), }); } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 5af33bdd..84f6017d 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -24,7 +24,7 @@ pub mod zwp_virtual_keyboard_v1; use { crate::{ async_engine::SpawnedFuture, - backend::{KeyState, LED_CAPS_LOCK, LED_NUM_LOCK, Leds}, + backend::{KeyState, Leds}, client::{Client, ClientError, ClientId}, cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner}, ei::ei_ifs::ei_seat::EiSeat, @@ -75,7 +75,7 @@ use { xdg_toplevel_drag_v1::XdgToplevelDragV1, }, kbvm::{KbvmMap, KbvmMapId, KbvmState, PhysicalKeyboardState}, - keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd, ModifiersListener}, + keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd, LedsListener}, leaks::Tracker, object::{Object, Version}, rect::Rect, @@ -107,7 +107,7 @@ use { }, ahash::AHashMap, jay_config::keyboard::syms::{KeySym, SYM_Escape}, - kbvm::{Components, Keycode, ModifierMask}, + kbvm::Keycode, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, @@ -236,8 +236,8 @@ pub struct WlSeatGlobal { focus_history_same_workspace: Cell, mark_mode: Cell>, marks: CopyHashMap>, - modifiers_listener: EventListener, - modifiers_forward: EventSource, + modifiers_listener: EventListener, + modifiers_forward: EventSource, } #[derive(Copy, Clone)] @@ -330,7 +330,7 @@ impl WlSeatGlobal { }); slf.pointer_cursor.set_owner(slf.clone()); slf.modifiers_listener - .attach(&seat_kb_state.borrow().kb_state.mods_changed); + .attach(&seat_kb_state.borrow().kb_state.leds_changed); let seat = slf.clone(); let future = state.eng.spawn("seat handler", async move { loop { @@ -541,8 +541,8 @@ impl WlSeatGlobal { } { let new = &*new.borrow(); - self.modifiers_listener.attach(&new.kb_state.mods_changed); - self.dispatch_seat_modifiers_listeners(&new.kb_state.mods); + self.modifiers_listener.attach(&new.kb_state.leds_changed); + self.dispatch_seat_leds_listeners(new.kb_state.leds); } self.handle_keyboard_state_change(&old.borrow().kb_state, &new.borrow().kb_state); } @@ -581,7 +581,7 @@ impl WlSeatGlobal { fn attach_modifiers_listener( &self, id: PhysicalKeyboardId, - listener: &EventListener, + listener: &EventListener, map: Option<&Rc>, ) { let _ = self.get_physical_keyboard(id, map); @@ -592,18 +592,18 @@ impl WlSeatGlobal { } Some(m) => { let state = self.get_kb_state(m); - listener.attach(&state.borrow().kb_state.mods_changed); + listener.attach(&state.borrow().kb_state.leds_changed); state } }; if let Some(l) = listener.get() { - l.locked_mods(&state.borrow().kb_state.mods); + l.leds(state.borrow().kb_state.leds); } } - fn dispatch_seat_modifiers_listeners(&self, mods: &Components) { + fn dispatch_seat_leds_listeners(&self, leds: Leds) { for listener in self.modifiers_forward.iter() { - listener.locked_mods(mods); + listener.leds(leds); } } @@ -1514,8 +1514,8 @@ impl WlSeat { pub fn keymap_fd(&self, state: &KeyboardState) -> Result { let fd = match self.client.is_xwayland { - true => &state.xwayland_map, - _ => &state.map, + true => &state.map.xwayland_map, + _ => &state.map.map, }; if self.version >= READ_ONLY_KEYMAP_SINCE { return Ok(fd.clone()); @@ -1739,22 +1739,15 @@ impl DeviceHandlerData { } } -impl ModifiersListener for DeviceHandlerData { - fn locked_mods(&self, mods: &Components) { - let mut leds = Leds::none(); - if mods.mods_locked.contains(ModifierMask::NUM_LOCK) { - leds |= LED_NUM_LOCK; - } - if mods.mods_locked.contains(ModifierMask::LOCK) { - leds |= LED_CAPS_LOCK; - } +impl LedsListener for DeviceHandlerData { + fn leds(&self, leds: Leds) { self.device.set_enabled_leds(leds); } } -impl ModifiersListener for WlSeatGlobal { - fn locked_mods(&self, mods: &Components) { - self.dispatch_seat_modifiers_listeners(mods); +impl LedsListener for WlSeatGlobal { + fn leds(&self, leds: Leds) { + self.dispatch_seat_leds_listeners(leds) } } diff --git a/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs b/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs index 32b05da1..adedd3b3 100644 --- a/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs +++ b/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs @@ -28,7 +28,7 @@ impl ZwpInputMethodKeyboardGrabV2 { } fn send_keymap(&self, kb_state: &KeyboardState) { - let map = match kb_state.map.create_unprotected_fd() { + let map = match kb_state.map.map.create_unprotected_fd() { Ok(m) => m, Err(e) => { log::error!("Could not create new keymap fd: {}", ErrorFmt(e)); diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs index a7a4b3f6..23d4ab8e 100644 --- a/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs @@ -86,11 +86,11 @@ impl ZwpVirtualKeyboardManagerV1RequestHandler for ZwpVirtualKeyboardManagerV1 { version: self.version, kb_state: Rc::new(RefCell::new(KeyboardState { id: self.client.state.keyboard_state_ids.next(), - map: seat_keymap.map.clone(), - xwayland_map: seat_keymap.xwayland_map.clone(), + map: seat_keymap.clone(), pressed_keys: Default::default(), mods: Default::default(), - mods_changed: Default::default(), + leds: Default::default(), + leds_changed: Default::default(), })), }); track!(self.client, kb); diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs index eb849a7f..7a218b3f 100644 --- a/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs @@ -80,11 +80,11 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 { .map_err(ZwpVirtualKeyboardV1Error::ParseKeymap)?; *self.kb_state.borrow_mut() = KeyboardState { id: self.client.state.keyboard_state_ids.next(), - map: map.map.clone(), - xwayland_map: map.xwayland_map.clone(), + map: map.clone(), pressed_keys: Default::default(), mods: Default::default(), - mods_changed: Default::default(), + leds: Default::default(), + leds_changed: Default::default(), }; Ok(()) } @@ -113,15 +113,12 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 { fn modifiers(&self, req: Modifiers, _slf: &Rc) -> Result<(), Self::Error> { let kb_state = &mut *self.kb_state.borrow_mut(); - let locked_mods = kb_state.mods.mods_locked; kb_state.mods.mods_pressed.0 = req.mods_depressed; kb_state.mods.mods_latched.0 = req.mods_latched; kb_state.mods.mods_locked.0 = req.mods_locked; kb_state.mods.group_locked.0 = req.group; kb_state.mods.update_effective(); - if locked_mods != kb_state.mods.mods_locked { - kb_state.dispatch_locked_mods_listeners(); - } + kb_state.update_leds(); self.for_each_kb(|serial, surface, kb| { kb.on_mods_changed(serial, surface.id, &kb_state); }); diff --git a/src/kbvm.rs b/src/kbvm.rs index 85c38689..a5b0d840 100644 --- a/src/kbvm.rs +++ b/src/kbvm.rs @@ -12,6 +12,7 @@ use { xkb::{ self, Keymap, diagnostic::{Diagnostic, WriteToLog}, + keymap::{Indicator, IndicatorMatcher}, }, }, std::{ @@ -52,6 +53,12 @@ pub struct KbvmMap { pub lookup_table: LookupTable, pub map: KeymapFd, pub xwayland_map: KeymapFd, + pub has_indicators: bool, + pub num_lock: Option, + pub caps_lock: Option, + pub scroll_lock: Option, + pub compose: Option, + pub kana: Option, } pub struct KbvmState { @@ -85,6 +92,23 @@ impl KbvmContext { .ctx .keymap_from_bytes(WriteToLog, None, keymap) .map_err(KbvmError::CouldNotParseKeymap)?; + let mut has_indicators = false; + let mut num_lock = None; + let mut caps_lock = None; + let mut scroll_lock = None; + let mut compose = None; + let mut kana = None; + for indicator in map.indicators() { + match indicator.name() { + Indicator::NUM_LOCK => num_lock = Some(indicator.matcher()), + Indicator::CAPS_LOCK => caps_lock = Some(indicator.matcher()), + Indicator::SCROLL_LOCK => scroll_lock = Some(indicator.matcher()), + Indicator::COMPOSE => compose = Some(indicator.matcher()), + Indicator::KANA => kana = Some(indicator.matcher()), + _ => continue, + } + has_indicators = true; + } let builder = map.to_builder(); Ok(Rc::new(KbvmMap { id: KbvmMapId(*blake3::hash(keymap).as_bytes()), @@ -92,6 +116,12 @@ impl KbvmContext { map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?, xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?, lookup_table: builder.build_lookup_table(), + has_indicators, + num_lock, + caps_lock, + scroll_lock, + compose, + kana, })) } } @@ -123,11 +153,11 @@ impl KbvmMap { state: self.state_machine.create_state(), kb_state: KeyboardState { id, - map: self.map.clone(), - xwayland_map: self.xwayland_map.clone(), + map: self.clone(), pressed_keys: Default::default(), mods: Default::default(), - mods_changed: Default::default(), + leds: Default::default(), + leds_changed: Default::default(), }, } } diff --git a/src/keyboard.rs b/src/keyboard.rs index 61dcd7b8..8aa0f1c6 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,5 +1,9 @@ use { - crate::utils::{event_listener::EventSource, oserror::OsError, vecset::VecSet}, + crate::{ + backend::{LED_CAPS_LOCK, LED_COMPOSE, LED_KANA, LED_NUM_LOCK, LED_SCROLL_LOCK, Leds}, + kbvm::KbvmMap, + utils::{event_listener::EventSource, oserror::OsError, vecset::VecSet}, + }, kbvm::{Components, state_machine::Event}, std::{ cell::{Ref, RefCell}, @@ -21,15 +25,15 @@ linear_ids!(KeyboardStateIds, KeyboardStateId, u64); pub struct KeyboardState { pub id: KeyboardStateId, - pub map: KeymapFd, - pub xwayland_map: KeymapFd, + pub map: Rc, pub pressed_keys: VecSet, pub mods: Components, - pub mods_changed: EventSource, + pub leds: Leds, + pub leds_changed: EventSource, } -pub trait ModifiersListener { - fn locked_mods(&self, mods: &Components); +pub trait LedsListener { + fn leds(&self, leds: Leds); } pub trait DynKeyboardState { @@ -44,17 +48,37 @@ impl DynKeyboardState for RefCell { impl KeyboardState { pub fn apply_event(&mut self, event: Event) -> bool { - let locked_mods = self.mods.mods_locked; let changed = self.mods.apply_event(event); - if locked_mods != self.mods.mods_locked { - self.dispatch_locked_mods_listeners(); + if changed && self.map.has_indicators { + self.update_leds(); } changed } - pub fn dispatch_locked_mods_listeners(&self) { - for listener in self.mods_changed.iter() { - listener.locked_mods(&self.mods); + pub fn update_leds(&mut self) { + if !self.map.has_indicators { + return; + } + let mut new = Leds::none(); + macro_rules! map_led { + ($field:ident, $led:ident) => { + if let Some(m) = &self.map.$field + && m.matches(&self.mods) + { + new |= $led; + } + }; + } + map_led!(num_lock, LED_NUM_LOCK); + map_led!(caps_lock, LED_CAPS_LOCK); + map_led!(scroll_lock, LED_SCROLL_LOCK); + map_led!(compose, LED_COMPOSE); + map_led!(kana, LED_KANA); + if new != self.leds { + self.leds = new; + for listener in self.leds_changed.iter() { + listener.leds(new); + } } } } diff --git a/src/state.rs b/src/state.rs index cc7c768e..21e61e93 100644 --- a/src/state.rs +++ b/src/state.rs @@ -83,7 +83,7 @@ use { }, io_uring::IoUring, kbvm::{KbvmContext, KbvmMap}, - keyboard::{KeyboardStateIds, ModifiersListener}, + keyboard::{KeyboardStateIds, LedsListener}, leaks::Tracker, logger::Logger, pr_caps::PrCapsThread, @@ -397,7 +397,7 @@ pub struct DeviceHandlerData { pub tablet_pad_init: Option>, pub is_touch: bool, pub is_kb: bool, - pub mods_listener: EventListener, + pub mods_listener: EventListener, } pub struct ConnectorData {