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
8const DISABLE_MONITOR_LIST_CACHING: bool = false;
10
11impl XConnection {
12 pub fn invalidate_cached_monitor_list(&self) -> Option<Vec<MonitorHandle>> {
13 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 pub(crate) id: randr::Crtc,
53 pub(crate) name: String,
55 dimensions: (u32, u32),
57 position: (i32, i32),
59 primary: bool,
61 refresh_rate_millihertz: Option<u32>,
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<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 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 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 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 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 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 !has_primary {
265 if let Some(ref mut fallback) = available_monitors.first_mut() {
266 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 let info = self
302 .xcb_connection()
303 .extension_information(randr::X11_EXTENSION_NAME)?
304 .ok_or(X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
305
306 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 modes: Vec<randr::ModeInfo>,
318
319 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}