diff --git a/shell/platform/linux/fl_application.cc b/shell/platform/linux/fl_application.cc index 84fc87d765ddf..203d8a12df5e0 100644 --- a/shell/platform/linux/fl_application.cc +++ b/shell/platform/linux/fl_application.cc @@ -31,6 +31,16 @@ 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, FlView* view) { + GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(view)); + + // Show the main window. + if (window != nullptr && GTK_IS_WINDOW(window)) { + gtk_window_present(GTK_WINDOW(window)); + } +} + // Default implementation of FlApplication::register_plugins static void fl_application_register_plugins(FlApplication* self, FlPluginRegistry* registry) {} @@ -80,17 +90,20 @@ static void fl_application_activate(GApplication* application) { 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)); GtkWindow* window; g_signal_emit(self, fl_application_signals[kSignalCreateWindow], 0, view, &window); - gtk_widget_show(GTK_WIDGET(window)); + + // 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 3dacc776f021f..40c500676f9d1 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -52,6 +52,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; @@ -82,7 +85,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); @@ -109,6 +114,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); @@ -678,6 +692,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); @@ -702,9 +726,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()); } @@ -810,7 +839,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) { diff --git a/shell/platform/linux/fl_view_test.cc b/shell/platform/linux/fl_view_test.cc index db87bf8b2f7a4..7800b1579cd84 100644 --- a/shell/platform/linux/fl_view_test.cc +++ b/shell/platform/linux/fl_view_test.cc @@ -3,31 +3,57 @@ // 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)) { + // Repeat until nothing to iterate on. + } + + // Check view has detected frame. + EXPECT_TRUE(first_frame_emitted); }