├── AppleEvents
├── AppleEvents-Bridging-Header.h
├── AppleEvents.h
├── Info.plist
├── AEMShim.h
├── Descriptor.swift
├── AddressDescriptor.swift
├── ScalarDescriptor.swift
├── AEMShim.m
├── Shim.swift
├── ListDescriptor.swift
├── AppleEventHandler.swift
├── Unflatten.swift
├── AppleEventError.swift
├── Support.swift
├── PackFuncs.swift
├── RecordDescriptor.swift
├── TestDescriptors.swift
├── AppleEventDescriptor.swift
├── UnpackFuncs.swift
└── QueryDescriptors.swift
├── AppleEvents.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── has.xcuserdatad
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ ├── AppleEvents.xcscheme
│ │ ├── test-send.xcscheme
│ │ └── test-receive.xcscheme
├── xcshareddata
│ └── xcschemes
│ │ └── libAppleEvents.xcscheme
└── project.pbxproj
├── test-receive
├── test-ae.py
└── main.swift
└── test-send
└── main.swift
/AppleEvents/AppleEvents-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "AEMShim.h"
6 |
7 |
--------------------------------------------------------------------------------
/AppleEvents.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/AppleEvents.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test-receive/test-ae.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from aem import *
4 |
5 |
6 | # update PID before running:
7 | pid = 90323
8 |
9 |
10 |
11 | p = Application(pid=pid)
12 |
13 | print(p.event(b'aevtodoc', {b'----': "/Users/foo/README.txt"}).send(timeout=240))
14 |
15 | print(p.event(b'coregetd', {b'----': app.elements(b'docu').byindex(1).property(b'ctxt')}).send(timeout=240))
16 |
17 | p.event(b'coreclos', {b'----': app.elements(b'docu')}).send(timeout=240)
18 |
19 | #p.event(b'aevtquit').send(timeout=240)
20 |
--------------------------------------------------------------------------------
/AppleEvents/AppleEvents.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppleEvents.h
3 | // AppleEvents
4 | //
5 |
6 | #import
7 |
8 | #import "AppleEvents-Bridging-Header.h"
9 |
10 | //! Project version number for AppleEvents.
11 | FOUNDATION_EXPORT double AppleEventsVersionNumber;
12 |
13 | //! Project version string for AppleEvents.
14 | FOUNDATION_EXPORT const unsigned char AppleEventsVersionString[];
15 |
16 | // In this header, you should import all the public headers of your framework using statements like #import
17 |
18 |
19 |
--------------------------------------------------------------------------------
/AppleEvents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/AppleEvents.xcodeproj/xcuserdata/has.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | AppleEvents.xcscheme
8 |
9 | isShown
10 |
11 | orderHint
12 | 0
13 |
14 | libAppleEvents.xcscheme_^#shared#^_
15 |
16 | orderHint
17 | 1
18 |
19 | test-receive.xcscheme
20 |
21 | isShown
22 |
23 | orderHint
24 | 2
25 |
26 | test-send.xcscheme
27 |
28 | isShown
29 |
30 | orderHint
31 | 3
32 |
33 |
34 | SuppressBuildableAutocreation
35 |
36 | 4F0B4C0923264B6D00BDA407
37 |
38 | primary
39 |
40 |
41 | 4F0B4C1723264B8600BDA407
42 |
43 | primary
44 |
45 |
46 | 4F257286232644F40002AED8
47 |
48 | primary
49 |
50 |
51 | 4F2572BD2326468C0002AED8
52 |
53 | primary
54 |
55 |
56 | 4F2572DF232647060002AED8
57 |
58 | primary
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/AppleEvents/AEMShim.h:
--------------------------------------------------------------------------------
1 | //
2 | // AEMShim.h
3 | //
4 | // various bits of CoreServices/AE.framework that AppleEvents.framework still needs until fully ported over to Mach APIs
5 | //
6 |
7 | #import
8 | #import
9 |
10 |
11 | //! Project version number for AEMShim.
12 | FOUNDATION_EXPORT double AEMShimVersionNumber;
13 |
14 | //! Project version string for AEMShim.
15 | FOUNDATION_EXPORT const unsigned char AEMShimVersionString[];
16 |
17 | #ifndef __AE__
18 |
19 | typedef FourCharCode DescType;
20 | typedef FourCharCode AEKeyword;
21 | typedef SInt32 AESendMode;
22 |
23 | typedef struct OpaqueAEDataStorageType* AEDataStorageType;
24 |
25 | typedef AEDataStorageType * AEDataStorage;
26 |
27 | typedef struct AEDesc {
28 | DescType descriptorType;
29 | AEDataStorage dataHandle;
30 | } AEDesc;
31 |
32 | typedef AEDesc AEDescList;
33 | typedef AEDescList AERecord;
34 | typedef AEDesc AEAddressDesc;
35 | typedef AERecord AppleEvent;
36 |
37 | CF_ENUM(DescType) {
38 | typeNull = 'null',
39 | typeAppleEvent = 'aevt',
40 | keyErrorNumber = 'errn',
41 | keyErrorString = 'errs',
42 | keyAEResult = '----'
43 | };
44 |
45 | #endif
46 |
47 | extern Size AESizeOfFlattenedDesc(const AEDesc *theAEDesc);
48 | extern OSStatus AEFlattenDesc(const AEDesc *theAEDesc, Ptr buffer, Size bufferSize, Size *actualSize);
49 | extern OSStatus AEUnflattenDesc(const void *buffer, AEDesc *result);
50 | extern OSErr AEDisposeDesc(AEDesc *theAEDesc);
51 | extern OSErr AEPutParamDesc(AppleEvent *theAppleEvent, AEKeyword theAEKeyword, const AEDesc *theAEDesc);
52 | extern mach_port_t AEGetRegisteredMachPort(void);
53 | extern OSStatus AEDecodeMessage(mach_msg_header_t *header, AppleEvent *event, AppleEvent *reply);
54 | extern OSStatus AESendMessage(const AppleEvent *event, AppleEvent *reply, AESendMode sendMode, long timeOutInTicks);
55 |
56 | extern OSStatus AEPrint(const AEDesc *desc, const char *msg);
57 |
58 |
--------------------------------------------------------------------------------
/test-receive/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // test-handler
4 | //
5 |
6 | import CoreFoundation
7 | //import Foundation
8 | import AppleEvents
9 |
10 |
11 |
12 | // TO DO: higher-level API should provide human-readable error messages (so needs to know parameter names and be able to describe required type[s]); ideally all params should be processed before generating complete error description as multiple params may be invalid (one problem with this is that standard kOSAExpectedType/kOSAErrorOffendingObject error keys can only describe a single issue, and kOSAExpectedType cannot describe complex types; may be worth defining new keys for extended error info)
13 |
14 | // TO DO: IDL should include human-readable descriptions of any custom error codes defined by server (error domain = bundle identifier); these may be simple strings or templates (fields would be param keys, allowing client to unpack reply event parameters and format them natively before inserting into template)
15 |
16 | appleEventHandlers[eventOpenDocuments] = { (event: AppleEventDescriptor) throws -> Descriptor? in
17 | guard let desc = event.parameter(keyDirectObject) else { throw AppleEventError.missingParameter }
18 | print("open", try unpackAsArray(desc, using: unpackAsFileURL))
19 | return RootSpecifierDescriptor.app.elements(cDocument).byIndex(packAsInt32(1)) // return [list of] specifier identifying opened document[s]
20 | }
21 |
22 | appleEventHandlers[coreEventGetData] = { (event: AppleEventDescriptor) throws -> Descriptor? in
23 | guard let desc = event.parameter(keyDirectObject) else { throw AppleEventError.missingParameter }
24 | print("get", desc)
25 | return packAsString("Hello")
26 | }
27 |
28 | appleEventHandlers[coreEventClose] = { (event: AppleEventDescriptor) throws -> Descriptor? in
29 | guard let desc = event.parameter(keyDirectObject) else { throw AppleEventError.missingParameter }
30 | // TO DO: optional `saving` parameter
31 | print("close", desc)
32 | return nil
33 | }
34 |
35 | appleEventHandlers[eventQuitApplication] = { (event: AppleEventDescriptor) throws -> Descriptor? in
36 | print("quit")
37 | CFRunLoopStop(CFRunLoopGetCurrent())
38 | return nil
39 | }
40 |
41 |
42 |
43 |
44 | print("pid = \(getpid())")
45 |
46 |
47 |
48 | // TO DO: registered Mach port also receives non-AE messages; how should these be handled? (e.g. when running test script, the received AEs are preceded by a non-AE message which AEDecodeMessage rejects with error -50)
49 | let source = CFMachPortCreateRunLoopSource(nil, AppleEvents.createMachPort(), 1)
50 | CFRunLoopAddSource(CFRunLoopGetCurrent(), source, .commonModes)
51 | CFRunLoopRun()
52 |
53 |
--------------------------------------------------------------------------------
/AppleEvents/Descriptor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Descriptor.swift
3 | //
4 |
5 | import Foundation
6 |
7 | // TO DO: ScalarDescriptor.toRecord() which attempts to read descriptor data as AERecord and throws if the numbers don't add up (this is how AEM does it)
8 |
9 | // TO DO: need an API to traverse nested descriptors in order to, e.g. print human-readable representation
10 |
11 | // TO DO: implement Comparable
12 |
13 | // TO DO: implement CustomStringConvertible (this should return literal representation, using unpackAsAny() to unroll lists and records; note that some Swift types, e.g. String, Date, should be shown as literal representation [even when not nested?])
14 |
15 | public protocol Descriptor: CustomDebugStringConvertible {
16 |
17 | var type: DescType { get } // AEDesc.descriptorType
18 | var data: Data { get } // TO DO: make this private? (caution: this may be a slice view into a larger underlying buffer, so do not assume it starts on index 0; use data.startIndex)
19 |
20 | func flatten() -> Data
21 | func appendTo(containerData: inout Data)
22 | }
23 |
24 | public extension Descriptor {
25 |
26 | var debugDescription: String {
27 | return "<\(Swift.type(of: self)) \(literalFourCharCode(self.type))>"
28 | }
29 | }
30 |
31 |
32 | // object specifiers
33 |
34 | // specifier root (wrapper), object specifier, insertion location
35 | public protocol QueryDescriptor: Scalar {
36 |
37 | var from: QueryDescriptor { get }
38 |
39 | }
40 |
41 |
42 | // (aka 'whose' clauses) comparison descriptor, logical descriptor
43 | public protocol TestDescriptor: Scalar {
44 | }
45 |
46 |
47 | // AEList/AERecord iterators are mostly used to unpack
48 |
49 | public protocol IterableDescriptor: Descriptor, Sequence { // AEList/AERecord; not sure about AppleEvent
50 |
51 | associatedtype Element
52 |
53 | var count: UInt32 { get }
54 |
55 | // TO DO: having this method public is not ideal as it requires internal knowledge to use correctly
56 | func element(at offset: Int) -> (item: Element, endOffset: Int)
57 | }
58 |
59 |
60 |
61 | public struct DescriptorIterator: IteratorProtocol {
62 |
63 | private var count = 0
64 | private var offset = 0
65 | private var descriptor: D
66 |
67 | public typealias Element = D.Element
68 |
69 | init(_ descriptor: D) {
70 | self.descriptor = descriptor
71 | self.offset = self.descriptor.data.startIndex
72 | }
73 |
74 | public mutating func next() -> Element? {
75 | if self.count >= self.descriptor.count { return nil }
76 | let (result, endOffset) = self.descriptor.element(at: self.offset)
77 | self.count += 1
78 | self.offset = endOffset
79 | return result
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/AppleEvents/AddressDescriptor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddressDescriptor.swift
3 | //
4 |
5 | import Foundation
6 |
7 |
8 |
9 | public struct AddressDescriptor: Descriptor, Scalar, CustomDebugStringConvertible {
10 |
11 | public let type: DescType
12 | public let data: Data
13 |
14 | public var debugDescription: String {
15 | var value: Any? = nil
16 | switch self.type {
17 | case typeProcessSerialNumber: value = try? decodeUInt64(self.data)
18 | case typeKernelProcessID: value = try? self.processIdentifier()
19 | case typeApplicationBundleID: value = try? self.bundleIdentifier().debugDescription
20 | case typeApplicationURL: value = try? self.applicationURL().debugDescription
21 | default: ()
22 | }
23 | return "<\(Swift.type(of: self)) \(literalFourCharCode(self.type)) \(value ?? "...")>"
24 | }
25 | }
26 |
27 |
28 | public extension AddressDescriptor {
29 |
30 | init() { // current application
31 | self.type = typeProcessSerialNumber
32 | self.data = Data([0,0,0,0,0,0,0,2]) // equivalent to `ProcessSerialNumber(0,kCurrentProcess)`, aka UInt64(bigEndian:2)
33 | }
34 |
35 | init(processIdentifier value: pid_t) {
36 | self.type = typeKernelProcessID
37 | self.data = encodeInt32(value) // pid_t = Int32
38 | }
39 |
40 | func processIdentifier() throws -> pid_t {
41 | switch self.type {
42 | case typeKernelProcessID:
43 | return try decodeInt32(self.data)
44 | default:
45 | throw AppleEventError(code: -1701)
46 | }
47 | }
48 |
49 | init(bundleIdentifier value: String) throws {
50 | self.type = typeApplicationBundleID
51 | self.data = encodeUTF8String(value)
52 | }
53 |
54 | func bundleIdentifier() throws -> String {
55 | switch self.type {
56 | case typeApplicationBundleID:
57 | guard let result = decodeUTF8String(self.data) else { throw AppleEventError.corruptData }
58 | return result
59 | default:
60 | throw AppleEventError(code: -1701)
61 | }
62 | }
63 |
64 | init(applicationURL value: URL) throws {
65 | // TO DO: check URL is valid (file/eppc) and throw if not
66 | self.type = typeApplicationURL
67 | self.data = encodeUTF8String(value.absoluteString)
68 | }
69 |
70 | func applicationURL() throws -> URL {
71 | switch self.type {
72 | case typeApplicationURL:
73 | guard let string = decodeUTF8String(self.data), let result = URL(string: string) else {
74 | throw AppleEventError(code: -1702)
75 | }
76 | return result
77 | default:
78 | throw AppleEventError(code: -1701)
79 | }
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/AppleEvents/ScalarDescriptor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppleEventDescriptor.swift
3 | //
4 | // Carbon AEDesc reimplemented in Swift
5 |
6 |
7 |
8 | // type128BitFloatingPoint, typeDecimalStruct?
9 |
10 |
11 | import Foundation
12 |
13 |
14 | // caution: when appending scalar descriptors containing arbitrary-length data (e.g. typeBoolean, typeUTF8Text) to AEList/AERecord/AppleEvent, the appended data must end on even-numbered byte
15 |
16 | func align(data: inout Data) {
17 | if data.count % 2 != 0 { data += Data([0]) }
18 | }
19 |
20 |
21 | public struct ScalarDescriptor: Descriptor, Scalar {
22 |
23 | public var debugDescription: String {
24 | var value = try? unpackAsAny(self)
25 | if let string = value as? String {
26 | value = string.debugDescription
27 | } else if value is Descriptor {
28 | if let code = try? unpackAsFourCharCode(self) {
29 | value = literalFourCharCode(code)
30 | } else {
31 | value = nil
32 | }
33 | }
34 | return "<\(Swift.type(of: self)) \(literalFourCharCode(self.type)) \(value ?? "...")>"
35 | }
36 |
37 | public let type: DescType
38 | public let data: Data
39 |
40 | public init(type: DescType, data: Data) {
41 | self.type = type
42 | self.data = data
43 | }
44 | }
45 |
46 |
47 | public protocol Scalar: Descriptor {}
48 |
49 | public extension Scalar {
50 |
51 | func flatten() -> Data {
52 | var result = Data([0x64, 0x6c, 0x65, 0x32, // format 'dle2'
53 | 0, 0, 0, 0]) // align
54 | self.appendTo(containerData: &result)
55 | return result
56 | }
57 |
58 | func appendTo(containerData result: inout Data) {
59 | let data = self.data
60 | result += encodeUInt32(self.type) // descriptor type
61 | result += encodeUInt32(UInt32(data.count)) // remaining bytes
62 | result += data // descriptor data
63 | align(data: &result) // even-byte align (e.g. Booleans, UTF8 strings)
64 | }
65 | }
66 |
67 |
68 | internal let nullData = Data(capacity: 0)
69 |
70 | public let nullDescriptor = ScalarDescriptor(type: typeNull, data: nullData)
71 | let trueDescriptor = ScalarDescriptor(type: typeTrue, data: nullData)
72 | let falseDescriptor = ScalarDescriptor(type: typeFalse, data: nullData)
73 |
74 | // TO DO: bridge Swift nil/cMissingValue? (cMissingValue is another AS/AE wart: it'd be much simpler and saner had original designers used typeNull as their "no-value" value, but backwards-compatibility with existing AS/AE/App ecosystem requires using `missing value`; SwiftAutomation seems to have found a reasonable compromise)
75 | public let missingValueDescriptor = ScalarDescriptor(type: typeType, data: Data([0x6D, 0x73, 0x6E, 0x67])) // cMissingValue
76 |
77 |
78 |
--------------------------------------------------------------------------------
/AppleEvents.xcodeproj/xcshareddata/xcschemes/libAppleEvents.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/AppleEvents.xcodeproj/xcuserdata/has.xcuserdatad/xcschemes/AppleEvents.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/AppleEvents.xcodeproj/xcuserdata/has.xcuserdatad/xcschemes/test-send.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/AppleEvents.xcodeproj/xcuserdata/has.xcuserdatad/xcschemes/test-receive.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/AppleEvents/AEMShim.m:
--------------------------------------------------------------------------------
1 | //
2 | // AEMShim.m
3 | //
4 |
5 | // CoreServices.framework should be present on all platforms, but will only include AE* symbols on macOS
6 |
7 | #import "AEMShim.h"
8 |
9 |
10 | #define LOAD { if (shouldLoad) loadCarbon(); }
11 |
12 | #define BIND(name) { if (!((ptr_##name) = CFBundleGetFunctionPointerForName(framework, CFSTR(#name)))) exit(5); }
13 |
14 |
15 | char *coreServicesPath = "/System/Library/Frameworks/CoreServices.framework";
16 |
17 |
18 |
19 | static Size (*ptr_AESizeOfFlattenedDesc)(const AEDesc *theAEDesc);
20 | static OSStatus (*ptr_AEFlattenDesc)(const AEDesc *theAEDesc, Ptr buffer, Size bufferSize, Size *actualSize);
21 | static OSStatus (*ptr_AEUnflattenDesc)(const void *buffer, AEDesc *result);
22 | static OSErr (*ptr_AEDisposeDesc)(AEDesc *theAEDesc);
23 | static OSErr (*ptr_AEPutParamDesc)(AppleEvent *theAppleEvent, AEKeyword theAEKeyword, const AEDesc *theAEDesc);
24 | static mach_port_t (*ptr_AEGetRegisteredMachPort)(void);
25 | static OSStatus (*ptr_AEDecodeMessage)(mach_msg_header_t *header, AppleEvent *event, AppleEvent *reply);
26 | static OSStatus (*ptr_AESendMessage)(const AppleEvent *event, AppleEvent *reply, AESendMode sendMode, long timeOutInTicks);
27 | static OSStatus (*ptr_AEPrintDescToHandle)(const AEDesc *desc, Handle *result);
28 |
29 |
30 | int shouldLoad = 1;
31 |
32 | void loadCarbon(void) {
33 | shouldLoad = 0;
34 | CFURLRef frameworkURL = CFURLCreateFromFileSystemRepresentation(nil, (UInt8 *)coreServicesPath, strlen(coreServicesPath), true);
35 | CFBundleRef framework = CFBundleCreate(nil, frameworkURL);
36 | CFRelease(frameworkURL);
37 | if (framework) {
38 | BIND(AESizeOfFlattenedDesc);
39 | BIND(AEFlattenDesc);
40 | BIND(AEUnflattenDesc);
41 | BIND(AEDisposeDesc);
42 | BIND(AEPutParamDesc);
43 | BIND(AEGetRegisteredMachPort);
44 | BIND(AEDecodeMessage);
45 | BIND(AESendMessage);
46 | BIND(AEPrintDescToHandle);
47 | CFRelease(framework);
48 | }
49 | }
50 |
51 |
52 | extern Size AESizeOfFlattenedDesc(const AEDesc *theAEDesc) {
53 | LOAD; return (*ptr_AESizeOfFlattenedDesc)(theAEDesc);
54 | }
55 | extern OSStatus AEFlattenDesc(const AEDesc *theAEDesc, Ptr buffer, Size bufferSize, Size *actualSize) {
56 | LOAD; return (*ptr_AEFlattenDesc)(theAEDesc, buffer, bufferSize, actualSize);
57 | }
58 | extern OSStatus AEUnflattenDesc(const void *buffer, AEDesc *result) {
59 | LOAD; return (*ptr_AEUnflattenDesc)(buffer, result);
60 | }
61 | extern OSErr AEDisposeDesc(AEDesc *theAEDesc) {
62 | LOAD; return (*ptr_AEDisposeDesc)(theAEDesc);
63 | }
64 | extern OSErr AEPutParamDesc(AppleEvent *theAppleEvent, AEKeyword theAEKeyword, const AEDesc *theAEDesc) {
65 | LOAD; return (*ptr_AEPutParamDesc)(theAppleEvent, theAEKeyword, theAEDesc);
66 | }
67 | extern mach_port_t AEGetRegisteredMachPort(void) {
68 | LOAD; return (*ptr_AEGetRegisteredMachPort)();
69 | }
70 | extern OSStatus AEDecodeMessage(mach_msg_header_t *header, AppleEvent *event, AppleEvent *reply) {
71 | LOAD; return (*ptr_AEDecodeMessage)(header, event, reply);
72 | }
73 | extern OSStatus AESendMessage(const AppleEvent *event, AppleEvent *reply, AESendMode sendMode, long timeOutInTicks) {
74 | //AEPrint(event, "AESendMessage event");
75 | LOAD; return (*ptr_AESendMessage)(event, reply, sendMode, timeOutInTicks);
76 | }
77 |
78 |
79 | extern OSStatus AEPrint(const AEDesc *desc, const char *msg) { // debugging use only (leaks memory)
80 | LOAD;
81 | Handle h = NULL; // DisposeHandle() is deprecated, so leak the returned char**
82 | OSStatus err = (*ptr_AEPrintDescToHandle)(desc, &h);
83 | if (!err) { NSLog(@"AEPrint %s: %s\n", msg, *h); }
84 | return err;
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/AppleEvents/Shim.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Shim.swift
3 | //
4 | // workaround until we have a pure Mach implementation; see also AEMShim.m
5 | //
6 |
7 |
8 | func carbonDescriptor(from desc: Descriptor, to result: inout AEDesc) {
9 | var data = desc.flatten()
10 | let err = data.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> Int in
11 | return Int(AEUnflattenDesc(ptr.baseAddress, &result))
12 | }
13 | if err != 0 { fatalError("AEUnflattenDesc error \(err), presumably due to malformed Descriptor.flatten() output.") }
14 | }
15 |
16 | // under MZ for some reason, passing AEDesc struct directly loses its data handle, so pass pointer to it
17 | func nativeDescriptor(from aeDesc: inout AEDesc) -> Descriptor {
18 | let size = AESizeOfFlattenedDesc(&aeDesc)
19 | let ptr = UnsafeMutablePointer.allocate(capacity: size)
20 | let err = Int(AEFlattenDesc(&aeDesc, ptr, size, nil))
21 | if err != 0 { fatalError("AEFlattenDesc should not fail \(err)") }
22 | return unflattenDescriptor(Data(bytesNoCopy: ptr, count: size, deallocator: .none))
23 | }
24 |
25 | public func carbonSend(event: AppleEventDescriptor) -> (code: Int, reply: ReplyEventDescriptor?) {
26 | var aeEvent = AEDesc(descriptorType: typeNull, dataHandle: nil)
27 | var aeReply = AEDesc(descriptorType: typeNull, dataHandle: nil)
28 | defer { AEDisposeDesc(&aeEvent); AEDisposeDesc(&aeReply) }
29 | carbonDescriptor(from: event, to: &aeEvent)
30 | let flags = Int32(event.interactionLevel.rawValue | (event.canSwitchLayer ? 0x40 : 0) | (event.wantsReply ? 0x03 : 0x01))
31 | let err = Int(AESendMessage(&aeEvent, &aeReply, flags, Int(event.timeout > 0 ? (event.timeout * 60) : event.timeout)))
32 | return (err, nativeDescriptor(from: &aeReply) as? ReplyEventDescriptor)
33 | }
34 |
35 |
36 | // TO DO: worth making this async as standard? or better to provide separate async alternative? (most use-cases don't require async operation, and sync is slightly simpler and safer)
37 | public func carbonReceive(message: UnsafeMutablePointer, callback: AppleEventHandler) -> OSStatus {
38 | var aeEvent = AEDesc(descriptorType: typeNull, dataHandle: nil)
39 | var aeReply = AEDesc(descriptorType: typeNull, dataHandle: nil)
40 | defer { AEDisposeDesc(&aeEvent); AEDisposeDesc(&aeReply) }
41 | let err = AEDecodeMessage(message, &aeEvent, &aeReply)
42 | //AEPrint(&aeEvent, "carbonReceive decoded aeEvent:")
43 | if err == 0 {
44 | do {
45 | guard let event = nativeDescriptor(from: &aeEvent) as? AppleEventDescriptor else { return 8 }
46 | if let result = try callback(event) {
47 | var aeResult = AEDesc(descriptorType: typeNull, dataHandle: nil)
48 | carbonDescriptor(from: result, to: &aeResult)
49 | //AEPrint(&aeResult, "handler returned aeResult:")
50 | AEPutParamDesc(&aeReply, keyAEResult, &aeResult)
51 | }
52 | } catch { // TO DO: decide how best to implement application error reporting (standard errors - e.g. 'coercion failed', 'object not found' - might be provided as enum [this would also take any necessary message, failed object, params]; this would be based on standard 'AppleEventError' protocol, allowing apps to define their own error structs/classes should they need to report custom errors as well)
53 | // one option may be to define self-packing error protocol, or at least a protocol describing all standard error fields (plus one or two new additions, e.g. domain, traceback)
54 | // TO DO: error codes should be OSStatus, aka Int32, so should still pack as typeSInt32; Q. if code is out of 32-bit range, pack as typeSInt64? or throw/log console warning and return 32-bit error code? (apart from anything else, AppleScript doesn't support 64-bit ints so returning an out-of-range codes will cause problems there)
55 | var aeError = AEDesc(descriptorType: typeNull, dataHandle: nil)
56 | defer { AEDisposeDesc(&aeError) }
57 | carbonDescriptor(from: packAsInt32(Int32(error._code)), to: &aeError)
58 | AEPutParamDesc(&aeReply, keyErrorNumber, &aeError)
59 | }
60 | //AEPrint(&aeReply, "carbonReceive sending aeReply:")
61 | if aeReply.descriptorType == typeAppleEvent { AESendMessage(&aeReply, nil, 0x01, -1) }
62 | }
63 | return err
64 | }
65 |
66 | public func carbonPort() -> mach_port_t {
67 | return AEGetRegisteredMachPort()
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/AppleEvents/ListDescriptor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListDescriptor.swift
3 | //
4 |
5 | import Foundation
6 |
7 |
8 | public struct ListDescriptor: IterableDescriptor {
9 |
10 | public var debugDescription: String {
11 | return "<\(Swift.type(of: self)) [\(self.map{ $0.debugDescription }.joined(separator: ", "))]>"
12 | }
13 |
14 | public typealias Element = Descriptor
15 | public typealias Iterator = DescriptorIterator
16 |
17 | public let type: DescType = typeAEList
18 | public let count: UInt32
19 | public let data: Data // whereas AEGetDescData() returns a complete flattened list/record (dle2), `data` only contains payload (in this case, list items); use flatten()/appendTo() to get complete list data // note: client code may wish to define its own list unpacking routines, e.g. Point/Rectangle may be quicker parsing list themselves rather than unpacking as [Int] and converting from that, particularly when supporting legacy QD struct representations as well)
20 |
21 | public init(count: UInt32, data: Data) { // also called by unflattenFirstDescriptor
22 | self.count = count
23 | self.data = data
24 | }
25 |
26 | // iteration
27 |
28 | public __consuming func makeIterator() -> Iterator {
29 | return Iterator(self)
30 | }
31 |
32 | public func element(at offset: Int) -> (item: Element, endOffset: Int) { // TO DO: type offsets as Data.Index, not Int
33 | assert(offset >= self.data.startIndex && offset < self.data.endIndex)
34 | return unflattenFirstDescriptor(in: self.data, startingAt: offset) as (Element, Int)
35 | }
36 |
37 | // serialize
38 |
39 | public func flatten() -> Data {
40 | var result = Data([0x64, 0x6c, 0x65, 0x32, // format 'dle2'
41 | 0, 0, 0, 0, // align
42 | 0x6C, 0x69, 0x73, 0x74, // type is always 'list'
43 | 0, 0, 0, 0, // [12..<16] remaining bytes (TBC)
44 | 0, 0, 0, 0, // reserved?
45 | 0, 0, 0, 0, // align?
46 | 0x00, 0x00, 0x00, 0x18, // reserved?
47 | 0x6C, 0x69, 0x73, 0x74]) // type is always 'list' (repeats 8..<12)
48 | result += encodeUInt32(self.count) // number of items
49 | result += Data([0, 0, 0, 0]) // align
50 | result += self.data // items
51 | result[(result.startIndex + 12)..<(result.startIndex + 16)] = encodeUInt32(UInt32(result.count - 16)) // set remaining bytes
52 | return result
53 | }
54 |
55 | public func appendTo(containerData result: inout Data) {
56 | result += Data([0x6C, 0x69, 0x73, 0x74]) // type is always 'list'
57 | result += encodeUInt32(UInt32(self.data.count + 8)) // remaining bytes
58 | result += encodeUInt32(self.count) // number of items
59 | result += Data([0, 0, 0, 0]) // align
60 | result += self.data // items
61 | }
62 | }
63 |
64 |
65 | extension ListDescriptor {
66 |
67 | // TO DO: rename pack/unpack?
68 |
69 | // TO DO: should error messages describe list position as 0-index or 1-index? (currently uses 0-index)
70 |
71 | init(from items: S, using packFunc: (S.Element) throws -> Descriptor) rethrows { // called by packAsArray
72 | var result = Data()
73 | var count: UInt32 = 0
74 | for value in items {
75 | do {
76 | try packFunc(value).appendTo(containerData: &result)
77 | count += 1
78 | } catch {
79 | throw AppleEventError(message: "Can't pack item \(count) of list.", cause: error)
80 | }
81 | }
82 | self.init(count: count, data: result)
83 | }
84 |
85 |
86 | func array(using unpackFunc: (Descriptor) throws -> T) rethrows -> [T] { // called by unpackAsArray // Q. throws vs rethrows?
87 | var result = [T]()
88 | for (count, descriptor) in self.enumerated() {
89 | do {
90 | result.append(try unpackFunc(descriptor))
91 | } catch {
92 | throw AppleEventError(message: "Can't unpack item \(count) of list.", cause: error)
93 | }
94 | }
95 | return result
96 | }
97 | }
98 |
99 |
--------------------------------------------------------------------------------
/AppleEvents/AppleEventHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventHandler.swift
3 | //
4 |
5 | import Foundation
6 |
7 | // TO DO: the registered Mach port receives both AE and non-AE messages; what should we do with the latter? would help to know what the non-AE message does - it precedes the first AE sent by a client, implying some sort of preparation (e.g. getting server’s AE entitlements info, as defined in its SDEF?)
8 |
9 | // TO DO: what about AppKit-based processes? installing our own Mach port source may conflict with AppKit's standard AE hooks, in which case we should install a wildcard handler via Carbon/NSAppleEventManager that forwards unhandled AEs to our own dispatcher, although that smells kludgy and inefficient)
10 |
11 | // TO DO: how should next layer above AppleEventHandler look? presumably we need some sort of app-specific glue to map Swift functions with native parameter and return types onto AppleEventHandler callbacks; should Swift functions use standardized naming conventions, allowing them to be auto-detected by glue generator and signatures mapped to 'SDEF' definitions (note: we want to architect a new, comprehensive IDL dictionary format, with basic SDEFs generated for backwards compatibility; the IDL should, as much as possible, be auto-generated from the Swift implementation)
12 |
13 |
14 | // TO DO: what about completion callbacks for async use? (analogous to suspend/resume current event) Q. what are [dis]advantages of using completion callbacks as standard? (avoids blocking [main] thread; may be slightly more expensive, cannot guarantee callback will ever be called [although clients will typically specify timeout to avoid waiting forever])
15 |
16 | // TO DO: may want to define AE handler as struct; that'll allow runtime introspection (the entire interface should be explorable, with any static definitions being generated from that) (if it weren't for AS's dependency on four-char code stability, we could also generate four-char codes on the fly; as it is, we will have to generate new four-char codes as delta to existing ones); another reason to use structs + protocol is that it makes it easy to support multimethods
17 |
18 | // Q. to what extent should we use pattern-based dispatching? (e.g. we might dispatch on topmost descriptor of target query, or on topmost descriptor's parent - e.g. in `count`; we might want to walk descriptor chain from application root, with ability to register functions at any point in that graph; safe vs mutating commands will want to use different behaviors; operations on concrete vs abstract nodes - e.g. window vs word - will definitely want to process differently; and so on; subject [target] vs object [receiver] specifiers); definitely something to be said for OSL's mix-n-match approach, though ideally we want to abstract away most if not all of that boilerplate behind a declarative IDL/DSL
19 |
20 |
21 | private func handleEvent(port: CFMachPort?, message: UnsafeMutableRawPointer?, size: CFIndex, info: UnsafeMutableRawPointer?) {
22 | // TO DO: reverse-engineer AE-over-Mach serialization format (it's not the same format as AEFlattenDesc!) and eliminate carbonReceive() kludge
23 | let header = message!.bindMemory(to: UInt32.self, capacity: 6)
24 | /*
25 | public var msgh_bits: mach_msg_bits_t
26 | public var msgh_size: mach_msg_size_t
27 | public var msgh_remote_port: mach_port_t
28 | public var msgh_local_port: mach_port_t
29 | public var msgh_voucher_port: mach_port_name_t
30 | public var msgh_id: mach_msg_id_t
31 | */
32 | print("handleEvent msgh_bits: \(String(format: "%08x", header[0])) msgh_size: \(header[1])")
33 | // carbonReceive will return (paramErr=-50) if not an AE; what other codes?
34 | let err = carbonReceive(message: message!.bindMemory(to: mach_msg_header_t.self, capacity: 1)) {
35 | // errors raised here are automatically packed into reply event
36 | try (appleEventHandlers[$0.code] ?? defaultEventHandler)($0)
37 | }
38 | if err != 0 { print("handleEvent error: \(err)") } // TO DO: delegate for non-AE messages?
39 | }
40 |
41 |
42 | // public API (this is NOT final design)
43 |
44 | public typealias AppleEventHandler = (AppleEventDescriptor) throws -> Descriptor?
45 |
46 |
47 | // installed handlers
48 | public var appleEventHandlers = [EventIdentifier: AppleEventHandler]() // this might eventually become private if we decide to mediate access (e.g. multimethods require additional logic as each handler is additive, e.g. `get document` and `get window` would both occupy same slot, regardless of whether they share a common implementation or each defines its own); also, we probably want to prevent "********" being registered here, as wildcard handler is defined separately below; Q. should we disallow installing over existing handlers? how can we safely support 'scriptable plugins' (also bear in mind that most plugins will operate as XPC subprocesses, rather than inject into main process)
49 |
50 |
51 | // wildcard handler
52 | public var defaultEventHandler: AppleEventHandler = { _ in throw AppleEventError.unsupportedAppleEvent }
53 |
54 |
55 | public func createMachPort() -> CFMachPort {
56 | return CFMachPortCreateWithPort(nil, carbonPort(), handleEvent, nil, nil)
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/test-send/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | //
4 |
5 | // unlike Apple Event Manager/NSAppleEventDescriptor, which include general list/record descriptor manipulation APIs, these APIs are solely concerned with converting data between Swift and AE types as quickly and safely as practical; descriptor records are always immutable, and do not provide random data access
6 |
7 |
8 | // TO DO: how best to map to/from client-code-defined Swift structs and enums?
9 |
10 | // TO DO: how to support default values? (in sylvia-lang, we distinguish between transient and permanent coercion errors; thus a 'missing value' coercion error that occurs on a list item can be intercepted by a 'use default item' function, but will not propagate beyond that point to inadvertently trigger a 'use default list' function)
11 |
12 | import Foundation
13 | import AppleEvents
14 |
15 | // simplest way to test our descriptors is to flatten them, then pass to AEUnflattenDesc() and wrap as NSAppleEventDescriptor and see how they compare
16 |
17 |
18 | @discardableResult func flattenNSDesc(_ desc: NSAppleEventDescriptor) -> Data {
19 | print("Flattening:", desc)
20 | var aeDesc = desc.aeDesc!.pointee
21 | let size = AESizeOfFlattenedDesc(&aeDesc)
22 | let ptr = UnsafeMutablePointer.allocate(capacity: size)
23 | let err = Int(AEFlattenDesc(&aeDesc, ptr, size, nil))
24 | if err != 0 { print("AEFlatten error \(err).") }
25 | let dat = Data(bytesNoCopy: ptr, count: size, deallocator: .none)
26 | dumpFourCharData(dat)
27 | return dat
28 | }
29 |
30 | @discardableResult func unflattenAsNSDesc(_ data: Data) -> NSAppleEventDescriptor? {
31 | var data = data
32 | var result = AEDesc(descriptorType: typeNull, dataHandle: nil)
33 | let err = data.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> Int in
34 | return Int(AEUnflattenDesc(ptr.baseAddress, &result))
35 | }
36 | if err != 0 {
37 | print("Unflatten error \(err).")
38 | return nil
39 | } else {
40 | let nsDesc = NSAppleEventDescriptor(aeDescNoCopy: &result)
41 | print(nsDesc)
42 | return nsDesc
43 | }
44 | }
45 |
46 |
47 | // pack/unpack functions are composable and reusable:
48 | let unpackAsArrayOfInt = newUnpackArrayFunc(using: unpackAsInt)
49 | let unpackAsArrayOfString = newUnpackArrayFunc(using: unpackAsString)
50 |
51 | /*
52 | do {
53 | let desc = packAsString("Hello, World!")
54 | print(desc)
55 | print(try unpackAsString(desc)) // "Hello, World!"
56 | print(try unpackAsArrayOfString(desc)) // ["Hello, World!"]
57 | }
58 |
59 |
60 |
61 |
62 | do {
63 | let desc = packAsString("32")
64 | print(try unpackAsArrayOfInt(desc)) // [32]
65 | }
66 |
67 |
68 | do {
69 | let desc = packAsArray([32, 4], using: packAsInt)
70 |
71 | dumpFourCharData(desc.flatten())
72 |
73 | print(try unpackAsArrayOfString(desc)) // ["32", "4"]
74 | }
75 | */
76 | /*
77 | do {
78 |
79 | let query = RootSpecifierDescriptor.app.elements(cDocument).byIndex(packAsInt(1)).property(pName)
80 | print(query)
81 |
82 | let d = query.flatten()
83 | //dumpFourCharData(d)
84 |
85 | unflattenAsNSDesc(d)
86 |
87 | }
88 | */
89 | /*
90 | do {
91 | //let query = RootSpecifierDescriptor.app.elements(cDocument).byIndex(packAsInt(1)).property(pName)
92 |
93 | let query = NSAppleEventDescriptor.record().coerce(toDescriptorType: typeInsertionLocation)!
94 | query.setParam(NSAppleEventDescriptor(typeCode: cProperty), forKeyword: keyAEDesiredClass)
95 | query.setParam(NSAppleEventDescriptor(enumCode: formPropertyID), forKeyword: keyAEKeyForm)
96 | query.setParam(NSAppleEventDescriptor(typeCode: 0x686f6d65), forKeyword: keyAEKeyData) // 'home'
97 | query.setParam(NSAppleEventDescriptor.null(), forKeyword: keyAEContainer)
98 |
99 | let ae = NSAppleEventDescriptor(eventClass: kAECoreSuite, eventID: kAEGetData, targetDescriptor: NSAppleEventDescriptor(bundleIdentifier: "com.apple.finder"), returnID: -1, transactionID: 0)
100 |
101 | ae.setParam(query, forKeyword: keyDirectObject)
102 | do {
103 | let result = try ae.sendEvent(options: [], timeout: 10)
104 | print(result)
105 | } catch {
106 | print(error)
107 | }
108 | print(ae)
109 | flattenNSDesc(ae)
110 | }
111 | */
112 |
113 |
114 |
115 |
116 | do {
117 | // note: app must already be running
118 | var ae = AppleEventDescriptor(code: coreEventGetData,
119 | target: try AddressDescriptor(bundleIdentifier: "com.apple.textedit"))
120 |
121 | let query = RootSpecifierDescriptor.app.elements(cDocument)
122 | ae.setParameter(keyDirectObject, to: query)
123 | let (code, reply) = ae.send()
124 | if code != 0 {
125 | print("AE error: \(code)")
126 | } else if let desc = reply?.parameter(keyErrorNumber) {
127 | print("App error: \(try unpackAsInt(desc))")
128 | } else if let result = reply?.parameter(keyAEResult) {
129 | dumpFourCharData(result.flatten())
130 | } else {
131 | print("")
132 | }
133 |
134 | } catch {
135 | print("Apple event failed:", error)
136 | }
137 |
138 |
--------------------------------------------------------------------------------
/AppleEvents/Unflatten.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Unflatten.swift
3 | //
4 |
5 | import Foundation
6 |
7 | // TO DO: Mach messages use different layout to AEFlattenDesc
8 |
9 | // caution: this does not perform bounds/sanity checks for malformed/truncated data
10 |
11 |
12 | private let formatMarker = Data([0x64, 0x6c, 0x65, 0x32]) // 'dle2'
13 |
14 |
15 | private struct Offsets {
16 | // typeOffset = 8/0
17 | // remainingBytesOffset = typeOffset+4
18 | // countOffset (reco/list) = 32/8 (additional padding inbetween)
19 | // dataStartOffset (scalar) = typeOffset+8
20 | // dataStartOffset (reco/list) = countOffset+8
21 |
22 | let type: Int
23 | let count: Int
24 | // TO DO: offsets for typeAppleEvent descriptor
25 |
26 | func add(_ offset: Int) -> Offsets {
27 | return Offsets(type: self.type + offset, count: self.count + offset)
28 | }
29 | }
30 |
31 |
32 | // offsets of descriptorType and [in AEList/AERecord only] numberOfItems fields
33 | private let dle2Offsets = Offsets(type: 8, count: 32)
34 | private let defaultOffsets = Offsets(type: 0, count: 8)
35 |
36 |
37 | // used by unflattenDescriptor(), unflattenFirstDescriptor() below
38 | // CAUTION: offsets are absolute to underlying data buffer // TO DO: how best to manage this? safest would be to wrap data in our own DataReader struct that mediates all access, adjusting offsets relative to underlying data buffer; or should we copy Data prior to instantiating new Descriptor (right now descriptors will hang onto shared underlying buffer)
39 | private func unflatten(data: Data, offsets: Offsets) throws -> (descriptor: Descriptor, endOffset: Int) {
40 | // TO DO: Descriptor.unflatten() calls should probably return end offset for sanity checking
41 | let type = data.readUInt32(at: offsets.type) // type
42 | let remainingBytesOffset = offsets.type + 4
43 | // data section's start index varies according to descriptor type
44 | let dataEnd = Int(data.readUInt32(at: remainingBytesOffset)) + remainingBytesOffset + 4 // remaining bytes
45 | let result: Descriptor
46 | switch type { // fields in brackets are only included in top-level structures flattened as dle2
47 | case typeAEList:
48 | // [format='dle2', align,] type='list', bytes, [16-byte reserved,] count, align, DATA
49 | result = ListDescriptor(count: data.readUInt32(at: offsets.count), data: data[(offsets.count + 8).. Descriptor { // analogous to AEUnflattenDesc()
80 | if data[data.startIndex..<(data.startIndex + 4)] != formatMarker {
81 | fatalError("'dle2' mark not found.") // TO DO: how to deal with malformed data? (check what AEUnflattenDesc does; if it accepts either then switch offsets)
82 | }
83 | let (result, endOffset) = try! unflatten(data: data, offsets: dle2Offsets)
84 | if endOffset != data.count { fatalError("Bad data length.") } // TO DO: ditto
85 | return result
86 | }
87 |
88 |
89 | // used by list/record/etc to unpack items
90 |
91 | // TO DO: ensure startOffset is always original Data[Slice]'s index
92 |
93 | internal func unflattenFirstDescriptor(in data: Data, startingAt startOffset: Int) -> (descriptor: Descriptor, endOffset: Int) {
94 | //print(">>>",startOffset, "in", data.startIndex, data.endIndex)
95 | if data[startOffset..<(startOffset + 4)] == formatMarker {
96 | fatalError("Unexpected 'dle2' mark found (expected descriptor type instead).")
97 | }
98 | return try! unflatten(data: data, offsets: defaultOffsets.add(startOffset))
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/AppleEvents/AppleEventError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppleEventError.swift
3 | //
4 |
5 | import Foundation
6 |
7 |
8 | internal let descriptionForError: [Int:String] = [
9 | // OS errors
10 | -34: "Disk is full.",
11 | -35: "Disk wasn't found.",
12 | -37: "Bad name for file.",
13 | -38: "File wasn't open.",
14 | -39: "End of file error.",
15 | -42: "Too many files open.",
16 | -43: "File wasn't found.",
17 | -44: "Disk is write protected.",
18 | -45: "File is locked.",
19 | -46: "Disk is locked.",
20 | -47: "File is busy.",
21 | -48: "Duplicate file name.",
22 | -49: "File is already open.",
23 | -50: "Parameter error.",
24 | -51: "File reference number error.",
25 | -61: "File not open with write permission.",
26 | -108: "Out of memory.",
27 | -120: "Folder wasn't found.",
28 | -124: "Disk is disconnected.",
29 | -128: "User canceled.",
30 | -192: "A resource wasn't found.",
31 | -600: "Application isn't running.",
32 | -601: "Not enough room to launch application with special requirements.",
33 | -602: "Application is not 32-bit clean.",
34 | -605: "More memory is needed than is specified in the size resource.",
35 | -606: "Application is background-only.",
36 | -607: "Buffer is too small.",
37 | -608: "No outstanding high-level event.",
38 | -609: "Connection is invalid.",
39 | -610: "No user interaction allowed.",
40 | -904: "Not enough system memory to connect to remote application.",
41 | -905: "Remote access is not allowed.",
42 | -906: "Application isn't running or program linking isn't enabled.",
43 | -915: "Can't find remote machine.",
44 | -30720: "Invalid date and time.",
45 | // AE errors
46 | -1700: "Can't make some data into the expected type.",
47 | -1701: "Some parameter is missing for command.",
48 | -1702: "Some data could not be read.",
49 | -1703: "Some data was the wrong type.",
50 | -1704: "Some parameter was invalid.",
51 | -1705: "Operation involving a list item failed.",
52 | -1706: "Need a newer version of the Apple Event Manager.",
53 | -1707: "Event isn't an Apple event.",
54 | -1708: "Application could not handle this command.",
55 | -1709: "AEResetTimer was passed an invalid reply.",
56 | -1710: "Invalid sending mode was passed.",
57 | -1711: "User canceled out of wait loop for reply or receipt.",
58 | -1712: "Apple event timed out.",
59 | -1713: "No user interaction allowed.",
60 | -1714: "Wrong keyword for a special function.",
61 | -1715: "Some parameter wasn't understood.",
62 | -1716: "Unknown Apple event address type.",
63 | -1717: "The handler is not defined.",
64 | -1718: "Reply has not yet arrived.",
65 | -1719: "Can't get reference. Invalid index.",
66 | -1720: "Invalid range.",
67 | -1721: "Wrong number of parameters for command.",
68 | -1723: "Can't get reference. Access not allowed.",
69 | -1725: "Illegal logical operator called.",
70 | -1726: "Illegal comparison or logical.",
71 | -1727: "Expected a reference.",
72 | -1728: "Can't get reference.",
73 | -1729: "Object counting procedure returned a negative count.",
74 | -1730: "Container specified was an empty list.",
75 | -1731: "Unknown object type.",
76 | -1739: "Attempting to perform an invalid operation on a null descriptor.",
77 | -1741: "Buffer for AEFlattenDesc too small.",
78 |
79 | // Application scripting errors
80 | -10000: "Apple event handler failed.",
81 | -10001: "Type error.",
82 | -10002: "Invalid key form.",
83 | -10003: "Can't set reference to given value. Access not allowed.",
84 | -10004: "A privilege violation occurred.",
85 | -10005: "The read operation wasn't allowed.",
86 | -10006: "Can't set reference to given value.",
87 | -10007: "The index of the event is too large to be valid.",
88 | -10008: "The specified object is a property, not an element.",
89 | -10009: "Can't supply the requested descriptor type for the data.",
90 | -10010: "The Apple event handler can't handle objects of this class.",
91 | -10011: "Couldn't handle this command because it wasn't part of the current transaction.",
92 | -10012: "The transaction to which this command belonged isn't a valid transaction.",
93 | -10013: "There is no user selection.",
94 | -10014: "Handler only handles single objects.",
95 | -10015: "Can't undo the previous Apple event or user action.",
96 | -10023: "Enumerated value is not allowed for this property.",
97 | -10024: "Class can't be an element of container.",
98 | -10025: "Illegal combination of properties settings."
99 | ]
100 |
101 |
102 |
103 | public struct AppleEventError: Error, CustomStringConvertible {
104 | public let domain = "SwiftAutomation"
105 | public let _code: Int // the OSStatus if known, or generic error code if not
106 | public let cause: Error? // the error that triggered this failure, if any
107 |
108 | let _message: String?
109 |
110 | public init(code: Int, message: String? = nil, cause: Error? = nil) {
111 | self._code = code
112 | self._message = message
113 | self.cause = cause
114 | }
115 |
116 | public init(message: String, cause: Error) { // chain errors to provide contextual information
117 | self.init(code: cause._code, message: message, cause: cause)
118 | }
119 |
120 | public var code: Int { return self._code }
121 | public var message: String? { return self._message } // TO DO: make non-optional?
122 |
123 | func description(_ previousCode: Int, separator: String = " ") -> String {
124 | let msg = self.message ?? descriptionForError[self._code]
125 | var string = self._code == previousCode ? "" : "Error \(self._code)\(msg == nil ? "." : ": ")"
126 | if let msg = msg { string += msg }
127 | if let error = self.cause as? AppleEventError {
128 | string += "\(separator)\(error.description(self._code))"
129 | } else if let error = self.cause {
130 | string += "\(separator)\(error)"
131 | }
132 | return string
133 | }
134 |
135 | public var description: String {
136 | return self.description(0)
137 | }
138 | }
139 |
140 | public extension AppleEventError {
141 | // TO DO: check these names are correct; what other codes?
142 | static let unsupportedCoercion = AppleEventError(code: -1700) // TO DO: what about taking desc types as arguments?
143 | static let missingParameter = AppleEventError(code: -1701)
144 | static let corruptData = AppleEventError(code: -1702)
145 | static let unsupportedType = AppleEventError(code: -1702)
146 | static let invalidParameter = AppleEventError(code: -1704)
147 | static let unsupportedAppleEvent = AppleEventError(code: -1708)
148 | static let appleEventTimedOut = AppleEventError(code: -1712)
149 | static let noUserInteraction = AppleEventError(code: -1713)
150 | static let invalidIndex = AppleEventError(code: -1719)
151 | static let invalidRange = AppleEventError(code: -1720)
152 | static let invalidParameterCount = AppleEventError(code: -1721)
153 | static let referenceNotFound = AppleEventError(code: -1728)
154 | }
155 |
--------------------------------------------------------------------------------
/AppleEvents/Support.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Support.swift
3 | //
4 |
5 | // TO DO: which utility funcs should be public?
6 |
7 | import Foundation
8 |
9 |
10 | //#if !canImport(Carbon)
11 |
12 | public typealias OSType = UInt32
13 | public typealias DescType = OSType
14 | public typealias AEKeyword = OSType
15 |
16 | //#endif
17 |
18 |
19 | public typealias EventIdentifier = UInt64 // combined eventClass and eventID
20 |
21 |
22 | public let noOSType: OSType = 0
23 |
24 | internal let epochDelta: TimeInterval = 35430.0 * 24 * 3600; // offset from 1904-01-01 to 2001-01-01
25 |
26 | internal var isLittleEndianHost: Bool { let n: UInt16 = 1; return n.littleEndian == n }
27 |
28 |
29 |
30 | extension Data {
31 | func readUInt32(at offset: Int) -> UInt32 { // read desc type, bytes remaining, count, etc; caution: this does not perform bounds checks // important: Data slices may not start at 0; use `data.readUInt32(at:data.startIndex+offset)`
32 | return try! decodeUInt32(self[offset..<(offset + 4)])
33 | }
34 | }
35 |
36 |
37 |
38 | // used by encodeTYPE/decodeTYPE funcs; caution: do not use directly for integer conversions as they are not endian-safe
39 |
40 | internal func encodeFixedWidthValue(_ value: T) -> Data {
41 | return Swift.withUnsafeBytes(of: value){ Data($0) }
42 | }
43 |
44 | internal func decodeFixedWidthValue(_ data: Data) throws -> T {
45 | if data.count != MemoryLayout.size { throw AppleEventError.corruptData }
46 | return data.withUnsafeBytes{ $0.baseAddress!.assumingMemoryBound(to: T.self).pointee } // TO DO: use bindMemory(to:capacity:)?
47 | }
48 |
49 |
50 | // endian-safe integer pack/unpack (caution: these store numeric values as big-endian so are not binary-compatible with Carbon AEDescs' storage (numeric AEDescs use native-endian storage and only convert to big-endian when packed into a complex [list/record/event] descriptor) // also used in SwiftAutomation.AETEParser
51 |
52 | public func encodeFixedWidthInteger(_ value: T) -> Data {
53 | return encodeFixedWidthValue(T(bigEndian: value))
54 | }
55 | public func decodeFixedWidthInteger(_ data: Data) throws -> T {
56 | return T(bigEndian: try decodeFixedWidthValue(data))
57 | }
58 |
59 | // convenience wrappers around the above // TO DO: internal?
60 |
61 | public func encodeInt64(_ value: Int64) -> Data {
62 | return encodeFixedWidthInteger(value)
63 | }
64 | public func encodeInt32(_ value: Int32) -> Data { // e.g. pid_t
65 | return encodeFixedWidthInteger(value)
66 | }
67 | public func encodeInt16(_ value: Int16) -> Data { // e.g. AEReturnID
68 | return encodeFixedWidthInteger(value)
69 | }
70 | public func encodeInt8(_ value: Int8) -> Data {
71 | return encodeFixedWidthInteger(value)
72 | }
73 |
74 | public func encodeUInt64(_ value: UInt64) -> Data { // e.g. EventIdentifier
75 | return encodeFixedWidthInteger(value)
76 | }
77 | public func encodeUInt32(_ value: UInt32) -> Data { // e.g. OSType
78 | return encodeFixedWidthInteger(value)
79 | }
80 | public func encodeUInt16(_ value: UInt16) -> Data {
81 | return encodeFixedWidthInteger(value)
82 | }
83 | public func encodeUInt8(_ value: UInt8) -> Data {
84 | return encodeFixedWidthInteger(value)
85 | }
86 |
87 |
88 |
89 | public func decodeInt64(_ data: Data) throws -> Int64 {
90 | return try decodeFixedWidthInteger(data)
91 | }
92 | public func decodeInt32(_ data: Data) throws -> Int32 {
93 | return try decodeFixedWidthInteger(data)
94 | }
95 | public func decodeInt16(_ data: Data) throws -> Int16 {
96 | return try decodeFixedWidthInteger(data)
97 | }
98 | public func decodeInt8(_ data: Data) throws -> Int8 {
99 | return try decodeFixedWidthInteger(data)
100 | }
101 |
102 | public func decodeUInt64(_ data: Data) throws -> UInt64 {
103 | return try decodeFixedWidthInteger(data)
104 | }
105 | public func decodeUInt32(_ data: Data) throws -> UInt32 {
106 | return try decodeFixedWidthInteger(data)
107 | }
108 | public func decodeUInt16(_ data: Data) throws -> UInt16 {
109 | return try decodeFixedWidthInteger(data)
110 | }
111 | public func decodeUInt8(_ data: Data) throws -> UInt8 {
112 | return try decodeFixedWidthInteger(data)
113 | }
114 |
115 | // pack/unpack UTF8-encoded data used in various descriptors (e.g. typeUTF8Text, typeFileURL, typeApplicationBundleID)
116 |
117 | internal func encodeUTF8String(_ value: String) -> Data { // confirm fileURL, bundleID is always UTF8-encoded
118 | return Data(value.utf8)
119 | }
120 |
121 | internal func decodeUTF8String(_ data: Data) -> String? {
122 | return String(data: data, encoding: .utf8)
123 | }
124 |
125 | // ditto for ISO8601 date strings (although AE dates are traditionally Int64)
126 |
127 | func encodeISO8601Date(_ value: Date) throws -> Data {
128 | return encodeUTF8String(ISO8601DateFormatter().string(from: value))
129 | }
130 |
131 | func decodeISO8601Date(_ data: Data) throws -> Date? {
132 | if let string = decodeUTF8String(data) { return ISO8601DateFormatter().date(from: string) }
133 | return nil
134 | }
135 |
136 |
137 | // utility functions for creating and splitting eight-char codes
138 |
139 | public func eventIdentifier(_ eventClass: AEEventClass, _ eventID: AEEventID) -> EventIdentifier {
140 | return (UInt64(eventClass) << 32) | UInt64(eventID)
141 | }
142 |
143 | public func eventIdentifier(_ eventIdentifier: EventIdentifier) -> (AEEventClass, AEEventID) {
144 | return (UInt32(eventIdentifier >> 32), UInt32(eventIdentifier % (1 << 32)))
145 | }
146 |
147 |
148 | // convert an OSType to String literal representation, e.g. 'docu' -> "\"docu\"", or hexadecimal integer if it contains problem characters
149 |
150 | public func literalFourCharCode(_ code: OSType) -> String {
151 | var bigCode = UInt32(bigEndian: code)
152 | var result = ""
153 | for _ in 0.. 0x7E { // found a non-printing, backslash, single quote, or non-ASCII character
156 | return String(format: "0x%08x", code)
157 | }
158 | result += String(format: "%c", c)
159 | bigCode >>= 8
160 | }
161 | return "\"\(result)\""
162 | }
163 |
164 | public func literalEightCharCode(_ code: EventIdentifier) -> String {
165 | var bigCode = UInt64(bigEndian: code)
166 | var result = ""
167 | for _ in 0.. 0x7E { // found a non-printing, backslash, single quote, or non-ASCII character
170 | return String(format: "0x%08x_%08x", UInt32(code >> 32), UInt32(code % (1 << 32)))
171 | }
172 | result += String(format: "%c", c)
173 | bigCode >>= 8
174 | }
175 | return "\"\(result)\""
176 | }
177 |
178 |
179 | // TO DO: functions for converting to/from four-char MacRoman strings
180 |
181 |
182 | // development
183 |
184 | public func dumpFourCharData(_ data: Data) {
185 | let data = Data(data)
186 | print("/*")
187 | for i in 0..<(data.count / 4) {
188 | print(" * ", literalFourCharCode(data.readUInt32(at: i * 4)))
189 | }
190 | let rem = data.count % 4
191 | if rem != 0 {
192 | var n = "0x"; var s: String! = ""
193 | for c in data[(data.count - rem)..<(data.count)] {
194 | if c < 0x20 || c == 0x27 || c == 0x5C || c > 0x7E { s = nil }
195 | n += String(format: "%02x", c)
196 | if s != nil { s += String(format: "%c", c) }
197 | }
198 | print(" * ", s != nil ? "\"\(s!)\"" : n)
199 | }
200 | print(" */")
201 | }
202 |
--------------------------------------------------------------------------------
/AppleEvents/PackFuncs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Pack.swift
3 | //
4 |
5 | import Foundation
6 |
7 |
8 | // keeping these as standalone functions allows List/Record descriptors to provide optimized packing in common use cases (e.g. list of string); Q. can functions that take Data as argument operate on slices of an existing Data value? (i.e. we don't want to create an extra data copying step when iterating over a list desc); OTOH, we might want to wrap pairs of pack/unpack functions in 'Coercion' structs, as those can then provide additional features such as bounds checks and documentation generation (c.f. sylvia-lang; mostly it depends on how apps implement their server-side AE interfaces - if it's all code-generated then structs are redundant as the glue generator will produce both code and docs in parallel, and can just as easily generate introspection support; OTOH, if users write interface code directly then docs and introspection must driven by that; in an ideal world, the AE interface would be described in a sylvia-lang dialect, which then generates the Swift code, etc)
9 |
10 |
11 | // TO DO: this is problematic as it requires unflattenDescriptor() to recognize AERecords with non-reco type and return them as RecordDescriptor, not ScalarDescriptor; e.g. an AERecord of type cDocument has the same layout as non-collection AEDescs of typeInteger/typeUTF8Text/etc, so how does AEIsRecord() tell the difference?
12 |
13 |
14 | // public pack functions should always be of type `(T) -> Descriptor` (scalar); pack funcs that validate input values should also throw
15 |
16 |
17 | public func packAsBool(_ value: Bool) -> ScalarDescriptor {
18 | return value ? trueDescriptor : falseDescriptor
19 | }
20 |
21 | public func packAsInt16(_ value: Int16) -> ScalarDescriptor {
22 | return ScalarDescriptor(type: typeSInt16, data: encodeFixedWidthInteger(value))
23 | }
24 | public func packAsUInt16(_ value: UInt16) -> ScalarDescriptor {
25 | return ScalarDescriptor(type: typeUInt16, data: encodeFixedWidthInteger(value))
26 | }
27 |
28 | public func packAsInt32(_ value: Int32) -> ScalarDescriptor {
29 | return ScalarDescriptor(type: typeSInt32, data: encodeFixedWidthInteger(value))
30 | }
31 | public func packAsUInt32(_ value: UInt32) -> ScalarDescriptor {
32 | return ScalarDescriptor(type: typeUInt32, data: encodeFixedWidthInteger(value))
33 | }
34 |
35 | public func packAsInt64(_ value: Int64) -> ScalarDescriptor {
36 | return ScalarDescriptor(type: typeSInt64, data: encodeFixedWidthInteger(value))
37 | }
38 | public func packAsUInt64(_ value: UInt64) -> ScalarDescriptor {
39 | return ScalarDescriptor(type: typeUInt64, data: encodeFixedWidthInteger(value))
40 | }
41 |
42 | public func packAsInt(_ value: Int) -> ScalarDescriptor { // caution: this always packs Int/UInt as typeSInt64/typeUInt64; this may break compatibility with poorly implemented apps that blindly expect typeSInt32 (because that's what AS gives them) instead of telling AEM that's what they need; while we could check if value falls within Int32.min…Int32.max and preferentially pack as typeSInt32, that's more work
43 | return packAsInt64(Int64(value))
44 | }
45 | public func packAsUInt(_ value: UInt) -> ScalarDescriptor { // ditto
46 | return packAsUInt64(UInt64(value))
47 | }
48 |
49 | public func packAsDouble(_ value: Double) -> ScalarDescriptor {
50 | return ScalarDescriptor(type: typeIEEE64BitFloatingPoint, data: encodeFixedWidthValue(value))
51 | }
52 |
53 | public func packAsString(_ value: String) -> ScalarDescriptor {
54 | return ScalarDescriptor(type: typeUTF8Text, data: encodeUTF8String(value))
55 | }
56 |
57 | public func packAsDate(_ value: Date) -> ScalarDescriptor {
58 | // caution: typeLongDateTime does not support sub-second precision; unfortunately, there isn't a desc type for TimeInterval (Double) since OSX epoch
59 | // TO DO: what about typeISO8601DateTime?
60 | return ScalarDescriptor(type: typeLongDateTime, data: encodeFixedWidthInteger(Int64(value.timeIntervalSinceReferenceDate - epochDelta)))
61 | }
62 |
63 | public func packAsFileURL(_ value: URL) throws -> ScalarDescriptor {
64 | // TO DO: option/initializer to create bookmark? (Q. how should bookmarks be supported?)
65 | // func bookmarkData(options: URL.BookmarkCreationOptions = [], includingResourceValuesForKeys keys: Set? = nil, relativeTo url: URL? = nil) throws -> Data
66 | if !value.isFileURL { throw AppleEventError.unsupportedCoercion } // TO DO: what error?
67 | return ScalarDescriptor(type: typeFileURL, data: encodeUTF8String(value.absoluteString))
68 | }
69 |
70 | public func packAsFourCharCode(type: DescType, code: OSType) -> ScalarDescriptor { // other four-char codes // TO DO: this is not ideal as caller can freely pass invalid types; safer to define dedicated initializers for all relevant types
71 | return ScalarDescriptor(type: type, data: encodeUInt32(code))
72 | }
73 |
74 | public func packAsType(_ value: OSType) -> ScalarDescriptor {
75 | // TO DO: how should cMissingValue be handled? (not much we can do about it here, as it must pack)
76 | return ScalarDescriptor(type: typeType, data: encodeUInt32(value))
77 | }
78 |
79 | public func packAsEnum(_ value: OSType) -> ScalarDescriptor {
80 | // TO DO: should we check for absolute ordinal values and pack as typeAbsoluteOrdinal as special case? (mostly depends on whether they've any possible use cases outside of by-index specifiers, e.g. in introspection APIs [TBH, it shouldn't matter what type they are as long as by-index specifiers continue to be built using typeAbsoluteOrdinal]); TBH, we probably want to treat both typeType and typeEnumerated as interchangeable (there was never any obvious reason not to have a single AE desc type cover all OSTypes, and might well have avoided various bugs and other confusion when mapping human-readable names to and from four-char codes)
81 | return ScalarDescriptor(type: typeEnumerated, data: encodeUInt32(value))
82 | }
83 |
84 | public func packAsDescriptor(_ value: Descriptor) -> Descriptor {
85 | return value
86 | }
87 |
88 |
89 | // TO DO: delete packAsArray?
90 |
91 | public func packAsArray(_ items: S, using packFunc: (S.Element) throws -> Descriptor) rethrows -> ListDescriptor {
92 | return try ListDescriptor(from: items, using: packFunc)
93 | }
94 |
95 | public func newPackArrayFunc(using packFunc: @escaping (T) throws -> Descriptor) -> ([T]) throws -> Descriptor {
96 | return { try ListDescriptor(from: $0, using: packFunc) }
97 | }
98 |
99 |
100 | // TO DO: how best to compose pack/unpack/validate behaviors for AERecords? Swift's type system gets a tad twitchy when attempting to nest generic functions (also, where should user-defined record keys be handled? here, or in higher-level client code?)
101 |
102 | /*
103 | try packAsRecord(self.lazy.map{ (key: Key, value: Value) -> (AEKeyword, Value) in
104 | if let key = key as? Symbol, key.code != noOSType { return (key.code, value) }
105 | throw AppleEventError.unsupportedCoercion
106 | }, using: appData.pack)
107 | */
108 |
109 |
110 | public func packAsRecord(_ items: S, using packFunc: (T) throws -> Descriptor) throws -> RecordDescriptor
111 | where S.Element == (AEKeyword, T) {
112 | var result = Data()
113 | var count: UInt32 = 0
114 | var type = typeAERecord
115 | var keys = Set()
116 | for (key, value) in items {
117 | if keys.contains(key) {
118 | throw AppleEventError(code: -1704, message: "Can't pack item \(literalFourCharCode(key)) of record: duplicate key.")
119 | }
120 | keys.insert(key)
121 | do {
122 | let desc = try packFunc(value)
123 | if key == pClass, let cls = try? unpackAsType(desc) {
124 | type = cls
125 | } else {
126 | result += encodeUInt32(key)
127 | desc.appendTo(containerData: &result)
128 | count += 1
129 | }
130 | } catch {
131 | throw AppleEventError(message: "Can't pack item \(literalFourCharCode(key)) of record.", cause: error)
132 | }
133 | }
134 | return RecordDescriptor(type: type, count: count, data: result)
135 | }
136 |
137 |
--------------------------------------------------------------------------------
/AppleEvents/RecordDescriptor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordDescriptor.swift
3 | //
4 |
5 | import Foundation
6 |
7 | // Q. better to implement this as pack/unpack protocol, leaving clients to implement their own structs/classes (if so, we need more capable conversion funcs, including ability to specify optionals, enums, etc)
8 |
9 | // TO DO: define separate descriptor structs for constructing specifiers? or define as methods on RecordDescriptor which pack/unpack Data directly
10 |
11 | // TO DO: what about AERecords with arbitrary type, e.g. `{class:document,name:…}`? one option is to make internal init public and trust clients to implement correctly; another is to implement 'RecordBuilder' struct that implements `public mutating func set(key:value:using:) throws`, or `public mutating func set(key:OSType,value:Descriptor)[throws?]` which is called repeatedly to pack each attribute (this func can be used to pack dynamic dictionaries as well as static structs); a third is to make RecordDescriptor mutable, appending directly to its data and updating existing keys, count, and bytes remaining each time (might be best to have `static func with{…}` style method which passes in a RecordBuilder and initializes the RecordDescriptor on return); or we can iterate an [(OSType,Descriptor)] sequence (although we're trying to avoid unnecessary looping)
12 |
13 |
14 |
15 | /*
16 | wrapped typeAERecord (wrapped typeAEList has the same layout):
17 |
18 | 1684825394 'dle2' # format
19 | 0 '\x00\x00\x00\x00' # align
20 | 1919247215 'reco' # type
21 | 64 '\x00\x00\x00@' # bytes remaining
22 |
23 | 0 '\x00\x00\x00\x00' # ? (these 16 bytes only appear when type is list/reco)
24 | 0 '\x00\x00\x00\x00' # ?
25 | 24 '\x00\x00\x00\x18' # ?
26 | 1919247215 'reco' # ? type
27 |
28 | 3 '\x00\x00\x00\x03' # count
29 | 0 '\x00\x00\x00\x00' # align
30 | …
31 |
32 | wrapped records with any other type omit the middle block (i.e. same layout as wrapped scalar descriptor):
33 |
34 | 1684825394 'dle2' # format
35 | 0 '\x00\x00\x00\x00' # align
36 | 1685021557 'docu' # type
37 | 32 '\x00\x00\x00 ' # bytes remaining
38 |
39 | 2 '\x00\x00\x00\x02' # count
40 | 0 '\x00\x00\x00\x00' # align
41 | …
42 |
43 | nested lists/records always use short form:
44 |
45 | 1919247215 'reco' # type
46 | 24 '\x00\x00\x00\x18' # remaining bytes (TBC)
47 | 1 '\x00\x00\x00\x01' # count
48 | 0 '\x00\x00\x00\x00' # align
49 | */
50 |
51 | /*
52 | Each property in record consists of a key (OSType) and associated value (flattened AEDesc), e.g:
53 |
54 | 1886282093 'pnam' # key
55 | 1954115685 'type' # value (type)
56 | 4 '\x00\x00\x00\x04' # value (bytes remaining)
57 | 1685021557 'docu' # value (data)
58 | */
59 |
60 |
61 |
62 | public struct RecordDescriptor: IterableDescriptor {
63 |
64 | public var debugDescription: String {
65 | return "<\(Swift.type(of: self)) [\(self.map{ "\(literalFourCharCode($0)):\($1.debugDescription)" }.joined(separator: ", "))]>"
66 | }
67 |
68 | public typealias Element = (key: AEKeyword, value: Descriptor)
69 | public typealias Iterator = DescriptorIterator
70 |
71 | public let type: DescType
72 | public let count: UInt32
73 | public let data: Data // caution: whereas AEGetDescData() returns complete flattened list/record (dle2), this contains list items only; use flatten()/appendTo() to get complete list data // note: client code may wish to define its own list unpacking routines, e.g. Point/Rectangle may be quicker parsing list themselves rather than unpacking as [Int] and converting from that, particularly when supporting legacy QD struct representations as well)
74 |
75 | public init(type: DescType, count: UInt32, data: Data) { // also called by unflattenFirstDescriptor
76 | self.type = type
77 | self.count = count
78 | self.data = data // key-value pairs, where key is DescType and value is added via appendTo()
79 | }
80 |
81 | // iteration
82 |
83 | public __consuming func makeIterator() -> Iterator {
84 | return Iterator(self)
85 | }
86 |
87 | public func element(at offset: Int) -> (item: Element, endOffset: Int) {
88 | let key = self.data.readUInt32(at: offset)
89 | let (value, endOffset) = unflattenFirstDescriptor(in: self.data, startingAt: offset + 4)
90 | return ((key, value) as Element, endOffset)
91 | }
92 |
93 | // TO DO: what about `descriptor(for key:)->Descriptor`? or do we require client code to unpack via iterator? could provide RecordReader as complement to RecordBuilder (records tend to be short, so just loop over keys and remaining bytes to build a map of keys to value offsets up front); that would allow non-sequential access by key while ignoring unknown fields or throwing on missing fields (unpackfuncs can use same technique as sylvia lang to intercept key-not-found errors and return default values for fields that can have them), and both can be driven from a single IDL definition
94 |
95 | // serialization
96 |
97 | public func flatten() -> Data {
98 | var result = Data([0x64, 0x6c, 0x65, 0x32, // 'dle2' format marker
99 | 0, 0, 0, 0]) // align
100 | result += encodeUInt32(self.type) // type
101 | result += Data([0, 0, 0, 0]) // remaining bytes (TBC)
102 | if self.type == typeAERecord { // reserved block ('dle2'-wrapped 'reco' only)
103 | result += Data([0, 0, 0, 0,
104 | 0, 0, 0, 0,
105 | 0, 0, 0, 0x18])
106 | result += encodeUInt32(self.type) // type (again)
107 | }
108 | result += encodeUInt32(UInt32(self.count)) // number of items
109 | result += Data([0, 0, 0, 0]) // align
110 | result += self.data // zero or more key-value pairs
111 | result[(result.startIndex + 12)..<(result.startIndex + 16)] = encodeUInt32(UInt32(result.count - 16)) // calculate and set remaining bytes
112 | return result
113 | }
114 |
115 | public func appendTo(containerData result: inout Data) {
116 | // appends this record to a list (item), record (property value), or event (attribute/parameter value)
117 | result += encodeUInt32(self.type) // type
118 | result += encodeUInt32(UInt32(self.data.count + 8)) // remaining bytes (= count + align + self.data)
119 | result += encodeUInt32(self.count) // number of items
120 | result += Data([0, 0, 0, 0]) // align
121 | result += self.data // zero or more key-value pairs
122 | }
123 | }
124 |
125 |
126 |
127 |
128 | public extension RecordDescriptor {
129 |
130 | // note: when packing/unpacking as dictionaries, T is normally `Any` as typical record properties are mixed type
131 |
132 | // TO DO: how best to handle non-reco descriptor types? (for now we attempt to store record type as 'class' property, c.f. AppleScript records, with the caveat that this information is silently discarded if DescType cannot be cast to/from T)
133 |
134 | // TO DO: optional keyFunc for mapping dictionary keys to/from AEKeyword (this'll also allow for better error messages, using dictionary keys instead of four-char codes when possible)
135 |
136 | func dictionary(using unpackFunc: (Descriptor) throws -> T) rethrows -> [AEKeyword: T] { // TO DO: should unpack func take (AEKeyword,Descriptor) and return (K,V)?
137 | var result = [AEKeyword: T]()
138 | if self.type != typeAERecord, let cls = self.type as? T {
139 | result[pClass] = cls
140 | }
141 | for (key, descriptor) in self {
142 | do {
143 | result[key] = try unpackFunc(descriptor)
144 | } catch {
145 | throw AppleEventError(message: "Can't unpack item \(literalFourCharCode(key)) of record.", cause: error)
146 | }
147 | }
148 | return result
149 | }
150 | }
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/AppleEvents/TestDescriptors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestDescriptors.swift
3 | //
4 |
5 | import Foundation
6 |
7 |
8 |
9 | public struct ComparisonDescriptor: TestDescriptor {
10 |
11 | public var debugDescription: String {
12 | return "<\(Swift.type(of: self)) \(self.object) \(self.comparison) \(self.value)>"
13 | }
14 |
15 | public enum Operator: OSType {
16 | case lessThan = 0x3C202020 // kAELessThan
17 | case lessThanOrEqual = 0x3C3D2020 // kAELessThanEquals
18 | case equal = 0x3D202020 // kAEEquals
19 | case notEqual = 0x00000001 // kAENotEquals // will pack as kAEEquals + kAENOT
20 | case greaterThan = 0x3E202020 // kAEGreaterThan
21 | case greaterThanOrEqual = 0x3E3D2020 // kAEGreaterThanEquals
22 | case beginsWith = 0x62677774 // kAEBeginsWith
23 | case endsWith = 0x656E6473 // kAEEndsWith
24 | case contains = 0x636F6E74 // kAEContains
25 | case isIn = 0x00000002 // kAEIsIn // will pack as kAEContains with operands reversed
26 | }
27 |
28 | public let type: DescType = typeCompDescriptor
29 |
30 | public var data: Data { // caution: this returns an incomplete representation of .notEqual; see also appendTo(:)
31 | let lhs: Descriptor, op: Operator, rhs: Descriptor
32 | switch self.comparison {
33 | case .notEqual: (lhs, op, rhs) = (self.object, .equal, self.value) // convert 'A != B' to 'NOT (A == B)'
34 | case .isIn: (lhs, op, rhs) = (self.value, .contains, self.object) // convert 'A in B' to 'B contains A'
35 | default: (lhs, op, rhs) = (self.object, self.comparison, self.value)
36 | }
37 | var result = Data([0x00, 0x00, 0x00, 0x03, // count (object1, operator, object2)
38 | 0, 0, 0, 0, // align
39 | 0x6F, 0x62, 0x6A, 0x31]) // * keyAEObject1
40 | lhs.appendTo(containerData: &result) // descriptor
41 | result += Data([0x72, 0x65, 0x6C, 0x6F, // * keyAECompOperator
42 | 0x65, 0x6E, 0x75, 0x6D, // typeEnumerated
43 | 0x00, 0x00, 0x00, 0x04]) // size (4 bytes)
44 | result += encodeUInt32(op.rawValue) // enum code
45 | result += Data([0x6F, 0x62, 0x6A, 0x32]) // * keyAEObject2
46 | rhs.appendTo(containerData: &result) // descriptor
47 | return result
48 | }
49 |
50 | public let object: QueryDescriptor // either RootSpecifierDescriptor.its (e.g. `words where it begins with "a"`) or InsertionLocationDescriptor (TO DO: SpecifierProtocol?)
51 | public let comparison: Operator
52 | public let value: Descriptor // this may be a primitive value or another query
53 |
54 | // TO DO: TEMPORARY; SwiftAutomation currently creates directly
55 | public init(object: QueryDescriptor, comparison: Operator, value: Descriptor) {
56 | self.object = object
57 | self.comparison = comparison
58 | self.value = value
59 | }
60 |
61 | public func appendTo(containerData result: inout Data) {
62 | // an 'is not equal to' test is constructed by wrapping a kAEEquals comparison descriptor in a kAENOT logical descriptor
63 | if self.comparison == .notEqual {
64 | result += Data([0x6C, 0x6F, 0x67, 0x69]) // typeLogicalDescriptor
65 | result += encodeUInt32(UInt32(self.data.count + 52)) // remaining bytes // TO DO: check this is correct
66 | result += Data([0x00, 0x00, 0x00, 0x02, // count (operator, operands list)
67 | 0, 0, 0, 0, // align
68 | 0x6C, 0x6F, 0x67, 0x63, // * keyAELogicalOperator
69 | 0x65, 0x6E, 0x75, 0x6D, // typeEnumerated
70 | 0x00, 0x00, 0x00, 0x04, // size (4 bytes)
71 | 0x4E, 0x4F, 0x54, 0x20, // kAENOT
72 | 0x74, 0x65, 0x72, 0x6D, // * keyAELogicalTerms // a single-item list containing this comparison
73 | 0x6C, 0x69, 0x73, 0x74]) // typeAEList
74 | result += encodeUInt32(UInt32(self.data.count + 16)) // remaining bytes // TO DO: ditto
75 | result += Data([0x00, 0x00, 0x00, 0x01, // number of items
76 | 0, 0, 0, 0]) // align
77 | }
78 | // append this comparison descriptor
79 | result += encodeUInt32(self.type) // descriptor type
80 | result += encodeUInt32(UInt32(self.data.count)) // remaining bytes
81 | result += self.data // descriptor data
82 | }
83 |
84 | // called by Unflatten.swift
85 | internal static func unflatten(_ data: Data, startingAt descStart: Int) throws -> ComparisonDescriptor {
86 | // type, remaining bytes // TO DO: sanity check these?
87 | var object: Descriptor? = nil, comparison: OSType? = nil, value: Descriptor? = nil
88 | let countOffset = descStart + 8
89 | if data.readUInt32(at: countOffset) != 3 { throw AppleEventError.invalidParameterCount }
90 | var offset = countOffset + 8
91 | for _ in 0..<3 {
92 | let key = data[offset..<(offset+4)]
93 | switch key {
94 | case Data([0x6F, 0x62, 0x6A, 0x31]): // * keyAEObject1
95 | let desc: Descriptor // QueryDescriptor/Descriptor
96 | (desc, offset) = unflattenFirstDescriptor(in: data, startingAt: offset+4)
97 | // object specifier's parent is either an object specifier or its terminal [root] descriptor
98 | object = desc
99 | case Data([0x72, 0x65, 0x6C, 0x6F]) // * keyAECompOperator
100 | where data[(offset+4)..<(offset+12)] == Data([0x65, 0x6E, 0x75, 0x6D, // typeEnumerated
101 | 0x00, 0x00, 0x00, 0x04]): // size (4 bytes)
102 | comparison = data.readUInt32(at: offset+12)
103 | offset += 16
104 | case Data([0x6F, 0x62, 0x6A, 0x32]): // * keyAEObject2
105 | let desc: Descriptor // QueryDescriptor/Descriptor
106 | (desc, offset) = unflattenFirstDescriptor(in: data, startingAt: offset+4)
107 | // object specifier's parent is either an object specifier or its terminal [root] descriptor
108 | value = desc
109 | default:
110 | throw AppleEventError.invalidParameter
111 | }
112 | }
113 | guard var object_ = object, let comparison_ = comparison, var value_ = value,
114 | var comparison__ = Operator(rawValue: comparison_) else {
115 | throw AppleEventError.invalidParameter
116 | }
117 | if comparison__ == .contains && !(object_ is QueryDescriptor) {
118 | (object_, comparison__, value_) = (value_, .isIn, object_)
119 | }
120 | guard let object__ = object_ as? QueryDescriptor else { throw AppleEventError.invalidParameter }
121 | return ComparisonDescriptor(object: object__, comparison: comparison__, value: value_)
122 | }
123 |
124 | }
125 |
126 |
127 | // logical tests, e.g. `not TEST`, `TEST1 or TEST2`, `and(TEST1,TEST2,TEST3,…)`
128 |
129 | public struct LogicalDescriptor: TestDescriptor {
130 |
131 | public var debugDescription: String {
132 | return "<\(Swift.type(of: self)) \(self.logical) \(self.operands)>"
133 | }
134 |
135 | public enum Operator: OSType {
136 | case AND = 0x414E4420 // kAEAND
137 | case OR = 0x4F522020 // kAEOR
138 | case NOT = 0x4E4F5420 // kAENOT
139 | }
140 |
141 | public let type: DescType = typeLogicalDescriptor
142 |
143 | public var data: Data {
144 | var result = Data([0x00, 0x00, 0x00, 0x02, // count
145 | 0, 0, 0, 0, // align
146 | 0x6C, 0x6F, 0x67, 0x63, // * keyAELogicalOperator
147 | 0x65, 0x6E, 0x75, 0x6D, // typeEnumerated
148 | 0x00, 0x00, 0x00, 0x04]) // size (4 bytes)
149 | result += encodeUInt32(self.logical.rawValue) // enum code
150 | result += Data([0x74, 0x65, 0x72, 0x6D]) // * keyAELogicalTerms
151 | self.operands.appendTo(containerData: &result) // descriptor
152 | return result
153 | }
154 |
155 | public let logical: Operator
156 | public let operands: ListDescriptor // must contain logical and/or comparison descriptors; initializers must ensure correct no of operands (>=2 for AND/OR; ==1 for NOT)
157 |
158 | // TO DO: TEMPORARY; SwiftAutomation currently creates directly
159 | public init(logical: Operator, operands: ListDescriptor) {
160 | self.logical = logical
161 | self.operands = operands
162 | }
163 |
164 | private init(logical: Operator, operands: [TestDescriptor]) { // used by AND/OR initializers below
165 | if operands.count < (logical == .NOT ? 1 : 2) { fatalError("Too few operands.") } // TO DO: how to deal with errors?
166 | self.logical = logical
167 | var result = Data()
168 | for op in operands { op.appendTo(containerData: &result) }
169 | self.operands = ListDescriptor(count: UInt32(operands.count), data: result)
170 | }
171 |
172 | // TO DO: use varargs for following? that'd prevent too few args being passed (first two args would be explicit)
173 |
174 | public init(AND operands: [TestDescriptor]) {
175 | self.init(logical: .AND, operands: operands)
176 | }
177 |
178 | public init(OR operands: [TestDescriptor]) {
179 | self.init(logical: .OR, operands: operands)
180 | }
181 |
182 | public init(NOT operand: TestDescriptor) {
183 | self.logical = .NOT
184 | var result = Data()
185 | operand.appendTo(containerData: &result)
186 | self.operands = ListDescriptor(count: 1, data: result)
187 | }
188 |
189 | // called by Unflatten.swift
190 | internal static func unflatten(_ data: Data, startingAt descStart: Int) throws -> LogicalDescriptor {
191 | // type, remaining bytes // TO DO: sanity check these?
192 | var logical: OSType? = nil, operands: [TestDescriptor]? = nil
193 | let countOffset = descStart + 8
194 | if data.readUInt32(at: countOffset) != 2 { throw AppleEventError.invalidParameterCount }
195 | var offset = countOffset + 8
196 | for _ in 0..<2 {
197 | let key = data[offset..<(offset+4)]
198 | switch key {
199 | case Data([0x6C, 0x6F, 0x67, 0x63]) // * keyAELogicalOperator
200 | where data[(offset+4)..<(offset+12)] == Data([0x65, 0x6E, 0x75, 0x6D, // typeEnumerated
201 | 0x00, 0x00, 0x00, 0x04]): // size (4 bytes)
202 | logical = data.readUInt32(at: offset+12)
203 | offset += 16
204 | case Data([0x74, 0x65, 0x72, 0x6D]): // * keyAELogicalTerms
205 | let desc: Descriptor // QueryDescriptor
206 | (desc, offset) = unflattenFirstDescriptor(in: data, startingAt: offset+4)
207 | // object specifier's parent is either an object specifier or its terminal [root] descriptor
208 | if desc.type != typeAEList { throw AppleEventError.invalidParameter }
209 | operands = try unpackAsArray(desc, using: { (desc: Descriptor) throws -> TestDescriptor in
210 | // TO DO: this is kludgy; would be better not to use -ve start indexes (probably be simpler just to iterate flattened list directly)
211 | switch desc.type {
212 | case typeLogicalDescriptor:
213 | return try LogicalDescriptor.unflatten(desc.data, startingAt: -8)
214 | case typeCompDescriptor:
215 | return try ComparisonDescriptor.unflatten(desc.data, startingAt: -8)
216 | default:
217 | throw AppleEventError.invalidParameter
218 | }
219 | })
220 | default:
221 | throw AppleEventError.invalidParameter
222 | }
223 | }
224 | guard let logical_ = logical, let operands_ = operands, let logical__ = Operator(rawValue: logical_),
225 | ((logical__ == .NOT && operands_.count == 1) || operands_.count >= 2) else {
226 | throw AppleEventError.invalidParameter
227 | }
228 | return LogicalDescriptor(logical: logical__, operands: operands_)
229 | }
230 |
231 | }
232 |
233 |
234 | public extension TestDescriptor {
235 |
236 | static func &&(lhs: TestDescriptor, rhs: TestDescriptor) -> TestDescriptor {
237 | return LogicalDescriptor(AND: [lhs, rhs])
238 | }
239 | static func ||(lhs: TestDescriptor, rhs: TestDescriptor) -> TestDescriptor {
240 | return LogicalDescriptor(OR: [lhs, rhs])
241 | }
242 | static prefix func !(lhs: TestDescriptor) -> TestDescriptor {
243 | return LogicalDescriptor(NOT: lhs)
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/AppleEvents/AppleEventDescriptor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppleEventDescriptor.swift
3 | //
4 |
5 | // Q. how might a modern UIKit app implement recording? (best way to record is for GUI to communicate with Model via AEs, as that ensures accurate representation of actions [it also in theory would allow an app's Model to run as independent headless server process to which any number of client UI processes may subscribe]; however, AppKit/UIKit aren't built to work that way, in which case they best they could do is emit recordable AEs as a side-effect to user interactions, which doesn't guarantee such AEs will be comprehensive or correct [since the App itself if not being made to eat its own dogfood, so is under no obligation to make sure those AEs are correct or that it has the AE handlers/AEOM capabilities to handle them], but may be "good enough" nevertheless in that even limited recordability will be helpful to users, both in its own right and as a learning tools for AE client languages)
6 |
7 |
8 | // TO DO: should AppleEventDescriptor.send() automatically consolidate AE and App error codes, avoiding need for double checks? or should that be left to layer above?
9 |
10 |
11 | /*
12 |
13 | TO DO: how to support following SendOptions?
14 |
15 | // ignore these for now (can revisit if/when we have an AEOM framework that supports recording)
16 | public static let dontRecord = SendOptions(rawValue: 0x00001000) /* don't record this event */
17 | public static let dontExecute = SendOptions(rawValue: 0x00002000) /* don't send the event for recording */
18 |
19 | // when is this needed? (and what does AESendMessage do with it?)
20 | public static let processNonReplyEvents = SendOptions(rawValue: 0x00008000) /* allow processing of non-reply events while awaiting synchronous AppleEvent reply */
21 |
22 | // ditto
23 | public static let dontAnnotate = SendOptions(rawValue: 0x00010000) /* if set, don't automatically add any sandbox or other annotations to the event */
24 | */
25 |
26 |
27 |
28 |
29 | /*
30 | * "dle2" // format marker
31 | * 0x00000000 // align
32 | * "aevt" // type
33 | * 0x00000184 // bytes remaining
34 | * 0x00000000
35 | * 0x00000000
36 | * 0x00000134 // offset to parameters
37 | * 0x00000004
38 | * 0x00000001 // parameter count
39 | * 0x00000000
40 | * 0x00000000
41 | * 0x00000000
42 | * "core"
43 | * "getd"
44 | * 0x666f 0xbbb3 // unused, return ID
45 | * […unused…]
46 | * "aevt"
47 | * 0x00010001 // version marker
48 | * [ATTRIBUTES]
49 | * "tran" * keyTransactionIDAttr
50 | * "long"
51 | * 0x00000004
52 | * 0x00000000
53 | * "addr" * keyAddressAttr
54 | * "bund"
55 | * 0x00000010
56 | * "com.apple.finder"
57 | * "tbsc" * ??
58 | * "psn "
59 | * 0x00000008
60 | * 0x00000000
61 | * 0x00000000
62 | * "inte" * keyInteractLevelAttr (this is set to 0 by AECreate; AESend will update it with SendOptions flags)
63 | * "long"
64 | * 0x00000004
65 | * 0x00000070 [= alwaysInteract + canSwitchLayer]
66 | * "repq" * keyReplyRequestedAttr (again, this is set during AECreate, but AESend must replace it)
67 | * "long"
68 | * 0x00000004
69 | * 0x00000000 // set to 1 by AESendMessage() if wait/queue reply flag is given
70 | * "tbsc" * ?? (duplicate field!)
71 | * "psn "
72 | * 0x00000008
73 | * 0x00000000
74 | * 0x00000000
75 | * "remo" * ?? ('remote'?)
76 | * "long"
77 | * 0x00000004
78 | * 0x00000000
79 | * "from" * keyOriginalAddressAttr
80 | * "psn "
81 | * 0x00000008
82 | * 0x00000001
83 | * 0x000032a6
84 | * "frec" * ?? ('recording'?)
85 | * "long"
86 | * 0x00000004
87 | * 0x00000000
88 | * ";;;;"
89 | * [PARAMETERS]
90 | * "----"
91 | * "obj "
92 | * 0x00000044
93 | * 0x00000004
94 | * 0x00000000
95 | * "want"
96 | * "type"
97 | * 0x00000004
98 | * "docu"
99 | * "form"
100 | * "enum"
101 | * 0x00000004
102 | * "indx"
103 | * "seld"
104 | * "long"
105 | * 0x00000004
106 | * 0x00000001
107 | * "from"
108 | * "null"
109 | * 0x00000000
110 | */
111 |
112 |
113 | // TO DO: API for this is TBC (attributes in particular)
114 |
115 | import Foundation
116 |
117 |
118 | public typealias ReplyEventDescriptor = AppleEventDescriptor // TO DO: define a dedicated struct for representing reply events (typeAppleEvent with event identifier 'aevtansr')
119 |
120 |
121 | public typealias AEReturnID = Int16
122 | public typealias AETransactionID = Int32
123 | public typealias AEEventClass = OSType
124 | public typealias AEEventID = OSType
125 |
126 |
127 | let kAutoGenerateReturnID: AEReturnID = -1 // TO DO: any reason this should be exposed to client code? inclined to hide it; only likely use-case is async messaging, where app's needs to know return IDs in order to match reply events when they arrive; however, this'd be best implemented as a modern Swift async API that takes a completion callback, in which case kAEWaitForReply and AEReturnID can be hidden behind that
128 |
129 | let kAnyTransactionID: AETransactionID = 0 // TO DO: similarly, transactions would be best implemented as `withTransaction{…}` block, ensuring correct start/stop/cancel behaviors and avoiding client code having to handle transaction IDs itself; low-priority as few/no apps currently use them
130 |
131 |
132 | private var returnIDCount: AEReturnID = AEReturnID.min // TO DO: auto-increment? or use sparse list? (what are chances of not receiving the reply to an outgoing event until 65536 outgoing events later?); caution: -1 is reserved (what about 0? -ve values? anything else?)
133 |
134 | private func newReturnID() -> AEReturnID {
135 | returnIDCount += 1
136 | switch returnIDCount {
137 | case -1:
138 | returnIDCount = 1
139 | case AEReturnID.max:
140 | returnIDCount = AEReturnID.min
141 | default:
142 | ()
143 | }
144 | return returnIDCount
145 | }
146 |
147 |
148 |
149 |
150 | public struct AppleEventDescriptor: Descriptor {
151 |
152 | public enum InteractionLevel: UInt8 {
153 | case neverInteract = 0x10 // server should not interact with user
154 | case canInteract = 0x20 // server may try to interact with user
155 | case alwaysInteract = 0x30 // server should always interact with user where appropriate
156 | }
157 |
158 | public typealias Attribute = (key: AEKeyword, value: Descriptor)
159 | public typealias Parameter = (key: AEKeyword, value: Descriptor)
160 |
161 | public var debugDescription: String {
162 | return ""
163 | }
164 |
165 | public let code: EventIdentifier
166 | public var target: AddressDescriptor? // pack as keyAddressAttr
167 | let returnID: AEReturnID
168 |
169 |
170 | public init(code: EventIdentifier, target: AddressDescriptor? = nil) { // create a new outgoing event
171 | self.code = code
172 | self.target = target
173 | self.returnID = newReturnID() // TO DO: always set this, or only when sending to another process with wantsReply=true?
174 | }
175 |
176 | internal init(code: EventIdentifier, returnID: AEReturnID) { // used by unflatten() below
177 | self.code = code
178 | self.returnID = returnID
179 | }
180 |
181 | // default SendOptions are .canInteract and .waitForReply
182 | public var interactionLevel: InteractionLevel = .canInteract
183 | public var canSwitchLayer: Bool = false // interaction may switch layer
184 | public var wantsReply: Bool = true // this will automatically be true when sendAsync{…} is used to dispatch event
185 | public var timeout: TimeInterval = 120 // TO DO: currently unsupported; also, how should .defaultTimeout (-1) and .neverTimeout (-2) options be supported? (might consider 0 = never, -ve = default; also bear in mind that 'timo' attribute requires timeout in ticks, so +ve time intervals need multiplied by 60 and converted to Int32)
186 |
187 | public private(set) var attributes = [Attribute]() // miscellaneous attributes for which we don't [currently] provide dedicated properties
188 | public private(set) var parameters = [Parameter]()
189 |
190 |
191 | public let type: DescType = typeAppleEvent
192 |
193 | // TO DO: separate data for attributes and parameters? (not needed as long as AE build is atomic) how best to access attributes? (probably sufficient to iterate attribute data)
194 | public var data: Data {
195 | var result = Data([0x61, 0x65, 0x76, 0x74, // type 'aevt'
196 | 0, 0, 0, 0, // bytes remaining (TBC) [4..<8]
197 | 0, 0, 0, 0, // reserved
198 | 0, 0, 0, 0, // reserved
199 | 0, 0, 0, 0, // offset to parameters (TBC) [16..<20]
200 | 0x00, 0x00, 0x00, 0x04]) // reserved (4)
201 | result += encodeUInt32(UInt32(parameters.count)) // parameter count
202 | result += Data([0, 0, 0, 0, // reserved
203 | 0, 0, 0, 0, // reserved
204 | 0, 0, 0, 0]) // reserved
205 | let (eventClass, eventID) = eventIdentifier(self.code)
206 | result += encodeUInt32(eventClass) // event class
207 | result += encodeUInt32(eventID) // event ID
208 | result += Data([0, 0]) // unused
209 | result += encodeInt16(returnID) // return ID
210 | result += Data(repeating: 0, count: 84) // unused
211 | result += Data([0x61, 0x65, 0x76, 0x74, // type 'aevt'
212 | 0x00, 0x01, 0x00, 0x01]) // version marker
213 | // begin attributes
214 | if let target = self.target { // TO DO: what if target == nil? omit field, or use nullDescriptor?
215 | result += Data([0x61, 0x64, 0x64, 0x72]) // keyAddressAttr
216 | target.appendTo(containerData: &result)
217 | }
218 | result += Data([0x66, 0x72, 0x6F, 0x6D]) // keyOriginalAddressAttr
219 | let pid = ProcessInfo.processInfo.processIdentifier
220 | AddressDescriptor(processIdentifier: pid).appendTo(containerData: &result)
221 | result += Data([0x69, 0x6E, 0x74, 0x65, // keyInteractLevelAttr
222 | 0x6C, 0x6F, 0x6E, 0x67, // typeSInt32
223 | 0x00, 0x00, 0x00, 0x04,
224 | 0x00, 0x00, 0x00, self.interactionLevel.rawValue | (self.canSwitchLayer ? 0x40 : 0)])
225 | result += Data([0x72, 0x65, 0x70, 0x71, // keyReplyRequestedAttr
226 | 0x6C, 0x6F, 0x6E, 0x67, // typeSInt32
227 | 0x00, 0x00, 0x00, 0x04,
228 | 0x00, 0x00, 0x00, self.wantsReply ? 1 : 0]) // kAEWaitForReply/kAEQueueReply = true; kAENoReply = false
229 |
230 | // TO DO: should timeout attr be included here? (if so, need to ensure the same value is passed to send)
231 | result += Data([0x74, 0x69, 0x6D, 0x6F, // keyTimeoutAttr
232 | 0x6C, 0x6F, 0x6E, 0x67, // typeSInt32
233 | 0x00, 0x00, 0x00, 0x04])
234 | result += encodeInt32(120 * 60) // TO DO; what about -1, -2?
235 | // keySubjectAttr = 0x7375626A // TO DO: should this be implemented as `var subject: QueryDescriptor?`? or left in misc attributes for parent code to deal with (it's arguably an [AppleScript-induced?] design wart: when an AppleScript command has a direct parameter AND an enclosing `tell` block, it can't pack the `tell` target as the direct parameter [its default behavior] as that's already given, so it sticks it in the 'subj' attribute instead; in py-appscript, the high-level appscript API does this automatically while the lower-level aem API leaves client code to set the 'subj' attribute itself)
236 | for (key, value) in attributes { // append any other attributes
237 | result += encodeUInt32(key)
238 | value.appendTo(containerData: &result)
239 | }
240 | result += Data([0x3b, 0x3b, 0x3b, 0x3b]) // end of attributes ';;;;'
241 | result[(result.startIndex + 16)..<(result.startIndex + 20)] = encodeUInt32(UInt32(result.count - 20)) // set offset to parameters // TO DO: FIX: parameter offset is relative to start of data!!!
242 | for (key, value) in parameters { // append parameters
243 | result += encodeUInt32(key)
244 | value.appendTo(containerData: &result)
245 | }
246 | result[(result.startIndex + 4)..<(result.startIndex + 8)] = encodeUInt32(UInt32(result.count - 8)) // set remaining bytes
247 | return result
248 | }
249 |
250 | public func flatten() -> Data {
251 | return Data([0x64, 0x6c, 0x65, 0x32, // format 'dle2'
252 | 0, 0, 0, 0]) + self.data // align
253 | }
254 |
255 | public func appendTo(containerData: inout Data) {
256 | containerData += self.data
257 | }
258 |
259 | // TO DO: how best to implement this? also, is it worth implementing separate ReplyEventDescriptor specifically for working with reply events (which normally contain a fixed set of result/error/no parameters)?
260 | internal static func unflatten(_ data: Data, startingAt descStart: Int) throws -> AppleEventDescriptor { // TO DO: should this throw? (how else to deal with malformed AEDescs in general)
261 | if descStart != 0 { fatalError("TO DO") }
262 | if data[descStart..<(descStart + 8)] != Data([0x64, 0x6c, 0x65, 0x32, // format 'dle2'
263 | 0, 0, 0, 0]) { // align
264 | throw AppleEventError(code: -1702, message: "dle2 header not found")
265 | }
266 | if data[(descStart + 8)..<(descStart + 12)] != Data([0x61, 0x65, 0x76, 0x74]) { // type 'aevt'
267 | throw AppleEventError(code: -1703, message: "not an apple event")
268 | }
269 | // let _ = data.readUInt32(at: descStart + 12) // bytes remaining, then 8-bytes reserved
270 | // let _ = data.readUInt32(at: descStart + 24) // offset to parameters
271 | if data[(descStart + 28)..<(descStart + 32)] != Data([0x00, 0x00, 0x00, 0x04]) { // reserved (4)
272 | throw AppleEventError(code: -1702, message: "unexpected bytes 28-32")
273 | }
274 | let parameterCount = data.readUInt32(at: descStart + 32) // parameter count, then 12-bytes reserved
275 | let eventClass = data.readUInt32(at: descStart + 48) // event class
276 | let eventID = data.readUInt32(at: descStart + 52) // event ID, then 2-bytes unused
277 | let returnID = try decodeInt16(data[(descStart + 58)..<(descStart + 60)]) // return ID, then 84-bytes unused
278 | if data[(descStart + 144)..<(descStart + 148)] != Data([0x61, 0x65, 0x76, 0x74]) { // type 'aevt'
279 | throw AppleEventError(code: -1702, message: "unexpected bytes 132-136: \(literalFourCharCode(data.readUInt32(at: 132)))")
280 | }
281 | if data[(descStart + 148)..<(descStart + 152)] != Data([0x00, 0x01, 0x00, 0x01]) { // version marker
282 | throw AppleEventError(code: -1706, message: "unexpected version marker")
283 | }
284 | var event = AppleEventDescriptor(code: eventIdentifier(eventClass, eventID), returnID: returnID)
285 | // iterate attributes and parameters to unpack them
286 | var offset = 152 // unflattenFirstDescriptor will add startIndex
287 | while true { // read up to end-of-attributes marker ';;;;'
288 | let key = data.readUInt32(at: offset)
289 | if key == 0x3b3b3b3b { break }
290 | let (descriptor, endOffset) = unflattenFirstDescriptor(in: data, startingAt: offset + 4)
291 | offset = endOffset
292 | do {
293 | switch descriptor.type {
294 | case keyAddressAttr:
295 | event.target = descriptor as? AddressDescriptor // TO DO: sloppy; should probably throw if not a valid address desc (also need to check if nullDescriptor is a legitimate value for this attribute, although if it is then it'd still be preferable to omit entirely)
296 | //case keyOriginalAddressAttr: // the process that sent this event (it's only needed in server-side framework when creating a reply (aevt/ansr) event to send back; for now, just add to misc. attributes list)
297 | case keyInteractLevelAttr:
298 | let flags = try unpackAsInt32(descriptor)
299 | event.canSwitchLayer = (flags & 0x40) != 0 ? true : false
300 | guard let level = InteractionLevel(rawValue: UInt8(flags & 0x30)) else { throw AppleEventError.corruptData }
301 | event.interactionLevel = level
302 | case keyReplyRequestedAttr:
303 | event.wantsReply = try unpackAsInt32(descriptor) != 0
304 | // case keyTimeoutAttr: // TO DO: implement
305 | default:
306 | event.setAttribute(key, to: descriptor)
307 | }
308 | } catch {
309 | throw AppleEventError(code: error._code, message: "Bad \(literalFourCharCode(key)) attribute.", cause: error)
310 | }
311 | }
312 | offset += 4 // step over ';;;;' marker
313 | for _ in 0.. Descriptor? {
350 | return self.attributes.first{ $0.key == key }?.value
351 | }
352 |
353 | func parameter(_ key: DescType) -> Descriptor? {
354 | return self.parameters.first{ $0.key == key }?.value
355 | }
356 | }
357 |
358 |
359 |
360 | // temporary kludge; allows us to send our homegrown AEs via established Carbon AESendMessage() API; aside from confirming that our code is reading and writing AEDesc data correctly (if not quirk-for-quirk compatible with AppleScript, then at least good enough to be understood by well-behaved apps), it gives us a benchmark to compare against as we implement our own Mach-AE bridging layer
361 |
362 |
363 | public extension AppleEventDescriptor {
364 |
365 | // TO DO: might hedge our bets by keeping 'low-level' send() that returns raw reply descriptor, while providing a higher-level API that unpacks standard result/error responses and returns Descriptor? result or throws AEM/application error
366 |
367 | // TO DO: possible/practical to implement sendAsync method that takes completion callback? (this'll need more research; presumably we can create our own mach port to listen on if app doesn't already have a main event loop on which to receive incoming AEs [e.g. see keyReplyPortAttr usage in AESendThreadSafe.c, although that still invokes AESendMessage to dispatch outgoing event and return reply event, so doesn't give us any clues on how to implement our own sendSync/sendAsync methods])
368 |
369 |
370 | func send() -> (code: Int, reply: ReplyEventDescriptor?) {
371 | return carbonSend(event: self)
372 | }
373 | }
374 |
375 |
376 | public extension ReplyEventDescriptor {
377 | // TO DO: implement a separate ReplyEventDescriptor? reply events are purely one-way, and *should* only contain standard result/error properties (if any)
378 |
379 | // TO DO: implement reply(withResult:Descriptor?=nil)/reply(withError:Int,message:String?,etc) methods on AppleEventDescriptor that build and dispatch the reply (aevt/ansr) event as an atomic operation, minimizing opportunities for parent code to break things (we still have to trust client code to call these methods only on events it's received, not on events it's built itself, but given that this is still a relatively low-level API that may be a reasonable compromise)
380 |
381 | /*
382 |
383 | public let coreEventAnswer: EventIdentifier = 0x61657674_616E7372
384 |
385 | */
386 |
387 | var errorNumber: Int {
388 | if let desc = self.parameter(keyErrorNumber) {
389 | return (try? unpackAsInt(desc)) ?? -1
390 | } else {
391 | return 0
392 | }
393 | }
394 |
395 | var errorMessage: String? {
396 | if let desc = self.parameter(keyErrorString) {
397 | return try? unpackAsString(desc)
398 | } else {
399 | return nil
400 | }
401 | }
402 | }
403 |
--------------------------------------------------------------------------------
/AppleEvents/UnpackFuncs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Unpack.swift
3 | //
4 | // standard unpack functions for converting AE descriptors to specified Swift types, coercing as needed (or throwing if the given descriptor can't be coerced to the required type)
5 | //
6 |
7 | // TO DO: what about packAsDescriptor/unpackAsDescriptor? (for use in packAsArray/unpackAsArray/packAsRecord/etc for shallow packing/unpacking); also need to decide on packAsAny/unpackAsAny, and packAs/unpackAs (currently SwiftAutomation implements these, with support for App-specific Symbols and Specifiers)
8 |
9 |
10 | import Foundation
11 |
12 |
13 | public func unpackAsBool(_ descriptor: Descriptor) throws -> Bool {
14 | switch descriptor.type {
15 | case typeTrue:
16 | return true
17 | case typeFalse:
18 | return false
19 | case typeBoolean:
20 | return try decodeFixedWidthValue(descriptor.data)
21 | case typeSInt64, typeSInt32, typeSInt16:
22 | switch try unpackAsInteger(descriptor) as Int {
23 | case 1: return true
24 | case 0: return false
25 | default: throw AppleEventError.unsupportedCoercion
26 | }
27 | case typeUInt64, typeUInt32, typeUInt16:
28 | switch try unpackAsInteger(descriptor) as UInt {
29 | case 1: return true
30 | case 0: return false
31 | default: throw AppleEventError.unsupportedCoercion
32 | }
33 | case typeUTF8Text, typeUTF16ExternalRepresentation, typeUnicodeText:
34 | switch try unpackAsString(descriptor).lowercased() {
35 | case "true", "yes": return true
36 | case "false", "no": return false
37 | default: throw AppleEventError.unsupportedCoercion
38 | }
39 | default:
40 | throw AppleEventError.unsupportedCoercion
41 | }
42 | }
43 |
44 |
45 | private func unpackAsInteger(_ descriptor: Descriptor) throws -> T {
46 | // caution: AEs are big-endian; unlike AEM's integer descriptors, which for historical reasons hold native-endian data and convert to big-endian later on, we immediately convert to/from big-endian
47 | var result: T? = nil
48 | switch descriptor.type {
49 | case typeSInt64:
50 | result = T(exactly: Int64(bigEndian: try decodeFixedWidthValue(descriptor.data)))
51 | case typeSInt32:
52 | result = T(exactly: Int32(bigEndian: try decodeFixedWidthValue(descriptor.data)))
53 | case typeSInt16:
54 | result = T(exactly: Int16(bigEndian: try decodeFixedWidthValue(descriptor.data)))
55 | case typeUInt64:
56 | result = T(exactly: UInt64(bigEndian: try decodeFixedWidthValue(descriptor.data)))
57 | case typeUInt32:
58 | result = T(exactly: UInt32(bigEndian: try decodeFixedWidthValue(descriptor.data)))
59 | case typeUInt16:
60 | result = T(exactly: UInt16(bigEndian: try decodeFixedWidthValue(descriptor.data)))
61 | // coercions
62 | case typeTrue, typeFalse, typeBoolean:
63 | result = try unpackAsBool(descriptor) ? 1 : 0
64 | case typeIEEE32BitFloatingPoint:
65 | result = T(exactly: try decodeFixedWidthValue(descriptor.data) as Float)
66 | case typeIEEE64BitFloatingPoint: // Q. what about typeIEEE128BitFloatingPoint?
67 | result = T(exactly: try decodeFixedWidthValue(descriptor.data) as Double)
68 | case typeUTF8Text, typeUTF16ExternalRepresentation, typeUnicodeText: // TO DO: do we care about non-Unicode strings (typeText? typeStyledText? etc) or are they sufficiently long-deprecated to ignore now (i.e. what, if any, macOS apps still use them?)
69 | result = T(try unpackAsString(descriptor)) // result is nil if non-numeric string // TO DO: any difference in how AEM converts string to integer?
70 | default:
71 | throw AppleEventError.unsupportedCoercion
72 | }
73 | guard let n = result else { throw AppleEventError.unsupportedCoercion }
74 | return n
75 | }
76 |
77 |
78 | public func unpackAsInt(_ descriptor: Descriptor) throws -> Int {
79 | return try unpackAsInteger(descriptor)
80 | }
81 | public func unpackAsUInt(_ descriptor: Descriptor) throws -> UInt {
82 | return try unpackAsInteger(descriptor)
83 | }
84 | public func unpackAsInt16(_ descriptor: Descriptor) throws -> Int16 {
85 | return try unpackAsInteger(descriptor)
86 | }
87 | public func unpackAsUInt16(_ descriptor: Descriptor) throws -> UInt16 {
88 | return try unpackAsInteger(descriptor)
89 | }
90 | public func unpackAsInt32(_ descriptor: Descriptor) throws -> Int32 {
91 | return try unpackAsInteger(descriptor)
92 | }
93 | public func unpackAsUInt32(_ descriptor: Descriptor) throws -> UInt32 {
94 | return try unpackAsInteger(descriptor)
95 | }
96 | public func unpackAsInt64(_ descriptor: Descriptor) throws -> Int64 {
97 | return try unpackAsInteger(descriptor)
98 | }
99 | public func unpackAsUInt64(_ descriptor: Descriptor) throws -> UInt64 {
100 | return try unpackAsInteger(descriptor)
101 | }
102 |
103 |
104 | public func unpackAsDouble(_ descriptor: Descriptor) throws -> Double { // coerces as needed
105 | switch descriptor.type {
106 | case typeIEEE64BitFloatingPoint:
107 | return try decodeFixedWidthValue(descriptor.data)
108 | case typeIEEE32BitFloatingPoint:
109 | return Double(try decodeFixedWidthValue(descriptor.data) as Float)
110 | case typeSInt64, typeSInt32, typeSInt16:
111 | return Double(try unpackAsInteger(descriptor) as Int)
112 | case typeUInt64, typeUInt32, typeUInt16:
113 | return Double(try unpackAsInteger(descriptor) as UInt)
114 | case typeUTF8Text, typeUTF16ExternalRepresentation, typeUnicodeText:
115 | guard let result = Double(try unpackAsString(descriptor)) else {
116 | throw AppleEventError.unsupportedCoercion
117 | }
118 | return result
119 | default:
120 | throw AppleEventError.unsupportedCoercion
121 | }
122 | }
123 |
124 |
125 | public func unpackAsString(_ descriptor: Descriptor) throws -> String { // coerces as needed
126 | switch descriptor.type {
127 | // typeUnicodeText: native endian UTF16 with optional BOM (deprecated, but still in common use)
128 | // typeUTF16ExternalRepresentation: big-endian 16 bit unicode with optional byte-order-mark,
129 | // or little-endian 16 bit unicode with required byte-order-mark
130 | case typeUTF8Text:
131 | guard let result = decodeUTF8String(descriptor.data) else { throw AppleEventError.corruptData }
132 | return result
133 | case typeUTF16ExternalRepresentation, typeUnicodeText: // UTF-16 BE/LE
134 | if descriptor.data.count < 2 {
135 | if descriptor.data.count > 0 { throw AppleEventError.corruptData }
136 | return ""
137 | }
138 | var bom: UInt16 = 0 // check for BOM before decoding
139 | let _ = Swift.withUnsafeMutableBytes(of: &bom, { descriptor.data.copyBytes(to: $0)} )
140 | let encoding: String.Encoding
141 | switch bom {
142 | case 0xFEFF:
143 | encoding = .utf16BigEndian
144 | case 0xFFFE:
145 | encoding = .utf16LittleEndian
146 | default: // no BOM
147 | // TO DO: according to AEDataModel.h, typeUnicodeText uses "native byte ordering, optional BOM"; however, the raw data in descriptors returned by Carbon/Cocoa apps appears to be big-endian UTF16, so use UTF16BE for now and figure out later
148 | // no BOM; if typeUnicodeText use platform endianness, else it's big-endian typeUTF16ExternalRepresentation
149 | //encoding = (descriptor.type == typeUnicodeText && isLittleEndianHost) ? .utf16LittleEndian : .utf16BigEndian
150 | encoding = .utf16BigEndian
151 | }
152 | // TO DO: FIX; endianness bug when decoding typeUnicodeText
153 | /*
154 | public var typeStyledUnicodeText: DescType { get } /* Not implemented */
155 | public var typeEncodedString: DescType { get } /* Not implemented */
156 | public var typeUnicodeText: DescType { get } /* native byte ordering, optional BOM */
157 | public var typeCString: DescType { get } /* MacRoman characters followed by a NULL byte */
158 | public var typePString: DescType { get } /* Unsigned length byte followed by MacRoman characters */
159 | /*
160 | * The preferred unicode text types. In both cases, there is no explicit null termination or length byte.
161 | */
162 |
163 | public var typeUTF16ExternalRepresentation: DescType { get } /* big-endian 16 bit unicode with optional byte-order-mark, or little-endian 16 bit unicode with required byte-order-mark. */
164 | public var typeUTF8Text: DescType { get } /* 8 bit unicode */
165 |
166 | */
167 | guard let result = String(data: descriptor.data, encoding: encoding) else { throw AppleEventError.corruptData }
168 | // print("STRING:", result)
169 | return result
170 | // TO DO: boolean, number
171 | case typeSInt64, typeSInt32, typeSInt16:
172 | return String(try unpackAsInteger(descriptor) as Int64)
173 | case typeUInt64, typeUInt32, typeUInt16:
174 | return String(try unpackAsInteger(descriptor) as UInt64)
175 | case typeFileURL:
176 | // note that AEM's typeFileURL->typeUnicodeText antiquated coercion handler returns an HFS(!) path (AEM also fails to support typeFileURL->typeUTF8Text), but we're going to be sensible and stick to POSIX paths throughout
177 | return try unpackAsFileURL(descriptor).path
178 | case typeLongDateTime, typeISO8601DateTime:
179 | // note that while ISO8601 data is ASCII string, we still unpack as Date first to ensure it's valid
180 | return ISO8601DateFormatter().string(from: try unpackAsDate(descriptor))
181 | // TO DO: typeVersion?
182 | // TO DO: throw on legacy types? (typeChar, typeIntlText, typeStyledText)
183 | default:
184 | throw AppleEventError.unsupportedCoercion
185 | }
186 | }
187 |
188 |
189 | public func unpackAsDate(_ descriptor: Descriptor) throws -> Date {
190 | let delta: TimeInterval
191 | switch descriptor.type {
192 | case typeLongDateTime: // assumes data handle is valid for descriptor type
193 | delta = TimeInterval(try unpackAsInteger(descriptor) as Int64)
194 | case typeISO8601DateTime:
195 | guard let result = try? decodeISO8601Date(descriptor.data) else { throw AppleEventError.corruptData }
196 | return result
197 | case typeUTF8Text, typeUTF16ExternalRepresentation, typeUnicodeText:
198 | guard let result = try? decodeISO8601Date(descriptor.data) else { throw AppleEventError.unsupportedCoercion }
199 | return result
200 | default:
201 | throw AppleEventError.unsupportedCoercion
202 | }
203 | return Date(timeIntervalSinceReferenceDate: delta + epochDelta)
204 | }
205 |
206 |
207 | public func unpackAsFileURL(_ descriptor: Descriptor) throws -> URL {
208 | switch descriptor.type {
209 | case typeFileURL:
210 | guard let result = URL(dataRepresentation: descriptor.data, relativeTo: nil, isAbsolute: true), result.isFileURL else {
211 | throw AppleEventError.corruptData
212 | }
213 | return result
214 | // TO DO: what about bookmarks?
215 | /*
216 | case typeBookmarkData:
217 | do {
218 | var bookmarkDataIsStale = false
219 | return try URL(resolvingBookmarkData: descriptor.data, bookmarkDataIsStale: &bookmarkDataIsStale)
220 | } catch {
221 | throw AppleEventError(code: -1702, cause: error)
222 | }
223 | */
224 | case typeUTF8Text, typeUTF16ExternalRepresentation, typeUnicodeText:
225 | // TO DO: relative paths?
226 | guard let path = try? unpackAsString(descriptor), path.hasPrefix("/") else { throw AppleEventError.unsupportedCoercion }
227 | return URL(fileURLWithPath: path)
228 | // TO DO: what other cases? typeAlias, typeFSRef are deprecated (typeAlias is still commonly used by AS, but would require use of deprecated APIs to coerce/unpack)
229 | default:
230 | throw AppleEventError.unsupportedCoercion
231 | }
232 | }
233 |
234 |
235 | public func unpackAsType(_ descriptor: Descriptor) throws -> OSType {
236 | // TO DO: how should cMissingValue be handled? (there is an argument for special-casing it, throwing a coercion error which `unpackAsOptional(_:using:)`) can intercept to return nil instead
237 | switch descriptor.type {
238 | case typeType, typeProperty, typeKeyword:
239 | return try decodeUInt32(descriptor.data)
240 | default:
241 | throw AppleEventError.unsupportedCoercion
242 | }
243 | }
244 |
245 |
246 | public func unpackAsEnum(_ descriptor: Descriptor) throws -> OSType {
247 | switch descriptor.type {
248 | case typeEnumerated, typeAbsoluteOrdinal: // TO DO: decide where to accept typeAbsoluteOrdinal (it's an odd quirk of Carbon AEM, as all other standard enums used in specifiers - e.g. relative position, comparison and logic operators - are defined as typeEnumerated)
249 | return try decodeUInt32(descriptor.data)
250 | default:
251 | throw AppleEventError.unsupportedCoercion
252 | }
253 | }
254 |
255 | private let absoluteOrdinals: Set = [OSType(kAEFirst), OSType(kAEMiddle), OSType(kAELast), OSType(kAEAny), OSType(kAEAll)]
256 | private let relativeOrdinals: Set = [OSType(kAEPrevious), OSType(kAENext)]
257 |
258 |
259 | public func unpackAsAbsoluteOrdinal(_ descriptor: Descriptor) throws -> OSType {
260 | guard descriptor.type == typeAbsoluteOrdinal, let code = try? decodeUInt32(descriptor.data), // TO DO: also accept typeEnumerated?
261 | absoluteOrdinals.contains(code) else { throw AppleEventError.unsupportedCoercion }
262 | return code
263 | }
264 |
265 | public func unpackAsRelativeOrdinal(_ descriptor: Descriptor) throws -> OSType {
266 | guard descriptor.type == typeEnumerated, let code = try? decodeUInt32(descriptor.data),
267 | relativeOrdinals.contains(code) else { throw AppleEventError.unsupportedCoercion }
268 | return code
269 | }
270 |
271 | public func unpackAsFourCharCode(_ descriptor: Descriptor) throws -> OSType {
272 | switch descriptor.type {
273 | case typeEnumerated, typeAbsoluteOrdinal, typeType, typeProperty, typeKeyword:
274 | return try decodeUInt32(descriptor.data)
275 | default:
276 | throw AppleEventError.unsupportedCoercion
277 | }
278 | }
279 |
280 | public func unpackAsDescriptor(_ descriptor: Descriptor) -> Descriptor {
281 | return descriptor
282 | }
283 |
284 | // TO DO: what about unpackAsSequence?
285 |
286 | public func newUnpackArrayFunc(using unpackFunc: @escaping (Descriptor) throws -> T) -> (Descriptor) throws -> [T] {
287 | return { try unpackAsArray($0, using: unpackFunc) }
288 | }
289 |
290 | public func unpackAsArray(_ descriptor: Descriptor, using unpackFunc: (Descriptor) throws -> T) rethrows -> [T] {
291 | if let listDescriptor = descriptor as? ListDescriptor {
292 | return try listDescriptor.array(using: unpackFunc)
293 | } else {
294 | switch descriptor.type {
295 | // there is no neat way to unpack these legacy types directly to [T], so unpack and repack as Int32, then unpack that
296 | case typeQDPoint, typeQDRectangle, typeRGBColor:
297 | return try (try! unpackCarbonTypeAsIntArray(descriptor) as [Int32]).map { try unpackFunc(packAsInt32($0)) }
298 | default: // any non-list value is coerced to single-item list
299 | return [try unpackFunc(descriptor)]
300 | }
301 | }
302 | }
303 |
304 | public func unpackCarbonTypeAsIntArray(_ descriptor: Descriptor) throws -> [T] {
305 | // legacy support as various Carbon-based apps still use these types; typeRGBColor is also used by CocoaScripting
306 | // replicate AppleScript/CocoaScripting behavior, which is to represent points/rects/colors as list of integers
307 | switch descriptor.type {
308 | case typeQDPoint:
309 | return try unpackAsQDPoint(descriptor)
310 | case typeQDRectangle:
311 | return try unpackAsQDRectangle(descriptor)
312 | case typeRGBColor:
313 | return try unpackAsRGBColor(descriptor)
314 | default:
315 | throw AppleEventError.unsupportedCoercion
316 | }
317 | }
318 |
319 | private struct RGBColor {
320 | var red: UInt16
321 | var green: UInt16
322 | var blue: UInt16
323 | }
324 |
325 | public func unpackAsRGBColor(_ descriptor: Descriptor) throws -> [T] {
326 | guard descriptor.data.count == MemoryLayout.size * 3 else { throw AppleEventError.corruptData }
327 | let rgbColor: RGBColor = descriptor.data.withUnsafeBytes { buffer in
328 | buffer.bindMemory(to: RGBColor.self)[0]
329 | }
330 | guard let red = T(exactly: UInt16(bigEndian: rgbColor.red)),
331 | let green = T(exactly: UInt16(bigEndian: rgbColor.green)),
332 | let blue = T(exactly: UInt16(bigEndian: rgbColor.blue)) else {
333 | throw AppleEventError.unsupportedCoercion
334 | }
335 | return [red, green, blue]
336 | }
337 |
338 | private struct Point { // derived from MacTypes.h
339 | var top: Int16
340 | var left: Int16
341 | }
342 |
343 | public func unpackAsQDPoint(_ descriptor: Descriptor) throws -> [T] {
344 | guard descriptor.data.count == MemoryLayout.size * 2 else { throw AppleEventError.corruptData }
345 | let point: Point = descriptor.data.withUnsafeBytes { buffer in
346 | buffer.bindMemory(to: Point.self)[0]
347 | }
348 | guard let top = T(exactly: Int16(bigEndian: point.top)),
349 | let left = T(exactly: Int16(bigEndian: point.left)) else {
350 | throw AppleEventError.unsupportedCoercion
351 | }
352 | return [left, top] // AppleScript returns {left, top}
353 | }
354 |
355 | private struct Rect { // derived from MacTypes.h
356 | var top: Int16
357 | var left: Int16
358 | var bottom: Int16
359 | var right: Int16
360 | }
361 |
362 | public func unpackAsQDRectangle(_ descriptor: Descriptor) throws -> [T] {
363 | guard descriptor.data.count == MemoryLayout.size * 4 else { throw AppleEventError.corruptData }
364 | let rect: Rect = descriptor.data.withUnsafeBytes { buffer in
365 | buffer.bindMemory(to: Rect.self)[0]
366 | }
367 | guard let top = T(exactly: Int16(bigEndian: rect.top)),
368 | let left = T(exactly: Int16(bigEndian: rect.left)),
369 | let bottom = T(exactly: Int16(bigEndian: rect.bottom)),
370 | let right = T(exactly: Int16(bigEndian: rect.right)) else {
371 | throw AppleEventError.unsupportedCoercion
372 | }
373 | return [left, top, right, bottom] // AppleScript returns {left, top, right, bottom}
374 | }
375 |
376 |
377 | // TO DO: how to unpack AERecords as structs?
378 |
379 | public func unpackAsDictionary(_ descriptor: Descriptor, using unpackFunc: (Descriptor) throws -> T) throws -> [AEKeyword: T] {
380 | // TO DO: this is problematic as it requires unflattenDescriptor() to recognize AERecords with non-reco type and return them as RecordDescriptor, not ScalarDescriptor; e.g. an AERecord of type cDocument has the same layout as non-collection AEDescs of typeInteger/typeUTF8Text/etc, so how does AEIsRecord() tell the difference?
381 | guard let recordDescriptor = descriptor as? RecordDescriptor else { throw AppleEventError.unsupportedCoercion }
382 | return try recordDescriptor.dictionary(using: unpackFunc)
383 | }
384 |
385 |
386 | // Q. how to represent 'missing value' when unpacking as Any?
387 |
388 | // TO DO: is Swift smart enough to compile `switch` down to O(1)-ish jump? if it's O(n) we may want to reorder cases so that commonest types (typeSInt32, typeUnicodeText, etc) appear first
389 |
390 | public func unpackAsAny(_ descriptor: Descriptor) throws -> Any {
391 | let result: Any
392 | switch descriptor.type {
393 | case typeTrue:
394 | result = true
395 | case typeFalse:
396 | result = false
397 | case typeBoolean:
398 | result = descriptor.data != Data([0])
399 | case typeSInt64:
400 | let n = Int64(bigEndian: try decodeFixedWidthValue(descriptor.data))
401 | result = Int(exactly: n) ?? n // on 32-bit machines, return Int64 if out-of-range for 32-bit Int
402 | case typeSInt32:
403 | result = Int(Int32(bigEndian: try decodeFixedWidthValue(descriptor.data)))
404 | case typeSInt16:
405 | result = Int(Int16(bigEndian: try decodeFixedWidthValue(descriptor.data)))
406 | case typeUInt64:
407 | let n = UInt64(bigEndian: try decodeFixedWidthValue(descriptor.data))
408 | result = UInt(exactly: n) ?? n // ditto
409 | case typeUInt32:
410 | result = UInt(UInt32(bigEndian: try decodeFixedWidthValue(descriptor.data)))
411 | case typeUInt16:
412 | result = UInt(UInt16(bigEndian: try decodeFixedWidthValue(descriptor.data)))
413 | case typeIEEE32BitFloatingPoint:
414 | result = try unpackAsDouble(descriptor)
415 | case typeIEEE64BitFloatingPoint: // Q. what about typeIEEE128BitFloatingPoint?
416 | result = try decodeFixedWidthValue(descriptor.data) as Double
417 | case typeUTF8Text:
418 | guard let string = decodeUTF8String(descriptor.data) else { throw AppleEventError.corruptData }
419 | result = string as Any
420 | case typeUTF16ExternalRepresentation, typeUnicodeText: // TO DO: do we care about non-Unicode strings (typeText? typeStyledText? etc) or are they sufficiently long-deprecated to ignore now (i.e. what, if any, macOS apps still use them?)
421 | result = try unpackAsString(descriptor) // result is nil if non-numeric string // TO DO: any difference in how AEM converts string to integer?
422 | case typeLongDateTime:
423 | result = try unpackAsDate(descriptor)
424 | case typeFileURL:
425 | result = try unpackAsFileURL(descriptor)
426 | case typeAEList:
427 | result = try unpackAsArray(descriptor, using: unpackAsAny)
428 | case typeAERecord:
429 | result = try unpackAsDictionary(descriptor, using: unpackAsAny)
430 | case typeQDPoint:
431 | return try unpackCarbonTypeAsIntArray(descriptor) as [Int]
432 | case typeQDRectangle:
433 | return try unpackCarbonTypeAsIntArray(descriptor) as [Int]
434 | case typeRGBColor:
435 | return try unpackCarbonTypeAsIntArray(descriptor) as [Int]
436 | default:
437 | result = descriptor
438 | }
439 | return result
440 | }
441 |
442 |
--------------------------------------------------------------------------------
/AppleEvents/QueryDescriptors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QueryDescriptors.swift
3 | //
4 |
5 | import Foundation
6 |
7 | // TO DO: also need unflatten() initializers (Q. how should this relate to unpack funcs?)
8 |
9 |
10 | // TO DO: should unflatten methods also return final offset to caller so it can sanity check against expected dataEnd?
11 |
12 | // TO DO: one disadvantage of using structs is that it makes caching more awkward; currently, the `data` attribute is calculated on each use, so queries are cheap to assemble but cost each time they're serialized; conversely, deserializing is expensive but working with the resulting structs' attributes is cheap (i.e. the current performance profile is more favorable to server-side use); constrast SwiftAutomation's class-based specifiers, which pack the descriptor on first use, then cache it for subsequent reuse (common in client-side code, where a single specifier may be used to derive many more); best solution is probably to leave client-side caching to SwiftAutomation specifiers (which will remain as class instances)
13 |
14 |
15 | // TO DO: how best to unpack record properties? composable approach would require unpack funcs that take a single property key + unpack func; alternative is code generation (which has advantage of mapping to completed structs); composing multiple property unpackfuncs would mean that return value would be recursively nested tuple, which is a bit scary (chances are Swift'll explode upon trying to unroll it), or an Array, which loses the benefits of using Swift in the first place (wonder if this is the sort of challenge where derived types come into their own…but that's academic here)
16 |
17 |
18 | // RootDescriptor (App, Con, Its, Custom)
19 |
20 | // the following descriptors are traditionally constructed as an AERecord of custom type containing type-specific properties (though not necessarily in a fixed order); while it'd be faster and simpler to build objspecs as simple scalar descriptors (fixed order struct; no need for count or property keys), we need to remain backwards-compatible with traditional Apple events (although future implementations could offer a choice, enabling clients and servers that can use the newer streamlined types to request them via content negotiation); for now, we split the difference and build the descriptors directly rather than via Record APIs, although we still need to unpack them the slow(er) way as we cannot assume the property order of descriptors received from other sources
21 |
22 | // ObjectSpecifierDescriptor
23 | // MultipleObjectSpecifierDescriptor (ObjectSpecifierDescriptor with additional constructors)
24 | // InsertionLocationDescriptor
25 | // RangeDescriptor
26 | // ComparisonDescriptor
27 | // LogicDescriptor
28 |
29 | // TO DO: unpackSpecifier function should probably take a callback/return an iterator, rather than returning a struct; that avoids unnecessary overheads when implementing server-side handling (no need to iterate twice, first to unpack objspec structs then to traverse them), and client-side too (we can save time on unpacking objspecs returned by app by only unpacking the topmost descriptor; the rest of the 'from' chain can be left packed and only unpacked if/when generating a display string)
30 |
31 | // TO DO: need to decide which protocols are public and which are private; also need to decide on naming scheme (e.g. Foo vs FooProtocol vs FooDescriptor, bearing in mind that we're using protocols to compose public behavior)
32 |
33 | // note that query dispatcher needs to be able to distinguish between single-object and multiple-object specifiers (single-object dispatch is usually easy to implement over conventional DOM-style model, as it forwards the operation to the target object [e.g. `get`/`set`] or its container [e.g. `move`/`copy`/`delete`] to perform; similarly, multiple-object specifiers can be dispatched the same way IF they are non-mutating [e.g. `get`/`count`]; the main gotchas when implementing an AEOM are 1. manipulating 'virtual' objects, e.g. `character`/`word`/`paragraph`, efficiently; and 2. performing mutating operations on multiple objects whose container is implemented as an ordered collection, e.g. Array); given an IDL/interface implementation that can precisely describe the Model's capabilities, we can determine which command+objspec combinations can operate on multi-object specifiers and which must be restricted to single-object specifiers (ideally, the IDL should contain enough info to enable full direct Siri voice control of applications, though obviously there's a lot of R&D to do before getting to that level)
34 |
35 |
36 | // TO DO: make these AbsolutePosition, RelativePosition enums on ObjectSpecifierDescriptor
37 |
38 | private let firstPosition = ScalarDescriptor(type: typeAbsoluteOrdinal, data: Data([0x66, 0x69, 0x72, 0x73])) // kAEFirst
39 | private let middlePosition = ScalarDescriptor(type: typeAbsoluteOrdinal, data: Data([0x6D, 0x69, 0x64, 0x64])) // kAEMiddle
40 | private let lastPosition = ScalarDescriptor(type: typeAbsoluteOrdinal, data: Data([0x6C, 0x61, 0x73, 0x74])) // kAELast
41 | private let anyPosition = ScalarDescriptor(type: typeAbsoluteOrdinal, data: Data([0x61, 0x6E, 0x79, 0x20])) // kAEAny
42 | private let allPosition = ScalarDescriptor(type: typeAbsoluteOrdinal, data: Data([0x61, 0x6C, 0x6C, 0x20])) // kAEAll
43 |
44 | private let previousElement = ScalarDescriptor(type: typeEnumerated, data: Data([0x70, 0x72, 0x65, 0x76])) // kAEPrevious
45 | private let nextElement = ScalarDescriptor(type: typeEnumerated, data: Data([0x6E, 0x65, 0x78, 0x74])) // kAENext
46 |
47 |
48 |
49 | public protocol SpecifierDescriptor: QueryDescriptor {
50 |
51 | // note: an enhanced AEOM could easily allow multiple properties to be retrieved per query by packing as AEList of typeType (the main challenge is finding a client-side syntax that works); what other behaviors could be improved (e.g. unborking not-equals and is-in tests; simplified query descriptor layouts)
52 |
53 | func userProperty(_ name: String) -> ObjectSpecifierDescriptor
54 | func property(_ code: OSType) -> ObjectSpecifierDescriptor
55 | func elements(_ code: OSType) -> MultipleObjectSpecifierDescriptor
56 | }
57 |
58 | public extension SpecifierDescriptor {
59 |
60 | func userProperty(_ name: String) -> ObjectSpecifierDescriptor {
61 | return ObjectSpecifierDescriptor(want: typeProperty, form: .userProperty, seld: packAsString(name), from: self)
62 | }
63 |
64 | func property(_ code: OSType) -> ObjectSpecifierDescriptor {
65 | return ObjectSpecifierDescriptor(want: typeProperty, form: .property, seld: packAsType(code), from: self)
66 | }
67 |
68 | func elements(_ code: OSType) -> MultipleObjectSpecifierDescriptor {
69 | return MultipleObjectSpecifierDescriptor(want: code, form: .absolutePosition, seld: allPosition, from: self)
70 | }
71 | }
72 |
73 |
74 | // base objects from which queries are constructed
75 |
76 | public struct RootSpecifierDescriptor: SpecifierDescriptor { // abstract wrapper for the terminal descriptor in an object specifier; like a single-object specifier it exposes methods for constructing property and all-elements specifiers, e.g. `RootSpecifierDescriptor.app.elements(cDocument)`, `RootSpecifierDescriptor.its.property(pName)`
77 |
78 | public static let app = RootSpecifierDescriptor(nullDescriptor)
79 | public static let con = RootSpecifierDescriptor(ScalarDescriptor(type: typeCurrentContainer, data: nullData))
80 | public static let its = RootSpecifierDescriptor(ScalarDescriptor(type: typeObjectBeingExamined, data: nullData))
81 |
82 |
83 | public var type: DescType { return self.descriptor.type }
84 | public var data: Data { return self.descriptor.data }
85 |
86 | public var from: QueryDescriptor { return self } // TO DO: rename `parent`?
87 |
88 | internal let descriptor: Descriptor // while atypical, it is possible for an object specifier to have any 'from' value, e.g. `folders of alias "…"` is undocumented but legal in Finder; whether we continue to support this or start to lock down to a sensible spec is TBC (e.g. in Finder, that query can be rewritten as `folders of item (alias "…")`, which at least tickles a different bit of the spec); presumably this flexibility in legal chunk expressions is, in part, to permit constructing queries over AppleScript types (in which case the ability to serialize those queries as AEs is simply undocumented behavior left open), although it may also be deliberate precisely to allow more "English-like" phrasing when dealing with apps such as Finder that are capable of interpreting aliases and other primitive specifier types (i.e. 'folders of alias…' reads better than 'folders of item alias…', although it goes without saying that such 'magical' behaviors end up creating as much consistency/learnability hell)
89 |
90 | public init(_ descriptor: Descriptor) {
91 | self.descriptor = descriptor
92 | }
93 |
94 | public func flatten() -> Data {
95 | return self.descriptor.flatten()
96 | }
97 |
98 | public func appendTo(containerData result: inout Data) {
99 | self.descriptor.appendTo(containerData: &result)
100 | }
101 | }
102 |
103 |
104 | // insertion location, e.g. `beginning of ELEMENTS`, `after ELEMENT`
105 |
106 | public struct InsertionLocationDescriptor: QueryDescriptor {
107 |
108 | public var debugDescription: String {
109 | return "<\(Swift.type(of: self)) \(self.position) \(self.from)>"
110 | }
111 |
112 | public enum Position: OSType, CustomDebugStringConvertible {
113 | case before = 0x6265666F // kAEBefore
114 | case after = 0x61667465 // kAEAfter
115 | case beginning = 0x62676E67 // kAEBeginning
116 | case end = 0x656E6420 // kAEEnd
117 |
118 | public var debugDescription: String {
119 | switch self {
120 | case .before: return ".before"
121 | case .after: return ".after"
122 | case .beginning: return ".beginning"
123 | case .end: return ".end"
124 | }
125 | }
126 | }
127 |
128 | public let type: DescType = typeInsertionLoc
129 |
130 | public var data: Data {
131 | var result = Data([0x00, 0x00, 0x00, 0x02, // count (position, object)
132 | 0, 0, 0, 0, // align
133 | 0x6B, 0x70, 0x6F, 0x73, // * keyAEPosition
134 | 0x65, 0x6E, 0x75, 0x6D, // typeEnumerated
135 | 0x00, 0x00, 0x00, 0x04]) // size (4 bytes)
136 | result += encodeUInt32(self.position.rawValue) // enum code
137 | result += Data([0x6B, 0x6F, 0x62, 0x6A]) // * keyAEObject
138 | self.from.appendTo(containerData: &result) // descriptor
139 | return result
140 | }
141 |
142 | public let position: Position
143 | public let from: QueryDescriptor
144 |
145 | public init(position: Position, from: QueryDescriptor) {
146 | self.position = position
147 | self.from = from
148 | }
149 |
150 | // called by Unflatten.swift
151 | internal static func unflatten(_ data: Data, startingAt descStart: Int) throws -> InsertionLocationDescriptor {
152 | // type, remaining bytes // TO DO: sanity check these?
153 | var position: OSType? = nil, from: QueryDescriptor? = nil
154 | let countOffset = descStart + 8
155 | if data.readUInt32(at: countOffset) != 2 { throw AppleEventError.invalidParameterCount }
156 | var offset = countOffset + 8
157 | for _ in 0..<2 {
158 | let key = data[offset..<(offset+4)]
159 | switch key {
160 | case Data([0x6B, 0x70, 0x6F, 0x73]) // * keyAEPosition
161 | where data[(offset+4)..<(offset+12)] == Data([0x65, 0x6E, 0x75, 0x6D, // typeEnumerated
162 | 0x00, 0x00, 0x00, 0x04]): // size (4 bytes)
163 | position = data.readUInt32(at: offset+12)
164 | offset += 16
165 | case Data([0x6B, 0x6F, 0x62, 0x6A]): // * keyAEObject
166 | let desc: Descriptor // QueryDescriptor
167 | (desc, offset) = unflattenFirstDescriptor(in: data, startingAt: offset+4)
168 | // object specifier's parent is either an object specifier or its terminal [root] descriptor
169 | from = (desc.type == typeObjectSpecifier) ? (desc as! QueryDescriptor) : RootSpecifierDescriptor(desc) // TO DO: could do with utility functions that cast to expected type and return or throw 'corrupt data' or 'internal error' (i.e. bug); alternatively, might build this into unflattenFirstDescriptor(), with return type being Descriptor (the default) or the expected FOODescriptor type
170 | default:
171 | throw AppleEventError.invalidParameter
172 | }
173 | }
174 | guard let position_ = position, let from_ = from, let position__ = Position(rawValue: position_) else {
175 | throw AppleEventError.invalidParameter
176 | }
177 | return InsertionLocationDescriptor(position: position__, from: from_)
178 | }
179 | }
180 |
181 |
182 | // object specifier, e.g. `PROPERTY of …`, `every ELEMENT of …`, `ELEMENT INDEX of …`, `(ELEMENTS where TEST) of …`
183 |
184 | public struct ObjectSpecifierDescriptor: SpecifierDescriptor { // TO DO: want to reuse this implementation in MultipleObjectSpecifierDescriptor
185 |
186 | public var debugDescription: String {
187 | return "<\(Swift.type(of: self)) \(literalFourCharCode(self.want)) \(self.form) \(self.seld) \(self.from)>"
188 | }
189 |
190 | // TO DO: combine form+seld? (in practice, this may be of limited value as a degree of sloppiness is necessary to ensure backwards compatibility with existing ecosystem, but it might help clarify usage)
191 | public enum Form: OSType, CustomDebugStringConvertible {
192 | case property = 0x70726F70
193 | case absolutePosition = 0x696E6478
194 | case name = 0x6E616D65
195 | case uniqueID = 0x49442020
196 | case relativePosition = 0x72656C65
197 | case range = 0x72616E67
198 | case test = 0x74657374
199 | case userProperty = 0x75737270
200 |
201 | public var debugDescription: String {
202 | switch self {
203 | case .property: return ".property"
204 | case .absolutePosition: return ".absolutePosition"
205 | case .name: return ".name"
206 | case .uniqueID: return ".uniqueID"
207 | case .relativePosition: return ".relativePosition"
208 | case .range: return ".range"
209 | case .test: return ".test"
210 | case .userProperty: return ".userProperty"
211 | }
212 | }
213 | }
214 |
215 | public let type: DescType = typeObjectSpecifier
216 |
217 | // TO DO: naming?
218 | public let want: DescType
219 | public let form: ObjectSpecifierDescriptor.Form
220 | public let seld: Descriptor // may be anything
221 | public let from: QueryDescriptor // (objspec or root; technically it can be anything, but if we define a dedicated QueryRoot struct then we can put appropriate constructors on that)
222 |
223 | public init(want: DescType, form: ObjectSpecifierDescriptor.Form, seld: Descriptor, from: QueryDescriptor) {
224 | self.want = want
225 | self.form = form
226 | self.seld = seld
227 | self.from = from
228 | }
229 |
230 | public var data: Data {
231 | // flatten()/appendTo() will prefix type, remaining bytes
232 | var result = Data([0x00, 0x00, 0x00, 0x04, // count (want, form, data, from)
233 | 0, 0, 0, 0, // align
234 | 0x77, 0x61, 0x6E, 0x74, // * keyAEDesiredClass
235 | 0x74, 0x79, 0x70, 0x65, // typeType
236 | 0x00, 0x00, 0x00, 0x04]) // size (4 bytes)
237 | result += encodeUInt32(self.want) // type code
238 | result += Data([0x66, 0x6F, 0x72, 0x6D, // * keyAEKeyForm
239 | 0x65, 0x6E, 0x75, 0x6D, // typeEnumerated
240 | 0x00, 0x00, 0x00, 0x04]) // size (4 bytes)
241 | result += encodeUInt32(self.form.rawValue) // enum code
242 | result += Data([0x73, 0x65, 0x6C, 0x64]) // * keyAEKeyData
243 | self.seld.appendTo(containerData: &result) // descriptor
244 | result += Data([0x66, 0x72, 0x6F, 0x6D]) // * keyAEContainer
245 | self.from.appendTo(containerData: &result) // descriptor
246 | return result
247 | }
248 |
249 | // called by Unflatten.swift
250 | internal static func unflatten(_ data: Data, startingAt descStart: Int) throws -> ObjectSpecifierDescriptor {
251 | // type, remaining bytes // TO DO: sanity check these?
252 | var want: OSType? = nil, form: OSType? = nil, seld: Descriptor? = nil, from: QueryDescriptor? = nil
253 | let countOffset = descStart + 8 // while typeObjectSpecifier is nominally an AERecord, it lacks the extra padding between bytes remaining and number of items found in typeAERecord
254 | if data.readUInt32(at: countOffset) != 4 { throw AppleEventError.invalidParameterCount }
255 | var offset = countOffset + 8
256 | for _ in 0..<4 {
257 | let key = data[offset..<(offset+4)]
258 | switch key {
259 | case Data([0x77, 0x61, 0x6E, 0x74]) // * keyAEDesiredClass
260 | where data[(offset+4)..<(offset+12)] == Data([0x74, 0x79, 0x70, 0x65, // typeType
261 | 0x00, 0x00, 0x00, 0x04]): // size (4 bytes)
262 | want = data.readUInt32(at: offset+12) // type code
263 | offset += 16
264 | case Data([0x66, 0x6F, 0x72, 0x6D]) // * keyAEKeyForm
265 | where data[(offset+4)..<(offset+12)] == Data([0x65, 0x6E, 0x75, 0x6D, // typeEnumerated
266 | 0x00, 0x00, 0x00, 0x04]): // size (4 bytes)
267 | form = data.readUInt32(at: offset+12)
268 | offset += 16
269 | case Data([0x73, 0x65, 0x6C, 0x64]): // * keyAEKeyData
270 | let desc: Descriptor // any Descriptor
271 | (desc, offset) = unflattenFirstDescriptor(in: data, startingAt: offset+4)
272 | seld = desc
273 | case Data([0x66, 0x72, 0x6F, 0x6D]): // * keyAEContainer
274 | // TO DO: how best to implement lazy unpacking for client-side use? (i.e. when an app returns an obj spec, only the topmost specifier needs unwrapped in order to be used; the remainder can be left in an opaque wrapper similar to RootSpecifierDescriptor and only fully unpacked when needed, e.g. when constructing specifier's display representation); this can measurably improve performance where an application command returns a large list of specifiers
275 | let desc: Descriptor // QueryDescriptor
276 | (desc, offset) = unflattenFirstDescriptor(in: data, startingAt: offset+4)
277 | // object specifier's parent is either another object specifier or its terminal [root] descriptor
278 | from = (desc.type == typeObjectSpecifier) ? (desc as! QueryDescriptor) : RootSpecifierDescriptor(desc)
279 | default:
280 | throw AppleEventError.invalidParameter
281 | }
282 | }
283 | guard let want_ = want, let form_ = form, let seld_ = seld, let from_ = from,
284 | let selform = Form(rawValue: form_) else {
285 | throw AppleEventError.invalidParameter
286 | }
287 | return ObjectSpecifierDescriptor(want: want_, form: selform, seld: seld_, from: from_)
288 | }
289 | }
290 |
291 |
292 | public extension ObjectSpecifierDescriptor {
293 |
294 | // TO DO: SA also exposes the following on Root specifiers
295 |
296 | // relative position selectors
297 | func previous(_ code: OSType? = nil) -> ObjectSpecifierDescriptor {
298 | return ObjectSpecifierDescriptor(want: code ?? self.want, form: .relativePosition, seld: previousElement, from: self)
299 | }
300 |
301 | func next(_ code: OSType? = nil) -> ObjectSpecifierDescriptor {
302 | return ObjectSpecifierDescriptor(want: code ?? self.want, form: .relativePosition, seld: nextElement, from: self)
303 | }
304 |
305 | // insertion specifiers
306 | // TO DO: AppleScript/CocoaScripting does allow `beginning/end/etc [of app]` as abbreviated `beginning/end/etc [of elements of app]` where element type can be inferred (e.g. `make new document at beginning with properties {…}`); for API equivalence the following would need to be exposed on RootSpecifierDescriptor as well
307 |
308 | var beginning: InsertionLocationDescriptor {
309 | return InsertionLocationDescriptor(position: .beginning, from: self)
310 | }
311 | var end: InsertionLocationDescriptor {
312 | return InsertionLocationDescriptor(position: .end, from: self)
313 | }
314 | var before: InsertionLocationDescriptor {
315 | return InsertionLocationDescriptor(position: .before, from: self)
316 | }
317 | var after: InsertionLocationDescriptor {
318 | return InsertionLocationDescriptor(position: .after, from: self)
319 | }
320 | }
321 |
322 |
323 | public typealias MultipleObjectSpecifierDescriptor = ObjectSpecifierDescriptor // TO DO: temporary, until we decide how best to 'subclass' ObjectSpecifierDescriptor
324 |
325 |
326 | public extension MultipleObjectSpecifierDescriptor {
327 |
328 | struct RangeDescriptor: Scalar {
329 |
330 | public let type: DescType = typeRangeDescriptor
331 |
332 | public var data: Data {
333 | var result = Data([0x00, 0x00, 0x00, 0x02, // count (start, stop)
334 | 0, 0, 0, 0, // align
335 | 0x73, 0x74, 0x61, 0x72]) // * keyAERangeStart
336 | self.start.appendTo(containerData: &result) // descriptor
337 | result += Data([0x73, 0x74, 0x6F, 0x70]) // * keyAERangeStop
338 | self.stop.appendTo(containerData: &result) // descriptor
339 | return result
340 | }
341 |
342 | // TO DO: should initializers accept Int/String as shorthand for RootSpecifierDescriptor.con.elements(TYPE).byIndex(INT)/.byName(STRING), or should that be dealt with upstream? (probably upstream, as RangeDescriptor does not inherently know what the element TYPE is)
343 |
344 | public let start: QueryDescriptor // should always be QueryDescriptor; root is either Con or App (con is standard; not sure we can discount absolute specifiers though)
345 | public let stop: QueryDescriptor // should always be QueryDescriptor
346 |
347 | public init(start: QueryDescriptor, stop: QueryDescriptor) {
348 | self.start = start
349 | self.stop = stop
350 | }
351 |
352 | internal static func unflatten(_ data: Data, startingAt descStart: Int) throws -> RangeDescriptor {
353 | // type, remaining bytes // TO DO: sanity check these?
354 | var start: QueryDescriptor? = nil, stop: QueryDescriptor? = nil
355 | let countOffset = descStart + 8
356 | if data.readUInt32(at: countOffset) != 2 { throw AppleEventError.invalidParameterCount }
357 | var offset = countOffset + 8
358 | for _ in 0..<2 {
359 | let key = data[offset..<(offset+4)]
360 | switch key {
361 | case Data([0x73, 0x74, 0x61, 0x72]): // * keyAERangeStart
362 | let desc: Descriptor // QueryDescriptor
363 | (desc, offset) = unflattenFirstDescriptor(in: data, startingAt: offset+4)
364 | // object specifier's parent is either an object specifier or its terminal [root] descriptor
365 | start = (desc.type == typeObjectSpecifier) ? (desc as! QueryDescriptor) : nil
366 | case Data([0x73, 0x74, 0x6F, 0x70]): // * keyAERangeStop
367 | let desc: Descriptor // QueryDescriptor
368 | (desc, offset) = unflattenFirstDescriptor(in: data, startingAt: offset+4)
369 | // object specifier's parent is either an object specifier or its terminal [root] descriptor
370 | stop = (desc.type == typeObjectSpecifier) ? (desc as! QueryDescriptor) : nil
371 | default:
372 | throw AppleEventError.invalidParameter
373 | }
374 | }
375 | guard let start_ = start, let stop_ = stop else {
376 | throw AppleEventError.invalidParameter
377 | }
378 | return RangeDescriptor(start: start_, stop: stop_)
379 | }
380 | }
381 |
382 | private var baseQuery: QueryDescriptor { // discards the default kAEAll selector when calling an element[s] selector on `elements(TYPE)`
383 | return self.form == .absolutePosition && (try? unpackAsEnum(self.seld)) == OSType(kAEAll) ? self.from : self
384 | }
385 |
386 | func byIndex(_ index: Descriptor) -> ObjectSpecifierDescriptor { // TO DO: also accept Int for convenience?
387 | return ObjectSpecifierDescriptor(want: self.want, form: .absolutePosition, seld: index, from: self.baseQuery)
388 | }
389 | func byName(_ name: Descriptor) -> ObjectSpecifierDescriptor { // TO DO: also accept String for convenience?
390 | return ObjectSpecifierDescriptor(want: self.want, form: .name, seld: name, from: self.baseQuery)
391 | }
392 | func byID(_ id: Descriptor) -> ObjectSpecifierDescriptor {
393 | return ObjectSpecifierDescriptor(want: self.want, form: .uniqueID, seld: id, from: self.baseQuery)
394 | }
395 | func byRange(from start: QueryDescriptor, to stop: QueryDescriptor) -> MultipleObjectSpecifierDescriptor {
396 | // TO DO: start/stop should always be absolute/container-based query; how best to implement? (if we allow passing Int/String descriptors here, RangeDescriptor needs to build the container specifiers)
397 | return MultipleObjectSpecifierDescriptor(want: self.want, form: .range,
398 | seld: RangeDescriptor(start: start, stop: stop), from: self.baseQuery)
399 | }
400 | func byTest(_ test: TestDescriptor) -> MultipleObjectSpecifierDescriptor {
401 | return MultipleObjectSpecifierDescriptor(want: self.want, form: .test, seld: test, from: self.baseQuery)
402 | }
403 |
404 | var first: ObjectSpecifierDescriptor {
405 | return ObjectSpecifierDescriptor(want: self.want, form: .absolutePosition, seld: firstPosition, from: self.baseQuery)
406 | }
407 | var middle: ObjectSpecifierDescriptor {
408 | return ObjectSpecifierDescriptor(want: self.want, form: .absolutePosition, seld: middlePosition, from: self.baseQuery)
409 | }
410 | var last: ObjectSpecifierDescriptor {
411 | return ObjectSpecifierDescriptor(want: self.want, form: .absolutePosition, seld: lastPosition, from: self.baseQuery)
412 | }
413 | var any: ObjectSpecifierDescriptor {
414 | return ObjectSpecifierDescriptor(want: self.want, form: .absolutePosition, seld: anyPosition, from: self.baseQuery)
415 | }
416 | }
417 |
418 |
419 | public extension ObjectSpecifierDescriptor {
420 |
421 | // Comparison test constructors
422 |
423 | static func <(lhs: ObjectSpecifierDescriptor, rhs: Descriptor) -> TestDescriptor {
424 | return ComparisonDescriptor(object: lhs, comparison: .lessThan, value: rhs)
425 | }
426 | static func <=(lhs: ObjectSpecifierDescriptor, rhs: Descriptor) -> TestDescriptor {
427 | return ComparisonDescriptor(object: lhs, comparison: .lessThanOrEqual, value: rhs)
428 | }
429 | static func ==(lhs: ObjectSpecifierDescriptor, rhs: Descriptor) -> TestDescriptor {
430 | return ComparisonDescriptor(object: lhs, comparison: .equal, value: rhs)
431 | }
432 | static func !=(lhs: ObjectSpecifierDescriptor, rhs: Descriptor) -> TestDescriptor {
433 | return ComparisonDescriptor(object: lhs, comparison: .notEqual, value: rhs)
434 | }
435 | static func >(lhs: ObjectSpecifierDescriptor, rhs: Descriptor) -> TestDescriptor {
436 | return ComparisonDescriptor(object: lhs, comparison: .greaterThan, value: rhs)
437 | }
438 | static func >=(lhs: ObjectSpecifierDescriptor, rhs: Descriptor) -> TestDescriptor {
439 | return ComparisonDescriptor(object: lhs, comparison: .greaterThanOrEqual, value: rhs)
440 | }
441 |
442 | // Containment test constructors
443 |
444 | // note: ideally the following would only appear on objects constructed from an Its root; however, this would complicate the implementation while failing to provide any real benefit to users, who are unlikely to make such a mistake in the first place
445 |
446 | func beginsWith(_ value: Descriptor) -> TestDescriptor {
447 | return ComparisonDescriptor(object: self, comparison: .beginsWith, value: value)
448 | }
449 | func endsWith(_ value: Descriptor) -> TestDescriptor {
450 | return ComparisonDescriptor(object: self, comparison: .endsWith, value: value)
451 | }
452 | func contains(_ value: Descriptor) -> TestDescriptor {
453 | return ComparisonDescriptor(object: self, comparison: .contains, value: value)
454 | }
455 | func isIn(_ value: Descriptor) -> TestDescriptor {
456 | return ComparisonDescriptor(object: self, comparison: .isIn, value: value)
457 | }
458 | }
459 |
460 |
461 |
--------------------------------------------------------------------------------
/AppleEvents.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4F0B4C3C23264FEC00BDA407 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572D9232646E10002AED8 /* main.swift */; };
11 | 4F0B4C3D23264FF100BDA407 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572DB232646E10002AED8 /* main.swift */; };
12 | 4F2572A5232645650002AED8 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257293232645650002AED8 /* Constants.swift */; };
13 | 4F2572A6232645650002AED8 /* AppleEventDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257294232645650002AED8 /* AppleEventDescriptor.swift */; };
14 | 4F2572A7232645650002AED8 /* AppleEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257295232645650002AED8 /* AppleEventHandler.swift */; };
15 | 4F2572A8232645650002AED8 /* ListDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257296232645650002AED8 /* ListDescriptor.swift */; };
16 | 4F2572A9232645650002AED8 /* AppleEventError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257297232645650002AED8 /* AppleEventError.swift */; };
17 | 4F2572AA232645650002AED8 /* Unflatten.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257298232645650002AED8 /* Unflatten.swift */; };
18 | 4F2572AB232645650002AED8 /* QueryDescriptors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257299232645650002AED8 /* QueryDescriptors.swift */; };
19 | 4F2572AC232645650002AED8 /* RecordDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729A232645650002AED8 /* RecordDescriptor.swift */; };
20 | 4F2572AD232645650002AED8 /* Descriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729B232645650002AED8 /* Descriptor.swift */; };
21 | 4F2572AE232645650002AED8 /* AddressDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729C232645650002AED8 /* AddressDescriptor.swift */; };
22 | 4F2572AF232645650002AED8 /* TestDescriptors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729D232645650002AED8 /* TestDescriptors.swift */; };
23 | 4F2572B0232645650002AED8 /* Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729E232645650002AED8 /* Support.swift */; };
24 | 4F2572B2232645650002AED8 /* Shim.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572A0232645650002AED8 /* Shim.swift */; };
25 | 4F2572B3232645650002AED8 /* ScalarDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572A1232645650002AED8 /* ScalarDescriptor.swift */; };
26 | 4F2572B4232645650002AED8 /* UnpackFuncs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572A2232645650002AED8 /* UnpackFuncs.swift */; };
27 | 4F2572B5232645650002AED8 /* PackFuncs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572A3232645650002AED8 /* PackFuncs.swift */; };
28 | 4F2572C3232646BB0002AED8 /* Unflatten.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257298232645650002AED8 /* Unflatten.swift */; };
29 | 4F2572C4232646BB0002AED8 /* PackFuncs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572A3232645650002AED8 /* PackFuncs.swift */; };
30 | 4F2572C5232646BB0002AED8 /* UnpackFuncs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572A2232645650002AED8 /* UnpackFuncs.swift */; };
31 | 4F2572C6232646BB0002AED8 /* Descriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729B232645650002AED8 /* Descriptor.swift */; };
32 | 4F2572C7232646BB0002AED8 /* ScalarDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572A1232645650002AED8 /* ScalarDescriptor.swift */; };
33 | 4F2572C8232646BB0002AED8 /* AddressDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729C232645650002AED8 /* AddressDescriptor.swift */; };
34 | 4F2572C9232646BB0002AED8 /* ListDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257296232645650002AED8 /* ListDescriptor.swift */; };
35 | 4F2572CA232646BB0002AED8 /* RecordDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729A232645650002AED8 /* RecordDescriptor.swift */; };
36 | 4F2572CB232646BB0002AED8 /* QueryDescriptors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257299232645650002AED8 /* QueryDescriptors.swift */; };
37 | 4F2572CC232646BB0002AED8 /* TestDescriptors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729D232645650002AED8 /* TestDescriptors.swift */; };
38 | 4F2572CD232646BB0002AED8 /* AppleEventDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257294232645650002AED8 /* AppleEventDescriptor.swift */; };
39 | 4F2572CE232646BB0002AED8 /* AppleEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257295232645650002AED8 /* AppleEventHandler.swift */; };
40 | 4F2572CF232646BB0002AED8 /* AppleEventError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257297232645650002AED8 /* AppleEventError.swift */; };
41 | 4F2572D0232646BB0002AED8 /* Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F25729E232645650002AED8 /* Support.swift */; };
42 | 4F2572D1232646BB0002AED8 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F257293232645650002AED8 /* Constants.swift */; };
43 | 4F2572D2232646BB0002AED8 /* Shim.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2572A0232645650002AED8 /* Shim.swift */; };
44 | 4F25730123264A620002AED8 /* AppleEvents.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F25729F232645650002AED8 /* AppleEvents.h */; settings = {ATTRIBUTES = (Public, ); }; };
45 | 4FC965A629750E0F00A83D11 /* AppleEvents-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 4FF4D48C23269E1D00837530 /* AppleEvents-Bridging-Header.h */; settings = {ATTRIBUTES = (Private, ); }; };
46 | 4FEF3B942975189C00AC4372 /* AppleEvents.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F25729F232645650002AED8 /* AppleEvents.h */; };
47 | 4FF4D48F23269E1E00837530 /* AEMShim.h in Headers */ = {isa = PBXBuildFile; fileRef = 4FF4D48D23269E1E00837530 /* AEMShim.h */; };
48 | 4FF4D49023269E1E00837530 /* AEMShim.h in Headers */ = {isa = PBXBuildFile; fileRef = 4FF4D48D23269E1E00837530 /* AEMShim.h */; settings = {ATTRIBUTES = (Private, ); }; };
49 | 4FF4D49123269E1E00837530 /* AEMShim.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FF4D48E23269E1E00837530 /* AEMShim.m */; };
50 | 4FF4D49223269E1E00837530 /* AEMShim.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FF4D48E23269E1E00837530 /* AEMShim.m */; };
51 | 4FF4D4A02326A2F400837530 /* libAppleEvents.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F2572BE2326468C0002AED8 /* libAppleEvents.a */; };
52 | 4FF4D4A52326A30C00837530 /* libAppleEvents.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F2572BE2326468C0002AED8 /* libAppleEvents.a */; };
53 | /* End PBXBuildFile section */
54 |
55 | /* Begin PBXContainerItemProxy section */
56 | 4FF4D4A12326A2F800837530 /* PBXContainerItemProxy */ = {
57 | isa = PBXContainerItemProxy;
58 | containerPortal = 4F25727E232644F40002AED8 /* Project object */;
59 | proxyType = 1;
60 | remoteGlobalIDString = 4F2572BD2326468C0002AED8;
61 | remoteInfo = libAppleEvents;
62 | };
63 | 4FF4D4A32326A30800837530 /* PBXContainerItemProxy */ = {
64 | isa = PBXContainerItemProxy;
65 | containerPortal = 4F25727E232644F40002AED8 /* Project object */;
66 | proxyType = 1;
67 | remoteGlobalIDString = 4F2572BD2326468C0002AED8;
68 | remoteInfo = libAppleEvents;
69 | };
70 | /* End PBXContainerItemProxy section */
71 |
72 | /* Begin PBXCopyFilesBuildPhase section */
73 | 4F0B4C0823264B6D00BDA407 /* CopyFiles */ = {
74 | isa = PBXCopyFilesBuildPhase;
75 | buildActionMask = 2147483647;
76 | dstPath = /usr/share/man/man1/;
77 | dstSubfolderSpec = 0;
78 | files = (
79 | );
80 | runOnlyForDeploymentPostprocessing = 1;
81 | };
82 | 4F0B4C1623264B8600BDA407 /* CopyFiles */ = {
83 | isa = PBXCopyFilesBuildPhase;
84 | buildActionMask = 2147483647;
85 | dstPath = /usr/share/man/man1/;
86 | dstSubfolderSpec = 0;
87 | files = (
88 | );
89 | runOnlyForDeploymentPostprocessing = 1;
90 | };
91 | /* End PBXCopyFilesBuildPhase section */
92 |
93 | /* Begin PBXFileReference section */
94 | 4F0B4C0A23264B6D00BDA407 /* test-receive */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "test-receive"; sourceTree = BUILT_PRODUCTS_DIR; };
95 | 4F0B4C1823264B8600BDA407 /* test-send */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "test-send"; sourceTree = BUILT_PRODUCTS_DIR; };
96 | 4F257287232644F40002AED8 /* AppleEvents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppleEvents.framework; sourceTree = BUILT_PRODUCTS_DIR; };
97 | 4F257293232645650002AED8 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
98 | 4F257294232645650002AED8 /* AppleEventDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleEventDescriptor.swift; sourceTree = ""; };
99 | 4F257295232645650002AED8 /* AppleEventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleEventHandler.swift; sourceTree = ""; };
100 | 4F257296232645650002AED8 /* ListDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListDescriptor.swift; sourceTree = ""; };
101 | 4F257297232645650002AED8 /* AppleEventError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleEventError.swift; sourceTree = ""; };
102 | 4F257298232645650002AED8 /* Unflatten.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Unflatten.swift; sourceTree = ""; };
103 | 4F257299232645650002AED8 /* QueryDescriptors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryDescriptors.swift; sourceTree = ""; };
104 | 4F25729A232645650002AED8 /* RecordDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordDescriptor.swift; sourceTree = ""; };
105 | 4F25729B232645650002AED8 /* Descriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Descriptor.swift; sourceTree = ""; };
106 | 4F25729C232645650002AED8 /* AddressDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressDescriptor.swift; sourceTree = ""; };
107 | 4F25729D232645650002AED8 /* TestDescriptors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDescriptors.swift; sourceTree = ""; };
108 | 4F25729E232645650002AED8 /* Support.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Support.swift; sourceTree = ""; };
109 | 4F25729F232645650002AED8 /* AppleEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppleEvents.h; sourceTree = ""; };
110 | 4F2572A0232645650002AED8 /* Shim.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shim.swift; sourceTree = ""; };
111 | 4F2572A1232645650002AED8 /* ScalarDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScalarDescriptor.swift; sourceTree = ""; };
112 | 4F2572A2232645650002AED8 /* UnpackFuncs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnpackFuncs.swift; sourceTree = ""; };
113 | 4F2572A3232645650002AED8 /* PackFuncs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PackFuncs.swift; sourceTree = ""; };
114 | 4F2572A4232645650002AED8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
115 | 4F2572BE2326468C0002AED8 /* libAppleEvents.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAppleEvents.a; sourceTree = BUILT_PRODUCTS_DIR; };
116 | 4F2572D8232646E10002AED8 /* test-ae.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = "test-ae.py"; sourceTree = ""; };
117 | 4F2572D9232646E10002AED8 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
118 | 4F2572DB232646E10002AED8 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
119 | 4F2572F72326485D0002AED8 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
120 | 4F2572F82326485D0002AED8 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
121 | 4FF4D48C23269E1D00837530 /* AppleEvents-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AppleEvents-Bridging-Header.h"; sourceTree = ""; };
122 | 4FF4D48D23269E1E00837530 /* AEMShim.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEMShim.h; sourceTree = ""; };
123 | 4FF4D48E23269E1E00837530 /* AEMShim.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEMShim.m; sourceTree = ""; };
124 | /* End PBXFileReference section */
125 |
126 | /* Begin PBXFrameworksBuildPhase section */
127 | 4F0B4C0723264B6D00BDA407 /* Frameworks */ = {
128 | isa = PBXFrameworksBuildPhase;
129 | buildActionMask = 2147483647;
130 | files = (
131 | 4FF4D4A52326A30C00837530 /* libAppleEvents.a in Frameworks */,
132 | );
133 | runOnlyForDeploymentPostprocessing = 0;
134 | };
135 | 4F0B4C1523264B8600BDA407 /* Frameworks */ = {
136 | isa = PBXFrameworksBuildPhase;
137 | buildActionMask = 2147483647;
138 | files = (
139 | 4FF4D4A02326A2F400837530 /* libAppleEvents.a in Frameworks */,
140 | );
141 | runOnlyForDeploymentPostprocessing = 0;
142 | };
143 | 4F257284232644F40002AED8 /* Frameworks */ = {
144 | isa = PBXFrameworksBuildPhase;
145 | buildActionMask = 2147483647;
146 | files = (
147 | );
148 | runOnlyForDeploymentPostprocessing = 0;
149 | };
150 | 4F2572BC2326468C0002AED8 /* Frameworks */ = {
151 | isa = PBXFrameworksBuildPhase;
152 | buildActionMask = 2147483647;
153 | files = (
154 | );
155 | runOnlyForDeploymentPostprocessing = 0;
156 | };
157 | /* End PBXFrameworksBuildPhase section */
158 |
159 | /* Begin PBXGroup section */
160 | 4F25727D232644F40002AED8 = {
161 | isa = PBXGroup;
162 | children = (
163 | 4F257292232645650002AED8 /* AppleEvents */,
164 | 4F2572D7232646E10002AED8 /* test-receive */,
165 | 4F2572DA232646E10002AED8 /* test-send */,
166 | 4F2572E8232647940002AED8 /* Frameworks */,
167 | 4F257288232644F40002AED8 /* Products */,
168 | );
169 | sourceTree = "";
170 | };
171 | 4F257288232644F40002AED8 /* Products */ = {
172 | isa = PBXGroup;
173 | children = (
174 | 4F257287232644F40002AED8 /* AppleEvents.framework */,
175 | 4F2572BE2326468C0002AED8 /* libAppleEvents.a */,
176 | 4F0B4C0A23264B6D00BDA407 /* test-receive */,
177 | 4F0B4C1823264B8600BDA407 /* test-send */,
178 | );
179 | name = Products;
180 | sourceTree = "";
181 | };
182 | 4F257292232645650002AED8 /* AppleEvents */ = {
183 | isa = PBXGroup;
184 | children = (
185 | 4F25729F232645650002AED8 /* AppleEvents.h */,
186 | 4F2572A4232645650002AED8 /* Info.plist */,
187 | 4F2572B7232645E00002AED8 /* codecs */,
188 | 4F2572B8232646010002AED8 /* descriptors */,
189 | 4F2572B9232646300002AED8 /* handlers */,
190 | 4F257297232645650002AED8 /* AppleEventError.swift */,
191 | 4F25729E232645650002AED8 /* Support.swift */,
192 | 4F257293232645650002AED8 /* Constants.swift */,
193 | 4FF4D49323269E5800837530 /* shim */,
194 | );
195 | path = AppleEvents;
196 | sourceTree = "";
197 | };
198 | 4F2572B7232645E00002AED8 /* codecs */ = {
199 | isa = PBXGroup;
200 | children = (
201 | 4F257298232645650002AED8 /* Unflatten.swift */,
202 | 4F2572A3232645650002AED8 /* PackFuncs.swift */,
203 | 4F2572A2232645650002AED8 /* UnpackFuncs.swift */,
204 | );
205 | name = codecs;
206 | sourceTree = "";
207 | };
208 | 4F2572B8232646010002AED8 /* descriptors */ = {
209 | isa = PBXGroup;
210 | children = (
211 | 4F25729B232645650002AED8 /* Descriptor.swift */,
212 | 4F2572A1232645650002AED8 /* ScalarDescriptor.swift */,
213 | 4F25729C232645650002AED8 /* AddressDescriptor.swift */,
214 | 4F257296232645650002AED8 /* ListDescriptor.swift */,
215 | 4F25729A232645650002AED8 /* RecordDescriptor.swift */,
216 | 4F257299232645650002AED8 /* QueryDescriptors.swift */,
217 | 4F25729D232645650002AED8 /* TestDescriptors.swift */,
218 | 4F257294232645650002AED8 /* AppleEventDescriptor.swift */,
219 | );
220 | name = descriptors;
221 | path = AppleEvents;
222 | sourceTree = SOURCE_ROOT;
223 | };
224 | 4F2572B9232646300002AED8 /* handlers */ = {
225 | isa = PBXGroup;
226 | children = (
227 | 4F257295232645650002AED8 /* AppleEventHandler.swift */,
228 | );
229 | name = handlers;
230 | path = AppleEvents;
231 | sourceTree = SOURCE_ROOT;
232 | };
233 | 4F2572D7232646E10002AED8 /* test-receive */ = {
234 | isa = PBXGroup;
235 | children = (
236 | 4F2572D8232646E10002AED8 /* test-ae.py */,
237 | 4F2572D9232646E10002AED8 /* main.swift */,
238 | );
239 | path = "test-receive";
240 | sourceTree = "";
241 | };
242 | 4F2572DA232646E10002AED8 /* test-send */ = {
243 | isa = PBXGroup;
244 | children = (
245 | 4F2572DB232646E10002AED8 /* main.swift */,
246 | );
247 | path = "test-send";
248 | sourceTree = "";
249 | };
250 | 4F2572E8232647940002AED8 /* Frameworks */ = {
251 | isa = PBXGroup;
252 | children = (
253 | 4F2572F82326485D0002AED8 /* CoreFoundation.framework */,
254 | 4F2572F72326485D0002AED8 /* Foundation.framework */,
255 | );
256 | name = Frameworks;
257 | sourceTree = "";
258 | };
259 | 4FF4D49323269E5800837530 /* shim */ = {
260 | isa = PBXGroup;
261 | children = (
262 | 4F2572A0232645650002AED8 /* Shim.swift */,
263 | 4FF4D48D23269E1E00837530 /* AEMShim.h */,
264 | 4FF4D48E23269E1E00837530 /* AEMShim.m */,
265 | 4FF4D48C23269E1D00837530 /* AppleEvents-Bridging-Header.h */,
266 | );
267 | name = shim;
268 | sourceTree = "";
269 | };
270 | /* End PBXGroup section */
271 |
272 | /* Begin PBXHeadersBuildPhase section */
273 | 4F257282232644F40002AED8 /* Headers */ = {
274 | isa = PBXHeadersBuildPhase;
275 | buildActionMask = 2147483647;
276 | files = (
277 | 4F25730123264A620002AED8 /* AppleEvents.h in Headers */,
278 | 4FF4D48F23269E1E00837530 /* AEMShim.h in Headers */,
279 | );
280 | runOnlyForDeploymentPostprocessing = 0;
281 | };
282 | 4F2572BA2326468C0002AED8 /* Headers */ = {
283 | isa = PBXHeadersBuildPhase;
284 | buildActionMask = 2147483647;
285 | files = (
286 | 4FC965A629750E0F00A83D11 /* AppleEvents-Bridging-Header.h in Headers */,
287 | 4FF4D49023269E1E00837530 /* AEMShim.h in Headers */,
288 | 4FEF3B942975189C00AC4372 /* AppleEvents.h in Headers */,
289 | );
290 | runOnlyForDeploymentPostprocessing = 0;
291 | };
292 | /* End PBXHeadersBuildPhase section */
293 |
294 | /* Begin PBXNativeTarget section */
295 | 4F0B4C0923264B6D00BDA407 /* test-receive */ = {
296 | isa = PBXNativeTarget;
297 | buildConfigurationList = 4F0B4C1023264B6D00BDA407 /* Build configuration list for PBXNativeTarget "test-receive" */;
298 | buildPhases = (
299 | 4F0B4C0623264B6D00BDA407 /* Sources */,
300 | 4F0B4C0723264B6D00BDA407 /* Frameworks */,
301 | 4F0B4C0823264B6D00BDA407 /* CopyFiles */,
302 | );
303 | buildRules = (
304 | );
305 | dependencies = (
306 | 4FF4D4A42326A30800837530 /* PBXTargetDependency */,
307 | );
308 | name = "test-receive";
309 | productName = "test-receive";
310 | productReference = 4F0B4C0A23264B6D00BDA407 /* test-receive */;
311 | productType = "com.apple.product-type.tool";
312 | };
313 | 4F0B4C1723264B8600BDA407 /* test-send */ = {
314 | isa = PBXNativeTarget;
315 | buildConfigurationList = 4F0B4C1C23264B8600BDA407 /* Build configuration list for PBXNativeTarget "test-send" */;
316 | buildPhases = (
317 | 4F0B4C1423264B8600BDA407 /* Sources */,
318 | 4F0B4C1523264B8600BDA407 /* Frameworks */,
319 | 4F0B4C1623264B8600BDA407 /* CopyFiles */,
320 | );
321 | buildRules = (
322 | );
323 | dependencies = (
324 | 4FF4D4A22326A2F800837530 /* PBXTargetDependency */,
325 | );
326 | name = "test-send";
327 | productName = "test-send";
328 | productReference = 4F0B4C1823264B8600BDA407 /* test-send */;
329 | productType = "com.apple.product-type.tool";
330 | };
331 | 4F257286232644F40002AED8 /* AppleEvents */ = {
332 | isa = PBXNativeTarget;
333 | buildConfigurationList = 4F25728F232644F40002AED8 /* Build configuration list for PBXNativeTarget "AppleEvents" */;
334 | buildPhases = (
335 | 4F257282232644F40002AED8 /* Headers */,
336 | 4F257283232644F40002AED8 /* Sources */,
337 | 4F257284232644F40002AED8 /* Frameworks */,
338 | 4F257285232644F40002AED8 /* Resources */,
339 | );
340 | buildRules = (
341 | );
342 | dependencies = (
343 | );
344 | name = AppleEvents;
345 | productName = AppleEvents;
346 | productReference = 4F257287232644F40002AED8 /* AppleEvents.framework */;
347 | productType = "com.apple.product-type.framework";
348 | };
349 | 4F2572BD2326468C0002AED8 /* libAppleEvents */ = {
350 | isa = PBXNativeTarget;
351 | buildConfigurationList = 4F2572BF2326468C0002AED8 /* Build configuration list for PBXNativeTarget "libAppleEvents" */;
352 | buildPhases = (
353 | 4F2572BA2326468C0002AED8 /* Headers */,
354 | 4F2572BB2326468C0002AED8 /* Sources */,
355 | 4F2572BC2326468C0002AED8 /* Frameworks */,
356 | );
357 | buildRules = (
358 | );
359 | dependencies = (
360 | );
361 | name = libAppleEvents;
362 | productName = libAppleEvents;
363 | productReference = 4F2572BE2326468C0002AED8 /* libAppleEvents.a */;
364 | productType = "com.apple.product-type.library.static";
365 | };
366 | /* End PBXNativeTarget section */
367 |
368 | /* Begin PBXProject section */
369 | 4F25727E232644F40002AED8 /* Project object */ = {
370 | isa = PBXProject;
371 | attributes = {
372 | LastSwiftUpdateCheck = 1030;
373 | LastUpgradeCheck = 1420;
374 | ORGANIZATIONNAME = "Hamish Sanderson";
375 | TargetAttributes = {
376 | 4F0B4C0923264B6D00BDA407 = {
377 | CreatedOnToolsVersion = 10.3;
378 | };
379 | 4F0B4C1723264B8600BDA407 = {
380 | CreatedOnToolsVersion = 10.3;
381 | };
382 | 4F257286232644F40002AED8 = {
383 | CreatedOnToolsVersion = 10.3;
384 | LastSwiftMigration = 1030;
385 | };
386 | 4F2572BD2326468C0002AED8 = {
387 | CreatedOnToolsVersion = 10.3;
388 | LastSwiftMigration = 1030;
389 | };
390 | };
391 | };
392 | buildConfigurationList = 4F257281232644F40002AED8 /* Build configuration list for PBXProject "AppleEvents" */;
393 | compatibilityVersion = "Xcode 9.3";
394 | developmentRegion = en;
395 | hasScannedForEncodings = 0;
396 | knownRegions = (
397 | en,
398 | Base,
399 | );
400 | mainGroup = 4F25727D232644F40002AED8;
401 | productRefGroup = 4F257288232644F40002AED8 /* Products */;
402 | projectDirPath = "";
403 | projectRoot = "";
404 | targets = (
405 | 4F257286232644F40002AED8 /* AppleEvents */,
406 | 4F2572BD2326468C0002AED8 /* libAppleEvents */,
407 | 4F0B4C0923264B6D00BDA407 /* test-receive */,
408 | 4F0B4C1723264B8600BDA407 /* test-send */,
409 | );
410 | };
411 | /* End PBXProject section */
412 |
413 | /* Begin PBXResourcesBuildPhase section */
414 | 4F257285232644F40002AED8 /* Resources */ = {
415 | isa = PBXResourcesBuildPhase;
416 | buildActionMask = 2147483647;
417 | files = (
418 | );
419 | runOnlyForDeploymentPostprocessing = 0;
420 | };
421 | /* End PBXResourcesBuildPhase section */
422 |
423 | /* Begin PBXSourcesBuildPhase section */
424 | 4F0B4C0623264B6D00BDA407 /* Sources */ = {
425 | isa = PBXSourcesBuildPhase;
426 | buildActionMask = 2147483647;
427 | files = (
428 | 4F0B4C3C23264FEC00BDA407 /* main.swift in Sources */,
429 | );
430 | runOnlyForDeploymentPostprocessing = 0;
431 | };
432 | 4F0B4C1423264B8600BDA407 /* Sources */ = {
433 | isa = PBXSourcesBuildPhase;
434 | buildActionMask = 2147483647;
435 | files = (
436 | 4F0B4C3D23264FF100BDA407 /* main.swift in Sources */,
437 | );
438 | runOnlyForDeploymentPostprocessing = 0;
439 | };
440 | 4F257283232644F40002AED8 /* Sources */ = {
441 | isa = PBXSourcesBuildPhase;
442 | buildActionMask = 2147483647;
443 | files = (
444 | 4F2572B2232645650002AED8 /* Shim.swift in Sources */,
445 | 4F2572A6232645650002AED8 /* AppleEventDescriptor.swift in Sources */,
446 | 4F2572A9232645650002AED8 /* AppleEventError.swift in Sources */,
447 | 4FF4D49123269E1E00837530 /* AEMShim.m in Sources */,
448 | 4F2572A5232645650002AED8 /* Constants.swift in Sources */,
449 | 4F2572A8232645650002AED8 /* ListDescriptor.swift in Sources */,
450 | 4F2572A7232645650002AED8 /* AppleEventHandler.swift in Sources */,
451 | 4F2572B0232645650002AED8 /* Support.swift in Sources */,
452 | 4F2572AF232645650002AED8 /* TestDescriptors.swift in Sources */,
453 | 4F2572B5232645650002AED8 /* PackFuncs.swift in Sources */,
454 | 4F2572AE232645650002AED8 /* AddressDescriptor.swift in Sources */,
455 | 4F2572AD232645650002AED8 /* Descriptor.swift in Sources */,
456 | 4F2572AB232645650002AED8 /* QueryDescriptors.swift in Sources */,
457 | 4F2572AA232645650002AED8 /* Unflatten.swift in Sources */,
458 | 4F2572B3232645650002AED8 /* ScalarDescriptor.swift in Sources */,
459 | 4F2572AC232645650002AED8 /* RecordDescriptor.swift in Sources */,
460 | 4F2572B4232645650002AED8 /* UnpackFuncs.swift in Sources */,
461 | );
462 | runOnlyForDeploymentPostprocessing = 0;
463 | };
464 | 4F2572BB2326468C0002AED8 /* Sources */ = {
465 | isa = PBXSourcesBuildPhase;
466 | buildActionMask = 2147483647;
467 | files = (
468 | 4F2572C3232646BB0002AED8 /* Unflatten.swift in Sources */,
469 | 4F2572C4232646BB0002AED8 /* PackFuncs.swift in Sources */,
470 | 4F2572C5232646BB0002AED8 /* UnpackFuncs.swift in Sources */,
471 | 4FF4D49223269E1E00837530 /* AEMShim.m in Sources */,
472 | 4F2572C6232646BB0002AED8 /* Descriptor.swift in Sources */,
473 | 4F2572C7232646BB0002AED8 /* ScalarDescriptor.swift in Sources */,
474 | 4F2572C8232646BB0002AED8 /* AddressDescriptor.swift in Sources */,
475 | 4F2572C9232646BB0002AED8 /* ListDescriptor.swift in Sources */,
476 | 4F2572CA232646BB0002AED8 /* RecordDescriptor.swift in Sources */,
477 | 4F2572CB232646BB0002AED8 /* QueryDescriptors.swift in Sources */,
478 | 4F2572CC232646BB0002AED8 /* TestDescriptors.swift in Sources */,
479 | 4F2572CD232646BB0002AED8 /* AppleEventDescriptor.swift in Sources */,
480 | 4F2572CE232646BB0002AED8 /* AppleEventHandler.swift in Sources */,
481 | 4F2572CF232646BB0002AED8 /* AppleEventError.swift in Sources */,
482 | 4F2572D0232646BB0002AED8 /* Support.swift in Sources */,
483 | 4F2572D1232646BB0002AED8 /* Constants.swift in Sources */,
484 | 4F2572D2232646BB0002AED8 /* Shim.swift in Sources */,
485 | );
486 | runOnlyForDeploymentPostprocessing = 0;
487 | };
488 | /* End PBXSourcesBuildPhase section */
489 |
490 | /* Begin PBXTargetDependency section */
491 | 4FF4D4A22326A2F800837530 /* PBXTargetDependency */ = {
492 | isa = PBXTargetDependency;
493 | target = 4F2572BD2326468C0002AED8 /* libAppleEvents */;
494 | targetProxy = 4FF4D4A12326A2F800837530 /* PBXContainerItemProxy */;
495 | };
496 | 4FF4D4A42326A30800837530 /* PBXTargetDependency */ = {
497 | isa = PBXTargetDependency;
498 | target = 4F2572BD2326468C0002AED8 /* libAppleEvents */;
499 | targetProxy = 4FF4D4A32326A30800837530 /* PBXContainerItemProxy */;
500 | };
501 | /* End PBXTargetDependency section */
502 |
503 | /* Begin XCBuildConfiguration section */
504 | 4F0B4C0E23264B6D00BDA407 /* Debug */ = {
505 | isa = XCBuildConfiguration;
506 | buildSettings = {
507 | CODE_SIGN_IDENTITY = "-";
508 | CODE_SIGN_STYLE = Automatic;
509 | DEAD_CODE_STRIPPING = YES;
510 | DEVELOPMENT_TEAM = 27XZ82KJ77;
511 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
512 | PRODUCT_NAME = "$(TARGET_NAME)";
513 | SWIFT_VERSION = 5.0;
514 | };
515 | name = Debug;
516 | };
517 | 4F0B4C0F23264B6D00BDA407 /* Release */ = {
518 | isa = XCBuildConfiguration;
519 | buildSettings = {
520 | CODE_SIGN_IDENTITY = "-";
521 | CODE_SIGN_STYLE = Automatic;
522 | DEAD_CODE_STRIPPING = YES;
523 | DEVELOPMENT_TEAM = 27XZ82KJ77;
524 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
525 | PRODUCT_NAME = "$(TARGET_NAME)";
526 | SWIFT_VERSION = 5.0;
527 | };
528 | name = Release;
529 | };
530 | 4F0B4C1D23264B8600BDA407 /* Debug */ = {
531 | isa = XCBuildConfiguration;
532 | buildSettings = {
533 | CODE_SIGN_IDENTITY = "-";
534 | CODE_SIGN_STYLE = Automatic;
535 | DEAD_CODE_STRIPPING = YES;
536 | DEVELOPMENT_TEAM = 27XZ82KJ77;
537 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
538 | PRODUCT_NAME = "$(TARGET_NAME)";
539 | SWIFT_VERSION = 5.0;
540 | };
541 | name = Debug;
542 | };
543 | 4F0B4C1E23264B8600BDA407 /* Release */ = {
544 | isa = XCBuildConfiguration;
545 | buildSettings = {
546 | CODE_SIGN_IDENTITY = "-";
547 | CODE_SIGN_STYLE = Automatic;
548 | DEAD_CODE_STRIPPING = YES;
549 | DEVELOPMENT_TEAM = 27XZ82KJ77;
550 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
551 | PRODUCT_NAME = "$(TARGET_NAME)";
552 | SWIFT_VERSION = 5.0;
553 | };
554 | name = Release;
555 | };
556 | 4F25728D232644F40002AED8 /* Debug */ = {
557 | isa = XCBuildConfiguration;
558 | buildSettings = {
559 | ALWAYS_SEARCH_USER_PATHS = NO;
560 | CLANG_ANALYZER_NONNULL = YES;
561 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
562 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
563 | CLANG_CXX_LIBRARY = "libc++";
564 | CLANG_ENABLE_MODULES = YES;
565 | CLANG_ENABLE_OBJC_ARC = YES;
566 | CLANG_ENABLE_OBJC_WEAK = YES;
567 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
568 | CLANG_WARN_BOOL_CONVERSION = YES;
569 | CLANG_WARN_COMMA = YES;
570 | CLANG_WARN_CONSTANT_CONVERSION = YES;
571 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
572 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
573 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
574 | CLANG_WARN_EMPTY_BODY = YES;
575 | CLANG_WARN_ENUM_CONVERSION = YES;
576 | CLANG_WARN_INFINITE_RECURSION = YES;
577 | CLANG_WARN_INT_CONVERSION = YES;
578 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
579 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
580 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
581 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
582 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
583 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
584 | CLANG_WARN_STRICT_PROTOTYPES = YES;
585 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
586 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
587 | CLANG_WARN_UNREACHABLE_CODE = YES;
588 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
589 | CODE_SIGN_IDENTITY = "Mac Developer";
590 | COPY_PHASE_STRIP = NO;
591 | CURRENT_PROJECT_VERSION = 1;
592 | DEAD_CODE_STRIPPING = YES;
593 | DEBUG_INFORMATION_FORMAT = dwarf;
594 | ENABLE_STRICT_OBJC_MSGSEND = YES;
595 | ENABLE_TESTABILITY = YES;
596 | GCC_C_LANGUAGE_STANDARD = gnu11;
597 | GCC_DYNAMIC_NO_PIC = NO;
598 | GCC_NO_COMMON_BLOCKS = YES;
599 | GCC_OPTIMIZATION_LEVEL = 0;
600 | GCC_PREPROCESSOR_DEFINITIONS = (
601 | "DEBUG=1",
602 | "$(inherited)",
603 | );
604 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
605 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
606 | GCC_WARN_UNDECLARED_SELECTOR = YES;
607 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
608 | GCC_WARN_UNUSED_FUNCTION = YES;
609 | GCC_WARN_UNUSED_VARIABLE = YES;
610 | MACOSX_DEPLOYMENT_TARGET = 10.15;
611 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
612 | MTL_FAST_MATH = YES;
613 | ONLY_ACTIVE_ARCH = YES;
614 | SDKROOT = macosx;
615 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
616 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
617 | SWIFT_VERSION = 5.0;
618 | VERSIONING_SYSTEM = "apple-generic";
619 | VERSION_INFO_PREFIX = "";
620 | };
621 | name = Debug;
622 | };
623 | 4F25728E232644F40002AED8 /* Release */ = {
624 | isa = XCBuildConfiguration;
625 | buildSettings = {
626 | ALWAYS_SEARCH_USER_PATHS = NO;
627 | CLANG_ANALYZER_NONNULL = YES;
628 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
629 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
630 | CLANG_CXX_LIBRARY = "libc++";
631 | CLANG_ENABLE_MODULES = YES;
632 | CLANG_ENABLE_OBJC_ARC = YES;
633 | CLANG_ENABLE_OBJC_WEAK = YES;
634 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
635 | CLANG_WARN_BOOL_CONVERSION = YES;
636 | CLANG_WARN_COMMA = YES;
637 | CLANG_WARN_CONSTANT_CONVERSION = YES;
638 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
639 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
640 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
641 | CLANG_WARN_EMPTY_BODY = YES;
642 | CLANG_WARN_ENUM_CONVERSION = YES;
643 | CLANG_WARN_INFINITE_RECURSION = YES;
644 | CLANG_WARN_INT_CONVERSION = YES;
645 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
646 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
647 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
648 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
649 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
650 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
651 | CLANG_WARN_STRICT_PROTOTYPES = YES;
652 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
653 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
654 | CLANG_WARN_UNREACHABLE_CODE = YES;
655 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
656 | CODE_SIGN_IDENTITY = "Mac Developer";
657 | COPY_PHASE_STRIP = NO;
658 | CURRENT_PROJECT_VERSION = 1;
659 | DEAD_CODE_STRIPPING = YES;
660 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
661 | ENABLE_NS_ASSERTIONS = NO;
662 | ENABLE_STRICT_OBJC_MSGSEND = YES;
663 | GCC_C_LANGUAGE_STANDARD = gnu11;
664 | GCC_NO_COMMON_BLOCKS = YES;
665 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
666 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
667 | GCC_WARN_UNDECLARED_SELECTOR = YES;
668 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
669 | GCC_WARN_UNUSED_FUNCTION = YES;
670 | GCC_WARN_UNUSED_VARIABLE = YES;
671 | MACOSX_DEPLOYMENT_TARGET = 10.15;
672 | MTL_ENABLE_DEBUG_INFO = NO;
673 | MTL_FAST_MATH = YES;
674 | SDKROOT = macosx;
675 | SWIFT_COMPILATION_MODE = wholemodule;
676 | SWIFT_OPTIMIZATION_LEVEL = "-O";
677 | SWIFT_VERSION = 5.0;
678 | VERSIONING_SYSTEM = "apple-generic";
679 | VERSION_INFO_PREFIX = "";
680 | };
681 | name = Release;
682 | };
683 | 4F257290232644F40002AED8 /* Debug */ = {
684 | isa = XCBuildConfiguration;
685 | buildSettings = {
686 | CLANG_ENABLE_MODULES = YES;
687 | CODE_SIGN_IDENTITY = "";
688 | CODE_SIGN_STYLE = Automatic;
689 | COMBINE_HIDPI_IMAGES = YES;
690 | DEAD_CODE_STRIPPING = YES;
691 | DEFINES_MODULE = YES;
692 | DEVELOPMENT_TEAM = 27XZ82KJ77;
693 | DYLIB_COMPATIBILITY_VERSION = 1;
694 | DYLIB_CURRENT_VERSION = 1;
695 | DYLIB_INSTALL_NAME_BASE = "@rpath";
696 | FRAMEWORK_VERSION = A;
697 | INFOPLIST_FILE = AppleEvents/Info.plist;
698 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
699 | LD_RUNPATH_SEARCH_PATHS = (
700 | "$(inherited)",
701 | "@executable_path/../Frameworks",
702 | "@loader_path/Frameworks",
703 | );
704 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
705 | PRODUCT_BUNDLE_IDENTIFIER = uk.nuggle.AppleEvents;
706 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
707 | SKIP_INSTALL = YES;
708 | SWIFT_OBJC_BRIDGING_HEADER = "";
709 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
710 | SWIFT_VERSION = 5.0;
711 | };
712 | name = Debug;
713 | };
714 | 4F257291232644F40002AED8 /* Release */ = {
715 | isa = XCBuildConfiguration;
716 | buildSettings = {
717 | CLANG_ENABLE_MODULES = YES;
718 | CODE_SIGN_IDENTITY = "";
719 | CODE_SIGN_STYLE = Automatic;
720 | COMBINE_HIDPI_IMAGES = YES;
721 | DEAD_CODE_STRIPPING = YES;
722 | DEFINES_MODULE = YES;
723 | DEVELOPMENT_TEAM = 27XZ82KJ77;
724 | DYLIB_COMPATIBILITY_VERSION = 1;
725 | DYLIB_CURRENT_VERSION = 1;
726 | DYLIB_INSTALL_NAME_BASE = "@rpath";
727 | FRAMEWORK_VERSION = A;
728 | INFOPLIST_FILE = AppleEvents/Info.plist;
729 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
730 | LD_RUNPATH_SEARCH_PATHS = (
731 | "$(inherited)",
732 | "@executable_path/../Frameworks",
733 | "@loader_path/Frameworks",
734 | );
735 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
736 | PRODUCT_BUNDLE_IDENTIFIER = uk.nuggle.AppleEvents;
737 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
738 | SKIP_INSTALL = YES;
739 | SWIFT_OBJC_BRIDGING_HEADER = "";
740 | SWIFT_VERSION = 5.0;
741 | };
742 | name = Release;
743 | };
744 | 4F2572C02326468C0002AED8 /* Debug */ = {
745 | isa = XCBuildConfiguration;
746 | buildSettings = {
747 | CLANG_ENABLE_MODULES = YES;
748 | CODE_SIGN_STYLE = Automatic;
749 | DEAD_CODE_STRIPPING = YES;
750 | DEVELOPMENT_TEAM = 27XZ82KJ77;
751 | EXECUTABLE_PREFIX = lib;
752 | LD_RUNPATH_SEARCH_PATHS = (
753 | "$(inherited)",
754 | "@executable_path/../Frameworks",
755 | "@loader_path/../Frameworks",
756 | );
757 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
758 | PRODUCT_NAME = "$(PROJECT_NAME)";
759 | SKIP_INSTALL = YES;
760 | SWIFT_OBJC_BRIDGING_HEADER = "AppleEvents/AppleEvents-Bridging-Header.h";
761 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
762 | SWIFT_VERSION = 5.0;
763 | };
764 | name = Debug;
765 | };
766 | 4F2572C12326468C0002AED8 /* Release */ = {
767 | isa = XCBuildConfiguration;
768 | buildSettings = {
769 | CLANG_ENABLE_MODULES = YES;
770 | CODE_SIGN_STYLE = Automatic;
771 | DEAD_CODE_STRIPPING = YES;
772 | DEVELOPMENT_TEAM = 27XZ82KJ77;
773 | EXECUTABLE_PREFIX = lib;
774 | LD_RUNPATH_SEARCH_PATHS = (
775 | "$(inherited)",
776 | "@executable_path/../Frameworks",
777 | "@loader_path/../Frameworks",
778 | );
779 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
780 | PRODUCT_NAME = "$(PROJECT_NAME)";
781 | SKIP_INSTALL = YES;
782 | SWIFT_OBJC_BRIDGING_HEADER = "AppleEvents/AppleEvents-Bridging-Header.h";
783 | SWIFT_VERSION = 5.0;
784 | };
785 | name = Release;
786 | };
787 | /* End XCBuildConfiguration section */
788 |
789 | /* Begin XCConfigurationList section */
790 | 4F0B4C1023264B6D00BDA407 /* Build configuration list for PBXNativeTarget "test-receive" */ = {
791 | isa = XCConfigurationList;
792 | buildConfigurations = (
793 | 4F0B4C0E23264B6D00BDA407 /* Debug */,
794 | 4F0B4C0F23264B6D00BDA407 /* Release */,
795 | );
796 | defaultConfigurationIsVisible = 0;
797 | defaultConfigurationName = Release;
798 | };
799 | 4F0B4C1C23264B8600BDA407 /* Build configuration list for PBXNativeTarget "test-send" */ = {
800 | isa = XCConfigurationList;
801 | buildConfigurations = (
802 | 4F0B4C1D23264B8600BDA407 /* Debug */,
803 | 4F0B4C1E23264B8600BDA407 /* Release */,
804 | );
805 | defaultConfigurationIsVisible = 0;
806 | defaultConfigurationName = Release;
807 | };
808 | 4F257281232644F40002AED8 /* Build configuration list for PBXProject "AppleEvents" */ = {
809 | isa = XCConfigurationList;
810 | buildConfigurations = (
811 | 4F25728D232644F40002AED8 /* Debug */,
812 | 4F25728E232644F40002AED8 /* Release */,
813 | );
814 | defaultConfigurationIsVisible = 0;
815 | defaultConfigurationName = Release;
816 | };
817 | 4F25728F232644F40002AED8 /* Build configuration list for PBXNativeTarget "AppleEvents" */ = {
818 | isa = XCConfigurationList;
819 | buildConfigurations = (
820 | 4F257290232644F40002AED8 /* Debug */,
821 | 4F257291232644F40002AED8 /* Release */,
822 | );
823 | defaultConfigurationIsVisible = 0;
824 | defaultConfigurationName = Release;
825 | };
826 | 4F2572BF2326468C0002AED8 /* Build configuration list for PBXNativeTarget "libAppleEvents" */ = {
827 | isa = XCConfigurationList;
828 | buildConfigurations = (
829 | 4F2572C02326468C0002AED8 /* Debug */,
830 | 4F2572C12326468C0002AED8 /* Release */,
831 | );
832 | defaultConfigurationIsVisible = 0;
833 | defaultConfigurationName = Release;
834 | };
835 | /* End XCConfigurationList section */
836 | };
837 | rootObject = 4F25727E232644F40002AED8 /* Project object */;
838 | }
839 |
--------------------------------------------------------------------------------