linux: Use randr
as fallback for scale factor in X11 (#34265)
Closes #14537 - Adds server-side scale factor detection via `randr` when client-side detection fails using `xrdb/Xft.dpi`. - Adds the `GPUI_X11_SCALE_FACTOR` flag to force a scale factor, which can be a positive number for custom scaling or `randr` for server-side scale factor detection. Release Notes: - Fixed an issue where the scale factor was not detected correctly on X11 systems when `Xft.dpi` is not defined (mostly in cases involving window managers).
This commit is contained in:
parent
8812e7cd14
commit
153840199e
1 changed files with 253 additions and 6 deletions
|
@ -77,6 +77,8 @@ pub(crate) const XINPUT_ALL_DEVICES: xinput::DeviceId = 0;
|
|||
/// terminology is both archaic and unclear.
|
||||
pub(crate) const XINPUT_ALL_DEVICE_GROUPS: xinput::DeviceId = 1;
|
||||
|
||||
const GPUI_X11_SCALE_FACTOR_ENV: &str = "GPUI_X11_SCALE_FACTOR";
|
||||
|
||||
pub(crate) struct WindowRef {
|
||||
window: X11WindowStatePtr,
|
||||
refresh_state: Option<RefreshState>,
|
||||
|
@ -424,12 +426,7 @@ impl X11Client {
|
|||
|
||||
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection)
|
||||
.context("Failed to create resource database")?;
|
||||
let scale_factor = resource_database
|
||||
.get_value("Xft.dpi", "Xft.dpi")
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|dpi: f32| dpi / 96.0)
|
||||
.unwrap_or(1.0);
|
||||
let scale_factor = get_scale_factor(&xcb_connection, &resource_database, x_root_index);
|
||||
let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database)
|
||||
.context("Failed to initialize cursor theme handler")?
|
||||
.reply()
|
||||
|
@ -2272,3 +2269,253 @@ fn create_invisible_cursor(
|
|||
xcb_flush(connection);
|
||||
Ok(cursor)
|
||||
}
|
||||
|
||||
enum DpiMode {
|
||||
Randr,
|
||||
Scale(f32),
|
||||
NotSet,
|
||||
}
|
||||
|
||||
fn get_scale_factor(
|
||||
connection: &XCBConnection,
|
||||
resource_database: &Database,
|
||||
screen_index: usize,
|
||||
) -> f32 {
|
||||
let env_dpi = std::env::var(GPUI_X11_SCALE_FACTOR_ENV)
|
||||
.ok()
|
||||
.map(|var| {
|
||||
if var.to_lowercase() == "randr" {
|
||||
DpiMode::Randr
|
||||
} else if let Ok(scale) = var.parse::<f32>() {
|
||||
if valid_scale_factor(scale) {
|
||||
DpiMode::Scale(scale)
|
||||
} else {
|
||||
panic!(
|
||||
"`{}` must be a positive normal number or `randr`. Got `{}`",
|
||||
GPUI_X11_SCALE_FACTOR_ENV, var
|
||||
);
|
||||
}
|
||||
} else if var.is_empty() {
|
||||
DpiMode::NotSet
|
||||
} else {
|
||||
panic!(
|
||||
"`{}` must be a positive number or `randr`. Got `{}`",
|
||||
GPUI_X11_SCALE_FACTOR_ENV, var
|
||||
);
|
||||
}
|
||||
})
|
||||
.unwrap_or(DpiMode::NotSet);
|
||||
|
||||
match env_dpi {
|
||||
DpiMode::Scale(scale) => {
|
||||
log::info!(
|
||||
"Using scale factor from {}: {}",
|
||||
GPUI_X11_SCALE_FACTOR_ENV,
|
||||
scale
|
||||
);
|
||||
return scale;
|
||||
}
|
||||
DpiMode::Randr => {
|
||||
if let Some(scale) = get_randr_scale_factor(connection, screen_index) {
|
||||
log::info!(
|
||||
"Using RandR scale factor from {}=randr: {}",
|
||||
GPUI_X11_SCALE_FACTOR_ENV,
|
||||
scale
|
||||
);
|
||||
return scale;
|
||||
}
|
||||
log::warn!("Failed to calculate RandR scale factor, falling back to default");
|
||||
return 1.0;
|
||||
}
|
||||
DpiMode::NotSet => {}
|
||||
}
|
||||
|
||||
// TODO: Use scale factor from XSettings here
|
||||
|
||||
if let Some(dpi) = resource_database
|
||||
.get_value::<f32>("Xft.dpi", "Xft.dpi")
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
let scale = dpi / 96.0; // base dpi
|
||||
log::info!("Using scale factor from Xft.dpi: {}", scale);
|
||||
return scale;
|
||||
}
|
||||
|
||||
if let Some(scale) = get_randr_scale_factor(connection, screen_index) {
|
||||
log::info!("Using RandR scale factor: {}", scale);
|
||||
return scale;
|
||||
}
|
||||
|
||||
log::info!("Using default scale factor: 1.0");
|
||||
1.0
|
||||
}
|
||||
|
||||
fn get_randr_scale_factor(connection: &XCBConnection, screen_index: usize) -> Option<f32> {
|
||||
let root = connection.setup().roots.get(screen_index)?.root;
|
||||
|
||||
let version_cookie = connection.randr_query_version(1, 6).ok()?;
|
||||
let version_reply = version_cookie.reply().ok()?;
|
||||
if version_reply.major_version < 1
|
||||
|| (version_reply.major_version == 1 && version_reply.minor_version < 5)
|
||||
{
|
||||
return legacy_get_randr_scale_factor(connection, root); // for randr <1.5
|
||||
}
|
||||
|
||||
let monitors_cookie = connection.randr_get_monitors(root, true).ok()?; // true for active only
|
||||
let monitors_reply = monitors_cookie.reply().ok()?;
|
||||
|
||||
let mut fallback_scale: Option<f32> = None;
|
||||
for monitor in monitors_reply.monitors {
|
||||
if monitor.width_in_millimeters == 0 || monitor.height_in_millimeters == 0 {
|
||||
continue;
|
||||
}
|
||||
let scale_factor = get_dpi_factor(
|
||||
(monitor.width as u32, monitor.height as u32),
|
||||
(
|
||||
monitor.width_in_millimeters as u64,
|
||||
monitor.height_in_millimeters as u64,
|
||||
),
|
||||
);
|
||||
if monitor.primary {
|
||||
return Some(scale_factor);
|
||||
} else if fallback_scale.is_none() {
|
||||
fallback_scale = Some(scale_factor);
|
||||
}
|
||||
}
|
||||
|
||||
fallback_scale
|
||||
}
|
||||
|
||||
fn legacy_get_randr_scale_factor(connection: &XCBConnection, root: u32) -> Option<f32> {
|
||||
let primary_cookie = connection.randr_get_output_primary(root).ok()?;
|
||||
let primary_reply = primary_cookie.reply().ok()?;
|
||||
let primary_output = primary_reply.output;
|
||||
|
||||
let primary_output_cookie = connection
|
||||
.randr_get_output_info(primary_output, x11rb::CURRENT_TIME)
|
||||
.ok()?;
|
||||
let primary_output_info = primary_output_cookie.reply().ok()?;
|
||||
|
||||
// try primary
|
||||
if primary_output_info.connection == randr::Connection::CONNECTED
|
||||
&& primary_output_info.mm_width > 0
|
||||
&& primary_output_info.mm_height > 0
|
||||
&& primary_output_info.crtc != 0
|
||||
{
|
||||
let crtc_cookie = connection
|
||||
.randr_get_crtc_info(primary_output_info.crtc, x11rb::CURRENT_TIME)
|
||||
.ok()?;
|
||||
let crtc_info = crtc_cookie.reply().ok()?;
|
||||
|
||||
if crtc_info.width > 0 && crtc_info.height > 0 {
|
||||
let scale_factor = get_dpi_factor(
|
||||
(crtc_info.width as u32, crtc_info.height as u32),
|
||||
(
|
||||
primary_output_info.mm_width as u64,
|
||||
primary_output_info.mm_height as u64,
|
||||
),
|
||||
);
|
||||
return Some(scale_factor);
|
||||
}
|
||||
}
|
||||
|
||||
// fallback: full scan
|
||||
let resources_cookie = connection.randr_get_screen_resources_current(root).ok()?;
|
||||
let screen_resources = resources_cookie.reply().ok()?;
|
||||
|
||||
let mut crtc_cookies = Vec::with_capacity(screen_resources.crtcs.len());
|
||||
for &crtc in &screen_resources.crtcs {
|
||||
if let Ok(cookie) = connection.randr_get_crtc_info(crtc, x11rb::CURRENT_TIME) {
|
||||
crtc_cookies.push((crtc, cookie));
|
||||
}
|
||||
}
|
||||
|
||||
let mut crtc_infos: HashMap<randr::Crtc, randr::GetCrtcInfoReply> = HashMap::default();
|
||||
let mut valid_outputs: HashSet<randr::Output> = HashSet::new();
|
||||
for (crtc, cookie) in crtc_cookies {
|
||||
if let Ok(reply) = cookie.reply() {
|
||||
if reply.width > 0 && reply.height > 0 && !reply.outputs.is_empty() {
|
||||
crtc_infos.insert(crtc, reply.clone());
|
||||
valid_outputs.extend(&reply.outputs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if valid_outputs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut output_cookies = Vec::with_capacity(valid_outputs.len());
|
||||
for &output in &valid_outputs {
|
||||
if let Ok(cookie) = connection.randr_get_output_info(output, x11rb::CURRENT_TIME) {
|
||||
output_cookies.push((output, cookie));
|
||||
}
|
||||
}
|
||||
let mut output_infos: HashMap<randr::Output, randr::GetOutputInfoReply> = HashMap::default();
|
||||
for (output, cookie) in output_cookies {
|
||||
if let Ok(reply) = cookie.reply() {
|
||||
output_infos.insert(output, reply);
|
||||
}
|
||||
}
|
||||
|
||||
let mut fallback_scale: Option<f32> = None;
|
||||
for crtc_info in crtc_infos.values() {
|
||||
for &output in &crtc_info.outputs {
|
||||
if let Some(output_info) = output_infos.get(&output) {
|
||||
if output_info.connection != randr::Connection::CONNECTED {
|
||||
continue;
|
||||
}
|
||||
|
||||
if output_info.mm_width == 0 || output_info.mm_height == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let scale_factor = get_dpi_factor(
|
||||
(crtc_info.width as u32, crtc_info.height as u32),
|
||||
(output_info.mm_width as u64, output_info.mm_height as u64),
|
||||
);
|
||||
|
||||
if output != primary_output && fallback_scale.is_none() {
|
||||
fallback_scale = Some(scale_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fallback_scale
|
||||
}
|
||||
|
||||
fn get_dpi_factor((width_px, height_px): (u32, u32), (width_mm, height_mm): (u64, u64)) -> f32 {
|
||||
let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt(); // pixels per mm
|
||||
|
||||
const MM_PER_INCH: f64 = 25.4;
|
||||
const BASE_DPI: f64 = 96.0;
|
||||
const QUANTIZE_STEP: f64 = 12.0; // e.g. 1.25 = 15/12, 1.5 = 18/12, 1.75 = 21/12, 2.0 = 24/12
|
||||
const MIN_SCALE: f64 = 1.0;
|
||||
const MAX_SCALE: f64 = 20.0;
|
||||
|
||||
let dpi_factor =
|
||||
((ppmm * (QUANTIZE_STEP * MM_PER_INCH / BASE_DPI)).round() / QUANTIZE_STEP).max(MIN_SCALE);
|
||||
|
||||
let validated_factor = if dpi_factor <= MAX_SCALE {
|
||||
dpi_factor
|
||||
} else {
|
||||
MIN_SCALE
|
||||
};
|
||||
|
||||
if valid_scale_factor(validated_factor as f32) {
|
||||
validated_factor as f32
|
||||
} else {
|
||||
log::warn!(
|
||||
"Calculated DPI factor {} is invalid, using 1.0",
|
||||
validated_factor
|
||||
);
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn valid_scale_factor(scale_factor: f32) -> bool {
|
||||
scale_factor.is_sign_positive() && scale_factor.is_normal()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue