From 1bb8199342fc40e00145ff06a634c3389f1ba0d6 Mon Sep 17 00:00:00 2001
From: Riteo <riteo@posteo.net>
Date: Sat, 16 Mar 2024 14:48:11 +0100
Subject: [PATCH] Wayland: Workaround API limitation in screen/UI scale logic

Mainly, this fixes auto UI scaling with _single-monitor_ fractional
setups (see the comment in `display_server_wayland.cpp` for more info).

This is the result of a bunch of current limitations, mainly the fact
that the UI scale is static (it's probed at startup) and the fact that
Wayland exposes fractional scales only at the window-level, by design.

The `screen_get_scale` special case should help in 99% of cases, while
the auto UI scale part will unfortunately only help with single-screen
situations, as multi-screen fractional scaling requires dynamic UI
scale changing.
---
 doc/classes/DisplayServer.xml                       |  5 +++--
 editor/editor_settings.cpp                          | 13 +++++++++++++
 .../linuxbsd/wayland/display_server_wayland.cpp     | 10 +++++++++-
 3 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index a0540482667..688e1b70cae 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1103,8 +1103,9 @@
 			<param index="0" name="screen" type="int" default="-1" />
 			<description>
 				Returns the scale factor of the specified screen by index.
-				[b]Note:[/b] On macOS returned value is [code]2.0[/code] for hiDPI (Retina) screen, and [code]1.0[/code] for all other cases.
-				[b]Note:[/b] This method is implemented only on macOS.
+				[b]Note:[/b] On macOS, the returned value is [code]2.0[/code] for hiDPI (Retina) screens, and [code]1.0[/code] for all other cases.
+				[b]Note:[/b] On Linux (Wayland), the returned value is accurate only when [param screen] is [constant SCREEN_OF_MAIN_WINDOW]. Due to API limitations, passing a direct index will return a rounded-up integer, if the screen has a fractional scale (e.g. [code]1.25[/code] would get rounded up to [code]2.0[/code]).
+				[b]Note:[/b] This method is implemented only on macOS and Linux (Wayland).
 			</description>
 		</method>
 		<method name="screen_get_size" qualifiers="const">
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index cec50ac30f0..bd68fa5de58 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -1540,6 +1540,19 @@ String EditorSettings::get_editor_layouts_config() const {
 float EditorSettings::get_auto_display_scale() const {
 #ifdef LINUXBSD_ENABLED
 	if (DisplayServer::get_singleton()->get_name() == "Wayland") {
+		float main_window_scale = DisplayServer::get_singleton()->screen_get_scale(DisplayServer::SCREEN_OF_MAIN_WINDOW);
+
+		if (DisplayServer::get_singleton()->get_screen_count() == 1 || Math::fract(main_window_scale) != 0) {
+			// If we have a single screen or the screen of the window is fractional, all
+			// bets are off. At this point, let's just return the current's window scale,
+			// which is special-cased to the scale of `SCREEN_OF_MAIN_WINDOW`.
+			return main_window_scale;
+		}
+
+		// If the above branch didn't fire, fractional scaling isn't going to work
+		// properly anyways (we're need the ability to change the UI scale at runtime).
+		// At this point it's more convenient to "supersample" like we do with other
+		// platforms, hoping that the user is only using integer-scaled screens.
 		return DisplayServer::get_singleton()->screen_get_max_scale();
 	}
 #endif
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index 1c3cae0435e..f7995472d08 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -550,7 +550,15 @@ float DisplayServerWayland::screen_get_scale(int p_screen) const {
 	MutexLock mutex_lock(wayland_thread.mutex);
 
 	if (p_screen == SCREEN_OF_MAIN_WINDOW) {
-		p_screen = window_get_current_screen();
+		// Wayland does not expose fractional scale factors at the screen-level, but
+		// some code relies on it. Since this special screen is the default and a lot
+		// of code relies on it, we'll return the window's scale, which is what we
+		// really care about. After all, we have very little use of the actual screen
+		// enumeration APIs and we're (for now) in single-window mode anyways.
+		struct wl_surface *wl_surface = wayland_thread.window_get_wl_surface(MAIN_WINDOW_ID);
+		WaylandThread::WindowState *ws = wayland_thread.wl_surface_get_window_state(wl_surface);
+
+		return wayland_thread.window_state_get_scale_factor(ws);
 	}
 
 	return wayland_thread.screen_get_data(p_screen).scale;