How to get a list of running applications sorted by last active time?

Hi,

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?

Maybe this helps a bit: https://stackoverflow.com/questions/46946860/macos-get-running-apps-ordered-by-most-recently-used-first in connection with Monkeybread Xojo plugin - Window class

See: Monkeybread Xojo plugin - CGWindowMBS methods

I can’t test everything right now. Busy here… :slight_smile:
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)
1 Like

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.

1 Like

I know this one! Let me find the code and post it.

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.

2 Likes

With that super helpful information from Eric, I found that the NSWorkspaceMBS plugin example project shows how to do this with MBS (as an option) :slight_smile:

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.

Thanks everyone!