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:
parent
600671f790
commit
d9cdbeebea
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user