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
11const DISABLE_MONITOR_LIST_CACHING: bool = false;
13
14impl XConnection {
15 pub fn invalidate_cached_monitor_list(&self) -> Option<Vec<MonitorHandle>> {
16 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 pub(crate) id: randr::Crtc,
57 pub(crate) name: String,
59 pub(crate) position: (i32, i32),
61 primary: bool,
63 pub(crate) scale_factor: f64,
65 pub(crate) rect: util::AaRect,
67 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 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 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 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 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 !has_primary {
244 if let Some(ref mut fallback) = available_monitors.first_mut() {
245 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 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 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 modes: Vec<randr::ModeInfo>,
297
298 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}