@@ -4,6 +4,11 @@ use godot::classes::Os;
44use godot:: { classes:: DisplayServer , obj:: Singleton } ;
55use process_path:: get_dylib_path;
66use 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+
35195fn get_dylib_path_checked ( ) -> CefResult < PathBuf > {
36196 get_dylib_path ( ) . ok_or_else ( || CefError :: ResourceNotFound ( "dylib path" . to_string ( ) ) )
37197}
0 commit comments