srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/udev/outputs.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/wayland/src/udev/outputs.rs')
-rw-r--r--crates/wayland/src/udev/outputs.rs108
1 files changed, 108 insertions, 0 deletions
diff --git a/crates/wayland/src/udev/outputs.rs b/crates/wayland/src/udev/outputs.rs
new file mode 100644
index 0000000..6e282d6
--- /dev/null
+++ b/crates/wayland/src/udev/outputs.rs
@@ -0,0 +1,108 @@
+use super::*;
+use super::drm::{bring_up_head, pick_crtc, probe_connected};
+
+impl CompState {
+ /// Re-probes connectors after a hotplug and reconciles the head list.
+ ///
+ /// Connectors that vanished have their head torn down (global removed,
+ /// output unmapped, DRM buffers freed); newly connected ones are brought
+ /// up exactly as they would have been at startup. Every head is then
+ /// repositioned left-to-right, because removing a monitor shifts the
+ /// ones after it.
+ pub(crate) fn reprobe_outputs(&mut self) {
+ let Some(udev) = self.udev.as_ref() else { return };
+ let card = udev.card.clone();
+
+ let probes = match probe_connected(&card) {
+ Ok(p) => p,
+ Err(e) => {
+ log::warn!("udev: hotplug re-probe failed: {e}");
+ return;
+ }
+ };
+ let present: Vec<connector::Handle> = probes.iter().map(|p| p.connector).collect();
+ let existing: Vec<connector::Handle> = udev.heads.iter().map(|h| h.connector).collect();
+
+ let gone: Vec<connector::Handle> = existing.iter().copied().filter(|c| !present.contains(c)).collect();
+ let added: Vec<usize> = probes
+ .iter()
+ .enumerate()
+ .filter(|(_, p)| !existing.contains(&p.connector))
+ .map(|(i, _)| i)
+ .collect();
+ if gone.is_empty() && added.is_empty() {
+ return; // a "changed" event that didn't change the connector set
+ }
+ log::info!("udev: hotplug - {} output(s) removed, {} added", gone.len(), added.len());
+
+ // ---- removals ----
+ for connector in &gone {
+ let Some(udev) = self.udev.as_mut() else { return };
+ let Some(index) = udev.heads.iter().position(|h| h.connector == *connector) else { continue };
+ let head = udev.heads.remove(index);
+ log::info!("udev: output {} disconnected", head.output.name());
+ self.dh.remove_global::<CompState>(head.global.clone());
+ self.space.unmap_output(&head.output);
+ self.outputs.retain(|e| e.output != head.output);
+ // A lock surface for a monitor that no longer exists would keep
+ // `confirm_lock_if_presented` waiting forever otherwise.
+ self.lock.surfaces.remove(&head.output.name());
+ self.lock.presented.remove(&head.output.name());
+ head.release(&card);
+ self.pending.borrow_mut().push(CoreEvent::MonitorRemoved(index as u32));
+ }
+
+ // ---- additions ----
+ for i in added {
+ let probe = &probes[i];
+ let used: Vec<crtc::Handle> =
+ self.udev.as_ref().map(|u| u.heads.iter().map(|h| h.crtc).collect()).unwrap_or_default();
+ let Some(crtc) = pick_crtc(&card, probe, &used) else {
+ log::warn!("udev: no free CRTC for newly connected {}; not driving it", probe.name);
+ continue;
+ };
+ // Placed at 0 for now; the re-layout below assigns real offsets.
+ match bring_up_head(&card, &self.dh.clone(), probe, crtc, 0) {
+ Ok((head, entry)) => {
+ log::info!("udev: output {} connected ({}x{})", probe.name, head.size.0, head.size.1);
+ let monitor_id = self.outputs.len() as u32;
+ let geometry = srdwm_core::Rect::new(0, 0, head.size.0 as u32, head.size.1 as u32);
+ if let Some(udev) = self.udev.as_mut() {
+ udev.heads.push(head);
+ }
+ self.outputs.push(entry);
+ self.pending
+ .borrow_mut()
+ .push(CoreEvent::MonitorAdded(srdwm_core::Monitor::new(monitor_id, probe.name.clone(), geometry)));
+ }
+ Err(e) => log::warn!("udev: failed to bring up {}: {e}", probe.name),
+ }
+ }
+
+ self.relayout_outputs();
+ }
+
+ /// Repositions every head left-to-right and republishes the new
+ /// positions to the output globals, the `Space`, and the layer maps.
+ fn relayout_outputs(&mut self) {
+ let Some(udev) = self.udev.as_mut() else { return };
+ let mut x = 0;
+ let mut placed: Vec<(Output, Point<i32, Logical>)> = Vec::new();
+ for head in &mut udev.heads {
+ head.location = (x, 0).into();
+ head.output.change_current_state(None, None, None, Some((x, 0).into()));
+ placed.push((head.output.clone(), head.location));
+ x += head.size.0;
+ }
+ for (output, location) in placed {
+ if let Some(entry) = self.outputs.iter_mut().find(|e| e.output == output) {
+ entry.location = location;
+ }
+ self.space.map_output(&output, (location.x, location.y));
+ // Bars are anchored to their output, so their geometry has to be
+ // recomputed against the moved output rectangle.
+ layer_map_for_output(&output).arrange();
+ }
+ }
+}
+