AECreateAppleEvent from Swift issues.

I'm tyring to send an AppleEvent using the C API's from Swift. I know I can use the Foundation types, but I want to learn about Swift / C interop at the same time. If someone could help me understand why this AppleEvent isn't sending and I'm getting a -1701 (No AEDesc Found) error, I'd be extremely grateful. The problem is a) I'm new to C interop and b) I don't know which AEDesc the error is actually referring too and c) if I'm using the C API correctly.


I'm trying to open TextEdit.


        let textEditID = "com.apple.TextEdit"
        let textEditIDC = textEditID.cString(using: .utf8)!
    
        let textEditPtr : UnsafeMutablePointer<[CChar]> = UnsafeMutablePointer<[CChar]>.allocate(capacity: textEditIDC.count)
        textEditPtr.initialize(to: textEditIDC)
          
        let ds : AEDataStorage = AEDataStorage(OpaquePointer(textEditPtr))
        let address : AEAddressDesc = AEAddressDesc(descriptorType: typeApplicationURL, dataHandle: ds)
        let addrPtr = UnsafeMutablePointer<AEAddressDesc>.allocate(capacity: 1)
        addrPtr.initialize(to: address)
                
        let eventPtr : UnsafeMutablePointer<AppleEvent>? = UnsafeMutablePointer<AppleEvent>.allocate(capacity: 1)
        let replyPtr : UnsafeMutablePointer<AppleEvent>? = UnsafeMutablePointer<AppleEvent>.allocate(capacity: 1)
        
        var error = AECreateAppleEvent(kCoreEventClass,
                                           kAEOpenApplication,
                                           addrPtr,
                                           Int16(kAutoGenerateReturnID),
                                           AETransactionID(kAnyTransactionID),
                                           eventPtr)
        
        os_log(.debug, log: .default, "AECreateAppleEvent: %d", error)
        
        var ae = eventPtr?.pointee
        let addrDescPtr : UnsafeMutablePointer<AEDesc> = UnsafeMutablePointer<AEDesc>.allocate(capacity: 1)
        error = AEGetAttributeDesc(eventPtr, keyAddressAttr, typeApplicationBundleID, addrDescPtr)    
        
        os_log(.debug, log: .default, "AEGetAttributeDesc: %d", error)
        
        let status = AESendMessage(eventPtr, replyPtr, Int32(kAEWaitReply), 900)
        os_log(.debug, log: .default, "send event rc: %d", status)
    

Accepted Reply

Try this:

var address = AEDesc.null
var bundleIDBytes = [UInt8]("com.apple.TextEdit".utf8)
var err = OSStatus( AECreateDesc(typeApplicationBundleID, &bundleIDBytes, bundleIDBytes.count, &address) )
assert(err == errSecSuccess)
defer { AEDisposeDesc(&address) }

var reply = AEDesc.null
var event = AEDesc.null

err = OSStatus( AECreateAppleEvent(
    kCoreEventClass,
    kAEOpenApplication,
    &address,
    Int16(kAutoGenerateReturnID),
    AETransactionID(kAnyTransactionID),
    &event
))
assert(err == errSecSuccess)
defer { AEDisposeDesc(&event) }

err = AESendMessage(&event, &reply, Int32(kAEWaitReply), 900)
print(err)
defer { AEDisposeDesc(&reply) }

Assuming the following extension on

AEDesc
:
extension AEDesc {
    static let null = AEDesc(descriptorType: typeNull, dataHandle: nil)
}

Also make sure you have

NSAppleEventsUsageDescription
in your
Info.plist
.

You wrote:

I'm trying to open TextEdit.

This won’t actually open TextEdit; that’s not the purpose of the

kAEOpenApplication
. Rather, that event is to tell TextEdit that it has been opened. Still, if you launch TextEdit in advance, this code will send the event and get the reply.

ps Trying to use the C API for Apple events from Swift is, IMO, pure masochism. That API is a pain to use from C )-:

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Try this:

var address = AEDesc.null
var bundleIDBytes = [UInt8]("com.apple.TextEdit".utf8)
var err = OSStatus( AECreateDesc(typeApplicationBundleID, &bundleIDBytes, bundleIDBytes.count, &address) )
assert(err == errSecSuccess)
defer { AEDisposeDesc(&address) }

var reply = AEDesc.null
var event = AEDesc.null

err = OSStatus( AECreateAppleEvent(
    kCoreEventClass,
    kAEOpenApplication,
    &address,
    Int16(kAutoGenerateReturnID),
    AETransactionID(kAnyTransactionID),
    &event
))
assert(err == errSecSuccess)
defer { AEDisposeDesc(&event) }

err = AESendMessage(&event, &reply, Int32(kAEWaitReply), 900)
print(err)
defer { AEDisposeDesc(&reply) }

Assuming the following extension on

AEDesc
:
extension AEDesc {
    static let null = AEDesc(descriptorType: typeNull, dataHandle: nil)
}

Also make sure you have

NSAppleEventsUsageDescription
in your
Info.plist
.

You wrote:

I'm trying to open TextEdit.

This won’t actually open TextEdit; that’s not the purpose of the

kAEOpenApplication
. Rather, that event is to tell TextEdit that it has been opened. Still, if you launch TextEdit in advance, this code will send the event and get the reply.

ps Trying to use the C API for Apple events from Swift is, IMO, pure masochism. That API is a pain to use from C )-:

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you! That works. It's amazing how much simpler your code is than mine, and how much more familiar I need to be with the APIs to do things correctly. It's interesting how, in some situations, you don't really need to create Swift Pointer instances to work with C APIs.


After posting this discussion I got to thinking about using the AE C APIs. I want to store these event descriptions to disk, and thought it would be better to serialize the AEDesc data structure than the NSAppleEventDescriptors, then I discovered that you can get and set the AEDesc on a NSAppleEventDescriptor.


Working with pointers, even from Swift is going to increase the potential for bugs and intruduce needless complexity so I will take your advice and work with NSAppleDescriptor from now on.


Thanks Eskimo!

NSAppleEventDescriptor is just a thin (and rather dumb) Objective-C class wrapper around an AEDesc struct, and includes methods for getting that struct data in and out. It does not support NSCoding so is not directly serializable, but I’m sure you can figure how to combine the aforementioned methods with AEM’s AEFlattenDesc/AEUnflattenDesc procedures yourself.


And yes, stick to the Foundation classes as Quinn says. Pointer-heavy Swift/C interop is not much fun, and neither is the old Carbon AEM API† in general. It’s more polished and capable than the crude Foundation methods that wrap it, but it’s hella arcane and you’re just making work for yourself when you don’t have to.


Oh, and as far as launching apps goes, you need to use LaunchServices for that (either what’s left of the old C LaunchServices API, or AppKit’s NSWorkspace). You’ll see all that in the SwiftAutomation code (which hauls in AppKit only because the stripped down LS* APIs no longer provide the means to locate/launch an app by file name).


--


† There’s a reason I totally redesigned the AEM API when doing an experimental reimplementation in pure Swift (github.com/hhas/AppleEvents). Much better API than either, though I quit work on it after realizing the Mach bridge uses a completely different serialization format to AEM itself (*after* I’d written it, derp) and wasn’t in a mood to reverse-engineer that too.

> I’m sure you can figure how to combine the aforementioned methods with AEM’s AEFlattenDesc/AEUnflattenDesc procedures yourself.


Thanks for the tip!


> Oh, and as far as launching apps goes, you need to use LaunchServices for that (either what’s left of the old C LaunchServices API, or AppKit’s NSWorkspace).


Did eventually find the NSWorkspace API call.


Managed to have some success creating AE's with NSAppleEventDescriptor today, including building object specifiers, so it should be all hunky dory from now one. :-/