gex-wayland: a cursor or subsurface drawing does not take the keyboard

On a host that composites from copies the keyboard focus followed whichever
surface last drew, and Xwayland's cursor surface draws after its window: every
key (GEX_WL_INPUT's included) went to the cursor. Only toplevels and popups
take the focus now; test a_cursor_drawing_does_not_take_the_keyboard.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_461fe3ff06374ecba693831148697ba6
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-06 12:11:59 -04:00
1 parent e61b5a4bab
commit 88b990063d
1 file changed
+26 -3
+26 -3
View File
@@ -1217,9 +1217,13 @@ impl Client {
}
if let Some(frame) = produced {
// The host that composites from copies has one window, and it is
// whichever last drew.
self.focus_surface = Some(surface);
self.ensure_keyboard_enter();
// whichever last drew -- of the windows: a cursor or a subsurface
// drawing must not take the keyboard (Xwayland's cursor surface
// did, and every scripted key went to it).
if matches!(state.role, Role::Toplevel | Role::Popup) {
self.focus_surface = Some(surface);
self.ensure_keyboard_enter();
}
self.frames.push(frame);
}
// Frame callbacks belong to the commit that carried them. The argument
@@ -2894,6 +2898,25 @@ mod tests {
assert_eq!(mode, 2, "the compositor draws the frames, so: server_side");
}
#[test]
fn a_cursor_drawing_does_not_take_the_keyboard() {
// Xwayland draws its cursor surface after its window; on a host that
// composites from copies, the keyboard stayed on the cursor and every
// key went nowhere.
let pool = Pool(vec![0u8; 16]);
let mut c = with_seat();
c.receive(&Writer::new(12, 0).new_id(13).finish(), &[], &pool); // get_pointer
c.receive(&Writer::new(12, 1).new_id(14).finish(), &[], &pool); // get_keyboard
assert_eq!(c.focus_surface, Some(6), "the window has the keyboard");
c.receive(&Writer::new(3, 0).new_id(20).finish(), &[], &pool); // a surface
c.receive(&Writer::new(9, 0).new_id(21).int(0).int(2).int(2).int(8).uint(1).finish(), &[], &pool);
c.receive(&Writer::new(13, 0).uint(1).object(20).int(0).int(0).finish(), &[], &pool); // set_cursor
c.receive(&Writer::new(20, 1).object(21).int(0).int(0).finish(), &[], &pool); // attach
c.receive(&Writer::new(20, 6).finish(), &[], &pool); // commit
assert!(c.failed.is_none(), "{:?}", c.failed);
assert_eq!(c.focus_surface, Some(6), "the cursor took the keyboard");
}
#[test]
fn a_cursor_surface_is_a_cursor_and_not_a_window() {
let pool = Placed(0x8000, 4096);