From 4e2135cd6ecb88b92b79fba7fe7a2891f6d24ccf Mon Sep 17 00:00:00 2001 From: Econa77 Date: Wed, 13 May 2020 12:52:53 +0900 Subject: [PATCH] Separate modifier event handling --- Lib/Magnet.xcodeproj/project.pbxproj | 4 ++ Lib/Magnet/HotKeyCenter.swift | 84 ++++++--------------------- Lib/Magnet/KeyCombo.swift | 3 +- Lib/Magnet/ModifierEventHandler.swift | 72 +++++++++++++++++++++++ 4 files changed, 95 insertions(+), 68 deletions(-) create mode 100644 Lib/Magnet/ModifierEventHandler.swift diff --git a/Lib/Magnet.xcodeproj/project.pbxproj b/Lib/Magnet.xcodeproj/project.pbxproj index f84f62f..3877697 100644 --- a/Lib/Magnet.xcodeproj/project.pbxproj +++ b/Lib/Magnet.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 091D85EB245A917500930473 /* IntExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091D85E7245A917500930473 /* IntExtension.swift */; }; 091D85EC245A917500930473 /* KeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091D85E8245A917500930473 /* KeyExtension.swift */; }; 091D85ED245A917500930473 /* CollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091D85E9245A917500930473 /* CollectionExtension.swift */; }; + 092485A6246B9BEF00F4368A /* ModifierEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092485A5246B9BEF00F4368A /* ModifierEventHandler.swift */; }; 09DEE124245B128C00169BEC /* Sauce.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 091D85CF2459553A00930473 /* Sauce.framework */; }; FA3AA2162315A6A3007EAA1F /* CollectionExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3AA2152315A6A3007EAA1F /* CollectionExtensionTests.swift */; }; FAEC34B31C9059DF004177E2 /* Magnet.h in Headers */ = {isa = PBXBuildFile; fileRef = FAEC34B21C9059DF004177E2 /* Magnet.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -52,6 +53,7 @@ 091D85E7245A917500930473 /* IntExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntExtension.swift; sourceTree = ""; }; 091D85E8245A917500930473 /* KeyExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyExtension.swift; sourceTree = ""; }; 091D85E9245A917500930473 /* CollectionExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = ""; }; + 092485A5246B9BEF00F4368A /* ModifierEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifierEventHandler.swift; sourceTree = ""; }; FA3AA2152315A6A3007EAA1F /* CollectionExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtensionTests.swift; sourceTree = ""; }; FAEC34AF1C9059DF004177E2 /* Magnet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Magnet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FAEC34B21C9059DF004177E2 /* Magnet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Magnet.h; sourceTree = ""; }; @@ -137,6 +139,7 @@ FAEC34D71C905B4D004177E2 /* HotKey.swift */, FAEC34D91C905B5A004177E2 /* KeyCombo.swift */, FAEC34DD1C905B7C004177E2 /* HotKeyCenter.swift */, + 092485A5246B9BEF00F4368A /* ModifierEventHandler.swift */, FAEC34B21C9059DF004177E2 /* Magnet.h */, FAEC34B41C9059DF004177E2 /* Info.plist */, ); @@ -291,6 +294,7 @@ FAEC34DE1C905B7C004177E2 /* HotKeyCenter.swift in Sources */, 091D85EA245A917500930473 /* NSEventExtension.swift in Sources */, FAEC34D81C905B4D004177E2 /* HotKey.swift in Sources */, + 092485A6246B9BEF00F4368A /* ModifierEventHandler.swift in Sources */, 091D85EC245A917500930473 /* KeyExtension.swift in Sources */, 091D85ED245A917500930473 /* CollectionExtension.swift in Sources */, 091D85EB245A917500930473 /* IntExtension.swift in Sources */, diff --git a/Lib/Magnet/HotKeyCenter.swift b/Lib/Magnet/HotKeyCenter.swift index 19fb525..a4d8d8c 100644 --- a/Lib/Magnet/HotKeyCenter.swift +++ b/Lib/Magnet/HotKeyCenter.swift @@ -18,13 +18,12 @@ public final class HotKeyCenter { private var hotKeys = [String: HotKey]() private var hotKeyCount: UInt32 = 0 - private var tappedModifierKey = NSEvent.ModifierFlags(rawValue: 0) - private var multiModifiers = false - private var lastHandledEventTimeStamp: TimeInterval? + private let modifierEventHandler: ModifierEventHandler private let notificationCenter: NotificationCenter // MARK: - Initialize - init(notificationCenter: NotificationCenter = .default) { + init(modifierEventHandler: ModifierEventHandler = .init(), notificationCenter: NotificationCenter = .default) { + self.modifierEventHandler = modifierEventHandler self.notificationCenter = notificationCenter installHotKeyPressedEventHandler() installModifiersChangedEventHandlerIfNeeded() @@ -46,8 +45,9 @@ public extension HotKeyCenter { hotKeys[hotKey.identifier] = hotKey guard !hotKey.keyCombo.doubledModifiers else { return true } - // Normal macOS shortcut /* + * Normal macOS shortcut + * * Discussion: * When registering a hotkey, a KeyCode that conforms to the * keyboard layout at the time of registration is registered. @@ -67,7 +67,7 @@ public extension HotKeyCenter { GetEventDispatcherTarget(), 0, &carbonHotKey) - if error != 0 { + guard error == noErr else { unregister(with: hotKey) return false } @@ -120,11 +120,11 @@ private extension HotKeyCenter { pressedEventType.eventClass = OSType(kEventClassKeyboard) pressedEventType.eventKind = OSType(kEventHotKeyPressed) InstallEventHandler(GetEventDispatcherTarget(), { _, inEvent, _ -> OSStatus in - return HotKeyCenter.shared.sendCarbonEvent(inEvent!) + return HotKeyCenter.shared.sendPressedKeyboardEvent(inEvent!) }, 1, &pressedEventType, nil, nil) } - func sendCarbonEvent(_ event: EventRef) -> OSStatus { + func sendPressedKeyboardEvent(_ event: EventRef) -> OSStatus { assert(Int(GetEventClass(event)) == kEventClassKeyboard, "Unknown event class") var hotKeyId = EventHotKeyID() @@ -136,85 +136,35 @@ private extension HotKeyCenter { nil, &hotKeyId) - if error != 0 { return error } - + guard error == noErr else { return error } assert(hotKeyId.signature == UTGetOSTypeFromString("Magnet" as CFString), "Invalid hot key id") let hotKey = hotKeys.values.first(where: { $0.hotKeyId == hotKeyId.id }) switch GetEventKind(event) { case EventParamName(kEventHotKeyPressed): - hotKeyDown(hotKey) + hotKey?.invoke() default: assert(false, "Unknown event kind") } return noErr } - - func hotKeyDown(_ hotKey: HotKey?) { - guard let hotKey = hotKey else { return } - hotKey.invoke() - } } // MARK: - Double Tap Modifier Event private extension HotKeyCenter { func installModifiersChangedEventHandlerIfNeeded() { NSEvent.addGlobalMonitorForEvents(matching: .flagsChanged) { [weak self] event in - self?.sendModifiersChangeEvent(event) + self?.modifierEventHandler.handleModifiersEvent(with: event.modifierFlags, timestamp: event.timestamp) } NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) { [weak self] event -> NSEvent? in - self?.sendModifiersChangeEvent(event) + self?.modifierEventHandler.handleModifiersEvent(with: event.modifierFlags, timestamp: event.timestamp) return event } - } - - func sendModifiersChangeEvent(_ event: NSEvent) { - guard lastHandledEventTimeStamp != event.timestamp else { return } - lastHandledEventTimeStamp = event.timestamp - - let modifierFlags = event.modifierFlags - let commandTapped = modifierFlags.contains(.command) - let shiftTapped = modifierFlags.contains(.shift) - let controlTapped = modifierFlags.contains(.control) - let optionTapped = modifierFlags.contains(.option) - let modifiersCount = [commandTapped, optionTapped, shiftTapped, controlTapped].trueCount - guard modifiersCount != 0 else { return } - guard modifiersCount == 1 else { - multiModifiers = true - return + modifierEventHandler.doubleTapped = { [weak self] tappedModifierFlags in + self?.hotKeys.values + .filter { $0.keyCombo.doubledModifiers } + .filter { $0.keyCombo.modifiers == tappedModifierFlags.carbonModifiers() } + .forEach { $0.invoke() } } - guard !multiModifiers else { - multiModifiers = false - return - } - if (tappedModifierKey.contains(.command) && commandTapped) || - (tappedModifierKey.contains(.shift) && shiftTapped) || - (tappedModifierKey.contains(.control) && controlTapped) || - (tappedModifierKey.contains(.option) && optionTapped) { - doubleTapped(with: tappedModifierKey.carbonModifiers()) - tappedModifierKey = NSEvent.ModifierFlags(rawValue: 0) - } else { - if commandTapped { - tappedModifierKey = .command - } else if shiftTapped { - tappedModifierKey = .shift - } else if controlTapped { - tappedModifierKey = .control - } else if optionTapped { - tappedModifierKey = .option - } else { - tappedModifierKey = NSEvent.ModifierFlags(rawValue: 0) - } - } - // Clean Flag - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: { [weak self] in - self?.tappedModifierKey = NSEvent.ModifierFlags(rawValue: 0) - }) - } - - func doubleTapped(with key: Int) { - hotKeys.values - .filter { $0.keyCombo.doubledModifiers && $0.keyCombo.modifiers == key } - .forEach { $0.invoke() } } } diff --git a/Lib/Magnet/KeyCombo.swift b/Lib/Magnet/KeyCombo.swift index 7b69b1c..77dfd15 100644 --- a/Lib/Magnet/KeyCombo.swift +++ b/Lib/Magnet/KeyCombo.swift @@ -74,8 +74,9 @@ public final class KeyCombo: NSObject, NSCopying, NSCoding, Codable { } public init?(doubledCocoaModifiers modifiers: NSEvent.ModifierFlags) { + let filterdCocoaModifiers = modifiers.filterUnsupportModifiers() guard modifiers.isSingleFlags else { return nil } - self.modifiers = modifiers.carbonModifiers() + self.modifiers = filterdCocoaModifiers.carbonModifiers() self.QWERTYKeyCode = 0 self.doubledModifiers = true self.key = .a diff --git a/Lib/Magnet/ModifierEventHandler.swift b/Lib/Magnet/ModifierEventHandler.swift new file mode 100644 index 0000000..25c4896 --- /dev/null +++ b/Lib/Magnet/ModifierEventHandler.swift @@ -0,0 +1,72 @@ +// +// ModifierEventHandler.swift +// +// Magnet +// GitHub: https://github.com/clipy +// HP: https://clipy-app.com +// +// Copyright © 2015-2020 Clipy Project. +// + +import Cocoa + +public final class ModifierEventHandler { + + // MARK: - Properties + public var doubleTapped: ((NSEvent.ModifierFlags) -> Void)? + + private var tappingModifierFlags = NSEvent.ModifierFlags() + private var isTappingMultiModifiers = false + private var lastHandledEventTimestamp: TimeInterval? + private let cleanTimeInterval: DispatchTimeInterval + private let cleanQueue: DispatchQueue + + // MARK: - Initialize + init(cleanTimeInterval: DispatchTimeInterval = .milliseconds(300), cleanQueue: DispatchQueue = .main) { + self.cleanTimeInterval = cleanTimeInterval + self.cleanQueue = cleanQueue + } + +} + +// MARK: - Handling +public extension ModifierEventHandler { + func handleModifiersEvent(with modifierFlags: NSEvent.ModifierFlags, timestamp: TimeInterval) { + guard lastHandledEventTimestamp != timestamp else { return } + lastHandledEventTimestamp = timestamp + + handleDoubleTapModifierEvent(modifierFlags: modifierFlags) + } + + private func handleDoubleTapModifierEvent(modifierFlags: NSEvent.ModifierFlags) { + let tappedModifierFlags = modifierFlags.filterUnsupportModifiers() + let commandTapped = tappedModifierFlags.contains(.command) + let shiftTapped = tappedModifierFlags.contains(.shift) + let controlTapped = tappedModifierFlags.contains(.control) + let optionTapped = tappedModifierFlags.contains(.option) + let tappedModifierCount = [commandTapped, shiftTapped, controlTapped, optionTapped].trueCount + guard tappedModifierCount != 0 else { return } + guard tappedModifierCount == 1 else { + isTappingMultiModifiers = true + return + } + guard !isTappingMultiModifiers else { + isTappingMultiModifiers = false + return + } + if (tappingModifierFlags.contains(.command) && commandTapped) || + (tappingModifierFlags.contains(.shift) && shiftTapped) || + (tappingModifierFlags.contains(.control) && controlTapped) || + (tappingModifierFlags.contains(.option) && optionTapped) { + doubleTapped?(tappingModifierFlags) + tappingModifierFlags = NSEvent.ModifierFlags() + } else { + tappingModifierFlags = tappedModifierFlags + } + + // After a certain amount of time, the tapped modifier will be reset. + cleanQueue.asyncAfter(deadline: .now() + cleanTimeInterval) { [weak self] in + self?.tappingModifierFlags = NSEvent.ModifierFlags() + } + } +}