Add windowing channel support to Linux embedder (#163180)

Implement flutter/windowing for the Linux embedder.
This commit is contained in:
Robert Ancell 2025-02-27 17:14:20 +13:00 committed by GitHub
parent 11d33f203e
commit d8a57c61dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1486 additions and 10 deletions

View File

@ -43869,6 +43869,11 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_view_test.cc + ../../../flutter
ORIGIN: ../../../flutter/shell/platform/linux/fl_window_state_monitor.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_window_state_monitor.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_window_state_monitor_test.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_windowing_channel.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_windowing_channel.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_windowing_handler.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_windowing_handler.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_windowing_handler_test.cc + ../../../flutter/LICENSE
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
@ -46813,6 +46818,11 @@ FILE: ../../../flutter/shell/platform/linux/fl_view_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_window_state_monitor.cc
FILE: ../../../flutter/shell/platform/linux/fl_window_state_monitor.h
FILE: ../../../flutter/shell/platform/linux/fl_window_state_monitor_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_windowing_channel.cc
FILE: ../../../flutter/shell/platform/linux/fl_windowing_channel.h
FILE: ../../../flutter/shell/platform/linux/fl_windowing_handler.cc
FILE: ../../../flutter/shell/platform/linux/fl_windowing_handler.h
FILE: ../../../flutter/shell/platform/linux/fl_windowing_handler_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

View File

@ -1,8 +1,10 @@
InheritParentConfig: true
# EnumCastOutOfRange warns about some common usages of GTK macros
# Malloc generates false positives with g_autofree usage.
Checks: >-
-clang-analyzer-optin.core.EnumCastOutOfRange
-clang-analyzer-optin.core.EnumCastOutOfRange,
-clang-analyzer-unix.Malloc
CheckOptions:
- key: readability-identifier-naming.EnumConstantCase

View File

@ -158,6 +158,8 @@ source_set("flutter_linux_sources") {
"fl_view.cc",
"fl_view_accessible.cc",
"fl_window_state_monitor.cc",
"fl_windowing_channel.cc",
"fl_windowing_handler.cc",
"key_mapping.g.cc",
]
@ -248,6 +250,7 @@ executable("flutter_linux_unittests") {
"fl_view_accessible_test.cc",
"fl_view_test.cc",
"fl_window_state_monitor_test.cc",
"fl_windowing_handler_test.cc",
"key_mapping_test.cc",
"testing/fl_mock_binary_messenger.cc",
"testing/fl_test.cc",

View File

@ -9,6 +9,7 @@
#include <gdk/gdkx.h>
#endif
#include "flutter/shell/platform/linux/fl_engine_private.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"
@ -31,6 +32,15 @@ G_DEFINE_TYPE_WITH_CODE(FlApplication,
GTK_TYPE_APPLICATION,
G_ADD_PRIVATE(FlApplication))
// Called when the platform creates a window.
static GtkWindow* create_window_cb(FlApplication* self, FlView* view) {
GtkWindow* window;
g_signal_emit(self, fl_application_signals[SIGNAL_CREATE_WINDOW], 0, view,
&window);
return window;
}
// 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));
@ -94,6 +104,11 @@ static void fl_application_activate(GApplication* application) {
self);
gtk_widget_show(GTK_WIDGET(view));
FlWindowingHandler* windowing_handler =
fl_engine_get_windowing_handler(fl_view_get_engine(view));
g_signal_connect_swapped(windowing_handler, "create_window",
G_CALLBACK(create_window_cb), self);
GtkWindow* window;
g_signal_emit(self, fl_application_signals[SIGNAL_CREATE_WINDOW], 0, view,
&window);

View File

@ -24,6 +24,7 @@
#include "flutter/shell/platform/linux/fl_settings_handler.h"
#include "flutter/shell/platform/linux/fl_texture_gl_private.h"
#include "flutter/shell/platform/linux/fl_texture_registrar_private.h"
#include "flutter/shell/platform/linux/fl_windowing_handler.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h"
// Unique number associated with platform tasks.
@ -58,6 +59,9 @@ struct _FlEngine {
// Implements the flutter/platform channel.
FlPlatformHandler* platform_handler;
// Implements the flutter/windowing channel.
FlWindowingHandler* windowing_handler;
// Process keyboard events.
FlKeyboardManager* keyboard_manager;
@ -487,6 +491,7 @@ static void fl_engine_dispose(GObject* object) {
g_clear_object(&self->binary_messenger);
g_clear_object(&self->settings_handler);
g_clear_object(&self->platform_handler);
g_clear_object(&self->windowing_handler);
g_clear_object(&self->keyboard_manager);
g_clear_object(&self->text_input_handler);
g_clear_object(&self->keyboard_handler);
@ -702,6 +707,7 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
fl_settings_handler_start(self->settings_handler, settings);
self->platform_handler = fl_platform_handler_new(self->binary_messenger);
self->windowing_handler = fl_windowing_handler_new(self);
setup_keyboard(self);
@ -1295,6 +1301,11 @@ void fl_engine_request_app_exit(FlEngine* self) {
fl_platform_handler_request_app_exit(self->platform_handler);
}
FlWindowingHandler* fl_engine_get_windowing_handler(FlEngine* self) {
g_return_val_if_fail(FL_IS_ENGINE(self), nullptr);
return self->windowing_handler;
}
FlKeyboardManager* fl_engine_get_keyboard_manager(FlEngine* self) {
g_return_val_if_fail(FL_IS_ENGINE(self), nullptr);
return self->keyboard_manager;

View File

@ -14,6 +14,7 @@
#include "flutter/shell/platform/linux/fl_renderer.h"
#include "flutter/shell/platform/linux/fl_task_runner.h"
#include "flutter/shell/platform/linux/fl_text_input_handler.h"
#include "flutter/shell/platform/linux/fl_windowing_handler.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
@ -576,13 +577,23 @@ void fl_engine_update_accessibility_features(FlEngine* engine, int32_t flags);
*/
void fl_engine_request_app_exit(FlEngine* engine);
/**
* fl_engine_get_windowing_handler:
* @engine: an #FlEngine.
*
* Gets the windowing handler used by this engine.
*
* Returns: an #FlWindowingHandler.
*/
FlWindowingHandler* fl_engine_get_windowing_handler(FlEngine* engine);
/**
* fl_engine_get_keyboard_manager:
* @engine: an #FlEngine.
*
* Gets the keyboard manager used by this engine.
*
* Returns: a #FlKeyboardManager.
* Returns: an #FlKeyboardManager.
*/
FlKeyboardManager* fl_engine_get_keyboard_manager(FlEngine* engine);
@ -592,7 +603,7 @@ FlKeyboardManager* fl_engine_get_keyboard_manager(FlEngine* engine);
*
* Gets the text input handler used by this engine.
*
* Returns: a #FlTextInputHandler.
* Returns: an #FlTextInputHandler.
*/
FlTextInputHandler* fl_engine_get_text_input_handler(FlEngine* engine);
@ -602,7 +613,7 @@ FlTextInputHandler* fl_engine_get_text_input_handler(FlEngine* engine);
*
* Gets the mouse cursor handler used by this engine.
*
* Returns: a #FlMouseCursorHandler.
* Returns: an #FlMouseCursorHandler.
*/
FlMouseCursorHandler* fl_engine_get_mouse_cursor_handler(FlEngine* engine);

View File

@ -765,7 +765,7 @@ G_MODULE_EXPORT FlView* fl_view_new(FlDartProject* project) {
fl_engine_set_update_semantics_handler(self->engine, update_semantics_cb,
self, nullptr);
self->on_pre_engine_restart_cb_id =
g_signal_connect_swapped(engine, "on-pre-engine-restart",
g_signal_connect_swapped(self->engine, "on-pre-engine-restart",
G_CALLBACK(on_pre_engine_restart_cb), self);
g_signal_connect_swapped(self->gl_area, "create-context",
@ -790,7 +790,7 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) {
g_signal_connect_swapped(engine, "on-pre-engine-restart",
G_CALLBACK(on_pre_engine_restart_cb), self);
self->view_id = fl_engine_add_view(self->engine, 1, 1, 1.0, self->cancellable,
self->view_id = fl_engine_add_view(engine, 1, 1, 1.0, self->cancellable,
view_added_cb, self);
fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id,
FL_RENDERABLE(self));

View File

@ -7,6 +7,7 @@
#include "flutter/shell/platform/linux/fl_engine_private.h"
#include "flutter/shell/platform/linux/testing/fl_test.h"
#include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
#include "flutter/shell/platform/linux/testing/mock_window.h"
#include "gtest/gtest.h"
@ -136,8 +137,8 @@ TEST(FlViewTest, ViewDestroy) {
int64_t implicit_view_id = fl_view_get_id(implicit_view);
int64_t secondary_view_id = fl_view_get_id(secondary_view);
gtk_widget_destroy(GTK_WIDGET(secondary_view));
gtk_widget_destroy(GTK_WIDGET(implicit_view));
fl_gtk_widget_destroy(GTK_WIDGET(secondary_view));
fl_gtk_widget_destroy(GTK_WIDGET(implicit_view));
EXPECT_EQ(removed_views->len, 2u);
EXPECT_EQ(GPOINTER_TO_INT(g_ptr_array_index(removed_views, 0)),
@ -165,6 +166,6 @@ TEST(FlViewTest, ViewDestroyError) {
FlView* secondary_view = fl_view_new_for_engine(engine);
gtk_widget_destroy(GTK_WIDGET(secondary_view));
gtk_widget_destroy(GTK_WIDGET(implicit_view));
fl_gtk_widget_destroy(GTK_WIDGET(secondary_view));
fl_gtk_widget_destroy(GTK_WIDGET(implicit_view));
}

View File

@ -0,0 +1,296 @@
// 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_windowing_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
static constexpr char kChannelName[] = "flutter/windowing";
static constexpr char kBadArgumentsError[] = "Bad Arguments";
static constexpr char kCreateRegularMethod[] = "createRegular";
static constexpr char kModifyRegularMethod[] = "modifyRegular";
static constexpr char kDestroyWindowMethod[] = "destroyWindow";
static constexpr char kSizeKey[] = "size";
static constexpr char kMinSizeKey[] = "minSize";
static constexpr char kMaxSizeKey[] = "maxSize";
static constexpr char kTitleKey[] = "title";
static constexpr char kStateKey[] = "state";
static constexpr char kViewIdKey[] = "viewId";
struct _FlWindowingChannel {
GObject parent_instance;
FlMethodChannel* channel;
// Handlers for incoming method calls.
FlWindowingChannelVTable* vtable;
// User data to pass to method call handlers.
gpointer user_data;
};
G_DEFINE_TYPE(FlWindowingChannel, fl_windowing_channel, G_TYPE_OBJECT)
// Returns TRUE if [args] is a valid size argument.
static gboolean is_valid_size_argument(FlValue* value) {
return fl_value_get_type(value) == FL_VALUE_TYPE_LIST &&
fl_value_get_length(value) == 2 &&
fl_value_get_type(fl_value_get_list_value(value, 0)) ==
FL_VALUE_TYPE_FLOAT &&
fl_value_get_type(fl_value_get_list_value(value, 1)) ==
FL_VALUE_TYPE_FLOAT;
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlWindowingSize, g_free)
static FlWindowingSize* parse_size_value(FlValue* value) {
FlWindowingSize* size = g_new0(FlWindowingSize, 1);
size->width = fl_value_get_float(fl_value_get_list_value(value, 0));
size->height = fl_value_get_float(fl_value_get_list_value(value, 1));
return size;
}
static gboolean parse_window_state_value(FlValue* value, FlWindowState* state) {
if (fl_value_get_type(value) != FL_VALUE_TYPE_STRING) {
return FALSE;
}
const gchar* text = fl_value_get_string(value);
if (strcmp(text, "WindowState.restored") == 0) {
*state = FL_WINDOW_STATE_RESTORED;
return TRUE;
} else if (strcmp(text, "WindowState.maximized") == 0) {
*state = FL_WINDOW_STATE_MAXIMIZED;
return TRUE;
} else if (strcmp(text, "WindowState.minimized") == 0) {
*state = FL_WINDOW_STATE_MINIMIZED;
return TRUE;
}
return FALSE;
}
static const gchar* window_state_to_string(FlWindowState state) {
switch (state) {
case FL_WINDOW_STATE_UNDEFINED:
return nullptr;
case FL_WINDOW_STATE_RESTORED:
return "WindowState.restored";
case FL_WINDOW_STATE_MAXIMIZED:
return "WindowState.maximized";
case FL_WINDOW_STATE_MINIMIZED:
return "WindowState.minimized";
}
return nullptr;
}
// Called when a regular window should be created.
static FlMethodResponse* create_regular(FlWindowingChannel* self,
FlValue* args) {
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Argument map missing or malformed", nullptr));
}
FlValue* size_value = fl_value_lookup_string(args, kSizeKey);
if (size_value == nullptr || !is_valid_size_argument(size_value)) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Missing/invalid size argument", nullptr));
}
g_autoptr(FlWindowingSize) size = parse_size_value(size_value);
FlValue* min_size_value = fl_value_lookup_string(args, kMinSizeKey);
g_autoptr(FlWindowingSize) min_size = nullptr;
if (min_size_value != nullptr) {
if (!is_valid_size_argument(min_size_value)) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid minSize argument", nullptr));
}
min_size = parse_size_value(min_size_value);
}
FlValue* max_size_value = fl_value_lookup_string(args, kMaxSizeKey);
g_autoptr(FlWindowingSize) max_size = nullptr;
if (max_size_value != nullptr) {
if (!is_valid_size_argument(max_size_value)) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid maxSize argument", nullptr));
}
max_size = parse_size_value(max_size_value);
}
FlValue* title_value = fl_value_lookup_string(args, kTitleKey);
const gchar* title = nullptr;
if (title_value != nullptr) {
if (fl_value_get_type(title_value) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid title argument", nullptr));
}
title = fl_value_get_string(title_value);
}
FlWindowState state = FL_WINDOW_STATE_UNDEFINED;
FlValue* state_value = fl_value_lookup_string(args, kStateKey);
if (state_value != nullptr) {
if (!parse_window_state_value(state_value, &state)) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid state argument", nullptr));
}
}
return self->vtable->create_regular(size, min_size, max_size, title, state,
self->user_data);
}
// Called when a regular window should be created.
static FlMethodResponse* modify_regular(FlWindowingChannel* self,
FlValue* args) {
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Argument map missing or malformed", nullptr));
}
FlValue* view_id_value = fl_value_lookup_string(args, kViewIdKey);
if (view_id_value == nullptr ||
fl_value_get_type(view_id_value) != FL_VALUE_TYPE_INT) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Missing/invalid viewId argument", nullptr));
}
int64_t view_id = fl_value_get_int(view_id_value);
g_autoptr(FlWindowingSize) size = nullptr;
FlValue* size_value = fl_value_lookup_string(args, kSizeKey);
if (size_value != nullptr) {
if (!is_valid_size_argument(size_value)) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid size argument", nullptr));
}
size = parse_size_value(size_value);
}
FlValue* title_value = fl_value_lookup_string(args, kTitleKey);
const gchar* title = nullptr;
if (title_value != nullptr) {
if (fl_value_get_type(title_value) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid title argument", nullptr));
}
title = fl_value_get_string(title_value);
}
FlWindowState state = FL_WINDOW_STATE_UNDEFINED;
FlValue* state_value = fl_value_lookup_string(args, kStateKey);
if (state_value != nullptr) {
if (!parse_window_state_value(state_value, &state)) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid state argument", nullptr));
}
}
return self->vtable->modify_regular(view_id, size, title, state,
self->user_data);
}
// Called when a window should be destroyed.
static FlMethodResponse* destroy_window(FlWindowingChannel* self,
FlValue* args) {
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Argument map missing or malformed", nullptr));
}
FlValue* view_id_value = fl_value_lookup_string(args, kViewIdKey);
if (view_id_value == nullptr ||
fl_value_get_type(view_id_value) != FL_VALUE_TYPE_INT) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Missing/invalid viewId argument", nullptr));
}
int64_t view_id = fl_value_get_int(view_id_value);
return self->vtable->destroy_window(view_id, self->user_data);
}
// Called when a method call is received from Flutter.
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlWindowingChannel* self = FL_WINDOWING_CHANNEL(user_data);
const gchar* method = fl_method_call_get_name(method_call);
FlValue* args = fl_method_call_get_args(method_call);
g_autoptr(FlMethodResponse) response = nullptr;
if (strcmp(method, kCreateRegularMethod) == 0) {
response = create_regular(self, args);
} else if (strcmp(method, kModifyRegularMethod) == 0) {
response = modify_regular(self, args);
} else if (strcmp(method, kDestroyWindowMethod) == 0) {
response = destroy_window(self, args);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
if (response != nullptr) {
g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send method call response: %s", error->message);
}
}
}
static void fl_windowing_channel_dispose(GObject* object) {
FlWindowingChannel* self = FL_WINDOWING_CHANNEL(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_windowing_channel_parent_class)->dispose(object);
}
static void fl_windowing_channel_class_init(FlWindowingChannelClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_windowing_channel_dispose;
}
static void fl_windowing_channel_init(FlWindowingChannel* self) {}
FlWindowingChannel* fl_windowing_channel_new(FlBinaryMessenger* messenger,
FlWindowingChannelVTable* vtable,
gpointer user_data) {
FlWindowingChannel* self = FL_WINDOWING_CHANNEL(
g_object_new(fl_windowing_channel_get_type(), nullptr));
self->vtable = vtable;
self->user_data = user_data;
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->channel =
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self,
nullptr);
return self;
}
FlMethodResponse* fl_windowing_channel_make_create_regular_response(
int64_t view_id,
FlWindowingSize* size,
FlWindowState state) {
g_autoptr(FlValue) result = fl_value_new_map();
fl_value_set_string_take(result, kViewIdKey, fl_value_new_int(view_id));
g_autoptr(FlValue) size_value = fl_value_new_list();
fl_value_append_take(size_value, fl_value_new_float(size->width));
fl_value_append_take(size_value, fl_value_new_float(size->height));
fl_value_set_string(result, kSizeKey, size_value);
fl_value_set_string_take(result, kStateKey,
fl_value_new_string(window_state_to_string(state)));
return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}
FlMethodResponse* fl_windowing_channel_make_modify_regular_response() {
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
FlMethodResponse* fl_windowing_channel_make_destroy_window_response() {
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}

View File

@ -0,0 +1,81 @@
// 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_WINDOWING_CHANNEL_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_WINDOWING_CHANNEL_H_
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_response.h"
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(FlWindowingChannel,
fl_windowing_channel,
FL,
WINDOWING_CHANNEL,
GObject);
// States that a window can be in.
typedef enum {
FL_WINDOW_STATE_UNDEFINED,
FL_WINDOW_STATE_RESTORED,
FL_WINDOW_STATE_MAXIMIZED,
FL_WINDOW_STATE_MINIMIZED
} FlWindowState;
// Size dimensions used in windowing channel.
typedef struct {
double width;
double height;
} FlWindowingSize;
/**
* FlWindowingChannel:
*
* #FlWindowingChannel is a channel that implements the shell side
* of SystemChannels.windowing from the Flutter services library.
*/
typedef struct {
FlMethodResponse* (*create_regular)(FlWindowingSize* size,
FlWindowingSize* min_size,
FlWindowingSize* max_size,
const gchar* title,
FlWindowState state,
gpointer user_data);
FlMethodResponse* (*modify_regular)(int64_t view_id,
FlWindowingSize* size,
const gchar* title,
FlWindowState state,
gpointer user_data);
FlMethodResponse* (*destroy_window)(int64_t view_id, gpointer user_data);
} FlWindowingChannelVTable;
/**
* fl_windowing_channel_new:
* @messenger: an #FlBinaryMessenger
* @vtable: callbacks for incoming method calls.
* @user_data: data to pass in callbacks.
*
* Creates a new channel that sends handled windowing requests from the
* platform.
*
* Returns: a new #FlWindowingChannel
*/
FlWindowingChannel* fl_windowing_channel_new(FlBinaryMessenger* messenger,
FlWindowingChannelVTable* vtable,
gpointer user_data);
FlMethodResponse* fl_windowing_channel_make_create_regular_response(
int64_t view_id,
FlWindowingSize* size,
FlWindowState state);
FlMethodResponse* fl_windowing_channel_make_modify_regular_response();
FlMethodResponse* fl_windowing_channel_make_destroy_window_response();
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_WINDOWING_CHANNEL_H_

View File

@ -0,0 +1,275 @@
// 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_windowing_handler.h"
#include "flutter/shell/platform/linux/fl_windowing_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
typedef struct {
GWeakRef engine;
FlWindowingChannel* channel;
GHashTable* windows_by_view_id;
} FlWindowingHandlerPrivate;
enum { SIGNAL_CREATE_WINDOW, LAST_SIGNAL };
static guint signals[LAST_SIGNAL];
G_DEFINE_TYPE_WITH_PRIVATE(FlWindowingHandler,
fl_windowing_handler,
G_TYPE_OBJECT)
typedef struct {
GtkWindow* window;
FlView* view;
guint first_frame_cb_id;
} WindowData;
static WindowData* window_data_new(GtkWindow* window, FlView* view) {
WindowData* data = g_new0(WindowData, 1);
data->window = GTK_WINDOW(g_object_ref(window));
data->view = FL_VIEW(g_object_ref(view));
return data;
}
static void window_data_free(WindowData* data) {
g_signal_handler_disconnect(data->view, data->first_frame_cb_id);
g_object_unref(data->window);
g_object_unref(data->view);
g_free(data);
}
// Called when the first frame is received.
static void first_frame_cb(FlView* view, WindowData* data) {
gtk_window_present(data->window);
}
static WindowData* get_window_data(FlWindowingHandler* self, int64_t view_id) {
FlWindowingHandlerPrivate* priv =
reinterpret_cast<FlWindowingHandlerPrivate*>(
fl_windowing_handler_get_instance_private(self));
return static_cast<WindowData*>(
g_hash_table_lookup(priv->windows_by_view_id, GINT_TO_POINTER(view_id)));
}
static GtkWindow* fl_windowing_handler_create_window(
FlWindowingHandler* handler,
FlView* view) {
GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
return GTK_WINDOW(window);
}
static FlMethodResponse* create_regular(FlWindowingSize* size,
FlWindowingSize* min_size,
FlWindowingSize* max_size,
const gchar* title,
FlWindowState state,
gpointer user_data) {
FlWindowingHandler* self = FL_WINDOWING_HANDLER(user_data);
FlWindowingHandlerPrivate* priv =
reinterpret_cast<FlWindowingHandlerPrivate*>(
fl_windowing_handler_get_instance_private(self));
g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine));
if (engine == nullptr) {
return FL_METHOD_RESPONSE(
fl_method_error_response_new("Internal error", "No engine", nullptr));
}
FlView* view = fl_view_new_for_engine(engine);
GtkWindow* window;
g_signal_emit(self, signals[SIGNAL_CREATE_WINDOW], 0, view, &window);
if (window == nullptr) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
"Internal error", "Failed to create window", nullptr));
}
gtk_window_set_default_size(GTK_WINDOW(window), size->width, size->height);
if (title != nullptr) {
gtk_window_set_title(GTK_WINDOW(window), title);
}
switch (state) {
case FL_WINDOW_STATE_MAXIMIZED:
gtk_window_maximize(GTK_WINDOW(window));
break;
case FL_WINDOW_STATE_MINIMIZED:
gtk_window_iconify(GTK_WINDOW(window));
break;
case FL_WINDOW_STATE_RESTORED:
case FL_WINDOW_STATE_UNDEFINED:
break;
}
GdkGeometry geometry;
GdkWindowHints geometry_mask = static_cast<GdkWindowHints>(0);
if (min_size != nullptr) {
geometry.min_width = min_size->width;
geometry.min_height = min_size->height;
geometry_mask =
static_cast<GdkWindowHints>(geometry_mask | GDK_HINT_MIN_SIZE);
}
if (max_size != nullptr) {
geometry.max_width = max_size->width;
geometry.max_height = max_size->height;
geometry_mask =
static_cast<GdkWindowHints>(geometry_mask | GDK_HINT_MAX_SIZE);
}
if (geometry_mask != 0) {
gtk_window_set_geometry_hints(GTK_WINDOW(window), nullptr, &geometry,
geometry_mask);
}
WindowData* data = window_data_new(GTK_WINDOW(window), view);
data->first_frame_cb_id =
g_signal_connect(view, "first-frame", G_CALLBACK(first_frame_cb), data);
// 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_hash_table_insert(priv->windows_by_view_id,
GINT_TO_POINTER(fl_view_get_id(view)), data);
// We don't know the current size and dimensions, so just reflect back what
// was requested.
FlWindowingSize* initial_size = size;
FlWindowState initial_state = state;
if (initial_state == FL_WINDOW_STATE_UNDEFINED) {
initial_state = FL_WINDOW_STATE_RESTORED;
}
return fl_windowing_channel_make_create_regular_response(
fl_view_get_id(view), initial_size, initial_state);
}
static FlMethodResponse* modify_regular(int64_t view_id,
FlWindowingSize* size,
const gchar* title,
FlWindowState state,
gpointer user_data) {
FlWindowingHandler* self = FL_WINDOWING_HANDLER(user_data);
WindowData* data = get_window_data(self, view_id);
if (data == nullptr) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
"Bad Arguments", "No window with given view ID", nullptr));
}
if (size != nullptr) {
gtk_window_resize(data->window, size->width, size->height);
}
if (title != nullptr) {
gtk_window_set_title(data->window, title);
}
GdkWindowState window_state;
switch (state) {
case FL_WINDOW_STATE_RESTORED:
if (gtk_window_is_maximized(data->window)) {
gtk_window_unmaximize(data->window);
}
window_state =
gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(data->window)));
if (window_state & GDK_WINDOW_STATE_ICONIFIED) {
gtk_window_deiconify(data->window);
}
break;
case FL_WINDOW_STATE_MAXIMIZED:
gtk_window_maximize(data->window);
break;
case FL_WINDOW_STATE_MINIMIZED:
gtk_window_iconify(data->window);
break;
case FL_WINDOW_STATE_UNDEFINED:
break;
}
return fl_windowing_channel_make_modify_regular_response();
}
static FlMethodResponse* destroy_window(int64_t view_id, gpointer user_data) {
FlWindowingHandler* self = FL_WINDOWING_HANDLER(user_data);
FlWindowingHandlerPrivate* priv =
reinterpret_cast<FlWindowingHandlerPrivate*>(
fl_windowing_handler_get_instance_private(self));
WindowData* data = get_window_data(self, view_id);
if (data == nullptr) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
"Bad Arguments", "No window with given view ID", nullptr));
}
gtk_widget_destroy(GTK_WIDGET(data->window));
g_hash_table_remove(priv->windows_by_view_id, GINT_TO_POINTER(view_id));
return fl_windowing_channel_make_destroy_window_response();
}
static void fl_windowing_handler_dispose(GObject* object) {
FlWindowingHandler* self = FL_WINDOWING_HANDLER(object);
FlWindowingHandlerPrivate* priv =
reinterpret_cast<FlWindowingHandlerPrivate*>(
fl_windowing_handler_get_instance_private(self));
g_weak_ref_clear(&priv->engine);
g_clear_object(&priv->channel);
g_clear_pointer(&priv->windows_by_view_id, g_hash_table_unref);
G_OBJECT_CLASS(fl_windowing_handler_parent_class)->dispose(object);
}
static void fl_windowing_handler_class_init(FlWindowingHandlerClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_windowing_handler_dispose;
klass->create_window = fl_windowing_handler_create_window;
signals[SIGNAL_CREATE_WINDOW] = g_signal_new(
"create-window", fl_windowing_handler_get_type(), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(FlWindowingHandlerClass, create_window),
g_signal_accumulator_first_wins, nullptr, nullptr, GTK_TYPE_WINDOW, 1,
fl_view_get_type());
}
static void fl_windowing_handler_init(FlWindowingHandler* self) {
FlWindowingHandlerPrivate* priv =
reinterpret_cast<FlWindowingHandlerPrivate*>(
fl_windowing_handler_get_instance_private(self));
priv->windows_by_view_id =
g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr,
reinterpret_cast<GDestroyNotify>(window_data_free));
}
static FlWindowingChannelVTable windowing_channel_vtable = {
.create_regular = create_regular,
.modify_regular = modify_regular,
.destroy_window = destroy_window,
};
FlWindowingHandler* fl_windowing_handler_new(FlEngine* engine) {
g_return_val_if_fail(FL_IS_ENGINE(engine), nullptr);
FlWindowingHandler* self = FL_WINDOWING_HANDLER(
g_object_new(fl_windowing_handler_get_type(), nullptr));
FlWindowingHandlerPrivate* priv =
reinterpret_cast<FlWindowingHandlerPrivate*>(
fl_windowing_handler_get_instance_private(self));
g_weak_ref_init(&priv->engine, engine);
priv->channel = fl_windowing_channel_new(
fl_engine_get_binary_messenger(engine), &windowing_channel_vtable, self);
return self;
}

View File

@ -0,0 +1,47 @@
// 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_WINDOWING_HANDLER_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_WINDOWING_HANDLER_H_
#include <gtk/gtk.h>
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
G_BEGIN_DECLS
G_DECLARE_DERIVABLE_TYPE(FlWindowingHandler,
fl_windowing_handler,
FL,
WINDOWING_HANDLER,
GObject);
struct _FlWindowingHandlerClass {
GObjectClass parent_class;
GtkWindow* (*create_window)(FlWindowingHandler* handler, FlView* view);
};
/**
* FlWindowingHandler:
*
* #FlWindowingHandler is a handler that implements the shell side
* of SystemChannels.windowing from the Flutter services library.
*/
/**
* fl_windowing_handler_new:
* @engine: an #FlEngine.
*
* Creates a new handler that implements SystemChannels.windowing from the
* Flutter services library.
*
* Returns: a new #FlWindowingHandler
*/
FlWindowingHandler* fl_windowing_handler_new(FlEngine* engine);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_WINDOWING_HANDLER_H_

View File

@ -0,0 +1,640 @@
// 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_windowing_handler.h"
#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
#include "flutter/shell/platform/linux/fl_engine_private.h"
#include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h"
#include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
#include "flutter/shell/platform/linux/testing/mock_window.h"
#include "flutter/testing/testing.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
static void set_size_arg(FlValue* args,
const gchar* name,
double width,
double height) {
g_autoptr(FlValue) size_value = fl_value_new_list();
fl_value_append_take(size_value, fl_value_new_float(width));
fl_value_append_take(size_value, fl_value_new_float(height));
fl_value_set_string(args, name, size_value);
}
static FlValue* make_create_regular_args(double width, double height) {
FlValue* args = fl_value_new_map();
set_size_arg(args, "size", width, height);
return args;
}
static int64_t parse_create_regular_response(FlMethodResponse* response) {
EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
FlValue* result = fl_method_success_response_get_result(
FL_METHOD_SUCCESS_RESPONSE(response));
EXPECT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_MAP);
FlValue* view_id_value = fl_value_lookup_string(result, "viewId");
EXPECT_NE(view_id_value, nullptr);
EXPECT_EQ(fl_value_get_type(view_id_value), FL_VALUE_TYPE_INT);
int64_t view_id = fl_value_get_int(view_id_value);
EXPECT_GT(view_id, 0);
FlValue* size_value = fl_value_lookup_string(result, "size");
EXPECT_NE(size_value, nullptr);
EXPECT_EQ(fl_value_get_type(size_value), FL_VALUE_TYPE_LIST);
EXPECT_EQ(fl_value_get_length(size_value), 2u);
EXPECT_EQ(fl_value_get_type(fl_value_get_list_value(size_value, 0)),
FL_VALUE_TYPE_FLOAT);
EXPECT_EQ(fl_value_get_type(fl_value_get_list_value(size_value, 1)),
FL_VALUE_TYPE_FLOAT);
FlValue* state_value = fl_value_lookup_string(result, "state");
EXPECT_NE(state_value, nullptr);
EXPECT_EQ(fl_value_get_type(state_value), FL_VALUE_TYPE_STRING);
return view_id;
}
static FlValue* make_modify_regular_args(int64_t view_id) {
FlValue* args = fl_value_new_map();
fl_value_set_string_take(args, "viewId", fl_value_new_int(view_id));
return args;
}
static FlValue* make_destroy_window_args(int64_t view_id) {
FlValue* args = fl_value_new_map();
fl_value_set_string_take(args, "viewId", fl_value_new_int(view_id));
return args;
}
TEST(FlWindowingHandlerTest, CreateRegular) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window, gtk_window_set_default_size(::testing::_, 800, 600));
g_autoptr(FlValue) args = make_create_regular_args(800, 600);
gboolean called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
parse_create_regular_response(response);
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&called);
EXPECT_TRUE(called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, CreateRegularMinSize) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window,
gtk_window_set_geometry_hints(
::testing::_, nullptr,
::testing::Pointee(::testing::AllOf(
::testing::Field(&GdkGeometry::min_width, 100),
::testing::Field(&GdkGeometry::min_height, 200))),
GDK_HINT_MIN_SIZE));
g_autoptr(FlValue) args = make_create_regular_args(800, 600);
set_size_arg(args, "minSize", 100, 200);
gboolean called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
parse_create_regular_response(response);
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&called);
EXPECT_TRUE(called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, CreateRegularMaxSize) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window,
gtk_window_set_geometry_hints(
::testing::_, nullptr,
::testing::Pointee(::testing::AllOf(
::testing::Field(&GdkGeometry::max_width, 1000),
::testing::Field(&GdkGeometry::max_height, 2000))),
GDK_HINT_MAX_SIZE));
g_autoptr(FlValue) args = make_create_regular_args(800, 600);
set_size_arg(args, "maxSize", 1000, 2000);
gboolean called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
parse_create_regular_response(response);
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&called);
EXPECT_TRUE(called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, CreateRegularWithTitle) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window,
gtk_window_set_title(::testing::_, ::testing::StrEq("TITLE")));
g_autoptr(FlValue) args = make_create_regular_args(800, 600);
fl_value_set_string_take(args, "title", fl_value_new_string("TITLE"));
gboolean called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
parse_create_regular_response(response);
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&called);
EXPECT_TRUE(called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, CreateRegularMaximized) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window, gtk_window_maximize(::testing::_));
g_autoptr(FlValue) args = make_create_regular_args(800, 600);
fl_value_set_string_take(args, "state",
fl_value_new_string("WindowState.maximized"));
gboolean called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
parse_create_regular_response(response);
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&called);
EXPECT_TRUE(called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, CreateRegularMinimized) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window, gtk_window_iconify(::testing::_));
g_autoptr(FlValue) args = make_create_regular_args(800, 600);
fl_value_set_string_take(args, "state",
fl_value_new_string("WindowState.minimized"));
gboolean called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
parse_create_regular_response(response);
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&called);
EXPECT_TRUE(called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, ModifyRegularSize) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window, gtk_window_resize(::testing::_, 1024, 768));
g_autoptr(FlValue) create_args = make_create_regular_args(800, 600);
int64_t view_id = -1;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", create_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
int64_t* view_id = static_cast<int64_t*>(user_data);
*view_id = parse_create_regular_response(response);
},
&view_id);
EXPECT_GT(view_id, 0);
g_autoptr(FlValue) modify_args = make_modify_regular_args(view_id);
set_size_arg(modify_args, "size", 1024, 768);
gboolean modify_called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "modifyRegular", modify_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&modify_called);
EXPECT_TRUE(modify_called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, ModifyRegularTitle) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window,
gtk_window_set_title(::testing::_, ::testing::StrEq("TITLE")));
g_autoptr(FlValue) create_args = make_create_regular_args(800, 600);
int64_t view_id = -1;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", create_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
int64_t* view_id = static_cast<int64_t*>(user_data);
*view_id = parse_create_regular_response(response);
},
&view_id);
EXPECT_GT(view_id, 0);
g_autoptr(FlValue) modify_args = make_modify_regular_args(view_id);
fl_value_set_string_take(modify_args, "title", fl_value_new_string("TITLE"));
gboolean modify_called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "modifyRegular", modify_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&modify_called);
EXPECT_TRUE(modify_called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, ModifyRegularMaximize) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
g_autoptr(FlValue) create_args = make_create_regular_args(800, 600);
int64_t view_id = -1;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", create_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
int64_t* view_id = static_cast<int64_t*>(user_data);
*view_id = parse_create_regular_response(response);
},
&view_id);
EXPECT_GT(view_id, 0);
EXPECT_CALL(mock_window, gtk_window_maximize(::testing::_));
g_autoptr(FlValue) modify_args = make_modify_regular_args(view_id);
fl_value_set_string_take(modify_args, "state",
fl_value_new_string("WindowState.maximized"));
gboolean modify_called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "modifyRegular", modify_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&modify_called);
EXPECT_TRUE(modify_called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, ModifyRegularUnmaximize) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window, gtk_window_maximize(::testing::_));
g_autoptr(FlValue) create_args = make_create_regular_args(800, 600);
fl_value_set_string_take(create_args, "state",
fl_value_new_string("WindowState.maximized"));
int64_t view_id = -1;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", create_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
int64_t* view_id = static_cast<int64_t*>(user_data);
*view_id = parse_create_regular_response(response);
},
&view_id);
EXPECT_GT(view_id, 0);
EXPECT_CALL(mock_window, gtk_window_is_maximized(::testing::_))
.WillOnce(::testing::Return(TRUE));
EXPECT_CALL(mock_window, gtk_window_unmaximize(::testing::_));
g_autoptr(FlValue) modify_args = make_modify_regular_args(view_id);
fl_value_set_string_take(modify_args, "state",
fl_value_new_string("WindowState.restored"));
gboolean modify_called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "modifyRegular", modify_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&modify_called);
EXPECT_TRUE(modify_called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, ModifyRegularMinimize) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
g_autoptr(FlValue) create_args = make_create_regular_args(800, 600);
int64_t view_id = -1;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", create_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
int64_t* view_id = static_cast<int64_t*>(user_data);
*view_id = parse_create_regular_response(response);
},
&view_id);
EXPECT_GT(view_id, 0);
EXPECT_CALL(mock_window, gtk_window_iconify(::testing::_));
g_autoptr(FlValue) modify_args = make_modify_regular_args(view_id);
fl_value_set_string_take(modify_args, "state",
fl_value_new_string("WindowState.minimized"));
gboolean modify_called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "modifyRegular", modify_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&modify_called);
EXPECT_TRUE(modify_called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, ModifyRegularUnminimize) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window, gtk_window_iconify(::testing::_));
g_autoptr(FlValue) create_args = make_create_regular_args(800, 600);
fl_value_set_string_take(create_args, "state",
fl_value_new_string("WindowState.minimized"));
int64_t view_id = -1;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", create_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
int64_t* view_id = static_cast<int64_t*>(user_data);
*view_id = parse_create_regular_response(response);
},
&view_id);
EXPECT_GT(view_id, 0);
EXPECT_CALL(mock_window, gdk_window_get_state(::testing::_))
.WillOnce(::testing::Return(GDK_WINDOW_STATE_ICONIFIED));
EXPECT_CALL(mock_window, gtk_window_deiconify(::testing::_));
g_autoptr(FlValue) modify_args = make_modify_regular_args(view_id);
fl_value_set_string_take(modify_args, "state",
fl_value_new_string("WindowState.restored"));
gboolean modify_called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "modifyRegular", modify_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&modify_called);
EXPECT_TRUE(modify_called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, ModifyUnknownWindow) {
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
g_autoptr(FlValue) args = make_modify_regular_args(99);
gboolean called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "modifyRegular", args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response));
EXPECT_STREQ(fl_method_error_response_get_code(
FL_METHOD_ERROR_RESPONSE(response)),
"Bad Arguments");
EXPECT_STREQ(fl_method_error_response_get_message(
FL_METHOD_ERROR_RESPONSE(response)),
"No window with given view ID");
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&called);
EXPECT_TRUE(called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, DestroyWindow) {
flutter::testing::fl_ensure_gtk_init();
::testing::NiceMock<flutter::testing::MockWindow> mock_window;
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
EXPECT_CALL(mock_window, gtk_window_new);
EXPECT_CALL(mock_window, gtk_widget_destroy);
g_autoptr(FlValue) create_args = make_create_regular_args(800, 600);
int64_t view_id = -1;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "createRegular", create_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
int64_t* view_id = static_cast<int64_t*>(user_data);
*view_id = parse_create_regular_response(response);
},
&view_id);
EXPECT_GT(view_id, 0);
g_autoptr(FlValue) destroy_args = make_destroy_window_args(view_id);
gboolean destroy_called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "destroyWindow", destroy_args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&destroy_called);
EXPECT_TRUE(destroy_called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}
TEST(FlWindowingHandlerTest, DestroyUnknownWindow) {
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
g_autoptr(FlEngine) engine =
fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger));
g_autoptr(FlWindowingHandler) handler = fl_windowing_handler_new(engine);
g_autoptr(FlValue) args = make_destroy_window_args(99);
gboolean called = FALSE;
fl_mock_binary_messenger_invoke_standard_method(
messenger, "flutter/windowing", "destroyWindow", args,
[](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
gpointer user_data) {
EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response));
EXPECT_STREQ(fl_method_error_response_get_code(
FL_METHOD_ERROR_RESPONSE(response)),
"Bad Arguments");
EXPECT_STREQ(fl_method_error_response_get_message(
FL_METHOD_ERROR_RESPONSE(response)),
"No window with given view ID");
gboolean* called = static_cast<gboolean*>(user_data);
*called = TRUE;
},
&called);
EXPECT_TRUE(called);
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
}

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <dlfcn.h>
#include "flutter/shell/platform/linux/testing/mock_window.h"
using namespace flutter::testing;
@ -50,3 +52,60 @@ GdkCursor* gdk_cursor_new_from_name(GdkDisplay* display, const gchar* name) {
}
void gdk_window_set_cursor(GdkWindow* window, GdkCursor* cursor) {}
GtkWidget* gtk_window_new(GtkWindowType type) {
GtkWindow* window = GTK_WINDOW(g_object_new(gtk_window_get_type(), nullptr));
mock->gtk_window_new(window, type);
return GTK_WIDGET(window);
}
void gtk_window_set_default_size(GtkWindow* window, gint width, gint height) {
mock->gtk_window_set_default_size(window, width, height);
}
void gtk_window_set_title(GtkWindow* window, const gchar* title) {
mock->gtk_window_set_title(window, title);
}
void gtk_window_set_geometry_hints(GtkWindow* window,
GtkWidget* widget,
GdkGeometry* geometry,
GdkWindowHints geometry_mask) {
mock->gtk_window_set_geometry_hints(window, widget, geometry, geometry_mask);
}
void gtk_window_resize(GtkWindow* window, gint width, gint height) {
mock->gtk_window_resize(window, width, height);
}
void gtk_window_maximize(GtkWindow* window) {
mock->gtk_window_maximize(window);
}
void gtk_window_unmaximize(GtkWindow* window) {
mock->gtk_window_unmaximize(window);
}
gboolean gtk_window_is_maximized(GtkWindow* window) {
return mock->gtk_window_is_maximized(window);
}
void gtk_window_iconify(GtkWindow* window) {
mock->gtk_window_iconify(window);
}
void gtk_window_deiconify(GtkWindow* window) {
mock->gtk_window_deiconify(window);
}
void gtk_widget_show(GtkWidget* widget) {}
void gtk_widget_destroy(GtkWidget* widget) {
mock->gtk_widget_destroy(widget);
}
void fl_gtk_widget_destroy(GtkWidget* widget) {
void (*destroy)(GtkWidget*) = reinterpret_cast<void (*)(GtkWidget*)>(
dlsym(RTLD_NEXT, "gtk_widget_destroy"));
destroy(widget);
}

View File

@ -18,9 +18,34 @@ class MockWindow {
~MockWindow();
MOCK_METHOD(GdkWindowState, gdk_window_get_state, (GdkWindow * window));
MOCK_METHOD(void, gtk_window_new, (GtkWindow * window, GtkWindowType type));
MOCK_METHOD(void,
gtk_window_set_default_size,
(GtkWindow * window, gint width, gint height));
MOCK_METHOD(void,
gtk_window_set_title,
(GtkWindow * window, const gchar* title));
MOCK_METHOD(void,
gtk_window_set_geometry_hints,
(GtkWindow * window,
GtkWidget* widget,
GdkGeometry* geometry,
GdkWindowHints geometry_mask));
MOCK_METHOD(void,
gtk_window_resize,
(GtkWindow * window, gint width, gint height));
MOCK_METHOD(void, gtk_window_maximize, (GtkWindow * window));
MOCK_METHOD(void, gtk_window_unmaximize, (GtkWindow * window));
MOCK_METHOD(gboolean, gtk_window_is_maximized, (GtkWindow * window));
MOCK_METHOD(void, gtk_window_iconify, (GtkWindow * window));
MOCK_METHOD(void, gtk_window_deiconify, (GtkWindow * window));
MOCK_METHOD(void, gtk_widget_destroy, (GtkWidget * widget));
};
} // namespace testing
} // namespace flutter
// Calls original gtk_widget_destroy.
void fl_gtk_widget_destroy(GtkWidget* widget);
#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_WINDOW_H_