I have a need to get the list of running applications in the order they were used last. E.g. if the Finder and Safari are already running, then I go to the Finder to launch Xojo and go back to Safari, the list would be: Safari, Xojo, Finder; that’s not sorted by launch time.
The application switcher (Command-tab) reflects exactly this; I bet it doesn’t record each app since the Mac has started, so how does it do that?
Anyone tried it in Xojo already?
I can’t test everything right now. Busy here…
I hope this helps you to get startet:
Var a(-1) As Dictionary = CGWindowMBS.GetWindowListInfo(0,0)
Var u As Integer = a.LastIndex
Var lines(-1) As String
For i As Integer = 0 To u
Var d As Dictionary = a(i)
#Pragma BreakOnExceptions False
Try
If d.Value(CGWindowMBS.kCGWindowName) <> "" Then
lines.Add d.Value(CGWindowMBS.kCGWindowName) + " von " + d.Value(CGWindowMBS.kCGWindowOwnerName)
End If
Catch err As KeyNotFoundException
End Try
#Pragma BreakOnExceptions True
Next
MessageBox Join(Lines, EndOfLine)
Thanks for your help.
Currently, this doesn’t work. I’m getting only “StatusIndicator (Window Server)”, “Menubar (Window Server)”, my app and “Wallpaper (Dock)” for the windows that have a non-empty name. Also, while I can see other applications using kCGWindowOwnerName (even for all the windows whose kCGWindowName returns an empty value), the list doesn’t seem ordered logically.
I’ll keep trying inspecting the list, but hopes aren’t high in this way.
For now, I’m using another trick, not ideal either: when my helper app launches, it hides itself (like the “Hide MyApp” menu item from the app menu), wait 100 milliseconds, query the now-frontmost app, and resume starting. It has to be done using Timer.CallLater, otherwise the app won’t hide before querying the other app (hiding requires an event loop cycle).
It’s limiting, since I’d like to do it on the main app, but I can’t hide a UI app that way, for obvious reasons.
I did a little research on this and I’m guessing that Finder subscribes to a macOS notification that fires whenever an application comes to the foreground and adds or reorders items to the top to a stack. Otherwise there doesn’t appear to be a way to get this list without accessing a private API.
Right. I’m using macOSLib to get access to the NSWorkspace object and its events. The event you want is NSWorkspaceDidActivateApplicationNotification, which you will receive every time an application is brought to the front. Receiving notifications like this is much more reliable than a Timer and will use fewer resources.
Using macOSLib: Create a subclass of the NSWorkspace object and add an Event Handler for NSWorkspaceDidActivateApplicationNotification. You’ll receive an instance of NSRunningApplication, which contains information like the app’s bundle ID which you can use to get more information about the app, like its name. You can then maintain a list of your own of the apps and the order in which they were activated.
If you don’t want to use macOSLib – which, though outdated in some areas, is still very useful – I still suggest you download it and take a look at how it manages these notifications. The full explanation with code is too involved for a single post.
Thank you for researching, Greg.
The Finder has a great advantage, though: it launches in the beginning of the session and won’t ordinarily quit, so it can monitor everything.
My app is already a launch agent, so launched at log-in time, but it can easily be quit by the user (it has a window, a dock icon and a menu bar).
Don’t get me wrong: I appreciate your answer (and the others too); I’m just afraid that if the method isn’t reliable, my app will query the wrong app, unexpectedly for the user.
Thank you, Eric, I appreciate your answer.
I’m somehow familiar with these events, using the MBS plugin.
If your app is launched lately (e.g. quit and relaunched), how do you use that method to know the z-order early (e.g. while the app launches)? Would you use a helper that never quits (and starts at login)? That seems overkill to me.
So why don’t you launch another headless daemon whose job it is to keep track of this info for you and transmit it to your main app on demand over IPC.
Originally, I had hoped for a direct API to query that list (the most reliable and quick way). Since that there doesn’t seem to be such a way, your solution is indeed the best available choice. I just thought perhaps it was overkill to make an app just for that.