Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

AppleScript action improvements. Fixes #1023 #1048

Merged
merged 11 commits into from

6 participants

@pjrobertson
Owner

This adds an open files method to Quicksilver's scripting library, for dealing with files.

At the moment, you can send a file from the dSelector to an AppleScript by writing in the AppleScript:

on open myDObject

This is somewhat misleading though, as this just uses the AppleScript standard open action with things piped from Quicksilver, meaning that doing on open myDObject with myIObject builds fine in the AppleScript Editor, but it doesn't run.

Adding a new open files method means that you now use

on open files myDObject with myIObject in AppleScript (the on open myDObject method still works for backwards compatibility), meaning there's much less ambiguity, since open files is defined by QS and will validate the AppleScript.

Along with this, I've fixed a small bug whereby if an AppleScript contained something like tell Application SomeApp to open, Quicksilver would think it was a file AppleScript, even if it started with on process text

An example of how to use this action:

    using terms from application "Quicksilver"
        on get argument count
            return 2 -- if you want the third pane to be filled, otherwise return 1 (or leave this handler out)
        end get argument count
        on open files dObject with iObject  
            tell application "Finder" to move file dObject to folder iObject
        end open files
    end using terms from
@daniels220

Any update on if/when this will be merged? I'd very much appreciate this feature.

@pjrobertson
Owner

@daniels220 - we're at the mercy of the other Quicksilver developers (most likely @skurfer ;-) ) on getting this merged.
Hopefully soon... :)

@skurfer
Owner

Careful what you wish for. I don't care if AppleScript works or not, so I'm as likely to merge it unseen as anything. ;-)

Seriously, I wonder if @ddlsmurf has time to look it over. He'd be much more qualified to render an opinion.

@skurfer
Owner

Does this mean on open theFile no longer works, or just that there's an additional (better) way? I was trying to save your example script to ~/Library/Application Support/Quicksilver/Actions using that AppleScript everyone has to set the location of the Open/Save Dialog and it didn't work. So, hey look, I do care! ;-)

Update: The console shows

2012-10-16 14:29:17.194 Quicksilver[26836:c817] Perform AppleScript Action Error: "NSAppleScriptErrorNumber" = -1708;
@skurfer
Owner

Trying to move a file to the Desktop using your example script, I get:

2012-10-16 14:40:41.210 Quicksilver[28268:900f] Perform AppleScript Action Error: "NSAppleScriptErrorMessage" = "Finder got an error: Can\U2019t get folder \"/Users/rob/Desktop\".";
"NSAppleScriptErrorRange" = NSRange: {0, 0};
"NSAppleScriptErrorBriefMessage" = "Can\U2019t get folder \"/Users/rob/Desktop\".";
"NSAppleScriptErrorNumber" = -1728;
"NSAppleScriptErrorAppName" = "Finder";
@pjrobertson
Owner

Hmm... I'll take a look. Thanks! :D

@pjrobertson
Owner

OK I've added a new commit that fixes the problem. It must have slipped through at some point.

Also note that the AS in the initial comment will give an NSLog when run, since it doesn't return anything. Adding return missing value (AS's equivalent of return nil) fixes this log (which doesn't cause any real problems)

    using terms from application "Quicksilver"
        on get argument count
            return 2 -- if you want the third pane to be filled, otherwise return 1 (or leave this handler out)
        end get argument count
        on open files dObject with iObject          
            tell application "Finder" to move file dObject to folder iObject
            return missing value
        end open files
    end using terms from
@skurfer
Owner

OK, that seems to work (if you build QS with the changes, recompile the script, then relaunch QS). It should probably return the file in its new location (if such a thing is possible), but that's obviously an issue with this particular script.

I still can't use an old script that starts with on open theFile:

2012-10-16 16:25:48.293 Quicksilver[41315:752b] Perform AppleScript Action Error: "NSAppleScriptErrorNumber" = -1708;
@pjrobertson
Owner

Yep, good catch on that one, thanks.
It should be fixed now.

It should probably return the file in its new location (if such a thing is possible), but that's obviously an issue with this particular script.

Yeah you'd just need to do something like (untested)

return (Posix path of THEFILE) as alias
@skurfer
Owner

OK, the old scripts work now, but the example you gave fails. :-/ I saw this when testing the other day, but wanted to be sure Launch Services forgot about any old versions so I waited a while. It crashes with this:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[QSAppleScriptActions eventDescriptorForIObject:]: unrecognized selector sent to instance 0x106925800'
@skurfer
Owner

This is pretty close, right? 1.0?

@pjrobertson
Owner
@studgeek

Will it include #1264 also?

@pjrobertson
Owner
@pjrobertson
Owner

OK, all outstanding problems should be fixed. I've actually tested it this time :)

... and I just couldn't stay away, once I'd got 'comfortable' with the scripting definitions.

I've added two new handlers which can be used in AppleScripts:

on get direct types and on get indirect types

You use them to specify the valid direct types the action will display for, and the files that appear in the 3rd pane for the action, respectively. An example:

Take either of the 'shorten URL' AppleScripts from http://qsapp.com/wiki/Shorten_URL_(AppleScript) and save them in the Actions folder (~/Lib/App support/QS/Actions)

Restart QS to add the action, and you'll notice that you can find the action for any text that's in the 1st pane. But preferably, you only want the action to display for URL types. Go ahead and add this to the script (on the 2nd line):

using terms from... (1st line. Add the text below:)
    on get direct types
        return {"Apple URL pasteboard type"}
    end get indirect types

Now, the action will only display for URLs. The types are listed here: http://qsapp.com/wiki/AppleScript_Types (messy atm, I'll tidy it up)

(Note to devs: since this then just uses [QSLib arrayForType:type] under the hood, you can specify any valid type so e.g. QSRemoteHostType or whatever)

@pjrobertson
Owner

@studgeek and @philostein

I've added lots of documentation to the wiki on how to use these new handlers. See
http://qsapp.com/wiki/AppleScript_Types#A_real_live_example
and for an overview of AppleScript actions see http://qsapp.com/wiki/AppleScript_Actions
please update them if you have more info. I'm sure you're both much more experiences with QS AppleScripts than I am!

We would greatly appreciate it if you could both build Quicksilver with these changes and test them out. We're running a tight schedule at the moment (you'll see why soon!) so any second you could spare would be really helpful to us

@pjrobertson
Owner

Will somebody please stop me before this gets out of hand ;-)

I've now added the option for the 3rd pane to be optional.
In the get argument count handler, just return 3

on get argument count
      return 3
end on get argument count
@pjrobertson
Owner

Rebased against master.
The last FIRST! commit fixes a strange thing whereby if an action has indirectObject set, but also returned an array in validIndirectsFor.... the 3rd pane would show.
Now, the 3rd pane only shows if you explicitly tab to it. This means that if you:

  • Use an action with indirectOptional (e.g. "some text" ⇥ Create File[...])
  • Tab to the (hidden) 3rd pane and set a folder
  • Tab away from the 3rd pane
  • Note the 3rd pane hides again.

I quite like this though because: a) it fixes the bug mentioned above, b) it makes the 'optional' factor even more obvious, c) cool that QS reveals and hides the pane. Kinda seems to me like 'peeking' round a corner, d) you can still see the iObject selected in the interface's 'description' label (depends on the interface - but works in Nostromo)

For @philostein, @studgeek (and potentially @daniels220) I've built a copy of this app, which you can test if you download it from here
Edit: this build isn't GateKeeper - proof

@studgeek

I'm not about to stop you, this is awesome :). It opens up a huge range of possibilities for not just AppleScript, but general scripting by wrapping it in AppleScript.

I made a few changes to the wiki, please correct if I made a mistake:

  • Separated "open" handlers and "argument" handlers and added explanation about the difference and when they are called (this is something that confused me when I first started doing QS AppleScripts).
  • Changed the "open" descriptions to say "receive files" rather than "send files".
  • Added syntax highlighting to the table.

Also, a question, open isn't really files anymore, is it? Its really a list of objects, limited to what is returned by get direct/indirect types. And are the items always in string form, or are they AppleScript "objects" in some cases (files)? I've wondered about that for a while (I'm also new to AppleScript).

Also, for AppleScript Types, there may be other types also that are introduced by other plugins, right? Just thinking of adding that to the wiki. Also, is there a way to figure them out that I could document also?

@studgeek

From @pjrobertson in #947

You can see the what 'handlers' exist in the wiki.
I'll add a section on 'returning' items to Quicksilver. At the moment you can only return strings (which Quicksilver will then inspect and potentially turn into a file/URL/string/email address etc).

So if you return a string quicksilver will parse it as the next direct object? Cool.

I'm also wondering about calling QS from outside an QS AppleScript. I know you can do it using the command line plugin, but is there an AppleScript version as well?

@studgeek

As I look at the closing of #947, I thought i would point out one other plugin vs AppleScript gap to consider for the future which is adding items to catalog.

For example, you could use a script to add to the catalog specific URLs, including different URLs schemes like youtube, itunes, messages, and custom ones (Apple URL Schemes).

So there would be a handler called "on get catalog type" that would return a type string and "on get catalog items" that would return an array of object strings. Those strings would then be objects that the scripts could indicate they support using "get direct/indirect types".

More generically, I could see cases where you want to introduce a whole new types like "monitor" or "USB Device" so you can write applescripts for that type. So you could actually introduce a new type and a whole set of actions for it with just a little applescript coding. You could also use the new types as indirect items that are appropriate for the script. For example you could add a "monitor" type, then have a "move to monitor" action that takes the indirect monitor name. The script's "on get catalog items" would scan and return the list of monitors.

Just a thought for the future...

@pjrobertson
Owner

Great, I'm glad it's useful! And thanks for making edits to the wiki. I agree with you on everything you've done. Much clearer :)

I've edited it a little further:

  • Added info on returning items.
  • Added info on on process text. We'd forgotten about that one.

Also, a question, open isn't really files anymore, is it? Its really a list of objects, limited to what is returned by get direct/indirect types

Yes, which makes me realise that on process text is a bit redundant now, but it has its uses, as it means you can easily set up a script that takes text without having to set any argument handlers (and for backwards compat of course)

And are the items always in string form, or are they AppleScript "objects" in some cases (files)?

Strings or files at the moment. Quicksilver will test to see if the objects are files. If they are it'll send file objects to the script, otherwise a 'string representation' of the object. (this can be misleading as it varies from object to object - to see the string representation of an object select it in the 1st pane, close QS, open QS then press '.' to enter text mode)

Also, for AppleScript Types, there may be other types also that are introduced by other plugins, right? Just thinking of adding that to the wiki. Also, is there a way to figure them out that I could document also?

Correct, I'd planned on adding some more to the list, but if you're happy to do so, here's the best way:

  • Download a debug build I've just made from here
  • Enable the QS menu item (prefs → Application → show menu bar item)
  • Select an object in the 1st pane then go to the menu item and choose Debug → Log object to console
  • Go to Console.app and look at the log. You'll see a data part like the following, it contains all the 'types' of the object on the left hand side. The example below shows a log for a Transmit Plugin object. You can see the object has a URL type and a 'QSTransmitSiteType' specific to the plugin.
    data =     {
        "Apple URL pasteboard type" = "sftp://qs:PasswordInKeychain@qsapp.com/home/qs/public_html";
        "QSTransmitSiteType" = "7E78BA8C-8EAD-4D42-85DA-12C7A2654AE3";
    };

So if you return a string quicksilver will parse it as the next direct object? Cool.

Yes :)

I'm also wondering about calling QS from outside an QS AppleScript. I know you can do it using the command line plugin, but is there an AppleScript version as well?

A good idea, I mentioned it in my comment on #947 that it's something to add in the future. Perhaps we should open another 'AppleScript specific' feature request issue.

Just to make sure - are you testing the version of Quicksilver I uploaded, and more importantly... is it working?!

Thanks for your help!

@skurfer
Owner

Just to make sure - are you testing the version of Quicksilver I uploaded, and more importantly... is it working?!

And even more importantly, does this mean I don't have to test it? ;-)

(I will. Grumble grumble.)

@skurfer
Owner

Looks good. Old scripts work, and the example you posted to move files works.

I still see the console message from #783 though. That's not going to hold this up, but I thought I'd mention that it's not fixed.

@pjrobertson
Owner
@skurfer
Owner
using terms from application "Quicksilver"
    on process text theText
        set logEntry to the quoted form of theText
        do shell script "/Users/rob/bin/tlog " & logEntry
    end process text
end using terms from

You obviously won't have tlog, but the example I posted on the original issues still generates the error. I don't remember the details, but I seem to have tracked it to QSObject_AEConversion.m. It can wait if you don't want to bother with it.

@philostein

Tried these changes, and I can set the object types for pane 1 and 3, but only if I compile the script so:

on «event DAEDgdob»
    return {"qs.process"}
end «event DAEDgdob»

on «event DAEDgiob»
    return {"NSFilenamesPboardType"}
end «event DAEDgiob»

The on get direct types syntax fails with a Expected function name, command name or function name but found “get”. compile error in ASE.

The on open files dObject with iObject syntax fails with a file dObject is illegal as a formal parameter. error. I don't know the raw code for that one.

I don't have any other QSes on my system now, 'ghost' or otherwise. :)

FYI: AS QS crashes 5 or 6 times on launch, but eventually it stays open and is generally fine until the next relaunch.

@pjrobertson
Owner

@philostein - thanks for trying this out. The errors you have are most definitely related to the AppleScript editor picking up the wrong QS.

If you go to AppleScript Editor → File → Open Dictionary, how many 'Quicksilvers' are in the list?
If you open one of the dictionaries and got to the 'script handlers' section do you see the new ones I've defined?

Thanks for trying this out - so do the scripts work (when compiled as you say), and any suggestions/problems? :)

@daniels220

You guys are amazing, thank you so much. Looking forward to the final release of these changes.

@philostein

There's only 1 QS in the dictionaries list, and the new handlers are there, but…

relaunching ASE did the trick! Must have a dictionary cache or some such. Annoying.

These changes work great! I was able to define different object types for the scripts to be valid for. They don't appear for other object types. The open files handler script only appears for files.

I'm not sure about the return 3 hidden 3rd pane part. If the user doesn't tab to pane 3, it's not possible to know what iObject is. QS also seems to randomly populate pane 3. Once, the example file mover script tried to move a file to Reminders.app, and another time to an Applescript file.

I'll update some of my scripts to use the object types and open files handlers, and then post back here.

One idea: object types for specific objects such as Sparrow.app or Todo.txt - then custom actions could be dragged to the top of Actions preferences and appear first for only those objects. I realise though that you're extending how QS handles generic object types, so I understand if my idea is impractical. :)

@skurfer
Owner

So everyone good with this?

@studgeek

I have been traveling and have not had a chance to test it, but am very excited for it none the less. I can create a separate issue for the "adding to the catalog/types" idea I listed above.

@skurfer skurfer merged commit f5da5b8 into quicksilver:master
@skurfer
Owner

Might need some help with the release notes on this one. All I have for the moment is:

@philostein

I'd like to make a command that goes like this: [email address]>[action]>[file]
But if use the following with on open files _emailAddress with _attachment, QS crashes:

on get direct types
    return {"NSStringPboardType"}
end get direct types

on get indirect types
    return {"NSFilenamesPboardType"}
end get indirect types

This would be useful to get an email address from Contacts as a recipient for an attachment in pane 3.

The other way round works well ([file]>[action]>[typed email address].

Just sent the crash report in QS.

@philostein

Fixed it, used on process text _emailAddress with _attachment instead, and it worked perfectly!

This is really useful. :)

@pjrobertson
Owner
@philostein

Sure:

using terms from application "Quicksilver"
on get direct types
    return {"NSStringPboardType"}
end get direct types

on get indirect types
    return {"NSFilenamesPboardType"}
end get indirect types

on open files _emailAddress with _attachment
    try
        -- Put your email address between the following quotes:
        set _myAddress to "johndoe@gmail.com"
        tell me to set _body to (current date) as text

        set _attachmentText to _attachment as text
        set _path to POSIX path of _attachmentText

        tell application "Sparrow"
            set _outgoingMessage to make new outgoing message with properties {subject:_attachmentText, content:_body, sender:_myAddress}

            tell _outgoingMessage to make new to recipient with properties {address:_emailAddress}
            tell _outgoingMessage to make new mail attachment with properties {filename:_path}

            sendmessage _outgoingMessage with to recipient and mail attachment
        end tell

    on error a number b
        activate
        display dialog a with title "Sparrow Attachment"
    end try
end open files

on get argument count
    return 2
end get argument count
end using terms from
@studgeek

@pjrobertson I'm interested in getting the object types again Is there a way to get it in a beta/release build. Failing that, how do I create a debug build? I can follow http://qsapp.com/wiki/Building_Quicksilver, but what else do I need to do to get the object types in the log.

I'll add I agree with #1817 that it would be very handy to just add an action for this (maybe one that is off by default).

@pjrobertson
Owner

Hmmm... the only way that I know of is to use a debug build. The debug build adds an option in the mneu bar icon called 'log object to console'. That will show all the types that exist for a single object (that's selected in QS' first pane)
Building should be pretty easy if you follow those instructions. I've just updated them for the latest Xcode 6 release

@skurfer
Owner

I don’t suppose you figured out how to sign the app for Gatekeeper with Xcode 6. :smiley: Yes, they changed it again, and I can’t even get it to sign on my wife’s machine with Xcode 5.

@pjrobertson
Owner

hmm.... nope not really :(
Is xcodebuild broken? You might need to update your certificates? I'm assuming it works if you add the certificate in the 'build settings', but we know that that's no good for git/sharing

@tiennou
Owner

Actually, I think you can use Developer.xcconfig for that. Bonus points if you can manage to make a nice section :

# Set this env variable to perform signed builds
BUILD_CERTIFICATE_ENV_VAR = 

Commit the file with those change for the posterity, and then make git ignore subsequent changes for this file — I know there's a way to exclude a file while having it still checked out.

@pjrobertson
Owner

I thought the env variable was
env x='() { :;}; echo vulnerable' bash -c 'echo hello'
;-)

@skurfer
Owner

The Tools/qsrelease script already takes care of setting up the environment. The problem is much more complicated. Long story short, I found some discussion on-line that led me to blame top-level files in the frameworks. If I do this before signing, it works again:

rm Quicksilver.app/Contents/Frameworks/*.framework/PkgInfo

The app appears to run, so I guess we don’t need those files. Can we change the framework build process so they don’t get created in the first place? I have some documentation open for that, but haven’t had a chance to read it.

@studgeek

FYI, I documented the current instructions for looking up other object types in http://qsapp.com/wiki/AppleScript_Types#Other_Quicksilver_Object_Types (along with basic UTI stuff).

@daniels220

Hey, sweet, exactly what I was looking for! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 1, 2013
  1. @pjrobertson

    Don't show the 3rd pane for indirectOptional actions

    pjrobertson authored
    Only show it if the user explicitly tabs over to it
  2. @pjrobertson

    add open files applescript action for Quicksilver to deal with files …

    pjrobertson authored
    …in AppleScripts (and iObjects)
  3. @pjrobertson

    remove a deprecated method

    pjrobertson authored
  4. @pjrobertson

    Add code to process iObjects that are files for the 'on process' action

    pjrobertson authored
    Also, fix the setting of the actionDict's Action Handler to ensure the action displays for the correct dObjects (validates objects correctly)
    Set file iObjects as pathDescriptors in AppleScript
  5. @pjrobertson
  6. @pjrobertson

    Add 'get .Object types' handlers to the scripting suite

    pjrobertson authored
    Allows you to return a list of valid iObject/dObject types for the script
  7. @pjrobertson
  8. @pjrobertson

    Parse direct types from AppleScripts

    pjrobertson authored
    Plus: Comments, tidy-up and DRY
  9. @pjrobertson
  10. @pjrobertson
  11. @pjrobertson

    Add the ability to make the indirect arg optional

    pjrobertson authored
    return `3` in the `get argument count` handler
This page is out of date. Refresh to see the latest.
View
2  Quicksilver/Code-QuickStepFoundation/NSAppleScript_BLTRExtensions.m
@@ -197,7 +197,7 @@ + (NSAppleEventDescriptor *)descriptorWithPath:(NSString *)path {
@implementation NSAppleScript (FilePeeking)
+ (NSArray *)validHandlersFromArray:(NSArray *)array inScriptFile:(NSString *)path {
- NSData *scriptData = [NSData dataWithContentsOfMappedFile:path];
+ NSData *scriptData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:path] options:NSDataReadingMappedAlways error:nil];
if (![scriptData length]) {
NDResourceFork *resource = [NDResourceFork resourceForkForReadingAtPath:path];
scriptData = [resource dataForType:'scpt' Id:128];
View
3  Quicksilver/Code-QuickStepInterface/QSResizingInterfaceController.m
@@ -45,8 +45,7 @@ - (void)adjustWindow:(id)sender {
} else {
NSResponder *firstResponder = [[self window] firstResponder];
if (firstResponder == iSelector
- || firstResponder == [iSelector currentEditor]
- || ([iSelector objectValue] != nil && ![[iSelector objectValue] objectForType:QSTextProxyType]) ) {
+ || firstResponder == [iSelector currentEditor]) {
[self expandWindow:sender];
return;
}
View
16 Quicksilver/PlugIns-Main/QSCorePlugIn/Code/QSAppleScriptActions.h
@@ -13,14 +13,28 @@
# define kAppleScriptOpenFilesAction @"AppleScriptOpenFilesAction"
# define kAppleScriptRunAction @"AppleScriptRunAction"
+// Used as a prefix to the identifier for all AppleScript action objects. Distinguishes them from plain old files
+# define kAppleScriptActionIDPrefix @"[Action]:"
+
+
# define kAppleScriptRunTextAction @"AppleScriptRunTextAction"
#define kQSScriptSuite 'DAED'
+// opnt = OPeN Text
#define kQSOpenTextScriptCommand 'opnt'
+// opfl = OPen FiLe
+#define kQSOpenFileScriptCommand 'opfl'
+// garc = Get ARgument Count
#define kQSGetArgumentCountCommand 'garc'
-#define kQSOpenTextIndirectParameter 'IdOb'
+// giob = Get Indirect OBject
+#define kQSGetIndirectObjectTypesCommand 'giob'
+// gdob = Get Direct OBject
+#define kQSGetDirectObjectTypesCommand 'gdob'
+// IdOb = Indirect Object
+#define kQSIndirectParameter 'IdOb'
@interface QSAppleScriptActions : QSActionProvider
- (QSObject*)runAppleScript:(NSString *)scriptPath withArguments:(QSObject *)iObject;
+-(NSAppleEventDescriptor *)eventDescriptorForObject:(QSObject *)iObject;
@end
View
197 Quicksilver/PlugIns-Main/QSCorePlugIn/Code/QSAppleScriptActions.m
@@ -23,7 +23,7 @@
@implementation QSAppleScriptActions
- (QSAction *)scriptActionForPath:(NSString *)path {
- NSArray *handlers = [NSAppleScript validHandlersFromArray:[NSArray arrayWithObjects:@"aevtoapp", @"DAEDopnt", @"aevtodoc", nil] inScriptFile:path];
+ NSArray *handlers = [NSAppleScript validHandlersFromArray:[NSArray arrayWithObjects:@"aevtoapp", @"DAEDopnt", @"aevtodoc",@"DAEDopfl", nil] inScriptFile:path];
NSMutableDictionary *actionDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
NSStringFromClass([self class]), kActionClass,
@@ -32,17 +32,48 @@ - (QSAction *)scriptActionForPath:(NSString *)path {
[NSNumber numberWithBool:YES], kActionDisplaysResult,
nil];
- if ([handlers containsObject:@"DAEDopnt"]) {
- [actionDict setObject:[NSArray arrayWithObject:QSTextType] forKey:kActionDirectTypes];
- [actionDict setObject:@"QSOpenTextEventPlaceholder" forKey:kActionHandler];
- [actionDict setObject:[NSArray arrayWithObject:QSTextType] forKey:kActionIndirectTypes];
+ // try to get the valid direct/indirect types from the AppleScript's 'get direct/indirect types' handler to set on the actionDict
+ NSArray *validDirectTypes = [self validDirectTypesForScript:path];
+ NSArray *validIndirectTypes = [self validIndrectTypesForScript:path];
+
+ // get the argument count of the script. 1 = dObj only. 2 = dObj + iObj. 3 = indirect Optional
+ NSInteger argumentCount = [self argumentCountForScript:path];
+
+ if ([handlers containsObject:@"aevtodoc"] || [handlers containsObject:@"DAEDopfl"]) {
+ // Only set the type if the user hasn't specified any (i.e. hasn't set the 'get direct types' handler)
+ if (!validDirectTypes) {
+ validDirectTypes = [NSArray arrayWithObject:QSFilePathType];
+ }
+ [actionDict setObject:[handlers containsObject:@"DAEDopfl"] ? @"QSOpenFileWithEventPlaceholder" : @"QSOpenFileEventPlaceholder" forKey:kActionHandler];
+ if (!validIndirectTypes) {
+ validIndirectTypes = [NSArray arrayWithObject:QSFilePathType];
+ }
}
- if ([handlers containsObject:@"aevtodoc"]) {
- [actionDict setObject:[NSArray arrayWithObject:QSFilePathType] forKey:kActionDirectTypes];
- [actionDict setObject:@"QSOpenFileEventPlaceholder" forKey:kActionHandler];
+ if ([handlers containsObject:@"DAEDopnt"] && ![handlers containsObject:@"DAEDopfl"]) {
+ // Only set the type if the user hasn't specified any (i.e. hasn't set the 'get direct types' handler)
+ if (!validDirectTypes) {
+ validDirectTypes = [NSArray arrayWithObject:QSTextType];
+ }
+ [actionDict setObject:@"QSOpenTextEventPlaceholder" forKey:kActionHandler];
+ if (!validIndirectTypes) {
+ validIndirectTypes = [NSArray arrayWithObject:QSTextType];
+ }
}
+ if ([validDirectTypes count]) {
+ [actionDict setObject:validDirectTypes forKey:kActionDirectTypes];
+ }
+ if ([validIndirectTypes count]) {
+ [actionDict setObject:validIndirectTypes forKey:kActionIndirectTypes];
+ }
+ if (argumentCount == 3) {
+ // argumentCount == 3 means indirect optional
+ [actionDict setObject:[NSNumber numberWithBool:YES] forKey:kActionIndirectOptional];
+ argumentCount = 2;
+ }
+ [actionDict setObject:[NSNumber numberWithInteger:argumentCount] forKey:kActionArgumentCount];
+
NSString *actionName = [[path lastPathComponent] stringByDeletingPathExtension];
- QSAction *action = [QSAction actionWithDictionary:actionDict identifier:[@"[Action]:" stringByAppendingString:path]];
+ QSAction *action = [QSAction actionWithDictionary:actionDict identifier:[kAppleScriptActionIDPrefix stringByAppendingString:path]];
[action setName:actionName];
[action setObject:path forMeta:kQSObjectIconName];
return action;
@@ -140,7 +171,7 @@ - (QSObject*)runAppleScript:(NSString *)scriptPath withArguments:(QSObject *)iOb
} else {
event = [[NSAppleEventDescriptor alloc] initWithEventClass:kQSScriptSuite eventID:kQSOpenTextScriptCommand targetDescriptor:targetAddress returnID:kAutoGenerateReturnID transactionID:kAnyTransactionID];
[event setParamDescriptor:[NSAppleEventDescriptor descriptorWithString:[iObject stringValue]] forKeyword:keyDirectObject];
- [event setDescriptor:[NSAppleEventDescriptor descriptorWithString:@""] forKeyword:kQSOpenTextIndirectParameter];
+ [event setDescriptor:[NSAppleEventDescriptor descriptorWithString:@""] forKeyword:kQSIndirectParameter];
}
[targetAddress release];
returnDesc = [script executeAppleEvent:event error:&errorInfo];
@@ -166,6 +197,17 @@ - (QSObject *)objectForDescriptor:(NSAppleEventDescriptor *)desc {
return nil;
}
+-(NSAppleEventDescriptor *)eventDescriptorForObject:(QSObject *)object {
+ NSArray *paths = [object validPaths];
+ NSAppleEventDescriptor *objectDescriptor = nil;
+ if ([paths count] > 0) {
+ objectDescriptor = [NSAppleEventDescriptor aliasListDescriptorWithArray:paths];
+ } else {
+ objectDescriptor = [NSAppleEventDescriptor descriptorWithString:[object stringValue]];
+ }
+ return objectDescriptor;
+}
+
- (QSObject *)performAction:(QSAction *)action directObject:(QSObject *)dObject indirectObject:(QSObject *)iObject {
NSDictionary *dict = [action objectForType:QSActionType];
NSString *scriptPath = [dict objectForKey:kActionScript];
@@ -186,10 +228,19 @@ - (QSObject *)performAction:(QSAction *)action directObject:(QSObject *)dObject
NSDictionary *errorDict = nil;
NSAppleScript *script = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:scriptPath] error:&errorDict];
- if ([handler isEqualToString:@"QSOpenFileEventPlaceholder"]) {
+ if ([handler isEqualToString:@"QSOpenFileEventPlaceholder"] || [handler isEqualToString:@"QSOpenFileWithEventPlaceholder"]) {
+ event = [[NSAppleEventDescriptor alloc] initWithEventClass:[handler isEqualToString:@"QSOpenFileEventPlaceholder"] ? kCoreEventClass :kQSScriptSuite
+ eventID:[handler isEqualToString:@"QSOpenFileEventPlaceholder"] ? kAEOpenDocuments : kQSOpenFileScriptCommand
+ targetDescriptor:targetAddress
+ returnID:kAutoGenerateReturnID
+ transactionID:kAnyTransactionID];
NSArray *files = [dObject validPaths];
- event = [[NSAppleEventDescriptor alloc] initWithEventClass:kCoreEventClass eventID:kAEOpenDocuments targetDescriptor:targetAddress returnID:kAutoGenerateReturnID transactionID:kAnyTransactionID];
[event setParamDescriptor:[NSAppleEventDescriptor aliasListDescriptorWithArray:files] forKeyword:keyDirectObject];
+ if(iObject && ![[iObject primaryType] isEqualToString:QSTextProxyType]) {
+ NSAppleEventDescriptor *iObjectDescriptor = [self eventDescriptorForObject:iObject];
+ [event setDescriptor:iObjectDescriptor forKeyword:kQSIndirectParameter];
+ }
+
} else if ([handler isEqualToString:@"QSOpenTextEventPlaceholder"]) {
event = [[NSAppleEventDescriptor alloc] initWithEventClass:kQSScriptSuite
eventID:kQSOpenTextScriptCommand
@@ -197,20 +248,8 @@ - (QSObject *)performAction:(QSAction *)action directObject:(QSObject *)dObject
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];
[event setParamDescriptor:[NSAppleEventDescriptor descriptorWithString:[dObject stringValue]] forKeyword:keyDirectObject];
- NSArray *indirectPaths = [iObject validPaths];
- NSAppleEventDescriptor *iObjectDescriptor = nil;
- NSUInteger indirectPathCount = [indirectPaths count];
- if (indirectPathCount == 1) {
- iObjectDescriptor = [NSAppleEventDescriptor descriptorWithString:[indirectPaths objectAtIndex:0]];
- } else if (indirectPathCount > 1) {
- iObjectDescriptor = [NSAppleEventDescriptor listDescriptor];
- for (NSString *path in indirectPaths) {
- [iObjectDescriptor insertDescriptor:[NSAppleEventDescriptor descriptorWithString:path] atIndex:0];
- }
- } else {
- iObjectDescriptor = [NSAppleEventDescriptor descriptorWithString:[iObject stringValue]];
- }
- [event setDescriptor:iObjectDescriptor forKeyword:kQSOpenTextIndirectParameter];
+ NSAppleEventDescriptor *iObjectDescriptor = [self eventDescriptorForObject:iObject];
+ [event setDescriptor:iObjectDescriptor forKeyword:kQSIndirectParameter];
} else {
id object;
NSArray *types = [action directTypes];
@@ -247,27 +286,77 @@ - (NSArray *)validActionsForDirectObject:(QSObject *)dObject indirectObject:(QSO
return nil;
}
+
+-(NSArray *)validDirectTypesForScript:(NSString *)path {
+ return [self typeArrayForScript:path forHandler:@"DAEDgdob"];
+}
+
+-(NSArray *)validIndrectTypesForScript:(NSString *)path {
+ return [self typeArrayForScript:path forHandler:@"DAEDgiob"];
+}
+
- (NSArray *)validIndirectObjectsForAction:(NSString *)action directObject:(QSObject *)dObject {
- return ([action isEqualToString:kAppleScriptOpenTextAction] ? [NSArray arrayWithObject:[QSObject textProxyObjectWithDefaultValue:@""]] : nil);
+ if ([action isEqualToString:kAppleScriptOpenTextAction]) {
+ return [NSArray arrayWithObject:[QSObject textProxyObjectWithDefaultValue:@""]];
+ } else if ([action isEqualToString:kAppleScriptOpenFilesAction]) {
+ return [QSLib arrayForType:QSFilePathType];
+ };
+ if ([action rangeOfString:kAppleScriptActionIDPrefix].location != NSNotFound) {
+ // Applescript action, so attempt to get the valid types from the file itself ('get indirect types' handler)
+ return [self validIndirectObjectsForAppleScript:action directObject:dObject];
+ }
+ return nil;
}
-- (NSInteger)argumentCountForAction:(NSString *)actionId {
- NSInteger argumentCount = 1;
+-(NSArray *)validIndirectObjectsForAppleScript:(NSString *)script directObject:(QSObject *)dObject {
+ NSString *scriptPath = [self scriptPathForID:script];
+
+ id indirectTypes = [self validIndrectTypesForScript:scriptPath];
+ if (indirectTypes) {
+ NSMutableArray *indirectObjects = [NSMutableArray array];
+ for (NSString *type in indirectTypes) {
+ [indirectObjects addObjectsFromArray:[QSLib arrayForType:type]];
+ }
+ return [[indirectObjects copy] autorelease];
+ }
+ return nil;
+}
+
+-(NSString *)scriptPathForID:(NSString *)actionId {
QSAction *action = [QSAction actionWithIdentifier:actionId];
NSString *scriptPath = [action objectForKey:kActionScript];
+
+ if (!scriptPath) {
+ return nil;
+ }
- if ([actionId isEqualToString:kAppleScriptOpenTextAction] || [actionId isEqualToString:kAppleScriptOpenFilesAction])
- argumentCount = 2;
+ if ([scriptPath hasPrefix:@"/"] || [scriptPath hasPrefix:@"~"]) {
+ scriptPath = [scriptPath stringByStandardizingPath];
+ } else {
+ scriptPath = [[action bundle] pathForResource:[scriptPath stringByDeletingPathExtension] ofType:[scriptPath pathExtension]];
+ }
+ return scriptPath;
+}
+
+- (NSInteger)argumentCountForAction:(NSString *)actionId {
- // TODO: figure out why all this code is here - scriptPath always seems to be nil
+ if ([actionId isEqualToString:kAppleScriptOpenTextAction] || [actionId isEqualToString:kAppleScriptOpenFilesAction]) {
+ return 2;
+ }
+
+ NSString *scriptPath = [self scriptPathForID:actionId];
+ if (!scriptPath) {
+ // can't find the script so just return 1 and hope for the best
+ return 1;
+ }
+
+ // this should never realistically be called, since the script argument count is set in scriptActionForPath: (above)
+ return [self argumentCountForScript:scriptPath] > 1 ? 2 : 1;
+}
- if (!scriptPath)
- return argumentCount;
- if ([scriptPath hasPrefix:@"/"] || [scriptPath hasPrefix:@"~"])
- scriptPath = [scriptPath stringByStandardizingPath];
- else
- scriptPath = [[action bundle] pathForResource:[scriptPath stringByDeletingPathExtension] ofType:[scriptPath pathExtension]];
+-(NSInteger)argumentCountForScript:(NSString *)scriptPath {
+ NSInteger argumentCount = 1;
NSArray *handlers = [NSAppleScript validHandlersFromArray:[NSArray arrayWithObject:@"DAEDgarc"] inScriptFile:scriptPath];
if( handlers != nil && [handlers count] != 0 ) {
@@ -291,11 +380,35 @@ - (NSInteger)argumentCountForAction:(NSString *)actionId {
[script release];
}
-
-#ifdef DEBUG
- NSLog(@"argument count for %@ is %ld", actionId, (long)argumentCount);
-#endif
return argumentCount;
}
+
+// Retrieves an array of types from either the 'get direct types' or 'get indirect types' AppleScript handlers (depending on the input parameter 'handler')
+-(NSArray *)typeArrayForScript:(NSString *)scriptPath forHandler:(NSString *)handler {
+ id types = nil;
+ NSArray *handlers = [NSAppleScript validHandlersFromArray:[NSArray arrayWithObject:handler] inScriptFile:scriptPath];
+ if( handlers != nil && [handlers count] != 0 ) {
+ NSAppleEventDescriptor *event;
+ int pid = [[NSProcessInfo processInfo] processIdentifier];
+ NSAppleEventDescriptor* targetAddress = [[NSAppleEventDescriptor alloc] initWithDescriptorType:typeKernelProcessID bytes:&pid length:sizeof(pid)];
+
+ NSDictionary *errorDict = nil;
+ NSAppleScript *script = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:scriptPath] error:&errorDict];
+
+ event = [[NSAppleEventDescriptor alloc] initWithEventClass:kQSScriptSuite eventID:[handler isEqualToString:@"DAEDgdob"] ? kQSGetDirectObjectTypesCommand : kQSGetIndirectObjectTypesCommand targetDescriptor:targetAddress returnID:kAutoGenerateReturnID transactionID:kAnyTransactionID];
+
+ NSAppleEventDescriptor *result = [script executeAppleEvent:event error:&errorDict];
+ if( result ) {
+ // Convert the AS list type to an array
+ types = (NSArray *)[result arrayValue];
+ } else if( errorDict != nil )
+ NSLog(@"error %@", errorDict);
+ [event release];
+ [targetAddress release];
+ [script release];
+ }
+ return types;
+}
+
@end
View
16 Quicksilver/Scripting/Quicksilver.sdef
@@ -75,15 +75,29 @@
</suite>
<suite name="Script Handlers" code="DAEH" description="Handlers for Actions and other scripts.">
<cocoa name="QuicksilverHandlers"/>
+ <command name="open files" code="DAEDopfl" description="Open file(s) from Quicksilver">
+ <cocoa class="NSScriptCommand" />
+ <direct-parameter description="File to open" type="any" />
+ <parameter name="with" code="IdOb" description="indirect object" type="any" optional="yes" />
+ <result description="value to return to Quicksilver" type="any" />
+ </command>
<command name="process text" code="DAEDopnt" description="Process some text. Scripts with this handler gain a &apos;Process Text&apos; action">
<cocoa class="NSScriptCommand"/>
<direct-parameter description="text to process" type="text"/>
<parameter name="with" code="IdOb" description="indirect object" type="any" optional="yes"/>
<result description="value to return to Quicksilver" type="any"/>
</command>
- <command name="get argument count" code="DAEDgarc" description="Get the argument count for this action. Scripts with this handler can customize their argument count. Valid values are 1, 2.">
+ <command name="get argument count" code="DAEDgarc" description="Get the argument count for this action. Scripts with this handler can customize their argument count. Valid values are: 1 = 1st pane only; 2 = 1st pane and 3rd pane; 3 = 1st pane, and 3rd pane (optional).">
<cocoa class="NSScriptCommand"/>
<result description="value to return to Quicksilver" type="integer"/>
</command>
+ <command name="get indirect types" code="DAEDgiob" description="Get the valid indirect (3rd pane) object types for this action. Scripts with this handler can customize the types of objects displayed in Quicksilver's 3rd pane by returning a list of types supported. Valid values are shown at http://qsapp.com/wiki/AppleScript_Types">
+ <cocoa class="NSScriptCommand"/>
+ <result description="value to return to Quicksilver" type="list"/>
+ </command>
+ <command name="get direct types" code="DAEDgdob" description="Get the valid direct (1st pane) object types for which this action will appear. Scripts with this handler can customize for which types of objects the action appears for, by returning a list of types supported. Valid values are shown at http://qsapp.com/wiki/AppleScript_Types">
+ <cocoa class="NSScriptCommand"/>
+ <result description="value to return to Quicksilver" type="list"/>
+ </command>
</suite>
</dictionary>
View
56 Quicksilver/Scripting/QuicksilverHandlers.scriptSuite
@@ -19,6 +19,62 @@
<key>Type</key>
<string>NSNumber&lt;Int&gt;</string>
</dict>
+ <key>GetDirectTypes</key>
+ <dict>
+ <key>AppleEventClassCode</key>
+ <string>DAED</string>
+ <key>AppleEventCode</key>
+ <string>gdob</string>
+ <key>CommandClass</key>
+ <string>NSScriptCommand</string>
+ <key>ResultAppleEventCode</key>
+ <string>****</string>
+ <key>Type</key>
+ <string>list</string>
+ </dict>
+ <key>GetIndirectTypes</key>
+ <dict>
+ <key>AppleEventClassCode</key>
+ <string>DAED</string>
+ <key>AppleEventCode</key>
+ <string>giob</string>
+ <key>CommandClass</key>
+ <string>NSScriptCommand</string>
+ <key>ResultAppleEventCode</key>
+ <string>****</string>
+ <key>Type</key>
+ <string>list</string>
+ </dict>
+ <key>OpenFiles</key>
+ <dict>
+ <key>AppleEventClassCode</key>
+ <string>DAED</string>
+ <key>AppleEventCode</key>
+ <string>opfl</string>
+ <key>Arguments</key>
+ <dict>
+ <key>with</key>
+ <dict>
+ <key>AppleEventCode</key>
+ <string>IdOb</string>
+ <key>Optional</key>
+ <string>YES</string>
+ <key>Type</key>
+ <string>NSObject</string>
+ </dict>
+ </dict>
+ <key>CommandClass</key>
+ <string>NSScriptCommand</string>
+ <key>ResultAppleEventCode</key>
+ <string>****</string>
+ <key>Type</key>
+ <string>NSObject</string>
+ <key>UnnamedArgument</key>
+ <dict>
+ <key>Type</key>
+ <string>NSObject</string>
+ </dict>
+ </dict>
<key>ProcessText</key>
<dict>
<key>AppleEventClassCode</key>
View
38 Quicksilver/Scripting/QuicksilverHandlers.scriptTerminology
@@ -7,10 +7,46 @@
<key>GetArgumentCount</key>
<dict>
<key>Description</key>
- <string>Get the argument count for this action. Scripts with this handler can customize their argument count. Valid values are 1, 2.</string>
+ <string>Get the argument count for this action. Scripts with this handler can customize their argument count. Valid values are: 1 = 1st pane only; 2 = 1st pane and 3rd pane; 3 = 1st pane, and 3rd pane (optional).</string>
<key>Name</key>
<string>get argument count</string>
</dict>
+ <key>GetDirectTypes</key>
+ <dict>
+ <key>Description</key>
+ <string>Get the valid direct (1st pane) object types for which this action will appear. Scripts with this handler can customize for which types of objects the action appears for, by returning a list of types supported. Valid values are shown at http://qsapp.com/wiki/AppleScript_Types</string>
+ <key>Name</key>
+ <string>get direct types</string>
+ </dict>
+ <key>GetIndirectTypes</key>
+ <dict>
+ <key>Description</key>
+ <string>Get the valid indirect (3rd pane) object types for this action. Scripts with this handler can customize the types of objects displayed in Quicksilver's 3rd pane by returning a list of types supported. Valid values are shown at http://qsapp.com/wiki/AppleScript_Types</string>
+ <key>Name</key>
+ <string>get indirect types</string>
+ </dict>
+ <key>OpenFiles</key>
+ <dict>
+ <key>Arguments</key>
+ <dict>
+ <key>with</key>
+ <dict>
+ <key>Description</key>
+ <string>indirect object</string>
+ <key>Name</key>
+ <string>with</string>
+ </dict>
+ </dict>
+ <key>Description</key>
+ <string>Open file(s) from Quicksilver</string>
+ <key>Name</key>
+ <string>open files</string>
+ <key>UnnamedArgument</key>
+ <dict>
+ <key>Description</key>
+ <string>File to open</string>
+ </dict>
+ </dict>
<key>ProcessText</key>
<dict>
<key>Arguments</key>
View
41 Quicksilver/Scripting/QuicksilverScripting.r
@@ -472,6 +472,23 @@ resource 'aete' (0, "Quicksilver") {
{
/* Events */
+ "open files",
+ "Open file(s) from Quicksilver",
+ 'DAED', 'opfl',
+ '****',
+ "value to return to Quicksilver",
+ replyRequired, singleItem, notEnumerated, Reserved13,
+ '****',
+ "File to open",
+ directParamRequired,
+ singleItem, notEnumerated, Reserved13,
+ {
+ "with", 'IdOb', '****',
+ "indirect object",
+ optional,
+ singleItem, notEnumerated, Reserved13
+ },
+
"process text",
"Process some text. Scripts with this handler gain a 'Process Text' action",
'DAED', 'opnt',
@@ -490,7 +507,7 @@ resource 'aete' (0, "Quicksilver") {
},
"get argument count",
- "Get the argument count for this action. Scripts with this handler can customize their argument count. Valid values are 1, 2.",
+ "Get the argument count for this action. Scripts with this handler can customize their argument count. Valid values are: 1 = 1st pane only; 2 = 1st pane and 3rd pane; 3 = 1st pane, and 3rd pane (optional).",
'DAED', 'garc',
'long',
"value to return to Quicksilver",
@@ -498,6 +515,28 @@ resource 'aete' (0, "Quicksilver") {
dp_none__,
{
+ },
+
+ "get indirect types",
+ "Get the valid indirect (3rd pane) object types for this action. Scripts with this handler can customize the types of objects displayed in Quicksilver's 3rd pane by returning a list of types supported. Valid values are shown at http://qsapp.com/wiki/AppleScript_Types",
+ 'DAED', 'giob',
+ '****',
+ "value to return to Quicksilver",
+ replyRequired, singleItem, notEnumerated, Reserved13,
+ dp_none__,
+ {
+
+ },
+
+ "get direct types",
+ "Get the valid direct (1st pane) object types for which this action will appear. Scripts with this handler can customize for which types of objects the action appears for, by returning a list of types supported. Valid values are shown at http://qsapp.com/wiki/AppleScript_Types",
+ 'DAED', 'gdob',
+ '****',
+ "value to return to Quicksilver",
+ replyRequired, singleItem, notEnumerated, Reserved13,
+ dp_none__,
+ {
+
}
},
{
Something went wrong with that request. Please try again.