Using the low-level `AEApplication` glue

While glue files' terminology-based properties and methods are recommended for controlling individual "AppleScriptable" applications, SwiftAutomation also includes lower-level APIs for interacting with "non-scriptable" "applications that do not include an AETE/SDEF terminology resource, or whose terminology contains defects that render some or all of the generated glue unusable, or when sending standard commands that do not require an application-specific glue. These low-level APIs are present on all generated glues' Application and ObjectSpecifier classes if needed, and also on the default AEApplicationGlue that is included in SwiftAutomation as standard.

Sending standard Apple events

The following commands are defined on all Application and Specifier classes, including the default AEApplication, and should be recognized by all macOS applications:

run()
reopen()
launch()
activate()
open(Array<URL>) // list of file URLs
openLocation(String) // a URL string (http:/mailto:/etc.)
print(Array<URL>) // list of file URLs
quit( [ saving: AE.yes|AE.no|AE.ask ] )

(Standard get and set commands are also defined, but will only work in apps that implement an AEOM.)

As with application-specific commands, standard commands will throw a CommandError on failure, so remember to prefix with try.

For example, to open a file:

// tell application id "com.apple.TextEdit" to open (POSIX file "/Users/jsmith/ReadMe.txt")
let textedit = AEApplication(bundleIdentifier: "com.apple.TextEdit")
try textedit.open(URL(fileURLWithPath: "/Users/jsmith/ReadMe.txt"))

Or to quit multiple applications without saving changes to any open documents:

for appName in ["TextEdit", "Preview", "Script Editor"] {
  let app = AEApplication(name: appName)
  if app.isRunning { try? app.quit(saving: AE.no) }
}

Sending Apple events using four-char codes

All specifiers implement a low-level sendAppleEvent(...) method, allowing Apple events to be built and sent using four-char codes (a.k.a. OSTypes):

sendAppleEvent(_ eventClass: OSType/String, _ eventID: OSType/String, _ parameters: [OSType/String:Any] = [:],
               requestedType: Symbol? = nil, waitReply: Bool = true, sendOptions: SendOptions? = nil,
               withTimeout: TimeInterval? = nil, considering: ConsideringOptions? = nil) throws -> T/Any

Four-char codes may be given as OSType (UInt32) values or as OSType-encodable String values containing exactly four MacRoman characters. Invalid strings will cause sendAppleEvent() to throw a CommandError.

For example:

// tell application id "com.apple.TextEdit" to open (POSIX file "/Users/jsmith/ReadMe.txt")
// tell application id "com.apple.TextEdit" to «event aevtodoc» (POSIX file "/Users/jsmith/ReadMe.txt")
let textedit = AEApplication(bundleIdentifier: "com.apple.TextEdit")
try textedit.sendAppleEvent("aevt", "odoc", ["----": URL(fileURLWithPath: "/Users/jsmith/ReadMe.txt")])

// tell application id "com.apple.TextEdit" to quit saving no
// tell application id "com.apple.TextEdit" to «event aevtquit» given «class savo»: «constant ****ask »
try textedit.sendAppleEvent("aevt", "quit", ["savo": AE.ask])

While the Carbon AE headers define constants for common four-char codes, e.g. cDocument = 'docu' = 0x646f6375, as of Swift3/Xcode8/macOS10.12 some constants are incorrectly mapped to Int (SInt64) instead of OSType (UInt32), so their use is best avoided.

Constructing object specifiers using four-char codes

All object specifiers implement low-level methods for constructing property and all-elements specifiers

* userProperty(_ name: String) -- user-defined identifier, e.g. `someProperty` (note: case-[in]sensitivity rules are target-specific)

* property(_ code: OSType/String) -- four-char code, either as OSType (UInt32) or four-char string, e.g. `cDocument`/`"docu"`

* elements(_ code: OSType/String) -- ditto

The default AEApplicationGlue defines AEApp, AECon, and AEIts roots for constructing untargeted specifiers using four-char codes only.

Insertion and element selectors are the same as in application-specific glues; see Chapter 7 for details.

For example:

// every paragraph of text of document 1 [of it]
// every «class cpar» of «property ctxt» of «class docu» [of it]
AEApp.elements("docu")[1].property("ctxt").elements("cpar")
AEApp.elements(0x646f6375)[1].property(0x63747874).elements(0x63706172)

Constructing symbols using four-char codes

The default AEApplicationGlue defines an AESymbol class, type aliased as AE, for constructing Symbol instances using four-char codes:

AESymbol(code: OSType/String, type: OSType/String = typeType/"type")

For example:

// document
// «class docu»
AE(code: "docu")
AE(code: 0x646f6375)

// name
// «property pnam»
AE(code: "pnam", type: "prop") // (note: "type" is more commonly used than "prop")
AE(code: 0x706e616d, type: 0x70726f70)

// ask
// «constant ****ask »
AE(code: "ask ", type: "enum")
AE(code: 0x61736b20, type: 0x656e756d)

AESymbol instances can be used interchangeably with glue-defined PREFIXSymbol classes. SwiftAutomation only compares Symbol instances' code and type properties when comparing for equality; thus the following equality test returns true:

AE(code: "docu") == TED.document

Using symbols as AERecord keys

AppleScript records can contain any combination of keyword- and/or identifier-based keys, so the Symbol class also defines an init(_ name: String) initializer, allowing identifier-based record keys to be constructed as well:

// {name: "Sam", age: 32, isSingle: true}
[AE(code:"pnam"): "Sam", AE("age"): 32, AE("issingle"): true]

Be aware that case-[in]sensitivity rules for identifier strings can vary depending on how and where the record is used; for case-insensitivity, use all-lowercase.

To determine if a Symbol instance represents a keyword or an identifier:

AE(code:"pnam").nameOnly // false 
AE("issingle").nameOnly  // true

Scriptable applications do not normally use identifier-based keys in records; however, they may be used by AppleScript-based applets and in NSAppleScript/NSUserAppleScriptTask calls.