// client/src/output/display.rs use gtk::gdk; use gtk::prelude::ListModelExt; use gtk::prelude::*; use tracing::debug; #[derive(Clone, Debug)] pub struct MonitorInfo { pub geometry: gdk::Rectangle, pub scale_factor: i32, pub is_internal: bool, } /// Enumerate monitors sorted by our desired priority. /// /// Inputs: none; the function reads the current GTK display if one is /// available. /// Outputs: a priority-ordered monitor list, or a single fallback monitor when /// no display is attached. /// Why: window placement should still have a sane default in headless tests /// and on fresh boots before the desktop session has fully settled. pub fn enumerate_monitors() -> Vec { let Some(display) = gdk::Display::default() else { tracing::warn!("⚠️ no GDK display - falling back to single-monitor 0,0"); return vec![MonitorInfo { geometry: gdk::Rectangle::new(0, 0, 1920, 1080), scale_factor: 1, is_internal: false, }]; }; let model = display.monitors(); // gio::ListModel let mut list: Vec<_> = (0..model.n_items()) .filter_map(|i| model.item(i)) .filter_map(|obj| obj.downcast::().ok()) .map(|m| { // -------- internal vs external ---------------------------------- let connector = m.connector().unwrap_or_default(); // e.g. "eDP-1" let is_internal = connector.starts_with("eDP") || connector.starts_with("LVDS") || connector.starts_with("DSI") || connector.to_ascii_lowercase().contains("internal"); // -------- geometry / scale -------------------------------------- let geometry = m.geometry(); let scale_factor = m.scale_factor(); debug!( "🖥️ monitor: {:?}, connector={:?}, geom={:?}, scale={}", m.model(), connector, geometry, scale_factor ); MonitorInfo { geometry, scale_factor, is_internal, } }) .collect(); // external first, built-in last list.sort_by_key(|m| m.is_internal); debug!("🖥️ sorted monitors = {:?}", list); list } #[cfg(test)] mod tests { use super::enumerate_monitors; #[test] fn enumerate_monitors_returns_fallback_when_headless() { let monitors = enumerate_monitors(); assert!(!monitors.is_empty()); assert!(monitors[0].geometry.width() > 0); assert!(monitors[0].geometry.height() > 0); } }