├── AppleEvents.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── libAppleEvents.xcscheme └── xcuserdata │ └── has.xcuserdatad │ └── xcschemes │ ├── AppleEvents.xcscheme │ ├── test-receive.xcscheme │ ├── test-send.xcscheme │ └── xcschememanagement.plist ├── AppleEvents ├── AEMShim.h ├── AEMShim.m ├── AddressDescriptor.swift ├── AppleEventDescriptor.swift ├── AppleEventError.swift ├── AppleEventHandler.swift ├── AppleEvents-Bridging-Header.h ├── AppleEvents.h ├── Constants.swift ├── Descriptor.swift ├── Info.plist ├── ListDescriptor.swift ├── PackFuncs.swift ├── QueryDescriptors.swift ├── RecordDescriptor.swift ├── ScalarDescriptor.swift ├── Shim.swift ├── Support.swift ├── TestDescriptors.swift ├── Unflatten.swift └── UnpackFuncs.swift ├── test-receive ├── main.swift └── test-ae.py └── test-send └── main.swift /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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.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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------