winit/platform_impl/linux/x11/
monitor.rs

1use super::{util, X11Error, XConnection};
2use crate::dpi::{PhysicalPosition, PhysicalSize};
3use crate::platform_impl::VideoModeHandle as PlatformVideoModeHandle;
4use x11rb::connection::RequestConnection;
5use x11rb::protocol::randr::{self, ConnectionExt as _};
6use x11rb::protocol::xproto;
7
8// Used for testing. This should always be committed as false.
9const DISABLE_MONITOR_LIST_CACHING: bool = false;
10
11impl XConnection {
12    pub fn invalidate_cached_monitor_list(&self) -> Option<Vec<MonitorHandle>> {
13        // We update this lazily.
14        self.monitor_handles.lock().unwrap().take()
15    }
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub struct VideoModeHandle {
20    pub(crate) size: (u32, u32),
21    pub(crate) bit_depth: u16,
22    pub(crate) refresh_rate_millihertz: u32,
23    pub(crate) native_mode: randr::Mode,
24    pub(crate) monitor: Option<MonitorHandle>,
25}
26
27impl VideoModeHandle {
28    #[inline]
29    pub fn size(&self) -> PhysicalSize<u32> {
30        self.size.into()
31    }
32
33    #[inline]
34    pub fn bit_depth(&self) -> u16 {
35        self.bit_depth
36    }
37
38    #[inline]
39    pub fn refresh_rate_millihertz(&self) -> u32 {
40        self.refresh_rate_millihertz
41    }
42
43    #[inline]
44    pub fn monitor(&self) -> MonitorHandle {
45        self.monitor.clone().unwrap()
46    }
47}
48
49#[derive(Debug, Clone)]
50pub struct MonitorHandle {
51    /// The actual id
52    pub(crate) id: randr::Crtc,
53    /// The name of the monitor
54    pub(crate) name: String,
55    /// The size of the monitor
56    dimensions: (u32, u32),
57    /// The position of the monitor in the X screen
58    position: (i32, i32),
59    /// If the monitor is the primary one
60    primary: bool,
61    /// The refresh rate used by monitor.
62    refresh_rate_millihertz: Option<u32>,
63    /// The DPI scale factor
64    pub(crate) scale_factor: f64,
65    /// Used to determine which windows are on this monitor
66    pub(crate) rect: util::AaRect,
67    /// Supported video modes on this monitor
68    video_modes: Vec<VideoModeHandle>,
69}
70
71impl PartialEq for MonitorHandle {
72    fn eq(&self, other: &Self) -> bool {
73        self.id == other.id
74    }
75}
76
77impl Eq for MonitorHandle {}
78
79impl PartialOrd for MonitorHandle {
80    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
81        Some(self.cmp(other))
82    }
83}
84
85impl Ord for MonitorHandle {
86    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
87        self.id.cmp(&other.id)
88    }
89}
90
91impl std::hash::Hash for MonitorHandle {
92    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
93        self.id.hash(state);
94    }
95}
96
97#[inline]
98pub fn mode_refresh_rate_millihertz(mode: &randr::ModeInfo) -> Option<u32> {
99    if mode.dot_clock > 0 && mode.htotal > 0 && mode.vtotal > 0 {
100        #[allow(clippy::unnecessary_cast)]
101        Some((mode.dot_clock as u64 * 1000 / (mode.htotal as u64 * mode.vtotal as u64)) as u32)
102    } else {
103        None
104    }
105}
106
107impl MonitorHandle {
108    fn new(
109        xconn: &XConnection,
110        resources: &ScreenResources,
111        id: randr::Crtc,
112        crtc: &randr::GetCrtcInfoReply,
113        primary: bool,
114    ) -> Option<Self> {
115        let (name, scale_factor, video_modes) = xconn.get_output_info(resources, crtc)?;
116        let dimensions = (crtc.width as u32, crtc.height as u32);
117        let position = (crtc.x as i32, crtc.y as i32);
118
119        // Get the refresh rate of the current video mode.
120        let current_mode = crtc.mode;
121        let screen_modes = resources.modes();
122        let refresh_rate_millihertz = screen_modes
123            .iter()
124            .find(|mode| mode.id == current_mode)
125            .and_then(mode_refresh_rate_millihertz);
126
127        let rect = util::AaRect::new(position, dimensions);
128
129        Some(MonitorHandle {
130            id,
131            name,
132            refresh_rate_millihertz,
133            scale_factor,
134            dimensions,
135            position,
136            primary,
137            rect,
138            video_modes,
139        })
140    }
141
142    pub fn dummy() -> Self {
143        MonitorHandle {
144            id: 0,
145            name: "<dummy monitor>".into(),
146            scale_factor: 1.0,
147            dimensions: (1, 1),
148            position: (0, 0),
149            refresh_rate_millihertz: None,
150            primary: true,
151            rect: util::AaRect::new((0, 0), (1, 1)),
152            video_modes: Vec::new(),
153        }
154    }
155
156    pub(crate) fn is_dummy(&self) -> bool {
157        // Zero is an invalid XID value; no real monitor will have it
158        self.id == 0
159    }
160
161    pub fn name(&self) -> Option<String> {
162        Some(self.name.clone())
163    }
164
165    #[inline]
166    pub fn native_identifier(&self) -> u32 {
167        self.id as _
168    }
169
170    pub fn size(&self) -> PhysicalSize<u32> {
171        self.dimensions.into()
172    }
173
174    pub fn position(&self) -> PhysicalPosition<i32> {
175        self.position.into()
176    }
177
178    pub fn refresh_rate_millihertz(&self) -> Option<u32> {
179        self.refresh_rate_millihertz
180    }
181
182    #[inline]
183    pub fn scale_factor(&self) -> f64 {
184        self.scale_factor
185    }
186
187    #[inline]
188    pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
189        let monitor = self.clone();
190        self.video_modes.clone().into_iter().map(move |mut x| {
191            x.monitor = Some(monitor.clone());
192            PlatformVideoModeHandle::X(x)
193        })
194    }
195}
196
197impl XConnection {
198    pub fn get_monitor_for_window(
199        &self,
200        window_rect: Option<util::AaRect>,
201    ) -> Result<MonitorHandle, X11Error> {
202        let monitors = self.available_monitors()?;
203
204        if monitors.is_empty() {
205            // Return a dummy monitor to avoid panicking
206            return Ok(MonitorHandle::dummy());
207        }
208
209        let default = monitors.first().unwrap();
210
211        let window_rect = match window_rect {
212            Some(rect) => rect,
213            None => return Ok(default.to_owned()),
214        };
215
216        let mut largest_overlap = 0;
217        let mut matched_monitor = default;
218        for monitor in &monitors {
219            let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
220            if overlapping_area > largest_overlap {
221                largest_overlap = overlapping_area;
222                matched_monitor = monitor;
223            }
224        }
225
226        Ok(matched_monitor.to_owned())
227    }
228
229    fn query_monitor_list(&self) -> Result<Vec<MonitorHandle>, X11Error> {
230        let root = self.default_root();
231        let resources =
232            ScreenResources::from_connection(self.xcb_connection(), root, self.randr_version())?;
233
234        // Pipeline all of the get-crtc requests.
235        let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len());
236        for &crtc in resources.crtcs() {
237            crtc_cookies
238                .push(self.xcb_connection().randr_get_crtc_info(crtc, x11rb::CURRENT_TIME)?);
239        }
240
241        // Do this here so we do all of our requests in one shot.
242        let primary = self.xcb_connection().randr_get_output_primary(root.root)?.reply()?.output;
243
244        let mut crtc_infos = Vec::with_capacity(crtc_cookies.len());
245        for cookie in crtc_cookies {
246            let reply = cookie.reply()?;
247            crtc_infos.push(reply);
248        }
249
250        let mut has_primary = false;
251        let mut available_monitors = Vec::with_capacity(resources.crtcs().len());
252        for (crtc_id, crtc) in resources.crtcs().iter().zip(crtc_infos.iter()) {
253            if crtc.width == 0 || crtc.height == 0 || crtc.outputs.is_empty() {
254                continue;
255            }
256
257            let is_primary = crtc.outputs[0] == primary;
258            has_primary |= is_primary;
259            let monitor = MonitorHandle::new(self, &resources, *crtc_id, crtc, is_primary);
260            available_monitors.extend(monitor);
261        }
262
263        // If we don't have a primary monitor, just pick one ourselves!
264        if !has_primary {
265            if let Some(ref mut fallback) = available_monitors.first_mut() {
266                // Setting this here will come in handy if we ever add an `is_primary` method.
267                fallback.primary = true;
268            }
269        }
270
271        Ok(available_monitors)
272    }
273
274    pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, X11Error> {
275        let mut monitors_lock = self.monitor_handles.lock().unwrap();
276        match *monitors_lock {
277            Some(ref monitors) => Ok(monitors.clone()),
278            None => {
279                let monitors = self.query_monitor_list()?;
280                if !DISABLE_MONITOR_LIST_CACHING {
281                    *monitors_lock = Some(monitors.clone());
282                }
283                Ok(monitors)
284            },
285        }
286    }
287
288    #[inline]
289    pub fn primary_monitor(&self) -> Result<MonitorHandle, X11Error> {
290        Ok(self
291            .available_monitors()?
292            .into_iter()
293            .find(|monitor| monitor.primary)
294            .unwrap_or_else(MonitorHandle::dummy))
295    }
296
297    pub fn select_xrandr_input(&self, root: xproto::Window) -> Result<u8, X11Error> {
298        use randr::NotifyMask;
299
300        // Get extension info.
301        let info = self
302            .xcb_connection()
303            .extension_information(randr::X11_EXTENSION_NAME)?
304            .ok_or(X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
305
306        // Select input data.
307        let event_mask =
308            NotifyMask::CRTC_CHANGE | NotifyMask::OUTPUT_PROPERTY | NotifyMask::SCREEN_CHANGE;
309        self.xcb_connection().randr_select_input(root, event_mask)?;
310
311        Ok(info.first_event)
312    }
313}
314
315pub struct ScreenResources {
316    /// List of attached modes.
317    modes: Vec<randr::ModeInfo>,
318
319    /// List of attached CRTCs.
320    crtcs: Vec<randr::Crtc>,
321}
322
323impl ScreenResources {
324    pub(crate) fn modes(&self) -> &[randr::ModeInfo] {
325        &self.modes
326    }
327
328    pub(crate) fn crtcs(&self) -> &[randr::Crtc] {
329        &self.crtcs
330    }
331
332    pub(crate) fn from_connection(
333        conn: &impl x11rb::connection::Connection,
334        root: &x11rb::protocol::xproto::Screen,
335        (major_version, minor_version): (u32, u32),
336    ) -> Result<Self, X11Error> {
337        if (major_version == 1 && minor_version >= 3) || major_version > 1 {
338            let reply = conn.randr_get_screen_resources_current(root.root)?.reply()?;
339            Ok(Self::from_get_screen_resources_current_reply(reply))
340        } else {
341            let reply = conn.randr_get_screen_resources(root.root)?.reply()?;
342            Ok(Self::from_get_screen_resources_reply(reply))
343        }
344    }
345
346    pub(crate) fn from_get_screen_resources_reply(reply: randr::GetScreenResourcesReply) -> Self {
347        Self { modes: reply.modes, crtcs: reply.crtcs }
348    }
349
350    pub(crate) fn from_get_screen_resources_current_reply(
351        reply: randr::GetScreenResourcesCurrentReply,
352    ) -> Self {
353        Self { modes: reply.modes, crtcs: reply.crtcs }
354    }
355}