From f6dce3316a7ff0cbb89780055902ba048445e2bc Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Fri, 25 Sep 2026 01:07:44 +0100 Subject: [PATCH] feat: support authorized input methods --- README.md | 13 +++++- compositor/src/handlers/input_method.rs | 61 +++++++++++++++++++++++++ compositor/src/handlers/mod.rs | 3 ++ compositor/src/state.rs | 11 +++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 compositor/src/handlers/input_method.rs diff --git a/README.md b/README.md index bb04b43..bc0ed29 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Ferese is an experimental Wayland tiling compositor written in Rust with Smithay. The nested M0–M3 path is complete, including tiling, workspaces, and timestamp-driven animated geometry. Development now targets the M4 early -hardware-session milestone in the +hardware-session acceptance pass and M5 native interoperability in the [window-manager specification](docs/ferese-window-manager-spec.md). ## Development @@ -23,6 +23,17 @@ cargo run -p ferese -- foot Set `RUST_LOG=ferese=debug` for detailed compositor logging. +Text-input-v3 is available to applications by default. Input-method-v2 is +hidden unless the session explicitly opts in, because an input method can +request privileged keyboard access. Enable it only when launching a trusted +IME for the session: + +```bash +FERESE_ENABLE_INPUT_METHOD=1 cargo run -p ferese +``` + +`Ctrl+Alt+Escape` always releases an active client shortcut inhibitor. + ### Direct DRM session Run the hardware backend from a spare TTY, outside another graphical session: diff --git a/compositor/src/handlers/input_method.rs b/compositor/src/handlers/input_method.rs new file mode 100644 index 0000000..904d371 --- /dev/null +++ b/compositor/src/handlers/input_method.rs @@ -0,0 +1,61 @@ +use smithay::{ + desktop::{PopupKind, WindowSurfaceType, layer_map_for_output}, + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::{Logical, Rectangle}, + wayland::input_method::{InputMethodHandler, PopupSurface}, +}; + +use crate::Ferese; + +impl InputMethodHandler for Ferese { + fn new_popup(&mut self, surface: PopupSurface) { + if let Err(error) = self + .popups + .track_popup(PopupKind::InputMethod(surface.clone())) + { + tracing::warn!(?error, "failed to track input method popup"); + } + crate::backends::direct::render_all(self); + } + + fn dismiss_popup(&mut self, _surface: PopupSurface) { + self.popups.cleanup(); + crate::backends::direct::render_all(self); + } + + fn popup_repositioned(&mut self, _surface: PopupSurface) { + crate::backends::direct::render_all(self); + } + + fn parent_geometry(&self, parent: &WlSurface) -> Rectangle { + if let Some(geometry) = self.space.elements().find_map(|window| { + window + .toplevel() + .is_some_and(|toplevel| toplevel.wl_surface() == parent) + .then(|| self.space.element_geometry(window)) + .flatten() + }) { + return geometry; + } + + let Some(layer) = self + .space + .layer_for_surface(parent, WindowSurfaceType::TOPLEVEL) + else { + return Rectangle::default(); + }; + + self.space + .outputs() + .find_map(|output| { + let output_geometry = self.space.output_geometry(output)?; + let layer_geometry = layer_map_for_output(output).layer_geometry(&layer)?; + + Some(Rectangle::new( + output_geometry.loc + layer_geometry.loc, + layer_geometry.size, + )) + }) + .unwrap_or_default() + } +} diff --git a/compositor/src/handlers/mod.rs b/compositor/src/handlers/mod.rs index 33c2fe5..909ea62 100644 --- a/compositor/src/handlers/mod.rs +++ b/compositor/src/handlers/mod.rs @@ -1,5 +1,6 @@ mod activation; mod compositor; +mod input_method; mod layer_shell; mod xdg_shell; @@ -237,6 +238,7 @@ smithay::delegate_data_device!(Ferese); smithay::delegate_fractional_scale!(Ferese); smithay::delegate_idle_inhibit!(Ferese); smithay::delegate_idle_notify!(Ferese); +smithay::delegate_input_method_manager!(Ferese); smithay::delegate_keyboard_shortcuts_inhibit!(Ferese); smithay::delegate_layer_shell!(Ferese); smithay::delegate_output!(Ferese); @@ -247,6 +249,7 @@ smithay::delegate_relative_pointer!(Ferese); smithay::delegate_seat!(Ferese); smithay::delegate_shm!(Ferese); smithay::delegate_single_pixel_buffer!(Ferese); +smithay::delegate_text_input_manager!(Ferese); smithay::delegate_viewporter!(Ferese); smithay::delegate_xdg_activation!(Ferese); smithay::delegate_xdg_decoration!(Ferese); diff --git a/compositor/src/state.rs b/compositor/src/state.rs index 753f8e1..c324193 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -37,6 +37,7 @@ use smithay::{ fractional_scale::FractionalScaleManagerState, idle_inhibit::IdleInhibitManagerState, idle_notify::IdleNotifierState, + input_method::InputMethodManagerState, keyboard_shortcuts_inhibit::{KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor}, output::OutputManagerState, pointer_constraints::PointerConstraintsState, @@ -50,6 +51,7 @@ use smithay::{ shm::ShmState, single_pixel_buffer::SinglePixelBufferState, socket::ListeningSocketSource, + text_input::TextInputManagerState, viewporter::ViewporterState, xdg_activation::XdgActivationState, xdg_foreign::XdgForeignState, @@ -84,6 +86,7 @@ pub struct Ferese { pub fractional_scale_state: FractionalScaleManagerState, pub idle_inhibit_state: IdleInhibitManagerState, pub idle_notifier_state: IdleNotifierState, + pub input_method_manager_state: InputMethodManagerState, pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub output_manager_state: OutputManagerState, pub pointer_constraints_state: PointerConstraintsState, @@ -93,6 +96,7 @@ pub struct Ferese { pub seat_state: SeatState, pub shm_state: ShmState, pub single_pixel_buffer_state: SinglePixelBufferState, + pub text_input_manager_state: TextInputManagerState, pub viewporter_state: ViewporterState, pub layer_shell_state: WlrLayerShellState, pub xdg_activation_state: XdgActivationState, @@ -113,10 +117,15 @@ impl Ferese { let fractional_scale_state = FractionalScaleManagerState::new::(&display_handle); let idle_inhibit_state = IdleInhibitManagerState::new::(&display_handle); let idle_notifier_state = IdleNotifierState::new(&display_handle, event_loop.handle()); + let input_method_enabled = + std::env::var_os("FERESE_ENABLE_INPUT_METHOD").is_some_and(|value| value == "1"); + let input_method_manager_state = + InputMethodManagerState::new::(&display_handle, move |_| input_method_enabled); let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(&display_handle); let shm_state = ShmState::new::(&display_handle, Vec::new()); let single_pixel_buffer_state = SinglePixelBufferState::new::(&display_handle); + let text_input_manager_state = TextInputManagerState::new::(&display_handle); let output_manager_state = OutputManagerState::new_with_xdg_output::(&display_handle); let pointer_constraints_state = PointerConstraintsState::new::(&display_handle); let presentation_state = @@ -167,6 +176,7 @@ impl Ferese { fractional_scale_state, idle_inhibit_state, idle_notifier_state, + input_method_manager_state, keyboard_shortcuts_inhibit_state, output_manager_state, pointer_constraints_state, @@ -176,6 +186,7 @@ impl Ferese { seat_state, shm_state, single_pixel_buffer_state, + text_input_manager_state, viewporter_state, layer_shell_state, xdg_activation_state,