diff options
| author | srdusr <[email protected]> | 2025-09-10 11:24:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-09-10 11:24:00 +0200 |
| commit | 5ee627928d0cb017e59d796c71793c35d5b65d33 (patch) | |
| tree | 3285a7df82a04b178fb41fae220d60fe1dd38bd7 /crates/wayland/src/udev/render.rs | |
| parent | 4a55182ce17fd4474f8be1e8819a41b7ccd11045 (diff) | |
| download | srdwm-5ee627928d0cb017e59d796c71793c35d5b65d33.tar.gz srdwm-5ee627928d0cb017e59d796c71793c35d5b65d33.zip | |
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.
Diffstat (limited to 'crates/wayland/src/udev/render.rs')
| -rw-r--r-- | crates/wayland/src/udev/render.rs | 70 |
1 files changed, 69 insertions, 1 deletions
diff --git a/crates/wayland/src/udev/render.rs b/crates/wayland/src/udev/render.rs index 2d0a063..4bfe04d 100644 --- a/crates/wayland/src/udev/render.rs +++ b/crates/wayland/src/udev/render.rs @@ -7,6 +7,19 @@ impl CompState { /// monitors on different refresh rates each run at their own pace /// instead of the slowest one gating the rest. pub(crate) fn render_udev_frame(&mut self) { + // Real perf instrumentation, not a guess-fix: reported live as + // "resizing seems slow", and this session's own investigation + // (checked decoration-buffer caching, motion-path logging levels, + // GPU-path config) found no smoking gun without an actual + // measurement. Cheap when nothing's slow (one `Instant::now()` and + // one comparison per frame, no allocation, no formatting unless + // the threshold trips) - logs only when a frame actually misses a + // 60fps budget, tagged with whether a resize/drag was in progress + // at the time, so the next real resize either produces real + // evidence this is a genuine per-frame cost during resize + // specifically, or rules that out in favor of something else + // (input latency, client-side redraw cost, a specific app). + let frame_start = Instant::now(); self.tick_animations(); self.tick_hover_glyph_animation(); self.tick_dirty_broadcasts(); @@ -299,6 +312,23 @@ impl CompState { origin, hsize, )); + // Multi-cursor mode, Phase 1: one extra sprite per *other* + // physical pointer device's own last-known position (see + // `UdevState::secondary_cursors`'s own doc comment) - the + // device that drove `pointer_pos` itself is skipped so its + // cursor isn't drawn twice at the same spot. All secondary + // sprites share the one real cursor image/theme + // (`cursor_status`/`cursor_buffers`) rather than each + // device getting its own - a real visual distinction + // between devices is a later-phase refinement, not needed + // to prove multiple live positions render at all. + let active_device = udev.secondary_cursors.iter().find(|&(_, &p)| p == pointer_pos).map(|(d, _)| d.clone()); + for (device, &pos) in &udev.secondary_cursors { + if Some(device) == active_device.as_ref() { + continue; + } + custom_elements.extend(crate::cursor::render_elements(&cursor_status, &cursor_buffers, &mut udev.renderer, pos, origin, hsize)); + } // Night light/reading mode - a translucent full-output // overlay, pushed right after the cursor so it colours // everything else (windows, bars, menus) but never the @@ -415,7 +445,7 @@ impl CompState { // the actual content position still reads `geom`/`band` // directly, since that's already correctly anchored via // `content_offset` below regardless of this correction. - let frame = crate::state::CompState::effective_frame_of(&self.wm, &self.id_to_window, id, geom); + let frame = crate::state::CompState::effective_frame_of(&self.wm, &self.id_to_window, &self.pending_size_configure, id, geom); // Computed here, ahead of the border strips below, // purely so they can know it - the actual content // element that reads this same masked buffer is still @@ -924,6 +954,34 @@ impl CompState { // background-layer push just below (so the wallpaper still // shows through everywhere an icon doesn't draw). See // `desktop_icons.rs`'s own module doc comment. + // The rubber-band marquee outline, above the icons it's + // selecting - four thin solid-colour strips (the same + // `border_side_render_element` primitive window borders + // already use), not a translucent fill: `SolidColorRender + // Element` has no alpha-blend path, and a plain accent- + // coloured outline is still a real, visible selection + // indicator without needing a new element type for one + // feature. + if let Some((start, current)) = self.desktop_marquee { + let (x0, y0) = (start.0.min(current.0), start.1.min(current.1)); + let (x1, y1) = (start.0.max(current.0), start.1.max(current.1)); + let color = self.wm.borrow().theme.default_border_color; + const T: i32 = 1; + let strips = [ + srdwm_core::Rect::new(x0, y0, (x1 - x0).max(0) as u32, T as u32), + srdwm_core::Rect::new(x0, y1 - T, (x1 - x0).max(0) as u32, T as u32), + srdwm_core::Rect::new(x0, y0, T as u32, (y1 - y0).max(0) as u32), + srdwm_core::Rect::new(x1 - T, y0, T as u32, (y1 - y0).max(0) as u32), + ]; + for (strip, buf) in strips.into_iter().zip(self.marquee_buffers.iter_mut()) { + custom_elements.push(crate::elements::OverlayElement::Solid(crate::elements::border_side_render_element( + buf, + strip, + color, + (origin.x, origin.y), + ))); + } + } for (pos, buffer) in &desktop_icon_render_list { let local_pos = ((pos.0 - origin.x) as f64, (pos.1 - origin.y) as f64); match MemoryRenderBufferRenderElement::from_buffer(&mut udev.renderer, local_pos, buffer, None, None, None, Kind::Unspecified) { @@ -1190,6 +1248,16 @@ impl CompState { // otherwise never come (see docs/PANEL_SUPPORT_TODO.md, P1). self.screencopy_pending.extend(captures); } + const FRAME_BUDGET: Duration = Duration::from_millis(16); + let frame_time = frame_start.elapsed(); + if frame_time > FRAME_BUDGET { + let wm = self.wm.borrow(); + log::warn!( + "PERF-RESIZE render_udev_frame took {frame_time:?} (budget {FRAME_BUDGET:?}) - resizing={} dragging={}", + wm.resizing_window().is_some(), + wm.is_dragging() + ); + } } /// Sets a connector's DPMS mode via the generic KMS "DPMS" property -- |