From 4b4f05d153b6a251c883a8df5969fbdb1acadbc9 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 16 Oct 2025 21:18:26 +0200 Subject: [PATCH] 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);