From 246cf69310b902a360c069a2adb0ff28e622e6b0 Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Wed, 25 Mar 2020 00:01:28 +0900 Subject: [PATCH] fix: don't hang waiting for faulty apps to reply (closes #182) --- src/api-wrappers/AXUIElement.swift | 34 ++++++++++++++++++------------ src/logic/Applications.swift | 33 ++++++++++++++++------------- src/logic/DispatchQueues.swift | 1 + src/logic/Spaces.swift | 1 + src/ui/App.swift | 3 ++- 5 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/api-wrappers/AXUIElement.swift b/src/api-wrappers/AXUIElement.swift index 4548ce6b7..50b224b99 100644 --- a/src/api-wrappers/AXUIElement.swift +++ b/src/api-wrappers/AXUIElement.swift @@ -53,21 +53,27 @@ extension AXUIElement { } func subscribeWithRetry(_ axObserver: AXObserver, _ notification: String, _ pointer: UnsafeMutableRawPointer?, _ callback: (() -> Void)? = nil, _ runningApplication: NSRunningApplication? = nil, _ wid: CGWindowID? = nil, _ attemptsCount: Int = 0) { - if let runningApplication = runningApplication, Applications.appsInSubscriptionRetryLoop.first(where: { $0 == String(runningApplication.processIdentifier) + String(notification) }) == nil { return } - if let wid = wid, Windows.windowsInSubscriptionRetryLoop.first(where: { $0 == String(wid) + String(notification) }) == nil { return } - let result = AXObserverAddNotification(axObserver, self, notification as CFString, pointer) - if result == .success || result == .notificationAlreadyRegistered { - callback?() - stopRetries(runningApplication, wid, notification) - return - } else if result == .notificationUnsupported || result == .notImplemented { - stopRetries(runningApplication, wid, notification) - return + DispatchQueue.global(qos: .userInteractive).async { + DispatchQueue.main.async { + if let runningApplication = runningApplication, Applications.appsInSubscriptionRetryLoop.first(where: { $0 == String(runningApplication.processIdentifier) + String(notification) }) == nil { return } + if let wid = wid, Windows.windowsInSubscriptionRetryLoop.first(where: { $0 == String(wid) + String(notification) }) == nil { return } + } + let result = AXObserverAddNotification(axObserver, self, notification as CFString, pointer) + if result == .success || result == .notificationAlreadyRegistered { + DispatchQueue.main.async { [weak self] in + callback?() + self?.stopRetries(runningApplication, wid, notification) + } + } else if result == .notificationUnsupported || result == .notImplemented { + DispatchQueue.main.async { [weak self] in + self?.stopRetries(runningApplication, wid, notification) + } + } else { + DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + .milliseconds(10), execute: { [weak self] in + self?.subscribeWithRetry(axObserver, notification, pointer, callback, runningApplication, wid, attemptsCount + 1) + }) + } } - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10), execute: { [weak self] in - guard let self = self else { return } - self.subscribeWithRetry(axObserver, notification, pointer, callback, runningApplication, wid, attemptsCount + 1) - }) } func stopRetries(_ runningApplication: NSRunningApplication?, _ wid: CGWindowID?, _ notification: String) { diff --git a/src/logic/Applications.swift b/src/logic/Applications.swift index 3b5376c97..8e81c8ee7 100644 --- a/src/logic/Applications.swift +++ b/src/logic/Applications.swift @@ -15,8 +15,8 @@ class Applications { static func initialDiscovery() { addInitialRunningApplications() - observeRunningApplications() addInitialRunningApplicationsWindows() + observeRunningApplications() } static func addInitialRunningApplications() { @@ -28,23 +28,18 @@ class Applications { } static func addInitialRunningApplicationsWindows() { - // on initial launch, we use private APIs to bring windows from other spaces into the current space, observe them, then remove them from the current space let spaces = Spaces.otherSpaces() if spaces.count == 0 { - Windows.sortByLevel() - return - } - let windows = Spaces.windowsInSpaces(spaces).filter { window in - return Windows.list.first(where: { $0.cgWindowId == window }) == nil - } - if windows.count > 0 { - CGSAddWindowsToSpaces(cgsMainConnectionId, windows as NSArray, [Spaces.currentSpaceId]) Applications.observeNewWindows() - Windows.sortByLevel() - CGSRemoveWindowsFromSpaces(cgsMainConnectionId, windows as NSArray, [Spaces.currentSpaceId]) - return + } else { + let windows = Spaces.windowsInSpaces(spaces) + if windows.count > 0 { + // on initial launch, we use private APIs to bring windows from other spaces into the current space, observe them, then remove them from the current space + CGSAddWindowsToSpaces(cgsMainConnectionId, windows as NSArray, [Spaces.currentSpaceId]) + Applications.observeNewWindows() + CGSRemoveWindowsFromSpaces(cgsMainConnectionId, windows as NSArray, [Spaces.currentSpaceId]) + } } - Windows.sortByLevel() } static func addRunningApplications(_ runningApps: [NSRunningApplication]) { @@ -71,7 +66,15 @@ class Applications { } private static func filterApplications(_ apps: [NSRunningApplication]) -> [NSRunningApplication] { - return apps.filter { $0.activationPolicy != .prohibited || $0.bundleIdentifier == "io.github.hluk.CopyQ" } + return apps.filter { + ($0.activationPolicy != .prohibited || + // Bug in CopyQ; see https://github.com/hluk/CopyQ/issues/1330 + $0.bundleIdentifier == "io.github.hluk.CopyQ" || + // Bug in Octave.app; see https://github.com/octave-app/octave-app/issues/193#issuecomment-603648857 + $0.localizedName == "octave-gui") && + // bug in Octave.app; see https://github.com/octave-app/octave-app/issues/193 + $0.bundleIdentifier != "org.octave-app.Octave" + } } } diff --git a/src/logic/DispatchQueues.swift b/src/logic/DispatchQueues.swift index 7b812b7e8..02e89dea0 100644 --- a/src/logic/DispatchQueues.swift +++ b/src/logic/DispatchQueues.swift @@ -1,5 +1,6 @@ import Foundation +// OS events should be observed on background threads class DispatchQueues { static let focusActions = DispatchQueue(label: "focusActions", qos: .userInteractive) static let keyboardEvents = DispatchQueue(label: "keyboardEvents", qos: .userInteractive) diff --git a/src/logic/Spaces.swift b/src/logic/Spaces.swift index 86212e147..42c334254 100644 --- a/src/logic/Spaces.swift +++ b/src/logic/Spaces.swift @@ -8,6 +8,7 @@ class Spaces { static func observeSpaceChanges() { NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.activeSpaceDidChangeNotification, object: nil, queue: nil, using: { _ in + debugPrint("OS event", "activeSpaceDidChangeNotification") updateCurrentSpace() Applications.observeNewWindows() let app = App.shared as! App diff --git a/src/ui/App.swift b/src/ui/App.swift index 053ea9c18..7feb6c543 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -42,7 +42,8 @@ class App: NSApplication, NSApplicationDelegate { Keyboard.listenToGlobalEvents(self) preferencesWindow = PreferencesWindow() UpdatesTab.observeUserDefaults() -// Windows.refreshAllThumbnails() + // TODO: undeterministic; events in the queue may still be processing; good enough for now + DispatchQueue.main.async { Windows.sortByLevel() } } // keyboard shortcuts are broken without a menu. We generated the default menu from XCode and load it