FLEXKeyboardShortcutManager.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. //
  2. // FLEXKeyboardShortcutManager.m
  3. // FLEX
  4. //
  5. // Created by Ryan Olson on 9/19/15.
  6. // Copyright © 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FLEXKeyboardShortcutManager.h"
  9. #import "FLEXUtility.h"
  10. #import <objc/runtime.h>
  11. #import <objc/message.h>
  12. #if TARGET_OS_SIMULATOR
  13. @interface UIEvent (UIPhysicalKeyboardEvent)
  14. @property (nonatomic) NSString *_modifiedInput;
  15. @property (nonatomic) NSString *_unmodifiedInput;
  16. @property (nonatomic) UIKeyModifierFlags _modifierFlags;
  17. @property (nonatomic) BOOL _isKeyDown;
  18. @property (nonatomic) long _keyCode;
  19. @end
  20. @interface FLEXKeyInput : NSObject <NSCopying>
  21. @property (nonatomic, copy, readonly) NSString *key;
  22. @property (nonatomic, readonly) UIKeyModifierFlags flags;
  23. @property (nonatomic, copy, readonly) NSString *helpDescription;
  24. @end
  25. @implementation FLEXKeyInput
  26. - (BOOL)isEqual:(id)object {
  27. BOOL isEqual = NO;
  28. if ([object isKindOfClass:[FLEXKeyInput class]]) {
  29. FLEXKeyInput *keyCommand = (FLEXKeyInput *)object;
  30. BOOL equalKeys = self.key == keyCommand.key || [self.key isEqual:keyCommand.key];
  31. BOOL equalFlags = self.flags == keyCommand.flags;
  32. isEqual = equalKeys && equalFlags;
  33. }
  34. return isEqual;
  35. }
  36. - (NSUInteger)hash {
  37. return self.key.hash ^ self.flags;
  38. }
  39. - (id)copyWithZone:(NSZone *)zone {
  40. return [[self class] keyInputForKey:self.key flags:self.flags helpDescription:self.helpDescription];
  41. }
  42. - (NSString *)description {
  43. NSDictionary<NSString *, NSString *> *keyMappings = @{
  44. UIKeyInputUpArrow : @"↑",
  45. UIKeyInputDownArrow : @"↓",
  46. UIKeyInputLeftArrow : @"←",
  47. UIKeyInputRightArrow : @"→",
  48. UIKeyInputEscape : @"␛",
  49. @" " : @"␠"
  50. };
  51. NSString *prettyKey = nil;
  52. if (self.key && keyMappings[self.key]) {
  53. prettyKey = keyMappings[self.key];
  54. } else {
  55. prettyKey = [self.key uppercaseString];
  56. }
  57. NSString *prettyFlags = @"";
  58. if (self.flags & UIKeyModifierControl) {
  59. prettyFlags = [prettyFlags stringByAppendingString:@"⌃"];
  60. }
  61. if (self.flags & UIKeyModifierAlternate) {
  62. prettyFlags = [prettyFlags stringByAppendingString:@"⌥"];
  63. }
  64. if (self.flags & UIKeyModifierShift) {
  65. prettyFlags = [prettyFlags stringByAppendingString:@"⇧"];
  66. }
  67. if (self.flags & UIKeyModifierCommand) {
  68. prettyFlags = [prettyFlags stringByAppendingString:@"⌘"];
  69. }
  70. // Fudging to get easy columns with tabs
  71. if (prettyFlags.length < 2) {
  72. prettyKey = [prettyKey stringByAppendingString:@"\t"];
  73. }
  74. return [NSString stringWithFormat:@"%@%@\t%@", prettyFlags, prettyKey, self.helpDescription];
  75. }
  76. + (instancetype)keyInputForKey:(NSString *)key flags:(UIKeyModifierFlags)flags {
  77. return [self keyInputForKey:key flags:flags helpDescription:nil];
  78. }
  79. + (instancetype)keyInputForKey:(NSString *)key
  80. flags:(UIKeyModifierFlags)flags
  81. helpDescription:(NSString *)helpDescription {
  82. FLEXKeyInput *keyInput = [self new];
  83. if (keyInput) {
  84. keyInput->_key = key;
  85. keyInput->_flags = flags;
  86. keyInput->_helpDescription = helpDescription;
  87. }
  88. return keyInput;
  89. }
  90. @end
  91. @interface FLEXKeyboardShortcutManager ()
  92. @property (nonatomic) NSMutableDictionary<FLEXKeyInput *, dispatch_block_t> *actionsForKeyInputs;
  93. @property (nonatomic, getter=isPressingShift) BOOL pressingShift;
  94. @property (nonatomic, getter=isPressingCommand) BOOL pressingCommand;
  95. @property (nonatomic, getter=isPressingControl) BOOL pressingControl;
  96. @end
  97. @implementation FLEXKeyboardShortcutManager
  98. + (instancetype)sharedManager {
  99. static FLEXKeyboardShortcutManager *sharedManager = nil;
  100. static dispatch_once_t onceToken;
  101. dispatch_once(&onceToken, ^{
  102. sharedManager = [self new];
  103. });
  104. return sharedManager;
  105. }
  106. + (void)load {
  107. SEL originalKeyEventSelector = NSSelectorFromString(@"handleKeyUIEvent:");
  108. SEL swizzledKeyEventSelector = [FLEXUtility swizzledSelectorForSelector:originalKeyEventSelector];
  109. void (^handleKeyUIEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
  110. [[[self class] sharedManager] handleKeyboardEvent:event];
  111. ((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledKeyEventSelector, event);
  112. };
  113. [FLEXUtility replaceImplementationOfKnownSelector:originalKeyEventSelector
  114. onClass:[UIApplication class]
  115. withBlock:handleKeyUIEventSwizzleBlock
  116. swizzledSelector:swizzledKeyEventSelector
  117. ];
  118. if ([[UITouch class] instancesRespondToSelector:@selector(maximumPossibleForce)]) {
  119. SEL originalSendEventSelector = NSSelectorFromString(@"sendEvent:");
  120. SEL swizzledSendEventSelector = [FLEXUtility swizzledSelectorForSelector:originalSendEventSelector];
  121. void (^sendEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
  122. if (event.type == UIEventTypeTouches) {
  123. FLEXKeyboardShortcutManager *keyboardManager = FLEXKeyboardShortcutManager.sharedManager;
  124. NSInteger pressureLevel = 0;
  125. if (keyboardManager.isPressingShift) {
  126. pressureLevel++;
  127. }
  128. if (keyboardManager.isPressingCommand) {
  129. pressureLevel++;
  130. }
  131. if (keyboardManager.isPressingControl) {
  132. pressureLevel++;
  133. }
  134. if (pressureLevel > 0) {
  135. if (@available(iOS 9.0, *)) {
  136. for (UITouch *touch in [event allTouches]) {
  137. double adjustedPressureLevel = pressureLevel * 20 * touch.maximumPossibleForce;
  138. [touch setValue:@(adjustedPressureLevel) forKey:@"_pressure"];
  139. }
  140. }
  141. }
  142. }
  143. ((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledSendEventSelector, event);
  144. };
  145. [FLEXUtility replaceImplementationOfKnownSelector:originalSendEventSelector
  146. onClass:[UIApplication class]
  147. withBlock:sendEventSwizzleBlock
  148. swizzledSelector:swizzledSendEventSelector
  149. ];
  150. SEL originalSupportsTouchPressureSelector = NSSelectorFromString(@"_supportsForceTouch");
  151. SEL swizzledSupportsTouchPressureSelector = [FLEXUtility swizzledSelectorForSelector:originalSupportsTouchPressureSelector];
  152. BOOL (^supportsTouchPressureSwizzleBlock)(UIDevice *) = ^BOOL(UIDevice *slf) {
  153. return YES;
  154. };
  155. [FLEXUtility replaceImplementationOfKnownSelector:originalSupportsTouchPressureSelector
  156. onClass:[UIDevice class]
  157. withBlock:supportsTouchPressureSwizzleBlock
  158. swizzledSelector:swizzledSupportsTouchPressureSelector
  159. ];
  160. }
  161. }
  162. - (instancetype)init {
  163. self = [super init];
  164. if (self) {
  165. _actionsForKeyInputs = [NSMutableDictionary new];
  166. _enabled = YES;
  167. }
  168. return self;
  169. }
  170. - (void)registerSimulatorShortcutWithKey:(NSString *)key
  171. modifiers:(UIKeyModifierFlags)modifiers
  172. action:(dispatch_block_t)action
  173. description:(NSString *)description
  174. allowOverride:(BOOL)allowOverride {
  175. FLEXKeyInput *keyInput = [FLEXKeyInput keyInputForKey:key flags:modifiers helpDescription:description];
  176. if (!allowOverride && self.actionsForKeyInputs[keyInput] != nil) {
  177. return;
  178. } else {
  179. [self.actionsForKeyInputs setObject:action forKey:keyInput];
  180. }
  181. }
  182. static const long kFLEXControlKeyCode = 0xe0;
  183. static const long kFLEXShiftKeyCode = 0xe1;
  184. static const long kFLEXCommandKeyCode = 0xe3;
  185. - (void)handleKeyboardEvent:(UIEvent *)event {
  186. if (!self.enabled) {
  187. return;
  188. }
  189. NSString *modifiedInput = nil;
  190. NSString *unmodifiedInput = nil;
  191. UIKeyModifierFlags flags = 0;
  192. BOOL isKeyDown = NO;
  193. if ([event respondsToSelector:@selector(_modifiedInput)]) {
  194. modifiedInput = [event _modifiedInput];
  195. }
  196. if ([event respondsToSelector:@selector(_unmodifiedInput)]) {
  197. unmodifiedInput = [event _unmodifiedInput];
  198. }
  199. if ([event respondsToSelector:@selector(_modifierFlags)]) {
  200. flags = [event _modifierFlags];
  201. }
  202. if ([event respondsToSelector:@selector(_isKeyDown)]) {
  203. isKeyDown = [event _isKeyDown];
  204. }
  205. BOOL interactionEnabled = !UIApplication.sharedApplication.isIgnoringInteractionEvents;
  206. BOOL hasFirstResponder = NO;
  207. if (isKeyDown && modifiedInput.length > 0 && interactionEnabled) {
  208. UIResponder *firstResponder = nil;
  209. for (UIWindow *window in FLEXUtility.allWindows) {
  210. firstResponder = [window valueForKey:@"firstResponder"];
  211. if (firstResponder) {
  212. hasFirstResponder = YES;
  213. break;
  214. }
  215. }
  216. // Ignore key commands (except escape) when there's an active responder
  217. if (firstResponder) {
  218. if ([unmodifiedInput isEqual:UIKeyInputEscape]) {
  219. [firstResponder resignFirstResponder];
  220. }
  221. } else {
  222. FLEXKeyInput *exactMatch = [FLEXKeyInput keyInputForKey:unmodifiedInput flags:flags];
  223. dispatch_block_t actionBlock = self.actionsForKeyInputs[exactMatch];
  224. if (!actionBlock) {
  225. FLEXKeyInput *shiftMatch = [FLEXKeyInput
  226. keyInputForKey:modifiedInput flags:flags&(~UIKeyModifierShift)
  227. ];
  228. actionBlock = self.actionsForKeyInputs[shiftMatch];
  229. }
  230. if (!actionBlock) {
  231. FLEXKeyInput *capitalMatch = [FLEXKeyInput
  232. keyInputForKey:[unmodifiedInput uppercaseString] flags:flags
  233. ];
  234. actionBlock = self.actionsForKeyInputs[capitalMatch];
  235. }
  236. if (actionBlock) {
  237. actionBlock();
  238. }
  239. }
  240. }
  241. // Calling _keyCode on events from the simulator keyboard will crash.
  242. // It is only safe to call _keyCode when there's not an active responder.
  243. if (!hasFirstResponder && [event respondsToSelector:@selector(_keyCode)]) {
  244. long keyCode = [event _keyCode];
  245. if (keyCode == kFLEXControlKeyCode) {
  246. self.pressingControl = isKeyDown;
  247. } else if (keyCode == kFLEXCommandKeyCode) {
  248. self.pressingCommand = isKeyDown;
  249. } else if (keyCode == kFLEXShiftKeyCode) {
  250. self.pressingShift = isKeyDown;
  251. }
  252. }
  253. }
  254. - (NSString *)keyboardShortcutsDescription {
  255. NSMutableString *description = [NSMutableString new];
  256. NSArray<FLEXKeyInput *> *keyInputs = [self.actionsForKeyInputs.allKeys
  257. sortedArrayUsingComparator:^NSComparisonResult(FLEXKeyInput *input1, FLEXKeyInput *input2) {
  258. return [input1.key caseInsensitiveCompare:input2.key];
  259. }
  260. ];
  261. for (FLEXKeyInput *keyInput in keyInputs) {
  262. [description appendFormat:@"%@\n", keyInput];
  263. }
  264. return [description copy];
  265. }
  266. @end
  267. #endif