blob: b00f1f7ae861d2e7fcd4ad624953cd5e9f30b222 [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_channel_responder.h"
#include <gtk/gtk.h>
#include <cinttypes>
#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 kSpecifiedLogicalKey[] = "specifiedLogicalKey";
static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues";
static constexpr char kGtkToolkit[] = "gtk";
static constexpr char kLinuxKeymap[] = "linux";
/* Declare and define FlKeyChannelUserData */
/**
* FlKeyChannelUserData:
* The user_data used when #FlKeyChannelResponder sends message through the
* channel.
*/
G_DECLARE_FINAL_TYPE(FlKeyChannelUserData,
fl_key_channel_user_data,
FL,
KEY_CHANNEL_USER_DATA,
GObject);
struct _FlKeyChannelUserData {
GObject parent_instance;
// The current responder.
FlKeyChannelResponder* responder;
// The callback provided by the caller #FlKeyboardManager.
FlKeyResponderAsyncCallback callback;
// The user_data provided by the caller #FlKeyboardManager.
gpointer user_data;
};
// Definition for FlKeyChannelUserData private class.
G_DEFINE_TYPE(FlKeyChannelUserData, fl_key_channel_user_data, G_TYPE_OBJECT)
// Dispose method for FlKeyChannelUserData private class.
static void fl_key_channel_user_data_dispose(GObject* object) {
g_return_if_fail(FL_IS_KEY_CHANNEL_USER_DATA(object));
FlKeyChannelUserData* self = FL_KEY_CHANNEL_USER_DATA(object);
if (self->responder != nullptr) {
g_object_remove_weak_pointer(
G_OBJECT(self->responder),
reinterpret_cast<gpointer*>(&(self->responder)));
self->responder = nullptr;
}
}
// Class initialization method for FlKeyChannelUserData private class.
static void fl_key_channel_user_data_class_init(
FlKeyChannelUserDataClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_key_channel_user_data_dispose;
}
// Instance initialization method for FlKeyChannelUserData private class.
static void fl_key_channel_user_data_init(FlKeyChannelUserData* self) {}
// Creates a new FlKeyChannelUserData private class with all information.
//
// The callback and the user_data might be nullptr.
static FlKeyChannelUserData* fl_key_channel_user_data_new(
FlKeyChannelResponder* responder,
FlKeyResponderAsyncCallback callback,
gpointer user_data) {
FlKeyChannelUserData* self = FL_KEY_CHANNEL_USER_DATA(
g_object_new(fl_key_channel_user_data_get_type(), nullptr));
self->responder = responder;
// Add a weak pointer so we can know if the key event responder disappeared
// while the framework was responding.
g_object_add_weak_pointer(G_OBJECT(responder),
reinterpret_cast<gpointer*>(&(self->responder)));
self->callback = callback;
self->user_data = user_data;
return self;
}
/* Define FlKeyChannelResponder */
// Definition of the FlKeyChannelResponder GObject class.
struct _FlKeyChannelResponder {
GObject parent_instance;
FlBasicMessageChannel* channel;
FlKeyChannelResponderMock* mock;
};
static void fl_key_channel_responder_iface_init(FlKeyResponderInterface* iface);
G_DEFINE_TYPE_WITH_CODE(
FlKeyChannelResponder,
fl_key_channel_responder,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(FL_TYPE_KEY_RESPONDER,
fl_key_channel_responder_iface_init))
static void fl_key_channel_responder_handle_event(
FlKeyResponder* responder,
FlKeyEvent* event,
uint64_t specified_logical_key,
FlKeyResponderAsyncCallback callback,
gpointer user_data);
static void fl_key_channel_responder_iface_init(
FlKeyResponderInterface* iface) {
iface->handle_event = fl_key_channel_responder_handle_event;
}
/* Implement FlKeyChannelResponder */
// Handles a response from the method channel to a key event sent to the
// framework earlier.
static void handle_response(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(FlKeyChannelUserData) data = FL_KEY_CHANNEL_USER_DATA(user_data);
// This is true if the weak pointer has been destroyed.
if (data->responder == nullptr) {
return;
}
FlKeyChannelResponder* self = data->responder;
g_autoptr(GError) error = nullptr;
FlBasicMessageChannel* messageChannel = FL_BASIC_MESSAGE_CHANNEL(object);
FlValue* message =
fl_basic_message_channel_send_finish(messageChannel, result, &error);
if (self->mock != nullptr && self->mock->value_converter != nullptr) {
message = self->mock->value_converter(message);
}
bool handled = false;
if (error != nullptr) {
g_warning("Unable to retrieve framework response: %s", error->message);
} else {
g_autoptr(FlValue) handled_value =
fl_value_lookup_string(message, "handled");
handled = fl_value_get_bool(handled_value);
}
data->callback(handled, data->user_data);
}
// Disposes of an FlKeyChannelResponder instance.
static void fl_key_channel_responder_dispose(GObject* object) {
FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_key_channel_responder_parent_class)->dispose(object);
}
// Initializes the FlKeyChannelResponder class methods.
static void fl_key_channel_responder_class_init(
FlKeyChannelResponderClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_key_channel_responder_dispose;
}
// Initializes an FlKeyChannelResponder instance.
static void fl_key_channel_responder_init(FlKeyChannelResponder* self) {}
// Creates a new FlKeyChannelResponder instance, with a messenger used to send
// messages to the framework, and an FlTextInputPlugin that is 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.
FlKeyChannelResponder* fl_key_channel_responder_new(
FlBinaryMessenger* messenger,
FlKeyChannelResponderMock* mock) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(
g_object_new(fl_key_channel_responder_get_type(), nullptr));
self->mock = mock;
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
const char* channel_name =
mock == nullptr ? kChannelName : mock->channel_name;
self->channel = fl_basic_message_channel_new(messenger, channel_name,
FL_MESSAGE_CODEC(codec));
return self;
}
// Sends a key event to the framework.
static void fl_key_channel_responder_handle_event(
FlKeyResponder* responder,
FlKeyEvent* event,
uint64_t specified_logical_key,
FlKeyResponderAsyncCallback callback,
gpointer user_data) {
FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(responder);
g_return_if_fail(event != nullptr);
g_return_if_fail(callback != nullptr);
const gchar* type = event->is_press ? kTypeValueDown : kTypeValueUp;
int64_t scan_code = event->keycode;
int64_t unicode_scarlar_values = 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).
// 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->is_press;
break;
case GDK_KEY_Caps_Lock:
caps_lock_pressed = event->is_press;
break;
case GDK_KEY_Shift_Lock:
shift_lock_pressed = event->is_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 (unicode_scarlar_values != 0) {
fl_value_set_string_take(message, kUnicodeScalarValuesKey,
fl_value_new_int(unicode_scarlar_values));
}
if (specified_logical_key != 0) {
fl_value_set_string_take(message, kSpecifiedLogicalKey,
fl_value_new_int(specified_logical_key));
}
FlKeyChannelUserData* data =
fl_key_channel_user_data_new(self, callback, 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);
}