fix: better handle apps that start as background processes
some apps start with .activationPolicy == .prohibited and then transition to != .prohibited later on. We now observe that attribute evolution, instead of hardcoding exceptions for some apps. For the initial filtering of processes, we now takes every process that is not an XPC process, as all others could spawn windows down the line
This commit is contained in:
1 parent
86aad6d68b
commit
49816ab320
5 files changed
+45
-47
No files matched your search
@@ -28,6 +28,7 @@ extension AXUIElement {
|
||||
// All Steam windows have subrole = AXUnknown
|
||||
// some dropdown menus are not desirable; they have title == "", or sometimes role == nil when switching between menus quickly
|
||||
(bundleIdentifier == "com.valvesoftware.steam" && title() != "" && role() != nil)) &&
|
||||
// don't show floating windows
|
||||
isOnNormalLevel()
|
||||
}
|
||||
|
||||
@@ -119,7 +120,7 @@ extension AXUIElement {
|
||||
|
||||
func handleSubscriptionAttempt(_ result: AXError, _ axObserver: AXObserver, _ notification: String, _ pointer: UnsafeMutableRawPointer?, _ callback: (() -> Void)?, _ runningApplication: NSRunningApplication?, _ wid: CGWindowID?, _ startTime: DispatchTime) -> Void {
|
||||
if result == .success || result == .notificationAlreadyRegistered {
|
||||
DispatchQueue.main.async { [weak self] () -> () in
|
||||
DispatchQueue.main.async { () -> () in
|
||||
callback?()
|
||||
}
|
||||
} else if result != .notificationUnsupported && result != .notImplemented {
|
||||
|
||||
@@ -160,6 +160,16 @@ let kAXFullscreenAttribute = "AXFullScreen"
|
||||
//@_silgen_name("GetProcessPID")
|
||||
//func GetProcessPID(_ psn: inout ProcessSerialNumber, _ pid: inout pid_t) -> Void
|
||||
//
|
||||
//// returns info about a given psn
|
||||
//// * macOS 10.9-
|
||||
//@_silgen_name("GetProcessInformation") @discardableResult
|
||||
//func GetProcessInformation(_ psn: inout ProcessSerialNumber, _ info: inout ProcessInfoRec) -> OSErr
|
||||
//
|
||||
//// returns the psn for a given pid
|
||||
//// * macOS 10.9-
|
||||
//@_silgen_name("GetProcessForPID") @discardableResult
|
||||
//func GetProcessForPID(_ pid: pid_t, _ psn: inout ProcessSerialNumber) -> OSStatus
|
||||
//
|
||||
//// crashed the app with SIGSEGV
|
||||
//// * macOS 10.10+
|
||||
//@_silgen_name("CGSGetWindowType") @discardableResult
|
||||
|
||||
+16
-23
@@ -1,6 +1,8 @@
|
||||
import Cocoa
|
||||
|
||||
class Application: NSObject {
|
||||
// kvObservers should be listed first, so it gets deinit'ed first; otherwise it can crash
|
||||
var kvObservers: [NSKeyValueObservation]?
|
||||
var runningApplication: NSRunningApplication
|
||||
var axUiElement: AXUIElement?
|
||||
var axObserver: AXObserver?
|
||||
@@ -26,32 +28,29 @@ class Application: NSObject {
|
||||
init(_ runningApplication: NSRunningApplication) {
|
||||
self.runningApplication = runningApplication
|
||||
super.init()
|
||||
if runningApplication.isFinishedLaunching {
|
||||
addAndObserveWindows()
|
||||
} else {
|
||||
runningApplication.addObserver(self, forKeyPath: "isFinishedLaunching", options: [.new], context: nil)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
// some apps never finish launching; observer should be removed to avoid leak
|
||||
removeObserver()
|
||||
addAndObserveWindows()
|
||||
kvObservers = [
|
||||
runningApplication.observe(\.isFinishedLaunching, options: [.new]) { [weak self] _, _ in self?.addAndObserveWindows() },
|
||||
runningApplication.observe(\.activationPolicy, options: [.new]) { [weak self] _, _ in self?.addAndObserveWindows() },
|
||||
]
|
||||
}
|
||||
|
||||
func removeObserver() {
|
||||
runningApplication.safeRemoveObserver(self, "isFinishedLaunching")
|
||||
}
|
||||
|
||||
private func addAndObserveWindows() {
|
||||
axUiElement = AXUIElementCreateApplication(runningApplication.processIdentifier)
|
||||
AXObserverCreate(runningApplication.processIdentifier, axObserverCallback, &axObserver)
|
||||
debugPrint("Adding app", runningApplication.processIdentifier, runningApplication.bundleIdentifier ?? "nil")
|
||||
observeEvents()
|
||||
func addAndObserveWindows() {
|
||||
if runningApplication.isFinishedLaunching && runningApplication.activationPolicy != .prohibited {
|
||||
axUiElement = AXUIElementCreateApplication(runningApplication.processIdentifier)
|
||||
AXObserverCreate(runningApplication.processIdentifier, axObserverCallback, &axObserver)
|
||||
debugPrint("Adding app", runningApplication.processIdentifier, runningApplication.bundleIdentifier ?? "nil")
|
||||
observeEvents()
|
||||
}
|
||||
}
|
||||
|
||||
func observeNewWindows() {
|
||||
if let windows = (axUiElement!.windows()?
|
||||
.filter { $0.isActualWindow(runningApplication.bundleIdentifier) }) {
|
||||
if runningApplication.isFinishedLaunching && runningApplication.activationPolicy != .prohibited,
|
||||
let windows = (axUiElement!.windows()?.filter { $0.isActualWindow(runningApplication.bundleIdentifier) }) {
|
||||
// bug in macOS: sometimes the OS returns multiple duplicate windows (e.g. Mail.app starting at login)
|
||||
let actualWindows = Array(Set(windows.filter { Windows.list.firstIndexThatMatches($0) == nil }))
|
||||
if actualWindows.count > 0 {
|
||||
@@ -60,12 +59,6 @@ class Application: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
|
||||
guard let isFinishedLaunching = change![.newKey], isFinishedLaunching as! Bool else { return }
|
||||
removeObserver()
|
||||
addAndObserveWindows()
|
||||
}
|
||||
|
||||
private func addWindows(_ axWindows: [AXUIElement]) {
|
||||
let windows = axWindows.map { Window($0, self) }
|
||||
Windows.list.insertAndScaleRecycledPool(windows, at: 0)
|
||||
|
||||
@@ -65,14 +65,7 @@ class Applications {
|
||||
}
|
||||
|
||||
private static func isActualApplication(_ app: NSRunningApplication) -> Bool {
|
||||
return (app.activationPolicy != .prohibited ||
|
||||
// Bug in CopyQ; see https://github.com/hluk/CopyQ/issues/1330
|
||||
app.bundleIdentifier == "io.github.hluk.CopyQ" ||
|
||||
// Bug in Parsec https://github.com/lwouis/alt-tab-macos/issues/206#issuecomment-609828033
|
||||
app.bundleIdentifier == "tv.parsec.www" ||
|
||||
// Bug in Octave.app; see https://github.com/octave-app/octave-app/issues/193#issuecomment-603648857
|
||||
app.localizedName == "octave-gui") &&
|
||||
// bug in Octave.app; see https://github.com/octave-app/octave-app/issues/193
|
||||
app.bundleIdentifier != "org.octave-app.Octave"
|
||||
let bundlePackageType = app.bundleURL.flatMap { Bundle(url: $0) }.flatMap { $0.infoDictionary }.flatMap { $0["CFBundlePackageType"] as? String }
|
||||
return bundlePackageType != "XPC!"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,24 @@
|
||||
import Cocoa
|
||||
|
||||
class WorkspaceEvents: NSObject {
|
||||
private static var appsObserver = WorkspaceEvents()
|
||||
class WorkspaceEvents {
|
||||
private static var appsObserver: NSKeyValueObservation!
|
||||
private static var previousValueOfRunningApps: Set<NSRunningApplication>!
|
||||
|
||||
static func observeRunningApplications() {
|
||||
NSWorkspace.shared.addObserver(appsObserver, forKeyPath: "runningApplications", options: [.old, .new], context: nil)
|
||||
previousValueOfRunningApps = Set(NSWorkspace.shared.runningApplications)
|
||||
appsObserver = NSWorkspace.shared.observe(\.runningApplications, options: [.old, .new], changeHandler: observerCallback)
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
|
||||
let type = NSKeyValueChange(rawValue: change![.kindKey]! as! UInt)
|
||||
if type == .insertion {
|
||||
let apps = change![.newKey] as! [NSRunningApplication]
|
||||
debugPrint("OS event", "apps launched", apps.map { ($0.processIdentifier, $0.bundleIdentifier) })
|
||||
Applications.addRunningApplications(apps)
|
||||
} else if type == .removal {
|
||||
let apps = change![.oldKey] as! [NSRunningApplication]
|
||||
debugPrint("OS event", "apps quit", apps.map { ($0.processIdentifier, $0.bundleIdentifier) })
|
||||
Applications.removeRunningApplications(apps)
|
||||
|
||||
static func observerCallback<A>(_ application: NSWorkspace, _ change: NSKeyValueObservedChange<A>) {
|
||||
let workspaceApps = Set(NSWorkspace.shared.runningApplications)
|
||||
let diff = Array(workspaceApps.symmetricDifference(previousValueOfRunningApps))
|
||||
if change.kind == .insertion {
|
||||
debugPrint("OS event", "apps launched", diff.map { ($0.processIdentifier, $0.bundleIdentifier) })
|
||||
Applications.addRunningApplications(diff)
|
||||
} else if change.kind == .removal {
|
||||
debugPrint("OS event", "apps quit", diff.map { ($0.processIdentifier, $0.bundleIdentifier) })
|
||||
Applications.removeRunningApplications(diff)
|
||||
}
|
||||
previousValueOfRunningApps = workspaceApps
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user