Skip to content

Commit f47096f

Browse files
authored
feat: dpi probe on gnome desktop environment (#201)
1 parent 178e247 commit f47096f

4 files changed

Lines changed: 164 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ url = "2"
5454
percent-encoding = "2"
5555
adblock = "0.12"
5656
ciborium = "0.2"
57+
quick-xml = "0.39"
5758

5859
[workspace.lints.rust]
5960
unconditional_panic = "deny"

crates/gdcef/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ retour = { workspace = true }
4444
ash = { workspace = true }
4545
libloading = { workspace = true }
4646
libc = { workspace = true }
47+
quick-xml = { workspace = true }
4748

4849
[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies]
4950
retour = { workspace = true }

crates/gdcef/src/utils.rs

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ use godot::classes::Os;
44
use godot::{classes::DisplayServer, obj::Singleton};
55
use process_path::get_dylib_path;
66
use std::path::PathBuf;
7+
#[cfg(target_os = "linux")]
8+
use std::sync::OnceLock;
9+
10+
#[cfg(target_os = "linux")]
11+
static LINUX_DESKTOP_SCALE_CANDIDATE: OnceLock<Option<f32>> = OnceLock::new();
712

813
/// Returns the display scale factor for the primary screen.
914
///
@@ -26,12 +31,167 @@ pub fn get_display_scale_factor() -> f32 {
2631
}
2732
}
2833

29-
#[cfg(not(target_os = "windows"))]
34+
#[cfg(target_os = "linux")]
35+
{
36+
let screen_scale = display_server.screen_get_scale();
37+
if screen_scale <= 1.0
38+
&& env_or_empty("XDG_SESSION_TYPE").eq_ignore_ascii_case("wayland")
39+
&& let Some(candidate) = linux_desktop_scale_candidate()
40+
&& candidate > 1.0
41+
{
42+
candidate
43+
} else {
44+
screen_scale
45+
}
46+
}
47+
48+
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
3049
{
3150
display_server.screen_get_scale()
3251
}
3352
}
3453

54+
#[cfg(target_os = "linux")]
55+
fn linux_desktop_scale_candidate() -> Option<f32> {
56+
*LINUX_DESKTOP_SCALE_CANDIDATE.get_or_init(|| {
57+
let scales = gnome_monitor_scales();
58+
match scales.primary_scale {
59+
Some(scale) if scale.is_finite() && scale > 1.0 => Some(scale),
60+
Some(_) => None,
61+
None => scales
62+
.all_scales
63+
.into_iter()
64+
.filter(|scale| scale.is_finite() && *scale > 1.0)
65+
.max_by(|a, b| a.total_cmp(b)),
66+
}
67+
})
68+
}
69+
70+
#[cfg(target_os = "linux")]
71+
#[derive(Default)]
72+
struct GnomeMonitorScales {
73+
primary_scale: Option<f32>,
74+
all_scales: Vec<f32>,
75+
}
76+
77+
#[cfg(target_os = "linux")]
78+
fn gnome_monitor_scales() -> GnomeMonitorScales {
79+
let scales = gnome_monitors_xml_scales();
80+
let primary_scale = scales
81+
.logical_monitors
82+
.iter()
83+
.find(|monitor| monitor.primary)
84+
.and_then(|monitor| monitor.scale);
85+
let all_scales = scales
86+
.logical_monitors
87+
.into_iter()
88+
.filter_map(|monitor| monitor.scale)
89+
.collect::<Vec<_>>();
90+
GnomeMonitorScales {
91+
primary_scale,
92+
all_scales,
93+
}
94+
}
95+
96+
#[cfg(target_os = "linux")]
97+
#[derive(Default)]
98+
struct GnomeMonitorsXml {
99+
logical_monitors: Vec<GnomeLogicalMonitor>,
100+
}
101+
102+
#[cfg(target_os = "linux")]
103+
#[derive(Default)]
104+
struct GnomeLogicalMonitor {
105+
primary: bool,
106+
scale: Option<f32>,
107+
}
108+
109+
#[cfg(target_os = "linux")]
110+
enum MonitorXmlText {
111+
Primary,
112+
Scale,
113+
}
114+
115+
#[cfg(target_os = "linux")]
116+
fn gnome_monitors_xml_scales() -> GnomeMonitorsXml {
117+
use quick_xml::Reader;
118+
use quick_xml::events::Event;
119+
120+
let Some(config_home) = linux_config_home() else {
121+
return GnomeMonitorsXml::default();
122+
};
123+
let path = config_home.join("monitors.xml");
124+
let Ok(contents) = std::fs::read_to_string(path) else {
125+
return GnomeMonitorsXml::default();
126+
};
127+
let mut reader = Reader::from_str(&contents);
128+
reader.config_mut().trim_text(true);
129+
130+
let mut logical_monitors = Vec::new();
131+
let mut current_monitor: Option<GnomeLogicalMonitor> = None;
132+
let mut text_target: Option<MonitorXmlText> = None;
133+
loop {
134+
match reader.read_event() {
135+
Ok(Event::Start(element)) if element.name().as_ref() == b"logicalmonitor" => {
136+
current_monitor = Some(GnomeLogicalMonitor::default());
137+
}
138+
Ok(Event::End(element)) if element.name().as_ref() == b"logicalmonitor" => {
139+
if let Some(monitor) = current_monitor.take() {
140+
logical_monitors.push(monitor);
141+
}
142+
text_target = None;
143+
}
144+
Ok(Event::Start(element)) if current_monitor.is_some() => {
145+
text_target = match element.name().as_ref() {
146+
b"primary" => Some(MonitorXmlText::Primary),
147+
b"scale" => Some(MonitorXmlText::Scale),
148+
_ => None,
149+
};
150+
}
151+
Ok(Event::End(element)) if matches!(element.name().as_ref(), b"primary" | b"scale") => {
152+
text_target = None;
153+
}
154+
Ok(Event::Text(text)) => {
155+
if let Some(monitor) = current_monitor.as_mut()
156+
&& let Some(value) = text.decode().ok()
157+
{
158+
match text_target {
159+
Some(MonitorXmlText::Primary) => {
160+
let normalized = value.trim();
161+
monitor.primary =
162+
normalized == "yes" || normalized == "true" || normalized == "1";
163+
}
164+
Some(MonitorXmlText::Scale) => {
165+
if let Ok(scale) = value.parse::<f32>() {
166+
monitor.scale = Some(scale);
167+
}
168+
}
169+
None => {}
170+
}
171+
}
172+
}
173+
Ok(Event::Eof) => break,
174+
Err(_) => return GnomeMonitorsXml::default(),
175+
_ => {}
176+
}
177+
}
178+
179+
GnomeMonitorsXml { logical_monitors }
180+
}
181+
182+
#[cfg(target_os = "linux")]
183+
fn linux_config_home() -> Option<PathBuf> {
184+
std::env::var_os("XDG_CONFIG_HOME")
185+
.filter(|path| !path.is_empty())
186+
.map(PathBuf::from)
187+
.or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".config")))
188+
}
189+
190+
#[cfg(target_os = "linux")]
191+
fn env_or_empty(name: &str) -> String {
192+
std::env::var(name).unwrap_or_default()
193+
}
194+
35195
fn get_dylib_path_checked() -> CefResult<PathBuf> {
36196
get_dylib_path().ok_or_else(|| CefError::ResourceNotFound("dylib path".to_string()))
37197
}

0 commit comments

Comments
 (0)