未验证 提交 94a40bc5 编写于 作者: T Tong Mu 提交者: GitHub

Hardware Keyboard: Linux (GTK) (#23467)

Linux (GTK) changes for the Hardware Keyboard project.
上级 82e1cc53
......@@ -1465,10 +1465,20 @@ FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec.cc
FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_json_method_codec.cc
FILE: ../../../flutter/shell/platform/linux/fl_json_method_codec_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_event_plugin.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_event_plugin.h
FILE: ../../../flutter/shell/platform/linux/fl_key_event_plugin_private.h
FILE: ../../../flutter/shell/platform/linux/fl_key_event_plugin_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_channel_responder.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_channel_responder.h
FILE: ../../../flutter/shell/platform/linux/fl_key_channel_responder_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_embedder_responder.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_embedder_responder.h
FILE: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_private.h
FILE: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_event.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_event.h
FILE: ../../../flutter/shell/platform/linux/fl_key_responder.cc
FILE: ../../../flutter/shell/platform/linux/fl_key_responder.h
FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager.cc
FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager.h
FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_message_codec.cc
FILE: ../../../flutter/shell/platform/linux/fl_message_codec_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_method_call.cc
......@@ -1513,6 +1523,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_view.cc
FILE: ../../../flutter/shell/platform/linux/fl_view_accessible.cc
FILE: ../../../flutter/shell/platform/linux/fl_view_accessible.h
FILE: ../../../flutter/shell/platform/linux/fl_view_private.h
FILE: ../../../flutter/shell/platform/linux/key_mapping.cc
FILE: ../../../flutter/shell/platform/linux/key_mapping.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_codec.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h
......
......@@ -12,7 +12,7 @@
* A primary responder of |FlutterKeyboardManager| that handles events by
* sending the raw information through the method channel.
*
* This class corresponds to the RawKeyboard API in the framework.
* This class communicates with the RawKeyboard API in the framework.
*/
@interface FlutterChannelKeyResponder : NSObject <FlutterKeyPrimaryResponder>
......
......@@ -19,7 +19,7 @@ typedef void (^FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent& /* event */,
* A primary responder of |FlutterKeyboardManager| that handles events by
* sending the converted events through the embedder API.
*
* This class corresponds to the HardwareKeyboard API in the framework.
* This class communicates with the HardwareKeyboard API in the framework.
*/
@interface FlutterEmbedderKeyResponder : NSObject <FlutterKeyPrimaryResponder>
......
......@@ -17,7 +17,7 @@
* secondary responders.
*
* An event that is received by |handleEvent| is first dispatched to *all*
* primary resopnders. Each primary responder responds *ascynchronously* with a
* primary responders. Each primary responder responds *ascynchronously* with a
* boolean, indicating whether it handles the event.
*
* An event that is not handled by any primary responders is then passed to to
......@@ -35,7 +35,7 @@
* because in reality this class will only support 2 hardcoded ones (channel
* and embedder), where the only purpose of supporting two is to support the
* legacy API (channel) during the deprecation window, after which the channel
* resopnder should be removed.
* responder should be removed.
*/
@interface FlutterKeyboardManager : NSObject
......@@ -50,7 +50,7 @@
- (nonnull instancetype)initWithOwner:(nonnull NSResponder*)weakOwner;
/**
* Add a primary resopnder, which asynchronously decides whether to handle an
* Add a primary responder, which asynchronously decides whether to handle an
* event.
*/
- (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder;
......
......@@ -13,7 +13,7 @@
// to run multiple times (e.g., using gtest_repeat).
//
// |proc| should be the name of an entry in FlutterEngineProcTable, such as
// "initialize". |mock_impl| should be a lamba that replaces its implementation,
// "Initialize". |mock_impl| should be a lamba that replaces its implementation,
// taking the same arguments and returning the same type.
#define MOCK_ENGINE_PROC(proc, mock_impl) \
([&]() { \
......
......@@ -74,12 +74,18 @@ source_set("flutter_linux_sources") {
"fl_binary_messenger_private.h",
"fl_dart_project_private.h",
"fl_engine_private.h",
"fl_key_event_plugin_private.h",
"fl_keyboard_manager.h",
"fl_key_event.h",
"fl_key_responder.h",
"fl_key_channel_responder.h",
"fl_key_embedder_responder.h",
"fl_key_embedder_responder_private.h",
"fl_method_call_private.h",
"fl_method_channel_private.h",
"fl_method_codec_private.h",
"fl_plugin_registrar_private.h",
"fl_standard_message_codec_private.h",
"key_mapping.h",
]
configs += [ "//flutter/shell/platform/linux/config:gtk" ]
......@@ -97,7 +103,11 @@ source_set("flutter_linux_sources") {
"fl_gl_area.cc",
"fl_json_message_codec.cc",
"fl_json_method_codec.cc",
"fl_key_event_plugin.cc",
"fl_key_channel_responder.cc",
"fl_key_embedder_responder.cc",
"fl_key_event.cc",
"fl_key_responder.cc",
"fl_keyboard_manager.cc",
"fl_message_codec.cc",
"fl_method_call.cc",
"fl_method_channel.cc",
......@@ -120,6 +130,7 @@ source_set("flutter_linux_sources") {
"fl_value.cc",
"fl_view.cc",
"fl_view_accessible.cc",
"key_mapping.cc",
]
# Set flag to stop headers being directly included (library users should not do this)
......@@ -166,7 +177,9 @@ executable("flutter_linux_unittests") {
"fl_event_channel_test.cc",
"fl_json_message_codec_test.cc",
"fl_json_method_codec_test.cc",
"fl_key_event_plugin_test.cc",
"fl_key_channel_responder_test.cc",
"fl_key_embedder_responder_test.cc",
"fl_keyboard_manager_test.cc",
"fl_message_codec_test.cc",
"fl_method_channel_test.cc",
"fl_method_codec_test.cc",
......
......@@ -639,6 +639,19 @@ void fl_engine_send_mouse_pointer_event(FlEngine* self,
self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1);
}
void fl_engine_send_key_event(FlEngine* self,
const FlutterKeyEvent* event,
FlutterKeyEventCallback callback,
void* user_data) {
g_return_if_fail(FL_IS_ENGINE(self));
if (self->engine == nullptr) {
return;
}
self->embedder_api.SendKeyEvent(self->engine, event, callback, user_data);
}
void fl_engine_dispatch_semantics_action(FlEngine* self,
uint64_t id,
FlutterSemanticsAction action,
......
......@@ -163,6 +163,14 @@ void fl_engine_send_mouse_pointer_event(FlEngine* engine,
double scroll_delta_y,
int64_t buttons);
/**
* fl_engine_send_key_event:
*/
void fl_engine_send_key_event(FlEngine* engine,
const FlutterKeyEvent* event,
FlutterKeyEventCallback callback,
void* user_data);
/**
* fl_engine_dispatch_semantics_action:
* @engine: an #FlEngine.
......
// 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_key_channel_responder.h"
#include <gtk/gtk.h>
#include <cinttypes>
#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
static constexpr char kChannelName[] = "flutter/keyevent";
static constexpr char kTypeKey[] = "type";
static constexpr char kTypeValueUp[] = "keyup";
static constexpr char kTypeValueDown[] = "keydown";
static constexpr char kKeymapKey[] = "keymap";
static constexpr char kKeyCodeKey[] = "keyCode";
static constexpr char kScanCodeKey[] = "scanCode";
static constexpr char kModifiersKey[] = "modifiers";
static constexpr char kToolkitKey[] = "toolkit";
static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues";
static constexpr char kGtkToolkit[] = "gtk";
static constexpr char kLinuxKeymap[] = "linux";
/* Declare and define FlKeyChannelUserData */
/**
* FlKeyChannelUserData:
* The user_data used when #FlKeyChannelResponder sends message through the
* channel.
*/
G_DECLARE_FINAL_TYPE(FlKeyChannelUserData,
fl_key_channel_user_data,
FL,
KEY_CHANNEL_USER_DATA,
GObject);
struct _FlKeyChannelUserData {
GObject parent_instance;
// The current responder.
FlKeyChannelResponder* responder;
// The callback provided by the caller #FlKeyboardManager.
FlKeyResponderAsyncCallback callback;
// The user_data provided by the caller #FlKeyboardManager.
gpointer user_data;
};
// Definition for FlKeyChannelUserData private class.
G_DEFINE_TYPE(FlKeyChannelUserData, fl_key_channel_user_data, G_TYPE_OBJECT)
// Dispose method for FlKeyChannelUserData private class.
static void fl_key_channel_user_data_dispose(GObject* object) {
g_return_if_fail(FL_IS_KEY_CHANNEL_USER_DATA(object));
FlKeyChannelUserData* self = FL_KEY_CHANNEL_USER_DATA(object);
if (self->responder != nullptr) {
g_object_remove_weak_pointer(
G_OBJECT(self->responder),
reinterpret_cast<gpointer*>(&(self->responder)));
self->responder = nullptr;
}
}
// Class initialization method for FlKeyChannelUserData private class.
static void fl_key_channel_user_data_class_init(
FlKeyChannelUserDataClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_key_channel_user_data_dispose;
}
// Instance initialization method for FlKeyChannelUserData private class.
static void fl_key_channel_user_data_init(FlKeyChannelUserData* self) {}
// Creates a new FlKeyChannelUserData private class with all information.
//
// The callback and the user_data might be nullptr.
static FlKeyChannelUserData* fl_key_channel_user_data_new(
FlKeyChannelResponder* responder,
FlKeyResponderAsyncCallback callback,
gpointer user_data) {
FlKeyChannelUserData* self = FL_KEY_CHANNEL_USER_DATA(
g_object_new(fl_key_channel_user_data_get_type(), nullptr));
self->responder = responder;
// Add a weak pointer so we can know if the key event responder disappeared
// while the framework was responding.
g_object_add_weak_pointer(G_OBJECT(responder),
reinterpret_cast<gpointer*>(&(self->responder)));
self->callback = callback;
self->user_data = user_data;
return self;
}
/* Define FlKeyChannelResponder */
// Definition of the FlKeyChannelResponder GObject class.
struct _FlKeyChannelResponder {
GObject parent_instance;
FlBasicMessageChannel* channel;
FlKeyChannelResponderMock* mock;
};
static void fl_key_channel_responder_iface_init(FlKeyResponderInterface* iface);
G_DEFINE_TYPE_WITH_CODE(
FlKeyChannelResponder,
fl_key_channel_responder,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(FL_TYPE_KEY_RESPONDER,
fl_key_channel_responder_iface_init))
static void fl_key_channel_responder_handle_event(
FlKeyResponder* responder,
FlKeyEvent* event,
FlKeyResponderAsyncCallback callback,
gpointer user_data);
static void fl_key_channel_responder_iface_init(
FlKeyResponderInterface* iface) {
iface->handle_event = fl_key_channel_responder_handle_event;
}
/* Implement FlKeyChannelResponder */
// Handles a response from the method channel to a key event sent to the
// framework earlier.
static void handle_response(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(FlKeyChannelUserData) data = FL_KEY_CHANNEL_USER_DATA(user_data);
// Will also return if the weak pointer has been destroyed.
if (data->responder == nullptr) {
return;
}
FlKeyChannelResponder* self = data->responder;
g_autoptr(GError) error = nullptr;
FlBasicMessageChannel* messageChannel = FL_BASIC_MESSAGE_CHANNEL(object);
FlValue* message =
fl_basic_message_channel_send_finish(messageChannel, result, &error);
if (self->mock != nullptr && self->mock->value_converter != nullptr) {
message = self->mock->value_converter(message);
}
if (error != nullptr) {
g_warning("Unable to retrieve framework response: %s", error->message);
return;
}
g_autoptr(FlValue) handled_value = fl_value_lookup_string(message, "handled");
bool handled = fl_value_get_bool(handled_value);
data->callback(handled, data->user_data);
}
// Disposes of an FlKeyChannelResponder instance.
static void fl_key_channel_responder_dispose(GObject* object) {
FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_key_channel_responder_parent_class)->dispose(object);
}
// Initializes the FlKeyChannelResponder class methods.
static void fl_key_channel_responder_class_init(
FlKeyChannelResponderClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_key_channel_responder_dispose;
}
// Initializes an FlKeyChannelResponder instance.
static void fl_key_channel_responder_init(FlKeyChannelResponder* self) {}
// Creates a new FlKeyChannelResponder instance, with a messenger used to send
// messages to the framework, and an FlTextInputPlugin that is 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.
FlKeyChannelResponder* fl_key_channel_responder_new(
FlBinaryMessenger* messenger,
FlKeyChannelResponderMock* mock) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(
g_object_new(fl_key_channel_responder_get_type(), nullptr));
self->mock = mock;
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
const char* channel_name =
mock == nullptr ? kChannelName : mock->channel_name;
self->channel = fl_basic_message_channel_new(messenger, channel_name,
FL_MESSAGE_CODEC(codec));
return self;
}
// Sends a key event to the framework.
static void fl_key_channel_responder_handle_event(
FlKeyResponder* responder,
FlKeyEvent* event,
FlKeyResponderAsyncCallback callback,
gpointer user_data) {
FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(responder);
g_return_if_fail(event != nullptr);
g_return_if_fail(callback != nullptr);
const gchar* type = event->is_press ? kTypeValueDown : kTypeValueUp;
int64_t scan_code = event->keycode;
int64_t unicode_scarlar_values = gdk_keyval_to_unicode(event->keyval);
// For most modifier keys, GTK keeps track of the "pressed" state of the
// modifier keys. Flutter uses this information to keep modifier keys from
// being "stuck" when a key-up event is lost because it happens after the app
// loses focus.
//
// For Lock keys (ShiftLock, CapsLock, NumLock), however, GTK keeps track of
// the state of the locks themselves, not the "pressed" state of the key.
//
// Since Flutter expects the "pressed" state of the modifier keys, the lock
// state for these keys is discarded here, and it is substituted for the
// pressed state of the key.
//
// This code has the flaw that if a key event is missed due to the app losing
// focus, then this state will still think the key is pressed when it isn't,
// but that is no worse than for "regular" keys until we implement the
// sync/cancel events on app focus changes.
//
// This is necessary to do here instead of in the framework because Flutter
// does modifier key syncing in the framework, and will turn on/off these keys
// as being "pressed" whenever the lock is on, which breaks a lot of
// interactions (for example, if shift-lock is on, tab traversal is broken).
// 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;
switch (event->keyval) {
case GDK_KEY_Num_Lock:
num_lock_pressed = event->is_press;
break;
case GDK_KEY_Caps_Lock:
caps_lock_pressed = event->is_press;
break;
case GDK_KEY_Shift_Lock:
shift_lock_pressed = event->is_press;
break;
}
// Add back in the state matching the actual pressed state of the lock keys,
// not the lock states.
state |= (shift_lock_pressed || caps_lock_pressed) ? GDK_LOCK_MASK : 0x0;
state |= num_lock_pressed ? GDK_MOD2_MASK : 0x0;
g_autoptr(FlValue) message = fl_value_new_map();
fl_value_set_string_take(message, kTypeKey, fl_value_new_string(type));
fl_value_set_string_take(message, kKeymapKey,
fl_value_new_string(kLinuxKeymap));
fl_value_set_string_take(message, kScanCodeKey, fl_value_new_int(scan_code));
fl_value_set_string_take(message, kToolkitKey,
fl_value_new_string(kGtkToolkit));
fl_value_set_string_take(message, kKeyCodeKey,
fl_value_new_int(event->keyval));
fl_value_set_string_take(message, kModifiersKey, fl_value_new_int(state));
if (unicode_scarlar_values != 0) {
fl_value_set_string_take(message, kUnicodeScalarValuesKey,
fl_value_new_int(unicode_scarlar_values));
}
FlKeyChannelUserData* data =
fl_key_channel_user_data_new(self, callback, user_data);
// Send the message off to the framework for handling (or not).
fl_basic_message_channel_send(self->channel, message, nullptr,
handle_response, data);
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_CHANNEL_RESPONDER_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_CHANNEL_RESPONDER_H_
#include <gdk/gdk.h>
#include "flutter/shell/platform/linux/fl_key_responder.h"
#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
typedef FlValue* (*FlValueConverter)(FlValue*);
/**
* FlKeyChannelResponderMock:
*
* Allows mocking of FlKeyChannelResponder methods and values. Only used in
* unittests.
*/
typedef struct _FlKeyChannelResponderMock {
/**
* FlKeyChannelResponderMock::value_converter:
* If #value_converter is not nullptr, then this function is applied to the
* reply of the message, whose return value is taken as the message reply.
*/
FlValueConverter value_converter;
/**
* FlKeyChannelResponderMock::channel_name:
* Mocks the channel name to send the message.
*/
const char* channel_name;
} FlKeyChannelResponderMock;
G_BEGIN_DECLS
#define FL_TYPE_KEY_CHANNEL_RESPONDER fl_key_channel_responder_get_type()
G_DECLARE_FINAL_TYPE(FlKeyChannelResponder,
fl_key_channel_responder,
FL,
KEY_CHANNEL_RESPONDER,
GObject);
/**
* FlKeyChannelResponder:
*
* A #FlKeyResponder that handles events by sending the raw event data
* in JSON through the message channel.
*
* This class communicates with the RawKeyboard API in the framework.
*/
/**
* fl_key_channel_responder_new:
* @messenger: the messenger that the message channel should be built on.
* @mock: options to mock several functionalities. Only used in unittests.
*
* Creates a new #FlKeyChannelResponder.
*
* Returns: a new #FlKeyChannelResponder.
*/
FlKeyChannelResponder* fl_key_channel_responder_new(
FlBinaryMessenger* messenger,
FlKeyChannelResponderMock* mock = nullptr);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_CHANNEL_RESPONDER_H_
// 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_key_channel_responder.h"
#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/testing/fl_test.h"
static const char* expected_value = nullptr;
static gboolean expected_handled = FALSE;
static FlValue* echo_response_cb(FlValue* echoed_value) {
gchar* text = fl_value_to_string(echoed_value);
EXPECT_STREQ(text, expected_value);
g_free(text);
FlValue* value = fl_value_new_map();
fl_value_set_string_take(value, "handled",
fl_value_new_bool(expected_handled));
return value;
}
static void responder_callback(bool handled, gpointer user_data) {
EXPECT_EQ(handled, expected_handled);
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}
// Clone string onto the heap.
//
// If #string is nullptr, returns nullptr. Otherwise, the returned pointer must
// be freed with g_free.
static char* clone_string(const char* string) {
if (string == nullptr) {
return nullptr;
}
size_t len = strlen(string);
char* result = g_new(char, len + 1);
strcpy(result, string);
return result;
}
namespace {
// A global variable to store new event. It is a global variable so that it can
// be returned by #fl_key_event_new_by_mock for easy use.
FlKeyEvent _g_key_event;
} // namespace
// Create a new #FlKeyEvent with the given information.
//
// This event is passed to #fl_key_responder_handle_event,
// which assumes that the event is managed by callee.
// Therefore #fl_key_event_new_by_mock doesn't need to
// dynamically allocate, but reuses the same global object.
static FlKeyEvent* fl_key_event_new_by_mock(guint32 time_in_milliseconds,
bool is_press,
guint keyval,
guint16 keycode,
int state,
const char* string,
gboolean is_modifier) {
if (_g_key_event.string != nullptr) {
g_free(const_cast<char*>(_g_key_event.string));
}
_g_key_event.is_press = is_press;
_g_key_event.time = time_in_milliseconds;
_g_key_event.state = state;
_g_key_event.keyval = keyval;
_g_key_event.string = clone_string(string);
_g_key_event.keycode = keycode;
_g_key_event.origin = nullptr;
_g_key_event.dispose_origin = nullptr;
return &_g_key_event;
}
// Test sending a letter "A";
TEST(FlKeyChannelResponderTest, SendKeyEvent) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine);
FlKeyChannelResponderMock mock{
.value_converter = echo_response_cb,
.channel_name = "test/echo",
};
g_autoptr(FlKeyResponder) responder =
FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger, &mock));
fl_key_responder_handle_event(
responder,
fl_key_event_new_by_mock(12345, true, GDK_KEY_A, 0x04, 0x0, "A", false),
responder_callback, loop);
expected_value =
"{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, "
"modifiers: 0, unicodeScalarValues: 65}";
expected_handled = FALSE;
// Blocks here until echo_response_cb is called.
g_main_loop_run(loop);
fl_key_responder_handle_event(
responder,
fl_key_event_new_by_mock(23456, false, GDK_KEY_A, 0x04, 0x0, "A", false),
responder_callback, loop);
expected_value =
"{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, "
"modifiers: 0, unicodeScalarValues: 65}";
expected_handled = FALSE;
// Blocks here until echo_response_cb is called.
g_main_loop_run(loop);
}
void test_lock_event(guint key_code,
const char* down_expected,
const char* up_expected) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine);
FlKeyChannelResponderMock mock{
.value_converter = echo_response_cb,
.channel_name = "test/echo",
};
g_autoptr(FlKeyResponder) responder =
FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger, &mock));
fl_key_responder_handle_event(
responder,
fl_key_event_new_by_mock(12345, true, key_code, 0x04, 0x0, nullptr,
false),
responder_callback, loop);
expected_value = down_expected;
expected_handled = FALSE;
// Blocks here until echo_response_cb is called.
g_main_loop_run(loop);
expected_value = up_expected;
expected_handled = FALSE;
fl_key_responder_handle_event(
responder,
fl_key_event_new_by_mock(12346, false, key_code, 0x04, 0x0, nullptr,
false),
responder_callback, loop);
// Blocks here until echo_response_cb is called.
g_main_loop_run(loop);
}
// Test sending a "NumLock" keypress.
TEST(FlKeyChannelResponderTest, SendNumLockKeyEvent) {
test_lock_event(GDK_KEY_Num_Lock,
"{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65407, modifiers: 16}",
"{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65407, modifiers: 0}");
}
// Test sending a "CapsLock" keypress.
TEST(FlKeyChannelResponderTest, SendCapsLockKeyEvent) {
test_lock_event(GDK_KEY_Caps_Lock,
"{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65509, modifiers: 2}",
"{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65509, modifiers: 0}");
}
// Test sending a "ShiftLock" keypress.
TEST(FlKeyChannelResponderTest, SendShiftLockKeyEvent) {
test_lock_event(GDK_KEY_Shift_Lock,
"{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65510, modifiers: 2}",
"{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65510, modifiers: 0}");
}
TEST(FlKeyChannelResponderTest, TestKeyEventHandledByFramework) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine);
FlKeyChannelResponderMock mock{
.value_converter = echo_response_cb,
.channel_name = "test/echo",
};
g_autoptr(FlKeyResponder) responder =
FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger, &mock));
fl_key_responder_handle_event(
responder,
fl_key_event_new_by_mock(12345, true, GDK_KEY_A, 0x04, 0x0, nullptr,
false),
responder_callback, loop);
expected_handled = TRUE;
expected_value =
"{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65, modifiers: 0, unicodeScalarValues: 65}";
// Blocks here until echo_response_cb is called.
g_main_loop_run(loop);
}
此差异已折叠。
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EMBEDDER_RESPONDER_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EMBEDDER_RESPONDER_H_
#include <gdk/gdk.h>
#include "flutter/shell/platform/linux/fl_key_responder.h"
#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
constexpr int kMaxConvertedKeyData = 3;
G_BEGIN_DECLS
#define FL_TYPE_KEY_EMBEDDER_RESPONDER fl_key_embedder_responder_get_type()
G_DECLARE_FINAL_TYPE(FlKeyEmbedderResponder,
fl_key_embedder_responder,
FL,
KEY_EMBEDDER_RESPONDER,
GObject);
/**
* FlKeyEmbedderResponder:
*
* A #FlKeyResponder that handles events by sending the converted events
* through the embedder API.
*
* This class communicates with the HardwareKeyboard API in the framework.
*/
/**
* fl_key_embedder_responder_new:
* @engine: The #FlEngine, whose the embedder API will be used to send
* the event.
*
* Creates a new #FlKeyEmbedderResponder.
*
* Returns: a new #FlKeyEmbedderResponder.
*/
FlKeyEmbedderResponder* fl_key_embedder_responder_new(FlEngine* engine);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EMBEDDER_RESPONDER_H_
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EMBEDDER_RESPONDER_PRIVATE_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EMBEDDER_RESPONDER_PRIVATE_H_
#include <gdk/gdk.h>
#include "flutter/shell/platform/linux/fl_key_responder.h"
#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
/**
* FlKeyEmbedderCheckedKey:
*
* The information for a key that #FlKeyEmbedderResponder should keep state
* synchronous on. For every record of #FlKeyEmbedderCheckedKey, the responder
* will check the #GdkEvent::state and the internal state, and synchronize
* events if they don't match.
*
* #FlKeyEmbedderCheckedKey can synchronize pressing states (such as
* whether ControlLeft is pressed) or lock states (such as whether CapsLock
* is enabled).
*
* #FlKeyEmbedderCheckedKey has a "primary key". For pressing states, the
* primary key is the left of the modifiers. For lock states, the primary
* key is the key.
*
* #FlKeyEmbedderCheckedKey may also have a "secondary key". It is only
* available to pressing states, which is the right of the modifiers.
*/
typedef struct {
// The physical key for the primary key.
uint64_t primary_physical_key;
// The logical key for the primary key.
uint64_t primary_logical_key;
// The logical key for the secondary key.
uint64_t secondary_logical_key;
// Whether this key is CapsLock. CapsLock uses a different event model in GDK
// and needs special treatment.
bool is_caps_lock;
} FlKeyEmbedderCheckedKey;
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EMBEDDER_RESPONDER_PRIVATE_H_
此差异已折叠。
// 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_key_event.h"
static void dispose_origin_from_gdk_event(gpointer origin) {
g_return_if_fail(origin != nullptr);
gdk_event_free(reinterpret_cast<GdkEvent*>(origin));
}
static char* clone_string(const char* source) {
if (source == nullptr) {
return nullptr;
}
size_t length = strlen(source);
char* result = g_new(char, length + 1);
strcpy(result, source);
return result;
}
FlKeyEvent* fl_key_event_new_from_gdk_event(GdkEvent* raw_event) {
g_return_val_if_fail(raw_event != nullptr, nullptr);
GdkEventKey* event = reinterpret_cast<GdkEventKey*>(raw_event);
GdkEventType type = event->type;
g_return_val_if_fail(type == GDK_KEY_PRESS || type == GDK_KEY_RELEASE,
nullptr);
FlKeyEvent* result = g_new(FlKeyEvent, 1);
result->time = event->time;
result->is_press = type == GDK_KEY_PRESS;
result->keycode = event->hardware_keycode;
result->keyval = event->keyval;
result->state = event->state;
result->string = clone_string(event->string);
result->origin = event;
result->dispose_origin = dispose_origin_from_gdk_event;
return result;
}
void fl_key_event_dispose(FlKeyEvent* event) {
if (event->string != nullptr) {
g_free(const_cast<char*>(event->string));
}
if (event->dispose_origin != nullptr) {
event->dispose_origin(event->origin);
}
g_free(event);
}
FlKeyEvent* fl_key_event_clone(const FlKeyEvent* event) {
FlKeyEvent* new_event = g_new(FlKeyEvent, 1);
*new_event = *event;
new_event->string = clone_string(event->string);
return new_event;
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_H_
#include <gdk/gdk.h>
/**
* FlKeyEventDispose:
* @origin: the #FlKeyEvent::origin to dispose.
*
* The signature for #FlKeyEvent::dispose_origin, which
* frees #FlKeyEvent::origin.
**/
typedef void (*FlKeyEventDisposeOrigin)(gpointer origin);
/**
* FlKeyEvent:
* A struct that stores information from GdkEvent.
*
* This is a class only used within the GTK embedding, created by
* FlView and consumed by FlKeyboardManager. It is not sent to
* the embedder.
*
* This object contains information from GdkEvent as well as an origin event
* object, so that Flutter can create an event object in unit tests even after
* migrating to GDK 4.0 which stops supporting creating GdkEvent.
*/
typedef struct _FlKeyEvent {
// Time in milliseconds.
guint32 time;
// True if is a press event, otherwise a release event.
bool is_press;
// Hardware keycode.
guint16 keycode;
// Keyval.
guint keyval;
// Modifier state.
int state;
// String, null-terminated.
//
// Can be nullptr.
const char* string;
// An opaque pointer to the original event.
//
// This is used when dispatching. For native events, this is #GdkEvent
// pointer. For unit tests, this is a dummy pointer.
gpointer origin;
// A callback to free #origin, called in #fl_key_event_dispose.
//
// Can be nullptr.
FlKeyEventDisposeOrigin dispose_origin;
} FlKeyEvent;
/**
* fl_key_event_new_from_gdk_event:
* @event: the #GdkEvent this #FlKeyEvent is based on. The #event must be a
* #GdkEventKey, and will be destroyed by #fl_key_event_dispose.
*
* Create a new #FlKeyEvent based on a #GdkEvent.
*
* Returns: a new #FlKeyEvent. Must be freed with #fl_key_event_dispose.
*/
FlKeyEvent* fl_key_event_new_from_gdk_event(GdkEvent* event);
/**
* fl_key_event_dispose:
* @event: the event to dispose.
*
* Properly disposes the content of #event and then the pointer.
*/
void fl_key_event_dispose(FlKeyEvent* event);
FlKeyEvent* fl_key_event_clone(const FlKeyEvent* source);
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_H_
// 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_key_event_plugin.h"
#include "flutter/shell/platform/linux/fl_key_event_plugin_private.h"
#include <gtk/gtk.h>
#include <cinttypes>
#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"
static constexpr char kChannelName[] = "flutter/keyevent";
static constexpr char kTypeKey[] = "type";
static constexpr char kTypeValueUp[] = "keyup";
static constexpr char kTypeValueDown[] = "keydown";
static constexpr char kKeymapKey[] = "keymap";
static constexpr char kKeyCodeKey[] = "keyCode";
static constexpr char kScanCodeKey[] = "scanCode";
static constexpr char kModifiersKey[] = "modifiers";
static constexpr char kToolkitKey[] = "toolkit";
static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues";
static constexpr char kGtkToolkit[] = "gtk";
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.
G_DECLARE_FINAL_TYPE(FlKeyEventPair,
fl_key_event_pair,
FL,
KEY_EVENT_PAIR,
GObject);
struct _FlKeyEventPair {
GObject parent_instance;
uint64_t id;
GdkEventKey* event;
};
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);
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));
// Copy the event to preserve refcounts for referenced values (mainly the
// window).
GdkEventKey* event_copy = reinterpret_cast<GdkEventKey*>(
gdk_event_copy(reinterpret_cast<GdkEvent*>(event)));
self->id = id;
self->event = event_copy;
return self;
}
// Declare and define a private class to hold response data from the framework.
G_DECLARE_FINAL_TYPE(FlKeyEventResponseData,
fl_key_event_response_data,
FL,
KEY_EVENT_RESPONSE_DATA,
GObject);
struct _FlKeyEventResponseData {
GObject parent_instance;
FlKeyEventPlugin* plugin;
uint64_t id;
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);
if (self->plugin != nullptr) {
g_object_remove_weak_pointer(G_OBJECT(self->plugin),
reinterpret_cast<gpointer*>(&(self->plugin)));
self->plugin = nullptr;
}
}
// 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) {
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<gpointer*>(&(self->plugin)));
self->id = id;
self->user_data = user_data;
return self;
}
// Calculates a unique ID for a given GdkEventKey object to use for
// identification of responses from the framework.
uint64_t fl_key_event_plugin_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<uint64_t>(event->type) & 0xffff) << 32 |
(static_cast<uint64_t>(event->hardware_keycode) & 0xffff) << 48;
}
// Finds an event in the event queue that was sent to the framework by its ID.
GdkEventKey* fl_key_event_plugin_find_pending_event(FlKeyEventPlugin* self,
uint64_t id) {
for (guint i = 0; i < self->pending_events->len; ++i) {
if (FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, i))->id ==
id) {
return FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, i))
->event;
}
}
return nullptr;
}
// Removes an event from the pending event queue.
static void remove_pending_event(FlKeyEventPlugin* self, uint64_t id) {
for (guint i = 0; i < self->pending_events->len; ++i) {
if (FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, i))->id ==
id) {
g_ptr_array_remove_index(self->pending_events, i);
return;
}
}
g_warning("Tried to remove pending event with id %" PRIu64
", but the event was not found.",
id);
}
// 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 "
"response from the framework. Are responses being sent?",
self->pending_events->len);
}
g_ptr_array_add(self->pending_events, fl_key_event_pair_new(id, event));
}
// 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.
if (data->plugin == nullptr) {
return;
}
FlKeyEventPlugin* self = data->plugin;
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_warning("Unable to retrieve framework response: %s", error->message);
return;
}
g_autoptr(FlValue) handled_value = fl_value_lookup_string(message, "handled");
bool handled = FALSE;
if (handled_value != nullptr) {
GdkEventKey* event = fl_key_event_plugin_find_pending_event(self, data->id);
if (event == nullptr) {
g_warning("Event response for event id %" PRIu64
" received, but pending event was not found.",
data->id);
} else {
handled = fl_value_get_bool(handled_value);
if (!handled) {
if (self->text_input_plugin != nullptr) {
// Propagate the event to the text input plugin.
handled = fl_text_input_plugin_filter_keypress(
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 && self->response_callback == nullptr) {
gdk_event_put(reinterpret_cast<GdkEvent*>(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.
remove_pending_event(self, data->id);
}
if (self->response_callback != nullptr) {
self->response_callback(object, message, handled, data->user_data);
}
}
// 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);
if (self->text_input_plugin != nullptr) {
g_object_remove_weak_pointer(
G_OBJECT(self->text_input_plugin),
reinterpret_cast<gpointer*>(&(self->text_input_plugin)));
self->text_input_plugin = nullptr;
}
g_ptr_array_free(self->pending_events, TRUE);
G_OBJECT_CLASS(fl_key_event_plugin_parent_class)->dispose(object);
}
// 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<gpointer*>(&(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);
// 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_key_event_plugin_get_event_id(event);
if (self->pending_events->len != 0 &&
fl_key_event_plugin_find_pending_event(self, id) != nullptr) {
// If the event is in the queue of pending events we've seen, then we know
// that this is a re-dispatched event, and we shouldn't respond to it, but
// we should remove it from tracking.
remove_pending_event(self, id);
return FALSE;
}
const gchar* type;
switch (event->type) {
case GDK_KEY_PRESS:
type = kTypeValueDown;
break;
case GDK_KEY_RELEASE:
type = kTypeValueUp;
break;
default:
return FALSE;
}
int64_t scan_code = event->hardware_keycode;
int64_t unicodeScalarValues = gdk_keyval_to_unicode(event->keyval);
// For most modifier keys, GTK keeps track of the "pressed" state of the
// modifier keys. Flutter uses this information to keep modifier keys from
// being "stuck" when a key-up event is lost because it happens after the app
// loses focus.
//
// For Lock keys (ShiftLock, CapsLock, NumLock), however, GTK keeps track of
// the state of the locks themselves, not the "pressed" state of the key.
//
// Since Flutter expects the "pressed" state of the modifier keys, the lock
// state for these keys is discarded here, and it is substituted for the
// pressed state of the key.
//
// This code has the flaw that if a key event is missed due to the app losing
// focus, then this state will still think the key is pressed when it isn't,
// but that is no worse than for "regular" keys until we implement the
// sync/cancel events on app focus changes.
//
// This is necessary to do here instead of in the framework because Flutter
// does modifier key syncing in the framework, and will turn on/off these keys
// as being "pressed" whenever the lock is on, which breaks a lot of
// interactions (for example, if shift-lock is on, tab traversal is broken).
//
// TODO(gspencergoog): get rid of this tracked state when we are tracking the
// state of all keys and sending sync/cancel events when focus is gained/lost.
// 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;
switch (event->keyval) {
case GDK_KEY_Num_Lock:
num_lock_pressed = event->type == GDK_KEY_PRESS;
break;
case GDK_KEY_Caps_Lock:
caps_lock_pressed = event->type == GDK_KEY_PRESS;
break;
case GDK_KEY_Shift_Lock:
shift_lock_pressed = event->type == GDK_KEY_PRESS;
break;
}
// Add back in the state matching the actual pressed state of the lock keys,
// not the lock states.
state |= (shift_lock_pressed || caps_lock_pressed) ? GDK_LOCK_MASK : 0x0;
state |= num_lock_pressed ? GDK_MOD2_MASK : 0x0;
g_autoptr(FlValue) message = fl_value_new_map();
fl_value_set_string_take(message, kTypeKey, fl_value_new_string(type));
fl_value_set_string_take(message, kKeymapKey,
fl_value_new_string(kLinuxKeymap));
fl_value_set_string_take(message, kScanCodeKey, fl_value_new_int(scan_code));
fl_value_set_string_take(message, kToolkitKey,
fl_value_new_string(kGtkToolkit));
fl_value_set_string_take(message, kKeyCodeKey,
fl_value_new_int(event->keyval));
fl_value_set_string_take(message, kModifiersKey, fl_value_new_int(state));
if (unicodeScalarValues != 0) {
fl_value_set_string_take(message, kUnicodeScalarValuesKey,
fl_value_new_int(unicodeScalarValues));
}
// Track the event as pending a response from the framework.
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,
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;
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_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 <gdk/gdk.h>
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(FlKeyEventPlugin,
fl_key_event_plugin,
FL,
KEY_EVENT_PLUGIN,
GObject);
/**
* FlKeyEventPlugin:
*
* #FlKeyEventPlugin is a plugin that implements the shell side
* of SystemChannels.keyEvent from the Flutter services library.
*/
/**
* FlKeyEventPluginCallback:
* @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.
* @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,
gboolean 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. 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.
*
* Returns: a new #FlKeyEventPlugin.
*/
FlKeyEventPlugin* fl_key_event_plugin_new(
FlBinaryMessenger* messenger,
FlTextInputPlugin* text_input_plugin,
FlKeyEventPluginCallback response_callback = nullptr,
const char* channel_name = nullptr);
/**
* fl_key_event_plugin_send_key_event:
* @plugin: an #FlKeyEventPlugin.
* @event: a #GdkEventKey.
* @user_data: a pointer to user data to send to the response callback via the
* messenger.
*
* @returns %TRUE if this key event should be considered handled and
* event propagation stopped.
*
* Sends a key event to Flutter.
*/
bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* plugin,
GdkEventKey* event,
gpointer user_data = nullptr);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_PLUGIN_H_
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_PLUGIN_PRIVATE_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_PLUGIN_PRIVATE_H_
#include "flutter/shell/platform/linux/fl_key_event_plugin.h"
#include <gtk/gtk.h>
G_BEGIN_DECLS
/**
* fl_key_event_plugin_get_event_id:
* @event: a GDK key event (GdkEventKey).
*
* Calculates an internal key ID for a given event. Used to look up pending
* events using fl_key_event_plugin_find_pending_event.
*
* Returns: a 64-bit ID representing this GDK key event.
*/
uint64_t fl_key_event_plugin_get_event_id(GdkEventKey* event);
/**
* fl_key_event_plugin_get_event_id:
* @id: a 64-bit id representing a GDK key event (GdkEventKey). Calculated
* using #fl_key_event_plugin_get_event_id.
*
* Looks up an event that is waiting for a response from the framework.
*
* Returns: the GDK key event requested, or nullptr if the event doesn't exist
* in the queue.
*/
GdkEventKey* fl_key_event_plugin_find_pending_event(FlKeyEventPlugin* self,
uint64_t id);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_PLUGIN_PRIVATE_H_
// 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_key_event_plugin.h"
#include "flutter/shell/platform/linux/fl_key_event_plugin_private.h"
#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/testing/fl_test.h"
#include "flutter/shell/platform/linux/testing/mock_text_input_plugin.h"
static const char* expected_value = nullptr;
static gboolean expected_handled = FALSE;
static uint64_t expected_id = 0;
static FlKeyEventPlugin* expected_self = nullptr;
// Called when the message response is received in the send_key_event test.
static void echo_response_cb(GObject* object,
FlValue* message,
gboolean handled,
gpointer user_data) {
EXPECT_NE(message, 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);
if (expected_self != nullptr) {
if (handled) {
EXPECT_EQ(
fl_key_event_plugin_find_pending_event(expected_self, expected_id),
nullptr);
} else {
EXPECT_NE(
fl_key_event_plugin_find_pending_event(expected_self, expected_id),
nullptr);
}
}
g_main_loop_quit(static_cast<GMainLoop*>(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(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{
GDK_KEY_PRESS, // event type
nullptr, // window (not needed)
FALSE, // event was sent explicitly
12345, // time
0x0, // modifier state
GDK_KEY_A, // key code
1, // length of string representation
reinterpret_cast<gchar*>(&string[0]), // string representation
0x04, // scan code
0, // keyboard group
0, // is a modifier
};
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.
g_main_loop_run(loop);
EXPECT_NE(fl_key_event_plugin_find_pending_event(
plugin, fl_key_event_plugin_get_event_id(&key_event)),
nullptr);
key_event = GdkEventKey{
GDK_KEY_RELEASE, // event type
nullptr, // window (not needed)
FALSE, // event was sent explicitly
23456, // time
0x0, // modifier state
GDK_KEY_A, // key code
1, // length of string representation
reinterpret_cast<gchar*>(&string[0]), // string representation
0x04, // scan code
0, // keyboard group
0, // is a modifier
};
expected_value =
"{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, "
"modifiers: 0, unicodeScalarValues: 65}";
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);
EXPECT_NE(fl_key_event_plugin_find_pending_event(
plugin, fl_key_event_plugin_get_event_id(&key_event)),
nullptr);
}
void test_lock_event(guint key_code,
const char* down_expected,
const char* up_expected) {
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/echo");
GdkEventKey key_event = GdkEventKey{
GDK_KEY_PRESS, // event type
nullptr, // window (not needed)
FALSE, // event was sent explicitly
12345, // time
0x10, // modifier state
key_code, // key code
1, // length of string representation
nullptr, // string representation
0x04, // scan code
0, // keyboard group
0, // is a modifier
};
expected_value = down_expected;
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);
EXPECT_NE(fl_key_event_plugin_find_pending_event(
plugin, fl_key_event_plugin_get_event_id(&key_event)),
nullptr);
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.
g_main_loop_run(loop);
EXPECT_NE(fl_key_event_plugin_find_pending_event(
plugin, fl_key_event_plugin_get_event_id(&key_event)),
nullptr);
}
// Test sending a "NumLock" keypress.
TEST(FlKeyEventPluginTest, SendNumLockKeyEvent) {
test_lock_event(GDK_KEY_Num_Lock,
"{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65407, modifiers: 16}",
"{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65407, modifiers: 0}");
}
// Test sending a "CapsLock" keypress.
TEST(FlKeyEventPluginTest, SendCapsLockKeyEvent) {
test_lock_event(GDK_KEY_Caps_Lock,
"{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65509, modifiers: 2}",
"{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65509, modifiers: 0}");
}
// Test sending a "ShiftLock" keypress.
TEST(FlKeyEventPluginTest, SendShiftLockKeyEvent) {
test_lock_event(GDK_KEY_Shift_Lock,
"{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
"keyCode: 65510, modifiers: 2}",
"{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);
EXPECT_EQ(fl_key_event_plugin_find_pending_event(
plugin, fl_key_event_plugin_get_event_id(&key_event)),
nullptr);
}
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);
EXPECT_EQ(fl_key_event_plugin_find_pending_event(
plugin, fl_key_event_plugin_get_event_id(&key_event)),
nullptr);
}
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);
EXPECT_NE(fl_key_event_plugin_find_pending_event(
plugin, fl_key_event_plugin_get_event_id(&key_event)),
nullptr);
}
TEST(FlKeyEventPluginTest, TestKeyEventResponseOutOfOrderFromFramework) {
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-delayed");
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;
expected_self = plugin;
uint64_t event_id_a = fl_key_event_plugin_get_event_id(&key_event);
expected_id = event_id_a;
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);
EXPECT_EQ(fl_key_event_plugin_find_pending_event(plugin, event_id_a)->keyval,
static_cast<guint>(GDK_KEY_A));
// Send a second key event that will be out of order.
key_event.keyval = GDK_KEY_B;
key_event.hardware_keycode = 0x05;
uint64_t event_id_b = fl_key_event_plugin_get_event_id(&key_event);
expected_id = event_id_b;
handled = fl_key_event_plugin_send_key_event(plugin, &key_event, loop);
EXPECT_TRUE(handled);
EXPECT_EQ(fl_key_event_plugin_find_pending_event(plugin, event_id_b)->keyval,
static_cast<guint>(GDK_KEY_B));
// Blocks here until echo_response_cb is called.
g_main_loop_run(loop);
// Make sure they both were removed
EXPECT_EQ(fl_key_event_plugin_find_pending_event(plugin, event_id_a),
nullptr);
EXPECT_EQ(fl_key_event_plugin_find_pending_event(plugin, event_id_b),
nullptr);
expected_self = nullptr;
expected_id = 0;
}
// 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_key_responder.h"
G_DEFINE_INTERFACE(FlKeyResponder, fl_key_responder, G_TYPE_OBJECT)
static void fl_key_responder_default_init(FlKeyResponderInterface* iface) {}
void fl_key_responder_handle_event(FlKeyResponder* self,
FlKeyEvent* event,
FlKeyResponderAsyncCallback callback,
gpointer user_data) {
g_return_if_fail(FL_IS_KEY_RESPONDER(self));
g_return_if_fail(event != nullptr);
g_return_if_fail(callback != nullptr);
FL_KEY_RESPONDER_GET_IFACE(self)->handle_event(self, event, callback,
user_data);
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_RESPONDER_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_RESPONDER_H_
#include <gdk/gdk.h>
#include <cinttypes>
#include "flutter/shell/platform/linux/fl_key_event.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
G_BEGIN_DECLS
typedef struct _FlKeyboardManager FlKeyboardManager;
/**
* FlKeyResponderAsyncCallback:
* @event: whether the event has been handled.
* @user_data: the same value as user_data sent by
* #fl_key_responder_handle_event.
*
* The signature for a callback with which a #FlKeyResponder asynchronously
* reports whether the responder handles the event.
**/
typedef void (*FlKeyResponderAsyncCallback)(bool handled, gpointer user_data);
#define FL_TYPE_KEY_RESPONDER fl_key_responder_get_type()
G_DECLARE_INTERFACE(FlKeyResponder,
fl_key_responder,
FL,
KEY_RESPONDER,
GObject);
/**
* FlKeyResponder:
*
* An interface for a responder that can process a key event and decides
* asynchronously whether to handle an event.
*
* To use this class, add it with #fl_keyboard_manager_add_responder.
*/
struct _FlKeyResponderInterface {
GTypeInterface g_iface;
/**
* FlKeyResponder::handle_event:
*
* The implementation of #fl_key_responder_handle_event.
*/
void (*handle_event)(FlKeyResponder* responder,
FlKeyEvent* event,
FlKeyResponderAsyncCallback callback,
gpointer user_data);
};
/**
* fl_key_responder_handle_event:
* @responder: the #FlKeyResponder self.
* @event: the event to be handled. Must not be null. The object is managed
* by callee and must not be assumed available after this function.
* @callback: the callback to report the result. It should be called exactly
* once. Must not be null.
* @user_data: a value that will be sent back in the callback. Can be null.
*
* Let the responder handle an event, expecting the responder to report
* whether to handle the event. The result will be reported by invoking
* `callback` exactly once, which might happen after
* `fl_key_responder_handle_event` or during it.
*/
void fl_key_responder_handle_event(FlKeyResponder* responder,
FlKeyEvent* event,
FlKeyResponderAsyncCallback callback,
gpointer user_data);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_RESPONDER_H_
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
#include <cinttypes>
/* Declare and define FlKeyboardPendingEvent */
/**
* FlKeyboardPendingEvent:
* A record for events that have been received by the manager, but
* dispatched to other objects, whose results have yet to return.
*
* This object is used by both the "pending_responds" list and the
* "pending_redispatches" list.
*/
G_DECLARE_FINAL_TYPE(FlKeyboardPendingEvent,
fl_keyboard_pending_event,
FL,
KEYBOARD_PENDING_EVENT,
GObject);
struct _FlKeyboardPendingEvent {
GObject parent_instance;
// The target event.
//
// This is freed by #FlKeyboardPendingEvent.
FlKeyEvent* event;
// Self-incrementing ID attached to an event sent to the framework.
//
// Used to identify pending responds.
uint64_t sequence_id;
// The number of responders that haven't replied.
size_t unreplied;
// Whether any replied responders reported true (handled).
bool any_handled;
// A value calculated out of critical event information that can be used
// to identify redispatched events.
uint64_t hash;
};
G_DEFINE_TYPE(FlKeyboardPendingEvent, fl_keyboard_pending_event, G_TYPE_OBJECT)
static void fl_keyboard_pending_event_dispose(GObject* object) {
// Redundant, but added so that we don't get a warning about unused function
// for FL_IS_KEYBOARD_PENDING_EVENT.
g_return_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(object));
FlKeyboardPendingEvent* self = FL_KEYBOARD_PENDING_EVENT(object);
fl_key_event_dispose(self->event);
G_OBJECT_CLASS(fl_keyboard_pending_event_parent_class)->dispose(object);
}
static void fl_keyboard_pending_event_class_init(
FlKeyboardPendingEventClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_keyboard_pending_event_dispose;
}
static void fl_keyboard_pending_event_init(FlKeyboardPendingEvent* self) {}
// Calculates a unique ID for a given FlKeyEvent object to use for
// identification of responses from the framework.
static uint64_t fl_keyboard_manager_get_event_hash(FlKeyEvent* 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.
guint64 type =
static_cast<uint64_t>(event->is_press ? GDK_KEY_PRESS : GDK_KEY_RELEASE);
guint64 keycode = static_cast<uint64_t>(event->keycode);
return (event->time & 0xffffffff) | ((type & 0xffff) << 32) |
((keycode & 0xffff) << 48);
}
// Create a new FlKeyboardPendingEvent by providing the target event,
// the sequence ID, and the number of responders that will reply.
//
// This will acquire the ownership of the event.
FlKeyboardPendingEvent* fl_keyboard_pending_event_new(FlKeyEvent* event,
uint64_t sequence_id,
size_t to_reply) {
FlKeyboardPendingEvent* self = FL_KEYBOARD_PENDING_EVENT(
g_object_new(fl_keyboard_pending_event_get_type(), nullptr));
self->event = event;
self->sequence_id = sequence_id;
self->unreplied = to_reply;
self->any_handled = false;
self->hash = fl_keyboard_manager_get_event_hash(event);
return self;
}
/* Declare and define FlKeyboardManagerUserData */
/**
* FlKeyEmbedderUserData:
* The user_data used when #FlKeyboardManagerUserData sends event to
* responders.
*/
#define FL_TYPE_KEYBOARD_MANAGER_USER_DATA \
fl_keyboard_manager_user_data_get_type()
G_DECLARE_FINAL_TYPE(FlKeyboardManagerUserData,
fl_keyboard_manager_user_data,
FL,
KEYBOARD_MANAGER_USER_DATA,
GObject);
struct _FlKeyboardManagerUserData {
GObject parent_instance;
// A weak reference to the owner manager.
FlKeyboardManager* manager;
uint64_t sequence_id;
};
namespace {
// Context variables for the foreach call used to dispatch events to responders.
typedef struct {
FlKeyEvent* event;
FlKeyboardManagerUserData* user_data;
} DispatchToResponderLoopContext;
} // namespace
G_DEFINE_TYPE(FlKeyboardManagerUserData,
fl_keyboard_manager_user_data,
G_TYPE_OBJECT)
static void fl_keyboard_manager_user_data_dispose(GObject* object) {
g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(object));
FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA(object);
if (self->manager != nullptr) {
g_object_remove_weak_pointer(G_OBJECT(self->manager),
reinterpret_cast<gpointer*>(&(self->manager)));
self->manager = nullptr;
}
}
static void fl_keyboard_manager_user_data_class_init(
FlKeyboardManagerUserDataClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_user_data_dispose;
}
static void fl_keyboard_manager_user_data_init(
FlKeyboardManagerUserData* self) {}
// Creates a new FlKeyboardManagerUserData private class with all information.
FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new(
FlKeyboardManager* manager,
uint64_t sequence_id) {
FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA(
g_object_new(fl_keyboard_manager_user_data_get_type(), nullptr));
self->manager = manager;
// Add a weak pointer so we can know if the key event responder disappeared
// while the framework was responding.
g_object_add_weak_pointer(G_OBJECT(manager),
reinterpret_cast<gpointer*>(&(self->manager)));
self->sequence_id = sequence_id;
return self;
}
/* Define FlKeyboardManager */
struct _FlKeyboardManager {
GObject parent_instance;
// The callback that unhandled events should be redispatched through.
FlKeyboardManagerRedispatcher redispatch_callback;
// A text plugin.
//
// Released by the manager on dispose.
FlTextInputPlugin* text_input_plugin;
// An array of #FlKeyResponder. Elements are added with
// #fl_keyboard_manager_add_responder immediately after initialization and are
// automatically released on dispose.
GPtrArray* responder_list;
// An array of #FlKeyboardPendingEvent. FlKeyboardManager must manually
// release the elements unless it is transferring them to
// pending_redispatches.
GPtrArray* pending_responds;
// An array of #FlKeyboardPendingEvent. FlKeyboardManager must manually
// release the elements.
GPtrArray* pending_redispatches;
// The last sequence ID used. Increased by 1 by every use.
uint64_t last_sequence_id;
};
G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT);
static void fl_keyboard_manager_dispose(GObject* object);
static void fl_keyboard_manager_class_init(FlKeyboardManagerClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_dispose;
}
static void fl_keyboard_manager_init(FlKeyboardManager* self) {}
static void fl_key_event_destroy_notify(gpointer event);
static void fl_keyboard_manager_dispose(GObject* object) {
FlKeyboardManager* self = FL_KEYBOARD_MANAGER(object);
if (self->text_input_plugin != nullptr)
g_clear_object(&self->text_input_plugin);
g_ptr_array_free(self->responder_list, TRUE);
g_ptr_array_set_free_func(self->pending_responds,
fl_key_event_destroy_notify);
g_ptr_array_free(self->pending_responds, TRUE);
g_ptr_array_set_free_func(self->pending_redispatches,
fl_key_event_destroy_notify);
g_ptr_array_free(self->pending_redispatches, TRUE);
G_OBJECT_CLASS(fl_keyboard_manager_parent_class)->dispose(object);
}
/* Implement FlKeyboardManager */
// This is an exact copy of g_ptr_array_find_with_equal_func. Somehow CI
// reports that can not find symbol g_ptr_array_find_with_equal_func, despite
// the fact that it runs well locally.
gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack,
gconstpointer needle,
GEqualFunc equal_func,
guint* index_) {
guint i;
g_return_val_if_fail(haystack != NULL, FALSE);
if (equal_func == NULL)
equal_func = g_direct_equal;
for (i = 0; i < haystack->len; i++) {
if (equal_func(g_ptr_array_index(haystack, i), needle)) {
if (index_ != NULL) {
*index_ = i;
}
return TRUE;
}
}
return FALSE;
}
// Compare a #FlKeyboardPendingEvent with the given sequence_id. The needle
// should be a pointer to uint64_t sequence_id.
static gboolean compare_pending_by_sequence_id(
gconstpointer pending,
gconstpointer needle_sequence_id) {
uint64_t sequence_id = *reinterpret_cast<const uint64_t*>(needle_sequence_id);
return static_cast<const FlKeyboardPendingEvent*>(pending)->sequence_id ==
sequence_id;
}
// Compare a #FlKeyboardPendingEvent with the given hash. The #needle should be
// a pointer to uint64_t hash.
static gboolean compare_pending_by_hash(gconstpointer pending,
gconstpointer needle_hash) {
uint64_t hash = *reinterpret_cast<const uint64_t*>(needle_hash);
return static_cast<const FlKeyboardPendingEvent*>(pending)->hash == hash;
}
// Try to remove a pending event from `pending_redispatches` with the target
// hash.
//
// Returns true if the event is found and removed.
static bool fl_keyboard_manager_remove_redispatched(FlKeyboardManager* self,
uint64_t hash) {
guint result_index;
gboolean found = g_ptr_array_find_with_equal_func1(
self->pending_redispatches, static_cast<const uint64_t*>(&hash),
compare_pending_by_hash, &result_index);
if (found) {
FlKeyboardPendingEvent* removed =
FL_KEYBOARD_PENDING_EVENT(g_ptr_array_remove_index_fast(
self->pending_redispatches, result_index));
g_return_val_if_fail(removed != nullptr, TRUE);
g_object_unref(removed);
return TRUE;
} else {
return FALSE;
}
}
// The callback used by a responder after the event was dispatched.
static void responder_handle_event_callback(bool handled,
gpointer user_data_ptr) {
g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(user_data_ptr));
FlKeyboardManagerUserData* user_data =
FL_KEYBOARD_MANAGER_USER_DATA(user_data_ptr);
FlKeyboardManager* self = user_data->manager;
guint result_index = -1;
gboolean found = g_ptr_array_find_with_equal_func1(
self->pending_responds, &user_data->sequence_id,
compare_pending_by_sequence_id, &result_index);
g_return_if_fail(found);
FlKeyboardPendingEvent* pending = FL_KEYBOARD_PENDING_EVENT(
g_ptr_array_index(self->pending_responds, result_index));
g_return_if_fail(pending != nullptr);
g_return_if_fail(pending->unreplied > 0);
pending->unreplied -= 1;
pending->any_handled = pending->any_handled || handled;
// All responders have replied.
if (pending->unreplied == 0) {
g_object_unref(user_data_ptr);
gpointer removed =
g_ptr_array_remove_index_fast(self->pending_responds, result_index);
g_return_if_fail(removed == pending);
bool should_redispatch =
!pending->any_handled && (self->text_input_plugin == nullptr ||
!fl_text_input_plugin_filter_keypress(
self->text_input_plugin, pending->event));
if (should_redispatch) {
g_ptr_array_add(self->pending_redispatches, pending);
self->redispatch_callback(pending->event->origin);
} else {
g_object_unref(pending);
}
}
}
FlKeyboardManager* fl_keyboard_manager_new(
FlTextInputPlugin* text_input_plugin,
FlKeyboardManagerRedispatcher redispatch_callback) {
g_return_val_if_fail(text_input_plugin == nullptr ||
FL_IS_TEXT_INPUT_PLUGIN(text_input_plugin),
nullptr);
g_return_val_if_fail(redispatch_callback != nullptr, nullptr);
FlKeyboardManager* self = FL_KEYBOARD_MANAGER(
g_object_new(fl_keyboard_manager_get_type(), nullptr));
self->text_input_plugin = text_input_plugin;
self->redispatch_callback = redispatch_callback;
self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
self->pending_responds = g_ptr_array_new();
self->pending_redispatches = g_ptr_array_new();
self->last_sequence_id = 1;
return self;
}
void fl_keyboard_manager_add_responder(FlKeyboardManager* self,
FlKeyResponder* responder) {
g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self));
g_return_if_fail(responder != nullptr);
g_ptr_array_add(self->responder_list, responder);
}
// The loop body to dispatch an event to a responder.
static void dispatch_to_responder(gpointer responder_data,
gpointer foreach_data_ptr) {
DispatchToResponderLoopContext* context =
reinterpret_cast<DispatchToResponderLoopContext*>(foreach_data_ptr);
FlKeyResponder* responder = FL_KEY_RESPONDER(responder_data);
fl_key_responder_handle_event(responder, context->event,
responder_handle_event_callback,
context->user_data);
}
gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* self,
FlKeyEvent* event) {
g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), FALSE);
g_return_val_if_fail(event != nullptr, FALSE);
uint64_t incoming_hash = fl_keyboard_manager_get_event_hash(event);
if (fl_keyboard_manager_remove_redispatched(self, incoming_hash)) {
return FALSE;
}
FlKeyboardPendingEvent* pending = fl_keyboard_pending_event_new(
event, ++self->last_sequence_id, self->responder_list->len);
g_ptr_array_add(self->pending_responds, pending);
FlKeyboardManagerUserData* user_data =
fl_keyboard_manager_user_data_new(self, pending->sequence_id);
DispatchToResponderLoopContext data{
.event = event,
.user_data = user_data,
};
g_ptr_array_foreach(self->responder_list, dispatch_to_responder, &data);
return TRUE;
}
gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* self) {
g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), FALSE);
return self->pending_responds->len == 0 &&
self->pending_redispatches->len == 0;
}
static void fl_key_event_destroy_notify(gpointer event) {
fl_key_event_dispose(reinterpret_cast<FlKeyEvent*>(event));
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
#include <gdk/gdk.h>
#include "flutter/shell/platform/linux/fl_key_responder.h"
#include "flutter/shell/platform/linux/fl_text_input_plugin.h"
/**
* FlKeyboardManagerRedispatcher:
* @event: the pointer to the event to dispatch.
*
* The signature for a callback with which a #FlKeyboardManager redispatches
* key events that are not handled by anyone.
**/
typedef void (*FlKeyboardManagerRedispatcher)(gpointer event);
G_BEGIN_DECLS
#define FL_TYPE_KEYBOARD_MANAGER fl_keyboard_manager_get_type()
G_DECLARE_FINAL_TYPE(FlKeyboardManager,
fl_keyboard_manager,
FL,
KEYBOARD_MANAGER,
GObject);
/**
* FlKeyboardManager:
*
* A hub that manages how key events are dispatched to various processing
* objects of Flutter, or possibly back to the system.
*
* This class manage one or more objects of #FlKeyResponder, as well as a
* #TextInputPlugin.
*
* An event that is received by #fl_keyboard_manager_handle_event is first
* dispatched to *all* responders. Each responder responds *ascynchronously*
* with a boolean, indicating whether it handles the event.
*
* An event that is not handled by any responders is then passed to to the
* #TextInputPlugin, which responds *synchronously* with a boolean, indicating
* whether it handles the event.
*
* If no processing objects handle the event, the event is then "redispatched":
* sent back to the system using #redispatch_callback.
*
* Preventing responders from receiving events is not supported, because in
* reality this class will only support 2 hardcoded ones (channel and
* embedder), where the only purpose of supporting two is to support the legacy
* API (channel) during the deprecation window, after which the channel
* responder should be removed.
*/
/**
* fl_keyboard_manager_new:
* @text_input_plugin: the #FlTextInputPlugin to send key events to if the
* framework doesn't handle them. This object will be managed and freed by
* #FlKeyboardManager.
* @redispatch_callback: how the events should be sent if no processing
* objects handle the event. Typically a function that calls #gdk_event_put
* on #FlKeyEvent::origin.
*
* Create a new #FlKeyboardManager. The text input plugin must be specified
* now, while the responders should be added later with
* #fl_keyboard_manager_add_responder.
*
* Returns: a new #FlKeyboardManager.
*/
FlKeyboardManager* fl_keyboard_manager_new(
FlTextInputPlugin* text_input_plugin,
FlKeyboardManagerRedispatcher redispatch_callback);
/**
* fl_keyboard_manager_add_responder:
* @manager: the #FlKeyboardManager self.
* @responder: the new responder to be added.
*
* Add a new #FlKeyResponder to the #FlKeyboardManager. Responders added
* earlier will receive events earlier.
*/
void fl_keyboard_manager_add_responder(FlKeyboardManager* manager,
FlKeyResponder* responder);
/**
* fl_keyboard_manager_handle_event:
* @manager: the #FlKeyboardManager self.
* @event: the event to be dispatched. This event will be managed and
* released by #FlKeyboardManager.
*
* Add a new #FlKeyResponder to the #FlKeyboardManager. Responders added
* earlier will receive events earlier.
*/
gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* manager,
FlKeyEvent* event);
/**
* fl_keyboard_manager_is_state_clear:
* @manager: the #FlKeyboardManager self.
*
* Whether the manager's various states are cleared, i.e. no pending events
* for redispatching or for responding. This is mostly used in unittests.
*
* Returns: true if the manager's various states are cleared.
*/
gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* manager);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
此差异已折叠。
......@@ -31,6 +31,8 @@ struct _FlSettingsPlugin {
FlBasicMessageChannel* channel;
GSettings* interface_settings;
GArray* connections;
};
G_DEFINE_TYPE(FlSettingsPlugin, fl_settings_plugin, G_TYPE_OBJECT)
......@@ -112,6 +114,11 @@ static void update_settings(FlSettingsPlugin* self) {
static void fl_settings_plugin_dispose(GObject* object) {
FlSettingsPlugin* self = FL_SETTINGS_PLUGIN(object);
for (guint i = 0; i < self->connections->len; i += 1) {
g_signal_handler_disconnect(self->interface_settings,
g_array_index(self->connections, gulong, i));
}
g_array_unref(self->connections);
g_clear_object(&self->channel);
g_clear_object(&self->interface_settings);
......@@ -133,6 +140,7 @@ FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger) {
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
self->channel = fl_basic_message_channel_new(messenger, kChannelName,
FL_MESSAGE_CODEC(codec));
self->connections = g_array_new(FALSE, FALSE, sizeof(gulong));
return self;
}
......@@ -147,15 +155,19 @@ void fl_settings_plugin_start(FlSettingsPlugin* self) {
g_settings_schema_source_lookup(source, kDesktopInterfaceSchema, FALSE);
if (schema != nullptr) {
self->interface_settings = g_settings_new_full(schema, nullptr, nullptr);
g_signal_connect_object(
self->interface_settings, "changed::text-scaling-factor",
G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED);
g_signal_connect_object(self->interface_settings, "changed::clock-format",
G_CALLBACK(update_settings), self,
G_CONNECT_SWAPPED);
g_signal_connect_object(self->interface_settings, "changed::gtk-theme",
G_CALLBACK(update_settings), self,
G_CONNECT_SWAPPED);
gulong new_connections[] = {
g_signal_connect_object(
self->interface_settings, "changed::text-scaling-factor",
G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED),
g_signal_connect_object(
self->interface_settings, "changed::clock-format",
G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED),
g_signal_connect_object(
self->interface_settings, "changed::gtk-theme",
G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED),
};
g_array_append_vals(self->connections, new_connections,
sizeof(new_connections) / sizeof(gulong));
}
}
......
......@@ -61,6 +61,9 @@ struct FlTextInputPluginPrivate {
// Input method.
GtkIMContext* im_context;
// IM filter.
FlTextInputPluginImFilter im_filter;
flutter::TextInputModel* text_model;
// The owning Flutter view.
......@@ -488,7 +491,7 @@ static void fl_text_input_plugin_dispose(GObject* object) {
// Implements FlTextInputPlugin::filter_keypress.
static gboolean fl_text_input_plugin_filter_keypress_default(
FlTextInputPlugin* self,
GdkEventKey* event) {
FlKeyEvent* event) {
g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(self), false);
FlTextInputPluginPrivate* priv = static_cast<FlTextInputPluginPrivate*>(
......@@ -498,7 +501,7 @@ static gboolean fl_text_input_plugin_filter_keypress_default(
return FALSE;
}
if (gtk_im_context_filter_keypress(priv->im_context, event)) {
if (priv->im_filter(priv->im_context, event->origin)) {
return TRUE;
}
......@@ -506,7 +509,7 @@ static gboolean fl_text_input_plugin_filter_keypress_default(
gboolean do_action = FALSE;
// Handle navigation keys.
gboolean changed = FALSE;
if (event->type == GDK_KEY_PRESS) {
if (event->is_press) {
switch (event->keyval) {
case GDK_KEY_End:
case GDK_KEY_KP_End:
......@@ -590,9 +593,12 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
priv->text_model = new flutter::TextInputModel();
}
FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger,
FlView* view) {
FlTextInputPlugin* fl_text_input_plugin_new(
FlBinaryMessenger* messenger,
FlView* view,
FlTextInputPluginImFilter im_filter) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
g_return_val_if_fail(im_filter != nullptr, nullptr);
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN(
g_object_new(fl_text_input_plugin_get_type(), nullptr));
......@@ -605,6 +611,7 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger,
fl_method_channel_set_method_call_handler(priv->channel, method_call_cb, self,
nullptr);
priv->view = view;
priv->im_filter = im_filter;
return self;
}
......@@ -612,7 +619,7 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger,
// 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) {
FlKeyEvent* 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);
......
......@@ -7,9 +7,23 @@
#include <gdk/gdk.h>
#include "flutter/shell/platform/linux/fl_key_event.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
/**
* FlTextInputPluginImFilter:
* @event: the pointer to the GdkEventKey.
*
* The signature for a callback with which a #FlTextInputPlugin allow an input
* method to internally handle key press and release events.
*
* The #gdk_event is an opaque pointer. It will be GdkEvent* in actual
* applications, or a dummy pointer in unit tests.
**/
typedef gboolean (*FlTextInputPluginImFilter)(GtkIMContext* im_context,
gpointer gdk_event);
G_BEGIN_DECLS
G_DECLARE_DERIVABLE_TYPE(FlTextInputPlugin,
......@@ -31,33 +45,38 @@ struct _FlTextInputPluginClass {
/**
* Virtual method called to filter a keypress.
*/
gboolean (*filter_keypress)(FlTextInputPlugin* self, GdkEventKey* event);
gboolean (*filter_keypress)(FlTextInputPlugin* self, FlKeyEvent* event);
};
/**
* fl_text_input_plugin_new:
* @messenger: an #FlBinaryMessenger.
* @view: the #FlView with which the text input plugin is associated.
* @im_filter: a function used to allow an input method to internally handle
* key press and release events. Typically a wrap of
* #gtk_im_context_filter_keypress. Must not be nullptr.
*
* Creates a new plugin that implements SystemChannels.textInput from the
* Flutter services library.
*
* Returns: a new #FlTextInputPlugin.
*/
FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger,
FlView* view);
FlTextInputPlugin* fl_text_input_plugin_new(
FlBinaryMessenger* messenger,
FlView* view,
FlTextInputPluginImFilter im_filter);
/**
* fl_text_input_plugin_filter_keypress
* @plugin: an #FlTextInputPlugin.
* @event: a #GdkEventKey
* @event: a #FlKeyEvent
*
* Process a Gdk key event.
*
* Returns: %TRUE if the event was used.
*/
gboolean fl_text_input_plugin_filter_keypress(FlTextInputPlugin* plugin,
GdkEventKey* event);
FlKeyEvent* event);
G_END_DECLS
......
此差异已折叠。
此差异已折叠。
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef KEYBOARD_MAP_H_
#define KEYBOARD_MAP_H_
#include <gdk/gdk.h>
#include <cinttypes>
#include <map>
inline uint64_t gpointer_to_uint64(gpointer pointer) {
return pointer == nullptr ? 0 : reinterpret_cast<uint64_t>(pointer);
}
inline gpointer uint64_to_gpointer(uint64_t number) {
return reinterpret_cast<gpointer>(number);
}
// Maps XKB specific key code values to Flutter's physical key code values.
extern std::map<uint64_t, uint64_t> xkb_to_physical_key_map;
// Maps GDK keyval values to Flutter's logical key code values.
extern std::map<uint64_t, uint64_t> gtk_keyval_to_logical_key_map;
void initialize_modifier_bit_to_checked_keys(GHashTable* table);
void initialize_lock_bit_to_checked_keys(GHashTable* table);
#endif // KEYBOARD_MAP_H_
......@@ -226,6 +226,14 @@ FlutterEngineResult FlutterEngineSendPointerEvent(
return kSuccess;
}
FlutterEngineResult FlutterEngineSendKeyEvent(FLUTTER_API_SYMBOL(FlutterEngine)
engine,
const FlutterKeyEvent* event,
FlutterKeyEventCallback callback,
void* user_data) {
return kSuccess;
}
FLUTTER_EXPORT
FlutterEngineResult FlutterEngineSendPlatformMessage(
FLUTTER_API_SYMBOL(FlutterEngine) engine,
......@@ -481,6 +489,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
table->RunInitialized = &FlutterEngineRunInitialized;
table->SendWindowMetricsEvent = &FlutterEngineSendWindowMetricsEvent;
table->SendPointerEvent = &FlutterEngineSendPointerEvent;
table->SendKeyEvent = &FlutterEngineSendKeyEvent;
table->SendPlatformMessage = &FlutterEngineSendPlatformMessage;
table->PlatformMessageCreateResponseHandle =
&FlutterPlatformMessageCreateResponseHandle;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册