lesavka/client/src/output/display.rs

82 lines
2.6 KiB
Rust

// 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<MonitorInfo> {
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::<gdk::Monitor>().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);
}
}