How to Cast from Cftyperef to Axuielement in Swift

how to cast from CFTypeRef to AXUIElement in Swift

This code worked as of XCode 6.1 and Swift 1.1.

However, it's 3 years later now and Swift has gotten a lot better. Still, this is still a top result when you search for how to work with the Accessibility API from Swift. So I'm back to update with the current simplest way I know:

func AXUIWindowArray(processIdentifier pid:pid_t) -> [AXUIElement] {
var result = [AXUIElement]()
var windowList: AnyObject? = nil // [AXUIElement]

let appRef = AXUIElementCreateApplication(pid)
if AXUIElementCopyAttributeValue(appRef, "AXWindows" as CFString, &windowList) == .success {
result = windowList as! [AXUIElement]
}
return result
}

Accessing Objective-C Pointers in Swift

If you pass an UnsafeMutablePointer<Optional<AnyObject>> as the last
argument to AXUIElementCopyAttributeValue() then you must
initialize it by allocating (and ultimately releasing) memory:

var resultPtr: UnsafeMutablePointer<Optional<AnyObject>> = UnsafeMutablePointer.allocate(capacity: 1)
resultPtr.initialize(to: nil)

let result = AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, resultPtr)
// ...

resultPtr.deinitialize()
resultPtr.deallocate(capacity: 1)

It is easier
to pass the address of an Optional<AnyObject> variable
with &. Then conditionally
cast the received object to the expected type, in this case an
array of AXUIElement:

var value: AnyObject?
let result = AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, &value)
if result == .success, let windowList = value as? [AXUIElement] {
// use `windowList`
}

and similarly:

if let window = windowList.first {
var value: AnyObject?
let result = AXUIElementCopyAttributeValue(window, kAXRoleAttribute as CFString, &value)
if result == .success, let role = value as? String {
// use `role` ...
}
}

One could define a generic utility function which encapsulates
all the casting:

func axUICopyAttributeValue<T>(of element: AXUIElement, attribute: String, as type: T.Type) -> T? {
var value: AnyObject?
let result = AXUIElementCopyAttributeValue(element, attribute as CFString, &value)
if result == .success, let typedValue = value as? T {
return typedValue
}
return nil
}

Example usage:

if let windowList = axUICopyAttributeValue(of: appRef, attribute: kAXWindowsAttribute, as:[AXUIElement].self) {

for window in windowList {
if let role = axUICopyAttributeValue(of: window, attribute: kAXRoleAttribute, as: String.self) {

// ...
}
}
}

How to properly use CFStringGetCString in swift?

Why are you torturing yourself with CFStringGetCString? If you have a CFString in Swift, you can cast it to a String and get a C string from that:

let cString: [Int8] = (cfString as String).cString(using: .utf8)!

Note also that the value of the kAXFocusedApplicationAttribute is not a CFString. It is an AXUIElement.

Here's my playground test:

import Foundation
import CoreFoundation

let axSystem = AXUIElementCreateSystemWide()
var cfValue: CFTypeRef?
AXUIElementCopyAttributeValue(axSystem, kAXFocusedApplicationAttribute as CFString, &cfValue)
if let cfValue = cfValue, CFGetTypeID(cfValue) == AXUIElementGetTypeID() {
let axFocusedApplication = cfValue
print(axFocusedApplication)
}

The first time I executed this playground, I got a system dialog box telling me that I need to give Xcode permission to control my computer. I went to System Preferences > Security & Privacy > Privacy > Accessibility, found Xcode at the bottom of the list, and turned on its checkbox.

Here's the output of the playground:

<AXUIElement Application 0x7fb2d60001c0> {pid=30253}

I assume you're on macOS since the AXUI API is only available on macOS. If you just want the name of the front application as a string, you can do this:

if let frontAppName = NSWorkspace.shared.frontmostApplication?.localizedName {
print(frontAppName)
}

Unwrapping issue with AXUIElementCopyAttributeValue in Mac OS

You have to assign the pointer to the variable with the in-out operator &

var position : CFTypeRef?
let res : AXError = AXUIElementCopyAttributeValue(windowAccessibilityElem,
kAXPositionAttribute as CFString,
&position)
  • res contains the error on failure.
  • position contains the position on success.

In the documentation an in-out parameter is indicated by On return, ...

Swift error while downcasting 'Any'

SecIdentity is “an abstract Core Foundation-type object representing an identity, ” and the type of Core Foundation types can be
checked with CFGetTypeID(). So you can check the type ID first. If it matches the type ID of an
SecIdentity then the forced cast is safe:

guard let cfIdentity = firstItem[kSecImportItemIdentity as String] as CFTypeRef?,
CFGetTypeID(cfIdentity) == SecIdentityGetTypeID() else {
throw AnError()
}
let identity = cfIdentity as! SecIdentity

See also the bug report SR-7015 The CoreFoundation conditional downcast diagnostic is not as helpful as it should be:

The diagnostic should be updated with a message that informs the developer to compare CFTypeIds (with a fixit if possible).

Swift - Get file path of currently opened document in another application

Thanks to the help of another post from @JamesWaldrop, I was able to answer this myself and wanted to post here for anyone looking for something similar:

let proToolsBundleIdentifier = "com.avid.ProTools"
let proToolsApp : NSRunningApplication? = NSRunningApplication
.runningApplications(withBundleIdentifier: proToolsBundleIdentifier).last as NSRunningApplication?

if let pid = proToolsApp?.processIdentifier {

var result = [AXUIElement]()
var windowList: AnyObject? = nil // [AXUIElement]

let appRef = AXUIElementCreateApplication(pid)
if AXUIElementCopyAttributeValue(appRef, "AXWindows" as CFString, &windowList) == .success {
result = windowList as! [AXUIElement]
}

var docRef: AnyObject? = nil
if AXUIElementCopyAttributeValue(result.first!, "AXDocument" as CFString, &docRef) == .success {
let result = docRef as! AXUIElement
print("Found Document: \(result)")
let filePath = result as! String
print(filePath)
}
}

This gets the AXDocument just like the AppleScript does. Would still be open to other methods of doing this that may be better or not using Accessibility.

Working with ObjC Functions with UnsafeMutableRawPointer or UnsafeMutablePointer Parameter from Swift?

AXUIElementCopyAttributeNames does not take a pointer to
an unmanaged CFArray anymore:

let element: AXUIElement = ...

var cfArray: CFArray?
let error = AXUIElementCopyAttributeNames(element, &cfArray)
if error == .success, let names = cfArray as? [String] {
// names is [String] array ...
}

cast of Objective-C pointer type 'NSString *' to C pointer type 'CFStringRef' (aka 'const struct __CFString *') requires a bridged cast

Have a look at the ARC documentation on the LLVM website. You'll have to use __bridge or one of the other keywords.

This is because Core Foundation objects (CF*Refs) are not controlled by ARC, only Obj-C objects are. So when you convert between them, you have to tell ARC about the object's ownership so it can properly clean them up. The simplest case is a __bridge cast, for which ARC will not do any extra work (it assumes you handle the object's memory yourself).



Related Topics



Leave a reply



Submit