Skip to content

Commit

Permalink
[Linux] read settings from XDG desktop portal if available (flutter#3…
Browse files Browse the repository at this point in the history
…3100)

* [Linux] read settings from XDG desktop portal if available

Fixes: flutter/flutter#101438
  • Loading branch information
jpnurmi authored May 12, 2022
1 parent 6543874 commit 14871f1
Show file tree
Hide file tree
Showing 6 changed files with 444 additions and 2 deletions.
3 changes: 3 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2182,6 +2182,9 @@ FILE: ../../../flutter/shell/platform/linux/fl_settings.h
FILE: ../../../flutter/shell/platform/linux/fl_settings_plugin.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings_plugin.h
FILE: ../../../flutter/shell/platform/linux/fl_settings_plugin_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings_portal.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings_portal.h
FILE: ../../../flutter/shell/platform/linux/fl_settings_portal_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec.cc
FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec_private.h
FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec_test.cc
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/linux/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ source_set("flutter_linux_sources") {
"fl_renderer_headless.cc",
"fl_settings.cc",
"fl_settings_plugin.cc",
"fl_settings_portal.cc",
"fl_standard_message_codec.cc",
"fl_standard_method_codec.cc",
"fl_string_codec.cc",
Expand Down Expand Up @@ -208,6 +209,7 @@ executable("flutter_linux_unittests") {
"fl_pixel_buffer_texture_test.cc",
"fl_plugin_registrar_test.cc",
"fl_settings_plugin_test.cc",
"fl_settings_portal_test.cc",
"fl_standard_message_codec_test.cc",
"fl_standard_method_codec_test.cc",
"fl_string_codec_test.cc",
Expand Down
12 changes: 10 additions & 2 deletions shell/platform/linux/fl_settings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "flutter/shell/platform/linux/fl_settings.h"
#include "flutter/shell/platform/linux/fl_gnome_settings.h"
#include "flutter/shell/platform/linux/fl_settings_portal.h"

G_DEFINE_INTERFACE(FlSettings, fl_settings, G_TYPE_OBJECT)

Expand Down Expand Up @@ -44,6 +45,13 @@ void fl_settings_emit_changed(FlSettings* self) {
}

FlSettings* fl_settings_new() {
// TODO(jpnurmi): add support for other desktop environments
return FL_SETTINGS(fl_gnome_settings_new());
g_autoptr(FlSettingsPortal) portal = fl_settings_portal_new();

g_autoptr(GError) error = nullptr;
if (!fl_settings_portal_start(portal, &error)) {
g_debug("XDG desktop portal settings unavailable: %s", error->message);
return fl_gnome_settings_new();
}

return FL_SETTINGS(g_object_ref(portal));
}
262 changes: 262 additions & 0 deletions shell/platform/linux/fl_settings_portal.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
// 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 pPortalSettings[] = "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 kGnomeDesktopInterface[] = "org.gnome.desktop.interface";
static const FlSetting kClockFormat = {
kGnomeDesktopInterface,
"clock-format",
G_VARIANT_TYPE_STRING,
};
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 all_settings[] = {
kClockFormat,
kColorScheme,
kGtkTheme,
kTextScalingFactor,
};

static constexpr char kClockFormat12Hour[] = "12h";
static constexpr char kGtkThemeDarkSuffix[] = "-dark";

typedef enum { DEFAULT, PREFER_DARK, PREFER_LIGHT } 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) == PREFER_DARK) {
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 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_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, pPortalSettings, nullptr, error);

if (self->dbus_proxy == nullptr) {
return false;
}

for (const FlSetting setting : all_settings) {
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,
GConnectFlags(0));

return true;
}
56 changes: 56 additions & 0 deletions shell/platform/linux/fl_settings_portal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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.

#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PORTAL_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PORTAL_H_

#include "flutter/shell/platform/linux/fl_settings.h"

G_BEGIN_DECLS

G_DECLARE_FINAL_TYPE(FlSettingsPortal,
fl_settings_portal,
FL,
SETTINGS_PORTAL,
GObject);

/**
* FlSettingsPortal:
* #FlSettingsPortal reads settings from the XDG desktop portal.
*/

/**
* fl_settings_portal_new:
*
* Creates a new settings portal instance.
*
* Returns: a new #FlSettingsPortal.
*/
FlSettingsPortal* fl_settings_portal_new();

/**
* fl_settings_portal_new_with_values:
* @values: (nullable): a #GVariantDict.
*
* Creates a new settings portal instance with initial values for testing.
*
* Returns: a new #FlSettingsPortal.
*/
FlSettingsPortal* fl_settings_portal_new_with_values(GVariantDict* values);

/**
* fl_settings_portal_start:
* @portal: an #FlSettingsPortal.
* @error: (allow-none): #GError location to store the error occurring, or %NULL
*
* Reads the current settings and starts monitoring for changes in the desktop
* portal settings.
*
* Returns: %TRUE on success, or %FALSE if the portal is not available.
*/
gboolean fl_settings_portal_start(FlSettingsPortal* portal, GError** error);

G_END_DECLS

#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PORTAL_H_
Loading

0 comments on commit 14871f1

Please sign in to comment.