diff --git a/src/logic/AppCenterCrashes.swift b/src/logic/AppCenterCrashes.swift
index 80d8b2e9..7c2a47b8 100755
--- a/src/logic/AppCenterCrashes.swift
+++ b/src/logic/AppCenterCrashes.swift
@@ -22,15 +22,13 @@ class AppCenterCrash: NSObject, CrashesDelegate {
if defaults.string(forKey: "crashPolicy") == nil {
defaults.register(defaults: ["crashPolicy": "1"])
}
- if BackgroundWork.crashReportsQueue == nil {
- BackgroundWork.crashReportsQueue = DispatchQueue.globalConcurrent("crashReportsQueue", .utility)
- }
}
// periphery:ignore
func confirmationHandler(_ errorReports: [ErrorReport]) -> Bool {
self.initNecessaryFacilities()
let shouldSend = checkIfShouldSend()
+ BackgroundWork.startCrashReportsQueue()
BackgroundWork.crashReportsQueue.async {
AppCenter.networkRequestsAllowed = shouldSend
Crashes.notify(with: shouldSend ? .send : .dontSend)
diff --git a/src/logic/BackgroundWork.swift b/src/logic/BackgroundWork.swift
index 2a556573..b0ade154 100644
--- a/src/logic/BackgroundWork.swift
+++ b/src/logic/BackgroundWork.swift
@@ -8,6 +8,7 @@ class BackgroundWork {
static var crashReportsQueue: DispatchQueue!
static var accessibilityEventsThread: BackgroundThreadWithRunLoop!
static var mouseEventsThread: BackgroundThreadWithRunLoop!
+ static var keyboardEventsThread: BackgroundThreadWithRunLoop!
static var systemPermissionsThread: BackgroundThreadWithRunLoop!
static var repeatingKeyThread: BackgroundThreadWithRunLoop!
static var missionControlThread: BackgroundThreadWithRunLoop!
@@ -19,17 +20,33 @@ class BackgroundWork {
// swift static variables are lazy; we artificially force the threads to init
static func start() {
+ // TODO: clarify how this works
mainQueueConcurrentWorkQueue = DispatchQueue.globalConcurrent("mainQueueConcurrentWorkQueue", .userInteractive)
+ // calls to act on windows (e.g. AXUIElementSetAttributeValue, AXUIElementPerformAction) are done off the main thread
accessibilityCommandsQueue = DispatchQueue.globalConcurrent("accessibilityCommandsQueue", .userInteractive)
+ // calls to the AX APIs are blocking. We dispatch those on a globalConcurrent queue
axCallsQueue = DispatchQueue.globalConcurrent("axCallsQueue", .userInteractive)
- crashReportsQueue = DispatchQueue.globalConcurrent("crashReportsQueue", .utility)
+ // we observe app and windows notifications. They arrive on this thread, and are handled off the main thread initially
accessibilityEventsThread = BackgroundThreadWithRunLoop("accessibilityEventsThread", .userInteractive)
+ // we observe mouse clicks when thumbnailsPanel is open. They arrive on this thread, and are handled off the main thread initially
mouseEventsThread = BackgroundThreadWithRunLoop("mouseEventsThread", .userInteractive)
+ // some instances of events can be handled off the main thread; maybe not worth moving to a background thread
+ keyboardEventsThread = BackgroundThreadWithRunLoop("keyboardEventsThread", .userInteractive)
+ // not 100% sure this shouldn't be on the main-thread; it doesn't do anything except dispatch to main.async
repeatingKeyThread = BackgroundThreadWithRunLoop("repeatingKeyThread", .userInteractive)
+ // not 100% sure this shouldn't be on the main-thread; it doesn't do anything except dispatch to main.async
missionControlThread = BackgroundThreadWithRunLoop("missionControlThread", .userInteractive)
}
+ static func startCrashReportsQueue() {
+ if crashReportsQueue == nil {
+ // crash reports can be sent off the main thread
+ crashReportsQueue = DispatchQueue.globalConcurrent("crashReportsQueue", .utility)
+ }
+ }
+
static func startSystemPermissionThread() {
+ // not 100% sure this shouldn't be on the main-thread; it doesn't do anything except dispatch to main.async
systemPermissionsThread = BackgroundThreadWithRunLoop("systemPermissionsThread", .utility)
}
}
diff --git a/src/logic/events/DockEvents.swift b/src/logic/events/DockEvents.swift
index c36d135f..782382a3 100644
--- a/src/logic/events/DockEvents.swift
+++ b/src/logic/events/DockEvents.swift
@@ -27,5 +27,7 @@ class DockEvents {
fileprivate let handleEvent: AXObserverCallback = { _, _, notificationName, _ in
logger.d(notificationName)
- MissionControl.setState(MissionControlState(rawValue: notificationName as String)!)
+ DispatchQueue.main.async {
+ MissionControl.setState(MissionControlState(rawValue: notificationName as String)!)
+ }
}
diff --git a/src/logic/events/KeyboardEvents.swift b/src/logic/events/KeyboardEvents.swift
index ccf430da..fb7fb796 100644
--- a/src/logic/events/KeyboardEvents.swift
+++ b/src/logic/events/KeyboardEvents.swift
@@ -71,6 +71,7 @@ class KeyboardEvents {
addCgEventTapForModifierFlags()
}
+ // TODO: handle this on a background thread?
private static func addLocalMonitorForKeyDownAndKeyUp() {
NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .keyUp]) { (event: NSEvent) in
let someShortcutTriggered = handleEvent(nil, nil, event.type == .keyDown ? UInt32(event.keyCode) : nil, cocoaToCarbonFlags(event.modifierFlags), event.type == .keyDown ? event.isARepeat : false)
@@ -91,7 +92,7 @@ class KeyboardEvents {
userInfo: nil)
if let eventTap = eventTap {
let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0)
- CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
+ CFRunLoopAddSource(BackgroundWork.keyboardEventsThread.runLoop, runLoopSource, .commonModes)
} else {
App.app.restart()
}
@@ -145,10 +146,13 @@ fileprivate func handleEvent(_ id: EventHotKeyID?, _ shortcutState: ShortcutStat
fileprivate let cgEventFlagsChangedHandler: CGEventTapCallBack = {_, type, cgEvent, _ in
if type == .flagsChanged {
- let modifiers = cocoaToCarbonFlags(NSEvent.ModifierFlags(rawValue: UInt(cgEvent.flags.rawValue)))
- handleEvent(nil, nil, nil, modifiers, false)
+ DispatchQueue.main.async {
+ let modifiers = cocoaToCarbonFlags(NSEvent.ModifierFlags(rawValue: UInt(cgEvent.flags.rawValue)))
+ handleEvent(nil, nil, nil, modifiers, false)
+ }
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) {
CGEvent.tapEnable(tap: eventTap!, enable: true)
}
- return Unmanaged.passUnretained(cgEvent) // focused app will receive the event
+ // we always return this because we want to let these event pass through to the currently focused app
+ return Unmanaged.passUnretained(cgEvent)
}
diff --git a/src/logic/events/RunningApplicationsEvents.swift b/src/logic/events/RunningApplicationsEvents.swift
index 6d9d7f41..b5f6f48a 100644
--- a/src/logic/events/RunningApplicationsEvents.swift
+++ b/src/logic/events/RunningApplicationsEvents.swift
@@ -9,6 +9,7 @@ class RunningApplicationsEvents {
appsObserver = NSWorkspace.shared.observe(\.runningApplications, options: [.old, .new], changeHandler: handleEvent)
}
+ // TODO: handle this on a separate thread?
private static func handleEvent(_: NSWorkspace, _ change: NSKeyValueObservedChange) {
let workspaceApps = Set(NSWorkspace.shared.runningApplications)
// TODO: symmetricDifference has bad performance