diff options
| author | srdusr <[email protected]> | 2026-04-20 00:48:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-04-20 00:48:00 +0200 |
| commit | 436d42da6ef61a5ea20d5102c4baed7bf0993606 (patch) | |
| tree | 5b013ae1877d81bfb67e422fdf56896d761076ed /crates/wayland/src/udev | |
| parent | 1f708f8aa09bf8bbce82314a76b7f34def90b798 (diff) | |
| download | srdwm-436d42da6ef61a5ea20d5102c4baed7bf0993606.tar.gz srdwm-436d42da6ef61a5ea20d5102c4baed7bf0993606.zip | |
Confirm Nemo's popup works; fix the two bugs that hid it, and shadow bleed across a monitor seam
Nemo's right-click context menu was the last open punch-list item, parked
twice as untestable. It works: verified end to end in a throwaway nested
compositor, menu and submenu both, at the correct position and stacking.
The popup path itself needed no fix, so the POPUP-GEOM-DIAG/POPUP-GRAB-DIAG
diagnostics are removed.
Two real bugs turned up in the way of testing it.
zwlr_virtual_pointer was a silent no-op on the winit backend. Every
Motion/MotionAbsolute handler read UdevState::bounds() behind an early
return when state.udev was None, and that field is Some only for the DRM
backend. The protocol advertised its global, accepted create_virtual_pointer
and accepted every request, then discarded all motion with no error and no
log. That is the backend a nested instance runs on, so the only safe way to
drive a throwaway compositor - a Wayland client of that compositor, which
cannot reach any other session, unlike ydotool's /dev/uinput writes - did
not work at all. Bounds now come from WindowManager::monitors() when udev is
absent; both backends fill that list from Platform::monitors().
The winit backend's screencopy pass rendered no popups and no shadows. It
re-renders the scene offscreen, and that second scene was missing tiers, so
grim on a nested instance reported the opposite of the truth: a menu drawing
perfectly on screen photographed as absent. The DRM backend never had this,
since it serves screencopy from the on-screen frame it just drew. Border
strips are still missing from that pass, called out in the code rather than
left silent.
Also fixed, from the "windows show a bit in the other monitor" report:
shadow_rect expanded by SHADOW_SIZE on every side with no monitor-boundary
awareness, so a window flush against a seam put its 24px shadow strip on the
neighbouring screen. shadow_rect_clipped clips to the bounding box of the
monitors the window's geometry actually touches - not just its assigned
one, since a window straddling a seam really does occupy both and clipping
there would cut its shadow off mid-body. The bitmap's own extent stays
unclipped, because the src rectangle indexes into it; only the fragment list
is clipped. Six tests on the incident's own numbers. Not confirmed on
screen: the nested backend cannot produce a second monitor.
New tool: tools/virtual-pointer-click, a scriptable virtual-pointer driver
that acknowledges each command after its round-trip, so a test script can
put a screenshot between a move and the click that follows it.
489 tests pass, clippy clean.
Diffstat (limited to 'crates/wayland/src/udev')
| -rw-r--r-- | crates/wayland/src/udev/mod.rs | 2 | ||||
| -rw-r--r-- | crates/wayland/src/udev/render.rs | 15 |
2 files changed, 14 insertions, 3 deletions
diff --git a/crates/wayland/src/udev/mod.rs b/crates/wayland/src/udev/mod.rs index 22df0e1..cae7177 100644 --- a/crates/wayland/src/udev/mod.rs +++ b/crates/wayland/src/udev/mod.rs @@ -338,7 +338,7 @@ impl UdevState { /// `(x, y, width, height)` tuples rather than real `UdevHead`s - pulled /// out so it's testable without a real DRM/`Card` handle, which every /// `UdevHead` in this module otherwise needs to even construct. -fn bounds_of(heads: impl Iterator<Item = (i32, i32, i32, i32)>) -> (f64, f64, f64, f64) { +pub(crate) fn bounds_of(heads: impl Iterator<Item = (i32, i32, i32, i32)>) -> (f64, f64, f64, f64) { let mut min_x = 0; let mut min_y = 0; let mut max_x = 0; diff --git a/crates/wayland/src/udev/render.rs b/crates/wayland/src/udev/render.rs index b435276..ed62290 100644 --- a/crates/wayland/src/udev/render.rs +++ b/crates/wayland/src/udev/render.rs @@ -40,6 +40,11 @@ impl CompState { // below just re-offsets these same positions by its own `origin`. self.ensure_desktop_icons(); let desktop_icon_render_list = self.desktop_icon_render_list(); + // Same "gather immutable state before `self.udev` is borrowed + // mutably" reason as everything else in this block - used by the + // per-window shadow push below to keep a shadow off any monitor its + // own window does not occupy (`decoration::shadow_rect_clipped`). + let monitor_bounds: Vec<srdwm_core::Rect> = self.wm.borrow().monitors().iter().map(|m| m.full_geometry).collect(); // Captured-and-blurred backgrounds collected during the per-head // loop below, applied via `self.capture_output` only after it // ends - `self.udev`'s mutable borrow is held for the whole loop @@ -876,11 +881,17 @@ impl CompState { // corners nearly meet, which this compositor's default // cascade placement does constantly. if let Some(shadow) = self.shadow_buffers.get(&id) { - let rect = decoration::shadow_rect(frame); + // `full` is the bitmap's own extent and stays + // unclipped, because `src` below indexes into that + // bitmap; `rect` is the same box clipped to the + // monitors this window actually occupies, and only + // decides which fragments get drawn. + let full = decoration::shadow_rect(frame); + let rect = decoration::shadow_rect_clipped(frame, &monitor_bounds); for fragment in crate::elements::visible_border_fragments(rect, &occluders) { let pos = ((fragment.x - origin.x) as f64, (fragment.y - origin.y) as f64); let src = Rectangle::new( - Point::from(((fragment.x - rect.x) as f64, (fragment.y - rect.y) as f64)), + Point::from(((fragment.x - full.x) as f64, (fragment.y - full.y) as f64)), Size::from((fragment.width as f64, fragment.height as f64)), ); match MemoryRenderBufferRenderElement::from_buffer(&mut udev.renderer, pos, shadow, None, Some(src), None, Kind::Unspecified) { |