1
0
Fork 0
forked from wry/wry

kbvm: use indicators to determine the active LEDs

This commit is contained in:
Julian Orth 2025-09-07 15:34:38 +02:00
parent 0c7f7429db
commit f5e04355d7
8 changed files with 101 additions and 57 deletions

View file

@ -36,8 +36,8 @@ impl EiKeyboard {
self.client.event(Keymap { self.client.event(Keymap {
self_id: self.id, self_id: self.id,
keymap_type: KEYMAP_TYPE_XKB, keymap_type: KEYMAP_TYPE_XKB,
size: state.map.len as _, size: state.map.map.len as _,
keymap: state.map.map.clone(), keymap: state.map.map.map.clone(),
}); });
} }

View file

@ -24,7 +24,7 @@ pub mod zwp_virtual_keyboard_v1;
use { use {
crate::{ crate::{
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
backend::{KeyState, LED_CAPS_LOCK, LED_NUM_LOCK, Leds}, backend::{KeyState, Leds},
client::{Client, ClientError, ClientId}, client::{Client, ClientError, ClientId},
cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner}, cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner},
ei::ei_ifs::ei_seat::EiSeat, ei::ei_ifs::ei_seat::EiSeat,
@ -75,7 +75,7 @@ use {
xdg_toplevel_drag_v1::XdgToplevelDragV1, xdg_toplevel_drag_v1::XdgToplevelDragV1,
}, },
kbvm::{KbvmMap, KbvmMapId, KbvmState, PhysicalKeyboardState}, kbvm::{KbvmMap, KbvmMapId, KbvmState, PhysicalKeyboardState},
keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd, ModifiersListener}, keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd, LedsListener},
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
rect::Rect, rect::Rect,
@ -107,7 +107,7 @@ use {
}, },
ahash::AHashMap, ahash::AHashMap,
jay_config::keyboard::syms::{KeySym, SYM_Escape}, jay_config::keyboard::syms::{KeySym, SYM_Escape},
kbvm::{Components, Keycode, ModifierMask}, kbvm::Keycode,
smallvec::SmallVec, smallvec::SmallVec,
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
@ -236,8 +236,8 @@ pub struct WlSeatGlobal {
focus_history_same_workspace: Cell<bool>, focus_history_same_workspace: Cell<bool>,
mark_mode: Cell<Option<MarkMode>>, mark_mode: Cell<Option<MarkMode>>,
marks: CopyHashMap<Keycode, Rc<dyn Node>>, marks: CopyHashMap<Keycode, Rc<dyn Node>>,
modifiers_listener: EventListener<dyn ModifiersListener>, modifiers_listener: EventListener<dyn LedsListener>,
modifiers_forward: EventSource<dyn ModifiersListener>, modifiers_forward: EventSource<dyn LedsListener>,
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -330,7 +330,7 @@ impl WlSeatGlobal {
}); });
slf.pointer_cursor.set_owner(slf.clone()); slf.pointer_cursor.set_owner(slf.clone());
slf.modifiers_listener 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 seat = slf.clone();
let future = state.eng.spawn("seat handler", async move { let future = state.eng.spawn("seat handler", async move {
loop { loop {
@ -541,8 +541,8 @@ impl WlSeatGlobal {
} }
{ {
let new = &*new.borrow(); let new = &*new.borrow();
self.modifiers_listener.attach(&new.kb_state.mods_changed); self.modifiers_listener.attach(&new.kb_state.leds_changed);
self.dispatch_seat_modifiers_listeners(&new.kb_state.mods); self.dispatch_seat_leds_listeners(new.kb_state.leds);
} }
self.handle_keyboard_state_change(&old.borrow().kb_state, &new.borrow().kb_state); self.handle_keyboard_state_change(&old.borrow().kb_state, &new.borrow().kb_state);
} }
@ -581,7 +581,7 @@ impl WlSeatGlobal {
fn attach_modifiers_listener( fn attach_modifiers_listener(
&self, &self,
id: PhysicalKeyboardId, id: PhysicalKeyboardId,
listener: &EventListener<dyn ModifiersListener>, listener: &EventListener<dyn LedsListener>,
map: Option<&Rc<KbvmMap>>, map: Option<&Rc<KbvmMap>>,
) { ) {
let _ = self.get_physical_keyboard(id, map); let _ = self.get_physical_keyboard(id, map);
@ -592,18 +592,18 @@ impl WlSeatGlobal {
} }
Some(m) => { Some(m) => {
let state = self.get_kb_state(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 state
} }
}; };
if let Some(l) = listener.get() { 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() { 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<KeymapFd, WlKeyboardError> { pub fn keymap_fd(&self, state: &KeyboardState) -> Result<KeymapFd, WlKeyboardError> {
let fd = match self.client.is_xwayland { let fd = match self.client.is_xwayland {
true => &state.xwayland_map, true => &state.map.xwayland_map,
_ => &state.map, _ => &state.map.map,
}; };
if self.version >= READ_ONLY_KEYMAP_SINCE { if self.version >= READ_ONLY_KEYMAP_SINCE {
return Ok(fd.clone()); return Ok(fd.clone());
@ -1739,22 +1739,15 @@ impl DeviceHandlerData {
} }
} }
impl ModifiersListener for DeviceHandlerData { impl LedsListener for DeviceHandlerData {
fn locked_mods(&self, mods: &Components) { fn leds(&self, leds: Leds) {
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;
}
self.device.set_enabled_leds(leds); self.device.set_enabled_leds(leds);
} }
} }
impl ModifiersListener for WlSeatGlobal { impl LedsListener for WlSeatGlobal {
fn locked_mods(&self, mods: &Components) { fn leds(&self, leds: Leds) {
self.dispatch_seat_modifiers_listeners(mods); self.dispatch_seat_leds_listeners(leds)
} }
} }

View file

@ -28,7 +28,7 @@ impl ZwpInputMethodKeyboardGrabV2 {
} }
fn send_keymap(&self, kb_state: &KeyboardState) { 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, Ok(m) => m,
Err(e) => { Err(e) => {
log::error!("Could not create new keymap fd: {}", ErrorFmt(e)); log::error!("Could not create new keymap fd: {}", ErrorFmt(e));

View file

@ -86,11 +86,11 @@ impl ZwpVirtualKeyboardManagerV1RequestHandler for ZwpVirtualKeyboardManagerV1 {
version: self.version, version: self.version,
kb_state: Rc::new(RefCell::new(KeyboardState { kb_state: Rc::new(RefCell::new(KeyboardState {
id: self.client.state.keyboard_state_ids.next(), id: self.client.state.keyboard_state_ids.next(),
map: seat_keymap.map.clone(), map: seat_keymap.clone(),
xwayland_map: seat_keymap.xwayland_map.clone(),
pressed_keys: Default::default(), pressed_keys: Default::default(),
mods: Default::default(), mods: Default::default(),
mods_changed: Default::default(), leds: Default::default(),
leds_changed: Default::default(),
})), })),
}); });
track!(self.client, kb); track!(self.client, kb);

View file

@ -80,11 +80,11 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 {
.map_err(ZwpVirtualKeyboardV1Error::ParseKeymap)?; .map_err(ZwpVirtualKeyboardV1Error::ParseKeymap)?;
*self.kb_state.borrow_mut() = KeyboardState { *self.kb_state.borrow_mut() = KeyboardState {
id: self.client.state.keyboard_state_ids.next(), id: self.client.state.keyboard_state_ids.next(),
map: map.map.clone(), map: map.clone(),
xwayland_map: map.xwayland_map.clone(),
pressed_keys: Default::default(), pressed_keys: Default::default(),
mods: Default::default(), mods: Default::default(),
mods_changed: Default::default(), leds: Default::default(),
leds_changed: Default::default(),
}; };
Ok(()) Ok(())
} }
@ -113,15 +113,12 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 {
fn modifiers(&self, req: Modifiers, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn modifiers(&self, req: Modifiers, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let kb_state = &mut *self.kb_state.borrow_mut(); 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_pressed.0 = req.mods_depressed;
kb_state.mods.mods_latched.0 = req.mods_latched; kb_state.mods.mods_latched.0 = req.mods_latched;
kb_state.mods.mods_locked.0 = req.mods_locked; kb_state.mods.mods_locked.0 = req.mods_locked;
kb_state.mods.group_locked.0 = req.group; kb_state.mods.group_locked.0 = req.group;
kb_state.mods.update_effective(); kb_state.mods.update_effective();
if locked_mods != kb_state.mods.mods_locked { kb_state.update_leds();
kb_state.dispatch_locked_mods_listeners();
}
self.for_each_kb(|serial, surface, kb| { self.for_each_kb(|serial, surface, kb| {
kb.on_mods_changed(serial, surface.id, &kb_state); kb.on_mods_changed(serial, surface.id, &kb_state);
}); });

View file

@ -12,6 +12,7 @@ use {
xkb::{ xkb::{
self, Keymap, self, Keymap,
diagnostic::{Diagnostic, WriteToLog}, diagnostic::{Diagnostic, WriteToLog},
keymap::{Indicator, IndicatorMatcher},
}, },
}, },
std::{ std::{
@ -52,6 +53,12 @@ pub struct KbvmMap {
pub lookup_table: LookupTable, pub lookup_table: LookupTable,
pub map: KeymapFd, pub map: KeymapFd,
pub xwayland_map: KeymapFd, pub xwayland_map: KeymapFd,
pub has_indicators: bool,
pub num_lock: Option<IndicatorMatcher>,
pub caps_lock: Option<IndicatorMatcher>,
pub scroll_lock: Option<IndicatorMatcher>,
pub compose: Option<IndicatorMatcher>,
pub kana: Option<IndicatorMatcher>,
} }
pub struct KbvmState { pub struct KbvmState {
@ -85,6 +92,23 @@ impl KbvmContext {
.ctx .ctx
.keymap_from_bytes(WriteToLog, None, keymap) .keymap_from_bytes(WriteToLog, None, keymap)
.map_err(KbvmError::CouldNotParseKeymap)?; .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(); let builder = map.to_builder();
Ok(Rc::new(KbvmMap { Ok(Rc::new(KbvmMap {
id: KbvmMapId(*blake3::hash(keymap).as_bytes()), id: KbvmMapId(*blake3::hash(keymap).as_bytes()),
@ -92,6 +116,12 @@ impl KbvmContext {
map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?, map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?,
xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?, xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?,
lookup_table: builder.build_lookup_table(), 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(), state: self.state_machine.create_state(),
kb_state: KeyboardState { kb_state: KeyboardState {
id, id,
map: self.map.clone(), map: self.clone(),
xwayland_map: self.xwayland_map.clone(),
pressed_keys: Default::default(), pressed_keys: Default::default(),
mods: Default::default(), mods: Default::default(),
mods_changed: Default::default(), leds: Default::default(),
leds_changed: Default::default(),
}, },
} }
} }

View file

@ -1,5 +1,9 @@
use { 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}, kbvm::{Components, state_machine::Event},
std::{ std::{
cell::{Ref, RefCell}, cell::{Ref, RefCell},
@ -21,15 +25,15 @@ linear_ids!(KeyboardStateIds, KeyboardStateId, u64);
pub struct KeyboardState { pub struct KeyboardState {
pub id: KeyboardStateId, pub id: KeyboardStateId,
pub map: KeymapFd, pub map: Rc<KbvmMap>,
pub xwayland_map: KeymapFd,
pub pressed_keys: VecSet<u32>, pub pressed_keys: VecSet<u32>,
pub mods: Components, pub mods: Components,
pub mods_changed: EventSource<dyn ModifiersListener>, pub leds: Leds,
pub leds_changed: EventSource<dyn LedsListener>,
} }
pub trait ModifiersListener { pub trait LedsListener {
fn locked_mods(&self, mods: &Components); fn leds(&self, leds: Leds);
} }
pub trait DynKeyboardState { pub trait DynKeyboardState {
@ -44,17 +48,37 @@ impl DynKeyboardState for RefCell<KeyboardState> {
impl KeyboardState { impl KeyboardState {
pub fn apply_event(&mut self, event: Event) -> bool { pub fn apply_event(&mut self, event: Event) -> bool {
let locked_mods = self.mods.mods_locked;
let changed = self.mods.apply_event(event); let changed = self.mods.apply_event(event);
if locked_mods != self.mods.mods_locked { if changed && self.map.has_indicators {
self.dispatch_locked_mods_listeners(); self.update_leds();
} }
changed changed
} }
pub fn dispatch_locked_mods_listeners(&self) { pub fn update_leds(&mut self) {
for listener in self.mods_changed.iter() { if !self.map.has_indicators {
listener.locked_mods(&self.mods); 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);
}
} }
} }
} }

View file

@ -83,7 +83,7 @@ use {
}, },
io_uring::IoUring, io_uring::IoUring,
kbvm::{KbvmContext, KbvmMap}, kbvm::{KbvmContext, KbvmMap},
keyboard::{KeyboardStateIds, ModifiersListener}, keyboard::{KeyboardStateIds, LedsListener},
leaks::Tracker, leaks::Tracker,
logger::Logger, logger::Logger,
pr_caps::PrCapsThread, pr_caps::PrCapsThread,
@ -397,7 +397,7 @@ pub struct DeviceHandlerData {
pub tablet_pad_init: Option<Box<TabletPadInit>>, pub tablet_pad_init: Option<Box<TabletPadInit>>,
pub is_touch: bool, pub is_touch: bool,
pub is_kb: bool, pub is_kb: bool,
pub mods_listener: EventListener<dyn ModifiersListener>, pub mods_listener: EventListener<dyn LedsListener>,
} }
pub struct ConnectorData { pub struct ConnectorData {