Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make fl_engine_send_key_event into a standard async function. #57112

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
40 changes: 37 additions & 3 deletions shell/platform/linux/fl_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -965,17 +965,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,
Expand Down
30 changes: 28 additions & 2 deletions shell/platform/linux/fl_engine_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,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:
Expand Down
128 changes: 128 additions & 0 deletions shell/platform/linux/fl_engine_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
32 changes: 30 additions & 2 deletions shell/platform/linux/fl_keyboard_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense for fl_engine_send_key_event to call fl_engine_send_key_event_finish automatically for us before calling the user provided callback? If it always has to be called after fl_engine_send_key_event gets called, it would make sense to encapsulate it at that level instead of putting the onus on the caller.

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);
}
}
},
Expand Down
Loading