|
| 1 | +// Copyright 2013 The Flutter Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style license that can be |
| 3 | +// found in the LICENSE file. |
| 4 | + |
| 5 | +#include "flutter/shell/platform/linux/fl_settings_portal.h" |
| 6 | + |
| 7 | +#include <gio/gio.h> |
| 8 | +#include <glib.h> |
| 9 | + |
| 10 | +static constexpr char kPortalName[] = "org.freedesktop.portal.Desktop"; |
| 11 | +static constexpr char kPortalPath[] = "/org/freedesktop/portal/desktop"; |
| 12 | +static constexpr char pPortalSettings[] = "org.freedesktop.portal.Settings"; |
| 13 | + |
| 14 | +struct FlSetting { |
| 15 | + const gchar* ns; |
| 16 | + const gchar* key; |
| 17 | + const GVariantType* type; |
| 18 | +}; |
| 19 | + |
| 20 | +static constexpr char kXdgAppearance[] = "org.freedesktop.appearance"; |
| 21 | +static const FlSetting kColorScheme = { |
| 22 | + kXdgAppearance, |
| 23 | + "color-scheme", |
| 24 | + G_VARIANT_TYPE_UINT32, |
| 25 | +}; |
| 26 | + |
| 27 | +static constexpr char kGnomeDesktopInterface[] = "org.gnome.desktop.interface"; |
| 28 | +static const FlSetting kClockFormat = { |
| 29 | + kGnomeDesktopInterface, |
| 30 | + "clock-format", |
| 31 | + G_VARIANT_TYPE_STRING, |
| 32 | +}; |
| 33 | +static const FlSetting kTextScalingFactor = { |
| 34 | + kGnomeDesktopInterface, |
| 35 | + "text-scaling-factor", |
| 36 | + G_VARIANT_TYPE_DOUBLE, |
| 37 | +}; |
| 38 | + |
| 39 | +static const FlSetting all_settings[] = { |
| 40 | + kClockFormat, |
| 41 | + kColorScheme, |
| 42 | + kTextScalingFactor, |
| 43 | +}; |
| 44 | + |
| 45 | +static constexpr char kClockFormat12Hour[] = "12h"; |
| 46 | + |
| 47 | +typedef enum { DEFAULT, PREFER_DARK, PREFER_LIGHT } ColorScheme; |
| 48 | + |
| 49 | +struct _FlSettingsPortal { |
| 50 | + GObject parent_instance; |
| 51 | + |
| 52 | + GDBusProxy* dbus_proxy; |
| 53 | + GVariantDict* values; |
| 54 | +}; |
| 55 | + |
| 56 | +static void fl_settings_portal_iface_init(FlSettingsInterface* iface); |
| 57 | + |
| 58 | +G_DEFINE_TYPE_WITH_CODE(FlSettingsPortal, |
| 59 | + fl_settings_portal, |
| 60 | + G_TYPE_OBJECT, |
| 61 | + G_IMPLEMENT_INTERFACE(fl_settings_get_type(), |
| 62 | + fl_settings_portal_iface_init)) |
| 63 | + |
| 64 | +#define FL_SETTINGS_PORTAL_GET_PRIVATE(portal) \ |
| 65 | + ((FlSettingsPortalPrivate*)fl_settings_portal_get_instance_private(portal)) |
| 66 | + |
| 67 | +static const gchar* fl_settings_portal_format_key(const FlSetting* setting) { |
| 68 | + return g_strconcat(setting->ns, "::", setting->key, nullptr); |
| 69 | +} |
| 70 | + |
| 71 | +static gboolean fl_settings_portal_get_value(FlSettingsPortal* self, |
| 72 | + const FlSetting* setting, |
| 73 | + GVariant** value) { |
| 74 | + g_autofree const gchar* key = fl_settings_portal_format_key(setting); |
| 75 | + *value = g_variant_dict_lookup_value(self->values, key, setting->type); |
| 76 | + return *value != nullptr; |
| 77 | +} |
| 78 | + |
| 79 | +static void fl_settings_portal_set_value(FlSettingsPortal* self, |
| 80 | + const FlSetting* setting, |
| 81 | + GVariant* value) { |
| 82 | + g_autofree const gchar* key = fl_settings_portal_format_key(setting); |
| 83 | + |
| 84 | + // ignore redundant changes from multiple XDG desktop portal backends |
| 85 | + g_autoptr(GVariant) old_value = |
| 86 | + g_variant_dict_lookup_value(self->values, key, nullptr); |
| 87 | + if (old_value != nullptr && value != nullptr && |
| 88 | + g_variant_equal(old_value, value)) { |
| 89 | + return; |
| 90 | + } |
| 91 | + |
| 92 | + g_variant_dict_insert_value(self->values, key, value); |
| 93 | + fl_settings_emit_changed(FL_SETTINGS(self)); |
| 94 | +} |
| 95 | + |
| 96 | +static FlClockFormat fl_settings_portal_get_clock_format(FlSettings* settings) { |
| 97 | + FlSettingsPortal* self = FL_SETTINGS_PORTAL(settings); |
| 98 | + |
| 99 | + FlClockFormat clock_format = FL_CLOCK_FORMAT_24H; |
| 100 | + |
| 101 | + g_autoptr(GVariant) value = nullptr; |
| 102 | + if (fl_settings_portal_get_value(self, &kClockFormat, &value)) { |
| 103 | + const gchar* clock_format_str = g_variant_get_string(value, nullptr); |
| 104 | + if (g_strcmp0(clock_format_str, kClockFormat12Hour) == 0) { |
| 105 | + clock_format = FL_CLOCK_FORMAT_12H; |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + return clock_format; |
| 110 | +} |
| 111 | + |
| 112 | +static FlColorScheme fl_settings_portal_get_color_scheme(FlSettings* settings) { |
| 113 | + FlSettingsPortal* self = FL_SETTINGS_PORTAL(settings); |
| 114 | + |
| 115 | + FlColorScheme color_scheme = FL_COLOR_SCHEME_LIGHT; |
| 116 | + |
| 117 | + g_autoptr(GVariant) value = nullptr; |
| 118 | + if (fl_settings_portal_get_value(self, &kColorScheme, &value)) { |
| 119 | + if (g_variant_get_uint32(value) == PREFER_DARK) { |
| 120 | + color_scheme = FL_COLOR_SCHEME_DARK; |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + return color_scheme; |
| 125 | +} |
| 126 | + |
| 127 | +static gdouble fl_settings_portal_get_text_scaling_factor( |
| 128 | + FlSettings* settings) { |
| 129 | + FlSettingsPortal* self = FL_SETTINGS_PORTAL(settings); |
| 130 | + |
| 131 | + gdouble scaling_factor = 1.0; |
| 132 | + |
| 133 | + g_autoptr(GVariant) value = nullptr; |
| 134 | + if (fl_settings_portal_get_value(self, &kTextScalingFactor, &value)) { |
| 135 | + scaling_factor = g_variant_get_double(value); |
| 136 | + } |
| 137 | + |
| 138 | + return scaling_factor; |
| 139 | +} |
| 140 | + |
| 141 | +static void fl_settings_portal_dispose(GObject* object) { |
| 142 | + FlSettingsPortal* self = FL_SETTINGS_PORTAL(object); |
| 143 | + |
| 144 | + g_clear_object(&self->dbus_proxy); |
| 145 | + g_clear_pointer(&self->values, g_variant_dict_unref); |
| 146 | + |
| 147 | + G_OBJECT_CLASS(fl_settings_portal_parent_class)->dispose(object); |
| 148 | +} |
| 149 | + |
| 150 | +static void fl_settings_portal_class_init(FlSettingsPortalClass* klass) { |
| 151 | + GObjectClass* object_class = G_OBJECT_CLASS(klass); |
| 152 | + object_class->dispose = fl_settings_portal_dispose; |
| 153 | +} |
| 154 | + |
| 155 | +static void fl_settings_portal_iface_init(FlSettingsInterface* iface) { |
| 156 | + iface->get_clock_format = fl_settings_portal_get_clock_format; |
| 157 | + iface->get_color_scheme = fl_settings_portal_get_color_scheme; |
| 158 | + iface->get_text_scaling_factor = fl_settings_portal_get_text_scaling_factor; |
| 159 | +} |
| 160 | + |
| 161 | +static void fl_settings_portal_init(FlSettingsPortal* self) {} |
| 162 | + |
| 163 | +FlSettingsPortal* fl_settings_portal_new(GVariantDict* values) { |
| 164 | + FlSettingsPortal* portal = |
| 165 | + FL_SETTINGS_PORTAL(g_object_new(fl_settings_portal_get_type(), nullptr)); |
| 166 | + portal->values = |
| 167 | + values ? g_variant_dict_ref(values) : g_variant_dict_new(nullptr); |
| 168 | + return portal; |
| 169 | +} |
| 170 | + |
| 171 | +// Based on |
| 172 | +// https://gitlab.gnome.org/GNOME/Initiatives/-/wikis/Dark-Style-Preference#other |
| 173 | +static gboolean settings_portal_read(GDBusProxy* proxy, |
| 174 | + const gchar* ns, |
| 175 | + const gchar* key, |
| 176 | + GVariant** out) { |
| 177 | + g_autoptr(GError) error = nullptr; |
| 178 | + g_autoptr(GVariant) value = |
| 179 | + g_dbus_proxy_call_sync(proxy, "Read", g_variant_new("(ss)", ns, key), |
| 180 | + G_DBUS_CALL_FLAGS_NONE, G_MAXINT, nullptr, &error); |
| 181 | + |
| 182 | + if (error) { |
| 183 | + if (error->domain == G_DBUS_ERROR && |
| 184 | + error->code == G_DBUS_ERROR_SERVICE_UNKNOWN) { |
| 185 | + g_debug("XDG desktop portal unavailable: %s", error->message); |
| 186 | + return false; |
| 187 | + } |
| 188 | + |
| 189 | + if (error->domain == G_DBUS_ERROR && |
| 190 | + error->code == G_DBUS_ERROR_UNKNOWN_METHOD) { |
| 191 | + g_debug("XDG desktop portal settings unavailable: %s", error->message); |
| 192 | + return false; |
| 193 | + } |
| 194 | + |
| 195 | + g_critical("Failed to read XDG desktop portal settings: %s", |
| 196 | + error->message); |
| 197 | + return false; |
| 198 | + } |
| 199 | + |
| 200 | + g_autoptr(GVariant) child = nullptr; |
| 201 | + g_variant_get(value, "(v)", &child); |
| 202 | + g_variant_get(child, "v", out); |
| 203 | + |
| 204 | + return true; |
| 205 | +} |
| 206 | + |
| 207 | +static void settings_portal_changed_cb(GDBusProxy* proxy, |
| 208 | + const char* sender_name, |
| 209 | + const char* signal_name, |
| 210 | + GVariant* parameters, |
| 211 | + gpointer user_data) { |
| 212 | + FlSettingsPortal* portal = FL_SETTINGS_PORTAL(user_data); |
| 213 | + if (g_strcmp0(signal_name, "SettingChanged")) { |
| 214 | + return; |
| 215 | + } |
| 216 | + |
| 217 | + FlSetting setting; |
| 218 | + g_autoptr(GVariant) value = nullptr; |
| 219 | + g_variant_get(parameters, "(&s&sv)", &setting.ns, &setting.key, &value); |
| 220 | + fl_settings_portal_set_value(portal, &setting, value); |
| 221 | +} |
| 222 | + |
| 223 | +gboolean fl_settings_portal_start(FlSettingsPortal* self) { |
| 224 | + g_return_val_if_fail(FL_IS_SETTINGS_PORTAL(self), false); |
| 225 | + g_return_val_if_fail(self->dbus_proxy == nullptr, false); |
| 226 | + |
| 227 | + g_autoptr(GError) error = nullptr; |
| 228 | + |
| 229 | + self->dbus_proxy = g_dbus_proxy_new_for_bus_sync( |
| 230 | + G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr, kPortalName, |
| 231 | + kPortalPath, pPortalSettings, nullptr, &error); |
| 232 | + |
| 233 | + if (error != nullptr) { |
| 234 | + g_debug("XDG desktop portal settings unavailable: %s", error->message); |
| 235 | + return false; |
| 236 | + } |
| 237 | + |
| 238 | + for (const FlSetting setting : all_settings) { |
| 239 | + g_autoptr(GVariant) value = nullptr; |
| 240 | + if (settings_portal_read(self->dbus_proxy, setting.ns, setting.key, |
| 241 | + &value)) { |
| 242 | + fl_settings_portal_set_value(self, &setting, value); |
| 243 | + } |
| 244 | + } |
| 245 | + |
| 246 | + g_signal_connect_object(self->dbus_proxy, "g-signal", |
| 247 | + G_CALLBACK(settings_portal_changed_cb), self, |
| 248 | + GConnectFlags(0)); |
| 249 | + |
| 250 | + return true; |
| 251 | +} |
0 commit comments