Skip to content

Commit 899af73

Browse files
maunstuartmorgan
authored andcommitted
[macOS] Add initial support for raw key events
Implements the core of raw key event handling on macOS. Uses the Android keyboard event structure, and only populates part of it for now. (Partially) addresses issue #72
1 parent 1145c85 commit 899af73

File tree

9 files changed

+321
-29
lines changed

9 files changed

+321
-29
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
import 'package:flutter/material.dart';
15+
import 'package:flutter/scheduler.dart';
16+
import 'package:flutter/services.dart';
17+
18+
/// Keyboard test page for the example application.
19+
class KeyboardTestPage extends StatefulWidget {
20+
@override
21+
State<StatefulWidget> createState() {
22+
return _KeyboardTestPageState();
23+
}
24+
}
25+
26+
class _KeyboardTestPageState extends State<KeyboardTestPage> {
27+
final List<String> _messages = [];
28+
29+
final FocusNode _focusNode = FocusNode();
30+
final ScrollController _scrollController = new ScrollController();
31+
32+
@override
33+
void initState() {
34+
super.initState();
35+
FocusScope.of(context).requestFocus(_focusNode);
36+
}
37+
38+
@override
39+
Widget build(BuildContext context) {
40+
return new Scaffold(
41+
appBar: AppBar(
42+
title: new Text('Keyboard events test'),
43+
leading: new IconButton(
44+
icon: new Icon(Icons.arrow_back),
45+
onPressed: () {
46+
Navigator.of(context).pop();
47+
})),
48+
body: new RawKeyboardListener(
49+
focusNode: _focusNode,
50+
onKey: onKeyEvent,
51+
child: Container(
52+
padding: EdgeInsets.symmetric(horizontal: 16.0),
53+
child: SingleChildScrollView(
54+
controller: _scrollController,
55+
child: Column(
56+
crossAxisAlignment: CrossAxisAlignment.start,
57+
children: _messages.map((m) => new Text(m)).toList())),
58+
),
59+
),
60+
);
61+
}
62+
63+
void onKeyEvent(RawKeyEvent event) {
64+
bool isKeyDown;
65+
switch (event.runtimeType) {
66+
case RawKeyDownEvent:
67+
isKeyDown = true;
68+
break;
69+
case RawKeyUpEvent:
70+
isKeyDown = false;
71+
break;
72+
default:
73+
throw new Exception('Unexpected runtimeType of RawKeyEvent');
74+
}
75+
76+
int keyCode;
77+
switch (event.data.runtimeType) {
78+
case RawKeyEventDataAndroid:
79+
final RawKeyEventDataAndroid data = event.data;
80+
keyCode = data.keyCode;
81+
break;
82+
default:
83+
throw new Exception('Unsupported platform');
84+
}
85+
86+
_addMessage('${isKeyDown ? 'KeyDown' : 'KeyUp'}: $keyCode');
87+
}
88+
89+
void _addMessage(String message) {
90+
setState(() {
91+
_messages.add(message);
92+
});
93+
SchedulerBinding.instance.addPostFrameCallback((_) {
94+
_scrollController.jumpTo(
95+
_scrollController.position.maxScrollExtent,
96+
);
97+
});
98+
}
99+
}

example_flutter/lib/main.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:flutter/foundation.dart'
1818
import 'package:flutter/material.dart';
1919

2020
import 'package:color_panel/color_panel.dart';
21+
import 'package:example_flutter/keyboard_test_page.dart';
2122
import 'package:file_chooser/file_chooser.dart' as file_chooser;
2223
import 'package:menubar/menubar.dart';
2324

@@ -181,6 +182,12 @@ class _MyHomePage extends StatelessWidget {
181182
),
182183
TextInputTestWidget(),
183184
FileChooserTestWidget(),
185+
new RaisedButton(
186+
child: new Text('Test raw keyboard events'),
187+
onPressed: () {
188+
Navigator.of(context).push(new MaterialPageRoute(
189+
builder: (context) => KeyboardTestPage()));
190+
})
184191
],
185192
),
186193
),

macos/library/FLECodecs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
* A codec that uses JSON as the encoding format. Method arguments and error details for plugins
5656
* using this codec must be serializable to JSON.
5757
*/
58-
@interface FLEJSONMethodCodec : NSObject<FLEMethodCodec>
58+
@interface FLEJSONMethodCodec : NSObject <FLEMethodCodec>
5959
@end
6060

6161
// TODO: Implement the other core Flutter codecs. Issue #67.

macos/library/FLEKeyEventPlugin.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#import <Cocoa/Cocoa.h>
16+
17+
#import "FLEPlugin.h"
18+
19+
/**
20+
* A FlutterPlugin to handle raw keyboard events. Owned by the FlutterViewController.
21+
* Responsible for bridging the native macOS keyboard event system with the
22+
* Flutter framework raw keyboard event classes, via system channels.
23+
*/
24+
@interface FLEKeyEventPlugin : NSResponder <FLEPlugin>
25+
26+
@end

macos/library/FLEKeyEventPlugin.m

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#import "FLEKeyEventPlugin.h"
16+
17+
#import "FLEViewController+Internal.h"
18+
#import "FLEViewController.h"
19+
20+
static NSString *const kKeyEventChannel = @"flutter/keyevent";
21+
22+
@implementation FLEKeyEventPlugin
23+
24+
@synthesize controller = _controller;
25+
26+
- (NSString *)channel {
27+
return kKeyEventChannel;
28+
}
29+
30+
- (void)handleMethodCall:(FLEMethodCall *)call result:(FLEMethodResult)result {
31+
result(FLEMethodNotImplemented);
32+
}
33+
34+
/**
35+
* Sends a key event to flutter.
36+
* @param event NSEvent received from the system.
37+
* @param type Type of this key event. Can be either "keydown" or "keyup".
38+
*/
39+
- (void)dispatchKeyEvent:(NSEvent *)event ofType:(NSString *)type {
40+
// TODO: Do not use 'android'. Reconsider when
41+
// https://github.com/google/flutter-desktop-embedding/issues/47 is solved.
42+
NSDictionary *message = @{
43+
@"keymap" : @"android",
44+
@"type" : type,
45+
@"keyCode" : @(event.keyCode),
46+
};
47+
48+
[self.controller dispatchMessage:message onChannel:kKeyEventChannel];
49+
}
50+
51+
#pragma mark - NSResponder
52+
53+
- (void)keyDown:(NSEvent *)event {
54+
[self dispatchKeyEvent:event ofType:@"keydown"];
55+
}
56+
57+
- (void)keyUp:(NSEvent *)event {
58+
[self dispatchKeyEvent:event ofType:@"keyup"];
59+
}
60+
61+
@end

macos/library/FLETextInputPlugin.m

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#import <objc/message.h>
1818

1919
#import "FLETextInputModel.h"
20-
#import "FLEViewController.h"
20+
#import "FLEViewController+Internal.h"
2121

2222
static NSString *const kTextInputChannel = @"flutter/textinput";
2323

@@ -91,10 +91,10 @@ - (void)handleMethodCall:(FLEMethodCall *)call result:(FLEMethodResult)result {
9191
_textInputModels[_activeClientID] = [[FLETextInputModel alloc] init];
9292
}
9393
} else if ([method isEqualToString:kShowMethod]) {
94-
[_controller.view.window makeFirstResponder:self];
94+
[self.controller addKeyResponder:self];
9595
[_textInputContext activate];
9696
} else if ([method isEqualToString:kHideMethod]) {
97-
[_controller.view.window makeFirstResponder:nil];
97+
[self.controller removeKeyResponder:self];
9898
[_textInputContext deactivate];
9999
} else if ([method isEqualToString:kClearClientMethod]) {
100100
_activeClientID = nil;
@@ -124,10 +124,6 @@ - (void)updateEditState {
124124
#pragma mark -
125125
#pragma mark NSResponder
126126

127-
- (BOOL)acceptsFirstResponder {
128-
return YES;
129-
}
130-
131127
/**
132128
* Note, the Apple docs suggest that clients should override essentially all the
133129
* mouse and keyboard event-handling methods of NSResponder. However, experimentation
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#import "FLEViewController.h"
16+
17+
@interface FLEViewController ()
18+
19+
/**
20+
* Adds a responder for keyboard events. Key up and key down events are forwarded to all added
21+
* responders.
22+
*/
23+
- (void)addKeyResponder:(nonnull NSResponder *)responder;
24+
25+
/**
26+
* Removes a responder for keyboard events.
27+
*/
28+
- (void)removeKeyResponder:(nonnull NSResponder *)responder;
29+
30+
/**
31+
* Sends a platform message to the Flutter engine on the given channel.
32+
* @param message The raw message.
33+
*/
34+
- (void)dispatchMessage:(nonnull NSDictionary *)message onChannel:(nonnull NSString *)channel;
35+
36+
@end

0 commit comments

Comments
 (0)