Querying Class & Command Descs from NSScriptSuiteRegistry

Hi!


I want to get access to the

NSScriptClassDescription & NSScriptCommandDescription objects for a running application via the NSScriptSuiteRegistry object but I'm not having much luck.


I was hoping that all running applications that have a sdef file would be listed when calling the var suiteNames: [String]function, but this doesn't return any suite names for running apps that have sdef files. It returns names like NSCoreSuite, NSTextSuite, ASKDocumentSuite.


I then thought perhaps the suites aren't loaded until the app responds to an Apple Event so I fired off a quick NSScriptObject instance targeting the app I'm wanting to see the suite defs for, but that had no effect on suite names ruturned from NSScriptSuiteRegistry.


Is it even possible to inspect the AppleScript definition for a 3rd party or Apple app with NSScriptSuiteRegistry? I really don't want to have to import an sdef into Core Data and build it myself, if I can simply query the system at run time.

Replies

I want to get access to the [terminology] for a running application via the

NSScriptSuiteRegistry
object but I'm not having much luck.

Yeah, I don’t think that’ll work.

NSScriptSuiteRegistry
is about the terminology exported by your app, not about the terminology of other apps.

I believe the droid you’re looking for here is

OSACopyScriptingDefinitionFromURL
. As far as I can tell this will issue the
kGetAETE
event to the app if required.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for pointing out that Carbon API. :-) I've not delved that deeply into AppleScript yet. Perhaps you can point me in the right direction as I start this project idea I have, or I may just be barking up the wrong trees.


The idea is to get hold of the KVC definitions for the AS object graphs from a list of running applications and call them using KVC.


While I can slurp up a sdef file and stick it into Core Data and run my command builder off those reference definitions, I was hoping to be able to get the AppleScript KVC keys at runtime via an Apple framework. Do you know if this is possible, and if so how? I was hoping to simply use the Scripting Bridge to query SBApplications using the cocoa keys in the sdef.


> I believe the droid you’re looking for here is

OSACopyScriptingDefinitionFromURL
. As far as I can tell this will issue the
kGetAETE
event to the app if required
.


What is the consequence of that? Would that then ensure I'd be able to query the system for KVC keys somehow, or will it simply load the sdef XML into CFData?

“The idea is to get hold of the KVC definitions for the AS object graphs from a list of running applications and call them using KVC.”


Apple event IPC = RPC + Queries, not OOP. It’s also notoriously high latency. Crawling the object graph will be murderously slow. Plus, application dictionaries are not IDLs; they are famously ambiguous/incomplete/incorrect, so there is a limit to how much you can rely on them.


The more important question is: why do you want to do it? If you’re just trying to learn your way around scriptable applications, get Script Debugger which includes powerful dictionary and object graph browsers. If you’re thinking of writing your own, don’t bother: the whole AE/AS/OSA stack was put into maintenance mode and left to die quietly years ago. Barring miracles, its day is done.


“I was hoping to simply use the Scripting Bridge to query SBApplications using the cocoa keys in the sdef.”


There is no desktop automation task that cannot be made worse by trying to do it in Scripting Bridge. It’s junk. Stick to AppleScript. It’s the only [nominally] supported option that speaks Apple events right.


If you like living dangerously then SwiftAutomation also speaks AEs right, but caveat emptor as I haven’t updated or tested it in a couple years and do not provide support.

I don’t really understand what you mean by “KVC definitions”. I presume you’re using KVC as a shortcut for key-value-coding, but that’s a Cocoa concept that doesn’t really map well to Apple events.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn,


> "I presume you’re using KVC as a shortcut for key-value-coding"


Yes! Sorry for not being particularly clear. What I mean by "KVC definitions" is the KVC-compliant keys in the sdef file. All I really want to do is use those keys to access the SBApplication attributes and elements just like any other KVC code would. I don't have a need for working directly with Apple Events. It's the Scripting Bridge that really interests me.


So the question is, is it possible to query a system framework at runtime to get these KVC keys that are described in a running app's sdef file?

All I really want to do is use those keys to access the

SBApplication
attributes and elements just like any other KVC code would.

Yeah, that’s going to be a challenge. ScriptingBridge tries to impose a Cocoa interpretation on an AppleScript world, which is tricky stuff. To get this info from an AppleScript terminology (

'aete'
) — that is, the thing you get back from
OSACopyScriptingDefinitionFromURL
— you would have to reimplement that logic. It would be nice if ScriptingBridge exported that mapping but, AFAICT, it does not.

hhas01 and I disagree on many things, but in this case I concur with their conclusion: Skip ScriptingBridge and do this work in terms of Apple events.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

“ScriptingBridge tries to impose a Cocoa interpretation on an AppleScript world”


This is why SB’s ORM approach doesn’t (and can’t) work right. ORMs covering RDBMSes are notoriously twitchy; and those only have to cover half a dozen apps speaking broadly the same language (SQL). In AppleScript’s world, there are hundreds of apps with wildly different structure and commands, and little consistency in behavior.


The only information in an AETE/SDEF that AppleScript actually uses are the name/OSType mappings and the type of each (type/enum/property/command/parameter). Everything else is ignored—classes and inheritance are a fiction created for documentation purposes; even one-to-one and one-to-many relationships (containment) aren’t guaranteed to hold. (You’ll see this logic replicated in SwiftAutomation’s AETE/SDEF parsers, AS quirks and all.)


It would really help if OP would describe the problem they’re trying to solve. We can talk about “how” till the cows come home, but unless we understand the “why” we don’t know if our advice is appropriate or not.

Thanks for the advice. I'll have to experiment a bit and see what is going to be a good fit for what I'm designing.


If I went the Apple event route, would it be easier to figure out at runtime what events an app responds to or would I have to parse the sdef in the app's bundle? Parsing the sdef is not really an issue for me, I've already designed a Core Data model from the SDEF.dtd, but, if as you say, there is no other way to get this info for a running app, then I'll have to parse it. That way, I'm guessing I'd make use of the AE codes in the sdef and not the <cocoa> element KVC-compliant key mappings, and use those with the Carbon framework?


My macOS app is obviously written in Swift and (Objective-C where necessary), so I just assumed Sripting Bridge would reduce the amount type coersion I've had to get involved in etc. Is not going to be quite difficult to handle type coersion and getting the AE data to play nicely with a Swift GUI?


I haven't read the legacy document, Apple Events Programming Guide (2007) yet. Is it still fairly accurate?


Is it not possible to use a support ticket to get some private feedback from you? I simply can't publically divulge some of the design details of my app. It's just frustrating because if I could tell you privately exactly what I'm trying to achieve, it may have a bearing on your opinion.

"In AppleScript’s world, there are hundreds of apps with wildly different structure and commands, and little consistency in behavior."


That's quite true regardless if which language you are using, otherwise all apps would be identical. From a user perspective, I think the main problem with AppleScript is the built-in natural language syntax grammar.

“That way, I'm guessing I'd make use of the AE codes in the sdef and not the <cocoa> element KVC-compliant key mappings, and use those with the Carbon framework?”


I think you’re getting confused by the KVC data found in .sdef files. That data is not provided for clients’ use; it’s purely for use by the CocoaScripting framework upon which Cocoa-based apps implement their AE support. See `man 5 sdef` if you’ve not already done so.


As to navigating an app’s object graph, run-time introspection is poor/non-existent. CS-based apps have a standard `properties` property, but this only tells you about attributes and one-to-one relationships; it doesn’t tell you what one-to-many relationships (elements) exist. Nor is it a standard feature in older non-CS based apps (e.g. most MS, Adobe apps). You have to resort to reading the app’s aete/sdef; however, even that is troublesome, e.g. SDEF does not distinguish between attributes of type `text` (really, typeUnicodeText) and one-to-one relationships of type `text` (really cText), so you’ve no idea if a given property is one or other except to try querying elements (e.g. `every character of PROPERTY`) and see if the event succeeds or fails. Human users, of course, learn an app’s object model and recognize which is which, e.g. `name of front document of app "TextEdit"` vs `text of front document of app "TextEdit"`; an automated crawler will not, so will have to trial-and-error extensively.


And remember, AppleScript does not use any of that “type” information itself, so there are zero guarantees that information is accurate. e.g. Properties that can return, say, a string or `missing value` constant may declare their type as `text`, so even if you do a `get` there is no guarantee the result is a String. If you try to enforce that type (as SB does), you won’t get the right result. And that’s even before we consider implementation bugs in CS and/or apps, e.g. try `tell app "TextEdit" to get path of front document` and see what happens when the front document is newly created and hasn’t been saved yet. This is why SwiftAutomation leaves casting entirely to the client code, e.g. `PROPERTY.get() as String?`; it expects the code’s author knows that property returns either String or `missing value`, regardless of what the app’s dictionary may claim, so puts them in control. Works great, and takes advantage of the Apple Event Manager’s ability to coerce between various types so if you say `[String]` and the returned data is 1.0 then you’ll still get `["1.0"]`, but it’s not something you can automate as SDEF data’s not reliable enough.


Another example: `tell application "Finder" to get entire contents of desktop`, where the dictionary defines the `entire contents` property as type `specifier` (typeObjectSpecifier). Run that command and it returns a list of specifiers. Yet you can also write `tell application "Finder" to get name of every file of entire contents of desktop`, so it’s clear the property’s type is not a list. As with `text of front document of app "TextEdit"`, or `text of every document of app "TextEdit"`, what you get it partly dependent on the query you ask, and partly dependent on what the app anticipates is the most helpful result for you. (Unfortunately, while AE IPC has the facility to do content negotiation—see keyAERequestedType—it is poorly specced, underpowered, and rarely supported by applications.)


..


Again, if you’re trying to extract all data from each running app, you are going to be crushed at how unspeakably slow it all is, even if you do apply each `get` operation to all elements at once. I just did a job for a customer, automating a task in Adobe Illustrator, which I prototyped in Python3+appscript because AppleScript is just a PITA. That script only sent a few thousand AEs; took half an hour to run!!! I dunno exactly what it was tickling wrong (a couple minutes is more typical performance), but even on a good day AEOM queries are hella expensive and any form of IPC is inevitably magnitudes slower than in-process messaging. (The production version, written for Illustrator’s own in-process JS engine: 10secs.)


That’s why, in practice, we only query/manipulate the remote data we need. Pulling mass data over the wire is totally impractical performance-wise; plus there’s no transactional guarantees so even while you’re doing it the remote state may be simultaneously changing.


Apple event IPC may have its virtues—e.g. I’ve long argued its query-driven model could be a great match for Siri’s query-builder (especially if the AEOM spec was tightened up)—but when you get down to details it is painfully rough to deal with.


There’s two decades’ hard experience encoded in SwiftAutomation, and even then it’s not perfect, but it’s maybe worth a skim of the code just to get some appreciation of how and why it works the way it does. Because every other approach (gensuitemodule, JavaScriptOSA, aeve, RubyOSA, and of course ScriptingBridge) falls apart when real-world *AppleScriptable* apps stomp all over their assumptions.


Oh, and for general insight into Apple event IPC and how and why it works the way it does (and why it was left so awkward and unpolished), read this paper by one of its original designers:


www.cs.utexas.edu/~wcook/Drafts/2006/ashopl.pdf


You’ll learn more from that than you ever will from Apple’s own public developer docs, which are riddled with omissions and lies, it pains me to say.


Not trying to deter you, mind (must be a pretty good idea to make you so determined to pursue it!); just letting you know what a world of hurt you are in for! 😉

"I think you’re getting confused by the KVC data found in .sdef files. That data is not provided for clients’ use; it’s purely for use by the CocoaScripting framework upon which Cocoa-based apps implement their AE support."


I realize this 100%. I'm really not confused about sdef files or the data therein. I've read the Cocoa Scripting Guide and the Scripting Bridge Programming Guide for Cocoa. So I know why sdefs exist and what the data therein describes. I think the mistake I've made is in assuming that every app has to provide cocoa keys in their sdef, but they don't. So just because an app has a scripting defintion doesn't mean you can access it via cocoa unless use use the sdp tool and link the generated header in. Which is not going to help me.


If you want to send Apple events to a running app that has Apple Script support, can I do this using the AE code information in the sdef file and creating my own events? I haven't read the Apple Event Programming Guide yet. Just wondering how far I'd get with this approach.

If you want to send Apple events to a running app that has Apple Script support, can I do this using the AE code information in the

sdef
file and creating my own events?

Yes. This is exactly what Script Editor does when you run a script.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks very much for your help. I know I'm on the correct track now.

Also, thanks for sharing your work on SwiftAutomation. For me it's invaluable as I haven't found any other code yet. I've had a cursory glance over your documentation and some of the code. I see what you mean by 20 years of hard experience going into the code. The example that interests me the most is the creation of NSAppleEventDescriptions and their collaboration with AEM. It looks like I'll have to fire off events based off the sdef data. Do you have an email or website submission form I can get you on if I have any questions? :-)

Be very wary of the SB documentation—it is a fiction, and very misleading. (There’s a reason SB and JXA never became popular, even though Mac Automation should be catnip for geeks.)


As to CS documentation, it’s worth reading around. Third-party tutorials can offer insights that the Apple docs don’t. e.g. Here’s one by Matt Neuburg (old, but still worth reading as CS itself hasn’t changed much):


www.apeth.net/matt/scriptability/scriptabilityTutorial.html


One good thing about Apple’s CS docs if memory serves: it’s the once place where they do talk about one-to-one and one-to-many relationships, which is the key to understanding how an Apple Event Object Model really works.


Also bear in mind that CS’s behaviors (which are themselves not without bugs and quirks) only apply to apps built on CS, and don’t generalize to apps built on the older, lower-level Apple Event Manager APIs, which are much more variable in their behaviors.


..


Yes, you can construct and send AEs yourself using NSAppleEventDescriptor. It’s low-level and a bit of a chore, but it can be done if that’s what you need. It’s what the “stable” version of SwiftAutomation on my BitBucket uses.†


As for finding SwiftAutomation useful, nice to hear it. Alas, I couldn’t get Apple to take it (the offer stands), which is why I don’t see much point in pursuing it further. As far as studying other code, the only other AE bridge of worth is Frontier’s, but I don’t think you’ll glean much from that as it’s ancient, ancient C; besides I’ve already been through that and every other AE bridge myself and distilled all that knowledge into appscript, which in turn is refined into SA.


You can message me via my SF profile (sourceforge.net/u/hhas), though as you can tell I’m pretty cheesed off at the whole mess and make no promises as to how helpful I can be.


--


(† There’s also an experimental fork of SA on my GitHub which assembles AE data directly, being intended as a modern cross-platform replacement to AEM itself, but ignore that. I got most of the way down to the Mach layer, but it’s a lot of effort to black-box reverse-engineer that last step when Apple themselves aren’t interested.)