I've been using this block of code to find a certain active application's window ID for months now:
let info = CGWindowListCopyWindowInfo(CGWindowListOption(kCGWindowListOptionAll), CGWindowID(0)).takeRetainedValue()
for dict in info as! [ [ String : AnyObject ] ] {
if let windowName = dict["kCGWindowName"] as? String{
if(windowName == "MyWindowName"){
let windowID = dict["kCGWindowNumber"] as! Int
println("found window, window number: \(windowID)")
return
}
}
}
As of a recent Swift update, however, the takeRetainedValue()
and its counterpart takeUnretainedValue()
have seemingly been removed. Every post about it online that I can find says just removing the call should leave it working with more or less the same behavior, but when I do, the application always crashes with the lovely "Thread 1: EXC_BAD_INSTRUCTION (code=EXC_i386_INVOP, subcode=0x0)" error on the "for dict in info" line, before the loop can even start.
I've spent hours and hours trying to solve the issue, and I've found a bunch of leads but none of them have taken my anywhere. I've gathered that it's got to do with the removal of takeRetainedValue()
leaving me with an unmanaged CFArray
object, but I'm still learning, and clueless as to where to go from here.
Is there any way to fix the issue I'm encountering, or if not, another approach that I should be using entirely?
There are some funky changes to accessing CoreFoundation objects in Swift 2. You no longer need to take a retained or unretained value from a CFArray
, you can bridge it directly to a Swift array. You're getting a crash because you're trying to cast a CFArray
to a [[String : AnyObject]]
at runtime and it's returning nil
.
CGWindowListCopyWindowInfo
returns CFArray?
(an optional CFArray
). Trying to bridge CFArray?
to [AnyObject]
will fail, but bridging it to an optional Swift array ([AnyObject]?
) will work. But in order to iterate through that array we must unwrap it. Here I check if the CFArray
returned by CGWindowListCopyWindowInfo
can be unwrapped and bridged to [AnyObject]!
:
if let info = CGWindowListCopyWindowInfo(.OptionAll, CGWindowID(0)) as [AnyObject]! {
for dict in info {
if let windowName = dict[kCGWindowName as String] as? String {
if (windowName == "MyWindowName"){
let windowID = dict[kCGWindowNumber as String] as? Int
print("found window, window number: \(windowID)")
break
}
}
}
}
If for whatever reason CGWindowListCopyWindowInfo
returns nil, we won't try to iterate through it.
Also note that the CFString
constants kCGWindowName
and kCGWindowNumber
can be bridged to a Swift String
object no problem. It's better to use the constants than hardcoded strings, as the value of the constant may change over time.