Make fl_engine_send_key_event into a standard async function. (flutter/engine#57112)

Add missing tests for this function.

Note this makes FlKeyboardManager a bit more complex, but this is
planned to be simplified in a future refactor.
This commit is contained in:
Robert Ancell 2024-12-13 09:21:00 +13:00 committed by GitHub
parent 600671f790
commit d9cdbeebea
4 changed files with 223 additions and 7 deletions

View File

@ -1095,17 +1095,51 @@ void fl_engine_send_pointer_pan_zoom_event(FlEngine* self,
self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1);
}
static void send_key_event_cb(bool handled, void* user_data) {
g_autoptr(GTask) task = G_TASK(user_data);
gboolean* return_value = g_new0(gboolean, 1);
*return_value = handled;
g_task_return_pointer(task, return_value, g_free);
}
void fl_engine_send_key_event(FlEngine* self,
const FlutterKeyEvent* event,
FlutterKeyEventCallback callback,
void* user_data) {
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
g_return_if_fail(FL_IS_ENGINE(self));
g_autoptr(GTask) task = g_task_new(self, cancellable, callback, user_data);
if (self->engine == nullptr) {
g_task_return_new_error(task, fl_engine_error_quark(),
FL_ENGINE_ERROR_FAILED, "No engine");
return;
}
self->embedder_api.SendKeyEvent(self->engine, event, callback, user_data);
if (self->embedder_api.SendKeyEvent(self->engine, event, send_key_event_cb,
g_object_ref(task)) != kSuccess) {
g_task_return_new_error(task, fl_engine_error_quark(),
FL_ENGINE_ERROR_FAILED, "Failed to send key event");
g_object_unref(task);
}
}
gboolean fl_engine_send_key_event_finish(FlEngine* self,
GAsyncResult* result,
gboolean* handled,
GError** error) {
g_return_val_if_fail(FL_IS_ENGINE(self), FALSE);
g_return_val_if_fail(g_task_is_valid(result, self), FALSE);
g_autofree gboolean* return_value =
static_cast<gboolean*>(g_task_propagate_pointer(G_TASK(result), error));
if (return_value == nullptr) {
return FALSE;
}
*handled = *return_value;
return TRUE;
}
void fl_engine_dispatch_semantics_action(FlEngine* self,

View File

@ -369,11 +369,37 @@ void fl_engine_send_pointer_pan_zoom_event(FlEngine* engine,
/**
* fl_engine_send_key_event:
* @engine: an #FlEngine.
* @event: key event to send.
* @cancellable: (allow-none): a #GCancellable or %NULL.
* @callback: (scope async): a #GAsyncReadyCallback to call when the request is
* satisfied.
* @user_data: (closure): user data to pass to @callback.
*
* Send a key event to the engine.
*/
void fl_engine_send_key_event(FlEngine* engine,
const FlutterKeyEvent* event,
FlutterKeyEventCallback callback,
void* user_data);
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
/**
* fl_engine_send_key_event_finish:
* @engine: an #FlEngine.
* @result: a #GAsyncResult.
* @handled: location to write if this event was handled by the engine.
* @error: (allow-none): #GError location to store the error occurring, or %NULL
* to ignore.
*
* Completes request started with fl_engine_send_key_event().
*
* Returns: %TRUE on success.
*/
gboolean fl_engine_send_key_event_finish(FlEngine* engine,
GAsyncResult* result,
gboolean* handled,
GError** error);
/**
* fl_engine_dispatch_semantics_action:

View File

@ -748,4 +748,132 @@ TEST(FlEngineTest, RemoveViewEngineError) {
g_main_loop_run(loop);
}
TEST(FlEngineTest, SendKeyEvent) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
bool called;
embedder_api->SendKeyEvent = MOCK_ENGINE_PROC(
SendKeyEvent,
([&called](auto engine, const FlutterKeyEvent* event,
FlutterKeyEventCallback callback, void* user_data) {
called = true;
EXPECT_EQ(event->timestamp, 1234);
EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
EXPECT_EQ(event->physical, static_cast<uint64_t>(42));
EXPECT_EQ(event->logical, static_cast<uint64_t>(123));
EXPECT_TRUE(event->synthesized);
EXPECT_EQ(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
callback(TRUE, user_data);
return kSuccess;
}));
FlutterKeyEvent event = {.struct_size = sizeof(FlutterKeyEvent),
.timestamp = 1234,
.type = kFlutterKeyEventTypeUp,
.physical = 42,
.logical = 123,
.character = nullptr,
.synthesized = true,
.device_type = kFlutterKeyEventDeviceTypeKeyboard};
fl_engine_send_key_event(
engine, &event, nullptr,
[](GObject* object, GAsyncResult* result, gpointer user_data) {
gboolean handled;
g_autoptr(GError) error = nullptr;
EXPECT_TRUE(fl_engine_send_key_event_finish(FL_ENGINE(object), result,
&handled, &error));
EXPECT_EQ(error, nullptr);
EXPECT_TRUE(handled);
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
},
loop);
g_main_loop_run(loop);
EXPECT_TRUE(called);
}
TEST(FlEngineTest, SendKeyEventNotHandled) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
bool called;
embedder_api->SendKeyEvent = MOCK_ENGINE_PROC(
SendKeyEvent,
([&called](auto engine, const FlutterKeyEvent* event,
FlutterKeyEventCallback callback, void* user_data) {
called = true;
callback(FALSE, user_data);
return kSuccess;
}));
FlutterKeyEvent event = {.struct_size = sizeof(FlutterKeyEvent),
.timestamp = 1234,
.type = kFlutterKeyEventTypeUp,
.physical = 42,
.logical = 123,
.character = nullptr,
.synthesized = true,
.device_type = kFlutterKeyEventDeviceTypeKeyboard};
fl_engine_send_key_event(
engine, &event, nullptr,
[](GObject* object, GAsyncResult* result, gpointer user_data) {
gboolean handled;
g_autoptr(GError) error = nullptr;
EXPECT_TRUE(fl_engine_send_key_event_finish(FL_ENGINE(object), result,
&handled, &error));
EXPECT_EQ(error, nullptr);
EXPECT_FALSE(handled);
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
},
loop);
g_main_loop_run(loop);
EXPECT_TRUE(called);
}
TEST(FlEngineTest, SendKeyEventError) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
bool called;
embedder_api->SendKeyEvent = MOCK_ENGINE_PROC(
SendKeyEvent,
([&called](auto engine, const FlutterKeyEvent* event,
FlutterKeyEventCallback callback, void* user_data) {
called = true;
return kInvalidArguments;
}));
FlutterKeyEvent event = {.struct_size = sizeof(FlutterKeyEvent),
.timestamp = 1234,
.type = kFlutterKeyEventTypeUp,
.physical = 42,
.logical = 123,
.character = nullptr,
.synthesized = true,
.device_type = kFlutterKeyEventDeviceTypeKeyboard};
fl_engine_send_key_event(
engine, &event, nullptr,
[](GObject* object, GAsyncResult* result, gpointer user_data) {
gboolean handled;
g_autoptr(GError) error = nullptr;
EXPECT_FALSE(fl_engine_send_key_event_finish(FL_ENGINE(object), result,
&handled, &error));
EXPECT_NE(error, nullptr);
EXPECT_STREQ(error->message, "Failed to send key event");
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
},
loop);
g_main_loop_run(loop);
EXPECT_TRUE(called);
}
// NOLINTEND(clang-analyzer-core.StackAddressEscape)

View File

@ -9,6 +9,7 @@
#include <memory>
#include <string>
#include "flutter/shell/platform/linux/fl_engine_private.h"
#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/fl_keyboard_layout.h"
@ -479,8 +480,35 @@ FlKeyboardManager* fl_keyboard_manager_new(
} else {
g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine));
if (engine != nullptr) {
fl_engine_send_key_event(engine, event, callback,
callback_user_data);
typedef struct {
FlutterKeyEventCallback callback;
void* callback_user_data;
} SendKeyEventData;
SendKeyEventData* data = g_new0(SendKeyEventData, 1);
data->callback = callback;
data->callback_user_data = callback_user_data;
fl_engine_send_key_event(
engine, event, self->cancellable,
[](GObject* object, GAsyncResult* result, gpointer user_data) {
g_autofree SendKeyEventData* data =
static_cast<SendKeyEventData*>(user_data);
gboolean handled = FALSE;
g_autoptr(GError) error = nullptr;
if (!fl_engine_send_key_event_finish(
FL_ENGINE(object), result, &handled, &error)) {
if (g_error_matches(error, G_IO_ERROR,
G_IO_ERROR_CANCELLED)) {
return;
}
g_warning("Failed to send key event: %s", error->message);
}
if (data->callback != nullptr) {
data->callback(handled, data->callback_user_data);
}
},
data);
}
}
},