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" />
 			<return type="bool" />
 			<description>
 			<description>
 				Returns [code]true[/code] if OS is using dark mode.
 				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>
 			</description>
 		</method>
 		</method>
 		<method name="is_dark_mode_supported" qualifiers="const">
 		<method name="is_dark_mode_supported" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<description>
 			<description>
 				Returns [code]true[/code] if OS supports dark mode.
 				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>
 			</description>
 		</method>
 		</method>
 		<method name="keyboard_get_current_layout" qualifiers="const">
 		<method name="keyboard_get_current_layout" qualifiers="const">

+ 1 - 0
platform/linuxbsd/SCsub

@@ -9,6 +9,7 @@ common_linuxbsd = [
     "crash_handler_linuxbsd.cpp",
     "crash_handler_linuxbsd.cpp",
     "os_linuxbsd.cpp",
     "os_linuxbsd.cpp",
     "joypad_linux.cpp",
     "joypad_linux.cpp",
+    "freedesktop_portal_desktop.cpp",
     "freedesktop_screensaver.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_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False),
         BoolVariable("use_msan", "Use LLVM compiler memory sanitizer (MSAN)", False),
         BoolVariable("use_msan", "Use LLVM compiler memory sanitizer (MSAN)", False),
         BoolVariable("pulseaudio", "Detect and use PulseAudio", True),
         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("speechd", "Detect and use Speech Dispatcher for Text-to-Speech support", True),
         BoolVariable("fontconfig", "Detect and use fontconfig for system fonts support", True),
         BoolVariable("fontconfig", "Detect and use fontconfig for system fonts support", True),
         BoolVariable("udev", "Use udev for gamepad connection callbacks", 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
 #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) {
 void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {
 	_THREAD_SAFE_METHOD_
 	_THREAD_SAFE_METHOD_
 
 
@@ -5009,6 +5031,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
 #ifdef DBUS_ENABLED
 #ifdef DBUS_ENABLED
 	screensaver = memnew(FreeDesktopScreenSaver);
 	screensaver = memnew(FreeDesktopScreenSaver);
 	screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
 	screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
+
+	portal_desktop = memnew(FreeDesktopPortalDesktop);
 #endif
 #endif
 
 
 	r_error = OK;
 	r_error = OK;
@@ -5094,6 +5118,7 @@ DisplayServerX11::~DisplayServerX11() {
 
 
 #ifdef DBUS_ENABLED
 #ifdef DBUS_ENABLED
 	memdelete(screensaver);
 	memdelete(screensaver);
+	memdelete(portal_desktop);
 #endif
 #endif
 }
 }
 
 

+ 10 - 0
platform/linuxbsd/display_server_x11.h

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