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