forked from FLEXTool/FLEX
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for keyboard shortcuts in the simulator
- Loading branch information
1 parent
b453086
commit b22d2b5
Showing
6 changed files
with
341 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// | ||
// FLEXKeyboardShortcutManager.h | ||
// FLEX | ||
// | ||
// Created by Ryan Olson on 9/19/15. | ||
// Copyright © 2015 Flipboard. All rights reserved. | ||
// | ||
|
||
#import <UIKit/UIKit.h> | ||
|
||
#if TARGET_OS_SIMULATOR | ||
|
||
@interface FLEXKeyboardShortcutManager : NSObject | ||
|
||
+ (instancetype)sharedManager; | ||
|
||
- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description; | ||
- (NSString *)keyboardShortcutsDescription; | ||
|
||
@property (nonatomic, assign, getter=isEnabled) BOOL enabled; | ||
|
||
@end | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
// | ||
// FLEXKeyboardShortcutManager.m | ||
// FLEX | ||
// | ||
// Created by Ryan Olson on 9/19/15. | ||
// Copyright © 2015 Flipboard. All rights reserved. | ||
// | ||
|
||
#import "FLEXKeyboardShortcutManager.h" | ||
#import "FLEXUtility.h" | ||
#import <objc/runtime.h> | ||
#import <objc/message.h> | ||
|
||
#if TARGET_OS_SIMULATOR | ||
|
||
@interface UIEvent (UIPhysicalKeyboardEvent) | ||
|
||
@property (nonatomic, strong) NSString *_modifiedInput; | ||
@property (nonatomic, strong) NSString *_unmodifiedInput; | ||
@property (nonatomic, assign) UIKeyModifierFlags _modifierFlags; | ||
@property (nonatomic, assign) BOOL _isKeyDown; | ||
|
||
@end | ||
|
||
@interface FLEXKeyInput : NSObject <NSCopying> | ||
|
||
@property (nonatomic, copy, readonly) NSString *key; | ||
@property (nonatomic, assign, readonly) UIKeyModifierFlags flags; | ||
@property (nonatomic, copy, readonly) NSString *helpDescription; | ||
|
||
@end | ||
|
||
@implementation FLEXKeyInput | ||
|
||
- (BOOL)isEqual:(id)object | ||
{ | ||
BOOL isEqual = NO; | ||
if ([object isKindOfClass:[FLEXKeyInput class]]) { | ||
FLEXKeyInput *keyCommand = (FLEXKeyInput *)object; | ||
BOOL equalKeys = self.key == keyCommand.key || [self.key isEqual:keyCommand.key]; | ||
BOOL equalFlags = self.flags == keyCommand.flags; | ||
isEqual = equalKeys && equalFlags; | ||
} | ||
return isEqual; | ||
} | ||
|
||
- (NSUInteger)hash | ||
{ | ||
return [self.key hash] ^ self.flags; | ||
} | ||
|
||
- (id)copyWithZone:(NSZone *)zone | ||
{ | ||
return [[self class] keyInputForKey:self.key flags:self.flags helpDescription:self.helpDescription]; | ||
} | ||
|
||
- (NSString *)description | ||
{ | ||
NSDictionary *keyMappings = @{ UIKeyInputUpArrow : @"↑", | ||
UIKeyInputDownArrow : @"↓", | ||
UIKeyInputLeftArrow : @"←", | ||
UIKeyInputRightArrow : @"→", | ||
UIKeyInputEscape : @"␛", | ||
@" " : @"␠"}; | ||
|
||
NSString *prettyKey = nil; | ||
if (self.key && [keyMappings objectForKey:self.key]) { | ||
prettyKey = [keyMappings objectForKey:self.key]; | ||
} else { | ||
prettyKey = [self.key uppercaseString]; | ||
} | ||
|
||
NSString *prettyFlags = @""; | ||
if (self.flags & UIKeyModifierControl) { | ||
prettyFlags = [prettyFlags stringByAppendingString:@"⌃"]; | ||
} | ||
if (self.flags & UIKeyModifierAlternate) { | ||
prettyFlags = [prettyFlags stringByAppendingString:@"⌥"]; | ||
} | ||
if (self.flags & UIKeyModifierShift) { | ||
prettyFlags = [prettyFlags stringByAppendingString:@"⇧"]; | ||
} | ||
if (self.flags & UIKeyModifierCommand) { | ||
prettyFlags = [prettyFlags stringByAppendingString:@"⌘"]; | ||
} | ||
|
||
// Fudging to get easy columns with tabs | ||
if ([prettyFlags length] < 2) { | ||
prettyKey = [prettyKey stringByAppendingString:@"\t"]; | ||
} | ||
|
||
return [NSString stringWithFormat:@"%@%@\t%@", prettyFlags, prettyKey, self.helpDescription]; | ||
} | ||
|
||
+ (instancetype)keyInputForKey:(NSString *)key flags:(UIKeyModifierFlags)flags | ||
{ | ||
return [self keyInputForKey:key flags:flags helpDescription:nil]; | ||
} | ||
|
||
+ (instancetype)keyInputForKey:(NSString *)key flags:(UIKeyModifierFlags)flags helpDescription:(NSString *)helpDescription | ||
{ | ||
FLEXKeyInput *keyInput = [[self alloc] init]; | ||
if (keyInput) { | ||
keyInput->_key = key; | ||
keyInput->_flags = flags; | ||
keyInput->_helpDescription = helpDescription; | ||
} | ||
return keyInput; | ||
} | ||
|
||
@end | ||
|
||
@interface FLEXKeyboardShortcutManager () | ||
|
||
@property (nonatomic, strong) NSMutableDictionary *actionsForKeyInputs; | ||
|
||
@end | ||
|
||
@implementation FLEXKeyboardShortcutManager | ||
|
||
+ (instancetype)sharedManager | ||
{ | ||
static FLEXKeyboardShortcutManager *sharedManager = nil; | ||
static dispatch_once_t onceToken; | ||
dispatch_once(&onceToken, ^{ | ||
sharedManager = [[[self class] alloc] init]; | ||
}); | ||
return sharedManager; | ||
} | ||
|
||
+ (void)load | ||
{ | ||
SEL originalKeyEventSelector = NSSelectorFromString(@"handleKeyUIEvent:"); | ||
SEL swizzledKeyEventSelector = [FLEXUtility swizzledSelectorForSelector:originalKeyEventSelector]; | ||
|
||
void (^sendEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) { | ||
|
||
[[[self class] sharedManager] handleKeyboardEvent:event]; | ||
|
||
((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledKeyEventSelector, event); | ||
}; | ||
|
||
[FLEXUtility replaceImplementationOfKnownSelector:originalKeyEventSelector onClass:[UIApplication class] withBlock:sendEventSwizzleBlock swizzledSelector:swizzledKeyEventSelector]; | ||
} | ||
|
||
- (instancetype)init | ||
{ | ||
self = [super init]; | ||
|
||
if (self) { | ||
_actionsForKeyInputs = [NSMutableDictionary dictionary]; | ||
_enabled = YES; | ||
} | ||
|
||
return self; | ||
} | ||
|
||
- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description | ||
{ | ||
FLEXKeyInput *keyInput = [FLEXKeyInput keyInputForKey:key flags:modifiers helpDescription:description]; | ||
[self.actionsForKeyInputs setObject:action forKey:keyInput]; | ||
} | ||
|
||
- (void)handleKeyboardEvent:(UIEvent *)event | ||
{ | ||
if (!self.enabled) { | ||
return; | ||
} | ||
|
||
NSString *modifiedInput = nil; | ||
NSString *unmodifiedInput = nil; | ||
UIKeyModifierFlags flags = 0; | ||
BOOL isKeyDown = NO; | ||
|
||
if ([event respondsToSelector:@selector(_modifiedInput)]) { | ||
modifiedInput = [event _modifiedInput]; | ||
} | ||
|
||
if ([event respondsToSelector:@selector(_unmodifiedInput)]) { | ||
unmodifiedInput = [event _unmodifiedInput]; | ||
} | ||
|
||
if ([event respondsToSelector:@selector(_modifierFlags)]) { | ||
flags = [event _modifierFlags]; | ||
} | ||
|
||
if ([event respondsToSelector:@selector(_isKeyDown)]) { | ||
isKeyDown = [event _isKeyDown]; | ||
} | ||
|
||
BOOL interactionEnabled = ![[UIApplication sharedApplication] isIgnoringInteractionEvents]; | ||
|
||
if (isKeyDown && [modifiedInput length] > 0 && interactionEnabled) { | ||
UIResponder *firstResponder = nil; | ||
for (UIWindow *window in [[UIApplication sharedApplication] windows]) { | ||
firstResponder = [window valueForKey:@"firstResponder"]; | ||
if (firstResponder) { | ||
break; | ||
} | ||
} | ||
|
||
// Ignore key commands (except escape) when there's an active responder | ||
if (firstResponder) { | ||
if ([unmodifiedInput isEqual:UIKeyInputEscape]) { | ||
[firstResponder resignFirstResponder]; | ||
} | ||
} else { | ||
FLEXKeyInput *exactMatch = [FLEXKeyInput keyInputForKey:unmodifiedInput flags:flags]; | ||
|
||
dispatch_block_t actionBlock = [self.actionsForKeyInputs objectForKey:exactMatch]; | ||
|
||
if (!actionBlock) { | ||
FLEXKeyInput *shiftMatch = [FLEXKeyInput keyInputForKey:modifiedInput flags:flags&(!UIKeyModifierShift)]; | ||
actionBlock = [self.actionsForKeyInputs objectForKey:shiftMatch]; | ||
} | ||
|
||
if (!actionBlock) { | ||
FLEXKeyInput *capitalMatch = [FLEXKeyInput keyInputForKey:[unmodifiedInput uppercaseString] flags:flags]; | ||
actionBlock = [self.actionsForKeyInputs objectForKey:capitalMatch]; | ||
} | ||
|
||
if (actionBlock) { | ||
actionBlock(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
- (NSString *)keyboardShortcutsDescription | ||
{ | ||
NSMutableString *description = [NSMutableString string]; | ||
NSArray *keyInputs = [[self.actionsForKeyInputs allKeys] sortedArrayUsingComparator:^NSComparisonResult(FLEXKeyInput *_Nonnull input1, FLEXKeyInput *_Nonnull input2) { | ||
return [input1.key caseInsensitiveCompare:input2.key]; | ||
}]; | ||
for (FLEXKeyInput *keyInput in keyInputs) { | ||
[description appendFormat:@"%@\n", keyInput]; | ||
} | ||
return [description copy]; | ||
} | ||
|
||
@end | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.