Split channel messaging out of handlers (flutter/engine#56667)

Split the channel messaging out of the system channels to make them
simpler to understand and refactor.

The new channel classes could be automatically generated in a second
phase, e.g. using Pigeon to reduce code usage.

The new classes don't have tests as they will already be covered by the
existing code.
This commit is contained in:
Robert Ancell 2024-11-21 09:32:20 +13:00 committed by GitHub
parent b8e58bbb0b
commit 56686525b2
17 changed files with 1712 additions and 595 deletions

View File

@ -44986,6 +44986,8 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_private.
ORIGIN: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_test.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_key_event.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_key_event.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_key_event.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_key_event.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_channel.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_channel.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc + ../../../flutter/LICENSE
@ -45011,11 +45013,15 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_method_codec_private.h + ../../
ORIGIN: ../../../flutter/shell/platform/linux/fl_method_codec_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_method_codec_test.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_method_response.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_method_response.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_method_response_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_method_response_test.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_channel.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_channel.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_private.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_private.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_test.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_channel.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_channel.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_handler.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_handler.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_handler_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_handler_test.cc + ../../../flutter/LICENSE
@ -45040,6 +45046,8 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_scrolling_manager.h + ../../../
ORIGIN: ../../../flutter/shell/platform/linux/fl_scrolling_manager_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_scrolling_manager_test.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_settings.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_settings.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_channel.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_channel.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_handler.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_handler.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_handler_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_handler_test.cc + ../../../flutter/LICENSE
@ -45056,6 +45064,8 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_string_codec.cc + ../../../flut
ORIGIN: ../../../flutter/shell/platform/linux/fl_string_codec_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_string_codec_test.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_task_runner.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_task_runner.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_task_runner.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_task_runner.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_channel.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_channel.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_handler.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_handler.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_handler_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_handler_test.cc + ../../../flutter/LICENSE
@ -47886,6 +47896,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_private.h
FILE: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_test.cc FILE: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_event.cc FILE: ../../../flutter/shell/platform/linux/fl_key_event.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_event.h FILE: ../../../flutter/shell/platform/linux/fl_key_event.h
FILE: ../../../flutter/shell/platform/linux/fl_keyboard_channel.cc
FILE: ../../../flutter/shell/platform/linux/fl_keyboard_channel.h
FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler.cc
FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler.h FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler.h
FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc
@ -47911,11 +47923,15 @@ FILE: ../../../flutter/shell/platform/linux/fl_method_codec_private.h
FILE: ../../../flutter/shell/platform/linux/fl_method_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_method_codec_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_method_response.cc FILE: ../../../flutter/shell/platform/linux/fl_method_response.cc
FILE: ../../../flutter/shell/platform/linux/fl_method_response_test.cc FILE: ../../../flutter/shell/platform/linux/fl_method_response_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_channel.cc
FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_channel.h
FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.cc
FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.h FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.h
FILE: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture.cc FILE: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture.cc
FILE: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_private.h FILE: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_private.h
FILE: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_test.cc FILE: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_platform_channel.cc
FILE: ../../../flutter/shell/platform/linux/fl_platform_channel.h
FILE: ../../../flutter/shell/platform/linux/fl_platform_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_platform_handler.cc
FILE: ../../../flutter/shell/platform/linux/fl_platform_handler.h FILE: ../../../flutter/shell/platform/linux/fl_platform_handler.h
FILE: ../../../flutter/shell/platform/linux/fl_platform_handler_test.cc FILE: ../../../flutter/shell/platform/linux/fl_platform_handler_test.cc
@ -47940,6 +47956,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_scrolling_manager.h
FILE: ../../../flutter/shell/platform/linux/fl_scrolling_manager_test.cc FILE: ../../../flutter/shell/platform/linux/fl_scrolling_manager_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings.cc FILE: ../../../flutter/shell/platform/linux/fl_settings.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings.h FILE: ../../../flutter/shell/platform/linux/fl_settings.h
FILE: ../../../flutter/shell/platform/linux/fl_settings_channel.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings_channel.h
FILE: ../../../flutter/shell/platform/linux/fl_settings_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_settings_handler.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings_handler.h FILE: ../../../flutter/shell/platform/linux/fl_settings_handler.h
FILE: ../../../flutter/shell/platform/linux/fl_settings_handler_test.cc FILE: ../../../flutter/shell/platform/linux/fl_settings_handler_test.cc
@ -47956,6 +47974,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_string_codec.cc
FILE: ../../../flutter/shell/platform/linux/fl_string_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_string_codec_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_task_runner.cc FILE: ../../../flutter/shell/platform/linux/fl_task_runner.cc
FILE: ../../../flutter/shell/platform/linux/fl_task_runner.h FILE: ../../../flutter/shell/platform/linux/fl_task_runner.h
FILE: ../../../flutter/shell/platform/linux/fl_text_input_channel.cc
FILE: ../../../flutter/shell/platform/linux/fl_text_input_channel.h
FILE: ../../../flutter/shell/platform/linux/fl_text_input_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_text_input_handler.cc
FILE: ../../../flutter/shell/platform/linux/fl_text_input_handler.h FILE: ../../../flutter/shell/platform/linux/fl_text_input_handler.h
FILE: ../../../flutter/shell/platform/linux/fl_text_input_handler_test.cc FILE: ../../../flutter/shell/platform/linux/fl_text_input_handler_test.cc

View File

@ -116,6 +116,7 @@ source_set("flutter_linux_sources") {
"fl_key_channel_responder.cc", "fl_key_channel_responder.cc",
"fl_key_embedder_responder.cc", "fl_key_embedder_responder.cc",
"fl_key_event.cc", "fl_key_event.cc",
"fl_keyboard_channel.cc",
"fl_keyboard_handler.cc", "fl_keyboard_handler.cc",
"fl_keyboard_layout.cc", "fl_keyboard_layout.cc",
"fl_keyboard_manager.cc", "fl_keyboard_manager.cc",
@ -126,8 +127,10 @@ source_set("flutter_linux_sources") {
"fl_method_channel.cc", "fl_method_channel.cc",
"fl_method_codec.cc", "fl_method_codec.cc",
"fl_method_response.cc", "fl_method_response.cc",
"fl_mouse_cursor_channel.cc",
"fl_mouse_cursor_handler.cc", "fl_mouse_cursor_handler.cc",
"fl_pixel_buffer_texture.cc", "fl_pixel_buffer_texture.cc",
"fl_platform_channel.cc",
"fl_platform_handler.cc", "fl_platform_handler.cc",
"fl_plugin_registrar.cc", "fl_plugin_registrar.cc",
"fl_plugin_registry.cc", "fl_plugin_registry.cc",
@ -138,6 +141,7 @@ source_set("flutter_linux_sources") {
"fl_renderer_headless.cc", "fl_renderer_headless.cc",
"fl_scrolling_manager.cc", "fl_scrolling_manager.cc",
"fl_settings.cc", "fl_settings.cc",
"fl_settings_channel.cc",
"fl_settings_handler.cc", "fl_settings_handler.cc",
"fl_settings_portal.cc", "fl_settings_portal.cc",
"fl_socket_accessible.cc", "fl_socket_accessible.cc",
@ -146,6 +150,7 @@ source_set("flutter_linux_sources") {
"fl_string_codec.cc", "fl_string_codec.cc",
"fl_task_runner.cc", "fl_task_runner.cc",
"fl_task_runner.h", "fl_task_runner.h",
"fl_text_input_channel.cc",
"fl_text_input_handler.cc", "fl_text_input_handler.cc",
"fl_text_input_view_delegate.cc", "fl_text_input_view_delegate.cc",
"fl_texture.cc", "fl_texture.cc",

View File

@ -0,0 +1,84 @@
// 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_keyboard_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/keyboard";
static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
struct _FlKeyboardChannel {
GObject parent_instance;
FlMethodChannel* channel;
FlKeyboardChannelVTable* vtable;
gpointer user_data;
};
G_DEFINE_TYPE(FlKeyboardChannel, fl_keyboard_channel, G_TYPE_OBJECT)
static FlMethodResponse* get_keyboard_state(FlKeyboardChannel* self) {
g_autoptr(FlValue) result = self->vtable->get_keyboard_state(self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}
// Called when a method call is received from Flutter.
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlKeyboardChannel* self = FL_KEYBOARD_CHANNEL(user_data);
const gchar* method = fl_method_call_get_name(method_call);
g_autoptr(FlMethodResponse) response = nullptr;
if (strcmp(method, kGetKeyboardStateMethod) == 0) {
response = get_keyboard_state(self);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
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_keyboard_channel_dispose(GObject* object) {
FlKeyboardChannel* self = FL_KEYBOARD_CHANNEL(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_keyboard_channel_parent_class)->dispose(object);
}
static void fl_keyboard_channel_class_init(FlKeyboardChannelClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_keyboard_channel_dispose;
}
static void fl_keyboard_channel_init(FlKeyboardChannel* self) {}
FlKeyboardChannel* fl_keyboard_channel_new(FlBinaryMessenger* messenger,
FlKeyboardChannelVTable* vtable,
gpointer user_data) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
g_return_val_if_fail(vtable != nullptr, nullptr);
FlKeyboardChannel* self = FL_KEYBOARD_CHANNEL(
g_object_new(fl_keyboard_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;
}

View File

@ -0,0 +1,46 @@
// 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_KEYBOARD_CHANNEL_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_CHANNEL_H_
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(FlKeyboardChannel,
fl_keyboard_channel,
FL,
KEYBOARD_CHANNEL,
GObject);
/**
* FlKeyboardChannel:
*
* #FlKeyboardChannel is a channel that implements the shell side
* of SystemChannels.keyboard from the Flutter services library.
*/
typedef struct {
FlValue* (*get_keyboard_state)(gpointer user_data);
} FlKeyboardChannelVTable;
/**
* fl_keyboard_channel_new:
* @messenger: an #FlBinaryMessenger
* @vtable: callbacks for incoming method calls.
* @user_data: data to pass in callbacks.
*
* Creates a new channel that implements SystemChannels.keyboard from the
* Flutter services library.
*
* Returns: a new #FlKeyboardChannel
*/
FlKeyboardChannel* fl_keyboard_channel_new(FlBinaryMessenger* messenger,
FlKeyboardChannelVTable* vtable,
gpointer user_data);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_CHANNEL_H_

View File

@ -4,11 +4,7 @@
#include "flutter/shell/platform/linux/fl_keyboard_handler.h" #include "flutter/shell/platform/linux/fl_keyboard_handler.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" #include "flutter/shell/platform/linux/fl_keyboard_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
static constexpr char kChannelName[] = "flutter/keyboard";
static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
struct _FlKeyboardHandler { struct _FlKeyboardHandler {
GObject parent_instance; GObject parent_instance;
@ -16,14 +12,16 @@ struct _FlKeyboardHandler {
FlKeyboardManager* keyboard_manager; FlKeyboardManager* keyboard_manager;
// The channel used by the framework to query the keyboard pressed state. // The channel used by the framework to query the keyboard pressed state.
FlMethodChannel* channel; FlKeyboardChannel* channel;
}; };
G_DEFINE_TYPE(FlKeyboardHandler, fl_keyboard_handler, G_TYPE_OBJECT); G_DEFINE_TYPE(FlKeyboardHandler, fl_keyboard_handler, G_TYPE_OBJECT);
// Returns the keyboard pressed state. // Returns the keyboard pressed state.
static FlMethodResponse* get_keyboard_state(FlKeyboardHandler* self) { static FlValue* get_keyboard_state(gpointer user_data) {
g_autoptr(FlValue) result = fl_value_new_map(); FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(user_data);
FlValue* result = fl_value_new_map();
GHashTable* pressing_records = GHashTable* pressing_records =
fl_keyboard_manager_get_pressed_state(self->keyboard_manager); fl_keyboard_manager_get_pressed_state(self->keyboard_manager);
@ -39,28 +37,8 @@ static FlMethodResponse* get_keyboard_state(FlKeyboardHandler* self) {
fl_value_new_int(logical_key)); fl_value_new_int(logical_key));
}, },
result); result);
return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}
// Called when a method call on flutter/keyboard is received from Flutter. return result;
static void method_call_handler(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(user_data);
const gchar* method = fl_method_call_get_name(method_call);
g_autoptr(FlMethodResponse) response = nullptr;
if (strcmp(method, kGetKeyboardStateMethod) == 0) {
response = get_keyboard_state(self);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
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_keyboard_handler_dispose(GObject* object) { static void fl_keyboard_handler_dispose(GObject* object) {
@ -76,6 +54,9 @@ static void fl_keyboard_handler_class_init(FlKeyboardHandlerClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_dispose; G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_dispose;
} }
static FlKeyboardChannelVTable keyboard_channel_vtable = {
.get_keyboard_state = get_keyboard_state};
static void fl_keyboard_handler_init(FlKeyboardHandler* self) {} static void fl_keyboard_handler_init(FlKeyboardHandler* self) {}
FlKeyboardHandler* fl_keyboard_handler_new( FlKeyboardHandler* fl_keyboard_handler_new(
@ -87,10 +68,7 @@ FlKeyboardHandler* fl_keyboard_handler_new(
self->keyboard_manager = FL_KEYBOARD_MANAGER(g_object_ref(keyboard_manager)); self->keyboard_manager = FL_KEYBOARD_MANAGER(g_object_ref(keyboard_manager));
// Setup the flutter/keyboard channel. // Setup the flutter/keyboard channel.
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->channel = self->channel =
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); fl_keyboard_channel_new(messenger, &keyboard_channel_vtable, self);
fl_method_channel_set_method_call_handler(self->channel, method_call_handler,
self, nullptr);
return self; return self;
} }

View File

@ -0,0 +1,104 @@
// 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_mouse_cursor_channel.h"
#include <cstring>
#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/mousecursor";
static constexpr char kBadArgumentsError[] = "Bad Arguments";
static constexpr char kActivateSystemCursorMethod[] = "activateSystemCursor";
static constexpr char kKindKey[] = "kind";
struct _FlMouseCursorChannel {
GObject parent_instance;
FlMethodChannel* channel;
FlMouseCursorChannelVTable* vtable;
gpointer user_data;
};
G_DEFINE_TYPE(FlMouseCursorChannel, fl_mouse_cursor_channel, G_TYPE_OBJECT)
// Sets the mouse cursor.
static FlMethodResponse* activate_system_cursor(FlMouseCursorChannel* 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* kind_value = fl_value_lookup_string(args, kKindKey);
const gchar* kind = nullptr;
if (fl_value_get_type(kind_value) == FL_VALUE_TYPE_STRING) {
kind = fl_value_get_string(kind_value);
}
self->vtable->activate_system_cursor(kind, self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Called when a method call is received from Flutter.
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlMouseCursorChannel* self = FL_MOUSE_CURSOR_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, kActivateSystemCursorMethod) == 0) {
response = activate_system_cursor(self, args);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
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_mouse_cursor_channel_dispose(GObject* object) {
FlMouseCursorChannel* self = FL_MOUSE_CURSOR_CHANNEL(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_mouse_cursor_channel_parent_class)->dispose(object);
}
static void fl_mouse_cursor_channel_class_init(
FlMouseCursorChannelClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_mouse_cursor_channel_dispose;
}
static void fl_mouse_cursor_channel_init(FlMouseCursorChannel* self) {}
FlMouseCursorChannel* fl_mouse_cursor_channel_new(
FlBinaryMessenger* messenger,
FlMouseCursorChannelVTable* vtable,
gpointer user_data) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
FlMouseCursorChannel* self = FL_MOUSE_CURSOR_CHANNEL(
g_object_new(fl_mouse_cursor_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;
}

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_MOUSE_CURSOR_CHANNEL_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_MOUSE_CURSOR_CHANNEL_H_
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(FlMouseCursorChannel,
fl_mouse_cursor_channel,
FL,
MOUSE_CURSOR_CHANNEL,
GObject);
/**
* FlMouseCursorChannel:
*
* #FlMouseCursorChannel is a cursor channel that implements the shell
* side of SystemChannels.mouseCursor from the Flutter services library.
*/
typedef struct {
void (*activate_system_cursor)(const gchar* kind, gpointer user_data);
} FlMouseCursorChannelVTable;
/**
* fl_mouse_cursor_channel_new:
* @messenger: an #FlBinaryMessenger.
* @vtable: callbacks for incoming method calls.
* @user_data: data to pass in callbacks.
*
* Creates a new channel that implements SystemChannels.mouseCursor from the
* Flutter services library.
*
* Returns: a new #FlMouseCursorChannel.
*/
FlMouseCursorChannel* fl_mouse_cursor_channel_new(
FlBinaryMessenger* messenger,
FlMouseCursorChannelVTable* vtable,
gpointer user_data);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_MOUSE_CURSOR_CHANNEL_H_

View File

@ -6,20 +6,14 @@
#include <cstring> #include <cstring>
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" #include "flutter/shell/platform/linux/fl_mouse_cursor_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
static constexpr char kChannelName[] = "flutter/mousecursor";
static constexpr char kBadArgumentsError[] = "Bad Arguments";
static constexpr char kActivateSystemCursorMethod[] = "activateSystemCursor";
static constexpr char kKindKey[] = "kind";
static constexpr char kFallbackCursor[] = "default"; static constexpr char kFallbackCursor[] = "default";
struct _FlMouseCursorHandler { struct _FlMouseCursorHandler {
GObject parent_instance; GObject parent_instance;
FlMethodChannel* channel; FlMouseCursorChannel* channel;
GHashTable* system_cursor_table; GHashTable* system_cursor_table;
@ -89,18 +83,8 @@ static void populate_system_cursor_table(GHashTable* table) {
} }
// Sets the mouse cursor. // Sets the mouse cursor.
FlMethodResponse* activate_system_cursor(FlMouseCursorHandler* self, static void activate_system_cursor(const gchar* kind, gpointer user_data) {
FlValue* args) { FlMouseCursorHandler* self = FL_MOUSE_CURSOR_HANDLER(user_data);
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* kind_value = fl_value_lookup_string(args, kKindKey);
const gchar* kind = nullptr;
if (fl_value_get_type(kind_value) == FL_VALUE_TYPE_STRING) {
kind = fl_value_get_string(kind_value);
}
if (self->system_cursor_table == nullptr) { if (self->system_cursor_table == nullptr) {
self->system_cursor_table = g_hash_table_new(g_str_hash, g_str_equal); self->system_cursor_table = g_hash_table_new(g_str_hash, g_str_equal);
@ -117,30 +101,6 @@ FlMethodResponse* activate_system_cursor(FlMouseCursorHandler* self,
self->cursor_name = g_strdup(cursor_name); self->cursor_name = g_strdup(cursor_name);
g_signal_emit(self, fl_mouse_cursor_handler_signals[kSignalCursorChanged], 0); g_signal_emit(self, fl_mouse_cursor_handler_signals[kSignalCursorChanged], 0);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Called when a method call is received from Flutter.
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlMouseCursorHandler* self = FL_MOUSE_CURSOR_HANDLER(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, kActivateSystemCursorMethod) == 0) {
response = activate_system_cursor(self, args);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
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_mouse_cursor_handler_dispose(GObject* object) { static void fl_mouse_cursor_handler_dispose(GObject* object) {
@ -166,6 +126,10 @@ static void fl_mouse_cursor_handler_init(FlMouseCursorHandler* self) {
self->cursor_name = g_strdup(""); self->cursor_name = g_strdup("");
} }
static FlMouseCursorChannelVTable mouse_cursor_vtable = {
.activate_system_cursor = activate_system_cursor,
};
FlMouseCursorHandler* fl_mouse_cursor_handler_new( FlMouseCursorHandler* fl_mouse_cursor_handler_new(
FlBinaryMessenger* messenger) { FlBinaryMessenger* messenger) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
@ -173,11 +137,8 @@ FlMouseCursorHandler* fl_mouse_cursor_handler_new(
FlMouseCursorHandler* self = FL_MOUSE_CURSOR_HANDLER( FlMouseCursorHandler* self = FL_MOUSE_CURSOR_HANDLER(
g_object_new(fl_mouse_cursor_handler_get_type(), nullptr)); g_object_new(fl_mouse_cursor_handler_get_type(), nullptr));
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->channel = self->channel =
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); fl_mouse_cursor_channel_new(messenger, &mouse_cursor_vtable, self);
fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self,
nullptr);
return self; return self;
} }

View File

@ -0,0 +1,347 @@
// 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_platform_channel.h"
#include <cstring>
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
static constexpr char kChannelName[] = "flutter/platform";
static constexpr char kBadArgumentsError[] = "Bad Arguments";
static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData";
static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData";
static constexpr char kClipboardHasStringsMethod[] = "Clipboard.hasStrings";
static constexpr char kExitApplicationMethod[] = "System.exitApplication";
static constexpr char kRequestAppExitMethod[] = "System.requestAppExit";
static constexpr char kInitializationCompleteMethod[] =
"System.initializationComplete";
static constexpr char kPlaySoundMethod[] = "SystemSound.play";
static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop";
static constexpr char kTextKey[] = "text";
static constexpr char kValueKey[] = "value";
static constexpr char kExitTypeKey[] = "type";
static constexpr char kExitTypeCancelable[] = "cancelable";
static constexpr char kExitTypeRequired[] = "required";
static constexpr char kExitResponseKey[] = "response";
static constexpr char kExitResponseCancel[] = "cancel";
static constexpr char kExitResponseExit[] = "exit";
struct _FlPlatformChannel {
GObject parent_instance;
FlMethodChannel* channel;
// Handlers for incoming method calls.
FlPlatformChannelVTable* vtable;
// User data to pass to method call handlers.
gpointer user_data;
};
G_DEFINE_TYPE(FlPlatformChannel, fl_platform_channel, G_TYPE_OBJECT)
static FlMethodResponse* clipboard_set_data(FlPlatformChannel* self,
FlMethodCall* method_call) {
FlValue* args = fl_method_call_get_args(method_call);
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* text_value = fl_value_lookup_string(args, kTextKey);
if (text_value == nullptr ||
fl_value_get_type(text_value) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Missing clipboard text", nullptr));
}
const gchar* text = fl_value_get_string(text_value);
return self->vtable->clipboard_set_data(method_call, text, self->user_data);
}
static FlMethodResponse* clipboard_get_data(FlPlatformChannel* self,
FlMethodCall* method_call) {
FlValue* args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Expected string", nullptr));
}
const gchar* format = fl_value_get_string(args);
return self->vtable->clipboard_get_data(method_call, format, self->user_data);
}
static FlMethodResponse* clipboard_has_strings(FlPlatformChannel* self,
FlMethodCall* method_call) {
return self->vtable->clipboard_has_strings(method_call, self->user_data);
}
// Get the exit response from a System.requestAppExit method call.
FlPlatformChannelExitResponse get_exit_response(FlMethodResponse* response) {
if (response == nullptr) {
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
g_autoptr(GError) error = nullptr;
FlValue* result = fl_method_response_get_result(response, &error);
if (result == nullptr) {
g_warning("Error returned from System.requestAppExit: %s", error->message);
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
if (fl_value_get_type(result) != FL_VALUE_TYPE_MAP) {
g_warning("System.requestAppExit result argument map missing or malformed");
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
FlValue* response_value = fl_value_lookup_string(result, kExitResponseKey);
if (fl_value_get_type(response_value) != FL_VALUE_TYPE_STRING) {
g_warning("Invalid response from System.requestAppExit");
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
const char* response_string = fl_value_get_string(response_value);
if (strcmp(response_string, kExitResponseCancel) == 0) {
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_CANCEL;
} else if (strcmp(response_string, kExitResponseExit) == 0) {
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_CANCEL;
}
// If something went wrong, then just exit.
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
static FlMethodResponse* system_exit_application(FlPlatformChannel* self,
FlMethodCall* method_call) {
FlValue* args = fl_method_call_get_args(method_call);
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* type_value = fl_value_lookup_string(args, kExitTypeKey);
if (type_value == nullptr ||
fl_value_get_type(type_value) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Missing type argument", nullptr));
}
const char* type_string = fl_value_get_string(type_value);
FlPlatformChannelExitType type;
if (strcmp(type_string, kExitTypeCancelable) == 0) {
type = FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE;
} else if (strcmp(type_string, kExitTypeRequired) == 0) {
type = FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED;
} else {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid exit type", nullptr));
}
return self->vtable->system_exit_application(method_call, type,
self->user_data);
}
static FlMethodResponse* system_initialization_complete(
FlPlatformChannel* self) {
self->vtable->system_initialization_complete(self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
static FlMethodResponse* system_sound_play(FlPlatformChannel* self,
FlValue* args) {
if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Expected string", nullptr));
}
const gchar* type = fl_value_get_string(args);
self->vtable->system_sound_play(type, self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
static FlMethodResponse* system_navigator_pop(FlPlatformChannel* self) {
self->vtable->system_navigator_pop(self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlPlatformChannel* self = FL_PLATFORM_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, kSetClipboardDataMethod) == 0) {
response = clipboard_set_data(self, method_call);
} else if (strcmp(method, kGetClipboardDataMethod) == 0) {
response = clipboard_get_data(self, method_call);
} else if (strcmp(method, kClipboardHasStringsMethod) == 0) {
response = clipboard_has_strings(self, method_call);
} else if (strcmp(method, kExitApplicationMethod) == 0) {
response = system_exit_application(self, method_call);
} else if (strcmp(method, kInitializationCompleteMethod) == 0) {
response = system_initialization_complete(self);
} else if (strcmp(method, kPlaySoundMethod) == 0) {
response = system_sound_play(self, args);
} else if (strcmp(method, kSystemNavigatorPopMethod) == 0) {
response = system_navigator_pop(self);
} 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_platform_channel_dispose(GObject* object) {
FlPlatformChannel* self = FL_PLATFORM_CHANNEL(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_platform_channel_parent_class)->dispose(object);
}
static void fl_platform_channel_class_init(FlPlatformChannelClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_platform_channel_dispose;
}
static void fl_platform_channel_init(FlPlatformChannel* self) {}
FlPlatformChannel* fl_platform_channel_new(FlBinaryMessenger* messenger,
FlPlatformChannelVTable* vtable,
gpointer user_data) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
g_return_val_if_fail(vtable != nullptr, nullptr);
FlPlatformChannel* self = FL_PLATFORM_CHANNEL(
g_object_new(fl_platform_channel_get_type(), nullptr));
self->vtable = vtable;
self->user_data = user_data;
g_autoptr(FlJsonMethodCodec) codec = fl_json_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;
}
void fl_platform_channel_system_request_app_exit(FlPlatformChannel* self,
FlPlatformChannelExitType type,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
g_return_if_fail(FL_IS_PLATFORM_CHANNEL(self));
g_autoptr(FlValue) args = fl_value_new_map();
const gchar* type_string;
switch (type) {
case FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE:
type_string = kExitTypeCancelable;
break;
case FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED:
type_string = kExitTypeRequired;
break;
default:
g_assert_not_reached();
}
fl_value_set_string_take(args, kExitTypeKey,
fl_value_new_string(type_string));
fl_method_channel_invoke_method(self->channel, kRequestAppExitMethod, args,
cancellable, callback, user_data);
}
gboolean fl_platform_channel_system_request_app_exit_finish(
GObject* object,
GAsyncResult* result,
FlPlatformChannelExitResponse* exit_response,
GError** error) {
g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
FL_METHOD_CHANNEL(object), result, error);
if (response == nullptr) {
return FALSE;
}
*exit_response = get_exit_response(response);
return TRUE;
}
void fl_platform_channel_respond_clipboard_get_data(FlMethodCall* method_call,
const gchar* text) {
g_autoptr(FlValue) result = nullptr;
if (text != nullptr) {
result = fl_value_new_map();
fl_value_set_string_take(result, kTextKey, fl_value_new_string(text));
}
g_autoptr(FlMethodResponse) response =
FL_METHOD_RESPONSE(fl_method_success_response_new(result));
g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send response to %s: %s", kGetClipboardDataMethod,
error->message);
}
}
void fl_platform_channel_respond_clipboard_has_strings(
FlMethodCall* method_call,
gboolean has_strings) {
g_autoptr(FlValue) result = fl_value_new_map();
fl_value_set_string_take(result, kValueKey, fl_value_new_bool(has_strings));
g_autoptr(FlMethodResponse) response =
FL_METHOD_RESPONSE(fl_method_success_response_new(result));
g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send response to %s: %s", kClipboardHasStringsMethod,
error->message);
}
}
void fl_platform_channel_respond_system_exit_application(
FlMethodCall* method_call,
FlPlatformChannelExitResponse exit_response) {
g_autoptr(FlMethodResponse) response =
fl_platform_channel_make_system_request_app_exit_response(exit_response);
g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send response to System.exitApplication: %s",
error->message);
}
}
FlMethodResponse* fl_platform_channel_make_system_request_app_exit_response(
FlPlatformChannelExitResponse exit_response) {
g_autoptr(FlValue) exit_result = fl_value_new_map();
const gchar* exit_response_string;
switch (exit_response) {
case FL_PLATFORM_CHANNEL_EXIT_RESPONSE_CANCEL:
exit_response_string = kExitResponseCancel;
break;
case FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT:
exit_response_string = kExitResponseExit;
break;
default:
g_assert_not_reached();
}
fl_value_set_string_take(exit_result, kExitResponseKey,
fl_value_new_string(exit_response_string));
return FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result));
}

View File

@ -0,0 +1,112 @@
// 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_PLATFORM_CHANNEL_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_CHANNEL_H_
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_call.h"
G_BEGIN_DECLS
typedef enum {
// NOLINTBEGIN(readability-identifier-naming)
FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE,
FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED,
// NOLINTEND(readability-identifier-naming)
} FlPlatformChannelExitType;
typedef enum {
// NOLINTBEGIN(readability-identifier-naming)
FL_PLATFORM_CHANNEL_EXIT_RESPONSE_CANCEL,
FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT,
// NOLINTEND(readability-identifier-naming)
} FlPlatformChannelExitResponse;
G_DECLARE_FINAL_TYPE(FlPlatformChannel,
fl_platform_channel,
FL,
PLATFORM_CHANNEL,
GObject);
/**
* FlPlatformChannel:
*
* #FlPlatformChannel is a channel that implements the shell side
* of SystemChannels.platform from the Flutter services library.
*/
typedef struct {
FlMethodResponse* (*clipboard_set_data)(FlMethodCall* method_call,
const gchar* text,
gpointer user_data);
FlMethodResponse* (*clipboard_get_data)(FlMethodCall* method_call,
const gchar* format,
gpointer user_data);
FlMethodResponse* (*clipboard_has_strings)(FlMethodCall* method_call,
gpointer user_data);
FlMethodResponse* (*system_exit_application)(FlMethodCall* method_call,
FlPlatformChannelExitType type,
gpointer user_data);
void (*system_initialization_complete)(gpointer user_data);
void (*system_sound_play)(const gchar* type, gpointer user_data);
void (*system_navigator_pop)(gpointer user_data);
} FlPlatformChannelVTable;
/**
* fl_platform_channel_new:
* @messenger: an #FlBinaryMessenger
* @vtable: callbacks for incoming method calls.
* @user_data: data to pass in callbacks.
*
* Creates a new channel that implements SystemChannels.platform from the
* Flutter services library.
*
* Returns: a new #FlPlatformChannel
*/
FlPlatformChannel* fl_platform_channel_new(FlBinaryMessenger* messenger,
FlPlatformChannelVTable* vtable,
gpointer user_data);
/**
* fl_platform_channel_system_request_app_exit:
* @channel: an #FlPlatformChannel
*
* Request the application exits (i.e. due to the window being requested to be
* closed).
*
* Calling this will only send an exit request to the framework if the framework
* has already indicated that it is ready to receive requests by sending a
* "System.initializationComplete" method call on the platform channel. Calls
* before initialization is complete will result in an immediate exit.
*/
void fl_platform_channel_system_request_app_exit(FlPlatformChannel* channel,
FlPlatformChannelExitType type,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean fl_platform_channel_system_request_app_exit_finish(
GObject* object,
GAsyncResult* result,
FlPlatformChannelExitResponse* exit_response,
GError** error);
void fl_platform_channel_respond_system_exit_application(
FlMethodCall* method_call,
FlPlatformChannelExitResponse exit_response);
void fl_platform_channel_respond_clipboard_get_data(FlMethodCall* method_call,
const gchar* text);
void fl_platform_channel_respond_clipboard_has_strings(
FlMethodCall* method_call,
gboolean has_strings);
FlMethodResponse* fl_platform_channel_make_system_request_app_exit_response(
FlPlatformChannelExitResponse exit_response);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_CHANNEL_H_

View File

@ -7,33 +7,12 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <cstring> #include <cstring>
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" #include "flutter/shell/platform/linux/fl_platform_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
static constexpr char kChannelName[] = "flutter/platform"; static constexpr char kInProgressError[] = "In Progress";
static constexpr char kBadArgumentsError[] = "Bad Arguments";
static constexpr char kUnknownClipboardFormatError[] = static constexpr char kUnknownClipboardFormatError[] =
"Unknown Clipboard Format"; "Unknown Clipboard Format";
static constexpr char kInProgressError[] = "In Progress";
static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData";
static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData";
static constexpr char kClipboardHasStringsMethod[] = "Clipboard.hasStrings";
static constexpr char kExitApplicationMethod[] = "System.exitApplication";
static constexpr char kRequestAppExitMethod[] = "System.requestAppExit";
static constexpr char kInitializationCompleteMethod[] =
"System.initializationComplete";
static constexpr char kPlaySoundMethod[] = "SystemSound.play";
static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop";
static constexpr char kTextKey[] = "text";
static constexpr char kValueKey[] = "value";
static constexpr char kExitTypeKey[] = "type";
static constexpr char kExitTypeCancelable[] = "cancelable";
static constexpr char kExitTypeRequired[] = "required";
static constexpr char kExitResponseKey[] = "response";
static constexpr char kExitResponseCancel[] = "cancel";
static constexpr char kExitResponseExit[] = "exit";
static constexpr char kTextPlainFormat[] = "text/plain"; static constexpr char kTextPlainFormat[] = "text/plain";
@ -43,38 +22,23 @@ static constexpr char kSoundTypeClick[] = "SystemSoundType.click";
struct _FlPlatformHandler { struct _FlPlatformHandler {
GObject parent_instance; GObject parent_instance;
FlMethodChannel* channel; FlPlatformChannel* channel;
FlMethodCall* exit_application_method_call; FlMethodCall* exit_application_method_call;
GCancellable* cancellable;
bool app_initialization_complete; bool app_initialization_complete;
GCancellable* cancellable;
}; };
G_DEFINE_TYPE(FlPlatformHandler, fl_platform_handler, G_TYPE_OBJECT) G_DEFINE_TYPE(FlPlatformHandler, fl_platform_handler, G_TYPE_OBJECT)
// Sends the method call response to Flutter.
static void send_response(FlMethodCall* method_call,
FlMethodResponse* response) {
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);
}
}
// Called when clipboard text received. // Called when clipboard text received.
static void clipboard_text_cb(GtkClipboard* clipboard, static void clipboard_text_cb(GtkClipboard* clipboard,
const gchar* text, const gchar* text,
gpointer user_data) { gpointer user_data) {
g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data); g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data);
fl_platform_channel_respond_clipboard_get_data(method_call, text);
g_autoptr(FlValue) result = nullptr;
if (text != nullptr) {
result = fl_value_new_map();
fl_value_set_string_take(result, kTextKey, fl_value_new_string(text));
}
g_autoptr(FlMethodResponse) response =
FL_METHOD_RESPONSE(fl_method_success_response_new(result));
send_response(method_call, response);
} }
// Called when clipboard text received during has_strings. // Called when clipboard text received during has_strings.
@ -82,50 +46,25 @@ static void clipboard_text_has_strings_cb(GtkClipboard* clipboard,
const gchar* text, const gchar* text,
gpointer user_data) { gpointer user_data) {
g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data); g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data);
fl_platform_channel_respond_clipboard_has_strings(
g_autoptr(FlValue) result = fl_value_new_map(); method_call, text != nullptr && strlen(text) > 0);
fl_value_set_string_take(
result, kValueKey,
fl_value_new_bool(text != nullptr && strlen(text) > 0));
g_autoptr(FlMethodResponse) response =
FL_METHOD_RESPONSE(fl_method_success_response_new(result));
send_response(method_call, response);
} }
// Called when Flutter wants to copy to the clipboard. // Called when Flutter wants to copy to the clipboard.
static FlMethodResponse* clipboard_set_data(FlPlatformHandler* self, static FlMethodResponse* clipboard_set_data(FlMethodCall* method_call,
FlValue* args) { const gchar* text,
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { gpointer user_data) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Argument map missing or malformed", nullptr));
}
FlValue* text_value = fl_value_lookup_string(args, kTextKey);
if (text_value == nullptr ||
fl_value_get_type(text_value) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Missing clipboard text", nullptr));
}
GtkClipboard* clipboard = GtkClipboard* clipboard =
gtk_clipboard_get_default(gdk_display_get_default()); gtk_clipboard_get_default(gdk_display_get_default());
gtk_clipboard_set_text(clipboard, fl_value_get_string(text_value), -1); gtk_clipboard_set_text(clipboard, text, -1);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} }
// Called when Flutter wants to paste from the clipboard. // Called when Flutter wants to paste from the clipboard.
static FlMethodResponse* clipboard_get_data_async(FlPlatformHandler* self, static FlMethodResponse* clipboard_get_data(FlMethodCall* method_call,
FlMethodCall* method_call) { const gchar* format,
FlValue* args = fl_method_call_get_args(method_call); gpointer user_data) {
if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Expected string", nullptr));
}
const gchar* format = fl_value_get_string(args);
if (strcmp(format, kTextPlainFormat) != 0) { if (strcmp(format, kTextPlainFormat) != 0) {
return FL_METHOD_RESPONSE(fl_method_error_response_new( return FL_METHOD_RESPONSE(fl_method_error_response_new(
kUnknownClipboardFormatError, "GTK clipboard API only supports text", kUnknownClipboardFormatError, "GTK clipboard API only supports text",
@ -143,9 +82,8 @@ static FlMethodResponse* clipboard_get_data_async(FlPlatformHandler* self,
// Called when Flutter wants to know if the content of the clipboard is able to // Called when Flutter wants to know if the content of the clipboard is able to
// be pasted, without actually accessing the clipboard content itself. // be pasted, without actually accessing the clipboard content itself.
static FlMethodResponse* clipboard_has_strings_async( static FlMethodResponse* clipboard_has_strings(FlMethodCall* method_call,
FlPlatformHandler* self, gpointer user_data) {
FlMethodCall* method_call) {
GtkClipboard* clipboard = GtkClipboard* clipboard =
gtk_clipboard_get_default(gdk_display_get_default()); gtk_clipboard_get_default(gdk_display_get_default());
gtk_clipboard_request_text(clipboard, clipboard_text_has_strings_cb, gtk_clipboard_request_text(clipboard, clipboard_text_has_strings_cb,
@ -155,31 +93,6 @@ static FlMethodResponse* clipboard_has_strings_async(
return nullptr; return nullptr;
} }
// Get the exit response from a System.requestAppExit method call.
static gchar* get_exit_response(FlMethodResponse* response) {
if (response == nullptr) {
return nullptr;
}
g_autoptr(GError) error = nullptr;
FlValue* result = fl_method_response_get_result(response, &error);
if (result == nullptr) {
g_warning("Error returned from System.requestAppExit: %s", error->message);
return nullptr;
}
if (fl_value_get_type(result) != FL_VALUE_TYPE_MAP) {
g_warning("System.requestAppExit result argument map missing or malformed");
return nullptr;
}
FlValue* response_value = fl_value_lookup_string(result, kExitResponseKey);
if (fl_value_get_type(response_value) != FL_VALUE_TYPE_STRING) {
g_warning("Invalid response from System.requestAppExit");
return nullptr;
}
return g_strdup(fl_value_get_string(response_value));
}
// Quit this application // Quit this application
static void quit_application() { static void quit_application() {
GApplication* app = g_application_get_default(); GApplication* app = g_application_get_default();
@ -212,88 +125,56 @@ static void request_app_exit_response_cb(GObject* object,
FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data); FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data);
g_autoptr(GError) error = nullptr; g_autoptr(GError) error = nullptr;
g_autoptr(FlMethodResponse) method_response = FlPlatformChannelExitResponse exit_response;
fl_method_channel_invoke_method_finish(FL_METHOD_CHANNEL(object), result, if (!fl_platform_channel_system_request_app_exit_finish(
&error); object, result, &exit_response, &error)) {
g_autofree gchar* exit_response = nullptr;
if (method_response == nullptr) {
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
return; return;
} }
g_warning("Failed to complete System.requestAppExit: %s", error->message); g_warning("Failed to complete System.requestAppExit: %s", error->message);
} else { quit_application();
exit_response = get_exit_response(method_response); return;
}
// If something went wrong, then just exit.
if (exit_response == nullptr) {
exit_response = g_strdup(kExitResponseExit);
} }
if (g_str_equal(exit_response, kExitResponseExit)) { if (exit_response == FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT) {
quit_application(); quit_application();
} else if (g_str_equal(exit_response, kExitResponseCancel)) {
// Canceled - no action to take.
} }
// If request was due to a request from Flutter, pass result back. // If request was due to a request from Flutter, pass result back.
if (self->exit_application_method_call != nullptr) { if (self->exit_application_method_call != nullptr) {
g_autoptr(FlValue) exit_result = fl_value_new_map(); fl_platform_channel_respond_system_exit_application(
fl_value_set_string_take(exit_result, kExitResponseKey, self->exit_application_method_call, exit_response);
fl_value_new_string(exit_response));
g_autoptr(FlMethodResponse) exit_response =
FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result));
if (!fl_method_call_respond(self->exit_application_method_call,
exit_response, &error)) {
g_warning("Failed to send response to System.exitApplication: %s",
error->message);
}
g_clear_object(&self->exit_application_method_call);
} }
} }
// Send a request to Flutter to exit the application, but only if it's ready for // Send a request to Flutter to exit the application, but only if it's ready for
// a request. // a request.
static void request_app_exit(FlPlatformHandler* self, const char* type) { static void request_app_exit(FlPlatformHandler* self,
g_autoptr(FlValue) args = fl_value_new_map(); FlPlatformChannelExitType type) {
if (!self->app_initialization_complete || if (!self->app_initialization_complete ||
g_str_equal(type, kExitTypeRequired)) { type == FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED) {
quit_application(); quit_application();
return; return;
} }
fl_value_set_string_take(args, kExitTypeKey, fl_value_new_string(type)); fl_platform_channel_system_request_app_exit(
fl_method_channel_invoke_method(self->channel, kRequestAppExitMethod, args, self->channel, type, self->cancellable, request_app_exit_response_cb,
self->cancellable, self);
request_app_exit_response_cb, self);
} }
// Called when the Dart app has finished initialization and is ready to handle // Called when the Dart app has finished initialization and is ready to handle
// requests. For the Flutter framework, this means after the ServicesBinding has // requests. For the Flutter framework, this means after the ServicesBinding has
// been initialized and it sends a System.initializationComplete message. // been initialized and it sends a System.initializationComplete message.
static FlMethodResponse* system_intitialization_complete( static void system_initialization_complete(gpointer user_data) {
FlPlatformHandler* self, FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data);
FlMethodCall* method_call) {
self->app_initialization_complete = TRUE; self->app_initialization_complete = TRUE;
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} }
// Called when Flutter wants to exit the application. // Called when Flutter wants to exit the application.
static FlMethodResponse* system_exit_application(FlPlatformHandler* self, static FlMethodResponse* system_exit_application(FlMethodCall* method_call,
FlMethodCall* method_call) { FlPlatformChannelExitType type,
FlValue* args = fl_method_call_get_args(method_call); gpointer user_data) {
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data);
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Argument map missing or malformed", nullptr));
}
FlValue* type_value = fl_value_lookup_string(args, kExitTypeKey);
if (type_value == nullptr ||
fl_value_get_type(type_value) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Missing type argument", nullptr));
}
const char* type = fl_value_get_string(type_value);
// Save method call to respond to when our request to Flutter completes. // Save method call to respond to when our request to Flutter completes.
if (self->exit_application_method_call != nullptr) { if (self->exit_application_method_call != nullptr) {
return FL_METHOD_RESPONSE(fl_method_error_response_new( return FL_METHOD_RESPONSE(fl_method_error_response_new(
@ -305,12 +186,10 @@ static FlMethodResponse* system_exit_application(FlPlatformHandler* self,
// Requested to immediately quit if the app hasn't yet signaled that it is // Requested to immediately quit if the app hasn't yet signaled that it is
// ready to handle requests, or if the type of exit requested is "required". // ready to handle requests, or if the type of exit requested is "required".
if (!self->app_initialization_complete || if (!self->app_initialization_complete ||
g_str_equal(type, kExitTypeRequired)) { type == FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED) {
quit_application(); quit_application();
g_autoptr(FlValue) exit_result = fl_value_new_map(); return fl_platform_channel_make_system_request_app_exit_response(
fl_value_set_string_take(exit_result, kExitResponseKey, FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT);
fl_value_new_string(kExitResponseExit));
return FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result));
} }
// Send the request back to Flutter to follow the standard process. // Send the request back to Flutter to follow the standard process.
@ -321,14 +200,7 @@ static FlMethodResponse* system_exit_application(FlPlatformHandler* self,
} }
// Called when Flutter wants to play a sound. // Called when Flutter wants to play a sound.
static FlMethodResponse* system_sound_play(FlPlatformHandler* self, static void system_sound_play(const gchar* type, gpointer user_data) {
FlValue* args) {
if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Expected string", nullptr));
}
const gchar* type = fl_value_get_string(args);
if (strcmp(type, kSoundTypeAlert) == 0) { if (strcmp(type, kSoundTypeAlert) == 0) {
GdkDisplay* display = gdk_display_get_default(); GdkDisplay* display = gdk_display_get_default();
if (display != nullptr) { if (display != nullptr) {
@ -339,47 +211,11 @@ static FlMethodResponse* system_sound_play(FlPlatformHandler* self,
} else { } else {
g_warning("Ignoring unknown sound type %s in SystemSound.play.\n", type); g_warning("Ignoring unknown sound type %s in SystemSound.play.\n", type);
} }
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} }
// Called when Flutter wants to quit the application. // Called when Flutter wants to quit the application.
static FlMethodResponse* system_navigator_pop(FlPlatformHandler* self) { static void system_navigator_pop(gpointer user_data) {
quit_application(); quit_application();
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Called when a method call is received from Flutter.
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlPlatformHandler* self = FL_PLATFORM_HANDLER(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, kSetClipboardDataMethod) == 0) {
response = clipboard_set_data(self, args);
} else if (strcmp(method, kGetClipboardDataMethod) == 0) {
response = clipboard_get_data_async(self, method_call);
} else if (strcmp(method, kClipboardHasStringsMethod) == 0) {
response = clipboard_has_strings_async(self, method_call);
} else if (strcmp(method, kExitApplicationMethod) == 0) {
response = system_exit_application(self, method_call);
} else if (strcmp(method, kInitializationCompleteMethod) == 0) {
response = system_intitialization_complete(self, method_call);
} else if (strcmp(method, kPlaySoundMethod) == 0) {
response = system_sound_play(self, args);
} else if (strcmp(method, kSystemNavigatorPopMethod) == 0) {
response = system_navigator_pop(self);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
if (response != nullptr) {
send_response(method_call, response);
}
} }
static void fl_platform_handler_dispose(GObject* object) { static void fl_platform_handler_dispose(GObject* object) {
@ -402,17 +238,24 @@ static void fl_platform_handler_init(FlPlatformHandler* self) {
self->cancellable = g_cancellable_new(); self->cancellable = g_cancellable_new();
} }
static FlPlatformChannelVTable platform_channel_vtable = {
.clipboard_set_data = clipboard_set_data,
.clipboard_get_data = clipboard_get_data,
.clipboard_has_strings = clipboard_has_strings,
.system_exit_application = system_exit_application,
.system_initialization_complete = system_initialization_complete,
.system_sound_play = system_sound_play,
.system_navigator_pop = system_navigator_pop,
};
FlPlatformHandler* fl_platform_handler_new(FlBinaryMessenger* messenger) { FlPlatformHandler* fl_platform_handler_new(FlBinaryMessenger* messenger) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
FlPlatformHandler* self = FL_PLATFORM_HANDLER( FlPlatformHandler* self = FL_PLATFORM_HANDLER(
g_object_new(fl_platform_handler_get_type(), nullptr)); g_object_new(fl_platform_handler_get_type(), nullptr));
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
self->channel = self->channel =
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); fl_platform_channel_new(messenger, &platform_channel_vtable, self);
fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self,
nullptr);
self->app_initialization_complete = FALSE; self->app_initialization_complete = FALSE;
return self; return self;
@ -421,5 +264,5 @@ FlPlatformHandler* fl_platform_handler_new(FlBinaryMessenger* messenger) {
void fl_platform_handler_request_app_exit(FlPlatformHandler* self) { void fl_platform_handler_request_app_exit(FlPlatformHandler* self) {
g_return_if_fail(FL_IS_PLATFORM_HANDLER(self)); g_return_if_fail(FL_IS_PLATFORM_HANDLER(self));
// Request a cancellable exit. // Request a cancellable exit.
request_app_exit(self, kExitTypeCancelable); request_app_exit(self, FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE);
} }

View File

@ -0,0 +1,77 @@
// 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_settings_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
static constexpr char kChannelName[] = "flutter/settings";
static constexpr char kTextScaleFactorKey[] = "textScaleFactor";
static constexpr char kAlwaysUse24HourFormatKey[] = "alwaysUse24HourFormat";
static constexpr char kPlatformBrightnessKey[] = "platformBrightness";
static constexpr char kPlatformBrightnessLight[] = "light";
static constexpr char kPlatformBrightnessDark[] = "dark";
struct _FlSettingsChannel {
GObject parent_instance;
FlBasicMessageChannel* channel;
};
G_DEFINE_TYPE(FlSettingsChannel, fl_settings_channel, G_TYPE_OBJECT)
static void fl_settings_channel_dispose(GObject* object) {
FlSettingsChannel* self = FL_SETTINGS_CHANNEL(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_settings_channel_parent_class)->dispose(object);
}
static void fl_settings_channel_class_init(FlSettingsChannelClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_settings_channel_dispose;
}
static void fl_settings_channel_init(FlSettingsChannel* self) {}
FlSettingsChannel* fl_settings_channel_new(FlBinaryMessenger* messenger) {
FlSettingsChannel* self = FL_SETTINGS_CHANNEL(
g_object_new(fl_settings_channel_get_type(), nullptr));
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
self->channel = fl_basic_message_channel_new(messenger, kChannelName,
FL_MESSAGE_CODEC(codec));
return self;
}
void fl_settings_channel_send(
FlSettingsChannel* self,
double text_scale_factor,
gboolean always_use_24_hour_format,
FlSettingsChannelPlatformBrightness platform_brightness) {
g_return_if_fail(FL_IS_SETTINGS_CHANNEL(self));
g_autoptr(FlValue) message = fl_value_new_map();
fl_value_set_string_take(message, kTextScaleFactorKey,
fl_value_new_float(text_scale_factor));
fl_value_set_string_take(message, kAlwaysUse24HourFormatKey,
fl_value_new_bool(always_use_24_hour_format));
const gchar* platform_brightness_string;
switch (platform_brightness) {
case FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_LIGHT:
platform_brightness_string = kPlatformBrightnessLight;
break;
case FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_DARK:
platform_brightness_string = kPlatformBrightnessDark;
break;
default:
g_assert_not_reached();
}
fl_value_set_string_take(message, kPlatformBrightnessKey,
fl_value_new_string(platform_brightness_string));
fl_basic_message_channel_send(self->channel, message, nullptr, nullptr,
nullptr);
}

View File

@ -0,0 +1,60 @@
// 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_SETTINGS_CHANNEL_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_CHANNEL_H_
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
G_BEGIN_DECLS
typedef enum {
// NOLINTBEGIN(readability-identifier-naming)
FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_LIGHT,
FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_DARK
// NOLINTEND(readability-identifier-naming)
} FlSettingsChannelPlatformBrightness;
G_DECLARE_FINAL_TYPE(FlSettingsChannel,
fl_settings_channel,
FL,
SETTINGS_CHANNEL,
GObject);
/**
* FlSettingsChannel:
*
* #FlSettingsChannel is a channel that implements the Flutter user settings
* channel.
*/
/**
* fl_settings_channel_new:
* @messenger: an #FlBinaryMessenger
*
* Creates a new channel that sends user settings to the platform.
*
* Returns: a new #FlSettingsChannel
*/
FlSettingsChannel* fl_settings_channel_new(FlBinaryMessenger* messenger);
/*
* fl_settings_channel_send:
* @channel: an #FlSettingsChannel.
* @text_scale_factor: scale factor for text.
* @always_use_24_hour_format: TRUE if time should always be shown in 24 hour
* format.
* @platform_brightness: The brightness theme in use.
*
* Send a settings update to the platform.
*/
void fl_settings_channel_send(
FlSettingsChannel* channel,
double text_scale_factor,
gboolean always_use_24_hour_format,
FlSettingsChannelPlatformBrightness platform_brightness);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_CHANNEL_H_

View File

@ -8,35 +8,27 @@
#include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/fl_engine_private.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" #include "flutter/shell/platform/linux/fl_settings_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
static constexpr char kChannelName[] = "flutter/settings";
static constexpr char kTextScaleFactorKey[] = "textScaleFactor";
static constexpr char kAlwaysUse24HourFormatKey[] = "alwaysUse24HourFormat";
static constexpr char kPlatformBrightnessKey[] = "platformBrightness";
static constexpr char kPlatformBrightnessLight[] = "light";
static constexpr char kPlatformBrightnessDark[] = "dark";
struct _FlSettingsHandler { struct _FlSettingsHandler {
GObject parent_instance; GObject parent_instance;
FlBasicMessageChannel* channel; FlSettingsChannel* channel;
GWeakRef engine; GWeakRef engine;
FlSettings* settings; FlSettings* settings;
}; };
G_DEFINE_TYPE(FlSettingsHandler, fl_settings_handler, G_TYPE_OBJECT) G_DEFINE_TYPE(FlSettingsHandler, fl_settings_handler, G_TYPE_OBJECT)
static const gchar* to_platform_brightness(FlColorScheme color_scheme) { static FlSettingsChannelPlatformBrightness to_platform_brightness(
FlColorScheme color_scheme) {
switch (color_scheme) { switch (color_scheme) {
case FL_COLOR_SCHEME_LIGHT: case FL_COLOR_SCHEME_LIGHT:
return kPlatformBrightnessLight; return FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_LIGHT;
case FL_COLOR_SCHEME_DARK: case FL_COLOR_SCHEME_DARK:
return kPlatformBrightnessDark; return FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_DARK;
default: default:
g_return_val_if_reached(nullptr); g_assert_not_reached();
} }
} }
@ -46,17 +38,9 @@ static void update_settings(FlSettingsHandler* self) {
FlColorScheme color_scheme = fl_settings_get_color_scheme(self->settings); FlColorScheme color_scheme = fl_settings_get_color_scheme(self->settings);
gdouble scaling_factor = fl_settings_get_text_scaling_factor(self->settings); gdouble scaling_factor = fl_settings_get_text_scaling_factor(self->settings);
g_autoptr(FlValue) message = fl_value_new_map(); fl_settings_channel_send(self->channel, scaling_factor,
fl_value_set_string_take(message, kTextScaleFactorKey, clock_format == FL_CLOCK_FORMAT_24H,
fl_value_new_float(scaling_factor)); to_platform_brightness(color_scheme));
fl_value_set_string_take(
message, kAlwaysUse24HourFormatKey,
fl_value_new_bool(clock_format == FL_CLOCK_FORMAT_24H));
fl_value_set_string_take(
message, kPlatformBrightnessKey,
fl_value_new_string(to_platform_brightness(color_scheme)));
fl_basic_message_channel_send(self->channel, message, nullptr, nullptr,
nullptr);
g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine));
if (engine != nullptr) { if (engine != nullptr) {
@ -96,9 +80,7 @@ FlSettingsHandler* fl_settings_handler_new(FlEngine* engine) {
g_weak_ref_init(&self->engine, engine); g_weak_ref_init(&self->engine, engine);
FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(engine); FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(engine);
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); self->channel = fl_settings_channel_new(messenger);
self->channel = fl_basic_message_channel_new(messenger, kChannelName,
FL_MESSAGE_CODEC(codec));
return self; return self;
} }

View File

@ -0,0 +1,409 @@
// 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_text_input_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
static constexpr char kChannelName[] = "flutter/textinput";
static constexpr char kBadArgumentsError[] = "Bad Arguments";
static constexpr char kSetClientMethod[] = "TextInput.setClient";
static constexpr char kShowMethod[] = "TextInput.show";
static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState";
static constexpr char kClearClientMethod[] = "TextInput.clearClient";
static constexpr char kHideMethod[] = "TextInput.hide";
static constexpr char kUpdateEditingStateMethod[] =
"TextInputClient.updateEditingState";
static constexpr char kUpdateEditingStateWithDeltasMethod[] =
"TextInputClient.updateEditingStateWithDeltas";
static constexpr char kPerformActionMethod[] = "TextInputClient.performAction";
static constexpr char kSetEditableSizeAndTransform[] =
"TextInput.setEditableSizeAndTransform";
static constexpr char kSetMarkedTextRect[] = "TextInput.setMarkedTextRect";
static constexpr char kInputActionKey[] = "inputAction";
static constexpr char kTextInputTypeKey[] = "inputType";
static constexpr char kEnableDeltaModel[] = "enableDeltaModel";
static constexpr char kTextInputTypeNameKey[] = "name";
static constexpr char kTextKey[] = "text";
static constexpr char kSelectionBaseKey[] = "selectionBase";
static constexpr char kSelectionExtentKey[] = "selectionExtent";
static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
static constexpr char kComposingBaseKey[] = "composingBase";
static constexpr char kComposingExtentKey[] = "composingExtent";
static constexpr char kTransform[] = "transform";
static constexpr char kMultilineInputType[] = "TextInputType.multiline";
static constexpr char kNoneInputType[] = "TextInputType.none";
static constexpr char kTextAffinityUpstream[] = "TextAffinity.upstream";
static constexpr char kTextAffinityDownstream[] = "TextAffinity.downstream";
struct _FlTextInputChannel {
GObject parent_instance;
FlMethodChannel* channel;
FlTextInputChannelVTable* vtable;
gpointer user_data;
};
G_DEFINE_TYPE(FlTextInputChannel, fl_text_input_channel, G_TYPE_OBJECT)
static const gchar* text_affinity_to_string(FlTextAffinity affinity) {
switch (affinity) {
case FL_TEXT_AFFINITY_UPSTREAM:
return kTextAffinityUpstream;
case FL_TEXT_AFFINITY_DOWNSTREAM:
return kTextAffinityDownstream;
default:
g_assert_not_reached();
}
}
// Called when the input method client is set up.
static FlMethodResponse* set_client(FlTextInputChannel* self, FlValue* args) {
if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST ||
fl_value_get_length(args) < 2) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Expected 2-element list", nullptr));
}
int64_t client_id = fl_value_get_int(fl_value_get_list_value(args, 0));
FlValue* config_value = fl_value_get_list_value(args, 1);
const gchar* input_action = nullptr;
FlValue* input_action_value =
fl_value_lookup_string(config_value, kInputActionKey);
if (fl_value_get_type(input_action_value) == FL_VALUE_TYPE_STRING) {
input_action = fl_value_get_string(input_action_value);
}
FlValue* enable_delta_model_value =
fl_value_lookup_string(config_value, kEnableDeltaModel);
gboolean enable_delta_model = fl_value_get_bool(enable_delta_model_value);
// Reset the input type, then set only if appropriate.
FlTextInputType input_type = FL_TEXT_INPUT_TYPE_TEXT;
FlValue* input_type_value =
fl_value_lookup_string(config_value, kTextInputTypeKey);
if (fl_value_get_type(input_type_value) == FL_VALUE_TYPE_MAP) {
FlValue* input_type_name_value =
fl_value_lookup_string(input_type_value, kTextInputTypeNameKey);
if (fl_value_get_type(input_type_name_value) == FL_VALUE_TYPE_STRING) {
const gchar* input_type_name = fl_value_get_string(input_type_name_value);
if (g_strcmp0(input_type_name, kMultilineInputType) == 0) {
input_type = FL_TEXT_INPUT_TYPE_MULTILINE;
} else if (g_strcmp0(input_type_name, kNoneInputType) == 0) {
input_type = FL_TEXT_INPUT_TYPE_NONE;
}
}
}
self->vtable->set_client(client_id, input_action, enable_delta_model,
input_type, self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Hides the input method.
static FlMethodResponse* hide(FlTextInputChannel* self) {
self->vtable->hide(self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Shows the input method.
static FlMethodResponse* show(FlTextInputChannel* self) {
self->vtable->show(self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Updates the editing state from Flutter.
static FlMethodResponse* set_editing_state(FlTextInputChannel* self,
FlValue* args) {
const gchar* text =
fl_value_get_string(fl_value_lookup_string(args, kTextKey));
int64_t selection_base =
fl_value_get_int(fl_value_lookup_string(args, kSelectionBaseKey));
int64_t selection_extent =
fl_value_get_int(fl_value_lookup_string(args, kSelectionExtentKey));
int64_t composing_base =
fl_value_get_int(fl_value_lookup_string(args, kComposingBaseKey));
int64_t composing_extent =
fl_value_get_int(fl_value_lookup_string(args, kComposingExtentKey));
self->vtable->set_editing_state(text, selection_base, selection_extent,
composing_base, composing_extent,
self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Called when the input method client is complete.
static FlMethodResponse* clear_client(FlTextInputChannel* self) {
self->vtable->clear_client(self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Handles updates to the EditableText size and position from the framework.
//
// On changes to the size or position of the RenderObject underlying the
// EditableText, this update may be triggered. It provides an updated size and
// transform from the local coordinate system of the EditableText to root
// Flutter coordinate system.
static FlMethodResponse* set_editable_size_and_transform(
FlTextInputChannel* self,
FlValue* args) {
FlValue* transform_value = fl_value_lookup_string(args, kTransform);
if (fl_value_get_length(transform_value) != 16) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid transform", nullptr));
}
double transform[16];
for (size_t i = 0; i < 16; i++) {
transform[i] =
fl_value_get_float(fl_value_get_list_value(transform_value, i));
}
self->vtable->set_editable_size_and_transform(transform, self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Handles updates to the composing rect from the framework.
//
// On changes to the state of the EditableText in the framework, this update
// may be triggered. It provides an updated rect for the composing region in
// local coordinates of the EditableText. In the case where there is no
// composing region, the cursor rect is sent.
static FlMethodResponse* set_marked_text_rect(FlTextInputChannel* self,
FlValue* args) {
double x = fl_value_get_float(fl_value_lookup_string(args, "x"));
double y = fl_value_get_float(fl_value_lookup_string(args, "y"));
double width = fl_value_get_float(fl_value_lookup_string(args, "width"));
double height = fl_value_get_float(fl_value_lookup_string(args, "height"));
self->vtable->set_marked_text_rect(x, y, width, height, self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Called when a method call is received from Flutter.
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlTextInputChannel* self = FL_TEXT_INPUT_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, kSetClientMethod) == 0) {
response = set_client(self, args);
} else if (strcmp(method, kShowMethod) == 0) {
response = show(self);
} else if (strcmp(method, kSetEditingStateMethod) == 0) {
response = set_editing_state(self, args);
} else if (strcmp(method, kClearClientMethod) == 0) {
response = clear_client(self);
} else if (strcmp(method, kHideMethod) == 0) {
response = hide(self);
} else if (strcmp(method, kSetEditableSizeAndTransform) == 0) {
response = set_editable_size_and_transform(self, args);
} else if (strcmp(method, kSetMarkedTextRect) == 0) {
response = set_marked_text_rect(self, args);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
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_text_input_channel_dispose(GObject* object) {
FlTextInputChannel* self = FL_TEXT_INPUT_CHANNEL(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_text_input_channel_parent_class)->dispose(object);
}
static void fl_text_input_channel_class_init(FlTextInputChannelClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_text_input_channel_dispose;
}
static void fl_text_input_channel_init(FlTextInputChannel* self) {}
FlTextInputChannel* fl_text_input_channel_new(FlBinaryMessenger* messenger,
FlTextInputChannelVTable* vtable,
gpointer user_data) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
g_return_val_if_fail(vtable != nullptr, nullptr);
FlTextInputChannel* self = FL_TEXT_INPUT_CHANNEL(
g_object_new(fl_text_input_channel_get_type(), nullptr));
self->vtable = vtable;
self->user_data = user_data;
g_autoptr(FlJsonMethodCodec) codec = fl_json_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;
}
void fl_text_input_channel_update_editing_state(
FlTextInputChannel* self,
int64_t client_id,
const gchar* text,
int64_t selection_base,
int64_t selection_extent,
FlTextAffinity selection_affinity,
gboolean selection_is_directional,
int64_t composing_base,
int64_t composing_extent,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
g_return_if_fail(FL_IS_TEXT_INPUT_CHANNEL(self));
g_autoptr(FlValue) args = fl_value_new_list();
fl_value_append_take(args, fl_value_new_int(client_id));
g_autoptr(FlValue) value = fl_value_new_map();
fl_value_set_string_take(value, kTextKey, fl_value_new_string(text));
fl_value_set_string_take(value, kSelectionBaseKey,
fl_value_new_int(selection_base));
fl_value_set_string_take(value, kSelectionExtentKey,
fl_value_new_int(selection_extent));
fl_value_set_string_take(
value, kSelectionAffinityKey,
fl_value_new_string(text_affinity_to_string(selection_affinity)));
fl_value_set_string_take(value, kSelectionIsDirectionalKey,
fl_value_new_bool(selection_is_directional));
fl_value_set_string_take(value, kComposingBaseKey,
fl_value_new_int(composing_base));
fl_value_set_string_take(value, kComposingExtentKey,
fl_value_new_int(composing_extent));
fl_value_append(args, value);
fl_method_channel_invoke_method(self->channel, kUpdateEditingStateMethod,
args, cancellable, callback, user_data);
}
gboolean fl_text_input_channel_update_editing_state_finish(GObject* object,
GAsyncResult* result,
GError** error) {
g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
FL_METHOD_CHANNEL(object), result, error);
if (response == nullptr) {
return FALSE;
}
return fl_method_response_get_result(response, error) != nullptr;
}
void fl_text_input_channel_update_editing_state_with_deltas(
FlTextInputChannel* self,
int64_t client_id,
const gchar* old_text,
const gchar* delta_text,
int64_t delta_start,
int64_t delta_end,
int64_t selection_base,
int64_t selection_extent,
FlTextAffinity selection_affinity,
gboolean selection_is_directional,
int64_t composing_base,
int64_t composing_extent,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
g_return_if_fail(FL_IS_TEXT_INPUT_CHANNEL(self));
g_autoptr(FlValue) args = fl_value_new_list();
fl_value_append_take(args, fl_value_new_int(client_id));
g_autoptr(FlValue) deltaValue = fl_value_new_map();
fl_value_set_string_take(deltaValue, "oldText",
fl_value_new_string(old_text));
fl_value_set_string_take(deltaValue, "deltaText",
fl_value_new_string(delta_text));
fl_value_set_string_take(deltaValue, "deltaStart",
fl_value_new_int(delta_start));
fl_value_set_string_take(deltaValue, "deltaEnd", fl_value_new_int(delta_end));
fl_value_set_string_take(deltaValue, "selectionBase",
fl_value_new_int(selection_base));
fl_value_set_string_take(deltaValue, "selectionExtent",
fl_value_new_int(selection_extent));
fl_value_set_string_take(
deltaValue, "selectionAffinity",
fl_value_new_string(text_affinity_to_string(selection_affinity)));
fl_value_set_string_take(deltaValue, "selectionIsDirectional",
fl_value_new_bool(selection_is_directional));
fl_value_set_string_take(deltaValue, "composingBase",
fl_value_new_int(composing_base));
fl_value_set_string_take(deltaValue, "composingExtent",
fl_value_new_int(composing_extent));
g_autoptr(FlValue) deltas = fl_value_new_list();
fl_value_append(deltas, deltaValue);
g_autoptr(FlValue) value = fl_value_new_map();
fl_value_set_string(value, "deltas", deltas);
fl_value_append(args, value);
fl_method_channel_invoke_method(self->channel,
kUpdateEditingStateWithDeltasMethod, args,
cancellable, callback, user_data);
}
gboolean fl_text_input_channel_update_editing_state_with_deltas_finish(
GObject* object,
GAsyncResult* result,
GError** error) {
g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
FL_METHOD_CHANNEL(object), result, error);
if (response == nullptr) {
return FALSE;
}
return fl_method_response_get_result(response, error) != nullptr;
}
void fl_text_input_channel_perform_action(FlTextInputChannel* self,
int64_t client_id,
const gchar* input_action,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
g_return_if_fail(FL_IS_TEXT_INPUT_CHANNEL(self));
g_autoptr(FlValue) args = fl_value_new_list();
fl_value_append_take(args, fl_value_new_int(client_id));
fl_value_append_take(args, fl_value_new_string(input_action));
fl_method_channel_invoke_method(self->channel, kPerformActionMethod, args,
cancellable, callback, user_data);
}
gboolean fl_text_input_channel_perform_action_finish(GObject* object,
GAsyncResult* result,
GError** error) {
g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
FL_METHOD_CHANNEL(object), result, error);
if (response == nullptr) {
return FALSE;
}
return fl_method_response_get_result(response, error) != nullptr;
}

View File

@ -0,0 +1,215 @@
// 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_TEXT_INPUT_CHANNEL_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_TEXT_INPUT_CHANNEL_H_
#include <gtk/gtk.h>
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
G_BEGIN_DECLS
typedef enum {
// NOLINTBEGIN(readability-identifier-naming)
FL_TEXT_INPUT_TYPE_TEXT,
// Send newline when multi-line and enter is pressed.
FL_TEXT_INPUT_TYPE_MULTILINE,
// The input method is not shown at all.
FL_TEXT_INPUT_TYPE_NONE,
// NOLINTEND(readability-identifier-naming)
} FlTextInputType;
typedef enum {
// NOLINTBEGIN(readability-identifier-naming)
FL_TEXT_AFFINITY_UPSTREAM,
FL_TEXT_AFFINITY_DOWNSTREAM,
// NOLINTEND(readability-identifier-naming)
} FlTextAffinity;
G_DECLARE_FINAL_TYPE(FlTextInputChannel,
fl_text_input_channel,
FL,
TEXT_INPUT_CHANNEL,
GObject);
/**
* FlTextInputChannel:
*
* #FlTextInputChannel is a channel that implements the shell side
* of SystemChannels.textInput from the Flutter services library.
*/
typedef struct {
void (*set_client)(int64_t client_id,
const gchar* input_action,
gboolean enable_delta_model,
FlTextInputType input_type,
gpointer user_data);
void (*hide)(gpointer user_data);
void (*show)(gpointer user_data);
void (*set_editing_state)(const gchar* text,
int64_t selection_base,
int64_t selection_extent,
int64_t composing_base,
int64_t composing_extent,
gpointer user_data);
void (*clear_client)(gpointer user_data);
void (*set_editable_size_and_transform)(double* transform,
gpointer user_data);
void (*set_marked_text_rect)(double x,
double y,
double width,
double height,
gpointer user_data);
} FlTextInputChannelVTable;
/**
* fl_text_input_channel_new:
* @messenger: an #FlBinaryMessenger.
* @vtable: callbacks for incoming method calls.
* @user_data: data to pass in callbacks.
*
* Creates a new channel that implements SystemChannels.textInput from the
* Flutter services library.
*
* Returns: a new #FlTextInputChannel.
*/
FlTextInputChannel* fl_text_input_channel_new(FlBinaryMessenger* messenger,
FlTextInputChannelVTable* vtable,
gpointer user_data);
/**
* fl_text_input_channel_update_editing_state:
* @channel: an #FlTextInputChannel.
* @client_id:
* @text:
* @selection_base:
* @selection_extent:
* @selection_affinity:
* @selection_is_directional:
* @composing_base:
* @composing_extent:
* @cancellable: (allow-none): a #GCancellable or %NULL.
* @callback: (scope async): a #GAsyncReadyCallback to call when the method
* returns.
* @user_data: (closure): user data to pass to @callback.
*/
void fl_text_input_channel_update_editing_state(
FlTextInputChannel* channel,
int64_t client_id,
const gchar* text,
int64_t selection_base,
int64_t selection_extent,
FlTextAffinity selection_affinity,
gboolean selection_is_directional,
int64_t composing_base,
int64_t composing_extent,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
/**
* fl_text_input_channel_update_editing_state_finish:
* @object:
* @result: a #GAsyncResult.
* @error: (allow-none): #GError location to store the error occurring, or %NULL
* to ignore.
*
* Completes request started with fl_text_input_channel_update_editing_state().
*
* Returns: %TRUE on success.
*/
gboolean fl_text_input_channel_update_editing_state_finish(GObject* object,
GAsyncResult* result,
GError** error);
/**
* fl_text_input_channel_update_editing_state_with_deltas:
* @channel: an #FlTextInputChannel.
* @client_id:
* @old_text:
* @delta_text:
* @delta_start:
* @delta_end:
* @selection_base:
* @selection_extent:
* @selection_affinity:
* @selection_is_directional:
* @composing_base:
* @composing_extent:
* @cancellable: (allow-none): a #GCancellable or %NULL.
* @callback: (scope async): a #GAsyncReadyCallback to call when the method
* returns.
* @user_data: (closure): user data to pass to @callback.
*/
void fl_text_input_channel_update_editing_state_with_deltas(
FlTextInputChannel* channel,
int64_t client_id,
const gchar* old_text,
const gchar* delta_text,
int64_t delta_start,
int64_t delta_end,
int64_t selection_base,
int64_t selection_extent,
FlTextAffinity selection_affinity,
gboolean selection_is_directional,
int64_t composing_base,
int64_t composing_extent,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
/**
* fl_text_input_channel_update_editing_state_with_deltas_finish:
* @object:
* @result: a #GAsyncResult.
* @error: (allow-none): #GError location to store the error occurring, or %NULL
* to ignore.
*
* Completes request started with
* fl_text_input_channel_update_editing_state_with_deltas().
*
* Returns: %TRUE on success.
*/
gboolean fl_text_input_channel_update_editing_state_with_deltas_finish(
GObject* object,
GAsyncResult* result,
GError** error);
/**
* fl_text_input_channel_perform_action:
* @channel: an #FlTextInputChannel.
* @client_id:
* @input_action: action to perform.
* @cancellable: (allow-none): a #GCancellable or %NULL.
* @callback: (scope async): a #GAsyncReadyCallback to call when the method
* returns.
* @user_data: (closure): user data to pass to @callback.
*/
void fl_text_input_channel_perform_action(FlTextInputChannel* channel,
int64_t client_id,
const gchar* input_action,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
/**
* fl_text_input_channel_perform_action_finish:
* @object:
* @result: a #GAsyncResult.
* @error: (allow-none): #GError location to store the error occurring, or %NULL
* to ignore.
*
* Completes request started with fl_text_input_channel_perform_action().
*
* Returns: %TRUE on success.
*/
gboolean fl_text_input_channel_perform_action_finish(GObject* object,
GAsyncResult* result,
GError** error);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_TEXT_INPUT_CHANNEL_H_

View File

@ -8,61 +8,16 @@
#include "flutter/shell/platform/common/text_editing_delta.h" #include "flutter/shell/platform/common/text_editing_delta.h"
#include "flutter/shell/platform/common/text_input_model.h" #include "flutter/shell/platform/common/text_input_model.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" #include "flutter/shell/platform/linux/fl_text_input_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
static constexpr char kChannelName[] = "flutter/textinput";
static constexpr char kBadArgumentsError[] = "Bad Arguments";
static constexpr char kSetClientMethod[] = "TextInput.setClient";
static constexpr char kShowMethod[] = "TextInput.show";
static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState";
static constexpr char kClearClientMethod[] = "TextInput.clearClient";
static constexpr char kHideMethod[] = "TextInput.hide";
static constexpr char kUpdateEditingStateMethod[] =
"TextInputClient.updateEditingState";
static constexpr char kUpdateEditingStateWithDeltasMethod[] =
"TextInputClient.updateEditingStateWithDeltas";
static constexpr char kPerformActionMethod[] = "TextInputClient.performAction";
static constexpr char kSetEditableSizeAndTransform[] =
"TextInput.setEditableSizeAndTransform";
static constexpr char kSetMarkedTextRect[] = "TextInput.setMarkedTextRect";
static constexpr char kInputActionKey[] = "inputAction";
static constexpr char kTextInputTypeKey[] = "inputType";
static constexpr char kEnableDeltaModel[] = "enableDeltaModel";
static constexpr char kTextInputTypeNameKey[] = "name";
static constexpr char kTextKey[] = "text";
static constexpr char kSelectionBaseKey[] = "selectionBase";
static constexpr char kSelectionExtentKey[] = "selectionExtent";
static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
static constexpr char kComposingBaseKey[] = "composingBase";
static constexpr char kComposingExtentKey[] = "composingExtent";
static constexpr char kTransform[] = "transform";
static constexpr char kTextAffinityDownstream[] = "TextAffinity.downstream";
static constexpr char kMultilineInputType[] = "TextInputType.multiline";
static constexpr char kNoneInputType[] = "TextInputType.none";
static constexpr char kNewlineInputAction[] = "TextInputAction.newline"; static constexpr char kNewlineInputAction[] = "TextInputAction.newline";
static constexpr int64_t kClientIdUnset = -1; static constexpr int64_t kClientIdUnset = -1;
typedef enum {
kFlTextInputTypeText,
// Send newline when multi-line and enter is pressed.
kFlTextInputTypeMultiline,
// The input method is not shown at all.
kFlTextInputTypeNone,
} FlTextInputType;
struct _FlTextInputHandler { struct _FlTextInputHandler {
GObject parent_instance; GObject parent_instance;
FlMethodChannel* channel; FlTextInputChannel* channel;
// Client ID provided by Flutter to report events with. // Client ID provided by Flutter to report events with.
int64_t client_id; int64_t client_id;
@ -101,26 +56,29 @@ struct _FlTextInputHandler {
G_DEFINE_TYPE(FlTextInputHandler, fl_text_input_handler, G_TYPE_OBJECT) G_DEFINE_TYPE(FlTextInputHandler, fl_text_input_handler, G_TYPE_OBJECT)
// Completes method call and returns TRUE if the call was successful.
static gboolean finish_method(GObject* object,
GAsyncResult* result,
GError** error) {
g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
FL_METHOD_CHANNEL(object), result, error);
if (response == nullptr) {
return FALSE;
}
return fl_method_response_get_result(response, error) != nullptr;
}
// Called when a response is received from TextInputClient.updateEditingState() // Called when a response is received from TextInputClient.updateEditingState()
static void update_editing_state_response_cb(GObject* object, static void update_editing_state_response_cb(GObject* object,
GAsyncResult* result, GAsyncResult* result,
gpointer user_data) { gpointer user_data) {
g_autoptr(GError) error = nullptr; g_autoptr(GError) error = nullptr;
if (!finish_method(object, result, &error)) { if (!fl_text_input_channel_update_editing_state_finish(object, result,
&error)) {
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warning("Failed to call %s: %s", kUpdateEditingStateMethod, g_warning("Failed to update editing state: %s", error->message);
}
}
}
// Called when a response is received from
// TextInputClient.updateEditingStateWithDeltas()
static void update_editing_state_with_deltas_response_cb(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(GError) error = nullptr;
if (!fl_text_input_channel_update_editing_state_with_deltas_finish(
object, result, &error)) {
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warning("Failed to update editing state with deltas: %s",
error->message); error->message);
} }
} }
@ -128,96 +86,36 @@ static void update_editing_state_response_cb(GObject* object,
// Informs Flutter of text input changes. // Informs Flutter of text input changes.
static void update_editing_state(FlTextInputHandler* self) { static void update_editing_state(FlTextInputHandler* self) {
g_autoptr(FlValue) args = fl_value_new_list();
fl_value_append_take(args, fl_value_new_int(self->client_id));
g_autoptr(FlValue) value = fl_value_new_map();
flutter::TextRange selection = self->text_model->selection();
fl_value_set_string_take(
value, kTextKey,
fl_value_new_string(self->text_model->GetText().c_str()));
fl_value_set_string_take(value, kSelectionBaseKey,
fl_value_new_int(selection.base()));
fl_value_set_string_take(value, kSelectionExtentKey,
fl_value_new_int(selection.extent()));
int composing_base = -1; int composing_base = -1;
int composing_extent = -1; int composing_extent = -1;
if (!self->text_model->composing_range().collapsed()) { if (!self->text_model->composing_range().collapsed()) {
composing_base = self->text_model->composing_range().base(); composing_base = self->text_model->composing_range().base();
composing_extent = self->text_model->composing_range().extent(); composing_extent = self->text_model->composing_range().extent();
} }
fl_value_set_string_take(value, kComposingBaseKey, flutter::TextRange selection = self->text_model->selection();
fl_value_new_int(composing_base)); fl_text_input_channel_update_editing_state(
fl_value_set_string_take(value, kComposingExtentKey, self->channel, self->client_id, self->text_model->GetText().c_str(),
fl_value_new_int(composing_extent)); selection.base(), selection.extent(), FL_TEXT_AFFINITY_DOWNSTREAM, FALSE,
composing_base, composing_extent, self->cancellable,
// The following keys are not implemented and set to default values. update_editing_state_response_cb, self);
fl_value_set_string_take(value, kSelectionAffinityKey,
fl_value_new_string(kTextAffinityDownstream));
fl_value_set_string_take(value, kSelectionIsDirectionalKey,
fl_value_new_bool(FALSE));
fl_value_append(args, value);
fl_method_channel_invoke_method(self->channel, kUpdateEditingStateMethod,
args, self->cancellable,
update_editing_state_response_cb, self);
} }
// Informs Flutter of text input changes by passing just the delta. // Informs Flutter of text input changes by passing just the delta.
static void update_editing_state_with_delta(FlTextInputHandler* self, static void update_editing_state_with_delta(FlTextInputHandler* self,
flutter::TextEditingDelta* delta) { flutter::TextEditingDelta* delta) {
g_autoptr(FlValue) args = fl_value_new_list();
fl_value_append_take(args, fl_value_new_int(self->client_id));
g_autoptr(FlValue) deltaValue = fl_value_new_map();
fl_value_set_string_take(deltaValue, "oldText",
fl_value_new_string(delta->old_text().c_str()));
fl_value_set_string_take(deltaValue, "deltaText",
fl_value_new_string(delta->delta_text().c_str()));
fl_value_set_string_take(deltaValue, "deltaStart",
fl_value_new_int(delta->delta_start()));
fl_value_set_string_take(deltaValue, "deltaEnd",
fl_value_new_int(delta->delta_end()));
flutter::TextRange selection = self->text_model->selection(); flutter::TextRange selection = self->text_model->selection();
fl_value_set_string_take(deltaValue, "selectionBase",
fl_value_new_int(selection.base()));
fl_value_set_string_take(deltaValue, "selectionExtent",
fl_value_new_int(selection.extent()));
fl_value_set_string_take(deltaValue, "selectionAffinity",
fl_value_new_string(kTextAffinityDownstream));
fl_value_set_string_take(deltaValue, "selectionIsDirectional",
fl_value_new_bool(FALSE));
int composing_base = -1; int composing_base = -1;
int composing_extent = -1; int composing_extent = -1;
if (!self->text_model->composing_range().collapsed()) { if (!self->text_model->composing_range().collapsed()) {
composing_base = self->text_model->composing_range().base(); composing_base = self->text_model->composing_range().base();
composing_extent = self->text_model->composing_range().extent(); composing_extent = self->text_model->composing_range().extent();
} }
fl_value_set_string_take(deltaValue, "composingBase", fl_text_input_channel_update_editing_state_with_deltas(
fl_value_new_int(composing_base)); self->channel, self->client_id, delta->old_text().c_str(),
fl_value_set_string_take(deltaValue, "composingExtent", delta->delta_text().c_str(), delta->delta_start(), delta->delta_end(),
fl_value_new_int(composing_extent)); selection.base(), selection.extent(), FL_TEXT_AFFINITY_DOWNSTREAM, FALSE,
composing_base, composing_extent, self->cancellable,
g_autoptr(FlValue) deltas = fl_value_new_list(); update_editing_state_with_deltas_response_cb, self);
fl_value_append(deltas, deltaValue);
g_autoptr(FlValue) value = fl_value_new_map();
fl_value_set_string(value, "deltas", deltas);
fl_value_append(args, value);
fl_method_channel_invoke_method(
self->channel, kUpdateEditingStateWithDeltasMethod, args,
self->cancellable, update_editing_state_response_cb, self);
} }
// Called when a response is received from TextInputClient.performAction() // Called when a response is received from TextInputClient.performAction()
@ -225,9 +123,9 @@ static void perform_action_response_cb(GObject* object,
GAsyncResult* result, GAsyncResult* result,
gpointer user_data) { gpointer user_data) {
g_autoptr(GError) error = nullptr; g_autoptr(GError) error = nullptr;
if (!finish_method(object, result, &error)) { if (!fl_text_input_channel_perform_action_finish(object, result, &error)) {
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warning("Failed to call %s: %s", kPerformActionMethod, error->message); g_warning("Failed to perform action: %s", error->message);
} }
} }
} }
@ -238,13 +136,9 @@ static void perform_action(FlTextInputHandler* self) {
g_return_if_fail(self->client_id != 0); g_return_if_fail(self->client_id != 0);
g_return_if_fail(self->input_action != nullptr); g_return_if_fail(self->input_action != nullptr);
g_autoptr(FlValue) args = fl_value_new_list(); fl_text_input_channel_perform_action(self->channel, self->client_id,
fl_value_append_take(args, fl_value_new_int(self->client_id)); self->input_action, self->cancellable,
fl_value_append_take(args, fl_value_new_string(self->input_action)); perform_action_response_cb, self);
fl_method_channel_invoke_method(self->channel, kPerformActionMethod, args,
self->cancellable, perform_action_response_cb,
self);
} }
// Signal handler for GtkIMContext::preedit-start // Signal handler for GtkIMContext::preedit-start
@ -344,76 +238,50 @@ static gboolean im_delete_surrounding_cb(FlTextInputHandler* self,
} }
// Called when the input method client is set up. // Called when the input method client is set up.
static FlMethodResponse* set_client(FlTextInputHandler* self, FlValue* args) { static void set_client(int64_t client_id,
if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || const gchar* input_action,
fl_value_get_length(args) < 2) { gboolean enable_delta_model,
return FL_METHOD_RESPONSE(fl_method_error_response_new( FlTextInputType input_type,
kBadArgumentsError, "Expected 2-element list", nullptr)); gpointer user_data) {
} FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data);
self->client_id = fl_value_get_int(fl_value_get_list_value(args, 0)); self->client_id = client_id;
FlValue* config_value = fl_value_get_list_value(args, 1);
g_free(self->input_action); g_free(self->input_action);
FlValue* input_action_value = self->input_action = g_strdup(input_action);
fl_value_lookup_string(config_value, kInputActionKey);
if (fl_value_get_type(input_action_value) == FL_VALUE_TYPE_STRING) {
self->input_action = g_strdup(fl_value_get_string(input_action_value));
}
FlValue* enable_delta_model_value =
fl_value_lookup_string(config_value, kEnableDeltaModel);
gboolean enable_delta_model = fl_value_get_bool(enable_delta_model_value);
self->enable_delta_model = enable_delta_model; self->enable_delta_model = enable_delta_model;
self->input_type = input_type;
// Reset the input type, then set only if appropriate.
self->input_type = kFlTextInputTypeText;
FlValue* input_type_value =
fl_value_lookup_string(config_value, kTextInputTypeKey);
if (fl_value_get_type(input_type_value) == FL_VALUE_TYPE_MAP) {
FlValue* input_type_name =
fl_value_lookup_string(input_type_value, kTextInputTypeNameKey);
if (fl_value_get_type(input_type_name) == FL_VALUE_TYPE_STRING) {
const gchar* input_type = fl_value_get_string(input_type_name);
if (g_strcmp0(input_type, kMultilineInputType) == 0) {
self->input_type = kFlTextInputTypeMultiline;
} else if (g_strcmp0(input_type, kNoneInputType) == 0) {
self->input_type = kFlTextInputTypeNone;
}
}
}
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} }
// Hides the input method. // Hides the input method.
static FlMethodResponse* hide(FlTextInputHandler* self) { static void hide(gpointer user_data) {
gtk_im_context_focus_out(self->im_context); FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); gtk_im_context_focus_out(self->im_context);
} }
// Shows the input method. // Shows the input method.
static FlMethodResponse* show(FlTextInputHandler* self) { static void show(gpointer user_data) {
if (self->input_type == kFlTextInputTypeNone) { FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data);
return hide(self);
if (self->input_type == FL_TEXT_INPUT_TYPE_NONE) {
hide(user_data);
return;
} }
gtk_im_context_focus_in(self->im_context); gtk_im_context_focus_in(self->im_context);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} }
// Updates the editing state from Flutter. // Updates the editing state from Flutter.
static FlMethodResponse* set_editing_state(FlTextInputHandler* self, static void set_editing_state(const gchar* text,
FlValue* args) { int64_t selection_base,
const gchar* text = int64_t selection_extent,
fl_value_get_string(fl_value_lookup_string(args, kTextKey)); int64_t composing_base,
int64_t composing_extent,
gpointer user_data) {
FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data);
self->text_model->SetText(text); self->text_model->SetText(text);
int64_t selection_base =
fl_value_get_int(fl_value_lookup_string(args, kSelectionBaseKey));
int64_t selection_extent =
fl_value_get_int(fl_value_lookup_string(args, kSelectionExtentKey));
// Flutter uses -1/-1 for invalid; translate that to 0/0 for the model. // Flutter uses -1/-1 for invalid; translate that to 0/0 for the model.
if (selection_base == -1 && selection_extent == -1) { if (selection_base == -1 && selection_extent == -1) {
selection_base = selection_extent = 0; selection_base = selection_extent = 0;
@ -423,10 +291,6 @@ static FlMethodResponse* set_editing_state(FlTextInputHandler* self,
self->text_model->SetSelection( self->text_model->SetSelection(
flutter::TextRange(selection_base, selection_extent)); flutter::TextRange(selection_base, selection_extent));
int64_t composing_base =
fl_value_get_int(fl_value_lookup_string(args, kComposingBaseKey));
int64_t composing_extent =
fl_value_get_int(fl_value_lookup_string(args, kComposingExtentKey));
if (composing_base == -1 && composing_extent == -1) { if (composing_base == -1 && composing_extent == -1) {
self->text_model->EndComposing(); self->text_model->EndComposing();
} else { } else {
@ -435,25 +299,22 @@ static FlMethodResponse* set_editing_state(FlTextInputHandler* self,
self->text_model->SetComposingRange( self->text_model->SetComposingRange(
flutter::TextRange(composing_base, composing_extent), cursor_offset); flutter::TextRange(composing_base, composing_extent), cursor_offset);
} }
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} }
// Called when the input method client is complete. // Called when the input method client is complete.
static FlMethodResponse* clear_client(FlTextInputHandler* self) { static void clear_client(gpointer user_data) {
FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data);
self->client_id = kClientIdUnset; self->client_id = kClientIdUnset;
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} }
// Update the IM cursor position. // Update the IM cursor position.
// //
// As text is input by the user, the framework sends two streams of updates // As text is input by the user, the framework sends two streams of updates
// over the text input channel: updates to the composing rect (cursor rect when // over the text input channel: updates to the composing rect (cursor rect
// not in IME composing mode) and updates to the matrix transform from local // when not in IME composing mode) and updates to the matrix transform from
// coordinates to Flutter root coordinates. This function is called after each // local coordinates to Flutter root coordinates. This function is called
// of these updates. It transforms the composing rect to GDK window coordinates // after each of these updates. It transforms the composing rect to GDK window
// and notifies GTK of the updated cursor position. // coordinates and notifies GTK of the updated cursor position.
static void update_im_cursor_position(FlTextInputHandler* self) { static void update_im_cursor_position(FlTextInputHandler* self) {
g_autoptr(FlTextInputViewDelegate) view_delegate = g_autoptr(FlTextInputViewDelegate) view_delegate =
FL_TEXT_INPUT_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); FL_TEXT_INPUT_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
@ -480,8 +341,8 @@ static void update_im_cursor_position(FlTextInputHandler* self) {
fl_text_input_view_delegate_translate_coordinates( fl_text_input_view_delegate_translate_coordinates(
view_delegate, x, y, &preedit_rect.x, &preedit_rect.y); view_delegate, x, y, &preedit_rect.x, &preedit_rect.y);
// Set the cursor location in window coordinates so that GTK can position any // Set the cursor location in window coordinates so that GTK can position
// system input method windows. // any system input method windows.
gtk_im_context_set_cursor_location(self->im_context, &preedit_rect); gtk_im_context_set_cursor_location(self->im_context, &preedit_rect);
} }
@ -491,20 +352,14 @@ static void update_im_cursor_position(FlTextInputHandler* self) {
// EditableText, this update may be triggered. It provides an updated size and // EditableText, this update may be triggered. It provides an updated size and
// transform from the local coordinate system of the EditableText to root // transform from the local coordinate system of the EditableText to root
// Flutter coordinate system. // Flutter coordinate system.
static FlMethodResponse* set_editable_size_and_transform( static void set_editable_size_and_transform(double* transform,
FlTextInputHandler* self, gpointer user_data) {
FlValue* args) { FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data);
FlValue* transform = fl_value_lookup_string(args, kTransform);
size_t transform_len = fl_value_get_length(transform);
g_warn_if_fail(transform_len == 16);
for (size_t i = 0; i < transform_len; ++i) { for (size_t i = 0; i < 16; i++) {
double val = fl_value_get_float(fl_value_get_list_value(transform, i)); self->editabletext_transform[i / 4][i % 4] = transform[i];
self->editabletext_transform[i / 4][i % 4] = val;
} }
update_im_cursor_position(self); update_im_cursor_position(self);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} }
// Handles updates to the composing rect from the framework. // Handles updates to the composing rect from the framework.
@ -513,53 +368,18 @@ static FlMethodResponse* set_editable_size_and_transform(
// may be triggered. It provides an updated rect for the composing region in // may be triggered. It provides an updated rect for the composing region in
// local coordinates of the EditableText. In the case where there is no // local coordinates of the EditableText. In the case where there is no
// composing region, the cursor rect is sent. // composing region, the cursor rect is sent.
static FlMethodResponse* set_marked_text_rect(FlTextInputHandler* self, static void set_marked_text_rect(double x,
FlValue* args) { double y,
self->composing_rect.x = double width,
fl_value_get_float(fl_value_lookup_string(args, "x")); double height,
self->composing_rect.y = gpointer user_data) {
fl_value_get_float(fl_value_lookup_string(args, "y"));
self->composing_rect.width =
fl_value_get_float(fl_value_lookup_string(args, "width"));
self->composing_rect.height =
fl_value_get_float(fl_value_lookup_string(args, "height"));
update_im_cursor_position(self);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Called when a method call is received from Flutter.
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data); FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data);
const gchar* method = fl_method_call_get_name(method_call); self->composing_rect.x = x;
FlValue* args = fl_method_call_get_args(method_call); self->composing_rect.y = y;
self->composing_rect.width = width;
g_autoptr(FlMethodResponse) response = nullptr; self->composing_rect.height = height;
if (strcmp(method, kSetClientMethod) == 0) { update_im_cursor_position(self);
response = set_client(self, args);
} else if (strcmp(method, kShowMethod) == 0) {
response = show(self);
} else if (strcmp(method, kSetEditingStateMethod) == 0) {
response = set_editing_state(self, args);
} else if (strcmp(method, kClearClientMethod) == 0) {
response = clear_client(self);
} else if (strcmp(method, kHideMethod) == 0) {
response = hide(self);
} else if (strcmp(method, kSetEditableSizeAndTransform) == 0) {
response = set_editable_size_and_transform(self, args);
} else if (strcmp(method, kSetMarkedTextRect) == 0) {
response = set_marked_text_rect(self, args);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
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);
}
} }
// Disposes of an FlTextInputHandler. // Disposes of an FlTextInputHandler.
@ -589,7 +409,7 @@ static void fl_text_input_handler_class_init(FlTextInputHandlerClass* klass) {
// Initializes an instance of the FlTextInputHandler class. // Initializes an instance of the FlTextInputHandler class.
static void fl_text_input_handler_init(FlTextInputHandler* self) { static void fl_text_input_handler_init(FlTextInputHandler* self) {
self->client_id = kClientIdUnset; self->client_id = kClientIdUnset;
self->input_type = kFlTextInputTypeText; self->input_type = FL_TEXT_INPUT_TYPE_TEXT;
self->text_model = new flutter::TextInputModel(); self->text_model = new flutter::TextInputModel();
self->cancellable = g_cancellable_new(); self->cancellable = g_cancellable_new();
} }
@ -622,6 +442,16 @@ static void init_im_context(FlTextInputHandler* self,
G_CONNECT_SWAPPED); G_CONNECT_SWAPPED);
} }
static FlTextInputChannelVTable text_input_vtable = {
.set_client = set_client,
.hide = hide,
.show = show,
.set_editing_state = set_editing_state,
.clear_client = clear_client,
.set_editable_size_and_transform = set_editable_size_and_transform,
.set_marked_text_rect = set_marked_text_rect,
};
FlTextInputHandler* fl_text_input_handler_new( FlTextInputHandler* fl_text_input_handler_new(
FlBinaryMessenger* messenger, FlBinaryMessenger* messenger,
GtkIMContext* im_context, GtkIMContext* im_context,
@ -633,11 +463,8 @@ FlTextInputHandler* fl_text_input_handler_new(
FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER( FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(
g_object_new(fl_text_input_handler_get_type(), nullptr)); g_object_new(fl_text_input_handler_get_type(), nullptr));
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
self->channel = self->channel =
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); fl_text_input_channel_new(messenger, &text_input_vtable, self);
fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self,
nullptr);
init_im_context(self, im_context); init_im_context(self, im_context);
@ -681,7 +508,7 @@ gboolean fl_text_input_handler_filter_keypress(FlTextInputHandler* self,
case GDK_KEY_Return: case GDK_KEY_Return:
case GDK_KEY_KP_Enter: case GDK_KEY_KP_Enter:
case GDK_KEY_ISO_Enter: case GDK_KEY_ISO_Enter:
if (self->input_type == kFlTextInputTypeMultiline && if (self->input_type == FL_TEXT_INPUT_TYPE_MULTILINE &&
strcmp(self->input_action, kNewlineInputAction) == 0) { strcmp(self->input_action, kNewlineInputAction) == 0) {
self->text_model->AddCodePoint('\n'); self->text_model->AddCodePoint('\n');
text = "\n"; text = "\n";