srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-09-26 22:07:00 +0200
committersrdusr <[email protected]>2025-09-26 22:07:00 +0200
commit9a9aa8fe9d4f4f9b15860b45244e695942c90afd (patch)
treea7a8289731d49713caeaf06f023ab04f78cc663c /docs
parentb46dfa56504f62ecf969914dfed106bf587c4c4a (diff)
downloadsrdwm-9a9aa8fe9d4f4f9b15860b45244e695942c90afd.tar.gz
srdwm-9a9aa8fe9d4f4f9b15860b45244e695942c90afd.zip
Fake (headless) monitors, and fix new windows all opening in one spot
Two independent pieces landed together this pass - both real, both scoped, see docs/TODO.md for the full narrative on each: Fake monitors: a genuinely independent, additional wl_output with no DRM connector/CRTC behind it at all - distinct from srd.monitor.split (divides one real output's own placement rectangle). Researched niri's own Headless backend first (cloned at ~/reference-wms/niri): its render() never actually composites anything, a no-render stub for that project's test suite only. This one is real: it renders whatever is placed on it, on demand, whenever a zwlr_screencopy_manager_v1 client asks for a frame. New crates/wayland/src/udev/virtual_heads.rs (create/remove a real Output + global, render-on-demand for screencopy, integrated into platform.rs's monitors() as a genuine srdwm_core::Monitor so core placement/workspace code needs zero special-casing). New IPC/CLI: srd dispatch create fake-monitor <name> <w>x<h> / remove fake-monitor <name>. Core-side request queue in crates/core/src/manager/fake_monitor.rs. Placement bug, root-caused and fixed: every new window opened alone landed in the exact same spot, "not at all like Windows" (reported live). SmartPlacement::place tried a grid cell first, and grid's own cell count is existing.len() + 1 - with nothing else open (opening one app at a time, the ordinary case), that's always 1, so a 1x1 grid returns the same single cell forever regardless of session history. Cascade had the same bug in a second form (its own step was existing.len() % max_steps, also always 0 with nothing open). Fixed: WindowManager::next_cascade_step (a Cell - add_window's own target_monitor stays borrowed across the call) advances on every real placement and is never reset by a window closing; place() now skips grid entirely when nothing else is open, going straight to cascade, since grid's real job (dividing space among concurrent windows) has nothing to divide when there's no concurrency. Full workspace build/test/clippy clean (223 core / 141 wayland / 29 platform / 24 ctl / 28 config / 10 x11 tests, 0 failed, 0 clippy warnings), built and installed.
Diffstat (limited to 'docs')
-rw-r--r--docs/TODO.md22
1 files changed, 22 insertions, 0 deletions
diff --git a/docs/TODO.md b/docs/TODO.md
index d7c62a8..98deb48 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -13,6 +13,28 @@ that has the full story. Keep this list current as items close or open;
update the source doc's own entry too, don't let this drift into a
second stale copy the way `PANEL_SUPPORT_TODO.md` did.
+## Real bug, root-caused and fixed: every new window opened alone landed in the exact same spot, not at all like Windows (2026-08-27)
+
+Reported live. Root-caused by reading `SmartPlacement::place`, not guessed: it tried a grid cell first, falling back to cascade only once the grid was full. Grid's own cell count is `existing.len() + 1` - with nothing else open (the overwhelmingly common real workflow: open one app, use it, close it, open the next), that count is always `1`, so the grid is always exactly one cell, and a 1x1 grid returns the same single cell every time regardless of session history. Cascade had a second, compounding version of the same bug: its own step was `existing.len() % max_steps`, also always `0` with nothing else open, so even a from-scratch cascade calculation reset to the origin on every call.
+
+Fixed both. `WindowManager` gained `next_cascade_step` (a `Cell<u32>`, not a plain field - `add_window`'s own `target_monitor` stays borrowed from `self.monitors` for the whole call, so a plain `&mut self` write would conflict with that live borrow), incremented on every real placement and *never* reset by a window closing - real Windows keeps advancing its own cascade position the same way even as earlier windows close. `SmartPlacement::cascade` now takes this counter instead of `existing.len()`. `SmartPlacement::place` now skips `grid` entirely when `existing` is empty, going straight to cascade instead - grid's real job is dividing screen space fairly among *concurrent* windows, and with nothing to divide against there is no way for a 1x1 grid to vary by history no matter how it's computed; cascading is the correct strategy for "nothing else is open right now."
+
+New/updated tests cover both the isolated `cascade` behavior and the full `place`/`add_window` integration (`a_window_opened_alone_cascades_rather_than_using_a_pointless_1x1_grid`, `opening_the_same_app_alone_twice_in_a_row_lands_in_different_spots`, `cascade_step_keeps_advancing_even_if_the_previous_window_closed`); two pre-existing tests that hardcoded the old grid-based first-window position were updated to match the new, deliberate behavior, not silently left contradicting it.
+
+Full workspace build/test/clippy clean (223 core / 141 wayland / 29 platform / 24 ctl / 28 config / 10 x11 tests). Not yet live-verified against a real interactive session (needs a restart) - the algorithm change itself is proven by the new tests, but "does it feel right opening real apps" is a live check still owed.
+
+## Fake (fully virtual, headless) monitors: a real, visible one, not a test stub (2026-08-27)
+
+Distinct from `srd.monitor.split` (divides one *real* output's own placement rectangle) and from the phone-monitor item's own aspect_ratio rule (a window-level primitive) - this is a genuinely independent, additional `wl_output` with no DRM connector/CRTC behind it at all. Researched prior art before building: niri ships a real `Headless` backend (`backend/headless.rs`, cloned at `~/reference-wms/niri`) but its own `render()` never actually composites anything - a no-render stub purely for that project's own test suite, not a usable feature. This is a real, visible one instead: it actually renders whatever is placed on it, on demand, whenever a `zwlr_screencopy_manager_v1` client (`grim`, `wf-recorder`, a custom viewer) asks for a frame.
+
+New `crates/wayland/src/udev/virtual_heads.rs` (its own module doc comment has the full design and stated scope limits: no layer-shell chrome, no native-lock participation, no `wlr-output-management-v1` listing - all additive later, none block basic use). `CompState::create_virtual_head`/`remove_virtual_head` create/destroy a real `Output` + `wl_output` global with no hardware behind it, placed left-to-right after every existing head. `service_virtual_head_captures` intercepts any pending screencopy capture targeting a fake monitor *before* `render_udev_frame` ever sees it (otherwise it would wait forever for a real page-flip that will never come - the exact hang class `docs/PANEL_SUPPORT_TODO.md`'s own P1 already named), and renders it on demand by reusing `udev/capture.rs::capture_workspace`'s exact off-screen-render technique, selecting windows by `Window::monitor` (a fake monitor's own real windows) rather than by workspace.
+
+Fully integrated with core placement: `platform.rs`'s `monitors()` reports each fake monitor as a genuine `srdwm_core::Monitor` (scale 1.0, no exclusive zone), so `add_window`/tiling/workspace-switching all treat it exactly like a real monitor with zero special-casing - and removing one rehomes its windows via `WindowManager::set_monitors`'s own existing safety net, the same one a real monitor unplug already relies on.
+
+New IPC (`create_fake_monitor`/`remove_fake_monitor`, `crates/platform/src/ipc.rs`) and CLI: `srd dispatch create fake-monitor <name> <width>x<height>` / `srd dispatch remove fake-monitor <name>`. Core-side request queue in `crates/core/src/manager/fake_monitor.rs`, same cross-boundary shape every other backend-owned request already uses.
+
+Full workspace build/test/clippy clean (221 core / 141 wayland / 29 platform / 24 ctl / 28 config / 10 x11 tests, 0 failed, 0 clippy warnings). Not yet live-verified against a real running session (needs a restart to pick up the installed binary, same standing rule as every other change this shift) - `srd dispatch create fake-monitor TEST-1 1920x1080` then `grim -o TEST-1 /tmp/test.png` is the concrete verification path once restarted.
+
## Optional phone mode for AGS and srdwm: srdwm's own real half built (2026-08-27)
Split the ask honestly rather than guessing at AGS's own side blind: the srdwm-side primitive a "phone mode" needs is a placement policy (new windows default to maximized, since a phone-shaped screen has no room for more than one at a time) plus a real signal a panel can read to adapt its own chrome - both now real; the AGS-side chrome adaptation itself is work in that project, not this one, the same boundary this session's other AGS-adjacent entries (combined monitor modes, the layout-restore race) already draw.