1 // Copyright 2015-present 650 Industries. All rights reserved. 2 3 import UIKit 4 import EXDevMenuInterface 5 6 class DevMenuKeyCommandsInterceptor { 7 /** 8 Returns bool value whether the dev menu key commands are being intercepted. 9 */ 10 static var isInstalled: Bool = false { 11 willSet { 12 if isInstalled != newValue { 13 // Capture touch gesture from any window by swizzling default implementation from UIWindow. 14 swizzle() 15 } 16 } 17 } 18 swizzlenull19 static private func swizzle() { 20 DevMenuUtils.swizzle( 21 selector: #selector(getter: UIResponder.keyCommands), 22 withSelector: #selector(getter: UIResponder.EXDevMenu_keyCommands), 23 forClass: UIResponder.self 24 ) 25 } 26 27 static let globalKeyCommands: [UIKeyCommand] = [ 28 UIKeyCommand(input: "d", modifierFlags: .command, action: #selector(UIResponder.EXDevMenu_toggleDevMenu(_:))), 29 UIKeyCommand(input: "d", modifierFlags: .control, action: #selector(UIResponder.EXDevMenu_toggleDevMenu(_:))) 30 ] 31 } 32 33 /** 34 Extend `UIResponder` so we can put our key commands to all responders. 35 */ 36 extension UIResponder: DevMenuUIResponderExtensionProtocol { 37 // NOTE: throttle the key handler because on iOS the handleKeyCommand: 38 // method gets called repeatedly if the command key is held down. 39 static private var lastKeyCommandExecutionTime: TimeInterval = 0 40 static private var lastKeyCommand: UIKeyCommand? 41 42 @objc 43 var EXDevMenu_keyCommands: [UIKeyCommand] { 44 if self is UITextField || self is UITextView || String(describing: type(of: self)) == "WKContentView" { 45 return [] 46 } 47 let actions = DevMenuManager.shared.devMenuCallable.filter { $0 is DevMenuExportedAction } as! [DevMenuExportedAction] 48 let actionsWithKeyCommands = actions.filter { $0.keyCommand != nil } 49 var keyCommands = actionsWithKeyCommands.map { $0.keyCommand! } 50 keyCommands.insert(contentsOf: DevMenuKeyCommandsInterceptor.globalKeyCommands, at: 0) 51 keyCommands.append(contentsOf: self.EXDevMenu_keyCommands) 52 return keyCommands 53 } 54 55 @objc EXDevMenu_handleKeyCommandnull56 public func EXDevMenu_handleKeyCommand(_ key: UIKeyCommand) { 57 tryHandleKeyCommand(key) { 58 let actions = DevMenuManager.shared.devMenuCallable.filter { $0 is DevMenuExportedAction } as! [DevMenuExportedAction] 59 guard let action = actions.first(where: { $0.keyCommand == key }) else { 60 return 61 } 62 63 if action.isAvailable() { 64 action.call() 65 DevMenuManager.shared.closeMenu() 66 } 67 } 68 } 69 70 @objc EXDevMenu_toggleDevMenunull71 func EXDevMenu_toggleDevMenu(_ key: UIKeyCommand) { 72 tryHandleKeyCommand(key) { 73 DevMenuManager.shared.toggleMenu() 74 } 75 } 76 shouldTriggerActionnull77 private func shouldTriggerAction(_ key: UIKeyCommand) -> Bool { 78 return UIResponder.lastKeyCommand !== key || CACurrentMediaTime() - UIResponder.lastKeyCommandExecutionTime > 0.1 79 } 80 81 private func tryHandleKeyCommand(_ key: UIKeyCommand, handler: () -> Void ) { 82 if shouldTriggerAction(key) { 83 handler() 84 UIResponder.lastKeyCommand = key 85 UIResponder.lastKeyCommandExecutionTime = CACurrentMediaTime() 86 } 87 } 88 } 89