-
Notifications
You must be signed in to change notification settings - Fork 610
Send key events to flutter #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Structurally this looks really good! The comments are mostly API and visibility notes.
A general comment: something I've started trying to do with every plugin is to ensure that it's easily testable in the example app; this helps with regression testing, and with bring-up on new platforms. (Text input is the last thing that hasn't been fixed, but adding a text field to the example app is on my to-do list.) Could you add something to the app that would allow trying this out?
Longer term I think the example will need to turn into a gallery-style app since things are getting crowded, but for now maybe a button that pushes a new screen that's a raw key event listener wrapping some kind of feedback UI showing that it's working--perhaps simply displaying the key code and whether it's a key up or down for each incoming event.
macos/library/FLEKeyEventPlugin.m
Outdated
|
||
#import "FLEKeyEventPlugin.h" | ||
|
||
#import <objc/message.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't appear to be used.
macos/library/FLEKeyEventPlugin.m
Outdated
|
||
- (void)handleMethodCall:(FLEMethodCall *)call result:(FLEMethodResult)result { | ||
NSString *method = call.methodName; | ||
NSLog(@"Unhandled text input method '%@'", method); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the documentation for handleMethodCall:result:, implementations must call |result|.
You can replace all of this with just:
result(FLEMethodNotImplemented);
since that will result in an error that the Flutter side would presumably log.
macos/library/FLEKeyEventPlugin.m
Outdated
} | ||
|
||
- (void)dispatchKeyEvent:(NSString *)type event:(NSEvent *)event { | ||
// TODO: Do not use 'android' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the only options are android and fuchsia at the moment, it's not entirely clear what course of action this TODO is suggesting; a TODO should have enough information that someone else encountering it can understand what needs to be done. Do you have a path forward in mind for this? E.g., a bug you could reference for adding some option other than those two that would better match desktop?
(Although I would expect that this channel would also fall under #47 and at some point in the future be replaced with a formal embedding API, so that may be the path forward.)
Relatedly, how deeply have you investigated the effects of the keymap type on the implementation? For instance, what differences would result if we passed fuchsia here instead?
@chinmaygarde Any thoughts on the implications of implementing this using the android map (in the short term)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the issue link to the TODO comment.
Using "fuchsia" instead of android would change the class of the data
property of the KeyEvent. If we change this handlers in the dart code would need to be adapted. Currently the handler in the example expects RawKeyEventDataAndroid
. Also I am not filling all expected properties of the data class.
The Fuchsia implementation has no keyCode
field, but instead hidUsage
, not sure how to convert this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I am not filling all expected properties of the data class.
Is there a reason you're not passing modifier key state via metaState? That seems like it would be necessary data for many uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wasn't resolved, at it still seems like passing modifier keys will be important, but I'm okay with leaving that for follow-up work.
macos/library/FLEViewController.h
Outdated
* Sends a platform message to the Flutter engine on the given channel. | ||
* @param message The raw message. | ||
*/ | ||
- (void)dispatchMessage:(nonnull NSDictionary*)message onChannel:(nonnull NSString *)channel; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Flutter use raw messages for any "public" (i.e., supported and stable, unlike these internal framework channels) APIs?
I'd like to keep the public API surface as clean as possible, so unless there's an expectation that stand-alone plugins would need this functionality it would be preferable to put this in FLEViewController+Internal.h as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are flutter plugins defining and using EventChannels which as I understand are just raw messages. It looks like PlattformMessages were changed to FlutterMessageChannel and FlutterMethodChannel. Maybe something similar should be done here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are flutter plugins defining and using EventChannels which as I understand are just raw messages.
EventChannels still use a MethodCodec, which means they follow the method call/response structure that the desktop plugins now use as well. The primary difference between the current desktop API and the iOS and Android APIs is that currently desktop hard-codes use of the JSON method codec.
Do you have examples of plugins or API that doesn't use the MethodCodec format, as with the new API you've added?
Maybe something similar should be done here.
That's planned, and covered by Issue #67. The recent plugin API rework was the first phase of aligning with the standard Flutter structure.
macos/library/FLEViewController.m
Outdated
} | ||
return self; | ||
} | ||
|
||
- (void)setup { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Methods called from init are a potential foot-gun (see the style guide discussion) so when it's done it needs to be extremely clear; that's why initPlugins starts (unusually) with init, and why it's clearly documented as being called from init.
Adding a layer of indirection makes that much less clear, so I would much rather the creation of the set (which is, after all, only used by plugins) be folded into initPlugins.
macos/library/FLEViewController.m
Outdated
} | ||
return self; | ||
} | ||
|
||
- (void)setup { | ||
self.additionalKeyResponders = [NSMutableSet new]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
macos/library/FLEViewController.h
Outdated
/** | ||
* A list of additional responders to keyboard events. Keybord events are forwarded to all of them. | ||
*/ | ||
@property NSMutableSet<NSResponder*> *additionalKeyResponders; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think embedders or stand-alone plugins should be able to interject themselves into the process of handing native keyboard events off to Flutter, so this should be declared in an FLEViewController+Internal.h that's marked as a project header.
Also, even for internal use, the API would be much safer if the variable were private, and there were public methods to add or remove an object. That way it's not possible to accidentally stomp other responders when adding or removing yourself. (As a general rule, public, mutable collections are problematic in the API surface of a class. Wrapped mutators and/or a readonly, copy, immutable collection property, ensure that all changes are mediated by the class.)
macos/library/FLEViewController.m
Outdated
#pragma mark - NSResponder key events | ||
|
||
- (void)keyDown:(NSEvent *)event { | ||
for (NSResponder* responder in self.additionalKeyResponders) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't dug into the Flutter internals here; is there any interaction between the handling on the two different keyboard channels? For instance, does Flutter expect key events to happen in a specific order relative to the higher-level input events? I'd like to be sure this isn't going to regress text input.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to work as is, when using text input and key events the order is as follows:
- KeyDown
- Text
- KeyUp
Because the keyevent plugin is added first it handles the event first. I changed the NSMutableSet to NSOrderedMutableSet to guarantee this.
macos/library/FLEViewController.m
Outdated
} | ||
|
||
#pragma mark - NSResponder key events |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather use a single group for protocol implementations in general, so just:
#pragma mark - NSResponder
and no second one below.
(There are also some things below this point that aren't NSResponder implementation, but that's a pre-existing ordering problem in this file that I can clean up in a follow-up CL)
macos/library/FLEKeyEventPlugin.m
Outdated
NSDictionary* message = @{ | ||
@"keymap": @"android", | ||
@"type": type, | ||
@"keyCode": [NSNumber numberWithUnsignedShort: event.keyCode], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That value can just be @(event.keyCode)
Thanks for your review @stuartmorgan. Can you recheck the PR? The biggest problem is that I misuse the android key event data, not even including all properties, but I do not know how to solve this. Also added some answers to your comments but GitHub hides them because i changed the code. In the example there seems to be a minor issue: Selecting the text field, then changing to the new page and back, the text field looks like it has focus but you have to select it again to type something. Not sure why requesting the focus does not unfocus the old one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple a substantive things I didn't catch before, but mostly nits. The changes all look really good. Thanks for the test page in the example app!
import 'package:flutter/scheduler.dart'; | ||
import 'package:flutter/services.dart'; | ||
|
||
class KeyboardTestPage extends StatefulWidget { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document at least the public class (which is an analyzer failure under the current settings).
@override | ||
Widget build(BuildContext context) { | ||
return new Scaffold( | ||
appBar: AppBar( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to give this a title to provide some context.
); | ||
} | ||
|
||
void onKeyEvent(RawKeyEvent event) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this async?
|
||
void onKeyEvent(RawKeyEvent event) async { | ||
int keyCode; | ||
bool isKeyDown; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to declare variables as close to their first use as possible, so keyCode attached to the second switch below, and this attached to the first switch.
example_flutter/lib/main.dart
Outdated
@@ -170,6 +171,10 @@ class _MyHomePage extends StatelessWidget { | |||
), | |||
TextInputTestWidget(), | |||
FileChooserTestWidget(), | |||
new RaisedButton(child: new Text('Test raw keyboard events'), onPressed: () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please dartfmt this file; this line is too long.
macos/library/FLEKeyEventPlugin.m
Outdated
result(FLEMethodNotImplemented); | ||
} | ||
|
||
- (void)dispatchKeyEvent:(NSString *)type event:(NSEvent *)event { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a declaration comment (per style guide). It should also follow ObjC naming conventions that the word before the : names the param, so dispatchKeyEvent:(...)event ofType:(...)type
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should import FLViewController.h
macos/library/FLEViewController.h
Outdated
* Sends a platform message to the Flutter engine on the given channel. | ||
* Adds a responder for keyboard events. Key up and key down events are forwarded to all added responders. | ||
*/ | ||
- (void)addKeyResponder:(nonnull NSResponder*)responder; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the first part of my comment about this change in the previous round, I don't think arbitrary third-party plugins should be able to be key event handlers, so these should be in the internal header.
macos/library/FLEViewController.m
Outdated
|
||
FlutterResult result = FlutterEngineSendPlatformMessage(_engine, &platformMessage); | ||
if (result != kSuccess) { | ||
NSLog(@"Flutter engine send unsuccessful response for message %@: (%d).", message, result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: send->sent
Also, since the dictionary could be a lot to display, I'd put the error response before it:
Flutter engine sent unsuccessful response (%d) for message %@
macos/library/FLEKeyEventPlugin.m
Outdated
@"keymap": @"android", | ||
@"type": type, | ||
@"keyCode": @(event.keyCode), | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pulling this into a review comment since GitHub's UI makes it easy to miss replies to comments on outdated changes:
Is there a reason you're not passing modifier key state via metaState? That seems like it would be necessary data for many uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No particular reason. I did not need it and was not sure about the encoding.
Is this still something you're interested in landing? The last review round is mostly just minor changes, so it's very close. |
@maun Rearding #73 (comment), you commented that you had trouble with key events. Did you ever get an answer about how to resolve this ? |
# Conflicts: # macos/library/FLEViewController.h # macos/library/FLEViewController.m
@stuartmorgan I was rather busy before, but now got around to address your review, sorry for the delay. @gedw99 I did not look further into this. |
- Add missing license header to new Dart file. - Resolve a Dart analyzer warning. - Revert changes to the now-removed pubspec.lock to avoid conflicts.
Thanks for the contribution, and sorry for the long delay! |
Start of implementation for #72
Currently the code pretends to be android, also the scan code and modifiers are not sent. Ideally there would either be a matching platform in flutter, or a generic/desktop platform.
It would also be a good idea to look at the iOS code base how they forward events to multiple plugins.