feat: keyboard events

This commit is contained in:
drendog 2025-12-22 10:10:34 +01:00
parent 53dc2e7218
commit 7ace00da50
Signed by: dwenya
GPG key ID: 8DD77074645332D0
12 changed files with 339 additions and 5 deletions

1
Cargo.lock generated
View file

@ -2006,6 +2006,7 @@ dependencies = [
"thiserror 2.0.17",
"wayland-client",
"wayland-protocols",
"xkbcommon 0.9.0",
]
[[package]]

View file

@ -58,6 +58,7 @@ smithay-client-toolkit = "0.20.0"
thiserror = "2.0.17"
wayland-client = "0.31.11"
wayland-protocols = { version = "0.32.9", features = ["client", "staging"] }
xkbcommon = { version = "0.9.0", features = ["wayland"] }
layer-shika-domain = { version = "0.2.0", path = "crates/domain" }
layer-shika-adapters = { version = "0.2.0", path = "crates/adapters" }

View file

@ -24,3 +24,4 @@ smithay-client-toolkit.workspace = true
thiserror.workspace = true
wayland-client.workspace = true
wayland-protocols.workspace = true
xkbcommon.workspace = true

View file

@ -7,11 +7,13 @@ use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::ZwlrLayerShellV1,
zwlr_layer_surface_v1::{self, ZwlrLayerSurfaceV1},
};
use std::os::fd::AsFd;
use wayland_client::{
Connection, Dispatch, Proxy, QueueHandle, WEnum,
globals::GlobalListContents,
protocol::{
wl_compositor::WlCompositor,
wl_keyboard::{self, WlKeyboard},
wl_output::{self, WlOutput},
wl_pointer::{self, WlPointer},
wl_registry::Event,
@ -275,6 +277,58 @@ impl Dispatch<WlPointer, ()> for AppState {
}
}
impl Dispatch<WlKeyboard, ()> for AppState {
fn event(
state: &mut Self,
_proxy: &WlKeyboard,
event: <WlKeyboard as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
match event {
wl_keyboard::Event::Keymap {
format: WEnum::Value(wl_keyboard::KeymapFormat::XkbV1),
fd,
size,
} => {
state.handle_keymap(fd.as_fd(), size);
}
wl_keyboard::Event::Enter {
serial,
surface,
keys,
} => {
state.handle_keyboard_enter(serial, &surface, &keys);
}
wl_keyboard::Event::Leave { serial, surface } => {
state.handle_keyboard_leave(serial, &surface);
}
wl_keyboard::Event::Key {
serial,
time,
key,
state: WEnum::Value(key_state),
} => {
state.handle_key(serial, time, key, key_state);
}
wl_keyboard::Event::Modifiers {
serial,
mods_depressed,
mods_latched,
mods_locked,
group,
} => {
state.handle_modifiers(serial, mods_depressed, mods_latched, mods_locked, group);
}
wl_keyboard::Event::RepeatInfo { rate, delay } => {
state.handle_repeat_info(rate, delay);
}
_ => {}
}
}
}
impl Dispatch<WpFractionalScaleV1, ()> for AppState {
fn event(
state: &mut Self,

View file

@ -1,16 +1,20 @@
use crate::wayland::surfaces::keyboard_state::KeyboardState;
use crate::wayland::surfaces::surface_state::SurfaceState;
use log::info;
use slint::{
PhysicalSize,
platform::{PointerEventButton, WindowEvent},
platform::{Key, PointerEventButton, WindowEvent},
};
use slint::SharedString;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
};
use wayland_client::backend::ObjectId;
use wayland_client::WEnum;
use wayland_client::{
Proxy,
protocol::{
wl_keyboard,
wl_pointer,
wl_surface::WlSurface,
},
@ -23,6 +27,7 @@ use wayland_protocols::xdg::shell::client::{
xdg_surface::XdgSurface,
xdg_wm_base::XdgWmBase,
};
use xkbcommon::xkb;
impl SurfaceState {
#[allow(clippy::cast_possible_truncation)]
@ -166,6 +171,47 @@ impl SurfaceState {
}
}
pub(crate) fn handle_keyboard_key(
&mut self,
surface_id: &ObjectId,
key: u32,
state: wl_keyboard::KeyState,
keyboard_state: &mut KeyboardState,
) {
let Some(xkb_state) = keyboard_state.xkb_state.as_mut() else {
return;
};
let keycode = xkb::Keycode::new(key + 8);
let direction = match state {
wl_keyboard::KeyState::Pressed => xkb::KeyDirection::Down,
wl_keyboard::KeyState::Released => xkb::KeyDirection::Up,
_ => return,
};
xkb_state.update_key(keycode, direction);
let text = xkb_state.key_get_utf8(keycode);
let text = if text.is_empty() {
let keysym = xkb_state.key_get_one_sym(keycode);
keysym_to_text(keysym)
} else {
Some(SharedString::from(text.as_str()))
};
let Some(text) = text else {
return;
};
let event = match state {
wl_keyboard::KeyState::Pressed => WindowEvent::KeyPressed { text },
wl_keyboard::KeyState::Released => WindowEvent::KeyReleased { text },
_ => return,
};
self.dispatch_to_surface(surface_id, event);
}
pub(crate) fn handle_fractional_scale(&mut self, proxy: &WpFractionalScaleV1, scale: u32) {
use crate::wayland::surfaces::display_metrics::DisplayMetrics;
let scale_float = DisplayMetrics::scale_factor_from_120ths(scale);
@ -226,3 +272,48 @@ impl SurfaceState {
xdg_wm_base.pong(serial);
}
}
fn keysym_to_text(keysym: xkb::Keysym) -> Option<SharedString> {
let key = match keysym.raw() {
xkb::keysyms::KEY_Return | xkb::keysyms::KEY_KP_Enter => Key::Return,
xkb::keysyms::KEY_BackSpace => Key::Backspace,
xkb::keysyms::KEY_Tab => Key::Tab,
xkb::keysyms::KEY_BackTab => Key::Backtab,
xkb::keysyms::KEY_Escape => Key::Escape,
xkb::keysyms::KEY_Delete => Key::Delete,
xkb::keysyms::KEY_Insert => Key::Insert,
xkb::keysyms::KEY_Home => Key::Home,
xkb::keysyms::KEY_End => Key::End,
xkb::keysyms::KEY_Page_Up => Key::PageUp,
xkb::keysyms::KEY_Page_Down => Key::PageDown,
xkb::keysyms::KEY_Left => Key::LeftArrow,
xkb::keysyms::KEY_Right => Key::RightArrow,
xkb::keysyms::KEY_Up => Key::UpArrow,
xkb::keysyms::KEY_Down => Key::DownArrow,
xkb::keysyms::KEY_space => Key::Space,
xkb::keysyms::KEY_Shift_L => Key::Shift,
xkb::keysyms::KEY_Shift_R => Key::ShiftR,
xkb::keysyms::KEY_Control_L => Key::Control,
xkb::keysyms::KEY_Control_R => Key::ControlR,
xkb::keysyms::KEY_Alt_L | xkb::keysyms::KEY_Alt_R => Key::Alt,
xkb::keysyms::KEY_Mode_switch => Key::AltGr,
xkb::keysyms::KEY_Meta_L => Key::Meta,
xkb::keysyms::KEY_Meta_R => Key::MetaR,
xkb::keysyms::KEY_Caps_Lock => Key::CapsLock,
xkb::keysyms::KEY_F1 => Key::F1,
xkb::keysyms::KEY_F2 => Key::F2,
xkb::keysyms::KEY_F3 => Key::F3,
xkb::keysyms::KEY_F4 => Key::F4,
xkb::keysyms::KEY_F5 => Key::F5,
xkb::keysyms::KEY_F6 => Key::F6,
xkb::keysyms::KEY_F7 => Key::F7,
xkb::keysyms::KEY_F8 => Key::F8,
xkb::keysyms::KEY_F9 => Key::F9,
xkb::keysyms::KEY_F10 => Key::F10,
xkb::keysyms::KEY_F11 => Key::F11,
xkb::keysyms::KEY_F12 => Key::F12,
_ => return None,
};
Some(SharedString::from(key))
}

View file

@ -1,5 +1,8 @@
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use wayland_client::{protocol::{wl_pointer::WlPointer, wl_surface::WlSurface}, Connection};
use wayland_client::{
protocol::{wl_keyboard::WlKeyboard, wl_pointer::WlPointer, wl_surface::WlSurface},
Connection,
};
use wayland_protocols::wp::{
fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1,
viewporter::client::wp_viewport::WpViewport,
@ -40,6 +43,39 @@ impl Drop for ManagedWlPointer {
}
}
pub struct ManagedWlKeyboard {
keyboard: Rc<WlKeyboard>,
connection: Rc<Connection>,
}
impl ManagedWlKeyboard {
#[must_use]
pub const fn new(keyboard: Rc<WlKeyboard>, connection: Rc<Connection>) -> Self {
Self {
keyboard,
connection,
}
}
}
impl Deref for ManagedWlKeyboard {
type Target = WlKeyboard;
fn deref(&self) -> &Self::Target {
&self.keyboard
}
}
impl Drop for ManagedWlKeyboard {
fn drop(&mut self) {
debug!("Releasing WlKeyboard");
self.keyboard.release();
if let Err(e) = self.connection.flush() {
error!("Failed to flush after releasing WlKeyboard: {e}");
}
}
}
pub struct ManagedWlSurface {
surface: Rc<WlSurface>,
connection: Rc<Connection>,

View file

@ -1,7 +1,7 @@
use crate::wayland::{
config::{LayerSurfaceConfig, ShellSurfaceConfig, WaylandSurfaceConfig},
globals::context::GlobalContext,
managed_proxies::ManagedWlPointer,
managed_proxies::{ManagedWlKeyboard, ManagedWlPointer},
ops::WaylandSystemOps,
outputs::{OutputManager, OutputManagerContext},
surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams},
@ -276,10 +276,12 @@ impl WaylandShellSystem {
let layer_surface_config = Self::create_layer_surface_config(config);
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
let keyboard = Rc::new(global_ctx.seat.get_keyboard(&event_queue.handle(), ()));
let shared_serial = Rc::new(SharedPointerSerial::new());
let mut app_state = AppState::new(
ManagedWlPointer::new(Rc::clone(&pointer), Rc::new(connection.clone())),
ManagedWlKeyboard::new(Rc::clone(&keyboard), Rc::new(connection.clone())),
Rc::clone(&shared_serial),
);
@ -342,10 +344,12 @@ impl WaylandShellSystem {
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?;
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
let keyboard = Rc::new(global_ctx.seat.get_keyboard(&event_queue.handle(), ()));
let shared_serial = Rc::new(SharedPointerSerial::new());
let mut app_state = AppState::new(
ManagedWlPointer::new(Rc::clone(&pointer), Rc::new(connection.clone())),
ManagedWlKeyboard::new(Rc::clone(&keyboard), Rc::new(connection.clone())),
Rc::clone(&shared_serial),
);

View file

@ -1,6 +1,7 @@
use super::event_context::SharedPointerSerial;
use super::keyboard_state::KeyboardState;
use super::surface_state::SurfaceState;
use crate::wayland::managed_proxies::ManagedWlPointer;
use crate::wayland::managed_proxies::{ManagedWlKeyboard, ManagedWlPointer};
use crate::wayland::outputs::{OutputManager, OutputMapping};
use layer_shika_domain::entities::output_registry::OutputRegistry;
use layer_shika_domain::value_objects::handle::SurfaceHandle;
@ -8,9 +9,13 @@ use layer_shika_domain::value_objects::output_handle::OutputHandle;
use layer_shika_domain::value_objects::output_info::OutputInfo;
use std::cell::RefCell;
use std::collections::HashMap;
use std::os::fd::BorrowedFd;
use std::rc::Rc;
use wayland_client::Proxy;
use wayland_client::backend::ObjectId;
use wayland_client::protocol::wl_keyboard;
use wayland_client::protocol::wl_surface::WlSurface;
use xkbcommon::xkb;
pub type PerOutputSurface = SurfaceState;
@ -36,14 +41,22 @@ pub struct AppState {
surface_to_key: HashMap<ObjectId, ShellSurfaceKey>,
surface_handle_to_name: HashMap<SurfaceHandle, String>,
_pointer: ManagedWlPointer,
_keyboard: ManagedWlKeyboard,
shared_pointer_serial: Rc<SharedPointerSerial>,
output_manager: Option<Rc<RefCell<OutputManager>>>,
registry_name_to_output_id: HashMap<u32, ObjectId>,
active_surface_key: Option<ShellSurfaceKey>,
keyboard_focus_key: Option<ShellSurfaceKey>,
keyboard_focus_surface_id: Option<ObjectId>,
keyboard_state: KeyboardState,
}
impl AppState {
pub fn new(pointer: ManagedWlPointer, shared_serial: Rc<SharedPointerSerial>) -> Self {
pub fn new(
pointer: ManagedWlPointer,
keyboard: ManagedWlKeyboard,
shared_serial: Rc<SharedPointerSerial>,
) -> Self {
Self {
output_registry: OutputRegistry::new(),
output_mapping: OutputMapping::new(),
@ -51,10 +64,14 @@ impl AppState {
surface_to_key: HashMap::new(),
surface_handle_to_name: HashMap::new(),
_pointer: pointer,
_keyboard: keyboard,
shared_pointer_serial: shared_serial,
output_manager: None,
registry_name_to_output_id: HashMap::new(),
active_surface_key: None,
keyboard_focus_key: None,
keyboard_focus_surface_id: None,
keyboard_state: KeyboardState::new(),
}
}
@ -322,6 +339,84 @@ impl AppState {
&self.shared_pointer_serial
}
pub fn handle_keymap(&mut self, fd: BorrowedFd<'_>, size: u32) {
let Ok(fd) = fd.try_clone_to_owned() else {
return;
};
let keymap = unsafe {
xkb::Keymap::new_from_fd(
&self.keyboard_state.xkb_context,
fd,
size as usize,
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
)
};
if let Ok(Some(keymap)) = keymap {
self.keyboard_state.set_keymap(keymap);
}
}
pub fn handle_keyboard_enter(&mut self, _serial: u32, surface: &WlSurface, _keys: &[u8]) {
let surface_id = surface.id();
if let Some(key) = self.get_key_by_surface(&surface_id).cloned() {
self.set_keyboard_focus(Some(key), Some(surface_id));
return;
}
if let Some(key) = self.get_key_by_popup(&surface_id).cloned() {
self.set_keyboard_focus(Some(key), Some(surface_id));
}
}
pub fn handle_keyboard_leave(&mut self, _serial: u32, surface: &WlSurface) {
if self.keyboard_focus_surface_id == Some(surface.id()) {
self.set_keyboard_focus(None, None);
}
}
pub fn handle_key(&mut self, _serial: u32, _time: u32, key: u32, state: wl_keyboard::KeyState) {
let Some(focus_key) = self.keyboard_focus_key.clone() else {
return;
};
let Some(surface_id) = self.keyboard_focus_surface_id.clone() else {
return;
};
let keyboard_state = &mut self.keyboard_state;
if let Some(surface) = self.surfaces.get_mut(&focus_key) {
surface.handle_keyboard_key(&surface_id, key, state, keyboard_state);
}
}
pub fn handle_modifiers(
&mut self,
_serial: u32,
mods_depressed: u32,
mods_latched: u32,
mods_locked: u32,
group: u32,
) {
if let Some(state) = self.keyboard_state.xkb_state.as_mut() {
state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
}
}
pub fn handle_repeat_info(&mut self, rate: i32, delay: i32) {
self.keyboard_state.repeat_rate = rate;
self.keyboard_state.repeat_delay = delay;
}
fn set_keyboard_focus(&mut self, key: Option<ShellSurfaceKey>, surface_id: Option<ObjectId>) {
if let Some(ref k) = key {
self.output_registry.set_active(Some(k.output_handle));
}
self.keyboard_focus_key = key;
self.keyboard_focus_surface_id = surface_id;
}
pub fn find_output_by_popup(&self, popup_surface_id: &ObjectId) -> Option<&PerOutputSurface> {
self.surfaces.values().find(|surface| {
surface

View file

@ -183,6 +183,22 @@ impl EventContext {
}
}
pub fn dispatch_to_surface(&self, surface_id: &ObjectId, event: WindowEvent) {
if self.main_surface_id == *surface_id {
self.main_window.window().dispatch_event(event);
return;
}
if let Some(popup_manager) = &self.popup_manager {
if let Some(handle) = popup_manager.find_by_surface(surface_id) {
if let Some(popup_surface) = popup_manager.get_popup_window(handle.key()) {
popup_surface.dispatch_event(event);
popup_surface.request_redraw();
}
}
}
}
pub fn update_output_size(&self, output_size: PhysicalSize) {
if let Some(popup_manager) = &self.popup_manager {
popup_manager.update_output_size(output_size);

View file

@ -0,0 +1,27 @@
use xkbcommon::xkb;
pub struct KeyboardState {
pub(crate) xkb_context: xkb::Context,
pub(crate) xkb_keymap: Option<xkb::Keymap>,
pub(crate) xkb_state: Option<xkb::State>,
pub(crate) repeat_rate: i32,
pub(crate) repeat_delay: i32,
}
impl KeyboardState {
#[must_use]
pub fn new() -> Self {
Self {
xkb_context: xkb::Context::new(xkb::CONTEXT_NO_FLAGS),
xkb_keymap: None,
xkb_state: None,
repeat_rate: 25,
repeat_delay: 600,
}
}
pub fn set_keymap(&mut self, keymap: xkb::Keymap) {
self.xkb_state = Some(xkb::State::new(&keymap));
self.xkb_keymap = Some(keymap);
}
}

View file

@ -3,6 +3,7 @@ pub mod component_state;
pub mod dimensions;
pub mod display_metrics;
pub mod event_context;
pub mod keyboard_state;
pub mod layer_surface;
pub mod popup_manager;
pub mod popup_surface;

View file

@ -22,6 +22,7 @@ use slint_interpreter::{ComponentInstance, CompilationResult};
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use wayland_client::{
Proxy,
backend::ObjectId,
protocol::{wl_pointer, wl_surface::WlSurface},
};
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
@ -275,6 +276,12 @@ impl SurfaceState {
self.event_context.borrow().dispatch_to_active_window(event);
}
pub fn dispatch_to_surface(&self, surface_id: &ObjectId, event: WindowEvent) {
self.event_context
.borrow()
.dispatch_to_surface(surface_id, event);
}
pub fn set_axis_source(&self, axis_source: wl_pointer::AxisSource) {
self.event_context.borrow_mut().set_axis_source(axis_source);
}