winit/platform_impl/linux/x11/
monitor.rs

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