1
0
Fork 0
forked from wry/wry

Merge pull request #594 from mahkoh/jorth/leds

metal: enable keyboard LEDs
This commit is contained in:
mahkoh 2025-09-07 13:03:45 +02:00 committed by GitHub
commit 0c7f7429db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 223 additions and 31 deletions

20
Cargo.lock generated
View file

@ -173,6 +173,19 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]]
name = "blake3"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
]
[[package]]
name = "bstr"
version = "1.11.3"
@ -295,6 +308,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -587,6 +606,7 @@ dependencies = [
"ash",
"backtrace",
"bincode",
"blake3",
"bstr",
"byteorder",
"cc",

View file

@ -67,6 +67,7 @@ regex = "1.11.1"
cfg-if = "1.0.0"
opera = "1.0.1"
with_builtin_macros = "0.1.0"
blake3 = "1.8.2"
[build-dependencies]
repc = "0.1.1"

View file

@ -259,6 +259,10 @@ pub trait InputDevice {
fn tablet_pad_info(&self) -> Option<Box<TabletPadInit>> {
None
}
fn set_enabled_leds(&self, leds: Leds) {
let _ = leds;
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
@ -328,6 +332,15 @@ pub enum AxisSource {
pub const AXIS_120: i32 = 120;
bitflags! {
Leds: u32;
LED_NUM_LOCK = 1 << 0,
LED_CAPS_LOCK = 1 << 1,
LED_SCROLL_LOCK = 1 << 2,
LED_COMPOSE = 1 << 3,
LED_KANA = 1 << 4,
}
#[derive(Debug)]
pub enum InputEvent {
Key {

View file

@ -9,7 +9,7 @@ use {
async_engine::SpawnedFuture,
backend::{
Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState,
InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, Leds,
TransformMatrix, transaction::BackendConnectorTransactionError,
},
backends::metal::video::{
@ -31,7 +31,7 @@ use {
AccelProfile, ConfigClickMethod, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS,
LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, LIBINPUT_CONFIG_CLICK_METHOD_NONE,
LIBINPUT_DEVICE_CAP_TABLET_PAD, LIBINPUT_DEVICE_CAP_TABLET_TOOL,
LIBINPUT_DEVICE_CAP_TABLET_PAD, LIBINPUT_DEVICE_CAP_TABLET_TOOL, Led,
},
device::{LibInputDevice, RegisteredDevice},
},
@ -373,6 +373,7 @@ struct InputDeviceProperties {
calibration_matrix: Cell<Option<[[f32; 3]; 2]>>,
click_method: Cell<Option<ConfigClickMethod>>,
middle_button_emulation_enabled: Cell<Option<bool>>,
enabled_leds: Cell<Option<Led>>,
}
#[derive(Clone)]
@ -442,6 +443,9 @@ impl MetalInputDevice {
if let Some(enabled) = self.desired.middle_button_emulation_enabled.get() {
self.set_middle_button_emulation_enabled(enabled);
}
if let Some(led) = self.desired.enabled_leds.get() {
self.set_enabled_leds_(led);
}
self.fetch_effective();
}
@ -527,6 +531,14 @@ impl MetalInputDevice {
.set(Some(dev.device().click_method()));
}
}
fn set_enabled_leds_(&self, led: Led) {
self.desired.enabled_leds.set(Some(led));
if let Some(dev) = self.inputdev.get() {
dev.device().led_update(led);
self.effective.enabled_leds.set(Some(led));
}
}
}
impl InputDevice for MetalInputDevice {
@ -810,6 +822,11 @@ impl InputDevice for MetalInputDevice {
groups,
}))
}
fn set_enabled_leds(&self, leds: Leds) {
let led = Led(leds.0 as _);
self.set_enabled_leds_(led);
}
}
impl MetalInputDevice {

View file

@ -24,7 +24,7 @@ pub mod zwp_virtual_keyboard_v1;
use {
crate::{
async_engine::SpawnedFuture,
backend::KeyState,
backend::{KeyState, LED_CAPS_LOCK, LED_NUM_LOCK, 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},
keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd, ModifiersListener},
leaks::Tracker,
object::{Object, Version},
rect::Rect,
@ -91,6 +91,7 @@ use {
bindings::PerClientBindings,
clonecell::CloneCell,
copyhashmap::CopyHashMap,
event_listener::{EventListener, EventSource},
linkedlist::{LinkedList, LinkedNode, NodeRef},
numcell::NumCell,
on_drop::OnDrop,
@ -106,7 +107,7 @@ use {
},
ahash::AHashMap,
jay_config::keyboard::syms::{KeySym, SYM_Escape},
kbvm::Keycode,
kbvm::{Components, Keycode, ModifierMask},
smallvec::SmallVec,
std::{
cell::{Cell, RefCell},
@ -235,6 +236,8 @@ pub struct WlSeatGlobal {
focus_history_same_workspace: Cell<bool>,
mark_mode: Cell<Option<MarkMode>>,
marks: CopyHashMap<Keycode, Rc<dyn Node>>,
modifiers_listener: EventListener<dyn ModifiersListener>,
modifiers_forward: EventSource<dyn ModifiersListener>,
}
#[derive(Copy, Clone)]
@ -256,7 +259,7 @@ impl WlSeatGlobal {
let cursor_user_group = CursorUserGroup::create(state);
let cursor_user = cursor_user_group.create_user();
cursor_user.activate();
let slf = Rc::new(Self {
let slf = Rc::new_cyclic(|slf: &Weak<WlSeatGlobal>| Self {
id: state.seat_ids.next(),
name,
state: state.clone(),
@ -322,8 +325,12 @@ impl WlSeatGlobal {
focus_history_same_workspace: Cell::new(false),
mark_mode: Default::default(),
marks: Default::default(),
modifiers_listener: EventListener::new(slf.clone()),
modifiers_forward: Default::default(),
});
slf.pointer_cursor.set_owner(slf.clone());
slf.modifiers_listener
.attach(&seat_kb_state.borrow().kb_state.mods_changed);
let seat = slf.clone();
let future = state.eng.spawn("seat handler", async move {
loop {
@ -516,14 +523,27 @@ impl WlSeatGlobal {
false
}
pub fn set_seat_keymap(&self, keymap: &Rc<KbvmMap>) {
pub fn set_seat_keymap(self: &Rc<Self>, keymap: &Rc<KbvmMap>) {
self.seat_kb_map.set(keymap.clone());
let new = self.get_kb_state(keymap);
let old = self.seat_kb_state.set(new.clone());
if rc_eq(&old, &new) {
return;
}
self.kb_devices.lock().retain(|_, p| p.has_custom_map.get());
let mut to_destroy = vec![];
for (id, s) in self.kb_devices.lock().iter() {
if !s.has_custom_map.get() {
to_destroy.push(*id);
}
}
for dev in to_destroy {
self.destroy_physical_keyboard(dev);
}
{
let new = &*new.borrow();
self.modifiers_listener.attach(&new.kb_state.mods_changed);
self.dispatch_seat_modifiers_listeners(&new.kb_state.mods);
}
self.handle_keyboard_state_change(&old.borrow().kb_state, &new.borrow().kb_state);
}
@ -558,6 +578,35 @@ impl WlSeatGlobal {
s
}
fn attach_modifiers_listener(
&self,
id: PhysicalKeyboardId,
listener: &EventListener<dyn ModifiersListener>,
map: Option<&Rc<KbvmMap>>,
) {
let _ = self.get_physical_keyboard(id, map);
let state = match map {
None => {
listener.attach(&self.modifiers_forward);
self.seat_kb_state.get()
}
Some(m) => {
let state = self.get_kb_state(m);
listener.attach(&state.borrow().kb_state.mods_changed);
state
}
};
if let Some(l) = listener.get() {
l.locked_mods(&state.borrow().kb_state.mods);
}
}
fn dispatch_seat_modifiers_listeners(&self, mods: &Components) {
for listener in self.modifiers_forward.iter() {
listener.locked_mods(mods);
}
}
pub fn prepare_for_lock(self: &Rc<Self>) {
self.pointer_owner.revert_to_default(self);
self.kb_owner.ungrab(self);
@ -1639,17 +1688,32 @@ impl DeviceHandlerData {
seat.update_capabilities();
}
}
self.attach_event_listeners();
}
fn destroy_physical_keyboard_state(&self) {
self.mods_listener.detach();
if let Some(seat) = self.seat.get() {
seat.destroy_physical_keyboard(self.keyboard_id);
};
}
fn attach_event_listeners(&self) {
if self.is_kb
&& let Some(seat) = self.seat.get()
{
seat.attach_modifiers_listener(
self.keyboard_id,
&self.mods_listener,
self.keymap.get().as_ref(),
);
};
}
pub fn set_keymap(&self, keymap: Option<Rc<KbvmMap>>) {
self.destroy_physical_keyboard_state();
self.keymap.set(keymap);
self.attach_event_listeners();
}
pub fn set_output(&self, output: Option<&WlOutputGlobal>) {
@ -1675,6 +1739,25 @@ 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;
}
self.device.set_enabled_leds(leds);
}
}
impl ModifiersListener for WlSeatGlobal {
fn locked_mods(&self, mods: &Components) {
self.dispatch_seat_modifiers_listeners(mods);
}
}
pub struct PositionHintRequest {
seat: Rc<WlSeatGlobal>,
node: NodeId,

View file

@ -890,7 +890,7 @@ impl WlSeatGlobal {
let mut shortcuts = SmallVec::<[_; 1]>::new();
let mut components_changed = false;
while let Some(event) = events.pop() {
components_changed |= kbvm_state.kb_state.mods.apply_event(event);
components_changed |= kbvm_state.kb_state.apply_event(event);
let (key_state, kc) = match event {
Event::KeyDown(kc) => (KeyState::Pressed, kc),
Event::KeyUp(kc) => (KeyState::Released, kc),

View file

@ -90,6 +90,7 @@ impl ZwpVirtualKeyboardManagerV1RequestHandler for ZwpVirtualKeyboardManagerV1 {
xwayland_map: seat_keymap.xwayland_map.clone(),
pressed_keys: Default::default(),
mods: Default::default(),
mods_changed: Default::default(),
})),
});
track!(self.client, kb);

View file

@ -84,6 +84,7 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 {
xwayland_map: map.xwayland_map.clone(),
pressed_keys: Default::default(),
mods: Default::default(),
mods_changed: Default::default(),
};
Ok(())
}
@ -112,11 +113,15 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 {
fn modifiers(&self, req: Modifiers, _slf: &Rc<Self>) -> 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();
}
self.for_each_kb(|serial, surface, kb| {
kb.on_mods_changed(serial, surface.id, &kb_state);
});

View file

@ -33,21 +33,18 @@ pub enum KbvmError {
pub struct KbvmContext {
pub ctx: xkb::Context,
pub ids: KbvmMapIds,
}
impl Default for KbvmContext {
fn default() -> Self {
let mut ctx = xkb::Context::builder();
ctx.enable_environment(true);
Self {
ctx: ctx.build(),
ids: Default::default(),
}
Self { ctx: ctx.build() }
}
}
linear_ids!(KbvmMapIds, KbvmMapId, u64);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct KbvmMapId([u8; 32]);
pub struct KbvmMap {
pub id: KbvmMapId,
@ -90,7 +87,7 @@ impl KbvmContext {
.map_err(KbvmError::CouldNotParseKeymap)?;
let builder = map.to_builder();
Ok(Rc::new(KbvmMap {
id: self.ids.next(),
id: KbvmMapId(*blake3::hash(keymap).as_bytes()),
state_machine: builder.build_state_machine(),
map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?,
xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?,
@ -130,6 +127,7 @@ impl KbvmMap {
xwayland_map: self.xwayland_map.clone(),
pressed_keys: Default::default(),
mods: Default::default(),
mods_changed: Default::default(),
},
}
}
@ -139,7 +137,7 @@ impl KbvmState {
pub fn apply_events(&mut self, events: &SyncQueue<Event>) {
let state = &mut self.kb_state;
while let Some(event) = events.pop() {
state.mods.apply_event(event);
state.apply_event(event);
match event {
Event::KeyDown(kc) => {
state.pressed_keys.insert(kc.to_evdev());

View file

@ -1,6 +1,6 @@
use {
crate::utils::{oserror::OsError, vecset::VecSet},
kbvm::Components,
crate::utils::{event_listener::EventSource, oserror::OsError, vecset::VecSet},
kbvm::{Components, state_machine::Event},
std::{
cell::{Ref, RefCell},
rc::Rc,
@ -25,6 +25,11 @@ pub struct KeyboardState {
pub xwayland_map: KeymapFd,
pub pressed_keys: VecSet<u32>,
pub mods: Components,
pub mods_changed: EventSource<dyn ModifiersListener>,
}
pub trait ModifiersListener {
fn locked_mods(&self, mods: &Components);
}
pub trait DynKeyboardState {
@ -37,6 +42,23 @@ impl DynKeyboardState for RefCell<KeyboardState> {
}
}
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();
}
changed
}
pub fn dispatch_locked_mods_listeners(&self) {
for listener in self.mods_changed.iter() {
listener.locked_mods(&self.mods);
}
}
}
#[derive(Clone)]
pub struct KeymapFd {
pub map: Rc<OwnedFd>,

View file

@ -33,6 +33,8 @@ cenum! {
LIBINPUT_LED_NUM_LOCK = 1 << 0,
LIBINPUT_LED_CAPS_LOCK = 1 << 1,
LIBINPUT_LED_SCROLL_LOCK = 1 << 2,
LIBINPUT_LED_COMPOSE = 1 << 3,
LIBINPUT_LED_KANA = 1 << 4,
}
cenum! {

View file

@ -7,7 +7,7 @@ use {
LIBINPUT_CONFIG_DRAG_DISABLED, LIBINPUT_CONFIG_DRAG_ENABLED,
LIBINPUT_CONFIG_DRAG_LOCK_DISABLED, LIBINPUT_CONFIG_DRAG_LOCK_ENABLED,
LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED, LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED,
LIBINPUT_CONFIG_TAP_DISABLED, LIBINPUT_CONFIG_TAP_ENABLED,
LIBINPUT_CONFIG_TAP_DISABLED, LIBINPUT_CONFIG_TAP_ENABLED, Led,
},
sys::{
libinput_device, libinput_device_config_accel_get_profile,
@ -36,8 +36,9 @@ use {
libinput_device_get_id_vendor, libinput_device_get_name, libinput_device_get_user_data,
libinput_device_group, libinput_device_group_get_user_data,
libinput_device_group_set_user_data, libinput_device_has_capability,
libinput_device_set_user_data, libinput_device_tablet_pad_get_mode_group,
libinput_device_tablet_pad_get_num_buttons, libinput_device_tablet_pad_get_num_dials,
libinput_device_led_update, libinput_device_set_user_data,
libinput_device_tablet_pad_get_mode_group, libinput_device_tablet_pad_get_num_buttons,
libinput_device_tablet_pad_get_num_dials,
libinput_device_tablet_pad_get_num_mode_groups,
libinput_device_tablet_pad_get_num_rings, libinput_device_tablet_pad_get_num_strips,
libinput_device_unref, libinput_path_remove_device, libinput_tablet_pad_mode_group,
@ -343,6 +344,12 @@ impl<'a> LibInputDevice<'a> {
}
[[m[0], m[1], m[2]], [m[3], m[4], m[5]]]
}
pub fn led_update(&self, led: Led) {
unsafe {
libinput_device_led_update(self.dev, led.raw() as _);
}
}
}
impl<'a> LibInputDeviceGroup<'a> {

View file

@ -410,6 +410,8 @@ unsafe extern "C" {
device: *mut libinput_device,
matrix: *mut [f32; 6],
) -> c::c_int;
pub fn libinput_device_led_update(device: *mut libinput_device, leds: libinput_led);
}
#[repr(C)]

View file

@ -83,7 +83,7 @@ use {
},
io_uring::IoUring,
kbvm::{KbvmContext, KbvmMap},
keyboard::KeyboardStateIds,
keyboard::{KeyboardStateIds, ModifiersListener},
leaks::Tracker,
logger::Logger,
pr_caps::PrCapsThread,
@ -100,11 +100,21 @@ use {
generic_node_visitor,
},
utils::{
activation_token::ActivationToken, asyncevent::AsyncEvent, bindings::Bindings,
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
event_listener::EventSource, fdcloser::FdCloser, hash_map_ext::HashMapExt,
linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted,
run_toplevel::RunToplevel, toplevel_identifier::ToplevelIdentifier,
activation_token::ActivationToken,
asyncevent::AsyncEvent,
bindings::Bindings,
clonecell::CloneCell,
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
event_listener::{EventListener, EventSource},
fdcloser::FdCloser,
hash_map_ext::HashMapExt,
linkedlist::LinkedList,
numcell::NumCell,
queue::AsyncQueue,
refcounted::RefCounted,
run_toplevel::RunToplevel,
toplevel_identifier::ToplevelIdentifier,
},
video::{
dmabuf::DmaBufIds,
@ -386,6 +396,8 @@ pub struct DeviceHandlerData {
pub tablet_init: Option<Box<TabletInit>>,
pub tablet_pad_init: Option<Box<TabletPadInit>>,
pub is_touch: bool,
pub is_kb: bool,
pub mods_listener: EventListener<dyn ModifiersListener>,
}
pub struct ConnectorData {

View file

@ -4,10 +4,13 @@ use {
ifs::wl_seat::PX_PER_SCROLL,
state::{DeviceHandlerData, InputDeviceData, State},
tasks::udev_utils::{UdevProps, udev_props},
utils::asyncevent::AsyncEvent,
utils::{asyncevent::AsyncEvent, event_listener::EventListener},
},
jay_config::_private::DEFAULT_SEAT_NAME,
std::{cell::Cell, rc::Rc},
std::{
cell::Cell,
rc::{Rc, Weak},
},
};
pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
@ -15,7 +18,7 @@ pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
None => UdevProps::default(),
Some(dev_t) => udev_props(dev_t, 3),
};
let data = Rc::new(DeviceHandlerData {
let data = Rc::new_cyclic(|slf: &Weak<DeviceHandlerData>| DeviceHandlerData {
keyboard_id: state.physical_keyboard_ids.next(),
seat: Default::default(),
px_per_scroll_wheel: Cell::new(PX_PER_SCROLL),
@ -27,6 +30,8 @@ pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
tablet_init: dev.tablet_info(),
tablet_pad_init: dev.tablet_pad_info(),
is_touch: dev.has_capability(InputDeviceCapability::Touch),
is_kb: dev.has_capability(InputDeviceCapability::Keyboard),
mods_listener: EventListener::new(slf.clone()),
});
let ae = Rc::new(AsyncEvent::default());
let oh = DeviceHandler {

View file

@ -78,4 +78,8 @@ impl<T: ?Sized> EventListener<T> {
pub fn detach(&self) {
self.link.detach();
}
pub fn get(&self) -> Option<Rc<T>> {
self.link.upgrade()
}
}