blob: faf43fdfb65e9ec59ff67c6b5c42519d9b2f2a88 [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/fl_key_event_channel.h"
struct _FlKeyChannelResponder {
GObject parent_instance;
FlKeyEventChannel* channel;
};
G_DEFINE_TYPE(FlKeyChannelResponder, fl_key_channel_responder, G_TYPE_OBJECT)
// 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(GTask) task = G_TASK(user_data);
gboolean handled = FALSE;
g_autoptr(GError) error = nullptr;
if (!fl_key_event_channel_send_finish(object, result, &handled, &error)) {
g_warning("Unable to retrieve framework response: %s", error->message);
}
gboolean* return_value = g_new0(gboolean, 1);
*return_value = handled;
g_task_return_pointer(task, return_value, g_free);
}
// 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 FlTextInputHandler that is used to handle
// key events that the framework doesn't handle.
FlKeyChannelResponder* fl_key_channel_responder_new(
FlBinaryMessenger* messenger) {
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->channel = fl_key_event_channel_new(messenger);
return self;
}
void fl_key_channel_responder_handle_event(FlKeyChannelResponder* self,
FlKeyEvent* event,
uint64_t specified_logical_key,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
g_return_if_fail(event != nullptr);
g_return_if_fail(callback != nullptr);
FlKeyEventType type = fl_key_event_get_is_press(event)
? FL_KEY_EVENT_TYPE_KEYDOWN
: FL_KEY_EVENT_TYPE_KEYUP;
int64_t scan_code = fl_key_event_get_keycode(event);
int64_t unicode_scalar_values =
gdk_keyval_to_unicode(fl_key_event_get_keyval(event));
// 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 =
fl_key_event_get_state(event) & ~(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 (fl_key_event_get_keyval(event)) {
case GDK_KEY_Num_Lock:
num_lock_pressed = fl_key_event_get_is_press(event);
break;
case GDK_KEY_Caps_Lock:
caps_lock_pressed = fl_key_event_get_is_press(event);
break;
case GDK_KEY_Shift_Lock:
shift_lock_pressed = fl_key_event_get_is_press(event);
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;
fl_key_event_channel_send(
self->channel, type, scan_code, fl_key_event_get_keyval(event), state,
unicode_scalar_values, specified_logical_key, nullptr, handle_response,
g_task_new(self, cancellable, callback, user_data));
}
gboolean fl_key_channel_responder_handle_event_finish(
FlKeyChannelResponder* self,
GAsyncResult* result,
gboolean* handled,
GError** error) {
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;
}