1 | use std::str::FromStr; |
2 | use std::{env, str}; |
3 | |
4 | use super::*; |
5 | use crate::dpi::validate_scale_factor; |
6 | use crate::platform_impl::platform::x11::{monitor, VideoModeHandle}; |
7 | |
8 | use tracing::warn; |
9 | use x11rb::protocol::randr::{self, ConnectionExt as _}; |
10 | |
11 | /// Represents values of `WINIT_HIDPI_FACTOR`. |
12 | pub enum EnvVarDPI { |
13 | Randr, |
14 | Scale(f64), |
15 | NotSet, |
16 | } |
17 | |
18 | pub fn calc_dpi_factor( |
19 | (width_px: u32, height_px: u32): (u32, u32), |
20 | (width_mm: u64, height_mm: u64): (u64, u64), |
21 | ) -> f64 { |
22 | // See http://xpra.org/trac/ticket/728 for more information. |
23 | if width_mm == 0 || height_mm == 0 { |
24 | warn!("XRandR reported that the display's 0mm in size, which is certifiably insane" ); |
25 | return 1.0; |
26 | } |
27 | |
28 | let ppmm: f64 = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt(); |
29 | // Quantize 1/12 step size |
30 | let dpi_factor: f64 = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0); |
31 | assert!(validate_scale_factor(dpi_factor)); |
32 | if dpi_factor <= 20. { |
33 | dpi_factor |
34 | } else { |
35 | 1. |
36 | } |
37 | } |
38 | |
39 | impl XConnection { |
40 | // Retrieve DPI from Xft.dpi property |
41 | pub fn get_xft_dpi(&self) -> Option<f64> { |
42 | // Try to get it from XSETTINGS first. |
43 | if let Some(xsettings_screen) = self.xsettings_screen() { |
44 | match self.xsettings_dpi(xsettings_screen) { |
45 | Ok(Some(dpi)) => return Some(dpi), |
46 | Ok(None) => {}, |
47 | Err(err) => { |
48 | tracing::warn!("failed to fetch XSettings: {err}" ); |
49 | }, |
50 | } |
51 | } |
52 | |
53 | self.database().get_string("Xft.dpi" , "" ).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<VideoModeHandle>)> { |
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 | VideoModeHandle { |
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 \ |
109 | WINIT_X11_SCALE_FACTOR" |
110 | ) |
111 | } |
112 | let dpi_env = env::var("WINIT_X11_SCALE_FACTOR" ).ok().map_or_else( |
113 | || EnvVarDPI::NotSet, |
114 | |var| { |
115 | if var.to_lowercase() == "randr" { |
116 | EnvVarDPI::Randr |
117 | } else if let Ok(dpi) = f64::from_str(&var) { |
118 | EnvVarDPI::Scale(dpi) |
119 | } else if var.is_empty() { |
120 | EnvVarDPI::NotSet |
121 | } else { |
122 | panic!( |
123 | "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal \ |
124 | floats greater than 0, or `randr`. Got ` {var}`" |
125 | ); |
126 | } |
127 | }, |
128 | ); |
129 | |
130 | let scale_factor = match dpi_env { |
131 | EnvVarDPI::Randr => calc_dpi_factor( |
132 | (crtc.width.into(), crtc.height.into()), |
133 | (output_info.mm_width as _, output_info.mm_height as _), |
134 | ), |
135 | EnvVarDPI::Scale(dpi_override) => { |
136 | if !validate_scale_factor(dpi_override) { |
137 | panic!( |
138 | "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal \ |
139 | floats greater than 0, or `randr`. Got ` {dpi_override}`" , |
140 | ); |
141 | } |
142 | dpi_override |
143 | }, |
144 | EnvVarDPI::NotSet => { |
145 | if let Some(dpi) = self.get_xft_dpi() { |
146 | dpi / 96. |
147 | } else { |
148 | calc_dpi_factor( |
149 | (crtc.width.into(), crtc.height.into()), |
150 | (output_info.mm_width as _, output_info.mm_height as _), |
151 | ) |
152 | } |
153 | }, |
154 | }; |
155 | |
156 | Some((name, scale_factor, modes)) |
157 | } |
158 | |
159 | pub fn set_crtc_config( |
160 | &self, |
161 | crtc_id: randr::Crtc, |
162 | mode_id: randr::Mode, |
163 | ) -> Result<(), X11Error> { |
164 | let crtc = |
165 | self.xcb_connection().randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?.reply()?; |
166 | |
167 | self.xcb_connection() |
168 | .randr_set_crtc_config( |
169 | crtc_id, |
170 | crtc.timestamp, |
171 | x11rb::CURRENT_TIME, |
172 | crtc.x, |
173 | crtc.y, |
174 | mode_id, |
175 | crtc.rotation, |
176 | &crtc.outputs, |
177 | )? |
178 | .reply() |
179 | .map(|_| ()) |
180 | .map_err(Into::into) |
181 | } |
182 | |
183 | pub fn get_crtc_mode(&self, crtc_id: randr::Crtc) -> Result<randr::Mode, X11Error> { |
184 | Ok(self.xcb_connection().randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?.reply()?.mode) |
185 | } |
186 | } |
187 | |