srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/udev/outputs.rs
blob: 6e282d62ea668b979d8f575f7e8e369a37bae514 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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();
        }
    }
}