git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit01b1f5f384e01d9e3b9f0366739814f708fe6581
parentd8a81d8e49
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-04 15:53
fix(cursor): a click never focuses a layer surface that asked for none

A button press or touch-down on any layer surface called
seat.focus(LayerSurface), whatever its keyboard_interactivity. wlr-layer-shell
says a surface with interactivity none is never given keyboard focus, and the
first client that depends on it -- cce-keyboard, an on-screen keyboard on the
Overlay layer -- took focus off the window it was typing into on its first key,
so nothing it sent arrived.

layer_takes_click_focus gates both paths; on_demand and exclusive surfaces are
focused on click as before, and the press still counts as clicking something.
Verified in scale-1 and scale-2 shadows: cce-text-editor keeps focus=true
while cce-keyboard's keys are clicked, and receives the text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/server/cursor.rs | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 81ad5bc3..0d034fdf 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -1395,6 +1395,19 @@ unsafe fn is_cloud_layer(layer_surface: *mut crate::layer_shell::LayerSurface) -
         .starts_with("cce-cloud")
 }
 
+/// Whether a click or touch on this layer surface may give it keyboard
+/// focus. Not when it asked for none: per wlr-layer-shell such a surface is
+/// never given keyboard focus, and the one that relies on it — an on-screen
+/// keyboard (cce-keyboard), typing into the window it was clicked over —
+/// cannot work if a click on its keys takes focus off that window.
+unsafe fn layer_takes_click_focus(layer_surface: *mut crate::layer_shell::LayerSurface) -> bool {
+    if layer_surface.is_null() || (*layer_surface).wlr_layer_surface.is_null() {
+        return false;
+    }
+    (*(*layer_surface).wlr_layer_surface).current.keyboard_interactive
+        != ffi::zwlr_layer_surface_v1_keyboard_interactivity_ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE
+}
+
 unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
     let cursor = &mut *crate::container_of!(listener, Cursor, button_listener);
     let event = data as *mut ffi::wlr_pointer_button_event;
@@ -2246,9 +2259,11 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
                         }
                     }
                 }
-                SceneNodeDataVal::LayerSurface(_) => {
+                SceneNodeDataVal::LayerSurface(layer_surface) => {
                     clicked_something = true;
-                    seat.focus(Focus::LayerSurface(result.surface));
+                    if layer_takes_click_focus(layer_surface) {
+                        seat.focus(Focus::LayerSurface(result.surface));
+                    }
                 }
                 SceneNodeDataVal::ShellSurface(_) | SceneNodeDataVal::LockSurface(_) | SceneNodeDataVal::OverrideRedirect(_) => {
                     clicked_something = true;
@@ -3574,8 +3589,10 @@ unsafe extern "C" fn handle_touch_down(listener: *mut ffi::wl_listener, data: *m
     let server = seat.server;
     if let Some(result) = (*server).scene.at(lx, ly) {
         match result.data {
-            SceneNodeDataVal::LayerSurface(_) => {
-                seat.focus(Focus::LayerSurface(result.surface));
+            SceneNodeDataVal::LayerSurface(layer_surface) => {
+                if layer_takes_click_focus(layer_surface) {
+                    seat.focus(Focus::LayerSurface(result.surface));
+                }
             }
             _ => {}
         }