├── .gitignore ├── ExampleApp ├── Cheese.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ └── daniel.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── Cheese │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Truck1024.png │ │ ├── Truck128.png │ │ ├── Truck16.png │ │ ├── Truck256-1.png │ │ ├── Truck256.png │ │ ├── Truck32-1.png │ │ ├── Truck32.png │ │ ├── Truck512-1.png │ │ ├── Truck512.png │ │ └── Truck64.png │ └── Contents.json │ ├── Base.lproj │ ├── Localizable.strings │ └── MainMenu.xib │ ├── Cheese.entitlements │ ├── Info.plist │ └── en.lproj │ └── MainMenu.strings ├── LICENSE ├── Package.swift ├── README.md └── RSAppMovementMonitor.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | -------------------------------------------------------------------------------- /ExampleApp/Cheese.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 65ED9A352319777800EE30A9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 65ED9A332319777800EE30A9 /* Localizable.strings */; }; 11 | 65F541EF231764F000637BC4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F541EE231764F000637BC4 /* AppDelegate.swift */; }; 12 | 65F541F1231764F300637BC4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65F541F0231764F300637BC4 /* Assets.xcassets */; }; 13 | 65F541F4231764F300637BC4 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 65F541F2231764F300637BC4 /* MainMenu.xib */; }; 14 | 65F541FD2317650B00637BC4 /* RSAppMovementMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F541FC2317650B00637BC4 /* RSAppMovementMonitor.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 65ED9A342319777800EE30A9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 19 | 65F541EB231764F000637BC4 /* Cheese.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cheese.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 65F541EE231764F000637BC4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 65F541F0231764F300637BC4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 22 | 65F541F3231764F300637BC4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 23 | 65F541F5231764F300637BC4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 24 | 65F541F6231764F300637BC4 /* Cheese.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Cheese.entitlements; sourceTree = ""; }; 25 | 65F541FC2317650B00637BC4 /* RSAppMovementMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RSAppMovementMonitor.swift; path = ../../RSAppMovementMonitor.swift; sourceTree = ""; }; 26 | /* End PBXFileReference section */ 27 | 28 | /* Begin PBXFrameworksBuildPhase section */ 29 | 65F541E8231764F000637BC4 /* Frameworks */ = { 30 | isa = PBXFrameworksBuildPhase; 31 | buildActionMask = 2147483647; 32 | files = ( 33 | ); 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXFrameworksBuildPhase section */ 37 | 38 | /* Begin PBXGroup section */ 39 | 65F541E2231764F000637BC4 = { 40 | isa = PBXGroup; 41 | children = ( 42 | 65F541ED231764F000637BC4 /* Cheese */, 43 | 65F541EC231764F000637BC4 /* Products */, 44 | ); 45 | sourceTree = ""; 46 | }; 47 | 65F541EC231764F000637BC4 /* Products */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | 65F541EB231764F000637BC4 /* Cheese.app */, 51 | ); 52 | name = Products; 53 | sourceTree = ""; 54 | }; 55 | 65F541ED231764F000637BC4 /* Cheese */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 65F541EE231764F000637BC4 /* AppDelegate.swift */, 59 | 65F541FC2317650B00637BC4 /* RSAppMovementMonitor.swift */, 60 | 65F541F0231764F300637BC4 /* Assets.xcassets */, 61 | 65F541F2231764F300637BC4 /* MainMenu.xib */, 62 | 65ED9A332319777800EE30A9 /* Localizable.strings */, 63 | 65F541F5231764F300637BC4 /* Info.plist */, 64 | 65F541F6231764F300637BC4 /* Cheese.entitlements */, 65 | ); 66 | path = Cheese; 67 | sourceTree = ""; 68 | }; 69 | /* End PBXGroup section */ 70 | 71 | /* Begin PBXNativeTarget section */ 72 | 65F541EA231764F000637BC4 /* Cheese */ = { 73 | isa = PBXNativeTarget; 74 | buildConfigurationList = 65F541F9231764F300637BC4 /* Build configuration list for PBXNativeTarget "Cheese" */; 75 | buildPhases = ( 76 | 65F541E7231764F000637BC4 /* Sources */, 77 | 65F541E8231764F000637BC4 /* Frameworks */, 78 | 65F541E9231764F000637BC4 /* Resources */, 79 | ); 80 | buildRules = ( 81 | ); 82 | dependencies = ( 83 | ); 84 | name = Cheese; 85 | productName = Cheese; 86 | productReference = 65F541EB231764F000637BC4 /* Cheese.app */; 87 | productType = "com.apple.product-type.application"; 88 | }; 89 | /* End PBXNativeTarget section */ 90 | 91 | /* Begin PBXProject section */ 92 | 65F541E3231764F000637BC4 /* Project object */ = { 93 | isa = PBXProject; 94 | attributes = { 95 | LastSwiftUpdateCheck = 1100; 96 | LastUpgradeCheck = 1100; 97 | ORGANIZATIONNAME = "Red Sweater Software"; 98 | TargetAttributes = { 99 | 65F541EA231764F000637BC4 = { 100 | CreatedOnToolsVersion = 11.0; 101 | }; 102 | }; 103 | }; 104 | buildConfigurationList = 65F541E6231764F000637BC4 /* Build configuration list for PBXProject "WhoMovedMyCheese" */; 105 | compatibilityVersion = "Xcode 9.3"; 106 | developmentRegion = en; 107 | hasScannedForEncodings = 0; 108 | knownRegions = ( 109 | en, 110 | Base, 111 | ); 112 | mainGroup = 65F541E2231764F000637BC4; 113 | productRefGroup = 65F541EC231764F000637BC4 /* Products */; 114 | projectDirPath = ""; 115 | projectRoot = ""; 116 | targets = ( 117 | 65F541EA231764F000637BC4 /* Cheese */, 118 | ); 119 | }; 120 | /* End PBXProject section */ 121 | 122 | /* Begin PBXResourcesBuildPhase section */ 123 | 65F541E9231764F000637BC4 /* Resources */ = { 124 | isa = PBXResourcesBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | 65F541F1231764F300637BC4 /* Assets.xcassets in Resources */, 128 | 65ED9A352319777800EE30A9 /* Localizable.strings in Resources */, 129 | 65F541F4231764F300637BC4 /* MainMenu.xib in Resources */, 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | /* End PBXResourcesBuildPhase section */ 134 | 135 | /* Begin PBXSourcesBuildPhase section */ 136 | 65F541E7231764F000637BC4 /* Sources */ = { 137 | isa = PBXSourcesBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | 65F541EF231764F000637BC4 /* AppDelegate.swift in Sources */, 141 | 65F541FD2317650B00637BC4 /* RSAppMovementMonitor.swift in Sources */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | /* End PBXSourcesBuildPhase section */ 146 | 147 | /* Begin PBXVariantGroup section */ 148 | 65ED9A332319777800EE30A9 /* Localizable.strings */ = { 149 | isa = PBXVariantGroup; 150 | children = ( 151 | 65ED9A342319777800EE30A9 /* Base */, 152 | ); 153 | name = Localizable.strings; 154 | sourceTree = ""; 155 | }; 156 | 65F541F2231764F300637BC4 /* MainMenu.xib */ = { 157 | isa = PBXVariantGroup; 158 | children = ( 159 | 65F541F3231764F300637BC4 /* Base */, 160 | ); 161 | name = MainMenu.xib; 162 | sourceTree = ""; 163 | }; 164 | /* End PBXVariantGroup section */ 165 | 166 | /* Begin XCBuildConfiguration section */ 167 | 65F541F7231764F300637BC4 /* Debug */ = { 168 | isa = XCBuildConfiguration; 169 | buildSettings = { 170 | ALWAYS_SEARCH_USER_PATHS = NO; 171 | CLANG_ANALYZER_NONNULL = YES; 172 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 173 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 174 | CLANG_CXX_LIBRARY = "libc++"; 175 | CLANG_ENABLE_MODULES = YES; 176 | CLANG_ENABLE_OBJC_ARC = YES; 177 | CLANG_ENABLE_OBJC_WEAK = YES; 178 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 179 | CLANG_WARN_BOOL_CONVERSION = YES; 180 | CLANG_WARN_COMMA = YES; 181 | CLANG_WARN_CONSTANT_CONVERSION = YES; 182 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 183 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 184 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 185 | CLANG_WARN_EMPTY_BODY = YES; 186 | CLANG_WARN_ENUM_CONVERSION = YES; 187 | CLANG_WARN_INFINITE_RECURSION = YES; 188 | CLANG_WARN_INT_CONVERSION = YES; 189 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 190 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 191 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 192 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 193 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 194 | CLANG_WARN_STRICT_PROTOTYPES = YES; 195 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 196 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 197 | CLANG_WARN_UNREACHABLE_CODE = YES; 198 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 199 | COPY_PHASE_STRIP = NO; 200 | DEBUG_INFORMATION_FORMAT = dwarf; 201 | ENABLE_STRICT_OBJC_MSGSEND = YES; 202 | ENABLE_TESTABILITY = YES; 203 | GCC_C_LANGUAGE_STANDARD = gnu11; 204 | GCC_DYNAMIC_NO_PIC = NO; 205 | GCC_NO_COMMON_BLOCKS = YES; 206 | GCC_OPTIMIZATION_LEVEL = 0; 207 | GCC_PREPROCESSOR_DEFINITIONS = ( 208 | "DEBUG=1", 209 | "$(inherited)", 210 | ); 211 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 212 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 213 | GCC_WARN_UNDECLARED_SELECTOR = YES; 214 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 215 | GCC_WARN_UNUSED_FUNCTION = YES; 216 | GCC_WARN_UNUSED_VARIABLE = YES; 217 | MACOSX_DEPLOYMENT_TARGET = 10.15; 218 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 219 | MTL_FAST_MATH = YES; 220 | ONLY_ACTIVE_ARCH = YES; 221 | SDKROOT = macosx; 222 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 223 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 224 | }; 225 | name = Debug; 226 | }; 227 | 65F541F8231764F300637BC4 /* Release */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | ALWAYS_SEARCH_USER_PATHS = NO; 231 | CLANG_ANALYZER_NONNULL = YES; 232 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 234 | CLANG_CXX_LIBRARY = "libc++"; 235 | CLANG_ENABLE_MODULES = YES; 236 | CLANG_ENABLE_OBJC_ARC = YES; 237 | CLANG_ENABLE_OBJC_WEAK = YES; 238 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_COMMA = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 243 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 244 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 245 | CLANG_WARN_EMPTY_BODY = YES; 246 | CLANG_WARN_ENUM_CONVERSION = YES; 247 | CLANG_WARN_INFINITE_RECURSION = YES; 248 | CLANG_WARN_INT_CONVERSION = YES; 249 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 251 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 253 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 254 | CLANG_WARN_STRICT_PROTOTYPES = YES; 255 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 256 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 257 | CLANG_WARN_UNREACHABLE_CODE = YES; 258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 259 | COPY_PHASE_STRIP = NO; 260 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 261 | ENABLE_NS_ASSERTIONS = NO; 262 | ENABLE_STRICT_OBJC_MSGSEND = YES; 263 | GCC_C_LANGUAGE_STANDARD = gnu11; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 266 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 267 | GCC_WARN_UNDECLARED_SELECTOR = YES; 268 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 269 | GCC_WARN_UNUSED_FUNCTION = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | MACOSX_DEPLOYMENT_TARGET = 10.15; 272 | MTL_ENABLE_DEBUG_INFO = NO; 273 | MTL_FAST_MATH = YES; 274 | SDKROOT = macosx; 275 | SWIFT_COMPILATION_MODE = wholemodule; 276 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 277 | }; 278 | name = Release; 279 | }; 280 | 65F541FA231764F300637BC4 /* Debug */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 284 | CODE_SIGN_ENTITLEMENTS = Cheese/Cheese.entitlements; 285 | CODE_SIGN_IDENTITY = "-"; 286 | CODE_SIGN_STYLE = Manual; 287 | COMBINE_HIDPI_IMAGES = YES; 288 | DEVELOPMENT_TEAM = ""; 289 | ENABLE_HARDENED_RUNTIME = NO; 290 | INFOPLIST_FILE = Cheese/Info.plist; 291 | LD_RUNPATH_SEARCH_PATHS = ( 292 | "$(inherited)", 293 | "@executable_path/../Frameworks", 294 | ); 295 | MACOSX_DEPLOYMENT_TARGET = 10.9; 296 | PRODUCT_BUNDLE_IDENTIFIER = "com.red-sweater.Cheese"; 297 | PRODUCT_NAME = "$(TARGET_NAME)"; 298 | PROVISIONING_PROFILE_SPECIFIER = ""; 299 | SWIFT_VERSION = 5.0; 300 | }; 301 | name = Debug; 302 | }; 303 | 65F541FB231764F300637BC4 /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | CODE_SIGN_ENTITLEMENTS = Cheese/Cheese.entitlements; 308 | CODE_SIGN_IDENTITY = "-"; 309 | CODE_SIGN_STYLE = Manual; 310 | COMBINE_HIDPI_IMAGES = YES; 311 | DEVELOPMENT_TEAM = ""; 312 | ENABLE_HARDENED_RUNTIME = NO; 313 | INFOPLIST_FILE = Cheese/Info.plist; 314 | LD_RUNPATH_SEARCH_PATHS = ( 315 | "$(inherited)", 316 | "@executable_path/../Frameworks", 317 | ); 318 | MACOSX_DEPLOYMENT_TARGET = 10.9; 319 | PRODUCT_BUNDLE_IDENTIFIER = "com.red-sweater.Cheese"; 320 | PRODUCT_NAME = "$(TARGET_NAME)"; 321 | PROVISIONING_PROFILE_SPECIFIER = ""; 322 | SWIFT_VERSION = 5.0; 323 | }; 324 | name = Release; 325 | }; 326 | /* End XCBuildConfiguration section */ 327 | 328 | /* Begin XCConfigurationList section */ 329 | 65F541E6231764F000637BC4 /* Build configuration list for PBXProject "WhoMovedMyCheese" */ = { 330 | isa = XCConfigurationList; 331 | buildConfigurations = ( 332 | 65F541F7231764F300637BC4 /* Debug */, 333 | 65F541F8231764F300637BC4 /* Release */, 334 | ); 335 | defaultConfigurationIsVisible = 0; 336 | defaultConfigurationName = Release; 337 | }; 338 | 65F541F9231764F300637BC4 /* Build configuration list for PBXNativeTarget "Cheese" */ = { 339 | isa = XCConfigurationList; 340 | buildConfigurations = ( 341 | 65F541FA231764F300637BC4 /* Debug */, 342 | 65F541FB231764F300637BC4 /* Release */, 343 | ); 344 | defaultConfigurationIsVisible = 0; 345 | defaultConfigurationName = Release; 346 | }; 347 | /* End XCConfigurationList section */ 348 | }; 349 | rootObject = 65F541E3231764F000637BC4 /* Project object */; 350 | } 351 | -------------------------------------------------------------------------------- /ExampleApp/Cheese.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ExampleApp/Cheese.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ExampleApp/Cheese.xcodeproj/project.xcworkspace/xcuserdata/daniel.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese.xcodeproj/project.xcworkspace/xcuserdata/daniel.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ExampleApp/Cheese/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Cheese 4 | // 5 | // Created by Daniel Jalkut on 8/28/19. 6 | // Copyright © 2019 Red Sweater Software. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | 16 | var movementMonitor: RSAppMovementMonitor? = nil 17 | 18 | func applicationDidFinishLaunching(_ aNotification: Notification) { 19 | 20 | // Simply create a movement handler early in your app's lifetime 21 | movementMonitor = RSAppMovementMonitor() 22 | 23 | // If you just want to let the default handler do everything, don't even 24 | // bother assigning this custom handler. If you want to change the behavior, 25 | // for example to support an alert option to keep running without relaunching, 26 | // you could provide that functionality here. 27 | movementMonitor?.appMovementHandler = { monitor in 28 | NSLog("App was moved! Letting RSAppMovementMonitor handle it...") 29 | return true 30 | } 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "Truck16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "Truck32-1.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "Truck32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "Truck64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "Truck128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "Truck256-1.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "Truck256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "Truck512-1.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "Truck512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "Truck1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck1024.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck128.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck16.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck256-1.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck256.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck32-1.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck32.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck512-1.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck512.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redsweater/RSAppMovementMonitor/4da02cca762c9fc061429fe62d99e1307623bf1d/ExampleApp/Cheese/Assets.xcassets/AppIcon.appiconset/Truck64.png -------------------------------------------------------------------------------- /ExampleApp/Cheese/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ExampleApp/Cheese/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "\(appName) was moved or renamed while open." = "\(appName) was zoomed or renamed while open."; 2 | -------------------------------------------------------------------------------- /ExampleApp/Cheese/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 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | Default 540 | 541 | 542 | 543 | 544 | 545 | 546 | Left to Right 547 | 548 | 549 | 550 | 551 | 552 | 553 | Right to Left 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | Default 565 | 566 | 567 | 568 | 569 | 570 | 571 | Left to Right 572 | 573 | 574 | 575 | 576 | 577 | 578 | Right to Left 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 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | -------------------------------------------------------------------------------- /ExampleApp/Cheese/Cheese.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ExampleApp/Cheese/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2019 Daniel Jalkut. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | NSSupportsAutomaticTermination 32 | 33 | NSSupportsSuddenTermination 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /ExampleApp/Cheese/en.lproj/MainMenu.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ 3 | "1UK-8n-QPP.title" = "Customize Toolbar…"; 4 | 5 | /* Class = "NSMenuItem"; title = "Cheese"; ObjectID = "1Xt-HY-uBw"; */ 6 | "1Xt-HY-uBw.title" = "Cheese"; 7 | 8 | /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ 9 | "1b7-l0-nxx.title" = "Find"; 10 | 11 | /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ 12 | "1tx-W0-xDw.title" = "Lower"; 13 | 14 | /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ 15 | "2h7-ER-AoG.title" = "Raise"; 16 | 17 | /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ 18 | "2oI-Rn-ZJC.title" = "Transformations"; 19 | 20 | /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ 21 | "3IN-sU-3Bg.title" = "Spelling"; 22 | 23 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ 24 | "3Om-Ey-2VK.title" = "Use Default"; 25 | 26 | /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ 27 | "3rS-ZA-NoH.title" = "Speech"; 28 | 29 | /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ 30 | "46P-cB-AYj.title" = "Tighten"; 31 | 32 | /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ 33 | "4EN-yA-p0u.title" = "Find"; 34 | 35 | /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ 36 | "4J7-dP-txa.title" = "Enter Full Screen"; 37 | 38 | /* Class = "NSMenuItem"; title = "Quit Cheese"; ObjectID = "4sb-4s-VLi"; */ 39 | "4sb-4s-VLi.title" = "Quit Cheese"; 40 | 41 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ 42 | "5QF-Oa-p0T.title" = "Edit"; 43 | 44 | /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ 45 | "5Vv-lz-BsD.title" = "Copy Style"; 46 | 47 | /* Class = "NSMenuItem"; title = "About Cheese"; ObjectID = "5kV-Vb-QxS"; */ 48 | "5kV-Vb-QxS.title" = "About Cheese"; 49 | 50 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ 51 | "6dh-zS-Vam.title" = "Redo"; 52 | 53 | /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ 54 | "78Y-hA-62v.title" = "Correct Spelling Automatically"; 55 | 56 | /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ 57 | "8mr-sm-Yjd.title" = "Writing Direction"; 58 | 59 | /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ 60 | "9ic-FL-obx.title" = "Substitutions"; 61 | 62 | /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ 63 | "9yt-4B-nSM.title" = "Smart Copy/Paste"; 64 | 65 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ 66 | "AYu-sK-qS6.title" = "Main Menu"; 67 | 68 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ 69 | "BOF-NM-1cW.title" = "Preferences…"; 70 | 71 | /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ 72 | "BgM-ve-c93.title" = "\tLeft to Right"; 73 | 74 | /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ 75 | "Bw7-FT-i3A.title" = "Save As…"; 76 | 77 | /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ 78 | "DVo-aG-piG.title" = "Close"; 79 | 80 | /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ 81 | "Dv1-io-Yv7.title" = "Spelling and Grammar"; 82 | 83 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ 84 | "F2S-fz-NVQ.title" = "Help"; 85 | 86 | /* Class = "NSMenuItem"; title = "Cheese Help"; ObjectID = "FKE-Sm-Kum"; */ 87 | "FKE-Sm-Kum.title" = "Cheese Help"; 88 | 89 | /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ 90 | "Fal-I4-PZk.title" = "Text"; 91 | 92 | /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ 93 | "FeM-D8-WVr.title" = "Substitutions"; 94 | 95 | /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ 96 | "GB9-OM-e27.title" = "Bold"; 97 | 98 | /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ 99 | "GEO-Iw-cKr.title" = "Format"; 100 | 101 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ 102 | "GUa-eO-cwY.title" = "Use Default"; 103 | 104 | /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ 105 | "Gi5-1S-RQB.title" = "Font"; 106 | 107 | /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ 108 | "H1b-Si-o9J.title" = "Writing Direction"; 109 | 110 | /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ 111 | "H8h-7b-M4v.title" = "View"; 112 | 113 | /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ 114 | "HFQ-gK-NFA.title" = "Text Replacement"; 115 | 116 | /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ 117 | "HFo-cy-zxI.title" = "Show Spelling and Grammar"; 118 | 119 | /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ 120 | "HyV-fh-RgO.title" = "View"; 121 | 122 | /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ 123 | "I0S-gh-46l.title" = "Subscript"; 124 | 125 | /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ 126 | "IAo-SY-fd9.title" = "Open…"; 127 | 128 | /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ 129 | "J5U-5w-g23.title" = "Justify"; 130 | 131 | /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ 132 | "J7y-lM-qPV.title" = "Use None"; 133 | 134 | /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ 135 | "KaW-ft-85H.title" = "Revert to Saved"; 136 | 137 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ 138 | "Kd2-mp-pUS.title" = "Show All"; 139 | 140 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ 141 | "LE2-aR-0XJ.title" = "Bring All to Front"; 142 | 143 | /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ 144 | "LVM-kO-fVI.title" = "Paste Ruler"; 145 | 146 | /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ 147 | "Lbh-J2-qVU.title" = "\tLeft to Right"; 148 | 149 | /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ 150 | "MkV-Pr-PK5.title" = "Copy Ruler"; 151 | 152 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ 153 | "NMo-om-nkz.title" = "Services"; 154 | 155 | /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ 156 | "Nop-cj-93Q.title" = "\tDefault"; 157 | 158 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ 159 | "OY7-WF-poV.title" = "Minimize"; 160 | 161 | /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ 162 | "OaQ-X3-Vso.title" = "Baseline"; 163 | 164 | /* Class = "NSMenuItem"; title = "Hide Cheese"; ObjectID = "Olw-nP-bQN"; */ 165 | "Olw-nP-bQN.title" = "Hide Cheese"; 166 | 167 | /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ 168 | "OwM-mh-QMV.title" = "Find Previous"; 169 | 170 | /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ 171 | "Oyz-dy-DGm.title" = "Stop Speaking"; 172 | 173 | /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ 174 | "Ptp-SP-VEL.title" = "Bigger"; 175 | 176 | /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ 177 | "Q5e-8K-NDq.title" = "Show Fonts"; 178 | 179 | /* Class = "NSWindow"; title = "Cheese"; ObjectID = "QvC-M9-y7g"; */ 180 | "QvC-M9-y7g.title" = "Cheese"; 181 | 182 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ 183 | "R4o-n2-Eq4.title" = "Zoom"; 184 | 185 | /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ 186 | "RB4-Sm-HuC.title" = "\tRight to Left"; 187 | 188 | /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ 189 | "Rqc-34-cIF.title" = "Superscript"; 190 | 191 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ 192 | "Ruw-6m-B2m.title" = "Select All"; 193 | 194 | /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ 195 | "S0p-oC-mLd.title" = "Jump to Selection"; 196 | 197 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ 198 | "Td7-aD-5lo.title" = "Window"; 199 | 200 | /* Class = "NSTextFieldCell"; title = "Move, rename, or delete this app and see what happens..."; ObjectID = "UCw-x2-oFL"; */ 201 | "UCw-x2-oFL.title" = "Move, rename, or delete this app and see what happens..."; 202 | 203 | /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ 204 | "UEZ-Bs-lqG.title" = "Capitalize"; 205 | 206 | /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ 207 | "VIY-Ag-zcb.title" = "Center"; 208 | 209 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ 210 | "Vdr-fp-XzO.title" = "Hide Others"; 211 | 212 | /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ 213 | "Vjx-xi-njq.title" = "Italic"; 214 | 215 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ 216 | "W48-6f-4Dl.title" = "Edit"; 217 | 218 | /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ 219 | "WRG-CD-K1S.title" = "Underline"; 220 | 221 | /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ 222 | "Was-JA-tGl.title" = "New"; 223 | 224 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ 225 | "WeT-3V-zwk.title" = "Paste and Match Style"; 226 | 227 | /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ 228 | "Xz5-n4-O0W.title" = "Find…"; 229 | 230 | /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ 231 | "YEy-JH-Tfz.title" = "Find and Replace…"; 232 | 233 | /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ 234 | "YGs-j5-SAR.title" = "\tDefault"; 235 | 236 | /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ 237 | "Ynk-f8-cLZ.title" = "Start Speaking"; 238 | 239 | /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ 240 | "ZM1-6Q-yy1.title" = "Align Left"; 241 | 242 | /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ 243 | "ZvO-Gk-QUH.title" = "Paragraph"; 244 | 245 | /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ 246 | "aTl-1u-JFS.title" = "Print…"; 247 | 248 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ 249 | "aUF-d1-5bR.title" = "Window"; 250 | 251 | /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ 252 | "aXa-aM-Jaq.title" = "Font"; 253 | 254 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ 255 | "agt-UL-0e3.title" = "Use Default"; 256 | 257 | /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ 258 | "bgn-CT-cEk.title" = "Show Colors"; 259 | 260 | /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ 261 | "bib-Uj-vzu.title" = "File"; 262 | 263 | /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ 264 | "buJ-ug-pKt.title" = "Use Selection for Find"; 265 | 266 | /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ 267 | "c8a-y6-VQd.title" = "Transformations"; 268 | 269 | /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ 270 | "cDB-IK-hbR.title" = "Use None"; 271 | 272 | /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ 273 | "cqv-fj-IhA.title" = "Selection"; 274 | 275 | /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ 276 | "cwL-P1-jid.title" = "Smart Links"; 277 | 278 | /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ 279 | "d9M-CD-aMd.title" = "Make Lower Case"; 280 | 281 | /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ 282 | "d9c-me-L2H.title" = "Text"; 283 | 284 | /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ 285 | "dMs-cI-mzQ.title" = "File"; 286 | 287 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ 288 | "dRJ-4n-Yzg.title" = "Undo"; 289 | 290 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ 291 | "gVA-U4-sdL.title" = "Paste"; 292 | 293 | /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ 294 | "hQb-2v-fYv.title" = "Smart Quotes"; 295 | 296 | /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ 297 | "hz2-CU-CR7.title" = "Check Document Now"; 298 | 299 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ 300 | "hz9-B4-Xy5.title" = "Services"; 301 | 302 | /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ 303 | "i1d-Er-qST.title" = "Smaller"; 304 | 305 | /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ 306 | "ijk-EB-dga.title" = "Baseline"; 307 | 308 | /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ 309 | "jBQ-r6-VK2.title" = "Kern"; 310 | 311 | /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ 312 | "jFq-tB-4Kx.title" = "\tRight to Left"; 313 | 314 | /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ 315 | "jxT-CU-nIS.title" = "Format"; 316 | 317 | /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ 318 | "kIP-vf-haE.title" = "Show Sidebar"; 319 | 320 | /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ 321 | "mK6-2p-4JG.title" = "Check Grammar With Spelling"; 322 | 323 | /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ 324 | "o6e-r0-MWq.title" = "Ligatures"; 325 | 326 | /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ 327 | "oas-Oc-fiZ.title" = "Open Recent"; 328 | 329 | /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ 330 | "ogc-rX-tC1.title" = "Loosen"; 331 | 332 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ 333 | "pa3-QI-u2k.title" = "Delete"; 334 | 335 | /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ 336 | "pxx-59-PXV.title" = "Save…"; 337 | 338 | /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ 339 | "q09-fT-Sye.title" = "Find Next"; 340 | 341 | /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ 342 | "qIS-W8-SiK.title" = "Page Setup…"; 343 | 344 | /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ 345 | "rbD-Rh-wIN.title" = "Check Spelling While Typing"; 346 | 347 | /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ 348 | "rgM-f4-ycn.title" = "Smart Dashes"; 349 | 350 | /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ 351 | "snW-S8-Cw5.title" = "Show Toolbar"; 352 | 353 | /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ 354 | "tRr-pd-1PS.title" = "Data Detectors"; 355 | 356 | /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ 357 | "tXI-mr-wws.title" = "Open Recent"; 358 | 359 | /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ 360 | "tlD-Oa-oAM.title" = "Kern"; 361 | 362 | /* Class = "NSMenu"; title = "Cheese"; ObjectID = "uQy-DD-JDr"; */ 363 | "uQy-DD-JDr.title" = "Cheese"; 364 | 365 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ 366 | "uRl-iY-unG.title" = "Cut"; 367 | 368 | /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ 369 | "vKC-jM-MkH.title" = "Paste Style"; 370 | 371 | /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ 372 | "vLm-3I-IUL.title" = "Show Ruler"; 373 | 374 | /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ 375 | "vNY-rz-j42.title" = "Clear Menu"; 376 | 377 | /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ 378 | "vmV-6d-7jI.title" = "Make Upper Case"; 379 | 380 | /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ 381 | "w0m-vy-SC9.title" = "Ligatures"; 382 | 383 | /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ 384 | "wb2-vD-lq4.title" = "Align Right"; 385 | 386 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ 387 | "wpr-3q-Mcd.title" = "Help"; 388 | 389 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ 390 | "x3v-GG-iWU.title" = "Copy"; 391 | 392 | /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ 393 | "xQD-1f-W4t.title" = "Use All"; 394 | 395 | /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ 396 | "xrE-MZ-jX0.title" = "Speech"; 397 | 398 | /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ 399 | "z6F-FW-3nz.title" = "Show Substitutions"; 400 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Daniel Jalkut, Red Sweater Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "RSAppMovementMonitor", 7 | platforms: [.macOS("10.12")], 8 | products: [ 9 | .library(name: "RSAppMovementMonitor", targets: ["RSAppMovementMonitor"]), 10 | ], 11 | dependencies: [], 12 | targets: [ 13 | .target(name: "RSAppMovementMonitor", dependencies: [], path: "."), 14 | ] 15 | ) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSAppMovementMonitor 2 | Gracefully handle user movement of a running Mac app. Read more on the [Red Sweater Blog](https://red-sweater.com/blog/3508/app-movement-monitoring). 3 | 4 | ## Why? 5 | 6 | The modular design of Mac applications postpones the loading of resources until such time as they are needed. If after launching your app, a user moves or renames the app on disk, then future resource loads will fail because the bundle is still considered by app frameworks to exist at the old location. 7 | 8 | ## What? 9 | 10 | One way to address this problem is by monitoring the location of the app bundle from within the app itself. When movement is detected, prompt the user to explain that they must relaunch the app, and then do it for them. 11 | 12 | ## How? 13 | 14 | RSAppMovementMonitor handles the nuanced details of monitoring for the movement, prompting the user, and relaunching the app: 15 | 16 | 1. Copy [RSAppMovementMonitor.swift](RSAppMovementMonitor.swift) to your source base. 17 | 2. Instantiate the class early in your app's lifetime: 18 | 19 | ```swift 20 | var movementMonitor: RSAppMovementMonitor? = nil 21 | 22 | func applicationDidFinishLaunching(_ aNotification: Notification) { 23 | movementMonitor = RSAppMovementMonitor() 24 | } 25 | ``` 26 | 3. There's no step 3. 27 | 28 | ## No Step 3? 29 | 30 | The default functionality demands that a user unconditionally relaunch the app. This works well for most apps that take advantage of Apple's autosave technologies, such that quitting and reopening the app will not result in data loss. 31 | 32 | If your app needs to customize the behavior, for example to only give the user a stern warning but leave them the option to keep running the app, you can provide a custom appMovementHandler which will be called when the app is moved. 33 | 34 | ## What else? 35 | 36 | Open and build the example app, "Cheese", to see an example of how to integrate the functionality, and how to provide a custom appMovementHandler. 37 | -------------------------------------------------------------------------------- /RSAppMovementMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSAppMovementMonitor.swift 3 | // 4 | // https:://github.com/RedSweater/RSAppMovementMonitor 5 | // 6 | // Created by Daniel Jalkut on 8/28/19. 7 | // Copyright © 2019 Red Sweater Software. All rights reserved. 8 | // 9 | 10 | import Cocoa 11 | 12 | public class RSAppMovementMonitor: NSObject { 13 | 14 | // If provided, the handler will be consulted when the app is moved. 15 | // Return true to indicate that the default handler should be invoked. 16 | public var appMovementHandler: ((RSAppMovementMonitor) -> Bool)? = nil 17 | 18 | // DispatchSource offers a monitoring mechanism based on an open file descriptor 19 | var fileDescriptor: Int32 = -1 20 | var dispatchSource: DispatchSourceFileSystemObject? = nil 21 | 22 | // Save the original location of the app in a file reference URL, which will track its new location. 23 | // Note this is NSURL, not URL, because file reference URLs violate value-type assumptions of URL. 24 | // Casting shenanigans here are required to avoid the NSURL ever bridging to URL, and losing its 25 | // "magical" fileReferenceURL status. 26 | // 27 | // See: https://christiantietze.de/posts/2018/09/nsurl-filereferenceurl-swift/ 28 | // 29 | let originalAppURL: URL? 30 | var appTrackingURL: NSURL? 31 | 32 | // We load these strings at launch time so that they can be localized. If we wait until 33 | // the application has been moved, the localization will fail. 34 | let alertMessageText: String 35 | let alertInformativeText: String 36 | let alertRelaunchButtonText: String 37 | 38 | // Same for the icon - if we wait until the app moves, it will show as generic 39 | let appIcon: NSImage 40 | 41 | override public init() { 42 | 43 | // Establish baseline URLs. Note that simply asking for Bundle.main.bundleURL will return 44 | // the translocated location of an app when it is launched in quarantine state. This leads 45 | // to a permanent false-positive detection that the app has moved. To work around this, we 46 | // ask for the fileReferenceURL's absoluteURL at launch time, and compare to the absoluteURL 47 | // later to detect bona fide user-driven app movement. 48 | self.appTrackingURL = (Bundle.main.bundleURL as NSURL).fileReferenceURL() as NSURL? 49 | self.originalAppURL = appTrackingURL?.absoluteURL 50 | 51 | let appName = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String ?? NSLocalizedString("This app", comment: "Backup name if the app name cannot be deduced from the bundle") 52 | let informativeTextTemplate = NSLocalizedString("%@ was moved or renamed while open.", comment: "Message text for app moved while running alert") 53 | self.alertMessageText = String(format: informativeTextTemplate, arguments: [appName]) 54 | self.alertInformativeText = NSLocalizedString("Moving an open application can cause unexpected behavior. Relaunch the application to continue.", comment: "Informative text for app moved while running alert") 55 | self.alertRelaunchButtonText = NSLocalizedString("Relaunch", comment: "Relaunch Button") 56 | self.appIcon = NSApp.applicationIconImage 57 | 58 | super.init() 59 | 60 | // Monitor for direct changes to the app bundle's folder - this will catch the 61 | // majority of direct manipulations to the app's location on disk immediately, 62 | // right as it happens. 63 | if let originalAppPath = originalAppURL?.path { 64 | self.fileDescriptor = open(originalAppPath, O_EVTONLY) 65 | if self.fileDescriptor != -1 { 66 | self.dispatchSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: self.fileDescriptor, eventMask: [.delete, .rename], queue: DispatchQueue.main) 67 | if let source = self.dispatchSource { 68 | source.setEventHandler { 69 | self.invokeEventHandler() 70 | } 71 | 72 | source.setCancelHandler { 73 | self.invalidate() 74 | } 75 | 76 | source.resume() 77 | } 78 | } 79 | 80 | // Also install a notification to re-check the location of the app on disk 81 | // every time the app becomes active. This catches a good number of edge-case 82 | // changes to the app bundle's path, such as when a containing folder or the 83 | // volume name changes. 84 | NotificationCenter.default.addObserver(forName: NSApplication.didBecomeActiveNotification, object: nil, queue: nil) { notification in 85 | // Removing observer in invalidate doesn't seem to prevent this getting called? Maybe 86 | // because it's on the same invocation of the runloop? 87 | if self.isValid() && self.originalAppURL != self.appTrackingURL?.absoluteURL { 88 | self.invokeEventHandler() 89 | } 90 | } 91 | } 92 | } 93 | 94 | deinit { 95 | self.invalidate() 96 | } 97 | 98 | func invokeEventHandler() { 99 | // Prevent re-entry when the app is activated while running handler 100 | self.invalidate() 101 | 102 | var useDefaultHandler = true 103 | if let customHandler = self.appMovementHandler { 104 | useDefaultHandler = customHandler(self) 105 | } 106 | 107 | if useDefaultHandler { 108 | self.defaultHandler() 109 | } 110 | } 111 | 112 | func isValid() -> Bool { 113 | return self.fileDescriptor != -1 114 | } 115 | 116 | func invalidate() { 117 | if let dispatchSource = self.dispatchSource { 118 | dispatchSource.cancel() 119 | self.dispatchSource = nil 120 | } 121 | 122 | if self.fileDescriptor != -1 { 123 | close(self.fileDescriptor) 124 | self.fileDescriptor = -1 125 | } 126 | 127 | NotificationCenter.default.removeObserver(self, name: NSApplication.didBecomeActiveNotification, object: nil) 128 | 129 | self.appMovementHandler = nil 130 | } 131 | 132 | func relaunchFromURL(_ appURL: URL) { 133 | // Relaunching is best achieved by requesting that the system launch the app 134 | // at the given URL with the "new instance" option to prevent it simply reactivating us. 135 | let _ = try? NSWorkspace.shared.launchApplication(at: appURL, options: .newInstance, configuration: [:]) 136 | NSApp.terminate(self) 137 | } 138 | 139 | func defaultHandler() { 140 | let quitAlert = NSAlert() 141 | quitAlert.alertStyle = .critical 142 | quitAlert.addButton(withTitle: self.alertRelaunchButtonText) 143 | 144 | quitAlert.messageText = self.alertMessageText 145 | quitAlert.informativeText = self.alertInformativeText 146 | quitAlert.icon = appIcon 147 | 148 | let modalResponse = quitAlert.runModal() 149 | if modalResponse == .alertFirstButtonReturn { 150 | self.invalidate() 151 | 152 | if let movedAppURL = self.appTrackingURL as URL? { 153 | self.relaunchFromURL(movedAppURL) 154 | } 155 | } 156 | } 157 | 158 | } 159 | --------------------------------------------------------------------------------