srdusr
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crates/wayland/src/winit/capture.rs16
-rw-r--r--crates/wayland/src/winit/nested_platform.rs48
-rw-r--r--docs/TODO.md63
3 files changed, 119 insertions, 8 deletions
diff --git a/crates/wayland/src/winit/capture.rs b/crates/wayland/src/winit/capture.rs
index c382190..095b56b 100644
--- a/crates/wayland/src/winit/capture.rs
+++ b/crates/wayland/src/winit/capture.rs
@@ -133,8 +133,20 @@ impl WaylandPlatform {
// and re-checked by. Border strips are still absent - a real
// remaining gap, called out here rather than left silent.
if let Some(shadow) = self.state.shadow_buffers.get(&id) {
- let full = crate::decoration::shadow_rect(w.geometry);
- let rect = crate::decoration::shadow_rect_clipped(w.geometry, &monitor_bounds);
+ // `effective_frame`, not `w.geometry` - the same correction
+ // both on-screen render loops apply before sizing any
+ // decoration bitmap (see its own doc comment). The shadow
+ // buffer was rasterised at the *frame's* size, and `src`
+ // below indexes into that buffer, so measuring from
+ // `w.geometry` instead reads the wrong region whenever the
+ // two differ. They differ for exactly the windows that
+ // matter here: a CSD client whose committed surface is not
+ // the size this compositor asked for. Found by sampling
+ // pixels - Alacritty's shadow appeared in a capture and
+ // Nemo's did not, and the only difference was that gap.
+ let frame = self.state.effective_frame(id, w.geometry);
+ let full = crate::decoration::shadow_rect(frame);
+ let rect = crate::decoration::shadow_rect_clipped(frame, &monitor_bounds);
for fragment in crate::elements::visible_border_fragments(rect, &occluders) {
let src = Rectangle::new(
Point::from(((fragment.x - full.x) as f64, (fragment.y - full.y) as f64)),
diff --git a/crates/wayland/src/winit/nested_platform.rs b/crates/wayland/src/winit/nested_platform.rs
index 40d03ff..3b762ab 100644
--- a/crates/wayland/src/winit/nested_platform.rs
+++ b/crates/wayland/src/winit/nested_platform.rs
@@ -79,6 +79,28 @@ impl Platform for WaylandPlatform {
for (pid, window) in self.wm.borrow_mut().drain_pin_input_requests() {
self.state.set_virtual_pointer_pin(pid, window);
}
+ // Monitor split, drained the same way `udev/platform.rs` drains it.
+ //
+ // This backend used to ignore the request entirely: the dispatch
+ // returned `{"ok":true}`, the request queued, and nothing ever
+ // took it off the queue, so `srd dispatch set output split` looked
+ // like it had worked and changed nothing. That silence cost real
+ // time - a two-monitor rendering bug could not be reproduced in a
+ // nested instance, and the conclusion drawn was "split needs DRM
+ // head machinery", which is not true of any part of it: `Monitor
+ // Split` is bookkeeping in `WindowManager` and `split_rect` is
+ // pure geometry in `core`. Splitting the one nested output into
+ // several logical monitors is exactly what a multi-monitor repro
+ // needs, and it works here for the same reason it works there.
+ let split_requests = self.wm.borrow_mut().drain_monitor_split_requests();
+ if !split_requests.is_empty() {
+ for (name, parts, rows) in split_requests {
+ self.wm.borrow_mut().set_monitor_split(name, parts, rows);
+ }
+ // Same "just go recompute the monitor list" event the udev
+ // drain pushes - the payload is ignored by the handler.
+ self.pending.borrow_mut().push(CoreEvent::MonitorAdded(srdwm_core::Monitor::new(0, "", srdwm_core::Rect::new(0, 0, 0, 0))));
+ }
let wait = TARGET_FRAME_TIME.saturating_sub(self.last_frame.elapsed());
let _ = self.idle_event_loop.dispatch(Some(wait), &mut self.state);
self.last_frame = Instant::now();
@@ -92,8 +114,32 @@ impl Platform for WaylandPlatform {
// single output at the global origin, so the output-local zone
// rectangle already is the usable global-space rect.
let zone = layer_map_for_output(&self.output).non_exclusive_zone();
+ let usable = srdwm_core::Rect::new(zone.loc.x, zone.loc.y, zone.size.w as u32, zone.size.h as u32);
+ let full_size = self.backend.window_size();
+ let full = srdwm_core::Rect::new(0, 0, full_size.w as u32, full_size.h as u32);
+ let maximize = crate::input::maximize_geometry_for(&self.output, full);
+ // Expanded into one `Monitor` per split part, exactly as
+ // `udev/platform.rs`'s own `monitors()` does - see the split drain
+ // in `poll` above for why this backend supports it at all.
+ let split = self.wm.borrow().monitor_split("winit");
+ let parts = split.map(|s| s.parts).unwrap_or(1).max(1);
+ let rows = split.map(|s| s.rows).unwrap_or(false);
+ if parts > 1 {
+ return Ok((0..parts)
+ .map(|part| {
+ let mut m = srdwm_core::Monitor::new(part, format!("winit-{}", part + 1), srdwm_core::monitor::split_rect(usable, part, parts, rows));
+ m.full_geometry = srdwm_core::monitor::split_rect(full, part, parts, rows);
+ m.maximize_geometry = srdwm_core::monitor::split_rect(maximize, part, parts, rows);
+ // Exactly one primary, same rule as the udev backend:
+ // the split parts share one underlying output.
+ m.primary = part == 0;
+ m.split = true;
+ m
+ })
+ .collect());
+ }
Ok(vec![{
- let rect = srdwm_core::Rect::new(zone.loc.x, zone.loc.y, zone.size.w as u32, zone.size.h as u32);
+ let rect = usable;
let mut m = srdwm_core::Monitor::new(0, "winit", rect);
// Same fix as `udev/platform.rs`'s matching function: `Monitor::new`
// defaults `full_geometry` to `geometry`, which is already
diff --git a/docs/TODO.md b/docs/TODO.md
index d24f9b8..e6f95f8 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -1,5 +1,58 @@
# TODO / planned features - master checklist
+## A wrong "blocked" of my own, corrected: monitor split works in a nested compositor (2026-08-28)
+
+Written earlier the same day, as the reason the monitor-seam shadow fix could
+not be checked on screen: "the nested backend cannot produce a second monitor
+- `set output split` and `create fake-monitor` both need real head machinery
+that only the DRM backend has". The first half of that is now false and the
+second half was never checked.
+
+What actually happened: both commands returned `{"ok":true}` and changed
+nothing, and the cause was inferred from that rather than read. The real
+cause is that `udev/platform.rs` was the only backend draining those request
+queues. The winit backend never took them off the queue, so the request sat
+there forever and the dispatch looked like it had worked. Nothing about
+split is DRM-bound: `MonitorSplit` is bookkeeping in `WindowManager` and
+`split_rect` is pure geometry in `core`.
+
+Wired up: the winit poll drains split requests the same way the udev one
+does, and its `monitors()` expands a split into one `Monitor` per part with
+its own `full_geometry`/`maximize_geometry`, matching the udev expansion
+exactly. `srd dispatch set output split winit 2 columns` now really does
+report two 640x800 monitors with a seam at x=640, which is what a
+multi-monitor repro needs. Fake monitors remain udev-only and that half was
+not re-checked, so it stays stated as unverified rather than as a fact.
+
+Prompted by the AGS peer session, which had carried its own "no typechecker
+works offline" as settled for a day and disproved it in twenty minutes once
+challenged. Their generalisation is the useful part and it applied here
+immediately: a "blocked" is a measurement and it decays, and two commands
+failing is two commands failing, not proof that the space is empty.
+
+**The seam check itself is still not done, and here is exactly where it
+stopped.** With the split working, a floating window was placed with its
+right edge exactly on the seam and the pixels just past it sampled: no
+shadow, correctly. But the negative control failed - the same window off
+the seam had no shadow either, so the test proved nothing. Running both
+clients in one instance settled why: Alacritty renders a shadow in a capture
+(a real measured gradient, 24 -> 27 -> 32 -> 35 -> 36 over ~24px), and Nemo
+renders none, with `shadows: true`, both floating, in the same instance,
+regardless of which is focused. Nemo is server-side decorated and Alacritty
+is not, which is a lead and not a conclusion - it has not been root-caused
+and is recorded here rather than guessed at. Finishing the seam check needs
+either that answer or a shadow-rendering client that can be positioned onto
+a seam (Alacritty has no titlebar to drag).
+
+One real inconsistency was found and fixed on the way: the winit capture
+pass measured the shadow from `w.geometry` while both on-screen loops
+measure it from `effective_frame`, which is the client's real committed
+size. `src` indexes into a buffer rasterised at the frame's size, so the two
+disagreeing reads the wrong region whenever a client settles on a different
+size than it was asked for. Corrected to match. It did not change the Nemo
+result, so it is not the cause of that - stated plainly rather than
+implied.
+
## Eight asks recovered from the previous session's transcript, all built (2026-08-28)
Reported as "there was more stuff from previous agent", then "please do all of
@@ -204,11 +257,11 @@ the fragment list is clipped.
Six tests, built on the incident's own numbers (two 1920x1080 outputs, seam
at x=1920): flush against the seam from either side, straddling it,
mid-monitor, the desktop's outer edge, and no monitors at all.
-**Not confirmed on screen.** The nested backend cannot produce a second
-monitor - `set output split` and `create fake-monitor` both return
-`{"ok":true}` and change nothing there, because both need real head
-machinery that only the DRM backend has. Confirming it needs the user's own
-two-monitor session.
+**Not confirmed on screen** - and the reason first given for that was
+wrong. See the correction entry at the top of this file: monitor split works
+in a nested instance now, so the seam itself is reproducible; what is still
+missing is a window that both renders a shadow and can be positioned onto
+the seam.
Correcting the entry below, which called this moot because the user had
turned shadows off: `srd settings` against the live session reports