Skip to content

Commit

Permalink
Hardware Keyboard: iOS (flutter#25961)
Browse files Browse the repository at this point in the history
Implement new keyboard event system for iOS.
  • Loading branch information
gspencergoog authored Jul 7, 2021
1 parent b243070 commit 50b5b3f
Show file tree
Hide file tree
Showing 48 changed files with 4,299 additions and 296 deletions.
23 changes: 20 additions & 3 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1046,9 +1046,15 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryM
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelayTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterChannelKeyResponder.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterChannelKeyResponder.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterChannelKeyResponderTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEmbedderKeyResponder.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEmbedderKeyResponder.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEmbedderKeyResponderTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm
Expand All @@ -1057,7 +1063,14 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineT
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterHeadlessDartRunner.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyPrimaryResponder.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeySecondaryResponder.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h
Expand All @@ -1070,15 +1083,17 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatfor
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPluginTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUIPressProxy.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUIPressProxy.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUmbrellaImport.m
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm
Expand All @@ -1087,6 +1102,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewCon
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/IOKit.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm
Expand Down Expand Up @@ -1228,7 +1245,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewC
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap_Internal.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSGLContextSwitch.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSGLContextSwitch.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart
Expand Down
75 changes: 72 additions & 3 deletions lib/ui/key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,78 @@ class KeyData {
/// [KeyRepeatEvent] is never synthesized.
final bool synthesized;

// Returns the bits that are not included in [valueMask], shifted to the
// right.
//
// For example, if the input is 0x12abcdabcd, then the result is 0x12.
//
// This is mostly equivalent to a right shift, resolving the problem that
// JavaScript only support 32-bit bitwise operations and needs to use division
// instead.
static int _nonValueBits(int n) {
const int valueMask = 0x000FFFFFFFF;
// `n >> valueMaskWidth` is equivalent to `n / divisorForValueMask`.
const int divisorForValueMask = valueMask + 1;
const int valueMaskWidth = 32;

// Equivalent to assert(divisorForValueMask == (1 << valueMaskWidth)).
const int _firstDivisorWidth = 28;
assert(divisorForValueMask ==
(1 << _firstDivisorWidth) * (1 << (valueMaskWidth - _firstDivisorWidth)));

// JS only supports up to 2^53 - 1, therefore non-value bits can only
// contain (maxSafeIntegerWidth - valueMaskWidth) bits.
const int maxSafeIntegerWidth = 52;
const int nonValueMask = (1 << (maxSafeIntegerWidth - valueMaskWidth)) - 1;

if (identical(0, 0.0)) { // Detects if we are on the web.
return (n / divisorForValueMask).floor() & nonValueMask;
} else {
return (n >> valueMaskWidth) & nonValueMask;
}
}

String _logicalToString() {
String result = '0x${logical.toRadixString(16)}';
final int nonValueBits = _nonValueBits(logical);
if (nonValueBits & 0x0FF == 0x000) {
result += '(Unicode)';
}
if (nonValueBits & 0x0FF == 0x001) {
result += '(HID)';
}
if (nonValueBits & 0x100 != 0x000) {
result += '(auto)';
}
if (nonValueBits & 0x200 != 0x000) {
result += '(synonym)';
}
return result;
}

String? _escapeCharacter() {
if (character == null) {
return character ?? '<none>';
}
switch (character!) {
case '\n':
return r'"\n"';
case '\t':
return r'"\t"';
case '\r':
return r'"\r"';
case '\b':
return r'"\b"';
case '\f':
return r'"\f"';
default:
return '"$character"';
}
}

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

/// Returns a complete textual description of the information in this object.
String toStringFull() {
Expand All @@ -91,7 +160,7 @@ class KeyData {
'timeStamp: $timeStamp, '
'physical: 0x${physical.toRadixString(16)}, '
'logical: 0x${logical.toRadixString(16)}, '
'character: $character, '
'character: ${_escapeCharacter()}, '
'synthesized: $synthesized'
')';
}
Expand Down
6 changes: 6 additions & 0 deletions lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ typedef PointerDataPacketCallback = void Function(PointerDataPacket packet);
typedef _KeyDataResponseCallback = void Function(int responseId, bool handled);

/// Signature for [PlatformDispatcher.onKeyData].
///
/// The callback should return true if the key event has been handled by the
/// framework and should not be propagated further.
typedef KeyDataCallback = bool Function(KeyData data);

/// Signature for [PlatformDispatcher.onSemanticsAction].
Expand Down Expand Up @@ -346,6 +349,9 @@ class PlatformDispatcher {
///
/// The framework invokes this callback in the same zone in which the callback
/// was set.
///
/// The callback should return true if the key event has been handled by the
/// framework and should not be propagated further.
KeyDataCallback? get onKeyData => _onKeyData;
KeyDataCallback? _onKeyData;
Zone _onKeyDataZone = Zone.root;
Expand Down
8 changes: 7 additions & 1 deletion lib/ui/window/platform_configuration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,13 @@ void PlatformConfiguration::CompleteKeyDataResponse(uint64_t response_id,
}
KeyDataResponse callback = std::move(it->second);
pending_key_responses_.erase(it);
callback(handled);
// The key result callback must be called on the platform thread. This is
// mainly because iOS needs to block the platform message loop with a nested
// loop to respond to key events synchronously, and if the callback is called
// from another thread, it won't stop the nested message loop, causing a
// deadlock.
UIDartState::Current()->GetTaskRunners().GetPlatformTaskRunner()->PostTask(
[handled, callback]() { callback(handled); });
}

Dart_Handle ComputePlatformResolvedLocale(Dart_Handle supportedLocalesHandle) {
Expand Down
25 changes: 23 additions & 2 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,20 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterBinaryMessengerRelay.mm",
"framework/Source/FlutterCallbackCache.mm",
"framework/Source/FlutterCallbackCache_Internal.h",
"framework/Source/FlutterChannelKeyResponder.h",
"framework/Source/FlutterChannelKeyResponder.mm",
"framework/Source/FlutterDartProject.mm",
"framework/Source/FlutterDartProject_Internal.h",
"framework/Source/FlutterEmbedderKeyResponder.h",
"framework/Source/FlutterEmbedderKeyResponder.mm",
"framework/Source/FlutterEngine.mm",
"framework/Source/FlutterEngineGroup.mm",
"framework/Source/FlutterEngine_Internal.h",
"framework/Source/FlutterHeadlessDartRunner.mm",
"framework/Source/FlutterKeyPrimaryResponder.h",
"framework/Source/FlutterKeySecondaryResponder.h",
"framework/Source/FlutterKeyboardManager.h",
"framework/Source/FlutterKeyboardManager.mm",
"framework/Source/FlutterObservatoryPublisher.h",
"framework/Source/FlutterObservatoryPublisher.mm",
"framework/Source/FlutterOverlayView.h",
Expand All @@ -71,10 +79,14 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterTextInputDelegate.h",
"framework/Source/FlutterTextInputPlugin.h",
"framework/Source/FlutterTextInputPlugin.mm",
"framework/Source/FlutterUIPressProxy.h",
"framework/Source/FlutterUIPressProxy.mm",
"framework/Source/FlutterView.h",
"framework/Source/FlutterView.mm",
"framework/Source/FlutterViewController.mm",
"framework/Source/FlutterViewController_Internal.h",
"framework/Source/KeyCodeMap.mm",
"framework/Source/KeyCodeMap_Internal.h",
"framework/Source/SemanticsObject.h",
"framework/Source/SemanticsObject.mm",
"framework/Source/accessibility_bridge.h",
Expand Down Expand Up @@ -146,6 +158,7 @@ source_set("flutter_framework_source") {
"//flutter/shell/common",
"//flutter/shell/platform/darwin/common",
"//flutter/shell/platform/darwin/common:framework_shared",
"//flutter/shell/platform/embedder:embedder_as_internal_library",
"//flutter/shell/profiling:profiling",
"//third_party/skia",
]
Expand Down Expand Up @@ -192,6 +205,7 @@ source_set("ios_test_flutter_mrc") {
deps = [
":flutter_framework_source",
"//flutter/shell/platform/darwin/common:framework_shared",
"//flutter/shell/platform/embedder:embedder_as_internal_library",
"//flutter/third_party/tonic",
"//flutter/third_party/txt",
"//third_party/ocmock:ocmock_shared",
Expand All @@ -201,6 +215,7 @@ source_set("ios_test_flutter_mrc") {
}

shared_library("ios_test_flutter") {
testonly = true
visibility = [ ":*" ]
cflags = [
"-fvisibility=default",
Expand All @@ -220,12 +235,17 @@ shared_library("ios_test_flutter") {
sources = [
"framework/Source/FlutterAppDelegateTest.mm",
"framework/Source/FlutterBinaryMessengerRelayTest.mm",
"framework/Source/FlutterChannelKeyResponderTest.mm",
"framework/Source/FlutterDartProjectTest.mm",
"framework/Source/FlutterEmbedderKeyResponderTest.mm",
"framework/Source/FlutterEngineGroupTest.mm",
"framework/Source/FlutterEngineTest.mm",
"framework/Source/FlutterPluginAppLifeCycleDelegateTest.m",
"framework/Source/FlutterFakeKeyEvents.h",
"framework/Source/FlutterFakeKeyEvents.mm",
"framework/Source/FlutterKeyboardManagerTest.mm",
"framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm",
"framework/Source/FlutterRestorationPluginTest.mm",
"framework/Source/FlutterTextInputPluginTest.m",
"framework/Source/FlutterTextInputPluginTest.mm",
"framework/Source/FlutterViewControllerTest.mm",
"framework/Source/SemanticsObjectTest.mm",
"framework/Source/connection_collection_test.mm",
Expand All @@ -236,6 +256,7 @@ shared_library("ios_test_flutter") {
":ios_test_flutter_mrc",
"//flutter/common:common",
"//flutter/shell/platform/darwin/common:framework_shared",
"//flutter/shell/platform/embedder:embedder_as_internal_library",
"//flutter/third_party/tonic",
"//flutter/third_party/txt",
"//third_party/ocmock:ocmock_shared",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTER_CHANNEL_KEY_RESPONDER_H_
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTER_CHANNEL_KEY_RESPONDER_H_

#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyPrimaryResponder.h"

#import <UIKit/UIKit.h>

#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"

/**
* A primary responder of |FlutterKeyboardManager| that handles events by
* sending the raw information through a method channel.
*
* This class corresponds to the RawKeyboard API in the framework.
*/
@interface FlutterChannelKeyResponder : NSObject <FlutterKeyPrimaryResponder>

/**
* Create an instance by specifying the method channel to use.
*/
- (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel;

@end

#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTER_CHANNEL_KEY_RESPONDER_H_
Loading

0 comments on commit 50b5b3f

Please sign in to comment.