From 2083ca7c88bc3e4b52ec68af384f334be1da2141 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:18:00 +0200 Subject: Fix XWayland: it now actually renders and receives keyboard input Three real bugs found and fixed via WAYLAND_DEBUG=1 protocol tracing in the QEMU VM, plus a fourth found along the way: - XWayland tried glamor (GBM rendering) first, which fails against this deliberately software-only compositor; its post-failure fallback path never used the xwayland_shell_v1 protocol at all, so X11Surface:: wl_surface() never resolved. Fixed by shadowing `Xwayland` on PATH with a wrapper script that always re-execs it with -shm (smithay's XWayland::spawn hardcodes its own argv and can't be bypassed either, since XWaylandClientData's fields are private). - Even with -shm, set_mapped(true) was only called after wl_surface() already resolved, deadlocking XWayland (it never advances a window past surface creation until the map is granted). Fixed by calling set_mapped(true) unconditionally in map_window_request. - The window then rendered as a ~1px sliver: initial geometry was seeded from X11Surface::geometry(), which can still be a tiny default at MapRequest time. Fixed by using the same 800x600 default the xdg-shell path already uses. - Typing didn't reach the window until a broader, XWayland-independent bug was fixed: nothing in the Wayland backend ever called KeyboardHandle::set_focus, so no window (native or X11) could ever receive keyboard input. Fixed in handle_pointer_button, along with TitlebarHit::Close being X11-surface-blind. Verified live: xterm launched via XWayland renders correctly sized and decorated, and a synthetic keypress sequence (ls + Enter) executed in its shell, screendump-confirmed. --- crates/wayland/src/lib.rs | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) (limited to 'crates/wayland/src/lib.rs') diff --git a/crates/wayland/src/lib.rs b/crates/wayland/src/lib.rs index b2fa9dc..507102e 100644 --- a/crates/wayland/src/lib.rs +++ b/crates/wayland/src/lib.rs @@ -562,6 +562,39 @@ fn handle_pointer_position(state: &mut CompState, pos: Point, time } } +/// The underlying `wl_surface` for a mapped window, regardless of whether +/// it's a native `xdg-shell` toplevel or an XWayland `X11Surface` -- +/// `desktop::Window` exposes these as two separate accessors with no +/// shared one. +fn dwindow_wl_surface(w: &DWindow) -> Option { + if let Some(top) = w.toplevel() { + return Some(top.wl_surface().clone()); + } + w.x11_surface().and_then(|x| x.wl_surface()) +} + +/// Requests a client close its window, whichever kind it is. +fn close_dwindow(w: &DWindow) { + if let Some(top) = w.toplevel() { + top.send_close(); + } else if let Some(x11) = w.x11_surface() { + let _ = x11.close(); + } +} + +/// Focuses `id` in our own `WindowManager` *and* gives its surface real +/// Wayland/X11 keyboard focus - without this, a window can be raised and +/// tiled correctly yet never receive a single keystroke. +fn focus_window(state: &mut CompState, id: WindowId) { + state.wm.borrow_mut().focus_window(id); + state.pending.borrow_mut().push(CoreEvent::WindowFocused(id)); + let surface = state.id_to_window.get(&id).and_then(dwindow_wl_surface); + if let Some(keyboard) = state.seat.get_keyboard() { + let serial = SERIAL_COUNTER.next_serial(); + keyboard.set_focus(state, surface, serial); + } +} + fn handle_pointer_button(state: &mut CompState, pos: Point, button: u32, pressed: bool, time: u32) { const BTN_LEFT: u32 = 0x110; let serial = SERIAL_COUNTER.next_serial(); @@ -569,13 +602,12 @@ fn handle_pointer_button(state: &mut CompState, pos: Point, button if pressed && button == BTN_LEFT { let hit = state.wm.borrow().hit_test(pos.x as i32, pos.y as i32); if let Some((id, hit)) = hit { - state.wm.borrow_mut().focus_window(id); - state.pending.borrow_mut().push(CoreEvent::WindowFocused(id)); + focus_window(state, id); match hit { TitlebarHit::Drag => state.wm.borrow_mut().start_drag(id, pos.x as i32, pos.y as i32), TitlebarHit::Close => { - if let Some(w) = state.id_to_window.get(&id).and_then(|w| w.toplevel()) { - w.send_close(); + if let Some(w) = state.id_to_window.get(&id) { + close_dwindow(w); } } TitlebarHit::Maximize => { @@ -586,7 +618,11 @@ fn handle_pointer_button(state: &mut CompState, pos: Point, button TitlebarHit::Resize(edge) => state.wm.borrow_mut().start_resize(id, edge, pos.x as i32, pos.y as i32), } } else if let Some((window, _loc)) = state.space.element_under(pos) { - state.space.raise_element(&window.clone(), true); + let window = window.clone(); + state.space.raise_element(&window, true); + if let Some(&id) = dwindow_wl_surface(&window).and_then(|s| state.surface_to_id.get(&s)) { + focus_window(state, id); + } } } else if !pressed { let mut wm = state.wm.borrow_mut(); -- cgit v1.2.3