winit/platform_impl/linux/x11/util/
randr.rs
1use std::num::NonZeroU16;
2use std::str::FromStr;
3use std::{env, str};
4
5use tracing::warn;
6use x11rb::protocol::randr::{self, ConnectionExt as _};
7
8use super::*;
9use crate::dpi::validate_scale_factor;
10use crate::platform_impl::platform::x11::{monitor, VideoModeHandle};
11
12pub enum EnvVarDPI {
14 Randr,
15 Scale(f64),
16 NotSet,
17}
18
19pub fn calc_dpi_factor(
20 (width_px, height_px): (u32, u32),
21 (width_mm, height_mm): (u64, u64),
22) -> f64 {
23 if width_mm == 0 || height_mm == 0 {
25 warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
26 return 1.0;
27 }
28
29 let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt();
30 let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
32 assert!(validate_scale_factor(dpi_factor));
33 if dpi_factor <= 20. {
34 dpi_factor
35 } else {
36 1.
37 }
38}
39
40impl XConnection {
41 pub fn get_xft_dpi(&self) -> Option<f64> {
43 if let Some(xsettings_screen) = self.xsettings_screen() {
45 match self.xsettings_dpi(xsettings_screen) {
46 Ok(Some(dpi)) => return Some(dpi),
47 Ok(None) => {},
48 Err(err) => {
49 tracing::warn!("failed to fetch XSettings: {err}");
50 },
51 }
52 }
53
54 self.database().get_string("Xft.dpi", "").and_then(|s| f64::from_str(s).ok())
55 }
56
57 pub fn get_output_info(
58 &self,
59 resources: &monitor::ScreenResources,
60 crtc: &randr::GetCrtcInfoReply,
61 ) -> Option<(String, f64, Vec<VideoModeHandle>)> {
62 let output_info = match self
63 .xcb_connection()
64 .randr_get_output_info(crtc.outputs[0], x11rb::CURRENT_TIME)
65 .map_err(X11Error::from)
66 .and_then(|r| r.reply().map_err(X11Error::from))
67 {
68 Ok(output_info) => output_info,
69 Err(err) => {
70 warn!("Failed to get output info: {:?}", err);
71 return None;
72 },
73 };
74
75 let bit_depth = self.default_root().root_depth;
76 let output_modes = &output_info.modes;
77 let resource_modes = resources.modes();
78 let current_mode = crtc.mode;
79
80 let modes = resource_modes
81 .iter()
82 .filter(|x| output_modes.iter().any(|id| x.id == *id))
85 .map(|mode| {
86 VideoModeHandle {
87 current: mode.id == current_mode,
88 size: (mode.width.into(), mode.height.into()),
89 refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode),
90 bit_depth: NonZeroU16::new(bit_depth as u16),
91 native_mode: mode.id,
92 monitor: None,
95 }
96 })
97 .collect();
98
99 let name = match str::from_utf8(&output_info.name) {
100 Ok(name) => name.to_owned(),
101 Err(err) => {
102 warn!("Failed to get output name: {:?}", err);
103 return None;
104 },
105 };
106 let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok();
108 if deprecated_dpi_override.is_some() {
109 warn!(
110 "The WINIT_HIDPI_FACTOR environment variable is deprecated; use \
111 WINIT_X11_SCALE_FACTOR"
112 )
113 }
114 let dpi_env = env::var("WINIT_X11_SCALE_FACTOR").ok().map_or_else(
115 || EnvVarDPI::NotSet,
116 |var| {
117 if var.to_lowercase() == "randr" {
118 EnvVarDPI::Randr
119 } else if let Ok(dpi) = f64::from_str(&var) {
120 EnvVarDPI::Scale(dpi)
121 } else if var.is_empty() {
122 EnvVarDPI::NotSet
123 } else {
124 panic!(
125 "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal \
126 floats greater than 0, or `randr`. Got `{var}`"
127 );
128 }
129 },
130 );
131
132 let scale_factor = match dpi_env {
133 EnvVarDPI::Randr => calc_dpi_factor(
134 (crtc.width.into(), crtc.height.into()),
135 (output_info.mm_width as _, output_info.mm_height as _),
136 ),
137 EnvVarDPI::Scale(dpi_override) => {
138 if !validate_scale_factor(dpi_override) {
139 panic!(
140 "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal \
141 floats greater than 0, or `randr`. Got `{dpi_override}`",
142 );
143 }
144 dpi_override
145 },
146 EnvVarDPI::NotSet => {
147 if let Some(dpi) = self.get_xft_dpi() {
148 dpi / 96.
149 } else {
150 calc_dpi_factor(
151 (crtc.width.into(), crtc.height.into()),
152 (output_info.mm_width as _, output_info.mm_height as _),
153 )
154 }
155 },
156 };
157
158 Some((name, scale_factor, modes))
159 }
160
161 pub fn set_crtc_config(
162 &self,
163 crtc_id: randr::Crtc,
164 mode_id: randr::Mode,
165 ) -> Result<(), X11Error> {
166 let crtc =
167 self.xcb_connection().randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?.reply()?;
168
169 self.xcb_connection()
170 .randr_set_crtc_config(
171 crtc_id,
172 crtc.timestamp,
173 x11rb::CURRENT_TIME,
174 crtc.x,
175 crtc.y,
176 mode_id,
177 crtc.rotation,
178 &crtc.outputs,
179 )?
180 .reply()
181 .map(|_| ())
182 .map_err(Into::into)
183 }
184
185 pub fn get_crtc_mode(&self, crtc_id: randr::Crtc) -> Result<randr::Mode, X11Error> {
186 Ok(self.xcb_connection().randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?.reply()?.mode)
187 }
188}