Using Swift (or objective-C) I want to get the list of apps currently running on the macOS, in order of recent usage. That would be the order in which cmd-tab
shows app icons on the Mac.
The following gives me the apps, but not in the order I want.
let apps = NSWorkspace.shared.runningApplications
In the documentation for the .runningApps property
, it says:
The order of the array is unspecified, but it is stable, meaning that the relative order of particular apps will not change across multiple calls to runningApplications.
Is there a way to sort/get the apps in the desired order?
Edit:
The approach suggested by in the answer by 'brimstone' seems promising, but CGWindowListCopyWindowInfo
returns windows in the front-to-back order only when CGWindowListOption.optionOnScreenOnly
is specified. In that case, however, only windows in the current space are returned.
cmd-tab
however is able to list apps across all spaces.
Does anyone know of any other way? Shouldn't there be a more direct/easier way to do this?
So I was looking at the cmd-tab, and I thought one way to mimic that behavior would be through window hierarchy. CGWindowListOption
will return a list of windows on the screen in order of hierarchy - therefore the most recent applications will be first. This would solve your problem of ordering your runningApps
property.
let options = CGWindowListOption(arrayLiteral: CGWindowListOption.excludeDesktopElements, CGWindowListOption.optionOnScreenOnly)
let windowList = CGWindowListCopyWindowInfo(options, CGWindowID(0))
let windows = windowList as NSArray? as! [[String: AnyObject]]
Then you can loop through the infoList
and retrieve any data you want, for example the names of every application.
for window in windows {
let name = window[kCGWindowOwnerName as String]!
print(name)
}
If you still want the NSRunningApplication
variable though, you can match the window's Owner PID to the application's PID.
let id = pid_t(window[kCGWindowOwnerPID as String]! as! Int)
let app = apps.filter { $0.processIdentifier == id } .first
app.hide() //Or whatever your desired action is
For me, this returned all the applications I had running in the same order that CMD-Tab showed. However, this method also returned some processes that were items in the menu bar or background processes, such as SystemUIServer
and Spotlight
.
You can subscribe to notification NSWorkspaceDidDeactivateApplicationNotification
from [NSWorkspace sharedWorkspace].notificationCenter
, and put to dictionary bundleId and timestamp, then use it to sort your array. Disadvantage is app doesn't know anything about apps used before it started to listen notifications.
@property (strong) NSMutableDictionary *appActivity;
...
- (instancetype)init {
if (self = [super init]) {
self.appActivity = [NSMutableDictionary dictionary];
[self setupNotifications];
}
return self;
}
- (void)setupNotifications {
NSNotificationCenter *workspaceNotificationCenter = [NSWorkspace sharedWorkspace].notificationCenter;
[workspaceNotificationCenter addObserver:self selector:@selector(deactivateApp:) name:NSWorkspaceDidDeactivateApplicationNotification object:nil];
}
- (void)deactivateApp:(NSNotification *)notification {
NSRunningApplication *app = notification.userInfo[NSWorkspaceApplicationKey];
if (app.bundleIdentifier) {
self.appActivity[app.bundleIdentifier] = @([[NSDate date] timeIntervalSince1970]);
}
}
...
NSArray<NSRunningApplication *> *apps = [[NSWorkspace sharedWorkspace] runningApplications];
NSComparator sortByLastAccess = ^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
NSRunningApplication *item1 = obj1;
NSRunningApplication *item2 = obj2;
NSComparisonResult result = NSOrderedSame;
if (item1.bundleIdentifier && item2.bundleIdentifier) {
NSNumber *ts1 = self.appActivity[item1.bundleIdentifier];
NSNumber *ts2 = self.appActivity[item2.bundleIdentifier];
if (ts1 && ts2) {
result = [ts2 compare:ts1];
} else if (ts1) {
result = NSOrderedAscending;
} else if (ts2) {
result = NSOrderedDescending;
}
}
return result;
};
apps = [apps sortedArrayUsingComparator:sortByLastAccess];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With