82 lines
2.6 KiB
Rust
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);
|
|
}
|
|
}
|