Search code examples
swiftmacosmacos-catalina

Swift: How to activate and unhide window of ANY application?


I see pretty strange behaviour of NSRunningApplicationInstance.activate() method.

Let's imagine that I have found app named "Finder". This app at the moment is:

Not active and/or hidden

I have really simple code:

let activeOptions: NSApplication.ActivationOptions = [.activateAllWindows, .activateIgnoringOtherApps]

print("1. isActive: \(app.isActive); isHidden: \(app.isHidden)")
if (!app.isActive)
{
   app.activate(options: activeOptions)
}

if ( app.isHidden )
{
   app.unhide()
}
print("2. isActive: \(app.isActive); isHidden: \(app.isHidden)")

for finding needed app you can use the following code:

let app = NSWorkspace.shared.runningApplications.filter{ $0.localizedName = "NameOfApp"}

2 times runned code result:

  1. isActive: false; isHidden: false
  2. isActive: false; isHidden: false
  3. isActive: true; isHidden: false
  4. isActive: true; isHidden: false

If you will try it... :

  1. Code is will show me menu of the app: enter image description here

  2. BUT only on second code run! (Why?)

  3. Will not show me app window! (Why?)

And I see similar behaviour with a lot of applications, not only with Finder.

As example SourceTree app.


Can somebody explain the logic and how do display window of the ANY of runned app by some code IN ANY CASE?


Solution

  • Here is working Playground module. The approach is to use KVO for observable properties to be informed when exactly desired state for target application occurs. Hope it would be helpful somehow.

    import Cocoa
    
    class AppActivator: NSObject {
    
        private var application: NSRunningApplication!
        private let filterName: String
    
        init(appName: String) {
            filterName = appName
        }
        
        func activate() {
            guard let app = NSWorkspace.shared.runningApplications.filter ({
                return $0.localizedName == self.filterName || $0.bundleIdentifier?.contains(self.filterName) ?? false
            }).first else {
                print("Application \(self.filterName) not found")
                return
            }
    
            guard app.activationPolicy != .prohibited else {
                print("Application \(self.filterName) prohibits activation")
                return
            }
    
            self.application = app
            
            self.unhideAppIfNeeded()
            self.activateAppIfNeeded()
        }
        
        private func unhideAppIfNeeded() {
            if application.isHidden {
                application.addObserver(self, forKeyPath: "isHidden", options: .new, context: nil)
                application.unhide()
            }
        }
        
        private func activateAppIfNeeded() {
            if !application.isHidden && !application.isActive {
                application.addObserver(self, forKeyPath: "isActive", options: .new, context: nil)
                application.activate(options: .activateIgnoringOtherApps)
            }
        }
        
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            if keyPath == "isHidden" {
                application.removeObserver(self, forKeyPath: "isHidden")
                activateAppIfNeeded()
            } else if keyPath == "isActive" {
                application.removeObserver(self, forKeyPath: "isActive")
                print("Application \(application.localizedName) - ACTIVATED!")
            }
        }
    }
    
    let activator = AppActivator(appName: "Finder")
    activator.activate()