diff options
| author | srdusr <[email protected]> | 2024-07-11 14:17:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-07-11 14:17:00 +0200 |
| commit | 86b557a6c0baa4146b30712d8b7633f1b9b46d43 (patch) | |
| tree | 96ab749c728ac11c2851ea361c6fda63990e588f /crates/core/src/manager/monitors.rs | |
| parent | 68b645b43e19330474ff106ce5c88dab520d7417 (diff) | |
| download | srdwm-86b557a6c0baa4146b30712d8b7633f1b9b46d43.tar.gz srdwm-86b557a6c0baa4146b30712d8b7633f1b9b46d43.zip | |
Split crates/core/src/manager.rs (2048 lines) into manager/
Pure reorganization, no behavior change - verified by diffing the
function-name set before/after (identical 130 functions) plus a full
cargo test pass. WindowManager's struct/field definitions, Default,
new(), and the three trivial constructors (add_rule/register_layout/
available_layouts) stay in mod.rs; the rest of the single ~950-line
impl block is split into one file per the section comments the file
already had (monitors, windows, focus, winops, hittest, dragresize,
workspaces, layout). Three methods called across section boundaries
(monitor_for, windows_on_workspace, cycle_focus) went from private to
pub(super) - Rust's privacy model doesn't let sibling submodules see
each other's private items, only a defining module's own descendants.
The ~1000-line test module moves to manager/tests.rs unsplit: its
helpers (wm_with_monitor, two_monitors, monitor_with_dock) are shared
across tests for every section, so splitting further would mean
duplicating them or adding another shared-support file for little
benefit.
Diffstat (limited to 'crates/core/src/manager/monitors.rs')
| -rw-r--r-- | crates/core/src/manager/monitors.rs | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/crates/core/src/manager/monitors.rs b/crates/core/src/manager/monitors.rs new file mode 100644 index 0000000..173e07d --- /dev/null +++ b/crates/core/src/manager/monitors.rs @@ -0,0 +1,96 @@ +//! Monitor list, hotplug rehoming, and lookups. +//! Split out of the original single `manager.rs` - see `super` (`mod.rs`) for +//! `WindowManager`'s field definitions; everything here is plain `impl WindowManager` +//! methods, unchanged from before the split. + +use super::*; + +impl WindowManager { + // ---- Monitors ---------------------------------------------------- + + /// Replaces the monitor list, rehoming any window left stranded. + /// + /// Called at startup and again on every hotplug. Unplugging a monitor + /// would otherwise leave its windows pointing at a `monitor` id that no + /// longer exists: `arrange_workspace` skips those (it looks the monitor + /// up to get a rectangle), so they would stop being tiled, and a + /// floating window would sit at coordinates that are no longer on any + /// screen - unreachable, with no way to drag it back. + /// + /// Stranded windows are moved to the primary monitor and, if their + /// geometry falls outside it, nudged back inside. + /// + /// This keys off **geometry**, not just the `monitor` field. That field + /// records which monitor a window was *assigned* at creation and does + /// not track where the window actually is: a floating window dragged -- + /// or placed by a rule - onto a second monitor keeps `monitor` + /// pointing at the first. Trusting the field alone left such a window + /// at coordinates that no longer existed once its real monitor was + /// unplugged: off-screen and unreachable, with no way to drag it back. + /// Found by unplugging a monitor out from under a window in the QEMU VM + /// and watching it vanish; the field-only check had passed its unit + /// tests because those set `monitor` explicitly. + pub fn set_monitors(&mut self, monitors: Vec<Monitor>) { + self.monitors = monitors; + + let Some(primary) = self.primary_monitor().cloned() else { + // No monitors at all (every output unplugged): leave windows + // as-is rather than collapsing them onto nothing, so they are + // restored intact when an output comes back. + return; + }; + let live = self.monitors.clone(); + for window in self.windows.values_mut() { + let visible_on = live.iter().find(|m| m.geometry.overlaps(&window.geometry)); + match visible_on { + // Still on screen: just make sure its monitor id points at a + // monitor that exists, so tiling keeps working. + Some(monitor) => { + if !live.iter().any(|m| m.id == window.monitor) { + window.monitor = monitor.id; + } + } + // Nothing on screen shows this window any more. + None => { + window.geometry = window.geometry.clamped_into(primary.geometry); + window.monitor = primary.id; + } + } + } + // A maximized/fullscreen window's geometry was set to a snapshot of + // its monitor's usable/full rect at the moment it was toggled on -- + // it is not live-bound to that rect afterward. Without this, a bar + // or dock changing its exclusive zone while a window is maximized + // (the live case: a dock dropping its reservation to 0 so a + // maximized window can cover its area) grows or shrinks `Monitor:: + // geometry`/`full_geometry` here, but the already-maximized window + // keeps its stale pre-change size until manually un-maximized and + // re-maximized - reported as "maximize does not extend past the + // dock" even though the dock's own zone change took effect + // immediately in every other respect (new windows placed correctly, + // `Monitor::geometry` itself correct if queried fresh). + for window in self.windows.values_mut() { + if !window.maximized && !window.fullscreen { + continue; + } + let Some(monitor) = live.iter().find(|m| m.id == window.monitor) else { continue }; + let target = if window.fullscreen { monitor.full_geometry } else { monitor.geometry }; + if window.geometry != target { + window.geometry = target; + } + } + } + + pub fn monitors(&self) -> &[Monitor] { + &self.monitors + } + + pub fn primary_monitor(&self) -> Option<&Monitor> { + self.monitors.iter().find(|m| m.primary).or_else(|| self.monitors.first()) + } + + pub(super) fn monitor_for(&self, id: MonitorId) -> Option<&Monitor> { + self.monitors.iter().find(|m| m.id == id).or_else(|| self.primary_monitor()) + } + +} |