| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "flutter/shell/platform/linux/fl_settings_portal.h" |
| |
| #include <gio/gio.h> |
| #include <glib.h> |
| |
| static constexpr char kPortalName[] = "org.freedesktop.portal.Desktop"; |
| static constexpr char kPortalPath[] = "/org/freedesktop/portal/desktop"; |
| static constexpr char kPortalSettings[] = "org.freedesktop.portal.Settings"; |
| |
| struct FlSetting { |
| const gchar* ns; |
| const gchar* key; |
| const GVariantType* type; |
| }; |
| |
| static constexpr char kXdgAppearance[] = "org.freedesktop.appearance"; |
| static const FlSetting kColorScheme = { |
| kXdgAppearance, |
| "color-scheme", |
| G_VARIANT_TYPE_UINT32, |
| }; |
| |
| static constexpr char kGnomeA11yInterface[] = |
| "org.gnome.desktop.a11y.interface"; |
| static const FlSetting kHighContrast = { |
| kGnomeA11yInterface, |
| "high-contrast", |
| G_VARIANT_TYPE_BOOLEAN, |
| }; |
| |
| static constexpr char kGnomeDesktopInterface[] = "org.gnome.desktop.interface"; |
| static const FlSetting kClockFormat = { |
| kGnomeDesktopInterface, |
| "clock-format", |
| G_VARIANT_TYPE_STRING, |
| }; |
| static const FlSetting kEnableAnimations = { |
| kGnomeDesktopInterface, |
| "enable-animations", |
| G_VARIANT_TYPE_BOOLEAN, |
| }; |
| static const FlSetting kGtkTheme = { |
| kGnomeDesktopInterface, |
| "gtk-theme", |
| G_VARIANT_TYPE_STRING, |
| }; |
| static const FlSetting kTextScalingFactor = { |
| kGnomeDesktopInterface, |
| "text-scaling-factor", |
| G_VARIANT_TYPE_DOUBLE, |
| }; |
| |
| static const FlSetting kAllSettings[] = { |
| kClockFormat, kColorScheme, kEnableAnimations, |
| kGtkTheme, kHighContrast, kTextScalingFactor, |
| }; |
| |
| static constexpr char kClockFormat12Hour[] = "12h"; |
| static constexpr char kGtkThemeDarkSuffix[] = "-dark"; |
| |
| typedef enum { kDefault, kPreferDark, kPreferLight } ColorScheme; |
| |
| struct _FlSettingsPortal { |
| GObject parent_instance; |
| |
| GDBusProxy* dbus_proxy; |
| GVariantDict* values; |
| }; |
| |
| static void fl_settings_portal_iface_init(FlSettingsInterface* iface); |
| |
| G_DEFINE_TYPE_WITH_CODE(FlSettingsPortal, |
| fl_settings_portal, |
| G_TYPE_OBJECT, |
| G_IMPLEMENT_INTERFACE(fl_settings_get_type(), |
| fl_settings_portal_iface_init)) |
| |
| static gchar* format_key(const FlSetting* setting) { |
| return g_strconcat(setting->ns, "::", setting->key, nullptr); |
| } |
| |
| static gboolean get_value(FlSettingsPortal* portal, |
| const FlSetting* setting, |
| GVariant** value) { |
| g_autofree const gchar* key = format_key(setting); |
| *value = g_variant_dict_lookup_value(portal->values, key, setting->type); |
| return *value != nullptr; |
| } |
| |
| static void set_value(FlSettingsPortal* portal, |
| const FlSetting* setting, |
| GVariant* value) { |
| g_autofree const gchar* key = format_key(setting); |
| |
| // ignore redundant changes from multiple XDG desktop portal backends |
| g_autoptr(GVariant) old_value = |
| g_variant_dict_lookup_value(portal->values, key, nullptr); |
| if (old_value != nullptr && value != nullptr && |
| g_variant_equal(old_value, value)) { |
| return; |
| } |
| |
| g_variant_dict_insert_value(portal->values, key, value); |
| fl_settings_emit_changed(FL_SETTINGS(portal)); |
| } |
| |
| // Based on |
| // https://gitlab.gnome.org/GNOME/Initiatives/-/wikis/Dark-Style-Preference#other |
| static gboolean settings_portal_read(GDBusProxy* proxy, |
| const gchar* ns, |
| const gchar* key, |
| GVariant** out) { |
| g_autoptr(GError) error = nullptr; |
| g_autoptr(GVariant) value = |
| g_dbus_proxy_call_sync(proxy, "Read", g_variant_new("(ss)", ns, key), |
| G_DBUS_CALL_FLAGS_NONE, G_MAXINT, nullptr, &error); |
| |
| if (error) { |
| if (error->domain == G_DBUS_ERROR && |
| error->code == G_DBUS_ERROR_SERVICE_UNKNOWN) { |
| g_debug("XDG desktop portal unavailable: %s", error->message); |
| return false; |
| } |
| |
| if (error->domain == G_DBUS_ERROR && |
| error->code == G_DBUS_ERROR_UNKNOWN_METHOD) { |
| g_debug("XDG desktop portal settings unavailable: %s", error->message); |
| return false; |
| } |
| |
| g_critical("Failed to read XDG desktop portal settings: %s", |
| error->message); |
| return false; |
| } |
| |
| g_autoptr(GVariant) child = nullptr; |
| g_variant_get(value, "(v)", &child); |
| g_variant_get(child, "v", out); |
| |
| return true; |
| } |
| |
| static void settings_portal_changed_cb(GDBusProxy* proxy, |
| const char* sender_name, |
| const char* signal_name, |
| GVariant* parameters, |
| gpointer user_data) { |
| FlSettingsPortal* portal = FL_SETTINGS_PORTAL(user_data); |
| if (g_strcmp0(signal_name, "SettingChanged")) { |
| return; |
| } |
| |
| FlSetting setting; |
| g_autoptr(GVariant) value = nullptr; |
| g_variant_get(parameters, "(&s&sv)", &setting.ns, &setting.key, &value); |
| set_value(portal, &setting, value); |
| } |
| |
| static FlClockFormat fl_settings_portal_get_clock_format(FlSettings* settings) { |
| FlSettingsPortal* self = FL_SETTINGS_PORTAL(settings); |
| |
| FlClockFormat clock_format = FL_CLOCK_FORMAT_24H; |
| |
| g_autoptr(GVariant) value = nullptr; |
| if (get_value(self, &kClockFormat, &value)) { |
| const gchar* clock_format_str = g_variant_get_string(value, nullptr); |
| if (g_strcmp0(clock_format_str, kClockFormat12Hour) == 0) { |
| clock_format = FL_CLOCK_FORMAT_12H; |
| } |
| } |
| |
| return clock_format; |
| } |
| |
| static FlColorScheme fl_settings_portal_get_color_scheme(FlSettings* settings) { |
| FlSettingsPortal* self = FL_SETTINGS_PORTAL(settings); |
| |
| FlColorScheme color_scheme = FL_COLOR_SCHEME_LIGHT; |
| |
| g_autoptr(GVariant) value = nullptr; |
| if (get_value(self, &kColorScheme, &value)) { |
| if (g_variant_get_uint32(value) == kPreferDark) { |
| color_scheme = FL_COLOR_SCHEME_DARK; |
| } |
| } else if (get_value(self, &kGtkTheme, &value)) { |
| const gchar* gtk_theme_str = g_variant_get_string(value, nullptr); |
| if (g_str_has_suffix(gtk_theme_str, kGtkThemeDarkSuffix)) { |
| color_scheme = FL_COLOR_SCHEME_DARK; |
| } |
| } |
| |
| return color_scheme; |
| } |
| |
| static gboolean fl_settings_portal_get_enable_animations(FlSettings* settings) { |
| FlSettingsPortal* self = FL_SETTINGS_PORTAL(settings); |
| |
| gboolean enable_animations = true; |
| |
| g_autoptr(GVariant) value = nullptr; |
| if (get_value(self, &kEnableAnimations, &value)) { |
| enable_animations = g_variant_get_boolean(value); |
| } |
| |
| return enable_animations; |
| } |
| |
| static gboolean fl_settings_portal_get_high_contrast(FlSettings* settings) { |
| FlSettingsPortal* self = FL_SETTINGS_PORTAL(settings); |
| |
| gboolean high_contrast = false; |
| |
| g_autoptr(GVariant) value = nullptr; |
| if (get_value(self, &kHighContrast, &value)) { |
| high_contrast = g_variant_get_boolean(value); |
| } |
| |
| return high_contrast; |
| } |
| |
| static gdouble fl_settings_portal_get_text_scaling_factor( |
| FlSettings* settings) { |
| FlSettingsPortal* self = FL_SETTINGS_PORTAL(settings); |
| |
| gdouble scaling_factor = 1.0; |
| |
| g_autoptr(GVariant) value = nullptr; |
| if (get_value(self, &kTextScalingFactor, &value)) { |
| scaling_factor = g_variant_get_double(value); |
| } |
| |
| return scaling_factor; |
| } |
| |
| static void fl_settings_portal_dispose(GObject* object) { |
| FlSettingsPortal* self = FL_SETTINGS_PORTAL(object); |
| |
| g_clear_object(&self->dbus_proxy); |
| g_clear_pointer(&self->values, g_variant_dict_unref); |
| |
| G_OBJECT_CLASS(fl_settings_portal_parent_class)->dispose(object); |
| } |
| |
| static void fl_settings_portal_class_init(FlSettingsPortalClass* klass) { |
| GObjectClass* object_class = G_OBJECT_CLASS(klass); |
| object_class->dispose = fl_settings_portal_dispose; |
| } |
| |
| static void fl_settings_portal_iface_init(FlSettingsInterface* iface) { |
| iface->get_clock_format = fl_settings_portal_get_clock_format; |
| iface->get_color_scheme = fl_settings_portal_get_color_scheme; |
| iface->get_enable_animations = fl_settings_portal_get_enable_animations; |
| iface->get_high_contrast = fl_settings_portal_get_high_contrast; |
| iface->get_text_scaling_factor = fl_settings_portal_get_text_scaling_factor; |
| } |
| |
| static void fl_settings_portal_init(FlSettingsPortal* self) {} |
| |
| FlSettingsPortal* fl_settings_portal_new() { |
| g_autoptr(GVariantDict) values = g_variant_dict_new(nullptr); |
| return fl_settings_portal_new_with_values(values); |
| } |
| |
| FlSettingsPortal* fl_settings_portal_new_with_values(GVariantDict* values) { |
| g_return_val_if_fail(values != nullptr, nullptr); |
| FlSettingsPortal* portal = |
| FL_SETTINGS_PORTAL(g_object_new(fl_settings_portal_get_type(), nullptr)); |
| portal->values = g_variant_dict_ref(values); |
| return portal; |
| } |
| |
| gboolean fl_settings_portal_start(FlSettingsPortal* self, GError** error) { |
| g_return_val_if_fail(FL_IS_SETTINGS_PORTAL(self), false); |
| g_return_val_if_fail(self->dbus_proxy == nullptr, false); |
| |
| self->dbus_proxy = g_dbus_proxy_new_for_bus_sync( |
| G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr, kPortalName, |
| kPortalPath, kPortalSettings, nullptr, error); |
| |
| if (self->dbus_proxy == nullptr) { |
| return false; |
| } |
| |
| for (const FlSetting setting : kAllSettings) { |
| g_autoptr(GVariant) value = nullptr; |
| if (settings_portal_read(self->dbus_proxy, setting.ns, setting.key, |
| &value)) { |
| set_value(self, &setting, value); |
| } |
| } |
| |
| g_signal_connect_object(self->dbus_proxy, "g-signal", |
| G_CALLBACK(settings_portal_changed_cb), self, |
| static_cast<GConnectFlags>(0)); |
| |
| return true; |
| } |