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

Commit 87aca98

Browse files
committed
[Linux] add testable FlSettings
FlSettings provides desktop settings and notifies changes, whereas FlSettingsPlugin merely communicates them to the framework. This makes it straightforward to test both separately. As a bonus, it will be easy to add FlSettings implementations for other DEs too.
1 parent 3398422 commit 87aca98

15 files changed

+699
-63
lines changed

shell/platform/linux/BUILD.gn

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ source_set("flutter_linux_sources") {
107107
"fl_engine.cc",
108108
"fl_event_channel.cc",
109109
"fl_gl_area.cc",
110+
"fl_gnome_settings.cc",
110111
"fl_json_message_codec.cc",
111112
"fl_json_method_codec.cc",
112113
"fl_key_channel_responder.cc",
@@ -128,6 +129,7 @@ source_set("flutter_linux_sources") {
128129
"fl_renderer.cc",
129130
"fl_renderer_gl.cc",
130131
"fl_renderer_headless.cc",
132+
"fl_settings.cc",
131133
"fl_settings_plugin.cc",
132134
"fl_standard_message_codec.cc",
133135
"fl_standard_method_codec.cc",
@@ -175,6 +177,16 @@ test_fixtures("flutter_linux_fixtures") {
175177
fixtures = []
176178
}
177179

180+
copy("flutter_linux_gschemas") {
181+
testonly = true
182+
183+
sources = [
184+
"testing/gschemas/ubuntu-20.04.compiled",
185+
"testing/gschemas/ubuntu-22.04.compiled",
186+
]
187+
outputs = [ "$target_gen_dir/assets/{{source_name_part}}/gschemas.compiled" ]
188+
}
189+
178190
executable("flutter_linux_unittests") {
179191
testonly = true
180192

@@ -186,6 +198,7 @@ executable("flutter_linux_unittests") {
186198
"fl_dart_project_test.cc",
187199
"fl_engine_test.cc",
188200
"fl_event_channel_test.cc",
201+
"fl_gnome_settings_test.cc",
189202
"fl_json_message_codec_test.cc",
190203
"fl_json_method_codec_test.cc",
191204
"fl_key_channel_responder_test.cc",
@@ -197,6 +210,7 @@ executable("flutter_linux_unittests") {
197210
"fl_method_response_test.cc",
198211
"fl_pixel_buffer_texture_test.cc",
199212
"fl_plugin_registrar_test.cc",
213+
"fl_settings_plugin_test.cc",
200214
"fl_standard_message_codec_test.cc",
201215
"fl_standard_method_codec_test.cc",
202216
"fl_string_codec_test.cc",
@@ -210,6 +224,7 @@ executable("flutter_linux_unittests") {
210224
"testing/mock_epoxy.cc",
211225
"testing/mock_plugin_registrar.cc",
212226
"testing/mock_renderer.cc",
227+
"testing/mock_settings.cc",
213228
"testing/mock_signal_handler.cc",
214229
"testing/mock_text_input_plugin.cc",
215230
"testing/mock_texture_registrar.cc",
@@ -229,6 +244,7 @@ executable("flutter_linux_unittests") {
229244

230245
deps = [
231246
":flutter_linux_fixtures",
247+
":flutter_linux_gschemas",
232248
":flutter_linux_sources",
233249
"//flutter/runtime:libdart",
234250
"//flutter/shell/platform/embedder:embedder_headers",

shell/platform/linux/fl_engine.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,8 +516,9 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
516516

517517
setup_locales(self);
518518

519+
g_autoptr(FlSettings) settings = fl_settings_new();
519520
self->settings_plugin = fl_settings_plugin_new(self->binary_messenger);
520-
fl_settings_plugin_start(self->settings_plugin);
521+
fl_settings_plugin_start(self->settings_plugin, settings);
521522

522523
result = self->embedder_api.UpdateSemanticsEnabled(self->engine, TRUE);
523524
if (result != kSuccess) {
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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_gnome_settings.h"
6+
7+
#include <glib.h>
8+
9+
static constexpr char kDesktopInterfaceSchema[] = "org.gnome.desktop.interface";
10+
static constexpr char kDesktopTextScalingFactorKey[] = "text-scaling-factor";
11+
static constexpr char kDesktopClockFormatKey[] = "clock-format";
12+
static constexpr char kDesktopGtkThemeKey[] = "gtk-theme";
13+
14+
static constexpr char kClockFormat12Hour[] = "12h";
15+
static constexpr char kColorSchemePreferDark[] = "prefer-dark";
16+
static constexpr char kGtkThemeDarkSuffix[] = "-dark";
17+
18+
G_DECLARE_FINAL_TYPE(FlGnomeSettings,
19+
fl_gnome_settings,
20+
FL,
21+
GNOME_SETTINGS,
22+
GObject);
23+
24+
struct _FlGnomeSettings {
25+
GObject parent_instance;
26+
27+
GSettings* interface_settings;
28+
};
29+
30+
static void fl_gnome_settings_iface_init(FlSettingsInterface* iface);
31+
32+
#define FL_UNUSED(x) (void)x;
33+
34+
G_DEFINE_TYPE_WITH_CODE(FlGnomeSettings,
35+
fl_gnome_settings,
36+
G_TYPE_OBJECT,
37+
G_IMPLEMENT_INTERFACE(fl_settings_get_type(),
38+
fl_gnome_settings_iface_init)
39+
FL_UNUSED(FL_IS_GNOME_SETTINGS))
40+
41+
static FlClockFormat fl_gnome_settings_get_clock_format(FlSettings* settings) {
42+
FlGnomeSettings* self = FL_GNOME_SETTINGS(settings);
43+
44+
FlClockFormat clock_format = FL_CLOCK_FORMAT_24H;
45+
46+
if (self->interface_settings != nullptr) {
47+
g_autofree gchar* value =
48+
g_settings_get_string(self->interface_settings, kDesktopClockFormatKey);
49+
if (g_strcmp0(value, kClockFormat12Hour) == 0) {
50+
clock_format = FL_CLOCK_FORMAT_12H;
51+
}
52+
}
53+
return clock_format;
54+
}
55+
56+
static FlColorScheme fl_gnome_settings_get_color_scheme(FlSettings* settings) {
57+
FlGnomeSettings* self = FL_GNOME_SETTINGS(settings);
58+
59+
FlColorScheme color_scheme = FL_COLOR_SCHEME_LIGHT;
60+
61+
if (self->interface_settings != nullptr) {
62+
// check whether org.gnome.desktop.interface.gtk-theme ends with "-dark"
63+
g_autofree gchar* value =
64+
g_settings_get_string(self->interface_settings, kDesktopGtkThemeKey);
65+
if (g_str_has_suffix(value, kGtkThemeDarkSuffix)) {
66+
color_scheme = FL_COLOR_SCHEME_DARK;
67+
}
68+
}
69+
return color_scheme;
70+
}
71+
72+
static gdouble fl_gnome_settings_get_text_scaling_factor(FlSettings* settings) {
73+
FlGnomeSettings* self = FL_GNOME_SETTINGS(settings);
74+
75+
gdouble scaling_factor = 1.0;
76+
77+
if (self->interface_settings != nullptr) {
78+
scaling_factor = g_settings_get_double(self->interface_settings,
79+
kDesktopTextScalingFactorKey);
80+
}
81+
return scaling_factor;
82+
}
83+
84+
static void fl_gnome_settings_dispose(GObject* object) {
85+
FlGnomeSettings* self = FL_GNOME_SETTINGS(object);
86+
87+
g_clear_object(&self->interface_settings);
88+
89+
G_OBJECT_CLASS(fl_gnome_settings_parent_class)->dispose(object);
90+
}
91+
92+
static void fl_gnome_settings_class_init(FlGnomeSettingsClass* klass) {
93+
G_OBJECT_CLASS(klass)->dispose = fl_gnome_settings_dispose;
94+
}
95+
96+
static void fl_gnome_settings_iface_init(FlSettingsInterface* iface) {
97+
iface->get_clock_format = fl_gnome_settings_get_clock_format;
98+
iface->get_color_scheme = fl_gnome_settings_get_color_scheme;
99+
iface->get_text_scaling_factor = fl_gnome_settings_get_text_scaling_factor;
100+
}
101+
102+
static void fl_gnome_settings_init(FlGnomeSettings* self) {}
103+
104+
static GSettings* default_interface_settings() {
105+
GSettings* interface_settings = nullptr;
106+
GSettingsSchemaSource* source = g_settings_schema_source_get_default();
107+
if (source != nullptr) {
108+
g_autoptr(GSettingsSchema) schema =
109+
g_settings_schema_source_lookup(source, kDesktopInterfaceSchema, TRUE);
110+
if (schema != nullptr) {
111+
interface_settings = g_settings_new_full(schema, nullptr, nullptr);
112+
}
113+
}
114+
return interface_settings;
115+
}
116+
117+
FlSettings* fl_gnome_settings_new(GSettings* interface_settings) {
118+
FlGnomeSettings* self =
119+
FL_GNOME_SETTINGS(g_object_new(fl_gnome_settings_get_type(), nullptr));
120+
121+
if (interface_settings != nullptr) {
122+
self->interface_settings = G_SETTINGS(g_object_ref(interface_settings));
123+
} else {
124+
self->interface_settings = default_interface_settings();
125+
}
126+
127+
if (self->interface_settings != nullptr) {
128+
g_signal_connect_object(self->interface_settings, "changed::clock-format",
129+
G_CALLBACK(fl_settings_emit_changed), self,
130+
G_CONNECT_SWAPPED);
131+
g_signal_connect_object(self->interface_settings, "changed::gtk-theme",
132+
G_CALLBACK(fl_settings_emit_changed), self,
133+
G_CONNECT_SWAPPED);
134+
g_signal_connect_object(
135+
self->interface_settings, "changed::text-scaling-factor",
136+
G_CALLBACK(fl_settings_emit_changed), self, G_CONNECT_SWAPPED);
137+
}
138+
139+
return FL_SETTINGS(self);
140+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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_GNOME_SETTINGS_H_
6+
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_GNOME_SETTINGS_H_
7+
8+
#include "flutter/shell/platform/linux/fl_settings.h"
9+
10+
#include <gio/gio.h>
11+
12+
G_BEGIN_DECLS
13+
14+
/**
15+
* fl_gnome_settings_new:
16+
* @interface_settings: (allow-none): #GSettings for testing purposes, or %NULL.
17+
*
18+
* Creates a new settings instance for GNOME.
19+
*
20+
* Returns: a new #FlSettings.
21+
*/
22+
FlSettings* fl_gnome_settings_new(GSettings* interface_settings);
23+
24+
G_END_DECLS
25+
26+
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_GNOME_SETTINGS_H_
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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_gnome_settings.h"
6+
#include "flutter/shell/platform/linux/testing/fl_test.h"
7+
#include "flutter/shell/platform/linux/testing/mock_settings.h"
8+
#include "flutter/shell/platform/linux/testing/mock_signal_handler.h"
9+
#include "flutter/testing/testing.h"
10+
11+
#include <gio/gio.h>
12+
#define G_SETTINGS_ENABLE_BACKEND
13+
#include <gio/gsettingsbackend.h>
14+
15+
#include "gmock/gmock.h"
16+
#include "gtest/gtest.h"
17+
18+
class FlGnomeSettingsTest : public ::testing::Test {
19+
protected:
20+
void SetUp() override {
21+
// force _g_io_modules_ensure_extension_points_registered() to get called
22+
g_settings_backend_get_default();
23+
}
24+
};
25+
26+
static GSettings* create_interface_settings(const gchar* name) {
27+
g_autofree gchar* path =
28+
g_build_filename(flutter::testing::GetFixturesPath(), name, nullptr);
29+
g_autoptr(GSettingsSchemaSource) source =
30+
g_settings_schema_source_new_from_directory(path, nullptr, false,
31+
nullptr);
32+
g_autoptr(GSettingsSchema) schema = g_settings_schema_source_lookup(
33+
source, "org.gnome.desktop.interface", false);
34+
g_autoptr(GSettingsBackend) backend = g_memory_settings_backend_new();
35+
return g_settings_new_full(schema, backend, nullptr);
36+
}
37+
38+
TEST_F(FlGnomeSettingsTest, ClockFormat) {
39+
g_autoptr(GSettings) interface_settings =
40+
create_interface_settings("ubuntu-20.04");
41+
g_settings_set_string(interface_settings, "clock-format", "24h");
42+
43+
g_autoptr(FlSettings) settings = fl_gnome_settings_new(interface_settings);
44+
EXPECT_EQ(fl_settings_get_clock_format(settings), FL_CLOCK_FORMAT_24H);
45+
46+
flutter::testing::MockSignalHandler settings_changed(settings, "changed");
47+
EXPECT_SIGNAL(settings_changed).Times(1);
48+
49+
g_settings_set_string(interface_settings, "clock-format", "12h");
50+
EXPECT_EQ(fl_settings_get_clock_format(settings), FL_CLOCK_FORMAT_12H);
51+
}
52+
53+
TEST_F(FlGnomeSettingsTest, GtkTheme) {
54+
g_autoptr(GSettings) interface_settings =
55+
create_interface_settings("ubuntu-20.04");
56+
g_settings_set_string(interface_settings, "gtk-theme", "Yaru");
57+
58+
g_autoptr(FlSettings) settings = fl_gnome_settings_new(interface_settings);
59+
EXPECT_EQ(fl_settings_get_color_scheme(settings), FL_COLOR_SCHEME_LIGHT);
60+
61+
flutter::testing::MockSignalHandler settings_changed(settings, "changed");
62+
EXPECT_SIGNAL(settings_changed).Times(1);
63+
64+
g_settings_set_string(interface_settings, "gtk-theme", "Yaru-dark");
65+
EXPECT_EQ(fl_settings_get_color_scheme(settings), FL_COLOR_SCHEME_DARK);
66+
}
67+
68+
TEST_F(FlGnomeSettingsTest, TextScalingFactor) {
69+
g_autoptr(GSettings) interface_settings =
70+
create_interface_settings("ubuntu-20.04");
71+
g_settings_set_double(interface_settings, "text-scaling-factor", 1.0);
72+
73+
g_autoptr(FlSettings) settings = fl_gnome_settings_new(interface_settings);
74+
EXPECT_EQ(fl_settings_get_text_scaling_factor(settings), 1.0);
75+
76+
flutter::testing::MockSignalHandler settings_changed(settings, "changed");
77+
EXPECT_SIGNAL(settings_changed).Times(1);
78+
79+
g_settings_set_double(interface_settings, "text-scaling-factor", 1.5);
80+
EXPECT_EQ(fl_settings_get_text_scaling_factor(settings), 1.5);
81+
}
82+
83+
TEST_F(FlGnomeSettingsTest, SignalHandlers) {
84+
g_autoptr(GSettings) interface_settings =
85+
create_interface_settings("ubuntu-20.04");
86+
87+
FlSettings* settings = fl_gnome_settings_new(interface_settings);
88+
flutter::testing::MockSignalHandler settings_changed(settings, "changed");
89+
90+
EXPECT_SIGNAL(settings_changed).Times(3);
91+
92+
g_settings_set_string(interface_settings, "clock-format", "12h");
93+
g_settings_set_string(interface_settings, "gtk-theme", "Yaru-dark");
94+
g_settings_set_double(interface_settings, "text-scaling-factor", 1.5);
95+
96+
EXPECT_SIGNAL(settings_changed).Times(0);
97+
98+
g_clear_object(&settings);
99+
100+
// destroyed FlSettings object must have disconnected its signal handlers
101+
g_settings_set_string(interface_settings, "clock-format", "24h");
102+
g_settings_set_string(interface_settings, "gtk-theme", "Yaru");
103+
g_settings_set_double(interface_settings, "text-scaling-factor", 2.0);
104+
}

shell/platform/linux/fl_settings.cc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.h"
6+
#include "flutter/shell/platform/linux/fl_gnome_settings.h"
7+
8+
G_DEFINE_INTERFACE(FlSettings, fl_settings, G_TYPE_OBJECT)
9+
10+
enum {
11+
SIGNAL_CHANGED,
12+
SIGNAL_LAST_SIGNAL,
13+
};
14+
15+
static guint signals[SIGNAL_LAST_SIGNAL];
16+
17+
static void fl_settings_default_init(FlSettingsInterface* iface) {
18+
/**
19+
* FlSettings::changed:
20+
* @settings: an #FlSettings
21+
*
22+
* This signal is emitted after the settings have been changed.
23+
*/
24+
signals[SIGNAL_CHANGED] =
25+
g_signal_new("changed", G_TYPE_FROM_INTERFACE(iface), G_SIGNAL_RUN_LAST,
26+
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
27+
}
28+
29+
FlClockFormat fl_settings_get_clock_format(FlSettings* self) {
30+
return FL_SETTINGS_GET_IFACE(self)->get_clock_format(self);
31+
}
32+
33+
FlColorScheme fl_settings_get_color_scheme(FlSettings* self) {
34+
return FL_SETTINGS_GET_IFACE(self)->get_color_scheme(self);
35+
}
36+
37+
gdouble fl_settings_get_text_scaling_factor(FlSettings* self) {
38+
return FL_SETTINGS_GET_IFACE(self)->get_text_scaling_factor(self);
39+
}
40+
41+
void fl_settings_emit_changed(FlSettings* self) {
42+
g_return_if_fail(FL_IS_SETTINGS(self));
43+
g_signal_emit(self, signals[SIGNAL_CHANGED], 0);
44+
}
45+
46+
FlSettings* fl_settings_new() {
47+
// TODO(jpnurmi): add support for other desktop environments
48+
return FL_SETTINGS(fl_gnome_settings_new(nullptr));
49+
}

0 commit comments

Comments
 (0)