// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. //! Wayland keyboard → ltk dispatch. //! //! Translates `wl_keyboard` events into focus / text-insertion / //! widget-submit actions. Does not share state with the gesture state //! machine — keyboard tracks its own focus (`AppData::keyboard_focus`) //! and modifier flags (`shift_pressed`, `ctrl_pressed`). //! //! The only cross-cutting state it reads is `SurfaceState::focused_idx` //! (set by pointer / touch focus updates) so keyboard navigation can //! branch on "is a widget focused?" — Return submits / Space presses / //! typing inserts all funnel through that check. use smithay_client_toolkit::seat::keyboard:: { KeyboardHandler, KeyEvent, Keysym, Modifiers, RawModifiers, RepeatInfo, }; use smithay_client_toolkit::reexports::client:: { protocol::{ wl_keyboard::WlKeyboard, wl_surface::WlSurface }, Connection, QueueHandle, }; use crate::app::App; use crate::event_loop::{ AppData, SurfaceFocus }; pub( super ) mod dispatch; pub( super ) mod text_keys; pub( super ) mod shortcuts; pub( super ) mod nav; impl KeyboardHandler for AppData { fn enter( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &WlKeyboard, surface: &WlSurface, _serial: u32, _raw: &[u32], _keysyms: &[Keysym], ) { let focus = self.focus_for_surface( surface ).unwrap_or( SurfaceFocus::Main ); self.keyboard_focus = focus; self.surface_mut( focus ).request_redraw(); } fn leave( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &WlKeyboard, _surface: &WlSurface, _serial: u32, ) { self.stop_key_repeat(); self.keyboard_focus = SurfaceFocus::Main; } fn press_key( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &WlKeyboard, _serial: u32, event: KeyEvent, ) { let focus = self.keyboard_focus; // Raw observer hook (e.g. forwarding to an embedded WPE view). // Fires before the focus-aware dispatch. self.app.on_raw_key( event.keysym, event.raw_code, true, self.ctrl_pressed, self.shift_pressed ); // A new press always cancels any in-flight repeat — the user // has either released the previous key or is pressing a // different one. Either way, the prior timer's keysym should // not keep firing. self.stop_key_repeat(); self.dispatch_key( focus, event.clone() ); self.start_key_repeat( focus, event ); } fn release_key( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &WlKeyboard, _serial: u32, event: KeyEvent, ) { self.app.on_raw_key( event.keysym, event.raw_code, false, self.ctrl_pressed, self.shift_pressed ); // Only stop if the released key matches the one currently // repeating; releasing a non-repeating key (e.g. a shift that // snuck through, or any key we never armed) leaves the timer // untouched. if let Some( ref state ) = self.key_repeat { if state.event.keysym == event.keysym { self.stop_key_repeat(); } } } fn update_modifiers( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &WlKeyboard, _serial: u32, modifiers: Modifiers, _raw_modifiers: RawModifiers, _layout: u32, ) { self.shift_pressed = modifiers.shift; self.ctrl_pressed = modifiers.ctrl; } fn update_repeat_info( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &WlKeyboard, info: RepeatInfo, ) { match info { RepeatInfo::Repeat { rate, delay } => { self.compositor_repeat_rate = rate.get(); self.compositor_repeat_delay = delay; } RepeatInfo::Disable => { self.compositor_repeat_rate = 0; self.compositor_repeat_delay = 0; self.stop_key_repeat(); } } } fn repeat_key( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &WlKeyboard, _serial: u32, event: KeyEvent, ) { // Compositor-driven repeat (wl_keyboard v10). When the // compositor takes the repeat job we just re-dispatch — and // suppress our internal timer to avoid double-firing. self.stop_key_repeat(); let focus = self.keyboard_focus; self.dispatch_key( focus, event ); } }