diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ef5efd64f1df3..4daa91924bed2 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1202,6 +1202,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_json_method_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_json_method_codec_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_key_event_plugin.cc +FILE: ../../../flutter/shell/platform/linux/fl_key_event_plugin.h FILE: ../../../flutter/shell/platform/linux/fl_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_message_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_method_call.cc diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index d32c572f75536..478288597eb60 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -79,6 +79,7 @@ source_set("flutter_linux") { "fl_engine.cc", "fl_json_message_codec.cc", "fl_json_method_codec.cc", + "fl_key_event_plugin.cc", "fl_message_codec.cc", "fl_method_call.cc", "fl_method_channel.cc", diff --git a/shell/platform/linux/fl_key_event_plugin.cc b/shell/platform/linux/fl_key_event_plugin.cc new file mode 100644 index 0000000000000..2a436c3340486 --- /dev/null +++ b/shell/platform/linux/fl_key_event_plugin.cc @@ -0,0 +1,255 @@ +// 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 kGLFWToolkit[] = "glfw"; +static constexpr char kLinuxKeymap[] = "linux"; + +struct _FlKeyEventPlugin { + GObject parent_instance; + + FlBasicMessageChannel* channel; +}; + +G_DEFINE_TYPE(FlKeyEventPlugin, fl_key_event_plugin, G_TYPE_OBJECT) + +// Converts a Gdk key code to its GLFW equivalent. +// TODO(robert-ancell) Create a "gtk" toolkit in Flutter so we don't have to +// convert values. https://github.com/flutter/flutter/issues/57603 +static int gdk_keyval_to_glfw_key_code(guint keyval) { + switch (keyval) { + case GDK_KEY_space: + return 32; + case GDK_KEY_apostrophe: + return 39; + case GDK_KEY_comma: + return 44; + case GDK_KEY_minus: + return 45; + case GDK_KEY_period: + return 46; + case GDK_KEY_slash: + return 47; + case GDK_KEY_0: + return 48; + case GDK_KEY_1: + return 49; + case GDK_KEY_2: + return 50; + case GDK_KEY_3: + return 51; + case GDK_KEY_4: + return 52; + case GDK_KEY_5: + return 53; + case GDK_KEY_6: + return 54; + case GDK_KEY_7: + return 55; + case GDK_KEY_8: + return 56; + case GDK_KEY_9: + return 57; + case GDK_KEY_semicolon: + return 59; + case GDK_KEY_equal: + return 61; + case GDK_KEY_a: + return 65; + case GDK_KEY_b: + return 66; + case GDK_KEY_c: + return 67; + case GDK_KEY_d: + return 68; + case GDK_KEY_e: + return 69; + case GDK_KEY_f: + return 70; + case GDK_KEY_g: + return 71; + case GDK_KEY_h: + return 72; + case GDK_KEY_i: + return 73; + case GDK_KEY_j: + return 74; + case GDK_KEY_k: + return 75; + case GDK_KEY_l: + return 76; + case GDK_KEY_m: + return 77; + case GDK_KEY_n: + return 78; + case GDK_KEY_o: + return 79; + case GDK_KEY_p: + return 80; + case GDK_KEY_q: + return 81; + case GDK_KEY_r: + return 82; + case GDK_KEY_s: + return 83; + case GDK_KEY_t: + return 84; + case GDK_KEY_u: + return 85; + case GDK_KEY_v: + return 86; + case GDK_KEY_w: + return 87; + case GDK_KEY_x: + return 88; + case GDK_KEY_y: + return 89; + case GDK_KEY_z: + return 90; + case GDK_KEY_bracketleft: + return 91; + case GDK_KEY_bracketright: + return 92; + case GDK_KEY_grave: + return 96; + case GDK_KEY_Escape: + return 256; + case GDK_KEY_Return: + return 257; + case GDK_KEY_Tab: + return 258; + case GDK_KEY_BackSpace: + return 259; + case GDK_KEY_Insert: + return 260; + case GDK_KEY_Delete: + return 261; + case GDK_KEY_Right: + return 262; + case GDK_KEY_Left: + return 263; + case GDK_KEY_Down: + return 264; + case GDK_KEY_Up: + return 265; + case GDK_KEY_Page_Up: + return 266; + case GDK_KEY_Page_Down: + return 267; + case GDK_KEY_Home: + return 268; + case GDK_KEY_End: + return 269; + case GDK_KEY_Shift_L: + return 340; + case GDK_KEY_Control_L: + return 341; + case GDK_KEY_Alt_L: + return 342; + case GDK_KEY_Super_L: + return 343; + case GDK_KEY_Shift_R: + return 344; + case GDK_KEY_Control_R: + return 345; + case GDK_KEY_Alt_R: + return 346; + case GDK_KEY_Super_R: + return 347; + default: + return 0; + } +} + +// Converts a Gdk key state to its GLFW equivalent. +int64_t gdk_state_to_glfw_modifiers(guint8 state) { + int64_t modifiers = 0; + + if ((state & GDK_SHIFT_MASK) != 0) + modifiers |= 0x0001; + if ((state & GDK_CONTROL_MASK) != 0) + modifiers |= 0x0002; + if ((state & GDK_MOD1_MASK) != 0) + modifiers |= 0x0004; + if ((state & GDK_SUPER_MASK) != 0) + modifiers |= 0x0008; + + return modifiers; +} + +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) { + 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, kChannelName, + FL_MESSAGE_CODEC(codec)); + + return self; +} + +void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self, + GdkEventKey* event) { + g_return_if_fail(FL_IS_KEY_EVENT_PLUGIN(self)); + g_return_if_fail(event != nullptr); + + const gchar* type; + if (event->type == GDK_KEY_PRESS) + type = kTypeValueDown; + else if (event->type == GDK_KEY_RELEASE) + type = kTypeValueUp; + else + return; + + int64_t scan_code = event->hardware_keycode; + int64_t key_code = gdk_keyval_to_glfw_key_code(event->keyval); + int64_t modifiers = gdk_state_to_glfw_modifiers(event->state); + int64_t unicodeScalarValues = gdk_keyval_to_unicode(event->keyval); + + 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(kGLFWToolkit)); + fl_value_set_string_take(message, kKeyCodeKey, fl_value_new_int(key_code)); + fl_value_set_string_take(message, kModifiersKey, fl_value_new_int(modifiers)); + if (unicodeScalarValues != 0) + fl_value_set_string_take(message, kUnicodeScalarValuesKey, + fl_value_new_int(unicodeScalarValues)); + + fl_basic_message_channel_send(self->channel, message, nullptr, nullptr, + nullptr); +} diff --git a/shell/platform/linux/fl_key_event_plugin.h b/shell/platform/linux/fl_key_event_plugin.h new file mode 100644 index 0000000000000..dee212d34c95e --- /dev/null +++ b/shell/platform/linux/fl_key_event_plugin.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_PLUGIN_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_PLUGIN_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" + +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlKeyEventPlugin, + fl_key_event_plugin, + FL, + KEY_EVENT_PLUGIN, + GObject); + +/** + * FlKeyEventPlugin: + * + * #FlKeyEventPlugin is a plugin that implements the shell side + * of SystemChannels.keyEvent from the Flutter services library. + */ + +/** + * fl_key_event_plugin_new: + * @messenger: an #FlBinaryMessenger. + * + * Creates a #FlKeyEventPlugin. + * + * Returns: a new #FlKeyEventPlugin. + */ +FlKeyEventPlugin* fl_key_event_plugin_new(FlBinaryMessenger* messenger); + +/** + * fl_key_event_plugin_send_key_event: + * @plugin: an #FlKeyEventPlugin. + * @event: a #GdkEventKey. + * + * Sends a key event to Flutter. + */ +void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* plugin, + GdkEventKey* event); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_PLUGIN_H_ diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 1c5bdbef92921..3b1c2c76b089d 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -5,6 +5,7 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" #include "flutter/shell/platform/linux/fl_engine_private.h" +#include "flutter/shell/platform/linux/fl_key_event_plugin.h" #include "flutter/shell/platform/linux/fl_renderer_x11.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" @@ -26,13 +27,16 @@ struct _FlView { // Pointer button state recorded for sending status updates int64_t button_state; + + // Flutter system channel handlers. + FlKeyEventPlugin* key_event_plugin; }; enum { PROP_FLUTTER_PROJECT = 1, PROP_LAST }; G_DEFINE_TYPE(FlView, fl_view, GTK_TYPE_WIDGET) -// Convert a GDK button event into a Flutter event and send to the engine +// Converts a GDK button event into a Flutter event and sends it to the engine. static gboolean fl_view_send_pointer_button_event(FlView* self, GdkEventButton* event) { int64_t button; @@ -81,6 +85,10 @@ static void fl_view_constructed(GObject* object) { self->renderer = fl_renderer_x11_new(); self->engine = fl_engine_new(self->project, FL_RENDERER(self->renderer)); + + // Create system channel handlers + FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine); + self->key_event_plugin = fl_key_event_plugin_new(messenger); } static void fl_view_set_property(GObject* object, @@ -122,6 +130,7 @@ static void fl_view_dispose(GObject* object) { g_clear_object(&self->project); g_clear_object(&self->renderer); g_clear_object(&self->engine); + g_clear_object(&self->key_event_plugin); G_OBJECT_CLASS(fl_view_parent_class)->dispose(object); } @@ -145,7 +154,8 @@ static void fl_view_realize(GtkWidget* widget) { window_attributes.visual = gtk_widget_get_visual(widget); window_attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK | - GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK; + GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK; gint window_attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; @@ -217,6 +227,25 @@ static gboolean fl_view_motion_notify_event(GtkWidget* widget, return TRUE; } +// Implements GtkWidget::key_press_event +static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) { + FlView* self = FL_VIEW(widget); + + fl_key_event_plugin_send_key_event(self->key_event_plugin, event); + + return TRUE; +} + +// Implements GtkWidget::key_release_event +static gboolean fl_view_key_release_event(GtkWidget* widget, + GdkEventKey* event) { + FlView* self = FL_VIEW(widget); + + fl_key_event_plugin_send_key_event(self->key_event_plugin, event); + + return TRUE; +} + static void fl_view_class_init(FlViewClass* klass) { G_OBJECT_CLASS(klass)->constructed = fl_view_constructed; G_OBJECT_CLASS(klass)->set_property = fl_view_set_property; @@ -227,6 +256,8 @@ static void fl_view_class_init(FlViewClass* klass) { GTK_WIDGET_CLASS(klass)->button_press_event = fl_view_button_press_event; GTK_WIDGET_CLASS(klass)->button_release_event = fl_view_button_release_event; GTK_WIDGET_CLASS(klass)->motion_notify_event = fl_view_motion_notify_event; + GTK_WIDGET_CLASS(klass)->key_press_event = fl_view_key_press_event; + GTK_WIDGET_CLASS(klass)->key_release_event = fl_view_key_release_event; g_object_class_install_property( G_OBJECT_CLASS(klass), PROP_FLUTTER_PROJECT, @@ -237,7 +268,9 @@ static void fl_view_class_init(FlViewClass* klass) { G_PARAM_STATIC_STRINGS))); } -static void fl_view_init(FlView* self) {} +static void fl_view_init(FlView* self) { + gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE); +} G_MODULE_EXPORT FlView* fl_view_new(FlDartProject* project) { return static_cast(