├── .DS_Store ├── .gitignore ├── DiscordX.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ ├── WorkspaceSettings.xcsettings │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ └── asad.xcuserdatad │ │ ├── IDEFindNavigatorScopes.plist │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── DiscordX.xcscheme └── xcuserdata │ └── asad.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── DiscordX ├── .DS_Store ├── AppDelegate.swift ├── Assets.xcassets │ ├── .DS_Store │ ├── AppIcon.appiconset │ │ ├── 128 copy.png │ │ ├── 1d79cbcdd7d6e1d59f437b1a79db9f2f_Q0SQBf1rNB 1.png │ │ ├── 1d79cbcdd7d6e1d59f437b1a79db9f2f_Q0SQBf1rNB.png │ │ ├── Contents.json │ │ ├── cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 1.png │ │ ├── cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 2.png │ │ ├── cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 3.png │ │ ├── cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 4.png │ │ ├── cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 5.png │ │ ├── cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 6.png │ │ └── cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V.png │ └── Contents.json ├── ContentView.swift ├── CoreData │ ├── DiscordX.xcdatamodeld │ │ └── DiscordX.xcdatamodel │ │ │ └── contents │ └── Persistence.swift ├── DiscordX.entitlements ├── DiscordX.swift ├── Info.plist ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json └── Supporting Files │ ├── Applescript.swift │ ├── Constants.swift │ └── HelperFunctions.swift ├── LICENSE └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /DiscordX.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5028EA45244B181D003A1830 /* HelperFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5028EA44244B181D003A1830 /* HelperFunctions.swift */; }; 11 | 5068E2DF244AA3C90012CCF7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5068E2DE244AA3C90012CCF7 /* Constants.swift */; }; 12 | 50C6CBB3244974A400F10C5D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C6CBB2244974A400F10C5D /* AppDelegate.swift */; }; 13 | 50C6CBB7244974A700F10C5D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50C6CBB6244974A700F10C5D /* Assets.xcassets */; }; 14 | 50C6CBBA244974A700F10C5D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50C6CBB9244974A700F10C5D /* Preview Assets.xcassets */; }; 15 | 50C6CBC9244975F200F10C5D /* Applescript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C6CBC8244975F200F10C5D /* Applescript.swift */; }; 16 | C3110E1E27E9E95700F5F30A /* DiscordX.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3110E1D27E9E95700F5F30A /* DiscordX.swift */; }; 17 | C3110E2027E9EA6B00F5F30A /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3110E1F27E9EA6B00F5F30A /* Persistence.swift */; }; 18 | C3110E2227E9EB6A00F5F30A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3110E2127E9EB6A00F5F30A /* ContentView.swift */; }; 19 | C3110E2527E9EC3900F5F30A /* DiscordX.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C3110E2327E9EC3900F5F30A /* DiscordX.xcdatamodeld */; }; 20 | C3DCE3CB251FBE3F008A8718 /* SwordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = C3DCE3CA251FBE3F008A8718 /* SwordRPC */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 5028EA44244B181D003A1830 /* HelperFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperFunctions.swift; sourceTree = ""; }; 25 | 5068E2DE244AA3C90012CCF7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 26 | 50C6CBAF244974A400F10C5D /* DiscordX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DiscordX.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 50C6CBB2244974A400F10C5D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 28 | 50C6CBB6244974A700F10C5D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | 50C6CBB9244974A700F10C5D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 30 | 50C6CBBE244974A700F10C5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 50C6CBBF244974A700F10C5D /* DiscordX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DiscordX.entitlements; sourceTree = ""; }; 32 | 50C6CBC8244975F200F10C5D /* Applescript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Applescript.swift; sourceTree = ""; }; 33 | C3110E1D27E9E95700F5F30A /* DiscordX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordX.swift; sourceTree = ""; }; 34 | C3110E1F27E9EA6B00F5F30A /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; 35 | C3110E2127E9EB6A00F5F30A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 36 | C3110E2427E9EC3900F5F30A /* DiscordX.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DiscordX.xcdatamodel; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 50C6CBAC244974A400F10C5D /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | C3DCE3CB251FBE3F008A8718 /* SwordRPC in Frameworks */, 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | 50C6CBA6244974A400F10C5D = { 52 | isa = PBXGroup; 53 | children = ( 54 | 50C6CBB1244974A400F10C5D /* DiscordX */, 55 | 50C6CBB0244974A400F10C5D /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | 50C6CBB0244974A400F10C5D /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 50C6CBAF244974A400F10C5D /* DiscordX.app */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | 50C6CBB1244974A400F10C5D /* DiscordX */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 50C6CBB2244974A400F10C5D /* AppDelegate.swift */, 71 | C3110E2127E9EB6A00F5F30A /* ContentView.swift */, 72 | C3110E1D27E9E95700F5F30A /* DiscordX.swift */, 73 | C3110E2727EA19FC00F5F30A /* Supporting Files */, 74 | C3110E2627EA19D400F5F30A /* CoreData */, 75 | 50C6CBB6244974A700F10C5D /* Assets.xcassets */, 76 | 50C6CBBE244974A700F10C5D /* Info.plist */, 77 | 50C6CBBF244974A700F10C5D /* DiscordX.entitlements */, 78 | 50C6CBB8244974A700F10C5D /* Preview Content */, 79 | ); 80 | path = DiscordX; 81 | sourceTree = ""; 82 | }; 83 | 50C6CBB8244974A700F10C5D /* Preview Content */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 50C6CBB9244974A700F10C5D /* Preview Assets.xcassets */, 87 | ); 88 | path = "Preview Content"; 89 | sourceTree = ""; 90 | }; 91 | C3110E2627EA19D400F5F30A /* CoreData */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | C3110E2327E9EC3900F5F30A /* DiscordX.xcdatamodeld */, 95 | C3110E1F27E9EA6B00F5F30A /* Persistence.swift */, 96 | ); 97 | path = CoreData; 98 | sourceTree = ""; 99 | }; 100 | C3110E2727EA19FC00F5F30A /* Supporting Files */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 50C6CBC8244975F200F10C5D /* Applescript.swift */, 104 | 5068E2DE244AA3C90012CCF7 /* Constants.swift */, 105 | 5028EA44244B181D003A1830 /* HelperFunctions.swift */, 106 | ); 107 | path = "Supporting Files"; 108 | sourceTree = ""; 109 | }; 110 | /* End PBXGroup section */ 111 | 112 | /* Begin PBXNativeTarget section */ 113 | 50C6CBAE244974A400F10C5D /* DiscordX */ = { 114 | isa = PBXNativeTarget; 115 | buildConfigurationList = 50C6CBC2244974A700F10C5D /* Build configuration list for PBXNativeTarget "DiscordX" */; 116 | buildPhases = ( 117 | 50C6CBAB244974A400F10C5D /* Sources */, 118 | 50C6CBAC244974A400F10C5D /* Frameworks */, 119 | 50C6CBAD244974A400F10C5D /* Resources */, 120 | ); 121 | buildRules = ( 122 | ); 123 | dependencies = ( 124 | ); 125 | name = DiscordX; 126 | packageProductDependencies = ( 127 | C3DCE3CA251FBE3F008A8718 /* SwordRPC */, 128 | ); 129 | productName = RPFX; 130 | productReference = 50C6CBAF244974A400F10C5D /* DiscordX.app */; 131 | productType = "com.apple.product-type.application"; 132 | }; 133 | /* End PBXNativeTarget section */ 134 | 135 | /* Begin PBXProject section */ 136 | 50C6CBA7244974A400F10C5D /* Project object */ = { 137 | isa = PBXProject; 138 | attributes = { 139 | LastSwiftUpdateCheck = 1110; 140 | LastUpgradeCheck = 1200; 141 | ORGANIZATIONNAME = "Asad Azam"; 142 | TargetAttributes = { 143 | 50C6CBAE244974A400F10C5D = { 144 | CreatedOnToolsVersion = 11.1; 145 | LastSwiftMigration = 1110; 146 | }; 147 | }; 148 | }; 149 | buildConfigurationList = 50C6CBAA244974A400F10C5D /* Build configuration list for PBXProject "DiscordX" */; 150 | compatibilityVersion = "Xcode 9.3"; 151 | developmentRegion = en; 152 | hasScannedForEncodings = 0; 153 | knownRegions = ( 154 | en, 155 | Base, 156 | ); 157 | mainGroup = 50C6CBA6244974A400F10C5D; 158 | packageReferences = ( 159 | C3DCE3C9251FBE3F008A8718 /* XCRemoteSwiftPackageReference "SwordRPC" */, 160 | ); 161 | productRefGroup = 50C6CBB0244974A400F10C5D /* Products */; 162 | projectDirPath = ""; 163 | projectRoot = ""; 164 | targets = ( 165 | 50C6CBAE244974A400F10C5D /* DiscordX */, 166 | ); 167 | }; 168 | /* End PBXProject section */ 169 | 170 | /* Begin PBXResourcesBuildPhase section */ 171 | 50C6CBAD244974A400F10C5D /* Resources */ = { 172 | isa = PBXResourcesBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | 50C6CBBA244974A700F10C5D /* Preview Assets.xcassets in Resources */, 176 | 50C6CBB7244974A700F10C5D /* Assets.xcassets in Resources */, 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | }; 180 | /* End PBXResourcesBuildPhase section */ 181 | 182 | /* Begin PBXSourcesBuildPhase section */ 183 | 50C6CBAB244974A400F10C5D /* Sources */ = { 184 | isa = PBXSourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | 5028EA45244B181D003A1830 /* HelperFunctions.swift in Sources */, 188 | 50C6CBB3244974A400F10C5D /* AppDelegate.swift in Sources */, 189 | C3110E2227E9EB6A00F5F30A /* ContentView.swift in Sources */, 190 | 50C6CBC9244975F200F10C5D /* Applescript.swift in Sources */, 191 | C3110E2027E9EA6B00F5F30A /* Persistence.swift in Sources */, 192 | C3110E2527E9EC3900F5F30A /* DiscordX.xcdatamodeld in Sources */, 193 | C3110E1E27E9E95700F5F30A /* DiscordX.swift in Sources */, 194 | 5068E2DF244AA3C90012CCF7 /* Constants.swift in Sources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXSourcesBuildPhase section */ 199 | 200 | /* Begin XCBuildConfiguration section */ 201 | 50C6CBC0244974A700F10C5D /* Debug */ = { 202 | isa = XCBuildConfiguration; 203 | buildSettings = { 204 | ALWAYS_SEARCH_USER_PATHS = NO; 205 | CLANG_ANALYZER_NONNULL = YES; 206 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 207 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 208 | CLANG_CXX_LIBRARY = "libc++"; 209 | CLANG_ENABLE_MODULES = YES; 210 | CLANG_ENABLE_OBJC_ARC = YES; 211 | CLANG_ENABLE_OBJC_WEAK = YES; 212 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 213 | CLANG_WARN_BOOL_CONVERSION = YES; 214 | CLANG_WARN_COMMA = YES; 215 | CLANG_WARN_CONSTANT_CONVERSION = YES; 216 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 217 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 218 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 219 | CLANG_WARN_EMPTY_BODY = YES; 220 | CLANG_WARN_ENUM_CONVERSION = YES; 221 | CLANG_WARN_INFINITE_RECURSION = YES; 222 | CLANG_WARN_INT_CONVERSION = YES; 223 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 224 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 225 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 226 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 227 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 228 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 229 | CLANG_WARN_STRICT_PROTOTYPES = YES; 230 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 231 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 232 | CLANG_WARN_UNREACHABLE_CODE = YES; 233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 234 | COPY_PHASE_STRIP = NO; 235 | DEBUG_INFORMATION_FORMAT = dwarf; 236 | ENABLE_STRICT_OBJC_MSGSEND = YES; 237 | ENABLE_TESTABILITY = YES; 238 | GCC_C_LANGUAGE_STANDARD = gnu11; 239 | GCC_DYNAMIC_NO_PIC = NO; 240 | GCC_NO_COMMON_BLOCKS = YES; 241 | GCC_OPTIMIZATION_LEVEL = s; 242 | GCC_PREPROCESSOR_DEFINITIONS = ( 243 | "DEBUG=1", 244 | "$(inherited)", 245 | ); 246 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 247 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 248 | GCC_WARN_UNDECLARED_SELECTOR = YES; 249 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 250 | GCC_WARN_UNUSED_FUNCTION = YES; 251 | GCC_WARN_UNUSED_VARIABLE = YES; 252 | MACOSX_DEPLOYMENT_TARGET = 11.0; 253 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 254 | MTL_FAST_MATH = YES; 255 | ONLY_ACTIVE_ARCH = YES; 256 | SDKROOT = macosx; 257 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 258 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 259 | }; 260 | name = Debug; 261 | }; 262 | 50C6CBC1244974A700F10C5D /* Release */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_ANALYZER_NONNULL = YES; 267 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 268 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 269 | CLANG_CXX_LIBRARY = "libc++"; 270 | CLANG_ENABLE_MODULES = YES; 271 | CLANG_ENABLE_OBJC_ARC = YES; 272 | CLANG_ENABLE_OBJC_WEAK = YES; 273 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 274 | CLANG_WARN_BOOL_CONVERSION = YES; 275 | CLANG_WARN_COMMA = YES; 276 | CLANG_WARN_CONSTANT_CONVERSION = YES; 277 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 279 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 280 | CLANG_WARN_EMPTY_BODY = YES; 281 | CLANG_WARN_ENUM_CONVERSION = YES; 282 | CLANG_WARN_INFINITE_RECURSION = YES; 283 | CLANG_WARN_INT_CONVERSION = YES; 284 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 285 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 286 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 287 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 288 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 289 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 290 | CLANG_WARN_STRICT_PROTOTYPES = YES; 291 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 292 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | COPY_PHASE_STRIP = NO; 296 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 297 | ENABLE_NS_ASSERTIONS = NO; 298 | ENABLE_STRICT_OBJC_MSGSEND = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu11; 300 | GCC_NO_COMMON_BLOCKS = YES; 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | MACOSX_DEPLOYMENT_TARGET = 11.0; 308 | MTL_ENABLE_DEBUG_INFO = NO; 309 | MTL_FAST_MATH = YES; 310 | SDKROOT = macosx; 311 | SWIFT_COMPILATION_MODE = wholemodule; 312 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 313 | }; 314 | name = Release; 315 | }; 316 | 50C6CBC3244974A700F10C5D /* Debug */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 320 | CODE_SIGN_ENTITLEMENTS = DiscordX/DiscordX.entitlements; 321 | CODE_SIGN_IDENTITY = "Apple Development"; 322 | CODE_SIGN_STYLE = Automatic; 323 | COMBINE_HIDPI_IMAGES = YES; 324 | CURRENT_PROJECT_VERSION = 2020.9.30; 325 | DEVELOPMENT_ASSET_PATHS = "\"DiscordX/Preview Content\""; 326 | DEVELOPMENT_TEAM = CQZU37S3DP; 327 | ENABLE_HARDENED_RUNTIME = YES; 328 | ENABLE_PREVIEWS = YES; 329 | INFOPLIST_FILE = DiscordX/Info.plist; 330 | LD_RUNPATH_SEARCH_PATHS = ( 331 | "$(inherited)", 332 | "@executable_path/../Frameworks", 333 | ); 334 | MACOSX_DEPLOYMENT_TARGET = 11.0; 335 | MARKETING_VERSION = 1.0.3; 336 | PRODUCT_BUNDLE_IDENTIFIER = com.asad.DiscordX; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | SWIFT_VERSION = 5.0; 339 | }; 340 | name = Debug; 341 | }; 342 | 50C6CBC4244974A700F10C5D /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 346 | CODE_SIGN_ENTITLEMENTS = DiscordX/DiscordX.entitlements; 347 | CODE_SIGN_IDENTITY = "Apple Development"; 348 | CODE_SIGN_STYLE = Automatic; 349 | COMBINE_HIDPI_IMAGES = YES; 350 | CURRENT_PROJECT_VERSION = 2020.9.30; 351 | DEVELOPMENT_ASSET_PATHS = "\"DiscordX/Preview Content\""; 352 | DEVELOPMENT_TEAM = CQZU37S3DP; 353 | ENABLE_HARDENED_RUNTIME = YES; 354 | ENABLE_PREVIEWS = YES; 355 | INFOPLIST_FILE = DiscordX/Info.plist; 356 | LD_RUNPATH_SEARCH_PATHS = ( 357 | "$(inherited)", 358 | "@executable_path/../Frameworks", 359 | ); 360 | MACOSX_DEPLOYMENT_TARGET = 11.0; 361 | MARKETING_VERSION = 1.0.3; 362 | PRODUCT_BUNDLE_IDENTIFIER = com.asad.DiscordX; 363 | PRODUCT_NAME = "$(TARGET_NAME)"; 364 | SWIFT_VERSION = 5.0; 365 | }; 366 | name = Release; 367 | }; 368 | /* End XCBuildConfiguration section */ 369 | 370 | /* Begin XCConfigurationList section */ 371 | 50C6CBAA244974A400F10C5D /* Build configuration list for PBXProject "DiscordX" */ = { 372 | isa = XCConfigurationList; 373 | buildConfigurations = ( 374 | 50C6CBC0244974A700F10C5D /* Debug */, 375 | 50C6CBC1244974A700F10C5D /* Release */, 376 | ); 377 | defaultConfigurationIsVisible = 0; 378 | defaultConfigurationName = Debug; 379 | }; 380 | 50C6CBC2244974A700F10C5D /* Build configuration list for PBXNativeTarget "DiscordX" */ = { 381 | isa = XCConfigurationList; 382 | buildConfigurations = ( 383 | 50C6CBC3244974A700F10C5D /* Debug */, 384 | 50C6CBC4244974A700F10C5D /* Release */, 385 | ); 386 | defaultConfigurationIsVisible = 0; 387 | defaultConfigurationName = Debug; 388 | }; 389 | /* End XCConfigurationList section */ 390 | 391 | /* Begin XCRemoteSwiftPackageReference section */ 392 | C3DCE3C9251FBE3F008A8718 /* XCRemoteSwiftPackageReference "SwordRPC" */ = { 393 | isa = XCRemoteSwiftPackageReference; 394 | repositoryURL = "https://github.com/PKBeam/SwordRPC.git"; 395 | requirement = { 396 | branch = master; 397 | kind = branch; 398 | }; 399 | }; 400 | /* End XCRemoteSwiftPackageReference section */ 401 | 402 | /* Begin XCSwiftPackageProductDependency section */ 403 | C3DCE3CA251FBE3F008A8718 /* SwordRPC */ = { 404 | isa = XCSwiftPackageProductDependency; 405 | package = C3DCE3C9251FBE3F008A8718 /* XCRemoteSwiftPackageReference "SwordRPC" */; 406 | productName = SwordRPC; 407 | }; 408 | /* End XCSwiftPackageProductDependency section */ 409 | 410 | /* Begin XCVersionGroup section */ 411 | C3110E2327E9EC3900F5F30A /* DiscordX.xcdatamodeld */ = { 412 | isa = XCVersionGroup; 413 | children = ( 414 | C3110E2427E9EC3900F5F30A /* DiscordX.xcdatamodel */, 415 | ); 416 | currentVersion = C3110E2427E9EC3900F5F30A /* DiscordX.xcdatamodel */; 417 | path = DiscordX.xcdatamodeld; 418 | sourceTree = ""; 419 | versionGroupType = wrapper.xcdatamodel; 420 | }; 421 | /* End XCVersionGroup section */ 422 | }; 423 | rootObject = 50C6CBA7244974A400F10C5D /* Project object */; 424 | } 425 | -------------------------------------------------------------------------------- /DiscordX.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DiscordX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DiscordX.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DiscordX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "bluesocket", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/Kitura/BlueSocket.git", 7 | "state" : { 8 | "revision" : "dd924c3bc2c1c144c42b8dda3896f1a03115ded4", 9 | "version" : "2.0.2" 10 | } 11 | }, 12 | { 13 | "identity" : "swift-argument-parser", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/apple/swift-argument-parser", 16 | "state" : { 17 | "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", 18 | "version" : "0.5.0" 19 | } 20 | }, 21 | { 22 | "identity" : "swordrpc", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/PKBeam/SwordRPC.git", 25 | "state" : { 26 | "branch" : "master", 27 | "revision" : "f9831261fcbe6a17243f8839921fb7820e99fa6e" 28 | } 29 | } 30 | ], 31 | "version" : 2 32 | } 33 | -------------------------------------------------------------------------------- /DiscordX.xcodeproj/project.xcworkspace/xcuserdata/asad.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /DiscordX.xcodeproj/project.xcworkspace/xcuserdata/asad.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX.xcodeproj/project.xcworkspace/xcuserdata/asad.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /DiscordX.xcodeproj/xcshareddata/xcschemes/DiscordX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /DiscordX.xcodeproj/xcuserdata/asad.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DiscordX.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 50C6CBAE244974A400F10C5D 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /DiscordX/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/.DS_Store -------------------------------------------------------------------------------- /DiscordX/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DiscordX 4 | // 5 | // Created by Asad Azam on 28/9/20. 6 | // Copyright © 2021 Asad Azam. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SwordRPC 11 | import SwiftUI 12 | 13 | class AppViewModel: ObservableObject { 14 | @Published var showPopover = false 15 | } 16 | 17 | enum RefreshConfigurable: Int { 18 | case strict = 0 19 | case flaunt 20 | 21 | var message: String { 22 | switch self { 23 | case .strict: 24 | return "Timer will only keep the time you were active on Xcode" 25 | case .flaunt: 26 | return "Timer will not stop on Sleep and Wakeup of MacOS" 27 | } 28 | } 29 | } 30 | 31 | //@NSApplicationMain 32 | class AppDelegate: NSObject, NSApplicationDelegate { 33 | 34 | var window: NSWindow! 35 | var timer: Timer? 36 | var rpc: SwordRPC? 37 | var startDate: Date? 38 | var inactiveDate: Date? 39 | var lastWindow: String? 40 | var notifCenter = NSWorkspace.shared.notificationCenter 41 | 42 | var statusItem: NSStatusItem! 43 | 44 | var isRelaunch: Bool = false 45 | 46 | func beginTimer() { 47 | timer = Timer(timeInterval: TimeInterval(refreshInterval), repeats: true, block: { _ in 48 | self.updateStatus() 49 | // print(Date()) 50 | }) 51 | RunLoop.main.add(timer!, forMode: .common) 52 | timer!.fire() 53 | } 54 | 55 | func clearTimer() { 56 | timer?.invalidate() 57 | } 58 | 59 | func updateStatus() { 60 | var p = RichPresence() 61 | 62 | let an = getActiveWindow() // an -> Application Name 63 | 64 | let fn = getActiveFilename() //fn -> File Name 65 | let ws = getActiveWorkspace() //ws -> Workspace 66 | 67 | // print("Application Name: \(an ?? "")\nFile Name: \(fn ?? "")\nWorkspace: \(ws ?? "")\n") 68 | 69 | // determine file type 70 | if fn != nil && an == "Xcode" { 71 | p.details = "Editing \(fn!)" 72 | if let fileExt = getFileExt(fn!), discordRPImageKeys.contains(fileExt) { 73 | p.assets.largeImage = fileExt 74 | p.assets.smallImage = discordRPImageKeyXcode 75 | } else { 76 | p.assets.largeImage = discordRPImageKeyDefault 77 | } 78 | } else { 79 | if let appName = an, xcodeWindowNames.contains(appName) { 80 | p.details = "Using \(appName)" 81 | p.assets.largeImage = appName.replacingOccurrences(of: "\\s", with: "", options: .regularExpression).lowercased() 82 | p.assets.smallImage = discordRPImageKeyXcode 83 | } 84 | } 85 | 86 | // determine workspace type 87 | if ws != nil { 88 | if an == "Xcode"{ 89 | if ws != "Untitled" { 90 | p.state = "in \(withoutFileExt(ws!))" 91 | lastWindow = ws! 92 | } 93 | } else { 94 | p.assets.smallImage = discordRPImageKeyXcode 95 | p.assets.largeImage = discordRPImageKeyDefault 96 | p.state = "Working on \(withoutFileExt((lastWindow ?? ws) ?? "?" ))" 97 | } 98 | } 99 | 100 | // Xcode was just launched? 101 | if fn == nil && ws == nil { 102 | p.assets.largeImage = discordRPImageKeyXcode 103 | p.details = "No file open" 104 | } 105 | 106 | p.timestamps.start = startDate! 107 | p.timestamps.end = nil 108 | rpc!.setPresence(p) 109 | // print("updating RP") 110 | } 111 | 112 | func initRPC() { 113 | // init discord stuff 114 | rpc = SwordRPC.init(appId: discordClientId) 115 | rpc!.delegate = self 116 | rpc!.connect() 117 | } 118 | 119 | func deinitRPC() { 120 | self.rpc!.setPresence(RichPresence()) 121 | self.rpc!.disconnect() 122 | self.rpc = nil 123 | } 124 | 125 | struct ContentView: View { 126 | @State var refreshConfigurable: RefreshConfigurable 127 | var appDelegate: AppDelegate 128 | 129 | init(_ appDelegate: AppDelegate) { 130 | self.appDelegate = appDelegate 131 | if strictMode { 132 | refreshConfigurable = .strict 133 | } else if flauntMode { 134 | refreshConfigurable = .flaunt 135 | } else { 136 | fatalError("Unspecified refresh type") 137 | } 138 | } 139 | 140 | var body: some View { 141 | VStack { 142 | VStack { 143 | Spacer() 144 | Button("Start DiscordX") { 145 | if self.appDelegate.rpc == nil { 146 | self.appDelegate.isRelaunch = true 147 | self.appDelegate.launchApplication() 148 | } else { 149 | print("DiscordX is already running") 150 | } 151 | } 152 | Spacer() 153 | Button("Stop DiscordX") { 154 | if let rpc = self.appDelegate.rpc { 155 | rpc.setPresence(RichPresence()) 156 | rpc.disconnect() 157 | self.appDelegate.rpc = nil 158 | self.appDelegate.clearTimer() 159 | } else { 160 | print("DiscordX is not running") 161 | } 162 | } 163 | 164 | Spacer() 165 | Picker(selection: $refreshConfigurable, label: Text("Select Mode :")) { 166 | Text("Strict").tag(RefreshConfigurable.strict) 167 | .help(RefreshConfigurable.strict.message) 168 | Text("Flaunt").tag(RefreshConfigurable.flaunt) 169 | .help(RefreshConfigurable.flaunt.message) 170 | } 171 | .pickerStyle(RadioGroupPickerStyle()) 172 | 173 | Spacer() 174 | Button("Quit DiscordX") { 175 | exit(-1) 176 | } 177 | .padding(.top) 178 | .foregroundColor(.red) 179 | Spacer() 180 | } 181 | } 182 | .onChange(of: refreshConfigurable) { newValue in 183 | switch newValue { 184 | case .strict: 185 | strictMode = true 186 | flauntMode = false 187 | case .flaunt: 188 | strictMode = false 189 | flauntMode = true 190 | } 191 | } 192 | } 193 | } 194 | 195 | func applicationDidFinishLaunching(_ aNotification: Notification) { 196 | launchApplication() 197 | 198 | let contentView = ContentView(self) 199 | let view = NSHostingView(rootView: contentView) 200 | print(strictMode, flauntMode) 201 | 202 | view.frame = NSRect(x: 0, y: 0, width: 200, height: 160) 203 | 204 | let menuItem = NSMenuItem() 205 | menuItem.view = view 206 | 207 | let menu = NSMenu() 208 | menu.addItem(menuItem) 209 | 210 | // StatusItem is stored as a class property. 211 | statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 212 | statusItem?.menu = menu 213 | let image = NSImage(named: "AppIcon") 214 | image?.size = NSMakeSize(24.0, 24.0) 215 | statusItem.button!.image = image 216 | statusItem.isVisible = true 217 | 218 | if let window = NSApplication.shared.windows.first { 219 | window.close() 220 | } 221 | 222 | } 223 | 224 | private lazy var addAllObservers: () = { 225 | // run on Xcode launch 226 | self.notifCenter.addObserver(forName: NSWorkspace.didLaunchApplicationNotification, object: nil, queue: nil, using: { notif in 227 | if let app = notif.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication { 228 | if app.bundleIdentifier == xcodeBundleId { 229 | // print("xcode launched, connecting...") 230 | self.initRPC() 231 | } 232 | } 233 | }) 234 | 235 | // run on Xcode close 236 | self.notifCenter.addObserver(forName: NSWorkspace.didTerminateApplicationNotification, object: nil, queue: nil, using: { notif in 237 | if let app = notif.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication { 238 | if app.bundleIdentifier == xcodeBundleId { 239 | // print("xcode closed, disconnecting...") 240 | self.deinitRPC() 241 | } 242 | } 243 | }) 244 | 245 | if strictMode { 246 | self.notifCenter.addObserver(forName: NSWorkspace.didActivateApplicationNotification, object: nil, queue: nil, using: { notif in 247 | if let app = notif.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication { 248 | if app.bundleIdentifier == xcodeBundleId { 249 | //Xcode became active again (Frontmost) 250 | if !self.isRelaunch { 251 | if let inactiveDate = self.inactiveDate { 252 | let newDate: Date? = self.startDate?.addingTimeInterval(-inactiveDate.timeIntervalSinceNow) 253 | // print(self.startDate, newDate) 254 | // print(self.startDate!.distance(to: newDate!)) 255 | self.startDate = newDate 256 | } 257 | } else { 258 | self.startDate = Date() 259 | self.inactiveDate = nil 260 | self.isRelaunch = false 261 | } 262 | // User can now start or stop DiscordX have to check if rpc is connected 263 | if self.rpc != nil { 264 | self.updateStatus() 265 | } 266 | } 267 | } 268 | }) 269 | 270 | self.notifCenter.addObserver(forName: NSWorkspace.didDeactivateApplicationNotification, object: nil, queue: nil, using: { notif in 271 | if let app = notif.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication { 272 | if app.bundleIdentifier == xcodeBundleId { 273 | //Xcode is inactive (Not frontmost) 274 | self.inactiveDate = Date() 275 | if self.rpc != nil { 276 | self.updateStatus() 277 | } 278 | } 279 | } 280 | }) 281 | } 282 | 283 | if !flauntMode { 284 | self.notifCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: nil, using: { notif in 285 | if let app = notif.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication { 286 | if app.bundleIdentifier == xcodeBundleId { 287 | //Xcode is going to become inactive (Sleep) 288 | self.inactiveDate = Date() 289 | if self.rpc != nil { 290 | self.updateStatus() 291 | } 292 | } 293 | } 294 | }) 295 | 296 | self.notifCenter.addObserver(forName: NSWorkspace.didWakeNotification, object: nil, queue: nil, using: { notif in 297 | if let app = notif.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication { 298 | if app.bundleIdentifier == xcodeBundleId { 299 | //Xcode woke up from sleep 300 | if let inactiveDate = self.inactiveDate { 301 | let newDate: Date? = self.startDate?.addingTimeInterval(-inactiveDate.timeIntervalSinceNow) 302 | // print(self.startDate, newDate) 303 | self.startDate = newDate 304 | } 305 | if self.rpc != nil { 306 | self.updateStatus() 307 | } 308 | } 309 | } 310 | }) 311 | } 312 | }() 313 | 314 | func launchApplication() { 315 | // print("app launched") 316 | 317 | for app in NSWorkspace.shared.runningApplications { 318 | // check if xcode is running 319 | if app.bundleIdentifier == xcodeBundleId { 320 | // print("xcode running, connecting...") 321 | initRPC() 322 | } 323 | } 324 | 325 | _ = addAllObservers 326 | 327 | } 328 | 329 | func applicationWillTerminate(_ aNotification: Notification) { 330 | // Insert code here to tear down your application 331 | // print("app closing") 332 | deinitRPC() 333 | clearTimer() 334 | } 335 | } 336 | 337 | extension AppDelegate: SwordRPCDelegate { 338 | func swordRPCDidConnect(_ rpc: SwordRPC) { 339 | // print("connected") 340 | startDate = Date() 341 | beginTimer() 342 | } 343 | 344 | func swordRPCDidDisconnect(_ rpc: SwordRPC, code: Int?, message msg: String?) { 345 | // print("disconnected") 346 | clearTimer() 347 | } 348 | 349 | func swordRPCDidReceiveError(_ rpc: SwordRPC, code: Int, message msg: String) { 350 | 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/128 copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/128 copy.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/1d79cbcdd7d6e1d59f437b1a79db9f2f_Q0SQBf1rNB 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/1d79cbcdd7d6e1d59f437b1a79db9f2f_Q0SQBf1rNB 1.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/1d79cbcdd7d6e1d59f437b1a79db9f2f_Q0SQBf1rNB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/1d79cbcdd7d6e1d59f437b1a79db9f2f_Q0SQBf1rNB.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 6.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 5.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 4.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 3.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "128 copy.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "1d79cbcdd7d6e1d59f437b1a79db9f2f_Q0SQBf1rNB.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "1d79cbcdd7d6e1d59f437b1a79db9f2f_Q0SQBf1rNB 1.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 2.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 1.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 1.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 2.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 3.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 4.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 5.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V 6.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadAzam/DiscordX/de2c40297bdbc8f260466c0ec8c4f31b52d0a40a/DiscordX/Assets.xcassets/AppIcon.appiconset/cf191685e8a47d1758fe14d9977b452e_WvnBOg4w2V.png -------------------------------------------------------------------------------- /DiscordX/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DiscordX/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // DiscordX 4 | // 5 | // Created by Asad Azam on 22/03/22. 6 | // Copyright © 2022 Asad Azam. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import CoreData 11 | import SwordRPC 12 | 13 | struct ContentView: View { 14 | 15 | @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 16 | 17 | @Environment(\.managedObjectContext) private var viewContext 18 | 19 | @FetchRequest( 20 | sortDescriptors: [NSSortDescriptor(keyPath: \Item.endTime, ascending: true)], 21 | animation: .default) 22 | private var items: FetchedResults 23 | 24 | var body: some View { 25 | NavigationView { 26 | Text("Hello World") 27 | List { 28 | ForEach(items) { item in 29 | NavigationLink { 30 | // Text("Item at \(item.startTime, formatter: itemFormatter)") 31 | } label: { 32 | // Text(item.startTime!, formatter: itemFormatter) 33 | } 34 | } 35 | } 36 | 37 | } 38 | } 39 | 40 | 41 | } 42 | 43 | private let itemFormatter: DateFormatter = { 44 | let formatter = DateFormatter() 45 | formatter.dateStyle = .short 46 | formatter.timeStyle = .medium 47 | return formatter 48 | }() 49 | 50 | struct ContentView_Previews: PreviewProvider { 51 | static var previews: some View { 52 | ContentView() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /DiscordX/CoreData/DiscordX.xcdatamodeld/DiscordX.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /DiscordX/CoreData/Persistence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Persistence.swift 3 | // DiscordX 4 | // 5 | // Created by Asad Azam on 22/03/22. 6 | // Copyright © 2022 Asad Azam. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | struct PersistenceController { 12 | static let shared = PersistenceController() 13 | 14 | let container: NSPersistentContainer 15 | 16 | init(inMemory: Bool = false) { 17 | container = NSPersistentContainer(name: "DiscordX") 18 | if inMemory { 19 | container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") 20 | } 21 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 22 | if let error = error as NSError? { 23 | // Replace this implementation with code to handle the error appropriately. 24 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 25 | 26 | /* 27 | Typical reasons for an error here include: 28 | * The parent directory does not exist, cannot be created, or disallows writing. 29 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 30 | * The device is out of space. 31 | * The store could not be migrated to the current model version. 32 | Check the error message to determine what the actual problem was. 33 | */ 34 | fatalError("Unresolved error \(error), \(error.userInfo)") 35 | } 36 | }) 37 | container.viewContext.automaticallyMergesChangesFromParent = true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /DiscordX/DiscordX.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.automation.apple-events 6 | 7 | com.apple.security.temporary-exception.apple-events 8 | 9 | com.apple.dt.Xcode 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /DiscordX/DiscordX.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiscordX.swift 3 | // DiscordX 4 | // 5 | // Created by Asad Azam on 22/03/22. 6 | // Copyright © 2022 Asad Azam. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | @main 12 | struct DiscordX: App { 13 | let persistenceController = PersistenceController.shared 14 | 15 | init() { 16 | UserDefaults.standard.register(defaults: ["strictMode": true, "flauntMode": false]) 17 | } 18 | 19 | var body: some Scene { 20 | WindowGroup { 21 | ContentView() 22 | .environment(\.managedObjectContext, persistenceController.container.viewContext) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DiscordX/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSApplicationCategoryType 22 | public.app-category.developer-tools 23 | LSBackgroundOnly 24 | 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSAppleEventsUsageDescription 28 | This is needed to monitor Xcode. 29 | NSHumanReadableCopyright 30 | Copyright © 2021 Asad Azam. All rights reserved. 31 | NSPrincipalClass 32 | NSApplication 33 | NSSupportsAutomaticTermination 34 | 35 | NSSupportsSuddenTermination 36 | 37 | LSUIElement 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /DiscordX/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DiscordX/Supporting Files/Applescript.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Applescript.swift 3 | // DiscordX 4 | // 5 | // Created by Asad Azam on 28/9/20. 6 | // Copyright © 2021 Asad Azam. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | enum APScripts: String { 13 | case windowNames = "return name of windows" 14 | case filePaths = "return file of documents" 15 | case documentNames = "return name of documents" 16 | case activeWorkspaceDocument = "return active workspace document" 17 | } 18 | 19 | func runAPScript(_ s: APScripts) -> [String]? { 20 | let scr = """ 21 | tell application "Xcode" 22 | \(s.rawValue) 23 | end tell 24 | """ 25 | 26 | // execute the script 27 | let script = NSAppleScript.init(source: scr) 28 | let result = script?.executeAndReturnError(nil) 29 | 30 | // format the result as a Swift array 31 | if let desc = result { 32 | var arr: [String] = [] 33 | if desc.numberOfItems == 0 { 34 | return arr 35 | } 36 | for i in 1...desc.numberOfItems { 37 | let strVal = desc.atIndex(i)!.stringValue 38 | if var uwStrVal = strVal { 39 | // remove " — Edited" suffix if it exists 40 | if uwStrVal.hasSuffix(" — Edited") { 41 | uwStrVal.removeSubrange(uwStrVal.lastIndex(of: "—")!...) 42 | uwStrVal.removeLast() 43 | } 44 | arr.append(uwStrVal) 45 | } 46 | } 47 | 48 | return arr 49 | } 50 | return nil 51 | } 52 | 53 | func getActiveFilename() -> String? { 54 | let activeApplicationVersion = """ 55 | tell application (path to frontmost application as Unicode text) 56 | if name is "Xcode" then 57 | get version 58 | end if 59 | end tell 60 | """ 61 | let result = NSAppleScript(source: activeApplicationVersion)?.executeAndReturnError(nil) 62 | let version = result?.stringValue?.split(separator: ".") 63 | // print(version?[0], version?[1]) 64 | 65 | guard let fileNames = runAPScript(.documentNames) else { 66 | return nil 67 | } 68 | 69 | guard var windowNames = runAPScript(.windowNames) else { 70 | return nil 71 | } 72 | 73 | // Hotfix for Xcode 13.2.1 or higher 74 | if (Int(version?[0] ?? "0") ?? 0) > 13 || ((Int(version?[0] ?? "0") ?? 0) == 13 && (Int(version?[1] ?? "0") ?? 0) >= 2) { 75 | var correctedNames = [String]() 76 | for var windowName in windowNames { 77 | if let index = windowName.firstIndex(of: "—") { // — is a special character not - DO NOT GET CONFUSED 78 | windowName.removeSubrange(...index) 79 | windowName.removeFirst() 80 | correctedNames.append(windowName) 81 | } 82 | } 83 | windowNames = correctedNames 84 | } 85 | // print("\n\tFile Names: \(fileNames)\n\tWindow Names: \(windowNames)\n") 86 | 87 | // find the first window title that matches a filename 88 | // (the first window name is the one in focus) 89 | for window in windowNames { 90 | // make sure the focused window refers to a file 91 | for file in fileNames { 92 | if file == window { 93 | return file 94 | } 95 | } 96 | } 97 | 98 | return nil 99 | } 100 | 101 | func getActiveWorkspace() -> String? { 102 | if let awd = runAPScript(.activeWorkspaceDocument), awd.count >= 2 { 103 | return awd[1] 104 | } 105 | return nil 106 | } 107 | 108 | func getActiveWindow() -> String? { 109 | let activeApplication = """ 110 | tell application "System Events" 111 | get the name of every application process whose frontmost is true 112 | end tell 113 | """ 114 | 115 | // get the name of every process whose visible is true 116 | 117 | let script = NSAppleScript.init(source: activeApplication) 118 | let result = script?.executeAndReturnError(nil) 119 | 120 | if let desc = result { 121 | var arr: [String] = [] 122 | for i in 1...desc.numberOfItems { 123 | guard let strVal = desc.atIndex(i)!.stringValue else { return "Xcode" } 124 | arr.append(strVal) 125 | } 126 | // print(arr[0]) 127 | return arr[0] 128 | } 129 | return "" 130 | } 131 | -------------------------------------------------------------------------------- /DiscordX/Supporting Files/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // DiscordX 4 | // 5 | // Created by Asad Azam on 28/9/20. 6 | // Copyright © 2021 Asad Azam. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // used to register for notifs when Xcode opens/closes 12 | let xcodeBundleId = "com.apple.dt.Xcode" 13 | 14 | // how often we check Xcode for a status update 15 | let refreshInterval = 5 // seconds 16 | 17 | // if you switch out of Xcode the timer stops 18 | /// true: timer will stop 19 | /// false: timer will not stop 20 | var strictMode: Bool { 21 | get { UserDefaults.standard.bool(forKey: "strictMode") } 22 | set { UserDefaults.standard.set(newValue, forKey: "strictMode") } 23 | } 24 | 25 | // it will never stop the timer regardless of anything except if you kill DiscordX 26 | /// true: timer will not stop 27 | /// false: timer will stop 28 | var flauntMode: Bool { 29 | get { UserDefaults.standard.bool(forKey: "flauntMode") } 30 | set { UserDefaults.standard.set(newValue, forKey: "flauntMode") } 31 | } 32 | 33 | // some other window names of Xcode 34 | let xcodeWindowNames = [ 35 | "Simulator", 36 | "Instruments", 37 | "Accessibility Inspector", 38 | "FileMerge", 39 | "Create ML", 40 | "RealityComposer", 41 | //doc://com.apple.documentation -> Process name -> Xcode Documentation 42 | //unable to find organiser 43 | ] 44 | 45 | // The following constants are for use with the Discord App 46 | // if you're using your own Discord App, update this as needed 47 | 48 | let discordClientId = "759699771689795615" 49 | 50 | // Chaniging the below list of files won't do anything as they are hardcoded 51 | // on the discord application. To add images you need to create your own application 52 | 53 | // discord image keys of supported file types 54 | let discordRPImageKeys = [ 55 | "swift", 56 | "playground", 57 | "storyboard", 58 | "xcodeproj", 59 | "h", 60 | "m", 61 | "cpp", 62 | "c", 63 | "sdef", 64 | "plist", 65 | "md", 66 | "appex", 67 | "rcproject", 68 | "rtf", 69 | "rtfd", 70 | "pch", 71 | "mm", 72 | "xcassets", 73 | "iig", 74 | "metal", 75 | "xib", 76 | "arobject", 77 | "entitlements", 78 | ] 79 | 80 | // default for unsupported file types 81 | let discordRPImageKeyDefault = "xcode" 82 | 83 | // Xcode application icon 84 | let discordRPImageKeyXcode = "xcode" 85 | 86 | -------------------------------------------------------------------------------- /DiscordX/Supporting Files/HelperFunctions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelperFunctions.swift 3 | // DiscordX 4 | // 5 | // Created by Asad Azam on 28/9/20. 6 | // Copyright © 2021 Asad Azam. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func getFileExt(_ file: String) -> String? { 12 | if let ext = file.split(separator: ".").last { 13 | return String(ext) 14 | } 15 | return nil 16 | } 17 | 18 | func withoutFileExt(_ file: String) -> String { 19 | if !file.contains(".") || file.last == "." { 20 | return file 21 | } 22 | 23 | var ret = file 24 | while (ret.popLast() != ".") {} 25 | return ret 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Asad Azam 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 | # DiscordX 2 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/AsadAzam/DiscordX?style=for-the-badge) 3 | 4 | ![GitHub](https://img.shields.io/github/license/AsadAzam/DiscordX?style=for-the-badge) 5 | ![GitHub](https://img.shields.io/github/issues-raw/AsadAzam/DiscordX?style=for-the-badge) 6 | ![GitHub](https://img.shields.io/github/issues-closed-raw/AsadAzam/DiscordX?style=for-the-badge) 7 | ![GitHub](https://img.shields.io/github/issues-pr/AsadAzam/DiscordX?style=for-the-badge) 8 | ![GitHub](https://img.shields.io/github/last-commit/AsadAzam/DiscordX?style=for-the-badge) 9 | ![GitHub All Releases](https://img.shields.io/github/downloads/AsadAzam/Discordx/total?style=for-the-badge) 10 | 11 | ## New Features 12 | 1. Added a simple Status Bar Menu to start, stop or close the App. 13 | 14 | ## TODOs 15 | 1. Keep a log file for time spent on every file 16 | 17 | DiscordX adds support for Xcode on Discord, AKA Discord Rich Presence. 18 | 19 |

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |

28 | 29 | DiscordX displays the current file you are working on and the current workspace in use. It uses the same icons which exist in Xcode 12.0.1 (12A7300) 30 | 31 | DIscordX adds support for the current application in use too. Other applications do the exact same thing, but none of them I used supported applications or such a wide variety of file types. Also, none of them are updated for Xcode 13. 32 | 33 | The following file type extensions are supported: 34 | - `.swift` 35 | - `.playground` 36 | - `.storyboard` 37 | - `.xcodeproj` 38 | - `.h` 39 | - `.m` 40 | - `.cpp` 41 | - `.c` 42 | - `.sdef` 43 | - `.plist` 44 | - `.md` 45 | - `.appex` 46 | - `.rcproject` 47 | - `.rtf` 48 | - `.rtfd` 49 | - `.pch` 50 | - `.mm` 51 | - `.xcassets` 52 | - `.iig` 53 | - `.metal` 54 | - `.xib` 55 | - `.arobject` 56 | - `.entitlements` 57 | 58 | The following applications are supported: 59 | - `Simulator` 60 | - `Instruments` 61 | - `Accessibility Inspector` 62 | - `FileMerge` 63 | - `Create ML` 64 | - `RealityComposer` 65 | 66 | ## Dependencies 67 | If you are modifying/ tweaking the application, make sure you change the *Team* under *Signing & Capabilities*. 68 | 69 | DiscordX uses [PKBeam's Fork](https://github.com/PKBeam/SwordRPC) of [Azoy's SwordRPC](https://github.com/Azoy/SwordRPC). 70 | 71 | ## System Requirements 72 | - macOS Big Sur (11) (Minimum) 73 | - Xcode installed 74 | 75 | ## Usage 76 | 1. Download the project, and open it on Xcode. 77 | 2. Go to Signing & Capabilities, choose **Team** and Select **Sign to Run Locally** in Signing Certificate. 78 | 3. Build and Run the App. 79 | 4. (Optional) Go to Products under the Navigator, right-click on DiscordX.app, and click on **Show in Finder**. Copy/Cut - Paste in the Applications folder, and it should be easier to launch next time. 80 | 81 | After running it, it will ask for two permissions; one is for controlling Xcode, and the other is for System Events. As the app uses Apple script to perform any operation, it would need access to whatever the script will run on. 82 | 83 | ### Configurable 84 | - You can set the refresh interval of rich presence (by default, it refreshes every 5 seconds) [Note: When strict mode is enabled, it will refresh immediately when switching to and from Xcode] 85 | - Strict Mode (Timer will only keep the time you were active on Xcode) 86 | - Flaunt Mode (Timer will not stop on Sleep and Wakeup of MacOS) 87 | 88 | List of Apple Scripts run: 89 | ``` 90 | tell application "Xcode" 91 | return name of windows 92 | end tell 93 | ``` 94 | ``` 95 | tell application "Xcode" 96 | return file of documents 97 | end tell 98 | ``` 99 | ``` 100 | tell application "Xcode" 101 | return name of documents 102 | end tell 103 | ``` 104 | ``` 105 | tell application "Xcode" 106 | return active workspace document 107 | end tell 108 | ``` 109 | ``` 110 | tell application "System Events" 111 | get the name of every application process whose frontmost is true 112 | end tell 113 | ``` 114 | ``` 115 | tell application (path to frontmost application as Unicode text) 116 | if name is "Xcode" then 117 | get version 118 | end if 119 | end tell 120 | ``` 121 | 122 | You can verify what permissions DiscordX uses by opening System Preferences and looking in Security & Privacy under Privacy, then Automation. 123 | That's it, you're done - DiscordX will now automatically monitor Xcode. 124 | 125 | If you like, you can set DiscordX to automatically open on login. 126 | 127 | # Note 128 | DiscordX is a fork of [RPFX](https://github.com/PKBeam/RPFX). I didn't contribute to the original as I didn't like the name RPFX. 129 | --------------------------------------------------------------------------------