├── .github └── FUNDING.yml ├── .gitignore ├── Air Drop Assistant.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ ├── Air Drop Assistant.xcscheme │ │ └── com.ttinc.Air-Drop-Assistant.helper.xcscheme └── xcuserdata │ └── boberito.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Air Drop Assistant ├── ADA_PF_Helper_Script.sh ├── Air_Drop_Assistant.entitlements ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── ADA_1024.png │ │ ├── ADA_128x128.png │ │ ├── ADA_128x128@2x.png │ │ ├── ADA_16x16.png │ │ ├── ADA_16x16@2x.png │ │ ├── ADA_256x256.png │ │ ├── ADA_256x256@2x.png │ │ ├── ADA_32x32.png │ │ ├── ADA_32x32@2x.png │ │ ├── ADA_512x512.png │ │ └── Contents.json │ ├── Contents.json │ ├── Image.imageset │ │ └── Contents.json │ ├── menuicon.imageset │ │ ├── Contents.json │ │ └── menuicon.png │ └── menuicon_mono.imageset │ │ ├── Contents.json │ │ ├── menuicon_mono 1.png │ │ ├── menuicon_mono copy.png │ │ └── menuicon_mono.png ├── Base.lproj │ └── Main.storyboard ├── Logger.swift ├── PrefWatcher.swift ├── PreferenceObserver.swift ├── PreferencesViewController.swift ├── PreferencesWindow.swift ├── UpdateCheck.swift ├── ViewController.swift ├── com.ttinc.Air-Drop-Assistant.plist ├── menuicon.png └── menuicon_mono.png ├── Air-Drop-Assistant-Info.plist ├── LICENSE ├── README.md ├── adamenu.png ├── adaprefs.png └── icon.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: boberito 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | .env 10 | .env.* 11 | !.env.example 12 | -------------------------------------------------------------------------------- /Air Drop Assistant.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7318DA982C8F710D004F262A /* UpdateCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7318DA972C8F710D004F262A /* UpdateCheck.swift */; }; 11 | 732889762CBCABBB00234AA2 /* ADA_PF_Helper_Script.sh in CopyFiles */ = {isa = PBXBuildFile; fileRef = 735DA8122CB8C2E3000F9D71 /* ADA_PF_Helper_Script.sh */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 12 | 7328CDA52C8BA87B0042A517 /* com.ttinc.Air-Drop-Assistant.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7328CDA42C8BA87B0042A517 /* com.ttinc.Air-Drop-Assistant.plist */; }; 13 | 7328CDA72C8BA9160042A517 /* com.ttinc.Air-Drop-Assistant.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7328CDA42C8BA87B0042A517 /* com.ttinc.Air-Drop-Assistant.plist */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 14 | 736C80F92C70302200E5E780 /* PrefWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736C80F82C70302200E5E780 /* PrefWatcher.swift */; }; 15 | 736C80FE2C70EC6800E5E780 /* PreferencesWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736C80FD2C70EC6800E5E780 /* PreferencesWindow.swift */; }; 16 | 736C81002C70EC9E00E5E780 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736C80FF2C70EC9E00E5E780 /* PreferencesViewController.swift */; }; 17 | 73B614B92CF77F5500C3A14F /* PreferenceObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73B614B82CF77F5500C3A14F /* PreferenceObserver.swift */; }; 18 | 73B6ABB62DDC16150028024F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73B6ABB52DDC16150028024F /* Logger.swift */; }; 19 | 73FFDCFB2C70210B00244D57 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73FFDCFA2C70210B00244D57 /* AppDelegate.swift */; }; 20 | 73FFDCFD2C70210B00244D57 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73FFDCFC2C70210B00244D57 /* ViewController.swift */; }; 21 | 73FFDCFF2C70210C00244D57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 73FFDCFE2C70210C00244D57 /* Assets.xcassets */; }; 22 | 73FFDD022C70210C00244D57 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 73FFDD012C70210C00244D57 /* Base */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXCopyFilesBuildPhase section */ 26 | 732889752CBCAB9700234AA2 /* CopyFiles */ = { 27 | isa = PBXCopyFilesBuildPhase; 28 | buildActionMask = 12; 29 | dstPath = "Air Drop Assistant.app/Contents/Resources"; 30 | dstSubfolderSpec = 16; 31 | files = ( 32 | 732889762CBCABBB00234AA2 /* ADA_PF_Helper_Script.sh in CopyFiles */, 33 | ); 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | 7328CDA62C8BA8FD0042A517 /* CopyFiles */ = { 37 | isa = PBXCopyFilesBuildPhase; 38 | buildActionMask = 2147483647; 39 | dstPath = "Air Drop Assistant.app/Contents/Library/LaunchAgents"; 40 | dstSubfolderSpec = 16; 41 | files = ( 42 | 7328CDA72C8BA9160042A517 /* com.ttinc.Air-Drop-Assistant.plist in CopyFiles */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXCopyFilesBuildPhase section */ 47 | 48 | /* Begin PBXFileReference section */ 49 | 7318DA972C8F710D004F262A /* UpdateCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCheck.swift; sourceTree = ""; }; 50 | 7328CDA42C8BA87B0042A517 /* com.ttinc.Air-Drop-Assistant.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "com.ttinc.Air-Drop-Assistant.plist"; sourceTree = ""; }; 51 | 735DA8122CB8C2E3000F9D71 /* ADA_PF_Helper_Script.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ADA_PF_Helper_Script.sh; sourceTree = ""; }; 52 | 736C80F82C70302200E5E780 /* PrefWatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefWatcher.swift; sourceTree = ""; }; 53 | 736C80FD2C70EC6800E5E780 /* PreferencesWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindow.swift; sourceTree = ""; }; 54 | 736C80FF2C70EC9E00E5E780 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; }; 55 | 737B8A342C8BE5D1006A4653 /* Air-Drop-Assistant-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Air-Drop-Assistant-Info.plist"; sourceTree = SOURCE_ROOT; }; 56 | 73B614B82CF77F5500C3A14F /* PreferenceObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceObserver.swift; sourceTree = ""; }; 57 | 73B6ABB52DDC16150028024F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 58 | 73FFDCF72C70210B00244D57 /* Air Drop Assistant.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Air Drop Assistant.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 73FFDCFA2C70210B00244D57 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 60 | 73FFDCFC2C70210B00244D57 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 61 | 73FFDCFE2C70210C00244D57 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | 73FFDD012C70210C00244D57 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 63 | 73FFDD032C70210C00244D57 /* Air_Drop_Assistant.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Air_Drop_Assistant.entitlements; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | 73FFDCF42C70210B00244D57 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 73FFDCEE2C70210B00244D57 = { 78 | isa = PBXGroup; 79 | children = ( 80 | 73FFDCF92C70210B00244D57 /* Air Drop Assistant */, 81 | 73FFDCF82C70210B00244D57 /* Products */, 82 | ); 83 | sourceTree = ""; 84 | }; 85 | 73FFDCF82C70210B00244D57 /* Products */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 73FFDCF72C70210B00244D57 /* Air Drop Assistant.app */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | 73FFDCF92C70210B00244D57 /* Air Drop Assistant */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 7318DA972C8F710D004F262A /* UpdateCheck.swift */, 97 | 737B8A342C8BE5D1006A4653 /* Air-Drop-Assistant-Info.plist */, 98 | 736C80FD2C70EC6800E5E780 /* PreferencesWindow.swift */, 99 | 73FFDCFA2C70210B00244D57 /* AppDelegate.swift */, 100 | 736C80F82C70302200E5E780 /* PrefWatcher.swift */, 101 | 73B6ABB52DDC16150028024F /* Logger.swift */, 102 | 73B614B82CF77F5500C3A14F /* PreferenceObserver.swift */, 103 | 736C80FF2C70EC9E00E5E780 /* PreferencesViewController.swift */, 104 | 7328CDA42C8BA87B0042A517 /* com.ttinc.Air-Drop-Assistant.plist */, 105 | 73FFDCFC2C70210B00244D57 /* ViewController.swift */, 106 | 73FFDCFE2C70210C00244D57 /* Assets.xcassets */, 107 | 73FFDD002C70210C00244D57 /* Main.storyboard */, 108 | 73FFDD032C70210C00244D57 /* Air_Drop_Assistant.entitlements */, 109 | 735DA8122CB8C2E3000F9D71 /* ADA_PF_Helper_Script.sh */, 110 | ); 111 | path = "Air Drop Assistant"; 112 | sourceTree = ""; 113 | }; 114 | /* End PBXGroup section */ 115 | 116 | /* Begin PBXNativeTarget section */ 117 | 73FFDCF62C70210B00244D57 /* Air Drop Assistant */ = { 118 | isa = PBXNativeTarget; 119 | buildConfigurationList = 73FFDD062C70210C00244D57 /* Build configuration list for PBXNativeTarget "Air Drop Assistant" */; 120 | buildPhases = ( 121 | 73FFDCF32C70210B00244D57 /* Sources */, 122 | 73FFDCF42C70210B00244D57 /* Frameworks */, 123 | 73FFDCF52C70210B00244D57 /* Resources */, 124 | 7328CDA62C8BA8FD0042A517 /* CopyFiles */, 125 | 732889752CBCAB9700234AA2 /* CopyFiles */, 126 | ); 127 | buildRules = ( 128 | ); 129 | dependencies = ( 130 | ); 131 | name = "Air Drop Assistant"; 132 | productName = "Air Drop Assistant"; 133 | productReference = 73FFDCF72C70210B00244D57 /* Air Drop Assistant.app */; 134 | productType = "com.apple.product-type.application"; 135 | }; 136 | /* End PBXNativeTarget section */ 137 | 138 | /* Begin PBXProject section */ 139 | 73FFDCEF2C70210B00244D57 /* Project object */ = { 140 | isa = PBXProject; 141 | attributes = { 142 | BuildIndependentTargetsInParallel = 1; 143 | LastSwiftUpdateCheck = 1540; 144 | LastUpgradeCheck = 1540; 145 | TargetAttributes = { 146 | 73FFDCF62C70210B00244D57 = { 147 | CreatedOnToolsVersion = 15.4; 148 | }; 149 | }; 150 | }; 151 | buildConfigurationList = 73FFDCF22C70210B00244D57 /* Build configuration list for PBXProject "Air Drop Assistant" */; 152 | compatibilityVersion = "Xcode 14.0"; 153 | developmentRegion = en; 154 | hasScannedForEncodings = 0; 155 | knownRegions = ( 156 | en, 157 | Base, 158 | ); 159 | mainGroup = 73FFDCEE2C70210B00244D57; 160 | productRefGroup = 73FFDCF82C70210B00244D57 /* Products */; 161 | projectDirPath = ""; 162 | projectRoot = ""; 163 | targets = ( 164 | 73FFDCF62C70210B00244D57 /* Air Drop Assistant */, 165 | ); 166 | }; 167 | /* End PBXProject section */ 168 | 169 | /* Begin PBXResourcesBuildPhase section */ 170 | 73FFDCF52C70210B00244D57 /* Resources */ = { 171 | isa = PBXResourcesBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | 7328CDA52C8BA87B0042A517 /* com.ttinc.Air-Drop-Assistant.plist in Resources */, 175 | 73FFDCFF2C70210C00244D57 /* Assets.xcassets in Resources */, 176 | 73FFDD022C70210C00244D57 /* Base in Resources */, 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | }; 180 | /* End PBXResourcesBuildPhase section */ 181 | 182 | /* Begin PBXSourcesBuildPhase section */ 183 | 73FFDCF32C70210B00244D57 /* Sources */ = { 184 | isa = PBXSourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | 736C81002C70EC9E00E5E780 /* PreferencesViewController.swift in Sources */, 188 | 73B614B92CF77F5500C3A14F /* PreferenceObserver.swift in Sources */, 189 | 736C80FE2C70EC6800E5E780 /* PreferencesWindow.swift in Sources */, 190 | 73FFDCFD2C70210B00244D57 /* ViewController.swift in Sources */, 191 | 73FFDCFB2C70210B00244D57 /* AppDelegate.swift in Sources */, 192 | 736C80F92C70302200E5E780 /* PrefWatcher.swift in Sources */, 193 | 7318DA982C8F710D004F262A /* UpdateCheck.swift in Sources */, 194 | 73B6ABB62DDC16150028024F /* Logger.swift in Sources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXSourcesBuildPhase section */ 199 | 200 | /* Begin PBXVariantGroup section */ 201 | 73FFDD002C70210C00244D57 /* Main.storyboard */ = { 202 | isa = PBXVariantGroup; 203 | children = ( 204 | 73FFDD012C70210C00244D57 /* Base */, 205 | ); 206 | name = Main.storyboard; 207 | sourceTree = ""; 208 | }; 209 | /* End PBXVariantGroup section */ 210 | 211 | /* Begin XCBuildConfiguration section */ 212 | 73FFDD042C70210C00244D57 /* Debug */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | ALWAYS_SEARCH_USER_PATHS = NO; 216 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 217 | CLANG_ANALYZER_NONNULL = YES; 218 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 219 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 220 | CLANG_ENABLE_MODULES = YES; 221 | CLANG_ENABLE_OBJC_ARC = YES; 222 | CLANG_ENABLE_OBJC_WEAK = YES; 223 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 224 | CLANG_WARN_BOOL_CONVERSION = YES; 225 | CLANG_WARN_COMMA = YES; 226 | CLANG_WARN_CONSTANT_CONVERSION = YES; 227 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 228 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 229 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 230 | CLANG_WARN_EMPTY_BODY = YES; 231 | CLANG_WARN_ENUM_CONVERSION = YES; 232 | CLANG_WARN_INFINITE_RECURSION = YES; 233 | CLANG_WARN_INT_CONVERSION = YES; 234 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 235 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 236 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 238 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 239 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 240 | CLANG_WARN_STRICT_PROTOTYPES = YES; 241 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 242 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 243 | CLANG_WARN_UNREACHABLE_CODE = YES; 244 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 245 | COPY_PHASE_STRIP = NO; 246 | DEBUG_INFORMATION_FORMAT = dwarf; 247 | ENABLE_STRICT_OBJC_MSGSEND = YES; 248 | ENABLE_TESTABILITY = YES; 249 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 250 | GCC_C_LANGUAGE_STANDARD = gnu17; 251 | GCC_DYNAMIC_NO_PIC = NO; 252 | GCC_NO_COMMON_BLOCKS = YES; 253 | GCC_OPTIMIZATION_LEVEL = 0; 254 | GCC_PREPROCESSOR_DEFINITIONS = ( 255 | "DEBUG=1", 256 | "$(inherited)", 257 | ); 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 265 | MACOSX_DEPLOYMENT_TARGET = 14.5; 266 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 267 | MTL_FAST_MATH = YES; 268 | ONLY_ACTIVE_ARCH = YES; 269 | SDKROOT = macosx; 270 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 271 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 272 | }; 273 | name = Debug; 274 | }; 275 | 73FFDD052C70210C00244D57 /* Release */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | ALWAYS_SEARCH_USER_PATHS = NO; 279 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 280 | CLANG_ANALYZER_NONNULL = YES; 281 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 282 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_ENABLE_OBJC_WEAK = YES; 286 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 287 | CLANG_WARN_BOOL_CONVERSION = YES; 288 | CLANG_WARN_COMMA = YES; 289 | CLANG_WARN_CONSTANT_CONVERSION = YES; 290 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 291 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 292 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INFINITE_RECURSION = YES; 296 | CLANG_WARN_INT_CONVERSION = YES; 297 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 299 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 301 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 302 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 303 | CLANG_WARN_STRICT_PROTOTYPES = YES; 304 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 305 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 306 | CLANG_WARN_UNREACHABLE_CODE = YES; 307 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 308 | COPY_PHASE_STRIP = NO; 309 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 310 | ENABLE_NS_ASSERTIONS = NO; 311 | ENABLE_STRICT_OBJC_MSGSEND = YES; 312 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 313 | GCC_C_LANGUAGE_STANDARD = gnu17; 314 | GCC_NO_COMMON_BLOCKS = YES; 315 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 316 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 317 | GCC_WARN_UNDECLARED_SELECTOR = YES; 318 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 319 | GCC_WARN_UNUSED_FUNCTION = YES; 320 | GCC_WARN_UNUSED_VARIABLE = YES; 321 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 322 | MACOSX_DEPLOYMENT_TARGET = 14.5; 323 | MTL_ENABLE_DEBUG_INFO = NO; 324 | MTL_FAST_MATH = YES; 325 | SDKROOT = macosx; 326 | SWIFT_COMPILATION_MODE = wholemodule; 327 | }; 328 | name = Release; 329 | }; 330 | 73FFDD072C70210C00244D57 /* Debug */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 334 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 335 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; 336 | CODE_SIGN_ENTITLEMENTS = "Air Drop Assistant/Air_Drop_Assistant.entitlements"; 337 | CODE_SIGN_IDENTITY = "Apple Development"; 338 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 339 | CODE_SIGN_STYLE = Automatic; 340 | COMBINE_HIDPI_IMAGES = YES; 341 | CURRENT_PROJECT_VERSION = 234; 342 | DEVELOPMENT_TEAM = 2WUMX954UB; 343 | ENABLE_HARDENED_RUNTIME = YES; 344 | GENERATE_INFOPLIST_FILE = YES; 345 | INFOPLIST_FILE = "Air-Drop-Assistant-Info.plist"; 346 | INFOPLIST_KEY_CFBundleDisplayName = "Air Drop Assistant"; 347 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 348 | INFOPLIST_KEY_LSUIElement = YES; 349 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 350 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 351 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 352 | LD_RUNPATH_SEARCH_PATHS = ( 353 | "$(inherited)", 354 | "@executable_path/../Frameworks", 355 | ); 356 | MACOSX_DEPLOYMENT_TARGET = 13.0; 357 | MARKETING_VERSION = 1.4; 358 | PRODUCT_BUNDLE_IDENTIFIER = "com.ttinc.Air-Drop-Assistant"; 359 | PRODUCT_NAME = "$(TARGET_NAME)"; 360 | PROVISIONING_PROFILE_SPECIFIER = ""; 361 | SWIFT_EMIT_LOC_STRINGS = YES; 362 | SWIFT_VERSION = 5.0; 363 | }; 364 | name = Debug; 365 | }; 366 | 73FFDD082C70210C00244D57 /* Release */ = { 367 | isa = XCBuildConfiguration; 368 | buildSettings = { 369 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 370 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 371 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; 372 | CODE_SIGN_ENTITLEMENTS = "Air Drop Assistant/Air_Drop_Assistant.entitlements"; 373 | CODE_SIGN_IDENTITY = "Apple Development"; 374 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 375 | CODE_SIGN_STYLE = Automatic; 376 | COMBINE_HIDPI_IMAGES = YES; 377 | CURRENT_PROJECT_VERSION = 234; 378 | DEVELOPMENT_TEAM = 2WUMX954UB; 379 | ENABLE_HARDENED_RUNTIME = YES; 380 | GENERATE_INFOPLIST_FILE = YES; 381 | INFOPLIST_FILE = "Air-Drop-Assistant-Info.plist"; 382 | INFOPLIST_KEY_CFBundleDisplayName = "Air Drop Assistant"; 383 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 384 | INFOPLIST_KEY_LSUIElement = YES; 385 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 386 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 387 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 388 | LD_RUNPATH_SEARCH_PATHS = ( 389 | "$(inherited)", 390 | "@executable_path/../Frameworks", 391 | ); 392 | MACOSX_DEPLOYMENT_TARGET = 13.0; 393 | MARKETING_VERSION = 1.4; 394 | PRODUCT_BUNDLE_IDENTIFIER = "com.ttinc.Air-Drop-Assistant"; 395 | PRODUCT_NAME = "$(TARGET_NAME)"; 396 | PROVISIONING_PROFILE_SPECIFIER = ""; 397 | SWIFT_EMIT_LOC_STRINGS = YES; 398 | SWIFT_VERSION = 5.0; 399 | }; 400 | name = Release; 401 | }; 402 | /* End XCBuildConfiguration section */ 403 | 404 | /* Begin XCConfigurationList section */ 405 | 73FFDCF22C70210B00244D57 /* Build configuration list for PBXProject "Air Drop Assistant" */ = { 406 | isa = XCConfigurationList; 407 | buildConfigurations = ( 408 | 73FFDD042C70210C00244D57 /* Debug */, 409 | 73FFDD052C70210C00244D57 /* Release */, 410 | ); 411 | defaultConfigurationIsVisible = 0; 412 | defaultConfigurationName = Release; 413 | }; 414 | 73FFDD062C70210C00244D57 /* Build configuration list for PBXNativeTarget "Air Drop Assistant" */ = { 415 | isa = XCConfigurationList; 416 | buildConfigurations = ( 417 | 73FFDD072C70210C00244D57 /* Debug */, 418 | 73FFDD082C70210C00244D57 /* Release */, 419 | ); 420 | defaultConfigurationIsVisible = 0; 421 | defaultConfigurationName = Release; 422 | }; 423 | /* End XCConfigurationList section */ 424 | }; 425 | rootObject = 73FFDCEF2C70210B00244D57 /* Project object */; 426 | } 427 | -------------------------------------------------------------------------------- /Air Drop Assistant.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Air Drop Assistant.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Air Drop Assistant.xcodeproj/xcshareddata/xcschemes/Air Drop Assistant.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 57 | 59 | 65 | 66 | 67 | 68 | 74 | 76 | 82 | 83 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Air Drop Assistant.xcodeproj/xcshareddata/xcschemes/com.ttinc.Air-Drop-Assistant.helper.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 58 | 60 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Air Drop Assistant.xcodeproj/xcuserdata/boberito.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Air Drop Assistant.xcodeproj/xcuserdata/boberito.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Air Drop Assistant.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Air Drop Assistant/ADA_PF_Helper_Script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | enableAirDropPF() { 4 | /usr/bin/logger "ADA: Enabling PF ADA Anchors" 5 | echo 'anchor "ada_anchor"' >> /etc/pf.conf 6 | echo 'load anchor "ada_anchor" from "/etc/pf.anchors/ada_anchor"' >> /etc/pf.conf 7 | /sbin/pfctl -e 2> /dev/null 8 | /sbin/pfctl -f /etc/pf.conf 2> /dev/null 9 | 10 | if [[ ! -f "/Library/LaunchDaemons/com.ttinc.Air-Drop-Assistant.pfctl.plist" ]]; then 11 | /usr/bin/logger "ADA: Creating ADA PF LaunchDaemon" 12 | /bin/cp "/System/Library/LaunchDaemons/com.apple.pfctl.plist" "/Library/LaunchDaemons/com.ttinc.Air-Drop-Assistant.pfctl.plist" 2> /dev/null 13 | /usr/libexec/PlistBuddy -c "Add :ProgramArguments:1 string -e" "/Library/LaunchDaemons/com.ttinc.Air-Drop-Assistant.pfctl.plist" 2> /dev/null 14 | /usr/libexec/PlistBuddy -c "Set :Label ada.pfctl" "/Library/LaunchDaemons/com.ttinc.Air-Drop-Assistant.pfctl.plist" 2> /dev/null 15 | /bin/launchctl enable system/ada.pfctl 2> /dev/null 16 | /bin/launchctl bootstrap system "/Library/LaunchDaemons/com.ttinc.Air-Drop-Assistant.pfctl.plist" 2> /dev/null 17 | fi 18 | 19 | } 20 | 21 | disableAirDropPF() { 22 | /usr/bin/logger "ADA: Disabling and Removing ADA PF Anchors" 23 | /bin/rm /etc/pf.anchors/ada_anchor 24 | /usr/bin/sed -i '' '/anchor "ada_anchor"/d' /etc/pf.conf 25 | /usr/bin/sed -i '' '/load anchor "ada_anchor" from "\/etc\/pf.anchors\/ada_anchor"/d' /etc/pf.conf 26 | /sbin/pfctl -e 2> /dev/null 27 | /sbin/pfctl -f /etc/pf.conf 2> /dev/null 28 | /bin/launchctl disable system/mscp.pfctl 2> /dev/null 29 | /bin/launchctl bootout system "/Library/LaunchDaemons/com.ttinc.Air-Drop-Assistant.pfctl.plist" 2> /dev/null 30 | /bin/rm -rf "/Library/LaunchDaemons/com.ttinc.Air-Drop-Assistant.pfctl.plist" 31 | /usr/bin/defaults write "/Library/Preferences/com.ttinc.Air-Drop-Assistant.plist" ADA_PF "off" 32 | } 33 | 34 | disableOutgoing() { 35 | /usr/bin/logger "ADA: Creating disable outgoing ada_anchor" 36 | echo "block out proto tcp to port 8770" > /etc/pf.anchors/ada_anchor 37 | /usr/bin/defaults write "/Library/Preferences/com.ttinc.Air-Drop-Assistant.plist" ADA_PF "DisableOut" 38 | enableAirDropPF 39 | } 40 | 41 | disableInComing() { 42 | /usr/bin/logger "ADA: Creating disable incoming ada_anchor" 43 | echo "block in proto tcp to port 8770" > /etc/pf.anchors/ada_anchor 44 | /usr/bin/defaults write "/Library/Preferences/com.ttinc.Air-Drop-Assistant.plist" ADA_PF "DisableIn" 45 | enableAirDropPF 46 | } 47 | 48 | /usr/bin/logger "ADA: PF Helper Script Launched" 49 | if [[ $EUID -ne 0 ]]; then 50 | /usr/bin/logger "ADA: Prompting for Admin Privs" 51 | /usr/bin/logger "ADA: $0 $1" 52 | # /usr/bin/osascript -e "do shell script \"$0 $1\" with administrator privileges" 53 | /usr/bin/osascript -e 'on run {commandName, commandArgument}' -e 'do shell script ((quoted form of commandName) & " " & (quoted form of commandArgument)) without altering line endings with administrator privileges with prompt "Air Drop Assistant would like permission to change system level settings."' -e 'end run' -- "$0" "$1" 54 | exit 0 55 | fi 56 | 57 | zparseopts -blockOut=blockOut -blockIn=blockIn -remove=disableAirDropPF 58 | 59 | if [[ $blockOut ]] || [[ $blockIn ]] || [[ $disableAirDropPF ]]; then 60 | if [[ $blockOut ]]; then 61 | disableOutgoing 62 | fi 63 | if [[ $blockIn ]]; then 64 | disableInComing 65 | fi 66 | if [[ $disableAirDropPF ]]; then 67 | disableAirDropPF 68 | fi 69 | fi 70 | -------------------------------------------------------------------------------- /Air Drop Assistant/Air_Drop_Assistant.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Air Drop Assistant/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Air Drop Assistant 4 | // 5 | // Created by Bob Gendler on 8/16/24. 6 | // 7 | 8 | import Cocoa 9 | import UserNotifications 10 | import ServiceManagement 11 | import OSLog 12 | 13 | @NSApplicationMain 14 | 15 | class AppDelegate: NSObject, NSApplicationDelegate, DataModelDelegate, PrefDataModelDelegate, AppPrefObserverDelegate { 16 | func newPreferenceValue() { 17 | menuIcon() 18 | 19 | guard let appBundleID = Bundle.main.bundleIdentifier else { return } 20 | let hideMenuIconValue = UserDefaults.standard.bool(forKey: "hideMenuIcon") 21 | let isForced = CFPreferencesAppValueIsForced("hideMenuIcon" as CFString, appBundleID as CFString) 22 | 23 | if hideMenuIconValue && isForced { 24 | adaMenu.menu?.removeAllItems() 25 | NSStatusBar.system.removeStatusItem(adaMenu) 26 | } else { 27 | adaMenu = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 28 | 29 | adaMenu.menu = NSMenu() 30 | 31 | menuIcon() 32 | 33 | adaMenuListing() 34 | let prefs = NSMenuItem(title: "Preferences", action: #selector(Preferences), keyEquivalent: "") 35 | let softwareUpdate = NSMenuItem(title: "Check for Update", action: #selector(updateCheckFunc), keyEquivalent: "") 36 | 37 | var IncreaseByOne: Int = 0 38 | if let menuItems = adaMenu.menu { 39 | for item in menuItems.items { 40 | 41 | if item.title == "AirDrop: Incoming Only"{ 42 | IncreaseByOne += 1 43 | } 44 | if item.title == "AirDrop: Outgoing Only" { 45 | IncreaseByOne += 1 46 | } 47 | } 48 | } 49 | 50 | adaMenu.menu?.insertItem(prefs, at: 1 + IncreaseByOne) 51 | let isForcedUpdatesDisable = CFPreferencesAppValueIsForced("disableUpdates" as CFString, appBundleID as CFString) 52 | if UserDefaults.standard.bool(forKey: "disableUpdates") && isForcedUpdatesDisable { 53 | Logger.general.info("Updates disabled, not adding the update menu") 54 | } else { 55 | adaMenu.menu?.insertItem(softwareUpdate, at: 2 + IncreaseByOne) 56 | } 57 | let quit = NSMenuItem(title: "Quit", action: #selector(QuitApp), keyEquivalent: "") 58 | guard let menuCount = adaMenu.menu?.items.count else { return } 59 | adaMenu.menu?.insertItem(quit, at: menuCount) 60 | } 61 | } 62 | 63 | func checkAirDrop() { 64 | if domain!.string(forKey: "DiscoverableMode") == UserDefaults.standard.string(forKey: "airDropSetting") || domain!.string(forKey: "DiscoverableMode") == "Off" { 65 | return 66 | } else { 67 | prefWatcher.resetDiscoverableMode() 68 | } 69 | } 70 | 71 | let nc = UNUserNotificationCenter.current() 72 | func didReceiveDataUpdate(airDropStatus: String) { 73 | let hideMenuIconValue = UserDefaults.standard.bool(forKey: "hideMenuIcon") 74 | if hideMenuIconValue { 75 | return 76 | } 77 | self.adaMenu.menu?.removeAllItems() 78 | self.adaMenu.menu = NSMenu() 79 | 80 | self.adaMenuListing() 81 | let prefs = NSMenuItem(title: "Preferences", action: #selector(Preferences), keyEquivalent: "") 82 | let softwareUpdate = NSMenuItem(title: "Check for Update", action: #selector(updateCheckFunc), keyEquivalent: "") 83 | 84 | var IncreaseByOne: Int = 0 85 | if let menuItems = adaMenu.menu { 86 | for item in menuItems.items { 87 | 88 | if item.title == "AirDrop: Incoming Only"{ 89 | IncreaseByOne += 1 90 | } 91 | if item.title == "AirDrop: Outgoing Only" { 92 | IncreaseByOne += 1 93 | } 94 | } 95 | } 96 | 97 | self.adaMenu.menu?.insertItem(prefs, at: 1 + IncreaseByOne) 98 | 99 | guard let appBundleID = Bundle.main.bundleIdentifier else { return } 100 | let isForcedUpdatesDisable = CFPreferencesAppValueIsForced("disableUpdates" as CFString, appBundleID as CFString) 101 | if UserDefaults.standard.bool(forKey: "disableUpdates") && isForcedUpdatesDisable { 102 | Logger.general.info("Updates disabled, not adding the update menu") 103 | } else { 104 | adaMenu.menu?.insertItem(softwareUpdate, at: 2 + IncreaseByOne) 105 | } 106 | 107 | let quit = NSMenuItem(title: "Quit", action: #selector(QuitApp), keyEquivalent: "") 108 | guard let menuCount = adaMenu.menu?.items.count else { return } 109 | self.adaMenu.menu?.insertItem(quit, at: menuCount) 110 | } 111 | func updatePF() { 112 | 113 | self.adaMenu.menu?.removeAllItems() 114 | self.adaMenu.menu = NSMenu() 115 | 116 | self.adaMenuListing() 117 | let prefs = NSMenuItem(title: "Preferences", action: #selector(Preferences), keyEquivalent: "") 118 | let softwareUpdate = NSMenuItem(title: "Check for Update", action: #selector(updateCheckFunc), keyEquivalent: "") 119 | 120 | var IncreaseByOne: Int = 0 121 | if let menuItems = adaMenu.menu { 122 | for item in menuItems.items { 123 | 124 | if item.title == "AirDrop: Incoming Only"{ 125 | IncreaseByOne += 1 126 | } 127 | if item.title == "AirDrop: Outgoing Only" { 128 | IncreaseByOne += 1 129 | } 130 | } 131 | } 132 | 133 | self.adaMenu.menu?.insertItem(prefs, at: 1 + IncreaseByOne) 134 | guard let appBundleID = Bundle.main.bundleIdentifier else { return } 135 | let isForcedUpdatesDisable = CFPreferencesAppValueIsForced("disableUpdates" as CFString, appBundleID as CFString) 136 | if UserDefaults.standard.bool(forKey: "disableUpdates") && isForcedUpdatesDisable { 137 | Logger.general.info("Updates disabled, not adding the update menu") 138 | } else { 139 | // adaMenu.menu?.insertItem(softwareUpdate, at: 2 + IncreaseByOne) 140 | adaMenu.menu?.insertItem(softwareUpdate, at: 2 + IncreaseByOne) 141 | } 142 | 143 | let quit = NSMenuItem(title: "Quit", action: #selector(QuitApp), keyEquivalent: "") 144 | guard let menuCount = adaMenu.menu?.items.count else { return } 145 | self.adaMenu.menu?.insertItem(quit, at: menuCount) 146 | } 147 | func didRecievePrefUpdate(iconMode: String) { 148 | self.menuIcon() 149 | } 150 | 151 | let domain = UserDefaults(suiteName: "com.apple.sharingd") 152 | var adaMenu = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 153 | var airDropStatus = "" 154 | let prefWatcher = PrefWatcher() 155 | let updater = UpdateCheck() 156 | let observer = AppPreferencesObserver() 157 | func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { 158 | NSApp.setActivationPolicy(.accessory) 159 | return false 160 | } 161 | func applicationDidFinishLaunching(_ aNotification: Notification) { 162 | Logger.general.info("ADA Launched") 163 | 164 | NSApp.setActivationPolicy(.accessory) 165 | 166 | let appService = SMAppService.agent(plistName: "com.ttinc.Air-Drop-Assistant.plist") 167 | if CommandLine.arguments.count > 1 { 168 | if airDropManagedDisabled() { 169 | Logger.general.info("AirDrop is disabled by an MDM Profile. Please contact your MDM administrator.") 170 | NSApp.terminate(nil) 171 | } 172 | let arguments = CommandLine.arguments 173 | 174 | if arguments[1] == "--register" { 175 | do { 176 | try appService.register() 177 | Logger.general.info("registered service") 178 | } catch { 179 | Logger.general.error("Problem registering service") 180 | } 181 | } 182 | 183 | if arguments[1] == "--unregister" { 184 | do { 185 | if appService.status == .enabled { 186 | try appService.unregister() 187 | 188 | Logger.general.info("Unregistered service") 189 | } 190 | 191 | } catch { 192 | 193 | Logger.general.error("Problem unregistering service") 194 | } 195 | 196 | } 197 | NSApp.terminate(nil) 198 | } 199 | if isAppAlreadyRunning() { 200 | NSApp.terminate(nil) 201 | } 202 | if airDropManagedDisabled() { 203 | let alert = NSAlert() 204 | alert.messageText = "Alert" 205 | alert.informativeText = """ 206 | AirDrop is disabled by an MDM Profile. Please contact your MDM administrator. 207 | """ 208 | alert.runModal() 209 | NSApp.terminate(nil) 210 | } 211 | 212 | if UserDefaults.standard.bool(forKey: "afterFirstLaunch") == false && appService.status != .enabled { 213 | 214 | let alert = NSAlert() 215 | alert.messageText = "First Launch" 216 | alert.informativeText = """ 217 | Would you like to allow Air Drop Assistant to launch at login? 218 | """ 219 | alert.addButton(withTitle: "Yes") 220 | alert.addButton(withTitle: "No") 221 | let response = alert.runModal() 222 | if response == .alertFirstButtonReturn { 223 | 224 | do { 225 | try appService.register() 226 | Logger.general.info("Registered service") 227 | } catch { 228 | Logger.general.error("problem registering service \(error.localizedDescription)") 229 | } 230 | } 231 | 232 | 233 | 234 | } 235 | UserDefaults.standard.setValue(true, forKey: "afterFirstLaunch") 236 | UNUserNotificationCenter.current().delegate = self 237 | NSApplication.shared.setActivationPolicy(.accessory) 238 | self.notificationPermissions() 239 | 240 | 241 | if UserDefaults.standard.string(forKey: "airDropSetting") == nil { 242 | UserDefaults.standard.set("Contacts Only", forKey: "airDropSetting") 243 | } 244 | 245 | if UserDefaults.standard.string(forKey: "timing") == nil { 246 | UserDefaults.standard.set(15, forKey: "timing") 247 | } 248 | prefWatcher.delegate = self 249 | observer.delegate = self 250 | 251 | if domain?.string(forKey: "DiscoverableMode") != UserDefaults.standard.string(forKey: "airDropSetting") && domain?.string(forKey: "DiscoverableMode") != "Off" { 252 | Task { 253 | await prefWatcher.resetAirDrop() 254 | } 255 | } 256 | 257 | let homeDirURL = FileManager.default.homeDirectoryForCurrentUser 258 | let pathToPref = "\(homeDirURL.path)/Library/Preferences/com.apple.sharingd.plist" 259 | prefWatcher.filePath = pathToPref 260 | 261 | 262 | guard let appBundleID = Bundle.main.bundleIdentifier else { return } 263 | 264 | let hideMenuIconValue = UserDefaults.standard.bool(forKey: "hideMenuIcon") 265 | let isForced = CFPreferencesAppValueIsForced("hideMenuIcon" as CFString, appBundleID as CFString) 266 | 267 | if hideMenuIconValue && isForced { 268 | prefWatcher.startMonitoring() 269 | } else { 270 | let isForcedUpdatesDisable = CFPreferencesAppValueIsForced("disableUpdates" as CFString, appBundleID as CFString) 271 | if UserDefaults.standard.bool(forKey: "disableUpdates") && isForcedUpdatesDisable { 272 | Logger.general.info("Updates disabled") 273 | } else { 274 | 275 | _ = updater.check() 276 | } 277 | adaMenu.menu = NSMenu() 278 | 279 | self.menuIcon() 280 | 281 | adaMenuListing() 282 | let prefs = NSMenuItem(title: "Preferences", action: #selector(Preferences), keyEquivalent: "") 283 | 284 | let softwareUpdate = NSMenuItem(title: "Check for Update", action: #selector(updateCheckFunc), keyEquivalent: "") 285 | 286 | var IncreaseByOne: Int = 0 287 | if let menuItems = adaMenu.menu { 288 | for item in menuItems.items { 289 | 290 | if item.title == "AirDrop: Incoming Only"{ 291 | IncreaseByOne += 1 292 | } 293 | if item.title == "AirDrop: Outgoing Only" { 294 | IncreaseByOne += 1 295 | } 296 | } 297 | } 298 | 299 | adaMenu.menu?.insertItem(prefs, at: 1 + IncreaseByOne) 300 | if UserDefaults.standard.bool(forKey: "disableUpdates") && isForcedUpdatesDisable { 301 | Logger.general.info("Updates disabled, not adding the update menu") 302 | } else { 303 | adaMenu.menu?.insertItem(softwareUpdate, at: 2 + IncreaseByOne) 304 | } 305 | 306 | let quit = NSMenuItem(title: "Quit", action: #selector(QuitApp), keyEquivalent: "") 307 | guard let menuCount = adaMenu.menu?.items.count else { return } 308 | adaMenu.menu?.insertItem(quit, at: menuCount) 309 | prefWatcher.startMonitoring() 310 | } 311 | 312 | } 313 | 314 | func notificationPermissions() { 315 | nc.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in 316 | if granted { 317 | self.prefWatcher.notificationsAllow = true 318 | 319 | } 320 | } 321 | 322 | } 323 | 324 | func menuIcon(){ 325 | let iconPref = UserDefaults.standard.string(forKey: "icon_mode") ?? "colorful" 326 | var menuIcon = "menuicon" 327 | 328 | if iconPref == "bw" { 329 | menuIcon = "menuicon_mono" 330 | } 331 | let icon = NSImage(named: NSImage.Name(menuIcon)) 332 | icon?.size.width = 18 333 | icon?.size.height = 18 334 | self.adaMenu.button?.image = icon 335 | 336 | } 337 | 338 | func adaMenuListing(){ 339 | 340 | var PFADAStatus: String? 341 | guard let bundleID = Bundle.main.bundleIdentifier else { return } 342 | let path = "/Library/Preferences/\(bundleID).plist" 343 | 344 | if FileManager.default.fileExists(atPath: path) { 345 | 346 | if let plist = NSDictionary(contentsOfFile: path) as? [String: Any] { 347 | 348 | if let adaPFValue = plist["ADA_PF"] as? String { 349 | 350 | PFADAStatus = adaPFValue as String 351 | } 352 | } 353 | } 354 | 355 | if let airDropPref = domain?.object(forKey: "DiscoverableMode") { 356 | airDropStatus = "Airdrop Status: " + String(describing: airDropPref) 357 | } else { 358 | airDropStatus = "Error reading AirDrop Status" 359 | } 360 | let airDropStatus = NSMenuItem(title: airDropStatus, action: nil, keyEquivalent: "") 361 | 362 | if adaMenu.menu?.items.count != 0 { 363 | adaMenu.menu?.removeItem(at: 0) 364 | } 365 | 366 | adaMenu.menu?.insertItem(airDropStatus, at: 0) 367 | if (PFADAStatus != "" || PFADAStatus != "off") && airDropStatus.title != "Airdrop Status: Off" { 368 | var status = String() 369 | if PFADAStatus == "DisableOut" { 370 | status = "AirDrop: Incoming Only" 371 | } 372 | if PFADAStatus == "DisableIn" { 373 | status = "AirDrop: Outgoing Only" 374 | } 375 | if status != "" { 376 | if let menuItems = adaMenu.menu { 377 | for item in menuItems.items { 378 | if item.title == "AirDrop: Incoming Only"{ 379 | adaMenu.menu?.removeItem(at: 1) 380 | } 381 | if item.title == "AirDrop: Outgoing Only" { 382 | adaMenu.menu?.removeItem(at: 1) 383 | } 384 | } 385 | } 386 | adaMenu.menu?.insertItem(NSMenuItem(title: status, action: nil, keyEquivalent: ""), at: 1) 387 | } 388 | } 389 | 390 | } 391 | 392 | @objc func launchAtLogin(){ 393 | Logger.general.info("Launch at Loging Function") 394 | 395 | UserDefaults.standard.setValue(true, forKey: "afterFirstLaunch") 396 | } 397 | @objc func QuitApp() { 398 | exit(0) 399 | } 400 | @objc func Preferences() { 401 | for currentWindow in NSApplication.shared.windows { 402 | if currentWindow.title.contains("Air Drop Assistant Preferences") { 403 | if #available(OSX 14.0, *) { 404 | NSApp.activate() 405 | } else { 406 | NSApp.activate(ignoringOtherApps: true) 407 | } 408 | return 409 | } 410 | } 411 | let prefViewController = PreferencesViewController() 412 | prefViewController.delegate = self 413 | var window: PreferencesWindow? 414 | let windowSize = NSSize(width: 415, height: 200) 415 | let screenSize = NSScreen.main?.frame.size ?? .zero 416 | let rect = NSMakeRect(screenSize.width/2 - windowSize.width/2, screenSize.height/2 - windowSize.height/2, windowSize.width, windowSize.height) 417 | window = PreferencesWindow(contentRect: rect, styleMask: [.miniaturizable, .closable, .titled], backing: .buffered, defer: false) 418 | window?.title = "Air Drop Assistant Preferences" 419 | NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) 420 | window?.makeKeyAndOrderFront(nil) 421 | window?.orderFrontRegardless() 422 | window?.contentViewController = prefViewController 423 | } 424 | func applicationWillTerminate(_ aNotification: Notification) { 425 | // Insert code here to tear down your application 426 | } 427 | 428 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 429 | return true 430 | } 431 | func isAppAlreadyRunning() -> Bool { 432 | let runningApps = NSWorkspace.shared.runningApplications 433 | let isRunning = runningApps.contains { app in 434 | return app.bundleIdentifier == Bundle.main.bundleIdentifier && app != NSRunningApplication.current 435 | } 436 | return isRunning 437 | } 438 | @objc func updateCheckFunc () { 439 | _ = updater.check() 440 | } 441 | func airDropManagedDisabled () -> Bool { 442 | let networkBrowser = UserDefaults(suiteName: "com.apple.NetworkBrowser") 443 | if let networkBrowserAirDrop = networkBrowser?.bool(forKey: "DisableAirDrop") { 444 | if networkBrowserAirDrop { 445 | Logger.general.info("com.apple.NetworkBrowser DisableAirDrop is set to true") 446 | return true 447 | } 448 | } 449 | if let value = UserDefaults.standard.persistentDomain(forName: "com.apple.applicationaccess")?["allowAirDrop"] { 450 | if let boolValue = value as? Bool { 451 | if !boolValue { 452 | Logger.general.info("com.apple.applicationaccess allowAirDrop is set to false") 453 | return true 454 | } 455 | 456 | } 457 | } 458 | return false 459 | } 460 | } 461 | 462 | 463 | 464 | extension AppDelegate: UNUserNotificationCenterDelegate { 465 | 466 | func userNotificationCenter(_ center: UNUserNotificationCenter, 467 | willPresent notification: UNNotification, 468 | withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) 469 | { 470 | completionHandler(.banner) 471 | } 472 | 473 | } 474 | -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_1024.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_128x128.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_128x128@2x.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_16x16.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_16x16@2x.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_256x256.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_256x256@2x.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_32x32.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_32x32@2x.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/ADA_512x512.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ADA_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "ADA_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "ADA_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "ADA_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "ADA_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "ADA_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "ADA_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "ADA_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "ADA_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "ADA_1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/menuicon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "menuicon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/menuicon.imageset/menuicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/menuicon.imageset/menuicon.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/menuicon_mono.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "menuicon_mono.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "light" 13 | } 14 | ], 15 | "filename" : "menuicon_mono copy.png", 16 | "idiom" : "universal", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ], 26 | "filename" : "menuicon_mono 1.png", 27 | "idiom" : "universal", 28 | "scale" : "1x" 29 | }, 30 | { 31 | "idiom" : "universal", 32 | "scale" : "2x" 33 | }, 34 | { 35 | "appearances" : [ 36 | { 37 | "appearance" : "luminosity", 38 | "value" : "light" 39 | } 40 | ], 41 | "idiom" : "universal", 42 | "scale" : "2x" 43 | }, 44 | { 45 | "appearances" : [ 46 | { 47 | "appearance" : "luminosity", 48 | "value" : "dark" 49 | } 50 | ], 51 | "idiom" : "universal", 52 | "scale" : "2x" 53 | }, 54 | { 55 | "idiom" : "universal", 56 | "scale" : "3x" 57 | }, 58 | { 59 | "appearances" : [ 60 | { 61 | "appearance" : "luminosity", 62 | "value" : "light" 63 | } 64 | ], 65 | "idiom" : "universal", 66 | "scale" : "3x" 67 | }, 68 | { 69 | "appearances" : [ 70 | { 71 | "appearance" : "luminosity", 72 | "value" : "dark" 73 | } 74 | ], 75 | "idiom" : "universal", 76 | "scale" : "3x" 77 | } 78 | ], 79 | "info" : { 80 | "author" : "xcode", 81 | "version" : 1 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/menuicon_mono.imageset/menuicon_mono 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/menuicon_mono.imageset/menuicon_mono 1.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/menuicon_mono.imageset/menuicon_mono copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/menuicon_mono.imageset/menuicon_mono copy.png -------------------------------------------------------------------------------- /Air Drop Assistant/Assets.xcassets/menuicon_mono.imageset/menuicon_mono.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/Assets.xcassets/menuicon_mono.imageset/menuicon_mono.png -------------------------------------------------------------------------------- /Air Drop Assistant/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | Default 533 | 534 | 535 | 536 | 537 | 538 | 539 | Left to Right 540 | 541 | 542 | 543 | 544 | 545 | 546 | Right to Left 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | Default 558 | 559 | 560 | 561 | 562 | 563 | 564 | Left to Right 565 | 566 | 567 | 568 | 569 | 570 | 571 | Right to Left 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 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | -------------------------------------------------------------------------------- /Air Drop Assistant/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // Air Drop Assistant 4 | // 5 | // Created by Bob Gendler on 5/19/25. 6 | // 7 | 8 | import Foundation 9 | import OSLog 10 | extension Logger { 11 | private static var subsystem = Bundle.main.bundleIdentifier! 12 | static let airdropstatus = Logger(subsystem: subsystem, category: "airdrop_status") 13 | static let updater = Logger(subsystem: subsystem, category: "updater") 14 | static let general = Logger(subsystem: subsystem, category: "general") 15 | } 16 | -------------------------------------------------------------------------------- /Air Drop Assistant/PrefWatcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrefWatcher.swift 3 | // Air Drop Assistant 4 | // 5 | // Created by Bob Gendler on 8/16/24. 6 | // 7 | 8 | import Foundation 9 | import System 10 | import UserNotifications 11 | import OSLog 12 | 13 | extension DispatchSourceFileSystemObject { 14 | var dataStrings: [String] { 15 | var s = [String]() 16 | if data.contains(.all) { s.append("all") } 17 | if data.contains(.attrib) { s.append("attrib") } 18 | if data.contains(.delete) { s.append("delete") } 19 | if data.contains(.extend) { s.append("extend") } 20 | if data.contains(.funlock) { s.append("funlock") } 21 | if data.contains(.link) { s.append("link") } 22 | if data.contains(.rename) { s.append("rename") } 23 | if data.contains(.revoke) { s.append("revoke") } 24 | if data.contains(.write) { s.append("write") } 25 | return s 26 | } 27 | } 28 | 29 | protocol DataModelDelegate { 30 | func didReceiveDataUpdate(airDropStatus: String) 31 | } 32 | 33 | 34 | class PrefWatcher { 35 | 36 | var delegate: DataModelDelegate? 37 | var notificationsAllow = false 38 | var eventStream: FSEventStreamRef? 39 | var source: DispatchSourceFileSystemObject? 40 | let domain = UserDefaults(suiteName: "com.apple.sharingd") 41 | var filePath = "" 42 | 43 | func startMonitoring() { 44 | 45 | do { 46 | // Open the file descriptor in event-only mode 47 | let fdesc = try FileDescriptor.open(filePath, .readOnly, options: .eventOnly) 48 | 49 | // Create the dispatch source 50 | source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fdesc.rawValue, eventMask: .all, queue: .global()) 51 | source?.setEventHandler { 52 | let event = self.source?.data 53 | 54 | if let eventStrings = self.source?.dataStrings { 55 | Logger.airdropstatus.info("\(self.filePath) File system event: \(eventStrings.joined(separator: ", "))") 56 | 57 | } 58 | 59 | if event?.contains(.delete) == true || event?.contains(.rename) == true { 60 | do { 61 | // Close the existing file descriptor 62 | try fdesc.close() 63 | 64 | // Stop the current dispatch source 65 | self.source?.cancel() 66 | self.source = nil 67 | 68 | if let ADstatus = self.domain?.string(forKey: "DiscoverableMode") { 69 | self.delegate?.didReceiveDataUpdate(airDropStatus: ADstatus) 70 | Logger.airdropstatus.info("Airdrop Status Changed to \(ADstatus)") 71 | } 72 | 73 | Task { 74 | await self.resetAirDrop() 75 | } 76 | } catch { 77 | Logger.airdropstatus.error("\(error.localizedDescription)") 78 | } 79 | } 80 | } 81 | 82 | source?.setCancelHandler { 83 | 84 | self.startMonitoring() 85 | } 86 | 87 | source?.resume() 88 | 89 | } catch { 90 | Logger.airdropstatus.error("\(error.localizedDescription)") 91 | } 92 | } 93 | 94 | func resetAirDrop() async { 95 | if domain!.string(forKey: "DiscoverableMode") == UserDefaults.standard.string(forKey: "airDropSetting") || domain!.string(forKey: "DiscoverableMode") == "Off" { 96 | return 97 | 98 | } else { 99 | let ADATimer = UserDefaults.standard.integer(forKey: "timing") 100 | let fullTime = Double(ADATimer * 60) 101 | Logger.airdropstatus.info("ADA will change AirDrop Setting in \(fullTime) seconds to \(UserDefaults.standard.string(forKey: "airDropSetting") ?? "")") 102 | Logger.airdropstatus.info("ADA Timer Started") 103 | let clock = ContinuousClock() 104 | let now = clock.now 105 | let futureTime = now.advanced(by: .seconds(fullTime)) 106 | let tolerance: Duration = .seconds(0.5) 107 | 108 | Task { 109 | try await Task.sleep(until: futureTime, tolerance: tolerance, clock: clock) 110 | self.resetDiscoverableMode() 111 | } 112 | } 113 | } 114 | func resetDiscoverableMode() { 115 | 116 | source?.cancel() 117 | let nc = UNUserNotificationCenter.current() 118 | let domain = UserDefaults(suiteName: "com.apple.sharingd") 119 | guard let ADASetting = UserDefaults.standard.string(forKey: "airDropSetting") else { return } 120 | domain?.set(ADASetting, forKey: "DiscoverableMode") 121 | Logger.airdropstatus.info("Airdrop Status changed by ADA to \(ADASetting)") 122 | var airDropInUse = true 123 | repeat { 124 | let task = Process() 125 | task.launchPath = "/bin/bash" 126 | let command = """ 127 | /usr/sbin/lsof -c sharingd | /usr/bin/awk '$5 == "REG" && $4 ~ /[rw]/ && $9 !~ /AirDropHashDB|\\.plist|\\.loctable|\\.car|\\/System/' | /usr/bin/tail -1 128 | """ 129 | task.arguments = ["-c", command] 130 | let pipe = Pipe() 131 | task.standardOutput = pipe 132 | task.launch() 133 | task.waitUntilExit() 134 | 135 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 136 | let output = String(data: data, encoding: .utf8) ?? "" 137 | 138 | if output == "" { 139 | airDropInUse = false 140 | } else { 141 | Logger.airdropstatus.info("Airdrop in use, will try again in 5 seconds.") 142 | airDropInUse = true 143 | Thread.sleep(forTimeInterval: 5) 144 | } 145 | 146 | } while airDropInUse 147 | let process = Process() 148 | process.launchPath = "/usr/bin/killall" 149 | process.arguments = ["sharingd"] 150 | 151 | let outputPipe = Pipe() 152 | process.standardOutput = outputPipe 153 | let errorPipe = Pipe() 154 | process.standardError = errorPipe 155 | 156 | process.launch() 157 | process.waitUntilExit() 158 | if notificationsAllow{ 159 | Task { 160 | let settings = await nc.notificationSettings() 161 | guard (settings.authorizationStatus == .authorized) || 162 | (settings.authorizationStatus == .provisional) else 163 | { return } 164 | let content = UNMutableNotificationContent() 165 | content.title = "AirDrop Status Changed" 166 | content.body = ADASetting 167 | let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) 168 | 169 | let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) 170 | try await nc.add(request) 171 | } 172 | } 173 | self.startMonitoring() 174 | 175 | 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Air Drop Assistant/PreferenceObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferenceObserver.swift 3 | // Air Drop Assistant 4 | // 5 | // Created by Bob Gendler on 11/27/24. 6 | // 7 | import Combine 8 | import Foundation 9 | 10 | 11 | 12 | extension UserDefaults { 13 | @objc dynamic var hideMenuIcon: String? { 14 | return string(forKey: "hideMenuIcon") 15 | } 16 | 17 | @objc dynamic var icon_mode: String? { 18 | return string(forKey: "icon_mode") 19 | } 20 | 21 | @objc dynamic var disableUpdates: Bool { 22 | return bool(forKey: "disableUpdates") 23 | } 24 | 25 | @objc dynamic var timing: Int { 26 | return integer(forKey: "timing") 27 | } 28 | 29 | @objc dynamic var airDropSetting: String? { 30 | return string(forKey: "airDropSetting") 31 | } 32 | } 33 | 34 | protocol AppPrefObserverDelegate { 35 | func newPreferenceValue() 36 | } 37 | 38 | class AppPreferencesObserver { 39 | 40 | var delegate: AppPrefObserverDelegate? 41 | private var cancellables = Set() 42 | 43 | init() { 44 | let hideMenuIconPref = UserDefaults.standard.publisher(for: \.hideMenuIcon) 45 | let icon_modePref = UserDefaults.standard.publisher(for: \.icon_mode) 46 | let disableUpdatesPref = UserDefaults.standard.publisher(for: \.disableUpdates) 47 | let timingPref = UserDefaults.standard.publisher(for: \.timing) 48 | let airDropSettingPref = UserDefaults.standard.publisher(for: \.airDropSetting) 49 | // Combine the two publishers 50 | Publishers.CombineLatest3( 51 | Publishers.CombineLatest(hideMenuIconPref, icon_modePref), 52 | disableUpdatesPref, 53 | timingPref 54 | ) 55 | .combineLatest(airDropSettingPref) 56 | .sink { _ in 57 | self.delegate?.newPreferenceValue() 58 | } 59 | .store(in: &cancellables) 60 | } 61 | } 62 | 63 | // Usage 64 | 65 | -------------------------------------------------------------------------------- /Air Drop Assistant/PreferencesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesViewController.swift 3 | // Air Drop Assistant 4 | // 5 | // Created by Bob Gendler on 8/17/24. 6 | // 7 | 8 | 9 | import Cocoa 10 | import ServiceManagement 11 | 12 | protocol PrefDataModelDelegate { 13 | func didRecievePrefUpdate(iconMode: String) 14 | func checkAirDrop() 15 | func updatePF() 16 | } 17 | 18 | class PreferencesViewController: NSViewController { 19 | 20 | var pfADAPref: String? 21 | var delegate: PrefDataModelDelegate? 22 | var currentButton: Int? 23 | var previousRadioButton = NSButton() 24 | 25 | 26 | override func viewDidDisappear() { 27 | NSLog("Preferences Window closed") 28 | delegate?.checkAirDrop() 29 | } 30 | override func loadView() { 31 | loadPref() 32 | let rect = NSRect(x: 0, y: 0, width: 600, height: 200) 33 | view = NSView(frame: rect) 34 | view.wantsLayer = true 35 | let timelengthButton = NSPopUpButton(frame: NSRect(x: 20, y: 140, width: 150, height: 25), pullsDown: false) 36 | let prefTime = UserDefaults.standard.integer(forKey: "timing") 37 | guard let appBundleID = Bundle.main.bundleIdentifier else { return } 38 | if CFPreferencesAppValueIsForced("timing" as CFString, appBundleID as CFString) { 39 | timelengthButton.isEnabled = false 40 | } 41 | timelengthButton.addItem(withTitle: "1 Minute") 42 | timelengthButton.addItem(withTitle: "5 Minutes") 43 | timelengthButton.addItem(withTitle: "10 Minutes") 44 | timelengthButton.addItem(withTitle: "15 Minutes") 45 | 46 | if prefTime != 1 && prefTime != 5 && prefTime != 10 && prefTime != 15 { 47 | timelengthButton.addItem(withTitle: "\(prefTime) Minutes") 48 | 49 | } 50 | 51 | if prefTime == 1 { 52 | timelengthButton.selectItem(withTitle: "1 Minute") 53 | } else { 54 | timelengthButton.selectItem(withTitle: "\(prefTime) Minutes") 55 | } 56 | 57 | timelengthButton.action = #selector(timeLengthSelect) 58 | 59 | let timelengthLabel = NSTextField(frame: NSRect(x: 20, y: 160, width: 150, height: 25)) 60 | timelengthLabel.stringValue = "Select Time Length:" 61 | timelengthLabel.isBordered = false 62 | timelengthLabel.isBezeled = false 63 | timelengthLabel.isEditable = false 64 | timelengthLabel.drawsBackground = false 65 | 66 | view.addSubview(timelengthButton) 67 | view.addSubview(timelengthLabel) 68 | 69 | 70 | let airDropSettingButton = NSPopUpButton(frame: NSRect(x: 200, y: 140, width: 150, height: 25), pullsDown: false) 71 | if CFPreferencesAppValueIsForced("airDropSetting" as CFString, appBundleID as CFString) { 72 | airDropSettingButton.isEnabled = false 73 | } 74 | airDropSettingButton.addItem(withTitle: "Off") 75 | airDropSettingButton.addItem(withTitle: "Contacts Only") 76 | airDropSettingButton.selectItem(withTitle: "Contacts Only") 77 | if let defaultMenuItem = UserDefaults.standard.string(forKey: "airDropSetting") { 78 | airDropSettingButton.selectItem(withTitle: defaultMenuItem) 79 | } 80 | airDropSettingButton.action = #selector(airDropSelect) 81 | 82 | let airDropSettingLabel = NSTextField(frame: NSRect(x: 200, y: 160, width: 200, height: 25)) 83 | airDropSettingLabel.stringValue = "Select Setting:" 84 | airDropSettingLabel.isBordered = false 85 | airDropSettingLabel.isBezeled = false 86 | airDropSettingLabel.isEditable = false 87 | airDropSettingLabel.drawsBackground = false 88 | 89 | let iconLabel = NSTextField(frame: NSRect(x: 200, y: 110, width: 150, height: 25)) 90 | iconLabel.stringValue = "Select Icon:" 91 | iconLabel.isBordered = false 92 | iconLabel.isBezeled = false 93 | iconLabel.isEditable = false 94 | iconLabel.drawsBackground = false 95 | 96 | let colorfulIcon = NSImageView(frame:NSRect(x: 205, y:80, width: 50, height: 40)) 97 | let coloricon = NSImage(named: NSImage.Name("menuicon")) 98 | coloricon?.size.width = 18 99 | coloricon?.size.height = 18 100 | colorfulIcon.image = coloricon 101 | 102 | 103 | let monochromeIcon = NSImageView(frame:NSRect(x: 205, y:60, width: 50, height: 40)) 104 | 105 | 106 | let monoicon = NSImage(named: NSImage.Name("menuicon_mono")) 107 | monoicon?.size.width = 18 108 | monoicon?.size.height = 18 109 | monochromeIcon.image = monoicon 110 | 111 | 112 | let iconOneRadioButton = NSButton(radioButtonWithTitle: "", target: Any?.self, action: #selector(changeIcon)) 113 | iconOneRadioButton.frame = NSRect(x: 200, y: 90, width: 150, height: 25) 114 | // iconOneRadioButton.title = "Colorful" 115 | iconOneRadioButton.title = " Colorful" 116 | 117 | let iconTwoRadioButton = NSButton(radioButtonWithTitle: "", target: Any?.self, action: #selector(changeIcon)) 118 | iconTwoRadioButton.frame = NSRect(x: 200, y: 65, width: 150, height: 25) 119 | // iconTwoRadioButton.title = "Monochrome" 120 | iconTwoRadioButton.title = " Monochrome" 121 | if UserDefaults.standard.string(forKey: "icon_mode") == "bw" { 122 | iconTwoRadioButton.state = .on 123 | iconOneRadioButton.state = .off 124 | } else { 125 | iconOneRadioButton.state = .on 126 | iconTwoRadioButton.state = .off 127 | } 128 | if CFPreferencesAppValueIsForced("icon_mode" as CFString, appBundleID as CFString) { 129 | iconOneRadioButton.isEnabled = false 130 | iconTwoRadioButton.isEnabled = false 131 | } 132 | 133 | // let infoTextView = NSTextField(frame: NSRect(x: 188, y: -40, width: 300, height: 100)) 134 | let infoTextView = NSTextField(frame: NSRect(x: 385, y: 30, width: 300, height: 50)) 135 | infoTextView.font = NSFont.systemFont(ofSize: 16) 136 | infoTextView.isBordered = false 137 | infoTextView.isBezeled = false 138 | infoTextView.isEditable = false 139 | infoTextView.drawsBackground = false 140 | 141 | if let versionText = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { 142 | let infoString = """ 143 | Air Drop Assistant 144 | Version: \(versionText) 145 | """ 146 | infoTextView.stringValue = infoString 147 | } 148 | 149 | let linkTextView = NSTextView(frame: NSRect(x: 320, y: 10, width: 300, height: 25)) 150 | linkTextView.textContainerInset = NSSize(width: 10, height: 10) 151 | linkTextView.isEditable = false 152 | linkTextView.isSelectable = true 153 | linkTextView.drawsBackground = false 154 | let linkString = "https://github.com/boberito/AirDropAssistant" 155 | let linkAttributeString = NSMutableAttributedString(string: linkString) 156 | let url = URL(string: "https://github.com/boberito/AirDropAssistant")! 157 | let linkRange = (linkString as NSString).range(of: url.absoluteString) 158 | linkAttributeString.addAttribute(.link, value: url, range: linkRange) 159 | let boldFont = NSFont.systemFont(ofSize: 12) 160 | linkAttributeString.addAttribute(.font, value: boldFont, range: linkRange) 161 | linkTextView.textStorage?.setAttributedString(linkAttributeString) 162 | 163 | let restrictLabel = NSTextField(frame: NSRect(x: 20, y: 110, width: 150, height: 25)) 164 | restrictLabel.stringValue = "Restrict AirDrop:" 165 | restrictLabel.isBordered = false 166 | restrictLabel.isBezeled = false 167 | restrictLabel.isEditable = false 168 | restrictLabel.drawsBackground = false 169 | 170 | let restrictRadioButtonOne = NSButton(radioButtonWithTitle: "Allow Both Ways", target: Any.self, action: #selector(pfADARadio)) 171 | restrictRadioButtonOne.frame = NSRect(x: 20, y: 90, width: 150, height: 25) 172 | 173 | let restrictRadioButtonTwo = NSButton(radioButtonWithTitle: "Incoming Only", target: Any.self, action: #selector(pfADARadio)) 174 | restrictRadioButtonTwo.frame = NSRect(x: 20, y: 70, width: 150, height: 25) 175 | 176 | let restrictRadioButtonThree = NSButton(radioButtonWithTitle: "Outgoing Only", target: Any.self, action: #selector(pfADARadio)) 177 | restrictRadioButtonThree.frame = NSRect(x: 20, y:50, width: 150, height: 25) 178 | 179 | if pfADAPref == nil || pfADAPref == "off" { 180 | restrictRadioButtonOne.state = .on 181 | restrictRadioButtonTwo.state = .off 182 | restrictRadioButtonThree.state = .off 183 | previousRadioButton = restrictRadioButtonOne 184 | } 185 | if pfADAPref == "DisableOut" { 186 | restrictRadioButtonOne.state = .off 187 | restrictRadioButtonTwo.state = .on 188 | restrictRadioButtonThree.state = .off 189 | previousRadioButton = restrictRadioButtonTwo 190 | } 191 | if pfADAPref == "DisableIn" { 192 | restrictRadioButtonOne.state = .off 193 | restrictRadioButtonTwo.state = .off 194 | restrictRadioButtonThree.state = .on 195 | previousRadioButton = restrictRadioButtonThree 196 | } 197 | let startUpButton = NSButton(checkboxWithTitle: "Launch at Login", target: Any?.self, action: #selector(loginItemChange)) 198 | startUpButton.frame = NSRect(x: 20, y: 25, width: 200, height: 25) 199 | //140 200 | let appService = SMAppService.agent(plistName: "com.ttinc.Air-Drop-Assistant.plist") 201 | switch appService.status { 202 | case .enabled: 203 | startUpButton.intValue = 1 204 | 205 | case .notFound: 206 | startUpButton.intValue = 0 207 | 208 | case .notRegistered: 209 | startUpButton.intValue = 0 210 | 211 | case .requiresApproval: 212 | startUpButton.intValue = 0 213 | 214 | default: 215 | startUpButton.intValue = 0 216 | } 217 | 218 | let appIcon = NSImageView(frame:NSRect(x: 415, y:85, width: 100, height: 100)) 219 | appIcon.image = NSImage(named: "AppIcon") 220 | 221 | view.addSubview(iconLabel) 222 | view.addSubview(colorfulIcon) 223 | view.addSubview(monochromeIcon) 224 | view.addSubview(iconOneRadioButton) 225 | view.addSubview(iconTwoRadioButton) 226 | view.addSubview(appIcon) 227 | view.addSubview(infoTextView) 228 | view.addSubview(linkTextView) 229 | view.addSubview(startUpButton) 230 | view.addSubview(airDropSettingButton) 231 | view.addSubview(airDropSettingLabel) 232 | view.addSubview(restrictLabel) 233 | view.addSubview(restrictRadioButtonOne) 234 | view.addSubview(restrictRadioButtonTwo) 235 | view.addSubview(restrictRadioButtonThree) 236 | self.view = view 237 | 238 | } 239 | override func viewDidLoad() { 240 | super.viewDidLoad() 241 | // Do any additional setup after loading the view. 242 | } 243 | 244 | override var representedObject: Any? { 245 | didSet { 246 | // Update the view, if already loaded. 247 | } 248 | } 249 | @objc func airDropSelect(_ popUpButton: NSPopUpButton){ 250 | if let selected = popUpButton.titleOfSelectedItem { 251 | UserDefaults.standard.set(selected, forKey: "airDropSetting") 252 | } 253 | 254 | } 255 | func loadPref() { 256 | 257 | guard let bundleID = Bundle.main.bundleIdentifier else { return } 258 | let path = "/Library/Preferences/\(bundleID).plist" 259 | if FileManager.default.fileExists(atPath: path) { 260 | if let plist = NSDictionary(contentsOfFile: path) as? [String: Any] { 261 | if let adaPFValue = plist["ADA_PF"] as? String { 262 | pfADAPref = adaPFValue as String 263 | } 264 | } 265 | } 266 | } 267 | 268 | @objc func timeLengthSelect(_ popUpButton: NSPopUpButton){ 269 | if let selected = popUpButton.titleOfSelectedItem { 270 | let min = selected.split(separator: " ")[0] 271 | let minInt = Int(min)! 272 | UserDefaults.standard.set(minInt, forKey: "timing") 273 | } 274 | 275 | } 276 | 277 | 278 | @objc func pfADARadio(_ sender: NSButton) { 279 | switch sender.title { 280 | case "Allow Both Ways": 281 | if pfADAPref == "DisableOut" || pfADAPref == "DisableIn" { 282 | if !runPFScript(argument: "--remove") { 283 | sender.state = .off 284 | previousRadioButton.state = .on 285 | } else { 286 | previousRadioButton = sender 287 | } 288 | 289 | } 290 | case "Incoming Only": 291 | if pfADAPref != "DisableOut" { 292 | if !runPFScript(argument: "--blockOut") { 293 | sender.state = .off 294 | previousRadioButton.state = .on 295 | } else { 296 | previousRadioButton = sender 297 | } 298 | } 299 | case "Outgoing Only": 300 | if pfADAPref != "DisableIn" { 301 | if !runPFScript(argument: "--blockIn") { 302 | sender.state = .off 303 | previousRadioButton.state = .on 304 | } else { 305 | previousRadioButton = sender 306 | } 307 | } 308 | default: 309 | NSLog("You crazy you got here") 310 | } 311 | loadPref() 312 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 313 | self.delegate?.updatePF() 314 | } 315 | 316 | NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) 317 | self.view.window?.makeKeyAndOrderFront(nil) 318 | self.view.window?.orderFrontRegardless() 319 | } 320 | 321 | func runPFScript(argument: String) -> Bool { 322 | let resourcesPath = Bundle.main.resourceURL!.appendingPathComponent("ADA_PF_Helper_Script.sh").path 323 | NSLog("Script Path: \(resourcesPath)") 324 | 325 | let task = Process() 326 | let pipe = Pipe() 327 | 328 | task.standardOutput = pipe 329 | task.standardError = pipe 330 | task.arguments = [resourcesPath, argument] // Correct argument passing 331 | task.launchPath = "/bin/zsh" 332 | task.standardInput = nil 333 | 334 | task.launch() 335 | task.waitUntilExit() // Wait for the task to complete 336 | 337 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 338 | let output = String(data: data, encoding: .utf8)! 339 | if output.contains("User canceled."){ 340 | return false 341 | } 342 | return true 343 | } 344 | 345 | @objc func changeIcon(_ sender: NSButton) { 346 | //use UserDefaults 347 | 348 | if sender.title == " Monochrome" { 349 | UserDefaults.standard.set("bw", forKey: "icon_mode") 350 | 351 | self.delegate?.didRecievePrefUpdate(iconMode: "bw") 352 | } 353 | 354 | if sender.title == " Colorful" { 355 | UserDefaults.standard.set("colorful", forKey: "icon_mode") 356 | self.delegate?.didRecievePrefUpdate(iconMode: "colorful") 357 | 358 | } 359 | } 360 | 361 | @objc func loginItemChange(_ sender: NSButton) { 362 | let appService = SMAppService.agent(plistName: "com.ttinc.Air-Drop-Assistant.plist") 363 | 364 | if sender.intValue == 1 { 365 | 366 | do { 367 | try appService.register() 368 | NSLog("registered service") 369 | } catch { 370 | NSLog("problem registering service") 371 | } 372 | } else { 373 | 374 | let alert = NSAlert() 375 | alert.messageText = "Alert" 376 | alert.informativeText = """ 377 | Air Drop Assistant may quit and need reopened when Launch At Login is unselected. 378 | """ 379 | alert.runModal() 380 | 381 | do { 382 | if appService.status == .enabled { 383 | try appService.unregister() 384 | NSLog("unregistered service") 385 | } 386 | 387 | } catch { 388 | 389 | NSLog("problem unregistering service") 390 | } 391 | } 392 | } 393 | 394 | } 395 | -------------------------------------------------------------------------------- /Air Drop Assistant/PreferencesWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesWindow.swift 3 | // Air Drop Assistant 4 | // 5 | // Created by Bob Gendler on 8/17/24. 6 | // 7 | 8 | import Cocoa 9 | class PreferencesWindow: NSWindow { 10 | override func close() { 11 | self.orderOut(NSApp) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Air Drop Assistant/UpdateCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateCheck.swift 3 | // SC Menu 4 | // 5 | // Created by Bob Gendler on 3/25/24. 6 | // 7 | import Cocoa 8 | import os 9 | import OSLog 10 | 11 | struct githubData: Decodable { 12 | let tag_name: String 13 | } 14 | 15 | class UpdateCheck { 16 | 17 | func check() -> Int{ 18 | let sc_menuURL = "https://api.github.com/repos/boberito/AirDropAssistant/releases/latest" 19 | var request = URLRequest(url: URL(string: sc_menuURL)!) 20 | request.timeoutInterval = 3.0 21 | let dispatchGroup = DispatchGroup() 22 | dispatchGroup.enter() 23 | var version: String? = nil 24 | var updateNeeded = 0 25 | let session = URLSession.shared 26 | let task = session.dataTask(with: request as URLRequest) {data,response,error in 27 | let httpResponse = response as? HTTPURLResponse 28 | let dataReturn = data 29 | if (error != nil) { 30 | Logger.updater.error("An Error Occured - offline or can't reach GitHub") 31 | updateNeeded = 2 32 | dispatchGroup.leave() 33 | } else { 34 | do { 35 | switch httpResponse!.statusCode { 36 | case 200: 37 | let decoder = JSONDecoder() 38 | if let githubData = try? decoder.decode(githubData.self, from: dataReturn!) { 39 | version = githubData.tag_name 40 | if let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, let gitHubVersion = version { 41 | let versionCompare = currentVersion.compare(gitHubVersion, options: .numeric) 42 | if versionCompare == .orderedSame { 43 | Logger.updater.info("ADA is update to date") 44 | updateNeeded = 0 45 | } else if versionCompare == .orderedAscending { 46 | DispatchQueue.main.async { 47 | self.alert(githubVersion: gitHubVersion, current: currentVersion) 48 | } 49 | Logger.updater.info("Current is \(currentVersion.description), newest is \(gitHubVersion.description)") 50 | updateNeeded = 1 51 | } else if versionCompare == .orderedDescending { 52 | Logger.updater.info("Current is \(currentVersion.description), newest is \(gitHubVersion.description)") 53 | updateNeeded = 0 54 | } 55 | } 56 | } 57 | dispatchGroup.leave() 58 | default: 59 | Logger.updater.error("Offline or cannot reach GitHub") 60 | updateNeeded = 2 61 | dispatchGroup.leave() 62 | } 63 | } 64 | } 65 | } 66 | task.resume() 67 | dispatchGroup.wait() 68 | 69 | return updateNeeded 70 | } 71 | 72 | func alert(githubVersion: String, current: String) { 73 | let alert = NSAlert() 74 | alert.messageText = "Update Available" 75 | alert.informativeText = """ 76 | An update is available for Air Drop Assistant. 77 | 78 | Current version is \(current). 79 | Newest version is \(githubVersion). 80 | """ 81 | alert.addButton(withTitle: "Update") 82 | alert.addButton(withTitle: "Later") 83 | let modalResult = alert.runModal() 84 | 85 | switch modalResult { 86 | case .alertFirstButtonReturn: // NSApplication.ModalResponse.alertFirstButtonReturn 87 | if let url = URL(string: "https://github.com/boberito/AirDropAssistant/releases") { 88 | NSWorkspace.shared.open(url) 89 | } 90 | case .alertSecondButtonReturn: 91 | Logger.general.log("Update later") 92 | 93 | default: 94 | Logger.general.debug("Somehow closed the alert without pushing a button") 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /Air Drop Assistant/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Air Drop Assistant 4 | // 5 | // Created by Bob Gendler on 8/16/24. 6 | // 7 | 8 | import Cocoa 9 | 10 | class ViewController: NSViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | override var representedObject: Any? { 19 | didSet { 20 | // Update the view, if already loaded. 21 | } 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Air Drop Assistant/com.ttinc.Air-Drop-Assistant.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | KeepAlive 6 | 7 | Label 8 | com.ttinc.Air-Drop-Assistant 9 | RunAtLoad 10 | 11 | BundleProgram 12 | Contents/MacOS/Air Drop Assistant 13 | ProcessType 14 | Interactive 15 | LimitLoadToSessionType 16 | Aqua 17 | 18 | 19 | -------------------------------------------------------------------------------- /Air Drop Assistant/menuicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/menuicon.png -------------------------------------------------------------------------------- /Air Drop Assistant/menuicon_mono.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/Air Drop Assistant/menuicon_mono.png -------------------------------------------------------------------------------- /Air-Drop-Assistant-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSMultipleInstancesProhibited 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Bob Gendler 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | Air Drop Assistant (ADA) gives you more granular control over your AirDrop Settings than normal MDM controls. 6 | 7 | On iOS, when switching AirDrop to Everyone, it switches back to Contacts Only after 10 minutes. 8 | 9 | On macOS, AirDrop is either Off, Contacts Only, or Everyone - a setting you have to toggle manually via System Settings > General > AirDrop & Handoff or from the Control Center menu item. 10 | 11 | ADA allows you to set, if AirDrop goes to Contacts Only or Off and the time frame to switch it. 12 | 13 | Why might you want this feature? 14 | - You use your computer frequently in public spaces and do not want AirDrop set to be left open to receive from Everyone. 15 | - Organizations allow AirDrop but want more granular control than Apple's device management gives you. 16 | 17 | 18 |
19 | 20 |
21 | 22 | ADA can be configured by configuration profile deployed by your MDM. 23 | 24 | **Domain** `com.ttinc.Air-Drop-Assistant` 25 | 26 | `airDropSetting` - **Type: String** 27 | Acceptable Value: **Contacts Only** or **Off** 28 | - When set ADA will be locked to switch AirDrop to that settings after the timer. 29 | 30 | `timing` - **Type: Integer** 31 | Acceptable Value: **0-1000** 32 | - Time length before ADA will switch AirDrop to the value from `airDropSetting` 33 | 34 | `hideMenuIcon` - **Type: Boolean** 35 | Acceptable Value: **true** or **false** 36 | - Hides the menu bar icon 37 | 38 | `icon_mode` - **Type: String** 39 | Acceptable Value: **colorful** or **bw** 40 | - Makes the icon colorful or monochrome. 41 | 42 | `afterFirstLaunch` - **Type: Boolean** 43 | Acceptable Value: **true** or **false** 44 | - Hides the first launch question. 45 | 46 | `disableUpdates` - **Type: Boolean** 47 | Acceptable Value: **true** or **false** 48 | - Disables update checking at launch and hides menu item to check. 49 | 50 | ADA can also be registered to load at login with the argument register and removed from login items as well. 51 | 52 | `/Applications/Utilities/Air\ Drop\ Assistant.app/Contents/MacOS/Air\ Drop\ Assistant --register` 53 | 54 | and 55 | 56 | `/Applications/Utilities/Air\ Drop\ Assistant.app/Contents/MacOS/Air\ Drop\ Assistant --unregister` 57 | 58 | ADA AirDrop directional restriction can be configured by MDM. 59 | 60 | `/Applications/Utilities/Air\ Drop\ Assistant.app/Contents/Resources/ADA_PF_Helper_Script.sh -argument` 61 | 62 | - `-blockOut` blocks outgoing AirDrop 63 | - `-blockIn` blocks incoming AirDrop 64 | - `-remove` allows AirDrop both directions 65 | 66 | Example mobileconfig 67 | ``` 68 | 69 | 70 | 71 | 72 | PayloadContent 73 | 74 | 75 | PayloadIdentifier 76 | ccom.ttinc.Air-Drop-Assistant.26F7424C-FEF3-4E94-8E78-2673B2B8CDD3 77 | PayloadType 78 | com.ttinc.Air-Drop-Assistant 79 | PayloadUUID 80 | 26F7424C-FEF3-4E94-8E78-2673B2B8CDD3 81 | PayloadVersion 82 | 1 83 | airDropSetting 84 | Contacts Only 85 | timing 86 | 5 87 | hideMenuIcon 88 | 89 | icon_mode 90 | colorful 91 | afterFirstLaunch 92 | 93 | 94 | 95 | PayloadDescription 96 | ADA Settings 97 | PayloadDisplayName 98 | ADA 99 | PayloadIdentifier 100 | com.ttinc.Air-Drop-Assistant 101 | PayloadOrganization 102 | BOB 103 | PayloadScope 104 | System 105 | PayloadType 106 | Configuration 107 | PayloadUUID 108 | 1BBD23DE-7ED7-45DF-8126-5085A681ED83 109 | PayloadVersion 110 | 1 111 | 112 | 113 | ``` 114 | 115 | To configuration notifications you'll want to create a `com.apple.notificationsettings` profile to include these keys 116 | ``` 117 | 118 | BundleIdentifier 119 | com.ttinc.Air-Drop-Assistant 120 | 121 | 122 | NotificationsEnabled 123 | 124 | CriticalAlertEnabled 125 | 126 | 127 | 128 | AlertType 129 | 1 130 | 131 | ShowInLockScreen 132 | 133 | ShowInNotificationCenter 134 | 135 | ``` -------------------------------------------------------------------------------- /adamenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/adamenu.png -------------------------------------------------------------------------------- /adaprefs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/adaprefs.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boberito/AirDropAssistant/ccda7c8446cb58b74765525679d6c373b0d15493/icon.png --------------------------------------------------------------------------------