From 5ee627928d0cb017e59d796c71793c35d5b65d33 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Wed, 10 Sep 2025 11:24:00 +0200 Subject: Wayland backend: desktop icons v2, menu rebuild, layer-shell/scale fixes, XWayland stability, GPU rendering, and multi-cursor Phase 2 The bulk of a multi-session shift's real work landed in crates/wayland. Full root-cause/verification narrative for every item below lives in docs/TODO.md (each has its own dated entry); this is the summary: Desktop shell: - Real desktop icons v2 (state/desktop_icons.rs, desktop_icons.rs): fixed origin-baked-before-the-bar-connects, fixed-icon sort order, and a proper Rename/Delete-to-Trash menu (window_memory.rs backs the rename-persistence side). Rubber-band marquee multi-select. - icon_theme.rs: real freedesktop icon-theme lookup (inherits chain, hicolor fallback) rendering actual theme SVGs via resvg/tiny-skia, replacing the hand-drawn placeholder glyphs. - Context/desktop menus (decoration.rs, desktop_menu.rs, state/menu.rs) rebuilt to match the project's own AGS panel styling: rounded floating panel, tinted-fill row highlight, real separators, a much fuller titlebar window-menu action set. Layer-shell / multi-monitor: - Layer-shell hit-testing and render positioning (input/pointer.rs, udev/render.rs's element placement) now correctly convert LayerMap's logical geometry into physical pixels on a fractionally-scaled output - root cause of a bottom-anchored dock being unclickable and unpainted while a top-anchored bar on the same output worked. udev/outputs.rs's relayout_outputs gained the same physical/logical split for cross-output positioning, now backed by a real unit test (next_logical_x) built from the original measured incident numbers. - state/geometry.rs: a window's border/decoration no longer briefly clips when moved between differently-scaled monitors mid-drag. XWayland / stability: - xwayland.rs, udev/session.rs, udev/platform.rs: fixed a 100%- reproducible cold-start XKEYBOARD crash-loop (XWayland's own stdin inherited a real, already-owned VT; env passthrough and idle-callback spawn timing were both real, independent gaps) that had silently taken down all X11-app support and the global-menu registrar every session. - state/toplevel.rs, state/lifecycle.rs: XWayland dialog detection via WM_TRANSIENT_FOR, not just a native xdg_toplevel parent. Rendering: - udev/render.rs, decoration.rs: real GPU (GBM+EGL+DrmCompositor) window-content and cursor rendering on the udev backend, falling back to the untouched Pixman path automatically on any init failure. - decoration/tests.rs, state/mod.rs: rounded-corner/border fixes for interactive resize lag and cross-monitor moves. Multi-cursor Phase 2 (virtual_pointer.rs, new; state/mod.rs, udev/ platform.rs, winit/nested_platform.rs): pins a zwlr_virtual_pointer_ unstable_v1 object to a specific window, bypassing the shared seat/ focus/pointer_pos path entirely via hand-rolled wl_pointer.enter/motion/ button/frame/leave against every WlPointer the target client has bound (PointerHandle::client_pointers). Lets an agent operate one window while a human uses another, genuinely simultaneously, with zero client cooperation and no second wl_seat (confirmed a dead end: real clients only ever bind the first seat advertised). Full workspace build/test/clippy clean. --- crates/wayland/src/state/geometry.rs | 40 ++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) (limited to 'crates/wayland/src/state/geometry.rs') diff --git a/crates/wayland/src/state/geometry.rs b/crates/wayland/src/state/geometry.rs index 26c23fe..3d201dd 100644 --- a/crates/wayland/src/state/geometry.rs +++ b/crates/wayland/src/state/geometry.rs @@ -36,7 +36,13 @@ impl CompState { /// through the inherent method below wherever a plain `&self` is /// available (input handling, `redraw_decoration_buffer`); this /// version exists for the render loops specifically. - pub(crate) fn effective_frame_of(wm: &Rc>, id_to_window: &HashMap, id: WindowId, geom: srdwm_core::Rect) -> srdwm_core::Rect { + pub(crate) fn effective_frame_of( + wm: &Rc>, + id_to_window: &HashMap, + pending_size_configure: &HashMap, + id: WindowId, + geom: srdwm_core::Rect, + ) -> srdwm_core::Rect { // A version of this function briefly (this same session) skipped // the committed-size correction below entirely during an active // resize, on the reasoning that trusting the client's stale last @@ -81,6 +87,36 @@ impl CompState { if wm.borrow().resizing_window() == Some(id) { return geom; } + // Same reasoning as the active-resize branch just above, for a gap + // that isn't a drag at all: a plain cross-monitor *move* between two + // differently-scaled outputs still forces a size-changing configure + // (see `sync_geometry`'s own doc comment - a window's physical + // footprint stays constant across the move, so its *logical* size, + // what the client is actually told, necessarily changes with the + // new monitor's scale). Until the client catches up, `bbox()` below + // still reflects its *last real commit* - content sized for the + // *old* monitor's scale - but `w.monitor` has already flipped to + // the *new* one (updated live, every drag tick, independently of + // any commit). Multiplying the stale logical content by the new + // scale produces neither the old physical size nor the new one, a + // real mismatch between where the border/shadow get drawn and where + // the client's actual pixels are. Reported live, confirmed with + // before/after screenshots: a window's left border missing and its + // content clipped for one frame right after crossing from a + // scale-0.843 output onto a scale-1.0 one, self-correcting once the + // client's own commit landed - see `docs/TODO.md`. `pending_size_ + // configure` (see its own doc comment) already tracks exactly this + // gap for the throttle above; reusing it here instead of adding new + // state closes it: while a configure is outstanding, trust this + // compositor's own live target (`geom`) the same way an active + // resize already does, rather than reconstructing physical size + // from a logical value that was never committed under this scale. + if pending_size_configure.get(&id).is_some_and(|(pending_size, sent_at)| { + let caught_up = id_to_window.get(&id).is_some_and(|w| w.geometry().size.w == pending_size.0 && w.geometry().size.h == pending_size.1); + !caught_up && sent_at.elapsed() < CONFIGURE_THROTTLE_TIMEOUT + }) { + return geom; + } let Some(w) = wm.borrow().window(id).cloned() else { return geom }; let Some(dwindow) = id_to_window.get(&id) else { return geom }; // `dwindow.geometry()` - `xdg_surface::set_window_geometry` - is, @@ -206,7 +242,7 @@ impl CompState { /// compositor's own bookkeeping staying self-consistent, not about /// matching a client's real pixels. pub(crate) fn effective_frame(&self, id: WindowId, geom: srdwm_core::Rect) -> srdwm_core::Rect { - Self::effective_frame_of(&self.wm, &self.id_to_window, id, geom) + Self::effective_frame_of(&self.wm, &self.id_to_window, &self.pending_size_configure, id, geom) } pub(crate) fn sync_geometry(&mut self, id: WindowId) { -- cgit v1.2.3