Skip to content

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

Merged
merged 8 commits into from
Aug 21, 2018
Merged

Send key events to flutter #73

merged 8 commits into from
Aug 21, 2018

Conversation

maun
Copy link
Contributor

@maun maun commented Jun 20, 2018

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.

Copy link
Collaborator

@stuartmorgan-g stuartmorgan-g left a 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.


#import "FLEKeyEventPlugin.h"

#import <objc/message.h>
Copy link
Collaborator

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.


- (void)handleMethodCall:(FLEMethodCall *)call result:(FLEMethodResult)result {
NSString *method = call.methodName;
NSLog(@"Unhandled text input method '%@'", method);
Copy link
Collaborator

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.

}

- (void)dispatchKeyEvent:(NSString *)type event:(NSEvent *)event {
// TODO: Do not use 'android'
Copy link
Collaborator

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)?

Copy link
Contributor Author

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.

Copy link
Collaborator

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.

Copy link
Collaborator

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.

* 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;
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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.

}
return self;
}

- (void)setup {
Copy link
Collaborator

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.

}
return self;
}

- (void)setup {
self.additionalKeyResponders = [NSMutableSet new];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/**
* A list of additional responders to keyboard events. Keybord events are forwarded to all of them.
*/
@property NSMutableSet<NSResponder*> *additionalKeyResponders;
Copy link
Collaborator

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.)

#pragma mark - NSResponder key events

- (void)keyDown:(NSEvent *)event {
for (NSResponder* responder in self.additionalKeyResponders) {
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

}

#pragma mark - NSResponder key events
Copy link
Collaborator

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)

NSDictionary* message = @{
@"keymap": @"android",
@"type": type,
@"keyCode": [NSNumber numberWithUnsignedShort: event.keyCode],
Copy link
Collaborator

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)

@maun maun changed the title [WIP] Send key events to flutter Send key events to flutter Jul 2, 2018
@maun
Copy link
Contributor Author

maun commented Jul 2, 2018

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.

Copy link
Collaborator

@stuartmorgan-g stuartmorgan-g left a 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 {
Copy link
Collaborator

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(
Copy link
Collaborator

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 {
Copy link
Collaborator

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;
Copy link
Collaborator

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.

@@ -170,6 +171,10 @@ class _MyHomePage extends StatelessWidget {
),
TextInputTestWidget(),
FileChooserTestWidget(),
new RaisedButton(child: new Text('Test raw keyboard events'), onPressed: () {
Copy link
Collaborator

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.

result(FLEMethodNotImplemented);
}

- (void)dispatchKeyEvent:(NSString *)type event:(NSEvent *)event {
Copy link
Collaborator

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.

Copy link
Collaborator

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

* 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;
Copy link
Collaborator

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.


FlutterResult result = FlutterEngineSendPlatformMessage(_engine, &platformMessage);
if (result != kSuccess) {
NSLog(@"Flutter engine send unsuccessful response for message %@: (%d).", message, result);
Copy link
Collaborator

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 %@

@"keymap": @"android",
@"type": type,
@"keyCode": @(event.keyCode),
};
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

@stuartmorgan-g
Copy link
Collaborator

Is this still something you're interested in landing? The last review round is mostly just minor changes, so it's very close.

@ghost
Copy link

ghost commented Aug 10, 2018

@maun Rearding #73 (comment), you commented that you had trouble with key events. Did you ever get an answer about how to resolve this ?

maun added 2 commits August 10, 2018 11:58
# Conflicts:
#	macos/library/FLEViewController.h
#	macos/library/FLEViewController.m
@maun
Copy link
Contributor Author

maun commented Aug 10, 2018

@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.
@stuartmorgan-g stuartmorgan-g merged commit 899af73 into google:master Aug 21, 2018
@stuartmorgan-g
Copy link
Collaborator

Thanks for the contribution, and sorry for the long delay!

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants