diff --git a/src/backend/wayland/compositor/handlers.rs b/src/backend/wayland/compositor/handlers.rs index edbd2904..62852a4c 100644 --- a/src/backend/wayland/compositor/handlers.rs +++ b/src/backend/wayland/compositor/handlers.rs @@ -7,6 +7,10 @@ use smithay::{ buffer::BufferHandler, compositor::{CompositorHandler, get_parent, is_sync_subsurface}, dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier}, + keyboard_shortcuts_inhibit::{ + KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, + KeyboardShortcutsInhibitor, + }, output::OutputHandler, seat::WaylandFocus, selection::data_device::{ClientDndGrabHandler, ServerDndGrabHandler}, @@ -210,8 +214,24 @@ impl XWaylandKeyboardGrabHandler for WaylandState { } } // For unmanaged X11 surfaces (like dmenu), search in the space - self.window_for_surface(surface) - .map(KeyboardFocusTarget::Window) + if let Some(window) = self.window_for_surface(surface) { + return Some(KeyboardFocusTarget::Window(window)); + } + + // Fallback: If XWayland requests a grab for a surface that isn't mapped + // as a full window (e.g., grabbing the root window or a dummy surface), + // we must still allow the grab by returning the raw WlSurface. + Some(KeyboardFocusTarget::WlSurface(surface.clone())) + } +} + +impl KeyboardShortcutsInhibitHandler for WaylandState { + fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState { + &mut self.keyboard_shortcuts_inhibit_state + } + + fn new_inhibitor(&mut self, _inhibitor: KeyboardShortcutsInhibitor) { + // We handle the inhibitor implicitly via KeyboardShortcutsInhibitState::keyboard_shortcuts_inhibited } } diff --git a/src/backend/wayland/compositor/mod.rs b/src/backend/wayland/compositor/mod.rs index 19aa1507..bbbc9182 100644 --- a/src/backend/wayland/compositor/mod.rs +++ b/src/backend/wayland/compositor/mod.rs @@ -42,10 +42,10 @@ pub use state::{WaylandClientState, WaylandState, WindowIdMarker}; use smithay::{ delegate_compositor, delegate_data_device, delegate_dmabuf, delegate_idle_inhibit, - delegate_layer_shell, delegate_output, delegate_pointer_gestures, delegate_relative_pointer, - delegate_seat, delegate_session_lock, delegate_shm, delegate_viewporter, - delegate_xdg_activation, delegate_xdg_decoration, delegate_xdg_shell, - delegate_xwayland_keyboard_grab, delegate_xwayland_shell, + delegate_keyboard_shortcuts_inhibit, delegate_layer_shell, delegate_output, + delegate_pointer_gestures, delegate_relative_pointer, delegate_seat, delegate_session_lock, + delegate_shm, delegate_viewporter, delegate_xdg_activation, delegate_xdg_decoration, + delegate_xdg_shell, delegate_xwayland_keyboard_grab, delegate_xwayland_shell, }; // --------------------------------------------------------------------------- @@ -67,5 +67,6 @@ delegate_xdg_activation!(WaylandState); delegate_xdg_decoration!(WaylandState); delegate_xdg_shell!(WaylandState); delegate_session_lock!(WaylandState); +delegate_keyboard_shortcuts_inhibit!(WaylandState); delegate_xwayland_keyboard_grab!(WaylandState); delegate_xwayland_shell!(WaylandState); diff --git a/src/backend/wayland/compositor/state.rs b/src/backend/wayland/compositor/state.rs index a89cde6d..c8ec3b46 100644 --- a/src/backend/wayland/compositor/state.rs +++ b/src/backend/wayland/compositor/state.rs @@ -25,6 +25,7 @@ use smithay::{ dmabuf::{DmabufFeedbackBuilder, DmabufGlobal, DmabufState}, foreign_toplevel_list::{ForeignToplevelHandle, ForeignToplevelListState}, idle_inhibit::IdleInhibitManagerState, + keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState, pointer_gestures::PointerGesturesState, relative_pointer::RelativePointerManagerState, @@ -114,6 +115,7 @@ pub struct WaylandState { pub session_lock_manager_state: SessionLockManagerState, pub lock_state: SessionLockState, pub lock_surfaces: HashMap, + pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub idle_inhibiting_surfaces: HashSet, pub(super) render_node: Option, @@ -224,6 +226,7 @@ impl WaylandState { let viewporter_state = ViewporterState::new::(&dh); let idle_inhibit_manager_state = IdleInhibitManagerState::new::(&dh); let session_lock_manager_state = SessionLockManagerState::new::(&dh, |_| true); + let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(&dh); // -- Seat (input devices) -- let cursor_config = wm.g.cfg.cursor.clone(); @@ -257,6 +260,7 @@ impl WaylandState { viewporter_state, idle_inhibit_manager_state, session_lock_manager_state, + keyboard_shortcuts_inhibit_state, lock_state: SessionLockState::Unlocked, lock_surfaces: HashMap::new(), idle_inhibiting_surfaces: HashSet::new(), diff --git a/src/backend/wayland/compositor/window/classify.rs b/src/backend/wayland/compositor/window/classify.rs index 8c50fe60..f8963fc4 100644 --- a/src/backend/wayland/compositor/window/classify.rs +++ b/src/backend/wayland/compositor/window/classify.rs @@ -73,17 +73,6 @@ impl WaylandState { WindowType::Normal } - /// Check if a window should suppress WM keyboard shortcuts when focused. - /// - /// Returns true for overlay windows (dmenu, popups, menus) where - /// keyboard input should go to the window without triggering keybindings. - pub fn should_suppress_shortcuts_for(&self, window: &Window) -> bool { - match self.classify_window(window) { - WindowType::Overlay | WindowType::Launcher | WindowType::Unmanaged => true, - WindowType::Normal | WindowType::Dying => false, - } - } - /// Iterator over windows in z-order (top-to-bottom), along with their type. /// /// This follows the render-order defined in `assemble_scene_elements!`: diff --git a/src/wayland/input/keyboard/mod.rs b/src/wayland/input/keyboard/mod.rs index d1872eb0..94c2263b 100644 --- a/src/wayland/input/keyboard/mod.rs +++ b/src/wayland/input/keyboard/mod.rs @@ -9,6 +9,7 @@ use crate::backend::wayland::compositor::{KeyboardFocusTarget, WaylandState}; use crate::wayland::common::modifiers_to_x11_mask; use smithay::utils::SERIAL_COUNTER; +use smithay::wayland::keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat; /// Handle keyboard events. pub fn handle_keyboard( @@ -17,20 +18,33 @@ pub fn handle_keyboard( event: impl KeyboardKeyEvent, ) { let serial = SERIAL_COUNTER.next_serial(); - let wm_shortcuts_allowed = match keyboard_handle.current_focus() { - None => true, - Some(KeyboardFocusTarget::Window(ref w)) => !state.should_suppress_shortcuts_for(w), - Some(KeyboardFocusTarget::WlSurface(ref s)) => { - let interactivity = with_states(s, |states| { - states - .cached_state - .get::() - .current() - .keyboard_interactivity - }); - interactivity != KeyboardInteractivity::Exclusive + let wm_shortcuts_allowed = if keyboard_handle.is_grabbed() { + false + } else { + if state.seat.keyboard_shortcuts_inhibited() { + false + } else { + match keyboard_handle.current_focus() { + None => true, + Some(KeyboardFocusTarget::Window(_)) => true, + Some(KeyboardFocusTarget::WlSurface(ref s)) => { + let is_exclusive = with_states(s, |states| { + if states.cached_state.has::() { + states + .cached_state + .get::() + .current() + .keyboard_interactivity + == KeyboardInteractivity::Exclusive + } else { + false + } + }); + !is_exclusive + } + Some(KeyboardFocusTarget::Popup(_)) => false, + } } - Some(KeyboardFocusTarget::Popup(_)) => false, }; let key_code = event.key_code(); let key_state = event.state();