Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2151,6 +2151,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