├── README ├── Swift-AppleScriptObjC.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── has.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── Swift-AppleScriptObjC ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Base.lproj └── MainMenu.xib ├── Info.plist ├── Swift-AppleScriptObjC.entitlements ├── iTunesBridge.applescript └── iTunesBridge.swift /README: -------------------------------------------------------------------------------- 1 | README 2 | 3 | A simple "remote control" for Music.app which demonstrates how to call AppleScript via the AppleScript-ObjC (ASOC) bridge within a Swift-Cocoa app. 4 | 5 | Caution: The AppleScript code lacks detailed error reporting and the Swift code's error handling is minimal. The app will fail *silently* if the following requirements are not met: 6 | 7 | - The Plist.info file must contain an "NSAppleEventsUsageDescription" entry. 8 | 9 | - If Hardened Runtime is enabled, the Apple Events exception in Targets > Signing & Capabilities must be selected. 10 | 11 | - If sandboxing is enabled, the appropriate "com.apple.Music.library..." entitlements must be declared. 12 | 13 | - The user grants the app Authorization to control Music.app when first prompted by macOS. 14 | 15 | This code is released into the public domain. 16 | -------------------------------------------------------------------------------- /Swift-AppleScriptObjC.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4F32BCC420D8272F00C99480 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F32BCC320D8272F00C99480 /* AppDelegate.swift */; }; 11 | 4F32BCC620D8273100C99480 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F32BCC520D8273100C99480 /* Assets.xcassets */; }; 12 | 4F32BCC920D8273100C99480 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4F32BCC720D8273100C99480 /* MainMenu.xib */; }; 13 | 4F32BCD220D827D500C99480 /* iTunesBridge.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 4F32BCD120D827D500C99480 /* iTunesBridge.applescript */; }; 14 | 4F32BCD520D8479E00C99480 /* AppleScriptObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F32BCD420D8479E00C99480 /* AppleScriptObjC.framework */; }; 15 | 4F32BCD720D861BD00C99480 /* iTunesBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F32BCD620D861BD00C99480 /* iTunesBridge.swift */; }; 16 | 4F32BCD920D8905200C99480 /* README in Resources */ = {isa = PBXBuildFile; fileRef = 4F32BCD820D8905200C99480 /* README */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 4F32BCC020D8272F00C99480 /* Swift-AppleScriptObjC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Swift-AppleScriptObjC.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 4F32BCC320D8272F00C99480 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | 4F32BCC520D8273100C99480 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | 4F32BCC820D8273100C99480 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 24 | 4F32BCCA20D8273100C99480 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | 4F32BCD120D827D500C99480 /* iTunesBridge.applescript */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.applescript; path = iTunesBridge.applescript; sourceTree = ""; }; 26 | 4F32BCD420D8479E00C99480 /* AppleScriptObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppleScriptObjC.framework; path = System/Library/Frameworks/AppleScriptObjC.framework; sourceTree = SDKROOT; }; 27 | 4F32BCD620D861BD00C99480 /* iTunesBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iTunesBridge.swift; sourceTree = ""; }; 28 | 4F32BCD820D8905200C99480 /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = ""; }; 29 | 4FC3BD57241CF7DF0071B1FB /* Swift-AppleScriptObjC.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Swift-AppleScriptObjC.entitlements"; sourceTree = ""; }; 30 | /* End PBXFileReference section */ 31 | 32 | /* Begin PBXFrameworksBuildPhase section */ 33 | 4F32BCBD20D8272F00C99480 /* Frameworks */ = { 34 | isa = PBXFrameworksBuildPhase; 35 | buildActionMask = 2147483647; 36 | files = ( 37 | 4F32BCD520D8479E00C99480 /* AppleScriptObjC.framework in Frameworks */, 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 4F32BCB720D8272F00C99480 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 4F32BCD820D8905200C99480 /* README */, 48 | 4F32BCC220D8272F00C99480 /* Swift-AppleScriptObjC */, 49 | 4F32BCC120D8272F00C99480 /* Products */, 50 | 4F32BCD320D8479D00C99480 /* Frameworks */, 51 | ); 52 | sourceTree = ""; 53 | }; 54 | 4F32BCC120D8272F00C99480 /* Products */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | 4F32BCC020D8272F00C99480 /* Swift-AppleScriptObjC.app */, 58 | ); 59 | name = Products; 60 | sourceTree = ""; 61 | }; 62 | 4F32BCC220D8272F00C99480 /* Swift-AppleScriptObjC */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 4F32BCC320D8272F00C99480 /* AppDelegate.swift */, 66 | 4F32BCD620D861BD00C99480 /* iTunesBridge.swift */, 67 | 4F32BCD120D827D500C99480 /* iTunesBridge.applescript */, 68 | 4F32BCC520D8273100C99480 /* Assets.xcassets */, 69 | 4F32BCC720D8273100C99480 /* MainMenu.xib */, 70 | 4F32BCCA20D8273100C99480 /* Info.plist */, 71 | 4FC3BD57241CF7DF0071B1FB /* Swift-AppleScriptObjC.entitlements */, 72 | ); 73 | path = "Swift-AppleScriptObjC"; 74 | sourceTree = ""; 75 | }; 76 | 4F32BCD320D8479D00C99480 /* Frameworks */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 4F32BCD420D8479E00C99480 /* AppleScriptObjC.framework */, 80 | ); 81 | name = Frameworks; 82 | sourceTree = ""; 83 | }; 84 | /* End PBXGroup section */ 85 | 86 | /* Begin PBXNativeTarget section */ 87 | 4F32BCBF20D8272F00C99480 /* Swift-AppleScriptObjC */ = { 88 | isa = PBXNativeTarget; 89 | buildConfigurationList = 4F32BCCE20D8273100C99480 /* Build configuration list for PBXNativeTarget "Swift-AppleScriptObjC" */; 90 | buildPhases = ( 91 | 4F32BCBC20D8272F00C99480 /* Sources */, 92 | 4F32BCBD20D8272F00C99480 /* Frameworks */, 93 | 4F32BCBE20D8272F00C99480 /* Resources */, 94 | ); 95 | buildRules = ( 96 | ); 97 | dependencies = ( 98 | ); 99 | name = "Swift-AppleScriptObjC"; 100 | productName = "Swift-AppleScriptObjC"; 101 | productReference = 4F32BCC020D8272F00C99480 /* Swift-AppleScriptObjC.app */; 102 | productType = "com.apple.product-type.application"; 103 | }; 104 | /* End PBXNativeTarget section */ 105 | 106 | /* Begin PBXProject section */ 107 | 4F32BCB820D8272F00C99480 /* Project object */ = { 108 | isa = PBXProject; 109 | attributes = { 110 | LastSwiftUpdateCheck = 0940; 111 | LastUpgradeCheck = 1320; 112 | TargetAttributes = { 113 | 4F32BCBF20D8272F00C99480 = { 114 | CreatedOnToolsVersion = 9.4.1; 115 | LastSwiftMigration = 1130; 116 | SystemCapabilities = { 117 | com.apple.Sandbox = { 118 | enabled = 0; 119 | }; 120 | }; 121 | }; 122 | }; 123 | }; 124 | buildConfigurationList = 4F32BCBB20D8272F00C99480 /* Build configuration list for PBXProject "Swift-AppleScriptObjC" */; 125 | compatibilityVersion = "Xcode 9.3"; 126 | developmentRegion = en; 127 | hasScannedForEncodings = 0; 128 | knownRegions = ( 129 | en, 130 | Base, 131 | ); 132 | mainGroup = 4F32BCB720D8272F00C99480; 133 | productRefGroup = 4F32BCC120D8272F00C99480 /* Products */; 134 | projectDirPath = ""; 135 | projectRoot = ""; 136 | targets = ( 137 | 4F32BCBF20D8272F00C99480 /* Swift-AppleScriptObjC */, 138 | ); 139 | }; 140 | /* End PBXProject section */ 141 | 142 | /* Begin PBXResourcesBuildPhase section */ 143 | 4F32BCBE20D8272F00C99480 /* Resources */ = { 144 | isa = PBXResourcesBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | 4F32BCD920D8905200C99480 /* README in Resources */, 148 | 4F32BCC620D8273100C99480 /* Assets.xcassets in Resources */, 149 | 4F32BCC920D8273100C99480 /* MainMenu.xib in Resources */, 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | /* End PBXResourcesBuildPhase section */ 154 | 155 | /* Begin PBXSourcesBuildPhase section */ 156 | 4F32BCBC20D8272F00C99480 /* Sources */ = { 157 | isa = PBXSourcesBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | 4F32BCD220D827D500C99480 /* iTunesBridge.applescript in Sources */, 161 | 4F32BCC420D8272F00C99480 /* AppDelegate.swift in Sources */, 162 | 4F32BCD720D861BD00C99480 /* iTunesBridge.swift in Sources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXSourcesBuildPhase section */ 167 | 168 | /* Begin PBXVariantGroup section */ 169 | 4F32BCC720D8273100C99480 /* MainMenu.xib */ = { 170 | isa = PBXVariantGroup; 171 | children = ( 172 | 4F32BCC820D8273100C99480 /* Base */, 173 | ); 174 | name = MainMenu.xib; 175 | sourceTree = ""; 176 | }; 177 | /* End PBXVariantGroup section */ 178 | 179 | /* Begin XCBuildConfiguration section */ 180 | 4F32BCCC20D8273100C99480 /* Debug */ = { 181 | isa = XCBuildConfiguration; 182 | buildSettings = { 183 | ALWAYS_SEARCH_USER_PATHS = NO; 184 | CLANG_ANALYZER_NONNULL = YES; 185 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 186 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 187 | CLANG_CXX_LIBRARY = "libc++"; 188 | CLANG_ENABLE_MODULES = YES; 189 | CLANG_ENABLE_OBJC_ARC = YES; 190 | CLANG_ENABLE_OBJC_WEAK = YES; 191 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 192 | CLANG_WARN_BOOL_CONVERSION = YES; 193 | CLANG_WARN_COMMA = YES; 194 | CLANG_WARN_CONSTANT_CONVERSION = YES; 195 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 196 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 197 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 198 | CLANG_WARN_EMPTY_BODY = YES; 199 | CLANG_WARN_ENUM_CONVERSION = YES; 200 | CLANG_WARN_INFINITE_RECURSION = YES; 201 | CLANG_WARN_INT_CONVERSION = YES; 202 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 203 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 204 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 205 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 206 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 207 | CLANG_WARN_STRICT_PROTOTYPES = YES; 208 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 209 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 210 | CLANG_WARN_UNREACHABLE_CODE = YES; 211 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 212 | CODE_SIGN_IDENTITY = "Mac Developer"; 213 | COPY_PHASE_STRIP = NO; 214 | DEBUG_INFORMATION_FORMAT = dwarf; 215 | ENABLE_STRICT_OBJC_MSGSEND = YES; 216 | ENABLE_TESTABILITY = YES; 217 | GCC_C_LANGUAGE_STANDARD = gnu11; 218 | GCC_DYNAMIC_NO_PIC = NO; 219 | GCC_NO_COMMON_BLOCKS = YES; 220 | GCC_OPTIMIZATION_LEVEL = 0; 221 | GCC_PREPROCESSOR_DEFINITIONS = ( 222 | "DEBUG=1", 223 | "$(inherited)", 224 | ); 225 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 226 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 227 | GCC_WARN_UNDECLARED_SELECTOR = YES; 228 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 229 | GCC_WARN_UNUSED_FUNCTION = YES; 230 | GCC_WARN_UNUSED_VARIABLE = YES; 231 | MACOSX_DEPLOYMENT_TARGET = 10.12; 232 | MTL_ENABLE_DEBUG_INFO = YES; 233 | ONLY_ACTIVE_ARCH = YES; 234 | SDKROOT = macosx; 235 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 236 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 237 | }; 238 | name = Debug; 239 | }; 240 | 4F32BCCD20D8273100C99480 /* Release */ = { 241 | isa = XCBuildConfiguration; 242 | buildSettings = { 243 | ALWAYS_SEARCH_USER_PATHS = NO; 244 | CLANG_ANALYZER_NONNULL = YES; 245 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_ENABLE_OBJC_WEAK = YES; 251 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 252 | CLANG_WARN_BOOL_CONVERSION = YES; 253 | CLANG_WARN_COMMA = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | CODE_SIGN_IDENTITY = "Mac Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 275 | ENABLE_NS_ASSERTIONS = NO; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu11; 278 | GCC_NO_COMMON_BLOCKS = YES; 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | MACOSX_DEPLOYMENT_TARGET = 10.12; 286 | MTL_ENABLE_DEBUG_INFO = NO; 287 | SDKROOT = macosx; 288 | SWIFT_COMPILATION_MODE = wholemodule; 289 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 290 | }; 291 | name = Release; 292 | }; 293 | 4F32BCCF20D8273100C99480 /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 297 | CODE_SIGN_ENTITLEMENTS = "Swift-AppleScriptObjC/Swift-AppleScriptObjC.entitlements"; 298 | CODE_SIGN_IDENTITY = "Mac Developer"; 299 | CODE_SIGN_STYLE = Automatic; 300 | COMBINE_HIDPI_IMAGES = YES; 301 | DEVELOPMENT_TEAM = 27XZ82KJ77; 302 | ENABLE_HARDENED_RUNTIME = YES; 303 | INFOPLIST_FILE = "Swift-AppleScriptObjC/Info.plist"; 304 | LD_RUNPATH_SEARCH_PATHS = ( 305 | "$(inherited)", 306 | "@executable_path/../Frameworks", 307 | ); 308 | MACOSX_DEPLOYMENT_TARGET = 10.12; 309 | PRODUCT_BUNDLE_IDENTIFIER = "uk.nuggle.Swift-AppleScriptObjC"; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | PROVISIONING_PROFILE_SPECIFIER = ""; 312 | SWIFT_VERSION = 5.0; 313 | }; 314 | name = Debug; 315 | }; 316 | 4F32BCD020D8273100C99480 /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 320 | CODE_SIGN_ENTITLEMENTS = "Swift-AppleScriptObjC/Swift-AppleScriptObjC.entitlements"; 321 | CODE_SIGN_IDENTITY = "Mac Developer"; 322 | CODE_SIGN_STYLE = Automatic; 323 | COMBINE_HIDPI_IMAGES = YES; 324 | DEVELOPMENT_TEAM = 27XZ82KJ77; 325 | ENABLE_HARDENED_RUNTIME = YES; 326 | INFOPLIST_FILE = "Swift-AppleScriptObjC/Info.plist"; 327 | LD_RUNPATH_SEARCH_PATHS = ( 328 | "$(inherited)", 329 | "@executable_path/../Frameworks", 330 | ); 331 | MACOSX_DEPLOYMENT_TARGET = 10.12; 332 | PRODUCT_BUNDLE_IDENTIFIER = "uk.nuggle.Swift-AppleScriptObjC"; 333 | PRODUCT_NAME = "$(TARGET_NAME)"; 334 | PROVISIONING_PROFILE_SPECIFIER = ""; 335 | SWIFT_VERSION = 5.0; 336 | }; 337 | name = Release; 338 | }; 339 | /* End XCBuildConfiguration section */ 340 | 341 | /* Begin XCConfigurationList section */ 342 | 4F32BCBB20D8272F00C99480 /* Build configuration list for PBXProject "Swift-AppleScriptObjC" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | 4F32BCCC20D8273100C99480 /* Debug */, 346 | 4F32BCCD20D8273100C99480 /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | 4F32BCCE20D8273100C99480 /* Build configuration list for PBXNativeTarget "Swift-AppleScriptObjC" */ = { 352 | isa = XCConfigurationList; 353 | buildConfigurations = ( 354 | 4F32BCCF20D8273100C99480 /* Debug */, 355 | 4F32BCD020D8273100C99480 /* Release */, 356 | ); 357 | defaultConfigurationIsVisible = 0; 358 | defaultConfigurationName = Release; 359 | }; 360 | /* End XCConfigurationList section */ 361 | }; 362 | rootObject = 4F32BCB820D8272F00C99480 /* Project object */; 363 | } 364 | -------------------------------------------------------------------------------- /Swift-AppleScriptObjC.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Swift-AppleScriptObjC.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Swift-AppleScriptObjC.xcodeproj/xcuserdata/has.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Swift-AppleScriptObjC.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | Swift-AppleScriptObjC.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Swift-AppleScriptObjC/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Swift-AppleScriptObjC 4 | // 5 | 6 | import Cocoa 7 | import AppleScriptObjC // ASOC adds its own 'load scripts' method to NSBundle 8 | 9 | 10 | @NSApplicationMain 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | @IBOutlet weak var window: NSWindow! 14 | 15 | // Cocoa Bindings 16 | @objc dynamic var trackName: NSString! 17 | @objc dynamic var trackArtist: NSString! 18 | @objc dynamic var trackAlbum: NSString! 19 | 20 | @objc dynamic var trackDuration: NSNumber! 21 | 22 | @objc dynamic var soundVolume: NSNumber! { 23 | get { return self.iTunesBridge.isRunning ? self.iTunesBridge.soundVolume : 0 } 24 | set(value) { self.iTunesBridge.soundVolume = value } 25 | } 26 | 27 | @objc dynamic var playerState: PlayerState = .unknown 28 | 29 | // AppleScriptObjC object for communicating with iTunes 30 | var iTunesBridge: iTunesBridge 31 | 32 | override init() { 33 | // AppleScriptObjC setup 34 | Bundle.main.loadAppleScriptObjectiveCScripts() 35 | // create an instance of iTunesBridge script object for Swift code to use 36 | let iTunesBridgeClass: AnyClass = NSClassFromString("iTunesBridge")! 37 | self.iTunesBridge = iTunesBridgeClass.alloc() as! iTunesBridge 38 | // general application setup 39 | ValueTransformer.setValueTransformer(PlayButtonNameTransformer(), 40 | forName: NSValueTransformerName(rawValue: "PlayButtonNameTransformer")) 41 | super.init() 42 | } 43 | 44 | // 45 | 46 | func applicationDidFinishLaunching(_ aNotification: Notification) { 47 | // iTunes emits track change notifications; very handy for UI refreshes 48 | let dnc = DistributedNotificationCenter.default() 49 | dnc.addObserver(self, selector:#selector(AppDelegate.updateTrackInfo), 50 | name:NSNotification.Name(rawValue:"com.apple.iTunes.playerInfo"), object:nil) 51 | // update UI only if iTunes is already running, otherwise wait until user performs an action 52 | if self.iTunesBridge.isRunning { self.updateTrackInfo() } 53 | } 54 | 55 | func applicationWillTerminate(_ aNotification: Notification) { 56 | // DistributedNotificationCenter.default().removeObserver(self) 57 | } 58 | 59 | // 60 | 61 | @objc func updateTrackInfo() { 62 | if let trackInfo = self.iTunesBridge.trackInfo { // nil indicates error, e.g. current track not available 63 | self.trackName = (trackInfo["trackName"] as! NSString) 64 | self.trackArtist = (trackInfo["trackArtist"] as! NSString) 65 | self.trackAlbum = (trackInfo["trackAlbum"] as! NSString) 66 | self.trackDuration = self.iTunesBridge.trackDuration 67 | self.playerState = self.iTunesBridge.playerState 68 | } 69 | } 70 | 71 | // buttons 72 | 73 | @IBAction func playPause(_ sender: Any) { 74 | self.iTunesBridge.playPause() 75 | self.updateTrackInfo() 76 | } 77 | 78 | @IBAction func gotoPreviousTrack(_ sender: Any) { 79 | self.iTunesBridge.gotoPreviousTrack() 80 | self.updateTrackInfo() 81 | } 82 | 83 | @IBAction func gotoNextTrack(_ sender: Any) { 84 | self.iTunesBridge.gotoNextTrack() 85 | self.updateTrackInfo() 86 | } 87 | } 88 | 89 | 90 | 91 | class PlayButtonNameTransformer: ValueTransformer { 92 | 93 | override static func transformedValueClass() -> Swift.AnyClass { 94 | return NSString.self 95 | } 96 | 97 | override class func allowsReverseTransformation() -> Bool { 98 | return false 99 | } 100 | 101 | override func transformedValue(_ value: Any?) -> Any? { 102 | guard let state = value else { return "Play" } 103 | return PlayerState(rawValue: state as! Int) == .playing ? "Pause" : "Play" 104 | } 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /Swift-AppleScriptObjC/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Swift-AppleScriptObjC/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Swift-AppleScriptObjC/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | Default 520 | 521 | 522 | 523 | 524 | 525 | 526 | Left to Right 527 | 528 | 529 | 530 | 531 | 532 | 533 | Right to Left 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | Default 545 | 546 | 547 | 548 | 549 | 550 | 551 | Left to Right 552 | 553 | 554 | 555 | 556 | 557 | 558 | Right to Left 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | N/A 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | N/A 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | N/A 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 00:00:00 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | -------------------------------------------------------------------------------- /Swift-AppleScriptObjC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainNibFile 26 | MainMenu 27 | NSAppleEventsUsageDescription 28 | Demonstrates how to control an “AppleScriptable” application from Swift via AppleScript-ObjC. 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Swift-AppleScriptObjC/Swift-AppleScriptObjC.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.automation.apple-events 8 | 9 | com.apple.security.scripting-targets 10 | 11 | com.apple.Music 12 | 13 | com.apple.Music.playback 14 | com.apple.Music.library.read-write 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Swift-AppleScriptObjC/iTunesBridge.applescript: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/Swift-AppleScriptObjC/68241eb197f5ffdbc57a6997e62550fa302d515a/Swift-AppleScriptObjC/iTunesBridge.applescript -------------------------------------------------------------------------------- /Swift-AppleScriptObjC/iTunesBridge.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Support.swift 3 | // Swift-AppleScriptObjC 4 | // 5 | 6 | import Cocoa 7 | 8 | 9 | 10 | @objc(NSObject) protocol iTunesBridge { 11 | 12 | // Important: ASOC does not bridge C primitives, only Cocoa classes and objects, 13 | // so Swift Bool/Int/Double values MUST be explicitly boxed/unboxed as NSNumber 14 | // when passing to/from AppleScript. 15 | 16 | var _isRunning: NSNumber { get } // Bool 17 | var _playerState: NSNumber { get } 18 | 19 | var trackInfo: [NSString:AnyObject]? { get } 20 | var trackDuration: NSNumber { get } 21 | 22 | var soundVolume: NSNumber { get set } 23 | 24 | func playPause() 25 | func gotoPreviousTrack() 26 | func gotoNextTrack() 27 | } 28 | 29 | 30 | extension iTunesBridge { // native Swift versions of the above ASOC APIs 31 | 32 | var isRunning: Bool { return self._isRunning.boolValue } 33 | 34 | var playerState: PlayerState { return PlayerState(rawValue: self._playerState as! Int)! } 35 | 36 | } 37 | 38 | 39 | @objc enum PlayerState: Int { // iTunes' 'player state' property 40 | case unknown // extra case e.g. iTunes is not running 41 | case stopped 42 | case playing 43 | case paused 44 | case fastForwarding 45 | case rewinding 46 | } 47 | 48 | --------------------------------------------------------------------------------