diff --git a/Cargo.lock b/Cargo.lock index de80f10..8ffedc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2006,6 +2006,7 @@ dependencies = [ "thiserror 2.0.17", "wayland-client", "wayland-protocols", + "xkbcommon 0.9.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 44b6801..81c8e0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/crates/adapters/Cargo.toml b/crates/adapters/Cargo.toml index 99a2d9a..9d0b3ac 100644 --- a/crates/adapters/Cargo.toml +++ b/crates/adapters/Cargo.toml @@ -24,3 +24,4 @@ smithay-client-toolkit.workspace = true thiserror.workspace = true wayland-client.workspace = true wayland-protocols.workspace = true +xkbcommon.workspace = true diff --git a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs index a332726..89bf92a 100644 --- a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs +++ b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs @@ -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 for AppState { } } +impl Dispatch for AppState { + fn event( + state: &mut Self, + _proxy: &WlKeyboard, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + 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 for AppState { fn event( state: &mut Self, diff --git a/crates/adapters/src/wayland/event_handling/event_dispatcher.rs b/crates/adapters/src/wayland/event_handling/event_dispatcher.rs index a29e57d..1a4c903 100644 --- a/crates/adapters/src/wayland/event_handling/event_dispatcher.rs +++ b/crates/adapters/src/wayland/event_handling/event_dispatcher.rs @@ -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 { + 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)) +} diff --git a/crates/adapters/src/wayland/managed_proxies.rs b/crates/adapters/src/wayland/managed_proxies.rs index 09bb577..a7fa73c 100644 --- a/crates/adapters/src/wayland/managed_proxies.rs +++ b/crates/adapters/src/wayland/managed_proxies.rs @@ -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, + connection: Rc, +} + +impl ManagedWlKeyboard { + #[must_use] + pub const fn new(keyboard: Rc, connection: Rc) -> 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, connection: Rc, diff --git a/crates/adapters/src/wayland/shell_adapter.rs b/crates/adapters/src/wayland/shell_adapter.rs index 964c015..b269c42 100644 --- a/crates/adapters/src/wayland/shell_adapter.rs +++ b/crates/adapters/src/wayland/shell_adapter.rs @@ -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), ); diff --git a/crates/adapters/src/wayland/surfaces/app_state.rs b/crates/adapters/src/wayland/surfaces/app_state.rs index 4adb9ef..1421ff7 100644 --- a/crates/adapters/src/wayland/surfaces/app_state.rs +++ b/crates/adapters/src/wayland/surfaces/app_state.rs @@ -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, surface_handle_to_name: HashMap, _pointer: ManagedWlPointer, + _keyboard: ManagedWlKeyboard, shared_pointer_serial: Rc, output_manager: Option>>, registry_name_to_output_id: HashMap, active_surface_key: Option, + keyboard_focus_key: Option, + keyboard_focus_surface_id: Option, + keyboard_state: KeyboardState, } impl AppState { - pub fn new(pointer: ManagedWlPointer, shared_serial: Rc) -> Self { + pub fn new( + pointer: ManagedWlPointer, + keyboard: ManagedWlKeyboard, + shared_serial: Rc, + ) -> 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, surface_id: Option) { + 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 diff --git a/crates/adapters/src/wayland/surfaces/event_context.rs b/crates/adapters/src/wayland/surfaces/event_context.rs index 8e81984..6d6fa03 100644 --- a/crates/adapters/src/wayland/surfaces/event_context.rs +++ b/crates/adapters/src/wayland/surfaces/event_context.rs @@ -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); diff --git a/crates/adapters/src/wayland/surfaces/keyboard_state.rs b/crates/adapters/src/wayland/surfaces/keyboard_state.rs new file mode 100644 index 0000000..d130e44 --- /dev/null +++ b/crates/adapters/src/wayland/surfaces/keyboard_state.rs @@ -0,0 +1,27 @@ +use xkbcommon::xkb; + +pub struct KeyboardState { + pub(crate) xkb_context: xkb::Context, + pub(crate) xkb_keymap: Option, + pub(crate) xkb_state: Option, + 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); + } +} diff --git a/crates/adapters/src/wayland/surfaces/mod.rs b/crates/adapters/src/wayland/surfaces/mod.rs index 442e90d..ec7a3cc 100644 --- a/crates/adapters/src/wayland/surfaces/mod.rs +++ b/crates/adapters/src/wayland/surfaces/mod.rs @@ -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; diff --git a/crates/adapters/src/wayland/surfaces/surface_state.rs b/crates/adapters/src/wayland/surfaces/surface_state.rs index ac37901..d93b793 100644 --- a/crates/adapters/src/wayland/surfaces/surface_state.rs +++ b/crates/adapters/src/wayland/surfaces/surface_state.rs @@ -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); }