From 280cc526106fb6bee78c04ed9538d36947d6e5f0 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Thu, 22 Feb 2024 16:58:27 +0100 Subject: [PATCH 01/11] Move the FlApplication from the template into the linux embedder. Based on a patch by jane400 --- ci/licenses_golden/licenses_flutter | 4 + shell/platform/linux/BUILD.gn | 2 + shell/platform/linux/fl_application.cc | 183 ++++++++++++++++++ .../public/flutter_linux/fl_application.h | 89 +++++++++ .../public/flutter_linux/flutter_linux.h | 1 + 5 files changed, 279 insertions(+) create mode 100644 shell/platform/linux/fl_application.cc create mode 100644 shell/platform/linux/public/flutter_linux/fl_application.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 693eff52faef1..eeea97f63c509 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -44776,6 +44776,8 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_accessible_node_test.cc + ../.. ORIGIN: ../../../flutter/shell/platform/linux/fl_accessible_text_field.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_accessible_text_field.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_accessible_text_field_test.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_application.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_application.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_basic_message_channel.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_basic_message_channel_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_binary_codec.cc + ../../../flutter/LICENSE @@ -47687,6 +47689,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_accessible_node_test.cc FILE: ../../../flutter/shell/platform/linux/fl_accessible_text_field.cc FILE: ../../../flutter/shell/platform/linux/fl_accessible_text_field.h FILE: ../../../flutter/shell/platform/linux/fl_accessible_text_field_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_application.cc +FILE: ../../../flutter/shell/platform/linux/fl_application.h FILE: ../../../flutter/shell/platform/linux/fl_basic_message_channel.cc FILE: ../../../flutter/shell/platform/linux/fl_basic_message_channel_test.cc FILE: ../../../flutter/shell/platform/linux/fl_binary_codec.cc diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index ef05b4ddd2117..73f6be93215f6 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -43,6 +43,7 @@ if (build_glfw_shell) { } _public_headers = [ + "public/flutter_linux/fl_application.h", "public/flutter_linux/fl_basic_message_channel.h", "public/flutter_linux/fl_binary_codec.h", "public/flutter_linux/fl_binary_messenger.h", @@ -99,6 +100,7 @@ source_set("flutter_linux_sources") { sources = [ "fl_accessible_node.cc", "fl_accessible_text_field.cc", + "fl_application.cc", "fl_basic_message_channel.cc", "fl_binary_codec.cc", "fl_binary_messenger.cc", diff --git a/shell/platform/linux/fl_application.cc b/shell/platform/linux/fl_application.cc new file mode 100644 index 0000000000000..fdc0d011fc5a6 --- /dev/null +++ b/shell/platform/linux/fl_application.cc @@ -0,0 +1,183 @@ +// Copyright 2024 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/public/flutter_linux/fl_application.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" + +#include + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +struct FlApplicationPrivate { + // Default window title to use. + gchar* window_title; + + // Arguments to pass to Dart. + gchar** dart_entrypoint_arguments; + + // Default width of a Flutter window in pixels. + int window_width; + + // Default height of a Flutter window in pixels. + int window_height; +}; + +#define FL_APPLICATION_GET_PRIVATE(app) \ + ((FlApplicationPrivate*)fl_application_get_instance_private( \ + FL_APPLICATION(app))) + +enum { SIGNAL_REGISTER_PLUGINS, NR_SIGNALS }; + +static guint fl_application_signals[NR_SIGNALS]; + +G_DEFINE_TYPE_WITH_CODE(FlApplication, + fl_application, + GTK_TYPE_APPLICATION, + G_ADD_PRIVATE(FlApplication)) + +// Default implementation of FlApplication::register_plugins +static void fl_application_register_plugins(FlApplication* self, + FlPluginRegistry* registry) {} + +// Implements GApplication::activate. +static void fl_application_activate(GApplication* application) { + FlApplication* self = FL_APPLICATION(application); + FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); + + GtkApplicationWindow* window = + GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(self))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(window)); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, priv->window_title); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(GTK_WINDOW(window), GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(GTK_WINDOW(window), priv->window_title); + } + + gtk_window_set_default_size(GTK_WINDOW(window), priv->window_width, + priv->window_height); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, priv->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + g_signal_emit(self, fl_application_signals[SIGNAL_REGISTER_PLUGINS], 0, + FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean fl_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + FlApplication* self = FL_APPLICATION(application); + FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); + + // Strip out the first argument as it is the binary name. + priv->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + // This will only run on the primary instance or this instance with + // G_APPLICATION_NON_UNIQUE + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void fl_application_dispose(GObject* object) { + FlApplication* self = FL_APPLICATION(object); + FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); + + g_clear_pointer(&priv->window_title, g_free); + g_clear_pointer(&priv->dart_entrypoint_arguments, g_strfreev); + + G_OBJECT_CLASS(fl_application_parent_class)->dispose(object); +} + +static void fl_application_class_init(FlApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = fl_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + fl_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = fl_application_dispose; + + klass->register_plugins = fl_application_register_plugins; + + fl_application_signals[SIGNAL_REGISTER_PLUGINS] = g_signal_new( + "register-plugins", fl_application_get_type(), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(FlApplicationClass, register_plugins), NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, + fl_plugin_registry_get_type()); +} + +static void fl_application_init(FlApplication* self) { + FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); + priv->window_title = g_strdup(""); + priv->window_width = 1280; + priv->window_height = 720; +} + +G_MODULE_EXPORT +FlApplication* fl_application_new(const gchar* application_id, + GApplicationFlags flags) { + return FL_APPLICATION(g_object_new(fl_application_get_type(), + "application-id", application_id, "flags", + flags, nullptr)); +} + +G_MODULE_EXPORT +void fl_application_set_default_window_title(FlApplication* self, + const gchar* window_title) { + g_return_if_fail(FL_IS_APPLICATION(self)); + FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); + g_free(priv->window_title); + priv->window_title = g_strdup(window_title); +} + +G_MODULE_EXPORT +void fl_application_set_default_window_size(FlApplication* self, + int width, + int height) { + g_return_if_fail(FL_IS_APPLICATION(self)); + FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); + priv->window_width = width; + priv->window_height = height; +} diff --git a/shell/platform/linux/public/flutter_linux/fl_application.h b/shell/platform/linux/public/flutter_linux/fl_application.h new file mode 100644 index 0000000000000..3c61de9cb4f8e --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_application.h @@ -0,0 +1,89 @@ +// Copyright 2024 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_PUBLIC_FLUTTER_LINUX_FL_APPLICATION_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_PUBLIC_FLUTTER_LINUX_FL_APPLICATION_H_ + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +#include "fl_plugin_registry.h" +#include "fl_view.h" + +#include +#include + +G_BEGIN_DECLS + +G_MODULE_EXPORT +G_DECLARE_DERIVABLE_TYPE(FlApplication, + fl_application, + FL, + APPLICATION, + GtkApplication); + +/** + * FlApplicationClass: + * @register_plugins: invoked when plugins should be registered. + */ +struct _FlApplicationClass { + GtkApplicationClass parent_class; + + /** + * FlApplication::register_plugins: + * @application: the application + * @registry: registry to use. + * + * The ::register_plugins signal is emitted when plugins can be registered. + */ + void (*register_plugins)(FlApplication* application, + FlPluginRegistry* registry); +}; + +/** + * FlApplication: + * + * #Flutter-based application with the GTK embedder. + * + * Provides default behaviour for basic Flutter applications. + */ + +/** + * fl_application_new: + * @application_id: (allow-none): The application ID or %NULL. + * @flags: The application flags. + * + * Creates a new Flutter-based application. + * + * Returns: a new #FlApplication + */ +FlApplication* fl_application_new(const gchar* application_id, + GApplicationFlags flags); + +/** + * fl_application_set_default_window_title: + * @application: an #FlApplication. + * @window_title: window title text. + * + * Sets the title to apply to Flutter windows. + */ +void fl_application_set_default_window_title(FlApplication* application, + const gchar* window_title); + +/** + * fl_application_set_default_window_size: + * @application: an #FlApplication. + * @width: width in pixels or -1 to set the default width. + * @height: height in pixels or -1 to set the default height. + * + * Sets the dimensions to apply to Flutter windows. + */ +void fl_application_set_default_window_size(FlApplication* application, + int width, + int height); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_PUBLIC_FLUTTER_LINUX_FL_APPLICATION_H_ diff --git a/shell/platform/linux/public/flutter_linux/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h index 715cb5597bae2..c0d1483baa02f 100644 --- a/shell/platform/linux/public/flutter_linux/flutter_linux.h +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -7,6 +7,7 @@ #define __FLUTTER_LINUX_INSIDE__ +#include #include #include #include From 8376a68a36110e19721e79dd290d42fda764097a Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 20 Aug 2024 15:13:22 +1200 Subject: [PATCH 02/11] Update license --- shell/platform/linux/fl_application.cc | 2 +- shell/platform/linux/public/flutter_linux/fl_application.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/linux/fl_application.cc b/shell/platform/linux/fl_application.cc index fdc0d011fc5a6..90ef60a2cddea 100644 --- a/shell/platform/linux/fl_application.cc +++ b/shell/platform/linux/fl_application.cc @@ -1,4 +1,4 @@ -// Copyright 2024 The Flutter Authors. All rights reserved. +// 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. diff --git a/shell/platform/linux/public/flutter_linux/fl_application.h b/shell/platform/linux/public/flutter_linux/fl_application.h index 3c61de9cb4f8e..262623c885071 100644 --- a/shell/platform/linux/public/flutter_linux/fl_application.h +++ b/shell/platform/linux/public/flutter_linux/fl_application.h @@ -1,4 +1,4 @@ -// Copyright 2024 The Flutter Authors. All rights reserved. +// 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. From 34c05aa6f077c8045276f281277f534b2087b94d Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 20 Aug 2024 15:15:32 +1200 Subject: [PATCH 03/11] Fix code style for signal enum --- shell/platform/linux/fl_application.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shell/platform/linux/fl_application.cc b/shell/platform/linux/fl_application.cc index 90ef60a2cddea..6b1907eaccfbb 100644 --- a/shell/platform/linux/fl_application.cc +++ b/shell/platform/linux/fl_application.cc @@ -31,9 +31,9 @@ struct FlApplicationPrivate { ((FlApplicationPrivate*)fl_application_get_instance_private( \ FL_APPLICATION(app))) -enum { SIGNAL_REGISTER_PLUGINS, NR_SIGNALS }; +enum { kSignalRegisterPlugins, kSignalLastSignal }; -static guint fl_application_signals[NR_SIGNALS]; +static guint fl_application_signals[kSignalLastSignal]; G_DEFINE_TYPE_WITH_CODE(FlApplication, fl_application, @@ -91,7 +91,7 @@ static void fl_application_activate(GApplication* application) { gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); - g_signal_emit(self, fl_application_signals[SIGNAL_REGISTER_PLUGINS], 0, + g_signal_emit(self, fl_application_signals[kSignalRegisterPlugins], 0, FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); @@ -141,7 +141,7 @@ static void fl_application_class_init(FlApplicationClass* klass) { klass->register_plugins = fl_application_register_plugins; - fl_application_signals[SIGNAL_REGISTER_PLUGINS] = g_signal_new( + fl_application_signals[kSignalRegisterPlugins] = g_signal_new( "register-plugins", fl_application_get_type(), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FlApplicationClass, register_plugins), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, From 457bf8c145771a99ea4081da9226a7d451931de6 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 20 Aug 2024 17:12:40 +1200 Subject: [PATCH 04/11] Add a test --- ci/licenses_golden/licenses_flutter | 6 ++++-- shell/platform/linux/BUILD.gn | 2 ++ shell/platform/linux/fl_application_test.cc | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 shell/platform/linux/fl_application_test.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index eeea97f63c509..733758d9e586d 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -44777,7 +44777,7 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_accessible_text_field.cc + ../. ORIGIN: ../../../flutter/shell/platform/linux/fl_accessible_text_field.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_accessible_text_field_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_application.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/linux/fl_application.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_application_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_basic_message_channel.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_basic_message_channel_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_binary_codec.cc + ../../../flutter/LICENSE @@ -44899,6 +44899,7 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_window_state_monitor_test.cc + ORIGIN: ../../../flutter/shell/platform/linux/key_mapping.g.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/key_mapping.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/key_mapping_test.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_application.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_codec.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h + ../../../flutter/LICENSE @@ -47690,7 +47691,7 @@ FILE: ../../../flutter/shell/platform/linux/fl_accessible_text_field.cc FILE: ../../../flutter/shell/platform/linux/fl_accessible_text_field.h FILE: ../../../flutter/shell/platform/linux/fl_accessible_text_field_test.cc FILE: ../../../flutter/shell/platform/linux/fl_application.cc -FILE: ../../../flutter/shell/platform/linux/fl_application.h +FILE: ../../../flutter/shell/platform/linux/fl_application_test.cc FILE: ../../../flutter/shell/platform/linux/fl_basic_message_channel.cc FILE: ../../../flutter/shell/platform/linux/fl_basic_message_channel_test.cc FILE: ../../../flutter/shell/platform/linux/fl_binary_codec.cc @@ -47812,6 +47813,7 @@ FILE: ../../../flutter/shell/platform/linux/fl_window_state_monitor_test.cc FILE: ../../../flutter/shell/platform/linux/key_mapping.g.cc FILE: ../../../flutter/shell/platform/linux/key_mapping.h FILE: ../../../flutter/shell/platform/linux/key_mapping_test.cc +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_application.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 73f6be93215f6..97e870cf9e923 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -199,6 +199,7 @@ executable("flutter_linux_unittests") { sources = [ "fl_accessible_node_test.cc", "fl_accessible_text_field_test.cc", + "fl_application_test.cc", "fl_basic_message_channel_test.cc", "fl_binary_codec_test.cc", "fl_binary_messenger_test.cc", @@ -240,6 +241,7 @@ executable("flutter_linux_unittests") { "testing/mock_binary_messenger_response_handle.cc", "testing/mock_engine.cc", "testing/mock_epoxy.cc", + "testing/mock_gtk.cc", "testing/mock_im_context.cc", "testing/mock_plugin_registrar.cc", "testing/mock_renderer.cc", diff --git a/shell/platform/linux/fl_application_test.cc b/shell/platform/linux/fl_application_test.cc new file mode 100644 index 0000000000000..73b5dc1173f35 --- /dev/null +++ b/shell/platform/linux/fl_application_test.cc @@ -0,0 +1,17 @@ +// 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 "gtest/gtest.h" + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_application.h" + +TEST(FlApplicationTest, ConstructorArgs) { + g_autoptr(FlApplication) app = fl_application_new( + "com.example.TestApplication", G_APPLICATION_FLAGS_NONE); + + EXPECT_STREQ(g_application_get_application_id(G_APPLICATION(app)), + "com.example.TestApplication"); + EXPECT_EQ(g_application_get_flags(G_APPLICATION(app)), + G_APPLICATION_FLAGS_NONE); +} From 49d05c60a11344bd6334888500af9d1674587992 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 20 Aug 2024 17:13:32 +1200 Subject: [PATCH 05/11] Remove mock file --- shell/platform/linux/BUILD.gn | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 97e870cf9e923..13511e93e4b32 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -241,7 +241,6 @@ executable("flutter_linux_unittests") { "testing/mock_binary_messenger_response_handle.cc", "testing/mock_engine.cc", "testing/mock_epoxy.cc", - "testing/mock_gtk.cc", "testing/mock_im_context.cc", "testing/mock_plugin_registrar.cc", "testing/mock_renderer.cc", From 66aafb8edfbe610d8b8007ef6f6cffebabb7eacf Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Thu, 22 Aug 2024 11:04:20 +1200 Subject: [PATCH 06/11] Fix ordering of includes --- shell/platform/linux/fl_application.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shell/platform/linux/fl_application.cc b/shell/platform/linux/fl_application.cc index 6b1907eaccfbb..1d3809433d02e 100644 --- a/shell/platform/linux/fl_application.cc +++ b/shell/platform/linux/fl_application.cc @@ -3,16 +3,16 @@ // found in the LICENSE file. #include "flutter/shell/platform/linux/public/flutter_linux/fl_application.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" #include - #ifdef GDK_WINDOWING_X11 #include #endif +#include "flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" + struct FlApplicationPrivate { // Default window title to use. gchar* window_title; From d965fb70fd9abc518b1b7f744b211d7cd004e92c Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Thu, 22 Aug 2024 16:31:48 +1200 Subject: [PATCH 07/11] Delay the window until the first frame is received from the Flutter engine. Fixes https://github.com/flutter/flutter/issues/151098 --- shell/platform/linux/fl_application.cc | 32 +++++++++++++++------ shell/platform/linux/fl_view.cc | 39 +++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/shell/platform/linux/fl_application.cc b/shell/platform/linux/fl_application.cc index 1d3809433d02e..6405cee5c4e4f 100644 --- a/shell/platform/linux/fl_application.cc +++ b/shell/platform/linux/fl_application.cc @@ -25,6 +25,9 @@ struct FlApplicationPrivate { // Default height of a Flutter window in pixels. int window_height; + + // The main window. + GtkApplicationWindow* window; }; #define FL_APPLICATION_GET_PRIVATE(app) \ @@ -40,6 +43,14 @@ G_DEFINE_TYPE_WITH_CODE(FlApplication, GTK_TYPE_APPLICATION, G_ADD_PRIVATE(FlApplication)) +// Called when the first frame is received. +static void first_frame_cb(FlApplication* self) { + FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); + + // Show the main window. + gtk_window_present(GTK_WINDOW(priv->window)); +} + // Default implementation of FlApplication::register_plugins static void fl_application_register_plugins(FlApplication* self, FlPluginRegistry* registry) {} @@ -49,7 +60,7 @@ static void fl_application_activate(GApplication* application) { FlApplication* self = FL_APPLICATION(application); FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); - GtkApplicationWindow* window = + priv->window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(self))); // Use a header bar when running in GNOME as this is the common style used @@ -61,7 +72,7 @@ static void fl_application_activate(GApplication* application) { // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 - GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(window)); + GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(priv->window)); if (GDK_IS_X11_SCREEN(screen)) { const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { @@ -74,27 +85,30 @@ static void fl_application_activate(GApplication* application) { gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, priv->window_title); gtk_header_bar_set_show_close_button(header_bar, TRUE); - gtk_window_set_titlebar(GTK_WINDOW(window), GTK_WIDGET(header_bar)); + gtk_window_set_titlebar(GTK_WINDOW(priv->window), GTK_WIDGET(header_bar)); } else { - gtk_window_set_title(GTK_WINDOW(window), priv->window_title); + gtk_window_set_title(GTK_WINDOW(priv->window), priv->window_title); } - gtk_window_set_default_size(GTK_WINDOW(window), priv->window_width, + gtk_window_set_default_size(GTK_WINDOW(priv->window), priv->window_width, priv->window_height); - gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments( project, priv->dart_entrypoint_arguments); FlView* view = fl_view_new(project); + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); gtk_widget_show(GTK_WIDGET(view)); - gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(priv->window), GTK_WIDGET(view)); + + // Make the resources for the view so rendering can start. + // We'll show the view when we have the first frame. + gtk_widget_realize(GTK_WIDGET(view)); g_signal_emit(self, fl_application_signals[kSignalRegisterPlugins], 0, FL_PLUGIN_REGISTRY(view)); - - gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 1874b265b2529..4165a07c7f1d9 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -49,6 +49,9 @@ struct _FlView { // Background color. GdkRGBA* background_color; + // TRUE if have got the first frame to render. + gboolean have_first_frame; + // Pointer button state recorded for sending status updates. int64_t button_state; @@ -79,7 +82,9 @@ struct _FlView { FlViewAccessible* view_accessible; }; -enum { kPropFlutterProject = 1, kPropLast }; +enum { kSignalFirstFrame, kSignalLastSignal }; + +static guint fl_view_signals[kSignalLastSignal]; static void fl_view_plugin_registry_iface_init( FlPluginRegistryInterface* iface); @@ -106,6 +111,15 @@ G_DEFINE_TYPE_WITH_CODE( G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(), fl_view_text_input_delegate_iface_init)) +// Emit the first frame signal in the main thread. +static gboolean first_frame_idle_cb(gpointer user_data) { + FlView* self = FL_VIEW(user_data); + + g_signal_emit(self, fl_view_signals[kSignalFirstFrame], 0); + + return FALSE; +} + // Signal handler for GtkWidget::delete-event static gboolean window_delete_event_cb(FlView* self) { fl_platform_handler_request_app_exit(self->platform_handler); @@ -672,6 +686,16 @@ static void fl_view_dispose(GObject* object) { G_OBJECT_CLASS(fl_view_parent_class)->dispose(object); } +// Implements GtkWidget::realize. +static void fl_view_realize(GtkWidget* widget) { + FlView* self = FL_VIEW(widget); + + GTK_WIDGET_CLASS(fl_view_parent_class)->realize(widget); + + // Realize the child widgets. + gtk_widget_realize(GTK_WIDGET(self->gl_area)); +} + // Implements GtkWidget::key_press_event. static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) { FlView* self = FL_VIEW(widget); @@ -696,9 +720,14 @@ static void fl_view_class_init(FlViewClass* klass) { object_class->dispose = fl_view_dispose; GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + widget_class->realize = fl_view_realize; widget_class->key_press_event = fl_view_key_press_event; widget_class->key_release_event = fl_view_key_release_event; + fl_view_signals[kSignalFirstFrame] = + g_signal_new("first-frame", fl_view_get_type(), G_SIGNAL_RUN_LAST, 0, + NULL, NULL, NULL, G_TYPE_NONE, 0); + gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass), fl_socket_accessible_get_type()); } @@ -803,7 +832,15 @@ G_MODULE_EXPORT void fl_view_set_background_color(FlView* self, void fl_view_redraw(FlView* self) { g_return_if_fail(FL_IS_VIEW(self)); + gtk_widget_queue_draw(GTK_WIDGET(self->gl_area)); + + if (!self->have_first_frame) { + self->have_first_frame = TRUE; + // This is not the main thread, so the signal needs to be done via an idle + // callback. + g_idle_add(first_frame_idle_cb, self); + } } GHashTable* fl_view_get_keyboard_state(FlView* self) { From 66c3c9fd8b9cd2a974e2791b2541d032291151bd Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Fri, 23 Aug 2024 11:07:30 +1200 Subject: [PATCH 08/11] Update shell/platform/linux/public/flutter_linux/fl_application.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com> --- shell/platform/linux/public/flutter_linux/fl_application.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/linux/public/flutter_linux/fl_application.h b/shell/platform/linux/public/flutter_linux/fl_application.h index 262623c885071..329b5356b492a 100644 --- a/shell/platform/linux/public/flutter_linux/fl_application.h +++ b/shell/platform/linux/public/flutter_linux/fl_application.h @@ -67,7 +67,7 @@ FlApplication* fl_application_new(const gchar* application_id, * @application: an #FlApplication. * @window_title: window title text. * - * Sets the title to apply to Flutter windows. + * Sets the title to apply to the window. */ void fl_application_set_default_window_title(FlApplication* application, const gchar* window_title); From ef9c4c5b9ce278961df680289dd716f9d575343d Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 3 Sep 2024 14:26:28 +1200 Subject: [PATCH 09/11] Remove default window properties and use the create-window signal for this instead --- shell/platform/linux/fl_application.cc | 78 +++++++------------ .../public/flutter_linux/fl_application.h | 36 ++++----- 2 files changed, 41 insertions(+), 73 deletions(-) diff --git a/shell/platform/linux/fl_application.cc b/shell/platform/linux/fl_application.cc index 1d3809433d02e..84fc87d765ddf 100644 --- a/shell/platform/linux/fl_application.cc +++ b/shell/platform/linux/fl_application.cc @@ -14,24 +14,15 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" struct FlApplicationPrivate { - // Default window title to use. - gchar* window_title; - // Arguments to pass to Dart. gchar** dart_entrypoint_arguments; - - // Default width of a Flutter window in pixels. - int window_width; - - // Default height of a Flutter window in pixels. - int window_height; }; #define FL_APPLICATION_GET_PRIVATE(app) \ ((FlApplicationPrivate*)fl_application_get_instance_private( \ FL_APPLICATION(app))) -enum { kSignalRegisterPlugins, kSignalLastSignal }; +enum { kSignalRegisterPlugins, kSignalCreateWindow, kSignalLastSignal }; static guint fl_application_signals[kSignalLastSignal]; @@ -44,11 +35,9 @@ G_DEFINE_TYPE_WITH_CODE(FlApplication, static void fl_application_register_plugins(FlApplication* self, FlPluginRegistry* registry) {} -// Implements GApplication::activate. -static void fl_application_activate(GApplication* application) { - FlApplication* self = FL_APPLICATION(application); - FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); - +// Default implementation of FlApplication::create_window +static GtkWindow* fl_application_create_window(FlApplication* self, + FlView* view) { GtkApplicationWindow* window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(self))); @@ -72,16 +61,19 @@ static void fl_application_activate(GApplication* application) { if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, priv->window_title); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(GTK_WINDOW(window), GTK_WIDGET(header_bar)); - } else { - gtk_window_set_title(GTK_WINDOW(window), priv->window_title); } - gtk_window_set_default_size(GTK_WINDOW(window), priv->window_width, - priv->window_height); - gtk_widget_show(GTK_WIDGET(window)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + return GTK_WINDOW(window); +} + +// Implements GApplication::activate. +static void fl_application_activate(GApplication* application) { + FlApplication* self = FL_APPLICATION(application); + FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments( @@ -89,7 +81,11 @@ static void fl_application_activate(GApplication* application) { FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); - gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + GtkWindow* window; + g_signal_emit(self, fl_application_signals[kSignalCreateWindow], 0, view, + &window); + gtk_widget_show(GTK_WIDGET(window)); g_signal_emit(self, fl_application_signals[kSignalRegisterPlugins], 0, FL_PLUGIN_REGISTRY(view)); @@ -127,7 +123,6 @@ static void fl_application_dispose(GObject* object) { FlApplication* self = FL_APPLICATION(object); FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); - g_clear_pointer(&priv->window_title, g_free); g_clear_pointer(&priv->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(fl_application_parent_class)->dispose(object); @@ -140,20 +135,20 @@ static void fl_application_class_init(FlApplicationClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_application_dispose; klass->register_plugins = fl_application_register_plugins; + klass->create_window = fl_application_create_window; fl_application_signals[kSignalRegisterPlugins] = g_signal_new( "register-plugins", fl_application_get_type(), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET(FlApplicationClass, register_plugins), NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - fl_plugin_registry_get_type()); + G_STRUCT_OFFSET(FlApplicationClass, register_plugins), nullptr, nullptr, + nullptr, G_TYPE_NONE, 1, fl_plugin_registry_get_type()); + fl_application_signals[kSignalCreateWindow] = g_signal_new( + "create-window", fl_application_get_type(), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(FlApplicationClass, create_window), + g_signal_accumulator_first_wins, nullptr, nullptr, GTK_TYPE_WINDOW, 1, + fl_view_get_type()); } -static void fl_application_init(FlApplication* self) { - FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); - priv->window_title = g_strdup(""); - priv->window_width = 1280; - priv->window_height = 720; -} +static void fl_application_init(FlApplication* self) {} G_MODULE_EXPORT FlApplication* fl_application_new(const gchar* application_id, @@ -162,22 +157,3 @@ FlApplication* fl_application_new(const gchar* application_id, "application-id", application_id, "flags", flags, nullptr)); } - -G_MODULE_EXPORT -void fl_application_set_default_window_title(FlApplication* self, - const gchar* window_title) { - g_return_if_fail(FL_IS_APPLICATION(self)); - FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); - g_free(priv->window_title); - priv->window_title = g_strdup(window_title); -} - -G_MODULE_EXPORT -void fl_application_set_default_window_size(FlApplication* self, - int width, - int height) { - g_return_if_fail(FL_IS_APPLICATION(self)); - FlApplicationPrivate* priv = FL_APPLICATION_GET_PRIVATE(self); - priv->window_width = width; - priv->window_height = height; -} diff --git a/shell/platform/linux/public/flutter_linux/fl_application.h b/shell/platform/linux/public/flutter_linux/fl_application.h index 329b5356b492a..c4c4133bdf5a2 100644 --- a/shell/platform/linux/public/flutter_linux/fl_application.h +++ b/shell/platform/linux/public/flutter_linux/fl_application.h @@ -40,6 +40,20 @@ struct _FlApplicationClass { */ void (*register_plugins)(FlApplication* application, FlPluginRegistry* registry); + + /** + * FlApplication::create_window: + * @application: the application + * @view: the view to add to this window. + * + * The ::create_window signal is emitted when a needs to be created for a + * view. By handling this signal the application can create the appropriate + * window for the given view and set any window properties or additional + * widgets required. + * + * If this signal is not handled a standard GTK window will be created. + */ + GtkWindow* (*create_window)(FlApplication* application, FlView* view); }; /** @@ -62,28 +76,6 @@ struct _FlApplicationClass { FlApplication* fl_application_new(const gchar* application_id, GApplicationFlags flags); -/** - * fl_application_set_default_window_title: - * @application: an #FlApplication. - * @window_title: window title text. - * - * Sets the title to apply to the window. - */ -void fl_application_set_default_window_title(FlApplication* application, - const gchar* window_title); - -/** - * fl_application_set_default_window_size: - * @application: an #FlApplication. - * @width: width in pixels or -1 to set the default width. - * @height: height in pixels or -1 to set the default height. - * - * Sets the dimensions to apply to Flutter windows. - */ -void fl_application_set_default_window_size(FlApplication* application, - int width, - int height); - G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_PUBLIC_FLUTTER_LINUX_FL_APPLICATION_H_ From bd847d0cee63cada50385091e1f7f6993b416ec0 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Mon, 16 Sep 2024 16:10:25 +1200 Subject: [PATCH 10/11] Add a first frame signal view test --- shell/platform/linux/fl_view_test.cc | 35 ++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/shell/platform/linux/fl_view_test.cc b/shell/platform/linux/fl_view_test.cc index db87bf8b2f7a4..33fe16e1a1b25 100644 --- a/shell/platform/linux/fl_view_test.cc +++ b/shell/platform/linux/fl_view_test.cc @@ -3,31 +3,56 @@ // found in the LICENSE file. #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" +#include "flutter/shell/platform/linux/fl_view_private.h" #include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h" #include "gtest/gtest.h" +static void first_frame_cb(FlView* view, gboolean* first_frame_emitted) { + *first_frame_emitted = TRUE; +} + TEST(FlViewTest, GetEngine) { flutter::testing::fl_ensure_gtk_init(); g_autoptr(FlDartProject) project = fl_dart_project_new(); - g_autoptr(FlView) view = fl_view_new(project); + FlView* view = fl_view_new(project); // Check the engine is immediately available (i.e. before the widget is // realized). FlEngine* engine = fl_view_get_engine(view); EXPECT_NE(engine, nullptr); - - g_object_ref_sink(view); } TEST(FlViewTest, StateUpdateDoesNotHappenInInit) { flutter::testing::fl_ensure_gtk_init(); g_autoptr(FlDartProject) project = fl_dart_project_new(); - g_autoptr(FlView) view = fl_view_new(project); + FlView* view = fl_view_new(project); // Check that creating a view doesn't try to query the window state in // initialization, causing a critical log to be issued. EXPECT_EQ( flutter::testing::fl_get_received_gtk_log_levels() & G_LOG_LEVEL_CRITICAL, (GLogLevelFlags)0x0); - g_object_ref_sink(view); + + (void)view; +} + +TEST(FlViewTest, FirstFrameSignal) { + flutter::testing::fl_ensure_gtk_init(); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + FlView* view = fl_view_new(project); + gboolean first_frame_emitted = FALSE; + g_signal_connect(view, "first-frame", G_CALLBACK(first_frame_cb), + &first_frame_emitted); + + EXPECT_FALSE(first_frame_emitted); + + fl_view_redraw(view); + + // Signal is emitted in idle, clear the main loop. + while (g_main_context_iteration(g_main_context_default(), FALSE)) + ; + + // Check view has detected frame. + EXPECT_TRUE(first_frame_emitted); } From 8f3a300ef1f3787c6ed66903fad525c51f11ac68 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Mon, 16 Sep 2024 16:53:26 +1200 Subject: [PATCH 11/11] Fix lint failure on CI --- shell/platform/linux/fl_view_test.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/platform/linux/fl_view_test.cc b/shell/platform/linux/fl_view_test.cc index 33fe16e1a1b25..7800b1579cd84 100644 --- a/shell/platform/linux/fl_view_test.cc +++ b/shell/platform/linux/fl_view_test.cc @@ -50,8 +50,9 @@ TEST(FlViewTest, FirstFrameSignal) { fl_view_redraw(view); // Signal is emitted in idle, clear the main loop. - while (g_main_context_iteration(g_main_context_default(), FALSE)) - ; + while (g_main_context_iteration(g_main_context_default(), FALSE)) { + // Repeat until nothing to iterate on. + } // Check view has detected frame. EXPECT_TRUE(first_frame_emitted);