blob: 1cabc6a1658e4e0ad172d29402dddfc2119153c2 [file] [log] [blame]
// 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;
}