| 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 | |