diff options
| author | srdusr <[email protected]> | 2024-04-12 22:35:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-04-12 22:35:00 +0200 |
| commit | a9dd8a6d4947cb537f7919c5a66ba4624af61800 (patch) | |
| tree | d4731cded04ce159ebf3547d4dfe95ca24f4bb02 /docs/IMPLEMENTATION_STATUS.md | |
| parent | 709333908d5b7d3157165d1811c8bc1795ab0028 (diff) | |
| download | srdwm-a9dd8a6d4947cb537f7919c5a66ba4624af61800.tar.gz srdwm-a9dd8a6d4947cb537f7919c5a66ba4624af61800.zip | |
udev: connector hotplug, and rescue windows on an unplugged monitor
Monitors were probed once at startup, so plugging or unplugging one while
srdwm was running went unnoticed. A UdevBackend event source now watches for
the kernel's `change` uevent and reconciles the head list against a fresh
connector probe - forcing a re-probe rather than trusting cached status,
since on a hotplug the cache is exactly what has gone stale.
Removing a head tears down everything it owned: the wl_output global, its
place in the Space, its DRM framebuffers and dumb buffers (dropping the Rust
structs alone leaks the kernel-side objects, which matters when a cable is
plugged repeatedly), and any lock surface for it - otherwise
confirm_lock_if_presented would wait forever on a monitor that no longer
exists. New connectors go through the same bring_up_head path as startup, so
a monitor plugged in later is set up identically to one present at boot.
Heads are then repositioned left-to-right, since removing one shifts the
rest, and layer maps re-arranged so bars follow their moved output.
set_monitors rehomes windows stranded by the change, and main.rs re-queries
the whole monitor list on MonitorAdded/MonitorRemoved rather than applying
the single monitor in the event, because the others' positions move too.
The rehoming had a bug that unit tests missed and live testing caught.
It originally keyed off Window::monitor, but that field records the monitor
a window was *assigned* at creation, not where it is: add_window always sets
it from the primary monitor, so a window placed on the second monitor by a
rule - or dragged there - still reads monitor == 0. The field-only check
saw a valid id, skipped the window, and left it at coordinates that no
longer existed: invisible and unreachable. Found by unplugging a monitor out
from under a real xterm and watching it vanish from both heads. It now keys
off geometry, with a regression test that fails against the old logic.
Verified in the QEMU VM, booting with one connector and toggling the second
at runtime: plug in -> head added and rendering at its own resolution;
unplug -> head removed cleanly; and an xterm at global x=1500 survived its
monitor being unplugged, reappearing at x=680 (= min(1500, 1280-600)) with
its size intact. Writing to /sys/class/drm/<connector>/status changes the
connector but emits no uevent on this kernel, so the signal the kernel would
send is synthesized with `udevadm trigger`; the whole reaction path is
genuinely exercised.
Diffstat (limited to 'docs/IMPLEMENTATION_STATUS.md')
| -rw-r--r-- | docs/IMPLEMENTATION_STATUS.md | 55 |
1 files changed, 49 insertions, 6 deletions
diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index ae78cd0..35635c0 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -389,6 +389,53 @@ once, so splitting it across three files would have hidden it. pixels differed from the clear colour). The nested winit backend remains single-output by construction (it is one window on a host compositor). +- ✅ **Connector hotplug**. A `UdevBackend` event source watches for the + kernel's `change` uevent on the DRM device; `CompState::reprobe_outputs` + then re-probes connectors (forcing a fresh probe - on a hotplug the + cached status is exactly what has gone stale) and reconciles the head + list. Vanished connectors have their head torn down: `wl_output` global + removed, output unmapped from the `Space`, DRM framebuffers and dumb + buffers explicitly freed (dropping the Rust structs alone leaks the + kernel-side objects, which matters when a cable is plugged repeatedly), + and any lock surface for that output dropped - otherwise + `confirm_lock_if_presented` would wait forever for a monitor that no + longer exists. New connectors are brought up through the same + `bring_up_head` path used at startup. Every head is then repositioned + left-to-right, since removing one shifts the rest, and the layer maps are + re-arranged so bars follow their moved output. + `WindowManager::set_monitors` rehomes windows left stranded, and + `main.rs` re-queries the whole monitor list on `MonitorAdded`/ + `MonitorRemoved` rather than applying the single monitor in the event + (positions of the others change too). + **Verified live in the QEMU VM**, booting with one connector and toggling + the second at runtime: + - plug in → `hotplug - 0 output(s) removed, 1 added`, + `output Virtual-2 connected (1024x768)`, `monitor layout changed: + 2 monitor(s)`, and a screendump of the new head showed it really + rendering at its own resolution; + - unplug → `1 output(s) removed, 0 added`, back to 1 monitor, compositor + healthy; + - **window rescue**: an xterm placed by rule at global x=1500 (on the + second monitor) was still visible after that monitor was unplugged -- + it reappeared on the remaining head at x=680, exactly + `min(1500, 1280-600)`, keeping its 600x400 size. + Caveat on method: writing to `/sys/class/drm/<connector>/status` changes + the connector but emits **no uevent** on this kernel, so the uevent the + kernel would send on real hardware is synthesized with `udevadm trigger + --subsystem-match=drm --action=change`. The reaction path - re-probe, + diff, bring up/tear down, re-layout, rehome - is genuinely exercised; + only the initial signal is injected. + + This turned up a real bug that the unit tests had **missed**: rehoming + originally keyed off `Window::monitor`, but that field records the + monitor a window was *assigned* at creation, not where it actually is -- + `add_window` always sets it from the primary monitor, so a window placed + on the second monitor by a rule (or dragged there) still reads + `monitor == 0`. The field-only check saw a valid id, skipped the window, + and left it at coordinates that no longer existed: invisible and + unreachable. Caught by unplugging a monitor out from under a real xterm + and watching it vanish from both heads. `set_monitors` now keys off + geometry, with a regression test that fails against the old logic. **Known limitation of the nested (winit) backend**: it renders through the host compositor's frame callbacks, so if the srdwm window is occluded or on @@ -473,12 +520,8 @@ being a real daily-driver session (bars/launchers/notifications/lock UIs, clipboard, screen locking) are now implemented and verified - see the Wayland backend section above. What is left: -- **Connector hotplug** - connectors are probed once at startup, so - plugging a monitor in (or unplugging one) while srdwm is running is not - noticed. Needs a udev event source, which this backend does not register - yet. Multi-monitor itself *is* implemented (see above); only hotplug is - missing. -- **Multi-GPU** - only the primary GPU's connectors are driven. +- **Multi-GPU** - only the primary GPU's connectors are driven. A GPU + appearing or disappearing is logged and ignored. - Animations (`general.animations`/`animation_duration` config keys exist and are read into defaults, but nothing consumes them yet). - A native GUI settings app (the legacy project's `GUI_SETTINGS.md` was |