From 0e1be7544f53e98dccf78d896128c62b2c51899d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 16 Oct 2025 19:50:21 +0200 Subject: [PATCH 1/2] backend: add ButtonState --- src/backend.rs | 8 +++++++- src/backends/metal.rs | 4 ++-- src/backends/metal/input.rs | 6 +++--- src/backends/x.rs | 15 ++++++++------- src/ei/ei_ifs/ei_button.rs | 8 ++++---- src/ei/ei_ifs/ei_device.rs | 4 ++-- src/ei/ei_ifs/ei_seat.rs | 4 ++-- src/ifs/jay_seat_events.rs | 4 ++-- src/ifs/wl_seat.rs | 6 +++--- src/ifs/wl_seat/event_handling.rs | 11 ++++++----- src/ifs/wl_seat/pointer_owner.rs | 30 +++++++++++++++--------------- src/ifs/wl_surface.rs | 4 ++-- src/it/test_backend.rs | 12 ++++++------ src/tree.rs | 4 ++-- src/tree/container.rs | 6 +++--- src/tree/float.rs | 8 ++++---- src/tree/output.rs | 7 ++++--- 17 files changed, 75 insertions(+), 66 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 7e6b497e..0a982457 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -317,6 +317,12 @@ pub enum KeyState { Pressed, } +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ButtonState { + Released, + Pressed, +} + #[derive(Debug, Copy, Clone, Eq, PartialEq, Linearize)] pub enum ScrollAxis { Horizontal = HORIZONTAL_SCROLL as _, @@ -369,7 +375,7 @@ pub enum InputEvent { Button { time_usec: u64, button: u32, - state: KeyState, + state: ButtonState, }, AxisPx { diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 3c900e73..978e0b98 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -8,7 +8,7 @@ use { crate::{ async_engine::SpawnedFuture, backend::{ - Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, + Backend, ButtonState, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, Leds, TransformMatrix, transaction::BackendConnectorTransactionError, }, @@ -598,7 +598,7 @@ impl MetalInputDevice { self.event(InputEvent::Button { time_usec, button, - state: KeyState::Released, + state: ButtonState::Released, }); } } diff --git a/src/backends/metal/input.rs b/src/backends/metal/input.rs index 58fdc569..baa05259 100644 --- a/src/backends/metal/input.rs +++ b/src/backends/metal/input.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::{AxisSource, InputEvent, KeyState, ScrollAxis}, + backend::{AxisSource, ButtonState, InputEvent, KeyState, ScrollAxis}, backends::metal::MetalBackend, fixed::Fixed, ifs::wl_seat::tablet::{ @@ -216,12 +216,12 @@ impl MetalBackend { if dev.pressed_buttons.insert(event.button(), ()).is_some() { return; } - KeyState::Pressed + ButtonState::Pressed } else { if dev.pressed_buttons.remove(&event.button()).is_none() { return; } - KeyState::Released + ButtonState::Released }; dev.event(InputEvent::Button { time_usec: event.time_usec(), diff --git a/src/backends/x.rs b/src/backends/x.rs index e2f5838a..dcb3ea3e 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -4,9 +4,10 @@ use { async_engine::{Phase, SpawnedFuture}, backend::{ AXIS_120, AxisSource, Backend, BackendConnectorState, BackendDrmDevice, BackendEvent, - Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, DrmEvent, - InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, - InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix, + ButtonState, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, + DrmEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, + InputDeviceClickMethod, InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, + ScrollAxis, TransformMatrix, transaction::{ BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, @@ -817,8 +818,8 @@ impl XBackend { match event.code() { XiMotion::OPCODE => self.handle_input_motion(event), XiEnter::OPCODE => self.handle_input_enter(event), - XiButtonPress::OPCODE => self.handle_input_button_press(event, KeyState::Pressed), - XiButtonRelease::OPCODE => self.handle_input_button_press(event, KeyState::Released), + XiButtonPress::OPCODE => self.handle_input_button_press(event, ButtonState::Pressed), + XiButtonRelease::OPCODE => self.handle_input_button_press(event, ButtonState::Released), XiKeyPress::OPCODE => self.handle_input_key_press(event, KeyState::Pressed), XiKeyRelease::OPCODE => self.handle_input_key_press(event, KeyState::Released), XiHierarchy::OPCODE => self.handle_input_hierarchy(event).await, @@ -829,14 +830,14 @@ impl XBackend { fn handle_input_button_press( self: &Rc, event: &Event, - state: KeyState, + state: ButtonState, ) -> Result<(), XBackendError> { let event: XiButtonPress = event.parse()?; if let Some(seat) = self.mouse_seats.get(&event.deviceid) { let button = event.detail; // let button = seat.button_map.get(&event.detail).unwrap_or(event.detail); if matches!(button, 4..=7) { - if state == KeyState::Pressed { + if state == ButtonState::Pressed { let (axis, val) = match button { 4 => (ScrollAxis::Vertical, -1), 5 => (ScrollAxis::Vertical, 1), diff --git a/src/ei/ei_ifs/ei_button.rs b/src/ei/ei_ifs/ei_button.rs index 31ccd5ff..1fbe05e2 100644 --- a/src/ei/ei_ifs/ei_button.rs +++ b/src/ei/ei_ifs/ei_button.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::KeyState, + backend::ButtonState, ei::{ ei_client::{EiClient, EiClientError}, ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, @@ -27,7 +27,7 @@ pub struct EiButton { ei_device_interface!(EiButton, ei_button, button); impl EiButton { - pub fn send_button(&self, button: u32, state: KeyState) { + pub fn send_button(&self, button: u32, state: ButtonState) { self.client.event(ServerButton { self_id: self.id, button, @@ -46,8 +46,8 @@ impl EiButtonRequestHandler for EiButton { fn client_button(&self, req: ClientButton, _slf: &Rc) -> Result<(), Self::Error> { let pressed = match req.state { - 0 => KeyState::Released, - 1 => KeyState::Pressed, + 0 => ButtonState::Released, + 1 => ButtonState::Pressed, _ => return Err(EiButtonError::InvalidButtonState(req.state)), }; self.device.button_changes.push((req.button, pressed)); diff --git a/src/ei/ei_ifs/ei_device.rs b/src/ei/ei_ifs/ei_device.rs index b8ddb5ac..d571ffa0 100644 --- a/src/ei/ei_ifs/ei_device.rs +++ b/src/ei/ei_ifs/ei_device.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::{KeyState, ScrollAxis}, + backend::{ButtonState, KeyState, ScrollAxis}, ei::{ ei_client::{EiClient, EiClientError}, ei_ifs::{ei_seat::EiSeat, ei_touchscreen::TouchChange}, @@ -40,7 +40,7 @@ pub struct EiDevice { pub version: EiVersion, pub seat: Rc, - pub button_changes: SyncQueue<(u32, KeyState)>, + pub button_changes: SyncQueue<(u32, ButtonState)>, pub touch_changes: CopyHashMap, pub scroll_px: [Cell>; 2], pub scroll_v120: [Cell>; 2], diff --git a/src/ei/ei_ifs/ei_seat.rs b/src/ei/ei_ifs/ei_seat.rs index 158248e4..89a3f84d 100644 --- a/src/ei/ei_ifs/ei_seat.rs +++ b/src/ei/ei_ifs/ei_seat.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::KeyState, + backend::{ButtonState, KeyState}, ei::{ EiContext, ei_client::{EiClient, EiClientError}, @@ -150,7 +150,7 @@ impl EiSeat { } } - pub fn handle_button(&self, time_usec: u64, button: u32, state: KeyState) { + pub fn handle_button(&self, time_usec: u64, button: u32, state: ButtonState) { if self.is_sender() { return; } diff --git a/src/ifs/jay_seat_events.rs b/src/ifs/jay_seat_events.rs index eabdad72..144d0b1e 100644 --- a/src/ifs/jay_seat_events.rs +++ b/src/ifs/jay_seat_events.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::{InputDeviceId, KeyState, ScrollAxis}, + backend::{ButtonState, InputDeviceId, KeyState, ScrollAxis}, client::Client, fixed::Fixed, ifs::wl_seat::{ @@ -79,7 +79,7 @@ impl JaySeatEvents { }); } - pub fn send_button(&self, seat: SeatId, time_usec: u64, button: u32, state: KeyState) { + pub fn send_button(&self, seat: SeatId, time_usec: u64, button: u32, state: ButtonState) { self.client.event(Button { self_id: self.id, seat: seat.raw(), diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index a2e3200e..8679d2a4 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, Leds}, + backend::{ButtonState, Leds}, client::{Client, ClientError, ClientId}, cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner}, ei::ei_ifs::ei_seat::EiSeat, @@ -1387,10 +1387,10 @@ impl WlSeatGlobal { node: Rc, time_usec: u64, button: u32, - state: KeyState, + state: ButtonState, serial: u64, ) { - if self.tray_popups.is_not_empty() && state == KeyState::Pressed { + if self.tray_popups.is_not_empty() && state == ButtonState::Pressed { let id = node.node_tray_item(); self.tray_popups.lock().retain(|&(tray_item_id, _), item| { let retain = Some(tray_item_id) == id; diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 8cb052b7..f84d3a33 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -1,7 +1,8 @@ use { crate::{ backend::{ - AXIS_120, AxisSource, ConnectorId, InputDeviceId, InputEvent, KeyState, ScrollAxis, + AXIS_120, AxisSource, ButtonState, ConnectorId, InputDeviceId, InputEvent, KeyState, + ScrollAxis, }, client::ClientId, config::InvokedShortcut, @@ -672,7 +673,7 @@ impl WlSeatGlobal { self.motion_event_abs(time_usec, x, y, false); } - pub fn button_event(self: &Rc, time_usec: u64, button: u32, state: KeyState) { + pub fn button_event(self: &Rc, time_usec: u64, button: u32, state: ButtonState) { self.for_each_ei_seat(|ei_seat| { ei_seat.handle_button(time_usec, button, state); }); @@ -1314,12 +1315,12 @@ impl WlSeatGlobal { surface: &Rc, time_usec: u64, button: u32, - state: KeyState, + state: ButtonState, serial: u64, ) { let (state, pressed) = match state { - KeyState::Released => (wl_pointer::RELEASED, false), - KeyState::Pressed => { + ButtonState::Released => (wl_pointer::RELEASED, false), + ButtonState::Pressed => { surface.client.focus_stealing_serial.set(Some(serial)); (wl_pointer::PRESSED, true) } diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index ffd6154f..a4c879a9 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::{AXIS_120, AxisSource, KeyState, ScrollAxis}, + backend::{AXIS_120, AxisSource, ButtonState, ScrollAxis}, cursor::KnownCursor, fixed::Fixed, ifs::{ @@ -57,7 +57,7 @@ impl Default for PointerOwnerHolder { } impl PointerOwnerHolder { - pub fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: KeyState) { + pub fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: ButtonState) { self.owner.get().button(seat, time_usec, button, state) } @@ -221,7 +221,7 @@ impl PointerOwnerHolder { } trait PointerOwner { - fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: KeyState); + fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: ButtonState); fn axis_node(&self, seat: &Rc) -> Option> { let _ = seat; None @@ -318,8 +318,8 @@ struct SelectWorkspaceUsecase { struct WindowManagementUsecase; impl PointerOwner for SimplePointerOwner { - fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: KeyState) { - if state != KeyState::Pressed { + fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: ButtonState) { + if state != ButtonState::Pressed { return; } let pn = match seat.pointer_node() { @@ -436,9 +436,9 @@ impl PointerOwner for SimplePointerOwner { } impl PointerOwner for SimpleGrabPointerOwner { - fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: KeyState) { + fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: ButtonState) { match state { - KeyState::Released => { + ButtonState::Released => { if self.buttons.remove(&button).is_none() { return; } @@ -449,7 +449,7 @@ impl PointerOwner for SimpleGrabPointerOwner { seat.tree_changed.trigger(); } } - KeyState::Pressed => { + ButtonState::Pressed => { if self.buttons.insert(button, ()).is_some() { return; } @@ -493,7 +493,7 @@ impl PointerOwner for SimpleGrabPointerOwner { self.node.clone(), time_usec, button, - KeyState::Released, + ButtonState::Released, serial, ); } @@ -511,8 +511,8 @@ impl PointerOwner for SimpleGrabPointerOwner { } impl PointerOwner for DndPointerOwner { - fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: KeyState) { - if button != self.button || state != KeyState::Released { + fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: ButtonState) { + if button != self.button || state != ButtonState::Released { return; } let target = self.target.get(); @@ -1068,8 +1068,8 @@ impl PointerOwner for ToplevelGrabPointerOwner where T: WindowManagementGrabUsecase, { - fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: KeyState) { - if button != T::BUTTON || state != KeyState::Released { + fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: ButtonState) { + if button != T::BUTTON || state != ButtonState::Released { return; } self.tl.node_seat_state().remove_pointer_grab(seat); @@ -1238,12 +1238,12 @@ impl PointerOwner for UiDragPointerOwner where T: UiDragUsecase, { - fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: KeyState) { + fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: ButtonState) { if button == BTN_RIGHT { self.do_revert_to_default(seat, false); return; } - if button != BTN_LEFT || state != KeyState::Released { + if button != BTN_LEFT || state != ButtonState::Released { return; } self.apply_changes(seat); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index a108b7f4..bde99176 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -21,7 +21,7 @@ pub mod zwp_input_popup_surface_v2; use { crate::{ - backend::KeyState, + backend::{ButtonState, KeyState}, client::{Client, ClientError}, cmm::cmm_description::ColorDescription, cursor_user::{CursorUser, CursorUserId}, @@ -1907,7 +1907,7 @@ impl Node for WlSurface { seat: &Rc, time_usec: u64, button: u32, - state: KeyState, + state: ButtonState, serial: u64, ) { seat.button_surface(&self, time_usec, button, state, serial); diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index cf0d2e0b..9934fef4 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -3,10 +3,10 @@ use { allocator::{Allocator, AllocatorError}, async_engine::SpawnedFuture, backend::{ - AxisSource, Backend, BackendConnectorState, BackendEvent, Connector, ConnectorEvent, - ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice, InputDeviceAccelProfile, - InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, InputEvent, KeyState, - Mode, MonitorInfo, ScrollAxis, TransformMatrix, + AxisSource, Backend, BackendConnectorState, BackendEvent, ButtonState, Connector, + ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice, + InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, + InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix, transaction::{ BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, @@ -423,7 +423,7 @@ impl Drop for TestMouseClick { self.mouse.common.event(InputEvent::Button { time_usec: self.mouse.common.state.now_usec(), button: self.button, - state: KeyState::Released, + state: ButtonState::Released, }); } } @@ -460,7 +460,7 @@ impl TestBackendMouse { self.common.event(InputEvent::Button { time_usec: self.common.state.now_usec(), button, - state: KeyState::Pressed, + state: ButtonState::Pressed, }); TestMouseClick { mouse: self.clone(), diff --git a/src/tree.rs b/src/tree.rs index db016d30..b2461344 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::KeyState, + backend::{ButtonState, KeyState}, client::{Client, ClientId}, fixed::Fixed, ifs::{ @@ -346,7 +346,7 @@ pub trait Node: 'static { seat: &Rc, time_usec: u64, button: u32, - state: KeyState, + state: ButtonState, serial: u64, ) { let _ = seat; diff --git a/src/tree/container.rs b/src/tree/container.rs index f52ed382..c2df4a3b 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::KeyState, + backend::ButtonState, cursor::KnownCursor, cursor_user::CursorUser, fixed::Fixed, @@ -1700,11 +1700,11 @@ impl Node for ContainerNode { seat: &Rc, time_usec: u64, button: u32, - state: KeyState, + state: ButtonState, _serial: u64, ) { let id = CursorType::Seat(seat.id()); - self.button(id, seat, time_usec, state == KeyState::Pressed, button); + self.button(id, seat, time_usec, state == ButtonState::Pressed, button); } fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { diff --git a/src/tree/float.rs b/src/tree/float.rs index 214a0eab..817f525d 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::KeyState, + backend::ButtonState, cursor::KnownCursor, cursor_user::CursorUser, fixed::Fixed, @@ -776,10 +776,10 @@ impl Node for FloatNode { seat: &Rc, time_usec: u64, button: u32, - state: KeyState, + state: ButtonState, _serial: u64, ) { - if button == BTN_RIGHT && state == KeyState::Pressed { + if button == BTN_RIGHT && state == ButtonState::Pressed { self.toggle_pinned(); } if button != BTN_LEFT { @@ -790,7 +790,7 @@ impl Node for FloatNode { seat.pointer_cursor(), seat, time_usec, - state == KeyState::Pressed, + state == ButtonState::Pressed, ); } diff --git a/src/tree/output.rs b/src/tree/output.rs index e6920ef0..7910c8bf 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1,7 +1,8 @@ use { crate::{ backend::{ - BackendColorSpace, BackendConnectorState, BackendEotfs, HardwareCursor, KeyState, Mode, + BackendColorSpace, BackendConnectorState, BackendEotfs, ButtonState, HardwareCursor, + Mode, }, client::ClientId, cmm::cmm_description::ColorDescription, @@ -1685,13 +1686,13 @@ impl Node for OutputNode { seat: &Rc, _time_usec: u64, button: u32, - state: KeyState, + state: ButtonState, _serial: u64, ) { if button != BTN_LEFT { return; } - if state != KeyState::Pressed { + if state != ButtonState::Pressed { self.pointer_down.remove(&seat.id()); return; } From 4b4f05d153b6a251c883a8df5969fbdb1acadbc9 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 16 Oct 2025 21:18:26 +0200 Subject: [PATCH 2/2] seat: implement key repeat --- docs/features.md | 2 +- src/backend.rs | 1 + src/ei/ei_ifs/ei_keyboard.rs | 1 + src/ifs/wl_seat.rs | 20 ++- src/ifs/wl_seat/event_handling.rs | 123 ++++++++++++++++-- src/ifs/wl_seat/text_input/simple_im.rs | 2 +- .../zwp_input_method_keyboard_grab_v2.rs | 1 + src/ifs/wl_seat/wl_keyboard.rs | 17 ++- src/it/test_ifs/test_virtual_keyboard.rs | 1 + src/kbvm.rs | 4 + 10 files changed, 153 insertions(+), 19 deletions(-) diff --git a/docs/features.md b/docs/features.md index ba8ac527..4531c49d 100644 --- a/docs/features.md +++ b/docs/features.md @@ -170,7 +170,7 @@ Jay supports the following wayland protocols: | wl_drm | 2 | | | wl_fixes | 1 | | | wl_output | 4 | | -| wl_seat | 9 | | +| wl_seat | 10 | | | wl_shm | 2 | | | wl_subcompositor | 1 | | | wp_alpha_modifier_v1 | 1 | | diff --git a/src/backend.rs b/src/backend.rs index 0a982457..0b0eebdf 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -315,6 +315,7 @@ pub enum BackendEvent { pub enum KeyState { Released, Pressed, + Repeated, } #[derive(Debug, Copy, Clone, Eq, PartialEq)] diff --git a/src/ei/ei_ifs/ei_keyboard.rs b/src/ei/ei_ifs/ei_keyboard.rs index bd2ab4a6..271f3ed8 100644 --- a/src/ei/ei_ifs/ei_keyboard.rs +++ b/src/ei/ei_ifs/ei_keyboard.rs @@ -59,6 +59,7 @@ impl EiKeyboard { state: match state { KeyState::Released => 0, KeyState::Pressed => 1, + KeyState::Repeated => return, }, }); } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 8679d2a4..508dcf64 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -188,6 +188,12 @@ pub struct WlSeatGlobal { >, data_control_devices: CopyHashMap>, repeat_rate: Cell<(i32, i32)>, + key_repeater: Cell>>, + repeat_key: Cell>, + repeat_key_state: CloneCell>>>, + repeat_key_version: NumCell, + repeat_key_start_ns: Cell, + have_repeat_key: AsyncEvent, seat_kb_map: CloneCell>, seat_kb_state: CloneCell>>, latest_kb_state: CloneCell>, @@ -280,6 +286,12 @@ impl WlSeatGlobal { data_devices: RefCell::new(Default::default()), primary_selection_devices: RefCell::new(Default::default()), repeat_rate: Cell::new((25, 250)), + key_repeater: Default::default(), + repeat_key: Default::default(), + repeat_key_state: Default::default(), + repeat_key_version: Default::default(), + repeat_key_start_ns: Default::default(), + have_repeat_key: Default::default(), seat_kb_map: CloneCell::new(state.default_keymap.clone()), seat_kb_state: CloneCell::new(seat_kb_state.clone()), latest_kb_state: CloneCell::new(seat_kb_state.clone()), @@ -336,6 +348,7 @@ impl WlSeatGlobal { slf.pointer_cursor.set_owner(slf.clone()); slf.modifiers_listener .attach(&seat_kb_state.borrow().kb_state.leds_changed); + slf.create_repeat_handler(); let seat = slf.clone(); let future = state.eng.spawn("seat handler", async move { loop { @@ -684,8 +697,9 @@ impl WlSeatGlobal { self.repeat_rate.get() } - pub fn set_rate(&self, rate: i32, delay: i32) { + pub fn set_rate(self: &Rc, rate: i32, delay: i32) { self.repeat_rate.set((rate, delay)); + self.create_repeat_handler(); let bindings = self.bindings.borrow_mut(); for client in bindings.values() { for seat in client.values() { @@ -1252,6 +1266,8 @@ impl WlSeatGlobal { self.tablet_clear(); self.ei_seats.clear(); self.marks.clear(); + self.key_repeater.take(); + self.repeat_key_state.take(); } pub fn id(&self) -> SeatId { @@ -1482,7 +1498,7 @@ impl Global for WlSeatGlobal { } fn version(&self) -> u32 { - 9 + 10 } } diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index f84d3a33..04eea58c 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -902,6 +902,7 @@ impl WlSeatGlobal { match key_state { KeyState::Released => pk.remove(&kc.to_evdev()), KeyState::Pressed => pk.insert(kc.to_evdev()), + KeyState::Repeated => unreachable!(), } }; if key_state == KeyState::Pressed @@ -922,6 +923,7 @@ impl WlSeatGlobal { continue; } shortcuts.clear(); + let repeats; { let mut mods = kbvm_state.kb_state.mods.mods.0 & !(CAPS.0 | NUM.0); if key_state == KeyState::Released { @@ -933,6 +935,7 @@ impl WlSeatGlobal { ModifierMask::default(), kc, ); + repeats = keysyms.repeats(); let mut revert_pointer_to_default = false; for props in keysyms { let sym = props.keysym().0; @@ -982,28 +985,44 @@ impl WlSeatGlobal { } } self.send_components(&mut components_changed, &kbvm_state); - let mut forward_to_node = true; - if let Some(g) = self.input_method_grab.get() { - forward_to_node = - g.on_key(time_usec, kc.to_evdev(), key_state, &kbvm_state.kb_state); - } - if forward_to_node { - self.keyboard_node.get().node_on_key( - self, - time_usec, - kc.to_evdev(), - key_state, - &kbvm_state.kb_state, - ) - } + self.send_key(time_usec, kc, key_state, &kbvm_state.kb_state); self.for_each_ei_seat(|ei_seat| { ei_seat.handle_key(time_usec, kc.to_evdev(), key_state, &kbvm_state.kb_state); }); update_pressed_keys(&mut kbvm_state); + match key_state { + KeyState::Released => { + if self.repeat_key.get() == Some(kc) { + self.clear_repeat_key(); + } + } + KeyState::Pressed => { + if repeats { + self.set_repeat_key(kc, kbvm_state_rc); + } + } + KeyState::Repeated => {} + } } self.send_components(&mut components_changed, &kbvm_state); } + fn send_key(&self, time_usec: u64, kc: Keycode, key_state: KeyState, kb_state: &KeyboardState) { + let mut forward_to_node = true; + if let Some(g) = self.input_method_grab.get() { + forward_to_node = g.on_key(time_usec, kc.to_evdev(), key_state, kb_state); + } + if forward_to_node { + self.keyboard_node.get().node_on_key( + self, + time_usec, + kc.to_evdev(), + key_state, + kb_state, + ) + } + } + pub fn create_mark_interactive(&self) { self.mark_mode.set(Some(MarkMode::Mark)); } @@ -1306,6 +1325,82 @@ impl WlSeatGlobal { } self.changes.set(0); } + + pub(super) fn set_repeat_key(&self, key: Keycode, state: &Rc>) { + self.repeat_key_version.fetch_add(1); + self.repeat_key_start_ns.set(self.state.now_nsec()); + self.repeat_key_state.set(Some(state.clone())); + let old = self.repeat_key.replace(Some(key)); + if old.is_none() { + self.have_repeat_key.trigger(); + } + } + + pub(super) fn clear_repeat_key(&self) { + self.repeat_key_version.fetch_add(1); + self.repeat_key_state.take(); + self.repeat_key.take(); + } + + pub(super) fn create_repeat_handler(self: &Rc) { + self.clear_repeat_key(); + let (rate, delay_ms) = self.repeat_rate.get(); + self.key_repeater.take(); + if rate == 0 { + return; + } + let delay_first_repeat_ns = delay_ms as u64 * 1_000_000; + let delay_subsequent_repeat_ns = 1_000_000_000 / rate as u64; + let slf = self.clone(); + let handle = self.state.eng.spawn("key repeat", async move { + 'outer: loop { + let Some(key) = slf.repeat_key.get() else { + slf.have_repeat_key.triggered().await; + continue; + }; + let mut base_ns = slf.repeat_key_start_ns.get(); + let kbvm_state = slf.repeat_key_state.get().unwrap(); + let version = slf.repeat_key_version.get(); + macro_rules! check_version { + () => { + if slf.repeat_key_version.get() != version { + continue 'outer; + } + }; + } + let target_ns = base_ns + delay_first_repeat_ns; + slf.state.ring.timeout(target_ns).await.unwrap(); + check_version!(); + let send_key = |now_ns: u64| { + slf.send_key( + now_ns / 1_000, + key, + KeyState::Repeated, + &kbvm_state.borrow().kb_state, + ); + }; + send_key(target_ns); + base_ns = target_ns; + let mut now_ns = slf.state.now_nsec(); + loop { + let max_sleep_ns = now_ns + delay_first_repeat_ns; + let target_ns = base_ns + delay_subsequent_repeat_ns; + slf.state + .ring + .timeout(target_ns.min(max_sleep_ns)) + .await + .unwrap(); + check_version!(); + now_ns = slf.state.now_nsec(); + if now_ns >= target_ns { + send_key(target_ns); + base_ns = target_ns; + } + } + } + }); + self.key_repeater.set(Some(handle)); + } } // Button callbacks diff --git a/src/ifs/wl_seat/text_input/simple_im.rs b/src/ifs/wl_seat/text_input/simple_im.rs index 1955855e..54307e01 100644 --- a/src/ifs/wl_seat/text_input/simple_im.rs +++ b/src/ifs/wl_seat/text_input/simple_im.rs @@ -168,7 +168,7 @@ impl UnicodeInput { impl InputMethodKeyboardGrab for SimpleIm { fn on_key(&self, _time_usec: u64, key: u32, state: KeyState, kb_state: &KeyboardState) -> bool { - if state != KeyState::Pressed { + if state == KeyState::Released { return true; } let Some(con) = self.con.get() else { 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 53de655d..718f63cc 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 @@ -69,6 +69,7 @@ impl ZwpInputMethodKeyboardGrabV2 { state: match state { KeyState::Released => wl_keyboard::RELEASED, KeyState::Pressed => wl_keyboard::PRESSED, + KeyState::Repeated => return, }, }) } diff --git a/src/ifs/wl_seat/wl_keyboard.rs b/src/ifs/wl_seat/wl_keyboard.rs index a8890f50..31666748 100644 --- a/src/ifs/wl_seat/wl_keyboard.rs +++ b/src/ifs/wl_seat/wl_keyboard.rs @@ -18,6 +18,7 @@ use { }; pub const REPEAT_INFO_SINCE: Version = Version(4); +pub const REPEATED_SINCE: Version = Version(10); #[expect(dead_code)] const NO_KEYMAP: u32 = 0; @@ -25,6 +26,7 @@ pub const XKB_V1: u32 = 1; pub const RELEASED: u32 = 0; pub const PRESSED: u32 = 1; +pub const REPEATED: u32 = 2; pub struct WlKeyboard { id: WlKeyboardId, @@ -132,6 +134,9 @@ impl WlKeyboard { } fn send_key(&self, serial: u64, time: u32, key: u32, state: KeyState) { + if state == KeyState::Repeated && self.seat.version < REPEATED_SINCE { + return; + } { let pk = &mut self.pressed_keys.borrow_mut(); match state { @@ -145,6 +150,11 @@ impl WlKeyboard { return; } } + KeyState::Repeated => { + if !pk.contains(&key) { + return; + } + } } } self.seat.client.event(Key { @@ -155,6 +165,7 @@ impl WlKeyboard { state: match state { KeyState::Released => RELEASED, KeyState::Pressed => PRESSED, + KeyState::Repeated => REPEATED, }, }) } @@ -178,7 +189,11 @@ impl WlKeyboard { }) } - pub fn send_repeat_info(self: &Rc, rate: i32, delay: i32) { + pub fn send_repeat_info(self: &Rc, mut rate: i32, mut delay: i32) { + if self.seat.version >= REPEATED_SINCE { + rate = 0; + delay = 0; + } self.seat.client.event(RepeatInfo { self_id: self.id, rate, diff --git a/src/it/test_ifs/test_virtual_keyboard.rs b/src/it/test_ifs/test_virtual_keyboard.rs index 830f0be3..f85e37c5 100644 --- a/src/it/test_ifs/test_virtual_keyboard.rs +++ b/src/it/test_ifs/test_virtual_keyboard.rs @@ -46,6 +46,7 @@ impl TestVirtualKeyboard { let state = match state { KeyState::Released => wl_keyboard::RELEASED, KeyState::Pressed => wl_keyboard::PRESSED, + KeyState::Repeated => wl_keyboard::REPEATED, }; self.tran.send(Key { self_id: self.id, diff --git a/src/kbvm.rs b/src/kbvm.rs index a5b0d840..969a8846 100644 --- a/src/kbvm.rs +++ b/src/kbvm.rs @@ -213,6 +213,9 @@ impl PhysicalKeyboardState { return; } } + KeyState::Repeated => { + return; + } } let state = &mut *self.state.borrow_mut(); state.map.state_machine.handle_key( @@ -222,6 +225,7 @@ impl PhysicalKeyboardState { match key_state { KeyState::Released => Direction::Up, KeyState::Pressed => Direction::Down, + KeyState::Repeated => unreachable!(), }, ); self.events.append(&mut inner.event_stash);