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