Question

I'm trying to use some Apple Events in my Cocoa project to get access to the Terminal application. I did not want to use embedded AppleScripts or any compiled scripts, so I started looking into the NSAppleEventDescriptor.

I have succeeded in creating a new window using the do script command, and I have succeeded to get the window from the return value. The only thing I want to do right now is get a property of that window.

I had now idea how to get such a property, so I started googling. Unfortunately, there aren't a lot of good examples how to use Apple Events, and I failed to find what I was looking for.

So I started digging deeper. And the first thing I did was looking for the code for the get command in the Terminal .sdef file. I failed to find it, so I did a grep on my /System/ directory and I found /System/Library/Frameworks/AppleScriptKit.framework/Versions/A/Resources/AppleScriptKit.sdef. I apparently found the core of the AppleScript syntax. That file did indeed have the getd four character code.

So now I know I have to use the getd event from the Core Suite. However, the argument to that get-command was an objectSpecifier. I have searched high and low for an example that uses kAEGetData. But I have failed to find any code from which I could learn anything.

So I am asking here: how do I build such an objectSpecifier descriptor?

This is what I've already got:

Code to create and get the tab descriptor

NSAppleEventDescriptor *createEvent;
NSAppleEventDescriptor *scriptParam;
AppleEvent aeReply;
OSErr err;

/* Make the do script event */
createEvent = [NSAppleEventDescriptor 
               appleEventWithEventClass:kAECoreSuite 
               eventID:kAEDoScript 
               targetDescriptor:_applicationDescriptor 
               returnID:kAutoGenerateReturnID 
               transactionID:kAnyTransactionID];
if(createEvent == nil) {
    NSLog(@"%s Failed to create a do script event",
          __PRETTY_FUNCTION__);
    return nil;
}

/* Make script parameter */
scriptParam = [NSAppleEventDescriptor descriptorWithString:@"echo Hello World"];
if(scriptParam == nil) {
    NSLog(@"%s Failed to create the script parameter",
          __PRETTY_FUNCTION__);
    return nil;
}

/* Set parameter */
[createEvent setParamDescriptor:scriptParam forKeyword:keyDirectObject];

/* Send event */
err = AESendMessage([createEvent aeDesc], &aeReply, 
                    kAEWaitReply | kAENeverInteract, kAEDefaultTimeout);
if(err != noErr) {
    NSLog(@"%s Failed to send the create command", 
          __PRETTY_FUNCTION__);
    return nil;
}

/* Retrieve information */
{
    /* SEE BELOW TO SEE HOW I GET THE WINDOW DESCRIPTOR */
}

Now I try and succeed in getting the window of that tab

   // NSAppleEventDescriptor *ansr is set to the result of the code above

NSAppleEventDescriptor *mainObj;
NSAppleEventDescriptor *desiredClass;
NSAppleEventDescriptor *window;

mainObj = [ansr paramDescriptorForKeyword:keyDirectObject];
if([mainObj descriptorType] != typeObjectSpecifier) {
    NSLog(@"%s Main object was not an object specifier",
          __PRETTY_FUNCTION__);
    return nil;
}

desiredClass = [mainObj paramDescriptorForKeyword:keyAEDesiredClass];
if([desiredClass typeCodeValue] != kAETerminalTab) {
    NSLog(@"%s Main object's desired class was not a Terminal tab",
          __PRETTY_FUNCTION__);
    return nil;
}

window = [mainObj paramDescriptorForKeyword:keyAEContainer];
if(window == nil) {
    NSLog(@"%s Couldn't get container of the tab",
          __PRETTY_FUNCTION__);
    return nil;
}

desiredClass = [window paramDescriptorForKeyword:keyAEDesiredClass];
if([desiredClass typeCodeValue] != cWindow) {
    NSLog(@"%s The container of the tab was not a window",
          __PRETTY_FUNCTION__);
    return nil;
}

return window;

And now I fail in getting, let's say, the bounds property

// _windowDescriptor is the result of the code above

NSAppleEventDescriptor *getEvent;
NSAppleEventDescriptor *prop;
AppleEvent aeReply;
NSAppleEventDescriptor *reply;
FourCharCode propName;
OSErr err;

propName = keyAEBounds;

/* Create get event */
getEvent = [NSAppleEventDescriptor
            appleEventWithEventClass:kAECoreSuite 
            eventID:kAEGetData 
            targetDescriptor:_windowDescriptor 
            returnID:kAutoGenerateReturnID 
            transactionID:kAnyTransactionID];
if(getEvent == nil) {
    NSLog(@"%s Failed to create a get event",
          __PRETTY_FUNCTION__);
    return NSZeroRect;
}

/* Get property */
prop = [NSAppleEventDescriptor
        descriptorWithDescriptorType:typeProperty 
        bytes:&propName length:sizeof(propName)];
if(prop == nil) {
    NSLog(@"%s Failed to create the bounds property",
          __PRETTY_FUNCTION__);
    return;
}

/* Set parameter */
[getEvent setParamDescriptor:prop forKeyword:keyDirectObject];

/* Exectue */
err = AESendMessage([getEvent aeDesc], &aeReply, 
                    kAEWaitReply | kAENeverInteract, kAEDefaultTimeout);
if(err != noErr) {
    NSLog(@"%s Failed to send the get message",
          __PRETTY_FUNCTION__);
    return;
}

reply = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&aeReply];
[reply autorelease];

NSLog(@"Bounds: %@", reply);

As explained, the above code works, just until the last block.
Thank you in advance for the help.

Was it helpful?

Solution

Thanks to Rob Keniger I've succeeded in what I wanted.

Apparently I had to create a record descriptor, set my wanted properties and, then coerce it to a typeObjectSpecifier.

Also, I was wrong in setting the window descriptor as a the receiver of my Apple Event. You always have to address the application itself, and set the from (keyAEContainer) property of the direct object to the window you want.

Here is the working code, with a little bit of NSLog-statements:

- (NSRect)bounds {

    // ! ! !
    // _windowDescriptor is an instance variable which points to a valid
    // window NSAppleEventDescriptor
    // ! ! !

    NSAppleEventDescriptor *getEvent;
    NSAppleEventDescriptor *objSpec;
    NSAppleEventDescriptor *propEnum, *propType, *propSeld;
    AppleEvent aeReply;
    NSAppleEventDescriptor *reply;
    FourCharCode propName;
    OSErr err;

    propName = keyAEBounds;

    /* Create get event */
    getEvent = [NSAppleEventDescriptor
                appleEventWithEventClass:kAECoreSuite 
                eventID:kAEGetData 
                targetDescriptor:[[FTMTerminalApp sharedApp] AEDescriptor] 
                returnID:kAutoGenerateReturnID 
                transactionID:kAnyTransactionID];
    if(getEvent == nil) {
        NSLog(@"%s Failed to create a get event",
              __PRETTY_FUNCTION__);
        return NSZeroRect;
    }

    /* Get property */
    /* create object specifier main ojcect */
    objSpec = [[[NSAppleEventDescriptor alloc] initRecordDescriptor] 
               autorelease];
    if(objSpec == nil) {
        NSLog(@"%s Failed to create the object specifier",
              __PRETTY_FUNCTION__);
        return NSZeroRect;
    }
    NSLog(@"%s Created object specifier %@",
          __PRETTY_FUNCTION__, objSpec);

    /* create property enum, we want a property */
    propEnum = [NSAppleEventDescriptor
                descriptorWithEnumCode:formPropertyID];
    if(propEnum == nil) {
        NSLog(@"%s Failed to create the property enum",
              __PRETTY_FUNCTION__);
        return NSZeroRect;
    }
    NSLog(@"%s Created property enum %@",
          __PRETTY_FUNCTION__, propEnum);
    [objSpec setDescriptor:propEnum forKeyword:keyAEKeyForm];

    /* create prop type */
    propType = [NSAppleEventDescriptor
                descriptorWithTypeCode:typeProperty];
    if(propType == nil) {
        NSLog(@"%s Failed to create the property type",
              __PRETTY_FUNCTION__);
        return NSZeroRect;
    }
    NSLog(@"%s Created property type %@",
          __PRETTY_FUNCTION__, propType);
    [objSpec setDescriptor:propType forKeyword:keyAEDesiredClass];

    /* create property key data */
    propSeld = [NSAppleEventDescriptor
                descriptorWithTypeCode:keyAEBounds];
    if(propSeld == nil) {
        NSLog(@"%s Failed to create the bounds property type",
              __PRETTY_FUNCTION__);
        return NSZeroRect;
    }
    NSLog(@"%s Created property key data %@",
          __PRETTY_FUNCTION__, propSeld);
    [objSpec setDescriptor:propSeld forKeyword:keyAEKeyData];

    /* Set destination */
    NSLog(@"%s Setting from key %@",
          __PRETTY_FUNCTION__, _windowDescriptor);
    [objSpec setDescriptor:_windowDescriptor forKeyword:keyAEContainer];

    /* Send message */
    objSpec = [objSpec coerceToDescriptorType:typeObjectSpecifier];
    NSLog(@"Coerced: %@", objSpec);
    [getEvent setParamDescriptor:objSpec forKeyword:keyDirectObject];
    err = AESendMessage([getEvent aeDesc], &aeReply, 
                        kAEWaitReply | kAENeverInteract, kAEDefaultTimeout);
    if(err != noErr) {
        NSLog(@"%s Failed to send the message (event = %@)",
              __PRETTY_FUNCTION__, getEvent);
        return NSZeroRect;
    }

    reply = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&aeReply];
    NSLog(@"BOUNDS = %@", reply);
    [reply autorelease];

    return NSZeroRect;
}

I hope this will help someone.

OTHER TIPS

Apple Events are complicated to use. Unless you really want to spend the time working through the convoluted and generally nasty API, I recommend that you save yourself a lot of time and heartache by using Mike Ash's excellent AEVTBuilder class.

It's a nice Cocoa wrapper for all the nasty Carbon Apple Event code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top