react-native-macos/React/Base/RCTKeyCommands.m

268 строки
7.0 KiB
Objective-C

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTKeyCommands.h"
#import <UIKit/UIKit.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import "RCTDefines.h"
#import "RCTUtils.h"
#if RCT_DEV
@interface UIEvent (UIPhysicalKeyboardEvent)
@property (nonatomic) NSString *_modifiedInput;
@property (nonatomic) NSString *_unmodifiedInput;
@property (nonatomic) UIKeyModifierFlags _modifierFlags;
@property (nonatomic) BOOL _isKeyDown;
@property (nonatomic) long _keyCode;
@end
@interface RCTKeyCommand : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *key;
@property (nonatomic, readonly) UIKeyModifierFlags flags;
@property (nonatomic, copy) void (^block)(UIKeyCommand *);
@end
@implementation RCTKeyCommand
- (instancetype)init:(NSString *)key flags:(UIKeyModifierFlags)flags block:(void (^)(UIKeyCommand *))block
{
if ((self = [super init])) {
_key = key;
_flags = flags;
_block = block;
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (id)copyWithZone:(__unused NSZone *)zone
{
return self;
}
- (NSUInteger)hash
{
return _key.hash ^ _flags;
}
- (BOOL)isEqual:(RCTKeyCommand *)object
{
if (![object isKindOfClass:[RCTKeyCommand class]]) {
return NO;
}
return [self matchesInput:object.key flags:object.flags];
}
- (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags
{
// We consider the key command a match if the modifier flags match
// exactly or is there are no modifier flags. This means that for
// `cmd + r`, we will match both `cmd + r` and `r` but not `opt + r`.
return [_key isEqual:input] && (_flags == flags || flags == 0);
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%lld hasBlock=%@>",
[self class],
self,
_key,
(long long)_flags,
_block ? @"YES" : @"NO"];
}
@end
@interface RCTKeyCommands ()
@property (nonatomic, strong) NSMutableSet<RCTKeyCommand *> *commands;
@end
@implementation RCTKeyCommands
+ (void)initialize
{
SEL originalKeyEventSelector = NSSelectorFromString(@"handleKeyUIEvent:");
SEL swizzledKeyEventSelector = NSSelectorFromString(
[NSString stringWithFormat:@"_rct_swizzle_%x_%@", arc4random(), NSStringFromSelector(originalKeyEventSelector)]);
void (^handleKeyUIEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
[[[self class] sharedInstance] handleKeyUIEventSwizzle:event];
((void (*)(id, SEL, id))objc_msgSend)(slf, swizzledKeyEventSelector, event);
};
RCTSwapInstanceMethodWithBlock(
[UIApplication class], originalKeyEventSelector, handleKeyUIEventSwizzleBlock, swizzledKeyEventSelector);
}
- (void)handleKeyUIEventSwizzle:(UIEvent *)event
{
NSString *modifiedInput = nil;
UIKeyModifierFlags modifierFlags = 0;
BOOL isKeyDown = NO;
if ([event respondsToSelector:@selector(_modifiedInput)]) {
modifiedInput = [event _modifiedInput];
}
if ([event respondsToSelector:@selector(_modifierFlags)]) {
modifierFlags = [event _modifierFlags];
}
if ([event respondsToSelector:@selector(_isKeyDown)]) {
isKeyDown = [event _isKeyDown];
}
BOOL interactionEnabled = !RCTSharedApplication().isIgnoringInteractionEvents;
BOOL hasFirstResponder = NO;
if (isKeyDown && modifiedInput.length > 0 && interactionEnabled) {
UIResponder *firstResponder = nil;
for (UIWindow *window in [self allWindows]) {
firstResponder = [window valueForKey:@"firstResponder"];
if (firstResponder) {
hasFirstResponder = YES;
break;
}
}
// Ignore key commands (except escape) when there's an active responder
if (!firstResponder) {
[self RCT_handleKeyCommand:modifiedInput flags:modifierFlags];
}
}
};
- (NSArray<UIWindow *> *)allWindows
{
BOOL includeInternalWindows = YES;
BOOL onlyVisibleWindows = NO;
// Obfuscating selector allWindowsIncludingInternalWindows:onlyVisibleWindows:
NSArray<NSString *> *allWindowsComponents =
@[ @"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:" ];
SEL allWindowsSelector = NSSelectorFromString([allWindowsComponents componentsJoinedByString:@""]);
NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.target = [UIWindow class];
invocation.selector = allWindowsSelector;
[invocation setArgument:&includeInternalWindows atIndex:2];
[invocation setArgument:&onlyVisibleWindows atIndex:3];
[invocation invoke];
__unsafe_unretained NSArray<UIWindow *> *windows = nil;
[invocation getReturnValue:&windows];
return windows;
}
- (void)RCT_handleKeyCommand:(NSString *)input flags:(UIKeyModifierFlags)modifierFlags
{
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
if ([command matchesInput:input flags:modifierFlags]) {
if (command.block) {
command.block(nil);
}
}
}
}
+ (instancetype)sharedInstance
{
static RCTKeyCommands *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self new];
});
return sharedInstance;
}
- (instancetype)init
{
if ((self = [super init])) {
_commands = [NSMutableSet new];
}
return self;
}
- (void)registerKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *))block
{
RCTAssertMainQueue();
RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] init:input flags:flags block:block];
[_commands removeObject:keyCommand];
[_commands addObject:keyCommand];
}
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
{
RCTAssertMainQueue();
for (RCTKeyCommand *command in _commands.allObjects) {
if ([command matchesInput:input flags:flags]) {
[_commands removeObject:command];
break;
}
}
}
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
{
RCTAssertMainQueue();
for (RCTKeyCommand *command in _commands) {
if ([command matchesInput:input flags:flags]) {
return YES;
}
}
return NO;
}
@end
#else
@implementation RCTKeyCommands
+ (instancetype)sharedInstance
{
return nil;
}
- (void)registerKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *))block
{
}
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
{
}
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
{
return NO;
}
@end
#endif