xwayland-satellite/src/xstate/settings.rs
Shawn Wallace 557ebeb616 xstate/settings: Clamp scale to >=1
Avoids crashing, and GTK apps also seem to generally dislike
when the reported DPI is less than the default.

Fixes #181
2025-06-29 16:24:45 -04:00

188 lines
5.8 KiB
Rust

use super::XState;
use log::warn;
use std::collections::HashMap;
use xcb::x;
impl XState {
pub(crate) fn set_xsettings_owner(&self) {
self.connection
.send_and_check_request(&x::SetSelectionOwner {
owner: self.settings.window,
selection: self.atoms.xsettings,
time: x::CURRENT_TIME,
})
.unwrap();
let reply = self
.connection
.wait_for_reply(self.connection.send_request(&x::GetSelectionOwner {
selection: self.atoms.xsettings,
}))
.unwrap();
if reply.owner() != self.settings.window {
warn!(
"Could not get XSETTINGS selection (owned by {:?})",
reply.owner()
);
}
}
pub(crate) fn update_global_scale(&mut self, scale: f64) {
self.settings.set_scale(scale);
self.connection
.send_and_check_request(&x::ChangeProperty {
window: self.settings.window,
mode: x::PropMode::Replace,
property: self.atoms.xsettings_settings,
r#type: self.atoms.xsettings_settings,
data: &self.settings.as_data(),
})
.unwrap();
}
}
/// The DPI consider 1x scale by X11.
const DEFAULT_DPI: i32 = 96;
/// I don't know why, but the DPI related xsettings seem to
/// divide the DPI by 1024.
const DPI_SCALE_FACTOR: i32 = 1024;
const XFT_DPI: &str = "Xft/DPI";
const GDK_WINDOW_SCALE: &str = "Gdk/WindowScalingFactor";
const GDK_UNSCALED_DPI: &str = "Gdk/UnscaledDPI";
pub(super) struct Settings {
window: x::Window,
serial: u32,
settings: HashMap<&'static str, IntSetting>,
}
#[derive(Copy, Clone)]
struct IntSetting {
value: i32,
last_change_serial: u32,
}
mod setting_type {
pub const INTEGER: u8 = 0;
}
impl Settings {
pub(super) fn new(connection: &xcb::Connection, atoms: &super::Atoms, root: x::Window) -> Self {
let window = connection.generate_id();
connection
.send_and_check_request(&x::CreateWindow {
wid: window,
width: 1,
height: 1,
depth: 0,
parent: root,
x: 0,
y: 0,
border_width: 0,
class: x::WindowClass::InputOnly,
visual: x::COPY_FROM_PARENT,
value_list: &[],
})
.expect("Couldn't create window for settings");
let s = Settings {
window,
serial: 0,
settings: HashMap::from([
(
XFT_DPI,
IntSetting {
value: DEFAULT_DPI * DPI_SCALE_FACTOR,
last_change_serial: 0,
},
),
(
GDK_WINDOW_SCALE,
IntSetting {
value: 1,
last_change_serial: 0,
},
),
(
GDK_UNSCALED_DPI,
IntSetting {
value: DEFAULT_DPI * DPI_SCALE_FACTOR,
last_change_serial: 0,
},
),
]),
};
connection
.send_and_check_request(&x::ChangeProperty {
window,
mode: x::PropMode::Replace,
property: atoms.xsettings_settings,
r#type: atoms.xsettings_settings,
data: &s.as_data(),
})
.unwrap();
s
}
fn as_data(&self) -> Vec<u8> {
// https://specifications.freedesktop.org/xsettings-spec/0.5/#format
let mut data = vec![
// GTK seems to use this value for byte order from the X.h header,
// so I assume I can use it too.
x::ImageOrder::LsbFirst as u8,
// unused
0,
0,
0,
];
data.extend_from_slice(&self.serial.to_le_bytes());
data.extend_from_slice(&(self.settings.len() as u32).to_le_bytes());
fn insert_with_padding(data: &[u8], out: &mut Vec<u8>) {
out.extend_from_slice(data);
// See https://x.org/releases/X11R7.7/doc/xproto/x11protocol.html#Syntactic_Conventions_b
let num_padding_bytes = (4 - (data.len() % 4)) % 4;
out.extend(std::iter::repeat_n(0, num_padding_bytes));
}
for (name, setting) in &self.settings {
data.extend_from_slice(&[setting_type::INTEGER, 0]);
data.extend_from_slice(&(name.len() as u16).to_le_bytes());
insert_with_padding(name.as_bytes(), &mut data);
data.extend_from_slice(&setting.last_change_serial.to_le_bytes());
data.extend_from_slice(&setting.value.to_le_bytes());
}
data
}
fn set_scale(&mut self, scale: f64) {
self.serial += 1;
let scale = scale.max(1.0);
let setting = IntSetting {
value: (scale * DEFAULT_DPI as f64 * DPI_SCALE_FACTOR as f64).round() as i32,
last_change_serial: self.serial,
};
self.settings.entry(XFT_DPI).insert_entry(setting);
// Gdk/WindowScalingFactor + Gdk/UnscaledDPI is identical to setting
// GDK_SCALE = scale and then GDK_DPI_SCALE = 1 / scale.
self.settings
.entry(GDK_UNSCALED_DPI)
.insert_entry(IntSetting {
value: setting.value / scale as i32,
last_change_serial: self.serial,
});
self.settings
.entry(GDK_WINDOW_SCALE)
.insert_entry(IntSetting {
value: scale as i32,
last_change_serial: self.serial,
});
}
}