Brett Bauman Brett Bauman - 4 months ago 42
Swift Question

close window based on kCGWindowName value

but I'm not new to programming. I'm trying to have an always running menubar app listen for a specific window in an application to be opened and then close that window, but not the application. Here is my current approach, it is mostly a mix of other SO answers I found.

func applicationDidFinishLaunching(aNotification: NSNotification) {
// listener for when user switches applications
NSWorkspace.sharedWorkspace().notificationCenter.addObserver(self,
selector: #selector(activated),
name: NSWorkspaceDidActivateApplicationNotification,
object: nil)
}

func activated(notification: NSNotification) {

// i feel like i should be using that NSNotification but I'm not

let options = CGWindowListOption(arrayLiteral: CGWindowListOption.ExcludeDesktopElements, CGWindowListOption.OptionOnScreenOnly)
let windowListInfo = CGWindowListCopyWindowInfo(options, CGWindowID(0))
let infoList = windowListInfo as NSArray? as? [[String: AnyObject]]

for item in infoList! {

// if i find the window i want to close
if item[kCGWindowName as String] == "Warnings" {
// close window item[kCGWindowName]
}

}


My question is, if I am even doing any of this right, how do I close the window based off of having its windowListInfo?

Also, the
item[kCGWindowName as String]
is saying
Cannot convert value of type 'AnyObject?' to expected argument type String.
How would I go about checking windows for the name I want without casting to a String? I have called print() on all objects in infoList and have seen a kCGWindowName value with the name of the window I am looking for, so I know its there.

Thanks in advance.

Answer

You can use the accessibility API:

  • You need to enable accessibility for your application or Xcode (if you test your application from Xcode).
  • Open the "Security & Privacy" pane on the "System Preferences" window. Click the "Privacy" tab.

  • Select "Accessibility" from the list in the left pane.

  • Click the lock icon to make changes.

  • Drag/drop your application or Xcode in the right pane.


Here's an example of the swift code (Swift version 2.2) to simulate a click on the red button of the window whose the title start with "Warnings":

func activated(notification: NSNotification) {
    if (notification.userInfo![NSWorkspaceApplicationKey]!.localizedName) == "Pages" { // when "Pages" application is active only
        let myApp = AXUIElementCreateApplication(notification.userInfo![NSWorkspaceApplicationKey]!.processIdentifier).takeUnretainedValue()
        let val1 = UnsafeMutablePointer<AnyObject?>.alloc(1)

        if AXUIElementCopyAttributeValue(myApp, kAXWindowsAttribute, val1).rawValue == 0 { // get windows of the "Pages" application
            let windowList = val1.memory as? [AXUIElement] ?? []
            for w in windowList { // loop each window, get the title of the window, and check if the title starts with "Warning"
                if AXUIElementCopyAttributeValue(w, kAXTitleAttribute, val1).rawValue == 0 && (val1.memory as! String).hasPrefix("Warnings") {
                    if AXUIElementCopyAttributeValue(w, kAXCloseButtonAttribute, val1).rawValue == 0 {// get the red button of the window
                        AXUIElementPerformAction(val1.memory as! AXUIElement, kAXPressAction); // close the window
                    }
                }
            }
        }
        val1.dealloc(1)
    }
}

To check the title equal "Warnings": use val1.memory as! String == "Warnings"


Informations on the infoList from your code:

How would I go about checking windows for the name I want without casting to a String?

Use a string instead of the constant, like this:

 if item["kCGWindowName"]! as! String == "Warnings" {