Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions shell/platform/linux/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -99,6 +100,7 @@ source_set("flutter_linux_sources") {
"fl_accessibility_plugin.cc",
"fl_accessible_node.cc",
"fl_accessible_text_field.cc",
"fl_application.cc",
"fl_backing_store_provider.cc",
"fl_basic_message_channel.cc",
"fl_binary_codec.cc",
Expand Down
189 changes: 189 additions & 0 deletions shell/platform/linux/fl_application.cc
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) {
Copy link
Contributor

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?

Copy link
Author

@jane400 jane400 Apr 29, 2024

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.

Copy link
Contributor

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.

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;
#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),
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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
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);
}
85 changes: 85 additions & 0 deletions shell/platform/linux/public/flutter_linux/fl_application.h
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_
1 change: 1 addition & 0 deletions shell/platform/linux/public/flutter_linux/flutter_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#define __FLUTTER_LINUX_INSIDE__

#include <flutter_linux/fl_application.h>
#include <flutter_linux/fl_basic_message_channel.h>
#include <flutter_linux/fl_binary_codec.h>
#include <flutter_linux/fl_binary_messenger.h>
Expand Down