Shaun Lebron
Shaun Lebron

Reputation: 2541

List all window names in Swift

I’m learning Swift. How do I fix the following code to list the window names?

import CoreGraphics

let windows = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, kCGNullWindowID)
for i in 0..<CFArrayGetCount(windows) {
  if let window = CFArrayGetValueAtIndex(windows, i) {
    print(CFDictionaryGetValue(window, kCGWindowName))
  }
}

The error:

main.swift:6:32: error: cannot convert value of type 'UnsafeRawPointer' to expected argument type 'CFDictionary?'
    print(CFDictionaryGetValue(window, kCGWindowName))
                               ^~~~~~
                                      as! CFDictionary

Upvotes: 0

Views: 1916

Answers (2)

Cristik
Cristik

Reputation: 32904

You can use unsafeBitCast(_:to:) to convert the opaque raw pointer to a CFDictionary. Note that you'll also need to convert the second parameter, to a raw pointer:

CFDictionaryGetValue(unsafeBitCast(window, to: CFDictionary.self), unsafeBitCast(kCGWindowName, to: UnsafeRawPointer.self))

unsafeBitCast(_:to:) tells the compiler to treat that variable as another type, however it's not very safe (thus the unsafe prefix), recommending to read the documentation for more details, especially the following note:

Warning

Calling this function breaks the guarantees of the Swift type system; use with extreme care.

In your particular case there should not be any problems using the function, since you're working with the appropriate types, as declared in the documentation of the Foundation functions you're calling.

Complete, workable code could look something like this:

import CoreGraphics

let windows = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, kCGNullWindowID)
for i in 0..<CFArrayGetCount(windows) {
    let windowDict = unsafeBitCast(CFArrayGetValueAtIndex(windows, i), to: CFDictionary.self)
    let rawWindowNameKey = unsafeBitCast(kCGWindowName, to: UnsafeRawPointer.self)
    let rawWindowName = CFDictionaryGetValue(windowDict, rawWindowNameKey)
    let windowName = unsafeBitCast(rawWindowName, to: CFString?.self) as String?
    print(windowName ?? "")
}

Update You can bring the CoreFoundation array sooner to the Swift world by casting right from the start:

let windows = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, kCGNullWindowID) as? [[AnyHashable: Any]]
windows?.forEach { window in
    print(window[kCGWindowName])
}

The code is much readable, however it might pose performance problems, as the cast to [[AnyHashable: Any]]` can be expensive for large array consisting of large dictionaries.

Upvotes: 1

Martin R
Martin R

Reputation: 540075

It becomes easier if you avoid using the Core Foundation types and methods, and bridge the values to native Swift types as early as possible.

Here, CGWindowListCopyWindowInfo() returns an optional CFArray of CFDictionaries, and that can be bridged to the corresponding Swift type [[String : Any]]. Then you can access its values with the usual Swift methods (array enumeration and dictionary subscripting):

if let windowInfo = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID) as? [[ String : Any]] {
    for windowDict in windowInfo {
        if let windowName = windowDict[kCGWindowName as String] as? String {
            print(windowName)
        }
    }
}

Upvotes: 5

Related Questions