GTK3: Support freedesktop color-scheme

On desktops which support the org.freedesktop.appearance
settings portal, the application will switch automatically
between light and dark themes.
This commit is contained in:
robxnano 2024-08-20 16:07:44 +01:00
parent 80d1946efd
commit bcc06d8d33
No known key found for this signature in database
GPG Key ID: 9FB6B03B782D1E42
9 changed files with 368 additions and 4 deletions

View File

@ -0,0 +1,290 @@
/* color_scheme.cpp - Change application theme based on D-Bus
*
* Copyright (C) 2022-2024 Rob Hall
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "color_scheme.h"
#if GTK_CHECK_VERSION(3, 22, 0)
#define APP_DARK APP_PREFERS_DARK /* 1 */
#define APP_FORCE APP_FORCES_LIGHT /* 2 */
#define DBUS_TIMEOUT 1000 /* Timeout for D-Bus to respond in ms */
#if GLIB_CHECK_VERSION(2, 72, 0)
#define SETTING_CHANGED_SIGNAL "g-signal::SettingChanged"
#else
#define SETTING_CHANGED_SIGNAL "g-signal"
#endif
static AppColorScheme app_scheme = APP_FORCES_LIGHT;
static GDBusProxy *desktop_portal;
static GtkSettings *settings;
static gboolean init_done = FALSE;
static void
set_gtk_theme (gboolean dark)
{
g_object_set(settings, "gtk-application-prefer-dark-theme", dark, NULL);
}
/*
* Returns the currently selected app colour scheme.
*/
AppColorScheme
color_scheme_get_app_scheme (void)
{
return app_scheme;
}
/*
* Returns TRUE if the app is currently using the dark theme.
*/
gboolean
color_scheme_is_dark_theme (void)
{
gboolean is_dark;
g_object_get (settings, "gtk-application-prefer-dark-theme", &is_dark, NULL);
return is_dark;
}
/*
* Switches the color state of the app, no matter which color
* is currently in use. Afterwards, the app color scheme is
* changed to APP_FORCES_LIGHT or APP_FORCES_DARK, so changes
* to the desktop color scheme no longer have any effect.
*/
gboolean
color_scheme_toggle (void)
{
gboolean set_dark = !color_scheme_is_dark_theme();
g_object_set(settings, "gtk-application-prefer-dark-theme",
set_dark, NULL);
/* Change app_scheme to APP_FORCES_[DARK|LIGHT] */
app_scheme = (AppColorScheme) (set_dark | APP_FORCE);
return set_dark;
}
static void
setting_changed (GDBusProxy *proxy, char *sender_name, char *signal_name,
GVariant *parameters, gpointer user_data)
{
const char *namespace_;
const char *key;
g_autoptr (GVariant) var = NULL;
guint32 portal_value;
gboolean set_dark;
if (g_strcmp0(signal_name, "SettingChanged"))
return;
g_variant_get(parameters, "(&s&sv)", &namespace_, &key, &var);
if (!g_strcmp0(namespace_, "org.freedesktop.appearance") &&
!g_strcmp0(key, "color-scheme"))
{
g_variant_get(var, "u", &portal_value);
set_dark = (portal_value == DESKTOP_PREFERS_DARK) ? TRUE : FALSE;
if (!(app_scheme & APP_FORCE))
set_gtk_theme(set_dark);
}
}
static GDBusProxy *
portal_init (void)
{
GDBusProxy *portal;
GError *error = NULL;
portal = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE, NULL,
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
NULL, &error);
if (!portal)
g_debug ("Could not access portal: %s", error->message);
return portal;
}
static void
set_theme (void)
{
DesktopColorScheme desktop_scheme;
gboolean set_dark;
desktop_scheme = color_scheme_get_desktop_scheme();
if ((app_scheme & APP_FORCE) || desktop_scheme == DESKTOP_NO_PREFERENCE)
set_dark = (app_scheme & APP_DARK);
else
set_dark = (desktop_scheme == DESKTOP_PREFERS_DARK);
set_gtk_theme(set_dark);
}
static void
init_color_scheme_cb (GObject *object, GAsyncResult *res, gpointer user_data)
{
GDBusProxy *portal;
GError *error = NULL;
portal = g_dbus_proxy_new_for_bus_finish(res, &error);
if (portal)
{
g_signal_connect(portal, SETTING_CHANGED_SIGNAL,
G_CALLBACK(setting_changed), NULL);
}
else
{
g_debug("Could not access portal: %s", error->message);
}
init_done = TRUE;
}
static void
init_color_scheme_async (void)
{
static size_t init = 0;
if (g_once_init_enter(&init))
{
settings = gtk_settings_get_default();
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE, NULL,
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
NULL,
(GAsyncReadyCallback)init_color_scheme_cb,
NULL);
g_once_init_leave(&init, 1);
}
set_theme();
}
static gboolean
init_color_scheme (void)
{
static GMutex init_mutex;
g_mutex_lock(&init_mutex);
{
if (!init_done)
{
settings = gtk_settings_get_default();
desktop_portal = portal_init();
if (desktop_portal)
{
g_signal_connect(desktop_portal, SETTING_CHANGED_SIGNAL,
G_CALLBACK(setting_changed), NULL);
}
}
}
init_done = TRUE;
g_mutex_unlock(&init_mutex);
return TRUE;
}
/*
* Returns the current desktop color scheme, given by
* the org.freedesktop.portal.Settings portal. If the portal
* is not available, DESKTOP_NO_PREFERENCE is returned.
*/
DesktopColorScheme
color_scheme_get_desktop_scheme (void)
{
g_autoptr (GVariant) outer = NULL;
g_autoptr (GVariant) inner = NULL;
g_autoptr (GVariant) result = NULL;
g_autoptr (GError) error = NULL;
DesktopColorScheme scheme;
if (!init_done && !init_color_scheme())
return DESKTOP_NO_PREFERENCE;
if (!desktop_portal)
return DESKTOP_NO_PREFERENCE;
GVariant *call = g_variant_new("(ss)", "org.freedesktop.appearance",
"color-scheme");
result = g_dbus_proxy_call_sync(desktop_portal, "Read", call,
G_DBUS_CALL_FLAGS_NONE, DBUS_TIMEOUT,
NULL, &error);
if (!result)
{
g_debug("%s", error->message);
return DESKTOP_NO_PREFERENCE;
}
g_variant_get(result, "(v)", &outer);
g_variant_get(outer, "v", &inner);
scheme = (DesktopColorScheme) g_variant_get_uint32(inner);
if (scheme > DESKTOP_PREFERS_LIGHT)
scheme = DESKTOP_NO_PREFERENCE;
return scheme;
}
/*
* Sets the color scheme to one of the four options.
* Can be called once GTK has been initialised.
* Returns FALSE if the color scheme could not be set.
* If the return value is not needed, use color_scheme_set_async instead.
*/
gboolean
color_scheme_set (AppColorScheme scheme)
{
gboolean set_dark;
DesktopColorScheme desktop_scheme;
if (!init_done && !init_color_scheme())
return FALSE;
app_scheme = scheme;
desktop_scheme = color_scheme_get_desktop_scheme();
if ((app_scheme & APP_FORCE) || desktop_scheme == DESKTOP_NO_PREFERENCE)
set_dark = (app_scheme & APP_DARK);
else
set_dark = (desktop_scheme == DESKTOP_PREFERS_DARK);
set_gtk_theme(set_dark);
return TRUE;
}
/*
* Sets the color scheme to one of the four options.
* Can be called once GTK has been initialised.
* Runs asynchronously, so preferred over color_scheme_set.
*/
void
color_scheme_set_async (AppColorScheme scheme)
{
app_scheme = scheme;
if (init_done)
set_theme();
else
init_color_scheme_async();
}
#endif // GTK_CHECK_VERSION

View File

@ -503,7 +503,7 @@ fi
if [[ "x$WANT_GTK" != "xno" -a "x$WANT_STANDALONE_GUI" = "xno" ]]; then if [[ "x$WANT_GTK" != "xno" -a "x$WANT_STANDALONE_GUI" = "xno" ]]; then
AC_DEFINE(ENABLE_GTK, 1, [Define if using GTK.]) AC_DEFINE(ENABLE_GTK, 1, [Define if using GTK.])
if [[ "$WANT_GTK" = "GTK3" ]]; then if [[ "$WANT_GTK" = "GTK3" ]]; then
UISRCS="prefs_editor_gtk3.cpp g_resource.cpp" UISRCS="prefs_editor_gtk3.cpp g_resource.cpp color_scheme.cpp"
else else
UISRCS="prefs_editor_gtk.cpp" UISRCS="prefs_editor_gtk.cpp"
fi fi

View File

@ -73,6 +73,10 @@ struct sigstate {
# if GTK_CHECK_VERSION(3, 14, 0) # if GTK_CHECK_VERSION(3, 14, 0)
# define ENABLE_GTK3 # define ENABLE_GTK3
# endif # endif
# if GTK_CHECK_VERSION(3, 22, 0)
# define ENABLE_GTK_3_22
# include "color_scheme.h"
# endif
#endif #endif
#ifdef ENABLE_XF86_DGA #ifdef ENABLE_XF86_DGA
@ -423,6 +427,9 @@ GtkWindow *win;
static void gui_startup (void) static void gui_startup (void)
{ {
#ifdef ENABLE_GTK_3_22
color_scheme_set(APP_PREFERS_LIGHT);
#endif
if (!PrefsEditor()) if (!PrefsEditor())
QuitEmulator(); QuitEmulator();
} }

View File

@ -0,0 +1,58 @@
/* color_scheme.h - Change application theme based on D-Bus
*
* Copyright (C) 2022-2024 Rob Hall
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
/*
* An enum representing the color state requested by
* the application. Before the color-scheme library is
* set up, the application is effectively in the
* APP_FORCES_LIGHT state.
*/
typedef enum {
APP_PREFERS_LIGHT,
APP_PREFERS_DARK,
APP_FORCES_LIGHT,
APP_FORCES_DARK
} AppColorScheme;
/*
* An enum representing the color state given by the
* desktop portal. If the portal is not available, it
* defaults to DESKTOP_NO_PREFERENCE.
*/
typedef enum {
DESKTOP_NO_PREFERENCE,
DESKTOP_PREFERS_DARK,
DESKTOP_PREFERS_LIGHT
} DesktopColorScheme;
gboolean color_scheme_set (AppColorScheme scheme);
void color_scheme_set_async (AppColorScheme scheme);
gboolean color_scheme_is_dark_theme (void);
gboolean color_scheme_toggle (void);
AppColorScheme color_scheme_get_app_scheme (void);
DesktopColorScheme color_scheme_get_desktop_scheme (void);
G_END_DECLS

View File

@ -58,7 +58,7 @@ links:
include/extfs.h include/extfs_defs.h include/pict.h \ include/extfs.h include/extfs_defs.h include/pict.h \
include/prefs.h include/scsi.h include/serial.h include/prefs_editor.h \ include/prefs.h include/scsi.h include/serial.h include/prefs_editor.h \
include/serial_defs.h include/sony.h include/sys.h \ include/serial_defs.h include/sony.h include/sys.h \
include/timer.h include/xpram.h \ include/timer.h include/xpram.h include/color_scheme.h \
BeOS/audio_beos.cpp BeOS/extfs_beos.cpp BeOS/scsi_beos.cpp \ BeOS/audio_beos.cpp BeOS/extfs_beos.cpp BeOS/scsi_beos.cpp \
BeOS/serial_beos.cpp BeOS/sys_beos.cpp BeOS/timer_beos.cpp \ BeOS/serial_beos.cpp BeOS/sys_beos.cpp BeOS/timer_beos.cpp \
BeOS/xpram_beos.cpp BeOS/SheepDriver BeOS/SheepNet \ BeOS/xpram_beos.cpp BeOS/SheepDriver BeOS/SheepNet \
@ -66,7 +66,7 @@ links:
CrossPlatform/video_vosf.h CrossPlatform/video_blit.h CrossPlatform/video_blit.cpp \ CrossPlatform/video_vosf.h CrossPlatform/video_blit.h CrossPlatform/video_blit.cpp \
Unix/audio_oss_esd.cpp \ Unix/audio_oss_esd.cpp \
Unix/vhd_unix.cpp \ Unix/vhd_unix.cpp \
Unix/extfs_unix.cpp Unix/serial_unix.cpp \ Unix/extfs_unix.cpp Unix/serial_unix.cpp Unix/color_scheme.cpp \
Unix/sshpty.h Unix/sshpty.c Unix/strlcpy.h Unix/strlcpy.c \ Unix/sshpty.h Unix/sshpty.c Unix/strlcpy.h Unix/strlcpy.c \
Unix/sys_unix.cpp Unix/timer_unix.cpp Unix/xpram_unix.cpp Unix/prefs_unix.cpp \ Unix/sys_unix.cpp Unix/timer_unix.cpp Unix/xpram_unix.cpp Unix/prefs_unix.cpp \
Unix/semaphore.h Unix/posix_sem.cpp Unix/config.sub Unix/config.guess Unix/m4 \ Unix/semaphore.h Unix/posix_sem.cpp Unix/config.sub Unix/config.guess Unix/m4 \

View File

@ -0,0 +1 @@
../../../BasiliskII/src/Unix/color_scheme.cpp

View File

@ -380,7 +380,7 @@ fi
if [[ "x$WANT_GTK" != "xno" -a "x$WANT_STANDALONE_GUI" = "xno" ]]; then if [[ "x$WANT_GTK" != "xno" -a "x$WANT_STANDALONE_GUI" = "xno" ]]; then
AC_DEFINE(ENABLE_GTK, 1, [Define if using GTK.]) AC_DEFINE(ENABLE_GTK, 1, [Define if using GTK.])
if [[ "$WANT_GTK" = "GTK3" ]]; then if [[ "$WANT_GTK" = "GTK3" ]]; then
UISRCS="prefs_editor_gtk3.cpp g_resource.cpp" UISRCS="prefs_editor_gtk3.cpp g_resource.cpp color_scheme.cpp"
else else
UISRCS="prefs_editor_gtk.cpp" UISRCS="prefs_editor_gtk.cpp"
fi fi

View File

@ -147,6 +147,10 @@
#if GTK_CHECK_VERSION(3, 14, 0) #if GTK_CHECK_VERSION(3, 14, 0)
#define ENABLE_GTK3 #define ENABLE_GTK3
#endif #endif
#if GTK_CHECK_VERSION(3, 22, 0)
#define ENABLE_GTK_3_22
#include "color_scheme.h"
#endif
#endif #endif
#ifdef ENABLE_XF86_DGA #ifdef ENABLE_XF86_DGA
@ -762,6 +766,9 @@ GtkWindow *win;
static void gui_startup (void) static void gui_startup (void)
{ {
#ifdef ENABLE_GTK_3_22
color_scheme_set(APP_PREFERS_LIGHT);
#endif
if (use_gui && !PrefsEditor()) if (use_gui && !PrefsEditor())
QuitEmulator(); QuitEmulator();
} }

View File

@ -0,0 +1 @@
../../../BasiliskII/src/include/color_scheme.h