Add support for detecting which modifier keys have been pressed on RawKeyboardEvents (#26265)

This adds some functions to the interface for RawKeyEventData and all subclasses that allow the recipient of an event to determine which modifier keys are currently being pressed without needing to know the specific modifier bitmasks for the platform.

Also adds constants for the modifier bitmasks for each platform, for completeness (and because I needed them anyhow to implement the above).

Added tests for the RawKeyEventData subclasses, and modified the raw_keyboard manual test app to show modifier keys being pressed. I also separated the different platform-specific subclasses into separate files.

Fixes #26155.
This commit is contained in:
Greg Spencer 2019-01-11 13:41:45 -08:00 committed by GitHub
parent 8af3e480a9
commit 141d6e1394
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 948 additions and 106 deletions

View file

@ -20,7 +20,7 @@ void main() {
}
class RawKeyboardDemo extends StatefulWidget {
const RawKeyboardDemo({ Key key }) : super(key: key);
const RawKeyboardDemo({Key key}) : super(key: key);
@override
_HardwareKeyDemoState createState() => _HardwareKeyDemoState();
@ -42,6 +42,14 @@ class _HardwareKeyDemoState extends State<RawKeyboardDemo> {
});
}
String _asHex(int value) => value != null ? '0x${value.toRadixString(16)}' : 'null';
String _getEnumName(dynamic enumItem) {
final String name = '$enumItem';
final int index = name.indexOf('.');
return index == -1 ? name : name.substring(index + 1);
}
@override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
@ -60,28 +68,42 @@ class _HardwareKeyDemoState extends State<RawKeyboardDemo> {
);
}
if (_event == null)
if (_event == null) {
return Text('Press a key', style: textTheme.display1);
int codePoint;
int keyCode;
int hidUsage;
final RawKeyEventData data = _event.data;
if (data is RawKeyEventDataAndroid) {
codePoint = data.codePoint;
keyCode = data.keyCode;
} else if (data is RawKeyEventDataFuchsia) {
codePoint = data.codePoint;
hidUsage = data.hidUsage;
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${_event.runtimeType}', style: textTheme.body2),
Text('codePoint: $codePoint', style: textTheme.display4),
Text('keyCode: $keyCode', style: textTheme.display4),
Text('hidUsage: $hidUsage', style: textTheme.display4),
],
final RawKeyEventData data = _event.data;
final String modifierList = data.modifiersPressed.keys.map<String>(_getEnumName).join(', ').replaceAll('Modifier', '');
final List<Widget> dataText = <Widget>[
Text('${_event.runtimeType}'),
Text('modifiers set: $modifierList'),
];
if (data is RawKeyEventDataAndroid) {
dataText.add(Text('codePoint: ${data.codePoint} (${_asHex(data.codePoint)})'));
dataText.add(Text('keyCode: ${data.keyCode} (${_asHex(data.keyCode)})'));
dataText.add(Text('scanCode: ${data.scanCode} (${_asHex(data.scanCode)})'));
dataText.add(Text('metaState: ${data.metaState} (${_asHex(data.metaState)})'));
dataText.add(Text('flags: ${data.flags} (${_asHex(data.flags)})'));
} else if (data is RawKeyEventDataFuchsia) {
dataText.add(Text('codePoint: ${data.codePoint} (${_asHex(data.codePoint)})'));
dataText.add(Text('hidUsage: ${data.hidUsage} (${_asHex(data.hidUsage)})'));
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
}
for (ModifierKey modifier in data.modifiersPressed.keys) {
for (KeyboardSide side in KeyboardSide.values) {
if (data.isModifierPressed(modifier, side: side)) {
dataText.add(
Text('${_getEnumName(side)} ${_getEnumName(modifier).replaceAll('Modifier', '')} pressed'),
);
}
}
}
return DefaultTextStyle(
style: textTheme.headline,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: dataText,
),
);
},
),

View file

@ -21,6 +21,8 @@ export 'src/services/platform_channel.dart';
export 'src/services/platform_messages.dart';
export 'src/services/platform_views.dart';
export 'src/services/raw_keyboard.dart';
export 'src/services/raw_keyboard_android.dart';
export 'src/services/raw_keyboard_fuschia.dart';
export 'src/services/system_channels.dart';
export 'src/services/system_chrome.dart';
export 'src/services/system_navigator.dart';

View file

@ -6,9 +6,94 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'raw_keyboard_android.dart';
import 'raw_keyboard_fuschia.dart';
import 'system_channels.dart';
/// Base class for platform specific key event data.
/// An enum describing the side of the keyboard that a key is on, to allow
/// discrimination between which key is pressed (e.g. the left or right SHIFT
/// key).
///
/// See also:
///
/// * [RawKeyEventData.isModifierPressed], which accepts this enum as an
/// argument.
enum KeyboardSide {
/// Matches if either the left, right or both versions of the key are pressed.
any,
/// Matches the left version of the key.
left,
/// Matches the right version of the key.
right,
/// Matches the left and right version of the key pressed simultaneously.
all,
}
/// An enum describing the type of modifier key that is being pressed.
///
/// See also:
///
/// * [RawKeyEventData.isModifierPressed], which accepts this enum as an
/// argument.
enum ModifierKey {
/// The CTRL modifier key.
///
/// Typically, there are two of these.
controlModifier,
/// The SHIFT modifier key.
///
/// Typically, there are two of these.
shiftModifier,
/// The ALT modifier key.
///
/// Typically, there are two of these.
altModifier,
/// The META modifier key.
///
/// Typically, there are two of these. This is, for example, the Windows key
/// on Windows (), the Command () key on macOS and iOS, and the Search (🔍)
/// key on Android.
metaModifier,
/// The CAPS LOCK modifier key.
///
/// Typically, there is one of these. Only shown as "pressed" when the caps
/// lock is on, so on a key up when the mode is turned on, on each key press
/// when it's enabled, and on a key down when it is turned off.
capsLockModifier,
/// The NUM LOCK modifier key.
///
/// Typically, there is one of these. Only shown as "pressed" when the num
/// lock is on, so on a key up when the mode is turned on, on each key press
/// when it's enabled, and on a key down when it is turned off.
numLockModifier,
/// The SCROLL LOCK modifier key.
///
/// Typically, there is one of these. Only shown as "pressed" when the scroll
/// lock is on, so on a key up when the mode is turned on, on each key press
/// when it's enabled, and on a key down when it is turned off.
scrollLockModifier,
/// The FUNCTION (Fn) modifier key.
///
/// Typically, there is one of these.
functionModifier,
/// The SYMBOL modifier key.
///
/// Typically, there is one of these.
symbolModifier,
}
/// Base class for platform-specific key event data.
///
/// This base class exists to have a common type to use for each of the
/// target platform's key event data structures.
@ -22,99 +107,79 @@ import 'system_channels.dart';
/// * [RawKeyboard], which uses these interfaces to expose key data.
@immutable
abstract class RawKeyEventData {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
/// Abstract const constructor.
///
/// This constructor enables subclasses to provide const constructors so that
/// they can be used in const expressions.
const RawKeyEventData();
}
/// Platform-specific key event data for Android.
///
/// This object contains information about key events obtained from Android's
/// `KeyEvent` interface.
///
/// See also:
///
/// * [RawKeyboard], which uses this interface to expose key data.
class RawKeyEventDataAndroid extends RawKeyEventData {
/// Creates a key event data structure specific for Android.
/// Returns true if the given [ModifierKey] was pressed at the time of this
/// event.
///
/// The [flags], [codePoint], [keyCode], [scanCode], and [metaState] arguments
/// must not be null.
const RawKeyEventDataAndroid({
this.flags = 0,
this.codePoint = 0,
this.keyCode = 0,
this.scanCode = 0,
this.metaState = 0,
}) : assert(flags != null),
assert(codePoint != null),
assert(keyCode != null),
assert(scanCode != null),
assert(metaState != null);
/// If [side] is specified, then this restricts its check to the specified
/// side of the keyboard. Defaults to checking for the key being down on
/// either side of the keyboard. If there is only one instance of the key on
/// the keyboard, then [side] is ignored.
bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any});
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getFlags()>
final int flags;
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getUnicodeChar()>
final int codePoint;
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getKeyCode()>
final int keyCode;
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getScanCode()>
final int scanCode;
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getMetaState()>
final int metaState;
}
/// Platform-specific key event data for Fuchsia.
///
/// This object contains information about key events obtained from Fuchsia's
/// `KeyData` interface.
///
/// See also:
///
/// * [RawKeyboard], which uses this interface to expose key data.
class RawKeyEventDataFuchsia extends RawKeyEventData {
/// Creates a key event data structure specific for Fuchsia.
/// Returns a [KeyboardSide] enum value that describes which side or sides of
/// the given keyboard modifier key were pressed at the time of this event.
///
/// The [hidUsage], [codePoint], and [modifiers] arguments must not be null.
const RawKeyEventDataFuchsia({
this.hidUsage = 0,
this.codePoint = 0,
this.modifiers = 0,
}) : assert(hidUsage != null),
assert(codePoint != null),
assert(modifiers != null);
/// If the modifier key wasn't pressed at the time of this event, returns
/// null. If the given key only appears in one place on the keyboard, returns
/// [KeyboardSide.all] if pressed. Never returns [KeyboardSide.any], because
/// that doesn't make sense in this context.
KeyboardSide getModifierSide(ModifierKey key);
/// The USB HID usage.
/// Returns true if a CTRL modifier key was pressed at the time of this event,
/// regardless of which side of the keyboard it is on.
///
/// See <http://www.usb.org/developers/hidpage/Hut1_12v2.pdf>
final int hidUsage;
/// Use [isModifierPressed] if you need to know which control key was pressed.
bool get isControlPressed => isModifierPressed(ModifierKey.controlModifier, side: KeyboardSide.any);
/// The Unicode code point represented by the key event, if any.
/// Returns true if a SHIFT modifier key was pressed at the time of this
/// event, regardless of which side of the keyboard it is on.
///
/// If there is no Unicode code point, this value is zero.
final int codePoint;
/// Use [isModifierPressed] if you need to know which shift key was pressed.
bool get isShiftPressed => isModifierPressed(ModifierKey.shiftModifier, side: KeyboardSide.any);
/// The modifiers that we present when the key event occurred.
/// Returns true if a ALT modifier key was pressed at the time of this event,
/// regardless of which side of the keyboard it is on.
///
/// See <https://fuchsia.googlesource.com/garnet/+/master/public/fidl/fuchsia.ui.input/input_event_constants.fidl>
/// for the numerical values of the modifiers.
final int modifiers;
/// Use [isModifierPressed] if you need to know which alt key was pressed.
bool get isAltPressed => isModifierPressed(ModifierKey.altModifier, side: KeyboardSide.any);
/// Returns true if a META modifier key was pressed at the time of this event,
/// regardless of which side of the keyboard it is on.
///
/// Use [isModifierPressed] if you need to know which meta key was pressed.
bool get isMetaPressed => isModifierPressed(ModifierKey.metaModifier, side: KeyboardSide.any);
/// Returns a map of modifier keys that were pressed at the time of this
/// event, and the keyboard side or sides that the key was on.
Map<ModifierKey, KeyboardSide> get modifiersPressed {
final Map<ModifierKey, KeyboardSide> result = <ModifierKey, KeyboardSide>{};
for (ModifierKey key in ModifierKey.values) {
if (isModifierPressed(key)) {
result[key] = getModifierSide(key);
}
}
return result;
}
}
/// Base class for raw key events.
///
/// Raw key events pass through as much information as possible from the
/// underlying platform's key events, which makes they provide a high level of
/// fidelity but a low level of portability.
/// underlying platform's key events, which allows them to provide a high level
/// of fidelity but a low level of portability.
///
/// See also:
///
/// * [RawKeyDownEvent], a specialization for events representing the user pressing a key.
/// * [RawKeyUpEvent], a specialization for events representing the user releasing a key.
/// * [RawKeyDownEvent], a specialization for events representing the user
/// pressing a key.
/// * [RawKeyUpEvent], a specialization for events representing the user
/// releasing a key.
/// * [RawKeyboard], which uses this interface to expose key data.
/// * [RawKeyboardListener], a widget that listens for raw key events.
@immutable
@ -148,8 +213,9 @@ abstract class RawKeyEvent {
);
break;
default:
// We don't yet implement raw key events on iOS, but we don't hit this
// exception because the engine never sends us these messages.
// We don't yet implement raw key events on iOS or other platforms, but
// we don't hit this exception because the engine never sends us these
// messages.
throw FlutterError('Unknown keymap for key events: $keymap');
}
@ -234,13 +300,17 @@ class RawKeyboard {
}
Future<dynamic> _handleKeyEvent(dynamic message) async {
if (_listeners.isEmpty)
if (_listeners.isEmpty) {
return;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(message);
if (event == null)
if (event == null) {
return;
for (ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners))
if (_listeners.contains(listener))
}
for (ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners)) {
if (_listeners.contains(listener)) {
listener(event);
}
}
}
}

View file

@ -0,0 +1,314 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'raw_keyboard.dart';
/// Platform-specific key event data for Android.
///
/// This object contains information about key events obtained from Android's
/// `KeyEvent` interface.
///
/// See also:
///
/// * [RawKeyboard], which uses this interface to expose key data.
class RawKeyEventDataAndroid extends RawKeyEventData {
/// Creates a key event data structure specific for Android.
///
/// The [flags], [codePoint], [keyCode], [scanCode], and [metaState] arguments
/// must not be null.
const RawKeyEventDataAndroid({
this.flags = 0,
this.codePoint = 0,
this.keyCode = 0,
this.scanCode = 0,
this.metaState = 0,
}) : assert(flags != null),
assert(codePoint != null),
assert(keyCode != null),
assert(scanCode != null),
assert(metaState != null);
/// The current set of additional flags for this event.
///
/// Flags indicate things like repeat state, etc.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getFlags()>
/// for more information.
final int flags;
/// The Unicode code point represented by the key event, if any.
///
/// If there is no Unicode code point, this value is zero.
///
/// Dead keys are represented as Unicode combining characters.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getUnicodeChar()>
/// for more information.
final int codePoint;
/// The hardware key code corresponding to this key event.
///
/// This is the physical key that was pressed, not the Unicode character.
/// See [codePoint] for the Unicode character.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getKeyCode()>
/// for more information.
final int keyCode;
/// The hardware scan code id corresponding to this key event.
///
/// These values are not reliable and vary from device to device, so this
/// information is mainly useful for debugging.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getScanCode()>
/// for more information.
final int scanCode;
/// The modifiers that were present when the key event occurred.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getMetaState()>
/// for the numerical values of the `metaState`. Many of these constants are
/// also replicated as static constants in this class.
///
/// See also:
///
/// * [modifiersPressed], which returns a Map of currently pressed modifiers
/// and their keyboard side.
/// * [isModifierPressed], to see if a specific modifier is pressed.
/// * [isControlPressed], to see if a CTRL key is pressed.
/// * [isShiftPressed], to see if a SHIFT key is pressed.
/// * [isAltPressed], to see if an ALT key is pressed.
/// * [isMetaPressed], to see if a META key is pressed.
final int metaState;
bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) {
if (metaState & anyMask == 0) {
return false;
}
switch (side) {
case KeyboardSide.any:
return true;
case KeyboardSide.all:
return metaState & leftMask != 0 && metaState & rightMask != 0;
case KeyboardSide.left:
return metaState & leftMask != 0;
case KeyboardSide.right:
return metaState & rightMask != 0;
}
return false;
}
@override
bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any}) {
assert(side != null);
switch (key) {
case ModifierKey.controlModifier:
return _isLeftRightModifierPressed(side, modifierControl, modifierLeftControl, modifierRightControl);
case ModifierKey.shiftModifier:
return _isLeftRightModifierPressed(side, modifierShift, modifierLeftShift, modifierRightShift);
case ModifierKey.altModifier:
return _isLeftRightModifierPressed(side, modifierAlt, modifierLeftAlt, modifierRightAlt);
case ModifierKey.metaModifier:
return _isLeftRightModifierPressed(side, modifierMeta, modifierLeftMeta, modifierRightMeta);
case ModifierKey.capsLockModifier:
return metaState & modifierCapsLock != 0;
case ModifierKey.numLockModifier:
return metaState & modifierNumLock != 0;
case ModifierKey.scrollLockModifier:
return metaState & modifierScrollLock != 0;
case ModifierKey.functionModifier:
return metaState & modifierFunction != 0;
case ModifierKey.symbolModifier:
return metaState & modifierSym != 0;
}
return false;
}
@override
KeyboardSide getModifierSide(ModifierKey key) {
KeyboardSide findSide(int leftMask, int rightMask) {
final int combinedMask = leftMask | rightMask;
final int combined = metaState & combinedMask;
if (combined == leftMask) {
return KeyboardSide.left;
} else if (combined == rightMask) {
return KeyboardSide.right;
} else if (combined == combinedMask) {
return KeyboardSide.all;
}
return null;
}
switch (key) {
case ModifierKey.controlModifier:
return findSide(modifierLeftControl, modifierRightControl);
case ModifierKey.shiftModifier:
return findSide(modifierLeftShift, modifierRightShift);
case ModifierKey.altModifier:
return findSide(modifierLeftAlt, modifierRightAlt);
case ModifierKey.metaModifier:
return findSide(modifierLeftMeta, modifierRightMeta);
case ModifierKey.capsLockModifier:
case ModifierKey.numLockModifier:
case ModifierKey.scrollLockModifier:
case ModifierKey.functionModifier:
case ModifierKey.symbolModifier:
return KeyboardSide.all;
}
assert(false, 'Not handling $key type properly.');
return null;
}
// Modifier key masks.
/// No modifier keys are pressed in the [metaState] field.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierNone = 0;
/// This mask is used to check the [metaState] field to test whether one of
/// the ALT modifier keys is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierAlt = 0x02;
/// This mask is used to check the [metaState] field to test whether the left
/// ALT modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftAlt = 0x10;
/// This mask is used to check the [metaState] field to test whether the right
/// ALT modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightAlt = 0x20;
/// This mask is used to check the [metaState] field to test whether one of
/// the SHIFT modifier keys is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierShift = 0x01;
/// This mask is used to check the [metaState] field to test whether the left
/// SHIFT modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftShift = 0x40;
/// This mask is used to check the [metaState] field to test whether the right
/// SHIFT modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightShift = 0x80;
/// This mask is used to check the [metaState] field to test whether the SYM
/// modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierSym = 0x04;
/// This mask is used to check the [metaState] field to test whether the
/// Function modifier key (Fn) is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierFunction = 0x08;
/// This mask is used to check the [metaState] field to test whether one of
/// the CTRL modifier keys is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierControl = 0x1000;
/// This mask is used to check the [metaState] field to test whether the left
/// CTRL modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftControl = 0x2000;
/// This mask is used to check the [metaState] field to test whether the right
/// CTRL modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightControl = 0x4000;
/// This mask is used to check the [metaState] field to test whether one of
/// the META modifier keys is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierMeta = 0x10000;
/// This mask is used to check the [metaState] field to test whether the left
/// META modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftMeta = 0x20000;
/// This mask is used to check the [metaState] field to test whether the right
/// META modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightMeta = 0x40000;
/// This mask is used to check the [metaState] field to test whether the CAPS
/// LOCK modifier key is on.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierCapsLock = 0x100000;
/// This mask is used to check the [metaState] field to test whether the NUM
/// LOCK modifier key is on.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierNumLock = 0x200000;
/// This mask is used to check the [metaState] field to test whether the
/// SCROLL LOCK modifier key is on.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierScrollLock = 0x400000;
@override
String toString() {
return '$runtimeType(flags: $flags, codePoint: $codePoint, keyCode: $keyCode, '
'scanCode: $scanCode, metaState: $metaState, modifiers down: $modifiersPressed)';
}
}

View file

@ -0,0 +1,254 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'raw_keyboard.dart';
/// Platform-specific key event data for Fuchsia.
///
/// This object contains information about key events obtained from Fuchsia's
/// `KeyData` interface.
///
/// See also:
///
/// * [RawKeyboard], which uses this interface to expose key data.
class RawKeyEventDataFuchsia extends RawKeyEventData {
/// Creates a key event data structure specific for Fuchsia.
///
/// The [hidUsage], [codePoint], and [modifiers] arguments must not be null.
const RawKeyEventDataFuchsia({
this.hidUsage = 0,
this.codePoint = 0,
this.modifiers = 0,
}) : assert(hidUsage != null),
assert(codePoint != null),
assert(modifiers != null);
/// The USB HID usage.
///
/// See <http://www.usb.org/developers/hidpage/Hut1_12v2.pdf> for more
/// information.
final int hidUsage;
/// The Unicode code point represented by the key event, if any.
///
/// If there is no Unicode code point, this value is zero.
///
/// Dead keys are represented as Unicode combining characters.
final int codePoint;
/// The modifiers that were present when the key event occurred.
///
/// See <https://fuchsia.googlesource.com/garnet/+/master/public/fidl/fuchsia.ui.input/input_event_constants.fidl>
/// for the numerical values of the modifiers. Many of these are also
/// replicated as static constants in this class.
///
/// See also:
///
/// * [modifiersPressed], which returns a Map of currently pressed modifiers
/// and their keyboard side.
/// * [isModifierPressed], to see if a specific modifier is pressed.
/// * [isControlPressed], to see if a CTRL key is pressed.
/// * [isShiftPressed], to see if a SHIFT key is pressed.
/// * [isAltPressed], to see if an ALT key is pressed.
/// * [isMetaPressed], to see if a META key is pressed.
final int modifiers;
bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) {
if (modifiers & anyMask == 0) {
return false;
}
switch (side) {
case KeyboardSide.any:
return true;
case KeyboardSide.all:
return modifiers & leftMask != 0 && modifiers & rightMask != 0;
case KeyboardSide.left:
return modifiers & leftMask != 0;
case KeyboardSide.right:
return modifiers & rightMask != 0;
}
return false;
}
@override
bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any}) {
assert(side != null);
switch (key) {
case ModifierKey.controlModifier:
return _isLeftRightModifierPressed(side, modifierControl, modifierLeftControl, modifierRightControl);
case ModifierKey.shiftModifier:
return _isLeftRightModifierPressed(side, modifierShift, modifierLeftShift, modifierRightShift);
case ModifierKey.altModifier:
return _isLeftRightModifierPressed(side, modifierAlt, modifierLeftAlt, modifierRightAlt);
case ModifierKey.metaModifier:
return _isLeftRightModifierPressed(side, modifierMeta, modifierLeftMeta, modifierRightMeta);
case ModifierKey.capsLockModifier:
return modifiers & modifierCapsLock != 0;
case ModifierKey.numLockModifier:
case ModifierKey.scrollLockModifier:
case ModifierKey.functionModifier:
case ModifierKey.symbolModifier:
// Fuschia doesn't have masks for these keys (yet).
return false;
}
return false;
}
@override
KeyboardSide getModifierSide(ModifierKey key) {
KeyboardSide findSide(int leftMask, int rightMask, int combinedMask) {
final int combined = modifiers & combinedMask;
if (combined == leftMask) {
return KeyboardSide.left;
} else if (combined == rightMask) {
return KeyboardSide.right;
} else if (combined == combinedMask) {
return KeyboardSide.all;
}
return null;
}
switch (key) {
case ModifierKey.controlModifier:
return findSide(modifierLeftControl, modifierRightControl, modifierControl);
case ModifierKey.shiftModifier:
return findSide(modifierLeftShift, modifierRightShift, modifierShift);
case ModifierKey.altModifier:
return findSide(modifierLeftAlt, modifierRightAlt, modifierAlt);
case ModifierKey.metaModifier:
return findSide(modifierLeftMeta, modifierRightMeta, modifierMeta);
case ModifierKey.capsLockModifier:
return (modifiers & modifierCapsLock == 0) ? null : KeyboardSide.all;
case ModifierKey.numLockModifier:
case ModifierKey.scrollLockModifier:
case ModifierKey.functionModifier:
case ModifierKey.symbolModifier:
// Fuchsia doesn't support these modifiers, so they can't be pressed.
return null;
}
assert(false, 'Not handling $key type properly.');
return null;
}
// Keyboard modifier masks for Fuschia modifiers.
/// The [modifiers] field indicates that no modifier keys are pressed if it
/// equals this value.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierNone = 0x0;
/// This mask is used to check the [modifiers] field to test whether the CAPS
/// LOCK modifier key is on.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierCapsLock = 0x1;
/// This mask is used to check the [modifiers] field to test whether the left
/// SHIFT modifier key is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftShift = 0x2;
/// This mask is used to check the [modifiers] field to test whether the right
/// SHIFT modifier key is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightShift = 0x4;
/// This mask is used to check the [modifiers] field to test whether one of
/// the SHIFT modifier keys is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierShift = modifierLeftShift | modifierRightShift;
/// This mask is used to check the [modifiers] field to test whether the left
/// CTRL modifier key is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftControl = 0x8;
/// This mask is used to check the [modifiers] field to test whether the right
/// CTRL modifier key is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightControl = 0x10;
/// This mask is used to check the [modifiers] field to test whether one of
/// the CTRL modifier keys is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierControl = modifierLeftControl | modifierRightControl;
/// This mask is used to check the [modifiers] field to test whether the left
/// ALT modifier key is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftAlt = 0x20;
/// This mask is used to check the [modifiers] field to test whether the right
/// ALT modifier key is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightAlt = 0x40;
/// This mask is used to check the [modifiers] field to test whether one of
/// the ALT modifier keys is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierAlt = modifierLeftAlt | modifierRightAlt;
/// This mask is used to check the [modifiers] field to test whether the left
/// META modifier key is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftMeta = 0x80;
/// This mask is used to check the [modifiers] field to test whether the right
/// META modifier key is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightMeta = 0x100;
/// This mask is used to check the [modifiers] field to test whether one of
/// the META modifier keys is pressed.
///
/// Use this value if you need to decode the [modifiers] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierMeta = modifierLeftMeta | modifierRightMeta;
@override
String toString() {
return '$runtimeType(hidUsage: $hidUsage, codePoint: $codePoint, modifiers: $modifiers, '
'modifiers down: $modifiersPressed)';
}
}

View file

@ -0,0 +1,179 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
class _ModifierCheck {
const _ModifierCheck(this.key, this.side);
final ModifierKey key;
final KeyboardSide side;
}
void main() {
group('RawKeyEventDataAndroid', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
RawKeyEventDataAndroid.modifierAlt | RawKeyEventDataAndroid.modifierLeftAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.left),
RawKeyEventDataAndroid.modifierAlt | RawKeyEventDataAndroid.modifierRightAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.right),
RawKeyEventDataAndroid.modifierShift | RawKeyEventDataAndroid.modifierLeftShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.left),
RawKeyEventDataAndroid.modifierShift | RawKeyEventDataAndroid.modifierRightShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.right),
RawKeyEventDataAndroid.modifierSym: _ModifierCheck(ModifierKey.symbolModifier, KeyboardSide.all),
RawKeyEventDataAndroid.modifierFunction: _ModifierCheck(ModifierKey.functionModifier, KeyboardSide.all),
RawKeyEventDataAndroid.modifierControl | RawKeyEventDataAndroid.modifierLeftControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.left),
RawKeyEventDataAndroid.modifierControl | RawKeyEventDataAndroid.modifierRightControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.right),
RawKeyEventDataAndroid.modifierMeta | RawKeyEventDataAndroid.modifierLeftMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.left),
RawKeyEventDataAndroid.modifierMeta | RawKeyEventDataAndroid.modifierRightMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.right),
RawKeyEventDataAndroid.modifierCapsLock: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.all),
RawKeyEventDataAndroid.modifierNumLock: _ModifierCheck(ModifierKey.numLockModifier, KeyboardSide.all),
RawKeyEventDataAndroid.modifierScrollLock: _ModifierCheck(ModifierKey.scrollLockModifier, KeyboardSide.all),
};
test('modifier keys are recognized individually', () {
for (int modifier in modifierTests.keys) {
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 0x04,
'codePoint': 0x64,
'scanCode': 0x64,
'metaState': modifier,
});
final RawKeyEventDataAndroid data = event.data;
for (ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier].key == key) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isTrue,
reason: "$key should be pressed with metaState $modifier, but isn't.",
);
expect(data.getModifierSide(key), equals(modifierTests[modifier].side));
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isFalse,
reason: '$key should not be pressed with metaState $modifier.',
);
}
}
}
});
test('modifier keys are recognized when combined', () {
for (int modifier in modifierTests.keys) {
if (modifier == RawKeyEventDataAndroid.modifierFunction) {
// No need to combine function key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 0x04,
'codePoint': 0x64,
'scanCode': 0x64,
'metaState': modifier | RawKeyEventDataAndroid.modifierFunction,
});
final RawKeyEventDataAndroid data = event.data;
for (ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier].key == key || key == ModifierKey.functionModifier) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${RawKeyEventDataAndroid.modifierFunction}, but isn't.",
);
if (key != ModifierKey.functionModifier) {
expect(data.getModifierSide(key), equals(modifierTests[modifier].side));
} else {
expect(data.getModifierSide(key), equals(KeyboardSide.all));
}
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isFalse,
reason: '$key should not be pressed with metaState $modifier with metaState $modifier '
'and additional key ${RawKeyEventDataAndroid.modifierFunction}.',
);
}
}
}
});
});
group('RawKeyEventDataFuchsia', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
RawKeyEventDataFuchsia.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.any),
RawKeyEventDataFuchsia.modifierLeftAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.left),
RawKeyEventDataFuchsia.modifierRightAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.right),
RawKeyEventDataFuchsia.modifierShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.any),
RawKeyEventDataFuchsia.modifierLeftShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.left),
RawKeyEventDataFuchsia.modifierRightShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.right),
RawKeyEventDataFuchsia.modifierControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.any),
RawKeyEventDataFuchsia.modifierLeftControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.left),
RawKeyEventDataFuchsia.modifierRightControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.right),
RawKeyEventDataFuchsia.modifierMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.any),
RawKeyEventDataFuchsia.modifierLeftMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.left),
RawKeyEventDataFuchsia.modifierRightMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.right),
RawKeyEventDataFuchsia.modifierCapsLock: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.any),
};
test('modifier keys are recognized individually', () {
for (int modifier in modifierTests.keys) {
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'fuchsia',
'hidUsage': 0x04,
'codePoint': 0x64,
'modifiers': modifier,
});
final RawKeyEventDataFuchsia data = event.data;
for (ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier].key == key) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isTrue,
reason: "$key should be pressed with metaState $modifier, but isn't.",
);
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isFalse,
reason: '$key should not be pressed with metaState $modifier.',
);
}
}
}
});
test('modifier keys are recognized when combined', () {
for (int modifier in modifierTests.keys) {
if (modifier == RawKeyEventDataFuchsia.modifierCapsLock) {
// No need to combine caps lock key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'fuchsia',
'hidUsage': 0x04,
'codePoint': 0x64,
'modifiers': modifier | RawKeyEventDataFuchsia.modifierCapsLock,
});
final RawKeyEventDataFuchsia data = event.data;
for (ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier].key == key || key == ModifierKey.capsLockModifier) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${RawKeyEventDataFuchsia.modifierCapsLock}, but isn't.",
);
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isFalse,
reason: '$key should not be pressed with metaState $modifier '
'and additional key ${RawKeyEventDataFuchsia.modifierCapsLock}.',
);
}
}
}
});
});
}

View file

@ -43,7 +43,7 @@ void main() {
'keymap': 'fuchsia',
'hidUsage': 0x04,
'codePoint': 0x64,
'modifiers': 0x08,
'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta,
});
await tester.idle();
@ -53,7 +53,8 @@ void main() {
final RawKeyEventDataFuchsia typedData = events[0].data;
expect(typedData.hidUsage, 0x04);
expect(typedData.codePoint, 0x64);
expect(typedData.modifiers, 0x08);
expect(typedData.modifiers, RawKeyEventDataFuchsia.modifierLeftMeta);
expect(typedData.isModifierPressed(ModifierKey.metaModifier, side: KeyboardSide.left), isTrue);
await tester.pumpWidget(Container());
focusNode.dispose();
@ -79,7 +80,7 @@ void main() {
'keymap': 'fuchsia',
'hidUsage': 0x04,
'codePoint': 0x64,
'modifiers': 0x08,
'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta,
});
await tester.idle();
@ -93,7 +94,7 @@ void main() {
'keymap': 'fuchsia',
'hidUsage': 0x04,
'codePoint': 0x64,
'modifiers': 0x08,
'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta,
});
await tester.idle();