blob: fe5628b55f0076dcfd4009aa2ad578c2c19f84c1 [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/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";
struct _FlKeyEventPlugin {
GObject parent_instance;
FlBasicMessageChannel* channel = nullptr;
GAsyncReadyCallback response_callback = nullptr;
};
G_DEFINE_TYPE(FlKeyEventPlugin, fl_key_event_plugin, G_TYPE_OBJECT)
static void fl_key_event_plugin_dispose(GObject* object) {
FlKeyEventPlugin* self = FL_KEY_EVENT_PLUGIN(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_key_event_plugin_parent_class)->dispose(object);
}
static void fl_key_event_plugin_class_init(FlKeyEventPluginClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_key_event_plugin_dispose;
}
static void fl_key_event_plugin_init(FlKeyEventPlugin* self) {}
FlKeyEventPlugin* fl_key_event_plugin_new(FlBinaryMessenger* messenger,
GAsyncReadyCallback response_callback,
const char* channel_name) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), 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;
return self;
}
void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self,
GdkEventKey* event,
gpointer user_data) {
g_return_if_fail(FL_IS_KEY_EVENT_PLUGIN(self));
g_return_if_fail(event != nullptr);
const gchar* type;
switch (event->type) {
case GDK_KEY_PRESS:
type = kTypeValueDown;
break;
case GDK_KEY_RELEASE:
type = kTypeValueUp;
break;
default:
return;
}
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));
}
fl_basic_message_channel_send(self->channel, message, nullptr,
self->response_callback, user_data);
}