azul_winit/platform_impl/linux/x11/
monitor.rs

1use std::os::raw::*;
2
3use parking_lot::Mutex;
4
5use super::{
6    ffi::{
7        RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
8        RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
9    },
10    util, XConnection, XError,
11};
12use crate::{
13    dpi::{PhysicalPosition, PhysicalSize},
14    monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
15    platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode},
16};
17
18// Used for testing. This should always be committed as false.
19const DISABLE_MONITOR_LIST_CACHING: bool = false;
20
21lazy_static! {
22    static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
23}
24
25pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
26    // We update this lazily.
27    (*MONITORS.lock()).take()
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub struct VideoMode {
32    pub(crate) size: (u32, u32),
33    pub(crate) bit_depth: u16,
34    pub(crate) refresh_rate: u16,
35    pub(crate) native_mode: RRMode,
36    pub(crate) monitor: Option<MonitorHandle>,
37}
38
39impl VideoMode {
40    #[inline]
41    pub fn size(&self) -> PhysicalSize<u32> {
42        self.size.into()
43    }
44
45    #[inline]
46    pub fn bit_depth(&self) -> u16 {
47        self.bit_depth
48    }
49
50    #[inline]
51    pub fn refresh_rate(&self) -> u16 {
52        self.refresh_rate
53    }
54
55    #[inline]
56    pub fn monitor(&self) -> RootMonitorHandle {
57        RootMonitorHandle {
58            inner: PlatformMonitorHandle::X(self.monitor.clone().unwrap()),
59        }
60    }
61}
62
63#[derive(Debug, Clone)]
64pub struct MonitorHandle {
65    /// The actual id
66    pub(crate) id: RRCrtc,
67    /// The name of the monitor
68    pub(crate) name: String,
69    /// The size of the monitor
70    dimensions: (u32, u32),
71    /// The position of the monitor in the X screen
72    position: (i32, i32),
73    /// If the monitor is the primary one
74    primary: bool,
75    /// The DPI scale factor
76    pub(crate) scale_factor: f64,
77    /// Used to determine which windows are on this monitor
78    pub(crate) rect: util::AaRect,
79    /// Supported video modes on this monitor
80    video_modes: Vec<VideoMode>,
81}
82
83impl PartialEq for MonitorHandle {
84    fn eq(&self, other: &Self) -> bool {
85        self.id == other.id
86    }
87}
88
89impl Eq for MonitorHandle {}
90
91impl PartialOrd for MonitorHandle {
92    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
93        Some(self.cmp(&other))
94    }
95}
96
97impl Ord for MonitorHandle {
98    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
99        self.id.cmp(&other.id)
100    }
101}
102
103impl std::hash::Hash for MonitorHandle {
104    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
105        self.id.hash(state);
106    }
107}
108
109impl MonitorHandle {
110    fn new(
111        xconn: &XConnection,
112        resources: *mut XRRScreenResources,
113        id: RRCrtc,
114        crtc: *mut XRRCrtcInfo,
115        primary: bool,
116    ) -> Option<Self> {
117        let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
118        let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
119        let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
120        let rect = util::AaRect::new(position, dimensions);
121        Some(MonitorHandle {
122            id,
123            name,
124            scale_factor,
125            dimensions,
126            position,
127            primary,
128            rect,
129            video_modes,
130        })
131    }
132
133    pub fn dummy() -> Self {
134        MonitorHandle {
135            id: 0,
136            name: "<dummy monitor>".into(),
137            scale_factor: 1.0,
138            dimensions: (1, 1),
139            position: (0, 0),
140            primary: true,
141            rect: util::AaRect::new((0, 0), (1, 1)),
142            video_modes: Vec::new(),
143        }
144    }
145
146    pub(crate) fn is_dummy(&self) -> bool {
147        // Zero is an invalid XID value; no real monitor will have it
148        self.id == 0
149    }
150
151    pub fn name(&self) -> Option<String> {
152        Some(self.name.clone())
153    }
154
155    #[inline]
156    pub fn native_identifier(&self) -> u32 {
157        self.id as u32
158    }
159
160    pub fn size(&self) -> PhysicalSize<u32> {
161        self.dimensions.into()
162    }
163
164    pub fn position(&self) -> PhysicalPosition<i32> {
165        self.position.into()
166    }
167
168    #[inline]
169    pub fn scale_factor(&self) -> f64 {
170        self.scale_factor
171    }
172
173    #[inline]
174    pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
175        let monitor = self.clone();
176        self.video_modes.clone().into_iter().map(move |mut x| {
177            x.monitor = Some(monitor.clone());
178            RootVideoMode {
179                video_mode: PlatformVideoMode::X(x),
180            }
181        })
182    }
183}
184
185impl XConnection {
186    pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle {
187        let monitors = self.available_monitors();
188
189        if monitors.is_empty() {
190            // Return a dummy monitor to avoid panicking
191            return MonitorHandle::dummy();
192        }
193
194        let default = monitors.get(0).unwrap();
195
196        let window_rect = match window_rect {
197            Some(rect) => rect,
198            None => return default.to_owned(),
199        };
200
201        let mut largest_overlap = 0;
202        let mut matched_monitor = default;
203        for monitor in &monitors {
204            let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
205            if overlapping_area > largest_overlap {
206                largest_overlap = overlapping_area;
207                matched_monitor = &monitor;
208            }
209        }
210
211        matched_monitor.to_owned()
212    }
213
214    fn query_monitor_list(&self) -> Vec<MonitorHandle> {
215        unsafe {
216            let mut major = 0;
217            let mut minor = 0;
218            (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
219
220            let root = (self.xlib.XDefaultRootWindow)(self.display);
221            let resources = if (major == 1 && minor >= 3) || major > 1 {
222                (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
223            } else {
224                // WARNING: this function is supposedly very slow, on the order of hundreds of ms.
225                // Upon failure, `resources` will be null.
226                (self.xrandr.XRRGetScreenResources)(self.display, root)
227            };
228
229            if resources.is_null() {
230                panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
231            }
232
233            let mut available;
234            let mut has_primary = false;
235
236            let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
237            available = Vec::with_capacity((*resources).ncrtc as usize);
238            for crtc_index in 0..(*resources).ncrtc {
239                let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
240                let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
241                let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
242                if is_active {
243                    let is_primary = *(*crtc).outputs.offset(0) == primary;
244                    has_primary |= is_primary;
245                    MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
246                        .map(|monitor_id| available.push(monitor_id));
247                }
248                (self.xrandr.XRRFreeCrtcInfo)(crtc);
249            }
250
251            // If no monitors were detected as being primary, we just pick one ourselves!
252            if !has_primary {
253                if let Some(ref mut fallback) = available.first_mut() {
254                    // Setting this here will come in handy if we ever add an `is_primary` method.
255                    fallback.primary = true;
256                }
257            }
258
259            (self.xrandr.XRRFreeScreenResources)(resources);
260            available
261        }
262    }
263
264    pub fn available_monitors(&self) -> Vec<MonitorHandle> {
265        let mut monitors_lock = MONITORS.lock();
266        (*monitors_lock)
267            .as_ref()
268            .cloned()
269            .or_else(|| {
270                let monitors = Some(self.query_monitor_list());
271                if !DISABLE_MONITOR_LIST_CACHING {
272                    (*monitors_lock) = monitors.clone();
273                }
274                monitors
275            })
276            .unwrap()
277    }
278
279    #[inline]
280    pub fn primary_monitor(&self) -> MonitorHandle {
281        self.available_monitors()
282            .into_iter()
283            .find(|monitor| monitor.primary)
284            .unwrap_or_else(MonitorHandle::dummy)
285    }
286
287    pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
288        let has_xrandr = unsafe {
289            let mut major = 0;
290            let mut minor = 0;
291            (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor)
292        };
293        assert!(
294            has_xrandr == True,
295            "[winit] XRandR extension not available."
296        );
297
298        let mut event_offset = 0;
299        let mut error_offset = 0;
300        let status = unsafe {
301            (self.xrandr.XRRQueryExtension)(self.display, &mut event_offset, &mut error_offset)
302        };
303
304        if status != True {
305            self.check_errors()?;
306            unreachable!("[winit] `XRRQueryExtension` failed but no error was received.");
307        }
308
309        let mask = RRCrtcChangeNotifyMask | RROutputPropertyNotifyMask | RRScreenChangeNotifyMask;
310        unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) };
311
312        Ok(event_offset)
313    }
314}