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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions shell/platform/linux/fl_key_embedder_responder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -885,3 +885,8 @@ void fl_key_embedder_responder_sync_modifiers_if_needed(
synchronize_pressed_states_loop_body,
&sync_state_context);
}

GHashTable* fl_key_embedder_responder_get_pressed_state(
FlKeyEmbedderResponder* self) {
return self->pressing_records;
}
10 changes: 10 additions & 0 deletions shell/platform/linux/fl_key_embedder_responder.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ void fl_key_embedder_responder_sync_modifiers_if_needed(
guint state,
double event_time);

/**
* fl_key_embedder_responder_get_pressed_state:
* @responder: the #FlKeyEmbedderResponder self.
*
* Returns the keyboard pressed state. The hash table contains one entry per
* pressed keys, mapping from the logical key to the physical key.
*/
GHashTable* fl_key_embedder_responder_get_pressed_state(
FlKeyEmbedderResponder* responder);

G_END_DECLS

#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EMBEDDER_RESPONDER_H_
70 changes: 70 additions & 0 deletions shell/platform/linux/fl_keyboard_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
#include "flutter/shell/platform/linux/fl_key_channel_responder.h"
#include "flutter/shell/platform/linux/fl_key_embedder_responder.h"
#include "flutter/shell/platform/linux/key_mapping.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"

// Turn on this flag to print complete layout data when switching IMEs. The data
// is used in unit tests.
#define DEBUG_PRINT_LAYOUT

static constexpr char kChannelName[] = "flutter/keyboard";
static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";

/* Declarations of private classes */

G_DECLARE_FINAL_TYPE(FlKeyboardPendingEvent,
Expand Down Expand Up @@ -287,6 +292,9 @@ struct _FlKeyboardManager {
// It is set up when the manager is initialized and is not changed ever after.
std::unique_ptr<std::map<uint64_t, const LayoutGoal*>>
logical_to_mandatory_goals;

// The channel used by the framework to query the keyboard pressed state.
FlMethodChannel* channel;
};

G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT);
Expand Down Expand Up @@ -532,7 +540,50 @@ static void guarantee_layout(FlKeyboardManager* self, FlKeyEvent* event) {
}
}

// Returns the keyboard pressed state.
FlMethodResponse* get_keyboard_state(FlKeyboardManager* self) {
g_autoptr(FlValue) result = fl_value_new_map();

GHashTable* pressing_records =
fl_keyboard_view_delegate_get_keyboard_state(self->view_delegate);

g_hash_table_foreach(
pressing_records,
[](gpointer key, gpointer value, gpointer user_data) {
int64_t physical_key = reinterpret_cast<int64_t>(key);
int64_t logical_key = reinterpret_cast<int64_t>(value);
FlValue* fl_value_map = reinterpret_cast<FlValue*>(user_data);

fl_value_set_take(fl_value_map, fl_value_new_int(physical_key),
fl_value_new_int(logical_key));
},
result);
return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}

// Called when a method call on flutter/keyboard is received from Flutter.
static void method_call_handler(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlKeyboardManager* self = FL_KEYBOARD_MANAGER(user_data);

const gchar* method = fl_method_call_get_name(method_call);

g_autoptr(FlMethodResponse) response = nullptr;
if (strcmp(method, kGetKeyboardStateMethod) == 0) {
response = get_keyboard_state(self);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}

g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send method call response: %s", error->message);
}
}

FlKeyboardManager* fl_keyboard_manager_new(
FlBinaryMessenger* messenger,
FlKeyboardViewDelegate* view_delegate) {
g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr);

Expand Down Expand Up @@ -560,6 +611,13 @@ FlKeyboardManager* fl_keyboard_manager_new(

fl_keyboard_view_delegate_subscribe_to_layout_change(
self->view_delegate, [self]() { self->derived_layout->clear(); });

// Setup the flutter/keyboard channel.
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->channel =
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(self->channel, method_call_handler,
self, nullptr);
return self;
}

Expand Down Expand Up @@ -614,10 +672,22 @@ gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* self) {
void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* self,
guint state,
double event_time) {
g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self));

// The embedder responder is the first element in
// FlKeyboardManager.responder_list.
FlKeyEmbedderResponder* responder =
FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
fl_key_embedder_responder_sync_modifiers_if_needed(responder, state,
event_time);
}

GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* self) {
g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), nullptr);

// The embedder responder is the first element in
// FlKeyboardManager.responder_list.
FlKeyEmbedderResponder* responder =
FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
return fl_key_embedder_responder_get_pressed_state(responder);
}
10 changes: 10 additions & 0 deletions shell/platform/linux/fl_keyboard_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ G_DECLARE_FINAL_TYPE(FlKeyboardManager,
* Returns: a new #FlKeyboardManager.
*/
FlKeyboardManager* fl_keyboard_manager_new(
FlBinaryMessenger* messenger,
FlKeyboardViewDelegate* view_delegate);

/**
Expand Down Expand Up @@ -83,6 +84,15 @@ void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* manager,
guint state,
double event_time);

/**
* fl_keyboard_manager_get_pressed_state:
* @manager: the #FlKeyboardManager self.
*
* Returns the keyboard pressed state. The hash table contains one entry per
* pressed keys, mapping from the logical key to the physical key.*
*/
GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* manager);

G_END_DECLS

#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
83 changes: 82 additions & 1 deletion shell/platform/linux/fl_keyboard_manager_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@
#include <vector>

#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
#include "flutter/shell/platform/linux/fl_method_codec_private.h"
#include "flutter/shell/platform/linux/key_mapping.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
#include "flutter/shell/platform/linux/testing/fl_test.h"
#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h"
#include "flutter/shell/platform/linux/testing/mock_text_input_plugin.h"
#include "flutter/testing/testing.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

// Define compound `expect` in macros. If they were defined in functions, the
Expand Down Expand Up @@ -100,6 +110,10 @@ constexpr guint16 kKeyCodeSemicolon = 0x2fu;
constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u;

static constexpr char kKeyEventChannelName[] = "flutter/keyevent";
static constexpr char kKeyboardChannelName[] = "flutter/keyboard";
static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
static constexpr uint64_t kMockPhysicalKey = 42;
static constexpr uint64_t kMockLogicalKey = 42;

// All key clues for a keyboard layout.
//
Expand Down Expand Up @@ -129,6 +143,19 @@ G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger,

G_END_DECLS

MATCHER_P(MethodSuccessResponse, result, "") {
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
g_autoptr(FlMethodResponse) response =
fl_method_codec_decode_response(FL_METHOD_CODEC(codec), arg, nullptr);
fl_method_response_get_result(response, nullptr);
if (fl_value_equal(fl_method_response_get_result(response, nullptr),
result)) {
return true;
}
*result_listener << ::testing::PrintToString(response);
return false;
}

/***** FlMockKeyBinaryMessenger *****/
/* Mock a binary messenger that only processes messages from the embedding on
* the key event channel, and does so according to the callback set by
Expand Down Expand Up @@ -322,6 +349,15 @@ static guint fl_mock_view_keyboard_lookup_key(FlKeyboardViewDelegate* delegate,
return (*group_layout)[key->keycode * 2 + shift];
}

static GHashTable* fl_mock_view_keyboard_get_keyboard_state(
FlKeyboardViewDelegate* view_delegate) {
GHashTable* result = g_hash_table_new(g_direct_hash, g_direct_equal);
g_hash_table_insert(result, reinterpret_cast<gpointer>(kMockPhysicalKey),
reinterpret_cast<gpointer>(kMockLogicalKey));

return result;
}

static void fl_mock_view_keyboard_delegate_iface_init(
FlKeyboardViewDelegateInterface* iface) {
iface->send_key_event = fl_mock_view_keyboard_send_key_event;
Expand All @@ -331,6 +367,7 @@ static void fl_mock_view_keyboard_delegate_iface_init(
iface->subscribe_to_layout_change =
fl_mock_view_keyboard_subscribe_to_layout_change;
iface->lookup_key = fl_mock_view_keyboard_lookup_key;
iface->get_keyboard_state = fl_mock_view_keyboard_get_keyboard_state;
}

static FlMockViewDelegate* fl_mock_view_delegate_new() {
Expand Down Expand Up @@ -406,13 +443,16 @@ static FlKeyEvent* fl_key_event_new_by_mock(bool is_press,
class KeyboardTester {
public:
KeyboardTester() {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;

view_ = fl_mock_view_delegate_new();
respondToEmbedderCallsWith(false);
respondToChannelCallsWith(false);
respondToTextInputWith(false);
setLayout(kLayoutUs);

manager_ = fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(view_));
manager_ =
fl_keyboard_manager_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(view_));
}

~KeyboardTester() {
Expand Down Expand Up @@ -926,6 +966,47 @@ TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) {
kLogicalShiftLeft);
}

TEST(FlKeyboardManagerTest, GetPressedState) {
KeyboardTester tester;
tester.respondToTextInputWith(true);

// Dispatch a key event.
fl_keyboard_manager_handle_event(
tester.manager(),
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0, false));

GHashTable* pressedState =
fl_keyboard_manager_get_pressed_state(tester.manager());
EXPECT_EQ(g_hash_table_size(pressedState), 1u);

gpointer physical_key =
g_hash_table_lookup(pressedState, uint64_to_gpointer(kPhysicalKeyA));
EXPECT_EQ(gpointer_to_uint64(physical_key), kLogicalKeyA);
}

TEST(FlKeyboardPluginTest, KeyboardChannelGetPressedState) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;

g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(
messenger, FL_KEYBOARD_VIEW_DELEGATE(fl_mock_view_delegate_new()));
EXPECT_NE(manager, nullptr);

g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), kGetKeyboardStateMethod, nullptr, nullptr);

g_autoptr(FlValue) response = fl_value_new_map();
fl_value_set_take(response, fl_value_new_int(kMockPhysicalKey),
fl_value_new_int(kMockLogicalKey));
EXPECT_CALL(messenger,
fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger), ::testing::_,
MethodSuccessResponse(response), ::testing::_))
.WillOnce(::testing::Return(true));

messenger.ReceiveMessage(kKeyboardChannelName, message);
}

// The following layout data is generated using DEBUG_PRINT_LAYOUT.

const MockGroupLayoutData kLayoutUs0{{
Expand Down
7 changes: 7 additions & 0 deletions shell/platform/linux/fl_keyboard_view_delegate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,10 @@ guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* self,

return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->lookup_key(self, key);
}

GHashTable* fl_keyboard_view_delegate_get_keyboard_state(
FlKeyboardViewDelegate* self) {
g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self), nullptr);

return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->get_keyboard_state(self);
}
12 changes: 12 additions & 0 deletions shell/platform/linux/fl_keyboard_view_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ struct _FlKeyboardViewDelegateInterface {

guint (*lookup_key)(FlKeyboardViewDelegate* view_delegate,
const GdkKeymapKey* key);

GHashTable* (*get_keyboard_state)(FlKeyboardViewDelegate* delegate);
};

/**
Expand Down Expand Up @@ -116,6 +118,16 @@ void fl_keyboard_view_delegate_subscribe_to_layout_change(
guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* delegate,
const GdkKeymapKey* key);

/**
* fl_keyboard_view_delegate_get_keyboard_state:
*
* Returns the keyboard pressed state. The hash table contains one entry per
* pressed keys, mapping from the logical key to the physical key.*
*
*/
GHashTable* fl_keyboard_view_delegate_get_keyboard_state(
FlKeyboardViewDelegate* delegate);

G_END_DECLS

#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_VIEW_DELEGATE_H_
14 changes: 13 additions & 1 deletion shell/platform/linux/fl_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ static void init_keyboard(FlView* self) {
self->text_input_plugin = fl_text_input_plugin_new(
messenger, im_context, FL_TEXT_INPUT_VIEW_DELEGATE(self));
self->keyboard_manager =
fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(self));
fl_keyboard_manager_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(self));
}

static void init_scrolling(FlView* self) {
Expand Down Expand Up @@ -297,6 +297,12 @@ static void fl_view_keyboard_delegate_iface_init(
g_return_val_if_fail(self->keymap != nullptr, 0);
return gdk_keymap_lookup_key(self->keymap, key);
};

iface->get_keyboard_state =
[](FlKeyboardViewDelegate* view_delegate) -> GHashTable* {
FlView* self = FL_VIEW(view_delegate);
return fl_view_get_keyboard_state(self);
};
}

static void fl_view_scrolling_delegate_iface_init(
Expand Down Expand Up @@ -709,3 +715,9 @@ void fl_view_set_textures(FlView* self,

fl_gl_area_queue_render(self->gl_area, textures);
}

GHashTable* fl_view_get_keyboard_state(FlView* self) {
g_return_val_if_fail(FL_IS_VIEW(self), nullptr);

return fl_keyboard_manager_get_pressed_state(self->keyboard_manager);
}
9 changes: 9 additions & 0 deletions shell/platform/linux/fl_view_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,13 @@ void fl_view_set_textures(FlView* view,
GdkGLContext* context,
GPtrArray* textures);

/**
* fl_view_get_keyboard_state:
* @view: an #FlView.
*
* Returns the keyboard pressed state. The hash table contains one entry per
* pressed keys, mapping from the logical key to the physical key.*
*/
GHashTable* fl_view_get_keyboard_state(FlView* view);

#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_