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 19 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 ] 30 } 31 32 /** 33 Extend `UIResponder` so we can put our key commands to all responders. 34 */ 35 extension UIResponder: DevMenuUIResponderExtensionProtocol { 36 // NOTE: throttle the key handler because on iOS the handleKeyCommand: 37 // method gets called repeatedly if the command key is held down. 38 static private var lastKeyCommandExecutionTime: TimeInterval = 0 39 static private var lastKeyCommand: UIKeyCommand? 40 41 @objc 42 var EXDevMenu_keyCommands: [UIKeyCommand] { 43 let actions = DevMenuManager.shared.devMenuCallable.filter { $0 is DevMenuExportedAction } as! [DevMenuExportedAction] 44 let actionsWithKeyCommands = actions.filter { $0.keyCommand != nil } 45 var keyCommands = actionsWithKeyCommands.map { $0.keyCommand! } 46 keyCommands.insert(contentsOf: DevMenuKeyCommandsInterceptor.globalKeyCommands, at: 0) 47 keyCommands.append(contentsOf: self.EXDevMenu_keyCommands) 48 return keyCommands 49 } 50 51 @objc 52 public func EXDevMenu_handleKeyCommand(_ key: UIKeyCommand) { 53 tryHandleKeyCommand(key) { 54 let actions = DevMenuManager.shared.devMenuCallable.filter { $0 is DevMenuExportedAction } as! [DevMenuExportedAction] 55 guard let action = actions.first(where: { $0.keyCommand == key }) else { 56 return 57 } 58 59 if action.isAvailable() { 60 action.call() 61 DevMenuManager.shared.closeMenu() 62 } 63 } 64 } 65 66 @objc 67 func EXDevMenu_toggleDevMenu(_ key: UIKeyCommand) { 68 tryHandleKeyCommand(key) { 69 DevMenuManager.shared.toggleMenu() 70 } 71 } 72 73 private func shouldTriggerAction(_ key: UIKeyCommand) -> Bool { 74 return UIResponder.lastKeyCommand !== key || CACurrentMediaTime() - UIResponder.lastKeyCommandExecutionTime > 0.5 75 } 76 77 private func tryHandleKeyCommand(_ key: UIKeyCommand, handler: () -> Void ) { 78 if shouldTriggerAction(key) { 79 handler() 80 UIResponder.lastKeyCommand = key 81 UIResponder.lastKeyCommandExecutionTime = CACurrentMediaTime() 82 } 83 } 84 } 85