srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/platform/src
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-10-28 20:09:00 +0200
committersrdusr <[email protected]>2025-10-28 20:09:00 +0200
commit9592fd7acb5b68f3fae139dfac22b728c35b6198 (patch)
treeebdaab6064388b67ffe06a611d934d7a29120959 /crates/platform/src
parent304f3a374408bb6a5a04ebb7d4652c631395f5c6 (diff)
downloadsrdwm-9592fd7acb5b68f3fae139dfac22b728c35b6198.tar.gz
srdwm-9592fd7acb5b68f3fae139dfac22b728c35b6198.zip
Fix a fake monitor's layer-shell surfaces misrouting onto the real primary
Live incident, root-caused jointly with the AGS peer session: creating a fake monitor visibly shrank the real primary output's usable area (full_y stayed 0 throughout - its true position never moved) each time, tracking almost exactly one bar height per fake monitor created. create_virtual_head registered its new Output in udev.virtual_heads but never in CompState::outputs, the list output_for_wl searches to resolve a client-named wl_output back to anything. new_layer_surface's own fallback for an output it can't resolve is landing on the primary output - so AGS's own per-monitor bar, aimed at the fake monitor it reasonably believed was a new real one, silently landed on the real primary output instead, stacking its own exclusive-zone reservation on top of the real bar already there. Two fake monitors, two misrouted bars, two zone increments, matching the observed climb exactly. Fixed by registering (and, on removal, deregistering) a virtual head's Output in CompState::outputs the same way bring_up_head already does for a real one. Also adds Monitor::is_virtual / MonitorInfo's "virtual" JSON field, requested directly by the AGS peer session as the real discriminator their own temporary FAKE- name-pattern match was standing in for. The X-position half of this same incident was AGS's own remembered- layout restore treating a fake monitor's wl_output as a real hotplug -- already fixed on their side (readArrangeable() now filters split/virtual outputs).
Diffstat (limited to 'crates/platform/src')
-rw-r--r--crates/platform/src/ipc/tests.rs24
-rw-r--r--crates/platform/src/ipc/types.rs20
2 files changed, 44 insertions, 0 deletions
diff --git a/crates/platform/src/ipc/tests.rs b/crates/platform/src/ipc/tests.rs
index aa86e95..591be56 100644
--- a/crates/platform/src/ipc/tests.rs
+++ b/crates/platform/src/ipc/tests.rs
@@ -497,6 +497,30 @@ fn set_monitor_split_with_neither_name_nor_a_resolvable_id_errors() {
}
#[test]
+fn monitors_query_marks_a_virtual_output_so_a_client_does_not_treat_it_as_a_real_hotplug() {
+ let dir = tempfile::tempdir().unwrap();
+ let mut server = IpcServer::bind_in(dir.path(), "test").unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ let real = srdwm_core::Monitor::new(0, "eDP-1", srdwm_core::Rect::new(0, 0, 1920, 1080));
+ let mut fake = srdwm_core::Monitor::new(1, "FAKE-1", srdwm_core::Rect::new(1920, 0, 1920, 1080));
+ fake.is_virtual = true;
+ wm.borrow_mut().set_monitors(vec![real, fake]);
+
+ let mut client = UnixStream::connect(&server.path).unwrap();
+ let mut reader = std::io::BufReader::new(client.try_clone().unwrap());
+ client.write_all(b"{\"cmd\":\"monitors\"}\n").unwrap();
+ server.poll(&wm);
+ let line = read_line(&mut reader);
+
+ let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
+ let monitors = parsed["monitors"].as_array().unwrap();
+ let real = monitors.iter().find(|m| m["name"] == "eDP-1").unwrap();
+ let fake = monitors.iter().find(|m| m["name"] == "FAKE-1").unwrap();
+ assert_eq!(real["virtual"], false, "a real output must not be marked virtual");
+ assert_eq!(fake["virtual"], true, "a fake monitor must be marked virtual so a client can tell it apart from a real hotplug");
+}
+
+#[test]
fn monitors_query_lists_a_disabled_output_alongside_live_ones() {
// What the AGS peer session asked for directly: a disabled output
// must not just vanish from `srd monitors` - it needs a row
diff --git a/crates/platform/src/ipc/types.rs b/crates/platform/src/ipc/types.rs
index 1dc95f6..df11592 100644
--- a/crates/platform/src/ipc/types.rs
+++ b/crates/platform/src/ipc/types.rs
@@ -288,6 +288,21 @@ pub(crate) struct MonitorInfo {
// actually displaying. See `WorkspaceInfo::monitor` for the same fact
// indexed from the other direction.
pub(crate) active_workspace: usize,
+ // `true` for a fully virtual/headless output (`srd dispatch create
+ // fake-monitor`) - a real `wl_output` global with no DRM connector
+ // behind it. Requested directly by the AGS peer session after a fake
+ // monitor's `wl_output` caused a real live incident: it looks like an
+ // ordinary new physical monitor to any client watching the core
+ // Wayland registry (unlike `wlr-output-management-v1`, which already
+ // excludes it), so AGS's own remembered-layout restore treated one
+ // appearing as a real hotplug and repositioned the *real* monitor to
+ // make room for it. Before this field existed, AGS's only option was
+ // matching the name against `^FAKE-` - this is the real
+ // discriminator that pattern was standing in for. `#[serde(rename)]`
+ // rather than a field literally named `virtual` because that word is
+ // a reserved identifier in Rust.
+ #[serde(rename = "virtual")]
+ pub(crate) is_virtual: bool,
}
/// Pushed to every subscriber (and used as `subscribe`'s own initial
@@ -446,6 +461,7 @@ pub(crate) fn monitor_snapshot(wm: &std::rc::Rc<std::cell::RefCell<WindowManager
split: m.split,
scale: m.scale,
active_workspace: wm.workspace_for_monitor(m.id),
+ is_virtual: m.is_virtual,
});
// Disabled-but-still-connected outputs, appended rather than merged in
// by name - see `MonitorInfo::enabled`'s own doc comment for why
@@ -482,6 +498,10 @@ pub(crate) fn monitor_snapshot(wm: &std::rc::Rc<std::cell::RefCell<WindowManager
// "shows nothing, not tracked" the same way `id: u32::MAX` above
// is a deliberate not-a-real-value sentinel for this same entry.
active_workspace: 0,
+ // A fake monitor is never administratively disabled/re-enabled --
+ // see `virtual_heads.rs`'s own module doc comment - so this
+ // branch (disabled-but-still-connected outputs) can never be one.
+ is_virtual: false,
});
live.chain(disabled).collect()
}