From 90d1f05686e74cc776a409cff0244f03d9b26f55 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 5 Jan 2021 12:28:43 +1300 Subject: [PATCH] Implement settings channel for the Linux shell (#22486) Implement settings channel for the Linux shell Fixes https://github.com/flutter/flutter/issues/65591 --- ci/licenses_golden/licenses_flutter | 2 + shell/platform/linux/BUILD.gn | 1 + .../linux/fl_basic_message_channel_test.cc | 10 +- shell/platform/linux/fl_engine.cc | 6 + shell/platform/linux/fl_engine_test.cc | 60 ++++++++- shell/platform/linux/fl_settings_plugin.cc | 120 ++++++++++++++++++ shell/platform/linux/fl_settings_plugin.h | 45 +++++++ 7 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 shell/platform/linux/fl_settings_plugin.cc create mode 100644 shell/platform/linux/fl_settings_plugin.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 8174bf5be..12543e387 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2134,6 +2134,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_renderer_wayland.cc FILE: ../../../flutter/shell/platform/linux/fl_renderer_wayland.h FILE: ../../../flutter/shell/platform/linux/fl_renderer_x11.cc FILE: ../../../flutter/shell/platform/linux/fl_renderer_x11.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_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 7defcbb1b..9e628bcf4 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -110,6 +110,7 @@ source_set("flutter_linux_sources") { "fl_renderer_headless.cc", "fl_renderer_wayland.cc", "fl_renderer_x11.cc", + "fl_settings_plugin.cc", "fl_standard_message_codec.cc", "fl_standard_method_codec.cc", "fl_string_codec.cc", diff --git a/shell/platform/linux/fl_basic_message_channel_test.cc b/shell/platform/linux/fl_basic_message_channel_test.cc index 91ac762f9..a806a8c81 100644 --- a/shell/platform/linux/fl_basic_message_channel_test.cc +++ b/shell/platform/linux/fl_basic_message_channel_test.cc @@ -24,11 +24,17 @@ TEST(FlBasicMessageChannelTest, SendMessageWithoutResponse) { FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); bool called = false; + FlutterEngineSendPlatformMessageFnPtr old_handler = + embedder_api->SendPlatformMessage; embedder_api->SendPlatformMessage = MOCK_ENGINE_PROC( SendPlatformMessage, - ([&called](auto engine, const FlutterPlatformMessage* message) { + ([&called, old_handler](auto engine, + const FlutterPlatformMessage* message) { + if (strcmp(message->channel, "test") != 0) { + return old_handler(engine, message); + } + called = true; - EXPECT_STREQ(message->channel, "test"); EXPECT_EQ(message->response_handle, nullptr); g_autoptr(GBytes) message_bytes = diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index a5a05eb75..741a5fa0a 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -14,6 +14,7 @@ #include "flutter/shell/platform/linux/fl_plugin_registrar_private.h" #include "flutter/shell/platform/linux/fl_renderer.h" #include "flutter/shell/platform/linux/fl_renderer_headless.h" +#include "flutter/shell/platform/linux/fl_settings_plugin.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h" static constexpr int kMicrosecondsPerNanosecond = 1000; @@ -30,6 +31,7 @@ struct _FlEngine { FlDartProject* project; FlRenderer* renderer; FlBinaryMessenger* binary_messenger; + FlSettingsPlugin* settings_plugin; FlutterEngineAOTData aot_data; FLUTTER_API_SYMBOL(FlutterEngine) engine; FlutterEngineProcTable embedder_api; @@ -315,6 +317,7 @@ static void fl_engine_dispose(GObject* object) { g_clear_object(&self->project); g_clear_object(&self->renderer); g_clear_object(&self->binary_messenger); + g_clear_object(&self->settings_plugin); if (self->platform_message_handler_destroy_notify) { self->platform_message_handler_destroy_notify( @@ -434,6 +437,9 @@ gboolean fl_engine_start(FlEngine* self, GError** error) { setup_locales(self); + self->settings_plugin = fl_settings_plugin_new(self->binary_messenger); + fl_settings_plugin_start(self->settings_plugin); + return TRUE; } diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index 5353df74f..418f15189 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -8,6 +8,7 @@ #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" #include "flutter/shell/platform/linux/testing/fl_test.h" // Checks sending window metrics events works. @@ -76,12 +77,18 @@ TEST(FlEngineTest, PlatformMessage) { FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); bool called = false; + FlutterEngineSendPlatformMessageFnPtr old_handler = + embedder_api->SendPlatformMessage; embedder_api->SendPlatformMessage = MOCK_ENGINE_PROC( SendPlatformMessage, - ([&called](auto engine, const FlutterPlatformMessage* message) { + ([&called, old_handler](auto engine, + const FlutterPlatformMessage* message) { + if (strcmp(message->channel, "test") != 0) { + return old_handler(engine, message); + } + called = true; - EXPECT_STREQ(message->channel, "test"); EXPECT_EQ(message->message_size, static_cast(4)); EXPECT_EQ(message->message[0], 't'); EXPECT_EQ(message->message[1], 'e'); @@ -137,3 +144,52 @@ TEST(FlEngineTest, PlatformMessageResponse) { EXPECT_TRUE(called); } + +// Checks settings plugin sends settings on startup. +TEST(FlEngineTest, SettingsPlugin) { + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + + bool called = false; + embedder_api->SendPlatformMessage = MOCK_ENGINE_PROC( + SendPlatformMessage, + ([&called](auto engine, const FlutterPlatformMessage* message) { + called = true; + + EXPECT_STREQ(message->channel, "flutter/settings"); + + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + g_autoptr(GBytes) data = + g_bytes_new(message->message, message->message_size); + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) settings = fl_message_codec_decode_message( + FL_MESSAGE_CODEC(codec), data, &error); + EXPECT_NE(settings, nullptr); + EXPECT_EQ(error, nullptr); + g_printerr("%s\n", fl_value_to_string(settings)); + + g_autoptr(FlValue) text_scale_factor = + fl_value_lookup_string(settings, "textScaleFactor"); + EXPECT_NE(text_scale_factor, nullptr); + EXPECT_EQ(fl_value_get_type(text_scale_factor), FL_VALUE_TYPE_FLOAT); + + g_autoptr(FlValue) always_use_24hr_format = + fl_value_lookup_string(settings, "alwaysUse24HourFormat"); + EXPECT_NE(always_use_24hr_format, nullptr); + EXPECT_EQ(fl_value_get_type(always_use_24hr_format), + FL_VALUE_TYPE_BOOL); + + g_autoptr(FlValue) platform_brightness = + fl_value_lookup_string(settings, "platformBrightness"); + EXPECT_NE(platform_brightness, nullptr); + EXPECT_EQ(fl_value_get_type(platform_brightness), FL_VALUE_TYPE_STRING); + + return kSuccess; + })); + + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_engine_start(engine, &error)); + EXPECT_EQ(error, nullptr); + + EXPECT_TRUE(called); +} diff --git a/shell/platform/linux/fl_settings_plugin.cc b/shell/platform/linux/fl_settings_plugin.cc new file mode 100644 index 000000000..446ae9751 --- /dev/null +++ b/shell/platform/linux/fl_settings_plugin.cc @@ -0,0 +1,120 @@ +// 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 + +#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" + +static constexpr char kChannelName[] = "flutter/settings"; +static constexpr char kTextScaleFactorKey[] = "textScaleFactor"; +static constexpr char kAlwaysUse24HourFormatKey[] = "alwaysUse24HourFormat"; +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 kDesktopGtkThemeKey[] = "gtk-theme"; +static constexpr char kDesktopTextScalingFactorKey[] = "text-scaling-factor"; +static constexpr char kDesktopClockFormatKey[] = "clock-format"; +static constexpr char kClockFormat24Hour[] = "24h"; + +struct _FlSettingsPlugin { + GObject parent_instance; + + FlBasicMessageChannel* channel; + + GSettings* interface_settings; +}; + +G_DEFINE_TYPE(FlSettingsPlugin, fl_settings_plugin, G_TYPE_OBJECT) + +// 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; + + // GTK doesn't have a specific flag for dark themes, so we have some + // hard-coded themes for Ubuntu (Yaru) and GNOME (Adwaita). + g_autofree gchar* gtk_theme = + g_settings_get_string(self->interface_settings, kDesktopGtkThemeKey); + if (g_strcmp0(gtk_theme, "Yaru-dark") == 0 || + g_strcmp0(gtk_theme, "Adwaita-dark") == 0) { + platform_brightness = kPlatformBrightnessDark; + } + } + + 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_basic_message_channel_send(self->channel, message, nullptr, nullptr, + nullptr); +} + +static void fl_settings_plugin_dispose(GObject* object) { + FlSettingsPlugin* self = FL_SETTINGS_PLUGIN(object); + + g_clear_object(&self->channel); + g_clear_object(&self->interface_settings); + + G_OBJECT_CLASS(fl_settings_plugin_parent_class)->dispose(object); +} + +static void fl_settings_plugin_class_init(FlSettingsPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_settings_plugin_dispose; +} + +static void fl_settings_plugin_init(FlSettingsPlugin* self) {} + +FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + + FlSettingsPlugin* self = + FL_SETTINGS_PLUGIN(g_object_new(fl_settings_plugin_get_type(), nullptr)); + + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + self->channel = fl_basic_message_channel_new(messenger, kChannelName, + FL_MESSAGE_CODEC(codec)); + + return self; +} + +void fl_settings_plugin_start(FlSettingsPlugin* self) { + g_return_if_fail(FL_IS_SETTINGS_PLUGIN(self)); + + // 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); + 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); + } + } + + update_settings(self); +} diff --git a/shell/platform/linux/fl_settings_plugin.h b/shell/platform/linux/fl_settings_plugin.h new file mode 100644 index 000000000..26c146880 --- /dev/null +++ b/shell/platform/linux/fl_settings_plugin.h @@ -0,0 +1,45 @@ +// 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_PLUGIN_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PLUGIN_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlSettingsPlugin, + fl_settings_plugin, + FL, + SETTINGS_PLUGIN, + GObject); + +/** + * FlSettingsPlugin: + * + * #FlSettingsPlugin is a plugin that implements the Flutter user settings + * channel. + */ + +/** + * fl_settings_plugin_new: + * @messenger: an #FlBinaryMessenger + * + * Creates a new plugin that sends user settings to the Flutter engine. + * + * Returns: a new #FlSettingsPlugin + */ +FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger); + +/** + * fl_settings_plugin_start: + * @self: an #FlSettingsPlugin. + * + * Sends the current settings to the engine and updates when they change. + */ +void fl_settings_plugin_start(FlSettingsPlugin* plugin); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PLUGIN_H_ -- GitLab