Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit a4d8546

Browse files
committed
[Linux] read settings from XDG desktop portal if available
Fixes: flutter/flutter#101438
1 parent e32059b commit a4d8546

File tree

5 files changed

+373
-2
lines changed

5 files changed

+373
-2
lines changed

shell/platform/linux/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ source_set("flutter_linux_sources") {
131131
"fl_renderer_headless.cc",
132132
"fl_settings.cc",
133133
"fl_settings_plugin.cc",
134+
"fl_settings_portal.cc",
134135
"fl_standard_message_codec.cc",
135136
"fl_standard_method_codec.cc",
136137
"fl_string_codec.cc",
@@ -208,6 +209,7 @@ executable("flutter_linux_unittests") {
208209
"fl_pixel_buffer_texture_test.cc",
209210
"fl_plugin_registrar_test.cc",
210211
"fl_settings_plugin_test.cc",
212+
"fl_settings_portal_test.cc",
211213
"fl_standard_message_codec_test.cc",
212214
"fl_standard_method_codec_test.cc",
213215
"fl_string_codec_test.cc",

shell/platform/linux/fl_settings.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "flutter/shell/platform/linux/fl_settings.h"
66
#include "flutter/shell/platform/linux/fl_gnome_settings.h"
7+
#include "flutter/shell/platform/linux/fl_settings_portal.h"
78

89
G_DEFINE_INTERFACE(FlSettings, fl_settings, G_TYPE_OBJECT)
910

@@ -44,6 +45,9 @@ void fl_settings_emit_changed(FlSettings* self) {
4445
}
4546

4647
FlSettings* fl_settings_new() {
47-
// TODO(jpnurmi): add support for other desktop environments
48-
return FL_SETTINGS(fl_gnome_settings_new());
48+
g_autoptr(FlSettingsPortal) portal = fl_settings_portal_new(nullptr);
49+
if (!fl_settings_portal_start(portal)) {
50+
return fl_gnome_settings_new();
51+
}
52+
return FL_SETTINGS(g_object_ref(portal));
4953
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
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+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PORTAL_H_
6+
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PORTAL_H_
7+
8+
#include "flutter/shell/platform/linux/fl_settings.h"
9+
10+
G_BEGIN_DECLS
11+
12+
G_DECLARE_FINAL_TYPE(FlSettingsPortal,
13+
fl_settings_portal,
14+
FL,
15+
SETTINGS_PORTAL,
16+
GObject);
17+
18+
/**
19+
* FlSettingsPortal:
20+
* #FlSettingsPortal reads settings from the XDG desktop portal.
21+
*/
22+
23+
/**
24+
* fl_settings_portal_new:
25+
* @values: (nullable): a #GVariantDict with values for testing.
26+
*
27+
* Creates a new settings portal instance.
28+
*
29+
* Returns: a new #FlSettingsPortal.
30+
*/
31+
FlSettingsPortal* fl_settings_portal_new(GVariantDict* values);
32+
33+
/**
34+
* fl_settings_portal_start:
35+
* @portal: an #FlSettingsPortal.
36+
*
37+
* Reads the current settings and starts monitoring for changes in the desktop
38+
* portal settings.
39+
*
40+
* Returns: %TRUE on success, or %FALSE if the portal is not available.
41+
*/
42+
gboolean fl_settings_portal_start(FlSettingsPortal* portal);
43+
44+
G_END_DECLS
45+
46+
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PORTAL_H_

0 commit comments

Comments
 (0)