fix: app could sometimes crash in the background
closes #5260 appcenter showed that access to debugId() from main and non-main threa could crash the app. Making debugId a string that's set once at init simplifies everything and allows concurrent access from threads
This commit is contained in:
1 parent
42c0a03ddc
commit
ab704dc4be
7 files changed
+26
-29
No files matched your search
@@ -19,10 +19,7 @@ class Application: NSObject {
|
||||
var dockLabel: String?
|
||||
var focusedWindow: Window? = nil
|
||||
var alreadyRequestedToQuit = false
|
||||
|
||||
func debugId() -> String {
|
||||
return "(pid:\(pid) \(bundleIdentifier ?? bundleURL?.absoluteString ?? executableURL?.absoluteString ?? localizedName))"
|
||||
}
|
||||
var debugId: String
|
||||
|
||||
static let notifications = [
|
||||
kAXApplicationActivatedNotification,
|
||||
@@ -81,8 +78,9 @@ class Application: NSObject {
|
||||
bundleIdentifier = runningApplication.bundleIdentifier
|
||||
bundleURL = runningApplication.bundleURL
|
||||
executableURL = runningApplication.executableURL
|
||||
debugId = "(pid:\(pid) \(bundleIdentifier ?? bundleURL?.absoluteString ?? executableURL?.absoluteString ?? localizedName))"
|
||||
super.init()
|
||||
Logger.info { self.debugId() }
|
||||
Logger.info { self.debugId }
|
||||
observeEventsIfEligible()
|
||||
kvObservers = [
|
||||
runningApplication.observe(\.isFinishedLaunching, options: [.new]) { [weak self] _, _ in
|
||||
@@ -100,7 +98,7 @@ class Application: NSObject {
|
||||
}
|
||||
|
||||
deinit {
|
||||
Logger.info { self.debugId() }
|
||||
Logger.info { self.debugId }
|
||||
}
|
||||
|
||||
|
||||
@@ -118,17 +116,17 @@ class Application: NSObject {
|
||||
|
||||
private func observeEvents() {
|
||||
guard let axObserver else { return }
|
||||
AXUIElement.retryAxCallUntilTimeout(context: debugId(), pid: pid, callType: .subscribeToAppNotification) { [weak self] in
|
||||
AXUIElement.retryAxCallUntilTimeout(context: debugId, pid: pid, callType: .subscribeToAppNotification) { [weak self] in
|
||||
guard let self, !self.isReallyFinishedLaunching else { return }
|
||||
if try self.axUiElement!.subscribeToNotification(axObserver, Application.notifications.first!) {
|
||||
Logger.debug { "Subscribed to app: \(self.debugId())" }
|
||||
Logger.debug { "Subscribed to app: \(self.debugId)" }
|
||||
if !self.isReallyFinishedLaunching {
|
||||
// some apps have `isFinishedLaunching == true` but are actually not finished, and will return .cannotComplete
|
||||
// we consider them ready when the first subscription succeeds
|
||||
// windows opened before that point won't send a notification, so check those windows manually here
|
||||
self.isReallyFinishedLaunching = true
|
||||
for notification in Application.notifications.dropFirst() {
|
||||
AXUIElement.retryAxCallUntilTimeout(context: self.debugId(), pid: self.pid, callType: .subscribeToAppNotification) { [weak self] in
|
||||
AXUIElement.retryAxCallUntilTimeout(context: self.debugId, pid: self.pid, callType: .subscribeToAppNotification) { [weak self] in
|
||||
try self?.axUiElement!.subscribeToNotification(axObserver, notification)
|
||||
}
|
||||
}
|
||||
@@ -142,7 +140,7 @@ class Application: NSObject {
|
||||
}
|
||||
|
||||
func manuallyUpdateWindows() {
|
||||
AXUIElement.retryAxCallUntilTimeout(context: debugId(), pid: pid, callType: .updateAppWindows) { [weak self] in
|
||||
AXUIElement.retryAxCallUntilTimeout(context: debugId, pid: pid, callType: .updateAppWindows) { [weak self] in
|
||||
guard let self, let axUiElement = self.axUiElement else { return }
|
||||
let axWindows = try axUiElement.allWindows(self.pid)
|
||||
guard !axWindows.isEmpty else {
|
||||
|
||||
@@ -47,7 +47,7 @@ class Applications {
|
||||
let zombies = believedAlive.subtracting(confirmedAlive)
|
||||
for window in Windows.list.reversed() {
|
||||
if let wid = window.cgWindowId, zombies.contains(wid) {
|
||||
Logger.debug { window.debugId() }
|
||||
Logger.debug { window.debugId }
|
||||
Windows.removeWindows([window], true)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-12
@@ -11,7 +11,7 @@ class Window {
|
||||
]
|
||||
private static var globalCreationCounter = Int.zero
|
||||
|
||||
var id: String!
|
||||
var id: String
|
||||
var cgWindowId: CGWindowID?
|
||||
var lastFocusOrder = Int.zero
|
||||
var creationOrder = Int.zero
|
||||
@@ -35,10 +35,7 @@ class Window {
|
||||
var application: Application
|
||||
var axObserver: AXObserver?
|
||||
var rowIndex: Int?
|
||||
|
||||
func debugId() -> String {
|
||||
return "\(application.debugId()) (wid:\(cgWindowId) title:\(title))"
|
||||
}
|
||||
var debugId: String!
|
||||
|
||||
init(_ axUiElement: AXUIElement, _ application: Application, _ wid: CGWindowID, _ title: String?, _ isFullscreen: Bool?, _ isMinimized: Bool?, _ position: CGPoint?, _ size: CGSize?) {
|
||||
id = "\(wid)"
|
||||
@@ -47,6 +44,7 @@ class Window {
|
||||
cgWindowId = wid
|
||||
self.updateSpacesAndScreen()
|
||||
updateFromAxAttributes(title, size, position, isFullscreen, isMinimized)
|
||||
debugId = "\(self.application.debugId) (wid:\(cgWindowId) title:\(self.title))"
|
||||
Window.globalCreationCounter += 1
|
||||
creationOrder = Window.globalCreationCounter
|
||||
application.removeWindowlessAppWindow()
|
||||
@@ -54,7 +52,7 @@ class Window {
|
||||
// It may be responsive now since it has a window; we attempt again
|
||||
application.observeEventsIfEligible()
|
||||
checkIfFocused()
|
||||
Logger.info { self.debugId() }
|
||||
Logger.info { self.debugId }
|
||||
observeEvents()
|
||||
}
|
||||
|
||||
@@ -64,11 +62,12 @@ class Window {
|
||||
title = bestEffortTitle(nil)
|
||||
Window.globalCreationCounter += 1
|
||||
creationOrder = Window.globalCreationCounter
|
||||
Logger.debug { self.debugId() }
|
||||
debugId = "\(application.debugId) (title:\(title))"
|
||||
Logger.debug { self.debugId }
|
||||
}
|
||||
|
||||
deinit {
|
||||
Logger.info { self.debugId() }
|
||||
Logger.info { self.debugId }
|
||||
}
|
||||
|
||||
func updateFromAxAttributes(_ title: String?, _ size: CGSize?, _ position: CGPoint?, _ isFullscreen: Bool?, _ isMinimized: Bool?) {
|
||||
@@ -88,12 +87,12 @@ class Window {
|
||||
private func observeEvents() {
|
||||
AXObserverCreate(application.pid, AccessibilityEvents.axObserverCallback, &axObserver)
|
||||
guard let axObserver else { return }
|
||||
AXUIElement.retryAxCallUntilTimeout(context: debugId(), pid: application.pid, callType: .subscribeToWindowNotification) { [weak self] in
|
||||
AXUIElement.retryAxCallUntilTimeout(context: debugId, pid: application.pid, callType: .subscribeToWindowNotification) { [weak self] in
|
||||
guard let self else { return }
|
||||
if try self.axUiElement!.subscribeToNotification(axObserver, Window.notifications.first!) {
|
||||
Logger.debug { "Subscribed to window: \(self.debugId())" }
|
||||
Logger.debug { "Subscribed to window: \(self.debugId)" }
|
||||
for notification in Window.notifications.dropFirst() {
|
||||
AXUIElement.retryAxCallUntilTimeout(context: self.debugId(), pid: self.application.pid, callType: .subscribeToWindowNotification) { [weak self] in
|
||||
AXUIElement.retryAxCallUntilTimeout(context: self.debugId, pid: self.application.pid, callType: .subscribeToWindowNotification) { [weak self] in
|
||||
try self?.axUiElement!.subscribeToNotification(axObserver, notification)
|
||||
}
|
||||
}
|
||||
@@ -316,7 +315,7 @@ class Window {
|
||||
/// some apps will not trigger AXApplicationActivated, where we usually update application.focusedWindow
|
||||
/// workaround: we check and possibly do it here
|
||||
private func checkIfFocused() {
|
||||
AXUIElement.retryAxCallUntilTimeout(context: debugId(), pid: application.pid, callType: .updateWindow) { [weak self] in
|
||||
AXUIElement.retryAxCallUntilTimeout(context: debugId, pid: application.pid, callType: .updateWindow) { [weak self] in
|
||||
guard let self,
|
||||
let focusedWindow = try self.application.axUiElement?.attributes([kAXFocusedWindowAttribute]).focusedWindow,
|
||||
let window = try (Windows.list.first { $0.isEqualRobust(focusedWindow, (try focusedWindow.cgWindowId())) }) else { return }
|
||||
|
||||
@@ -10,7 +10,7 @@ class Windows {
|
||||
static func updateIsFullscreenOnCurrentSpace() {
|
||||
let windowsOnCurrentSpace = list.filter { !$0.isWindowlessApp }
|
||||
for window in windowsOnCurrentSpace {
|
||||
AXUIElement.retryAxCallUntilTimeout(context: window.debugId(), after: .now() + humanPerceptionDelay, callType: .updateWindow) { [weak window] in
|
||||
AXUIElement.retryAxCallUntilTimeout(context: window.debugId, after: .now() + humanPerceptionDelay, callType: .updateWindow) { [weak window] in
|
||||
guard let window else { return }
|
||||
// we reuse existing code, to update .isFullscreen, as if there was a kAXWindowResizedNotification
|
||||
try AccessibilityEvents.handleEventWindow(kAXWindowResizedNotification, window.cgWindowId!, window.application.pid, window.axUiElement!)
|
||||
|
||||
@@ -25,7 +25,7 @@ class AccessibilityEvents {
|
||||
let appFocusedWindow = try element.attributes([kAXFocusedWindowAttribute]).focusedWindow
|
||||
DispatchQueue.main.async {
|
||||
guard let app = Applications.findOrCreate(pid) else { return }
|
||||
Logger.info { "\(type) app:\(app.debugId())" }
|
||||
Logger.info { "\(type) app:\(app.debugId)" }
|
||||
if type == kAXApplicationActivatedNotification {
|
||||
applicationActivated(app, pid, type, appFocusedWindow)
|
||||
} else if type == kAXApplicationHiddenNotification || type == kAXApplicationShownNotification {
|
||||
@@ -84,7 +84,7 @@ class AccessibilityEvents {
|
||||
let a = try element.attributes([kAXTitleAttribute, kAXSubroleAttribute, kAXRoleAttribute, kAXSizeAttribute, kAXPositionAttribute, kAXFullscreenAttribute, kAXMinimizedAttribute])
|
||||
DispatchQueue.main.async {
|
||||
guard let app = Applications.findOrCreate(pid) else { return }
|
||||
Logger.info { "\(type) wid:\(wid) app:\(app.debugId())" }
|
||||
Logger.info { "\(type) wid:\(wid) app:\(app.debugId)" }
|
||||
if type == kAXUIElementDestroyedNotification {
|
||||
windowDestroyed(element, pid, wid)
|
||||
return
|
||||
@@ -96,7 +96,7 @@ class AccessibilityEvents {
|
||||
}
|
||||
return
|
||||
}
|
||||
Logger.debug { "\(type) win:\(window.debugId())" }
|
||||
Logger.debug { "\(type) win:\(window.debugId)" }
|
||||
if type == kAXMainWindowChangedNotification || type == kAXFocusedWindowChangedNotification {
|
||||
focusedWindowChanged(window)
|
||||
} else if type == kAXWindowResizedNotification || type == kAXWindowMovedNotification {
|
||||
|
||||
@@ -62,7 +62,7 @@ class WindowCaptureScreenshots {
|
||||
ActiveWindowCaptures.increment()
|
||||
SCScreenshotManager.captureImage(contentFilter: filter, configuration: config) { cgImage, error in
|
||||
ActiveWindowCaptures.decrement()
|
||||
guard let cgImage, error == nil else { Logger.error { "\(window.debugId()) \(cgImage == nil) \(error)" }; return }
|
||||
guard let cgImage, error == nil else { Logger.error { "\(window.debugId) \(cgImage == nil) \(error)" }; return }
|
||||
guard source != .refreshOnlyThumbnailsAfterShowUi || App.app.appIsBeingUsed else { return }
|
||||
DispatchQueue.main.async {
|
||||
guard source != .refreshOnlyThumbnailsAfterShowUi || App.app.appIsBeingUsed else { return }
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ class App: AppCenterApplication {
|
||||
func focusTarget() {
|
||||
guard appIsBeingUsed else { return } // already hidden
|
||||
let selectedWindow = Windows.selectedWindow()
|
||||
Logger.info { selectedWindow?.debugId() }
|
||||
Logger.info { selectedWindow?.debugId }
|
||||
focusSelectedWindow(selectedWindow)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user