From d03ffc4908c82cfd23a08460a36e38ab99d38dbb Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Tue, 29 Sep 2020 18:02:45 -0700 Subject: [PATCH 1/5] Add delayed event delivery for Linux. This changes the text handling so that keyboard events are sent to the framework first for handling, and then passed to the text input plugin, so that the framework has a chance to handle keys before they get given to the text field. --- shell/platform/linux/BUILD.gn | 1 + shell/platform/linux/fl_key_event_plugin.cc | 173 +++++++++++- shell/platform/linux/fl_key_event_plugin.h | 36 ++- .../linux/fl_key_event_plugin_test.cc | 153 +++++++++-- shell/platform/linux/fl_text_input_plugin.cc | 255 +++++++++++------- shell/platform/linux/fl_text_input_plugin.h | 19 +- shell/platform/linux/fl_view.cc | 19 +- shell/platform/linux/testing/mock_engine.cc | 13 + .../linux/testing/mock_text_input_plugin.cc | 41 +++ .../linux/testing/mock_text_input_plugin.h | 18 ++ 10 files changed, 579 insertions(+), 149 deletions(-) create mode 100644 shell/platform/linux/testing/mock_text_input_plugin.cc create mode 100644 shell/platform/linux/testing/mock_text_input_plugin.h diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 6dcc1779f1930..58e5fdfa7f174 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -177,6 +177,7 @@ executable("flutter_linux_unittests") { "testing/mock_egl.cc", "testing/mock_engine.cc", "testing/mock_renderer.cc", + "testing/mock_text_input_plugin.cc", ] public_configs = [ "//flutter:config" ] diff --git a/shell/platform/linux/fl_key_event_plugin.cc b/shell/platform/linux/fl_key_event_plugin.cc index fe5628b55f007..d1e605069547f 100644 --- a/shell/platform/linux/fl_key_event_plugin.cc +++ b/shell/platform/linux/fl_key_event_plugin.cc @@ -3,6 +3,10 @@ // found in the LICENSE file. #include "flutter/shell/platform/linux/fl_key_event_plugin.h" + +#include + +#include "flutter/shell/platform/linux/fl_text_input_plugin.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" @@ -20,11 +24,16 @@ static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues"; static constexpr char kGtkToolkit[] = "gtk"; static constexpr char kLinuxKeymap[] = "linux"; +static constexpr uint64_t kMaxPendingEvents = 1000; + struct _FlKeyEventPlugin { GObject parent_instance; FlBasicMessageChannel* channel = nullptr; - GAsyncReadyCallback response_callback = nullptr; + FlTextInputPlugin* text_input_plugin = nullptr; + FlKeyEventPluginCallback response_callback = nullptr; + + std::deque> pendingEvents; }; G_DEFINE_TYPE(FlKeyEventPlugin, fl_key_event_plugin, G_TYPE_OBJECT) @@ -41,12 +50,17 @@ static void fl_key_event_plugin_class_init(FlKeyEventPluginClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_key_event_plugin_dispose; } -static void fl_key_event_plugin_init(FlKeyEventPlugin* self) {} +static void fl_key_event_plugin_init(FlKeyEventPlugin* self) { + self->pendingEvents.clear(); +} -FlKeyEventPlugin* fl_key_event_plugin_new(FlBinaryMessenger* messenger, - GAsyncReadyCallback response_callback, - const char* channel_name) { +FlKeyEventPlugin* fl_key_event_plugin_new( + FlBinaryMessenger* messenger, + FlTextInputPlugin* text_input_plugin, + FlKeyEventPluginCallback response_callback, + const char* channel_name) { g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(text_input_plugin), nullptr); FlKeyEventPlugin* self = FL_KEY_EVENT_PLUGIN( g_object_new(fl_key_event_plugin_get_type(), nullptr)); @@ -56,15 +70,143 @@ FlKeyEventPlugin* fl_key_event_plugin_new(FlBinaryMessenger* messenger, messenger, channel_name == nullptr ? kChannelName : channel_name, FL_MESSAGE_CODEC(codec)); self->response_callback = response_callback; + // Add a weak pointer so we know if the text input plugin goes away. + g_object_add_weak_pointer( + G_OBJECT(text_input_plugin), + reinterpret_cast(&(self->text_input_plugin))); + self->text_input_plugin = text_input_plugin; return self; } -void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, +uint64_t fl_get_event_id(GdkEventKey* event) { + // Combine the event timestamp, the type of event, and the hardware keycode + // (scan code) of the event to come up with a unique id for this event that + // can be derived solely from the event data itself, so that we can identify + // whether or not we have seen this event already. + return (event->time & 0xffffffff) | + (static_cast(event->type) & 0xffff) << 32 | + (static_cast(event->hardware_keycode) & 0xffff) << 48; +} + +GdkEventKey* fl_find_pending_event(FlKeyEventPlugin* self, uint64_t id) { + if (self->pendingEvents.empty() || self->pendingEvents.front().first != id) { + return nullptr; + } + return self->pendingEvents.front().second; +} + +void fl_remove_pending_event(FlKeyEventPlugin* self, uint64_t id) { + if (self->pendingEvents.empty() || self->pendingEvents.front().first != id) { + g_warning( + "Tried to remove pending event with id %ld, but the event was out of " + "order, or is unknown.", + id); + return; + } + self->pendingEvents.pop_front(); +} + +void fl_add_pending_event(FlKeyEventPlugin* self, + uint64_t id, + GdkEventKey* event) { + if (self->pendingEvents.size() > kMaxPendingEvents) { + g_warning( + "There are %ld keyboard events that have not yet received a " + "response from the framework. Are responses being sent?", + self->pendingEvents.size()); + } + // Copy the event to preserve refcounts for referenced values (mainly the + // window). + GdkEventKey* event_copy = reinterpret_cast( + gdk_event_copy(reinterpret_cast(event))); + self->pendingEvents.push_back(std::make_pair(id, event_copy)); +} + +struct _KeyEventResponseData { + FlKeyEventPlugin* self; + uint64_t id; + gpointer user_data; +}; + +void fl_handle_response(GObject* object, + GAsyncResult* result, + gpointer user_data) { + _KeyEventResponseData* data = + reinterpret_cast<_KeyEventResponseData*>(user_data); + if (data->self == nullptr) { + // Weak pointer to the plugin has been destroyed. + return; + } + g_return_if_fail(FL_IS_KEY_EVENT_PLUGIN(data->self)); + + g_autoptr(GError) error = nullptr; + FlBasicMessageChannel* messageChannel = FL_BASIC_MESSAGE_CHANNEL(object); + FlValue* message = + fl_basic_message_channel_send_finish(messageChannel, result, &error); + if (error != nullptr) { + g_error("Unable to retrieve framework response: %s", error->message); + g_error_free(error); + return; + } + g_autoptr(FlValue) handled_value = fl_value_lookup_string(message, "handled"); + bool handled = false; + if (handled_value != nullptr) { + GdkEventKey* event = fl_find_pending_event(data->self, data->id); + if (event == nullptr) { + g_warning( + "Event response for event id %ld received, but event was received " + "out of order, or is unknown.", + data->id); + } else { + handled = fl_value_get_bool(handled_value); + if (!handled) { + if (data->self->text_input_plugin != nullptr) { + // Propagate the event to the text input plugin. + handled = fl_text_input_plugin_filter_keypress( + data->self->text_input_plugin, event); + } + // Dispatch the event to other GTK windows if the text input plugin + // didn't handle it. We keep track of the event id so we can recognize + // the event when our window receives it again and not respond to it. If + // the response callback is set, then use that instead. + if (!handled && data->self->response_callback == nullptr) { + gdk_event_put(reinterpret_cast(event)); + } + } + } + } + + if (handled) { + // Because the event was handled, we no longer need to track it. Unhandled + // events will be removed when the event is re-dispatched to the window. + fl_remove_pending_event(data->self, data->id); + } + + if (data->self->response_callback != nullptr) { + data->self->response_callback(object, message, handled, data->user_data); + } +} + +bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, GdkEventKey* event, gpointer user_data) { - g_return_if_fail(FL_IS_KEY_EVENT_PLUGIN(self)); - g_return_if_fail(event != nullptr); + g_return_val_if_fail(FL_IS_KEY_EVENT_PLUGIN(self), false); + g_return_val_if_fail(event != nullptr, false); + + // Get an ID for the event, so we can match them up when we get a response + // from the framework. Use the event time, type, and hardware keycode as a + // unique ID, since they are part of the event structure that we can look up + // when we receive a random event that may or may not have been + // tracked/produced by this code. + uint64_t id = fl_get_event_id(event); + if (!self->pendingEvents.empty() && self->pendingEvents.front().first == id) { + // If the event is at the head of the queue of pending events we've seen, + // and has the same id, then we know that this is a re-dispatched event, and + // we shouldn't respond to it, but we should remove it from tracking. + fl_remove_pending_event(self, id); + return false; + } const gchar* type; switch (event->type) { @@ -75,7 +217,7 @@ void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, type = kTypeValueUp; break; default: - return; + return false; } int64_t scan_code = event->hardware_keycode; @@ -144,6 +286,17 @@ void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, fl_value_new_int(unicodeScalarValues)); } + // Track the event as pending a response from the framework. + fl_add_pending_event(self, id, event); + _KeyEventResponseData* data = new _KeyEventResponseData{self, id, user_data}; + // Add a weak pointer so we can know if the key event plugin disappeared + // while the framework was responding. + g_object_add_weak_pointer(G_OBJECT(self), + reinterpret_cast(&(data->self))); + // Send the message off to the framework for handling (or not). fl_basic_message_channel_send(self->channel, message, nullptr, - self->response_callback, user_data); + fl_handle_response, data); + // Return true before we know what the framework will do, because if it + // doesn't handle the key, we'll re-dispatch it later. + return true; } diff --git a/shell/platform/linux/fl_key_event_plugin.h b/shell/platform/linux/fl_key_event_plugin.h index 4b6b2ae822399..17a1eec17ecef 100644 --- a/shell/platform/linux/fl_key_event_plugin.h +++ b/shell/platform/linux/fl_key_event_plugin.h @@ -5,7 +5,9 @@ #ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_PLUGIN_H_ #define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_PLUGIN_H_ +#include "flutter/shell/platform/linux/fl_text_input_plugin.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h" #include @@ -24,15 +26,33 @@ G_DECLARE_FINAL_TYPE(FlKeyEventPlugin, * of SystemChannels.keyEvent from the Flutter services library. */ +/** + * FlKeyEventPluginCallback: + * @source_object: (nullable): the object the key event was started with. + * @handled: a boolean indicating if the key event was handled or not. + * @user_data: user data passed to the callback. + * + * Type definition for a function that will be called when a key event is + * received from the engine. + **/ +typedef void (*FlKeyEventPluginCallback)(GObject* source_object, + FlValue* message, + bool handled, + gpointer user_data); + /** * fl_key_event_plugin_new: * @messenger: an #FlBinaryMessenger. * @response_callback: the callback to call when a response is received. If not * given (nullptr), then the default response callback is - * used. - * @channel_name: the name of the channel to send key events on. If not given - * (nullptr), then the standard key event channel name is used. - * Typically used for tests to send on a test channel. + * used. Typically used for tests to receive event information. + * If specified, unhandled events will not be re-dispatched. + * @text_input_plugin: The #FlTextInputPlugin to send key events to if the framework + * doesn't handle them. + * @channel_name: the name of the channel to send key events to the framework + * on. If not given (nullptr), then the standard key event + * channel name is used. Typically used for tests to send on a + * test channel. * * Creates a new plugin that implements SystemChannels.keyEvent from the * Flutter services library. @@ -41,7 +61,8 @@ G_DECLARE_FINAL_TYPE(FlKeyEventPlugin, */ FlKeyEventPlugin* fl_key_event_plugin_new( FlBinaryMessenger* messenger, - GAsyncReadyCallback response_callback = nullptr, + FlTextInputPlugin* text_input_plugin, + FlKeyEventPluginCallback response_callback = nullptr, const char* channel_name = nullptr); /** @@ -51,9 +72,12 @@ FlKeyEventPlugin* fl_key_event_plugin_new( * @user_data: a pointer to user data to send to the response callback via the * messenger. * + * @returns Whether or not this key event should be considered handled and + * event propagation stopped. + * * Sends a key event to Flutter. */ -void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* plugin, +bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* plugin, GdkEventKey* event, gpointer user_data = nullptr); diff --git a/shell/platform/linux/fl_key_event_plugin_test.cc b/shell/platform/linux/fl_key_event_plugin_test.cc index 4f3f99a39de39..174efc5538d5e 100644 --- a/shell/platform/linux/fl_key_event_plugin_test.cc +++ b/shell/platform/linux/fl_key_event_plugin_test.cc @@ -4,41 +4,47 @@ #include "flutter/shell/platform/linux/fl_key_event_plugin.h" -#include #include "gtest/gtest.h" #include "flutter/shell/platform/linux/fl_binary_messenger_private.h" #include "flutter/shell/platform/linux/fl_engine_private.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h" #include "flutter/shell/platform/linux/testing/fl_test.h" -#include "flutter/shell/platform/linux/testing/mock_renderer.h" +#include "flutter/shell/platform/linux/testing/mock_text_input_plugin.h" const char* expected_value = nullptr; +bool expected_handled = false; // Called when the message response is received in the send_key_event test. static void echo_response_cb(GObject* object, - GAsyncResult* result, + FlValue* message, + bool handled, gpointer user_data) { - g_autoptr(GError) error = nullptr; - g_autoptr(FlValue) message = fl_basic_message_channel_send_finish( - FL_BASIC_MESSAGE_CHANNEL(object), result, &error); EXPECT_NE(message, nullptr); - EXPECT_EQ(error, nullptr); - EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_MAP); EXPECT_STREQ(fl_value_to_string(message), expected_value); + EXPECT_EQ(handled, expected_handled); + g_main_loop_quit(static_cast(user_data)); } +static gboolean handle_keypress(FlTextInputPlugin* plugin, GdkEventKey* event) { + return true; +} + +static gboolean ignore_keypress(FlTextInputPlugin* plugin, GdkEventKey* event) { + return false; +} + // Test sending a letter "A"; TEST(FlKeyEventPluginTest, SendKeyEvent) { g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); g_autoptr(FlEngine) engine = make_mock_engine(); FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); - g_autoptr(FlKeyEventPlugin) plugin = - fl_key_event_plugin_new(messenger, echo_response_cb, "test/echo"); + g_autoptr(FlTextInputPlugin) text_input_plugin = + FL_TEXT_INPUT_PLUGIN(fl_mock_text_input_plugin_new(handle_keypress)); + g_autoptr(FlKeyEventPlugin) plugin = fl_key_event_plugin_new( + messenger, text_input_plugin, echo_response_cb, "test/echo"); char string[] = "A"; GdkEventKey key_event = GdkEventKey{ @@ -58,6 +64,7 @@ TEST(FlKeyEventPluginTest, SendKeyEvent) { expected_value = "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, " "modifiers: 0, unicodeScalarValues: 65}"; + expected_handled = false; fl_key_event_plugin_send_key_event(plugin, &key_event, loop); // Blocks here until echo_response_cb is called. @@ -67,7 +74,7 @@ TEST(FlKeyEventPluginTest, SendKeyEvent) { GDK_KEY_RELEASE, // event type nullptr, // window (not needed) FALSE, // event was sent explicitly - 12345, // time + 23456, // time 0x0, // modifier state GDK_KEY_A, // key code 1, // length of string representation @@ -80,7 +87,9 @@ TEST(FlKeyEventPluginTest, SendKeyEvent) { expected_value = "{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, " "modifiers: 0, unicodeScalarValues: 65}"; - fl_key_event_plugin_send_key_event(plugin, &key_event, loop); + expected_handled = false; + bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); + EXPECT_TRUE(handled); // Blocks here until echo_response_cb is called. g_main_loop_run(loop); @@ -93,8 +102,10 @@ void test_lock_event(guint key_code, g_autoptr(FlEngine) engine = make_mock_engine(); FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); - g_autoptr(FlKeyEventPlugin) plugin = - fl_key_event_plugin_new(messenger, echo_response_cb, "test/echo"); + g_autoptr(FlTextInputPlugin) text_input_plugin = + FL_TEXT_INPUT_PLUGIN(fl_mock_text_input_plugin_new(handle_keypress)); + g_autoptr(FlKeyEventPlugin) plugin = fl_key_event_plugin_new( + messenger, text_input_plugin, echo_response_cb, "test/echo"); GdkEventKey key_event = GdkEventKey{ GDK_KEY_PRESS, // event type @@ -111,14 +122,18 @@ void test_lock_event(guint key_code, }; expected_value = down_expected; - fl_key_event_plugin_send_key_event(plugin, &key_event, loop); + expected_handled = false; + bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); + EXPECT_TRUE(handled); // Blocks here until echo_response_cb is called. g_main_loop_run(loop); key_event.type = GDK_KEY_RELEASE; + key_event.time++; expected_value = up_expected; + expected_handled = false; fl_key_event_plugin_send_key_event(plugin, &key_event, loop); // Blocks here until echo_response_cb is called. @@ -151,3 +166,107 @@ TEST(FlKeyEventPluginTest, SendShiftLockKeyEvent) { "{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, " "keyCode: 65510, modifiers: 0}"); } + +TEST(FlKeyEventPluginTest, TestKeyEventHandledByFramework) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlTextInputPlugin) text_input_plugin = + FL_TEXT_INPUT_PLUGIN(fl_mock_text_input_plugin_new(handle_keypress)); + g_autoptr(FlKeyEventPlugin) plugin = fl_key_event_plugin_new( + messenger, text_input_plugin, echo_response_cb, "test/key-event-handled"); + + GdkEventKey key_event = GdkEventKey{ + GDK_KEY_PRESS, // event type + nullptr, // window (not needed) + FALSE, // event was sent explicitly + 12345, // time + 0x10, // modifier state + GDK_KEY_A, // key code + 1, // length of string representation + nullptr, // string representation + 0x04, // scan code + 0, // keyboard group + 0, // is a modifier + }; + + expected_value = "{handled: true}"; + expected_handled = true; + bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); + // Should always be true, because the event was delayed. + EXPECT_TRUE(handled); + + // Blocks here until echo_response_cb is called. + g_main_loop_run(loop); +} + +TEST(FlKeyEventPluginTest, TestKeyEventHandledByTextInputPlugin) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlTextInputPlugin) text_input_plugin = + FL_TEXT_INPUT_PLUGIN(fl_mock_text_input_plugin_new(handle_keypress)); + g_autoptr(FlKeyEventPlugin) plugin = + fl_key_event_plugin_new(messenger, text_input_plugin, echo_response_cb, + "test/key-event-not-handled"); + + GdkEventKey key_event = GdkEventKey{ + GDK_KEY_PRESS, // event type + nullptr, // window (not needed) + FALSE, // event was sent explicitly + 12345, // time + 0x10, // modifier state + GDK_KEY_A, // key code + 1, // length of string representation + nullptr, // string representation + 0x04, // scan code + 0, // keyboard group + 0, // is a modifier + }; + + expected_value = "{handled: false}"; + expected_handled = true; + bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); + // Should always be true, because the event was delayed. + EXPECT_TRUE(handled); + + // Blocks here until echo_response_cb is called. + g_main_loop_run(loop); +} + +TEST(FlKeyEventPluginTest, TestKeyEventNotHandledByTextInputPlugin) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlTextInputPlugin) text_input_plugin = + FL_TEXT_INPUT_PLUGIN(fl_mock_text_input_plugin_new(ignore_keypress)); + g_autoptr(FlKeyEventPlugin) plugin = + fl_key_event_plugin_new(messenger, text_input_plugin, echo_response_cb, + "test/key-event-not-handled"); + + GdkEventKey key_event = GdkEventKey{ + GDK_KEY_PRESS, // event type + nullptr, // window (not needed) + FALSE, // event was sent explicitly + 12345, // time + 0x10, // modifier state + GDK_KEY_A, // key code + 1, // length of string representation + nullptr, // string representation + 0x04, // scan code + 0, // keyboard group + 0, // is a modifier + }; + + expected_value = "{handled: false}"; + expected_handled = false; + bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); + // Should always be true, because the event was delayed. + EXPECT_TRUE(handled); + + // Blocks here until echo_response_cb is called. + g_main_loop_run(loop); +} diff --git a/shell/platform/linux/fl_text_input_plugin.cc b/shell/platform/linux/fl_text_input_plugin.cc index 2841db1e79ca7..248338b37cce4 100644 --- a/shell/platform/linux/fl_text_input_plugin.cc +++ b/shell/platform/linux/fl_text_input_plugin.cc @@ -44,7 +44,7 @@ static constexpr char kMultilineInputType[] = "TextInputType.multiline"; static constexpr int64_t kClientIdUnset = -1; -struct _FlTextInputPlugin { +struct FlTextInputPluginPrivate { GObject parent_instance; FlMethodChannel* channel; @@ -77,7 +77,9 @@ struct _FlTextInputPlugin { GdkRectangle composing_rect; }; -G_DEFINE_TYPE(FlTextInputPlugin, fl_text_input_plugin, G_TYPE_OBJECT) +G_DEFINE_TYPE_WITH_PRIVATE(FlTextInputPlugin, + fl_text_input_plugin, + G_TYPE_OBJECT) // Completes method call and returns TRUE if the call was successful. static gboolean finish_method(GObject* object, @@ -104,24 +106,27 @@ static void update_editing_state_response_cb(GObject* object, // Informs Flutter of text input changes. static void update_editing_state(FlTextInputPlugin* self) { + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + g_autoptr(FlValue) args = fl_value_new_list(); - fl_value_append_take(args, fl_value_new_int(self->client_id)); + fl_value_append_take(args, fl_value_new_int(priv->client_id)); g_autoptr(FlValue) value = fl_value_new_map(); - TextRange selection = self->text_model->selection(); + TextRange selection = priv->text_model->selection(); fl_value_set_string_take( value, kTextKey, - fl_value_new_string(self->text_model->GetText().c_str())); + fl_value_new_string(priv->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 = self->text_model->composing() - ? self->text_model->composing_range().base() + int composing_base = priv->text_model->composing() + ? priv->text_model->composing_range().base() : -1; - int composing_extent = self->text_model->composing() - ? self->text_model->composing_range().extent() + int composing_extent = priv->text_model->composing() + ? priv->text_model->composing_range().extent() : -1; fl_value_set_string_take(value, kComposingBaseKey, fl_value_new_int(composing_base)); @@ -136,7 +141,7 @@ static void update_editing_state(FlTextInputPlugin* self) { fl_value_append(args, value); - fl_method_channel_invoke_method(self->channel, kUpdateEditingStateMethod, + fl_method_channel_invoke_method(priv->channel, kUpdateEditingStateMethod, args, nullptr, update_editing_state_response_cb, self); } @@ -153,61 +158,74 @@ static void perform_action_response_cb(GObject* object, // Inform Flutter that the input has been activated. static void perform_action(FlTextInputPlugin* self) { + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + g_return_if_fail(FL_IS_TEXT_INPUT_PLUGIN(self)); - g_return_if_fail(self->client_id != 0); - g_return_if_fail(self->input_action != nullptr); + g_return_if_fail(priv->client_id != 0); + g_return_if_fail(priv->input_action != nullptr); g_autoptr(FlValue) args = fl_value_new_list(); - fl_value_append_take(args, fl_value_new_int(self->client_id)); - fl_value_append_take(args, fl_value_new_string(self->input_action)); + fl_value_append_take(args, fl_value_new_int(priv->client_id)); + fl_value_append_take(args, fl_value_new_string(priv->input_action)); - fl_method_channel_invoke_method(self->channel, kPerformActionMethod, args, + fl_method_channel_invoke_method(priv->channel, kPerformActionMethod, args, nullptr, perform_action_response_cb, self); } // Signal handler for GtkIMContext::preedit-start static void im_preedit_start_cb(FlTextInputPlugin* self) { - self->text_model->BeginComposing(); + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + priv->text_model->BeginComposing(); // Set the top-level window used for system input method windows. GdkWindow* window = - gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self->view))); - gtk_im_context_set_client_window(self->im_context, window); + gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(priv->view))); + gtk_im_context_set_client_window(priv->im_context, window); } // Signal handler for GtkIMContext::preedit-changed static void im_preedit_changed_cb(FlTextInputPlugin* self) { + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); g_autofree gchar* buf = nullptr; gint cursor_offset = 0; - gtk_im_context_get_preedit_string(self->im_context, &buf, nullptr, + gtk_im_context_get_preedit_string(priv->im_context, &buf, nullptr, &cursor_offset); - cursor_offset += self->text_model->composing_range().base(); - self->text_model->UpdateComposingText(buf); - self->text_model->SetSelection(TextRange(cursor_offset, cursor_offset)); + cursor_offset += priv->text_model->composing_range().base(); + priv->text_model->UpdateComposingText(buf); + priv->text_model->SetSelection(TextRange(cursor_offset, cursor_offset)); update_editing_state(self); } // Signal handler for GtkIMContext::commit static void im_commit_cb(FlTextInputPlugin* self, const gchar* text) { - self->text_model->AddText(text); - if (self->text_model->composing()) { - self->text_model->CommitComposing(); + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + priv->text_model->AddText(text); + if (priv->text_model->composing()) { + priv->text_model->CommitComposing(); } update_editing_state(self); } // Signal handler for GtkIMContext::preedit-end static void im_preedit_end_cb(FlTextInputPlugin* self) { - self->text_model->EndComposing(); + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + priv->text_model->EndComposing(); update_editing_state(self); } // Signal handler for GtkIMContext::retrieve-surrounding static gboolean im_retrieve_surrounding_cb(FlTextInputPlugin* self) { - auto text = self->text_model->GetText(); - size_t cursor_offset = self->text_model->GetCursorOffset(); - gtk_im_context_set_surrounding(self->im_context, text.c_str(), -1, + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + auto text = priv->text_model->GetText(); + size_t cursor_offset = priv->text_model->GetCursorOffset(); + gtk_im_context_set_surrounding(priv->im_context, text.c_str(), -1, cursor_offset); return TRUE; } @@ -216,7 +234,9 @@ static gboolean im_retrieve_surrounding_cb(FlTextInputPlugin* self) { static gboolean im_delete_surrounding_cb(FlTextInputPlugin* self, gint offset, gint n_chars) { - if (self->text_model->DeleteSurrounding(offset, n_chars)) { + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + if (priv->text_model->DeleteSurrounding(offset, n_chars)) { update_editing_state(self); } return TRUE; @@ -229,18 +249,20 @@ static FlMethodResponse* set_client(FlTextInputPlugin* self, FlValue* args) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kBadArgumentsError, "Expected 2-element list", nullptr)); } + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); - self->client_id = fl_value_get_int(fl_value_get_list_value(args, 0)); + priv->client_id = fl_value_get_int(fl_value_get_list_value(args, 0)); FlValue* config_value = fl_value_get_list_value(args, 1); - g_free(self->input_action); + g_free(priv->input_action); FlValue* input_action_value = 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)); + priv->input_action = g_strdup(fl_value_get_string(input_action_value)); } // Clear the multiline flag, then set it only if the field is multiline. - self->input_multiline = FALSE; + priv->input_multiline = FALSE; FlValue* input_type_value = fl_value_lookup_string(config_value, kTextInputTypeKey); if (fl_value_get_type(input_type_value) == FL_VALUE_TYPE_MAP) { @@ -249,7 +271,7 @@ static FlMethodResponse* set_client(FlTextInputPlugin* self, FlValue* args) { if (fl_value_get_type(input_type_name) == FL_VALUE_TYPE_STRING && g_strcmp0(fl_value_get_string(input_type_name), kMultilineInputType) == 0) { - self->input_multiline = TRUE; + priv->input_multiline = TRUE; } } @@ -258,12 +280,14 @@ static FlMethodResponse* set_client(FlTextInputPlugin* self, FlValue* args) { // Shows the input method. static FlMethodResponse* show(FlTextInputPlugin* self) { + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); // Set the top-level window used for system input method windows. GdkWindow* window = - gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self->view))); - gtk_im_context_set_client_window(self->im_context, window); + gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(priv->view))); + gtk_im_context_set_client_window(priv->im_context, window); - gtk_im_context_focus_in(self->im_context); + gtk_im_context_focus_in(priv->im_context); return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } @@ -271,9 +295,11 @@ static FlMethodResponse* show(FlTextInputPlugin* self) { // Updates the editing state from Flutter. static FlMethodResponse* set_editing_state(FlTextInputPlugin* self, FlValue* args) { + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); const gchar* text = fl_value_get_string(fl_value_lookup_string(args, kTextKey)); - self->text_model->SetText(text); + priv->text_model->SetText(text); int64_t selection_base = fl_value_get_int(fl_value_lookup_string(args, kSelectionBaseKey)); @@ -284,19 +310,19 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self, selection_base = selection_extent = 0; } - self->text_model->SetText(text); - self->text_model->SetSelection(TextRange(selection_base, selection_extent)); + priv->text_model->SetText(text); + priv->text_model->SetSelection(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) { - self->text_model->EndComposing(); + priv->text_model->EndComposing(); } else { size_t composing_start = std::min(composing_base, composing_extent); size_t cursor_offset = selection_base - composing_start; - self->text_model->SetComposingRange( + priv->text_model->SetComposingRange( TextRange(composing_base, composing_extent), cursor_offset); } @@ -305,14 +331,18 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self, // Called when the input method client is complete. static FlMethodResponse* clear_client(FlTextInputPlugin* self) { - self->client_id = kClientIdUnset; + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + priv->client_id = kClientIdUnset; return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } // Hides the input method. static FlMethodResponse* hide(FlTextInputPlugin* self) { - gtk_im_context_focus_out(self->im_context); + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + gtk_im_context_focus_out(priv->im_context); return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } @@ -326,29 +356,32 @@ static FlMethodResponse* hide(FlTextInputPlugin* self) { // of these updates. It transforms the composing rect to GTK window coordinates // and notifies GTK of the updated cursor position. static void update_im_cursor_position(FlTextInputPlugin* self) { + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + // Skip update if not composing to avoid setting to position 0. - if (!self->text_model->composing()) { + if (!priv->text_model->composing()) { return; } // Transform the x, y positions of the cursor from local coordinates to // Flutter view coordinates. - gint x = self->composing_rect.x * self->editabletext_transform[0][0] + - self->composing_rect.y * self->editabletext_transform[1][0] + - self->editabletext_transform[3][0] + self->composing_rect.width; - gint y = self->composing_rect.x * self->editabletext_transform[0][1] + - self->composing_rect.y * self->editabletext_transform[1][1] + - self->editabletext_transform[3][1] + self->composing_rect.height; + gint x = priv->composing_rect.x * priv->editabletext_transform[0][0] + + priv->composing_rect.y * priv->editabletext_transform[1][0] + + priv->editabletext_transform[3][0] + priv->composing_rect.width; + gint y = priv->composing_rect.x * priv->editabletext_transform[0][1] + + priv->composing_rect.y * priv->editabletext_transform[1][1] + + priv->editabletext_transform[3][1] + priv->composing_rect.height; // Transform from Flutter view coordinates to GTK window coordinates. GdkRectangle preedit_rect; gtk_widget_translate_coordinates( - GTK_WIDGET(self->view), gtk_widget_get_toplevel(GTK_WIDGET(self->view)), + GTK_WIDGET(priv->view), gtk_widget_get_toplevel(GTK_WIDGET(priv->view)), x, y, &preedit_rect.x, &preedit_rect.y); // Set the cursor location in window coordinates so that GTK can position any // system input method windows. - gtk_im_context_set_cursor_location(self->im_context, &preedit_rect); + gtk_im_context_set_cursor_location(priv->im_context, &preedit_rect); } // Handles updates to the EditableText size and position from the framework. @@ -366,7 +399,9 @@ static FlMethodResponse* set_editable_size_and_transform( for (size_t i = 0; i < transform_len; ++i) { double val = fl_value_get_float(fl_value_get_list_value(transform, i)); - self->editabletext_transform[i / 4][i % 4] = val; + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + priv->editabletext_transform[i / 4][i % 4] = val; } update_im_cursor_position(self); @@ -381,13 +416,15 @@ static FlMethodResponse* set_editable_size_and_transform( // composing region, the cursor rect is sent. static FlMethodResponse* set_marked_text_rect(FlTextInputPlugin* self, FlValue* args) { - self->composing_rect.x = + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + priv->composing_rect.x = fl_value_get_float(fl_value_lookup_string(args, "x")); - self->composing_rect.y = + priv->composing_rect.y = fl_value_get_float(fl_value_lookup_string(args, "y")); - self->composing_rect.width = + priv->composing_rect.width = fl_value_get_float(fl_value_lookup_string(args, "width")); - self->composing_rect.height = + priv->composing_rect.height = fl_value_get_float(fl_value_lookup_string(args, "height")); update_im_cursor_position(self); @@ -430,45 +467,46 @@ static void method_call_cb(FlMethodChannel* channel, static void fl_text_input_plugin_dispose(GObject* object) { FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN(object); - - g_clear_object(&self->channel); - g_clear_pointer(&self->input_action, g_free); - g_clear_object(&self->im_context); - if (self->text_model != nullptr) { - delete self->text_model; - self->text_model = nullptr; + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + + g_clear_object(&priv->channel); + g_clear_pointer(&priv->input_action, g_free); + g_clear_object(&priv->im_context); + if (priv->text_model != nullptr) { + delete priv->text_model; + priv->text_model = nullptr; } - self->view = nullptr; + priv->view = nullptr; G_OBJECT_CLASS(fl_text_input_plugin_parent_class)->dispose(object); } -static void fl_text_input_plugin_class_init(FlTextInputPluginClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_text_input_plugin_dispose; -} - static void fl_text_input_plugin_init(FlTextInputPlugin* self) { - self->client_id = kClientIdUnset; - self->im_context = gtk_im_multicontext_new(); - self->input_multiline = FALSE; - g_signal_connect_object(self->im_context, "preedit-start", + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + + priv->client_id = kClientIdUnset; + priv->im_context = gtk_im_multicontext_new(); + priv->input_multiline = FALSE; + g_signal_connect_object(priv->im_context, "preedit-start", G_CALLBACK(im_preedit_start_cb), self, G_CONNECT_SWAPPED); - g_signal_connect_object(self->im_context, "preedit-end", + g_signal_connect_object(priv->im_context, "preedit-end", G_CALLBACK(im_preedit_end_cb), self, G_CONNECT_SWAPPED); - g_signal_connect_object(self->im_context, "preedit-changed", + g_signal_connect_object(priv->im_context, "preedit-changed", G_CALLBACK(im_preedit_changed_cb), self, G_CONNECT_SWAPPED); - g_signal_connect_object(self->im_context, "commit", G_CALLBACK(im_commit_cb), + g_signal_connect_object(priv->im_context, "commit", G_CALLBACK(im_commit_cb), self, G_CONNECT_SWAPPED); - g_signal_connect_object(self->im_context, "retrieve-surrounding", + g_signal_connect_object(priv->im_context, "retrieve-surrounding", G_CALLBACK(im_retrieve_surrounding_cb), self, G_CONNECT_SWAPPED); - g_signal_connect_object(self->im_context, "delete-surrounding", + g_signal_connect_object(priv->im_context, "delete-surrounding", G_CALLBACK(im_delete_surrounding_cb), self, G_CONNECT_SWAPPED); - self->text_model = new flutter::TextInputModel(); + priv->text_model = new flutter::TextInputModel(); } FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, @@ -479,11 +517,13 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, g_object_new(fl_text_input_plugin_get_type(), nullptr)); g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - self->channel = + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + priv->channel = fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, + fl_method_channel_set_method_call_handler(priv->channel, method_call_cb, self, nullptr); - self->view = view; + priv->view = view; return self; } @@ -491,12 +531,25 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, gboolean fl_text_input_plugin_filter_keypress(FlTextInputPlugin* self, GdkEventKey* event) { g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(self), FALSE); + if (FL_TEXT_INPUT_PLUGIN_GET_CLASS(self)->filter_keypress) { + return FL_TEXT_INPUT_PLUGIN_GET_CLASS(self)->filter_keypress(self, event); + } + return FALSE; +} + +// Implements FlTextInputPlugin::filter_keypress. +gboolean fl_text_input_plugin_filter_keypress_default(FlTextInputPlugin* self, + GdkEventKey* event) { + g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(self), false); + + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); - if (self->client_id == kClientIdUnset) { + if (priv->client_id == kClientIdUnset) { return FALSE; } - if (gtk_im_context_filter_keypress(self->im_context, event)) { + if (gtk_im_context_filter_keypress(priv->im_context, event)) { return TRUE; } @@ -506,37 +559,31 @@ gboolean fl_text_input_plugin_filter_keypress(FlTextInputPlugin* self, gboolean changed = FALSE; if (event->type == GDK_KEY_PRESS) { switch (event->keyval) { - case GDK_KEY_BackSpace: - changed = self->text_model->Backspace(); - break; - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - // Already handled inside Flutter. - break; case GDK_KEY_End: case GDK_KEY_KP_End: - changed = self->text_model->MoveCursorToEnd(); + changed = priv->text_model->MoveCursorToEnd(); break; case GDK_KEY_Return: case GDK_KEY_KP_Enter: case GDK_KEY_ISO_Enter: - if (self->input_multiline == TRUE) { - self->text_model->AddCodePoint('\n'); + if (priv->input_multiline == TRUE) { + priv->text_model->AddCodePoint('\n'); changed = TRUE; } do_action = TRUE; break; case GDK_KEY_Home: case GDK_KEY_KP_Home: - changed = self->text_model->MoveCursorToBeginning(); + changed = priv->text_model->MoveCursorToBeginning(); break; + case GDK_KEY_BackSpace: + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: case GDK_KEY_Left: case GDK_KEY_KP_Left: - // Already handled inside Flutter. - break; case GDK_KEY_Right: case GDK_KEY_KP_Right: - // Already handled inside Flutter. + // Already handled inside the framework in RenderEditable. break; } } @@ -548,5 +595,11 @@ gboolean fl_text_input_plugin_filter_keypress(FlTextInputPlugin* self, perform_action(self); } - return FALSE; + return changed; +} + +static void fl_text_input_plugin_class_init(FlTextInputPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_text_input_plugin_dispose; + FL_TEXT_INPUT_PLUGIN_CLASS(klass)->filter_keypress = + fl_text_input_plugin_filter_keypress_default; } diff --git a/shell/platform/linux/fl_text_input_plugin.h b/shell/platform/linux/fl_text_input_plugin.h index d68f5903c17a6..0f5d216f7da53 100644 --- a/shell/platform/linux/fl_text_input_plugin.h +++ b/shell/platform/linux/fl_text_input_plugin.h @@ -12,11 +12,11 @@ G_BEGIN_DECLS -G_DECLARE_FINAL_TYPE(FlTextInputPlugin, - fl_text_input_plugin, - FL, - TEXT_INPUT_PLUGIN, - GObject); +G_DECLARE_DERIVABLE_TYPE(FlTextInputPlugin, + fl_text_input_plugin, + FL, + TEXT_INPUT_PLUGIN, + GObject); /** * FlTextInputPlugin: @@ -25,6 +25,15 @@ G_DECLARE_FINAL_TYPE(FlTextInputPlugin, * of SystemChannels.textInput from the Flutter services library. */ +struct _FlTextInputPluginClass { + GObjectClass parent_class; + + /** + * Virtual method called to filter a keypress. + */ + gboolean (*filter_keypress)(FlTextInputPlugin* self, GdkEventKey* event); +}; + /** * fl_text_input_plugin_new: * @messenger: an #FlBinaryMessenger. diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 87decdce97c88..82e9e903b28b8 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -158,10 +158,11 @@ static void fl_view_constructed(GObject* object) { // Create system channel handlers. FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine); - self->key_event_plugin = fl_key_event_plugin_new(messenger); + self->text_input_plugin = fl_text_input_plugin_new(messenger, self); + self->key_event_plugin = + fl_key_event_plugin_new(messenger, self->text_input_plugin); self->mouse_cursor_plugin = fl_mouse_cursor_plugin_new(messenger, self); self->platform_plugin = fl_platform_plugin_new(messenger); - self->text_input_plugin = fl_text_input_plugin_new(messenger, self); } static void fl_view_set_property(GObject* object, @@ -348,10 +349,9 @@ static gboolean fl_view_motion_notify_event(GtkWidget* widget, static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) { FlView* self = FL_VIEW(widget); - fl_key_event_plugin_send_key_event(self->key_event_plugin, event); - fl_text_input_plugin_filter_keypress(self->text_input_plugin, event); - - return TRUE; + return fl_key_event_plugin_send_key_event(self->key_event_plugin, event) + ? TRUE + : FALSE; } // Implements GtkWidget::key_release_event. @@ -359,10 +359,9 @@ static gboolean fl_view_key_release_event(GtkWidget* widget, GdkEventKey* event) { FlView* self = FL_VIEW(widget); - fl_key_event_plugin_send_key_event(self->key_event_plugin, event); - fl_text_input_plugin_filter_keypress(self->text_input_plugin, event); - - return TRUE; + return fl_key_event_plugin_send_key_event(self->key_event_plugin, event) + ? TRUE + : FALSE; } static void fl_view_class_init(FlViewClass* klass) { diff --git a/shell/platform/linux/testing/mock_engine.cc b/shell/platform/linux/testing/mock_engine.cc index c498e92f955b2..98d3267cc3248 100644 --- a/shell/platform/linux/testing/mock_engine.cc +++ b/shell/platform/linux/testing/mock_engine.cc @@ -13,6 +13,7 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/linux/fl_method_codec_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_response.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" #include "gtest/gtest.h" @@ -310,6 +311,18 @@ FlutterEngineResult FlutterEngineSendPlatformMessage( } else if (strcmp(message->channel, "test/failure") == 0) { // Generates an internal error. return kInternalInconsistency; + } else if (strcmp(message->channel, "test/key-event-handled") == 0 || + strcmp(message->channel, "test/key-event-not-handled") == 0) { + bool value = strcmp(message->channel, "test/key-event-handled") == 0; + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + g_autoptr(FlValue) handledValue = fl_value_new_map(); + fl_value_set_string_take(handledValue, "handled", fl_value_new_bool(value)); + g_autoptr(GBytes) response = fl_message_codec_encode_message( + FL_MESSAGE_CODEC(codec), handledValue, nullptr); + send_response( + engine, message->channel, message->response_handle, + static_cast(g_bytes_get_data(response, nullptr)), + g_bytes_get_size(response)); } return kSuccess; diff --git a/shell/platform/linux/testing/mock_text_input_plugin.cc b/shell/platform/linux/testing/mock_text_input_plugin.cc new file mode 100644 index 0000000000000..083472c532f9e --- /dev/null +++ b/shell/platform/linux/testing/mock_text_input_plugin.cc @@ -0,0 +1,41 @@ +// 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/testing/mock_text_input_plugin.h" + +struct _FlMockTextInputPlugin { + FlTextInputPlugin parent_instance; + + gboolean (*filter_keypress)(FlTextInputPlugin* self, GdkEventKey* event); +}; + +G_DEFINE_TYPE(FlMockTextInputPlugin, + fl_mock_text_input_plugin, + fl_text_input_plugin_get_type()) + +gboolean fl_mock_text_input_plugin_filter_keypress(FlTextInputPlugin* self, + GdkEventKey* event) { + FlMockTextInputPlugin* mock_self = FL_MOCK_TEXT_INPUT_PLUGIN(self); + if (mock_self->filter_keypress) { + return mock_self->filter_keypress(self, event); + } + return FALSE; +} + +static void fl_mock_text_input_plugin_class_init( + FlMockTextInputPluginClass* klass) { + FL_TEXT_INPUT_PLUGIN_CLASS(klass)->filter_keypress = + fl_mock_text_input_plugin_filter_keypress; +} + +static void fl_mock_text_input_plugin_init(FlMockTextInputPlugin* self) {} + +// Creates a mock text_input_plugin +FlMockTextInputPlugin* fl_mock_text_input_plugin_new( + gboolean (*filter_keypress)(FlTextInputPlugin* self, GdkEventKey* event)) { + FlMockTextInputPlugin* self = FL_MOCK_TEXT_INPUT_PLUGIN( + g_object_new(fl_mock_text_input_plugin_get_type(), nullptr)); + self->filter_keypress = filter_keypress; + return self; +} diff --git a/shell/platform/linux/testing/mock_text_input_plugin.h b/shell/platform/linux/testing/mock_text_input_plugin.h new file mode 100644 index 0000000000000..64b079e2c9d08 --- /dev/null +++ b/shell/platform/linux/testing/mock_text_input_plugin.h @@ -0,0 +1,18 @@ +// 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_plugin.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlMockTextInputPlugin, + fl_mock_text_input_plugin, + FL, + MOCK_TEXT_INPUT_PLUGIN, + FlTextInputPlugin) + +FlMockTextInputPlugin* fl_mock_text_input_plugin_new( + gboolean (*filter_keypress)(FlTextInputPlugin* self, GdkEventKey* event)); + +G_END_DECLS From 2642404caf2f3d86192beca14f43521333a294f6 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 20 Nov 2020 15:48:51 -0800 Subject: [PATCH 2/5] Review Changes --- shell/platform/linux/fl_key_event_plugin.cc | 35 ++++++++++++--------- shell/platform/linux/fl_key_event_plugin.h | 12 ++++--- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/shell/platform/linux/fl_key_event_plugin.cc b/shell/platform/linux/fl_key_event_plugin.cc index d1e605069547f..e381b610a7959 100644 --- a/shell/platform/linux/fl_key_event_plugin.cc +++ b/shell/platform/linux/fl_key_event_plugin.cc @@ -4,6 +4,7 @@ #include "flutter/shell/platform/linux/fl_key_event_plugin.h" +#include #include #include "flutter/shell/platform/linux/fl_text_input_plugin.h" @@ -42,6 +43,9 @@ static void fl_key_event_plugin_dispose(GObject* object) { FlKeyEventPlugin* self = FL_KEY_EVENT_PLUGIN(object); g_clear_object(&self->channel); + g_object_remove_weak_pointer( + G_OBJECT(self->text_input_plugin), + reinterpret_cast(&(self->text_input_plugin))); G_OBJECT_CLASS(fl_key_event_plugin_parent_class)->dispose(object); } @@ -50,9 +54,7 @@ static void fl_key_event_plugin_class_init(FlKeyEventPluginClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_key_event_plugin_dispose; } -static void fl_key_event_plugin_init(FlKeyEventPlugin* self) { - self->pendingEvents.clear(); -} +static void fl_key_event_plugin_init(FlKeyEventPlugin* self) {} FlKeyEventPlugin* fl_key_event_plugin_new( FlBinaryMessenger* messenger, @@ -104,6 +106,8 @@ void fl_remove_pending_event(FlKeyEventPlugin* self, uint64_t id) { id); return; } + gdk_event_free( + reinterpret_cast(self->pendingEvents.front().second)); self->pendingEvents.pop_front(); } @@ -134,12 +138,15 @@ void fl_handle_response(GObject* object, gpointer user_data) { _KeyEventResponseData* data = reinterpret_cast<_KeyEventResponseData*>(user_data); - if (data->self == nullptr) { - // Weak pointer to the plugin has been destroyed. - return; - } + + // Will also return if the weak pointer has been destroyed. g_return_if_fail(FL_IS_KEY_EVENT_PLUGIN(data->self)); + FlKeyEventPlugin* self = data->self; + // Don't need to weak pointer anymore. + g_object_remove_weak_pointer(G_OBJECT(self), + reinterpret_cast(&(data->self))); + g_autoptr(GError) error = nullptr; FlBasicMessageChannel* messageChannel = FL_BASIC_MESSAGE_CHANNEL(object); FlValue* message = @@ -152,7 +159,7 @@ void fl_handle_response(GObject* object, g_autoptr(FlValue) handled_value = fl_value_lookup_string(message, "handled"); bool handled = false; if (handled_value != nullptr) { - GdkEventKey* event = fl_find_pending_event(data->self, data->id); + GdkEventKey* event = fl_find_pending_event(self, data->id); if (event == nullptr) { g_warning( "Event response for event id %ld received, but event was received " @@ -161,16 +168,16 @@ void fl_handle_response(GObject* object, } else { handled = fl_value_get_bool(handled_value); if (!handled) { - if (data->self->text_input_plugin != nullptr) { + if (self->text_input_plugin != nullptr) { // Propagate the event to the text input plugin. handled = fl_text_input_plugin_filter_keypress( - data->self->text_input_plugin, event); + self->text_input_plugin, event); } // Dispatch the event to other GTK windows if the text input plugin // didn't handle it. We keep track of the event id so we can recognize // the event when our window receives it again and not respond to it. If // the response callback is set, then use that instead. - if (!handled && data->self->response_callback == nullptr) { + if (!handled && self->response_callback == nullptr) { gdk_event_put(reinterpret_cast(event)); } } @@ -180,11 +187,11 @@ void fl_handle_response(GObject* object, if (handled) { // Because the event was handled, we no longer need to track it. Unhandled // events will be removed when the event is re-dispatched to the window. - fl_remove_pending_event(data->self, data->id); + fl_remove_pending_event(self, data->id); } - if (data->self->response_callback != nullptr) { - data->self->response_callback(object, message, handled, data->user_data); + if (self->response_callback != nullptr) { + self->response_callback(object, message, handled, data->user_data); } } diff --git a/shell/platform/linux/fl_key_event_plugin.h b/shell/platform/linux/fl_key_event_plugin.h index 17a1eec17ecef..91bc639fb82c4 100644 --- a/shell/platform/linux/fl_key_event_plugin.h +++ b/shell/platform/linux/fl_key_event_plugin.h @@ -29,7 +29,9 @@ G_DECLARE_FINAL_TYPE(FlKeyEventPlugin, /** * FlKeyEventPluginCallback: * @source_object: (nullable): the object the key event was started with. - * @handled: a boolean indicating if the key event was handled or not. + * @message: the message returned from the framework. + * @handled: a boolean indicating whether the key event was handled in the + *framework. * @user_data: user data passed to the callback. * * Type definition for a function that will be called when a key event is @@ -45,10 +47,10 @@ typedef void (*FlKeyEventPluginCallback)(GObject* source_object, * @messenger: an #FlBinaryMessenger. * @response_callback: the callback to call when a response is received. If not * given (nullptr), then the default response callback is - * used. Typically used for tests to receive event information. - * If specified, unhandled events will not be re-dispatched. - * @text_input_plugin: The #FlTextInputPlugin to send key events to if the framework - * doesn't handle them. + * used. Typically used for tests to receive event + * information. If specified, unhandled events will not be re-dispatched. + * @text_input_plugin: The #FlTextInputPlugin to send key events to if the + * framework doesn't handle them. * @channel_name: the name of the channel to send key events to the framework * on. If not given (nullptr), then the standard key event * channel name is used. Typically used for tests to send on a From 118ffac1ad500dbca6c5ea1aae2e86b2fe1de1aa Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 30 Nov 2020 15:23:19 -0800 Subject: [PATCH 3/5] Review Changes: convert from std::deque to GPtrArray --- shell/platform/linux/fl_key_event_plugin.cc | 156 +++++++++++++++----- 1 file changed, 120 insertions(+), 36 deletions(-) diff --git a/shell/platform/linux/fl_key_event_plugin.cc b/shell/platform/linux/fl_key_event_plugin.cc index e381b610a7959..8a4f4fc2dc03b 100644 --- a/shell/platform/linux/fl_key_event_plugin.cc +++ b/shell/platform/linux/fl_key_event_plugin.cc @@ -5,7 +5,6 @@ #include "flutter/shell/platform/linux/fl_key_event_plugin.h" #include -#include #include "flutter/shell/platform/linux/fl_text_input_plugin.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" @@ -27,14 +26,111 @@ static constexpr char kLinuxKeymap[] = "linux"; static constexpr uint64_t kMaxPendingEvents = 1000; +// Declare and define a private pair object to bind the id and the event +// together. + +G_BEGIN_DECLS +G_DECLARE_FINAL_TYPE(FlKeyEventPair, + fl_key_event_pair, + FL, + KEY_EVENT_PAIR, + GObject); +G_END_DECLS + +struct _FlKeyEventPair { + GObject parent_instance; + + uint64_t id; + GdkEventKey* event; +}; + +G_DEFINE_TYPE(FlKeyEventPair, fl_key_event_pair, G_TYPE_OBJECT) + +static void fl_key_event_pair_dispose(GObject* object) { + g_return_if_fail(FL_IS_KEY_EVENT_PAIR(object)); + FlKeyEventPair* self = FL_KEY_EVENT_PAIR(object); + gdk_event_free(reinterpret_cast(self->event)); +} + +static void fl_key_event_pair_class_init(FlKeyEventPairClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_key_event_pair_dispose; +} + +static void fl_key_event_pair_init(FlKeyEventPair* self) {} + +FlKeyEventPair* fl_key_event_pair_new(uint64_t id, GdkEventKey* event) { + FlKeyEventPair* self = + FL_KEY_EVENT_PAIR(g_object_new(fl_key_event_pair_get_type(), nullptr)); + + // Copy the event to preserve refcounts for referenced values (mainly the + // window). + GdkEventKey* event_copy = reinterpret_cast( + gdk_event_copy(reinterpret_cast(event))); + self->id = id; + self->event = event_copy; + return self; +} + +// Declare and define a private class to hold response data from the framework. + +G_BEGIN_DECLS +G_DECLARE_FINAL_TYPE(FlKeyEventResponseData, + fl_key_event_response_data, + FL, + KEY_EVENT_RESPONSE_DATA, + GObject); +G_END_DECLS + +struct _FlKeyEventResponseData { + GObject parent_instance; + + FlKeyEventPlugin* plugin; + uint64_t id; + gpointer user_data; +}; + +G_DEFINE_TYPE(FlKeyEventResponseData, fl_key_event_response_data, G_TYPE_OBJECT) + +static void fl_key_event_response_data_dispose(GObject* object) { + g_return_if_fail(FL_IS_KEY_EVENT_RESPONSE_DATA(object)); + FlKeyEventResponseData* self = FL_KEY_EVENT_RESPONSE_DATA(object); + // Don't need to weak pointer anymore. + g_object_remove_weak_pointer(G_OBJECT(self->plugin), + reinterpret_cast(&(self->plugin))); +} + +static void fl_key_event_response_data_class_init( + FlKeyEventResponseDataClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_key_event_response_data_dispose; +} + +static void fl_key_event_response_data_init(FlKeyEventResponseData* self) {} + +FlKeyEventResponseData* fl_key_event_response_data_new(FlKeyEventPlugin* plugin, + uint64_t id, + gpointer user_data) { + FlKeyEventResponseData* self = FL_KEY_EVENT_RESPONSE_DATA( + g_object_new(fl_key_event_response_data_get_type(), nullptr)); + + self->plugin = plugin; + // Add a weak pointer so we can know if the key event plugin disappeared + // while the framework was responding. + g_object_add_weak_pointer(G_OBJECT(plugin), + reinterpret_cast(&(self->plugin))); + self->id = id; + self->user_data = user_data; + return self; +} + +// Definition of the FlKeyEventPlugin GObject class. + struct _FlKeyEventPlugin { GObject parent_instance; FlBasicMessageChannel* channel = nullptr; FlTextInputPlugin* text_input_plugin = nullptr; FlKeyEventPluginCallback response_callback = nullptr; - - std::deque> pendingEvents; + GPtrArray* pending_events; }; G_DEFINE_TYPE(FlKeyEventPlugin, fl_key_event_plugin, G_TYPE_OBJECT) @@ -46,6 +142,7 @@ static void fl_key_event_plugin_dispose(GObject* object) { g_object_remove_weak_pointer( G_OBJECT(self->text_input_plugin), reinterpret_cast(&(self->text_input_plugin))); + g_ptr_array_free(self->pending_events, TRUE); G_OBJECT_CLASS(fl_key_event_plugin_parent_class)->dispose(object); } @@ -78,6 +175,7 @@ FlKeyEventPlugin* fl_key_event_plugin_new( reinterpret_cast(&(self->text_input_plugin))); self->text_input_plugin = text_input_plugin; + self->pending_events = g_ptr_array_new_with_free_func(g_object_unref); return self; } @@ -92,60 +190,48 @@ uint64_t fl_get_event_id(GdkEventKey* event) { } GdkEventKey* fl_find_pending_event(FlKeyEventPlugin* self, uint64_t id) { - if (self->pendingEvents.empty() || self->pendingEvents.front().first != id) { + if (self->pending_events->len == 0 || + FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->id != id) { return nullptr; } - return self->pendingEvents.front().second; + + return FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->event; } void fl_remove_pending_event(FlKeyEventPlugin* self, uint64_t id) { - if (self->pendingEvents.empty() || self->pendingEvents.front().first != id) { + if (self->pending_events->len == 0 || + FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->id != id) { g_warning( "Tried to remove pending event with id %ld, but the event was out of " "order, or is unknown.", id); return; } - gdk_event_free( - reinterpret_cast(self->pendingEvents.front().second)); - self->pendingEvents.pop_front(); + g_ptr_array_remove_index(self->pending_events, 0); } void fl_add_pending_event(FlKeyEventPlugin* self, uint64_t id, GdkEventKey* event) { - if (self->pendingEvents.size() > kMaxPendingEvents) { + if (self->pending_events->len > kMaxPendingEvents) { g_warning( - "There are %ld keyboard events that have not yet received a " + "There are %d keyboard events that have not yet received a " "response from the framework. Are responses being sent?", - self->pendingEvents.size()); + self->pending_events->len); } - // Copy the event to preserve refcounts for referenced values (mainly the - // window). - GdkEventKey* event_copy = reinterpret_cast( - gdk_event_copy(reinterpret_cast(event))); - self->pendingEvents.push_back(std::make_pair(id, event_copy)); + g_ptr_array_add(self->pending_events, fl_key_event_pair_new(id, event)); } -struct _KeyEventResponseData { - FlKeyEventPlugin* self; - uint64_t id; - gpointer user_data; -}; - void fl_handle_response(GObject* object, GAsyncResult* result, gpointer user_data) { - _KeyEventResponseData* data = - reinterpret_cast<_KeyEventResponseData*>(user_data); + g_autoptr(FlKeyEventResponseData) data = + FL_KEY_EVENT_RESPONSE_DATA(user_data); // Will also return if the weak pointer has been destroyed. - g_return_if_fail(FL_IS_KEY_EVENT_PLUGIN(data->self)); + g_return_if_fail(FL_IS_KEY_EVENT_PLUGIN(data->plugin)); - FlKeyEventPlugin* self = data->self; - // Don't need to weak pointer anymore. - g_object_remove_weak_pointer(G_OBJECT(self), - reinterpret_cast(&(data->self))); + FlKeyEventPlugin* self = data->plugin; g_autoptr(GError) error = nullptr; FlBasicMessageChannel* messageChannel = FL_BASIC_MESSAGE_CHANNEL(object); @@ -207,7 +293,8 @@ bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, // when we receive a random event that may or may not have been // tracked/produced by this code. uint64_t id = fl_get_event_id(event); - if (!self->pendingEvents.empty() && self->pendingEvents.front().first == id) { + if (self->pending_events->len != 0 && + FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->id == id) { // If the event is at the head of the queue of pending events we've seen, // and has the same id, then we know that this is a re-dispatched event, and // we shouldn't respond to it, but we should remove it from tracking. @@ -295,11 +382,8 @@ bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, // Track the event as pending a response from the framework. fl_add_pending_event(self, id, event); - _KeyEventResponseData* data = new _KeyEventResponseData{self, id, user_data}; - // Add a weak pointer so we can know if the key event plugin disappeared - // while the framework was responding. - g_object_add_weak_pointer(G_OBJECT(self), - reinterpret_cast(&(data->self))); + FlKeyEventResponseData* data = + fl_key_event_response_data_new(self, id, user_data); // Send the message off to the framework for handling (or not). fl_basic_message_channel_send(self->channel, message, nullptr, fl_handle_response, data); From 263c1baddaa92349e76ab5225d30f46f5763f284 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 3 Dec 2020 12:40:10 -0800 Subject: [PATCH 4/5] Review Changes --- shell/platform/linux/fl_key_event_plugin.cc | 213 ++++++++++-------- shell/platform/linux/fl_key_event_plugin.h | 9 +- .../linux/fl_key_event_plugin_test.cc | 20 +- shell/platform/linux/fl_text_input_plugin.cc | 192 ++++++++-------- shell/platform/linux/fl_view.cc | 8 +- .../linux/testing/mock_text_input_plugin.cc | 10 +- 6 files changed, 248 insertions(+), 204 deletions(-) diff --git a/shell/platform/linux/fl_key_event_plugin.cc b/shell/platform/linux/fl_key_event_plugin.cc index 8a4f4fc2dc03b..eb8a3fc324d89 100644 --- a/shell/platform/linux/fl_key_event_plugin.cc +++ b/shell/platform/linux/fl_key_event_plugin.cc @@ -10,6 +10,21 @@ #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" +// Definition of the FlKeyEventPlugin GObject class. + +struct _FlKeyEventPlugin { + GObject parent_instance; + + FlBasicMessageChannel* channel = nullptr; + FlTextInputPlugin* text_input_plugin = nullptr; + FlKeyEventPluginCallback response_callback = nullptr; + GPtrArray* pending_events; +}; + +G_DEFINE_TYPE(FlKeyEventPlugin, fl_key_event_plugin, G_TYPE_OBJECT) + +namespace { + static constexpr char kChannelName[] = "flutter/keyevent"; static constexpr char kTypeKey[] = "type"; static constexpr char kTypeValueUp[] = "keyup"; @@ -29,13 +44,11 @@ static constexpr uint64_t kMaxPendingEvents = 1000; // Declare and define a private pair object to bind the id and the event // together. -G_BEGIN_DECLS G_DECLARE_FINAL_TYPE(FlKeyEventPair, fl_key_event_pair, FL, KEY_EVENT_PAIR, GObject); -G_END_DECLS struct _FlKeyEventPair { GObject parent_instance; @@ -46,18 +59,27 @@ struct _FlKeyEventPair { G_DEFINE_TYPE(FlKeyEventPair, fl_key_event_pair, G_TYPE_OBJECT) +// Dispose method for FlKeyEventPair. static void fl_key_event_pair_dispose(GObject* object) { + // Redundant, but added so that we don't get a warning about unused function + // for FL_IS_KEY_EVENT_PAIR. g_return_if_fail(FL_IS_KEY_EVENT_PAIR(object)); + FlKeyEventPair* self = FL_KEY_EVENT_PAIR(object); - gdk_event_free(reinterpret_cast(self->event)); + g_clear_pointer(&self->event, gdk_event_free); + G_OBJECT_CLASS(fl_key_event_pair_parent_class)->dispose(object); } +// Class Initialization method for FlKeyEventPair class. static void fl_key_event_pair_class_init(FlKeyEventPairClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_key_event_pair_dispose; } +// Initialization for FlKeyEventPair instances. static void fl_key_event_pair_init(FlKeyEventPair* self) {} +// Creates a new FlKeyEventPair instance, given a unique ID, and an event struct +// to keep. FlKeyEventPair* fl_key_event_pair_new(uint64_t id, GdkEventKey* event) { FlKeyEventPair* self = FL_KEY_EVENT_PAIR(g_object_new(fl_key_event_pair_get_type(), nullptr)); @@ -72,14 +94,11 @@ FlKeyEventPair* fl_key_event_pair_new(uint64_t id, GdkEventKey* event) { } // Declare and define a private class to hold response data from the framework. - -G_BEGIN_DECLS G_DECLARE_FINAL_TYPE(FlKeyEventResponseData, fl_key_event_response_data, FL, KEY_EVENT_RESPONSE_DATA, GObject); -G_END_DECLS struct _FlKeyEventResponseData { GObject parent_instance; @@ -89,8 +108,10 @@ struct _FlKeyEventResponseData { gpointer user_data; }; +// Definition for FlKeyEventResponseData private class. G_DEFINE_TYPE(FlKeyEventResponseData, fl_key_event_response_data, G_TYPE_OBJECT) +// Dispose method for FlKeyEventResponseData private class. static void fl_key_event_response_data_dispose(GObject* object) { g_return_if_fail(FL_IS_KEY_EVENT_RESPONSE_DATA(object)); FlKeyEventResponseData* self = FL_KEY_EVENT_RESPONSE_DATA(object); @@ -99,13 +120,18 @@ static void fl_key_event_response_data_dispose(GObject* object) { reinterpret_cast(&(self->plugin))); } +// Class initialization method for FlKeyEventResponseData private class. static void fl_key_event_response_data_class_init( FlKeyEventResponseDataClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_key_event_response_data_dispose; } +// Instance initialization method for FlKeyEventResponseData private class. static void fl_key_event_response_data_init(FlKeyEventResponseData* self) {} +// Creates a new FlKeyEventResponseData private class with a plugin that created +// the request, a unique ID for tracking, and optional user data. +// Will keep a weak pointer to the plugin. FlKeyEventResponseData* fl_key_event_response_data_new(FlKeyEventPlugin* plugin, uint64_t id, gpointer user_data) { @@ -122,64 +148,9 @@ FlKeyEventResponseData* fl_key_event_response_data_new(FlKeyEventPlugin* plugin, return self; } -// Definition of the FlKeyEventPlugin GObject class. - -struct _FlKeyEventPlugin { - GObject parent_instance; - - FlBasicMessageChannel* channel = nullptr; - FlTextInputPlugin* text_input_plugin = nullptr; - FlKeyEventPluginCallback response_callback = nullptr; - GPtrArray* pending_events; -}; - -G_DEFINE_TYPE(FlKeyEventPlugin, fl_key_event_plugin, G_TYPE_OBJECT) - -static void fl_key_event_plugin_dispose(GObject* object) { - FlKeyEventPlugin* self = FL_KEY_EVENT_PLUGIN(object); - - g_clear_object(&self->channel); - g_object_remove_weak_pointer( - G_OBJECT(self->text_input_plugin), - reinterpret_cast(&(self->text_input_plugin))); - g_ptr_array_free(self->pending_events, TRUE); - - G_OBJECT_CLASS(fl_key_event_plugin_parent_class)->dispose(object); -} - -static void fl_key_event_plugin_class_init(FlKeyEventPluginClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_key_event_plugin_dispose; -} - -static void fl_key_event_plugin_init(FlKeyEventPlugin* self) {} - -FlKeyEventPlugin* fl_key_event_plugin_new( - FlBinaryMessenger* messenger, - FlTextInputPlugin* text_input_plugin, - FlKeyEventPluginCallback response_callback, - const char* channel_name) { - g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); - g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(text_input_plugin), nullptr); - - FlKeyEventPlugin* self = FL_KEY_EVENT_PLUGIN( - g_object_new(fl_key_event_plugin_get_type(), nullptr)); - - g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); - self->channel = fl_basic_message_channel_new( - messenger, channel_name == nullptr ? kChannelName : channel_name, - FL_MESSAGE_CODEC(codec)); - self->response_callback = response_callback; - // Add a weak pointer so we know if the text input plugin goes away. - g_object_add_weak_pointer( - G_OBJECT(text_input_plugin), - reinterpret_cast(&(self->text_input_plugin))); - self->text_input_plugin = text_input_plugin; - - self->pending_events = g_ptr_array_new_with_free_func(g_object_unref); - return self; -} - -uint64_t fl_get_event_id(GdkEventKey* event) { +// Calculates a unique ID for a given GdkEventKey object to use for +// identification of responses from the framework. +static uint64_t get_event_id(GdkEventKey* event) { // Combine the event timestamp, the type of event, and the hardware keycode // (scan code) of the event to come up with a unique id for this event that // can be derived solely from the event data itself, so that we can identify @@ -189,7 +160,8 @@ uint64_t fl_get_event_id(GdkEventKey* event) { (static_cast(event->hardware_keycode) & 0xffff) << 48; } -GdkEventKey* fl_find_pending_event(FlKeyEventPlugin* self, uint64_t id) { +// Finds an event in the event queue that was sent to the framework by its ID. +static GdkEventKey* find_pending_event(FlKeyEventPlugin* self, uint64_t id) { if (self->pending_events->len == 0 || FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->id != id) { return nullptr; @@ -198,7 +170,8 @@ GdkEventKey* fl_find_pending_event(FlKeyEventPlugin* self, uint64_t id) { return FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->event; } -void fl_remove_pending_event(FlKeyEventPlugin* self, uint64_t id) { +// Removes an event from the pending event queue. +static void remove_pending_event(FlKeyEventPlugin* self, uint64_t id) { if (self->pending_events->len == 0 || FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->id != id) { g_warning( @@ -210,9 +183,11 @@ void fl_remove_pending_event(FlKeyEventPlugin* self, uint64_t id) { g_ptr_array_remove_index(self->pending_events, 0); } -void fl_add_pending_event(FlKeyEventPlugin* self, - uint64_t id, - GdkEventKey* event) { +// Adds an GdkEventKey to the pending event queue, with a unique ID, and the +// plugin that added it. +static void add_pending_event(FlKeyEventPlugin* self, + uint64_t id, + GdkEventKey* event) { if (self->pending_events->len > kMaxPendingEvents) { g_warning( "There are %d keyboard events that have not yet received a " @@ -222,14 +197,18 @@ void fl_add_pending_event(FlKeyEventPlugin* self, g_ptr_array_add(self->pending_events, fl_key_event_pair_new(id, event)); } -void fl_handle_response(GObject* object, - GAsyncResult* result, - gpointer user_data) { +// Handles a response from the framework to a key event sent to the framework +// earlier. +static void handle_response(GObject* object, + GAsyncResult* result, + gpointer user_data) { g_autoptr(FlKeyEventResponseData) data = FL_KEY_EVENT_RESPONSE_DATA(user_data); // Will also return if the weak pointer has been destroyed. - g_return_if_fail(FL_IS_KEY_EVENT_PLUGIN(data->plugin)); + if (data->plugin == nullptr) { + return; + } FlKeyEventPlugin* self = data->plugin; @@ -238,14 +217,13 @@ void fl_handle_response(GObject* object, FlValue* message = fl_basic_message_channel_send_finish(messageChannel, result, &error); if (error != nullptr) { - g_error("Unable to retrieve framework response: %s", error->message); - g_error_free(error); + g_warning("Unable to retrieve framework response: %s", error->message); return; } g_autoptr(FlValue) handled_value = fl_value_lookup_string(message, "handled"); - bool handled = false; + bool handled = FALSE; if (handled_value != nullptr) { - GdkEventKey* event = fl_find_pending_event(self, data->id); + GdkEventKey* event = find_pending_event(self, data->id); if (event == nullptr) { g_warning( "Event response for event id %ld received, but event was received " @@ -273,7 +251,7 @@ void fl_handle_response(GObject* object, if (handled) { // Because the event was handled, we no longer need to track it. Unhandled // events will be removed when the event is re-dispatched to the window. - fl_remove_pending_event(self, data->id); + remove_pending_event(self, data->id); } if (self->response_callback != nullptr) { @@ -281,25 +259,80 @@ void fl_handle_response(GObject* object, } } +// Disposes of an FlKeyEventPlugin instance. +static void fl_key_event_plugin_dispose(GObject* object) { + FlKeyEventPlugin* self = FL_KEY_EVENT_PLUGIN(object); + + g_clear_object(&self->channel); + g_object_remove_weak_pointer( + G_OBJECT(self->text_input_plugin), + reinterpret_cast(&(self->text_input_plugin))); + g_ptr_array_free(self->pending_events, TRUE); + + G_OBJECT_CLASS(fl_key_event_plugin_parent_class)->dispose(object); +} + +} // namespace + +// Initializes the FlKeyEventPlugin class methods. +static void fl_key_event_plugin_class_init(FlKeyEventPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_key_event_plugin_dispose; +} + +// Initializes an FlKeyEventPlugin instance. +static void fl_key_event_plugin_init(FlKeyEventPlugin* self) {} + +// Creates a new FlKeyEventPlugin instance, with a messenger used to send +// messages to the framework, an FlTextInputPlugin used to handle key events +// that the framework doesn't handle. Mainly for testing purposes, it also takes +// an optional callback to call when a response is received, and an optional +// channel name to use when sending messages. +FlKeyEventPlugin* fl_key_event_plugin_new( + FlBinaryMessenger* messenger, + FlTextInputPlugin* text_input_plugin, + FlKeyEventPluginCallback response_callback, + const char* channel_name) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(text_input_plugin), nullptr); + + FlKeyEventPlugin* self = FL_KEY_EVENT_PLUGIN( + g_object_new(fl_key_event_plugin_get_type(), nullptr)); + + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + self->channel = fl_basic_message_channel_new( + messenger, channel_name == nullptr ? kChannelName : channel_name, + FL_MESSAGE_CODEC(codec)); + self->response_callback = response_callback; + // Add a weak pointer so we know if the text input plugin goes away. + g_object_add_weak_pointer( + G_OBJECT(text_input_plugin), + reinterpret_cast(&(self->text_input_plugin))); + self->text_input_plugin = text_input_plugin; + + self->pending_events = g_ptr_array_new_with_free_func(g_object_unref); + return self; +} + +// Sends a key event to the framework. bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, GdkEventKey* event, gpointer user_data) { - g_return_val_if_fail(FL_IS_KEY_EVENT_PLUGIN(self), false); - g_return_val_if_fail(event != nullptr, false); + g_return_val_if_fail(FL_IS_KEY_EVENT_PLUGIN(self), FALSE); + g_return_val_if_fail(event != nullptr, FALSE); // Get an ID for the event, so we can match them up when we get a response // from the framework. Use the event time, type, and hardware keycode as a // unique ID, since they are part of the event structure that we can look up // when we receive a random event that may or may not have been // tracked/produced by this code. - uint64_t id = fl_get_event_id(event); + uint64_t id = get_event_id(event); if (self->pending_events->len != 0 && FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->id == id) { // If the event is at the head of the queue of pending events we've seen, // and has the same id, then we know that this is a re-dispatched event, and // we shouldn't respond to it, but we should remove it from tracking. - fl_remove_pending_event(self, id); - return false; + remove_pending_event(self, id); + return FALSE; } const gchar* type; @@ -311,7 +344,7 @@ bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, type = kTypeValueUp; break; default: - return false; + return FALSE; } int64_t scan_code = event->hardware_keycode; @@ -345,9 +378,9 @@ bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, // Remove lock states from state mask. guint state = event->state & ~(GDK_LOCK_MASK | GDK_MOD2_MASK); - static bool shift_lock_pressed = false; - static bool caps_lock_pressed = false; - static bool num_lock_pressed = false; + static bool shift_lock_pressed = FALSE; + static bool caps_lock_pressed = FALSE; + static bool num_lock_pressed = FALSE; switch (event->keyval) { case GDK_KEY_Num_Lock: num_lock_pressed = event->type == GDK_KEY_PRESS; @@ -381,13 +414,13 @@ bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, } // Track the event as pending a response from the framework. - fl_add_pending_event(self, id, event); + add_pending_event(self, id, event); FlKeyEventResponseData* data = fl_key_event_response_data_new(self, id, user_data); // Send the message off to the framework for handling (or not). fl_basic_message_channel_send(self->channel, message, nullptr, - fl_handle_response, data); + handle_response, data); // Return true before we know what the framework will do, because if it // doesn't handle the key, we'll re-dispatch it later. - return true; + return TRUE; } diff --git a/shell/platform/linux/fl_key_event_plugin.h b/shell/platform/linux/fl_key_event_plugin.h index 91bc639fb82c4..687045285a897 100644 --- a/shell/platform/linux/fl_key_event_plugin.h +++ b/shell/platform/linux/fl_key_event_plugin.h @@ -31,7 +31,7 @@ G_DECLARE_FINAL_TYPE(FlKeyEventPlugin, * @source_object: (nullable): the object the key event was started with. * @message: the message returned from the framework. * @handled: a boolean indicating whether the key event was handled in the - *framework. + * framework. * @user_data: user data passed to the callback. * * Type definition for a function that will be called when a key event is @@ -48,9 +48,10 @@ typedef void (*FlKeyEventPluginCallback)(GObject* source_object, * @response_callback: the callback to call when a response is received. If not * given (nullptr), then the default response callback is * used. Typically used for tests to receive event - * information. If specified, unhandled events will not be re-dispatched. + * information. If specified, unhandled events will not be + * re-dispatched. * @text_input_plugin: The #FlTextInputPlugin to send key events to if the - * framework doesn't handle them. + * framework doesn't handle them. * @channel_name: the name of the channel to send key events to the framework * on. If not given (nullptr), then the standard key event * channel name is used. Typically used for tests to send on a @@ -74,7 +75,7 @@ FlKeyEventPlugin* fl_key_event_plugin_new( * @user_data: a pointer to user data to send to the response callback via the * messenger. * - * @returns Whether or not this key event should be considered handled and + * @returns %TRUE if this key event should be considered handled and * event propagation stopped. * * Sends a key event to Flutter. diff --git a/shell/platform/linux/fl_key_event_plugin_test.cc b/shell/platform/linux/fl_key_event_plugin_test.cc index 174efc5538d5e..f39b0b54ff221 100644 --- a/shell/platform/linux/fl_key_event_plugin_test.cc +++ b/shell/platform/linux/fl_key_event_plugin_test.cc @@ -12,7 +12,7 @@ #include "flutter/shell/platform/linux/testing/mock_text_input_plugin.h" const char* expected_value = nullptr; -bool expected_handled = false; +gboolean expected_handled = FALSE; // Called when the message response is received in the send_key_event test. static void echo_response_cb(GObject* object, @@ -28,11 +28,11 @@ static void echo_response_cb(GObject* object, } static gboolean handle_keypress(FlTextInputPlugin* plugin, GdkEventKey* event) { - return true; + return TRUE; } static gboolean ignore_keypress(FlTextInputPlugin* plugin, GdkEventKey* event) { - return false; + return FALSE; } // Test sending a letter "A"; @@ -64,7 +64,7 @@ TEST(FlKeyEventPluginTest, SendKeyEvent) { expected_value = "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, " "modifiers: 0, unicodeScalarValues: 65}"; - expected_handled = false; + expected_handled = FALSE; fl_key_event_plugin_send_key_event(plugin, &key_event, loop); // Blocks here until echo_response_cb is called. @@ -87,7 +87,7 @@ TEST(FlKeyEventPluginTest, SendKeyEvent) { expected_value = "{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, " "modifiers: 0, unicodeScalarValues: 65}"; - expected_handled = false; + expected_handled = FALSE; bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); EXPECT_TRUE(handled); @@ -122,7 +122,7 @@ void test_lock_event(guint key_code, }; expected_value = down_expected; - expected_handled = false; + expected_handled = FALSE; bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); EXPECT_TRUE(handled); @@ -133,7 +133,7 @@ void test_lock_event(guint key_code, key_event.time++; expected_value = up_expected; - expected_handled = false; + expected_handled = FALSE; fl_key_event_plugin_send_key_event(plugin, &key_event, loop); // Blocks here until echo_response_cb is called. @@ -192,7 +192,7 @@ TEST(FlKeyEventPluginTest, TestKeyEventHandledByFramework) { }; expected_value = "{handled: true}"; - expected_handled = true; + expected_handled = TRUE; bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); // Should always be true, because the event was delayed. EXPECT_TRUE(handled); @@ -227,7 +227,7 @@ TEST(FlKeyEventPluginTest, TestKeyEventHandledByTextInputPlugin) { }; expected_value = "{handled: false}"; - expected_handled = true; + expected_handled = TRUE; bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); // Should always be true, because the event was delayed. EXPECT_TRUE(handled); @@ -262,7 +262,7 @@ TEST(FlKeyEventPluginTest, TestKeyEventNotHandledByTextInputPlugin) { }; expected_value = "{handled: false}"; - expected_handled = false; + expected_handled = FALSE; bool handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop); // Should always be true, because the event was delayed. EXPECT_TRUE(handled); diff --git a/shell/platform/linux/fl_text_input_plugin.cc b/shell/platform/linux/fl_text_input_plugin.cc index 248338b37cce4..dbbb560246b94 100644 --- a/shell/platform/linux/fl_text_input_plugin.cc +++ b/shell/platform/linux/fl_text_input_plugin.cc @@ -10,40 +10,6 @@ #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 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 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 int64_t kClientIdUnset = -1; - struct FlTextInputPluginPrivate { GObject parent_instance; @@ -81,6 +47,42 @@ G_DEFINE_TYPE_WITH_PRIVATE(FlTextInputPlugin, fl_text_input_plugin, G_TYPE_OBJECT) +namespace { + +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 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 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 int64_t kClientIdUnset = -1; + // Completes method call and returns TRUE if the call was successful. static gboolean finish_method(GObject* object, GAsyncResult* result, @@ -465,6 +467,7 @@ static void method_call_cb(FlMethodChannel* channel, } } +// Disposes of an FlTextInputPlugin. static void fl_text_input_plugin_dispose(GObject* object) { FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN(object); FlTextInputPluginPrivate* priv = static_cast( @@ -482,64 +485,10 @@ static void fl_text_input_plugin_dispose(GObject* object) { G_OBJECT_CLASS(fl_text_input_plugin_parent_class)->dispose(object); } -static void fl_text_input_plugin_init(FlTextInputPlugin* self) { - FlTextInputPluginPrivate* priv = static_cast( - fl_text_input_plugin_get_instance_private(self)); - - priv->client_id = kClientIdUnset; - priv->im_context = gtk_im_multicontext_new(); - priv->input_multiline = FALSE; - g_signal_connect_object(priv->im_context, "preedit-start", - G_CALLBACK(im_preedit_start_cb), self, - G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "preedit-end", - G_CALLBACK(im_preedit_end_cb), self, - G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "preedit-changed", - G_CALLBACK(im_preedit_changed_cb), self, - G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "commit", G_CALLBACK(im_commit_cb), - self, G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "retrieve-surrounding", - G_CALLBACK(im_retrieve_surrounding_cb), self, - G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "delete-surrounding", - G_CALLBACK(im_delete_surrounding_cb), self, - G_CONNECT_SWAPPED); - priv->text_model = new flutter::TextInputModel(); -} - -FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, - FlView* view) { - g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); - - FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN( - g_object_new(fl_text_input_plugin_get_type(), nullptr)); - - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - FlTextInputPluginPrivate* priv = static_cast( - fl_text_input_plugin_get_instance_private(self)); - priv->channel = - fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(priv->channel, method_call_cb, self, - nullptr); - priv->view = view; - - return self; -} - -gboolean fl_text_input_plugin_filter_keypress(FlTextInputPlugin* self, - GdkEventKey* event) { - g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(self), FALSE); - if (FL_TEXT_INPUT_PLUGIN_GET_CLASS(self)->filter_keypress) { - return FL_TEXT_INPUT_PLUGIN_GET_CLASS(self)->filter_keypress(self, event); - } - return FALSE; -} - // Implements FlTextInputPlugin::filter_keypress. -gboolean fl_text_input_plugin_filter_keypress_default(FlTextInputPlugin* self, - GdkEventKey* event) { +static gboolean fl_text_input_plugin_filter_keypress_default( + FlTextInputPlugin* self, + GdkEventKey* event) { g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(self), false); FlTextInputPluginPrivate* priv = static_cast( @@ -598,8 +547,69 @@ gboolean fl_text_input_plugin_filter_keypress_default(FlTextInputPlugin* self, return changed; } +} // namespace + +// Initializes the FlTextInputPlugin class. static void fl_text_input_plugin_class_init(FlTextInputPluginClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_text_input_plugin_dispose; FL_TEXT_INPUT_PLUGIN_CLASS(klass)->filter_keypress = fl_text_input_plugin_filter_keypress_default; } + +// Initializes an instance of the FlTextInputPlugin class. +static void fl_text_input_plugin_init(FlTextInputPlugin* self) { + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + + priv->client_id = kClientIdUnset; + priv->im_context = gtk_im_multicontext_new(); + priv->input_multiline = FALSE; + g_signal_connect_object(priv->im_context, "preedit-start", + G_CALLBACK(im_preedit_start_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(priv->im_context, "preedit-end", + G_CALLBACK(im_preedit_end_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(priv->im_context, "preedit-changed", + G_CALLBACK(im_preedit_changed_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(priv->im_context, "commit", G_CALLBACK(im_commit_cb), + self, G_CONNECT_SWAPPED); + g_signal_connect_object(priv->im_context, "retrieve-surrounding", + G_CALLBACK(im_retrieve_surrounding_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(priv->im_context, "delete-surrounding", + G_CALLBACK(im_delete_surrounding_cb), self, + G_CONNECT_SWAPPED); + priv->text_model = new flutter::TextInputModel(); +} + +FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, + FlView* view) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + + FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN( + g_object_new(fl_text_input_plugin_get_type(), nullptr)); + + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + FlTextInputPluginPrivate* priv = static_cast( + fl_text_input_plugin_get_instance_private(self)); + priv->channel = + fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(priv->channel, method_call_cb, self, + nullptr); + priv->view = view; + + return self; +} + +// Filters the a keypress given to the plugin through the plugin's +// filter_keypress callback. +gboolean fl_text_input_plugin_filter_keypress(FlTextInputPlugin* self, + GdkEventKey* event) { + g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(self), FALSE); + if (FL_TEXT_INPUT_PLUGIN_GET_CLASS(self)->filter_keypress) { + return FL_TEXT_INPUT_PLUGIN_GET_CLASS(self)->filter_keypress(self, event); + } + return FALSE; +} diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 82e9e903b28b8..6f1565e9bcf0c 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -349,9 +349,7 @@ static gboolean fl_view_motion_notify_event(GtkWidget* widget, static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) { FlView* self = FL_VIEW(widget); - return fl_key_event_plugin_send_key_event(self->key_event_plugin, event) - ? TRUE - : FALSE; + return fl_key_event_plugin_send_key_event(self->key_event_plugin, event); } // Implements GtkWidget::key_release_event. @@ -359,9 +357,7 @@ static gboolean fl_view_key_release_event(GtkWidget* widget, GdkEventKey* event) { FlView* self = FL_VIEW(widget); - return fl_key_event_plugin_send_key_event(self->key_event_plugin, event) - ? TRUE - : FALSE; + return fl_key_event_plugin_send_key_event(self->key_event_plugin, event); } static void fl_view_class_init(FlViewClass* klass) { diff --git a/shell/platform/linux/testing/mock_text_input_plugin.cc b/shell/platform/linux/testing/mock_text_input_plugin.cc index 083472c532f9e..bb064b182a191 100644 --- a/shell/platform/linux/testing/mock_text_input_plugin.cc +++ b/shell/platform/linux/testing/mock_text_input_plugin.cc @@ -14,8 +14,10 @@ G_DEFINE_TYPE(FlMockTextInputPlugin, fl_mock_text_input_plugin, fl_text_input_plugin_get_type()) -gboolean fl_mock_text_input_plugin_filter_keypress(FlTextInputPlugin* self, - GdkEventKey* event) { +namespace { + +static gboolean mock_text_input_plugin_filter_keypress(FlTextInputPlugin* self, + GdkEventKey* event) { FlMockTextInputPlugin* mock_self = FL_MOCK_TEXT_INPUT_PLUGIN(self); if (mock_self->filter_keypress) { return mock_self->filter_keypress(self, event); @@ -23,10 +25,12 @@ gboolean fl_mock_text_input_plugin_filter_keypress(FlTextInputPlugin* self, return FALSE; } +} // namespace + static void fl_mock_text_input_plugin_class_init( FlMockTextInputPluginClass* klass) { FL_TEXT_INPUT_PLUGIN_CLASS(klass)->filter_keypress = - fl_mock_text_input_plugin_filter_keypress; + mock_text_input_plugin_filter_keypress; } static void fl_mock_text_input_plugin_init(FlMockTextInputPlugin* self) {} From 35db692141af14546043e657bf51a74c408e47dd Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 3 Dec 2020 13:47:56 -0800 Subject: [PATCH 5/5] Remove anonymous namespaces, since everything's static anyhow --- shell/platform/linux/fl_key_event_plugin.cc | 30 ++++---- shell/platform/linux/fl_text_input_plugin.cc | 72 +++++++++---------- .../linux/testing/mock_text_input_plugin.cc | 4 -- 3 files changed, 47 insertions(+), 59 deletions(-) diff --git a/shell/platform/linux/fl_key_event_plugin.cc b/shell/platform/linux/fl_key_event_plugin.cc index eb8a3fc324d89..432ff5c1853f6 100644 --- a/shell/platform/linux/fl_key_event_plugin.cc +++ b/shell/platform/linux/fl_key_event_plugin.cc @@ -10,21 +10,6 @@ #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" -// Definition of the FlKeyEventPlugin GObject class. - -struct _FlKeyEventPlugin { - GObject parent_instance; - - FlBasicMessageChannel* channel = nullptr; - FlTextInputPlugin* text_input_plugin = nullptr; - FlKeyEventPluginCallback response_callback = nullptr; - GPtrArray* pending_events; -}; - -G_DEFINE_TYPE(FlKeyEventPlugin, fl_key_event_plugin, G_TYPE_OBJECT) - -namespace { - static constexpr char kChannelName[] = "flutter/keyevent"; static constexpr char kTypeKey[] = "type"; static constexpr char kTypeValueUp[] = "keyup"; @@ -41,6 +26,19 @@ static constexpr char kLinuxKeymap[] = "linux"; static constexpr uint64_t kMaxPendingEvents = 1000; +// Definition of the FlKeyEventPlugin GObject class. + +struct _FlKeyEventPlugin { + GObject parent_instance; + + FlBasicMessageChannel* channel = nullptr; + FlTextInputPlugin* text_input_plugin = nullptr; + FlKeyEventPluginCallback response_callback = nullptr; + GPtrArray* pending_events; +}; + +G_DEFINE_TYPE(FlKeyEventPlugin, fl_key_event_plugin, G_TYPE_OBJECT) + // Declare and define a private pair object to bind the id and the event // together. @@ -272,8 +270,6 @@ static void fl_key_event_plugin_dispose(GObject* object) { G_OBJECT_CLASS(fl_key_event_plugin_parent_class)->dispose(object); } -} // namespace - // Initializes the FlKeyEventPlugin class methods. static void fl_key_event_plugin_class_init(FlKeyEventPluginClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_key_event_plugin_dispose; diff --git a/shell/platform/linux/fl_text_input_plugin.cc b/shell/platform/linux/fl_text_input_plugin.cc index dbbb560246b94..eb7841f723788 100644 --- a/shell/platform/linux/fl_text_input_plugin.cc +++ b/shell/platform/linux/fl_text_input_plugin.cc @@ -10,6 +10,40 @@ #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 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 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 int64_t kClientIdUnset = -1; + struct FlTextInputPluginPrivate { GObject parent_instance; @@ -47,42 +81,6 @@ G_DEFINE_TYPE_WITH_PRIVATE(FlTextInputPlugin, fl_text_input_plugin, G_TYPE_OBJECT) -namespace { - -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 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 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 int64_t kClientIdUnset = -1; - // Completes method call and returns TRUE if the call was successful. static gboolean finish_method(GObject* object, GAsyncResult* result, @@ -547,8 +545,6 @@ static gboolean fl_text_input_plugin_filter_keypress_default( return changed; } -} // namespace - // Initializes the FlTextInputPlugin class. static void fl_text_input_plugin_class_init(FlTextInputPluginClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_text_input_plugin_dispose; diff --git a/shell/platform/linux/testing/mock_text_input_plugin.cc b/shell/platform/linux/testing/mock_text_input_plugin.cc index bb064b182a191..92309fa0e1c7c 100644 --- a/shell/platform/linux/testing/mock_text_input_plugin.cc +++ b/shell/platform/linux/testing/mock_text_input_plugin.cc @@ -14,8 +14,6 @@ G_DEFINE_TYPE(FlMockTextInputPlugin, fl_mock_text_input_plugin, fl_text_input_plugin_get_type()) -namespace { - static gboolean mock_text_input_plugin_filter_keypress(FlTextInputPlugin* self, GdkEventKey* event) { FlMockTextInputPlugin* mock_self = FL_MOCK_TEXT_INPUT_PLUGIN(self); @@ -25,8 +23,6 @@ static gboolean mock_text_input_plugin_filter_keypress(FlTextInputPlugin* self, return FALSE; } -} // namespace - static void fl_mock_text_input_plugin_class_init( FlMockTextInputPluginClass* klass) { FL_TEXT_INPUT_PLUGIN_CLASS(klass)->filter_keypress =