Make new chat in Adium using Cocoa ScriptingBridge - objective-c

The following AppleScript code works fine:
tell application "Adium" to tell first account to make new chat with contacts {first contact} with new chat window
But how can I do the same using Cocoa's ScriptingBridge?

Generally, you ought to be able to do it following Apple's Scripting Bridge Programming Guide for Cocoa. To start, I created a header file for Adium by running sdef /Applications/Adium.app | sdp -fh --basename Adium in Terminal (creates Adium.h in the current directory). The header file produced gives clues about making the AppleScript calls via the Scripting Bridge.
The problem that I ran into is that I cannot see a way, based on the generated header file, to do make new chat with contacts {...} with new chat window (I can make a new chat and maybe even hook it into a new window, but I could not find a way to make that chat take the contact).
The next best thing might be to use NSAppleScript to execute your valid AppleScript code:
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:#"tell application \"Adium\" to tell first account to make new chat with contacts {first contact} with new chat window"];
NSDictionary *errorDictionary;
NSAppleEventDescriptor *eventDescriptor = [appleScript executeAndReturnError:&errorDictionary];

Short of using raw Apple event codes, you can't. Should work with objc-appscript though. Running your AppleScript command through appscript's ASTranslate tool produces the following:
#import "ADGlue/ADGlue.h"
ADApplication *adium = [ADApplication applicationWithName: #"Adium"];
ADReference *ref = [[adium accounts] at: 1];
ADMakeCommand *cmd = [[[[ref make] newChatWindow: ASTrue] withContacts: [NSArray arrayWithObject: [[[[adium accounts] at: 1] contacts] at: 1]]] new_: [ADConstant chat]];
id result = [cmd send];

Related

Code to check if an external app exists without breaking the sandbox

I'm making an app and I would like to check if my computer has already installed an app (Example.app, com.example.test) and be able to send the app to the Mac App Store.
I tried several things, like:
NSString *script = #"try\r tell application \"Finder\" to get application file id \"com.example.test\"\r set appExists to true\r on error\r set appExists to false\r end try";
NSAppleEventDescriptor *result = [self executeScript:script];
return [result booleanValue];
This works perfectly, but I've been reading that Apple doesn't allow temporary exceptions for Finder in the entitlements file in order to keep the app secure.
I also tried something similar but avoiding the use of Finder:
NSString *script = #"set appID to id of application \"Example\"\r set msg to exists application id appID\r tell application \"Example\" to quit\r return msg";
NSAppleEventDescriptor *result = [self executeScript:script];
return [result booleanValue];
This works only if the user has the app, if not, it will prompt a dialog asking for the app location. (and shows the Example icon in the dock for a few milliseconds)
I also been trying some more hacky solutions like:
NSTask *task = [NSTask new];
[task setLaunchPath:#"/bin/bash"];
[task setArguments:#[#"if ls /Applications/Example.app >/dev/null 2>&1; then echo FOUND; else echo NOT FOUND; fi"]];
[task launch];
[task waitUntilExit];
int status = [task terminationStatus];
if (status == 0)
NSLog(#"Task succeeded.");
else
NSLog(#"Task failed.");
But the task always fails, I think command line stuff will never work (if so, nonsense sandboxing).
I've been thinking to put a button (checkbox), that prompts a dialog in order to select the path of the App, and check if the app name is equal to Example, if it is, check the checkbox, and uncheck if it's not. But I don't know how to prompt that dialog. (And I would like to avoid this solution if it's possible)
My questions are:
Is it possible to know if the app exists (without open it) following the rules of sandboxing?
Could I set Finder as a temporary exception and Apple will approve it? (Explaining what's the intention)
Thanks
Have you tried the following using NSWorkspace?:
NSURL *appURL = [[NSWorkspace sharedWorkspace]
URLForApplicationWithBundleIdentifier:#"com.example.test"];
If the result is nil, you can consider that as Launch Services not "knowing about" the application, wherever it might be located, otherwise it will return the NSURL. (Launch Services, part of the CoreServices umbrella framework, is the framework that handles dealing with applications and document bindings). Using Launch Services is usually a better approach than checking at a specific path, since app bundles can be moved around the file system but still be present.
The AppleScript code you posted is probably about the equivalent of having the Finder call the code shown above: it's simply calling into Launch Services to find an app with that bundle identifier.
I think (according to the Automation conferences at the WWDC in 2012) you should use the NSUserScriptTask. To be exactly you need the subclass NSUserAppleScriptTask and and you're be able to fire an user defined AppleScript outside the sandbox. What it will do is executing the script using an XPC-service and where it run the script using the command line utility osascript. Your application is sandboxed so the only allowed place where the script may be installed is at NSApplicationScriptsDirectory which is located at ~/Library/Application Scripts/<bundle-id>/
So be aware, the application context it not self targeted by default, so when you need to script your application you need to explicitly tell your application to perform the action. So it doesn't run like NSAppleScript which will be executed by the Application itself, NSUserScriptTask and it's subclasses will run outside the application (sandbox).

Get Path to selected file in Finder

How would I retrieve an array of paths to the selected files in Finder?
I have searched around but have only found links regarding AppleScript. I have also looked at NSWorkspace and NSFileManager but I didn't find anything.
Expanding on #Bavarious's (correct) answer, here's how I've gotten the selection from Finder using the Scripting Bridge:
#import "Finder.h" //my copy is here: https://github.com/davedelong/BetterInfo/blob/master/Finder.h
FinderApplication * finder = [SBApplication applicationWithBundleIdentifier:#"com.apple.finder"];
SBElementArray * selection = [[finder selection] get];
NSArray * items = [selection arrayByApplyingSelector:#selector(URL)];
for (NSString * item in items) {
NSURL * url = [NSURL URLWithString:item];
NSLog(#"selected item url: %#", url);
}
If it is possible to get the list of selected files in a given Finder window using AppleScript, you can probably use Scripting Bridge in a Cocoa application to interface with Finder. Quoting Apple’s documentation,
Scripting Bridge is a framework and a technology that makes it much easier for Cocoa developers to control and communicate with scriptable applications. Instead of incorporating AppleScript scripts in your application or dealing with the complexities of sending and handling Apple events, you can simply send Objective-C messages to an object that represents an application with a scripting interface. Your Cocoa application can do anything an AppleScript script can, but it does so in Objective-C code that is integrated with the rest of your project’s code.
There is no Cocoa class that represents Finder or, more specifically, Finder windows. Finder is an application, and a scriptable application at that, so Scripting Bridge is the way to go.

Comparing different 'the launching Cocoa app with parameters' methods

I found there are at least three ways to launch an app with Mac OS X from an application.
NSTask. I can give parameters, but it seems that it's not for an Cocoa App, but an UNIX style binary.
system function (system()) just the same way as C does. I don't know the reason why but it seems that nobody recommends this method.
NSWorkspace, but I can't find a way to pass parameters to this function.
Questions
Q1 : Is there any other way to launch an App (from an App) other than three methods?
Q2 : What's the pros and cons for each method?
Q3 : What's the preferable way for launching an App (from an App)?
Q4 : What's the preferable way for launching an App with parameters (from an App)?
Q5 : What's the preferable way to open a document (from an App)?
ADDED
NSWorkspace openFile:withApplication: : For running "TextMate README.txt", based on Roadmaster's answer and this code, I could make it as follows.
But, I can't give the parameters to the App.
NSString * path = #"/Users/smcho/Desktop/README.txt";
NSURL * fileURL = [NSURL fileURLWithPath: path];
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
[ws openFile:[fileURL path] withApplication:#"TextMate"];
NSWorkspace launchApplicationAtURL:options: : It works with 10.6 or later, you can get an example from this question.
NSURL * bURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:#"com.macromates.textmate"];
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
[ws launchApplicationAtURL:bURL options:NSWorkspaceLaunchDefault configuration:nil error:nil];
NSTask : This is the working code. I need to give the correct binary path, and it doesn't look like a Cocoa way, as it's for running binary, not bundle. Though, it's possible to give more parameters than just a file name.
[NSTask launchedTaskWithLaunchPath:#"/Applications/TextMate.app/Contents/MacOS/TextMate" arguments:[NSArray arrayWithObjects:#"hello.txt", nil]];
system() : With the shell, I could run "system(open -a ABC --args hello.txt)", just like I do with the command line. It seems that this is the easiest way to go.
In 10.6 and later, NSWorkspace has a method launchApplicationAtURL:options:configuration:error: that can be used to pass arguments to the app.
There are also Launch Services functions such as LSOpenItemsWithRole.
You could also send an AppleEvent to the Finder asking it to open something.
EDIT TO ADD: "best" is subjective, but I'd say if you can use NSWorkspace, use it. If you can't, e.g., you need to pass command-line parameters and you need to support Leopard, then use Launch Services.
By using Scripting Bridge, you can use the method activate to launch a cocoa app. See: Scripting Bridge.

Add movie to iTunes using Scripting Bridge

I want to use Scripting Bridge to add a movie to iTunes. And preferably letting me choose between a 'music video' and a 'movie'. I know both Objective-C and AppleScript so I thought it wouldn't be that hard but I can't figure it out. I know how I would use NSAppleScript for it but I'm targeting 10.5 or later and read that Scripting Bridge obsoletes NSAppleScript. Is that right?
All I got is
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier: #"com.apple.iTunes"];
Which is, as you can see, not much at all.
Step 1. Generate iTunes.h header file:
sdef /Applications/iTunes.app | sdp -fh --basename "iTunes"
Step 2. The code to add a media file looks like the following:
NSString* sourceMediaFile = ...;
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
iTunesTrack * track = [iTunes add:[NSArray arrayWithObject:[NSURL fileURLWithPath:sourceMediaFile]] to:nil];
NSLog(#"Added %# to track: %#",sourceMediaFile,track);
You should use the "scripting definition processor" (sdp) program to generate a header file from iTunes' scripting definition (.sdef) file (which you can get using the sdef program):
sdef /Applications/iTunes.app | sdp -fh --basename "iTunes"
This'll give you a file called iTunes.h. Then you include that header into your project and read through it to see what the iTunes scripting bridge interface offers.
If it seems like you won't be able to do this with the scripting bridge (it's possible -- not everything that can be done via an app's AppleScript interface can also be done via the scripting bridge), just go ahead and write an AppleScript to do it instead, and then execute that in your program with NSAppleScript.
For the second parameter, it takes a playlist object (or nil as previously mentioned). Once you have fetched an instance of a iTunesPlaylist* object through some means (there are several depending on your needs), you can pass it as the second parameter.

How can I discover what apps are installed on OS X using Objective C or Macruby?

could you tell me please - which object and which method can i use to fetch information about installed applications on OSX with objective c or macruby?
You can just call the Apple command-line tool system_profiler, e.g.
% system_profiler SPApplicationsDataType
For C or Objective-C you could use system() to do this.
For more info:
% man system
% man system_profiler
If you meant to list all of GUI apps, you can use Launch Services. For the list of functions, see the reference.
Do not manually list .app bundles! It's already done by the system, and you just have to query it.
There is a command-line program called lsregister at
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister
which can be used to get the detailed dump of the Launch Service database. This tool is sometimes helpful,
but don't depend on that in a shipping program,
OS X doesn't really have a notion of "installing an app". OS X applications are self contained bundles that appear in the Finder as a single virtual file. There's no central registry, just a convention that applications are placed in /Applications or ~/Applications for a per user "install".
About the best you can do will be enumerate those directories for their contents, and every ".app" directory you find is an application. It will not be a exhaustive (I often run small apps from e.g. my Desktop if I've just downloaded them).
I moved in a following way:
execute command system_profiler SPApplicationsDataType -xml
from code. Key SPApplicationsDataType means that only application data is required. -xml means that I expect to see xml result for easier parsing.
parse result array
Here you can find nice example regarding command execution from code: Execute a terminal command from a Cocoa app
Total code example is looked like following:
NSStirng * commangString = #"system_profiler SPApplicationsDataType -xml";
NSData * resultData = [CommandLineTool runCommand:commangString];
NSArray * appArray = [PlistManager objectFromData:resultData];
// parse array of NSDictionaries here ....
// method from PlistManager
+ (id) objectFromData: (NSData *) data {
NSString *errorString = nil;
NSPropertyListFormat format;
id object = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainers format:&format errorDescription:&errorString];
if (!errorString) {
return object;
} else {
NSLog(#"Error while plist to object conversion: %#", errorString);
return nil;
}
}
// runCommand method was used from post I mentioned before
Use Spotlight via NSMetadataQuery.
You can view the results you'll get using mdfind at the command line:
mdfind "kMDItemContentType == 'com.apple.application-bundle'"
Works like a charm and very very fast.
[[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:#"com.google.Chrome"];
This would return a path to Google Chrome app if it is installed, nil otherwise.
If you don't know the BundleID, there are two options to find it:
1) Open .plist file of the app by right-clicking the app icon and choosing Show Package Contents option.
Default path to Chrome .plist is /Applications/Google\ Chrome.app/Contents/Info.plist
2) Use lsregister alongside with grep.
Try typing the following into the Terminal app, you will find the BundleID there:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -dump | grep -i chrome
As it is a unix-like OS there is no real way of telling what you have installed (unless of course you have control of the other app - then there is a lot of ways of doing this for obvious reasons).
Generally apps get put into /Applications or ~/Applications BUT you can run it from anywhere and not those folders (just like a unix machine)