blob: 98abd505d4d9be5fcc1de10429897951a4a8731b [file] [log] [blame]
// 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.
#import <objc/message.h>
#import "FlutterChannelKeyResponder.h"
#import "KeyCodeMap_Internal.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/embedder/embedder.h"
@interface FlutterChannelKeyResponder ()
/**
* The channel used to communicate with Flutter.
*/
@property(nonatomic) FlutterBasicMessageChannel* channel;
/**
* The |NSEvent.modifierFlags| of the last event received.
*
* Used to determine whether a FlagsChanged event should count as a keydown or
* a keyup event.
*/
@property(nonatomic) uint64_t previouslyPressedFlags;
@end
@implementation FlutterChannelKeyResponder
@synthesize layoutMap;
- (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel {
self = [super init];
if (self != nil) {
_channel = channel;
_previouslyPressedFlags = 0;
}
return self;
}
/// Checks single modifier flag from event flags and sends appropriate key event
/// if it is different from the previous state.
- (void)checkModifierFlag:(NSUInteger)targetMask
forEventFlags:(NSEventModifierFlags)eventFlags
keyCode:(NSUInteger)keyCode
timestamp:(NSTimeInterval)timestamp {
NSAssert((targetMask & (targetMask - 1)) == 0, @"targetMask must only have one bit set");
if ((eventFlags & targetMask) != (_previouslyPressedFlags & targetMask)) {
uint64_t newFlags = (_previouslyPressedFlags & ~targetMask) | (eventFlags & targetMask);
// Sets combined flag if either left or right modifier is pressed, unsets otherwise.
auto updateCombinedFlag = [&](uint64_t side1, uint64_t side2, NSEventModifierFlags flag) {
if (newFlags & (side1 | side2)) {
newFlags |= flag;
} else {
newFlags &= ~flag;
}
};
updateCombinedFlag(flutter::kModifierFlagShiftLeft, flutter::kModifierFlagShiftRight,
NSEventModifierFlagShift);
updateCombinedFlag(flutter::kModifierFlagControlLeft, flutter::kModifierFlagControlRight,
NSEventModifierFlagControl);
updateCombinedFlag(flutter::kModifierFlagAltLeft, flutter::kModifierFlagAltRight,
NSEventModifierFlagOption);
updateCombinedFlag(flutter::kModifierFlagMetaLeft, flutter::kModifierFlagMetaRight,
NSEventModifierFlagCommand);
NSEvent* event = [NSEvent keyEventWithType:NSEventTypeFlagsChanged
location:NSZeroPoint
modifierFlags:newFlags
timestamp:timestamp
windowNumber:0
context:nil
characters:@""
charactersIgnoringModifiers:@""
isARepeat:NO
keyCode:keyCode];
[self handleEvent:event
callback:^(BOOL){
}];
};
}
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
timestamp:(NSTimeInterval)timestamp {
modifierFlags = modifierFlags & ~0x100;
if (_previouslyPressedFlags == modifierFlags) {
return;
}
[flutter::modifierFlagToKeyCode
enumerateKeysAndObjectsUsingBlock:^(NSNumber* flag, NSNumber* keyCode, BOOL* stop) {
[self checkModifierFlag:[flag unsignedShortValue]
forEventFlags:modifierFlags
keyCode:[keyCode unsignedShortValue]
timestamp:timestamp];
}];
// Caps lock is not included in the modifierFlagToKeyCode map.
[self checkModifierFlag:NSEventModifierFlagCapsLock
forEventFlags:modifierFlags
keyCode:0x00000039 // kVK_CapsLock
timestamp:timestamp];
// At the end we should end up with the same modifier flags as the event.
FML_DCHECK(_previouslyPressedFlags == modifierFlags);
}
- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {
// Remove the modifier bits that Flutter is not interested in.
NSEventModifierFlags modifierFlags = event.modifierFlags & ~0x100;
NSString* type;
switch (event.type) {
case NSEventTypeKeyDown:
type = @"keydown";
break;
case NSEventTypeKeyUp:
type = @"keyup";
break;
case NSEventTypeFlagsChanged:
if (modifierFlags < _previouslyPressedFlags) {
type = @"keyup";
} else if (modifierFlags > _previouslyPressedFlags) {
type = @"keydown";
} else {
// ignore duplicate modifiers; This can happen in situations like switching
// between application windows when MacOS only sends the up event to new window.
callback(true);
return;
}
break;
default: {
NSAssert(false, @"Unexpected key event type (got %lu).", event.type);
callback(false);
}
}
_previouslyPressedFlags = modifierFlags;
NSMutableDictionary* keyMessage = [@{
@"keymap" : @"macos",
@"type" : type,
@"keyCode" : @(event.keyCode),
@"modifiers" : @(modifierFlags),
} mutableCopy];
// Calling these methods on any other type of event
// (e.g NSEventTypeFlagsChanged) will raise an exception.
if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) {
keyMessage[@"characters"] = event.characters;
keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers;
}
NSNumber* specifiedLogicalKey = layoutMap[@(event.keyCode)];
if (specifiedLogicalKey != nil) {
keyMessage[@"specifiedLogicalKey"] = specifiedLogicalKey;
}
[self.channel sendMessage:keyMessage
reply:^(id reply) {
if (!reply) {
return callback(true);
}
// Only propagate the event to other responders if the framework didn't
// handle it.
callback([[reply valueForKey:@"handled"] boolValue]);
}];
}
#pragma mark - Private
@end