1

I am trying to add AppleScripting support to my non-Cocoa based application.

I'm working the low level functions such as AEGetParamDesc, handling the form / want / seld parameters myself.

My vocabulary offers a class, let's call it "Image". It has a property "name".

I've gotten some Applescript code working, such as:

get Images
get name of every Image
get count Images
get every Image
get first Image
get Image 1

So, basically, access to both the objects and its properties works.

However, when I tried these similar access forms, they all fail:

get Images whose name = "foo"

and

repeat with img in Images
end repeat

In the first case, it appears I'll have to handle a test form.

In the second case, the count operator (cnte) does not request the class object directly but instead uses a cobj operator describing an index object.

This all makes me wonder how far this will go. Will I have to implement every possible syntax and operator of Applescript individually in my code? I'd assume that the "whose" operator would simply combine the requests for "every Image" and "name of Image x" the way I can indivually write them in Applescript, instead of using different AppleEvent formulas for each of them.

Same for whose <boolean-test>. Why doesn't AppleScript simply perform the equality test of name = "foo" itself, as it's a text comparison which should not have to involve my application code at all?

Is there something I'm missing? Can I forward these to AE functions I'm not yet aware of or do I have to handle every possible comparison and flow control command myself?

jweaks
  • 3,674
  • 1
  • 19
  • 32
Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149

3 Answers3

1

The Apple Event Object Model was designed for high-latency IPC on an OS (System 7) that could do 60 context switches per second max. Complex queries and procedures that can operate on multiple objects allow more work to be done using fewer cross-process messages. In addition, it's designed to be a relatively thick View-Controller abstraction with the emphasis on UI/UX, presenting an idealized view of the user's data as a relational graph, regardless of how the underlying data is actually stored or how complex or difficult to implement the VC code to map from one to the other may be. The AEOM has far more in common with relational databases than anything in the OOP world, while the closest analogy to Apple event IPC would be sending XQueries over XML-RPC. For background see:

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

Nowadays, of course, OS X can do thousands of process switches per second without breaking a sweat, so there's less necessity to implement a complex AE View-Controller just to get acceptable performance. TBH, I'd suggest you save your own time and implement the simplest RPC that works simply, reliably, and fast. Restrict handlers that mutate state to operating on a single object per message (because doing Set operations on Arrays is hell to get right), implement the simplest query forms needed to locate objects, and return by-ID specifiers that basically act as safe pointers to previously identified objects so users can rapidly manipulate them with further commands without the cost of repeatedly roundtripping complex full queries.

Oh, and unless you've a particular reason to be using C, I'd recommend using NSAppleEventDescriptor and NSAppleEventManager only. The C Apple Event Manager API is ancient, gnarly, and legacy since 10.6 so not recommended for new development. You might (repeat, might) even find a way to use of some of the Cocoa Scripting classes to do a bit of the heavy lifting w.r.t. object specifiers, although CS is pretty awful itself and heavily coupled to ApplicationKit so don't waste time messing with it unless it actually helps (CS has buried better projects before).

The other thing, of course, is that the entire Mac Automation ecosystem is in a thoroughly moribund state, and unlikely to get any better under the current management, so the cost-vs-benefit of developing a complete, sophisticated AEOM from scratch just isn't there any more. If "simple, fast, safe, and dumb" is "good enough" for whatever users you hope to attract, just do that.

foo
  • 3,171
  • 17
  • 18
  • Thanks for the background info. I had also considered providing access to my app's data via Automator Action plugins but that didn't look too easy either because of lack of easy ways to transmit data between my running app and the Action plugin. And no, it's not made in C but in Xojo (formerly REALbasic), so I have to cope with yet another layer of complexity here, and the fewer dynamic objects that require subclassing with custom methods I have to handle, the easier it should be. Unless the NS classes do more work for me of the type I described in the question. Would they? – Thomas Tempelmann Mar 16 '16 at 12:53
  • BTW, where can I find a comprehensive list of all the AE related codes that a scriptable app might encounter? I mean codes such as test, cobj and such. Cannot find them in current Apple dev docs, not even googling. Do any of the old AppleScript book cover app side development? They mainly seem to be written for AppleScript users, not for app devs – Thomas Tempelmann Mar 16 '16 at 13:12
  • Update: It appears that the good old Inside Mac contained the type codes. Shouldn't have given those books away ten years ago, after all! Still, I can't believe that there's no library or framework around that handles all these operators in a smarter and generic way. I can't be the first to having to deal with this. – Thomas Tempelmann Mar 16 '16 at 14:35
1

Apple's sample code for Sketch provides a good example of using the Cocoa Scripting API.

The sample code does not perform any special handling for "repeat" and "whose", yet it can run AppleScripts using these terms.

This suggests that Cocoa Scripting, i.e. mainly the classes and protocols in NSScriptObjectSpecifiers.h, takes care of the complex handling that the old Carbon API exposes.

Therefore, also based on @foo's answer, it appears to be smart to attampt to create proxy classes based on NSObject(NSScriptObjectSpecifiers), implementing the objectSpecifier method as well as get/set accessors for any properties, and then use the ability to reference those classes in the .sdef file. Even without Objective C, such classes can be created using the ObjC runtime functions such as objc_registerClassPair.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
1

Thomas Tempelmann (of Find Any File fame?), if you really can't rewrite the app in Cocoa, here is an article I wrote many years ago with some detail about coding (in C) for scriptability pre-Cocoa, including handling formWhose, which you asked about above.

http://www.mactech.com/articles/develop/issue_28/reuter.html

The article was published by Apple in its Develop magazine, and included source code I wrote for a sample application called "Sketch". This was years before Apple released its own sample project also named "Sketch." I still have the source code if it would help you.

Good luck!

Ron Reuter
  • 1,287
  • 1
  • 8
  • 14
  • Yes, I'm the FAF author, trying to add Scriptability to it, actually :) Funny - I've been looking at the current Sketch2 source, which uses NS classes and which magically handles all the complicated stuff. I'd be really interested in the old version using the ancient APIs. I'll take a look at the linked article now, maybe that's already all I need. – Thomas Tempelmann Mar 20 '16 at 22:39
  • Huh, while the article handles the "whose" term in general, it doesn't seem to have any specific code to handle tests (such as for "is" and "="). But I see calls to AEResolve, which I haven't used myself so far. I wonder if AEResolve does that work for me? Hard to figure out with Apple having removed all those old docs. Will need to do some more digging. – Thomas Tempelmann Mar 21 '16 at 00:28
  • The pre-Cocoa AS engine used a callback mechanism to handlers you had to install, where AEResolve was kind of like the switching terminal that kept asking for tokens and then calling your methods with that token, etc, until it got a token of the requested type containing the requested data. That took a lot of gnarly code, including AETE resources, but once you got the hang of it it was do-able. It is generally much easier in Cocoa, which uses the magic of calling classes and methods by string lookups, so the mapping of AS terminology to Cocoa methods is much clearer in the SDEF. – Ron Reuter Mar 22 '16 at 03:06
  • Thomas, formWhose resolution is discussed in "Handling formTest And formWhose" in the article, and the two methods that are used to handle whose tests in the sample code are OSLCountObjectsCallback and OSLCompareObjectsCallback, both in OSLHelpers.c. Whose resolution is basically done by examining each object of the desired class and checking the requested property or properties for the requested value or values. Its much like implementing an object-oriented "equals" method where two objects are considered equal if and only if a specified subset of the fields are equal. The result is a list. – Ron Reuter Apr 26 '16 at 15:52
  • Got me thinking, so I discovered my scriptable text editor Ted handles formWhose without any special code. For example, "get words whose size is 24" calls the intrinsic Get command with a Direct Parameter: = 24> of scriptableContent of orderedDocuments named "Test Whose Resolution"> – Ron Reuter Apr 26 '16 at 16:58
  • The Gotten Value from the above (which is what my app returns) is a list of NSAttributedStrings, which cocoa has already filtered on the fontSize property, and the Result: is returned. This is all (mostly) free behavior due to the benefits of adhering to Key Value Coding in Cocoa. – Ron Reuter Apr 26 '16 at 17:13