Skip to content

Commit

Permalink
Add KeyEventDeviceType to KeyData (flutter#47315)
Browse files Browse the repository at this point in the history
## Description

Before we deprecate the `RawKeyEvent` code, it needs to provide parity in functionality. One thing that is missing is the `eventSource` field from the `RawKeyEventDataAndroid` class, which provides the device type for the event.

See https://developer.android.com/reference/android/view/InputDevice#SOURCE_KEYBOARD for an example.

This PR implements that support, and sets the source to `KeyEventDeviceType.keyboard` for platforms that don't provide this information. The main thing it does is add the enum `KeyEventDeviceType`, and a new field `KeyData.deviceType`.

## Related Issues
 - flutter/flutter#136419

## Tests
 - Updated tests to also read/write/verify this property.
  • Loading branch information
gspencergoog committed Nov 6, 2023
1 parent 331d9bf commit cc925b0
Show file tree
Hide file tree
Showing 17 changed files with 991 additions and 281 deletions.
108 changes: 74 additions & 34 deletions lib/ui/key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,47 @@ enum KeyEventType {
up,

/// The key is held, causing a repeated key input.
repeat,
repeat;

String get label {
return switch (this) {
down => 'Key Down',
up => 'Key Up',
repeat => 'Key Repeat',
};
}
}

/// The source device for the key event.
///
/// Not all platforms supply an accurate type.
// Must match the KeyEventDeviceType enum in ui/window/key_data.h.
enum KeyEventDeviceType {
/// The device is a keyboard.
keyboard,

/// The device is a directional pad on something like a television remote
/// control or similar.
directionalPad,

/// The device is a gamepad button
gamepad,

/// The device is a joystick button
joystick,

/// The device is a device connected to an HDMI bus.
hdmi;

String get label {
return switch (this) {
keyboard => 'Keyboard',
directionalPad => 'Directional Pad',
gamepad => 'Gamepad',
joystick => 'Joystick',
hdmi => 'HDMI',
};
}
}

/// Information about a key event.
Expand All @@ -27,6 +67,7 @@ class KeyData {
required this.logical,
required this.character,
required this.synthesized,
this.deviceType = KeyEventDeviceType.keyboard,
});

/// Time of event dispatch, relative to an arbitrary timeline.
Expand All @@ -38,6 +79,10 @@ class KeyData {
/// The type of the event.
final KeyEventType type;

/// Describes what type of device (keyboard, directional pad, etc.) this event
/// originated from.
final KeyEventDeviceType deviceType;

/// The key code for the physical key that has changed.
final int physical;

Expand All @@ -49,30 +94,31 @@ class KeyData {
/// Ignored for up events.
final String? character;

/// If [synthesized] is true, this event does not correspond to a native event.
/// 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).
/// 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.
/// focus. Some platforms provide ways to query whether a key is being held.
/// If the embedder detects an inconsistency 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.
/// As another example, macOS treats CapsLock in a special way by sending down
/// and up events at the down of alternate 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
/// 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.
Expand Down Expand Up @@ -172,29 +218,23 @@ class KeyData {
}

@override
String toString() => 'KeyData(key ${_typeToString(type)}, physical: 0x${physical.toRadixString(16)}, '
'logical: ${_logicalToString()}, character: ${_escapeCharacter()}${_quotedCharCode()}${synthesized ? ', synthesized' : ''})';
String toString() {
return 'KeyData(${type.label}, '
'physical: 0x${physical.toRadixString(16)}, '
'logical: ${_logicalToString()}, '
'character: ${_escapeCharacter()}${_quotedCharCode()}'
'${synthesized ? ', synthesized' : ''}';
}

/// 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: ${_escapeCharacter()}, '
'synthesized: $synthesized'
return '$runtimeType(type: ${type.label}, '
'deviceType: ${deviceType.label}, '
'timeStamp: $timeStamp, '
'physical: 0x${physical.toRadixString(16)}, '
'logical: 0x${logical.toRadixString(16)}, '
'character: ${_escapeCharacter()}, '
'synthesized: $synthesized'
')';
}

static String _typeToString(KeyEventType type) {
switch (type) {
case KeyEventType.up:
return 'up';
case KeyEventType.down:
return 'down';
case KeyEventType.repeat:
return 'repeat';
}
}
}
8 changes: 3 additions & 5 deletions lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -506,11 +506,9 @@ class PlatformDispatcher {

// 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;
// * key_data.h (kKeyDataFieldCount)
// * KeyData.java (KeyData.FIELD_COUNT)
static const int _kKeyDataFieldCount = 6;

// The packet structure is described in `key_data_packet.h`.
static KeyData _unpackKeyData(ByteData packet) {
Expand Down
32 changes: 30 additions & 2 deletions lib/ui/window/key_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@

namespace flutter {

// If this value changes, update the key data unpacking code in hooks.dart.
static constexpr int kKeyDataFieldCount = 5;
// If this value changes, update the encoding code in the following files:
//
// * KeyData.java (KeyData.FIELD_COUNT)
// * platform_dispatcher.dart (_kKeyDataFieldCount)
static constexpr int kKeyDataFieldCount = 6;
static constexpr int kBytesPerKeyField = sizeof(int64_t);

// The change of the key event, used by KeyData.
Expand All @@ -22,6 +25,30 @@ enum class KeyEventType : int64_t {
kRepeat,
};

// The source device for the key event.
//
// Not all platforms supply an accurate source.
//
// Defaults to [keyboard].
// Must match the KeyEventDeviceType enum in ui/key.dart.
enum class KeyEventDeviceType : int64_t {
// The source is a keyboard.
kKeyboard = 0,

// The source is a directional pad on something like a television remote
// control or similar.
kDirectionalPad,

// The source is a gamepad button.
kGamepad,

// The source is a joystick button.
kJoystick,

// The source is a device connected to an HDMI bus.
kHdmi,
};

// The fixed-length sections of a KeyDataPacket.
//
// KeyData does not contain `character`, for variable-length data are stored in
Expand All @@ -41,6 +68,7 @@ struct alignas(8) KeyData {
//
// The value is 1 for true, and 0 for false.
uint64_t synthesized;
KeyEventDeviceType device_type;

// Sets all contents of `Keydata` to 0.
void Clear();
Expand Down
5 changes: 3 additions & 2 deletions lib/ui/window/key_data_packet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "flutter/lib/ui/window/key_data_packet.h"

#include <cstring>
#include <iostream>

#include "flutter/fml/logging.h"

Expand All @@ -13,8 +14,8 @@ 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));
data_.resize(sizeof(char_size_64) + sizeof(KeyData) + char_size);
memcpy(CharacterSizeStart(), &char_size_64, sizeof(char_size_64));
memcpy(KeyDataStart(), &event, sizeof(KeyData));
if (character != nullptr) {
memcpy(CharacterStart(), character, char_size);
Expand Down
Loading

0 comments on commit cc925b0

Please sign in to comment.