Browse Source

Add support for system dark mode (Linux)

- Use `org.freedesktop.appearance color-scheme` to support system dark mode.
Raul Santos 2 years ago
parent
commit
471c9c2969

+ 2 - 2
doc/classes/DisplayServer.xml

@@ -681,14 +681,14 @@
 			<return type="bool" />
 			<description>
 				Returns [code]true[/code] if OS is using dark mode.
-				[b]Note:[/b] This method is implemented on macOS and Windows.
+				[b]Note:[/b] This method is implemented on macOS, Windows and Linux.
 			</description>
 		</method>
 		<method name="is_dark_mode_supported" qualifiers="const">
 			<return type="bool" />
 			<description>
 				Returns [code]true[/code] if OS supports dark mode.
-				[b]Note:[/b] This method is implemented on macOS and Windows.
+				[b]Note:[/b] This method is implemented on macOS, Windows and Linux.
 			</description>
 		</method>
 		<method name="keyboard_get_current_layout" qualifiers="const">

+ 1 - 0
platform/linuxbsd/SCsub

@@ -9,6 +9,7 @@ common_linuxbsd = [
     "crash_handler_linuxbsd.cpp",
     "os_linuxbsd.cpp",
     "joypad_linux.cpp",
+    "freedesktop_portal_desktop.cpp",
     "freedesktop_screensaver.cpp",
 ]
 

+ 1 - 1
platform/linuxbsd/detect.py

@@ -40,7 +40,7 @@ def get_opts():
         BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False),
         BoolVariable("use_msan", "Use LLVM compiler memory sanitizer (MSAN)", False),
         BoolVariable("pulseaudio", "Detect and use PulseAudio", True),
-        BoolVariable("dbus", "Detect and use D-Bus to handle screensaver", True),
+        BoolVariable("dbus", "Detect and use D-Bus to handle screensaver and portal desktop settings", True),
         BoolVariable("speechd", "Detect and use Speech Dispatcher for Text-to-Speech support", True),
         BoolVariable("fontconfig", "Detect and use fontconfig for system fonts support", True),
         BoolVariable("udev", "Use udev for gamepad connection callbacks", True),

+ 25 - 0
platform/linuxbsd/display_server_x11.cpp

@@ -349,6 +349,28 @@ void DisplayServerX11::tts_stop() {
 
 #endif
 
+#ifdef DBUS_ENABLED
+
+bool DisplayServerX11::is_dark_mode_supported() const {
+	return portal_desktop->is_supported();
+}
+
+bool DisplayServerX11::is_dark_mode() const {
+	switch (portal_desktop->get_appearance_color_scheme()) {
+		case 1:
+			// Prefers dark theme.
+			return true;
+		case 2:
+			// Prefers light theme.
+			return false;
+		default:
+			// Preference unknown.
+			return false;
+	}
+}
+
+#endif
+
 void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {
 	_THREAD_SAFE_METHOD_
 
@@ -5009,6 +5031,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
 #ifdef DBUS_ENABLED
 	screensaver = memnew(FreeDesktopScreenSaver);
 	screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
+
+	portal_desktop = memnew(FreeDesktopPortalDesktop);
 #endif
 
 	r_error = OK;
@@ -5094,6 +5118,7 @@ DisplayServerX11::~DisplayServerX11() {
 
 #ifdef DBUS_ENABLED
 	memdelete(screensaver);
+	memdelete(portal_desktop);
 #endif
 }
 

+ 10 - 0
platform/linuxbsd/display_server_x11.h

@@ -60,6 +60,7 @@
 #endif
 
 #if defined(DBUS_ENABLED)
+#include "freedesktop_portal_desktop.h"
 #include "freedesktop_screensaver.h"
 #endif
 
@@ -120,6 +121,10 @@ class DisplayServerX11 : public DisplayServer {
 	TTS_Linux *tts = nullptr;
 #endif
 
+#if defined(DBUS_ENABLED)
+	FreeDesktopPortalDesktop *portal_desktop = nullptr;
+#endif
+
 	struct WindowData {
 		Window x11_window;
 		::XIC xic;
@@ -320,6 +325,11 @@ public:
 	virtual void tts_stop() override;
 #endif
 
+#if defined(DBUS_ENABLED)
+	virtual bool is_dark_mode_supported() const override;
+	virtual bool is_dark_mode() const override;
+#endif
+
 	virtual void mouse_set_mode(MouseMode p_mode) override;
 	virtual MouseMode mouse_get_mode() const override;
 

+ 135 - 0
platform/linuxbsd/freedesktop_portal_desktop.cpp

@@ -0,0 +1,135 @@
+/*************************************************************************/
+/*  freedesktop_portal_desktop.cpp                                       */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "freedesktop_portal_desktop.h"
+
+#ifdef DBUS_ENABLED
+
+#include "core/error/error_macros.h"
+#include "core/os/os.h"
+#include "core/string/ustring.h"
+
+#include "dbus-so_wrap.h"
+
+#include "core/variant/variant.h"
+
+#define BUS_OBJECT_NAME "org.freedesktop.portal.Desktop"
+#define BUS_OBJECT_PATH "/org/freedesktop/portal/desktop"
+
+#define BUS_INTERFACE_SETTINGS "org.freedesktop.portal.Settings"
+
+static bool try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) {
+	DBusMessageIter iter[3];
+
+	dbus_message_iter_init(p_reply_message, &iter[0]);
+	if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) {
+		return false;
+	}
+
+	dbus_message_iter_recurse(&iter[0], &iter[1]);
+	if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) {
+		return false;
+	}
+
+	dbus_message_iter_recurse(&iter[1], &iter[2]);
+	if (dbus_message_iter_get_arg_type(&iter[2]) != p_type) {
+		return false;
+	}
+
+	dbus_message_iter_get_basic(&iter[2], r_value);
+	return true;
+}
+
+bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value) {
+	if (unsupported) {
+		return false;
+	}
+
+	DBusError error;
+	dbus_error_init(&error);
+
+	DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error);
+	if (dbus_error_is_set(&error)) {
+		dbus_error_free(&error);
+		unsupported = true;
+		if (OS::get_singleton()->is_stdout_verbose()) {
+			ERR_PRINT(String() + "Error opening D-Bus connection: " + error.message);
+		}
+		return false;
+	}
+
+	DBusMessage *message = dbus_message_new_method_call(
+			BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_SETTINGS,
+			"Read");
+	dbus_message_append_args(
+			message,
+			DBUS_TYPE_STRING, &p_namespace,
+			DBUS_TYPE_STRING, &p_key,
+			DBUS_TYPE_INVALID);
+
+	DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error);
+	dbus_message_unref(message);
+	if (dbus_error_is_set(&error)) {
+		dbus_error_free(&error);
+		dbus_connection_unref(bus);
+		if (OS::get_singleton()->is_stdout_verbose()) {
+			ERR_PRINT(String() + "Error on D-Bus communication: " + error.message);
+		}
+		return false;
+	}
+
+	bool success = try_parse_variant(reply, p_type, r_value);
+
+	dbus_message_unref(reply);
+	dbus_connection_unref(bus);
+
+	return success;
+}
+
+uint32_t FreeDesktopPortalDesktop::get_appearance_color_scheme() {
+	if (unsupported) {
+		return 0;
+	}
+
+	uint32_t value = 0;
+	read_setting("org.freedesktop.appearance", "color-scheme", DBUS_TYPE_UINT32, &value);
+	return value;
+}
+
+FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() {
+#ifdef DEBUG_ENABLED
+	int dylibloader_verbose = 1;
+#else
+	int dylibloader_verbose = 0;
+#endif
+	unsupported = (initialize_dbus(dylibloader_verbose) != 0);
+}
+
+#endif // DBUS_ENABLED

+ 59 - 0
platform/linuxbsd/freedesktop_portal_desktop.h

@@ -0,0 +1,59 @@
+/*************************************************************************/
+/*  freedesktop_portal_desktop.h                                         */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef FREEDESKTOP_PORTAL_DESKTOP_H
+#define FREEDESKTOP_PORTAL_DESKTOP_H
+
+#ifdef DBUS_ENABLED
+
+#include <stdint.h>
+
+class FreeDesktopPortalDesktop {
+private:
+	bool unsupported = false;
+
+	// Read a setting from org.freekdesktop.portal.Settings
+	bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value);
+
+public:
+	FreeDesktopPortalDesktop();
+
+	bool is_supported() { return !unsupported; }
+
+	// Retrieve the system's preferred color scheme.
+	// 0: No preference or unknown.
+	// 1: Prefer dark appearance.
+	// 2: Prefer light appearance.
+	uint32_t get_appearance_color_scheme();
+};
+
+#endif // DBUS_ENABLED
+
+#endif // FREEDESKTOP_PORTAL_DESKTOP_H