Execute Applescript from Cocoa App with params - cocoa

I would like to know how to execute an applescript from a cocoa application passing parameters.
I have seen how easy is to execute applescripts with no parameters in other questions here at stackoverflow, however the use NSAppleScript class, in which, i haven't seen no method that solve my problem. Does anyone have any idea.
I would like a Cocoa code with the same effect o this shell:
osascript teste.applescript "snow:Users:MyUser:Desktop:MyFolder" "snow:Users:MyUser:Desktop:Example:"
So it may run this AppleScript.
on run argv
set source to (item 1 of argv)
set destiny to (item 2 of argv)
tell application "Finder" to make new alias file at destiny to source
0
end run
Any help is appreciated. Thanks in advance.

Look at my GitHub repository, I have a category of NSAppleEventDescriptor that makes it much easier to create NSAppleEventDescriptor to call different AppleScript procedures with arguments, and coercion to and from many AppleScript typed.
NSAppleEventDescriptor-NDCoercion

I found easier to follow this piece code. I took a code from here and modified it to my purpose.
- (BOOL) executeScriptWithPath:(NSString*)path function:(NSString*)functionName andArguments:(NSArray*)scriptArgumentArray
{
BOOL executionSucceed = NO;
NSAppleScript * appleScript;
NSAppleEventDescriptor * thisApplication, *containerEvent;
NSURL * pathURL = [NSURL fileURLWithPath:path];
NSDictionary * appleScriptCreationError = nil;
appleScript = [[NSAppleScript alloc] initWithContentsOfURL:pathURL error:&appleScriptCreationError];
if (appleScriptCreationError)
{
NSLog([NSString stringWithFormat:#"Could not instantiate applescript %#",appleScriptCreationError]);
}
else
{
if (functionName && [functionName length])
{
/* If we have a functionName (and potentially arguments), we build
* an NSAppleEvent to execute the script. */
//Get a descriptor for ourself
int pid = [[NSProcessInfo processInfo] processIdentifier];
thisApplication = [NSAppleEventDescriptor descriptorWithDescriptorType:typeKernelProcessID
bytes:&pid
length:sizeof(pid)];
//Create the container event
//We need these constants from the Carbon OpenScripting framework, but we don't actually need Carbon.framework...
#define kASAppleScriptSuite 'ascr'
#define kASSubroutineEvent 'psbr'
#define keyASSubroutineName 'snam'
containerEvent = [NSAppleEventDescriptor appleEventWithEventClass:kASAppleScriptSuite
eventID:kASSubroutineEvent
targetDescriptor:thisApplication
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];
//Set the target function
[containerEvent setParamDescriptor:[NSAppleEventDescriptor descriptorWithString:functionName]
forKeyword:keyASSubroutineName];
//Pass arguments - arguments is expecting an NSArray with only NSString objects
if ([scriptArgumentArray count])
{
NSAppleEventDescriptor *arguments = [[NSAppleEventDescriptor alloc] initListDescriptor];
NSString *object;
for (object in scriptArgumentArray) {
[arguments insertDescriptor:[NSAppleEventDescriptor descriptorWithString:object]
atIndex:([arguments numberOfItems] + 1)]; //This +1 seems wrong... but it's not
}
[containerEvent setParamDescriptor:arguments forKeyword:keyDirectObject];
[arguments release];
}
//Execute the event
NSDictionary * executionError = nil;
NSAppleEventDescriptor * result = [appleScript executeAppleEvent:containerEvent error:&executionError];
if (executionError != nil)
{
NSLog([NSString stringWithFormat:#"error while executing script. Error %#",executionError]);
}
else
{
NSLog(#"Script execution has succeed. Result(%#)",result);
executionSucceed = YES;
}
}
else
{
NSDictionary * executionError = nil;
NSAppleEventDescriptor * result = [appleScript executeAndReturnError:&executionError];
if (executionError != nil)
{
NSLog([NSString stringWithFormat:#"error while executing script. Error %#",executionError]);
}
else
{
NSLog(#"Script execution has succeed. Result(%#)",result);
executionSucceed = YES;
}
}
}
[appleScript release];
return executionSucceed;
}

Technical Note TN2084
Using AppleScript Scripts in Cocoa Applications
Even though your application is written in Objective-C using Cocoa, you can use AppleScript scripts to perform certain operations. This Technical Note explains how to integrate and execute AppleScripts from within your Cocoa application. It discusses how to leverage the NSAppleScript class and the use of NSAppleEventDescriptor to send data to the receiver.
https://developer.apple.com/library/archive/technotes/tn2084/_index.html
https://applescriptlibrary.files.wordpress.com/2013/11/technical-note-tn2084-using-applescript-scripts-in-cocoa-applications.pdf
Swift 4 version, modified from the code here:
https://gist.github.com/chbeer/3666e4b7b2e71eb47b15eaae63d4192f
import Carbon
static func runAppleScript(_ url: URL) {
var appleScriptError: NSDictionary? = nil
guard let script = NSAppleScript(contentsOf: url, error: &appleScriptError) else {
return
}
let message = NSAppleEventDescriptor(string: "String parameter")
let parameters = NSAppleEventDescriptor(listDescriptor: ())
parameters.insert(message, at: 1)
var psn = ProcessSerialNumber(highLongOfPSN: UInt32(0), lowLongOfPSN: UInt32(kCurrentProcess))
let target = NSAppleEventDescriptor(descriptorType: typeProcessSerialNumber, bytes: &psn, length: MemoryLayout<ProcessSerialNumber>.size)
let handler = NSAppleEventDescriptor(string: "MyMethodName")
let event = NSAppleEventDescriptor.appleEvent(withEventClass: AEEventClass(kASAppleScriptSuite), eventID: AEEventID(kASSubroutineEvent), targetDescriptor: target, returnID: AEReturnID(kAutoGenerateReturnID), transactionID: AETransactionID(kAnyTransactionID))
event.setParam(handler, forKeyword: AEKeyword(keyASSubroutineName))
event.setParam(parameters, forKeyword: AEKeyword(keyDirectObject))
var executeError: NSDictionary? = nil
script.executeAppleEvent(event, error: &executeError)
if let executeError = executeError {
print("ERROR: \(executeError)")
}
}
For running the apple script:
on MyMethodName(theParameter)
display dialog theParameter
end MyMethodName

I'm not all too familiar with AppleScript, but I seem to remember that they are heavily based on (the rather crappy) Apple Events mechanism which dates back to the days where the 56k Modem was the coolest Gadget in your house.
Therefore I'd guess that you're looking for executeAppleEvent:error: which is part of NSAppleScript. Maybe you can find some information on how to encapsulate execution arguments in the instance of NSAppleEventDescriptor that you have to pass along with this function.

Related

Validating URL from drag and drop in a sandbox

With file access in a sandboxed osx app with swift in mind, does it work the same with URLs provided via Finder or other apps drops?
As there's no NSOpenPanel call to afford folder access as in this example, just urls - I think the folder access is implicit since the user dragged the file from the source / desktop "folder" much the same as implicit selection via the open dialog.
I have not begun the sandbox migration yet but wanted to verify my thinking was accurate, but here's a candidate routine that does not work in sandbox mode:
func performDragOperation(_ sender: NSDraggingInfo!) -> Bool {
let pboard = sender.draggingPasteboard()
let items = pboard.pasteboardItems
if (pboard.types?.contains(NSURLPboardType))! {
for item in items! {
if let urlString = item.string(forType: kUTTypeURL as String) {
self.webViewController.loadURL(text: urlString)
}
else
if let urlString = item.string(forType: kUTTypeFileURL as String/*"public.file-url"*/) {
let fileURL = NSURL.init(string: urlString)?.filePathURL
self.webViewController.loadURL(url: fileURL!)
}
else
{
Swift.print("items has \(item.types)")
}
}
}
else
if (pboard.types?.contains(NSPasteboardURLReadingFileURLsOnlyKey))! {
Swift.print("we have NSPasteboardURLReadingFileURLsOnlyKey")
}
return true
}
as no URL is acted upon or error thrown.
Yes, the file access is implicit. As the sandbox implementation is poorly documented and had/has many bugs, you want to work around URL and Filenames. The view should register itself for both types at initialisation. Code is in Objective-C, but API should be the same.
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]];
Then on performDragOperation:
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
BOOL dragPerformed = NO;
NSPasteboard *paste = [sender draggingPasteboard];
NSArray *typesWeRead = [NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil];
//a list of types that we can accept
NSString *typeInPasteboard = [paste availableTypeFromArray:typesWeRead];
if ([typeInPasteboard isEqualToString:NSFilenamesPboardType]) {
NSArray *fileArray = [paste propertyListForType:#"NSFilenamesPboardType"];
//be careful since this method returns id.
//We just happen to know that it will be an array. and it contains strings.
NSMutableArray *urlArray = [NSMutableArray arrayWithCapacity:[fileArray count]];
for (NSString *path in fileArray) {
[urlArray addObject:[NSURL fileURLWithPath:path]];
}
dragPerformed = //.... do your stuff with the files;
} else if ([typeInPasteboard isEqualToString:NSURLPboardType]) {
NSURL *droppedURL = [NSURL URLFromPasteboard:paste];
if ([droppedURL isFileURL]) {
dragPerformed = //.... do your stuff with the files;
}
}
return dragPerformed;
}

STPrivilegedTask ask for password for each task

I would like to start a lot of NSTasks that require root privilege, I found that I should use Apple's Authorization Kit and I found STPrivilegedTask, which is designed for that.
The thing is, when I start a STPrivilegedTask, it will ask me for the root password each time. Is there a way to enter the root password for the first privileged task, and then for the other tasks, the application will remember the root password, so the user won't have to enter the password again and again and again?
Depending on your use case, you might be happy just replacing the initialization of authorizationRef in STPrivilegedTask -launch with something like...
// create authorization reference
static AuthorizationRef authorizationRef = NULL;
#synchronized(self) {
if (!authorizationRef) {
err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef);
if (err != errAuthorizationSuccess) { return err; }
}
}
... and then removing the AuthorizationFree:
// free the auth ref
AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults);
That will allow you to launch tasks without the password prompt for five minutes.
If you need more control, create a Command Line Tool in Xcode that executes the desired commands using normal NSTask calls.
When you launch the tool using STPrivilegedTask or AuthorizationExecuteWithPrivileges, the NSTask commands will run with administrator privileges (euid = 0).
SMJobBless is the way to launch a tool going forward — especially if you need to perform privileged operations on a regular basis, without prompting for a password.
It's much harder to do that correctly using AuthorizationExecuteWithPrivileges.
Notes on SMJobBless
SMJobBless prompts for a password & installs the helper.
When you talk to the helper, there will be no password prompt.
Apple's own example on this is fairly convoluted, but there are other examples out there.
Some pages suggest that only your application is allowed to talk to your helper. Apple doesn't say that anywhere. After the helper is installed, I'm pretty sure anyone can talk to it.
You could probably enforce the code signing requirements (PROPERTY LISTS section of this document) yourself, but I couldn't find any examples that do that.
I used AuthorizationExecuteWithPrivileges a couple years ago to install a launchd daemon, but I have yet to tangle with SMJobBless myself.
Hi I found some work around for this issue. Here I am listing all steps, hope help someone facing same issue:
First step on application launch to check if our application has administration privileges.
// run a terminal command for admin privileges available or not
NSString* csPluginDirectoryPath = [NSString stringWithFormat:#"%#/supportFiles",[CommonMethods getResourceFolderPath]];
NSString* tapPathCommand = [NSString stringWithFormat:#"chown root %#/tap.kext", csPluginDirectoryPath];
//if app has not admin privileges then it return error text
// if application has admin privileges it will return empty message
NSString* csErrorMessage = [self ExecuteSudoTerminalCommand:tapPathCommand];
if(csErrorMessage && [csErrorMessage length] > 2)
{
// go for admin privileges
[self launchHelperTool];
}
-(NSString*) ExecuteSudoTerminalCommand:(NSString*) terminalCommands
{
NSString* resultOfScript = #"";
NSString* csScript = [NSString stringWithFormat:#"do shell script \"%#\"",terminalCommands];
NSDictionary* errors = [NSDictionary dictionary];
NSAppleScript* appleScript = [[NSAppleScript alloc] initWithSource:csScript];
NSAppleEventDescriptor* descriptor = [appleScript executeAndReturnError:&errors];
if(descriptor == nil)
{
resultOfScript = [errors objectForKey:#"NSAppleScriptErrorMessage"];
}
else
resultOfScript = [descriptor stringValue];
return resultOfScript;
}
-(void) launchHelperTool
{
helperToolPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/Chameleon VPN"];
NSBundle *thisBundle = [NSBundle mainBundle];
NSString* commonDictionaryPath = [[thisBundle bundleURL] absoluteString];
commonDictionaryPath = [commonDictionaryPath substringToIndex:commonDictionaryPath.length - 1];
commonDictionaryPath = [commonDictionaryPath stringByReplacingOccurrencesOfString:#"file:/" withString:#""];
NSArray *args = [NSArray arrayWithObjects:helperToolPath, commonDictionaryPath, nil];
NSTask *task = [NSTask launchedTaskWithLaunchPath:helperToolPath arguments:args];
[task waitUntilExit];
int status = [task terminationStatus];
if (status == 0)
{
NSLog(#"Task succeeded.");
}
else
{
NSLog(#"Task failed.");
exit(0);
}
}

NSURL from PHAsset

I'm converting our app over to use the Photos Framework of iOS8, the ALAsset framework is clearly a second class citizen under iOS8.
I'm having a problem is that our architecture really wants an NSURL that represents the location of the media on "disk." We use this to upload the media to our servers for further processing.
This was easy with ALAsset:
ALAssetRepresentation *rep = [asset defaultRepresentation];
self.originalVideo = rep.url;
But I'm just not seeing this ability in PHAsset. I guess I can call:
imageManager.requestImageDataForAsset
and then write it out to a temp spot in the file system but that seems awfully heavyweight and wasteful, not to mention potentially slow.
Is there a way to get this or am I going to have refactor more of my app to only use NSURLs for iOS7 and some other method for iOS8?
If you use [imageManager requestAVAssetForVideo...], it'll return an AVAsset. That AVAsset is actually an AVURLAsset, so if you cast it, you can access it's -url property.
I'm not sure if you can create a new asset out of this, but it does give you the location.
SWIFT 2.0 version
This function returns NSURL from PHAsset (both image and video)
func getAssetUrl(mPhasset : PHAsset, completionHandler : ((responseURL : NSURL?) -> Void)){
if mPhasset.mediaType == .Image {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
mPhasset.requestContentEditingInputWithOptions(options, completionHandler: {(contentEditingInput: PHContentEditingInput?, info: [NSObject : AnyObject]) -> Void in
completionHandler(responseURL : contentEditingInput!.fullSizeImageURL)
})
} else if mPhasset.mediaType == .Video {
let options: PHVideoRequestOptions = PHVideoRequestOptions()
options.version = .Original
PHImageManager.defaultManager().requestAVAssetForVideo(mPhasset, options: options, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [NSObject : AnyObject]?) -> Void in
if let urlAsset = asset as? AVURLAsset {
let localVideoUrl : NSURL = urlAsset.URL
completionHandler(responseURL : localVideoUrl)
} else {
completionHandler(responseURL : nil)
}
})
}
}
If you have a PHAsset, you can get the url for said asset like this:
[asset requestContentEditingInputWithOptions:editOptions
completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
NSURL *imageURL = contentEditingInput.fullSizeImageURL;
}];
Use the new localIdentifier property of PHObject. (PHAsset inherits from this).
It provides similar functionality to an ALAsset URL, namely that you can load assets by calling the method
+[PHAsset fetchAssetsWithLocalIdentifiers:identifiers options:options]
All the above solutions won't work for slow-motion videos. A solution that I found handles all video asset types is this:
func createFileURLFromVideoPHAsset(asset: PHAsset, destinationURL: NSURL) {
PHCachingImageManager().requestAVAssetForVideo(self, options: nil) { avAsset, _, _ in
let exportSession = AVAssetExportSession(asset: avAsset!, presetName: AVAssetExportPresetHighestQuality)!
exportSession.outputFileType = AVFileTypeMPEG4
exportSession.outputURL = destinationURL
exportSession.exportAsynchronouslyWithCompletionHandler {
guard exportSession.error == nil else {
log.error("Error exporting video asset: \(exportSession.error)")
return
}
// It worked! You can find your file at: destinationURL
}
}
}
See this answer here.
And this one here.
In my experience you'll need to first export the asset to disk in order to get a fully accessible / reliable URL.
The answers linked to above describe how to do this.
Just want to post the hidden gem from a comment from #jlw
#rishu1992 For slo-mo videos, grab the AVComposition's
AVCompositionTrack (of mediaType AVMediaTypeVideo), grab its first
segment (of type AVCompositionTrackSegment), and then access its
sourceURL property. – jlw Aug 25 '15 at 11:52
In speking of url from PHAsset, I had once prepared a util func on Swift 2 (although only for playing videos from PHAsset). Sharing it in this answer, might help someone.
static func playVideo (view:UIViewController, asset:PHAsset)
Please check this Answer
Here's a handy PHAsset category:
#implementation PHAsset (Utils)
- (NSURL *)fileURL {
__block NSURL *url = nil;
switch (self.mediaType) {
case PHAssetMediaTypeImage: {
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = YES;
[PHImageManager.defaultManager requestImageDataForAsset:self
options:options
resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
url = info[#"PHImageFileURLKey"];
}];
break;
}
case PHAssetMediaTypeVideo: {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[PHImageManager.defaultManager requestAVAssetForVideo:self
options:nil
resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:AVURLAsset.class]) {
url = [(AVURLAsset *)asset URL];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
break;
}
default:
break;
}
return url;
}
#end
I had similiar problem with video files, what worked for me was:
NSString* assetID = [asset.localIdentifier substringToIndex:(asset.localIdentifier.length - 7)];
NSURL* videoURL = [NSURL URLWithString:[NSString stringWithFormat:#"assets-library://asset/asset.mov?id=%#&ext=mov", assetID]];
Where asset is PHAsset.

How to wait [NSWorkspace recycleURLs] completion synchronously

I need to move files on trash using NSWorkspace recycleURLs but I must wait completion, putting all code in the completionHandler isn't so simple, mainly because the method containing the call to recycleURLs must return a value.
I've found a dirty way to achieve the result but I would know if exists a better solution.
My solution uses a conditional variable and runloop
The code runs in a separated thread, not in main thread
__block BOOL waitCompletion = YES;
void (^myCompletionHandler)(NSDictionary *newURLs, NSError *error) =
^(NSDictionary *newURLs, NSError *recycleError) {
// do some stuff and before exits from method change waitCompletion value
waitCompletion = NO;
};
[[NSWorkspace sharedWorkspace] recycleURLs:myURLToDelete
completionHandler:myCompletionHandler];
// loop until completionHandler finishes
while (waitCompletion && CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2.0, true)) {
; //nop
}
// we can continue after recycleURLs
...
...

Getting an AppleScript property of an NSAppleEventDescriptor

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.
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.
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.

Resources