From 6dc2a5300e80034702a4e378105112395a1728d4 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Fri, 25 Sep 2026 01:00:58 +0100 Subject: [PATCH] feat: enforce focused shortcut inhibition --- compositor/src/handlers/mod.rs | 41 ++++++++++++++++++++++++++++++++++ compositor/src/input.rs | 34 ++++++++++++++++++++++++++-- compositor/src/state.rs | 11 +++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/compositor/src/handlers/mod.rs b/compositor/src/handlers/mod.rs index 5d362ba..33c2fe5 100644 --- a/compositor/src/handlers/mod.rs +++ b/compositor/src/handlers/mod.rs @@ -15,6 +15,10 @@ use smithay::{ fractional_scale::{FractionalScaleHandler, with_fractional_scale}, idle_inhibit::IdleInhibitHandler, idle_notify::{IdleNotifierHandler, IdleNotifierState}, + keyboard_shortcuts_inhibit::{ + KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, + KeyboardShortcutsInhibitor, KeyboardShortcutsInhibitorSeat, + }, output::OutputHandler, pointer_constraints::{ PointerConstraint, PointerConstraintsHandler, with_pointer_constraint, @@ -56,6 +60,18 @@ impl SeatHandler for Ferese { } fn focus_changed(&mut self, seat: &Seat, focused: Option<&WlSurface>) { + if let Some(inhibitor) = self.active_shortcuts_inhibitor.take() + && inhibitor.is_active() + { + inhibitor.inactivate(); + } + if let Some(inhibitor) = + focused.and_then(|surface| seat.keyboard_shortcuts_inhibitor_for_surface(surface)) + { + inhibitor.activate(); + self.active_shortcuts_inhibitor = Some(inhibitor); + } + let client = focused.and_then(|surface| self.display_handle.get_client(surface.id()).ok()); set_data_device_focus(&self.display_handle, seat, client.clone()); set_primary_focus(&self.display_handle, seat, client); @@ -106,6 +122,30 @@ impl IdleInhibitHandler for Ferese { } } +impl KeyboardShortcutsInhibitHandler for Ferese { + fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState { + &mut self.keyboard_shortcuts_inhibit_state + } + + fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) { + let focused = self + .seat + .get_keyboard() + .and_then(|keyboard| keyboard.current_focus()); + + if focused.as_ref() == Some(inhibitor.wl_surface()) { + inhibitor.activate(); + self.active_shortcuts_inhibitor = Some(inhibitor); + } + } + + fn inhibitor_destroyed(&mut self, inhibitor: KeyboardShortcutsInhibitor) { + if self.active_shortcuts_inhibitor.as_ref() == Some(&inhibitor) { + self.active_shortcuts_inhibitor = None; + } + } +} + impl Ferese { pub fn update_fractional_scale(&self, scale: f64) { for window in self.space.elements() { @@ -197,6 +237,7 @@ smithay::delegate_data_device!(Ferese); smithay::delegate_fractional_scale!(Ferese); smithay::delegate_idle_inhibit!(Ferese); smithay::delegate_idle_notify!(Ferese); +smithay::delegate_keyboard_shortcuts_inhibit!(Ferese); smithay::delegate_layer_shell!(Ferese); smithay::delegate_output!(Ferese); smithay::delegate_pointer_constraints!(Ferese); diff --git a/compositor/src/input.rs b/compositor/src/input.rs index 288253c..1dc2636 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -9,7 +9,10 @@ use smithay::{ }, reexports::wayland_server::protocol::wl_surface::WlSurface, utils::{Logical, Point, SERIAL_COUNTER, Serial}, - wayland::pointer_constraints::{PointerConstraint, with_pointer_constraint}, + wayland::{ + keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, + pointer_constraints::{PointerConstraint, with_pointer_constraint}, + }, }; use crate::Ferese; @@ -38,6 +41,22 @@ impl Ferese { } let symbol = keysym.modified_sym().raw(); + if emergency_shortcut_escape(symbol, modifiers.ctrl, modifiers.alt) { + if state == KeyState::Pressed { + data.intercepted_keys.insert(keycode); + + if let Some(inhibitor) = data.active_shortcuts_inhibitor.take() { + inhibitor.inactivate(); + } + } + + return FilterResult::Intercept(()); + } + + if data.seat.keyboard_shortcuts_inhibited() { + return FilterResult::Forward; + } + if let Some(vt) = virtual_terminal( symbol, modifiers.ctrl, @@ -383,9 +402,20 @@ fn virtual_terminal(symbol: u32, ctrl: bool, alt: bool, direct: bool) -> Option< .then_some((symbol - keysyms::KEY_F1 + 1) as i32) } +fn emergency_shortcut_escape(symbol: u32, ctrl: bool, alt: bool) -> bool { + ctrl && alt && symbol == keysyms::KEY_Escape +} + #[cfg(test)] mod tests { - use super::virtual_terminal; + use super::{emergency_shortcut_escape, virtual_terminal}; + + #[test] + fn emergency_escape_requires_control_alt_escape() { + assert!(emergency_shortcut_escape(keysyms::KEY_Escape, true, true)); + assert!(!emergency_shortcut_escape(keysyms::KEY_Escape, true, false)); + assert!(!emergency_shortcut_escape(keysyms::KEY_q, true, true)); + } use smithay::input::keyboard::keysyms; #[test] diff --git a/compositor/src/state.rs b/compositor/src/state.rs index 532d116..753f8e1 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, + keyboard_shortcuts_inhibit::{KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor}, output::OutputManagerState, pointer_constraints::PointerConstraintsState, presentation::PresentationState, @@ -70,6 +71,7 @@ pub struct Ferese { pub(crate) named_cursors: HashMap, pub intercepted_keys: HashSet, pub idle_inhibitors: HashMap, + pub active_shortcuts_inhibitor: Option, pub direct_backend: Option, next_window_id: u64, last_animation_tick: Instant, @@ -82,6 +84,7 @@ pub struct Ferese { pub fractional_scale_state: FractionalScaleManagerState, pub idle_inhibit_state: IdleInhibitManagerState, pub idle_notifier_state: IdleNotifierState, + pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub output_manager_state: OutputManagerState, pub pointer_constraints_state: PointerConstraintsState, pub presentation_state: PresentationState, @@ -110,6 +113,8 @@ 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 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 output_manager_state = OutputManagerState::new_with_xdg_output::(&display_handle); @@ -149,6 +154,7 @@ impl Ferese { named_cursors, intercepted_keys: HashSet::new(), idle_inhibitors: HashMap::new(), + active_shortcuts_inhibitor: None, direct_backend: None, next_window_id: 1, last_animation_tick: start_time, @@ -161,6 +167,7 @@ impl Ferese { fractional_scale_state, idle_inhibit_state, idle_notifier_state, + keyboard_shortcuts_inhibit_state, output_manager_state, pointer_constraints_state, presentation_state, @@ -346,6 +353,10 @@ impl Ferese { } self.space.map_element(window, (0, 0), focus_new_window); self.relayout(); + + if focus_new_window { + self.restore_keyboard_focus(); + } } pub fn add_transient_window(&mut self, window: Window, parent: WindowId) {