diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index ba389d41..f6e30920 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -10,7 +10,10 @@ use smithay::{ input::Seat, output::Output, utils::{IsAlive, Serial, SERIAL_COUNTER}, - wayland::seat::WaylandFocus, + wayland::{ + seat::WaylandFocus, + shell::wlr_layer::{KeyboardInteractivity, Layer}, + }, }; use std::cell::RefCell; use tracing::{debug, trace}; @@ -291,15 +294,27 @@ impl Common { } fn focus_target_is_valid( - state: &mut State, + state: &State, seat: &Seat, output: &Output, target: KeyboardFocusTarget, ) -> bool { + // If a session lock is active, only lock surfaces can be focused if state.common.session_lock.is_some() { return matches!(target, KeyboardFocusTarget::LockSurface(_)); } + // If an exclusive layer shell surface exists (on any output), only exclusive + // shell surfaces can have focus, on the highest layer with exclusive surfaces. + if let Some(layer) = exclusive_layer_surface_layer(state) { + return if let KeyboardFocusTarget::LayerSurface(layer_surface) = target { + let data = layer_surface.cached_state(); + (data.keyboard_interactivity, data.layer) == (KeyboardInteractivity::Exclusive, layer) + } else { + false + }; + } + match target { KeyboardFocusTarget::Element(mapped) => { let workspace = state.common.shell.active_space(&output); @@ -334,7 +349,7 @@ fn focus_target_is_valid( } fn update_focus_target( - state: &mut State, + state: &State, seat: &Seat, output: &Output, ) -> Option { @@ -344,6 +359,16 @@ fn update_focus_target( .get(output) .cloned() .map(KeyboardFocusTarget::from) + } else if let Some(layer) = exclusive_layer_surface_layer(state) { + layer_map_for_output(output) + .layers() + .find(|layer_surface| { + let data = layer_surface.cached_state(); + (data.keyboard_interactivity, data.layer) + == (KeyboardInteractivity::Exclusive, layer) + }) + .cloned() + .map(KeyboardFocusTarget::from) } else if let Some(surface) = state.common.shell.active_space(&output).get_fullscreen() { Some(KeyboardFocusTarget::Fullscreen(surface.clone())) } else { @@ -358,3 +383,20 @@ fn update_focus_target( .map(KeyboardFocusTarget::from) } } + +// Get the top-most layer, if any, with at least one surface with exclusive keyboard interactivity. +// Only considers surface in `Top` or `Overlay` layer. +fn exclusive_layer_surface_layer(state: &State) -> Option { + let mut layer = None; + for output in state.common.shell.outputs() { + for layer_surface in layer_map_for_output(output).layers() { + let data = layer_surface.cached_state(); + if data.keyboard_interactivity == KeyboardInteractivity::Exclusive { + if data.layer as u32 >= layer.unwrap_or(Layer::Top) as u32 { + layer = Some(data.layer); + } + } + } + } + layer +}