Closed n8henrie closed 2 years ago
I've made a little progress on this by replacing NSAppleScript
with OSAScript
from OSAKit/OSAKit.h
; it seems like the AppleScript-related functionality continues to work as a drop-in replacement, but should give QS the ability to use other scripting language like JavaScript.
but having a hard time finding much documentation on the little magic strings like aevtoapp
that are used to find handlers. A few relevant links:
An example of how Hammerspoon is doing it:
Are these better documented somewhere by Apple?: @"aevtoapp", @"DAEDopnt", @"aevtodoc", @"DAEDopfl"]
Getting no hits with https://developer.apple.com/search/?q=aevtoapp&type=Documentation or https://duckduckgo.com/?t=ffab&q=aevtoapp+site%3Ahttps%3A%2F%2Fdeveloper.apple.com
Are these better documented somewhere by Apple?: @"aevtoapp", @"DAEDopnt", @"aevtodoc", @"DAEDopfl"]
No, these are defined in https://github.com/quicksilver/Quicksilver/blob/master/Quicksilver/Scripting/Quicksilver.sdef; each app that wants to implement "scriptability" can apparently define their own magic strings in this way.
Moreover, the key phrases from the AppleScript Dictionary such as on process text
get compiled to these strings in the saved AppleScript. For example, if one opens and saves this as QSTest.scpt
in Script Editor.app:
using terms from application "Quicksilver"
end using terms from
You could then see:
$ strings QStest.scpt
FasdUAS 1.101.10
daed
alis
NateSSD
Quicksilver.app
Debug
-/:private:tmp:QS:build:Debug:Quicksilver.app/
*private/tmp/QS/build/Debug/Quicksilver.app
ascr
But if you expand it to:
using terms from application "Quicksilver"
on process text
end process text
end using terms from
You'll now see DAEDopnt
showing up:
$ strings QStest.scpt
FasdUAS 1.101.10
.DAEDopnt****
utxt
daed
alis
NateSSD
Quicksilver.app
Debug
-/:private:tmp:QS:build:Debug:Quicksilver.app/
*private/tmp/QS/build/Debug/Quicksilver.app
.DAEDopnt****
utxt
.DAEDopnt****
utxt
ascr
Interestingly, Quicksilver seems to determine what handlers a script supports by doing a plain old search for the magic string^1.
I was extremely surprised this morning to discover that one can run JXA scripts with no modifications to Quicksilver just by including these magic strings somewhere in the script. In contrast to AppleScript, when saved in Script Editor (configured as JavaScript), there is relatively little "compilation" done to JXA scripts -- one can cat
them and it's mostly unchanged. I think this is why the same "search for the string" strategy works if you just put the magic string in your script.
For example, the below should work find in a current build of QS; the magic strings like DAEDopnt
could be included anywhere, but I think they make sense as a comment above the function in question.
const app = Application.currentApplication()
// When *not* running from XCode, you can also use:
// const app = Application("Quicksilver")
// but from XCode, this will hang on `app.showNotification` or `app.displayDialog`
app.includeStandardAdditions = true
// DAEDopnt
function processText(dobj, {with: iobj}) {
app.showNotification(typeof(iobj))
app.showNotification("dobj: " + dobj + ", iobjc: " + iobj)
return "bar"
}
// DAEDgdob
function getDirectTypes() {
return ["NSStringPboardType", "NSFilenamesPboardType"]
}
// DAEDgiob
function getIndirectTypes() {
return ["NSFilenamesPboardType"]
}
// Remove the asterisk to enable this handler; without disrupting the magic string,
// QS will try to use it preferentially
// D*AEDopfl
// function openFiles(dobj, {with: iobj}) {
// return
// }
// DAEDgarc
function getArgumentCount() {
return 3
}
Once I'd figured this out, it was pretty each to add the JavaScript version of the functions in question to the list of magic strings: https://github.com/quicksilver/Quicksilver/pull/2745
With that PR, the below JXA works as expected, including indirect
const app = Application.currentApplication()
function processText(dobj, {with: iobj}) {
app.showNotification(typeof(iobj))
app.showNotification("dobj: " + dobj + ", iobjc: " + iobj)
return "bar"
}
function getDirectTypes() {
return ["NSStringPboardType", "NSFilenamesPboardType"]
}
function getIndirectTypes() {
return ["NSFilenamesPboardType"]
}
function getArgumentCount() {
return 3
}
openFiles
also seems to work, but I've left it out because it seems to override the other logic and disables it from accepting text input:
function openFiles(dobj, {with: iobj}) {
app.showNotification("dobj: " + dobj + ", iobjc: " + iobj)
return
}
I know it seems like JXA support may not be long for this world, but it certainly is much more pleasant to work with than AppleScript in my opinion; if this seems reasonable to merge, I'll be happy to update the wiki:
Nice! I never knew this was a thing.
My only concern is that the function names are a bit generic (not prefixed with say QS
) but I think the risk of collision is very low, and the readability benefit is worth it.
Having to hard-code all the strings is ugly - I was looking at whether there's a cleaner way to do it (e.g. here ) but I don't think it's worth the effort.
Documentation is definitely needed, but let's do away with the Wiki :) Can you copy the contents of those two files to create some new Markdown files, then add them here: https://github.com/quicksilver/manual
Once done, I'll merge this.
Nice! I never knew this was a thing.
Oh man, using ~QS~ JS instead of AS is such an upgrade -- map / filter alone makes it worthwhile IMO. Really a shame its support seems to be withering away. A few examples:
My only concern is that the function names are a bit generic (not prefixed with say QS)
I agree and had considered this, but the function names seem to be (automatically?) determined from the .sdef
file; they're not something I came up with:
I think the risk of collision is very low
I agree, I included at least the function
prefix and the opening parenthesis suffix in the magic search string to this effect (to hopefully avoid false positives with people making references in documentation). It should be fairly uncommon for people to unintentionally make a reference to function processText()
in code that is not also going to be using that handler. Hopefully.
Of note, the existing codebase suffers from this same potential drawback, though accidentally including DAEDopnt
may be less likely than accidentally including function processText(
:) As I noted above, if you do happen to include the magic string DAEDopfl
(or function openFiles(
) somewhere in your code, it will cause problems.
Ah, I'd forgotten about the manual when bemoaning the sprawl. The wiki pages for these topics are actually pretty good; I'd like to make a new section under the Features
tab for Custom Actions
and I'll migrate the existing stuff from the wiki and update with the JXA
syntax (and perhaps a link to https://n8henrie.com/2013/03/template-for-writing-quicksilver-actions-in-applescript/ as well).
Not working for text objects for some reason. I'll investigate.
Obvious issues: I included closing parenthesis for function processText()
in validActionsForDirectObject
, and omitted function openFiles
entirely. But fixing this doesn't seem to resolve the issue in my first round of testing.
Working now, should be fixed by https://github.com/quicksilver/Quicksilver/pull/2883
Copied from: https://groups.google.com/g/blacktree-quicksilver/c/I6dkTGN0bI8/m/ebbi1s46AAAJ
Low priority. I will take another look at this after 1.7.0
After a couple days of StackOverflow threads, I can't figure out whether I can use JavaScript for Automation (JXA) in Quicksilver actions.
I have a ton of AppleScript actions, and they're one of the best parts of Quicksilver IMO, but I really hate working with AppleScript. I'm not huge on JavaScript either, but having
map
/filter
and a more traditional syntax really is a blessing compared to AppleScript. (For context, most of my current AppleScript actions just shell out to python or what have you, which is workable.)If you open up Script Editor, set the language to JavaScript, then open the Quicksilver dictionary, it looks like it supports the same handlers, just with slightly different names (instead of
on process text
it'sprocessText
).Unfortunately I've had no luck! I've tried a million or so different configurations of the below.
Looking through the source, I don't know enough Obj C to say what the issue may be. I do see `"AppleScript Action: No handler? Aborting..."`` in the logs, and poking around that place in the source and adding a few debug logs, I can see that it isn't finding any handlers in my test file.
Any ideas on this? I'd sure rather be writing these in JS (or python, or go, or swift, or rust, or ...) rather than AppleScript!