-
Notifications
You must be signed in to change notification settings - Fork 6k
Draft: Move the FlApplication from the template into the linux embedder. #50868
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| // 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_view.h" | ||
|
|
||
| #include <gmodule.h> | ||
| #include <gtk/gtk.h> | ||
|
|
||
| #ifdef GDK_WINDOWING_X11 | ||
| #include <gdk/gdkx.h> | ||
| #endif | ||
|
|
||
| struct FlApplicationPrivate { | ||
| char** dart_entrypoint_arguments; | ||
| gboolean activated; | ||
| }; | ||
| #define FL_APPLICATION_PRIVATE(val) static_cast<FlApplicationPrivate*>(val) | ||
|
|
||
| enum { | ||
| VIEW_CONSTRUCTED, | ||
| LAST_SIGNAL | ||
| }; | ||
|
|
||
| static guint signals[LAST_SIGNAL] = {0}; | ||
|
|
||
| G_DEFINE_TYPE_WITH_CODE(FlApplication, | ||
| fl_application, | ||
| GTK_TYPE_APPLICATION, | ||
| G_ADD_PRIVATE(FlApplication)) | ||
|
|
||
| // Implements GApplication::activate. | ||
| static void fl_application_activate(GApplication* application) { | ||
| FlApplication* self = FL_APPLICATION(application); | ||
| FlApplicationPrivate* priv = | ||
| FL_APPLICATION_PRIVATE(fl_application_get_instance_private(self)); | ||
|
|
||
| GtkApplicationWindow* app_window = | ||
| GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(self))); | ||
| GtkWindow* window = GTK_WINDOW(app_window); | ||
|
|
||
| // Ensure we only run this function once. We currently don't do anything fancy | ||
| // like signaling the application that it should show itself again. | ||
| // This only stops the same instance of GApplication creating multiple | ||
| // windows/views. | ||
| if (priv->activated) { | ||
| return; | ||
| } | ||
| priv->activated = TRUE; | ||
|
|
||
| // 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; | ||
robert-ancell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #ifdef GDK_WINDOWING_X11 | ||
| GdkScreen* screen = gtk_window_get_screen(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_header_bar_set_show_close_button(header_bar, true); | ||
| gtk_widget_show(GTK_WIDGET(header_bar)); | ||
|
|
||
| gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); | ||
| } | ||
|
|
||
| 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(app_window), GTK_WIDGET(view)); | ||
|
|
||
| gtk_widget_grab_focus(GTK_WIDGET(view)); | ||
|
|
||
| g_signal_emit (G_OBJECT (self), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This shouldn't be necessary - a Flutter application can just override the activate method (chaining up to the FlApplication implementation) or connect to the GApplication::activate signal.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been thinking how this would work for multi-window. I think the user needs the ability to create the window themselves in this signal, as a multi-window app might have special windows that use GTK widgets. Since this is complicated and needs to be well thought out let's drop this from this PR and resolve in follow up PRs. |
||
| signals[VIEW_CONSTRUCTED], | ||
| 0, | ||
| window, view, nullptr); | ||
| } | ||
|
|
||
| // 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_PRIVATE(fl_application_get_instance_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_PRIVATE(fl_application_get_instance_private(self)); | ||
|
|
||
| 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; | ||
|
|
||
| /** | ||
| * FlApplication::view-constructed: | ||
| * @application: the #FlApplication which emitted the signal | ||
| * @window: the created #GtkWindow with the FlView embedded | ||
| * @view: the created #FlView | ||
| * | ||
| * Emitted when a FlView with its GtkWindow is created by FlApplication. | ||
| * Applications can connect to this signal to register plugins and setup | ||
| * communication with the Flutter Engine. | ||
| * | ||
| * Also see #fl_application_connect_view_constructed for a more type-safe | ||
| * g_signal_connect wrapper for this signal. | ||
| */ | ||
| signals[VIEW_CONSTRUCTED] = | ||
| g_signal_new ("view-constructed", | ||
| G_TYPE_FROM_CLASS (klass), | ||
| static_cast<GSignalFlags>(G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS), | ||
| 0 /* closure */, | ||
| nullptr /* accumulator */, | ||
| nullptr /* accumulator data */, | ||
| nullptr /* C marshaller */, | ||
| G_TYPE_NONE /* return_type */, | ||
| 2 /* n_params */, | ||
| /* param_types */ | ||
| GTK_TYPE_WINDOW, | ||
| fl_view_get_type()); | ||
| } | ||
|
|
||
| static void fl_application_init(FlApplication* self) { | ||
| FlApplicationPrivate* priv = | ||
| FL_APPLICATION_PRIVATE(fl_application_get_instance_private(self)); | ||
|
|
||
| priv->activated = FALSE; | ||
| priv->dart_entrypoint_arguments = nullptr; | ||
| } | ||
|
|
||
| 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 | ||
jane400 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| gulong fl_application_connect_view_constructed( | ||
| FlApplication* app, | ||
| ViewConstructedCallback c_handler, | ||
| gpointer user_data) { | ||
| g_assert(FL_IS_APPLICATION(app)); | ||
|
|
||
| return g_signal_connect (app, "view-constructed", G_CALLBACK(c_handler), user_data); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| // 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 <flutter_linux/flutter_linux.h> can be included directly." | ||
| #endif | ||
|
|
||
| #include "fl_view.h" | ||
|
|
||
| #include <gmodule.h> | ||
| #include <gtk/gtk.h> | ||
|
|
||
| G_BEGIN_DECLS | ||
|
|
||
| G_MODULE_EXPORT | ||
| G_DECLARE_DERIVABLE_TYPE(FlApplication, fl_application, FL, APPLICATION, GtkApplication); | ||
|
|
||
| struct _FlApplicationClass { | ||
| GtkApplicationClass parent_class; | ||
| }; | ||
|
|
||
| /** | ||
| * FlApplication: | ||
| * | ||
| * #Flutter-based application with the GTK embedder. | ||
| * | ||
| * Provides default behaviour for basic flutter applications. | ||
| */ | ||
|
|
||
| /** | ||
| * fl_application_new: | ||
| * @application_id: The unique identifier for the application. (Sets | ||
| * Gio.Application:application-id) | ||
| * @flags: Flags specifying the behaviour of the application. (Sets | ||
| * Gio.Application:flags) | ||
| * | ||
| * Creates a new Flutter-based application. | ||
| * | ||
| * Flags should be set according to the Gio.Applicationflags. For most | ||
| * applications this should be either G_APPLICATION_NON_UNIQUE or | ||
| * G_APPLICATION_DEFAULT_FLAGS. (The default behavior ensures that only a | ||
| * primary instance is open at all times. Using other flags requires special | ||
| * care, refer to the Gio documentation for that) | ||
| * | ||
| * Note that properties like :handle-local-options and signals like | ||
| * GApplication::command-line don't work. FlApplication overrides | ||
| * Gio.Application:local-command-line(). | ||
| * | ||
| * Returns: a new #FlApplication | ||
| */ | ||
| FlApplication* fl_application_new(const gchar* application_id, | ||
| GApplicationFlags flags); | ||
|
|
||
| /** | ||
| * ViewConstructedCallback: | ||
| * @app: The #FlApplication that invoked this callback | ||
| * @window: the #GtkWindow embedding the #FlView | ||
| * @view: the constructed #FlView | ||
| * @user_data: (closure): user-supplied data | ||
| */ | ||
| typedef void (*ViewConstructedCallback)(FlApplication* app, | ||
| GtkWindow* window, | ||
| FlView* view, | ||
| gpointer user_data); | ||
|
|
||
| /** | ||
| fl_application_connect_view_constructed: | ||
| @c_handler: A #ViewConstructedCallback function to connect to this signal. | ||
| @user_data: Data to pass to the c_handler call. Can be nullptr | ||
|
|
||
| It is not guranted that this is only called once, as MultiView Flutter | ||
| applications are in development. | ||
| */ | ||
| gulong fl_application_connect_view_constructed( | ||
| FlApplication* app, | ||
| ViewConstructedCallback c_handler, | ||
| gpointer user_data); | ||
|
|
||
| G_END_DECLS | ||
|
|
||
| #endif // FLUTTER_SHELL_PLATFORM_LINUX_PUBLIC_FLUTTER_LINUX_FL_APPLICATION_H_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why this is required - activate() is only ever called once isn't it?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the application is guaranteed to be unique (by not giving GApplication the flag non-unique) a second instance will send the argv to the main instance over the dbus and run activate there again. The gtk4 template in gnome-builder can be used to reproduce this.
Other things can also activate it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at all existing GTK apps I can find they don't seem to do this - if you call activate a second instance of the main window is shown, which is what Flutter applications do as well. I think we should match the existing behaviour and propose this as a second change if it's required.