From 4b5fa1a9db4bb3d20f478d78dee455a723dc35c6 Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Mon, 28 Oct 2019 12:03:34 +0900 Subject: [PATCH] feat: don't show the UI on very fast shortcut triggers from the user By listening to keyboard events from another thread, that thread is able to cancel tasks scheduled on the main UI thread. This means that if the user activates the app, and very quickly release the keys to focus the other window, that key release event may be processed before the main UI thread tasks have finished. The keyboard processing thread may then cancel the UI work resulting in the UI not showing, which is good here --- alt-tab-macos/logic/Keyboard.swift | 32 ++++++++++++++++-------------- alt-tab-macos/ui/Application.swift | 5 ++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/alt-tab-macos/logic/Keyboard.swift b/alt-tab-macos/logic/Keyboard.swift index 8e06d1b1..97e8c4c7 100644 --- a/alt-tab-macos/logic/Keyboard.swift +++ b/alt-tab-macos/logic/Keyboard.swift @@ -22,21 +22,23 @@ class Keyboard { var eventTap: CFMachPort? func listenToGlobalKeyboardEvents(_ delegate: Application) { - let eventMask = [CGEventType.keyDown, CGEventType.keyUp, CGEventType.flagsChanged].reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) }) - eventTap = CGEvent.tapCreate( - tap: .cgSessionEventTap, - place: .headInsertEventTap, - options: .defaultTap, - eventsOfInterest: CGEventMask(eventMask), - callback: { (_, _, event, delegate_) -> Unmanaged? in - let d = Unmanaged.fromOpaque(delegate_!).takeUnretainedValue() - return keyboardHandler(event, d) - }, - userInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(delegate).toOpaque())) - let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0) - CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes) - CGEvent.tapEnable(tap: eventTap!, enable: true) - CFRunLoopRun() + DispatchQueue.global(qos: .userInteractive).async { + let eventMask = [CGEventType.keyDown, CGEventType.keyUp, CGEventType.flagsChanged].reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) }) + eventTap = CGEvent.tapCreate( + tap: .cgSessionEventTap, + place: .headInsertEventTap, + options: .defaultTap, + eventsOfInterest: CGEventMask(eventMask), + callback: { (_, _, event, delegate_) -> Unmanaged? in + let d = Unmanaged.fromOpaque(delegate_!).takeUnretainedValue() + return keyboardHandler(event, d) + }, + userInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(delegate).toOpaque())) + let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0) + CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes) + CGEvent.tapEnable(tap: eventTap!, enable: true) + CFRunLoopRun() + } } func keyboardHandler(_ cgEvent: CGEvent, _ delegate: Application) -> Unmanaged? { diff --git a/alt-tab-macos/ui/Application.swift b/alt-tab-macos/ui/Application.swift index 1eb09c8e..b14c868b 100644 --- a/alt-tab-macos/ui/Application.swift +++ b/alt-tab-macos/ui/Application.swift @@ -47,7 +47,7 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate { func hideUi() { debugPrint("hideUi") - thumbnailsPanel!.orderOut(nil) + DispatchQueue.main.async(execute: { self.thumbnailsPanel!.orderOut(nil) }) appIsBeingUsed = false isFirstSummon = true } @@ -96,7 +96,7 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate { func cycleSelection(_ step: Int) { selectedOpenWindow = cellWithStep(step) - thumbnailsPanel!.highlightCellAt(step) + DispatchQueue.main.async(execute: { self.thumbnailsPanel!.highlightCellAt(step) }) } func showUiOrCycleSelection(_ step: Int) { @@ -117,7 +117,6 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate { } workItems.append(workItem) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Preferences.windowDisplayDelay!, execute: workItem) - } else { cycleSelection(step) }