mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-31 04:44:14 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			327 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| //
 | |
| //  FLEXKeyboardShortcutManager.m
 | |
| //  FLEX
 | |
| //
 | |
| //  Created by Ryan Olson on 9/19/15.
 | |
| //  Copyright © 2020 FLEX Team. 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) NSString *_modifiedInput;
 | |
| @property (nonatomic) NSString *_unmodifiedInput;
 | |
| @property (nonatomic) UIKeyModifierFlags _modifierFlags;
 | |
| @property (nonatomic) BOOL _isKeyDown;
 | |
| @property (nonatomic) long _keyCode;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface FLEXKeyInput : NSObject <NSCopying>
 | |
| 
 | |
| @property (nonatomic, copy, readonly) NSString *key;
 | |
| @property (nonatomic, 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<NSString *, NSString *> *keyMappings = @{
 | |
|         UIKeyInputUpArrow : @"↑",
 | |
|         UIKeyInputDownArrow : @"↓",
 | |
|         UIKeyInputLeftArrow : @"←",
 | |
|         UIKeyInputRightArrow : @"→",
 | |
|         UIKeyInputEscape : @"␛",
 | |
|         @" " : @"␠"
 | |
|     };
 | |
|     
 | |
|     NSString *prettyKey = nil;
 | |
|     if (self.key && keyMappings[self.key]) {
 | |
|         prettyKey = keyMappings[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 new];
 | |
|     if (keyInput) {
 | |
|         keyInput->_key = key;
 | |
|         keyInput->_flags = flags;
 | |
|         keyInput->_helpDescription = helpDescription;
 | |
|     }
 | |
|     return keyInput;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface FLEXKeyboardShortcutManager ()
 | |
| 
 | |
| @property (nonatomic) NSMutableDictionary<FLEXKeyInput *, dispatch_block_t> *actionsForKeyInputs;
 | |
| 
 | |
| @property (nonatomic, getter=isPressingShift) BOOL pressingShift;
 | |
| @property (nonatomic, getter=isPressingCommand) BOOL pressingCommand;
 | |
| @property (nonatomic, getter=isPressingControl) BOOL pressingControl;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation FLEXKeyboardShortcutManager
 | |
| 
 | |
| + (instancetype)sharedManager {
 | |
|     static FLEXKeyboardShortcutManager *sharedManager = nil;
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         sharedManager = [self new];
 | |
|     });
 | |
|     return sharedManager;
 | |
| }
 | |
| 
 | |
| + (void)load {
 | |
|     SEL originalKeyEventSelector = NSSelectorFromString(@"handleKeyUIEvent:");
 | |
|     SEL swizzledKeyEventSelector = [FLEXUtility swizzledSelectorForSelector:originalKeyEventSelector];
 | |
|     
 | |
|     void (^handleKeyUIEventSwizzleBlock)(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:handleKeyUIEventSwizzleBlock
 | |
|         swizzledSelector:swizzledKeyEventSelector
 | |
|     ];
 | |
|     
 | |
|     if ([[UITouch class] instancesRespondToSelector:@selector(maximumPossibleForce)]) {
 | |
|         SEL originalSendEventSelector = NSSelectorFromString(@"sendEvent:");
 | |
|         SEL swizzledSendEventSelector = [FLEXUtility swizzledSelectorForSelector:originalSendEventSelector];
 | |
|         
 | |
|         void (^sendEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
 | |
|             if (event.type == UIEventTypeTouches) {
 | |
|                 FLEXKeyboardShortcutManager *keyboardManager = FLEXKeyboardShortcutManager.sharedManager;
 | |
|                 NSInteger pressureLevel = 0;
 | |
|                 if (keyboardManager.isPressingShift) {
 | |
|                     pressureLevel++;
 | |
|                 }
 | |
|                 if (keyboardManager.isPressingCommand) {
 | |
|                     pressureLevel++;
 | |
|                 }
 | |
|                 if (keyboardManager.isPressingControl) {
 | |
|                     pressureLevel++;
 | |
|                 }
 | |
|                 if (pressureLevel > 0) {
 | |
|                     if (@available(iOS 9.0, *)) {
 | |
|                         for (UITouch *touch in [event allTouches]) {
 | |
|                             double adjustedPressureLevel = pressureLevel * 20 * touch.maximumPossibleForce;
 | |
|                             [touch setValue:@(adjustedPressureLevel) forKey:@"_pressure"];
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             ((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledSendEventSelector, event);
 | |
|         };
 | |
|         
 | |
|         [FLEXUtility replaceImplementationOfKnownSelector:originalSendEventSelector
 | |
|             onClass:[UIApplication class]
 | |
|             withBlock:sendEventSwizzleBlock
 | |
|             swizzledSelector:swizzledSendEventSelector
 | |
|         ];
 | |
|         
 | |
|         SEL originalSupportsTouchPressureSelector = NSSelectorFromString(@"_supportsForceTouch");
 | |
|         SEL swizzledSupportsTouchPressureSelector = [FLEXUtility swizzledSelectorForSelector:originalSupportsTouchPressureSelector];
 | |
|         
 | |
|         BOOL (^supportsTouchPressureSwizzleBlock)(UIDevice *) = ^BOOL(UIDevice *slf) {
 | |
|             return YES;
 | |
|         };
 | |
|         
 | |
|         [FLEXUtility replaceImplementationOfKnownSelector:originalSupportsTouchPressureSelector
 | |
|             onClass:[UIDevice class]
 | |
|             withBlock:supportsTouchPressureSwizzleBlock
 | |
|             swizzledSelector:swizzledSupportsTouchPressureSelector
 | |
|         ];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (instancetype)init {
 | |
|     self = [super init];
 | |
|     
 | |
|     if (self) {
 | |
|         _actionsForKeyInputs = [NSMutableDictionary new];
 | |
|         _enabled = YES;
 | |
|     }
 | |
|     
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| - (void)registerSimulatorShortcutWithKey:(NSString *)key
 | |
|                                modifiers:(UIKeyModifierFlags)modifiers
 | |
|                                   action:(dispatch_block_t)action
 | |
|                              description:(NSString *)description
 | |
|                            allowOverride:(BOOL)allowOverride {
 | |
|     FLEXKeyInput *keyInput = [FLEXKeyInput keyInputForKey:key flags:modifiers helpDescription:description];
 | |
|     if (!allowOverride && self.actionsForKeyInputs[keyInput] != nil) {
 | |
|         return;
 | |
|     } else {
 | |
|         [self.actionsForKeyInputs setObject:action forKey:keyInput];
 | |
|     }
 | |
| }
 | |
| 
 | |
| static const long kFLEXControlKeyCode = 0xe0;
 | |
| static const long kFLEXShiftKeyCode = 0xe1;
 | |
| static const long kFLEXCommandKeyCode = 0xe3;
 | |
| 
 | |
| - (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;
 | |
|     BOOL hasFirstResponder = NO;
 | |
|     if (isKeyDown && modifiedInput.length > 0 && interactionEnabled) {
 | |
|         UIResponder *firstResponder = nil;
 | |
|         for (UIWindow *window in FLEXUtility.allWindows) {
 | |
|             firstResponder = [window valueForKey:@"firstResponder"];
 | |
|             if (firstResponder) {
 | |
|                 hasFirstResponder = YES;
 | |
|                 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[exactMatch];
 | |
|             
 | |
|             if (!actionBlock) {
 | |
|                 FLEXKeyInput *shiftMatch = [FLEXKeyInput
 | |
|                     keyInputForKey:modifiedInput flags:flags&(~UIKeyModifierShift)
 | |
|                 ];
 | |
|                 actionBlock = self.actionsForKeyInputs[shiftMatch];
 | |
|             }
 | |
|             
 | |
|             if (!actionBlock) {
 | |
|                 FLEXKeyInput *capitalMatch = [FLEXKeyInput
 | |
|                     keyInputForKey:[unmodifiedInput uppercaseString] flags:flags
 | |
|                 ];
 | |
|                 actionBlock = self.actionsForKeyInputs[capitalMatch];
 | |
|             }
 | |
|             
 | |
|             if (actionBlock) {
 | |
|                 actionBlock();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // Calling _keyCode on events from the simulator keyboard will crash.
 | |
|     // It is only safe to call _keyCode when there's not an active responder.
 | |
|     if (!hasFirstResponder && [event respondsToSelector:@selector(_keyCode)]) {
 | |
|         long keyCode = [event _keyCode];
 | |
|         if (keyCode == kFLEXControlKeyCode) {
 | |
|             self.pressingControl = isKeyDown;
 | |
|         } else if (keyCode == kFLEXCommandKeyCode) {
 | |
|             self.pressingCommand = isKeyDown;
 | |
|         } else if (keyCode == kFLEXShiftKeyCode) {
 | |
|             self.pressingShift = isKeyDown;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSString *)keyboardShortcutsDescription {
 | |
|     NSMutableString *description = [NSMutableString new];
 | |
|     NSArray<FLEXKeyInput *> *keyInputs = [self.actionsForKeyInputs.allKeys
 | |
|         sortedArrayUsingComparator:^NSComparisonResult(FLEXKeyInput *input1, FLEXKeyInput *input2) {
 | |
|             return [input1.key caseInsensitiveCompare:input2.key];
 | |
|         }
 | |
|     ];
 | |
|     for (FLEXKeyInput *keyInput in keyInputs) {
 | |
|         [description appendFormat:@"%@\n", keyInput];
 | |
|     }
 | |
|     return [description copy];
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| #endif
 | 
