Hardware keyboard: Web, embedder, and dart:ui (flutter/engine#23466)
This commit is contained in:
parent
250ab7b6b2
commit
ee1cc9f5ab
@ -319,6 +319,7 @@ FILE: ../../../flutter/lib/ui/isolate_name_server/isolate_name_server.cc
|
|||||||
FILE: ../../../flutter/lib/ui/isolate_name_server/isolate_name_server.h
|
FILE: ../../../flutter/lib/ui/isolate_name_server/isolate_name_server.h
|
||||||
FILE: ../../../flutter/lib/ui/isolate_name_server/isolate_name_server_natives.cc
|
FILE: ../../../flutter/lib/ui/isolate_name_server/isolate_name_server_natives.cc
|
||||||
FILE: ../../../flutter/lib/ui/isolate_name_server/isolate_name_server_natives.h
|
FILE: ../../../flutter/lib/ui/isolate_name_server/isolate_name_server_natives.h
|
||||||
|
FILE: ../../../flutter/lib/ui/key.dart
|
||||||
FILE: ../../../flutter/lib/ui/lerp.dart
|
FILE: ../../../flutter/lib/ui/lerp.dart
|
||||||
FILE: ../../../flutter/lib/ui/natives.dart
|
FILE: ../../../flutter/lib/ui/natives.dart
|
||||||
FILE: ../../../flutter/lib/ui/painting.dart
|
FILE: ../../../flutter/lib/ui/painting.dart
|
||||||
@ -406,6 +407,10 @@ FILE: ../../../flutter/lib/ui/ui_dart_state.h
|
|||||||
FILE: ../../../flutter/lib/ui/volatile_path_tracker.cc
|
FILE: ../../../flutter/lib/ui/volatile_path_tracker.cc
|
||||||
FILE: ../../../flutter/lib/ui/volatile_path_tracker.h
|
FILE: ../../../flutter/lib/ui/volatile_path_tracker.h
|
||||||
FILE: ../../../flutter/lib/ui/window.dart
|
FILE: ../../../flutter/lib/ui/window.dart
|
||||||
|
FILE: ../../../flutter/lib/ui/window/key_data.cc
|
||||||
|
FILE: ../../../flutter/lib/ui/window/key_data.h
|
||||||
|
FILE: ../../../flutter/lib/ui/window/key_data_packet.cc
|
||||||
|
FILE: ../../../flutter/lib/ui/window/key_data_packet.h
|
||||||
FILE: ../../../flutter/lib/ui/window/platform_configuration.cc
|
FILE: ../../../flutter/lib/ui/window/platform_configuration.cc
|
||||||
FILE: ../../../flutter/lib/ui/window/platform_configuration.h
|
FILE: ../../../flutter/lib/ui/window/platform_configuration.h
|
||||||
FILE: ../../../flutter/lib/ui/window/platform_configuration_unittests.cc
|
FILE: ../../../flutter/lib/ui/window/platform_configuration_unittests.cc
|
||||||
@ -500,7 +505,9 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/surface.dart
|
|||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/surface_stats.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/surface_stats.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/transform.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/transform.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart
|
||||||
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/key_map.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard.dart
|
||||||
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart
|
||||||
@ -560,6 +567,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/ui/compositing.dart
|
|||||||
FILE: ../../../flutter/lib/web_ui/lib/src/ui/geometry.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/ui/geometry.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/ui/hash_codes.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/ui/hash_codes.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/ui/initialization.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/ui/initialization.dart
|
||||||
|
FILE: ../../../flutter/lib/web_ui/lib/src/ui/key.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/ui/lerp.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/ui/lerp.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/ui/natives.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/ui/natives.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/ui/painting.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/ui/painting.dart
|
||||||
|
@ -93,6 +93,10 @@ source_set("ui") {
|
|||||||
"ui_dart_state.h",
|
"ui_dart_state.h",
|
||||||
"volatile_path_tracker.cc",
|
"volatile_path_tracker.cc",
|
||||||
"volatile_path_tracker.h",
|
"volatile_path_tracker.h",
|
||||||
|
"window/key_data.cc",
|
||||||
|
"window/key_data.h",
|
||||||
|
"window/key_data_packet.cc",
|
||||||
|
"window/key_data_packet.h",
|
||||||
"window/platform_configuration.cc",
|
"window/platform_configuration.cc",
|
||||||
"window/platform_configuration.h",
|
"window/platform_configuration.h",
|
||||||
"window/platform_message.cc",
|
"window/platform_message.cc",
|
||||||
|
@ -10,6 +10,7 @@ dart_ui_files = [
|
|||||||
"//flutter/lib/ui/hash_codes.dart",
|
"//flutter/lib/ui/hash_codes.dart",
|
||||||
"//flutter/lib/ui/hooks.dart",
|
"//flutter/lib/ui/hooks.dart",
|
||||||
"//flutter/lib/ui/isolate_name_server.dart",
|
"//flutter/lib/ui/isolate_name_server.dart",
|
||||||
|
"//flutter/lib/ui/key.dart",
|
||||||
"//flutter/lib/ui/lerp.dart",
|
"//flutter/lib/ui/lerp.dart",
|
||||||
"//flutter/lib/ui/natives.dart",
|
"//flutter/lib/ui/natives.dart",
|
||||||
"//flutter/lib/ui/painting.dart",
|
"//flutter/lib/ui/painting.dart",
|
||||||
|
@ -96,6 +96,12 @@ void _dispatchPointerDataPacket(ByteData packet) {
|
|||||||
PlatformDispatcher.instance._dispatchPointerDataPacket(packet);
|
PlatformDispatcher.instance._dispatchPointerDataPacket(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
// ignore: unused_element
|
||||||
|
void _dispatchKeyData(ByteData packet, int responseId) {
|
||||||
|
PlatformDispatcher.instance._dispatchKeyData(packet, responseId);
|
||||||
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
void _dispatchSemanticsAction(int id, int action, ByteData? args) {
|
void _dispatchSemanticsAction(int id, int action, ByteData? args) {
|
||||||
|
109
engine/src/flutter/lib/ui/key.dart
Normal file
109
engine/src/flutter/lib/ui/key.dart
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// @dart = 2.12
|
||||||
|
|
||||||
|
part of dart.ui;
|
||||||
|
|
||||||
|
/// The type of a key event.
|
||||||
|
// Must match the KeyEventType enum in ui/window/key_data.h.
|
||||||
|
enum KeyEventType {
|
||||||
|
/// The key is pressed.
|
||||||
|
down,
|
||||||
|
|
||||||
|
/// The key is released.
|
||||||
|
up,
|
||||||
|
|
||||||
|
/// The key is held, causing a repeated key input.
|
||||||
|
repeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about a key event.
|
||||||
|
class KeyData {
|
||||||
|
/// Creates an object that represents a key event.
|
||||||
|
const KeyData({
|
||||||
|
required this.timeStamp,
|
||||||
|
required this.type,
|
||||||
|
required this.physical,
|
||||||
|
required this.logical,
|
||||||
|
required this.character,
|
||||||
|
required this.synthesized,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Time of event dispatch, relative to an arbitrary timeline.
|
||||||
|
///
|
||||||
|
/// For synthesized events, the [timeStamp] might not be the actual time that
|
||||||
|
/// the key press or release happens.
|
||||||
|
final Duration timeStamp;
|
||||||
|
|
||||||
|
/// The type of the event.
|
||||||
|
final KeyEventType type;
|
||||||
|
|
||||||
|
/// The key code for the physical key that has changed.
|
||||||
|
final int physical;
|
||||||
|
|
||||||
|
/// The key code for the logical key that has changed.
|
||||||
|
final int logical;
|
||||||
|
|
||||||
|
/// Character input from the event.
|
||||||
|
///
|
||||||
|
/// Ignored for up events.
|
||||||
|
final String? character;
|
||||||
|
|
||||||
|
/// If [synthesized] is true, this event does not correspond to a native event.
|
||||||
|
///
|
||||||
|
/// Although most of Flutter's keyboard events are transformed from native
|
||||||
|
/// events, some events are not based on native events, and are synthesized
|
||||||
|
/// only to conform Flutter's key event model (as documented in
|
||||||
|
/// the `HardwareKeyboard` class in the framework).
|
||||||
|
///
|
||||||
|
/// For example, some key downs or ups might be lost when the window loses
|
||||||
|
/// focus. Some platforms provides ways to query whether a key is being held.
|
||||||
|
/// If the embedder detects an inconsistancy between its internal record and
|
||||||
|
/// the state returned by the system, the embedder will synthesize a
|
||||||
|
/// corresponding event to synchronize the state without breaking the event
|
||||||
|
/// model.
|
||||||
|
///
|
||||||
|
/// As another example, macOS treats CapsLock in a special way by sending
|
||||||
|
/// down and up events at the down of alterate presses to indicate the
|
||||||
|
/// direction in which the lock is toggled instead of that the physical key is
|
||||||
|
/// going. A macOS embedder should normalize the behavior by converting a
|
||||||
|
/// native down event into a down event followed immediately by a synthesized
|
||||||
|
/// up event, and the native up event also into a down event followed
|
||||||
|
/// immediately by a synthesized up event.
|
||||||
|
///
|
||||||
|
/// Synthesized events do not have a trustworthy [timeStamp], and should not be
|
||||||
|
/// processed as if the key actually went down or up at the time of the
|
||||||
|
/// callback.
|
||||||
|
///
|
||||||
|
/// [KeyRepeatEvent] is never synthesized.
|
||||||
|
final bool synthesized;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'KeyData(type: ${_typeToString(type)}, physical: 0x${physical.toRadixString(16)}, '
|
||||||
|
'logical: 0x${logical.toRadixString(16)}, character: $character)';
|
||||||
|
|
||||||
|
/// Returns a complete textual description of the information in this object.
|
||||||
|
String toStringFull() {
|
||||||
|
return '$runtimeType('
|
||||||
|
'type: ${_typeToString(type)}, '
|
||||||
|
'timeStamp: $timeStamp, '
|
||||||
|
'physical: 0x${physical.toRadixString(16)}, '
|
||||||
|
'logical: 0x${logical.toRadixString(16)}, '
|
||||||
|
'character: $character, '
|
||||||
|
'synthesized: $synthesized'
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _typeToString(KeyEventType type) {
|
||||||
|
switch (type) {
|
||||||
|
case KeyEventType.up:
|
||||||
|
return 'up';
|
||||||
|
case KeyEventType.down:
|
||||||
|
return 'down';
|
||||||
|
case KeyEventType.repeat:
|
||||||
|
return 'repeat';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,12 @@ typedef TimingsCallback = void Function(List<FrameTiming> timings);
|
|||||||
/// Signature for [PlatformDispatcher.onPointerDataPacket].
|
/// Signature for [PlatformDispatcher.onPointerDataPacket].
|
||||||
typedef PointerDataPacketCallback = void Function(PointerDataPacket packet);
|
typedef PointerDataPacketCallback = void Function(PointerDataPacket packet);
|
||||||
|
|
||||||
|
// Signature for the response to KeyDataCallback.
|
||||||
|
typedef _KeyDataResponseCallback = void Function(int responseId, bool handled);
|
||||||
|
|
||||||
|
/// Signature for [PlatformDispatcher.onKeyData].
|
||||||
|
typedef KeyDataCallback = bool Function(KeyData data);
|
||||||
|
|
||||||
/// Signature for [PlatformDispatcher.onSemanticsAction].
|
/// Signature for [PlatformDispatcher.onSemanticsAction].
|
||||||
typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args);
|
typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args);
|
||||||
|
|
||||||
@ -332,6 +338,63 @@ class PlatformDispatcher {
|
|||||||
return PointerDataPacket(data: data);
|
return PointerDataPacket(data: data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by [_dispatchKeyData].
|
||||||
|
void _respondToKeyData(int responseId, bool handled)
|
||||||
|
native 'PlatformConfiguration_respondToKeyData';
|
||||||
|
|
||||||
|
/// A callback that is invoked when key data is available.
|
||||||
|
///
|
||||||
|
/// The framework invokes this callback in the same zone in which the callback
|
||||||
|
/// was set.
|
||||||
|
KeyDataCallback? get onKeyData => _onKeyData;
|
||||||
|
KeyDataCallback? _onKeyData;
|
||||||
|
Zone _onKeyDataZone = Zone.root;
|
||||||
|
set onKeyData(KeyDataCallback? callback) {
|
||||||
|
_onKeyData = callback;
|
||||||
|
_onKeyDataZone = Zone.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from the engine, via hooks.dart
|
||||||
|
void _dispatchKeyData(ByteData packet, int responseId) {
|
||||||
|
_invoke2<KeyData, _KeyDataResponseCallback>(
|
||||||
|
(KeyData data, _KeyDataResponseCallback callback) {
|
||||||
|
callback(responseId, onKeyData == null ? false : onKeyData!(data));
|
||||||
|
},
|
||||||
|
_onKeyDataZone,
|
||||||
|
_unpackKeyData(packet),
|
||||||
|
_respondToKeyData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this value changes, update the encoding code in the following files:
|
||||||
|
//
|
||||||
|
// * key_data.h
|
||||||
|
// * key.dart (ui)
|
||||||
|
// * key.dart (web_ui)
|
||||||
|
// * HardwareKeyboard.java
|
||||||
|
static const int _kKeyDataFieldCount = 5;
|
||||||
|
|
||||||
|
// The packet structure is described in `key_data_packet.h`.
|
||||||
|
static KeyData _unpackKeyData(ByteData packet) {
|
||||||
|
const int kStride = Int64List.bytesPerElement;
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
final int charDataSize = packet.getUint64(kStride * offset++, _kFakeHostEndian);
|
||||||
|
final String? character = charDataSize == 0 ? null : utf8.decoder.convert(
|
||||||
|
packet.buffer.asUint8List(kStride * (offset + _kKeyDataFieldCount), charDataSize));
|
||||||
|
|
||||||
|
final KeyData keyData = KeyData(
|
||||||
|
timeStamp: Duration(microseconds: packet.getUint64(kStride * offset++, _kFakeHostEndian)),
|
||||||
|
type: KeyEventType.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],
|
||||||
|
physical: packet.getUint64(kStride * offset++, _kFakeHostEndian),
|
||||||
|
logical: packet.getUint64(kStride * offset++, _kFakeHostEndian),
|
||||||
|
character: character,
|
||||||
|
synthesized: packet.getUint64(kStride * offset++, _kFakeHostEndian) != 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return keyData;
|
||||||
|
}
|
||||||
|
|
||||||
/// A callback that is invoked to report the [FrameTiming] of recently
|
/// A callback that is invoked to report the [FrameTiming] of recently
|
||||||
/// rasterized frames.
|
/// rasterized frames.
|
||||||
///
|
///
|
||||||
@ -1547,4 +1610,4 @@ class Locale {
|
|||||||
out.write('$separator$countryCode');
|
out.write('$separator$countryCode');
|
||||||
return out.toString();
|
return out.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
// @dart = 2.12
|
// @dart = 2.12
|
||||||
library dart.ui;
|
library dart.ui;
|
||||||
|
|
||||||
import 'dart:_internal' hide Symbol; // ignore: unused_import
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection' as collection;
|
import 'dart:collection' as collection;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
@ -30,6 +29,7 @@ part 'geometry.dart';
|
|||||||
part 'hash_codes.dart';
|
part 'hash_codes.dart';
|
||||||
part 'hooks.dart';
|
part 'hooks.dart';
|
||||||
part 'isolate_name_server.dart';
|
part 'isolate_name_server.dart';
|
||||||
|
part 'key.dart';
|
||||||
part 'lerp.dart';
|
part 'lerp.dart';
|
||||||
part 'natives.dart';
|
part 'natives.dart';
|
||||||
part 'painting.dart';
|
part 'painting.dart';
|
||||||
|
@ -556,6 +556,15 @@ class SingletonFlutterWindow extends FlutterWindow {
|
|||||||
platformDispatcher.onPointerDataPacket = callback;
|
platformDispatcher.onPointerDataPacket = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A callback that is invoked when key data is available.
|
||||||
|
///
|
||||||
|
/// The framework invokes this callback in the same zone in which the
|
||||||
|
/// callback was set.
|
||||||
|
KeyDataCallback? get onKeyData => platformDispatcher.onKeyData;
|
||||||
|
set onKeyData(KeyDataCallback? callback) {
|
||||||
|
platformDispatcher.onKeyData = callback;
|
||||||
|
}
|
||||||
|
|
||||||
/// The route or path that the embedder requested when the application was
|
/// The route or path that the embedder requested when the application was
|
||||||
/// launched.
|
/// launched.
|
||||||
///
|
///
|
||||||
|
18
engine/src/flutter/lib/ui/window/key_data.cc
Normal file
18
engine/src/flutter/lib/ui/window/key_data.cc
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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/lib/ui/window/key_data.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace flutter {
|
||||||
|
|
||||||
|
static_assert(sizeof(KeyData) == kBytesPerKeyField * kKeyDataFieldCount,
|
||||||
|
"KeyData has the wrong size");
|
||||||
|
|
||||||
|
void KeyData::Clear() {
|
||||||
|
memset(this, 0, sizeof(KeyData));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace flutter
|
48
engine/src/flutter/lib/ui/window/key_data.h
Normal file
48
engine/src/flutter/lib/ui/window/key_data.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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_LIB_UI_WINDOW_KEY_DATA_H_
|
||||||
|
#define FLUTTER_LIB_UI_WINDOW_KEY_DATA_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace flutter {
|
||||||
|
|
||||||
|
// If this value changes, update the key data unpacking code in hooks.dart.
|
||||||
|
static constexpr int kKeyDataFieldCount = 5;
|
||||||
|
static constexpr int kBytesPerKeyField = sizeof(int64_t);
|
||||||
|
|
||||||
|
// The change of the key event, used by KeyData.
|
||||||
|
//
|
||||||
|
// Must match the KeyEventType enum in ui/key.dart.
|
||||||
|
enum class KeyEventType : int64_t {
|
||||||
|
kDown = 0,
|
||||||
|
kUp,
|
||||||
|
kRepeat,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The fixed-length sections of a KeyDataPacket.
|
||||||
|
//
|
||||||
|
// KeyData does not contain `character`, for variable-length data are stored in
|
||||||
|
// a different way in KeyDataPacket.
|
||||||
|
//
|
||||||
|
// This structure is unpacked by hooks.dart.
|
||||||
|
struct alignas(8) KeyData {
|
||||||
|
// Timestamp in microseconds from an arbitrary and consistant start point
|
||||||
|
uint64_t timestamp;
|
||||||
|
KeyEventType type;
|
||||||
|
uint64_t physical;
|
||||||
|
uint64_t logical;
|
||||||
|
// True if the event does not correspond to a native event.
|
||||||
|
//
|
||||||
|
// The value is 1 for true, and 0 for false.
|
||||||
|
uint64_t synthesized;
|
||||||
|
|
||||||
|
// Sets all contents of `Keydata` to 0.
|
||||||
|
void Clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace flutter
|
||||||
|
|
||||||
|
#endif // FLUTTER_LIB_UI_WINDOW_POINTER_DATA_H_
|
26
engine/src/flutter/lib/ui/window/key_data_packet.cc
Normal file
26
engine/src/flutter/lib/ui/window/key_data_packet.cc
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 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/lib/ui/window/key_data_packet.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "flutter/fml/logging.h"
|
||||||
|
|
||||||
|
namespace flutter {
|
||||||
|
|
||||||
|
KeyDataPacket::KeyDataPacket(const KeyData& event, const char* character) {
|
||||||
|
size_t char_size = character == nullptr ? 0 : strlen(character);
|
||||||
|
uint64_t char_size_64 = char_size;
|
||||||
|
data_.resize(sizeof(uint64_t) + sizeof(KeyData) + char_size);
|
||||||
|
memcpy(CharacterSizeStart(), &char_size_64, sizeof(char_size));
|
||||||
|
memcpy(KeyDataStart(), &event, sizeof(KeyData));
|
||||||
|
if (character != nullptr) {
|
||||||
|
memcpy(CharacterStart(), character, char_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyDataPacket::~KeyDataPacket() = default;
|
||||||
|
|
||||||
|
} // namespace flutter
|
46
engine/src/flutter/lib/ui/window/key_data_packet.h
Normal file
46
engine/src/flutter/lib/ui/window/key_data_packet.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// 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_LIB_UI_WINDOW_KEY_DATA_MESSAGE_H_
|
||||||
|
#define FLUTTER_LIB_UI_WINDOW_KEY_DATA_MESSAGE_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "flutter/fml/macros.h"
|
||||||
|
#include "flutter/lib/ui/window/key_data.h"
|
||||||
|
|
||||||
|
namespace flutter {
|
||||||
|
|
||||||
|
// A byte stream representing a key event, to be sent to the framework.
|
||||||
|
class KeyDataPacket {
|
||||||
|
public:
|
||||||
|
// Build the key data packet by providing information.
|
||||||
|
//
|
||||||
|
// The `character` is a nullable C-string that ends with a '\0'.
|
||||||
|
KeyDataPacket(const KeyData& event, const char* character);
|
||||||
|
~KeyDataPacket();
|
||||||
|
|
||||||
|
// Prevent copying.
|
||||||
|
KeyDataPacket(KeyDataPacket const&) = delete;
|
||||||
|
KeyDataPacket& operator=(KeyDataPacket const&) = delete;
|
||||||
|
|
||||||
|
const std::vector<uint8_t>& data() const { return data_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Packet structure:
|
||||||
|
// | CharDataSize | (1 field)
|
||||||
|
// | Key Data | (kKeyDataFieldCount fields)
|
||||||
|
// | CharData | (CharDataSize bits)
|
||||||
|
|
||||||
|
uint8_t* CharacterSizeStart() { return data_.data(); }
|
||||||
|
uint8_t* KeyDataStart() { return CharacterSizeStart() + sizeof(uint64_t); }
|
||||||
|
uint8_t* CharacterStart() { return KeyDataStart() + sizeof(KeyData); }
|
||||||
|
|
||||||
|
std::vector<uint8_t> data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace flutter
|
||||||
|
|
||||||
|
#endif // FLUTTER_LIB_UI_WINDOW_POINTER_DATA_MESSAGE_H_
|
@ -181,6 +181,15 @@ void GetPersistentIsolateData(Dart_NativeArguments args) {
|
|||||||
persistent_isolate_data->GetSize()));
|
persistent_isolate_data->GetSize()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RespondToKeyData(Dart_Handle window, int response_id, bool handled) {
|
||||||
|
UIDartState::Current()->platform_configuration()->CompleteKeyDataResponse(
|
||||||
|
response_id, handled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _RespondToKeyData(Dart_NativeArguments args) {
|
||||||
|
tonic::DartCallStatic(&RespondToKeyData, args);
|
||||||
|
}
|
||||||
|
|
||||||
Dart_Handle ToByteData(const std::vector<uint8_t>& buffer) {
|
Dart_Handle ToByteData(const std::vector<uint8_t>& buffer) {
|
||||||
return tonic::DartByteData::Create(buffer.data(), buffer.size());
|
return tonic::DartByteData::Create(buffer.data(), buffer.size());
|
||||||
}
|
}
|
||||||
@ -349,6 +358,13 @@ void PlatformConfiguration::DispatchSemanticsAction(int32_t id,
|
|||||||
args_handle}));
|
args_handle}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t PlatformConfiguration::RegisterKeyDataResponse(
|
||||||
|
KeyDataResponse callback) {
|
||||||
|
uint64_t response_id = next_key_response_id_++;
|
||||||
|
pending_key_responses_[response_id] = std::move(callback);
|
||||||
|
return response_id;
|
||||||
|
}
|
||||||
|
|
||||||
void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime) {
|
void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime) {
|
||||||
std::shared_ptr<tonic::DartState> dart_state =
|
std::shared_ptr<tonic::DartState> dart_state =
|
||||||
begin_frame_.dart_state().lock();
|
begin_frame_.dart_state().lock();
|
||||||
@ -424,6 +440,21 @@ void PlatformConfiguration::CompletePlatformMessageResponse(
|
|||||||
response->Complete(std::make_unique<fml::DataMapping>(std::move(data)));
|
response->Complete(std::make_unique<fml::DataMapping>(std::move(data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformConfiguration::CompleteKeyDataResponse(uint64_t response_id,
|
||||||
|
bool handled) {
|
||||||
|
if (response_id == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto it = pending_key_responses_.find(response_id);
|
||||||
|
FML_DCHECK(it != pending_key_responses_.end());
|
||||||
|
if (it == pending_key_responses_.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
KeyDataResponse callback = std::move(it->second);
|
||||||
|
pending_key_responses_.erase(it);
|
||||||
|
callback(handled);
|
||||||
|
}
|
||||||
|
|
||||||
Dart_Handle ComputePlatformResolvedLocale(Dart_Handle supportedLocalesHandle) {
|
Dart_Handle ComputePlatformResolvedLocale(Dart_Handle supportedLocalesHandle) {
|
||||||
std::vector<std::string> supportedLocales =
|
std::vector<std::string> supportedLocales =
|
||||||
tonic::DartConverter<std::vector<std::string>>::FromDart(
|
tonic::DartConverter<std::vector<std::string>>::FromDart(
|
||||||
@ -454,6 +485,7 @@ void PlatformConfiguration::RegisterNatives(
|
|||||||
true},
|
true},
|
||||||
{"PlatformConfiguration_respondToPlatformMessage",
|
{"PlatformConfiguration_respondToPlatformMessage",
|
||||||
_RespondToPlatformMessage, 3, true},
|
_RespondToPlatformMessage, 3, true},
|
||||||
|
{"PlatformConfiguration_respondToKeyData", _RespondToKeyData, 3, true},
|
||||||
{"PlatformConfiguration_render", Render, 3, true},
|
{"PlatformConfiguration_render", Render, 3, true},
|
||||||
{"PlatformConfiguration_updateSemantics", UpdateSemantics, 2, true},
|
{"PlatformConfiguration_updateSemantics", UpdateSemantics, 2, true},
|
||||||
{"PlatformConfiguration_setIsolateDebugName", SetIsolateDebugName, 2,
|
{"PlatformConfiguration_setIsolateDebugName", SetIsolateDebugName, 2,
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#ifndef FLUTTER_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_
|
#ifndef FLUTTER_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_
|
||||||
#define FLUTTER_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_
|
#define FLUTTER_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@ -22,6 +23,8 @@ class FontCollection;
|
|||||||
class PlatformMessage;
|
class PlatformMessage;
|
||||||
class Scene;
|
class Scene;
|
||||||
|
|
||||||
|
typedef std::function<void(bool /* handled */)> KeyDataResponse;
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
/// @brief An enum for defining the different kinds of accessibility features
|
/// @brief An enum for defining the different kinds of accessibility features
|
||||||
/// that can be enabled by the platform.
|
/// that can be enabled by the platform.
|
||||||
@ -323,6 +326,24 @@ class PlatformConfiguration final {
|
|||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
std::vector<uint8_t> args);
|
std::vector<uint8_t> args);
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
/// @brief Registers a callback to be invoked when the framework has
|
||||||
|
/// decided whether to handle an event. This callback originates
|
||||||
|
/// in the platform view and has been forwarded through the engine
|
||||||
|
/// to here.
|
||||||
|
///
|
||||||
|
/// This method will move and store the `callback`, associate it
|
||||||
|
/// with a self-incrementing identifier, the response ID, then
|
||||||
|
/// return the ID, which is typically used by
|
||||||
|
/// Window::DispatchKeyDataPacket.
|
||||||
|
///
|
||||||
|
/// @param[in] callback The callback to be registered.
|
||||||
|
///
|
||||||
|
/// @return The response ID to be associated with the callback. Using this
|
||||||
|
/// ID in CompleteKeyDataResponse will invoke the callback.
|
||||||
|
///
|
||||||
|
uint64_t RegisterKeyDataResponse(KeyDataResponse callback);
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
/// @brief Notifies the framework that it is time to begin working on a
|
/// @brief Notifies the framework that it is time to begin working on a
|
||||||
/// new frame previously scheduled via a call to
|
/// new frame previously scheduled via a call to
|
||||||
@ -411,6 +432,21 @@ class PlatformConfiguration final {
|
|||||||
///
|
///
|
||||||
void CompletePlatformMessageEmptyResponse(int response_id);
|
void CompletePlatformMessageEmptyResponse(int response_id);
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
/// @brief Responds to a previously registered key data message from the
|
||||||
|
/// framework to the engine.
|
||||||
|
///
|
||||||
|
/// For each response_id, this method should be called exactly
|
||||||
|
/// once. Responding to a response_id that has not been registered
|
||||||
|
/// or has been invoked will lead to a fatal error.
|
||||||
|
///
|
||||||
|
/// @param[in] response_id The unique id that identifies the original platform
|
||||||
|
/// message to respond to, created by
|
||||||
|
/// RegisterKeyDataResponse.
|
||||||
|
/// @param[in] handled Whether the key data is handled.
|
||||||
|
///
|
||||||
|
void CompleteKeyDataResponse(uint64_t response_id, bool handled);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PlatformConfigurationClient* client_;
|
PlatformConfigurationClient* client_;
|
||||||
tonic::DartPersistentValue update_locales_;
|
tonic::DartPersistentValue update_locales_;
|
||||||
@ -419,6 +455,7 @@ class PlatformConfiguration final {
|
|||||||
tonic::DartPersistentValue update_semantics_enabled_;
|
tonic::DartPersistentValue update_semantics_enabled_;
|
||||||
tonic::DartPersistentValue update_accessibility_features_;
|
tonic::DartPersistentValue update_accessibility_features_;
|
||||||
tonic::DartPersistentValue dispatch_platform_message_;
|
tonic::DartPersistentValue dispatch_platform_message_;
|
||||||
|
tonic::DartPersistentValue dispatch_key_message_;
|
||||||
tonic::DartPersistentValue dispatch_semantics_action_;
|
tonic::DartPersistentValue dispatch_semantics_action_;
|
||||||
tonic::DartPersistentValue begin_frame_;
|
tonic::DartPersistentValue begin_frame_;
|
||||||
tonic::DartPersistentValue draw_frame_;
|
tonic::DartPersistentValue draw_frame_;
|
||||||
@ -426,10 +463,14 @@ class PlatformConfiguration final {
|
|||||||
|
|
||||||
std::unordered_map<int64_t, std::unique_ptr<Window>> windows_;
|
std::unordered_map<int64_t, std::unique_ptr<Window>> windows_;
|
||||||
|
|
||||||
// We use id 0 to mean that no response is expected.
|
// ID starts at 1 because an ID of 0 indicates that no response is expected.
|
||||||
int next_response_id_ = 1;
|
int next_response_id_ = 1;
|
||||||
std::unordered_map<int, fml::RefPtr<PlatformMessageResponse>>
|
std::unordered_map<int, fml::RefPtr<PlatformMessageResponse>>
|
||||||
pending_responses_;
|
pending_responses_;
|
||||||
|
|
||||||
|
// ID starts at 1 because an ID of 0 indicates that no response is expected.
|
||||||
|
uint64_t next_key_response_id_ = 1;
|
||||||
|
std::unordered_map<uint64_t, KeyDataResponse> pending_key_responses_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
@ -36,6 +36,24 @@ void Window::DispatchPointerDataPacket(const PointerDataPacket& packet) {
|
|||||||
library_.value(), "_dispatchPointerDataPacket", {data_handle}));
|
library_.value(), "_dispatchPointerDataPacket", {data_handle}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::DispatchKeyDataPacket(const KeyDataPacket& packet,
|
||||||
|
uint64_t response_id) {
|
||||||
|
std::shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
|
||||||
|
if (!dart_state)
|
||||||
|
return;
|
||||||
|
tonic::DartState::Scope scope(dart_state);
|
||||||
|
|
||||||
|
const std::vector<uint8_t>& buffer = packet.data();
|
||||||
|
Dart_Handle data_handle =
|
||||||
|
tonic::DartByteData::Create(buffer.data(), buffer.size());
|
||||||
|
if (Dart_IsError(data_handle)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tonic::LogIfError(
|
||||||
|
tonic::DartInvokeField(library_.value(), "_dispatchKeyData",
|
||||||
|
{data_handle, tonic::ToDart(response_id)}));
|
||||||
|
}
|
||||||
|
|
||||||
void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) {
|
void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) {
|
||||||
viewport_metrics_ = metrics;
|
viewport_metrics_ = metrics;
|
||||||
|
|
||||||
|
@ -5,10 +5,12 @@
|
|||||||
#ifndef FLUTTER_LIB_UI_WINDOW_WINDOW_H_
|
#ifndef FLUTTER_LIB_UI_WINDOW_WINDOW_H_
|
||||||
#define FLUTTER_LIB_UI_WINDOW_WINDOW_H_
|
#define FLUTTER_LIB_UI_WINDOW_WINDOW_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "flutter/lib/ui/window/key_data_packet.h"
|
||||||
#include "flutter/lib/ui/window/platform_message.h"
|
#include "flutter/lib/ui/window/platform_message.h"
|
||||||
#include "flutter/lib/ui/window/pointer_data_packet.h"
|
#include "flutter/lib/ui/window/pointer_data_packet.h"
|
||||||
#include "flutter/lib/ui/window/viewport_metrics.h"
|
#include "flutter/lib/ui/window/viewport_metrics.h"
|
||||||
@ -26,7 +28,17 @@ class Window final {
|
|||||||
|
|
||||||
const ViewportMetrics& viewport_metrics() const { return viewport_metrics_; }
|
const ViewportMetrics& viewport_metrics() const { return viewport_metrics_; }
|
||||||
|
|
||||||
|
// Dispatch a packet to the framework that indicates one or a few pointer
|
||||||
|
// events.
|
||||||
void DispatchPointerDataPacket(const PointerDataPacket& packet);
|
void DispatchPointerDataPacket(const PointerDataPacket& packet);
|
||||||
|
// Dispatch a packet to the framework that indicates a key event.
|
||||||
|
//
|
||||||
|
// The `response_id` is used to label the response of whether the key event
|
||||||
|
// is handled by the framework, typically the return value of
|
||||||
|
// PlatformConfiguration::RegisterKeyDataResponse.
|
||||||
|
// It should be used later in
|
||||||
|
// PlatformConfiguration::CompleteKeyDataResponse.
|
||||||
|
void DispatchKeyDataPacket(const KeyDataPacket& packet, uint64_t response_id);
|
||||||
void UpdateWindowMetrics(const ViewportMetrics& metrics);
|
void UpdateWindowMetrics(const ViewportMetrics& metrics);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -98,7 +98,9 @@ part 'engine/html/surface.dart';
|
|||||||
part 'engine/html/surface_stats.dart';
|
part 'engine/html/surface_stats.dart';
|
||||||
part 'engine/html/transform.dart';
|
part 'engine/html/transform.dart';
|
||||||
part 'engine/html_image_codec.dart';
|
part 'engine/html_image_codec.dart';
|
||||||
|
part 'engine/keyboard_binding.dart';
|
||||||
part 'engine/keyboard.dart';
|
part 'engine/keyboard.dart';
|
||||||
|
part 'engine/key_map.dart';
|
||||||
part 'engine/mouse_cursor.dart';
|
part 'engine/mouse_cursor.dart';
|
||||||
part 'engine/onscreen_logging.dart';
|
part 'engine/onscreen_logging.dart';
|
||||||
part 'engine/picture.dart';
|
part 'engine/picture.dart';
|
||||||
|
@ -444,6 +444,7 @@ flt-glass-pane * {
|
|||||||
glassPaneElement.insertBefore(_accesibilityPlaceholder, _sceneHostElement);
|
glassPaneElement.insertBefore(_accesibilityPlaceholder, _sceneHostElement);
|
||||||
|
|
||||||
PointerBinding.initInstance(glassPaneElement);
|
PointerBinding.initInstance(glassPaneElement);
|
||||||
|
KeyboardBinding.initInstance(glassPaneElement);
|
||||||
|
|
||||||
// Hide the DOM nodes used to render the scene from accessibility, because
|
// Hide the DOM nodes used to render the scene from accessibility, because
|
||||||
// the accessibility tree is built from the SemanticsNode tree as a parallel
|
// the accessibility tree is built from the SemanticsNode tree as a parallel
|
||||||
|
706
engine/src/flutter/lib/web_ui/lib/src/engine/key_map.dart
Normal file
706
engine/src/flutter/lib/web_ui/lib/src/engine/key_map.dart
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT
|
||||||
|
// This file is generated by dev/tools/gen_keycodes/bin/gen_keycodes.dart and
|
||||||
|
// should not be edited directly.
|
||||||
|
//
|
||||||
|
// Edit the template dev/tools/gen_keycodes/data/web_key_map_dart.tmpl instead.
|
||||||
|
// See dev/tools/gen_keycodes/README.md for more information.
|
||||||
|
|
||||||
|
// @dart = 2.12
|
||||||
|
part of engine;
|
||||||
|
|
||||||
|
/// Maps Web KeyboardEvent codes to the matching LogicalKeyboardKey id.
|
||||||
|
const Map<String, int> kWebToLogicalKey = <String, int>{
|
||||||
|
'None': 0x0000000000,
|
||||||
|
'Unidentified': 0x0000000001,
|
||||||
|
'Backspace': 0x0000000008,
|
||||||
|
'Tab': 0x0000000009,
|
||||||
|
'Enter': 0x000000000d,
|
||||||
|
'Escape': 0x000000001b,
|
||||||
|
'Space': 0x0000000020,
|
||||||
|
'Exclamation': 0x0000000021,
|
||||||
|
'Quote': 0x0000000022,
|
||||||
|
'NumberSign': 0x0000000023,
|
||||||
|
'Dollar': 0x0000000024,
|
||||||
|
'Ampersand': 0x0000000026,
|
||||||
|
'QuoteSingle': 0x0000000027,
|
||||||
|
'ParenthesisLeft': 0x0000000028,
|
||||||
|
'ParenthesisRight': 0x0000000029,
|
||||||
|
'Asterisk': 0x000000002a,
|
||||||
|
'Add': 0x000000002b,
|
||||||
|
'Comma': 0x000000002c,
|
||||||
|
'Minus': 0x000000002d,
|
||||||
|
'Period': 0x000000002e,
|
||||||
|
'Slash': 0x000000002f,
|
||||||
|
'Digit0': 0x0000000030,
|
||||||
|
'Digit1': 0x0000000031,
|
||||||
|
'Digit2': 0x0000000032,
|
||||||
|
'Digit3': 0x0000000033,
|
||||||
|
'Digit4': 0x0000000034,
|
||||||
|
'Digit5': 0x0000000035,
|
||||||
|
'Digit6': 0x0000000036,
|
||||||
|
'Digit7': 0x0000000037,
|
||||||
|
'Digit8': 0x0000000038,
|
||||||
|
'Digit9': 0x0000000039,
|
||||||
|
'Colon': 0x000000003a,
|
||||||
|
'Semicolon': 0x000000003b,
|
||||||
|
'Less': 0x000000003c,
|
||||||
|
'Equal': 0x000000003d,
|
||||||
|
'Greater': 0x000000003e,
|
||||||
|
'Question': 0x000000003f,
|
||||||
|
'At': 0x0000000040,
|
||||||
|
'BracketLeft': 0x000000005b,
|
||||||
|
'Backslash': 0x000000005c,
|
||||||
|
'BracketRight': 0x000000005d,
|
||||||
|
'Caret': 0x000000005e,
|
||||||
|
'Underscore': 0x000000005f,
|
||||||
|
'Backquote': 0x0000000060,
|
||||||
|
'KeyA': 0x0000000061,
|
||||||
|
'KeyB': 0x0000000062,
|
||||||
|
'KeyC': 0x0000000063,
|
||||||
|
'KeyD': 0x0000000064,
|
||||||
|
'KeyE': 0x0000000065,
|
||||||
|
'KeyF': 0x0000000066,
|
||||||
|
'KeyG': 0x0000000067,
|
||||||
|
'KeyH': 0x0000000068,
|
||||||
|
'KeyI': 0x0000000069,
|
||||||
|
'KeyJ': 0x000000006a,
|
||||||
|
'KeyK': 0x000000006b,
|
||||||
|
'KeyL': 0x000000006c,
|
||||||
|
'KeyM': 0x000000006d,
|
||||||
|
'KeyN': 0x000000006e,
|
||||||
|
'KeyO': 0x000000006f,
|
||||||
|
'KeyP': 0x0000000070,
|
||||||
|
'KeyQ': 0x0000000071,
|
||||||
|
'KeyR': 0x0000000072,
|
||||||
|
'KeyS': 0x0000000073,
|
||||||
|
'KeyT': 0x0000000074,
|
||||||
|
'KeyU': 0x0000000075,
|
||||||
|
'KeyV': 0x0000000076,
|
||||||
|
'KeyW': 0x0000000077,
|
||||||
|
'KeyX': 0x0000000078,
|
||||||
|
'KeyY': 0x0000000079,
|
||||||
|
'KeyZ': 0x000000007a,
|
||||||
|
'BraceLeft': 0x000000007b,
|
||||||
|
'Bar': 0x000000007c,
|
||||||
|
'BraceRight': 0x000000007d,
|
||||||
|
'Tilde': 0x000000007e,
|
||||||
|
'Delete': 0x000000007f,
|
||||||
|
'Accel': 0x0000000101,
|
||||||
|
'AltGraph': 0x0000000103,
|
||||||
|
'CapsLock': 0x0000000104,
|
||||||
|
'Fn': 0x0000000106,
|
||||||
|
'FnLock': 0x0000000107,
|
||||||
|
'Hyper': 0x0000000108,
|
||||||
|
'NumLock': 0x000000010a,
|
||||||
|
'ScrollLock': 0x000000010c,
|
||||||
|
'Super': 0x000000010e,
|
||||||
|
'Symbol': 0x000000010f,
|
||||||
|
'SymbolLock': 0x0000000110,
|
||||||
|
'ShiftLevel5': 0x0000000111,
|
||||||
|
'AltGraphLatch': 0x0000000112,
|
||||||
|
'ArrowDown': 0x0000000301,
|
||||||
|
'ArrowLeft': 0x0000000302,
|
||||||
|
'ArrowRight': 0x0000000303,
|
||||||
|
'ArrowUp': 0x0000000304,
|
||||||
|
'End': 0x0000000305,
|
||||||
|
'Home': 0x0000000306,
|
||||||
|
'PageDown': 0x0000000307,
|
||||||
|
'PageUp': 0x0000000308,
|
||||||
|
'Clear': 0x0000000401,
|
||||||
|
'Copy': 0x0000000402,
|
||||||
|
'CrSel': 0x0000000403,
|
||||||
|
'Cut': 0x0000000404,
|
||||||
|
'EraseEof': 0x0000000405,
|
||||||
|
'ExSel': 0x0000000406,
|
||||||
|
'Insert': 0x0000000407,
|
||||||
|
'Paste': 0x0000000408,
|
||||||
|
'Redo': 0x0000000409,
|
||||||
|
'Undo': 0x000000040a,
|
||||||
|
'Accept': 0x0000000501,
|
||||||
|
'Again': 0x0000000502,
|
||||||
|
'Attn': 0x0000000503,
|
||||||
|
'Cancel': 0x0000000504,
|
||||||
|
'ContextMenu': 0x0000000505,
|
||||||
|
'Execute': 0x0000000506,
|
||||||
|
'Find': 0x0000000507,
|
||||||
|
'Help': 0x0000000508,
|
||||||
|
'Pause': 0x0000000509,
|
||||||
|
'Play': 0x000000050a,
|
||||||
|
'Props': 0x000000050b,
|
||||||
|
'Select': 0x000000050c,
|
||||||
|
'ZoomIn': 0x000000050d,
|
||||||
|
'ZoomOut': 0x000000050e,
|
||||||
|
'BrightnessDown': 0x0000000601,
|
||||||
|
'BrightnessUp': 0x0000000602,
|
||||||
|
'Camera': 0x0000000603,
|
||||||
|
'Eject': 0x0000000604,
|
||||||
|
'LogOff': 0x0000000605,
|
||||||
|
'Power': 0x0000000606,
|
||||||
|
'PowerOff': 0x0000000607,
|
||||||
|
'PrintScreen': 0x0000000608,
|
||||||
|
'Hibernate': 0x0000000609,
|
||||||
|
'Standby': 0x000000060a,
|
||||||
|
'WakeUp': 0x000000060b,
|
||||||
|
'AllCandidates': 0x0000000701,
|
||||||
|
'Alphanumeric': 0x0000000702,
|
||||||
|
'CodeInput': 0x0000000703,
|
||||||
|
'Compose': 0x0000000704,
|
||||||
|
'Convert': 0x0000000705,
|
||||||
|
'FinalMode': 0x0000000706,
|
||||||
|
'GroupFirst': 0x0000000707,
|
||||||
|
'GroupLast': 0x0000000708,
|
||||||
|
'GroupNext': 0x0000000709,
|
||||||
|
'GroupPrevious': 0x000000070a,
|
||||||
|
'ModeChange': 0x000000070b,
|
||||||
|
'NextCandidate': 0x000000070c,
|
||||||
|
'NonConvert': 0x000000070d,
|
||||||
|
'PreviousCandidate': 0x000000070e,
|
||||||
|
'Process': 0x000000070f,
|
||||||
|
'SingleCandidate': 0x0000000710,
|
||||||
|
'HangulMode': 0x0000000711,
|
||||||
|
'HanjaMode': 0x0000000712,
|
||||||
|
'JunjaMode': 0x0000000713,
|
||||||
|
'Eisu': 0x0000000714,
|
||||||
|
'Hankaku': 0x0000000715,
|
||||||
|
'Hiragana': 0x0000000716,
|
||||||
|
'HiraganaKatakana': 0x0000000717,
|
||||||
|
'KanaMode': 0x0000000718,
|
||||||
|
'KanjiMode': 0x0000000719,
|
||||||
|
'Katakana': 0x000000071a,
|
||||||
|
'Romaji': 0x000000071b,
|
||||||
|
'Zenkaku': 0x000000071c,
|
||||||
|
'ZenkakuHankaku': 0x000000071d,
|
||||||
|
'F1': 0x0000000801,
|
||||||
|
'F2': 0x0000000802,
|
||||||
|
'F3': 0x0000000803,
|
||||||
|
'F4': 0x0000000804,
|
||||||
|
'F5': 0x0000000805,
|
||||||
|
'F6': 0x0000000806,
|
||||||
|
'F7': 0x0000000807,
|
||||||
|
'F8': 0x0000000808,
|
||||||
|
'F9': 0x0000000809,
|
||||||
|
'F10': 0x000000080a,
|
||||||
|
'F11': 0x000000080b,
|
||||||
|
'F12': 0x000000080c,
|
||||||
|
'F13': 0x000000080d,
|
||||||
|
'F14': 0x000000080e,
|
||||||
|
'F15': 0x000000080f,
|
||||||
|
'F16': 0x0000000810,
|
||||||
|
'F17': 0x0000000811,
|
||||||
|
'F18': 0x0000000812,
|
||||||
|
'F19': 0x0000000813,
|
||||||
|
'F20': 0x0000000814,
|
||||||
|
'F21': 0x0000000815,
|
||||||
|
'F22': 0x0000000816,
|
||||||
|
'F23': 0x0000000817,
|
||||||
|
'F24': 0x0000000818,
|
||||||
|
'Soft1': 0x0000000901,
|
||||||
|
'Soft2': 0x0000000902,
|
||||||
|
'Soft3': 0x0000000903,
|
||||||
|
'Soft4': 0x0000000904,
|
||||||
|
'Soft5': 0x0000000905,
|
||||||
|
'Soft6': 0x0000000906,
|
||||||
|
'Soft7': 0x0000000907,
|
||||||
|
'Soft8': 0x0000000908,
|
||||||
|
'Close': 0x0000000a01,
|
||||||
|
'MailForward': 0x0000000a02,
|
||||||
|
'MailReply': 0x0000000a03,
|
||||||
|
'MailSend': 0x0000000a04,
|
||||||
|
'MediaPlayPause': 0x0000000a05,
|
||||||
|
'MediaStop': 0x0000000a07,
|
||||||
|
'MediaTrackNext': 0x0000000a08,
|
||||||
|
'MediaTrackPrevious': 0x0000000a09,
|
||||||
|
'New': 0x0000000a0a,
|
||||||
|
'Open': 0x0000000a0b,
|
||||||
|
'Print': 0x0000000a0c,
|
||||||
|
'Save': 0x0000000a0d,
|
||||||
|
'SpellCheck': 0x0000000a0e,
|
||||||
|
'AudioVolumeDown': 0x0000000a0f,
|
||||||
|
'AudioVolumeUp': 0x0000000a10,
|
||||||
|
'AudioVolumeMute': 0x0000000a11,
|
||||||
|
'LaunchApplication2': 0x0000000b01,
|
||||||
|
'LaunchCalendar': 0x0000000b02,
|
||||||
|
'LaunchMail': 0x0000000b03,
|
||||||
|
'LaunchMediaPlayer': 0x0000000b04,
|
||||||
|
'LaunchMusicPlayer': 0x0000000b05,
|
||||||
|
'LaunchApplication1': 0x0000000b06,
|
||||||
|
'LaunchScreenSaver': 0x0000000b07,
|
||||||
|
'LaunchSpreadsheet': 0x0000000b08,
|
||||||
|
'LaunchWebBrowser': 0x0000000b09,
|
||||||
|
'LaunchWebCam': 0x0000000b0a,
|
||||||
|
'LaunchWordProcessor': 0x0000000b0b,
|
||||||
|
'LaunchContacts': 0x0000000b0c,
|
||||||
|
'LaunchPhone': 0x0000000b0d,
|
||||||
|
'LaunchAssistant': 0x0000000b0e,
|
||||||
|
'LaunchControlPanel': 0x0000000b0f,
|
||||||
|
'BrowserBack': 0x0000000c01,
|
||||||
|
'BrowserFavorites': 0x0000000c02,
|
||||||
|
'BrowserForward': 0x0000000c03,
|
||||||
|
'BrowserHome': 0x0000000c04,
|
||||||
|
'BrowserRefresh': 0x0000000c05,
|
||||||
|
'BrowserSearch': 0x0000000c06,
|
||||||
|
'BrowserStop': 0x0000000c07,
|
||||||
|
'AudioBalanceLeft': 0x0000000d01,
|
||||||
|
'AudioBalanceRight': 0x0000000d02,
|
||||||
|
'AudioBassBoostDown': 0x0000000d03,
|
||||||
|
'AudioBassBoostUp': 0x0000000d04,
|
||||||
|
'AudioFaderFront': 0x0000000d05,
|
||||||
|
'AudioFaderRear': 0x0000000d06,
|
||||||
|
'AudioSurroundModeNext': 0x0000000d07,
|
||||||
|
'AVRInput': 0x0000000d08,
|
||||||
|
'AVRPower': 0x0000000d09,
|
||||||
|
'ChannelDown': 0x0000000d0a,
|
||||||
|
'ChannelUp': 0x0000000d0b,
|
||||||
|
'ColorF0Red': 0x0000000d0c,
|
||||||
|
'ColorF1Green': 0x0000000d0d,
|
||||||
|
'ColorF2Yellow': 0x0000000d0e,
|
||||||
|
'ColorF3Blue': 0x0000000d0f,
|
||||||
|
'ColorF4Grey': 0x0000000d10,
|
||||||
|
'ColorF5Brown': 0x0000000d11,
|
||||||
|
'ClosedCaptionToggle': 0x0000000d12,
|
||||||
|
'Dimmer': 0x0000000d13,
|
||||||
|
'DisplaySwap': 0x0000000d14,
|
||||||
|
'Exit': 0x0000000d15,
|
||||||
|
'FavoriteClear0': 0x0000000d16,
|
||||||
|
'FavoriteClear1': 0x0000000d17,
|
||||||
|
'FavoriteClear2': 0x0000000d18,
|
||||||
|
'FavoriteClear3': 0x0000000d19,
|
||||||
|
'FavoriteRecall0': 0x0000000d1a,
|
||||||
|
'FavoriteRecall1': 0x0000000d1b,
|
||||||
|
'FavoriteRecall2': 0x0000000d1c,
|
||||||
|
'FavoriteRecall3': 0x0000000d1d,
|
||||||
|
'FavoriteStore0': 0x0000000d1e,
|
||||||
|
'FavoriteStore1': 0x0000000d1f,
|
||||||
|
'FavoriteStore2': 0x0000000d20,
|
||||||
|
'FavoriteStore3': 0x0000000d21,
|
||||||
|
'Guide': 0x0000000d22,
|
||||||
|
'GuideNextDay': 0x0000000d23,
|
||||||
|
'GuidePreviousDay': 0x0000000d24,
|
||||||
|
'Info': 0x0000000d25,
|
||||||
|
'InstantReplay': 0x0000000d26,
|
||||||
|
'Link': 0x0000000d27,
|
||||||
|
'ListProgram': 0x0000000d28,
|
||||||
|
'LiveContent': 0x0000000d29,
|
||||||
|
'Lock': 0x0000000d2a,
|
||||||
|
'MediaApps': 0x0000000d2b,
|
||||||
|
'MediaFastForward': 0x0000000d2c,
|
||||||
|
'MediaLast': 0x0000000d2d,
|
||||||
|
'MediaPause': 0x0000000d2e,
|
||||||
|
'MediaPlay': 0x0000000d2f,
|
||||||
|
'MediaRecord': 0x0000000d30,
|
||||||
|
'MediaRewind': 0x0000000d31,
|
||||||
|
'MediaSkip': 0x0000000d32,
|
||||||
|
'NextFavoriteChannel': 0x0000000d33,
|
||||||
|
'NextUserProfile': 0x0000000d34,
|
||||||
|
'OnDemand': 0x0000000d35,
|
||||||
|
'PinPDown': 0x0000000d36,
|
||||||
|
'PinPMove': 0x0000000d37,
|
||||||
|
'PinPToggle': 0x0000000d38,
|
||||||
|
'PinPUp': 0x0000000d39,
|
||||||
|
'PlaySpeedDown': 0x0000000d3a,
|
||||||
|
'PlaySpeedReset': 0x0000000d3b,
|
||||||
|
'PlaySpeedUp': 0x0000000d3c,
|
||||||
|
'RandomToggle': 0x0000000d3d,
|
||||||
|
'RcLowBattery': 0x0000000d3e,
|
||||||
|
'RecordSpeedNext': 0x0000000d3f,
|
||||||
|
'RfBypass': 0x0000000d40,
|
||||||
|
'ScanChannelsToggle': 0x0000000d41,
|
||||||
|
'ScreenModeNext': 0x0000000d42,
|
||||||
|
'Settings': 0x0000000d43,
|
||||||
|
'SplitScreenToggle': 0x0000000d44,
|
||||||
|
'STBInput': 0x0000000d45,
|
||||||
|
'STBPower': 0x0000000d46,
|
||||||
|
'Subtitle': 0x0000000d47,
|
||||||
|
'Teletext': 0x0000000d48,
|
||||||
|
'TV': 0x0000000d49,
|
||||||
|
'TVInput': 0x0000000d4a,
|
||||||
|
'TVPower': 0x0000000d4b,
|
||||||
|
'VideoModeNext': 0x0000000d4c,
|
||||||
|
'Wink': 0x0000000d4d,
|
||||||
|
'ZoomToggle': 0x0000000d4e,
|
||||||
|
'DVR': 0x0000000d4f,
|
||||||
|
'MediaAudioTrack': 0x0000000d50,
|
||||||
|
'MediaSkipBackward': 0x0000000d51,
|
||||||
|
'MediaSkipForward': 0x0000000d52,
|
||||||
|
'MediaStepBackward': 0x0000000d53,
|
||||||
|
'MediaStepForward': 0x0000000d54,
|
||||||
|
'MediaTopMenu': 0x0000000d55,
|
||||||
|
'NavigateIn': 0x0000000d56,
|
||||||
|
'NavigateNext': 0x0000000d57,
|
||||||
|
'NavigateOut': 0x0000000d58,
|
||||||
|
'NavigatePrevious': 0x0000000d59,
|
||||||
|
'Pairing': 0x0000000d5a,
|
||||||
|
'MediaClose': 0x0000000d5b,
|
||||||
|
'AudioBassBoostToggle': 0x0000000e02,
|
||||||
|
'AudioTrebleDown': 0x0000000e04,
|
||||||
|
'AudioTrebleUp': 0x0000000e05,
|
||||||
|
'MicrophoneToggle': 0x0000000e06,
|
||||||
|
'MicrophoneVolumeDown': 0x0000000e07,
|
||||||
|
'MicrophoneVolumeUp': 0x0000000e08,
|
||||||
|
'MicrophoneVolumeMute': 0x0000000e09,
|
||||||
|
'SpeechCorrectionList': 0x0000000f01,
|
||||||
|
'SpeechInputToggle': 0x0000000f02,
|
||||||
|
'AppSwitch': 0x0000001001,
|
||||||
|
'Call': 0x0000001002,
|
||||||
|
'CameraFocus': 0x0000001003,
|
||||||
|
'EndCall': 0x0000001004,
|
||||||
|
'GoBack': 0x0000001005,
|
||||||
|
'GoHome': 0x0000001006,
|
||||||
|
'HeadsetHook': 0x0000001007,
|
||||||
|
'LastNumberRedial': 0x0000001008,
|
||||||
|
'Notification': 0x0000001009,
|
||||||
|
'MannerMode': 0x000000100a,
|
||||||
|
'VoiceDial': 0x000000100b,
|
||||||
|
'TV3DMode': 0x0000001101,
|
||||||
|
'TVAntennaCable': 0x0000001102,
|
||||||
|
'TVAudioDescription': 0x0000001103,
|
||||||
|
'TVAudioDescriptionMixDown': 0x0000001104,
|
||||||
|
'TVAudioDescriptionMixUp': 0x0000001105,
|
||||||
|
'TVContentsMenu': 0x0000001106,
|
||||||
|
'TVDataService': 0x0000001107,
|
||||||
|
'TVInputComponent1': 0x0000001108,
|
||||||
|
'TVInputComponent2': 0x0000001109,
|
||||||
|
'TVInputComposite1': 0x000000110a,
|
||||||
|
'TVInputComposite2': 0x000000110b,
|
||||||
|
'TVInputHDMI1': 0x000000110c,
|
||||||
|
'TVInputHDMI2': 0x000000110d,
|
||||||
|
'TVInputHDMI3': 0x000000110e,
|
||||||
|
'TVInputHDMI4': 0x000000110f,
|
||||||
|
'TVInputVGA1': 0x0000001110,
|
||||||
|
'TVMediaContext': 0x0000001111,
|
||||||
|
'TVNetwork': 0x0000001112,
|
||||||
|
'TVNumberEntry': 0x0000001113,
|
||||||
|
'TVRadioService': 0x0000001114,
|
||||||
|
'TVSatellite': 0x0000001115,
|
||||||
|
'TVSatelliteBS': 0x0000001116,
|
||||||
|
'TVSatelliteCS': 0x0000001117,
|
||||||
|
'TVSatelliteToggle': 0x0000001118,
|
||||||
|
'TVTerrestrialAnalog': 0x0000001119,
|
||||||
|
'TVTerrestrialDigital': 0x000000111a,
|
||||||
|
'TVTimer': 0x000000111b,
|
||||||
|
'Key11': 0x0000001201,
|
||||||
|
'Key12': 0x0000001202,
|
||||||
|
'GameButton1': 0x000005ff01,
|
||||||
|
'GameButton2': 0x000005ff02,
|
||||||
|
'GameButton3': 0x000005ff03,
|
||||||
|
'GameButton4': 0x000005ff04,
|
||||||
|
'GameButton5': 0x000005ff05,
|
||||||
|
'GameButton6': 0x000005ff06,
|
||||||
|
'GameButton7': 0x000005ff07,
|
||||||
|
'GameButton8': 0x000005ff08,
|
||||||
|
'GameButton9': 0x000005ff09,
|
||||||
|
'GameButton10': 0x000005ff0a,
|
||||||
|
'GameButton11': 0x000005ff0b,
|
||||||
|
'GameButton12': 0x000005ff0c,
|
||||||
|
'GameButton13': 0x000005ff0d,
|
||||||
|
'GameButton14': 0x000005ff0e,
|
||||||
|
'GameButton15': 0x000005ff0f,
|
||||||
|
'GameButton16': 0x000005ff10,
|
||||||
|
'GameButtonA': 0x000005ff11,
|
||||||
|
'GameButtonB': 0x000005ff12,
|
||||||
|
'GameButtonC': 0x000005ff13,
|
||||||
|
'GameButtonLeft1': 0x000005ff14,
|
||||||
|
'GameButtonLeft2': 0x000005ff15,
|
||||||
|
'GameButtonMode': 0x000005ff16,
|
||||||
|
'GameButtonRight1': 0x000005ff17,
|
||||||
|
'GameButtonRight2': 0x000005ff18,
|
||||||
|
'GameButtonSelect': 0x000005ff19,
|
||||||
|
'GameButtonStart': 0x000005ff1a,
|
||||||
|
'GameButtonThumbLeft': 0x000005ff1b,
|
||||||
|
'GameButtonThumbRight': 0x000005ff1c,
|
||||||
|
'GameButtonX': 0x000005ff1d,
|
||||||
|
'GameButtonY': 0x000005ff1e,
|
||||||
|
'GameButtonZ': 0x000005ff1f,
|
||||||
|
'Suspend': 0x0100000014,
|
||||||
|
'Resume': 0x0100000015,
|
||||||
|
'Sleep': 0x0100010082,
|
||||||
|
'IntlBackslash': 0x0100070064,
|
||||||
|
'IntlRo': 0x0100070087,
|
||||||
|
'IntlYen': 0x0100070089,
|
||||||
|
'Lang1': 0x0100070090,
|
||||||
|
'Lang2': 0x0100070091,
|
||||||
|
'Lang3': 0x0100070092,
|
||||||
|
'Lang4': 0x0100070093,
|
||||||
|
'Lang5': 0x0100070094,
|
||||||
|
'Abort': 0x010007009b,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Maps Web KeyboardEvent codes to the matching PhysicalKeyboardKey USB HID code.
|
||||||
|
const Map<String, int> kWebToPhysicalKey = <String, int>{
|
||||||
|
'None': 0x00000000,
|
||||||
|
'Hyper': 0x00000010,
|
||||||
|
'Super': 0x00000011,
|
||||||
|
'FnLock': 0x00000013,
|
||||||
|
'Suspend': 0x00000014,
|
||||||
|
'Resume': 0x00000015,
|
||||||
|
'Turbo': 0x00000016,
|
||||||
|
'PrivacyScreenToggle': 0x00000017,
|
||||||
|
'Sleep': 0x00010082,
|
||||||
|
'WakeUp': 0x00010083,
|
||||||
|
'DisplayToggleIntExt': 0x000100b5,
|
||||||
|
'KeyA': 0x00070004,
|
||||||
|
'KeyB': 0x00070005,
|
||||||
|
'KeyC': 0x00070006,
|
||||||
|
'KeyD': 0x00070007,
|
||||||
|
'KeyE': 0x00070008,
|
||||||
|
'KeyF': 0x00070009,
|
||||||
|
'KeyG': 0x0007000a,
|
||||||
|
'KeyH': 0x0007000b,
|
||||||
|
'KeyI': 0x0007000c,
|
||||||
|
'KeyJ': 0x0007000d,
|
||||||
|
'KeyK': 0x0007000e,
|
||||||
|
'KeyL': 0x0007000f,
|
||||||
|
'KeyM': 0x00070010,
|
||||||
|
'KeyN': 0x00070011,
|
||||||
|
'KeyO': 0x00070012,
|
||||||
|
'KeyP': 0x00070013,
|
||||||
|
'KeyQ': 0x00070014,
|
||||||
|
'KeyR': 0x00070015,
|
||||||
|
'KeyS': 0x00070016,
|
||||||
|
'KeyT': 0x00070017,
|
||||||
|
'KeyU': 0x00070018,
|
||||||
|
'KeyV': 0x00070019,
|
||||||
|
'KeyW': 0x0007001a,
|
||||||
|
'KeyX': 0x0007001b,
|
||||||
|
'KeyY': 0x0007001c,
|
||||||
|
'KeyZ': 0x0007001d,
|
||||||
|
'Digit1': 0x0007001e,
|
||||||
|
'Digit2': 0x0007001f,
|
||||||
|
'Digit3': 0x00070020,
|
||||||
|
'Digit4': 0x00070021,
|
||||||
|
'Digit5': 0x00070022,
|
||||||
|
'Digit6': 0x00070023,
|
||||||
|
'Digit7': 0x00070024,
|
||||||
|
'Digit8': 0x00070025,
|
||||||
|
'Digit9': 0x00070026,
|
||||||
|
'Digit0': 0x00070027,
|
||||||
|
'Enter': 0x00070028,
|
||||||
|
'Escape': 0x00070029,
|
||||||
|
'Backspace': 0x0007002a,
|
||||||
|
'Tab': 0x0007002b,
|
||||||
|
'Space': 0x0007002c,
|
||||||
|
'Minus': 0x0007002d,
|
||||||
|
'Equal': 0x0007002e,
|
||||||
|
'BracketLeft': 0x0007002f,
|
||||||
|
'BracketRight': 0x00070030,
|
||||||
|
'Backslash': 0x00070031,
|
||||||
|
'Semicolon': 0x00070033,
|
||||||
|
'Quote': 0x00070034,
|
||||||
|
'Backquote': 0x00070035,
|
||||||
|
'Comma': 0x00070036,
|
||||||
|
'Period': 0x00070037,
|
||||||
|
'Slash': 0x00070038,
|
||||||
|
'CapsLock': 0x00070039,
|
||||||
|
'F1': 0x0007003a,
|
||||||
|
'F2': 0x0007003b,
|
||||||
|
'F3': 0x0007003c,
|
||||||
|
'F4': 0x0007003d,
|
||||||
|
'F5': 0x0007003e,
|
||||||
|
'F6': 0x0007003f,
|
||||||
|
'F7': 0x00070040,
|
||||||
|
'F8': 0x00070041,
|
||||||
|
'F9': 0x00070042,
|
||||||
|
'F10': 0x00070043,
|
||||||
|
'F11': 0x00070044,
|
||||||
|
'F12': 0x00070045,
|
||||||
|
'PrintScreen': 0x00070046,
|
||||||
|
'ScrollLock': 0x00070047,
|
||||||
|
'Pause': 0x00070048,
|
||||||
|
'Insert': 0x00070049,
|
||||||
|
'Home': 0x0007004a,
|
||||||
|
'PageUp': 0x0007004b,
|
||||||
|
'Delete': 0x0007004c,
|
||||||
|
'End': 0x0007004d,
|
||||||
|
'PageDown': 0x0007004e,
|
||||||
|
'ArrowRight': 0x0007004f,
|
||||||
|
'ArrowLeft': 0x00070050,
|
||||||
|
'ArrowDown': 0x00070051,
|
||||||
|
'ArrowUp': 0x00070052,
|
||||||
|
'NumLock': 0x00070053,
|
||||||
|
'NumpadDivide': 0x00070054,
|
||||||
|
'NumpadMultiply': 0x00070055,
|
||||||
|
'NumpadSubtract': 0x00070056,
|
||||||
|
'NumpadAdd': 0x00070057,
|
||||||
|
'NumpadEnter': 0x00070058,
|
||||||
|
'Numpad1': 0x00070059,
|
||||||
|
'Numpad2': 0x0007005a,
|
||||||
|
'Numpad3': 0x0007005b,
|
||||||
|
'Numpad4': 0x0007005c,
|
||||||
|
'Numpad5': 0x0007005d,
|
||||||
|
'Numpad6': 0x0007005e,
|
||||||
|
'Numpad7': 0x0007005f,
|
||||||
|
'Numpad8': 0x00070060,
|
||||||
|
'Numpad9': 0x00070061,
|
||||||
|
'Numpad0': 0x00070062,
|
||||||
|
'NumpadDecimal': 0x00070063,
|
||||||
|
'IntlBackslash': 0x00070064,
|
||||||
|
'ContextMenu': 0x00070065,
|
||||||
|
'Power': 0x00070066,
|
||||||
|
'NumpadEqual': 0x00070067,
|
||||||
|
'F13': 0x00070068,
|
||||||
|
'F14': 0x00070069,
|
||||||
|
'F15': 0x0007006a,
|
||||||
|
'F16': 0x0007006b,
|
||||||
|
'F17': 0x0007006c,
|
||||||
|
'F18': 0x0007006d,
|
||||||
|
'F19': 0x0007006e,
|
||||||
|
'F20': 0x0007006f,
|
||||||
|
'F21': 0x00070070,
|
||||||
|
'F22': 0x00070071,
|
||||||
|
'F23': 0x00070072,
|
||||||
|
'F24': 0x00070073,
|
||||||
|
'Open': 0x00070074,
|
||||||
|
'Help': 0x00070075,
|
||||||
|
'Select': 0x00070077,
|
||||||
|
'Again': 0x00070079,
|
||||||
|
'Undo': 0x0007007a,
|
||||||
|
'Cut': 0x0007007b,
|
||||||
|
'Copy': 0x0007007c,
|
||||||
|
'Paste': 0x0007007d,
|
||||||
|
'Find': 0x0007007e,
|
||||||
|
'AudioVolumeMute': 0x0007007f,
|
||||||
|
'AudioVolumeUp': 0x00070080,
|
||||||
|
'AudioVolumeDown': 0x00070081,
|
||||||
|
'NumpadComma': 0x00070085,
|
||||||
|
'IntlRo': 0x00070087,
|
||||||
|
'KanaMode': 0x00070088,
|
||||||
|
'IntlYen': 0x00070089,
|
||||||
|
'Convert': 0x0007008a,
|
||||||
|
'NonConvert': 0x0007008b,
|
||||||
|
'Lang1': 0x00070090,
|
||||||
|
'Lang2': 0x00070091,
|
||||||
|
'Lang3': 0x00070092,
|
||||||
|
'Lang4': 0x00070093,
|
||||||
|
'Lang5': 0x00070094,
|
||||||
|
'Abort': 0x0007009b,
|
||||||
|
'Props': 0x000700a3,
|
||||||
|
'NumpadParenLeft': 0x000700b6,
|
||||||
|
'NumpadParenRight': 0x000700b7,
|
||||||
|
'NumpadBackspace': 0x000700bb,
|
||||||
|
'NumpadMemoryStore': 0x000700d0,
|
||||||
|
'NumpadMemoryRecall': 0x000700d1,
|
||||||
|
'NumpadMemoryClear': 0x000700d2,
|
||||||
|
'NumpadMemoryAdd': 0x000700d3,
|
||||||
|
'NumpadMemorySubtract': 0x000700d4,
|
||||||
|
'NumpadClear': 0x000700d8,
|
||||||
|
'NumpadClearEntry': 0x000700d9,
|
||||||
|
'ControlLeft': 0x000700e0,
|
||||||
|
'ShiftLeft': 0x000700e1,
|
||||||
|
'AltLeft': 0x000700e2,
|
||||||
|
'MetaLeft': 0x000700e3,
|
||||||
|
'ControlRight': 0x000700e4,
|
||||||
|
'ShiftRight': 0x000700e5,
|
||||||
|
'AltRight': 0x000700e6,
|
||||||
|
'MetaRight': 0x000700e7,
|
||||||
|
'BrightnessUp': 0x000c006f,
|
||||||
|
'BrightnessDown': 0x000c0070,
|
||||||
|
'MediaPlay': 0x000c00b0,
|
||||||
|
'MediaPause': 0x000c00b1,
|
||||||
|
'MediaRecord': 0x000c00b2,
|
||||||
|
'MediaFastForward': 0x000c00b3,
|
||||||
|
'MediaRewind': 0x000c00b4,
|
||||||
|
'MediaTrackNext': 0x000c00b5,
|
||||||
|
'MediaTrackPrevious': 0x000c00b6,
|
||||||
|
'MediaStop': 0x000c00b7,
|
||||||
|
'Eject': 0x000c00b8,
|
||||||
|
'MediaPlayPause': 0x000c00cd,
|
||||||
|
'MediaSelect': 0x000c0183,
|
||||||
|
'LaunchMail': 0x000c018a,
|
||||||
|
'LaunchApp2': 0x000c0192,
|
||||||
|
'LaunchApp1': 0x000c0194,
|
||||||
|
'LaunchControlPanel': 0x000c019f,
|
||||||
|
'SelectTask': 0x000c01a2,
|
||||||
|
'LaunchScreenSaver': 0x000c01b1,
|
||||||
|
'LaunchAssistant': 0x000c01cb,
|
||||||
|
'BrowserSearch': 0x000c0221,
|
||||||
|
'BrowserHome': 0x000c0223,
|
||||||
|
'BrowserBack': 0x000c0224,
|
||||||
|
'BrowserForward': 0x000c0225,
|
||||||
|
'BrowserStop': 0x000c0226,
|
||||||
|
'BrowserRefresh': 0x000c0227,
|
||||||
|
'BrowserFavorites': 0x000c022a,
|
||||||
|
'ZoomToggle': 0x000c0232,
|
||||||
|
'MailReply': 0x000c0289,
|
||||||
|
'MailForward': 0x000c028b,
|
||||||
|
'MailSend': 0x000c028c,
|
||||||
|
'KeyboardLayoutSelect': 0x000c029d,
|
||||||
|
'ShowAllWindows': 0x000c029f,
|
||||||
|
'GameButton1': 0x0005ff01,
|
||||||
|
'GameButton2': 0x0005ff02,
|
||||||
|
'GameButton3': 0x0005ff03,
|
||||||
|
'GameButton4': 0x0005ff04,
|
||||||
|
'GameButton5': 0x0005ff05,
|
||||||
|
'GameButton6': 0x0005ff06,
|
||||||
|
'GameButton7': 0x0005ff07,
|
||||||
|
'GameButton8': 0x0005ff08,
|
||||||
|
'GameButton9': 0x0005ff09,
|
||||||
|
'GameButton10': 0x0005ff0a,
|
||||||
|
'GameButton11': 0x0005ff0b,
|
||||||
|
'GameButton12': 0x0005ff0c,
|
||||||
|
'GameButton13': 0x0005ff0d,
|
||||||
|
'GameButton14': 0x0005ff0e,
|
||||||
|
'GameButton15': 0x0005ff0f,
|
||||||
|
'GameButton16': 0x0005ff10,
|
||||||
|
'GameButtonA': 0x0005ff11,
|
||||||
|
'GameButtonB': 0x0005ff12,
|
||||||
|
'GameButtonC': 0x0005ff13,
|
||||||
|
'GameButtonLeft1': 0x0005ff14,
|
||||||
|
'GameButtonLeft2': 0x0005ff15,
|
||||||
|
'GameButtonMode': 0x0005ff16,
|
||||||
|
'GameButtonRight1': 0x0005ff17,
|
||||||
|
'GameButtonRight2': 0x0005ff18,
|
||||||
|
'GameButtonSelect': 0x0005ff19,
|
||||||
|
'GameButtonStart': 0x0005ff1a,
|
||||||
|
'GameButtonThumbLeft': 0x0005ff1b,
|
||||||
|
'GameButtonThumbRight': 0x0005ff1c,
|
||||||
|
'GameButtonX': 0x0005ff1d,
|
||||||
|
'GameButtonY': 0x0005ff1e,
|
||||||
|
'GameButtonZ': 0x0005ff1f,
|
||||||
|
'Fn': 0x00000012,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Maps Web KeyboardEvent keys to Flutter logical IDs that depend on locations.
|
||||||
|
///
|
||||||
|
/// `KeyboardEvent.location` is defined as:
|
||||||
|
///
|
||||||
|
/// * 0: Standard
|
||||||
|
/// * 1: Left
|
||||||
|
/// * 2: Right
|
||||||
|
/// * 3: Numpad
|
||||||
|
const Map<String, List<int?>> kWebLogicalLocationMap = <String, List<int?>>{
|
||||||
|
'0': <int?>[0x0000000030, null, null, 0x0200000030],
|
||||||
|
'1': <int?>[0x0000000031, null, null, 0x0200000031],
|
||||||
|
'2': <int?>[0x0000000032, null, null, 0x0200000032],
|
||||||
|
'3': <int?>[0x0000000033, null, null, 0x0200000033],
|
||||||
|
'4': <int?>[0x0000000034, null, null, 0x0200000034],
|
||||||
|
'5': <int?>[0x0000000035, null, null, 0x0200000035],
|
||||||
|
'6': <int?>[0x0000000036, null, null, 0x0200000036],
|
||||||
|
'7': <int?>[0x0000000037, null, null, 0x0200000037],
|
||||||
|
'8': <int?>[0x0000000038, null, null, 0x0200000038],
|
||||||
|
'9': <int?>[0x0000000039, null, null, 0x0200000039],
|
||||||
|
'.': <int?>[0x000000002e, null, null, 0x020000002e],
|
||||||
|
'Insert': <int?>[0x0000000407, null, null, 0x0200000030],
|
||||||
|
'End': <int?>[0x0000000305, null, null, 0x0200000031],
|
||||||
|
'ArrowDown': <int?>[0x0000000301, null, null, 0x0200000032],
|
||||||
|
'PageDown': <int?>[0x0000000307, null, null, 0x0200000033],
|
||||||
|
'ArrowLeft': <int?>[0x0000000302, null, null, 0x0200000034],
|
||||||
|
'Clear': <int?>[0x0000000401, null, null, 0x0200000035],
|
||||||
|
'ArrowRight': <int?>[0x0000000303, null, null, 0x0200000036],
|
||||||
|
'Home': <int?>[0x0000000306, null, null, 0x0200000037],
|
||||||
|
'ArrowUp': <int?>[0x0000000304, null, null, 0x0200000038],
|
||||||
|
'PageUp': <int?>[0x0000000308, null, null, 0x0200000039],
|
||||||
|
'Delete': <int?>[0x000000007f, null, null, 0x020000002e],
|
||||||
|
'/': <int?>[0x000000002f, null, null, 0x020000002f],
|
||||||
|
'*': <int?>[0x000000002a, null, null, 0x020000002a],
|
||||||
|
'-': <int?>[0x000000002d, null, null, 0x020000002d],
|
||||||
|
'+': <int?>[0x000000002b, null, null, 0x020000002b],
|
||||||
|
'Enter': <int?>[0x000000000d, null, null, 0x020000000d],
|
||||||
|
'Shift': <int?>[null, 0x030000010d, 0x040000010d, null],
|
||||||
|
'Control': <int?>[null, 0x0300000105, 0x0400000105, null],
|
||||||
|
'Alt': <int?>[null, 0x0300000102, 0x0400000102, null],
|
||||||
|
'Meta': <int?>[null, 0x0300000109, 0x0400000109, null],
|
||||||
|
};
|
@ -0,0 +1,481 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// @dart = 2.12
|
||||||
|
part of engine;
|
||||||
|
|
||||||
|
typedef _VoidCallback = void Function();
|
||||||
|
typedef ValueGetter<T> = T Function();
|
||||||
|
typedef _ModifierGetter = bool Function(FlutterHtmlKeyboardEvent event);
|
||||||
|
|
||||||
|
// Set this flag to true to see all the fired events in the console.
|
||||||
|
const bool _debugLogKeyEvents = false;
|
||||||
|
|
||||||
|
const int _kLogicalAltLeft = 0x0300000102;
|
||||||
|
const int _kLogicalAltRight = 0x0400000102;
|
||||||
|
const int _kLogicalControlLeft = 0x0300000105;
|
||||||
|
const int _kLogicalControlRight = 0x0400000105;
|
||||||
|
const int _kLogicalShiftLeft = 0x030000010d;
|
||||||
|
const int _kLogicalShiftRight = 0x040000010d;
|
||||||
|
const int _kLogicalMetaLeft = 0x0300000109;
|
||||||
|
const int _kLogicalMetaRight = 0x0400000109;
|
||||||
|
// Map logical keys for modifier keys to the functions that can get their
|
||||||
|
// modifier flag out of an event.
|
||||||
|
final Map<int, _ModifierGetter> _kLogicalKeyToModifierGetter = {
|
||||||
|
_kLogicalAltLeft: (FlutterHtmlKeyboardEvent event) => event.altKey,
|
||||||
|
_kLogicalAltRight: (FlutterHtmlKeyboardEvent event) => event.altKey,
|
||||||
|
_kLogicalControlLeft: (FlutterHtmlKeyboardEvent event) => event.ctrlKey,
|
||||||
|
_kLogicalControlRight: (FlutterHtmlKeyboardEvent event) => event.ctrlKey,
|
||||||
|
_kLogicalShiftLeft: (FlutterHtmlKeyboardEvent event) => event.shiftKey,
|
||||||
|
_kLogicalShiftRight: (FlutterHtmlKeyboardEvent event) => event.shiftKey,
|
||||||
|
_kLogicalMetaLeft: (FlutterHtmlKeyboardEvent event) => event.metaKey,
|
||||||
|
_kLogicalMetaRight: (FlutterHtmlKeyboardEvent event) => event.metaKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
// After a keydown is received, this is the duration we wait for a repeat event
|
||||||
|
// before we decide to synthesize a keyup event.
|
||||||
|
//
|
||||||
|
// On Linux and Windows, the typical ranges for keyboard repeat delay go up to
|
||||||
|
// 1000ms. On Mac, the range goes up to 2000ms.
|
||||||
|
const Duration _kKeydownCancelDurationNormal = Duration(milliseconds: 1000);
|
||||||
|
const Duration _kKeydownCancelDurationMacOs = Duration(milliseconds: 2000);
|
||||||
|
|
||||||
|
// ASCII for a, z, A, and Z
|
||||||
|
const int _kCharLowerA = 0x61;
|
||||||
|
const int _kCharLowerZ = 0x7a;
|
||||||
|
const int _kCharUpperA = 0x41;
|
||||||
|
const int _kCharUpperZ = 0x5a;
|
||||||
|
bool isAlphabet(int charCode) {
|
||||||
|
return (charCode >= _kCharLowerA && charCode <= _kCharLowerZ)
|
||||||
|
|| (charCode >= _kCharUpperA && charCode <= _kCharUpperZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
const String _kPhysicalCapsLock = 'CapsLock';
|
||||||
|
|
||||||
|
const String _kLogicalDead = 'Dead';
|
||||||
|
|
||||||
|
const int _kWebKeyIdPlane = 0x00800000000;
|
||||||
|
const int _kAutogeneratedMask = 0x10000000000;
|
||||||
|
|
||||||
|
// Bits in a Flutter logical event to generate the logical key for dead keys.
|
||||||
|
//
|
||||||
|
// Logical keys for dead keys are generated by annotating physical keys with
|
||||||
|
// modifiers (see `_getLogicalCode`).
|
||||||
|
const int _kDeadKeyCtrl = 0x100000000000;
|
||||||
|
const int _kDeadKeyShift = 0x200000000000;
|
||||||
|
const int _kDeadKeyAlt = 0x400000000000;
|
||||||
|
const int _kDeadKeyMeta = 0x800000000000;
|
||||||
|
|
||||||
|
typedef DispatchKeyData = bool Function(ui.KeyData data);
|
||||||
|
|
||||||
|
/// Converts a floating number timestamp (in milliseconds) to a [Duration] by
|
||||||
|
/// splitting it into two integer components: milliseconds + microseconds.
|
||||||
|
Duration _eventTimeStampToDuration(num milliseconds) {
|
||||||
|
final int ms = milliseconds.toInt();
|
||||||
|
final int micro = ((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt();
|
||||||
|
return Duration(milliseconds: ms, microseconds: micro);
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyboardBinding {
|
||||||
|
/// The singleton instance of this object.
|
||||||
|
static KeyboardBinding? get instance => _instance;
|
||||||
|
static KeyboardBinding? _instance;
|
||||||
|
|
||||||
|
static void initInstance(html.Element glassPaneElement) {
|
||||||
|
if (_instance == null) {
|
||||||
|
_instance = KeyboardBinding._(glassPaneElement);
|
||||||
|
assert(() {
|
||||||
|
registerHotRestartListener(_instance!._reset);
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardBinding._(this.glassPaneElement) {
|
||||||
|
_setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
final html.Element glassPaneElement;
|
||||||
|
late KeyboardConverter _converter;
|
||||||
|
final Map<String, html.EventListener> _listeners = <String, html.EventListener>{};
|
||||||
|
|
||||||
|
void _addEventListener(String eventName, html.EventListener handler) {
|
||||||
|
final html.EventListener loggedHandler = (html.Event event) {
|
||||||
|
if (_debugLogKeyEvents) {
|
||||||
|
print(event.type);
|
||||||
|
}
|
||||||
|
if (EngineSemanticsOwner.instance.receiveGlobalEvent(event)) {
|
||||||
|
return handler(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert(!_listeners.containsKey(eventName));
|
||||||
|
_listeners[eventName] = loggedHandler;
|
||||||
|
html.window.addEventListener(eventName, loggedHandler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove all active event listeners.
|
||||||
|
void _clearListeners() {
|
||||||
|
_listeners.forEach((String eventName, html.EventListener listener) {
|
||||||
|
html.window.removeEventListener(eventName, listener, true);
|
||||||
|
});
|
||||||
|
_listeners.clear();
|
||||||
|
}
|
||||||
|
bool _onKeyData(ui.KeyData data) {
|
||||||
|
bool? result;
|
||||||
|
// This callback is designed to be invoked synchronously. This is enforced
|
||||||
|
// by `result`, which starts null and is asserted non-null when returned.
|
||||||
|
EnginePlatformDispatcher.instance.invokeOnKeyData(data,
|
||||||
|
(bool handled) { result = handled; });
|
||||||
|
return result!;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setup() {
|
||||||
|
_addEventListener('keydown', (html.Event event) {
|
||||||
|
return _converter.handleEvent(FlutterHtmlKeyboardEvent(event as html.KeyboardEvent));
|
||||||
|
});
|
||||||
|
_addEventListener('keyup', (html.Event event) {
|
||||||
|
return _converter.handleEvent(FlutterHtmlKeyboardEvent(event as html.KeyboardEvent));
|
||||||
|
});
|
||||||
|
_converter = KeyboardConverter(_onKeyData, onMacOs: operatingSystem == OperatingSystem.macOs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reset() {
|
||||||
|
_clearListeners();
|
||||||
|
_converter.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncKeyboardDispatching {
|
||||||
|
AsyncKeyboardDispatching({
|
||||||
|
required this.keyData,
|
||||||
|
this.callback,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ui.KeyData keyData;
|
||||||
|
final _VoidCallback? callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper of [html.KeyboardEvent] with reduced methods delegated to the event
|
||||||
|
// for the convenience of testing.
|
||||||
|
class FlutterHtmlKeyboardEvent {
|
||||||
|
FlutterHtmlKeyboardEvent(this._event);
|
||||||
|
|
||||||
|
final html.KeyboardEvent _event;
|
||||||
|
|
||||||
|
String get type => _event.type;
|
||||||
|
String? get code => _event.code;
|
||||||
|
String? get key => _event.key;
|
||||||
|
bool? get repeat => _event.repeat;
|
||||||
|
int? get location => _event.location;
|
||||||
|
num? get timeStamp => _event.timeStamp;
|
||||||
|
bool get altKey => _event.altKey;
|
||||||
|
bool get ctrlKey => _event.ctrlKey;
|
||||||
|
bool get shiftKey => _event.shiftKey;
|
||||||
|
bool get metaKey => _event.metaKey;
|
||||||
|
|
||||||
|
bool getModifierState(String key) => _event.getModifierState(key);
|
||||||
|
void preventDefault() => _event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads [html.KeyboardEvent], then [dispatches ui.KeyData] accordingly.
|
||||||
|
//
|
||||||
|
// The events are read through [handleEvent], and dispatched through the
|
||||||
|
// [dispatchKeyData] as given in the constructor. Some key data might be
|
||||||
|
// dispatched asynchronously.
|
||||||
|
class KeyboardConverter {
|
||||||
|
KeyboardConverter(this.dispatchKeyData, {this.onMacOs = false});
|
||||||
|
|
||||||
|
final DispatchKeyData dispatchKeyData;
|
||||||
|
final bool onMacOs;
|
||||||
|
|
||||||
|
bool _disposed = false;
|
||||||
|
void dispose() {
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On macOS, CapsLock behaves differently in that, a keydown event occurs when
|
||||||
|
// the key is pressed and the light turns on, while a keyup event occurs when the
|
||||||
|
// key is pressed and the light turns off. Flutter considers both events as
|
||||||
|
// key down, and synthesizes immediate cancel events following them. The state
|
||||||
|
// of "whether CapsLock is on" should be accessed by "activeLocks".
|
||||||
|
bool _shouldSynthesizeCapsLockUp() {
|
||||||
|
return onMacOs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration get _keydownCancelDuration => onMacOs ? _kKeydownCancelDurationMacOs : _kKeydownCancelDurationNormal;
|
||||||
|
|
||||||
|
static int _getPhysicalCode(String code) {
|
||||||
|
return kWebToPhysicalKey[code] ?? (code.hashCode + _kWebKeyIdPlane + _kAutogeneratedMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _getModifierMask(FlutterHtmlKeyboardEvent event) {
|
||||||
|
final bool altDown = event.altKey;
|
||||||
|
final bool ctrlDown = event.ctrlKey;
|
||||||
|
final bool shiftDown = event.shiftKey;
|
||||||
|
final bool metaDown = event.metaKey;
|
||||||
|
return (altDown ? _kDeadKeyAlt : 0) +
|
||||||
|
(ctrlDown ? _kDeadKeyCtrl : 0) +
|
||||||
|
(shiftDown ? _kDeadKeyShift : 0) +
|
||||||
|
(metaDown ? _kDeadKeyMeta : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether `event.key` should be considered a key name.
|
||||||
|
//
|
||||||
|
// The `event.key` can either be a key name or the printable character. If the
|
||||||
|
// first character is an alphabet, it must be either 'A' to 'Z' ( and return
|
||||||
|
// true), or be a key name (and return false). Otherwise, return true.
|
||||||
|
static bool _eventKeyIsKeyname(String key) {
|
||||||
|
assert(key.length > 0);
|
||||||
|
return isAlphabet(key.codeUnitAt(0)) && key.length > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _characterToLogicalKey(String key) {
|
||||||
|
// Assume the length being <= 2 to be sufficient in all cases. If not,
|
||||||
|
// extend the algorithm.
|
||||||
|
assert(key.length <= 2);
|
||||||
|
int result = key.codeUnitAt(0) & 0xffff;
|
||||||
|
if (key.length == 2) {
|
||||||
|
result += key.codeUnitAt(1) << 16;
|
||||||
|
}
|
||||||
|
// Convert upper letters to lower letters
|
||||||
|
if (result >= _kCharUpperA && result <= _kCharUpperZ) {
|
||||||
|
result = result + _kCharLowerA - _kCharUpperA;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _deadKeyToLogicalKey(int physicalKey, FlutterHtmlKeyboardEvent event) {
|
||||||
|
// 'Dead' is used to represent dead keys, such as a diacritic to the
|
||||||
|
// following base letter (such as Option-e results in ´).
|
||||||
|
//
|
||||||
|
// Assume they can be told apart with the physical key and the modifiers
|
||||||
|
// pressed.
|
||||||
|
return physicalKey + _getModifierMask(event) + _kWebKeyIdPlane + _kAutogeneratedMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _otherLogicalKey(String key) {
|
||||||
|
return kWebToLogicalKey[key] ?? (key.hashCode + _kWebKeyIdPlane + _kAutogeneratedMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map from pressed physical key to corresponding pressed logical key.
|
||||||
|
//
|
||||||
|
// Multiple physical keys can be mapped to the same logical key, usually due
|
||||||
|
// to positioned keys (left/right/numpad) or multiple keyboards.
|
||||||
|
final Map<int, int> _pressingRecords = <int, int>{};
|
||||||
|
|
||||||
|
// Schedule the dispatching of an event in the future. The `callback` will
|
||||||
|
// invoked before that.
|
||||||
|
//
|
||||||
|
// Returns a callback that cancels the schedule. Disposal of
|
||||||
|
// `KeyBoardConverter` also cancels the shedule automatically.
|
||||||
|
_VoidCallback _scheduleAsyncEvent(Duration duration, ValueGetter<ui.KeyData> getData, _VoidCallback callback) {
|
||||||
|
bool canceled = false;
|
||||||
|
Future<void>.delayed(duration).then<void>((_) {
|
||||||
|
if (!canceled && !_disposed) {
|
||||||
|
callback();
|
||||||
|
dispatchKeyData(getData());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () { canceled = true; };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ## About Key guards
|
||||||
|
//
|
||||||
|
// When the user enters a browser/system shortcut (e.g. `cmd+alt+i`) the
|
||||||
|
// browser doesn't send a keyup for it. This puts the framework in a corrupt
|
||||||
|
// state because it thinks the key was never released.
|
||||||
|
//
|
||||||
|
// To avoid this, we rely on the fact that browsers send repeat events
|
||||||
|
// while the key is held down by the user. If we don't receive a repeat
|
||||||
|
// event within a specific duration ([_keydownCancelDuration]) we assume
|
||||||
|
// the user has released the key and we synthesize a keyup event.
|
||||||
|
final Map<int, _VoidCallback> _keyGuards = <int, _VoidCallback>{};
|
||||||
|
// Call this method on the down or repeated event of a non-modifier key.
|
||||||
|
void _startGuardingKey(int physicalKey, int logicalKey, Duration currentTimeStamp) {
|
||||||
|
final _VoidCallback cancelingCallback = _scheduleAsyncEvent(
|
||||||
|
_keydownCancelDuration,
|
||||||
|
() => ui.KeyData(
|
||||||
|
timeStamp: currentTimeStamp + _keydownCancelDuration,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: physicalKey,
|
||||||
|
logical: logicalKey,
|
||||||
|
character: null,
|
||||||
|
synthesized: true,
|
||||||
|
),
|
||||||
|
() {
|
||||||
|
_pressingRecords.remove(physicalKey);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_keyGuards.remove(physicalKey)?.call();
|
||||||
|
_keyGuards[physicalKey] = cancelingCallback;
|
||||||
|
}
|
||||||
|
// Call this method on an up event event of a non-modifier key.
|
||||||
|
void _stopGuardingKey(int physicalKey) {
|
||||||
|
_keyGuards.remove(physicalKey)?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the HTML event, update states, and dispatch Flutter key data through
|
||||||
|
// [dispatchKeyData].
|
||||||
|
//
|
||||||
|
// * The method might dispatch some synthesized key data first to update states,
|
||||||
|
// results discarded.
|
||||||
|
// * Then it dispatches exactly one non-synthesized key data that corresponds
|
||||||
|
// to the `event`, i.e. the primary key data. If this dispatching returns
|
||||||
|
// true, then this event will be invoked `preventDefault`.
|
||||||
|
// * Some key data might be synthesized to update states after the main key
|
||||||
|
// data. They are always scheduled asynchronously with results discarded.
|
||||||
|
void handleEvent(FlutterHtmlKeyboardEvent event) {
|
||||||
|
final Duration timeStamp = _eventTimeStampToDuration(event.timeStamp!);
|
||||||
|
|
||||||
|
final String eventKey = event.key!;
|
||||||
|
|
||||||
|
final int physicalKey = _getPhysicalCode(event.code!);
|
||||||
|
final bool logicalKeyIsCharacter = !_eventKeyIsKeyname(eventKey);
|
||||||
|
final String? character = logicalKeyIsCharacter ? eventKey : null;
|
||||||
|
final int logicalKey = () {
|
||||||
|
if (kWebLogicalLocationMap.containsKey(event.key!)) {
|
||||||
|
final int? result = kWebLogicalLocationMap[event.key!]?[event.location!];
|
||||||
|
assert(result != null, 'Invalid modifier location: ${event.key}, ${event.location}');
|
||||||
|
return result!;
|
||||||
|
}
|
||||||
|
if (character != null)
|
||||||
|
return _characterToLogicalKey(character);
|
||||||
|
if (eventKey == _kLogicalDead)
|
||||||
|
return _deadKeyToLogicalKey(physicalKey, event);
|
||||||
|
return _otherLogicalKey(eventKey);
|
||||||
|
}();
|
||||||
|
|
||||||
|
assert(event.type == 'keydown' || event.type == 'keyup');
|
||||||
|
final bool isPhysicalDown = event.type == 'keydown' ||
|
||||||
|
// On macOS, both keydown and keyup events of CapsLock should be considered keydown,
|
||||||
|
// followed by an immediate cancel event.
|
||||||
|
(_shouldSynthesizeCapsLockUp() && event.code! == _kPhysicalCapsLock);
|
||||||
|
|
||||||
|
final int? lastLogicalRecord = _pressingRecords[physicalKey];
|
||||||
|
|
||||||
|
ui.KeyEventType type;
|
||||||
|
|
||||||
|
if (_shouldSynthesizeCapsLockUp() && event.code! == _kPhysicalCapsLock) {
|
||||||
|
// Case 1: Handle CapsLock on macOS
|
||||||
|
//
|
||||||
|
// On macOS, both keydown and keyup events of CapsLock are considered
|
||||||
|
// keydown, followed by an immediate synchronized up event.
|
||||||
|
|
||||||
|
_scheduleAsyncEvent(
|
||||||
|
Duration.zero,
|
||||||
|
() => ui.KeyData(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: physicalKey,
|
||||||
|
logical: logicalKey,
|
||||||
|
character: null,
|
||||||
|
synthesized: true,
|
||||||
|
),
|
||||||
|
() {
|
||||||
|
_pressingRecords.remove(physicalKey);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
type = ui.KeyEventType.down;
|
||||||
|
|
||||||
|
} else if (isPhysicalDown) {
|
||||||
|
// Case 2: Handle key down of normal keys
|
||||||
|
type = ui.KeyEventType.down;
|
||||||
|
if (lastLogicalRecord != null) {
|
||||||
|
// This physical key is being pressed according to the record.
|
||||||
|
if (event.repeat ?? false) {
|
||||||
|
// A normal repeated key.
|
||||||
|
type = ui.KeyEventType.repeat;
|
||||||
|
} else {
|
||||||
|
// A non-repeated key has been pressed that has the exact physical key as
|
||||||
|
// a currently pressed one, usually indicating multiple keyboards are
|
||||||
|
// pressing keys with the same physical key, or the up event was lost
|
||||||
|
// during a loss of focus. The down event is ignored.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This physical key is not being pressed according to the record. It's a
|
||||||
|
// normal down event, whether the system event is a repeat or not.
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // isPhysicalDown is false and not CapsLock
|
||||||
|
// Case 2: Handle key up of normal keys
|
||||||
|
if (lastLogicalRecord == null) {
|
||||||
|
// The physical key has been released before. It indicates multiple
|
||||||
|
// keyboards pressed keys with the same physical key. Ignore the up event.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
type = ui.KeyEventType.up;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int? nextLogicalRecord;
|
||||||
|
switch (type) {
|
||||||
|
case ui.KeyEventType.down:
|
||||||
|
assert(lastLogicalRecord == null);
|
||||||
|
nextLogicalRecord = logicalKey;
|
||||||
|
break;
|
||||||
|
case ui.KeyEventType.up:
|
||||||
|
assert(lastLogicalRecord != null);
|
||||||
|
nextLogicalRecord = null;
|
||||||
|
break;
|
||||||
|
case ui.KeyEventType.repeat:
|
||||||
|
assert(lastLogicalRecord != null);
|
||||||
|
nextLogicalRecord = lastLogicalRecord;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (nextLogicalRecord == null) {
|
||||||
|
_pressingRecords.remove(physicalKey);
|
||||||
|
} else {
|
||||||
|
_pressingRecords[physicalKey] = nextLogicalRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After updating _pressingRecords, synchronize modifier states. The
|
||||||
|
// `event.***Key` fields can be used to reduce some omitted modifier key
|
||||||
|
// events. We can deduce key cancel events if they are false. Key sync
|
||||||
|
// events can not be deduced since we don't know which physical key they
|
||||||
|
// represent.
|
||||||
|
_kLogicalKeyToModifierGetter.forEach((int logicalKey, _ModifierGetter getModifier) {
|
||||||
|
if (_pressingRecords.containsValue(logicalKey) && !getModifier(event)) {
|
||||||
|
_pressingRecords.removeWhere((int physicalKey, int logicalRecord) {
|
||||||
|
if (logicalRecord != logicalKey)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
dispatchKeyData(ui.KeyData(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: physicalKey,
|
||||||
|
logical: logicalKey,
|
||||||
|
character: null,
|
||||||
|
synthesized: true,
|
||||||
|
));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update key guards
|
||||||
|
if (logicalKeyIsCharacter) {
|
||||||
|
if (nextLogicalRecord != null) {
|
||||||
|
_startGuardingKey(physicalKey, logicalKey, timeStamp);
|
||||||
|
} else {
|
||||||
|
_stopGuardingKey(physicalKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ui.KeyData keyData = ui.KeyData(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
type: type,
|
||||||
|
physical: physicalKey,
|
||||||
|
logical: lastLogicalRecord ?? logicalKey,
|
||||||
|
character: type == ui.KeyEventType.up ? null : character,
|
||||||
|
synthesized: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
bool primaryHandled = dispatchKeyData(keyData);
|
||||||
|
if (primaryHandled) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,8 @@ part of engine;
|
|||||||
/// This may be overridden in tests, for example, to pump fake frames.
|
/// This may be overridden in tests, for example, to pump fake frames.
|
||||||
ui.VoidCallback? scheduleFrameCallback;
|
ui.VoidCallback? scheduleFrameCallback;
|
||||||
|
|
||||||
|
typedef _KeyDataResponseCallback = void Function(bool handled);
|
||||||
|
|
||||||
/// Platform event dispatcher.
|
/// Platform event dispatcher.
|
||||||
///
|
///
|
||||||
/// This is the central entry point for platform messages and configuration
|
/// This is the central entry point for platform messages and configuration
|
||||||
@ -170,6 +172,34 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
|||||||
invoke1<ui.PointerDataPacket>(_onPointerDataPacket, _onPointerDataPacketZone, dataPacket);
|
invoke1<ui.PointerDataPacket>(_onPointerDataPacket, _onPointerDataPacketZone, dataPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A callback that is invoked when key data is available.
|
||||||
|
///
|
||||||
|
/// The framework invokes this callback in the same zone in which the
|
||||||
|
/// callback was set.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [GestureBinding], the Flutter framework class which manages pointer
|
||||||
|
/// events.
|
||||||
|
@override
|
||||||
|
ui.KeyDataCallback? get onKeyData => _onKeyData;
|
||||||
|
ui.KeyDataCallback? _onKeyData;
|
||||||
|
Zone? _onKeyDataZone;
|
||||||
|
@override
|
||||||
|
set onKeyData(ui.KeyDataCallback? callback) {
|
||||||
|
_onKeyData = callback;
|
||||||
|
_onKeyDataZone = Zone.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Engine code should use this method instead of the callback directly.
|
||||||
|
/// Otherwise zones won't work properly.
|
||||||
|
void invokeOnKeyData(ui.KeyData data, _KeyDataResponseCallback callback) {
|
||||||
|
invoke(
|
||||||
|
() { callback(onKeyData == null ? false : onKeyData!(data)); },
|
||||||
|
_onKeyDataZone,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// A callback that is invoked to report the [FrameTiming] of recently
|
/// A callback that is invoked to report the [FrameTiming] of recently
|
||||||
/// rasterized frames.
|
/// rasterized frames.
|
||||||
///
|
///
|
||||||
@ -947,4 +977,3 @@ void invoke3<A1, A2, A3>(void Function(A1 a1, A2 a2, A3 a3)? callback, Zone? zon
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
108
engine/src/flutter/lib/web_ui/lib/src/ui/key.dart
Normal file
108
engine/src/flutter/lib/web_ui/lib/src/ui/key.dart
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// @dart = 2.12
|
||||||
|
|
||||||
|
part of ui;
|
||||||
|
|
||||||
|
/// The type of a key event.
|
||||||
|
// Must match the KeyEventType enum in ui/window/key_data.h.
|
||||||
|
enum KeyEventType {
|
||||||
|
/// The key is pressed.
|
||||||
|
down,
|
||||||
|
|
||||||
|
/// The key is released.
|
||||||
|
up,
|
||||||
|
|
||||||
|
/// The key is held, causing a repeated key input.
|
||||||
|
repeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about a key event.
|
||||||
|
class KeyData {
|
||||||
|
/// Creates an object that represents a key event.
|
||||||
|
const KeyData({
|
||||||
|
required this.timeStamp,
|
||||||
|
required this.type,
|
||||||
|
required this.physical,
|
||||||
|
required this.logical,
|
||||||
|
required this.character,
|
||||||
|
required this.synthesized,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Time of event dispatch, relative to an arbitrary timeline.
|
||||||
|
///
|
||||||
|
/// For [KeyEventType.synchronize] and [KeyEventType.cancel] events, the [timeStamp]
|
||||||
|
/// might not be the actual time that the key press or release happens.
|
||||||
|
final Duration timeStamp;
|
||||||
|
|
||||||
|
/// The type of the event.
|
||||||
|
final KeyEventType type;
|
||||||
|
|
||||||
|
/// The key code for the physical key that has changed.
|
||||||
|
final int physical;
|
||||||
|
|
||||||
|
/// The key code for the logical key that has changed.
|
||||||
|
final int logical;
|
||||||
|
|
||||||
|
/// Character input from the event.
|
||||||
|
///
|
||||||
|
/// Ignored for up events.
|
||||||
|
final String? character;
|
||||||
|
|
||||||
|
/// If [synthesized] is true, this event does not correspond to a native event.
|
||||||
|
///
|
||||||
|
/// Although most of Flutter's keyboard events are transformed from native
|
||||||
|
/// events, some events are not based on native events, and are synthesized
|
||||||
|
/// only to conform Flutter's key event model (as documented in
|
||||||
|
/// the `HardwareKeyboard` class in the framework).
|
||||||
|
///
|
||||||
|
/// For example, some key downs or ups might be lost when the window loses
|
||||||
|
/// focus. Some platforms provides ways to query whether a key is being held.
|
||||||
|
/// If Flutter detects an inconsistancy between the state Flutter records and
|
||||||
|
/// the state returned by the system, Flutter will synthesize a corresponding
|
||||||
|
/// event to synchronize the state without breaking the event model.
|
||||||
|
///
|
||||||
|
/// As another example, macOS treats CapsLock in a special way by sending
|
||||||
|
/// down and up events at the down of alterate presses to indicate the
|
||||||
|
/// direction in which the lock is toggled instead of that the physical key is
|
||||||
|
/// going. Flutter normalizes the behavior by converting a native down event
|
||||||
|
/// into a down event followed immediately by a synthesized up event, and
|
||||||
|
/// the native up event also into a down event followed immediately by a
|
||||||
|
/// synthesized up event.
|
||||||
|
///
|
||||||
|
/// Synthesized events do not have a trustworthy [timeStamp], and should not be
|
||||||
|
/// processed as if the key actually went down or up at the time of the
|
||||||
|
/// callback.
|
||||||
|
///
|
||||||
|
/// [KeyRepeatEvent] is never synthesized.
|
||||||
|
final bool synthesized;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'KeyData(type: ${_typeToString(type)}, physical: 0x${physical.toRadixString(16)}, '
|
||||||
|
'logical: 0x${logical.toRadixString(16)}, character: $character)';
|
||||||
|
|
||||||
|
/// Returns a complete textual description of the information in this object.
|
||||||
|
String toStringFull() {
|
||||||
|
return '$runtimeType('
|
||||||
|
'type: ${_typeToString(type)}, '
|
||||||
|
'timeStamp: $timeStamp, '
|
||||||
|
'physical: 0x${physical.toRadixString(16)}, '
|
||||||
|
'logical: 0x${logical.toRadixString(16)}, '
|
||||||
|
'character: $character, '
|
||||||
|
'synthesized: $synthesized'
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _typeToString(KeyEventType type) {
|
||||||
|
switch (type) {
|
||||||
|
case KeyEventType.up:
|
||||||
|
return 'up';
|
||||||
|
case KeyEventType.down:
|
||||||
|
return 'down';
|
||||||
|
case KeyEventType.repeat:
|
||||||
|
return 'repeat';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ typedef VoidCallback = void Function();
|
|||||||
typedef FrameCallback = void Function(Duration duration);
|
typedef FrameCallback = void Function(Duration duration);
|
||||||
typedef TimingsCallback = void Function(List<FrameTiming> timings);
|
typedef TimingsCallback = void Function(List<FrameTiming> timings);
|
||||||
typedef PointerDataPacketCallback = void Function(PointerDataPacket packet);
|
typedef PointerDataPacketCallback = void Function(PointerDataPacket packet);
|
||||||
|
typedef KeyDataCallback = bool Function(KeyData packet);
|
||||||
typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args);
|
typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args);
|
||||||
typedef PlatformMessageResponseCallback = void Function(ByteData? data);
|
typedef PlatformMessageResponseCallback = void Function(ByteData? data);
|
||||||
typedef PlatformMessageCallback = void Function(
|
typedef PlatformMessageCallback = void Function(
|
||||||
@ -36,6 +37,9 @@ abstract class PlatformDispatcher {
|
|||||||
PointerDataPacketCallback? get onPointerDataPacket;
|
PointerDataPacketCallback? get onPointerDataPacket;
|
||||||
set onPointerDataPacket(PointerDataPacketCallback? callback);
|
set onPointerDataPacket(PointerDataPacketCallback? callback);
|
||||||
|
|
||||||
|
KeyDataCallback? get onKeyData;
|
||||||
|
set onKeyData(KeyDataCallback? callback);
|
||||||
|
|
||||||
TimingsCallback? get onReportTimings;
|
TimingsCallback? get onReportTimings;
|
||||||
set onReportTimings(TimingsCallback? callback);
|
set onReportTimings(TimingsCallback? callback);
|
||||||
|
|
||||||
@ -417,4 +421,4 @@ class Locale {
|
|||||||
}
|
}
|
||||||
return out.toString();
|
return out.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,11 @@ abstract class SingletonFlutterWindow extends FlutterWindow {
|
|||||||
platformDispatcher.onPointerDataPacket = callback;
|
platformDispatcher.onPointerDataPacket = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KeyDataCallback? get onKeyData => platformDispatcher.onKeyData;
|
||||||
|
set onKeyData(KeyDataCallback? callback) {
|
||||||
|
platformDispatcher.onKeyData = callback;
|
||||||
|
}
|
||||||
|
|
||||||
String get defaultRouteName => platformDispatcher.defaultRouteName;
|
String get defaultRouteName => platformDispatcher.defaultRouteName;
|
||||||
|
|
||||||
void scheduleFrame() => platformDispatcher.scheduleFrame();
|
void scheduleFrame() => platformDispatcher.scheduleFrame();
|
||||||
|
@ -24,6 +24,7 @@ part 'src/ui/compositing.dart';
|
|||||||
part 'src/ui/geometry.dart';
|
part 'src/ui/geometry.dart';
|
||||||
part 'src/ui/hash_codes.dart';
|
part 'src/ui/hash_codes.dart';
|
||||||
part 'src/ui/initialization.dart';
|
part 'src/ui/initialization.dart';
|
||||||
|
part 'src/ui/key.dart';
|
||||||
part 'src/ui/lerp.dart';
|
part 'src/ui/lerp.dart';
|
||||||
part 'src/ui/natives.dart';
|
part 'src/ui/natives.dart';
|
||||||
part 'src/ui/painting.dart';
|
part 'src/ui/painting.dart';
|
||||||
|
969
engine/src/flutter/lib/web_ui/test/keyboard_converter_test.dart
Normal file
969
engine/src/flutter/lib/web_ui/test/keyboard_converter_test.dart
Normal file
@ -0,0 +1,969 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// @dart = 2.10
|
||||||
|
|
||||||
|
import 'package:quiver/testing/async.dart';
|
||||||
|
import 'package:test/bootstrap/browser.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ui/src/engine.dart';
|
||||||
|
import 'package:ui/ui.dart' as ui;
|
||||||
|
import 'package:meta/meta.dart' show isTest;
|
||||||
|
|
||||||
|
const int kLocationLeft = 1;
|
||||||
|
const int kLocationRight = 2;
|
||||||
|
const int kLocationNumpad = 3;
|
||||||
|
|
||||||
|
const int kPhysicalKeyA = 0x00070004;
|
||||||
|
const int kPhysicalKeyE = 0x00070008;
|
||||||
|
const int kPhysicalKeyU = 0x00070018;
|
||||||
|
const int kPhysicalDigit1 = 0x0007001e;
|
||||||
|
const int kPhysicalNumpad1 = 0x00070059;
|
||||||
|
const int kPhysicalShiftLeft = 0x000700e1;
|
||||||
|
const int kPhysicalShiftRight = 0x000700e5;
|
||||||
|
const int kPhysicalMetaLeft = 0x000700e3;
|
||||||
|
const int kPhysicalTab = 0x0007002b;
|
||||||
|
const int kPhysicalCapsLock = 0x00070039;
|
||||||
|
const int kPhysicalScrollLock = 0x00070047;
|
||||||
|
|
||||||
|
const int kLogicalKeyA = 0x00000000061;
|
||||||
|
const int kLogicalKeyU = 0x00000000075;
|
||||||
|
const int kLogicalDigit1 = 0x00000000031;
|
||||||
|
const int kLogicalNumpad1 = 0x00200000031;
|
||||||
|
const int kLogicalShiftLeft = 0x030000010d;
|
||||||
|
const int kLogicalShiftRight = 0x040000010d;
|
||||||
|
const int kLogicalMetaLeft = 0x0300000109;
|
||||||
|
const int kLogicalTab = 0x0000000009;
|
||||||
|
const int kLogicalCapsLock = 0x00000000104;
|
||||||
|
const int kLogicalScrollLock = 0x0000000010c;
|
||||||
|
|
||||||
|
typedef VoidCallback = void Function();
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
internalBootstrapBrowserTest(() => testMain);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testMain() {
|
||||||
|
test('Single key press, repeat, and release', () {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
// Only handle down events
|
||||||
|
return key.type == ui.KeyEventType.down;
|
||||||
|
});
|
||||||
|
bool preventedDefault = false;
|
||||||
|
final onPreventDefault = () { preventedDefault = true; };
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a')
|
||||||
|
..timeStamp = 1
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: Duration(milliseconds: 1),
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
expect(preventedDefault, true);
|
||||||
|
preventedDefault = false;
|
||||||
|
|
||||||
|
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'a')
|
||||||
|
..timeStamp = 1.5
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: Duration(milliseconds: 1, microseconds: 500),
|
||||||
|
type: ui.KeyEventType.repeat,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'a')
|
||||||
|
..timeStamp = 1500
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: Duration(seconds: 1, milliseconds: 500),
|
||||||
|
type: ui.KeyEventType.repeat,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'a')
|
||||||
|
..timeStamp = 2000.5
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: Duration(seconds: 2, microseconds: 500),
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Release modifier during a repeated sequence', () {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
// Only handle down events
|
||||||
|
return key.type == ui.KeyEventType.down;
|
||||||
|
});
|
||||||
|
bool preventedDefault = false;
|
||||||
|
final onPreventDefault = () { preventedDefault = true; };
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('ShiftLeft', 'Shift', kShift, kLocationLeft)
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalShiftLeft,
|
||||||
|
logical: kLogicalShiftLeft,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
expect(preventedDefault, true);
|
||||||
|
preventedDefault = false;
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'A', kShift)
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'A',
|
||||||
|
);
|
||||||
|
expect(preventedDefault, true);
|
||||||
|
preventedDefault = false;
|
||||||
|
|
||||||
|
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'A', kShift)
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.repeat,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'A',
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('ShiftLeft', 'Shift', 0, kLocationLeft)
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalShiftLeft,
|
||||||
|
logical: kLogicalShiftLeft,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'a')
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.repeat,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'a')
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.repeat,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'a'));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Distinguish between left and right modifiers', () {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('ShiftLeft', 'Shift', kShift, kLocationLeft));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalShiftLeft,
|
||||||
|
logical: kLogicalShiftLeft,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('ShiftRight', 'Shift', kShift, kLocationRight));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalShiftRight,
|
||||||
|
logical: kLogicalShiftRight,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('ShiftLeft', 'Shift', kShift, kLocationLeft));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalShiftLeft,
|
||||||
|
logical: kLogicalShiftLeft,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('ShiftRight', 'Shift', 0, kLocationRight));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalShiftRight,
|
||||||
|
logical: kLogicalShiftRight,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Distinguish between normal and numpad digits', () {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('Digit1', '1', 0, 0));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalDigit1,
|
||||||
|
logical: kLogicalDigit1,
|
||||||
|
character: '1',
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('Numpad1', '1', 0, kLocationNumpad));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalNumpad1,
|
||||||
|
logical: kLogicalNumpad1,
|
||||||
|
character: '1',
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('Digit1', '1', 0, 0));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalDigit1,
|
||||||
|
logical: kLogicalDigit1,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('Numpad1', '1', 0, kLocationNumpad));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalNumpad1,
|
||||||
|
logical: kLogicalNumpad1,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Dead keys are distinguishable', () {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// The absolute values of the following logical keys are not guaranteed.
|
||||||
|
const int kLogicalAltE = 0x410800070008;
|
||||||
|
const int kLogicalAltU = 0x410800070018;
|
||||||
|
const int kLogicalAltShiftE = 0x610800070008;
|
||||||
|
// The values must be distinguishable.
|
||||||
|
expect(kLogicalAltE, isNot(equals(kLogicalAltU)));
|
||||||
|
expect(kLogicalAltE, isNot(equals(kLogicalAltShiftE)));
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('AltLeft', 'Alt', kAlt, kLocationLeft));
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('KeyE', 'Dead', kAlt));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyE,
|
||||||
|
logical: kLogicalAltE,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('KeyE', 'Dead', kAlt));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyE,
|
||||||
|
logical: kLogicalAltE,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('KeyU', 'Dead', kAlt));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyU,
|
||||||
|
logical: kLogicalAltU,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('KeyU', 'Dead', kAlt));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyU,
|
||||||
|
logical: kLogicalAltU,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('ShiftLeft', 'Shift', kAlt | kShift, kLocationLeft));
|
||||||
|
|
||||||
|
// This does not actually produce a Dead key on macOS (US layout); just for
|
||||||
|
// testing.
|
||||||
|
converter.handleEvent(keyDownEvent('KeyE', 'Dead', kAlt | kShift));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyE,
|
||||||
|
logical: kLogicalAltShiftE,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('AltLeft', 'Alt', kShift, kLocationLeft));
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('KeyE', 'e', kShift));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyE,
|
||||||
|
logical: kLogicalAltShiftE,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('ShiftLeft', 'Shift', 0, kLocationLeft));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Duplicate down is ignored', () {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
bool preventedDefault = false;
|
||||||
|
final onPreventDefault = () { preventedDefault = true; };
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('ShiftLeft', 'Shift', kShift, kLocationLeft)
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expect(preventedDefault, true);
|
||||||
|
preventedDefault = false;
|
||||||
|
// A KeyUp of ShiftLeft is missed due to loss of focus.
|
||||||
|
|
||||||
|
keyDataList.clear();
|
||||||
|
converter.handleEvent(keyDownEvent('ShiftLeft', 'Shift', kShift, kLocationLeft)
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expect(keyDataList, isEmpty);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('ShiftLeft', 'Shift', 0, kLocationLeft)
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalShiftLeft,
|
||||||
|
logical: kLogicalShiftLeft,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
expect(preventedDefault, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Duplicate ups are skipped', () {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
bool preventedDefault = false;
|
||||||
|
final onPreventDefault = () { preventedDefault = true; };
|
||||||
|
|
||||||
|
// A KeyDown of ShiftRight is missed due to loss of focus.
|
||||||
|
converter.handleEvent(keyUpEvent('ShiftRight', 'Shift', 0, kLocationRight)
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expect(keyDataList, isEmpty);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Conflict from multiple keyboards do not crash', () {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Same layout
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a'));
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a'));
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'a'));
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'a'));
|
||||||
|
|
||||||
|
// Different layout
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a'));
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'u'));
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'u'));
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'a'));
|
||||||
|
|
||||||
|
// Passes if there's no crash, and states are reset after everything is released.
|
||||||
|
keyDataList.clear();
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a'));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('KeyU', 'u'));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyU,
|
||||||
|
logical: kLogicalKeyU,
|
||||||
|
character: 'u',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testFakeAsync('CapsLock down synthesizes an immediate cancel on macOS', (FakeAsync async) {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
}, onMacOs: true);
|
||||||
|
bool preventedDefault = false;
|
||||||
|
final onPreventDefault = () { preventedDefault = true; };
|
||||||
|
|
||||||
|
// A KeyDown of ShiftRight is missed due to loss of focus.
|
||||||
|
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock')
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalCapsLock,
|
||||||
|
logical: kLogicalCapsLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
expect(preventedDefault, true);
|
||||||
|
keyDataList.clear();
|
||||||
|
preventedDefault = false;
|
||||||
|
|
||||||
|
async.elapse(Duration(microseconds: 1));
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalCapsLock,
|
||||||
|
logical: kLogicalCapsLock,
|
||||||
|
character: null,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('CapsLock', 'CapsLock')
|
||||||
|
..onPreventDefault = onPreventDefault
|
||||||
|
);
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalCapsLock,
|
||||||
|
logical: kLogicalCapsLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
expect(preventedDefault, true);
|
||||||
|
keyDataList.clear();
|
||||||
|
preventedDefault = false;
|
||||||
|
|
||||||
|
async.elapse(Duration(microseconds: 1));
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalCapsLock,
|
||||||
|
logical: kLogicalCapsLock,
|
||||||
|
character: null,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
expect(preventedDefault, false);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
// Another key down works
|
||||||
|
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalCapsLock,
|
||||||
|
logical: kLogicalCapsLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
|
||||||
|
// Schedules are canceled after disposal
|
||||||
|
converter.dispose();
|
||||||
|
async.elapse(Duration(seconds: 10));
|
||||||
|
expect(keyDataList, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
testFakeAsync('CapsLock behaves normally on non-macOS', (FakeAsync async) {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
}, onMacOs: false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalCapsLock,
|
||||||
|
logical: kLogicalCapsLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
async.elapse(Duration(seconds: 10));
|
||||||
|
expect(keyDataList, isEmpty);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('CapsLock', 'CapsLock'));
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalCapsLock,
|
||||||
|
logical: kLogicalCapsLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
async.elapse(Duration(seconds: 10));
|
||||||
|
expect(keyDataList, isEmpty);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalCapsLock,
|
||||||
|
logical: kLogicalCapsLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('CapsLock', 'CapsLock'));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalCapsLock,
|
||||||
|
logical: kLogicalCapsLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testFakeAsync('Key guards: key down events are guarded', (FakeAsync async) {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kMeta, kLocationLeft)..timeStamp = 100);
|
||||||
|
async.elapse(Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a', kMeta)..timeStamp = 200);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 200),
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
// Keyup of KeyA is omitted due to being a shortcut.
|
||||||
|
|
||||||
|
async.elapse(Duration(milliseconds: 2500));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 1200),
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: null,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('MetaLeft', 'Meta', 0, kLocationLeft)..timeStamp = 2700);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 2700),
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalMetaLeft,
|
||||||
|
logical: kLogicalMetaLeft,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
async.elapse(Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
// Key A states are cleared
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a')..timeStamp = 2800);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 2800),
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
async.elapse(Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'a')..timeStamp = 2900);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 2900),
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testFakeAsync('Key guards: key repeated down events refreshes guards', (FakeAsync async) {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kMeta, kLocationLeft)..timeStamp = 100);
|
||||||
|
async.elapse(Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a', kMeta)..timeStamp = 200);
|
||||||
|
async.elapse(Duration(milliseconds: 400));
|
||||||
|
|
||||||
|
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'a', kMeta)..timeStamp = 600);
|
||||||
|
async.elapse(Duration(milliseconds: 50));
|
||||||
|
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'a', kMeta)..timeStamp = 650);
|
||||||
|
async.elapse(Duration(milliseconds: 50));
|
||||||
|
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'a', kMeta)..timeStamp = 700);
|
||||||
|
|
||||||
|
// Keyup of KeyA is omitted due to being a shortcut.
|
||||||
|
|
||||||
|
async.elapse(Duration(milliseconds: 2500));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 1700),
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: null,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('MetaLeft', 'Meta', 0, kLocationLeft)..timeStamp = 3200);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 3200),
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalMetaLeft,
|
||||||
|
logical: kLogicalMetaLeft,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
async.elapse(Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
// Key A states are cleared
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a')..timeStamp = 3300);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 3300),
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
async.elapse(Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'a')..timeStamp = 3400);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 3400),
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testFakeAsync('Key guards: cleared by keyups', (FakeAsync async) {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kMeta, kLocationLeft)..timeStamp = 100);
|
||||||
|
async.elapse(Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a', kCtrl)..timeStamp = 200);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 200),
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
async.elapse(Duration(milliseconds: 500));
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('MetaLeft', 'Meta', 0, kLocationLeft)..timeStamp = 700);
|
||||||
|
async.elapse(Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'a')..timeStamp = 800);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 800),
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
async.elapse(Duration(milliseconds: 2000));
|
||||||
|
expect(keyDataList, isEmpty);
|
||||||
|
|
||||||
|
// Key A states are cleared
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a')..timeStamp = 2800);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 2800),
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
async.elapse(Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('KeyA', 'a')..timeStamp = 2900);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
timeStamp: const Duration(milliseconds: 2900),
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testFakeAsync('Lock flags of other keys', (FakeAsync async) {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
}, onMacOs: false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('ScrollLock', 'ScrollLock'));
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalScrollLock,
|
||||||
|
logical: kLogicalScrollLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
async.elapse(Duration(seconds: 10));
|
||||||
|
expect(keyDataList, isEmpty);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('ScrollLock', 'ScrollLock'));
|
||||||
|
expect(keyDataList, hasLength(1));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalScrollLock,
|
||||||
|
logical: kLogicalScrollLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('ScrollLock', 'ScrollLock'));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalScrollLock,
|
||||||
|
logical: kLogicalScrollLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyUpEvent('ScrollLock', 'ScrollLock'));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalScrollLock,
|
||||||
|
logical: kLogicalScrollLock,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Deduce modifier key up from modifier field', () {
|
||||||
|
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
|
||||||
|
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
|
||||||
|
keyDataList.add(key);
|
||||||
|
return true;
|
||||||
|
}, onMacOs: false);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('ShiftRight', 'Shift', kShift, kLocationRight));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalShiftRight,
|
||||||
|
logical: kLogicalShiftRight,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('ShiftLeft', 'Shift', kShift, kLocationLeft));
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalShiftLeft,
|
||||||
|
logical: kLogicalShiftLeft,
|
||||||
|
character: null,
|
||||||
|
);
|
||||||
|
keyDataList.clear();
|
||||||
|
|
||||||
|
// The release of the shift keys are omitted
|
||||||
|
|
||||||
|
converter.handleEvent(keyDownEvent('KeyA', 'a'));
|
||||||
|
expect(keyDataList, hasLength(3));
|
||||||
|
expectKeyData(keyDataList[0],
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalShiftLeft,
|
||||||
|
logical: kLogicalShiftLeft,
|
||||||
|
character: null,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList[1],
|
||||||
|
type: ui.KeyEventType.up,
|
||||||
|
physical: kPhysicalShiftRight,
|
||||||
|
logical: kLogicalShiftRight,
|
||||||
|
character: null,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
expectKeyData(keyDataList.last,
|
||||||
|
type: ui.KeyEventType.down,
|
||||||
|
physical: kPhysicalKeyA,
|
||||||
|
logical: kLogicalKeyA,
|
||||||
|
character: 'a',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockKeyboardEvent implements FlutterHtmlKeyboardEvent {
|
||||||
|
MockKeyboardEvent({
|
||||||
|
required this.type,
|
||||||
|
required this.code,
|
||||||
|
required this.key,
|
||||||
|
this.timeStamp = 0,
|
||||||
|
this.repeat = false,
|
||||||
|
this.altKey = false,
|
||||||
|
this.ctrlKey = false,
|
||||||
|
this.shiftKey = false,
|
||||||
|
this.metaKey = false,
|
||||||
|
this.location = 0,
|
||||||
|
this.onPreventDefault,
|
||||||
|
});
|
||||||
|
|
||||||
|
String type;
|
||||||
|
String? code;
|
||||||
|
String? key;
|
||||||
|
bool? repeat;
|
||||||
|
num? timeStamp;
|
||||||
|
bool altKey;
|
||||||
|
bool ctrlKey;
|
||||||
|
bool shiftKey;
|
||||||
|
bool metaKey;
|
||||||
|
int? location;
|
||||||
|
|
||||||
|
bool getModifierState(String key) => modifierState.contains(key);
|
||||||
|
final Set<String> modifierState = <String>{};
|
||||||
|
|
||||||
|
void preventDefault() { onPreventDefault?.call(); }
|
||||||
|
VoidCallback? onPreventDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flags used for the `modifiers` argument of `key***Event` functions.
|
||||||
|
const kAlt = 0x1;
|
||||||
|
const kCtrl = 0x2;
|
||||||
|
const kShift = 0x4;
|
||||||
|
const kMeta = 0x8;
|
||||||
|
|
||||||
|
// Utility functions to make code more concise.
|
||||||
|
//
|
||||||
|
// To add timeStamp or onPreventDefault, use syntax like `..timeStamp = `.
|
||||||
|
MockKeyboardEvent keyDownEvent(String code, String key, [int modifiers = 0, int location = 0]) {
|
||||||
|
return MockKeyboardEvent(
|
||||||
|
type: 'keydown',
|
||||||
|
code: code,
|
||||||
|
key: key,
|
||||||
|
altKey: modifiers & kAlt != 0,
|
||||||
|
ctrlKey: modifiers & kCtrl != 0,
|
||||||
|
shiftKey: modifiers & kShift != 0,
|
||||||
|
metaKey: modifiers & kMeta != 0,
|
||||||
|
location: location,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MockKeyboardEvent keyUpEvent(String code, String key, [int modifiers = 0, int location = 0]) {
|
||||||
|
return MockKeyboardEvent(
|
||||||
|
type: 'keyup',
|
||||||
|
code: code,
|
||||||
|
key: key,
|
||||||
|
altKey: modifiers & kAlt != 0,
|
||||||
|
ctrlKey: modifiers & kCtrl != 0,
|
||||||
|
shiftKey: modifiers & kShift != 0,
|
||||||
|
metaKey: modifiers & kMeta != 0,
|
||||||
|
location: location,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MockKeyboardEvent keyRepeatedDownEvent(String code, String key, [int modifiers = 0, int location = 0]) {
|
||||||
|
return MockKeyboardEvent(
|
||||||
|
type: 'keydown',
|
||||||
|
code: code,
|
||||||
|
key: key,
|
||||||
|
altKey: modifiers & kAlt != 0,
|
||||||
|
ctrlKey: modifiers & kCtrl != 0,
|
||||||
|
shiftKey: modifiers & kShift != 0,
|
||||||
|
metaKey: modifiers & kMeta != 0,
|
||||||
|
repeat: true,
|
||||||
|
location: location,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flags used for the `activeLocks` argument of expectKeyData.
|
||||||
|
const kCapsLock = 0x1;
|
||||||
|
const kNumlLock = 0x2;
|
||||||
|
const kScrollLock = 0x4;
|
||||||
|
|
||||||
|
void expectKeyData(
|
||||||
|
ui.KeyData target, {
|
||||||
|
required ui.KeyEventType type,
|
||||||
|
required int physical,
|
||||||
|
required int logical,
|
||||||
|
required String? character,
|
||||||
|
Duration? timeStamp,
|
||||||
|
bool synthesized = false,
|
||||||
|
}) {
|
||||||
|
expect(target.type, type);
|
||||||
|
expect(target.physical, physical);
|
||||||
|
expect(target.logical, logical);
|
||||||
|
expect(target.character, character);
|
||||||
|
expect(target.synthesized, synthesized);
|
||||||
|
if (timeStamp != null)
|
||||||
|
expect(target.timeStamp, equals(timeStamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef FakeAsyncTest = void Function(FakeAsync);
|
||||||
|
|
||||||
|
@isTest
|
||||||
|
void testFakeAsync(String description, FakeAsyncTest fn) {
|
||||||
|
test(description, () {
|
||||||
|
FakeAsync().run(fn);
|
||||||
|
});
|
||||||
|
}
|
@ -262,6 +262,20 @@ bool RuntimeController::DispatchPointerDataPacket(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RuntimeController::DispatchKeyDataPacket(const KeyDataPacket& packet,
|
||||||
|
KeyDataResponse callback) {
|
||||||
|
if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) {
|
||||||
|
TRACE_EVENT1("flutter", "RuntimeController::DispatchKeyDataPacket", "mode",
|
||||||
|
"basic");
|
||||||
|
uint64_t response_id =
|
||||||
|
platform_configuration->RegisterKeyDataResponse(std::move(callback));
|
||||||
|
platform_configuration->get_window(0)->DispatchKeyDataPacket(packet,
|
||||||
|
response_id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool RuntimeController::DispatchSemanticsAction(int32_t id,
|
bool RuntimeController::DispatchSemanticsAction(int32_t id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
std::vector<uint8_t> args) {
|
std::vector<uint8_t> args) {
|
||||||
|
@ -424,6 +424,20 @@ class RuntimeController : public PlatformConfigurationClient {
|
|||||||
///
|
///
|
||||||
bool DispatchPointerDataPacket(const PointerDataPacket& packet);
|
bool DispatchPointerDataPacket(const PointerDataPacket& packet);
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
/// @brief Dispatch the specified pointer data message to the running
|
||||||
|
/// root isolate.
|
||||||
|
///
|
||||||
|
/// @param[in] packet The key data message to dispatch to the isolate.
|
||||||
|
/// @param[in] callback Called when the framework has decided whether
|
||||||
|
/// to handle this key data.
|
||||||
|
///
|
||||||
|
/// @return If the key data message was dispatched. This may fail is
|
||||||
|
/// an isolate is not running.
|
||||||
|
///
|
||||||
|
bool DispatchKeyDataPacket(const KeyDataPacket& packet,
|
||||||
|
KeyDataResponse callback);
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
/// @brief Dispatch the semantics action to the specified accessibility
|
/// @brief Dispatch the semantics action to the specified accessibility
|
||||||
/// node.
|
/// node.
|
||||||
|
@ -415,6 +415,14 @@ void Engine::DispatchPointerDataPacket(
|
|||||||
pointer_data_dispatcher_->DispatchPacket(std::move(packet), trace_flow_id);
|
pointer_data_dispatcher_->DispatchPacket(std::move(packet), trace_flow_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::DispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
KeyDataResponse callback) {
|
||||||
|
TRACE_EVENT0("flutter", "Engine::DispatchKeyDataPacket");
|
||||||
|
if (runtime_controller_) {
|
||||||
|
runtime_controller_->DispatchKeyDataPacket(*packet, std::move(callback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::DispatchSemanticsAction(int id,
|
void Engine::DispatchSemanticsAction(int id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
std::vector<uint8_t> args) {
|
std::vector<uint8_t> args) {
|
||||||
|
@ -728,6 +728,21 @@ class Engine final : public RuntimeDelegate,
|
|||||||
void DispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet,
|
void DispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet,
|
||||||
uint64_t trace_flow_id);
|
uint64_t trace_flow_id);
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
/// @brief Notifies the engine that the embedder has sent it a key data
|
||||||
|
/// packet. A key data packet contains one key event. This call
|
||||||
|
/// originates in the platform view and the shell has forwarded
|
||||||
|
/// the same to the engine on the UI task runner here. The engine
|
||||||
|
/// will decide whether to handle this event, and send the
|
||||||
|
/// result using `callback`, which will be called exactly once.
|
||||||
|
///
|
||||||
|
/// @param[in] packet The key data packet.
|
||||||
|
/// @param[in] callback Called when the framework has decided whether
|
||||||
|
/// to handle this key data.
|
||||||
|
///
|
||||||
|
void DispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
KeyDataResponse callback);
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
/// @brief Notifies the engine that the embedder encountered an
|
/// @brief Notifies the engine that the embedder encountered an
|
||||||
/// accessibility related action on the specified node. This call
|
/// accessibility related action on the specified node. This call
|
||||||
|
@ -42,6 +42,12 @@ void PlatformView::DispatchPointerDataPacket(
|
|||||||
pointer_data_packet_converter_.Convert(std::move(packet)));
|
pointer_data_packet_converter_.Convert(std::move(packet)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformView::DispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
KeyDataResponse callback) {
|
||||||
|
delegate_.OnPlatformViewDispatchKeyDataPacket(std::move(packet),
|
||||||
|
std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
void PlatformView::DispatchSemanticsAction(int32_t id,
|
void PlatformView::DispatchSemanticsAction(int32_t id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
std::vector<uint8_t> args) {
|
std::vector<uint8_t> args) {
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#ifndef COMMON_PLATFORM_VIEW_H_
|
#ifndef COMMON_PLATFORM_VIEW_H_
|
||||||
#define COMMON_PLATFORM_VIEW_H_
|
#define COMMON_PLATFORM_VIEW_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "flow/embedded_views.h"
|
#include "flow/embedded_views.h"
|
||||||
@ -16,6 +17,7 @@
|
|||||||
#include "flutter/fml/memory/weak_ptr.h"
|
#include "flutter/fml/memory/weak_ptr.h"
|
||||||
#include "flutter/lib/ui/semantics/custom_accessibility_action.h"
|
#include "flutter/lib/ui/semantics/custom_accessibility_action.h"
|
||||||
#include "flutter/lib/ui/semantics/semantics_node.h"
|
#include "flutter/lib/ui/semantics/semantics_node.h"
|
||||||
|
#include "flutter/lib/ui/window/key_data_packet.h"
|
||||||
#include "flutter/lib/ui/window/platform_message.h"
|
#include "flutter/lib/ui/window/platform_message.h"
|
||||||
#include "flutter/lib/ui/window/pointer_data_packet.h"
|
#include "flutter/lib/ui/window/pointer_data_packet.h"
|
||||||
#include "flutter/lib/ui/window/pointer_data_packet_converter.h"
|
#include "flutter/lib/ui/window/pointer_data_packet_converter.h"
|
||||||
@ -52,6 +54,7 @@ class PlatformView {
|
|||||||
///
|
///
|
||||||
class Delegate {
|
class Delegate {
|
||||||
public:
|
public:
|
||||||
|
using KeyDataResponse = std::function<void(bool)>;
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
/// @brief Notifies the delegate that the platform view was created
|
/// @brief Notifies the delegate that the platform view was created
|
||||||
/// with the given render surface. This surface is platform
|
/// with the given render surface. This surface is platform
|
||||||
@ -125,6 +128,20 @@ class PlatformView {
|
|||||||
virtual void OnPlatformViewDispatchPointerDataPacket(
|
virtual void OnPlatformViewDispatchPointerDataPacket(
|
||||||
std::unique_ptr<PointerDataPacket> packet) = 0;
|
std::unique_ptr<PointerDataPacket> packet) = 0;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
/// @brief Notifies the delegate that the platform view has encountered
|
||||||
|
/// a key event. This key event and the callback needs to be
|
||||||
|
/// forwarded to the running root isolate hosted by the engine
|
||||||
|
/// on the UI thread.
|
||||||
|
///
|
||||||
|
/// @param[in] packet The key data packet containing one key event.
|
||||||
|
/// @param[in] callback Called when the framework has decided whether
|
||||||
|
/// to handle this key data.
|
||||||
|
///
|
||||||
|
virtual void OnPlatformViewDispatchKeyDataPacket(
|
||||||
|
std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
std::function<void(bool /* handled */)> callback) = 0;
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
/// @brief Notifies the delegate that the platform view has encountered
|
/// @brief Notifies the delegate that the platform view has encountered
|
||||||
/// an accessibility related action on the specified node. This
|
/// an accessibility related action on the specified node. This
|
||||||
@ -575,6 +592,17 @@ class PlatformView {
|
|||||||
///
|
///
|
||||||
void DispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet);
|
void DispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet);
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
/// @brief Dispatches key events from the embedder to the framework. Each
|
||||||
|
/// key data packet contains one physical event and multiple
|
||||||
|
/// logical key events. Each call to this method wakes up the UI
|
||||||
|
/// thread.
|
||||||
|
///
|
||||||
|
/// @param[in] packet The key data packet to dispatch to the framework.
|
||||||
|
///
|
||||||
|
void DispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
Delegate::KeyDataResponse callback);
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
/// @brief Used by the embedder to specify a texture that it wants the
|
/// @brief Used by the embedder to specify a texture that it wants the
|
||||||
/// rasterizer to composite within the Flutter layer tree. All
|
/// rasterizer to composite within the Flutter layer tree. All
|
||||||
|
@ -961,6 +961,23 @@ void Shell::OnPlatformViewDispatchPointerDataPacket(
|
|||||||
next_pointer_flow_id_++;
|
next_pointer_flow_id_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// |PlatformView::Delegate|
|
||||||
|
void Shell::OnPlatformViewDispatchKeyDataPacket(
|
||||||
|
std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
std::function<void(bool /* handled */)> callback) {
|
||||||
|
TRACE_EVENT0("flutter", "Shell::OnPlatformViewDispatchKeyDataPacket");
|
||||||
|
FML_DCHECK(is_setup_);
|
||||||
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
||||||
|
|
||||||
|
task_runners_.GetUITaskRunner()->PostTask(
|
||||||
|
fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet),
|
||||||
|
callback = std::move(callback)]() mutable {
|
||||||
|
if (engine) {
|
||||||
|
engine->DispatchKeyDataPacket(std::move(packet), std::move(callback));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// |PlatformView::Delegate|
|
// |PlatformView::Delegate|
|
||||||
void Shell::OnPlatformViewDispatchSemanticsAction(int32_t id,
|
void Shell::OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
|
@ -504,6 +504,11 @@ class Shell final : public PlatformView::Delegate,
|
|||||||
void OnPlatformViewDispatchPointerDataPacket(
|
void OnPlatformViewDispatchPointerDataPacket(
|
||||||
std::unique_ptr<PointerDataPacket> packet) override;
|
std::unique_ptr<PointerDataPacket> packet) override;
|
||||||
|
|
||||||
|
// |PlatformView::Delegate|
|
||||||
|
void OnPlatformViewDispatchKeyDataPacket(
|
||||||
|
std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
std::function<void(bool /* handled */)> callback) override;
|
||||||
|
|
||||||
// |PlatformView::Delegate|
|
// |PlatformView::Delegate|
|
||||||
void OnPlatformViewDispatchSemanticsAction(
|
void OnPlatformViewDispatchSemanticsAction(
|
||||||
int32_t id,
|
int32_t id,
|
||||||
|
@ -62,6 +62,10 @@ class MockPlatformViewDelegate : public PlatformView::Delegate {
|
|||||||
MOCK_METHOD1(OnPlatformViewDispatchPointerDataPacket,
|
MOCK_METHOD1(OnPlatformViewDispatchPointerDataPacket,
|
||||||
void(std::unique_ptr<PointerDataPacket> packet));
|
void(std::unique_ptr<PointerDataPacket> packet));
|
||||||
|
|
||||||
|
MOCK_METHOD2(OnPlatformViewDispatchKeyDataPacket,
|
||||||
|
void(std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
KeyDataResponse callback));
|
||||||
|
|
||||||
MOCK_METHOD3(OnPlatformViewDispatchSemanticsAction,
|
MOCK_METHOD3(OnPlatformViewDispatchSemanticsAction,
|
||||||
void(int32_t id,
|
void(int32_t id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
|
@ -25,6 +25,8 @@ class MockDelegate : public PlatformView::Delegate {
|
|||||||
void OnPlatformViewDispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) override {}
|
void OnPlatformViewDispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) override {}
|
||||||
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
||||||
}
|
}
|
||||||
|
void OnPlatformViewDispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
std::function<void(bool)> callback) override {}
|
||||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
std::vector<uint8_t> args) override {}
|
std::vector<uint8_t> args) override {}
|
||||||
|
@ -95,6 +95,8 @@ class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::De
|
|||||||
void OnPlatformViewDispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) override {}
|
void OnPlatformViewDispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) override {}
|
||||||
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
||||||
}
|
}
|
||||||
|
void OnPlatformViewDispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
std::function<void(bool)> callback) override {}
|
||||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
std::vector<uint8_t> args) override {}
|
std::vector<uint8_t> args) override {}
|
||||||
|
@ -78,6 +78,8 @@ class MockDelegate : public PlatformView::Delegate {
|
|||||||
void OnPlatformViewDispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) override {}
|
void OnPlatformViewDispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) override {}
|
||||||
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
||||||
}
|
}
|
||||||
|
void OnPlatformViewDispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||||
|
std::function<void(bool)> callback) override {}
|
||||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
std::vector<uint8_t> args) override {}
|
std::vector<uint8_t> args) override {}
|
||||||
|
@ -1492,6 +1492,58 @@ FlutterEngineResult FlutterEngineSendPointerEvent(
|
|||||||
"running Flutter application.");
|
"running Flutter application.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline flutter::KeyEventType MapKeyEventType(
|
||||||
|
FlutterKeyEventType event_kind) {
|
||||||
|
switch (event_kind) {
|
||||||
|
case kFlutterKeyEventTypeUp:
|
||||||
|
return flutter::KeyEventType::kUp;
|
||||||
|
case kFlutterKeyEventTypeDown:
|
||||||
|
return flutter::KeyEventType::kDown;
|
||||||
|
case kFlutterKeyEventTypeRepeat:
|
||||||
|
return flutter::KeyEventType::kRepeat;
|
||||||
|
}
|
||||||
|
return flutter::KeyEventType::kUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
FlutterEngineResult FlutterEngineSendKeyEvent(FLUTTER_API_SYMBOL(FlutterEngine)
|
||||||
|
engine,
|
||||||
|
const FlutterKeyEvent* event,
|
||||||
|
FlutterKeyEventCallback callback,
|
||||||
|
void* user_data) {
|
||||||
|
if (engine == nullptr) {
|
||||||
|
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == nullptr) {
|
||||||
|
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid key event.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* character = SAFE_ACCESS(event, character, nullptr);
|
||||||
|
|
||||||
|
flutter::KeyData key_data;
|
||||||
|
key_data.Clear();
|
||||||
|
key_data.timestamp = (uint64_t)SAFE_ACCESS(event, timestamp, 0);
|
||||||
|
key_data.type = MapKeyEventType(
|
||||||
|
SAFE_ACCESS(event, type, FlutterKeyEventType::kFlutterKeyEventTypeUp));
|
||||||
|
key_data.physical = SAFE_ACCESS(event, physical, 0);
|
||||||
|
key_data.logical = SAFE_ACCESS(event, logical, 0);
|
||||||
|
key_data.synthesized = SAFE_ACCESS(event, synthesized, false);
|
||||||
|
|
||||||
|
auto packet = std::make_unique<flutter::KeyDataPacket>(key_data, character);
|
||||||
|
|
||||||
|
auto response = [callback, user_data](bool handled) {
|
||||||
|
if (callback != nullptr)
|
||||||
|
callback(handled, user_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return reinterpret_cast<flutter::EmbedderEngine*>(engine)
|
||||||
|
->DispatchKeyDataPacket(std::move(packet), response)
|
||||||
|
? kSuccess
|
||||||
|
: LOG_EMBEDDER_ERROR(kInternalInconsistency,
|
||||||
|
"Could not dispatch the key event to the "
|
||||||
|
"running Flutter application.");
|
||||||
|
}
|
||||||
|
|
||||||
FlutterEngineResult FlutterEngineSendPlatformMessage(
|
FlutterEngineResult FlutterEngineSendPlatformMessage(
|
||||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
const FlutterPlatformMessage* flutter_message) {
|
const FlutterPlatformMessage* flutter_message) {
|
||||||
@ -2166,6 +2218,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
|
|||||||
SET_PROC(RunInitialized, FlutterEngineRunInitialized);
|
SET_PROC(RunInitialized, FlutterEngineRunInitialized);
|
||||||
SET_PROC(SendWindowMetricsEvent, FlutterEngineSendWindowMetricsEvent);
|
SET_PROC(SendWindowMetricsEvent, FlutterEngineSendWindowMetricsEvent);
|
||||||
SET_PROC(SendPointerEvent, FlutterEngineSendPointerEvent);
|
SET_PROC(SendPointerEvent, FlutterEngineSendPointerEvent);
|
||||||
|
SET_PROC(SendKeyEvent, FlutterEngineSendKeyEvent);
|
||||||
SET_PROC(SendPlatformMessage, FlutterEngineSendPlatformMessage);
|
SET_PROC(SendPlatformMessage, FlutterEngineSendPlatformMessage);
|
||||||
SET_PROC(PlatformMessageCreateResponseHandle,
|
SET_PROC(PlatformMessageCreateResponseHandle,
|
||||||
FlutterPlatformMessageCreateResponseHandle);
|
FlutterPlatformMessageCreateResponseHandle);
|
||||||
|
@ -611,6 +611,66 @@ typedef struct {
|
|||||||
int64_t buttons;
|
int64_t buttons;
|
||||||
} FlutterPointerEvent;
|
} FlutterPointerEvent;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
kFlutterKeyEventTypeUp = 1,
|
||||||
|
kFlutterKeyEventTypeDown,
|
||||||
|
kFlutterKeyEventTypeRepeat,
|
||||||
|
} FlutterKeyEventType;
|
||||||
|
|
||||||
|
/// A structure to represent a key event.
|
||||||
|
///
|
||||||
|
/// Sending `FlutterKeyEvent` via `FlutterEngineSendKeyEvent` results in a
|
||||||
|
/// corresponding `FlutterKeyEvent` to be dispatched in the framework. It is
|
||||||
|
/// embedder's responsibility to ensure the regularity of sent events, since the
|
||||||
|
/// framework only performs simple one-to-one mapping. The events must conform
|
||||||
|
/// the following rules:
|
||||||
|
///
|
||||||
|
/// * Each key press sequence shall consist of one key down event (`kind` being
|
||||||
|
/// `kFlutterKeyEventTypeDown`), zero or more repeat events, and one key up
|
||||||
|
/// event, representing a physical key button being pressed, held, and
|
||||||
|
/// released.
|
||||||
|
/// * All events throughout a key press sequence shall have the same `physical`
|
||||||
|
/// and `logical`. Having different `character`s is allowed.
|
||||||
|
typedef struct {
|
||||||
|
/// The size of this struct. Must be sizeof(FlutterKeyEvent).
|
||||||
|
size_t struct_size;
|
||||||
|
/// The timestamp at which the key event was generated. The timestamp should
|
||||||
|
/// be specified in microseconds and the clock should be the same as that used
|
||||||
|
/// by `FlutterEngineGetCurrentTime`.
|
||||||
|
double timestamp;
|
||||||
|
/// The event kind.
|
||||||
|
FlutterKeyEventType type;
|
||||||
|
/// The USB HID code for the physical key of the event.
|
||||||
|
///
|
||||||
|
/// For the full definition and list of pre-defined physical keys, see
|
||||||
|
/// `PhysicalKeyboardKey` from the framework.
|
||||||
|
uint64_t physical;
|
||||||
|
/// The key ID for the logical key of this event.
|
||||||
|
///
|
||||||
|
/// For the full definition and a list of pre-defined logical keys, see
|
||||||
|
/// `LogicalKeyboardKey` from the framework.
|
||||||
|
uint64_t logical;
|
||||||
|
/// Null-terminated character input from the event. Can be null. Ignored for
|
||||||
|
/// up events.
|
||||||
|
const char* character;
|
||||||
|
/// True if this event does not correspond to a native event.
|
||||||
|
///
|
||||||
|
/// The embedder is likely to skip events and/or construct new events that do
|
||||||
|
/// not correspond to any native events in order to conform the regularity
|
||||||
|
/// of events (as documented in `FlutterKeyEvent`). An example is when a key
|
||||||
|
/// up is missed due to loss of window focus, on a platform that provides
|
||||||
|
/// query to key pressing status, the embedder might realize that the key has
|
||||||
|
/// been released at the next key event, and should construct a synthesized up
|
||||||
|
/// event immediately before the actual event.
|
||||||
|
///
|
||||||
|
/// An event being synthesized means that the `timestamp` might greatly
|
||||||
|
/// deviate from the actual time when the event occurs physically.
|
||||||
|
bool synthesized;
|
||||||
|
} FlutterKeyEvent;
|
||||||
|
|
||||||
|
typedef void (*FlutterKeyEventCallback)(bool /* handled */,
|
||||||
|
void* /* user_data */);
|
||||||
|
|
||||||
struct _FlutterPlatformMessageResponseHandle;
|
struct _FlutterPlatformMessageResponseHandle;
|
||||||
typedef struct _FlutterPlatformMessageResponseHandle
|
typedef struct _FlutterPlatformMessageResponseHandle
|
||||||
FlutterPlatformMessageResponseHandle;
|
FlutterPlatformMessageResponseHandle;
|
||||||
@ -1549,6 +1609,32 @@ FlutterEngineResult FlutterEngineSendPointerEvent(
|
|||||||
const FlutterPointerEvent* events,
|
const FlutterPointerEvent* events,
|
||||||
size_t events_count);
|
size_t events_count);
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/// @brief Sends a key event to the engine. The framework will decide
|
||||||
|
/// whether to handle this event in a synchronous fashion, although
|
||||||
|
/// due to technical limitation, the result is always reported
|
||||||
|
/// asynchronously. The `callback` is guaranteed to be called
|
||||||
|
/// exactly once.
|
||||||
|
///
|
||||||
|
/// @param[in] engine A running engine instance.
|
||||||
|
/// @param[in] event The event data to be sent. This function will no
|
||||||
|
/// longer access `event` after returning.
|
||||||
|
/// @param[in] callback The callback invoked by the engine when the
|
||||||
|
/// Flutter application has decided whether it
|
||||||
|
/// handles this event. Accepts nullptr.
|
||||||
|
/// @param[in] user_data The context associated with the callback. The
|
||||||
|
/// exact same value will used to invoke `callback`.
|
||||||
|
/// Accepts nullptr.
|
||||||
|
///
|
||||||
|
/// @return The result of the call.
|
||||||
|
///
|
||||||
|
FLUTTER_EXPORT
|
||||||
|
FlutterEngineResult FlutterEngineSendKeyEvent(FLUTTER_API_SYMBOL(FlutterEngine)
|
||||||
|
engine,
|
||||||
|
const FlutterKeyEvent* event,
|
||||||
|
FlutterKeyEventCallback callback,
|
||||||
|
void* user_data);
|
||||||
|
|
||||||
FLUTTER_EXPORT
|
FLUTTER_EXPORT
|
||||||
FlutterEngineResult FlutterEngineSendPlatformMessage(
|
FlutterEngineResult FlutterEngineSendPlatformMessage(
|
||||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
@ -2062,6 +2148,11 @@ typedef FlutterEngineResult (*FlutterEngineSendPointerEventFnPtr)(
|
|||||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
const FlutterPointerEvent* events,
|
const FlutterPointerEvent* events,
|
||||||
size_t events_count);
|
size_t events_count);
|
||||||
|
typedef FlutterEngineResult (*FlutterEngineSendKeyEventFnPtr)(
|
||||||
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
|
const FlutterKeyEvent* event,
|
||||||
|
FlutterKeyEventCallback callback,
|
||||||
|
void* user_data);
|
||||||
typedef FlutterEngineResult (*FlutterEngineSendPlatformMessageFnPtr)(
|
typedef FlutterEngineResult (*FlutterEngineSendPlatformMessageFnPtr)(
|
||||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
const FlutterPlatformMessage* message);
|
const FlutterPlatformMessage* message);
|
||||||
@ -2155,6 +2246,7 @@ typedef struct {
|
|||||||
FlutterEngineRunInitializedFnPtr RunInitialized;
|
FlutterEngineRunInitializedFnPtr RunInitialized;
|
||||||
FlutterEngineSendWindowMetricsEventFnPtr SendWindowMetricsEvent;
|
FlutterEngineSendWindowMetricsEventFnPtr SendWindowMetricsEvent;
|
||||||
FlutterEngineSendPointerEventFnPtr SendPointerEvent;
|
FlutterEngineSendPointerEventFnPtr SendPointerEvent;
|
||||||
|
FlutterEngineSendKeyEventFnPtr SendKeyEvent;
|
||||||
FlutterEngineSendPlatformMessageFnPtr SendPlatformMessage;
|
FlutterEngineSendPlatformMessageFnPtr SendPlatformMessage;
|
||||||
FlutterEnginePlatformMessageCreateResponseHandleFnPtr
|
FlutterEnginePlatformMessageCreateResponseHandleFnPtr
|
||||||
PlatformMessageCreateResponseHandle;
|
PlatformMessageCreateResponseHandle;
|
||||||
|
@ -136,6 +136,22 @@ bool EmbedderEngine::DispatchPointerDataPacket(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EmbedderEngine::DispatchKeyDataPacket(
|
||||||
|
std::unique_ptr<flutter::KeyDataPacket> packet,
|
||||||
|
KeyDataResponse callback) {
|
||||||
|
if (!IsValid() || !packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto platform_view = shell_->GetPlatformView();
|
||||||
|
if (!platform_view) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_view->DispatchKeyDataPacket(std::move(packet), std::move(callback));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool EmbedderEngine::SendPlatformMessage(
|
bool EmbedderEngine::SendPlatformMessage(
|
||||||
fml::RefPtr<flutter::PlatformMessage> message) {
|
fml::RefPtr<flutter::PlatformMessage> message) {
|
||||||
if (!IsValid() || !message) {
|
if (!IsValid() || !message) {
|
||||||
|
@ -60,6 +60,22 @@ class EmbedderEngine {
|
|||||||
bool DispatchPointerDataPacket(
|
bool DispatchPointerDataPacket(
|
||||||
std::unique_ptr<flutter::PointerDataPacket> packet);
|
std::unique_ptr<flutter::PointerDataPacket> packet);
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
/// @brief Notifies the platform view that the embedder has sent it a key
|
||||||
|
/// data packet. A key data packet contains one key event. This
|
||||||
|
/// call originates in the platform view and the shell has
|
||||||
|
/// forwarded the same to the engine on the UI task runner here.
|
||||||
|
/// The platform view will decide whether to handle this event,
|
||||||
|
/// and send the result using `callback`, which will be called
|
||||||
|
/// exactly once.
|
||||||
|
///
|
||||||
|
/// @param[in] packet The key data packet.
|
||||||
|
/// @param[in] callback Called when the framework has decided whether
|
||||||
|
/// to handle this key data.
|
||||||
|
///
|
||||||
|
bool DispatchKeyDataPacket(std::unique_ptr<flutter::KeyDataPacket> packet,
|
||||||
|
KeyDataResponse callback);
|
||||||
|
|
||||||
bool SendPlatformMessage(fml::RefPtr<flutter::PlatformMessage> message);
|
bool SendPlatformMessage(fml::RefPtr<flutter::PlatformMessage> message);
|
||||||
|
|
||||||
bool RegisterTexture(int64_t texture);
|
bool RegisterTexture(int64_t texture);
|
||||||
|
@ -495,6 +495,47 @@ Picture CreateGradientBox(Size size) {
|
|||||||
return baseRecorder.endRecording();
|
return baseRecorder.endRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _echoKeyEvent(
|
||||||
|
int change,
|
||||||
|
int timestamp,
|
||||||
|
int physical,
|
||||||
|
int logical,
|
||||||
|
int charCode,
|
||||||
|
bool synthesized)
|
||||||
|
native 'EchoKeyEvent';
|
||||||
|
|
||||||
|
// Convert `kind` in enum form to its integer form.
|
||||||
|
//
|
||||||
|
// It performs a revesed mapping from `unserializeKeyEventKind`
|
||||||
|
// in shell/platform/embedder/tests/embedder_unittests.cc.
|
||||||
|
int _serializeKeyEventType(KeyEventType change) {
|
||||||
|
switch(change) {
|
||||||
|
case KeyEventType.up:
|
||||||
|
return 1;
|
||||||
|
case KeyEventType.down:
|
||||||
|
return 2;
|
||||||
|
case KeyEventType.repeat:
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Echo the event data with `_echoKeyEvent`, and returns synthesized as handled.
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void key_data_echo() async { // ignore: non_constant_identifier_names
|
||||||
|
PlatformDispatcher.instance.onKeyData = (KeyData data) {
|
||||||
|
_echoKeyEvent(
|
||||||
|
_serializeKeyEventType(data.type),
|
||||||
|
data.timeStamp.inMicroseconds,
|
||||||
|
data.physical,
|
||||||
|
data.logical,
|
||||||
|
data.character == null ? 0 : data.character!.codeUnitAt(0),
|
||||||
|
data.synthesized,
|
||||||
|
);
|
||||||
|
return data.synthesized;
|
||||||
|
};
|
||||||
|
signalNativeTest();
|
||||||
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void render_gradient() {
|
void render_gradient() {
|
||||||
PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
|
PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
|
||||||
|
@ -1191,5 +1191,208 @@ TEST_F(EmbedderTest, CanLaunchAndShutdownWithAValidElfSource) {
|
|||||||
engine.reset();
|
engine.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Key Data
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
std::shared_ptr<fml::AutoResetWaitableEvent> latch;
|
||||||
|
bool returned;
|
||||||
|
} KeyEventUserData;
|
||||||
|
|
||||||
|
// Convert `kind` in integer form to its enum form.
|
||||||
|
//
|
||||||
|
// It performs a revesed mapping from `_serializeKeyEventType`
|
||||||
|
// in shell/platform/embedder/fixtures/main.dart.
|
||||||
|
FlutterKeyEventType UnserializeKeyEventKind(uint64_t kind) {
|
||||||
|
switch (kind) {
|
||||||
|
case 1:
|
||||||
|
return kFlutterKeyEventTypeUp;
|
||||||
|
case 2:
|
||||||
|
return kFlutterKeyEventTypeDown;
|
||||||
|
case 3:
|
||||||
|
return kFlutterKeyEventTypeRepeat;
|
||||||
|
default:
|
||||||
|
FML_UNREACHABLE();
|
||||||
|
return kFlutterKeyEventTypeUp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks the equality of two `FlutterKeyEvent` by each of their members except
|
||||||
|
// for `character`. The `character` must be checked separately.
|
||||||
|
void ExpectKeyEventEq(const FlutterKeyEvent& subject,
|
||||||
|
const FlutterKeyEvent& baseline) {
|
||||||
|
EXPECT_EQ(subject.timestamp, baseline.timestamp);
|
||||||
|
EXPECT_EQ(subject.type, baseline.type);
|
||||||
|
EXPECT_EQ(subject.physical, baseline.physical);
|
||||||
|
EXPECT_EQ(subject.logical, baseline.logical);
|
||||||
|
EXPECT_EQ(subject.synthesized, baseline.synthesized);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EmbedderTest, KeyDataIsCorrectlySerialized) {
|
||||||
|
auto message_latch = std::make_shared<fml::AutoResetWaitableEvent>();
|
||||||
|
uint64_t echoed_char;
|
||||||
|
FlutterKeyEvent echoed_event;
|
||||||
|
|
||||||
|
auto native_echo_event = [&](Dart_NativeArguments args) {
|
||||||
|
echoed_event.type =
|
||||||
|
UnserializeKeyEventKind(tonic::DartConverter<uint64_t>::FromDart(
|
||||||
|
Dart_GetNativeArgument(args, 0)));
|
||||||
|
echoed_event.timestamp = tonic::DartConverter<uint64_t>::FromDart(
|
||||||
|
Dart_GetNativeArgument(args, 1));
|
||||||
|
echoed_event.physical = tonic::DartConverter<uint64_t>::FromDart(
|
||||||
|
Dart_GetNativeArgument(args, 2));
|
||||||
|
echoed_event.logical = tonic::DartConverter<uint64_t>::FromDart(
|
||||||
|
Dart_GetNativeArgument(args, 3));
|
||||||
|
echoed_char = tonic::DartConverter<uint64_t>::FromDart(
|
||||||
|
Dart_GetNativeArgument(args, 4));
|
||||||
|
echoed_event.synthesized =
|
||||||
|
tonic::DartConverter<bool>::FromDart(Dart_GetNativeArgument(args, 5));
|
||||||
|
|
||||||
|
message_latch->Signal();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);
|
||||||
|
EmbedderConfigBuilder builder(context);
|
||||||
|
builder.SetSoftwareRendererConfig();
|
||||||
|
builder.SetDartEntrypoint("key_data_echo");
|
||||||
|
fml::AutoResetWaitableEvent ready;
|
||||||
|
context.AddNativeCallback(
|
||||||
|
"SignalNativeTest",
|
||||||
|
CREATE_NATIVE_ENTRY(
|
||||||
|
[&ready](Dart_NativeArguments args) { ready.Signal(); }));
|
||||||
|
|
||||||
|
context.AddNativeCallback("EchoKeyEvent",
|
||||||
|
CREATE_NATIVE_ENTRY(native_echo_event));
|
||||||
|
|
||||||
|
auto engine = builder.LaunchEngine();
|
||||||
|
ASSERT_TRUE(engine.is_valid());
|
||||||
|
ready.Wait();
|
||||||
|
|
||||||
|
// A normal down event
|
||||||
|
const FlutterKeyEvent down_event_upper_a{
|
||||||
|
.struct_size = sizeof(FlutterKeyEvent),
|
||||||
|
.timestamp = 1,
|
||||||
|
.type = kFlutterKeyEventTypeDown,
|
||||||
|
.physical = 0x00070004,
|
||||||
|
.logical = 0x00000000061,
|
||||||
|
.character = "A",
|
||||||
|
.synthesized = false,
|
||||||
|
};
|
||||||
|
FlutterEngineSendKeyEvent(
|
||||||
|
engine.get(), &down_event_upper_a, [](bool handled, void* user_data) {},
|
||||||
|
nullptr);
|
||||||
|
message_latch->Wait();
|
||||||
|
|
||||||
|
ExpectKeyEventEq(echoed_event, down_event_upper_a);
|
||||||
|
EXPECT_EQ(echoed_char, 0x41llu);
|
||||||
|
|
||||||
|
// A repeat event with multi-byte character
|
||||||
|
const FlutterKeyEvent repeat_event_wide_char{
|
||||||
|
.struct_size = sizeof(FlutterKeyEvent),
|
||||||
|
.timestamp = 1000,
|
||||||
|
.type = kFlutterKeyEventTypeRepeat,
|
||||||
|
.physical = 0x00070005,
|
||||||
|
.logical = 0x00000000062,
|
||||||
|
.character = "∆",
|
||||||
|
.synthesized = false,
|
||||||
|
};
|
||||||
|
FlutterEngineSendKeyEvent(
|
||||||
|
engine.get(), &repeat_event_wide_char,
|
||||||
|
[](bool handled, void* user_data) {}, nullptr);
|
||||||
|
message_latch->Wait();
|
||||||
|
|
||||||
|
ExpectKeyEventEq(echoed_event, repeat_event_wide_char);
|
||||||
|
EXPECT_EQ(echoed_char, 0x2206llu);
|
||||||
|
|
||||||
|
// An up event with no character, synthesized
|
||||||
|
const FlutterKeyEvent up_event{
|
||||||
|
.struct_size = sizeof(FlutterKeyEvent),
|
||||||
|
.timestamp = 1000000,
|
||||||
|
.type = kFlutterKeyEventTypeUp,
|
||||||
|
.physical = 0x00070006,
|
||||||
|
.logical = 0x00000000063,
|
||||||
|
.character = nullptr,
|
||||||
|
.synthesized = true,
|
||||||
|
};
|
||||||
|
FlutterEngineSendKeyEvent(
|
||||||
|
engine.get(), &up_event, [](bool handled, void* user_data) {}, nullptr);
|
||||||
|
message_latch->Wait();
|
||||||
|
|
||||||
|
ExpectKeyEventEq(echoed_event, up_event);
|
||||||
|
EXPECT_EQ(echoed_char, 0llu);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EmbedderTest, KeyDataResponseIsCorrectlyInvoked) {
|
||||||
|
auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);
|
||||||
|
EmbedderConfigBuilder builder(context);
|
||||||
|
builder.SetSoftwareRendererConfig();
|
||||||
|
builder.SetDartEntrypoint("key_data_echo");
|
||||||
|
fml::AutoResetWaitableEvent ready;
|
||||||
|
context.AddNativeCallback(
|
||||||
|
"SignalNativeTest",
|
||||||
|
CREATE_NATIVE_ENTRY(
|
||||||
|
[&ready](Dart_NativeArguments args) { ready.Signal(); }));
|
||||||
|
|
||||||
|
context.AddNativeCallback(
|
||||||
|
"EchoKeyEvent", CREATE_NATIVE_ENTRY([](Dart_NativeArguments args) {}));
|
||||||
|
|
||||||
|
auto engine = builder.LaunchEngine();
|
||||||
|
ASSERT_TRUE(engine.is_valid());
|
||||||
|
ready.Wait();
|
||||||
|
|
||||||
|
// Dispatch a single event
|
||||||
|
FlutterKeyEvent event{
|
||||||
|
.struct_size = sizeof(FlutterKeyEvent),
|
||||||
|
.timestamp = 1000,
|
||||||
|
.type = kFlutterKeyEventTypeDown,
|
||||||
|
.physical = 0x00070005,
|
||||||
|
.logical = 0x00000000062,
|
||||||
|
.character = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyEventUserData user_data1{
|
||||||
|
.latch = std::make_shared<fml::AutoResetWaitableEvent>(),
|
||||||
|
};
|
||||||
|
// Entrypoint `key_data_echo` uses `event.synthesized` as `handled`.
|
||||||
|
event.synthesized = true;
|
||||||
|
FlutterEngineSendKeyEvent(
|
||||||
|
engine.get(), &event,
|
||||||
|
[](bool handled, void* untyped_user_data) {
|
||||||
|
KeyEventUserData* user_data =
|
||||||
|
reinterpret_cast<KeyEventUserData*>(untyped_user_data);
|
||||||
|
EXPECT_EQ(handled, true);
|
||||||
|
user_data->latch->Signal();
|
||||||
|
},
|
||||||
|
&user_data1);
|
||||||
|
user_data1.latch->Wait();
|
||||||
|
|
||||||
|
// Dispatch two events back to back, using the same callback on different
|
||||||
|
// user_data
|
||||||
|
KeyEventUserData user_data2{
|
||||||
|
.latch = std::make_shared<fml::AutoResetWaitableEvent>(),
|
||||||
|
.returned = false,
|
||||||
|
};
|
||||||
|
KeyEventUserData user_data3{
|
||||||
|
.latch = std::make_shared<fml::AutoResetWaitableEvent>(),
|
||||||
|
.returned = false,
|
||||||
|
};
|
||||||
|
auto callback23 = [](bool handled, void* untyped_user_data) {
|
||||||
|
KeyEventUserData* user_data =
|
||||||
|
reinterpret_cast<KeyEventUserData*>(untyped_user_data);
|
||||||
|
EXPECT_EQ(handled, false);
|
||||||
|
user_data->returned = true;
|
||||||
|
user_data->latch->Signal();
|
||||||
|
};
|
||||||
|
|
||||||
|
event.synthesized = false;
|
||||||
|
FlutterEngineSendKeyEvent(engine.get(), &event, callback23, &user_data2);
|
||||||
|
FlutterEngineSendKeyEvent(engine.get(), &event, callback23, &user_data3);
|
||||||
|
user_data2.latch->Wait();
|
||||||
|
user_data3.latch->Wait();
|
||||||
|
EXPECT_TRUE(user_data2.returned);
|
||||||
|
EXPECT_TRUE(user_data3.returned);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace testing
|
} // namespace testing
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
@ -81,6 +81,10 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
|
|||||||
void OnPlatformViewDispatchPointerDataPacket(
|
void OnPlatformViewDispatchPointerDataPacket(
|
||||||
std::unique_ptr<flutter::PointerDataPacket> packet) {}
|
std::unique_ptr<flutter::PointerDataPacket> packet) {}
|
||||||
// |flutter::PlatformView::Delegate|
|
// |flutter::PlatformView::Delegate|
|
||||||
|
void OnPlatformViewDispatchKeyDataPacket(
|
||||||
|
std::unique_ptr<flutter::KeyDataPacket> packet,
|
||||||
|
std::function<void(bool)> callback) {}
|
||||||
|
// |flutter::PlatformView::Delegate|
|
||||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||||
flutter::SemanticsAction action,
|
flutter::SemanticsAction action,
|
||||||
std::vector<uint8_t> args) {}
|
std::vector<uint8_t> args) {}
|
||||||
|
@ -24,6 +24,7 @@ part '../../lib/ui/compositing.dart';
|
|||||||
part '../../lib/ui/geometry.dart';
|
part '../../lib/ui/geometry.dart';
|
||||||
part '../../lib/ui/hash_codes.dart';
|
part '../../lib/ui/hash_codes.dart';
|
||||||
part '../../lib/ui/hooks.dart';
|
part '../../lib/ui/hooks.dart';
|
||||||
|
part '../../lib/ui/key.dart';
|
||||||
part '../../lib/ui/lerp.dart';
|
part '../../lib/ui/lerp.dart';
|
||||||
part '../../lib/ui/natives.dart';
|
part '../../lib/ui/natives.dart';
|
||||||
part '../../lib/ui/painting.dart';
|
part '../../lib/ui/painting.dart';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user