swiftcfstring

How to properly use CFStringGetCString in swift?


I am looking at the docs for CFStringGetCString and AXUIElementCopyAttributeValue.

CFStringGetCString takes the param buffer: UnsafeMutablePointer<Int8>! AXUIElementCopyAttributeValue takes the param value: UnsafeMutablePointer<CFTypeRef?>

For the latter, I can do a call like this:

var value: CFTypeRef?
let err = AXUIElementCopyAttributeValue(element, attribute as CFString, &value);

This satisfies the doc asking for an UnsafeMutablePointer of type CFTypeRef.

However I can't get the same logic to apply by doing

let buffer: Int8!
CFStringGetCString(attribute as! CFString, &buffer, 2048, CFStringBuiltInEncodings.UTF8.rawValue)

I also tried

let buffer: Int8?
CFStringGetCString(attribute as! CFString, &buffer!, 2048, CFStringBuiltInEncodings.UTF8.rawValue)

Either way, it complains about using buffer before it's initialized, even though it never complained about value in the working method with similar param requirements.

All the working examples I've seen for CFStringGetCString are using objective-c like syntax with *. Not sure what the proper swift way is here.

I also tried this way to get the value I wanted:

let app = AXUIElementCreateSystemWide();
var valueString = "";

var value: CFTypeRef?
// An exception on execution happens here when passing app
// Passing in the element I clicked instead of app
// yields error -25205 (attributeunsupported)
let err = AXUIElementCopyAttributeValue(app, "AXFocusedApplication" as CFString, &value);
if (err == AXError.success) {
    valueString = value! as! NSString as String;
} else {
    print("ERROR!");
    print(err.rawValue);
}

return valueString;

Solution

  • 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)
    }