2

I have this Objective-C Code fragment, which I want to express in Swift

CFArrayRef windowList;
AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute, (CFTypeRef *)&windowList);

if ((!windowList) || CFArrayGetCount(windowList)<1)
        continue;

AXUIElementRef windowRef = (AXUIElementRef) CFArrayGetValueAtIndex( windowList, 0);
CFTypeRef role;
AXUIElementCopyAttributeValue(windowRef, kAXRoleAttribute, (CFTypeRef *)&role);         

The first thing I´m not sure about: Who allocates the memory behind the windowListPointer. I tried with this fragment:

var windowListPointer : UnsafeMutablePointer<Optional<AnyObject>>
AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, windowListPointer );

But that does not even compile: It complains, the windowListPointer is not initialised. What Object I could create, to let the WindowListPointer point to?

mica
  • 3,898
  • 4
  • 34
  • 62
  • 1
    Does this help https://stackoverflow.com/questions/25124002/how-to-handle-unsafepointerunmanagedcfarray ? – Martin R Nov 25 '17 at 14:53
  • @MartinR TNT, As in the example `var windowList : CFArray?` `AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, &windowList )` does not compile too: Cannot pass immutable value of type 'CFTypeRef?' (aka 'Optional') as inout – mica Nov 25 '17 at 15:10

2 Answers2

3

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) {

            // ...
        }
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks! Two Questions, 1) where can I find, that I have to cast to `[AXUIElement]` 2) `var value: AnyObject? ` Does that declaration allocate any memory for an Object or just a reference to AnyObject (or nil) – mica Nov 25 '17 at 15:43
  • 1
    1) https://developer.apple.com/documentation/applicationservices/kaxwindowsattribute states that the values is "an array of accessibility objects". – 2) Optional variables are implicitly initialized to `nil`. No memory is allocated there. – Martin R Nov 25 '17 at 15:47
  • Compiles fine now, but returns `AXError` . Got the other parameters as in this post: https://stackoverflow.com/questions/47480873/set-the-size-and-position-of-all-windows-on-the-screen-in-swift – mica Nov 25 '17 at 15:54
  • The row value of the AXError is kAXErrorCannotComplete = -25204, Doku says: A fundamental error has occurred, such as a failure to allocate memory during processing. Could it be possible, that I have to allocate anything for `var value: AnyObject?`? – mica Nov 25 '17 at 16:00
  • 2
    Sorry, I have no idea. I tested it with a reference to the current application `let appRef = AXUIElementCreateApplication(getpid())`, and that worked. – Martin R Nov 25 '17 at 16:12
  • The problem was the sandbox. I didn't know, it had to be switched off to "controlling" other apps. – mica Nov 25 '17 at 18:53
1

CFArray is the Foundation C version of NSArray (since C doesn't understand Objective C NSObjects). Swift papers over both NSArray and CFArray for you so you don't need to use a pointer; you should just be able to cast it to a Swift array of the appropriate type with as?

Josh Homann
  • 15,933
  • 3
  • 30
  • 33
  • What Swift Type would you use for windowList in `AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, windowList )' – mica Nov 25 '17 at 15:20