├── .github └── FUNDING.yml ├── Artemisia.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ └── user.xcuserdatad │ │ └── IDEFindNavigatorScopes.plist ├── xcshareddata │ └── xcschemes │ │ └── Artemisia.xcscheme └── xcuserdata │ └── user.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Artemisia ├── App │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── WindowController.h │ ├── WindowController.m │ └── main.m ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ └── Contents.json ├── Backend │ ├── EventMonitor.h │ ├── EventMonitor.m │ ├── SystemUtilities.h │ └── SystemUtilities.m ├── Bridge.h └── UI │ ├── DesktopViewController.h │ ├── DesktopViewController.m │ ├── EventBarView.swift │ ├── EventKind.h │ └── ViewCreatorStub.swift └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: nsantoine 2 | -------------------------------------------------------------------------------- /Artemisia.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE0C70F12ADA9E7900EE23AD /* DisplayServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE0C70F02ADA9E7900EE23AD /* DisplayServices.framework */; }; 11 | CE0C70F22ADA9E7900EE23AD /* DisplayServices.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE0C70F02ADA9E7900EE23AD /* DisplayServices.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | CE0C70FB2ADAA04500EE23AD /* DesktopViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0C70FA2ADAA04500EE23AD /* DesktopViewController.m */; }; 13 | CE0C70FE2ADAA0FD00EE23AD /* WindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0C70FD2ADAA0FD00EE23AD /* WindowController.m */; }; 14 | CE60BAB52ADC149800FA9225 /* SystemUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CE60BAB42ADC149800FA9225 /* SystemUtilities.m */; }; 15 | CE8288232ADAAC010045538A /* ViewCreatorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8288222ADAAC010045538A /* ViewCreatorStub.swift */; }; 16 | CE8288272ADAACDF0045538A /* EventBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8288262ADAACDF0045538A /* EventBarView.swift */; }; 17 | CEBE93532ADDC11E002AF39E /* MacControlCenterUI in Frameworks */ = {isa = PBXBuildFile; productRef = CEBE93522ADDC11E002AF39E /* MacControlCenterUI */; }; 18 | CEEEBE0F2ADA7C940087CB1C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CEEEBE0E2ADA7C940087CB1C /* AppDelegate.m */; }; 19 | CEEEBE142ADA7C960087CB1C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CEEEBE132ADA7C960087CB1C /* Assets.xcassets */; }; 20 | CEEEBE192ADA7C960087CB1C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CEEEBE182ADA7C960087CB1C /* main.m */; }; 21 | CEEEBE212ADA7CB80087CB1C /* EventMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = CEEEBE202ADA7CB80087CB1C /* EventMonitor.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXCopyFilesBuildPhase section */ 25 | CE0C70EF2ADA9D9900EE23AD /* Embed Frameworks */ = { 26 | isa = PBXCopyFilesBuildPhase; 27 | buildActionMask = 2147483647; 28 | dstPath = ""; 29 | dstSubfolderSpec = 10; 30 | files = ( 31 | CE0C70F22ADA9E7900EE23AD /* DisplayServices.framework in Embed Frameworks */, 32 | ); 33 | name = "Embed Frameworks"; 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXCopyFilesBuildPhase section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | CE0C70EC2ADA9D9900EE23AD /* CoreDisplay.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreDisplay.framework; path = ../../../../System/Library/Frameworks/CoreDisplay.framework; sourceTree = ""; }; 40 | CE0C70F02ADA9E7900EE23AD /* DisplayServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DisplayServices.framework; path = ../../../../System/Library/PrivateFrameworks/DisplayServices.framework; sourceTree = ""; }; 41 | CE0C70F32ADA9EDF00EE23AD /* Bridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Bridge.h; sourceTree = ""; }; 42 | CE0C70FA2ADAA04500EE23AD /* DesktopViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DesktopViewController.m; sourceTree = ""; }; 43 | CE0C70FC2ADAA05900EE23AD /* DesktopViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DesktopViewController.h; sourceTree = ""; }; 44 | CE0C70FD2ADAA0FD00EE23AD /* WindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WindowController.m; sourceTree = ""; }; 45 | CE0C70FF2ADAA10300EE23AD /* WindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WindowController.h; sourceTree = ""; }; 46 | CE60BAB42ADC149800FA9225 /* SystemUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SystemUtilities.m; sourceTree = ""; }; 47 | CE60BAB62ADC149F00FA9225 /* SystemUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SystemUtilities.h; sourceTree = ""; }; 48 | CE8288222ADAAC010045538A /* ViewCreatorStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCreatorStub.swift; sourceTree = ""; }; 49 | CE8288262ADAACDF0045538A /* EventBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBarView.swift; sourceTree = ""; }; 50 | CE8288282ADAADF50045538A /* EventKind.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EventKind.h; sourceTree = ""; }; 51 | CEEEBE0A2ADA7C940087CB1C /* Artemisia.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Artemisia.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | CEEEBE0D2ADA7C940087CB1C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 53 | CEEEBE0E2ADA7C940087CB1C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 54 | CEEEBE132ADA7C960087CB1C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | CEEEBE182ADA7C960087CB1C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 56 | CEEEBE202ADA7CB80087CB1C /* EventMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EventMonitor.m; sourceTree = ""; }; 57 | CEEEBE222ADA7CBF0087CB1C /* EventMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EventMonitor.h; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | CEEEBE072ADA7C940087CB1C /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | CE0C70F12ADA9E7900EE23AD /* DisplayServices.framework in Frameworks */, 66 | CEBE93532ADDC11E002AF39E /* MacControlCenterUI in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | CE0C70EB2ADA9D9900EE23AD /* Frameworks */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | CE0C70F02ADA9E7900EE23AD /* DisplayServices.framework */, 77 | CE0C70EC2ADA9D9900EE23AD /* CoreDisplay.framework */, 78 | ); 79 | name = Frameworks; 80 | sourceTree = ""; 81 | }; 82 | CE60BAB32ADC146C00FA9225 /* Backend */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | CEEEBE202ADA7CB80087CB1C /* EventMonitor.m */, 86 | CEEEBE222ADA7CBF0087CB1C /* EventMonitor.h */, 87 | CE60BAB42ADC149800FA9225 /* SystemUtilities.m */, 88 | CE60BAB62ADC149F00FA9225 /* SystemUtilities.h */, 89 | ); 90 | path = Backend; 91 | sourceTree = ""; 92 | }; 93 | CE8288242ADAACA40045538A /* App */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | CEEEBE182ADA7C960087CB1C /* main.m */, 97 | CEEEBE0D2ADA7C940087CB1C /* AppDelegate.h */, 98 | CEEEBE0E2ADA7C940087CB1C /* AppDelegate.m */, 99 | CE0C70FD2ADAA0FD00EE23AD /* WindowController.m */, 100 | CE0C70FF2ADAA10300EE23AD /* WindowController.h */, 101 | ); 102 | path = App; 103 | sourceTree = ""; 104 | }; 105 | CE8288252ADAACB90045538A /* UI */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | CE0C70FA2ADAA04500EE23AD /* DesktopViewController.m */, 109 | CE0C70FC2ADAA05900EE23AD /* DesktopViewController.h */, 110 | CE8288282ADAADF50045538A /* EventKind.h */, 111 | CE8288222ADAAC010045538A /* ViewCreatorStub.swift */, 112 | CE8288262ADAACDF0045538A /* EventBarView.swift */, 113 | ); 114 | path = UI; 115 | sourceTree = ""; 116 | }; 117 | CEEEBE012ADA7C940087CB1C = { 118 | isa = PBXGroup; 119 | children = ( 120 | CEEEBE0C2ADA7C940087CB1C /* Artemisia */, 121 | CEEEBE0B2ADA7C940087CB1C /* Products */, 122 | CE0C70EB2ADA9D9900EE23AD /* Frameworks */, 123 | ); 124 | sourceTree = ""; 125 | }; 126 | CEEEBE0B2ADA7C940087CB1C /* Products */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | CEEEBE0A2ADA7C940087CB1C /* Artemisia.app */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | CEEEBE0C2ADA7C940087CB1C /* Artemisia */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | CE8288242ADAACA40045538A /* App */, 138 | CE8288252ADAACB90045538A /* UI */, 139 | CE60BAB32ADC146C00FA9225 /* Backend */, 140 | CEEEBE132ADA7C960087CB1C /* Assets.xcassets */, 141 | CE0C70F32ADA9EDF00EE23AD /* Bridge.h */, 142 | ); 143 | path = Artemisia; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | CEEEBE092ADA7C940087CB1C /* Artemisia */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = CEEEBE1D2ADA7C960087CB1C /* Build configuration list for PBXNativeTarget "Artemisia" */; 152 | buildPhases = ( 153 | CEEEBE062ADA7C940087CB1C /* Sources */, 154 | CEEEBE072ADA7C940087CB1C /* Frameworks */, 155 | CEEEBE082ADA7C940087CB1C /* Resources */, 156 | CE0C70EF2ADA9D9900EE23AD /* Embed Frameworks */, 157 | ); 158 | buildRules = ( 159 | ); 160 | dependencies = ( 161 | ); 162 | name = Artemisia; 163 | packageProductDependencies = ( 164 | CEBE93522ADDC11E002AF39E /* MacControlCenterUI */, 165 | ); 166 | productName = Artemisia; 167 | productReference = CEEEBE0A2ADA7C940087CB1C /* Artemisia.app */; 168 | productType = "com.apple.product-type.application"; 169 | }; 170 | /* End PBXNativeTarget section */ 171 | 172 | /* Begin PBXProject section */ 173 | CEEEBE022ADA7C940087CB1C /* Project object */ = { 174 | isa = PBXProject; 175 | attributes = { 176 | BuildIndependentTargetsInParallel = 1; 177 | LastUpgradeCheck = 1500; 178 | TargetAttributes = { 179 | CEEEBE092ADA7C940087CB1C = { 180 | CreatedOnToolsVersion = 15.0; 181 | LastSwiftMigration = 1500; 182 | }; 183 | }; 184 | }; 185 | buildConfigurationList = CEEEBE052ADA7C940087CB1C /* Build configuration list for PBXProject "Artemisia" */; 186 | compatibilityVersion = "Xcode 14.0"; 187 | developmentRegion = en; 188 | hasScannedForEncodings = 0; 189 | knownRegions = ( 190 | en, 191 | Base, 192 | ); 193 | mainGroup = CEEEBE012ADA7C940087CB1C; 194 | packageReferences = ( 195 | CEBE93512ADDC11E002AF39E /* XCRemoteSwiftPackageReference "MacControlCenterUI" */, 196 | ); 197 | productRefGroup = CEEEBE0B2ADA7C940087CB1C /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | CEEEBE092ADA7C940087CB1C /* Artemisia */, 202 | ); 203 | }; 204 | /* End PBXProject section */ 205 | 206 | /* Begin PBXResourcesBuildPhase section */ 207 | CEEEBE082ADA7C940087CB1C /* Resources */ = { 208 | isa = PBXResourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | CEEEBE142ADA7C960087CB1C /* Assets.xcassets in Resources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXResourcesBuildPhase section */ 216 | 217 | /* Begin PBXSourcesBuildPhase section */ 218 | CEEEBE062ADA7C940087CB1C /* Sources */ = { 219 | isa = PBXSourcesBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | CE0C70FE2ADAA0FD00EE23AD /* WindowController.m in Sources */, 223 | CE8288232ADAAC010045538A /* ViewCreatorStub.swift in Sources */, 224 | CEEEBE192ADA7C960087CB1C /* main.m in Sources */, 225 | CEEEBE0F2ADA7C940087CB1C /* AppDelegate.m in Sources */, 226 | CEEEBE212ADA7CB80087CB1C /* EventMonitor.m in Sources */, 227 | CE60BAB52ADC149800FA9225 /* SystemUtilities.m in Sources */, 228 | CE0C70FB2ADAA04500EE23AD /* DesktopViewController.m in Sources */, 229 | CE8288272ADAACDF0045538A /* EventBarView.swift in Sources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | /* End PBXSourcesBuildPhase section */ 234 | 235 | /* Begin XCBuildConfiguration section */ 236 | CEEEBE1B2ADA7C960087CB1C /* Debug */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_ENABLE_OBJC_WEAK = YES; 247 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 248 | CLANG_WARN_BOOL_CONVERSION = YES; 249 | CLANG_WARN_COMMA = YES; 250 | CLANG_WARN_CONSTANT_CONVERSION = YES; 251 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 252 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 253 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 254 | CLANG_WARN_EMPTY_BODY = YES; 255 | CLANG_WARN_ENUM_CONVERSION = YES; 256 | CLANG_WARN_INFINITE_RECURSION = YES; 257 | CLANG_WARN_INT_CONVERSION = YES; 258 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 260 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 261 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 262 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 263 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 264 | CLANG_WARN_STRICT_PROTOTYPES = YES; 265 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 266 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | COPY_PHASE_STRIP = NO; 270 | DEBUG_INFORMATION_FORMAT = dwarf; 271 | ENABLE_STRICT_OBJC_MSGSEND = YES; 272 | ENABLE_TESTABILITY = YES; 273 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu17; 275 | GCC_DYNAMIC_NO_PIC = NO; 276 | GCC_NO_COMMON_BLOCKS = YES; 277 | GCC_OPTIMIZATION_LEVEL = 0; 278 | GCC_PREPROCESSOR_DEFINITIONS = ( 279 | "DEBUG=1", 280 | "$(inherited)", 281 | ); 282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 284 | GCC_WARN_UNDECLARED_SELECTOR = YES; 285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 286 | GCC_WARN_UNUSED_FUNCTION = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 289 | MACOSX_DEPLOYMENT_TARGET = 14.0; 290 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 291 | MTL_FAST_MATH = YES; 292 | ONLY_ACTIVE_ARCH = YES; 293 | SDKROOT = macosx; 294 | }; 295 | name = Debug; 296 | }; 297 | CEEEBE1C2ADA7C960087CB1C /* Release */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ALWAYS_SEARCH_USER_PATHS = NO; 301 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 302 | CLANG_ANALYZER_NONNULL = YES; 303 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 305 | CLANG_ENABLE_MODULES = YES; 306 | CLANG_ENABLE_OBJC_ARC = YES; 307 | CLANG_ENABLE_OBJC_WEAK = YES; 308 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 309 | CLANG_WARN_BOOL_CONVERSION = YES; 310 | CLANG_WARN_COMMA = YES; 311 | CLANG_WARN_CONSTANT_CONVERSION = YES; 312 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 313 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 314 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INFINITE_RECURSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 321 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 323 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 324 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 325 | CLANG_WARN_STRICT_PROTOTYPES = YES; 326 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 327 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | COPY_PHASE_STRIP = NO; 331 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 332 | ENABLE_NS_ASSERTIONS = NO; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu17; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 344 | MACOSX_DEPLOYMENT_TARGET = 14.0; 345 | MTL_ENABLE_DEBUG_INFO = NO; 346 | MTL_FAST_MATH = YES; 347 | SDKROOT = macosx; 348 | }; 349 | name = Release; 350 | }; 351 | CEEEBE1E2ADA7C960087CB1C /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 355 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 356 | CLANG_ENABLE_MODULES = YES; 357 | CODE_SIGN_STYLE = Automatic; 358 | COMBINE_HIDPI_IMAGES = YES; 359 | CURRENT_PROJECT_VERSION = 1; 360 | DEVELOPMENT_TEAM = L9735M962H; 361 | ENABLE_HARDENED_RUNTIME = YES; 362 | GENERATE_INFOPLIST_FILE = YES; 363 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 364 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 365 | LD_RUNPATH_SEARCH_PATHS = ( 366 | "$(inherited)", 367 | "@executable_path/../Frameworks", 368 | ); 369 | MACOSX_DEPLOYMENT_TARGET = 11; 370 | MARKETING_VERSION = 1.0; 371 | PRODUCT_BUNDLE_IDENTIFIER = com.serena.Artemisia; 372 | PRODUCT_NAME = "$(TARGET_NAME)"; 373 | SWIFT_EMIT_LOC_STRINGS = YES; 374 | SWIFT_OBJC_BRIDGING_HEADER = Artemisia/Bridge.h; 375 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 376 | SWIFT_VERSION = 5.0; 377 | SYSTEM_FRAMEWORK_SEARCH_PATHS = ( 378 | "$(inherited)", 379 | "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", 380 | ); 381 | }; 382 | name = Debug; 383 | }; 384 | CEEEBE1F2ADA7C960087CB1C /* Release */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 388 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 389 | CLANG_ENABLE_MODULES = YES; 390 | CODE_SIGN_STYLE = Automatic; 391 | COMBINE_HIDPI_IMAGES = YES; 392 | CURRENT_PROJECT_VERSION = 1; 393 | DEVELOPMENT_TEAM = L9735M962H; 394 | ENABLE_HARDENED_RUNTIME = YES; 395 | GENERATE_INFOPLIST_FILE = YES; 396 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 397 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 398 | LD_RUNPATH_SEARCH_PATHS = ( 399 | "$(inherited)", 400 | "@executable_path/../Frameworks", 401 | ); 402 | MACOSX_DEPLOYMENT_TARGET = 11; 403 | MARKETING_VERSION = 1.0; 404 | PRODUCT_BUNDLE_IDENTIFIER = com.serena.Artemisia; 405 | PRODUCT_NAME = "$(TARGET_NAME)"; 406 | SWIFT_EMIT_LOC_STRINGS = YES; 407 | SWIFT_OBJC_BRIDGING_HEADER = Artemisia/Bridge.h; 408 | SWIFT_VERSION = 5.0; 409 | SYSTEM_FRAMEWORK_SEARCH_PATHS = ( 410 | "$(inherited)", 411 | "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", 412 | ); 413 | }; 414 | name = Release; 415 | }; 416 | /* End XCBuildConfiguration section */ 417 | 418 | /* Begin XCConfigurationList section */ 419 | CEEEBE052ADA7C940087CB1C /* Build configuration list for PBXProject "Artemisia" */ = { 420 | isa = XCConfigurationList; 421 | buildConfigurations = ( 422 | CEEEBE1B2ADA7C960087CB1C /* Debug */, 423 | CEEEBE1C2ADA7C960087CB1C /* Release */, 424 | ); 425 | defaultConfigurationIsVisible = 0; 426 | defaultConfigurationName = Release; 427 | }; 428 | CEEEBE1D2ADA7C960087CB1C /* Build configuration list for PBXNativeTarget "Artemisia" */ = { 429 | isa = XCConfigurationList; 430 | buildConfigurations = ( 431 | CEEEBE1E2ADA7C960087CB1C /* Debug */, 432 | CEEEBE1F2ADA7C960087CB1C /* Release */, 433 | ); 434 | defaultConfigurationIsVisible = 0; 435 | defaultConfigurationName = Release; 436 | }; 437 | /* End XCConfigurationList section */ 438 | 439 | /* Begin XCRemoteSwiftPackageReference section */ 440 | CEBE93512ADDC11E002AF39E /* XCRemoteSwiftPackageReference "MacControlCenterUI" */ = { 441 | isa = XCRemoteSwiftPackageReference; 442 | repositoryURL = "https://github.com/NSAntoine/MacControlCenterUI.git"; 443 | requirement = { 444 | branch = main; 445 | kind = branch; 446 | }; 447 | }; 448 | /* End XCRemoteSwiftPackageReference section */ 449 | 450 | /* Begin XCSwiftPackageProductDependency section */ 451 | CEBE93522ADDC11E002AF39E /* MacControlCenterUI */ = { 452 | isa = XCSwiftPackageProductDependency; 453 | package = CEBE93512ADDC11E002AF39E /* XCRemoteSwiftPackageReference "MacControlCenterUI" */; 454 | productName = MacControlCenterUI; 455 | }; 456 | /* End XCSwiftPackageProductDependency section */ 457 | }; 458 | rootObject = CEEEBE022ADA7C940087CB1C /* Project object */; 459 | } 460 | -------------------------------------------------------------------------------- /Artemisia.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Artemisia.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Artemisia.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "maccontrolcenterui", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/NSAntoine/MacControlCenterUI.git", 7 | "state" : { 8 | "branch" : "main", 9 | "revision" : "520159dcb9b4a54a9891725338c93b2fdaf10fbb" 10 | } 11 | }, 12 | { 13 | "identity" : "menubarextraaccess", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/orchetect/MenuBarExtraAccess", 16 | "state" : { 17 | "revision" : "f5896b47e15e114975897354c7e1082c51a2bffd", 18 | "version" : "1.0.5" 19 | } 20 | } 21 | ], 22 | "version" : 2 23 | } 24 | -------------------------------------------------------------------------------- /Artemisia.xcodeproj/project.xcworkspace/xcuserdata/user.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Artemisia.xcodeproj/xcshareddata/xcschemes/Artemisia.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 59 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Artemisia.xcodeproj/xcuserdata/user.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Artemisia.xcodeproj/xcuserdata/user.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Artemisia.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | BetterBars.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | CEEEBE092ADA7C940087CB1C 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Artemisia/App/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #import 9 | #import "DesktopViewController.h" 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | @property DesktopViewController *desktopVC; 14 | @property NSStatusItem *statusItem; 15 | 16 | +(instancetype)sharedDelegate; 17 | 18 | 19 | @end 20 | 21 | -------------------------------------------------------------------------------- /Artemisia/App/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | #import "EventMonitor.h" 10 | #import "WindowController.h" 11 | #import "DesktopViewController.h" 12 | 13 | @implementation AppDelegate 14 | 15 | + (instancetype)sharedDelegate { 16 | static AppDelegate *del; 17 | static dispatch_once_t onceToken; 18 | dispatch_once(&onceToken, ^{ 19 | del = [[AppDelegate alloc] init]; 20 | }); 21 | 22 | return del; 23 | } 24 | 25 | 26 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 27 | // Insert code here to initialize your application 28 | [NSApplication.sharedApplication setActivationPolicy:NSApplicationActivationPolicyAccessory]; 29 | 30 | [[EventMonitor sharedMonitor] startMonitoring]; 31 | 32 | WindowController *ctrl = [WindowController controllerWithKind:WindowControllerWindowKindBar]; 33 | self.desktopVC = (DesktopViewController *)ctrl.contentViewController; 34 | [ctrl showWindow:self]; 35 | 36 | self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; 37 | self.statusItem.button.image = [NSImage imageWithSystemSymbolName:@"slider.horizontal.below.rectangle" accessibilityDescription:nil]; 38 | 39 | NSMenu *menu = [[NSMenu alloc] init]; 40 | NSMenuItem *itm = [[NSMenuItem alloc] init]; 41 | itm.view = [self titleView]; 42 | [menu addItem:itm]; 43 | 44 | [menu addItem: NSMenuItem.separatorItem]; 45 | 46 | NSMenuItem *posItm = [[NSMenuItem alloc] initWithTitle:@"HUD Position" action:nil keyEquivalent:@""]; 47 | 48 | posItm.submenu = [self positionMenu]; 49 | 50 | [menu addItem:posItm]; 51 | 52 | [menu addItem: NSMenuItem.separatorItem]; 53 | NSMenuItem *quit = [menu addItemWithTitle:@"Quit" action:@selector(terminateApp) keyEquivalent:@"q"]; 54 | quit.target = self; 55 | 56 | self.statusItem.menu = menu; 57 | } 58 | 59 | - (NSMenu *)positionMenu { 60 | NSMenu *menu = [[NSMenu alloc] init]; 61 | 62 | DesktopViewPosition currPos = [DesktopViewController HUDPosition]; 63 | 64 | NSMenuItem *topLeft = [menu addItemWithTitle:@"Top Left" action:@selector(positionMenuItemAction:) keyEquivalent:@""]; 65 | topLeft.tag = DesktopViewPositionTopLeft; 66 | topLeft.state = currPos == DesktopViewPositionTopLeft ? NSControlStateValueOn : NSControlStateValueOff; 67 | 68 | NSMenuItem *topRight = [menu addItemWithTitle:@"Top Right (Default)" action:@selector(positionMenuItemAction:) keyEquivalent:@""]; 69 | topRight.tag = DesktopViewPositionTopRight; 70 | topRight.state = currPos == DesktopViewPositionTopRight ? NSControlStateValueOn : NSControlStateValueOff; 71 | 72 | NSMenuItem *bottomLeft = [menu addItemWithTitle:@"Bottom Left" action:@selector(positionMenuItemAction:) keyEquivalent:@""]; 73 | bottomLeft.tag = DesktopViewPositionBottomLeft; 74 | bottomLeft.state = currPos == DesktopViewPositionBottomLeft ? NSControlStateValueOn : NSControlStateValueOff; 75 | 76 | NSMenuItem *bottomRight = [menu addItemWithTitle:@"Bottom Right" action:@selector(positionMenuItemAction:) keyEquivalent:@""]; 77 | bottomRight.tag = DesktopViewPositionBottomRight; 78 | bottomRight.state = currPos == DesktopViewPositionBottomRight ? NSControlStateValueOn : NSControlStateValueOff; 79 | 80 | return menu; 81 | } 82 | 83 | -(void)positionMenuItemAction: (NSMenuItem *)item { 84 | [[NSUserDefaults standardUserDefaults] setInteger:item.tag forKey:@"DesktopViewPosition"]; 85 | item.parentItem.submenu = [self positionMenu]; 86 | } 87 | 88 | -(void)terminateApp { 89 | [NSApplication.sharedApplication terminate:nil]; 90 | } 91 | 92 | -(NSView *)titleView { 93 | NSView *v = [[NSView alloc] init]; 94 | [v setFrameSize:CGSizeMake(150, 20)]; 95 | 96 | NSTextField *titleField = [NSTextField labelWithString: NSRunningApplication.currentApplication.localizedName ]; 97 | titleField.translatesAutoresizingMaskIntoConstraints = NO; 98 | 99 | titleField.font = [NSFont preferredFontForTextStyle:NSFontTextStyleHeadline options:@{}]; 100 | 101 | [v addSubview:titleField]; 102 | 103 | [NSLayoutConstraint activateConstraints:@[ 104 | [titleField.topAnchor constraintEqualToAnchor:v.topAnchor constant:2.5], 105 | [titleField.leadingAnchor constraintEqualToAnchor:v.leadingAnchor constant:14] 106 | ]]; 107 | 108 | return v; 109 | } 110 | 111 | 112 | 113 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 114 | // Insert code here to tear down your application 115 | } 116 | 117 | 118 | - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app { 119 | return YES; 120 | } 121 | 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /Artemisia/App/WindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WindowController.h 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #ifndef WindowController_h 9 | #define WindowController_h 10 | 11 | #import 12 | 13 | typedef NS_CLOSED_ENUM(NSUInteger, WindowControllerWindowKind) { 14 | WindowControllerWindowKindBar, 15 | }; 16 | 17 | @interface WindowController : NSWindowController 18 | 19 | +(instancetype)controllerWithKind: (WindowControllerWindowKind)kind; 20 | 21 | @end 22 | 23 | 24 | #endif /* WindowController_h */ 25 | -------------------------------------------------------------------------------- /Artemisia/App/WindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WindowController.m 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #import 9 | #import "DesktopViewController.h" 10 | #import "WindowController.h" 11 | 12 | @implementation WindowController 13 | + (instancetype)controllerWithKind:(WindowControllerWindowKind)kind { 14 | NSViewController *ctrller; 15 | 16 | switch (kind) { 17 | case WindowControllerWindowKindBar: 18 | ctrller = [[DesktopViewController alloc] init]; 19 | } 20 | 21 | NSWindow *window = [NSWindow windowWithContentViewController:ctrller]; 22 | WindowController *ret = [[WindowController alloc] initWithWindow:window]; 23 | 24 | switch (kind) { 25 | case WindowControllerWindowKindBar: 26 | window.styleMask -= NSWindowStyleMaskTitled; 27 | window.backgroundColor = [NSColor clearColor]; 28 | // window.level = CGWindowLevelForKey(kCGMaximumWindowLevelKey); 29 | window.level = NSFloatingWindowLevel; 30 | window.collectionBehavior = NSWindowCollectionBehaviorCanJoinAllSpaces; 31 | [window setFrame:NSScreen.mainScreen.frame display:YES]; 32 | } 33 | 34 | return ret; 35 | } 36 | @end 37 | -------------------------------------------------------------------------------- /Artemisia/App/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, const char * argv[]) { 12 | @autoreleasepool { 13 | // Setup code that might create autoreleased objects goes here. 14 | } 15 | 16 | AppDelegate *del = [AppDelegate sharedDelegate]; 17 | NSApplication.sharedApplication.delegate = del; 18 | return NSApplicationMain(argc, argv); 19 | } 20 | -------------------------------------------------------------------------------- /Artemisia/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 | -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "iconfly" 67 | } 68 | } -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSAntoine/Artemisia/e7b75dfe27cd1b8353490e445ff8c76a55a9868c/Artemisia/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Artemisia/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Artemisia/Backend/EventMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // EventMonitor.h 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #ifndef EventMonitor_h 9 | #define EventMonitor_h 10 | 11 | #import 12 | #import "EventKind.h" 13 | 14 | typedef void(^EventKindUpdate)(EventBarKind, EventBarKindChange, bool); 15 | 16 | @interface EventMonitor : NSObject 17 | 18 | +(nonnull instancetype)sharedMonitor; 19 | 20 | @property (nullable) CFMachPortRef currentEventTap; 21 | @property (nullable) EventKindUpdate updateCallback; 22 | 23 | -(void)startMonitoring; 24 | -(void)pauseMonitoring; 25 | -(void)updateWithKind: (EventBarKind) kind change: (EventBarKindChange)change isFractional: (bool)isFractional; 26 | 27 | @end 28 | 29 | #endif /* EventMonitor_h */ 30 | -------------------------------------------------------------------------------- /Artemisia/Backend/EventMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // EventMonitor.m 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #import 9 | #import "EventMonitor.h" 10 | #import "AppDelegate.h" 11 | 12 | CGEventType kCGEventSystemDefined = 14; 13 | 14 | bool shouldSustainNSEvent(NSEvent *ev) { 15 | if (!ev) 16 | return false; 17 | return ev.subtype == NSEventSubtypeScreenChanged; 18 | } 19 | 20 | CGEventRef _eventMonitorCallback(CGEventTapProxy prox, CGEventType type, CGEventRef event, void * __nullable userInfo) { 21 | NSEvent *nsEvent = [NSEvent eventWithCGEvent:event]; 22 | if (!shouldSustainNSEvent(nsEvent)) 23 | return event; 24 | 25 | uint32_t keyCode = (nsEvent.data1 & 0xFFFF0000) >> 16; 26 | uint32_t keyFlags = (nsEvent.data1 & 0x0000FFFF); 27 | bool isKeyDown = (((keyFlags & 0xFF00) >> 8)) == 0xA; 28 | 29 | CGEventFlags flags = CGEventGetFlags(event); 30 | // on stock macOS, if you press the volume or brightness up/down buttons 31 | // while holding down shift & option 32 | // then the change will be 1/5 of it's usual 33 | bool isFractional = (flags & kCGEventFlagMaskShift) && (flags & kCGEventFlagMaskAlternate); 34 | 35 | if (isKeyDown) { 36 | switch (keyCode) { 37 | case NX_KEYTYPE_SOUND_DOWN: 38 | [EventMonitor.sharedMonitor updateWithKind: EventBarKindVolume change: EventBarKindChangeDecrease isFractional:isFractional]; 39 | return nil; 40 | case NX_KEYTYPE_SOUND_UP: 41 | [EventMonitor.sharedMonitor updateWithKind: EventBarKindVolume change: EventBarKindChangeIncrease isFractional:isFractional]; 42 | return nil; 43 | case NX_KEYTYPE_MUTE: 44 | [EventMonitor.sharedMonitor updateWithKind: EventBarKindVolume change:EventBarKindChangeMuted isFractional:isFractional]; 45 | return nil; 46 | case NX_KEYTYPE_BRIGHTNESS_DOWN: 47 | [EventMonitor.sharedMonitor updateWithKind: EventBarKindBrightness change: EventBarKindChangeDecrease isFractional:isFractional]; 48 | return nil; 49 | case NX_KEYTYPE_BRIGHTNESS_UP: 50 | [EventMonitor.sharedMonitor updateWithKind: EventBarKindBrightness change: EventBarKindChangeIncrease isFractional:isFractional]; 51 | return nil; 52 | default: 53 | break; 54 | } 55 | } 56 | 57 | return event; 58 | } 59 | 60 | @implementation EventMonitor 61 | 62 | + (instancetype)sharedMonitor { 63 | static EventMonitor *monitor; 64 | static dispatch_once_t onceToken; 65 | dispatch_once(&onceToken, ^{ 66 | monitor = [[EventMonitor alloc] init]; 67 | }); 68 | 69 | return monitor; 70 | } 71 | 72 | - (void)updateWithKind:(EventBarKind)kind change:(EventBarKindChange)change isFractional:(bool)isFractional { 73 | [AppDelegate.sharedDelegate.desktopVC retainOrAddBarViewWithKind:kind]; 74 | 75 | if (self.updateCallback) 76 | self.updateCallback(kind, change, isFractional); 77 | } 78 | 79 | - (void)startMonitoring { 80 | 81 | CFDictionaryRef opts = (__bridge CFDictionaryRef) @{ 82 | (__bridge NSString *)kAXTrustedCheckOptionPrompt: @(true) 83 | }; 84 | 85 | if (!AXIsProcessTrustedWithOptions(opts)) { 86 | NSAlert *alert = [[NSAlert alloc] init]; 87 | alert.informativeText = [NSString stringWithFormat:@"In order to use %@, grant it Accessibility permissions in Settings (Privacy & Security -> Accessibility) and relaunch the app", NSRunningApplication.currentApplication.localizedName];; 88 | alert.messageText = @"Permissions Required"; 89 | 90 | [alert runModal]; 91 | return; 92 | } 93 | 94 | if (self.currentEventTap) { 95 | if (!CGEventTapIsEnabled(self.currentEventTap)) 96 | CGEventTapEnable(self.currentEventTap, YES); 97 | return; 98 | } 99 | 100 | self.currentEventTap = CGEventTapCreate(kCGHIDEventTap, 101 | kCGHeadInsertEventTap, 102 | kCGEventTapOptionDefault, 103 | CGEventMaskBit(kCGEventSystemDefined), _eventMonitorCallback, nil); 104 | CGEventTapEnable(self.currentEventTap, YES); 105 | CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, self.currentEventTap, 0); 106 | CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes); 107 | } 108 | 109 | - (void)pauseMonitoring { 110 | if (self.currentEventTap) { 111 | CGEventTapEnable(self.currentEventTap, NO); 112 | } 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /Artemisia/Backend/SystemUtilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // SystemUtilities.h 3 | // Artemisia 4 | // 5 | // Created by Serena on 15/10/2023. 6 | // 7 | 8 | #ifndef SystemUtilities_h 9 | #define SystemUtilities_h 10 | 11 | #import 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface SystemUtilites : NSObject 17 | 18 | @property (class, readonly) SystemUtilites *sharedUtilities NS_SWIFT_NAME(shared); 19 | 20 | -(float)currentVolume; 21 | 22 | -(bool)setVolume: (float)newValue; 23 | -(float)decreaseVolume: (bool)fractional; 24 | -(float)increaseVolume: (bool)fractional; 25 | 26 | @property BOOL isAudioMuted; 27 | 28 | -(AudioDeviceID)audioDeviceID; 29 | 30 | -(float)displayBrightnessWithDisplayID: (CGDirectDisplayID)displayID; 31 | 32 | -(bool)setBrightnessWithDisplayID: (CGDirectDisplayID)displayID newValue: (float)newValue; 33 | -(float)increaseBrightnessWithDisplayID: (CGDirectDisplayID)displayID isFractional: (bool)isFractional; 34 | -(float)decreaseBrightnessWithDisplayID: (CGDirectDisplayID)displayID isFractional: (bool)isFractional; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | 40 | #endif /* SystemUtilities_h */ 41 | -------------------------------------------------------------------------------- /Artemisia/Backend/SystemUtilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // SystemUtilities.m 3 | // Artemisia 4 | // 5 | // Created by Serena on 15/10/2023. 6 | // 7 | 8 | #import 9 | #import 10 | #import "SystemUtilities.h" 11 | 12 | /* Yes, they're both the same value */ 13 | #define SYSTEM_VOLUME_CHANGE_CONSTANT (0.0625) 14 | #define BRIGHTNESS_CHANGE_CONSTANT (0.0625) 15 | 16 | #define NORMALIZE_VALUE(value, min, max) MIN(MAX(min, value), max); 17 | 18 | WEAK_IMPORT_ATTRIBUTE 19 | extern CGError DisplayServicesGetBrightness(CGDirectDisplayID display, float *brightness); 20 | 21 | WEAK_IMPORT_ATTRIBUTE 22 | extern CGError DisplayServicesSetBrightness(CGDirectDisplayID display, float brightness); 23 | 24 | @implementation SystemUtilites 25 | 26 | + (instancetype)sharedUtilities { 27 | static SystemUtilites *utils; 28 | static dispatch_once_t onceToken; 29 | dispatch_once(&onceToken, ^{ 30 | utils = [[SystemUtilites alloc] init]; 31 | }); 32 | 33 | 34 | return utils; 35 | } 36 | 37 | - (AudioDeviceID)audioDeviceID { 38 | AudioObjectPropertyAddress address = { 39 | .mSelector = kAudioHardwarePropertyDefaultOutputDevice, 40 | .mScope = kAudioObjectPropertyScopeGlobal, 41 | .mElement = kAudioObjectPropertyElementMain 42 | }; 43 | 44 | if (!AudioObjectHasProperty(kAudioObjectSystemObject, &address)) { 45 | printf("%s: AudioObjectHasProperty is false\n", __func__); 46 | } 47 | 48 | uint32_t size = sizeof(AudioDeviceID); 49 | AudioObjectID result = kAudioObjectUnknown; 50 | /*OSStatus status = */AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, nil, &size, &result); 51 | return result; 52 | } 53 | 54 | -(float)currentVolume { 55 | uint32_t size = sizeof(Float32); 56 | float volume = 0; 57 | 58 | AudioDeviceID device = [self audioDeviceID]; 59 | 60 | AudioObjectPropertyAddress addr = { 61 | .mSelector = kAudioHardwareServiceDeviceProperty_VirtualMainVolume, 62 | .mScope = kAudioDevicePropertyScopeOutput, 63 | .mElement = kAudioObjectPropertyElementMain 64 | }; 65 | 66 | if (!AudioObjectHasProperty(device, &addr)) { 67 | printf("%s: AudioObjectHasProperty failed\n", __func__); 68 | } 69 | 70 | AudioObjectGetPropertyData(device, &addr, 0, nil, &size, &volume); 71 | 72 | return volume; 73 | } 74 | 75 | -(bool)setVolume: (float)newValue { 76 | AudioDeviceID device = [self audioDeviceID]; 77 | // Normalize volume 78 | float volumeToSet = newValue; 79 | 80 | AudioObjectPropertyAddress address = { 81 | .mSelector = kAudioHardwareServiceDeviceProperty_VirtualMainVolume, 82 | .mScope = kAudioDevicePropertyScopeOutput, 83 | .mElement = kAudioObjectPropertyElementMain 84 | }; 85 | 86 | if (!AudioObjectHasProperty(device, &address)) { 87 | printf("%s: AudioObjectHasProperty false\n", __func__); 88 | return false; 89 | } 90 | 91 | Boolean isSettable = true; 92 | uint32_t size = sizeof(volumeToSet); 93 | OSStatus status = AudioObjectIsPropertySettable(device, &address, &isSettable); 94 | if (status != noErr) { 95 | printf("AudioObjectIsPropertySettable returned status error\n"); 96 | return false; 97 | } 98 | 99 | if (!isSettable) { 100 | printf("isSettable is false\n"); 101 | return false; 102 | } 103 | 104 | OSStatus setStatus = AudioObjectSetPropertyData(device, &address, 0, nil, size, &volumeToSet); 105 | return setStatus == noErr; 106 | } 107 | 108 | - (float)volumeChangeAmount: (bool)fractionally { 109 | float fromDefaults = [NSUserDefaults.standardUserDefaults floatForKey:@"VolumeChangeAmount"]; 110 | float final = (fromDefaults) ? fromDefaults : SYSTEM_VOLUME_CHANGE_CONSTANT; 111 | return fractionally ? final / 5 : final; 112 | } 113 | 114 | - (float)increaseVolume:(bool)fractional { 115 | float volume = [self currentVolume]; 116 | 117 | float newValue = NORMALIZE_VALUE(volume + [self volumeChangeAmount:fractional], 0, 1); 118 | bool succeeded = [self setVolume: newValue]; 119 | return succeeded ? newValue : volume; 120 | } 121 | 122 | - (float)decreaseVolume:(bool)fractional { 123 | float volume = [self currentVolume]; 124 | 125 | float newValue = NORMALIZE_VALUE(volume - [self volumeChangeAmount:fractional], 0, 1); 126 | bool succeeded = [self setVolume: newValue]; 127 | return succeeded ? newValue : volume; 128 | } 129 | 130 | - (BOOL)isAudioMuted { 131 | AudioDeviceID deviceID = [self audioDeviceID]; 132 | 133 | uint32_t isMuted = 0; 134 | uint32_t size = sizeof(isMuted); 135 | AudioObjectPropertyAddress address = { 136 | .mSelector = kAudioDevicePropertyMute, 137 | .mScope = kAudioDevicePropertyScopeOutput, 138 | .mElement = kAudioObjectPropertyElementMain 139 | }; 140 | 141 | if (!AudioObjectHasProperty(deviceID, &address)) { 142 | printf("%s: AudioObjectHasProperty false\n", __func__); 143 | return false; 144 | } 145 | 146 | AudioObjectGetPropertyData(deviceID, &address, 0, nil, &size, &isMuted); 147 | 148 | return isMuted; 149 | } 150 | 151 | -(void)setIsAudioMuted:(BOOL)newValue { 152 | 153 | AudioDeviceID deviceID = [self audioDeviceID]; 154 | AudioObjectPropertyAddress addr = { 155 | .mSelector = kAudioDevicePropertyMute, 156 | .mScope = kAudioDevicePropertyScopeOutput, 157 | .mElement = kAudioObjectPropertyElementMain 158 | }; 159 | 160 | if (!AudioObjectHasProperty(deviceID, &addr)) { 161 | printf("%s: AudioObjectHasProperty returned false\n", __func__); 162 | return; 163 | } 164 | 165 | // (We actually need to do this) 166 | uint32_t new = newValue; 167 | 168 | AudioObjectSetPropertyData(deviceID, &addr, 0, nil, sizeof(new), &new); 169 | } 170 | 171 | - (float)_displayBrightnessIOKit { 172 | float brightness = 1.0f; 173 | io_iterator_t iterator; 174 | kern_return_t result = 175 | IOServiceGetMatchingServices(kIOMasterPortDefault, 176 | IOServiceMatching("IODisplayConnect"), 177 | &iterator); 178 | 179 | // If we were successful 180 | if (result == kIOReturnSuccess) 181 | { 182 | io_object_t service; 183 | 184 | while ((service = IOIteratorNext(iterator))) 185 | { 186 | IODisplayGetFloatParameter(service, 187 | kNilOptions, 188 | CFSTR(kIODisplayBrightnessKey), 189 | &brightness); 190 | 191 | // Let the object go 192 | IOObjectRelease(service); 193 | } 194 | } 195 | 196 | return brightness; 197 | } 198 | 199 | - (float)displayBrightnessWithDisplayID:(CGDirectDisplayID)displayID { 200 | if (DisplayServicesGetBrightness) { 201 | float ret = 0; 202 | DisplayServicesGetBrightness(displayID, &ret); 203 | return ret; 204 | } 205 | 206 | return [self _displayBrightnessIOKit]; 207 | } 208 | 209 | -(bool)setBrightnessWithDisplayID: (CGDirectDisplayID)displayID newValue: (float)newValue { 210 | // float new = MIN(MAX(0, newValue), 1); 211 | float new = newValue; 212 | if (DisplayServicesSetBrightness) { 213 | CGError error = DisplayServicesSetBrightness(CGMainDisplayID(), new); 214 | return (error == kCGErrorSuccess); 215 | } else { 216 | io_iterator_t iterator; 217 | kern_return_t result = IOServiceGetMatchingServices(kIOMasterPortDefault, 218 | IOServiceMatching("IODisplayConnect"), 219 | &iterator); 220 | 221 | if (result != kIOReturnSuccess) 222 | return false; 223 | 224 | // If we were successful 225 | io_object_t service; 226 | while ((service = IOIteratorNext(iterator))) { 227 | IODisplaySetFloatParameter(service, kNilOptions, CFSTR(kIODisplayBrightnessKey), newValue); 228 | 229 | // Let the object go 230 | IOObjectRelease(service); 231 | 232 | break; 233 | } 234 | 235 | return true; 236 | } 237 | } 238 | 239 | -(float)brightnessChangeAmount: (bool)isFractional { 240 | return isFractional ? BRIGHTNESS_CHANGE_CONSTANT / 5 : BRIGHTNESS_CHANGE_CONSTANT; 241 | } 242 | 243 | - (float)increaseBrightnessWithDisplayID:(CGDirectDisplayID)displayID isFractional:(bool)isFractional { 244 | float cur = [self displayBrightnessWithDisplayID:displayID]; 245 | 246 | float newValue = NORMALIZE_VALUE(cur + [self brightnessChangeAmount:isFractional], 0, 1); 247 | bool succeeded = [self setBrightnessWithDisplayID:CGMainDisplayID() newValue: newValue]; 248 | return succeeded ? newValue : cur; 249 | } 250 | 251 | - (float)decreaseBrightnessWithDisplayID:(CGDirectDisplayID)displayID isFractional:(bool)isFractional { 252 | float cur = [self displayBrightnessWithDisplayID:displayID]; 253 | 254 | float newValue = NORMALIZE_VALUE(cur - [self brightnessChangeAmount:isFractional], 0, 1) 255 | bool succeeded = [self setBrightnessWithDisplayID:CGMainDisplayID() newValue: newValue]; 256 | 257 | return succeeded ? newValue : cur; 258 | } 259 | @end 260 | -------------------------------------------------------------------------------- /Artemisia/Bridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "EventKind.h" 6 | #import "EventMonitor.h" 7 | #import "SystemUtilities.h" 8 | #import "AppDelegate.h" 9 | -------------------------------------------------------------------------------- /Artemisia/UI/DesktopViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DesktopViewController.h 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #ifndef DesktopViewController_h 9 | #define DesktopViewController_h 10 | 11 | #import 12 | #import "EventKind.h" 13 | 14 | typedef NS_ENUM(NSUInteger, DesktopViewPosition) { 15 | DesktopViewPositionTopLeft, 16 | DesktopViewPositionTopRight, 17 | DesktopViewPositionBottomLeft, 18 | DesktopViewPositionBottomRight, 19 | }; 20 | 21 | @interface DesktopViewController: NSViewController 22 | 23 | @property NSView *barView; 24 | @property NSTimer *timer; 25 | 26 | -(void)retainOrAddBarViewWithKind: (EventBarKind)kind; 27 | +(DesktopViewPosition)HUDPosition; 28 | 29 | @end 30 | 31 | #endif /* DesktopViewController_h */ 32 | -------------------------------------------------------------------------------- /Artemisia/UI/DesktopViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DesktopViewController.m 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #import 9 | #import 10 | #import "DesktopViewController.h" 11 | #import "EventMonitor.h" 12 | #import "AppDelegate.h" 13 | #import "Artemisia-Swift.h" 14 | 15 | @implementation DesktopViewController 16 | 17 | - (void)loadView { 18 | self.view = [[NSView alloc] init]; 19 | 20 | [self.view setFrame:NSScreen.mainScreen.frame]; 21 | } 22 | 23 | 24 | 25 | - (void)removeTimer { 26 | [self.timer invalidate]; 27 | self.timer = nil; 28 | } 29 | 30 | - (void)retainOrAddBarViewWithKind: (EventBarKind)kind { 31 | 32 | if (!self.barView) 33 | [self addBarViewWithKind:kind]; 34 | 35 | if (self.timer) 36 | [self removeTimer]; 37 | 38 | 39 | [self instantiateTimer]; 40 | } 41 | 42 | -(void)instantiateTimer { 43 | // Remove bar view & animate removal 44 | self.timer = [NSTimer timerWithTimeInterval:1.50 repeats:NO block:^(NSTimer * _Nonnull timer) { 45 | [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { 46 | context.duration = 0.25; 47 | context.allowsImplicitAnimation = YES; 48 | 49 | self.barView.animator.alphaValue = 0; 50 | [self.barView removeFromSuperview]; 51 | self.barView = nil; 52 | }]; 53 | }]; 54 | 55 | [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode]; 56 | } 57 | 58 | +(DesktopViewPosition)HUDPosition { 59 | NSNumber *num = [[NSUserDefaults standardUserDefaults] objectForKey:@"DesktopViewPosition"]; 60 | return (num != nil) ? [num unsignedIntegerValue] : DesktopViewPositionTopRight; 61 | } 62 | 63 | -(void)addBarViewWithKind: (EventBarKind)kind { 64 | self.barView = [ViewCreatorStub makeViewWithKind:kind]; 65 | 66 | self.barView.translatesAutoresizingMaskIntoConstraints = NO; 67 | self.barView.alphaValue = 0; 68 | [self.view addSubview:self.barView]; 69 | 70 | // Activate Height & Width 71 | [NSLayoutConstraint activateConstraints:@[ 72 | [self.barView.widthAnchor constraintEqualToConstant:340], 73 | [self.barView.heightAnchor constraintEqualToConstant:75] 74 | ]]; 75 | 76 | DesktopViewPosition pos = [DesktopViewController HUDPosition]; 77 | 78 | // printf("isVisible: %s\n", [NSMenu menuBarVisible] ? "Yes" : "No"); 79 | CGFloat topConst = 40; 80 | switch (pos) { 81 | case DesktopViewPositionTopLeft: 82 | [NSLayoutConstraint activateConstraints:@[ 83 | [self.barView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30], 84 | [self.barView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:topConst], 85 | ]]; 86 | break; 87 | case DesktopViewPositionTopRight: 88 | [NSLayoutConstraint activateConstraints:@[ 89 | [self.barView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20], 90 | [self.barView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:topConst], 91 | ]]; 92 | break; 93 | case DesktopViewPositionBottomLeft: 94 | [NSLayoutConstraint activateConstraints:@[ 95 | [self.barView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30], 96 | [self.barView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-30], 97 | ]]; 98 | break; 99 | case DesktopViewPositionBottomRight: 100 | [NSLayoutConstraint activateConstraints:@[ 101 | [self.barView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20], 102 | [self.barView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-30], 103 | ]]; 104 | break; 105 | default: 106 | break; 107 | } 108 | 109 | [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { 110 | context.duration = 0.40; 111 | context.allowsImplicitAnimation = YES; 112 | 113 | self.barView.animator.alphaValue = 1; 114 | }]; 115 | } 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /Artemisia/UI/EventBarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventBarView.swift 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | import SwiftUI 9 | import MacControlCenterUI 10 | 11 | struct EventBarView: View { 12 | 13 | @State var kind: EventBarKind = .volume 14 | @State var currentValue: CGFloat = 0 15 | // @State var isMuted: Bool = false 16 | // @State var curentValueSetAutomatically: Bool = false 17 | 18 | var body: some View { 19 | mainView 20 | .frame(maxWidth: .infinity, maxHeight: .infinity) 21 | .background(BackgroundVisualEffectView()) 22 | .cornerRadius(10) 23 | .onAppear { 24 | // fetch initial value 25 | initiallyFetchValues() 26 | 27 | EventMonitor.shared().updateCallback = { newValue, changeKind, isFractional in 28 | self.kind = newValue 29 | 30 | withAnimation(Animation.smooth) { 31 | switch newValue { 32 | case .volume: 33 | handleVolumeChange(change: changeKind, isFractional: isFractional) 34 | case .brightness: 35 | handleBrightnessChange(change: changeKind, isFractional: isFractional) 36 | } 37 | } 38 | } 39 | } 40 | .onChange(of: currentValue) { newValue in 41 | AppDelegate.shared().desktopVC.retainOrAddBarView(with: kind) 42 | } 43 | .shadow(radius: 10) 44 | } 45 | 46 | func initiallyFetchValues() { 47 | switch kind { 48 | case .volume: 49 | self.currentValue = SystemUtilites.shared.isAudioMuted ? 0 : CGFloat(SystemUtilites.shared.currentVolume()) 50 | case .brightness: 51 | self.currentValue = CGFloat(SystemUtilites.shared.displayBrightness(withDisplayID: currentDisplayID())) 52 | } 53 | } 54 | 55 | func handleBrightnessChange(change: EventBarKindChange, isFractional: Bool) { 56 | switch change { 57 | case .increase: 58 | self.currentValue = CGFloat(SystemUtilites.shared.increaseBrightness(withDisplayID: currentDisplayID(), 59 | isFractional: isFractional)) 60 | case .decrease: 61 | self.currentValue = CGFloat(SystemUtilites.shared.decreaseBrightness(withDisplayID: currentDisplayID(), 62 | isFractional: isFractional)) 63 | default: 64 | break 65 | } 66 | 67 | // print(SystemUtilites.shared.displayBrightness(withDisplayID: currentDisplayID())) 68 | } 69 | 70 | func currentDisplayID() -> CGDirectDisplayID { 71 | let screen = NSApplication.shared.windows.first { $0.contentView is NSHostingView }?.screen ?? NSScreen.main 72 | 73 | // Try get current display ID 74 | // if that fails, just fall back to the *main* display ID 75 | return (screen?.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID) ?? CGMainDisplayID() 76 | } 77 | 78 | func handleVolumeChange(change: EventBarKindChange, isFractional: Bool) { 79 | switch change { 80 | case .increase: 81 | if SystemUtilites.shared.isAudioMuted { SystemUtilites.shared.isAudioMuted = false } 82 | self.currentValue = CGFloat(SystemUtilites.shared.increaseVolume(isFractional)) 83 | case .decrease: 84 | if SystemUtilites.shared.isAudioMuted { SystemUtilites.shared.isAudioMuted = false } 85 | self.currentValue = CGFloat(SystemUtilites.shared.decreaseVolume(isFractional)) 86 | case .muted: 87 | SystemUtilites.shared.isAudioMuted.toggle() 88 | 89 | self.currentValue = CGFloat(SystemUtilites.shared.isAudioMuted ? 0 : SystemUtilites.shared.currentVolume()) 90 | } 91 | } 92 | 93 | @ViewBuilder 94 | var mainView: some View { 95 | switch kind { 96 | case .brightness: 97 | HStack { 98 | Text("Brightness") 99 | .bold() 100 | .padding(.horizontal) 101 | Spacer() 102 | } 103 | MenuSlider(value: $currentValue, image: BrightnessSliderImage()) { /* newValue from gesture, so we can't use onChange(of:)*/ newValue in 104 | SystemUtilites.shared.setBrightnessWithDisplayID(currentDisplayID(), newValue: Float(newValue)) 105 | } 106 | .padding(.horizontal) 107 | case .volume: 108 | HStack { 109 | Text("Volume") 110 | .bold() 111 | .padding(.horizontal) 112 | Spacer() 113 | } 114 | MenuVolumeSlider(value: $currentValue) { newValue in 115 | if SystemUtilites.shared.isAudioMuted { SystemUtilites.shared.isAudioMuted = false } 116 | SystemUtilites.shared.setVolume(Float(newValue)) 117 | } 118 | .padding(.horizontal) 119 | } 120 | } 121 | } 122 | 123 | fileprivate struct BrightnessSliderImage: MenuSliderImage { 124 | func image(for value: CGFloat, oldValue: CGFloat?, force: Bool) -> MenuSliderImageUpdate? { 125 | return .newImage(image) 126 | } 127 | 128 | let image = Image(systemName: "sun.max.fill") 129 | 130 | func transform(image: Image, for value: CGFloat) -> AnyView? { 131 | return AnyView( 132 | image.resizable() 133 | .frame(width: 15, height: 15) 134 | .padding(.horizontal, 4) 135 | ) 136 | } 137 | } 138 | 139 | fileprivate struct BackgroundVisualEffectView: NSViewRepresentable { 140 | func makeNSView(context: Context) -> some NSView { 141 | let v = NSVisualEffectView() 142 | v.material = .menu 143 | v.state = .active 144 | 145 | return v 146 | } 147 | 148 | func updateNSView(_ nsView: NSViewType, context: Context) {} 149 | } 150 | -------------------------------------------------------------------------------- /Artemisia/UI/EventKind.h: -------------------------------------------------------------------------------- 1 | // 2 | // EventKind.h 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | #ifndef EventKind_h 9 | #define EventKind_h 10 | 11 | #import 12 | 13 | typedef NS_CLOSED_ENUM(NSUInteger, EventBarKind) { 14 | EventBarKindVolume, 15 | EventBarKindBrightness, 16 | }; 17 | 18 | typedef NS_CLOSED_ENUM(NSUInteger, EventBarKindChange) { 19 | EventBarKindChangeIncrease, 20 | EventBarKindChangeDecrease, 21 | EventBarKindChangeMuted, // Only applies to audio 22 | }; 23 | 24 | 25 | #endif /* EventKind_h */ 26 | -------------------------------------------------------------------------------- /Artemisia/UI/ViewCreatorStub.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewCreatorStub.swift 3 | // Artemisia 4 | // 5 | // Created by Serena on 14/10/2023. 6 | // 7 | 8 | import SwiftUI 9 | import Cocoa 10 | 11 | class CustomHostingView: NSHostingView { 12 | override func viewWillMove(toWindow newWindow: NSWindow?) { 13 | newWindow?.hasShadow = false 14 | } 15 | } 16 | 17 | @objc 18 | class ViewCreatorStub: NSObject { 19 | @objc 20 | static func makeView(withKind kind: EventBarKind) -> NSView { 21 | return CustomHostingView(rootView: EventBarView(kind: kind)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Artemisia 2 | ## Beautify your Volume/Brightness HUDs. 3 | Artemisia beautifies your Volume/Brightness HUD by getting rid of the intrusive stock one and replacing it with a beautiful, non-obstructive UI. 4 | 5 | https://github.com/NSAntoine/Artemisia/assets/48022799/5f085f2d-b724-40db-831b-314f3014094a 6 | 7 | Screenshot 2023-10-17 at 7 32 47 PM 8 | 9 | Screenshot 2023-10-17 at 7 18 45 PM 10 | 11 | 12 | Available for macOS 11+. 13 | --------------------------------------------------------------------------------