diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e1dec7770725b..45dc550c37d46 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2027,6 +2027,9 @@ FILE: ../../../flutter/shell/platform/linux/fl_event_channel.cc FILE: ../../../flutter/shell/platform/linux/fl_event_channel_test.cc FILE: ../../../flutter/shell/platform/linux/fl_gl_area.cc FILE: ../../../flutter/shell/platform/linux/fl_gl_area.h +FILE: ../../../flutter/shell/platform/linux/fl_gnome_settings.cc +FILE: ../../../flutter/shell/platform/linux/fl_gnome_settings.h +FILE: ../../../flutter/shell/platform/linux/fl_gnome_settings_test.cc FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_json_method_codec.cc @@ -2076,8 +2079,11 @@ FILE: ../../../flutter/shell/platform/linux/fl_renderer_gl.cc FILE: ../../../flutter/shell/platform/linux/fl_renderer_gl.h FILE: ../../../flutter/shell/platform/linux/fl_renderer_headless.cc FILE: ../../../flutter/shell/platform/linux/fl_renderer_headless.h +FILE: ../../../flutter/shell/platform/linux/fl_settings.cc +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_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 diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 9d218cd186fe4..a8c2a1c4023d2 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -107,6 +107,7 @@ source_set("flutter_linux_sources") { "fl_engine.cc", "fl_event_channel.cc", "fl_gl_area.cc", + "fl_gnome_settings.cc", "fl_json_message_codec.cc", "fl_json_method_codec.cc", "fl_key_channel_responder.cc", @@ -128,6 +129,7 @@ source_set("flutter_linux_sources") { "fl_renderer.cc", "fl_renderer_gl.cc", "fl_renderer_headless.cc", + "fl_settings.cc", "fl_settings_plugin.cc", "fl_standard_message_codec.cc", "fl_standard_method_codec.cc", @@ -175,6 +177,13 @@ test_fixtures("flutter_linux_fixtures") { fixtures = [] } +copy("flutter_linux_gschemas") { + testonly = true + + sources = [ "testing/gschemas/ubuntu-20.04.compiled" ] + outputs = [ "$target_gen_dir/assets/{{source_name_part}}/gschemas.compiled" ] +} + executable("flutter_linux_unittests") { testonly = true @@ -186,6 +195,7 @@ executable("flutter_linux_unittests") { "fl_dart_project_test.cc", "fl_engine_test.cc", "fl_event_channel_test.cc", + "fl_gnome_settings_test.cc", "fl_json_message_codec_test.cc", "fl_json_method_codec_test.cc", "fl_key_channel_responder_test.cc", @@ -197,6 +207,7 @@ executable("flutter_linux_unittests") { "fl_method_response_test.cc", "fl_pixel_buffer_texture_test.cc", "fl_plugin_registrar_test.cc", + "fl_settings_plugin_test.cc", "fl_standard_message_codec_test.cc", "fl_standard_method_codec_test.cc", "fl_string_codec_test.cc", @@ -210,6 +221,7 @@ executable("flutter_linux_unittests") { "testing/mock_epoxy.cc", "testing/mock_plugin_registrar.cc", "testing/mock_renderer.cc", + "testing/mock_settings.cc", "testing/mock_signal_handler.cc", "testing/mock_text_input_plugin.cc", "testing/mock_texture_registrar.cc", @@ -229,6 +241,7 @@ executable("flutter_linux_unittests") { deps = [ ":flutter_linux_fixtures", + ":flutter_linux_gschemas", ":flutter_linux_sources", "//flutter/runtime:libdart", "//flutter/shell/platform/embedder:embedder_headers", diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index 8684feae285f3..1f6b92a3cb72e 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -516,8 +516,9 @@ gboolean fl_engine_start(FlEngine* self, GError** error) { setup_locales(self); + g_autoptr(FlSettings) settings = fl_settings_new(); self->settings_plugin = fl_settings_plugin_new(self->binary_messenger); - fl_settings_plugin_start(self->settings_plugin); + fl_settings_plugin_start(self->settings_plugin, settings); result = self->embedder_api.UpdateSemanticsEnabled(self->engine, TRUE); if (result != kSuccess) { diff --git a/shell/platform/linux/fl_gnome_settings.cc b/shell/platform/linux/fl_gnome_settings.cc new file mode 100644 index 0000000000000..977c4c47b780a --- /dev/null +++ b/shell/platform/linux/fl_gnome_settings.cc @@ -0,0 +1,160 @@ +// 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_gnome_settings.h" + +#include +#include + +static constexpr char kDesktopInterfaceSchema[] = "org.gnome.desktop.interface"; +static constexpr char kDesktopTextScalingFactorKey[] = "text-scaling-factor"; +static constexpr char kDesktopClockFormatKey[] = "clock-format"; +static constexpr char kDesktopGtkThemeKey[] = "gtk-theme"; + +static constexpr char kClockFormat12Hour[] = "12h"; +static constexpr char kGtkThemeDarkSuffix[] = "-dark"; +static constexpr char kInterfaceSettings[] = "interface-settings"; + +struct _FlGnomeSettings { + GObject parent_instance; + + GSettings* interface_settings; +}; + +enum { PROP_0, PROP_INTERFACE_SETTINGS, PROP_LAST }; + +static void fl_gnome_settings_iface_init(FlSettingsInterface* iface); + +G_DEFINE_TYPE_WITH_CODE(FlGnomeSettings, + fl_gnome_settings, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(fl_settings_get_type(), + fl_gnome_settings_iface_init)) + +static FlClockFormat fl_gnome_settings_get_clock_format(FlSettings* settings) { + FlGnomeSettings* self = FL_GNOME_SETTINGS(settings); + + FlClockFormat clock_format = FL_CLOCK_FORMAT_24H; + + if (self->interface_settings != nullptr) { + g_autofree gchar* value = + g_settings_get_string(self->interface_settings, kDesktopClockFormatKey); + if (g_strcmp0(value, kClockFormat12Hour) == 0) { + clock_format = FL_CLOCK_FORMAT_12H; + } + } + return clock_format; +} + +static FlColorScheme fl_gnome_settings_get_color_scheme(FlSettings* settings) { + FlGnomeSettings* self = FL_GNOME_SETTINGS(settings); + + FlColorScheme color_scheme = FL_COLOR_SCHEME_LIGHT; + + if (self->interface_settings != nullptr) { + // check whether org.gnome.desktop.interface.gtk-theme ends with "-dark" + g_autofree gchar* value = + g_settings_get_string(self->interface_settings, kDesktopGtkThemeKey); + if (g_str_has_suffix(value, kGtkThemeDarkSuffix)) { + color_scheme = FL_COLOR_SCHEME_DARK; + } + } + return color_scheme; +} + +static gdouble fl_gnome_settings_get_text_scaling_factor(FlSettings* settings) { + FlGnomeSettings* self = FL_GNOME_SETTINGS(settings); + + gdouble scaling_factor = 1.0; + + if (self->interface_settings != nullptr) { + scaling_factor = g_settings_get_double(self->interface_settings, + kDesktopTextScalingFactorKey); + } + return scaling_factor; +} + +static void fl_gnome_settings_set_interface_settings(FlGnomeSettings* self, + GSettings* settings) { + g_return_if_fail(G_IS_SETTINGS(settings)); + + g_signal_connect_object(settings, "changed::clock-format", + G_CALLBACK(fl_settings_emit_changed), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(settings, "changed::gtk-theme", + G_CALLBACK(fl_settings_emit_changed), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(settings, "changed::text-scaling-factor", + G_CALLBACK(fl_settings_emit_changed), self, + G_CONNECT_SWAPPED); + + self->interface_settings = G_SETTINGS(g_object_ref(settings)); +} + +static void fl_gnome_settings_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) { + FlGnomeSettings* self = FL_GNOME_SETTINGS(object); + switch (prop_id) { + case PROP_INTERFACE_SETTINGS: + fl_gnome_settings_set_interface_settings( + self, G_SETTINGS(g_value_get_object(value))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void fl_gnome_settings_dispose(GObject* object) { + FlGnomeSettings* self = FL_GNOME_SETTINGS(object); + + g_clear_object(&self->interface_settings); + + G_OBJECT_CLASS(fl_gnome_settings_parent_class)->dispose(object); +} + +static void fl_gnome_settings_class_init(FlGnomeSettingsClass* klass) { + GObjectClass* object_class = G_OBJECT_CLASS(klass); + object_class->dispose = fl_gnome_settings_dispose; + object_class->set_property = fl_gnome_settings_set_property; + + g_object_class_install_property( + object_class, PROP_INTERFACE_SETTINGS, + g_param_spec_object( + kInterfaceSettings, kInterfaceSettings, kDesktopInterfaceSchema, + g_settings_get_type(), + static_cast(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS))); +} + +static void fl_gnome_settings_iface_init(FlSettingsInterface* iface) { + iface->get_clock_format = fl_gnome_settings_get_clock_format; + iface->get_color_scheme = fl_gnome_settings_get_color_scheme; + iface->get_text_scaling_factor = fl_gnome_settings_get_text_scaling_factor; +} + +static void fl_gnome_settings_init(FlGnomeSettings* self) {} + +static GSettings* create_settings(const gchar* schema_id) { + GSettings* settings = nullptr; + GSettingsSchemaSource* source = g_settings_schema_source_get_default(); + if (source != nullptr) { + g_autoptr(GSettingsSchema) schema = + g_settings_schema_source_lookup(source, schema_id, TRUE); + if (schema != nullptr) { + settings = g_settings_new_full(schema, nullptr, nullptr); + } + } + return settings; +} + +FlSettings* fl_gnome_settings_new() { + g_autoptr(GSettings) interface_settings = + create_settings(kDesktopInterfaceSchema); + return FL_SETTINGS(g_object_new(fl_gnome_settings_get_type(), + kInterfaceSettings, interface_settings, + nullptr)); +} diff --git a/shell/platform/linux/fl_gnome_settings.h b/shell/platform/linux/fl_gnome_settings.h new file mode 100644 index 0000000000000..5aca6a583c9a8 --- /dev/null +++ b/shell/platform/linux/fl_gnome_settings.h @@ -0,0 +1,29 @@ +// 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_GNOME_SETTINGS_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_GNOME_SETTINGS_H_ + +#include "flutter/shell/platform/linux/fl_settings.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlGnomeSettings, + fl_gnome_settings, + FL, + GNOME_SETTINGS, + GObject); + +/** + * fl_gnome_settings_new: + * + * Creates a new settings instance for GNOME. + * + * Returns: a new #FlSettings. + */ +FlSettings* fl_gnome_settings_new(); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_GNOME_SETTINGS_H_ diff --git a/shell/platform/linux/fl_gnome_settings_test.cc b/shell/platform/linux/fl_gnome_settings_test.cc new file mode 100644 index 0000000000000..f4cd92e0ddafb --- /dev/null +++ b/shell/platform/linux/fl_gnome_settings_test.cc @@ -0,0 +1,112 @@ +// 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_gnome_settings.h" +#include "flutter/shell/platform/linux/testing/fl_test.h" +#include "flutter/shell/platform/linux/testing/mock_settings.h" +#include "flutter/shell/platform/linux/testing/mock_signal_handler.h" +#include "flutter/testing/testing.h" + +#include +#define G_SETTINGS_ENABLE_BACKEND +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +class FlGnomeSettingsTest : public ::testing::Test { + protected: + void SetUp() override { + // force _g_io_modules_ensure_extension_points_registered() to get called + g_settings_backend_get_default(); + } +}; + +static GSettings* create_settings(const gchar* name, const gchar* schema_id) { + g_autofree gchar* path = + g_build_filename(flutter::testing::GetFixturesPath(), name, nullptr); + g_autoptr(GSettingsSchemaSource) source = + g_settings_schema_source_new_from_directory(path, nullptr, false, + nullptr); + g_autoptr(GSettingsSchema) schema = + g_settings_schema_source_lookup(source, schema_id, false); + g_autoptr(GSettingsBackend) backend = g_memory_settings_backend_new(); + return g_settings_new_full(schema, backend, nullptr); +} + +TEST_F(FlGnomeSettingsTest, ClockFormat) { + g_autoptr(GSettings) interface_settings = + create_settings("ubuntu-20.04", "org.gnome.desktop.interface"); + g_settings_set_string(interface_settings, "clock-format", "24h"); + + g_autoptr(FlSettings) settings = FL_SETTINGS( + g_object_new(fl_gnome_settings_get_type(), "interface_settings", + interface_settings, nullptr)); + EXPECT_EQ(fl_settings_get_clock_format(settings), FL_CLOCK_FORMAT_24H); + + flutter::testing::MockSignalHandler settings_changed(settings, "changed"); + EXPECT_SIGNAL(settings_changed).Times(1); + + g_settings_set_string(interface_settings, "clock-format", "12h"); + EXPECT_EQ(fl_settings_get_clock_format(settings), FL_CLOCK_FORMAT_12H); +} + +TEST_F(FlGnomeSettingsTest, GtkTheme) { + g_autoptr(GSettings) interface_settings = + create_settings("ubuntu-20.04", "org.gnome.desktop.interface"); + g_settings_set_string(interface_settings, "gtk-theme", "Yaru"); + + g_autoptr(FlSettings) settings = FL_SETTINGS( + g_object_new(fl_gnome_settings_get_type(), "interface_settings", + interface_settings, nullptr)); + EXPECT_EQ(fl_settings_get_color_scheme(settings), FL_COLOR_SCHEME_LIGHT); + + flutter::testing::MockSignalHandler settings_changed(settings, "changed"); + EXPECT_SIGNAL(settings_changed).Times(1); + + g_settings_set_string(interface_settings, "gtk-theme", "Yaru-dark"); + EXPECT_EQ(fl_settings_get_color_scheme(settings), FL_COLOR_SCHEME_DARK); +} + +TEST_F(FlGnomeSettingsTest, TextScalingFactor) { + g_autoptr(GSettings) interface_settings = + create_settings("ubuntu-20.04", "org.gnome.desktop.interface"); + g_settings_set_double(interface_settings, "text-scaling-factor", 1.0); + + g_autoptr(FlSettings) settings = FL_SETTINGS( + g_object_new(fl_gnome_settings_get_type(), "interface_settings", + interface_settings, nullptr)); + EXPECT_EQ(fl_settings_get_text_scaling_factor(settings), 1.0); + + flutter::testing::MockSignalHandler settings_changed(settings, "changed"); + EXPECT_SIGNAL(settings_changed).Times(1); + + g_settings_set_double(interface_settings, "text-scaling-factor", 1.5); + EXPECT_EQ(fl_settings_get_text_scaling_factor(settings), 1.5); +} + +TEST_F(FlGnomeSettingsTest, SignalHandlers) { + g_autoptr(GSettings) interface_settings = + create_settings("ubuntu-20.04", "org.gnome.desktop.interface"); + + g_autoptr(FlSettings) settings = FL_SETTINGS( + g_object_new(fl_gnome_settings_get_type(), "interface_settings", + interface_settings, nullptr)); + flutter::testing::MockSignalHandler settings_changed(settings, "changed"); + + EXPECT_SIGNAL(settings_changed).Times(3); + + g_settings_set_string(interface_settings, "clock-format", "12h"); + g_settings_set_string(interface_settings, "gtk-theme", "Yaru-dark"); + g_settings_set_double(interface_settings, "text-scaling-factor", 1.5); + + EXPECT_SIGNAL(settings_changed).Times(0); + + g_clear_object(&settings); + + // destroyed FlSettings object must have disconnected its signal handlers + g_settings_set_string(interface_settings, "clock-format", "24h"); + g_settings_set_string(interface_settings, "gtk-theme", "Yaru"); + g_settings_set_double(interface_settings, "text-scaling-factor", 2.0); +} diff --git a/shell/platform/linux/fl_settings.cc b/shell/platform/linux/fl_settings.cc new file mode 100644 index 0000000000000..c129da3146d26 --- /dev/null +++ b/shell/platform/linux/fl_settings.cc @@ -0,0 +1,49 @@ +// 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.h" +#include "flutter/shell/platform/linux/fl_gnome_settings.h" + +G_DEFINE_INTERFACE(FlSettings, fl_settings, G_TYPE_OBJECT) + +enum { + SIGNAL_CHANGED, + SIGNAL_LAST_SIGNAL, +}; + +static guint signals[SIGNAL_LAST_SIGNAL]; + +static void fl_settings_default_init(FlSettingsInterface* iface) { + /** + * FlSettings::changed: + * @settings: an #FlSettings + * + * This signal is emitted after the settings have been changed. + */ + signals[SIGNAL_CHANGED] = + g_signal_new("changed", G_TYPE_FROM_INTERFACE(iface), G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +FlClockFormat fl_settings_get_clock_format(FlSettings* self) { + return FL_SETTINGS_GET_IFACE(self)->get_clock_format(self); +} + +FlColorScheme fl_settings_get_color_scheme(FlSettings* self) { + return FL_SETTINGS_GET_IFACE(self)->get_color_scheme(self); +} + +gdouble fl_settings_get_text_scaling_factor(FlSettings* self) { + return FL_SETTINGS_GET_IFACE(self)->get_text_scaling_factor(self); +} + +void fl_settings_emit_changed(FlSettings* self) { + g_return_if_fail(FL_IS_SETTINGS(self)); + g_signal_emit(self, signals[SIGNAL_CHANGED], 0); +} + +FlSettings* fl_settings_new() { + // TODO(jpnurmi): add support for other desktop environments + return FL_SETTINGS(fl_gnome_settings_new()); +} diff --git a/shell/platform/linux/fl_settings.h b/shell/platform/linux/fl_settings.h new file mode 100644 index 0000000000000..25f823614f858 --- /dev/null +++ b/shell/platform/linux/fl_settings.h @@ -0,0 +1,106 @@ +// 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_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_H_ + +#include + +G_BEGIN_DECLS + +G_DECLARE_INTERFACE(FlSettings, fl_settings, FL, SETTINGS, GObject) + +/** + * FlClockFormat: + * @FL_CLOCK_FORMAT_12H: 12-hour clock format. + * @FL_CLOCK_FORMAT_24H: 24-hour clock format. + * + * Available clock formats. + */ +typedef enum { + FL_CLOCK_FORMAT_12H, + FL_CLOCK_FORMAT_24H, +} FlClockFormat; + +/** + * FlColorScheme: + * @FL_COLOR_SCHEME_LIGHT: Prefer light theme. + * @FL_COLOR_SCHEME_DARK: Prefer dark theme. + * + * Available color schemes. + */ +typedef enum { + FL_COLOR_SCHEME_LIGHT, + FL_COLOR_SCHEME_DARK, +} FlColorScheme; + +/** + * FlSettings: + * #FlSettings is and object that provides desktop settings. + */ +struct _FlSettingsInterface { + GTypeInterface parent; + FlClockFormat (*get_clock_format)(FlSettings* settings); + FlColorScheme (*get_color_scheme)(FlSettings* settings); + gdouble (*get_text_scaling_factor)(FlSettings* settings); +}; + +/** + * fl_settings_new: + * + * Creates a new settings instance. + * + * Returns: a new #FlSettings. + */ +FlSettings* fl_settings_new(); + +/** + * fl_settings_get_clock_format: + * @settings: an #FlSettings. + * + * Whether the clock displays in 24-hour or 12-hour format. + * + * This corresponds to `org.gnome.desktop.interface.clock-format` in GNOME. + * + * Returns: an #FlClockFormat. + */ +FlClockFormat fl_settings_get_clock_format(FlSettings* settings); + +/** + * fl_settings_get_color_scheme: + * @settings: an #FlSettings. + * + * The preferred color scheme for the user interface. + * + * This corresponds to `org.gnome.desktop.interface.color-scheme` in GNOME. + * + * Returns: an #FlColorScheme. + */ +FlColorScheme fl_settings_get_color_scheme(FlSettings* settings); + +/** + * fl_settings_get_text_scaling_factor: + * @settings: an #FlSettings. + * + * Factor used to enlarge or reduce text display, without changing font size. + * + * This corresponds to `org.gnome.desktop.interface.text-scaling-factor` in + * GNOME. + * + * Returns: a floating point number. + */ +gdouble fl_settings_get_text_scaling_factor(FlSettings* settings); + +/** + * fl_settings_emit_changed: + * @settings: an #FlSettings. + * + * Emits the "changed" signal. Used by FlSettings implementations to notify when + * the desktop settings have changed. + */ +void fl_settings_emit_changed(FlSettings* settings); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_H_ diff --git a/shell/platform/linux/fl_settings_plugin.cc b/shell/platform/linux/fl_settings_plugin.cc index 1cf50112f8efa..89c800167860c 100644 --- a/shell/platform/linux/fl_settings_plugin.cc +++ b/shell/platform/linux/fl_settings_plugin.cc @@ -5,8 +5,6 @@ #include "flutter/shell/platform/linux/fl_settings_plugin.h" #include -#include -#include #include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" @@ -18,98 +16,42 @@ static constexpr char kPlatformBrightnessKey[] = "platformBrightness"; static constexpr char kPlatformBrightnessLight[] = "light"; static constexpr char kPlatformBrightnessDark[] = "dark"; -static constexpr char kDesktopInterfaceSchema[] = "org.gnome.desktop.interface"; -static constexpr char kDesktopTextScalingFactorKey[] = "text-scaling-factor"; -static constexpr char kDesktopClockFormatKey[] = "clock-format"; -static constexpr char kClockFormat24Hour[] = "24h"; - -enum class Brightness { Light, Dark }; - struct _FlSettingsPlugin { GObject parent_instance; FlBasicMessageChannel* channel; - GSettings* interface_settings; - - GArray* connections; + FlSettings* settings; }; G_DEFINE_TYPE(FlSettingsPlugin, fl_settings_plugin, G_TYPE_OBJECT) -// The color brightness calculation has been adapted from theme_data.dart: -// https://github.com/flutter/flutter/blob/8fe4cc79648a952f9c7e49a5248756c2ff98fa3b/packages/flutter/lib/src/material/theme_data.dart#L1470-L1488 - -// See . -static gdouble linearize_color_component(gdouble component) { - if (component <= 0.03928) { - return component / 12.92; - } - return pow((component + 0.055) / 1.055, 2.4); -} - -// See . -gdouble compute_luminance(GdkRGBA* color) { - gdouble r = linearize_color_component(color->red); - gdouble g = linearize_color_component(color->green); - gdouble b = linearize_color_component(color->blue); - return 0.2126 * r + 0.7152 * g + 0.0722 * b; -} - -static Brightness estimate_brightness_for_color(GdkRGBA* color) { - gdouble relative_luminance = compute_luminance(color); - - // See and - // . - const gdouble kThreshold = 0.15; - if ((relative_luminance + 0.05) * (relative_luminance + 0.05) > kThreshold) { - return Brightness::Light; +static const gchar* to_platform_brightness(FlColorScheme color_scheme) { + switch (color_scheme) { + case FL_COLOR_SCHEME_LIGHT: + return kPlatformBrightnessLight; + case FL_COLOR_SCHEME_DARK: + return kPlatformBrightnessDark; + default: + g_return_val_if_reached(nullptr); } - return Brightness::Dark; -} - -static bool is_dark_theme() { - // GTK doesn't have a specific flag for dark themes, so we check if the - // style text color is light or dark - GList* windows = gtk_window_list_toplevels(); - if (windows == nullptr) { - return false; - } - - GtkWidget* window = GTK_WIDGET(windows->data); - g_list_free(windows); - - GdkRGBA text_color; - GtkStyleContext* style = gtk_widget_get_style_context(window); - gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &text_color); - return estimate_brightness_for_color(&text_color) == Brightness::Light; } // Sends the current settings to the Flutter engine. static void update_settings(FlSettingsPlugin* self) { - gdouble scaling_factor = 1.0; - gboolean always_use_24hr = FALSE; - const gchar* platform_brightness = kPlatformBrightnessLight; - - if (self->interface_settings != nullptr) { - scaling_factor = g_settings_get_double(self->interface_settings, - kDesktopTextScalingFactorKey); - g_autofree gchar* clock_format = - g_settings_get_string(self->interface_settings, kDesktopClockFormatKey); - always_use_24hr = g_strcmp0(clock_format, kClockFormat24Hour) == 0; - } - - if (is_dark_theme()) { - platform_brightness = kPlatformBrightnessDark; - } + FlClockFormat clock_format = fl_settings_get_clock_format(self->settings); + FlColorScheme color_scheme = fl_settings_get_color_scheme(self->settings); + gdouble scaling_factor = fl_settings_get_text_scaling_factor(self->settings); g_autoptr(FlValue) message = fl_value_new_map(); fl_value_set_string_take(message, kTextScaleFactorKey, fl_value_new_float(scaling_factor)); - fl_value_set_string_take(message, kAlwaysUse24HourFormatKey, - fl_value_new_bool(always_use_24hr)); - fl_value_set_string_take(message, kPlatformBrightnessKey, - fl_value_new_string(platform_brightness)); + fl_value_set_string_take( + message, kAlwaysUse24HourFormatKey, + fl_value_new_bool(clock_format == FL_CLOCK_FORMAT_24H)); + fl_value_set_string_take( + message, kPlatformBrightnessKey, + fl_value_new_string(to_platform_brightness(color_scheme))); fl_basic_message_channel_send(self->channel, message, nullptr, nullptr, nullptr); } @@ -117,13 +59,8 @@ static void update_settings(FlSettingsPlugin* self) { static void fl_settings_plugin_dispose(GObject* object) { FlSettingsPlugin* self = FL_SETTINGS_PLUGIN(object); - for (guint i = 0; i < self->connections->len; i += 1) { - g_signal_handler_disconnect(self->interface_settings, - g_array_index(self->connections, gulong, i)); - } - g_array_unref(self->connections); g_clear_object(&self->channel); - g_clear_object(&self->interface_settings); + g_clear_object(&self->settings); G_OBJECT_CLASS(fl_settings_plugin_parent_class)->dispose(object); } @@ -143,36 +80,17 @@ FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger) { g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); self->channel = fl_basic_message_channel_new(messenger, kChannelName, FL_MESSAGE_CODEC(codec)); - self->connections = g_array_new(FALSE, FALSE, sizeof(gulong)); return self; } -void fl_settings_plugin_start(FlSettingsPlugin* self) { +void fl_settings_plugin_start(FlSettingsPlugin* self, FlSettings* settings) { g_return_if_fail(FL_IS_SETTINGS_PLUGIN(self)); + g_return_if_fail(FL_IS_SETTINGS(settings)); - // If we are on GNOME, get settings from GSettings. - GSettingsSchemaSource* source = g_settings_schema_source_get_default(); - if (source != nullptr) { - g_autoptr(GSettingsSchema) schema = - g_settings_schema_source_lookup(source, kDesktopInterfaceSchema, FALSE); - if (schema != nullptr) { - self->interface_settings = g_settings_new_full(schema, nullptr, nullptr); - gulong new_connections[] = { - g_signal_connect_object( - self->interface_settings, "changed::text-scaling-factor", - G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED), - g_signal_connect_object( - self->interface_settings, "changed::clock-format", - G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED), - g_signal_connect_object( - self->interface_settings, "changed::gtk-theme", - G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED), - }; - g_array_append_vals(self->connections, new_connections, - sizeof(new_connections) / sizeof(gulong)); - } - } + self->settings = FL_SETTINGS(g_object_ref(settings)); + g_signal_connect_object(settings, "changed", G_CALLBACK(update_settings), + self, G_CONNECT_SWAPPED); update_settings(self); } diff --git a/shell/platform/linux/fl_settings_plugin.h b/shell/platform/linux/fl_settings_plugin.h index 26c146880d4cb..344a98dd8db91 100644 --- a/shell/platform/linux/fl_settings_plugin.h +++ b/shell/platform/linux/fl_settings_plugin.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PLUGIN_H_ #define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PLUGIN_H_ +#include "flutter/shell/platform/linux/fl_settings.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" G_BEGIN_DECLS @@ -38,7 +39,7 @@ FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger); * * Sends the current settings to the engine and updates when they change. */ -void fl_settings_plugin_start(FlSettingsPlugin* plugin); +void fl_settings_plugin_start(FlSettingsPlugin* plugin, FlSettings* settings); G_END_DECLS diff --git a/shell/platform/linux/fl_settings_plugin_test.cc b/shell/platform/linux/fl_settings_plugin_test.cc new file mode 100644 index 0000000000000..20a47314c08d0 --- /dev/null +++ b/shell/platform/linux/fl_settings_plugin_test.cc @@ -0,0 +1,111 @@ +// 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_plugin.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h" +#include "flutter/shell/platform/linux/testing/fl_test.h" +#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" +#include "flutter/shell/platform/linux/testing/mock_settings.h" +#include "flutter/testing/testing.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +MATCHER_P2(HasSetting, key, value, "") { + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + g_autoptr(FlValue) message = + fl_message_codec_decode_message(FL_MESSAGE_CODEC(codec), arg, nullptr); + if (fl_value_equal(fl_value_lookup_string(message, key), value)) { + return true; + } + *result_listener << ::testing::PrintToString(message); + return false; +} + +#define EXPECT_SETTING(mock, messenger, key, value) \ + EXPECT_CALL(mock, fl_binary_messenger_send_on_channel( \ + messenger, ::testing::StrEq("flutter/settings"), \ + HasSetting(key, value), ::testing::A(), \ + ::testing::A(), \ + ::testing::A())) + +TEST(FlSettingsPluginTest, AlwaysUse24HourFormat) { + ::testing::NiceMock settings; + + ::testing::NiceMock mock_messenger; + g_autoptr(FlBinaryMessenger) messenger = + fl_binary_messenger_new_mock(&mock_messenger); + + g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger); + + g_autoptr(FlValue) use_12h = fl_value_new_bool(false); + g_autoptr(FlValue) use_24h = fl_value_new_bool(true); + + EXPECT_CALL(settings, fl_settings_get_clock_format( + ::testing::Eq(settings))) + .WillOnce(::testing::Return(FL_CLOCK_FORMAT_12H)) + .WillOnce(::testing::Return(FL_CLOCK_FORMAT_24H)); + + EXPECT_SETTING(mock_messenger, messenger, "alwaysUse24HourFormat", use_12h); + + fl_settings_plugin_start(plugin, settings); + + EXPECT_SETTING(mock_messenger, messenger, "alwaysUse24HourFormat", use_24h); + + fl_settings_emit_changed(settings); +} + +TEST(FlSettingsPluginTest, PlatformBrightness) { + ::testing::NiceMock settings; + + ::testing::NiceMock mock_messenger; + g_autoptr(FlBinaryMessenger) messenger = + fl_binary_messenger_new_mock(&mock_messenger); + + g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger); + + g_autoptr(FlValue) light = fl_value_new_string("light"); + g_autoptr(FlValue) dark = fl_value_new_string("dark"); + + EXPECT_CALL(settings, fl_settings_get_color_scheme( + ::testing::Eq(settings))) + .WillOnce(::testing::Return(FL_COLOR_SCHEME_LIGHT)) + .WillOnce(::testing::Return(FL_COLOR_SCHEME_DARK)); + + EXPECT_SETTING(mock_messenger, messenger, "platformBrightness", light); + + fl_settings_plugin_start(plugin, settings); + + EXPECT_SETTING(mock_messenger, messenger, "platformBrightness", dark); + + fl_settings_emit_changed(settings); +} + +TEST(FlSettingsPluginTest, TextScaleFactor) { + ::testing::NiceMock settings; + + ::testing::NiceMock mock_messenger; + g_autoptr(FlBinaryMessenger) messenger = + fl_binary_messenger_new_mock(&mock_messenger); + + g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger); + + g_autoptr(FlValue) one = fl_value_new_float(1.0); + g_autoptr(FlValue) two = fl_value_new_float(2.0); + + EXPECT_CALL(settings, fl_settings_get_text_scaling_factor( + ::testing::Eq(settings))) + .WillOnce(::testing::Return(1.0)) + .WillOnce(::testing::Return(2.0)); + + EXPECT_SETTING(mock_messenger, messenger, "textScaleFactor", one); + + fl_settings_plugin_start(plugin, settings); + + EXPECT_SETTING(mock_messenger, messenger, "textScaleFactor", two); + + fl_settings_emit_changed(settings); +} diff --git a/shell/platform/linux/testing/gschemas/README.md b/shell/platform/linux/testing/gschemas/README.md new file mode 100644 index 0000000000000..8897df6ca64c0 --- /dev/null +++ b/shell/platform/linux/testing/gschemas/README.md @@ -0,0 +1,21 @@ +# gsettings-desktop-schemas + +This directory contains a few variants of +[gsettings-desktop-schemas](https://packages.ubuntu.com/search?keywords=gsettings-desktop-schemas) +with different schemas for testing purposes. + +- [`ubuntu-20.04.compiled`](https://packages.ubuntu.com/focal/gsettings-desktop-schemas) + +### Add or update schemas + +```bash +# download gsettings-desktop-schemas package +wget http://archive.ubuntu.com/ubuntu/pool/main/g/gsettings-desktop-schemas/gsettings-desktop-schemas_.deb + +# extract schema sources (/usr/share/glib-2.0/schemas/*.gschema.xml & .override) +ar x gsettings-desktop-schemas_.deb +tar xf data.tar.zst + +# compile schemas (/usr/share/glib-2.0/schemas/gschemas.compiled) +glib-compile-schemas --targetdir path/to/testing/gschemas usr/share/glib-2.0/schemas/ +``` diff --git a/shell/platform/linux/testing/gschemas/ubuntu-20.04.compiled b/shell/platform/linux/testing/gschemas/ubuntu-20.04.compiled new file mode 100644 index 0000000000000..1ad3a51de9998 Binary files /dev/null and b/shell/platform/linux/testing/gschemas/ubuntu-20.04.compiled differ diff --git a/shell/platform/linux/testing/mock_settings.cc b/shell/platform/linux/testing/mock_settings.cc new file mode 100644 index 0000000000000..944b13f2eb880 --- /dev/null +++ b/shell/platform/linux/testing/mock_settings.cc @@ -0,0 +1,70 @@ +// 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/testing/mock_settings.h" + +using namespace flutter::testing; + +G_DECLARE_FINAL_TYPE(FlMockSettings, + fl_mock_settings, + FL, + MOCK_SETTINGS, + GObject) + +struct _FlMockSettings { + GObject parent_instance; + MockSettings* mock; +}; + +static void fl_mock_settings_iface_init(FlSettingsInterface* iface); + +#define FL_UNUSED(x) (void)x; + +G_DEFINE_TYPE_WITH_CODE(FlMockSettings, + fl_mock_settings, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(fl_settings_get_type(), + fl_mock_settings_iface_init) + FL_UNUSED(FL_IS_MOCK_SETTINGS)) + +static void fl_mock_settings_class_init(FlMockSettingsClass* klass) {} + +static FlClockFormat fl_mock_settings_get_clock_format(FlSettings* settings) { + FlMockSettings* self = FL_MOCK_SETTINGS(settings); + return self->mock->fl_settings_get_clock_format(settings); +} + +static FlColorScheme fl_mock_settings_get_color_scheme(FlSettings* settings) { + FlMockSettings* self = FL_MOCK_SETTINGS(settings); + return self->mock->fl_settings_get_color_scheme(settings); +} + +static gdouble fl_mock_settings_get_text_scaling_factor(FlSettings* settings) { + FlMockSettings* self = FL_MOCK_SETTINGS(settings); + return self->mock->fl_settings_get_text_scaling_factor(settings); +} + +static void fl_mock_settings_iface_init(FlSettingsInterface* iface) { + iface->get_clock_format = fl_mock_settings_get_clock_format; + iface->get_color_scheme = fl_mock_settings_get_color_scheme; + iface->get_text_scaling_factor = fl_mock_settings_get_text_scaling_factor; +} + +static void fl_mock_settings_init(FlMockSettings* self) {} + +MockSettings::MockSettings() + : instance_( + FL_SETTINGS(g_object_new(fl_mock_settings_get_type(), nullptr))) { + FL_MOCK_SETTINGS(instance_)->mock = this; +} + +MockSettings::~MockSettings() { + if (instance_ != nullptr) { + g_clear_object(&instance_); + } +} + +MockSettings::operator FlSettings*() { + return instance_; +} diff --git a/shell/platform/linux/testing/mock_settings.h b/shell/platform/linux/testing/mock_settings.h new file mode 100644 index 0000000000000..79f77faab28d0 --- /dev/null +++ b/shell/platform/linux/testing/mock_settings.h @@ -0,0 +1,39 @@ +// 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_TESTING_MOCK_SETTINGS_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_SETTINGS_H_ + +#include "flutter/shell/platform/linux/fl_settings.h" + +#include "gmock/gmock.h" + +namespace flutter { +namespace testing { + +// Mock for FlSettings. +class MockSettings { + public: + MockSettings(); + ~MockSettings(); + + operator FlSettings*(); + + MOCK_METHOD1(fl_settings_get_clock_format, + FlClockFormat(FlSettings* settings)); + + MOCK_METHOD1(fl_settings_get_color_scheme, + FlColorScheme(FlSettings* settings)); + + MOCK_METHOD1(fl_settings_get_text_scaling_factor, + gdouble(FlSettings* settings)); + + private: + FlSettings* instance_ = nullptr; +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_SETTINGS_H_