From 50ebcd1dff38293307f2e0368a7e2c2ab2e835bf Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Tue, 18 Oct 2016 00:59:40 -0700 Subject: [PATCH] Migrate RawKeyboard to platform events (#6366) This patch moves RawKeyboard from mojom over to platform messages. In the process, I've also cleaned up the interface substantially. Currently raw key events are supported only on Android, but the interfaces defined in this patch should scale up to multiple platforms. --- dev/manual_tests/raw_keyboard.dart | 31 +-- packages/flutter/lib/services.dart | 1 + .../lib/src/services/raw_keyboard.dart | 176 ++++++++++++++++++ .../src/widgets/raw_keyboard_listener.dart | 28 ++- 4 files changed, 208 insertions(+), 28 deletions(-) create mode 100644 packages/flutter/lib/src/services/raw_keyboard.dart diff --git a/dev/manual_tests/raw_keyboard.dart b/dev/manual_tests/raw_keyboard.dart index dc54956283..1d7dfddb7a 100644 --- a/dev/manual_tests/raw_keyboard.dart +++ b/dev/manual_tests/raw_keyboard.dart @@ -3,8 +3,7 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; - -import 'package:flutter_services/input_event.dart' as mojom; +import 'package:flutter/services.dart'; GlobalKey _key = new GlobalKey(); @@ -32,9 +31,9 @@ class RawKeyboardDemo extends StatefulWidget { } class _HardwareKeyDemoState extends State { - mojom.InputEvent _event; + RawKeyEvent _event; - void _handleKey(mojom.InputEvent event) { + void _handleKeyEvent(RawKeyEvent event) { setState(() { _event = event; }); @@ -50,26 +49,34 @@ class _HardwareKeyDemoState extends State { Focus.moveTo(config.key); }, child: new Center( - child: new Text('Tap to focus', style: Typography.black.display1) - ) + child: new Text('Tap to focus', style: Typography.black.display1), + ), ); } else if (_event == null) { child = new Center( - child: new Text('Press a key', style: Typography.black.display1) + child: new Text('Press a key', style: Typography.black.display1), ); } else { + int codePoint; + int keyCode; + final RawKeyEventData data = _event.data; + if (data is RawKeyEventDataAndroid) { + codePoint = data.codePoint; + keyCode = data.keyCode; + } child = new Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - new Text('${_event.type}', style: Typography.black.body2), - new Text('${_event.keyData.keyCode}', style: Typography.black.display4) + new Text('${_event.runtimeType}', style: Typography.black.body2), + new Text('codePoint: $codePoint', style: Typography.black.display4), + new Text('keyCode: $keyCode', style: Typography.black.display4), ], - mainAxisAlignment: MainAxisAlignment.center ); } return new RawKeyboardListener( focused: focused, - onKey: _handleKey, - child: child + onKey: _handleKeyEvent, + child: child, ); } } diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart index 31bdc04054..bf3b3baa97 100644 --- a/packages/flutter/lib/services.dart +++ b/packages/flutter/lib/services.dart @@ -27,6 +27,7 @@ export 'src/services/image_stream.dart'; export 'src/services/keyboard.dart'; export 'src/services/path_provider.dart'; export 'src/services/platform_messages.dart'; +export 'src/services/raw_keyboard.dart'; export 'src/services/shell.dart'; export 'src/services/system_chrome.dart'; export 'src/services/system_navigator.dart'; diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart new file mode 100644 index 0000000000..a38f415d64 --- /dev/null +++ b/packages/flutter/lib/src/services/raw_keyboard.dart @@ -0,0 +1,176 @@ +// Copyright 2016 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 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:meta/meta.dart'; + +import 'platform_messages.dart'; + +/// 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. +/// +/// See also: +/// +/// * [RawKeyEventDataAndroid] +/// * [RawKeyEvent] +/// * [RawKeyDownEvent] +/// * [RawKeyUpEvent] +abstract class RawKeyEventData { + /// 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. +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, + }); + + /// See + final int flags; + + /// See + final int codePoint; + + /// See + final int keyCode; + + /// See + final int scanCode; + + /// See + final int metaState; +} + +/// 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. +/// +/// See also: +/// +/// * [RawKeyDownEvent] +/// * [RawKeyUpEvent] +/// * [RawKeyboardListener], a widget that listens for raw key events. +abstract class RawKeyEvent { + /// Initializes fields for subclasses. + const RawKeyEvent({ + @required this.data, + }); + + /// Platform-specific information about the key event. + final RawKeyEventData data; +} + +/// The user has pressed a key on the keyboard. +class RawKeyDownEvent extends RawKeyEvent { + /// Creates a key event that represents the user pressing a key. + const RawKeyDownEvent({ + @required RawKeyEventData data, + }) : super(data: data); +} + +/// The user has released a key on the keyboard. +class RawKeyUpEvent extends RawKeyEvent { + /// Creates a key event that represents the user releasing a key. + const RawKeyUpEvent({ + @required RawKeyEventData data, + }) : super(data: data); +} + +RawKeyEvent _toRawKeyEvent(dynamic message) { + RawKeyEventData data; + + String keymap = message['keymap']; + switch (keymap) { + case 'android': + data = new RawKeyEventDataAndroid( + flags: message['flags'] ?? 0, + codePoint: message['codePoint'] ?? 0, + keyCode: message['keyCode'] ?? 0, + scanCode: message['scanCode'] ?? 0, + metaState: message['metaState'] ?? 0, + ); + break; + default: + throw new FlutterError('Unknown keymap for key events: $keymap'); + } + + String type = message['type']; + switch (type) { + case 'keydown': + return new RawKeyDownEvent(data: data); + case 'keyup': + return new RawKeyUpEvent(data: data); + } + throw new FlutterError('Unknown key event type: $type'); +} + +/// An interface for listening to 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. +/// +/// A [RawKeyboard] is useful for listening to raw key events and hardware +/// buttons that are represented as keys. Typically used by games and other apps +/// that use keyboards for purposes other than text entry. +/// +/// See also: +/// +/// * [RawKeyEvent] +/// * [RawKeyDownEvent] +/// * [RawKeyUpEvent] +class RawKeyboard { + RawKeyboard._() { + PlatformMessages.setJSONMessageHandler('flutter/keyevent', _handleKeyEvent); + } + + /// The shared instance of [RawKeyboard]. + static final RawKeyboard instance = new RawKeyboard._(); + + final List> _listeners = >[]; + + /// Calls the listener every time the user presses or releases a key. + /// + /// Listeners can be removed with [removeListener]. + void addListener(ValueChanged listener) { + _listeners.add(listener); + } + + /// Stop calling the listener every time the user presses or releases a key. + /// + /// Listeners can be added with [addListener]. + void removeListener(ValueChanged listener) { + _listeners.remove(listener); + } + + Future _handleKeyEvent(dynamic message) async { + if (_listeners.isEmpty) + return; + RawKeyEvent event = _toRawKeyEvent(message); + if (event == null) + return; + for (ValueChanged listener in new List>.from(_listeners)) + if (_listeners.contains(listener)) + listener(event); + } +} diff --git a/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart b/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart index fdcd7c1a26..c0220c0ac1 100644 --- a/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart +++ b/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart @@ -3,13 +3,12 @@ // found in the LICENSE file. import 'package:flutter/services.dart'; -import 'package:flutter_services/raw_keyboard.dart' as mojom; -import 'package:flutter_services/input_event.dart' as mojom; import 'basic.dart'; import 'framework.dart'; -/// A widget that calls a callback whenever the user presses a key on a keyboard. +/// A widget that calls a callback whenever the user presses or releases a key +/// on a keyboard. /// /// A [RawKeyboardListener] is useful for listening to raw key events and /// hardware buttons that are represented as keys. Typically used by games and @@ -43,7 +42,7 @@ class RawKeyboardListener extends StatefulWidget { final bool focused; /// Called whenever this widget receives a raw keyboard event. - final ValueChanged onKey; + final ValueChanged onKey; /// The widget below this widget in the tree. final Widget child; @@ -52,15 +51,13 @@ class RawKeyboardListener extends StatefulWidget { _RawKeyboardListenerState createState() => new _RawKeyboardListenerState(); } -class _RawKeyboardListenerState extends State implements mojom.RawKeyboardListener { +class _RawKeyboardListenerState extends State { @override void initState() { super.initState(); _attachOrDetachKeyboard(); } - mojom.RawKeyboardListenerStub _stub; - @override void didUpdateConfig(RawKeyboardListener oldConfig) { _attachOrDetachKeyboard(); @@ -79,22 +76,21 @@ class _RawKeyboardListenerState extends State implements mo _detachKeyboardIfAttached(); } + bool _listening = false; + void _attachKeyboardIfDetached() { - if (_stub != null) + if (_listening) return; - _stub = new mojom.RawKeyboardListenerStub.unbound()..impl = this; - mojom.RawKeyboardServiceProxy keyboard = shell.connectToViewAssociatedService(mojom.RawKeyboardService.connectToService); - keyboard.addListener(_stub); - keyboard.close(); + RawKeyboard.instance.addListener(_handleRawKeyEvent); } void _detachKeyboardIfAttached() { - _stub?.close(); - _stub = null; + if (!_listening) + return; + RawKeyboard.instance.removeListener(_handleRawKeyEvent); } - @override - void onKey(mojom.InputEvent event) { + void _handleRawKeyEvent(RawKeyEvent event) { if (config.onKey != null) config.onKey(event); }