Retrieve Properties in a better way

New to AppleScript and have searched around but might not be searching the right terms. I have the following code

local names
tell application "Airfoil"
	set names to the name of every speaker & the connected of every speaker
end tell

and it returns the following

I would like to have it return something similar to this

A more usable array. Do I have to parse this information or is there a way to retrieve it in a better manor? Thanks for any help or pointers you can give me.
-scott

this is the documentation from the Dictionary

Using a slightly different data structure (list of lists), you could use a handler like this:

--tell application "Airfoil" to set names_and_connecteds to {name,connected} of every speaker
set names_and_connecteds to {{"Computer", "Stereo"}, {true, false}}
|transpose|(names_and_connecteds) --> {{"Computer", true}, {"Stereo", false}}
to |transpose|(m)
	set rows to length of m
	set columns to length of item 1 of m
	set new_m to {}
	repeat columns times
		set end of new_m to {}
	end repeat
	repeat with row in m
		repeat with column_num from 1 to length of row
			set end of ¬
				item column_num of new_m to ¬
				item column_num of row
		end repeat
	end repeat
	new_m
end |transpose|

I have never used AirFoil before. Neat. :smiley:

set bigList to {}
tell application "Airfoil"
	repeat with i from 1 to count of speakers
		set this_item to speaker i
		set theName to name of this_item as list
		set theConnected to connected of this_item
		copy theName & theConnected to end of bigList
	end repeat
end tell
bigList --> {{"Computer", false}, {"Power Mac G5", true}}

Notice the result puts each set of speaker and connected inside its own list inside the BigList
I serperated them like this as I felt it was easier to work with. *edit, and just noticed great minds think alike(post above)

To get a set , Example:.

set conNection1 to item 1 of bigList --> {"Computer", false}
set conNection2 to item 2 of bigList --> {"Power Mac G5", true}

Wow. thanks chrys and mark. that was a lot more than I was expecting to get. It’s to bad AppleScript doesn’t make that a little easier.

What are you actually trying to Do ??

I meant the work you guys put into writing the scripts was more than I expected. :stuck_out_tongue: But I would think it would be easier to do. I am running this AppleScript in Objective-C to get a list of the speakers and their connected boolean values so I can update a tableview. This will work out great and thanks again for your work.

Hah, I mean’t your end goal:P

I have just started to learn Objective -c, and am liking it.
But I realise, I will want to incorporate some AS into some apps I make, and was wondering how it was done.

I just had a quick play and put this together… As you can see Running before I can walk, but it does kind of return
the values.

I would be interested in how you do it for future ref…

NSDictionary *scriptError = [[NSDictionary alloc] init]; 
	NSAppleScript  * newAS =[[NSAppleScript alloc] initWithSource:@"set bigList to {}\r tell application \"Airfoil\" \r repeat with i from 1 to count of speakers \r set this_item to speaker i \r set theName to name of this_item as list \r set theConnected to connected of this_item \r copy theName & theConnected to end of bigList \r end repeat \r end tell \r bigList"];

	NSAppleEventDescriptor * paramList = [newAS executeAndReturnError:&scriptError];
	NSLog(@"The List : %@",paramList);

I know this is most likely NOT the way to do this, But wanted to see what I could come up with, which helps me understand bits and pieces of Object -c,

#import <Foundation/Foundation.h>

#import <Cocoa/Cocoa.h>	
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	NSDictionary *scriptError = [[NSDictionary alloc] init]; 
	NSAppleScript  * newAS =[[NSAppleScript alloc] initWithSource:@"set bigList to {}\r tell application \"Airfoil\" \r repeat with i from 1 to count of speakers \r set this_item to speaker i \r set theName to name of this_item \r set theConnected to connected of this_item \r copy theName & \"/\" & theConnected to end of bigList \r end repeat \r end tell \r bigList  "];
	
	NSAppleEventDescriptor * paramList = [newAS executeAndReturnError:&scriptError];
	unsigned int count = [paramList numberOfItems]; 
	unsigned int i = 1;
	NSLog(@"%i",count);
	for (i = 1; i <= count;   ++i ) { 
		NSAppleEventDescriptor  * theDat = [paramList descriptorAtIndex:i] ;
		NSString  * theSpeakerAndValue = [theDat stringValue];
		NSString  * theSpeaker = [theSpeakerAndValue stringByDeletingLastPathComponent];
		NSString  * theValue = [theSpeakerAndValue lastPathComponent];
		NSLog(@"Speaker: %@		is: %@ ",theSpeaker,theValue);
		
	} 
	
	 [pool drain];
    return 0;
}

OUTPUT:
Speaker: Computer is: false
Speaker: Power Mac G5 is: true

And I know I could the PathComponent methods are really for file and url paths, but they looked handy for this.
:cool:

thanks Mark. I might actually use that. I am just trying to learn Objective-C. And it looks like you know more than I do. What I have found out about using AppleScript with Objective-C is that NSApplescript doesn’t allow threading and can be slower than some other methods. However it is the most universal. There are a few other methods to get it working but they all have their issues.

  1. appscript
    http://appscript.sourceforge.net/
    http://www.cocoadev.com/index.pl?ObjCAppscript

I couldn’t get this to work because you have to create a glue between the program and appscript and what is included is deprecated and there is no documentation on how to use the new Python application. I’m sure someone with some kind of programing background could figure it out. I couldn’t

  1. Scripting Bridge
    http://robnapier.net/blog/scripting-bridge-265 (good place to start)
    http://developer.apple.com/mac/library/documentation/AppleScript/Conceptual/AppleScriptX/Concepts/scripting_bridge.html
    http://www.macosxautomation.com/applescript/features/scriptingbridge.html
    http://awkward.org/2007/10/26/scriptingbridge/

this is the best way to do it however it doesn’t always integrate with the application you are trying to control. This was the case with Airfoil. There were a couple errors I ran into when Scripting Bridge builds the header file required.

3)KFAppleScript
http://homepage.mac.com/kenferry/software.html#KFAppleScript

4)NSAppleScript
http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSAppleScript_Class/Reference/Reference.html
http://www.cocoadev.com/index.pl?NSAppleScript

This has lots of problems. Can’t thread and memory leaks have been reported. Also it is slower than a lot of the other methods.

  1. OSAScript
    http://boredzo.org/blog/archives/2008-11-09/framework-friday-osakit

That is what I have been able to find. I was trying to use KFAppleSCript but I can’t figure out how it is dealing with lists. It automatically converts your data types but something is screwed up. So I will probably just use what you provided because it does exactly what I am looking for. Thanks again.

Hi scottedge,
I will have a look at those links, Thanks.
I will post now and comment it up later, as i have to rush out now.
Note: the embedded AS has also slightly changed…

I actually had a further play the other day and improved the way I get the data.

#import <Foundation/Foundation.h>

#import <Cocoa/Cocoa.h>



	
	
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
		/*COMMENT/ set a debug to true or false for any NSLogs used for printing while debugging.
		 example of NSLog : if (debug ){ NSLog(@"theDate:: %@	",theDat);}
		 */
	BOOL debug  = false;
		//COMMENT// initiate a Dictionary to hold any error feed back.
	NSDictionary *scriptError = [[NSDictionary alloc] init];
	
		//COMMENT//initiate  a NSAppleScript and set the Source to the script string. This can be changed to a file.
	NSAppleScript  * newAS =[[NSAppleScript alloc] initWithSource:@"set bigList to {}\r tell application \"Airfoil\" \r repeat with i from 1 to count of speakers \r set this_item to speaker i \r set theName to name of this_item \r set theConnected to connected of this_item \r copy theName & space & theConnected to end of bigList \r end repeat \r end tell \r bigList  "];
	
		//COMMENT//initiate  NSAppleEventDescriptor to Execute the script, return any results and errors.
	NSAppleEventDescriptor * paramList = [newAS executeAndReturnError:&scriptError];
		
	unsigned int count = [paramList numberOfItems]; //COMMENT//set count to the Count of items in result
	unsigned int i = 1;
	
	NSLog(@"=======  Avalible Speakers  %i ========",count);
		//COMMENT//enumerate each item found while count is less than or equal to i,  i adding 1 on each iteration.
	for (i = 1; i <= count;   ++i ) { 
			//COMMENT//get a descriptor (record) using its index
		NSAppleEventDescriptor  * theDat = [paramList descriptorAtIndex:i] ;if (debug ){ NSLog(@"theDate:: %@	",theDat);}
		
			//COMMENT//get the string value of the descriptor (record)
		NSString  * theSpeakerAndValue = [theDat stringValue]; if (debug ){ NSLog(@"theSpeakerAndValue:: %@	",theSpeakerAndValue);}
		
			//COMMENT//The String will be something like: Computer true. Use a NSRange to search for a matching string i.e @"true"
		NSRange aRange = [theSpeakerAndValue rangeOfString : @"true"];
		NSString * state;		
		NSString * searchForMe;
		
			//COMMENT//enumerate each search result for each item.
		if (aRange.location != NSNotFound) {
				//COMMENT// If the @"true" string is NOT found, then.... 
		searchForMe = @"true";
			
			state =	@"ON";
			
			}else {
				
				searchForMe = @"false";
				state =	@"OFF";
				
			}
			/*COMMENT/ Replace the 	the string  matching  searchForMe  in the  theSpeakerAndValue, with @" -"
			 So:  'Computer true' wil become 'Computer  -'
		*/
			NSString *theSpeaker =  [theSpeakerAndValue stringByReplacingOccurrencesOfString:searchForMe withString:@" -"];	
		NSLog(@" %@		:		%@ ",theSpeaker,state);
		
		
	} 
		//NSLog(@"%@",scriptError);
		
	
	
	
    [pool drain];
    return 0;
}


**Edit, Just added comments.

Comments done…

Hi scottedge,

I wanted to play with using applescript in a cocoa app a bit more, and instead of putting the code into the
main() as a string, I wanted to do it as a source file.
I had some luck with this but still have some things to sort out…

But while I was goggling, I was reminded of the Scripting Bridge.( and noted you posted about it)
And wanted to share what I have worked out so far…

In short this has allowed me to not use any applescript as such to do the same thing and I think maybe simpler
once the learning curve is topped…
Have a look at the Scripting Bridge docs.
The script below works in a cocoa app, and the setup is very simple. Which I put comments in for you to follow.
(which also maybe longer than the code :D)
Getting the syntax is the main learning curve.
And you can look in the header file for the converted definitions.

one thing, when you create the header file for Airfoil. You will get an error:
error: duplicate declaration of method ‘-open:’

This is simple to fix.
In the “Airfoil.h” file you will have two methods with the same selector name.

[i]- (void) open:(NSURL *)x; // Open an object.

  • (AirfoilDocument *) open:(NSURL *)x; // Open an objec[/i]t.

just change one of the names, and rebuild

- (void) openx:(NSURL *)x; // Open an object

I do not think its something to worry about.

I should point out I am a beginner at Objective C, and the only other language i know a bit about is AS. and
a little scattering of Unix.

I have not even got to the parts about pointers and casting them blah blah which i know I am going to need…
And I say this because I know that in this script most likely reflects that.

//
//  main.m
//  AirfoilCococa
//
//  Created by markhunte on 25/10/2009.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#import <ScriptingBridge/ScriptingBridge.h> // add foundation//
#import "Airfoil.h" 

/* //COMMENT/

Run the command below in Terminal, it will create a header file for the application you want to control.
This header is the converted form of the applescipt dictionary for the app.

Note the form: sdef /path/to/the.app | sdp -basename shortNameOfappWithoutExtension

The file will be created in the current directory of the terminal.
Add the file to your project. and build.
then add the #import  for it.
command below:

sdef /Applications/Airfoil.app | sdp -fh --basename Airfoil

*/

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
		/* //COMMENT/ 
Quote from the apple docs:
		 The recommended method for creating an instance of a scriptable application is applicationWithBundleIdentifier:. The method can locate an application on a system even if the user has renamed the application, and it doesn't require you to know where an application is installed in the file system (which could be anywhere). he following line of code creates an instance of the iTunes application:

		 iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
		 */
	
	AirfoilApplication *Airfoil = [SBApplication applicationWithBundleIdentifier:@"com.rogueamoeba.Airfoil"];
	
	SBElementArray * speaker =[Airfoil speakers] ;
	id item;
	NSString * state;		
	NSString * searchForMe;
	NSNumber *theState, * num1;
	num1 =[NSNumber numberWithUnsignedInt:1];
	
	
	NSLog(@"=======  Avalible Speakers  %i ========",[speaker count]);
	if ([speaker count] > 0) {
		for (item in speaker) {
			NSString * theSpeaker = [item name];
			theState = [NSNumber numberWithUnsignedInt:  [item connected]];
		
			if (theState == num1) {

				searchForMe = @"true";
				
				state =	@"ON";
				
			}else {
				
				searchForMe = @"false";
				state =	@"OFF";
				
			}
NSLog(@" %@		:		%@  ",theSpeaker,state);
		}
	}
	
	
    [pool drain];
    return 0;
}

Mark thanks alot. I will look into doing it that way because it is a lot better. There is a way to build the header file right in Xcode. I guess I missed putting this link in my previous post sorry. But it is a good place to look about Scripting Bridge.

http://robnapier.net/blog/scripting-bridge-265

(edited post 9 to update with this link)

Thanks,

I actually found and read that page which really helped, I reverted to the command line way as I did not want to keep creating the rule , and ticking/unticking the copy to project for the app I want to control. :D.

Also I can build up a folder for the headers for when I need them.

I would like to know how you get on if thats ok. Many thanks

Hi,

I quickly put together a Automator workflow to create the header file,… EDIT* follows***

But have now removed it in favour of a Shell Script.

To use in the Xcode User Script Menu to create a header file for use with Scritping Bridge
( At some point I may clean it up… )
The script will place the new .h file in the Main project directory.
And either create a Reference to it in the Source group, Other Sources or if niether exist, in the projects first group.

It will also get the Bundle Id and construct the Application Bundle instance, and place that depending on what you have selected for Output :
In my case i want it inserted After Selection.
example:

iCalApplication *iCal = [SBApplication applicationWithBundleIdentifier:@"com.apple.iCal"];¨¨

Also set, input : No Input.¨
I Hope this works for you and remember to use Make snapshot, before you use this, I have tested it as best as I could and had no problems . But as usaul with things like this, you use it at your own risk.

You can view and copy it from:
View the Script in the Xcode window User Scripts Edit window

Note that AppleScript can be invoked from background threads on 10.6, with some caveats (IIRC, you have to create a separate component instance for each thread, which may require using OSAKit instead). See the 10.6 release notes for AppleScript for details.

Apologies; osaglue is long dead - I really need to update the objc-appscript manual to cover ASDictionary instead. Meantime, see the README file in the ASDictionary distribution for instructions on generating ObjC glues. It’s pretty simple: select the application(s) you want, check the ‘objc-appscript glue’ option, and click Export.

As a long-time AppleScript user, I’d debate the ‘SB is the best way’ bit - SB’s the reason I wrote objc-appscript, after all. :slight_smile:

As you say, SB tends to choke at least partly on application dictionaries and Apple event APIs that aren’t 100% perfect. It also lacks some functionality due to its insistence on hiding everything behind a faux-object-oriented interface. Appscript’s much better here since it largely mimics the way that AppleScript parses terminology and builds Apple events. Appscript’s API isn’t heavily obfuscated either, so if you already know AppleScript or Apple events you’ll find it easier to construct commands and references. (Plus you can use ASTranslate to convert existing AppleScript commands to appscript syntax.)

Just updated the chapter on generating objc-appscript glues in the svn repository (revision 688.)

Hi,
I just edited my last post and removed the workflow, I now have a shell script that can be used to better effect.

You can view and copy it from:
View the Script in the Xcode window User Scripts Edit window

Thanks alot mark. I just used this and it worked great. I was struggling with a couple other things for my app. But I should be able to share pretty soon.

I’m having trouble sending commands to Airfoil.

I think I need to alloc something here but I’m not sure what. Also do I need to change this to an array? It runs fine but nothing happens.



-(void)connectToSpeaker:(NSString*)connectToThisSpeaker
{
	AirfoilApplication *Airfoil = [SBApplication applicationWithBundleIdentifier:@"com.rogueamoeba.Airfoil"];
	NSMutableArray *setTheSpeaker;
	setTheSpeaker = [[NSMutableArray alloc] init];
	[setTheSpeaker addObject:connectToThisSpeaker];
	[Airfoil connectTo:setTheSpeaker password:nil]; 
	NSLog(@"in method || speaker: %@",connectToThisSpeaker);
	
}




// An application's top level scripting object.
@interface AirfoilApplication : SBApplication

- (SBElementArray *) documents;
- (SBElementArray *) windows;

@property (readonly) BOOL frontmost;  // Is this the frontmost (active) application?
@property (copy, readonly) NSString *name;  // The name of the application.
@property (copy, readonly) NSString *version;  // The version of the application.

- (AirfoilDocument *) open:(NSURL *)x;  // Open an object.
- (void) print:(NSURL *)x printDialog:(BOOL)printDialog withProperties:(AirfoilPrintSettings *)withProperties;  // Print an object.
- (void) quitSaving:(AirfoilSavo)saving;  // Quit an application.
- (void) connectTo:(NSArray *)x password:(NSString *)password;  // Connect to the Airport Express speakers
- (void) disconnectFrom:(NSArray *)x;  // Disconnect from the speakers
- (void) openx:(NSURL *)x;  // Open an object.
- (void) print:(NSURL *)x;  // Print an object.
- (void) quitSaving:(AirfoilSavo)saving;  // Quit an application.

@end