1 | use std::{env, str, str::FromStr}; |
2 | |
3 | use super::*; |
4 | use crate::platform_impl::platform::x11::monitor; |
5 | use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode}; |
6 | |
7 | use x11rb::protocol::randr::{self, ConnectionExt as _}; |
8 | |
9 | /// Represents values of `WINIT_HIDPI_FACTOR`. |
10 | pub enum EnvVarDPI { |
11 | Randr, |
12 | Scale(f64), |
13 | NotSet, |
14 | } |
15 | |
16 | pub 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 | |
37 | impl 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 | |