Skip to content

Commit

Permalink
fix: don't hang waiting for faulty apps to reply (closes #182)
Browse files Browse the repository at this point in the history
  • Loading branch information
lwouis committed Mar 25, 2020
1 parent 9110f69 commit 246cf69
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 30 deletions.
34 changes: 20 additions & 14 deletions src/api-wrappers/AXUIElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
33 changes: 18 additions & 15 deletions src/logic/Applications.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class Applications {

static func initialDiscovery() {
addInitialRunningApplications()
observeRunningApplications()
addInitialRunningApplicationsWindows()
observeRunningApplications()
}

static func addInitialRunningApplications() {
Expand All @@ -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]) {
Expand All @@ -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"
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/logic/DispatchQueues.swift
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/logic/Spaces.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/ui/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 246cf69

Please sign in to comment.