From 503c38a1fb7ae067c03fe41d95b7dcb7d4863846 Mon Sep 17 00:00:00 2001 From: Alin Panaitiu Date: Fri, 15 Sep 2023 09:39:41 +0300 Subject: [PATCH] Split views into files --- .gitignore | 2 + .gitsecret/paths/mapping.cfg | 9 +- Lunar.xcodeproj/project.pbxproj | 169 +- .../xcshareddata/swiftpm/Package.resolved | 22 +- .../{Debug.xcscheme => Lunar.xcscheme} | 2 +- .../xcshareddata/xcschemes/Release.xcscheme | 84 - .../xcschemes/xcschememanagement.plist | 9 +- .../xcshareddata/swiftpm/Package.resolved | 30 +- Lunar/AppDelegate.swift | 14 +- .../QuickActionsViewController.swift | 2408 ----------------- Lunar/DDC/DDC.c | 4 +- Lunar/DDC/DDC.swift | 27 +- Lunar/DDC/DDC2.c.secret | Bin 2422 -> 2435 bytes Lunar/DDC/Lunar-Bridging-Header.h | 1 - Lunar/Data/Display.swift | 1 - Lunar/Data/Pro.swift.secret | Bin 40320 -> 25594 bytes Lunar/Data/SwiftyLogger.swift | 32 +- Lunar/Data/Util.swift | 108 +- Lunar/Headers/Extensions.m | 4 +- Lunar/Modes/AdaptiveMode.swift | 2 +- Lunar/Modes/SensorMode.swift.secret | Bin 6047 -> 5675 bytes Lunar/Modes/SyncMode.swift.secret | Bin 7706 -> 7440 bytes Lunar/SwiftUIViews/AdvancedSettingsView.swift | 308 +++ Lunar/SwiftUIViews/AllDisplaysView.swift | 58 + .../BlackoutPopoverHeaderView.swift | 25 + .../SwiftUIViews/BlackoutPopoverRowView.swift | 48 + Lunar/SwiftUIViews/BlackoutPopoverView.swift | 93 + .../DisconnectedDisplayView.swift | 103 + Lunar/SwiftUIViews/DisplayRowView.swift | 404 +++ Lunar/SwiftUIViews/HDRSettingsView.swift | 222 ++ Lunar/SwiftUIViews/ManageButtonView.swift | 29 + Lunar/SwiftUIViews/NeedsLunarProView.swift | 19 + Lunar/SwiftUIViews/NitsTextField.swift | 60 + .../PaddedPopoverView.swift | 18 + Lunar/SwiftUIViews/PowerOffButtonView.swift | 127 + Lunar/SwiftUIViews/PresetButtonView.swift | 13 + .../SwiftUIViews/QuickActionsLayoutView.swift | 95 + Lunar/SwiftUIViews/QuickActionsMenuView.swift | 620 +++++ Lunar/SwiftUIViews/QuickActionsView.swift | 12 + Lunar/SwiftUIViews/RawValuesView.swift | 46 + Lunar/SwiftUIViews/TextInputView.swift | 17 + Lunar/SwiftUIViews/UnmanagedDisplayView.swift | 22 + Lunar/SwiftUIViews/UsefulInfo.swift | 48 + Lunar/SwiftUIViews/ViewGeometry.swift | 10 + Lunar/Utils/Extensions.swift | 168 +- Lunar/Views/ModernWindow.swift | 4 +- Lunar/Views/OSDWindow.swift | 12 +- Lunar/Views/ScrollViewIfNeeded.swift | 4 +- Lunar/required.swift.secret | Bin 0 -> 30338 bytes LunarShortcuts/LunarShortcuts.swift | 151 +- Makefile | 14 +- ReleaseNotes/6.2.6.md | 1 + 52 files changed, 2781 insertions(+), 2898 deletions(-) rename Lunar.xcodeproj/xcshareddata/xcschemes/{Debug.xcscheme => Lunar.xcscheme} (99%) delete mode 100644 Lunar.xcodeproj/xcshareddata/xcschemes/Release.xcscheme create mode 100644 Lunar/SwiftUIViews/AdvancedSettingsView.swift create mode 100644 Lunar/SwiftUIViews/AllDisplaysView.swift create mode 100644 Lunar/SwiftUIViews/BlackoutPopoverHeaderView.swift create mode 100644 Lunar/SwiftUIViews/BlackoutPopoverRowView.swift create mode 100644 Lunar/SwiftUIViews/BlackoutPopoverView.swift create mode 100644 Lunar/SwiftUIViews/DisconnectedDisplayView.swift create mode 100644 Lunar/SwiftUIViews/DisplayRowView.swift create mode 100644 Lunar/SwiftUIViews/HDRSettingsView.swift create mode 100644 Lunar/SwiftUIViews/ManageButtonView.swift create mode 100644 Lunar/SwiftUIViews/NeedsLunarProView.swift create mode 100644 Lunar/SwiftUIViews/NitsTextField.swift create mode 100644 Lunar/SwiftUIViews/PaddedPopoverView.swift create mode 100644 Lunar/SwiftUIViews/PowerOffButtonView.swift create mode 100644 Lunar/SwiftUIViews/PresetButtonView.swift create mode 100644 Lunar/SwiftUIViews/QuickActionsLayoutView.swift create mode 100644 Lunar/SwiftUIViews/QuickActionsMenuView.swift create mode 100644 Lunar/SwiftUIViews/QuickActionsView.swift create mode 100644 Lunar/SwiftUIViews/RawValuesView.swift create mode 100644 Lunar/SwiftUIViews/TextInputView.swift create mode 100644 Lunar/SwiftUIViews/UnmanagedDisplayView.swift create mode 100644 Lunar/SwiftUIViews/UsefulInfo.swift create mode 100644 Lunar/SwiftUIViews/ViewGeometry.swift create mode 100644 Lunar/required.swift.secret diff --git a/.gitignore b/.gitignore index 5691ca64..8f063d8f 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ Lunar/DDC/DDC2.h Lunar/DDC/DDC2.c old_updates .ruby-lsp +Retired.swift +Lunar/required.swift diff --git a/.gitsecret/paths/mapping.cfg b/.gitsecret/paths/mapping.cfg index e12a026c..9bd995d1 100644 --- a/.gitsecret/paths/mapping.cfg +++ b/.gitsecret/paths/mapping.cfg @@ -1,9 +1,10 @@ Lunar/Resources/dsa_priv.pem:4724e8ee0055cf3ddc55ca528691955d68879ec8abaafd3200cdfa20298ea1d9 Lunar/Resources/eddsa_priv:d079018c2b1c003c9e239ea8f8cc999b7d98adfd0616911ba0263dd20629e646 -Lunar/Modes/SensorMode.swift:9f798c2648117de29c66ed008ff85f699bccec3e018118f3a0473501585e2afe -Lunar/Modes/SyncMode.swift:923d6e3fda70191799b9247db94e96a7f407fcd74d7c57e7a2e39198caebd62e +Lunar/Modes/SensorMode.swift:5463b100b6cea38f5f9475e30832286a7c502e279182f3cc9530c4730ae7843a +Lunar/Modes/SyncMode.swift:8bfee97d9fc77e674ce4adff7aecb7c70d5c7734d8453db403c80f43af453fe7 Lunar/Modes/LocationMode.swift:b76654d4be9bcad914ef5cb5d91cf5dcb6b6fa3e152da84f17402b9ac7accb61 -Lunar/Data/Pro.swift:961e08602ceb9971b7038700db4501381cceb40dcd5dc37800835fba49b5dd90 +Lunar/Data/Pro.swift:5af963cd110177933651cee5e7fcf975c8b4156729b3c229f651183c39346794 Lunar/Modes/ClockMode.swift:94e469f2e134069f3a1a57743299c4ed1119d241e42d4fb49b92e62197061557 Lunar/DDC/DDC2.h:f1472daaae41855fa32901fedc441fd14e958fefe938f7829e9679d3ca7684c6 -Lunar/DDC/DDC2.c:e55a1aa8c81ab991b46a88d1564911d1d3fa2486b928b784d285d208522f89ce +Lunar/DDC/DDC2.c:d396061e84fe2cdc7679d6936656824d1e2e89f5412cee7af2965916447fdd18 +Lunar/required.swift:9e2efda4a18426d4605992625334a4d6eece11c8aa632c39123fc12bb14475ba diff --git a/Lunar.xcodeproj/project.pbxproj b/Lunar.xcodeproj/project.pbxproj index 7bef59c6..4d0312ed 100644 --- a/Lunar.xcodeproj/project.pbxproj +++ b/Lunar.xcodeproj/project.pbxproj @@ -12,6 +12,29 @@ C7045DB82440DEEE002289E5 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = C7045DB72440DEEE002289E5 /* SwiftyJSON */; }; C7045DC12441015C002289E5 /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = C7045DC02441015C002289E5 /* SwiftDate */; }; C705A04D268BA02E001ABBA9 /* AXSwift in Frameworks */ = {isa = PBXBuildFile; productRef = C705A04C268BA02E001ABBA9 /* AXSwift */; }; + C70A793D2AB471F900289426 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = C70A793C2AB471F900289426 /* SwiftUIIntrospect */; }; + C70A79542AB4A11600289426 /* ManageButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A793F2AB4A11600289426 /* ManageButtonView.swift */; }; + C70A79552AB4A11600289426 /* NitsTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79402AB4A11600289426 /* NitsTextField.swift */; }; + C70A79562AB4A11600289426 /* TextInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79412AB4A11600289426 /* TextInputView.swift */; }; + C70A79572AB4A11600289426 /* ViewGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79422AB4A11600289426 /* ViewGeometry.swift */; }; + C70A79582AB4A11600289426 /* AdvancedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79432AB4A11600289426 /* AdvancedSettingsView.swift */; }; + C70A79592AB4A11600289426 /* PresetButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79442AB4A11600289426 /* PresetButtonView.swift */; }; + C70A795A2AB4A11600289426 /* AllDisplaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79452AB4A11600289426 /* AllDisplaysView.swift */; }; + C70A795B2AB4A11600289426 /* UnmanagedDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79462AB4A11600289426 /* UnmanagedDisplayView.swift */; }; + C70A795C2AB4A11600289426 /* PaddedPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79472AB4A11600289426 /* PaddedPopoverView.swift */; }; + C70A795D2AB4A11600289426 /* HDRSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79482AB4A11600289426 /* HDRSettingsView.swift */; }; + C70A795E2AB4A11600289426 /* DisconnectedDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79492AB4A11600289426 /* DisconnectedDisplayView.swift */; }; + C70A795F2AB4A11600289426 /* PowerOffButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A794A2AB4A11600289426 /* PowerOffButtonView.swift */; }; + C70A79602AB4A11600289426 /* DisplayRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A794B2AB4A11600289426 /* DisplayRowView.swift */; }; + C70A79612AB4A11600289426 /* QuickActionsLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A794C2AB4A11600289426 /* QuickActionsLayoutView.swift */; }; + C70A79622AB4A11600289426 /* QuickActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A794D2AB4A11600289426 /* QuickActionsView.swift */; }; + C70A79632AB4A11600289426 /* QuickActionsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A794E2AB4A11600289426 /* QuickActionsMenuView.swift */; }; + C70A79642AB4A11600289426 /* BlackoutPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A794F2AB4A11600289426 /* BlackoutPopoverView.swift */; }; + C70A79652AB4A11600289426 /* BlackoutPopoverHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79502AB4A11600289426 /* BlackoutPopoverHeaderView.swift */; }; + C70A79662AB4A11600289426 /* RawValuesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79512AB4A11600289426 /* RawValuesView.swift */; }; + C70A79672AB4A11600289426 /* UsefulInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79522AB4A11600289426 /* UsefulInfo.swift */; }; + C70A79682AB4A11600289426 /* BlackoutPopoverRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79532AB4A11600289426 /* BlackoutPopoverRowView.swift */; }; + C70A796A2AB4A1EB00289426 /* NeedsLunarProView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70A79692AB4A1EB00289426 /* NeedsLunarProView.swift */; }; C7138D8D2634AF0500406EE3 /* KeyHolder in Frameworks */ = {isa = PBXBuildFile; productRef = C7138D8C2634AF0500406EE3 /* KeyHolder */; }; C7138D962634B02900406EE3 /* Paddle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C7138D952634B02900406EE3 /* Paddle.framework */; }; C7138D982634B03400406EE3 /* Paddle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C7138D952634B02900406EE3 /* Paddle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -57,12 +80,10 @@ C7521D34226C762B0062EC81 /* DDC.c in Sources */ = {isa = PBXBuildFile; fileRef = C7521D32226C762A0062EC81 /* DDC.c */; }; C7521D37226C77510062EC81 /* DDC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7151944224E34BA0024C6F6 /* DDC.swift */; }; C7545E3225B1996300383AFB /* PopUpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7545E3125B1996300383AFB /* PopUpButton.swift */; }; - C75FFA0A266D24AA0034DDD6 /* Burritos in Frameworks */ = {isa = PBXBuildFile; productRef = C75FFA09266D24AA0034DDD6 /* Burritos */; }; C76168FA29B8B79F00D0A33A /* Bridge.h in Headers */ = {isa = PBXBuildFile; fileRef = C76168F929B8B79F00D0A33A /* Bridge.h */; }; C76168FC29B8B91C00D0A33A /* SidecarCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C76168FB29B8B91C00D0A33A /* SidecarCore.framework */; }; C762AAEA271F3D6200198EDA /* ColorsPopoverController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C762AAE8271F3D6200198EDA /* ColorsPopoverController.swift */; }; C7656C56277BBAAD00F4922A /* Swizzle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7656C55277BBAAD00F4922A /* Swizzle.swift */; }; - C765E4FB26FF9F3500FD6463 /* CryptorECC in Frameworks */ = {isa = PBXBuildFile; productRef = C765E4FA26FF9F3500FD6463 /* CryptorECC */; }; C7683425245227290087946C /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7683424245227290087946C /* DisplayName.swift */; }; C76A129C20D795A7005D4BE3 /* ModernWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76A129B20D795A7005D4BE3 /* ModernWindow.swift */; }; C76A129E20D8084E005D4BE3 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76A129D20D8084E005D4BE3 /* NumberFormatter.swift */; }; @@ -80,7 +101,6 @@ C77B176927BE430200B957D1 /* SimplyCoreAudio in Frameworks */ = {isa = PBXBuildFile; productRef = C77B176827BE430200B957D1 /* SimplyCoreAudio */; }; C77C77522811A77B006326BE /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C77C77512811A77B006326BE /* Sparkle.framework */; }; C77C77532811A77B006326BE /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C77C77512811A77B006326BE /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C77C77582812AA80006326BE /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = C77C77572812AA80006326BE /* Introspect */; }; C77CC7551FED5501009AA718 /* SplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77CC7541FED5501009AA718 /* SplitViewController.swift */; }; C77CC7591FED9ECE009AA718 /* DisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77CC7581FED9ECE009AA718 /* DisplayViewController.swift */; }; C77CC75C1FEDB3A3009AA718 /* DisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77CC75B1FEDB3A3009AA718 /* DisplayView.swift */; }; @@ -163,6 +183,7 @@ C7DD622F2614CBAA00B07B31 /* PaddedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD622E2614CBAA00B07B31 /* PaddedButton.swift */; }; C7DE9391201A40B00017F965 /* ScrollableTextFieldCaption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE9390201A40B00017F965 /* ScrollableTextFieldCaption.swift */; }; C7DED369267A0663002E0188 /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = C7DED368267A0663002E0188 /* LaunchAtLoginController.m */; }; + C7DF1A202AB609B200AF8EDC /* required.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DF1A1F2AB609B200AF8EDC /* required.swift */; }; C7E07373259BAC660019C9C2 /* AdaptiveMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E07372259BAC660019C9C2 /* AdaptiveMode.swift */; }; C7E0737A259CB2C20019C9C2 /* ManualMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E07379259CB2C20019C9C2 /* ManualMode.swift */; }; C7E0737C259CB2FA0019C9C2 /* LocationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E0737B259CB2FA0019C9C2 /* LocationMode.swift */; }; @@ -216,6 +237,28 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + C70A793F2AB4A11600289426 /* ManageButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManageButtonView.swift; sourceTree = ""; }; + C70A79402AB4A11600289426 /* NitsTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NitsTextField.swift; sourceTree = ""; }; + C70A79412AB4A11600289426 /* TextInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextInputView.swift; sourceTree = ""; }; + C70A79422AB4A11600289426 /* ViewGeometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewGeometry.swift; sourceTree = ""; }; + C70A79432AB4A11600289426 /* AdvancedSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsView.swift; sourceTree = ""; }; + C70A79442AB4A11600289426 /* PresetButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresetButtonView.swift; sourceTree = ""; }; + C70A79452AB4A11600289426 /* AllDisplaysView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllDisplaysView.swift; sourceTree = ""; }; + C70A79462AB4A11600289426 /* UnmanagedDisplayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnmanagedDisplayView.swift; sourceTree = ""; }; + C70A79472AB4A11600289426 /* PaddedPopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PaddedPopoverView.swift"; sourceTree = ""; }; + C70A79482AB4A11600289426 /* HDRSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HDRSettingsView.swift; sourceTree = ""; }; + C70A79492AB4A11600289426 /* DisconnectedDisplayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisconnectedDisplayView.swift; sourceTree = ""; }; + C70A794A2AB4A11600289426 /* PowerOffButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PowerOffButtonView.swift; sourceTree = ""; }; + C70A794B2AB4A11600289426 /* DisplayRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayRowView.swift; sourceTree = ""; }; + C70A794C2AB4A11600289426 /* QuickActionsLayoutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsLayoutView.swift; sourceTree = ""; }; + C70A794D2AB4A11600289426 /* QuickActionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsView.swift; sourceTree = ""; }; + C70A794E2AB4A11600289426 /* QuickActionsMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsMenuView.swift; sourceTree = ""; }; + C70A794F2AB4A11600289426 /* BlackoutPopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlackoutPopoverView.swift; sourceTree = ""; }; + C70A79502AB4A11600289426 /* BlackoutPopoverHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlackoutPopoverHeaderView.swift; sourceTree = ""; }; + C70A79512AB4A11600289426 /* RawValuesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawValuesView.swift; sourceTree = ""; }; + C70A79522AB4A11600289426 /* UsefulInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsefulInfo.swift; sourceTree = ""; }; + C70A79532AB4A11600289426 /* BlackoutPopoverRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlackoutPopoverRowView.swift; sourceTree = ""; }; + C70A79692AB4A1EB00289426 /* NeedsLunarProView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeedsLunarProView.swift; sourceTree = ""; }; C7138D952634B02900406EE3 /* Paddle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Paddle.framework; sourceTree = ""; }; C714C997280572920061588E /* ProViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProViews.swift; sourceTree = ""; }; C714C99A2805BDEA0061588E /* SkyLight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SkyLight.framework; path = ../../../../../System/Library/PrivateFrameworks/SkyLight.framework; sourceTree = ""; }; @@ -357,6 +400,7 @@ C7DE9390201A40B00017F965 /* ScrollableTextFieldCaption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableTextFieldCaption.swift; sourceTree = ""; }; C7DED367267A0639002E0188 /* LaunchAtLoginController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = ""; }; C7DED368267A0663002E0188 /* LaunchAtLoginController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = ""; }; + C7DF1A1F2AB609B200AF8EDC /* required.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = required.swift; sourceTree = ""; }; C7E07372259BAC660019C9C2 /* AdaptiveMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveMode.swift; sourceTree = ""; }; C7E07379259CB2C20019C9C2 /* ManualMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualMode.swift; sourceTree = ""; }; C7E0737B259CB2FA0019C9C2 /* LocationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationMode.swift; sourceTree = ""; }; @@ -395,6 +439,7 @@ files = ( C799C2F527BF67C4003A2FB9 /* Socket in Frameworks */, C73CC6AE24616462003B2658 /* Sauce in Frameworks */, + C70A793D2AB471F900289426 /* SwiftUIIntrospect in Frameworks */, C7045DAA24407CBC002289E5 /* Surge in Frameworks */, C76168FC29B8B91C00D0A33A /* SidecarCore.framework in Frameworks */, C7ECB6722750C75C00DA212D /* LDSwiftEventSourceStatic in Frameworks */, @@ -405,7 +450,6 @@ C7F99AC727175E0C00FBF192 /* SwiftyMarkdown in Frameworks */, C73B41EC263BE02E006F6783 /* LetsMove.framework in Frameworks */, C77AB65C269A032E0046BA78 /* Magnet in Frameworks */, - C765E4FB26FF9F3500FD6463 /* CryptorECC in Frameworks */, C705A04D268BA02E001ABBA9 /* AXSwift in Frameworks */, C7138D962634B02900406EE3 /* Paddle.framework in Frameworks */, C7E80E352637060F004718A4 /* libcrypto.a in Frameworks */, @@ -427,10 +471,8 @@ C7E896DD2652C37E0079A328 /* CoreBrightness.framework in Frameworks */, C701A23F2546C8BF004747B5 /* MediaKeyTap in Frameworks */, C7B5E8592548B3F900530B47 /* Defaults in Frameworks */, - C75FFA0A266D24AA0034DDD6 /* Burritos in Frameworks */, C7138D8D2634AF0500406EE3 /* KeyHolder in Frameworks */, C714C99B2805BDEA0061588E /* SkyLight.framework in Frameworks */, - C77C77582812AA80006326BE /* Introspect in Frameworks */, C73B9A0D25FB9AD7003184FC /* Glob in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -445,6 +487,35 @@ name = Packages; sourceTree = ""; }; + C70A793E2AB4A11600289426 /* SwiftUIViews */ = { + isa = PBXGroup; + children = ( + C70A79692AB4A1EB00289426 /* NeedsLunarProView.swift */, + C70A793F2AB4A11600289426 /* ManageButtonView.swift */, + C70A79402AB4A11600289426 /* NitsTextField.swift */, + C70A79412AB4A11600289426 /* TextInputView.swift */, + C70A79422AB4A11600289426 /* ViewGeometry.swift */, + C70A79432AB4A11600289426 /* AdvancedSettingsView.swift */, + C70A79442AB4A11600289426 /* PresetButtonView.swift */, + C70A79452AB4A11600289426 /* AllDisplaysView.swift */, + C70A79462AB4A11600289426 /* UnmanagedDisplayView.swift */, + C70A79472AB4A11600289426 /* PaddedPopoverView.swift */, + C70A79482AB4A11600289426 /* HDRSettingsView.swift */, + C70A79492AB4A11600289426 /* DisconnectedDisplayView.swift */, + C70A794A2AB4A11600289426 /* PowerOffButtonView.swift */, + C70A794B2AB4A11600289426 /* DisplayRowView.swift */, + C70A794C2AB4A11600289426 /* QuickActionsLayoutView.swift */, + C70A794D2AB4A11600289426 /* QuickActionsView.swift */, + C70A794E2AB4A11600289426 /* QuickActionsMenuView.swift */, + C70A794F2AB4A11600289426 /* BlackoutPopoverView.swift */, + C70A79502AB4A11600289426 /* BlackoutPopoverHeaderView.swift */, + C70A79512AB4A11600289426 /* RawValuesView.swift */, + C70A79522AB4A11600289426 /* UsefulInfo.swift */, + C70A79532AB4A11600289426 /* BlackoutPopoverRowView.swift */, + ); + path = SwiftUIViews; + sourceTree = ""; + }; C7151945224E34BA0024C6F6 /* DDC */ = { isa = PBXGroup; children = ( @@ -648,6 +719,8 @@ C7AEC7F71FD0B4350039B562 /* Lunar */ = { isa = PBXGroup; children = ( + C7DF1A1F2AB609B200AF8EDC /* required.swift */, + C70A793E2AB4A11600289426 /* SwiftUIViews */, C772802529AE09D900401C78 /* edid-decode */, C73B41F5263E1301006F6783 /* ddcctl */, C7E896D72652C0F60079A328 /* Headers */, @@ -830,18 +903,16 @@ C740A1F726249176004BC1C7 /* ArgumentParser */, C7138D8C2634AF0500406EE3 /* KeyHolder */, C7F48C492657A258000715A8 /* DataCompression */, - C75FFA09266D24AA0034DDD6 /* Burritos */, C705A04C268BA02E001ABBA9 /* AXSwift */, C77AB65B269A032E0046BA78 /* Magnet */, - C765E4FA26FF9F3500FD6463 /* CryptorECC */, C7F99AC627175E0C00FBF192 /* SwiftyMarkdown */, C7ECB6712750C75C00DA212D /* LDSwiftEventSourceStatic */, C77B176827BE430200B957D1 /* SimplyCoreAudio */, C799C2F427BF67C4003A2FB9 /* Socket */, C731E85D27E1E5090016B36D /* Sentry */, - C77C77572812AA80006326BE /* Introspect */, C7BD5F84287B44130077CCB7 /* Charts */, C7A7F2532913ECCA00CD450C /* FuzzyMatcher */, + C70A793C2AB471F900289426 /* SwiftUIIntrospect */, ); productName = Lunar; productReference = C7AEC7F51FD0B4350039B562 /* Lunar.app */; @@ -853,8 +924,9 @@ C7AEC7ED1FD0B4350039B562 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = NO; LastSwiftUpdateCheck = 1410; - LastUpgradeCheck = 1400; + LastUpgradeCheck = 1500; ORGANIZATIONNAME = Alin; TargetAttributes = { C7AEC7F41FD0B4350039B562 = { @@ -892,10 +964,8 @@ C740A1F626249176004BC1C7 /* XCRemoteSwiftPackageReference "swift-argument-parser" */, C7138D8B2634AF0500406EE3 /* XCRemoteSwiftPackageReference "KeyHolder" */, C7F48C482657A258000715A8 /* XCRemoteSwiftPackageReference "DataCompression" */, - C75FFA08266D24AA0034DDD6 /* XCRemoteSwiftPackageReference "Burritos" */, C705A04B268BA02E001ABBA9 /* XCRemoteSwiftPackageReference "AXSwift" */, C77AB65A269A032E0046BA78 /* XCRemoteSwiftPackageReference "Magnet" */, - C765E4F926FF9F3500FD6463 /* XCRemoteSwiftPackageReference "BlueECC" */, C7F99AC527175E0C00FBF192 /* XCRemoteSwiftPackageReference "SwiftyMarkdown" */, C7ECB6702750C75C00DA212D /* XCRemoteSwiftPackageReference "swift-eventsource" */, C77B176727BE430200B957D1 /* XCRemoteSwiftPackageReference "SimplyCoreAudio" */, @@ -954,6 +1024,7 @@ outputFileListPaths = ( ); outputPaths = ( + "$(CODESIGNING_FOLDER_PATH)/Contents/_MASReceipt/receipt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -966,19 +1037,27 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C70A79682AB4A11600289426 /* BlackoutPopoverRowView.swift in Sources */, C7521D37226C77510062EC81 /* DDC.swift in Sources */, C71F8DBA265640C400C4648A /* DDCCTLControl.swift in Sources */, + C70A795E2AB4A11600289426 /* DisconnectedDisplayView.swift in Sources */, C7521D34226C762B0062EC81 /* DDC.c in Sources */, + C70A79662AB4A11600289426 /* RawValuesView.swift in Sources */, C7AEC7FB1FD0B4350039B562 /* PageController.swift in Sources */, C7C68F0D201D3D9800F49A2B /* SettingsView.swift in Sources */, C7A82DC3261DF513004146A8 /* Pro.swift in Sources */, C7C1F7031FD5FD390039507C /* Display.swift in Sources */, C7E80E302637060F004718A4 /* FileAttributes.swift in Sources */, + C70A795B2AB4A11600289426 /* UnmanagedDisplayView.swift in Sources */, C7A82D9F2618E1B9004146A8 /* InstallOutputViewController.swift in Sources */, C78BD04C259B365500FC823F /* Extensions.swift in Sources */, + C7DF1A202AB609B200AF8EDC /* required.swift in Sources */, + C70A79562AB4A11600289426 /* TextInputView.swift in Sources */, C78F121E27073284009A3301 /* ControlChoiceViewController.swift in Sources */, + C70A79612AB4A11600289426 /* QuickActionsLayoutView.swift in Sources */, C77CC7551FED5501009AA718 /* SplitViewController.swift in Sources */, C73A4E6429C87149006A22F3 /* Trap.swift in Sources */, + C70A79632AB4A11600289426 /* QuickActionsMenuView.swift in Sources */, C7A9AAB62707130E0058D1B2 /* OnboardPageController.swift in Sources */, C71F8DB126542E1000C4648A /* GammaViewController.swift in Sources */, C7764170272139A100C7C308 /* ResetPopoverController.swift in Sources */, @@ -994,6 +1073,7 @@ C7EF9535243F4E2200839A62 /* Sysctl.swift in Sources */, C7520C081FD20CC200756F82 /* DisplayController.swift in Sources */, C7789AE01FF9029700D326A8 /* ScrollableContrast.swift in Sources */, + C70A79582AB4A11600289426 /* AdvancedSettingsView.swift in Sources */, C7E0737A259CB2C20019C9C2 /* ManualMode.swift in Sources */, C73AAC4B22241B690063AD2A /* Hotkeys.swift in Sources */, C78A1964294E700800B0410A /* DDC2.c in Sources */, @@ -1003,6 +1083,7 @@ C79975D725DEEDE000C797FF /* Control.swift in Sources */, C7BADA12263889CD0058974C /* ProgressViewController.swift in Sources */, C72776ED201F26CB0055934F /* AppException.swift in Sources */, + C70A79592AB4A11600289426 /* PresetButtonView.swift in Sources */, C7DD622C261240A800B07B31 /* SSHConnectionViewController.swift in Sources */, C7DD622126120B7600B07B31 /* RaspberryPageController.swift in Sources */, C7DE9391201A40B00017F965 /* ScrollableTextFieldCaption.swift in Sources */, @@ -1020,6 +1101,7 @@ C740A1F4262490E1004BC1C7 /* CLI.swift in Sources */, C7E80E2F2637060F004718A4 /* FileType.swift in Sources */, C78BD055259B6BAD00FC823F /* SyncMode.swift in Sources */, + C70A795D2AB4A11600289426 /* HDRSettingsView.swift in Sources */, C7482C3427D2874D00A12E82 /* ColorizeSwift.swift in Sources */, C77CC75C1FEDB3A3009AA718 /* DisplayView.swift in Sources */, C7C68F0B201D356800F49A2B /* SettingsViewController.swift in Sources */, @@ -1027,18 +1109,22 @@ C790D4BE259B9DC100868689 /* NanoID.swift in Sources */, C7656C56277BBAAD00F4922A /* Swizzle.swift in Sources */, C77CC7591FED9ECE009AA718 /* DisplayViewController.swift in Sources */, + C70A795C2AB4A11600289426 /* PaddedPopoverView.swift in Sources */, C7A7C248259391C200745637 /* HotkeyPopoverController.swift in Sources */, C79975DB25DEEE9300C797FF /* DDCControl.swift in Sources */, + C70A79602AB4A11600289426 /* DisplayRowView.swift in Sources */, C762AAEA271F3D6200198EDA /* ColorsPopoverController.swift in Sources */, C722FB9F271AC83200ED4C94 /* Slider.swift in Sources */, C72BB4B72727E03500941708 /* Extensions.m in Sources */, C7E4747420CD5C1400B662A9 /* RemoveAppButton.swift in Sources */, C78F121A27073278009A3301 /* ModeChoiceViewController.swift in Sources */, C7DED369267A0663002E0188 /* LaunchAtLoginController.m in Sources */, + C70A795A2AB4A11600289426 /* AllDisplaysView.swift in Sources */, C7E14F7F266BBC7500A4E2F1 /* Ciao.swift in Sources */, C7A63205238C04C10005D792 /* QuickActionsViewController.swift in Sources */, C7E80E2E2637060F004718A4 /* Agent.swift in Sources */, C7E80E312637060F004718A4 /* SSHError.swift in Sources */, + C70A79622AB4A11600289426 /* QuickActionsView.swift in Sources */, C714C99E2809B4FB0061588E /* ScrollViewIfNeeded.swift in Sources */, C731260C22B38899001ED199 /* HelpButton.swift in Sources */, C7E07373259BAC660019C9C2 /* AdaptiveMode.swift in Sources */, @@ -1054,9 +1140,12 @@ C7A7C24A2593B5EA00745637 /* HotkeyButton.swift in Sources */, C78AB66720F0C73900863CB1 /* SwiftyLogger.swift in Sources */, C7AB59782396C4A90028E573 /* Transformers.swift in Sources */, + C70A796A2AB4A1EB00289426 /* NeedsLunarProView.swift in Sources */, C722FBA0271AC83200ED4C94 /* Button.swift in Sources */, + C70A79652AB4A11600289426 /* BlackoutPopoverHeaderView.swift in Sources */, C7CBEFF4238C147800031E93 /* StatusItemButtonController.swift in Sources */, C7E80E322637060F004718A4 /* SSH.swift in Sources */, + C70A795F2AB4A11600289426 /* PowerOffButtonView.swift in Sources */, C76A129E20D8084E005D4BE3 /* NumberFormatter.swift in Sources */, C772BDD725E7A296006A1684 /* NetworkControl.swift in Sources */, C7E80E332637060F004718A4 /* SSHAuthMethod.swift in Sources */, @@ -1066,19 +1155,24 @@ C76A129C20D795A7005D4BE3 /* ModernWindow.swift in Sources */, C7E80E342637060F004718A4 /* SFTP.swift in Sources */, C784143827DDFBC400E14941 /* OSDWindow.swift in Sources */, + C70A79552AB4A11600289426 /* NitsTextField.swift in Sources */, C7E80E2C2637060F004718A4 /* FilePermissions.swift in Sources */, C7A82DB7261B779E004146A8 /* SettingsPopoverController.swift in Sources */, C7CBEFFD238E81FF00031E93 /* Util.swift in Sources */, C7B6EA10201B41C10090EE0E /* Theme.swift in Sources */, + C70A79672AB4A11600289426 /* UsefulInfo.swift in Sources */, C7714AF927E4A3A9002E0B2E /* Components.swift in Sources */, C7C09CD1256AC09800B4BC5B /* AdaptiveModeButton.swift in Sources */, C72776E9201F1BED0055934F /* ExceptionsView.swift in Sources */, C75083622222DC3C0020270E /* HotKeyViewController.swift in Sources */, C7E80E292637060F004718A4 /* ReadWrite.swift in Sources */, + C70A79572AB4A11600289426 /* ViewGeometry.swift in Sources */, C7C16321208503B000C7EA8F /* ConfigurationViewController.swift in Sources */, C74820FA26DE6CFA00556EA8 /* PageNavigationControl.swift in Sources */, C7AF321926F5EEDE00AC14AF /* ClockMode.swift in Sources */, C728D7ED2296D81B003CAD65 /* TextButton.swift in Sources */, + C70A79542AB4A11600289426 /* ManageButtonView.swift in Sources */, + C70A79642AB4A11600289426 /* BlackoutPopoverView.swift in Sources */, C7C1F7071FD6354C0039507C /* Geolocation.swift in Sources */, C7A22756281C19D1005F2351 /* XDRTipView.swift in Sources */, C7C1F7051FD635360039507C /* Moment.swift in Sources */, @@ -1107,7 +1201,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; @@ -1152,7 +1245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -1169,7 +1262,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; @@ -1208,11 +1300,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_SUPPRESS_WARNINGS = YES; SWIFT_VERSION = 5.0; }; name = Release; @@ -1263,6 +1356,7 @@ "$(PROJECT_DIR)/Frameworks", "$(PROJECT_DIR)/Lunar/CSSH", ); + LLVM_LTO = YES_THIN; MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 6.2.6; MTL_ENABLE_DEBUG_INFO = NO; @@ -1314,7 +1408,7 @@ "$(PROJECT_DIR)/Frameworks/Sparkle", "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", ); - GCC_OPTIMIZATION_LEVEL = 2; + GCC_OPTIMIZATION_LEVEL = s; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/Frameworks/Shout/Sources/CSSH/libssh2"; INFOPLIST_FILE = Lunar/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lunar; @@ -1336,11 +1430,12 @@ MARKETING_VERSION = 6.2.6; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CODE_SIGN_FLAGS = "--timestamp"; + OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = fyi.lunar.Lunar; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Lunar/DDC/Lunar-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_OPTIMIZATION_LEVEL = "-Osize"; SWIFT_VERSION = 5.0; SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited)"; SYSTEM_HEADER_SEARCH_PATHS = ""; @@ -1457,22 +1552,6 @@ minimumVersion = 1.0.0; }; }; - C75FFA08266D24AA0034DDD6 /* XCRemoteSwiftPackageReference "Burritos" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/alin23/Burritos"; - requirement = { - branch = master; - kind = branch; - }; - }; - C765E4F926FF9F3500FD6463 /* XCRemoteSwiftPackageReference "BlueECC" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/Kitura/BlueECC.git"; - requirement = { - branch = master; - kind = branch; - }; - }; C77AB65A269A032E0046BA78 /* XCRemoteSwiftPackageReference "Magnet" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/alin23/Magnet"; @@ -1494,7 +1573,7 @@ repositoryURL = "https://github.com/siteline/SwiftUI-Introspect"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.1.4; + minimumVersion = 1.0.0; }; }; C78BD048259B33D500FC823F /* XCRemoteSwiftPackageReference "Path" */ = { @@ -1613,6 +1692,11 @@ package = C705A04B268BA02E001ABBA9 /* XCRemoteSwiftPackageReference "AXSwift" */; productName = AXSwift; }; + C70A793C2AB471F900289426 /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + package = C77C77562812AA80006326BE /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = SwiftUIIntrospect; + }; C7138D8C2634AF0500406EE3 /* KeyHolder */ = { isa = XCSwiftPackageProductDependency; package = C7138D8B2634AF0500406EE3 /* XCRemoteSwiftPackageReference "KeyHolder" */; @@ -1638,16 +1722,6 @@ package = C740A1F626249176004BC1C7 /* XCRemoteSwiftPackageReference "swift-argument-parser" */; productName = ArgumentParser; }; - C75FFA09266D24AA0034DDD6 /* Burritos */ = { - isa = XCSwiftPackageProductDependency; - package = C75FFA08266D24AA0034DDD6 /* XCRemoteSwiftPackageReference "Burritos" */; - productName = Burritos; - }; - C765E4FA26FF9F3500FD6463 /* CryptorECC */ = { - isa = XCSwiftPackageProductDependency; - package = C765E4F926FF9F3500FD6463 /* XCRemoteSwiftPackageReference "BlueECC" */; - productName = CryptorECC; - }; C77AB65B269A032E0046BA78 /* Magnet */ = { isa = XCSwiftPackageProductDependency; package = C77AB65A269A032E0046BA78 /* XCRemoteSwiftPackageReference "Magnet" */; @@ -1658,11 +1732,6 @@ package = C77B176727BE430200B957D1 /* XCRemoteSwiftPackageReference "SimplyCoreAudio" */; productName = SimplyCoreAudio; }; - C77C77572812AA80006326BE /* Introspect */ = { - isa = XCSwiftPackageProductDependency; - package = C77C77562812AA80006326BE /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = Introspect; - }; C78BD049259B33D500FC823F /* Path */ = { isa = XCSwiftPackageProductDependency; package = C78BD048259B33D500FC823F /* XCRemoteSwiftPackageReference "Path" */; diff --git a/Lunar.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Lunar.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bb8c0347..5adc192f 100644 --- a/Lunar.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Lunar.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,15 +18,6 @@ "revision" : "055c6abb49bb86c8d700da74d834523b03b4d702" } }, - { - "identity" : "blueecc", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Kitura/BlueECC.git", - "state" : { - "branch" : "master", - "revision" : "1485268a54f8135435a825a855e733f026fa6cc8" - } - }, { "identity" : "bluesocket", "kind" : "remoteSourceControl", @@ -36,15 +27,6 @@ "revision" : "4e334a848f89c44b2348332f0996f312dc6ed0d2" } }, - { - "identity" : "burritos", - "kind" : "remoteSourceControl", - "location" : "https://github.com/alin23/Burritos", - "state" : { - "branch" : "master", - "revision" : "3dd5867dc5b321fbd5a28d2d8b283e4252b2e4d7" - } - }, { "identity" : "charts", "kind" : "remoteSourceControl", @@ -230,8 +212,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/siteline/SwiftUI-Introspect", "state" : { - "revision" : "adb9e7a69fd75322dcdee0c20f2fb6640d6f0087", - "version" : "0.9.0" + "revision" : "3ba734dd20faada0e3234b68e78db97005315f0e", + "version" : "1.0.0" } }, { diff --git a/Lunar.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme b/Lunar.xcodeproj/xcshareddata/xcschemes/Lunar.xcscheme similarity index 99% rename from Lunar.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme rename to Lunar.xcodeproj/xcshareddata/xcschemes/Lunar.xcscheme index f226a3e4..db1bf2c1 100644 --- a/Lunar.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme +++ b/Lunar.xcodeproj/xcshareddata/xcschemes/Lunar.xcscheme @@ -1,6 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Lunar.xcodeproj/xcuserdata/alin.xcuserdatad/xcschemes/xcschememanagement.plist b/Lunar.xcodeproj/xcuserdata/alin.xcuserdatad/xcschemes/xcschememanagement.plist index a4892a44..f9a13141 100644 --- a/Lunar.xcodeproj/xcuserdata/alin.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Lunar.xcodeproj/xcuserdata/alin.xcuserdatad/xcschemes/xcschememanagement.plist @@ -25,8 +25,10 @@ orderHint 7 - Debug.xcscheme_^#shared#^_ + Lunar.xcscheme_^#shared#^_ + isShown + orderHint 2 @@ -124,11 +126,6 @@ orderHint 19 - Release.xcscheme_^#shared#^_ - - orderHint - 0 - Surge (Playground) 1.xcscheme isShown diff --git a/Lunar.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Lunar.xcworkspace/xcshareddata/swiftpm/Package.resolved index bb8c0347..ce67ef6a 100644 --- a/Lunar.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Lunar.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,15 +18,6 @@ "revision" : "055c6abb49bb86c8d700da74d834523b03b4d702" } }, - { - "identity" : "blueecc", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Kitura/BlueECC.git", - "state" : { - "branch" : "master", - "revision" : "1485268a54f8135435a825a855e733f026fa6cc8" - } - }, { "identity" : "bluesocket", "kind" : "remoteSourceControl", @@ -36,15 +27,6 @@ "revision" : "4e334a848f89c44b2348332f0996f312dc6ed0d2" } }, - { - "identity" : "burritos", - "kind" : "remoteSourceControl", - "location" : "https://github.com/alin23/Burritos", - "state" : { - "branch" : "master", - "revision" : "3dd5867dc5b321fbd5a28d2d8b283e4252b2e4d7" - } - }, { "identity" : "charts", "kind" : "remoteSourceControl", @@ -150,7 +132,7 @@ "location" : "https://github.com/alin23/sentry-cocoa.git", "state" : { "branch" : "master", - "revision" : "a890c38828010ebcdbe6b0edad84a97d06e54e0b" + "revision" : "04bee4ad86d74d4cb4d7101ff826d6e355301ba9" } }, { @@ -159,7 +141,7 @@ "location" : "https://github.com/rnine/SimplyCoreAudio", "state" : { "branch" : "develop", - "revision" : "35cc0e6eac5c2ee5049431f4238b0e333cf79869" + "revision" : "99caee2ca2bcd33f49e4e2c0d8ba898c33421508" } }, { @@ -185,8 +167,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a", - "version" : "1.2.2" + "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", + "version" : "1.2.3" } }, { @@ -230,8 +212,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/siteline/SwiftUI-Introspect", "state" : { - "revision" : "adb9e7a69fd75322dcdee0c20f2fb6640d6f0087", - "version" : "0.9.0" + "revision" : "3ba734dd20faada0e3234b68e78db97005315f0e", + "version" : "1.0.0" } }, { diff --git a/Lunar/AppDelegate.swift b/Lunar/AppDelegate.swift index 41bd4168..b4faedc6 100644 --- a/Lunar/AppDelegate.swift +++ b/Lunar/AppDelegate.swift @@ -1800,7 +1800,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, CLLocationManagerDeleg } func applicationWillFinishLaunching(_: Notification) { - #if !DEBUG + #if !DEBUG && arch(arm64) PFMoveToApplicationsFolderIfNecessary() #endif } @@ -2080,11 +2080,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, CLLocationManagerDeleg scope.setTag(value: DC.adaptiveModeString(), key: "adaptiveMode") scope.setTag(value: DC.adaptiveModeString(last: true), key: "lastAdaptiveMode") scope.setTag(value: CachedDefaults[.overrideAdaptiveMode] ? "false" : "true", key: "autoMode") - if let secondPhase = Defaults[.secondPhase] { - scope.setTag(value: secondPhase ? "true" : "false", key: "secondPhase") - } else { - scope.setTag(value: "null", key: "secondPhase") - } } } @@ -2188,7 +2183,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, CLLocationManagerDeleg startTime = Date() lastBlackOutToggleDate = Date() - Defaults[.secondPhase] = initFirstPhase() + Defaults[.secondPhase] = initSecondPhase() #if arch(arm64) if #available(macOS 13, *) { if restarted { @@ -2277,11 +2272,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, CLLocationManagerDeleg scope.setTag(value: DC.adaptiveModeString(), key: "adaptiveMode") scope.setTag(value: DC.adaptiveModeString(last: true), key: "lastAdaptiveMode") scope.setTag(value: CachedDefaults[.overrideAdaptiveMode] ? "false" : "true", key: "autoMode") - if let secondPhase = Defaults[.secondPhase] { - scope.setTag(value: secondPhase ? "false" : "true", key: "secondPhase") - } else { - scope.setTag(value: "null", key: "secondPhase") - } } DC.addSentryData() SentrySDK.capture(message: "Launch New") diff --git a/Lunar/Controllers/QuickActionsViewController.swift b/Lunar/Controllers/QuickActionsViewController.swift index deffb5ca..277c892a 100644 --- a/Lunar/Controllers/QuickActionsViewController.swift +++ b/Lunar/Controllers/QuickActionsViewController.swift @@ -21,1696 +21,6 @@ prefix func ! (value: Binding) -> Binding { ) } -// MARK: - PresetButtonView - -struct PresetButtonView: View { - @State var percent: Int8 - - var body: some View { - SwiftUI.Button("\(percent)%") { - appDelegate!.setLightPercent(percent: percent) - } - .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) - .font(.system(size: 12, weight: .medium, design: .monospaced)) - } -} - -// MARK: - PowerOffButtonView - -struct PowerOffButtonView: View { - @ObservedObject var display: Display - @ObservedObject var km = KM - @State var showPopover = false - @Default(.newBlackOutDisconnect) var newBlackOutDisconnect - @Default(.neverShowBlackoutPopover) var neverShowBlackoutPopover - @Default(.allowBlackOutOnSingleScreen) var allowBlackOutOnSingleScreen - - @State var hovering = false - @StateObject var poweringOff: ExpiringBool = false - - var actionText: String { - if km.controlKeyPressed { - if km.commandKeyPressed, !display.blackOutEnabled, DC.activeDisplayCount > 1 { - return "Ignore" - } - return "Show Help" - } - - if display.blackOutEnabled { - return "Power On" - } - - if allowBlackOutOnSingleScreen, DC.activeDisplayCount == 1 { - if km.optionKeyPressed { - return display.hasDDC ? "Power Off" : "Needs DDC" - } - return "Darken" - } - - if km.optionKeyPressed { - if km.shiftKeyPressed { - return "Focus" - } - return display.hasDDC ? "Power Off" : "Needs DDC" - } - - if km.shiftKeyPressed { - return "Darken" - } - - #if arch(arm64) - if #available(macOS 13, *), km.commandKeyPressed { - return newBlackOutDisconnect ? "BlackOut" : "Disconnect" - } - - return newBlackOutDisconnect ? "Disconnect" : "BlackOut" - #else - return "BlackOut" - #endif - } - - var color: Color { - if poweringOff.value || display.blackOutEnabled { - return Color.gray - } - - if km.controlKeyPressed { - return Color.orange - } - - if DC.activeDisplayCount == 1 { - return Colors.red - } - - if km.optionKeyPressed, !km.shiftKeyPressed, !display.hasDDC { - return Color.gray - } - return Colors.red - } - - var body: some View { - HStack(spacing: 2) { - SwiftUI.Button(action: { - if km.controlKeyPressed, km.commandKeyPressed, DC.activeDisplayCount > 1 { - display.unmanaged = true - return - } - - guard !KM.controlKeyPressed, - lunarProActive || lunarProOnTrial || (KM.optionKeyPressed && !KM.shiftKeyPressed) - else { - showPopover = true - return - } - - guard neverShowBlackoutPopover else { - showPopover = true - return - } - - poweringOff.set(true, expireAfter: 1) - if display.blackOutEnabled { - display.powerOn() - } else { - display.powerOff() - } - }) { - Image(systemName: "power").font(.system(size: 10, weight: .heavy)) - } - - .buttonStyle(FlatButton( - color: color, - circle: true, - horizontalPadding: 3, - verticalPadding: 3 - )) - .popover(isPresented: $showPopover) { - BlackoutPopoverView(hasDDC: display.hasDDC).onDisappear { - if !neverShowBlackoutPopover { - neverShowBlackoutPopover = true - } - } - } - .onHover { h in withAnimation { hovering = h } } - .disabled((km.optionKeyPressed && !km.shiftKeyPressed && !display.hasDDC) || poweringOff.value) - - Text(actionText) - .font(.system(size: 10, weight: .semibold)) - .opacity(hovering ? 1 : 0) - } - .frame(width: 100, alignment: .leading) - } -} - -#if arch(arm64) - @available(macOS 13, *) - struct ReconnectButtonView: View { - @State var display: CGDirectDisplayID - @State var hovering = false - @State var off = true - - var body: some View { - HStack(spacing: 2) { - SwiftUI.Button(action: { - off = false - DC.autoBlackoutPause = true - DC.en(display) - }) { - Image(systemName: "power").font(.system(size: 10, weight: .heavy)) - } - .buttonStyle(FlatButton( - color: off ? Color.gray : Colors.red, - circle: true, - horizontalPadding: 3, - verticalPadding: 3 - )) - .onHover { h in withAnimation { hovering = h } } - Text("Connect") - .font(.system(size: 10, weight: .semibold)) - .opacity(hovering ? 1 : 0) - } - .frame(width: 100, alignment: .leading) - } - } - - @available(macOS 13, *) - struct DisconnectedDisplayView: View { - @Environment(\.colors) var colors - - @State var id: CGDirectDisplayID - @State var name: String - @State var possibly = false - - @ObservedObject var display: Display - @Default(.autoBlackoutBuiltin) var autoBlackoutBuiltin - - var body: some View { - VStack(spacing: 1) { - HStack(alignment: .top, spacing: -10) { - Text(name) - .font(.system(size: 22, weight: .black)) - .padding(6) - .background(RoundedRectangle(cornerRadius: 6, style: .continuous).fill(colors.bg.primary.opacity(0.5))) - - ReconnectButtonView(display: id) - .offset(y: -8) - }.offset(x: 45) - - Text(possibly ? "Possibly disconnected" : "Disconnected") - .font(.system(size: 10, weight: .semibold, design: .rounded)) - - if display.id == id, !display.isSidecar, !display.isAirplay { - let binding = !display.isMacBook ? $display.keepDisconnected : Binding( - get: { autoBlackoutBuiltin }, - set: { - autoBlackoutBuiltin = $0 - display.keepDisconnected = $0 - } - ) - VStack { - SettingsToggle( - text: "Auto Disconnect", - setting: binding, - color: nil, - help: !display.isMacBook - ? """ - The display might come back on by itself after standby/wake or when - reconnecting the monitor cable. - - This option will automatically disconnect the display whenever that - happens, until you reconnect the display manually using the power button. - - Note: Press ⌘ Command more than 8 times in a row to force connect all displays. - """ - : """ - Turns off the built-in screen automatically when a monitor is connected and turns - it back on when the last monitor is disconnected. - - Keeps the screen disconnected between standby/wake or lid open/close states. - - Note: Press ⌘ Command more than 8 times in a row to force connect all displays. - """ - ) - .font(.system(size: 12, weight: .medium, design: .rounded)) - } - .padding(8) - .background(RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color.primary.opacity(0.05))) - .padding(.vertical, 3) - } - } - } - } - -#endif - -struct UnmanagedDisplayView: View { - @Environment(\.colors) var colors - - @ObservedObject var display: Display - - var body: some View { - VStack(spacing: 1) { - HStack(alignment: .top, spacing: -10) { - Text(display.name) - .font(.system(size: 22, weight: .black)) - .padding(6) - .background(RoundedRectangle(cornerRadius: 6, style: .continuous).fill(colors.bg.primary.opacity(0.5))) - - ManageButtonView(display: display) - .offset(y: -8) - }.offset(x: 45) - Text("Not managed").font(.system(size: 10, weight: .semibold, design: .rounded)) - } - } -} - -struct ManageButtonView: View { - @State var display: Display - @State var hovering = false - @State var off = true - - var body: some View { - HStack(spacing: 2) { - SwiftUI.Button(action: { - off = false - display.unmanaged = false - }) { - Image(systemName: "power").font(.system(size: 10, weight: .heavy)) - } - .buttonStyle(FlatButton( - color: off ? Color.gray : Colors.red, - circle: true, - horizontalPadding: 3, - verticalPadding: 3 - )) - Text("Unignore") - .font(.system(size: 10, weight: .semibold)) - .opacity(hovering ? 1 : 0) - } - .onHover { h in withAnimation { hovering = h } } - .frame(width: 100, alignment: .leading) - } -} - -// MARK: - DisplayRowView - -struct AllDisplaysView: View { - @ObservedObject var display: Display = ALL_DISPLAYS - @Environment(\.colorScheme) var colorScheme - @Environment(\.colors) var colors - @Default(.showSliderValues) var showSliderValues - @Default(.mergeBrightnessContrast) var mergeBrightnessContrast - - @ViewBuilder var softwareSliders: some View { - if display.subzero { - BigSurSlider( - percentage: $display.softwareBrightness, - image: "moon.circle.fill", - color: Colors.subzero.opacity(0.7), - backgroundColor: Colors.subzero.opacity(colorScheme == .dark ? 0.1 : 0.2), - knobColor: Colors.subzero, - showValue: $showSliderValues - ) - } - } - - var body: some View { - VStack(spacing: 4) { - Text(display.name) - .font(.system(size: 22, weight: .black)) - .padding(.bottom, 6) - - if mergeBrightnessContrast { - BigSurSlider( - percentage: $display.preciseBrightnessContrast.f, - image: "sun.max.fill", - colorBinding: .constant(colors.accent), - backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), - showValue: $showSliderValues - ) - softwareSliders - } else { - BigSurSlider( - percentage: $display.preciseBrightness.f, - image: "sun.max.fill", - colorBinding: .constant(colors.accent), - backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), - showValue: $showSliderValues - ) - softwareSliders - BigSurSlider( - percentage: $display.preciseContrast.f, - image: "circle.righthalf.fill", - colorBinding: .constant(colors.accent), - backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), - showValue: $showSliderValues - ) - } - } - } -} - -struct DisplayRowView: View { - static var hoveringVolumeSliderTask: DispatchWorkItem? { - didSet { - oldValue?.cancel() - } - } - - @ObservedObject var display: Display - @Environment(\.colorScheme) var colorScheme - @Environment(\.colors) var colors - @Default(.showSliderValues) var showSliderValues - #if arch(arm64) - @Default(.syncNits) var syncNits - #endif - - @Default(.showInputInQuickActions) var showInputInQuickActions - @Default(.showPowerInQuickActions) var showPowerInQuickActions - @Default(.showXDRSelector) var showXDRSelector - @Default(.showRawValues) var showRawValues - @Default(.xdrTipShown) var xdrTipShown - @Default(.autoXdr) var autoXdr - @Default(.syncMode) var syncMode - - @State var showNeedsLunarPro = false - @State var showXDRTip = false - @State var showSubzero = false - @State var showXDR = false - - @State var hoveringVolumeSlider = false - - @Default(.autoBlackoutBuiltin) var autoBlackoutBuiltin - - @State var adaptiveStateText = "" - @State var adaptivePausedText = "Adaptive brightness paused" - - @State var editingMaxNits = false - @State var editingMinNits = false - - @State var hovering = false - - var softwareSliders: some View { - Group { - if display.enhanced { - BigSurSlider( - percentage: $display.xdrBrightness, - image: "sun.max.circle.fill", - color: Colors.xdr.opacity(0.7), - backgroundColor: Colors.xdr.opacity(colorScheme == .dark ? 0.1 : 0.2), - knobColor: Colors.xdr, - showValue: $showSliderValues, - beforeSettingPercentage: { _ in display.forceHideSoftwareOSD = true } - ) - } - if display.subzero { - BigSurSlider( - percentage: $display.softwareBrightness, - image: "moon.circle.fill", - color: Colors.subzero.opacity(0.7), - backgroundColor: Colors.subzero.opacity(colorScheme == .dark ? 0.1 : 0.2), - knobColor: Colors.subzero, - showValue: $showSliderValues, - beforeSettingPercentage: { _ in display.forceHideSoftwareOSD = true } - ) { _ in - guard display.adaptiveSubzero else { return } - - let lastDataPoint = datapointLock.around { DC.adaptiveMode.brightnessDataPoint.last } - display.insertBrightnessUserDataPoint(lastDataPoint, display.brightness.doubleValue, modeKey: DC.adaptiveModeKey) - } - } - } - } - - var sdrXdrSelector: some View { - HStack(spacing: 2) { - SwiftUI.Button("SDR") { - guard display.enhanced else { return } - withAnimation(.fastSpring) { display.enhanced = false } - } - .buttonStyle(PickerButton( - onColor: Color.black, offColor: .oneway { colors.invertedGray }, enumValue: $display.enhanced, onValue: false - )) - .font(.system(size: 11, weight: display.enhanced ? .semibold : .bold, design: .monospaced)) - - SwiftUI.Button("XDR") { - guard lunarProActive || lunarProOnTrial else { - showNeedsLunarPro = true - return - } - guard !display.enhanced else { return } - withAnimation(.fastSpring) { display.enhanced = true } - if !xdrTipShown, autoXdr { - xdrTipShown = true - mainAsyncAfter(ms: 2000) { - showXDRTip = true - } - } - } - .buttonStyle(PickerButton( - onColor: Color.black, offColor: .oneway { colors.invertedGray }, enumValue: $display.enhanced, onValue: true - )) - .font(.system(size: 11, weight: display.enhanced ? .bold : .semibold, design: .monospaced)) - .popover(isPresented: $showNeedsLunarPro) { NeedsLunarProView() } - .popover(isPresented: $showXDRTip) { XDRTipView() } - } - .padding(2) - .background(RoundedRectangle(cornerRadius: 8, style: .continuous).fill(colors.invertedGray)) - .padding(.bottom, 4) - } - - var inputSelector: some View { - Dropdown( - selection: $display.inputSource, - width: 150, - height: 20, - noValueText: "Video Input", - noValueImage: "input", - content: display.vendor == .lg ? .constant(VideoInputSource.mostUsed + [VideoInputSource.separator] + VideoInputSource.lgSpecific) : .constant(VideoInputSource.mostUsed) - ) - .frame(width: 150, height: 20, alignment: .center) - .padding(.vertical, 2) - .padding(.horizontal, 4) - .background( - RoundedRectangle( - cornerRadius: 8, - style: .continuous - ).fill(Color.primary.opacity(0.15)) - ) - .colorMultiply(colors.accent.blended(withFraction: 0.7, of: .white)) - } - - var rotationSelector: some View { - HStack { - rotationPicker(0).help("No rotation") - rotationPicker(90).help("90 degree rotation (vertical)") - rotationPicker(180).help("180 degree rotation (upside down)") - rotationPicker(270).help("270 degree rotation (vertical)") - } - } - - var disabledReason: String? { - if display.noControls { - return "No controls available" - } else if display.useOverlay { - if display.isInHardwareMirrorSet { - return "Overlay dimming disabled while mirroring" - } else if display.isIndependentDummy { - return "Overlay dimming disabled for dummy" - } - } - - return nil - } - - @ViewBuilder var appPresetAdaptivePaused: some View { - if (display.hasDDC && showInputInQuickActions) - || display.showOrientation - || display.appPreset != nil - || display.adaptivePaused - || showRawValues && (display.lastRawBrightness != nil || display.lastRawContrast != nil || display.lastRawVolume != nil) - || SWIFTUI_PREVIEW - { - VStack { - if (display.hasDDC && showInputInQuickActions) || SWIFTUI_PREVIEW { inputSelector } - if display.showOrientation || SWIFTUI_PREVIEW { rotationSelector } - if let app = display.appPreset { - SwiftUI.Button("App Preset: \(app.name)") { - app.runningApps?.first?.activate() - } - .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .secondary.opacity(0.8))) - .font(.system(size: 9, weight: .semibold)) - } - if display.adaptivePaused || SWIFTUI_PREVIEW { - SwiftUI.Button(adaptivePausedText) { display.adaptivePaused.toggle() } - .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .secondary.opacity(0.8))) - .font(.system(size: 9, weight: .semibold)) - .onHover { hovering in - adaptivePausedText = hovering ? "Resume adaptive brightness" : "Adaptive brightness paused" - } - } - - if showRawValues { - RawValuesView(display: display).frame(width: 220).padding(.vertical, 3) - } - } - .padding(8) - .background( - RoundedRectangle( - cornerRadius: 10, - style: .continuous - ).fill(Color.primary.opacity(0.05)) - ) - .padding(.vertical, 3) - } - } - - @ViewBuilder var volumeSlider: some View { - if display.hasDDC, display.showVolumeSlider, display.ddcEnabled || display.networkEnabled { - ZStack { - BigSurSlider( - percentage: $display.preciseVolume.f, - imageBinding: .oneway { display.audioMuted ? "speaker.slash.fill" : "speaker.2.fill" }, - colorBinding: .constant(colors.accent), - backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), - showValue: $showSliderValues, - disabled: $display.audioMuted, - enableText: "Unmute" - ) - if hoveringVolumeSlider, !display.audioMuted { - SwiftUI.Button("Mute") { - display.audioMuted = true - } - .buttonStyle(FlatButton( - color: Colors.red.opacity(0.7), - textColor: .white, - horizontalPadding: 6, - verticalPadding: 2 - )) - .font(.system(size: 10, weight: .medium, design: .rounded)) - .transition(.scale.animation(.fastSpring)) - .frame(maxWidth: .infinity, alignment: .center) - .offset(x: -120, y: 0) - } - }.onHover { hovering in - Self.hoveringVolumeSliderTask = mainAsyncAfter(ms: 150) { - hoveringVolumeSlider = hovering - } - } - } - } - - @ViewBuilder var sliders: some View { - if display.noDDCOrMergedBrightnessContrast { - let mergedLockBinding = Binding( - get: { display.lockedBrightness && display.lockedContrast }, - set: { display.lockedBrightness = $0 } - ) - HStack { - #if arch(arm64) - NitsTextField(nits: $display.minNits, placeholder: "min", display: display) - .opacity(hovering ? 1 : 0) - #endif - BigSurSlider( - percentage: $display.preciseBrightnessContrast.f, - image: "sun.max.fill", - colorBinding: .constant(colors.accent), - backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), - showValue: $showSliderValues, - disabled: mergedLockBinding, - enableText: "Unlock", - insideText: nitsText - ) - #if arch(arm64) - let maxNitsBinding = Binding( - get: { display.userMaxNits ?? display.maxNits }, - set: { - display.userMaxNits = nil - display.maxNits = $0 - } - ) - NitsTextField(nits: maxNitsBinding, placeholder: "max", display: display) - .opacity(hovering ? 1 : 0) - #endif - } - softwareSliders - } else { - BigSurSlider( - percentage: $display.preciseBrightness.f, - image: "sun.max.fill", - colorBinding: .constant(colors.accent), - backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), - showValue: $showSliderValues, - disabled: $display.lockedBrightness, - enableText: "Unlock", - insideText: nitsText - ) - softwareSliders - let contrastBinding: Binding = Binding( - get: { display.preciseContrast.f }, - set: { val in - display.withoutLockedContrast { - display.preciseContrast = val.d - } - } - ) - BigSurSlider( - percentage: contrastBinding, - image: "circle.righthalf.fill", - colorBinding: .constant(colors.accent), - backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), - showValue: $showSliderValues - ) - } - } - - var body: some View { - VStack(spacing: 4) { - let xdrSelectorShown = display.supportsEnhance && showXDRSelector && !display.blackOutEnabled - if showPowerInQuickActions, display.getPowerOffEnabled() { - HStack(alignment: .top, spacing: -10) { - Text(display.name) - .font(.system(size: 22, weight: .black)) - .padding(6) - .background(RoundedRectangle(cornerRadius: 6, style: .continuous).fill(colors.bg.primary.opacity(0.5))) - .padding(.bottom, xdrSelectorShown ? 0 : 6) - - PowerOffButtonView(display: display) - .offset(y: -8) - }.offset(x: 45) - - } else { - Text(display.name ?! "Unknown") - .font(.system(size: 22, weight: .black)) - .padding(.bottom, xdrSelectorShown ? 0 : 6) - } - - if let disabledReason { - Text(disabledReason).font(.system(size: 10, weight: .semibold, design: .rounded)) - } else if display.blackOutEnabled { - Text("Blacked Out").font(.system(size: 10, weight: .semibold, design: .rounded)) - - if display.isMacBook { - let binding = Binding( - get: { autoBlackoutBuiltin }, - set: { - autoBlackoutBuiltin = $0 - display.keepDisconnected = $0 - } - ) - - VStack { - SettingsToggle( - text: "Auto BlackOut", - setting: binding, - color: nil, - help: """ - Turns off the built-in screen automatically when a monitor is connected and turns - it back on when the last monitor is disconnected. - - Keeps the screen disconnected between standby/wake or lid open/close states. - - Note: Press ⌘ Command more than 8 times in a row to force connect all displays. - """ - ) - .font(.system(size: 12, weight: .medium, design: .rounded)) - } - .padding(8) - .background(RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color.primary.opacity(0.05))) - .padding(.vertical, 3) - } - } else { - if xdrSelectorShown { sdrXdrSelector } - - sliders - adaptiveState - volumeSlider - appPresetAdaptivePaused - } - }.onHover { h in withAnimation { hovering = h } } - } - - @ViewBuilder var adaptiveState: some View { - let systemAdaptive = display.systemAdaptiveBrightness - let key = DC.adaptiveModeKey - if !display.adaptive, !display.xdr, !display.blackout, !display.facelight, !display.subzero, (key == .sync && !display.isActiveSyncSource) || key == .location || key == .sensor || key == .clock { - SwiftUI.Button(adaptiveStateText.isEmpty ? (systemAdaptive ? "Adapted by the system" : "Adaptive brightness disabled") : adaptiveStateText) { - display.adaptive = true - } - .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .secondary.opacity(0.8))) - .font(.system(size: 9, weight: .semibold)) - .onHover { hovering in - adaptiveStateText = hovering ? "Adapt brightness with Lunar" : "" - } - } - } - - func nitsText() -> AnyView { - #if arch(arm64) - guard SyncMode.isUsingNits(), display.isNative, let nits = display.nits else { - return EmptyView().any - } - return VStack(spacing: -3) { - Text("\(nits.intround)") - Text("nits") - } - .font(.system(size: 8, weight: .bold, design: .rounded).leading(.tight)) - .foregroundColor((display.preciseBrightnessContrast < 0.25 && colorScheme == .dark) ? Colors.lightGray.opacity(0.6) : Colors.grayMauve.opacity(0.7)) - .any - #else - EmptyView().any - #endif - } - - func rotationPicker(_ degrees: Int) -> some View { - SwiftUI.Button("\(degrees)°") { - display.rotation = degrees - } - .buttonStyle(PickerButton(enumValue: $display.rotation, onValue: degrees)) - .font(.system(size: 12, weight: display.rotation == degrees ? .bold : .semibold, design: .monospaced)) - .help("No rotation") - } -} - -#if arch(arm64) - struct NitsTextField: View { - @Binding var nits: Double - @State var placeholder: String - @ObservedObject var display: Display - @State var editing = false - - @Default(.syncMode) var syncMode - - var editPopover: some View { - PaddedPopoverView(background: Colors.peach.any) { - VStack { - Text("\(placeholder.titleCase())imum nits") - .font(.title.bold()) - Text("for \(display.name)") - - TextField("nits", value: $nits, formatter: NumberFormatter.shared(decimals: 0, padding: 0)) - .onReceive(Just(nits)) { _ in - display.nitsEditPublisher.send(true) - } - .textFieldStyle(PaddedTextFieldStyle(backgroundColor: .primary.opacity(0.1))) - .font(.system(size: 20, weight: .bold, design: .monospaced).leading(.tight)) - .lineLimit(1) - .frame(width: 70) - .multilineTextAlignment(.center) - .padding(.vertical) - - Text("Value estimated from monitor\nfirmware data and user input") - .font(.system(size: 12, weight: .medium, design: .rounded).leading(.tight)) - .foregroundColor(Colors.grayMauve.opacity(0.8)) - .multilineTextAlignment(.center) - } - } - } - - var body: some View { - if syncMode, SyncMode.isUsingNits() { - let disabled = display.isNative && (placeholder == "max" || display.isActiveSyncSource) - SwiftUI.Button(action: { editing = true }) { - VStack(spacing: -3) { - Text(nits.str(decimals: 0)) - .font(.system(size: 10, weight: .bold, design: .monospaced).leading(.tight)) - Text("nits") - .font(.system(size: 8, weight: .semibold, design: .rounded).leading(.tight)) - } - } - .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) - .frame(width: 50) - .popover(isPresented: $editing) { editPopover } - .disabled(disabled) - .help(disabled ? "Managed by the system" : "") - } - } - } - -#endif - -// MARK: - NeedsLunarProView - -struct NeedsLunarProView: View { - var body: some View { - PaddedPopoverView(background: Colors.red.brightness(0.1).any) { - HStack(spacing: 4) { - Text("Needs a") - .foregroundColor(.black.opacity(0.8)) - .font(.system(size: 16, weight: .semibold)) - SwiftUI.Button("Lunar Pro") { appDelegate!.getLunarPro(appDelegate!) } - .buttonStyle(FlatButton(color: .black.opacity(0.3), textColor: .white)) - .font(.system(size: 15, weight: .bold, design: .rounded)) - Text("licence") - .foregroundColor(.black.opacity(0.8)) - .font(.system(size: 16, weight: .semibold)) - } - } - } -} - -// MARK: - RawValueView - -struct RawValueView: View { - @Binding var value: Double? - @State var icon: String - @State var decimals: UInt8 = 0 - - var body: some View { - if let v = value?.str(decimals: decimals) { - HStack(spacing: 2) { - Image(systemName: icon) - Text(v) - } - .font(.system(size: 10, weight: .heavy, design: .monospaced)) - .padding(.horizontal, 4) - .padding(.vertical, 2) - .background(RoundedRectangle(cornerRadius: 5, style: .continuous).fill(Color.primary.opacity(0.07))) - } else { - EmptyView() - } - } -} - -// MARK: - RawValuesView - -struct RawValuesView: View { - @ObservedObject var display: Display - - var body: some View { - if display.lastRawBrightness != nil || display.lastRawContrast != nil || display.lastRawVolume != nil { - HStack(spacing: 0) { - Text("Raw Values").font(.system(size: 12, weight: .semibold, design: .monospaced)) - Spacer() - HStack(spacing: 4) { - RawValueView( - value: $display.lastRawBrightness, - icon: "sun.max.fill", - decimals: display.control is AppleNativeControl && (display.lastRawBrightness ?? 0 <= 1) ? 2 : 0 - ) - RawValueView(value: $display.lastRawContrast, icon: "circle.righthalf.fill") - RawValueView(value: $display.lastRawVolume, icon: "speaker.2.fill") - }.fixedSize() - }.foregroundColor(.secondary).padding(.horizontal, 3) - } else { - EmptyView() - } - } -} - -// MARK: - HDRSettingsView - -struct HDRSettingsView: View { - @ObservedObject var dc: DisplayController = DC - - @Default(.hdrWorkaround) var hdrWorkaround - @Default(.xdrContrast) var xdrContrast - @Default(.xdrContrastFactor) var xdrContrastFactor - @Default(.subzeroContrast) var subzeroContrast - @Default(.subzeroContrastFactor) var subzeroContrastFactor - @Default(.allowHDREnhanceBrightness) var allowHDREnhanceBrightness - @Default(.allowHDREnhanceContrast) var allowHDREnhanceContrast - - @Default(.autoXdr) var autoXdr - @Default(.autoSubzero) var autoSubzero - @Default(.disableNightShiftXDR) var disableNightShiftXDR - @Default(.enableDarkModeXDR) var enableDarkModeXDR - @Default(.autoXdrSensor) var autoXdrSensor - @Default(.autoXdrSensorShowOSD) var autoXdrSensorShowOSD - @Default(.autoXdrSensorLuxThreshold) var autoXdrSensorLuxThreshold - - var body: some View { - ZStack { - Color.clear.frame(maxWidth: .infinity, alignment: .leading) - VStack(alignment: .leading) { - Group { - SettingsToggle( - text: "Run in HDR compatibility mode", setting: $hdrWorkaround, - help: """ - Because of a macOS bug, any app that uses the Gamma API will break HDR. - - This workaround tries to keep HDR working by periodically resetting Gamma changes. - - This will stop working in the following cases: - - • Using "Software Dimming" with the Gamma method on any display - • Having the f.lux app running - • Having the Gamma Control app running - • Using "XDR Brightness" - • Using "Sub-zero dimming" - """ - ) - SettingsToggle( - text: "Allow XDR on non-Apple HDR monitors", setting: $allowHDREnhanceBrightness.animation(.fastSpring), - help: """ - This should work for HDR monitors that have higher brightness LEDs. - Known issues: some monitors turn to grayscale/monochrome when XDR is enabled. - - In case of any issue, uncheck this and restart your computer to revert any changes. - """ - ) - - if DC.activeDisplayList.contains(where: \.supportsEnhance) { - SettingsToggle( - text: "Enhance contrast in XDR Brightness", setting: $xdrContrast, - help: """ - Improve readability in sunlight by increasing XDR contrast. - This option is especially useful when using apps with dark backgrounds. - - Note: works only when using a single display - """ - ) - HStack { - BigSurSlider( - percentage: $xdrContrastFactor, - image: "circle.lefthalf.filled", - color: Colors.lightGray, - backgroundColor: Colors.grayMauve.opacity(0.1), - knobColor: Colors.lightGray, - showValue: .constant(false), - disabled: !$xdrContrast - ) - .padding(.leading) - - SwiftUI.Button("Reset") { xdrContrastFactor = 0.3 } - .buttonStyle(FlatButton( - color: Colors.lightGray, - textColor: Colors.darkGray, - radius: 10, - verticalPadding: 3 - )) - .font(.system(size: 12, weight: .semibold, design: .monospaced)) - .disabled(!xdrContrast) - } - SettingsToggle(text: "Allow on non-Apple HDR monitors", setting: $allowHDREnhanceContrast.animation(.fastSpring)) - .padding(.leading) - .disabled(!xdrContrast) - } - if DC.activeDisplayList.contains(where: \.supportsGammaByDefault) { - SettingsToggle( - text: "Enhance contrast in Sub-zero Dimming", setting: $subzeroContrast, - help: """ - Improve readability in very low light by increasing contrast. - This option is especially useful when using apps with light backgrounds. - - Note: works only when using a single display - """ - ) - let binding = Binding( - get: { subzeroContrastFactor / 5.0 }, - set: { subzeroContrastFactor = $0 * 5.0 } - ) - HStack { - BigSurSlider( - percentage: binding, - image: "circle.lefthalf.filled", - color: Colors.lightGray, - backgroundColor: Colors.grayMauve.opacity(0.1), - knobColor: Colors.lightGray, - showValue: .constant(false), - disabled: !$subzeroContrast - ) - .padding(.leading) - - SwiftUI.Button("Reset") { subzeroContrastFactor = 1.75 } - .buttonStyle(FlatButton( - color: Colors.lightGray, - textColor: Colors.darkGray, - radius: 10, - verticalPadding: 3 - )) - .font(.system(size: 12, weight: .semibold, design: .monospaced)) - .disabled(!subzeroContrast) - } - } - } - Divider() - xdrSettings - Spacer() - Color.clear - }.frame(maxWidth: .infinity, alignment: .leading) - } - } - - var xdrSettings: some View { - Group { - SettingsToggle(text: "Disable Night Shift and f.lux when toggling XDR", setting: $disableNightShiftXDR.animation(.fastSpring)) - SettingsToggle( - text: "Enable Dark Mode when enabling XDR", setting: $enableDarkModeXDR.animation(.fastSpring), - help: """ - Use dark backgrounds with bright text for minimizing power usage and LED temperature while XDR is active. - - This works best in combination with the "Enhance contrast in XDR Brightness" setting. - """ - ) - SettingsToggle(text: "Toggle XDR Brightness when going over 100%", setting: $autoXdr.animation(.fastSpring)) - SettingsToggle( - text: "Toggle Sub-zero Dimming when going below 0%", - setting: $autoSubzero.animation(.fastSpring) - ) - - if Sysctl.isMacBook, DC.builtinDisplay?.supportsEnhance ?? false { - Divider().padding(.horizontal) - VStack(alignment: .leading, spacing: 2) { - SettingsToggle(text: "Toggle XDR Brightness based on ambient light", setting: $autoXdrSensor) - Text( - """ - XDR Brightness will be automatically enabled - when ambient light is above \(autoXdrSensorLuxThreshold.str(decimals: 0)) lux - """ - ) - .font(.system(size: 10, weight: .semibold, design: .monospaced)) - .foregroundColor(.black.opacity(0.4)) - .fixedSize() - .padding(.leading, 20) - } - HStack { - let luxBinding = Binding( - get: { powf(max(autoXdrSensorLuxThreshold - XDR_LUX_LEAST_NONZERO, 0) / XDR_MAX_LUX, 0.25) }, - set: { autoXdrSensorLuxThreshold = powf($0, 4) * XDR_MAX_LUX + XDR_LUX_LEAST_NONZERO } - ) - BigSurSlider( - percentage: luxBinding, - image: "sun.dust.fill", - color: Colors.lightGray, - backgroundColor: Colors.grayMauve.opacity(0.1), - knobColor: Colors.lightGray, - showValue: .constant(false), - disabled: !$autoXdrSensor, - mark: .oneway { powf(max(dc.internalSensorLux - XDR_LUX_LEAST_NONZERO, 0) / XDR_MAX_LUX, 0.25) } - ) - .padding(.leading) - - SwiftUI.Button("Reset") { autoXdrSensorLuxThreshold = XDR_DEFAULT_LUX } - .buttonStyle(FlatButton( - color: Colors.lightGray, - textColor: Colors.darkGray, - radius: 10, - verticalPadding: 3 - )) - .font(.system(size: 12, weight: .semibold, design: .monospaced)) - .disabled(!autoXdrSensor) - } - if autoXdrSensor { - ( - Text(dc.autoXdrSensorPausedReason ?? "Current ambient light: ") - .font(.system(size: 10, weight: .semibold, design: .monospaced)) - + Text(dc.autoXdrSensorPausedReason == nil ? "\(dc.internalSensorLux.str(decimals: 0)) lux" : "") - .font(.system(size: 10, weight: .heavy, design: .monospaced)) - ) - .foregroundColor(.black.opacity(0.4)) - .padding(.leading, 20) - } - VStack(alignment: .leading, spacing: 2) { - SettingsToggle(text: "Show OSD when toggling XDR automatically", setting: $autoXdrSensorShowOSD.animation(.fastSpring)) - ( - Text("Notifies you when XDR is activating and\n") - .font(.system(size: 10, weight: .semibold, design: .monospaced).leading(.tight)) - + Text("allows aborting AutoXDR by pressing ") - .font(.system(size: 10, weight: .semibold, design: .monospaced).leading(.tight)) - + Text("Esc") - .font(.system(size: 10, weight: .heavy, design: .monospaced).leading(.tight)) - ) - .foregroundColor(.black.opacity(0.4)) - .fixedSize() - .padding(.leading, 20) - }.padding(.leading) - } - } - } -} - -// MARK: - AdvancedSettingsView - -struct AdvancedSettingsView: View { - @ObservedObject var dc: DisplayController = DC - - @Default(.workaroundBuiltinDisplay) var workaroundBuiltinDisplay - @Default(.ddcSleepLonger) var ddcSleepLonger - @Default(.clamshellModeDetection) var clamshellModeDetection - @Default(.enableOrientationHotkeys) var enableOrientationHotkeys - @Default(.detectResponsiveness) var detectResponsiveness - @Default(.disableControllerVideo) var disableControllerVideo - @Default(.allowBlackOutOnSingleScreen) var allowBlackOutOnSingleScreen - @Default(.reapplyValuesAfterWake) var reapplyValuesAfterWake - @Default(.clockMode) var clockMode - @Default(.nonManualMode) var nonManualMode - @Default(.oldBlackOutMirroring) var oldBlackOutMirroring - @Default(.newBlackOutDisconnect) var newBlackOutDisconnect - - @Default(.refreshValues) var refreshValues - @Default(.gammaDisabledCompletely) var gammaDisabledCompletely - @Default(.waitAfterWakeSeconds) var waitAfterWakeSeconds - @Default(.delayDDCAfterWake) var delayDDCAfterWake - - @Default(.autoRestartOnFailedDDC) var autoRestartOnFailedDDC - @Default(.autoRestartOnFailedDDCSooner) var autoRestartOnFailedDDCSooner - @Default(.sleepInClamshellMode) var sleepInClamshellMode - @Default(.disableCliffDetection) var disableCliffDetection - @Default(.keyboardBacklightOffBlackout) var keyboardBacklightOffBlackout - - @State var sensorCheckerEnabled = !Defaults[.sensorHostname].isEmpty - - var body: some View { - ZStack { - Color.clear.frame(maxWidth: .infinity, alignment: .leading) - VStack(alignment: .leading) { - Group { - #if arch(arm64) - if #available(macOS 13, *) { - SettingsToggle( - text: "Disable the Disconnect API in BlackOut", setting: !$newBlackOutDisconnect, - help: """ - BlackOut can use a hidden macOS API to disconnect the display entirely, - freeing up GPU resources and allowing for an easy reconnection when needed. - - If you're having trouble with how this works, you can switch to the old - method of mirroring the display to disable it. - - Note: Press ⌘ Command more than 8 times in a row to force connect all displays. - - In case the built-in MacBook display doesn't reconnect itself when it should, - close the laptop lid and reopen it to bring the display back. - - For external displays, disconnect and reconnect the cable to fix any issue. - """ - ) - } - #endif - - SettingsToggle( - text: "Allow BlackOut on single screen", setting: $allowBlackOutOnSingleScreen, - help: "Allows turning off a screen even if it's the only visible screen left" - ) - if Sysctl.isMacBook { - SettingsToggle( - text: "Turn off keyboard backlight in BlackOut", setting: $keyboardBacklightOffBlackout - ) - SettingsToggle( - text: "Force Sleep when the lid is closed", setting: $sleepInClamshellMode, - help: """ - When the MacBook is connected to a monitor that's also charging the Mac, - closing the lid will start Clamshell Mode. - - That system feature keeps the system awake to allow you to use the external - monitor with the lid closed. - - If you don't use that feature, enabling this option will disable Clamshell - Mode automatically when the lid is closed. - """ - ) - } - - #if !arch(arm64) - if #available(macOS 13, *) { - } else { - SettingsToggle( - text: "Switch to the old BlackOut mirroring system", setting: $oldBlackOutMirroring, - help: """ - Some setups will have trouble enabling mirroring with the new macOS 11+ API. - - You can try enabling this option if BlackOut is not working properly. - - Note: the old mirroring system can't handle complex mirror sets with dummies and virtual/wireless displays. - The best covered cases are "BlackOut built-in display" and "BlackOut only external displays". - """ - ) - } - #endif - Divider() - - SettingsToggle( - text: "Use workaround for built-in display", setting: $workaroundBuiltinDisplay, - help: """ - Forward brightness key events to the system instead of - changing built-in display brightness from Lunar. - - This setting might be needed to persist brightness - changes better on some specific older devices. - - Disables the following functions for the built-in display: - • Hotkey Step - • Auto XDR - • Sub-zero Dimming - """ - ) - if Sysctl.isMacBook { - SettingsToggle( - text: "Toggle Manual/Sync when the lid is closed/opened", - setting: $clamshellModeDetection - ) - } - SettingsToggle( - text: "Re-apply last brightness on screen wake", setting: $reapplyValuesAfterWake, - help: """ - On each screen wake/reconnection, Lunar will try to - re-apply previous brightness and contrast 3 times. - - Disable this if system appears slow on screen wake. - """ - ) - } - Divider() - Group { - SettingsToggle( - text: "Enable rotation hotkeys", - setting: $enableOrientationHotkeys, - help: """ - Pressing the following keys will change the - orientation for the display with the cursor on it: - - Ctrl+0: 0° - Ctrl+9: 90° - Ctrl+8: 180° - Ctrl+7: 270° - """ - ) - if dc.activeDisplayList.contains(where: \.hasDDC) { - SettingsToggle( - text: "Wait longer between DDC requests", setting: $ddcSleepLonger, - help: """ - Some monitors have a slower response time on DDC requests. - - This option might help reduce flicker in those cases. - """ - ) - SettingsToggle( - text: "Check for DDC responsiveness periodically", setting: $detectResponsiveness, - help: """ - Detects when DDC becomes unresponsive and presents - the choice to switch to Software Dimming. - """ - ) - } - if dc.activeDisplayList.contains(where: { $0.control is NetworkControl }) { - SettingsToggle( - text: "Disable Network Controller video ", setting: $disableControllerVideo, - help: """ - When using "Network Control" with a Raspberry Pi, it might be - helpful to disable the Pi desktop if you don't need it. - """ - ) - } - SettingsToggle( - text: "Check for network light sensors periodically", setting: $sensorCheckerEnabled, - help: """ - To enable "Sensor Mode", Lunar periodically checks if a wireless light - sensor is available using local DNS requests. You can disable this if - you never intend to use a wireless ambient light sensor. - """ - ) - Divider() - Group { - Text("EXPERIMENTAL!") - .foregroundColor(Colors.red) - .bold() - Text("Don't use unless really needed or asked by the developer") - .foregroundColor(Colors.red) - .font(.caption) - SettingsToggle( - text: "Disable usage of Gamma API completely", setting: $gammaDisabledCompletely, - help: """ - Experimental: for people running into macOS bugs like the color profile - being constantly reset, display turning to monochrome or HDR being disabled, - this could be a safe measure to ensure Lunar never touches the Gamma API of macOS. - - This will disable or cripple the following features: - - • XDR Brightness - • Facelight - • Blackout - • Software Dimming - • Sub-zero Dimming - """ - ) - if dc.activeDisplayList.contains(where: \.hasDDC) { - SettingsToggle( - text: "Auto restart Lunar when DDC fails", setting: $autoRestartOnFailedDDC, - help: """ - Experimental: for people running into macOS bugs where a monitor can no longer - be controlled. You might see a lock icon when brightness keys are pressed. - - To avoid jarring brightness changes, this will not restart the app - if any of the following features are in active use: - - • XDR Brightness - • Facelight - • Blackout - • Sub-zero Dimming - """ - ) - SettingsToggle( - text: "Avoid safety checks", setting: $autoRestartOnFailedDDCSooner, - help: """ - Don't wait for the detection of DDC fail to happen more than once, and restart - the app even if it could cause a jarring brightness change. - """ - ).padding(.leading) - - SettingsToggle( - text: "Delay DDC commands after wake", setting: $delayDDCAfterWake, - help: """ - Experimental: for people running into monitor bugs like the video signal being - lost or screen not waking up after system sleep, this could be a safe measure - to ensure Lunar doesn't send any DDC command until the monitor connection - is fully established. - - This will disable or cripple the following features: - - • Smooth transitions - • DDC responsiveness checker - • Re-applying color gain on wake - • Re-applying brightness/contrast on wake - """ - ) - HStack { - let secondsBinding = Binding( - get: { waitAfterWakeSeconds.f / 100 }, - set: { waitAfterWakeSeconds = ($0 * 100).i } - ) - BigSurSlider( - percentage: secondsBinding, - image: "clock.circle", - color: Colors.lightGray, - backgroundColor: Colors.grayMauve.opacity(0.1), - knobColor: Colors.lightGray, - showValue: .constant(true), - disabled: !$delayDDCAfterWake - ) - .padding(.leading) - - SwiftUI.Button("Reset") { waitAfterWakeSeconds = 30 } - .buttonStyle(FlatButton( - color: Colors.lightGray, - textColor: Colors.darkGray, - radius: 10, - verticalPadding: 3 - )) - .font(.system(size: 12, weight: .semibold, design: .monospaced)) - .disabled(!delayDDCAfterWake) - } - if delayDDCAfterWake { - Text("Lunar will wait \(waitAfterWakeSeconds) seconds before sending\nthe first DDC command after screen wake") - .font(.system(size: 10, weight: .semibold, design: .monospaced)) - .foregroundColor(.black.opacity(0.4)) - .frame(height: 28, alignment: .topLeading) - .fixedSize() - .lineLimit(2) - .padding(.leading, 20) - .padding(.top, -5) - } - } - if nonManualMode { - SettingsToggle( - text: "Disable cliff detection for auto-learning", setting: $disableCliffDetection, - help: """ - The Cliff Detection algorithm is a safe guard that avoids having Lunar learn - a very low brightness and a very high brightness very close together. - (e.g. 5% at 10lux, 100% at 20lux) - - That would cause constant transitioning between the two learnt brightnesses - and very high CPU usage when the ambient light changed continuously. - (which happens often when clouds are passing, or when in a moving vehicle) - - This will disable that safe guard. - """ - ) - } - } - } - Spacer() - Color.clear - }.frame(maxWidth: .infinity, alignment: .leading) - } - .onChange(of: sensorCheckerEnabled) { enabled in - Defaults[.sensorHostname] = enabled ? "lunarsensor.local" : "" - } - } -} - -// MARK: - QuickActionsLayoutView - -struct QuickActionsLayoutView: View { - @ObservedObject var dc: DisplayController = DC - - @Default(.showSliderValues) var showSliderValues - @Default(.mergeBrightnessContrast) var mergeBrightnessContrast - @Default(.showVolumeSlider) var showVolumeSlider - @Default(.showRawValues) var showRawValues - @Default(.showBrightnessMenuBar) var showBrightnessMenuBar - @Default(.showOnlyExternalBrightnessMenuBar) var showOnlyExternalBrightnessMenuBar - @Default(.showOrientationInQuickActions) var showOrientationInQuickActions - @Default(.showInputInQuickActions) var showInputInQuickActions - @Default(.showPowerInQuickActions) var showPowerInQuickActions - @Default(.showStandardPresets) var showStandardPresets - @Default(.showCustomPresets) var showCustomPresets - @Default(.showXDRSelector) var showXDRSelector - @Default(.showHeaderOnHover) var showHeaderOnHover - @Default(.showFooterOnHover) var showFooterOnHover - @Default(.keepOptionsMenu) var keepOptionsMenu - - @Default(.alternateMenuBarIcon) var alternateMenuBarIcon - @Default(.hideMenuBarIcon) var hideMenuBarIcon - @Default(.showDockIcon) var showDockIcon - @Default(.moreGraphData) var moreGraphData - @Default(.infoMenuShown) var infoMenuShown - @Default(.adaptiveBrightnessMode) var adaptiveBrightnessMode - - var body: some View { - ZStack { - Color.clear.frame(maxWidth: .infinity, alignment: .leading) - VStack(alignment: .leading) { - Group { - Group { - SettingsToggle(text: "Only show top buttons on hover", setting: $showHeaderOnHover.animation(.fastSpring)) - SettingsToggle(text: "Only show bottom buttons on hover", setting: $showFooterOnHover.animation(.fastSpring)) - SettingsToggle(text: "Save open-state for this menu", setting: $keepOptionsMenu.animation(.fastSpring)) - } - Divider() - Group { - SettingsToggle(text: "Show slider values", setting: $showSliderValues.animation(.fastSpring)) - if dc.activeDisplayList.contains(where: \.hasDDC) { - SettingsToggle(text: "Show volume slider", setting: $showVolumeSlider.animation(.fastSpring)) - SettingsToggle(text: "Show input source selector", setting: $showInputInQuickActions.animation(.fastSpring)) - } - SettingsToggle(text: "Show rotation selector", setting: $showOrientationInQuickActions.animation(.fastSpring)) - SettingsToggle(text: "Show power button", setting: $showPowerInQuickActions.animation(.fastSpring)) - } - Divider() - Group { - SettingsToggle(text: "Show standard presets", setting: $showStandardPresets.animation(.fastSpring)) - SettingsToggle(text: "Show custom presets", setting: $showCustomPresets.animation(.fastSpring)) - if dc.activeDisplayList.contains(where: \.supportsEnhance) { - SettingsToggle(text: "Show XDR Brightness buttons", setting: $showXDRSelector.animation(.fastSpring)) - } - } - } - if dc.activeDisplayList.contains(where: \.hasDDC) { - SettingsToggle(text: "Merge brightness and contrast", setting: $mergeBrightnessContrast.animation(.fastSpring)) - } - Divider() - Group { - if adaptiveBrightnessMode.hasUsefulInfo { - SettingsToggle(text: "Show useful adaptive info near mode selector", setting: $infoMenuShown.animation(.fastSpring)) - } - if dc.activeDisplayList.contains(where: \.hasDDC) { - SettingsToggle(text: "Show last raw values sent to the display", setting: $showRawValues.animation(.fastSpring)) - } - SettingsToggle(text: "Show brightness near menubar icon", setting: $showBrightnessMenuBar.animation(.fastSpring)) - SettingsToggle( - text: "Show only external monitor brightness", - setting: $showOnlyExternalBrightnessMenuBar.animation(.fastSpring) - ) - .padding(.leading) - .disabled(!showBrightnessMenuBar) - } - Divider() - Group { - SettingsToggle(text: "Hide menubar icon", setting: $hideMenuBarIcon) - SettingsToggle(text: "Alternate menubar icon", setting: $alternateMenuBarIcon) - SettingsToggle(text: "Show dock icon", setting: $showDockIcon) - SettingsToggle( - text: "Show more graph data", - setting: $moreGraphData, - help: "Renders values and data lines on the bottom graph of the Display Settings window" - ) - } - Spacer() - Color.clear - } - .frame(maxWidth: .infinity, alignment: .leading) - } - } -} - -// MARK: - BlackoutPopoverRowView - -struct BlackoutPopoverRowView: View { - @State var modifiers: [String] = [] - @State var action: String - @State var hotkeyText = "" - @State var actionInfo = "" - - var body: some View { - HStack { - Text((modifiers + ["Click"]).joined(separator: " + ")).font(.system(size: 13, weight: .medium, design: .monospaced)) - .foregroundColor(.white.opacity(0.9)) - .padding(10) - .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(Color.white.opacity(0.1)) - ).frame(width: 200, alignment: .leading) - HStack { - if !hotkeyText.isEmpty { - Text("or").font(.system(size: 12, weight: .semibold)).foregroundColor(.white.opacity(0.5)) - Text(hotkeyText).font(.system(size: 13, weight: .medium, design: .monospaced)) - .foregroundColor(.white.opacity(0.9)) - .kerning(3) - .padding(10) - .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(Color.white.opacity(0.1)) - ) - } - }.frame(width: 100, alignment: .leading) - HStack { - Text(action) - .font(.system(size: 13, weight: .medium)) - .foregroundColor(.white.opacity(0.9)) - if !actionInfo.isEmpty { - Text(actionInfo) - .font(.system(size: 12, weight: .medium, design: .monospaced)) - .foregroundColor(.white.opacity(0.7)) - } - } - .padding(10) - .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(Color.white.opacity(0.1)) - ) - } - } -} - -// MARK: - BlackoutPopoverHeaderView - -struct BlackoutPopoverHeaderView: View { - @Default(.neverShowBlackoutPopover) var neverShowBlackoutPopover - - var body: some View { - HStack { - Text("BlackOut") - .font(.system(size: 24, weight: .black)) - .foregroundColor(.white) - Spacer() - if lunarProActive || lunarProOnTrial { - Text("Click anywhere to hide") - .font(.system(size: 12, weight: .semibold, design: .monospaced)) - .foregroundColor(.white.opacity(0.8)) - } else { - SwiftUI.Button("Needs Lunar Pro") { - showCheckout() - appDelegate?.windowController?.window?.makeKeyAndOrderFront(nil) - }.buttonStyle(FlatButton(color: Colors.red, textColor: .white)) - } - } - } -} - -// MARK: - PaddedPopoverView - -struct PaddedPopoverView: View where Content: View { - @State var background: AnyView - - @ViewBuilder let content: Content - - var body: some View { - ZStack { - background.scaleEffect(1.5) - VStack(alignment: .leading, spacing: 10) { - content - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - }.preferredColorScheme(.light) - } -} - -// MARK: - BlackoutPopoverView - -struct BlackoutPopoverView: View { - @State var hasDDC: Bool - @Default(.hotkeys) var hotkeys - @Default(.newBlackOutDisconnect) var newBlackOutDisconnect - - var body: some View { - ZStack { - Color.black.brightness(0.02).scaleEffect(1.5) - VStack(alignment: .leading, spacing: 10) { - BlackoutPopoverHeaderView().padding(.bottom) - if DC.activeDisplayCount == 1 { - BlackoutPopoverRowView(action: "Make screen black", hotkeyText: hotkeyText(id: .blackOut), actionInfo: "(without disabling it)") - } else { - if newBlackOutDisconnect, #available(macOS 13, *) { - BlackoutPopoverRowView(action: "Disconnect screen", hotkeyText: hotkeyText(id: .blackOut), actionInfo: "(free up GPU)") - } else { - BlackoutPopoverRowView(action: "Soft power off", hotkeyText: hotkeyText(id: .blackOut), actionInfo: "(disables screen by mirroring)") - } - BlackoutPopoverRowView( - modifiers: ["Shift"], - action: "Make screen black", - hotkeyText: hotkeyText(id: .blackOutNoMirroring), - actionInfo: "(without disabling it)" - ) - BlackoutPopoverRowView( - modifiers: ["Option", "Shift"], - action: "Make other screens black", - hotkeyText: hotkeyText(id: .blackOutOthers), - actionInfo: "(keep this one visible)" - ) - - #if arch(arm64) - if #available(macOS 13, *) { - Divider().background(Color.white.opacity(0.2)) - if newBlackOutDisconnect { - BlackoutPopoverRowView(modifiers: ["Command"], action: "Soft power off", hotkeyText: "", actionInfo: "(disables screen by mirroring)") - .colorMultiply(Color.orange) - } else { - BlackoutPopoverRowView( - modifiers: ["Command"], - action: "Disconnect screen", - hotkeyText: "", - actionInfo: "(free up GPU)" - ) - .colorMultiply(Color.orange) - } - } - #endif - } - - if hasDDC { - BlackoutPopoverRowView( - modifiers: ["Option"], - action: "Hardware power off", - hotkeyText: hotkeyText(id: .blackOutPowerOff), - actionInfo: "(uses DDC)" - ) - .colorMultiply(Colors.red) - } - Divider().background(Color.white.opacity(0.2)) - BlackoutPopoverRowView(modifiers: ["Control"], action: "Show this help menu") - .colorMultiply(Colors.peach) - - HStack(spacing: 7) { - Text("Press") - Text("⌘ Command") - .padding(.vertical, 3) - .padding(.horizontal, 5) - .background(RoundedRectangle(cornerRadius: 3, style: .continuous).fill(Color.white)) - .foregroundColor(.black) - Text("more than 8 times in a row to force turn on all displays and reset BlackOut") - } - .font(.system(size: 13, weight: .medium)) - .foregroundColor(.white.opacity(0.6)) - .padding(.vertical, 6) - .padding(.horizontal, 12) - .background(RoundedRectangle(cornerRadius: 8, style: .continuous).fill(Color.white.opacity(0.1))) - .colorMultiply(Colors.peach) - .padding(.top) - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - }.preferredColorScheme(.light) - } - - func hotkeyText(id: HotkeyIdentifier) -> String { - guard let h = hotkeys.first(where: { $0.identifier == id.rawValue }), h.isEnabled else { return "" } - return h.keyCombo.keyEquivalentModifierMaskString + h.keyCombo.keyEquivalent - } -} - -// MARK: - PaddedTextFieldStyle - struct PaddedTextFieldStyle: TextFieldStyle { @State var verticalPadding: CGFloat = 4 @State var horizontalPadding: CGFloat = 8 @@ -1733,34 +43,12 @@ struct PaddedTextFieldStyle: TextFieldStyle { } } -// MARK: - TextInputView - -struct TextInputView: View { - @State var label: String - @State var placeholder: String - @Binding var data: String - - var body: some View { - VStack(alignment: .leading, spacing: 4) { - if !label.isEmpty { - Text(label).font(.system(size: 12, weight: .semibold)) - } - TextField(placeholder, text: $data) - .textFieldStyle(PaddedTextFieldStyle()) - } - } -} - -// MARK: - MenuDensity - enum MenuDensity: String, Codable, Defaults.Serializable { case clean case comfortable case dense } -// MARK: - EnvState - final class EnvState: ObservableObject { @Published var recording = false @Published var menuWidth: CGFloat = MENU_WIDTH @@ -1772,680 +60,12 @@ final class EnvState: ObservableObject { @Published var optionsTab: OptionsTab = .layout } -// MARK: - OptionsTab - enum OptionsTab: String, DefaultsSerializable { case layout case advanced case hdr } -// MARK: - QuickActionsMenuView - -struct QuickActionsMenuView: View { - @Environment(\.colorScheme) var colorScheme - @Environment(\.colors) var colors - @EnvironmentObject var env: EnvState - @ObservedObject var dc: DisplayController = DC - @ObservedObject var um = UM - @Namespace var namespace - - @Default(.overrideAdaptiveMode) var overrideAdaptiveMode - @Default(.showStandardPresets) var showStandardPresets - @Default(.showCustomPresets) var showCustomPresets - @Default(.showHeaderOnHover) var showHeaderOnHover - @Default(.showFooterOnHover) var showFooterOnHover - @Default(.showOptionsMenu) var showOptionsMenu - - @Default(.menuBarClosed) var menuBarClosed - @Default(.menuDensity) var menuDensity - - @Default(.showBrightnessMenuBar) var showBrightnessMenuBar - @Default(.showOnlyExternalBrightnessMenuBar) var showOnlyExternalBrightnessMenuBar - @Default(.showAdditionalInfo) var showAdditionalInfo - @Default(.startAtLogin) var startAtLogin - - @State var displays: [Display] = DC.nonCursorDisplays - @State var cursorDisplay: Display? = DC.cursorDisplay - @State var sourceDisplay: Display? = DC.sourceDisplay - #if arch(arm64) - @State var disconnectedDisplays: [Display] = DC.possiblyDisconnectedDisplayList - @State var possiblyDisconnectedDisplays: [Display] = [] - #endif - @State var unmanagedDisplays: [Display] = DC.unmanagedDisplays - @State var adaptiveModes: [AdaptiveModeKey] = [.sensor, .sync, .location, .clock, .manual, .auto] - - @State var headerOpacity: CGFloat = 1.0 - @State var footerOpacity: CGFloat = 1.0 - @State var additionalInfoButtonOpacity: CGFloat = 0.3 - @State var headerIndicatorOpacity: CGFloat = 0.0 - @State var footerIndicatorOpacity: CGFloat = 0 - - @State var displayCount = DC.activeDisplayCount - - @ObservedObject var menuBarIcon: StatusItemButtonController - - @ObservedObject var km = KM - @ObservedObject var wm = WM - - var modeSelector: some View { - let titleBinding = Binding( - get: { overrideAdaptiveMode ? "⁣\(dc.adaptiveModeKey.name)⁣" : "Auto: \(dc.adaptiveModeKey.str)" }, set: { _ in } - ) - let imageBinding = Binding( - get: { overrideAdaptiveMode ? dc.adaptiveModeKey.image ?? "automode" : "automode" }, set: { _ in } - ) - - return Dropdown( - selection: $dc.adaptiveModeKey, - width: 140, - height: 20, - noValueText: "Adaptive Mode", - noValueImage: "automode", - content: $adaptiveModes, - title: titleBinding, - image: imageBinding, - validate: AdaptiveModeButton.validate(_:) - ) - .frame(width: 140, height: 20, alignment: .center) - .padding(.vertical, 2) - .padding(.horizontal, 4) - .background( - RoundedRectangle( - cornerRadius: 8, - style: .continuous - ).fill(Color.primary.opacity(0.15)) - ) - .colorMultiply(colors.accent.blended(withFraction: 0.7, of: .white)) - } - - var topRightButtons: some View { - Group { - SwiftUI.Button( - action: { showOptionsMenu.toggle() }, - label: { - HStack(spacing: 2) { - Image(systemName: "gear.circle.fill").font(.system(size: 12, weight: .semibold)) - Text("Settings").font(.system(size: 13, weight: .semibold)) - } - } - ) - .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) - .onChange(of: showBrightnessMenuBar) { _ in - let old = showOptionsMenu - showOptionsMenu = false - if old { mainAsyncAfter(ms: 500) { showOptionsMenu = true }} - } - .onChange(of: showOnlyExternalBrightnessMenuBar) { _ in - let old = showOptionsMenu - showOptionsMenu = false - if old { mainAsyncAfter(ms: 500) { showOptionsMenu = true }} - } - -// SwiftUI.Button( -// action: { -// guard let view = menuWindow?.contentViewController?.view else { return } -// appDelegate!.menu.popUp( -// positioning: nil, -// at: NSPoint( -// x: env -// .menuWidth + -// (showOptionsMenu ? MENU_HORIZONTAL_PADDING * 2 : OPTIONS_MENU_WIDTH / 2 + MENU_HORIZONTAL_PADDING / 2), -// y: 0 -// ), -// in: view -// ) -// }, -// label: { -// HStack(spacing: 2) { -// Image(systemName: "ellipsis.circle.fill").font(.system(size: 12, weight: .semibold)) -// Text("Menu").font(.system(size: 13, weight: .semibold)) -// } -// } -// ).buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) - } - } - - var standardPresets: some View { - HStack { - VStack(alignment: .center, spacing: -2) { - Text("Standard").font(.system(size: 10, weight: .bold)).opacity(0.7) - Text("Presets").font(.system(size: 12, weight: .heavy)).opacity(0.7) - } - Spacer() - PresetButtonView(percent: 0) - PresetButtonView(percent: 25) - PresetButtonView(percent: 50) - PresetButtonView(percent: 75) - PresetButtonView(percent: 100) - } - } - - var footer: some View { - Group { - let dynamicFooter = footerOpacity == 0.0 && showFooterOnHover - ZStack { - VStack(spacing: 5) { - HStack { - Toggle(um.newVersion != nil ? "" : "App info", isOn: $showAdditionalInfo.animation(.fastSpring)) - .toggleStyle(DetailToggleStyle(style: .circle)) - .foregroundColor(Color.secondary) - .font(.system(size: 12, weight: .semibold)) - .fixedSize() - - Spacer() - - if let version = um.newVersion { - SwiftUI.Button("v\(version) available") { appDelegate!.updater.checkForUpdates() } - .buttonStyle(FlatButton( - color: Colors.peach, - textColor: Colors.blackMauve, - horizontalPadding: 6, - verticalPadding: 3 - )) - .font(.system(size: 12, weight: .semibold)) - .lineLimit(1) - .minimumScaleFactor(.leastNonzeroMagnitude) - .scaledToFit() - } - - SwiftUI.Button("Display Settings") { appDelegate!.showPreferencesWindow(sender: nil) } - .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) - .font(.system(size: 12, weight: .medium, design: .rounded)) - .fixedSize() - - SwiftUI.Button("Restart") { appDelegate!.restartApp(appDelegate!) } - .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) - .font(.system(size: 12, weight: .medium, design: .rounded)) - .fixedSize() - - SwiftUI.Button("Quit") { NSApplication.shared.terminate(nil) } - .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) - .font(.system(size: 12, weight: .medium, design: .rounded)) - .fixedSize() - } - .padding(.bottom, showAdditionalInfo ? 0 : 7) - } - .padding(.horizontal, MENU_HORIZONTAL_PADDING / 2) - .opacity(showFooterOnHover ? footerOpacity : 1.0) - .contentShape(Rectangle()) - .onChange(of: showFooterOnHover) { showOnHover in - withAnimation(.fastTransition) { footerOpacity = showOnHover ? 0.0 : 1.0 } - } - Rectangle() - .fill(Color.primary.opacity(dynamicFooter ? footerIndicatorOpacity : 0.0)) - .frame(maxWidth: .infinity, maxHeight: dynamicFooter ? 20.0 : 0.0) - .onHover { hovering in - guard footerOpacity == 0.0, showFooterOnHover else { return } - if hovering { - withAnimation(.spring()) { - footerIndicatorOpacity = 0.1 - } - withAnimation(.easeOut.delay(0.5)) { - footerIndicatorOpacity = 0 - } - } - } - } - .frame(maxWidth: .infinity, maxHeight: footerOpacity == 0.0 ? 8 : nil) - .onHover { hovering in - guard showFooterOnHover else { - footerShowHideTask = nil - footerOpacity = 1.0 - return - } - - guard hovering else { - footerShowHideTask = mainAsyncAfter(ms: 500) { - withAnimation(.fastTransition) { - footerOpacity = 0.0 - footerIndicatorOpacity = 0.0 - } - } - return - } - footerShowHideTask = mainAsyncAfter(ms: 50) { - withAnimation(.fastTransition) { footerOpacity = 1.0 } - } - } - - if let appDelegate, showAdditionalInfo { - Divider().padding(.bottom, 5) - VStack(alignment: .leading, spacing: 6) { - HStack { - Toggle("Launch at login", isOn: $startAtLogin) - .toggleStyle(CheckboxToggleStyle(style: .circle)) - .foregroundColor(.primary) - .font(.system(size: 12, weight: .medium)) - Spacer() - SwiftUI.Button("Contact") { NSWorkspace.shared.open("https://lunar.fyi/contact".asURL()!) } - .buttonStyle(OutlineButton(thickness: 1, font: .system(size: 9, weight: .medium, design: .rounded))) - SwiftUI.Button("FAQ") { NSWorkspace.shared.open("https://lunar.fyi/faq".asURL()!) } - .buttonStyle(OutlineButton(thickness: 1, font: .system(size: 9, weight: .medium, design: .rounded))) - } - LicenseView() - VersionView(updater: appDelegate.updater) - MenuDensityView() - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 25) - .padding(.bottom, 10) - } - } - .frame(maxWidth: .infinity) - .onAppear { - if Defaults[.launchCount] == 1, !appInfoHiddenAfterLaunch { - appInfoHiddenAfterLaunch = true - additionInfoTask = mainAsyncAfter(ms: 1000) { - withAnimation(.spring()) { - showAdditionalInfo = true - } - } - } - if Defaults[.launchCount] == 2, !appInfoHiddenAfterLaunch { - appInfoHiddenAfterLaunch = true - additionInfoTask = mainAsyncAfter(ms: 1000) { - withAnimation(.spring()) { - showAdditionalInfo = false - } - } - } - } - } - - var header: some View { - let op = (showHeaderOnHover && !showOptionsMenu) ? headerOpacity : 1.0 - return ZStack { - HStack { - if !menuBarClosed { - modeSelector.fixedSize() - UsefulInfo().fixedSize() - } - Spacer() - topRightButtons.fixedSize() - } - .padding(.horizontal, 10) - .padding(.top, 10 * op) - .padding(.bottom, 10 * op) - .opacity(op) - - let dynamicHeader = headerOpacity == 0.0 && showHeaderOnHover - Rectangle() - .fill(Color.primary.opacity(dynamicHeader ? headerIndicatorOpacity : 0.0)) - .frame(maxWidth: .infinity, maxHeight: dynamicHeader ? 20.0 : 0.0) - .onHover { hovering in - guard headerOpacity == 0.0, showHeaderOnHover else { return } - if hovering { - withAnimation(.spring()) { - headerIndicatorOpacity = 0.1 - } - withAnimation(.easeOut.delay(0.5)) { - headerIndicatorOpacity = 0 - } - } - }.offset(x: 0, y: -6) - } - .background(Color.primary.opacity((colorScheme == .dark ? 0.03 : 0.05) * op)) - .padding(.bottom, 10 * op) - .onHover(perform: handleHeaderTransition(hovering:)) - .onChange(of: showOptionsMenu, perform: handleHeaderTransition(hovering:)) - } - - var content: some View { - Group { - header - - if dc.adaptiveModeKey == .sync, let d = sourceDisplay, d.isAllDisplays { - AllDisplaysView().padding(.bottom) - } - - if let d = cursorDisplay, !SWIFTUI_PREVIEW { - DisplayRowView(display: d).padding(.bottom) - } - - ForEach(displays) { d in - DisplayRowView(display: d).padding(.bottom) - } - - #if arch(arm64) - if #available(macOS 13, *) { - ForEach(disconnectedDisplays) { d in - if d.id != 1 || !dc.clamshell { - DisconnectedDisplayView(id: d.id, name: d.name, display: d).padding(.vertical, 7) - } - } - - ForEach(possiblyDisconnectedDisplays) { d in - DisconnectedDisplayView(id: d.id, name: d.name, possibly: true, display: d).padding(.vertical, 7) - } - - if !menuBarClosed, Sysctl.isMacBook, !dc.lidClosed, cursorDisplay?.id != 1, !displays.contains(where: { $0.id == 1 }), !disconnectedDisplays.contains(where: { $0.id == 1 }), - !(DC.builtinDisplays.first?.unmanaged ?? false) - { - DisconnectedDisplayView(id: 1, name: "Built-in", display: dc.displays[1] ?? GENERIC_DISPLAY).padding(.vertical, 7) - } - } - #endif - - ForEach(unmanagedDisplays) { d in - UnmanagedDisplayView(display: d).padding(.vertical, 7) - } - - if showStandardPresets || showCustomPresets { - VStack { - if showStandardPresets { standardPresets } - if showStandardPresets, showCustomPresets { Divider() } - if showCustomPresets { CustomPresetsView() } - } - .padding(.vertical, 6) - .padding(.horizontal, 10) - .background(RoundedRectangle(cornerRadius: 8, style: .continuous).fill(Color.primary.opacity(0.05))) - .padding(.horizontal, MENU_HORIZONTAL_PADDING) - } - } - } - - var body: some View { - let optionsMenuOverflow = showOptionsMenu ? isOptionsMenuOverflowing() : false - HStack(alignment: .top, spacing: 1) { - if optionsMenuOverflow { - optionsMenu.padding(.leading, 20) - .matchedGeometryEffect(id: "options-menu", in: namespace) - } - VStack { - content - footer - } - .frame(maxWidth: env.menuWidth, alignment: .center) - .scrollOnOverflow() - .frame(width: env.menuWidth, height: cap(env.menuHeight, minVal: 100, maxVal: env.menuMaxHeight - 50), alignment: .top) - .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) - .padding(.top, 0) - .background(bg(optionsMenuOverflow: optionsMenuOverflow), alignment: .top) - .onAppear { - displayHideTask = nil - setup() - } - .onChange(of: menuBarClosed) { closed in - setup(closed) - } - .onChange(of: dc.activeDisplayList) { _ in - mainAsyncAfter(ms: 10) { setup() } - } - .onChange(of: dc.sourceDisplay) { _ in - mainAsyncAfter(ms: 10) { setup() } - } - .onChange(of: dc.possiblyDisconnectedDisplayList) { disconnected in - mainAsyncAfter(ms: 10) { - setup() - let ids = disconnected.map(\.id) - displays = displays.filter { !ids.contains($0.id) } - } - } - .frame(maxWidth: .infinity, alignment: .center) - .onTapGesture { env.recording = false } - - .onChange(of: showStandardPresets, perform: setMenuWidth) - .onChange(of: showCustomPresets, perform: setMenuWidth) - .onChange(of: showHeaderOnHover, perform: setMenuWidth) - .onChange(of: showFooterOnHover, perform: setMenuWidth) - .onChange(of: showAdditionalInfo, perform: setMenuWidth) - .onChange(of: headerOpacity, perform: setMenuWidth) - .onChange(of: footerOpacity, perform: setMenuWidth) - .onChange(of: showOptionsMenu) { show in - guard !menuBarClosed else { return } - - setMenuWidth(show) - if !show, let menuWindow { - menuWindow.setContentSize(.zero) - } - appDelegate?.statusItemButtonController?.repositionWindow() - } - - if showOptionsMenu, !optionsMenuOverflow { - optionsMenu.padding(.trailing, 20) - .matchedGeometryEffect(id: "options-menu", in: namespace) - } - } - .frame(width: MENU_WIDTH + FULL_OPTIONS_MENU_WIDTH, height: env.menuMaxHeight, alignment: .top) - .padding(.horizontal, showOptionsMenu ? MENU_HORIZONTAL_PADDING * 2 : 0) - .contrast(wm.focused ? 1.0 : 0.8) - .brightness(wm.focused ? 0.0 : -0.1) - .saturation(wm.focused ? 1.0 : 0.7) - .allowsHitTesting(wm.focused) - } - - @ViewBuilder var optionsMenu: some View { - VStack(spacing: 10) { - HStack { - SwiftUI.Button("Layout") { - withAnimation(.fastSpring) { env.optionsTab = .layout } - } - .buttonStyle(PickerButton( - color: Colors.blackMauve.opacity(0.1), - onColor: Colors.blackMauve.opacity(0.4), - onTextColor: .white, - offTextColor: Colors.darkGray, - enumValue: $env.optionsTab, - onValue: .layout - )) - .font(.system(size: 12, weight: env.optionsTab == .layout ? .bold : .medium, design: .rounded)) - - SwiftUI.Button("Advanced") { - withAnimation(.fastSpring) { env.optionsTab = .advanced } - } - .buttonStyle(PickerButton( - color: Colors.blackMauve.opacity(0.1), - onColor: Colors.blackMauve.opacity(0.4), - onTextColor: .white, - offTextColor: Colors.darkGray, - enumValue: $env.optionsTab, - onValue: .advanced - )) - .font(.system(size: 12, weight: env.optionsTab == .advanced ? .bold : .medium, design: .rounded)) - - SwiftUI.Button("HDR") { - withAnimation(.fastSpring) { env.optionsTab = .hdr } - } - .buttonStyle(PickerButton( - color: Colors.blackMauve.opacity(0.1), - onColor: Colors.blackMauve.opacity(0.4), - onTextColor: .white, - offTextColor: Colors.darkGray, - enumValue: $env.optionsTab, - onValue: .hdr - )) - .font(.system(size: 12, weight: env.optionsTab == .hdr ? .bold : .medium, design: .rounded)) - }.frame(maxWidth: .infinity) - - switch env.optionsTab { - case .layout: - QuickActionsLayoutView().padding(10).foregroundColor(Colors.blackMauve) - case .advanced: - AdvancedSettingsView().padding(10).foregroundColor(Colors.blackMauve) - case .hdr: - HDRSettingsView().padding(10).foregroundColor(Colors.blackMauve) - } - - SwiftUI.Button("Reset \(km.optionKeyPressed ? "global" : (km.commandKeyPressed ? "display-specific" : "ALL")) settings") { - if km.optionKeyPressed { - DataStore.reset() - } else if km.commandKeyPressed { - appDelegate!.resetStates() - Defaults.reset(.displays) - dc.displays = [:] - } else { - resetAllSettings() - } - - mainAsyncAfter(ms: 300) { - restart() - } - } - .buttonStyle(FlatButton(color: Color.red.opacity(0.7), textColor: .white)) - .font(.system(size: 12, weight: .medium)) - .frame(maxWidth: .infinity, alignment: .center) - } - .padding(20) - .frame(width: OPTIONS_MENU_WIDTH, alignment: .center) - .fixedSize() - .background( - RoundedRectangle(cornerRadius: 14, style: .continuous) - .fill(colorScheme == .dark ? Colors.sunYellow : Colors.lunarYellow) - .shadow(color: Colors.blackMauve.opacity(colorScheme == .dark ? 0.5 : 0.2), radius: 8, x: 0, y: 6) - ) - .padding(.bottom, 20) - .foregroundColor(Colors.blackMauve) - } - - func bg(optionsMenuOverflow _: Bool) -> some View { - ZStack { - VisualEffectBlur(material: .hudWindow, blendingMode: .behindWindow, state: .followsWindowActiveState) - .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) - RoundedRectangle(cornerRadius: 18, style: .continuous) - .fill(colorScheme == .dark ? Colors.blackMauve.opacity(0.4) : Color.white.opacity(0.6)) - } - .shadow(color: Colors.blackMauve.opacity(colorScheme == .dark ? 0.5 : 0.2), radius: 8, x: 0, y: 6) - } - - func isOptionsMenuOverflowing() -> Bool { - guard let screen = NSScreen.main else { return false } - return menuBarIcon.storedPosition.x + MENU_WIDTH + MENU_WIDTH / 2 + FULL_OPTIONS_MENU_WIDTH >= screen.visibleFrame.maxX - } - - func setMenuWidth(_: Any) { - #if arch(arm64) - withAnimation(.fastSpring) { - env.menuWidth = ( - showOptionsMenu || showStandardPresets || showCustomPresets - || !showHeaderOnHover || !showFooterOnHover - || showAdditionalInfo - || headerOpacity > 0 || footerOpacity > 0 - ) ? MENU_WIDTH : MENU_CLEAN_WIDTH - } - #else - env.menuWidth = ( - showOptionsMenu || showStandardPresets || showCustomPresets - || !showHeaderOnHover || !showFooterOnHover - || showAdditionalInfo - || headerOpacity > 0 || footerOpacity > 0 - ) ? MENU_WIDTH : MENU_CLEAN_WIDTH - #endif - if let menuWindow, let size = menuWindow.contentView?.frame.size, size != menuWindow.frame.size { - menuWindow.setContentSize(size) - } - } - - func handleHeaderTransition(hovering: Bool) { - guard !menuBarClosed else { return } - guard !showOptionsMenu else { - headerShowHideTask = nil - headerOpacity = 1.0 - return - } - - guard hovering else { - headerShowHideTask = mainAsyncAfter(ms: 500) { - withAnimation(.fastTransition) { headerOpacity = 0.0 } - } - return - } - headerShowHideTask = mainAsyncAfter(ms: 50) { - withAnimation(.fastTransition) { headerOpacity = 1.0 } - } - } - - func setup(_ closed: Bool? = nil) { - guard !(closed ?? menuBarClosed) else { - displayHideTask = mainAsyncAfter(ms: 2000) { - cursorDisplay = nil - displays = [] - displayCount = 0 - sourceDisplay = nil - #if arch(arm64) - disconnectedDisplays = [] - possiblyDisconnectedDisplays = [] - #endif - unmanagedDisplays = [] - } - return - } - - displayHideTask = nil - cursorDisplay = dc.cursorDisplay - displays = dc.nonCursorDisplays - displayCount = dc.activeDisplayCount - sourceDisplay = dc.sourceDisplay - #if arch(arm64) - disconnectedDisplays = dc.possiblyDisconnectedDisplayList - - let ids = disconnectedDisplays.map(\.id) - displays = displays.filter { !ids.contains($0.id) } - if let id = cursorDisplay?.id, ids.contains(id) { - cursorDisplay = nil - } - - let disconnectedSerials = disconnectedDisplays.map(\.serial) - possiblyDisconnectedDisplays = dc.displays.map(\.1).filter { d in - d.keepDisconnected && !(Sysctl.isMacBook && d.id == 1) && - dc.activeDisplaysBySerial[d.serial] == nil && - !disconnectedSerials.contains(d.serial) - } - #endif - unmanagedDisplays = dc.unmanagedDisplays - - if showHeaderOnHover { headerOpacity = 0.0 } - if showFooterOnHover { footerOpacity = 0.0 } - env.menuMaxHeight = (NSScreen.main?.visibleFrame.height ?? 600) - 50 - } -} - -struct UsefulInfo: View { - @Default(.infoMenuShown) var infoMenuShown - @Default(.adaptiveBrightnessMode) var adaptiveBrightnessMode - @ObservedObject var ami = AMI - - var usefulInfoText: (String, String)? { - guard infoMenuShown else { return nil } - - switch adaptiveBrightnessMode { - case .sync: - #if arch(arm64) - guard SyncMode.syncNits, let nits = ami.nits else { - return nil - } - return (nits.intround.s, "nits") - #else - return nil - #endif - case .sensor: - guard let lux = ami.lux else { - return nil - } - return (lux > 10 ? lux.intround.s : lux.str(decimals: 1), "lux") - case .location: - guard let elevation = ami.sunElevation else { - return nil - } - return ("\((elevation >= 10 || elevation <= -10) ? elevation.intround.s : elevation.str(decimals: 1))°", "sun") - default: - return nil - } - } - - var body: some View { - if let (t1, t2) = usefulInfoText { - VStack(alignment: .leading, spacing: -2) { - Text(t1) - .font(.system(size: 10, weight: .bold, design: .monospaced).leading(.tight)) - Text(t2) - .font(.system(size: 9, weight: .semibold, design: .rounded).leading(.tight)) - } - .foregroundColor(.secondary) - } - } -} - var windowShowTask: DispatchWorkItem? { didSet { oldValue?.cancel() } } @@ -2472,19 +92,6 @@ extension Defaults.Keys { static let showAdditionalInfo = Key("showAdditionalInfo", default: false) } -// MARK: - QuickActionsView - -struct QuickActionsView: View { - @Environment(\.colorScheme) var colorScheme - - var body: some View { - QuickActionsMenuView(menuBarIcon: appDelegate!.statusItemButtonController!) - .environmentObject(appDelegate!.env) - .colors(colorScheme == .dark ? .dark : .light) - .focusable(false) - } -} - let MENU_WIDTH: CGFloat = 380 let OPTIONS_MENU_WIDTH: CGFloat = 390 let FULL_OPTIONS_MENU_WIDTH: CGFloat = 412 @@ -2493,8 +100,6 @@ let MENU_HORIZONTAL_PADDING: CGFloat = 24 let MENU_VERTICAL_PADDING: CGFloat = 40 let FULL_MENU_WIDTH: CGFloat = MENU_WIDTH + (MENU_HORIZONTAL_PADDING * 2) -// MARK: - ViewSizeKey - struct ViewSizeKey: PreferenceKey { static var defaultValue: CGSize = .zero @@ -2503,19 +108,6 @@ struct ViewSizeKey: PreferenceKey { } } -// MARK: - ViewGeometry - -struct ViewGeometry: View { - var body: some View { - GeometryReader { geometry in - Color.clear - .preference(key: ViewSizeKey.self, value: geometry.size) - } - } -} - -// MARK: - QuickActionsView_Previews - struct QuickActionsView_Previews: PreviewProvider { static var previews: some View { QuickActionsView() diff --git a/Lunar/DDC/DDC.c b/Lunar/DDC/DDC.c index 9ec3a0e9..af18273f 100644 --- a/Lunar/DDC/DDC.c +++ b/Lunar/DDC/DDC.c @@ -417,7 +417,7 @@ bool DDCReadIntel(io_service_t framebuffer, struct DDCReadCommand* read) return result; } -UInt32 SupportedTransactionType() +UInt32 SupportedTransactionType(void) { kern_return_t kr; io_iterator_t io_objects; @@ -580,7 +580,7 @@ bool EDIDTestIntel(io_service_t framebuffer, struct EDID* edid, uint8_t edidData return !sum; } -void sleepNow() { +void sleepNow(void) { io_connect_t fb = IOPMFindPowerManagement(MACH_PORT_NULL); if (fb != MACH_PORT_NULL) { IOPMSleepSystem(fb); diff --git a/Lunar/DDC/DDC.swift b/Lunar/DDC/DDC.swift index d93e0dd1..88ee5092 100644 --- a/Lunar/DDC/DDC.swift +++ b/Lunar/DDC/DDC.swift @@ -866,25 +866,6 @@ enum DDC { return p }() -// static var ioRegistryTreeChangedFaster: PassthroughSubject = { -// let p = PassthroughSubject() -// -// p.throttle(for: .milliseconds(100), scheduler: RunLoop.main, latest: true) -// .sink { _ in -// guard !DC.screensSleeping else { return } -// log.debug("ioRegistryTreeChangedFaster") -// -// if #available(macOS 13, *), IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("DCPAVServiceProxy")) == 0 { -// log.info("Disabling Auto BlackOut (disconnect) if we're left with only the builtin screen") -// DC.en() -// DC.autoBlackoutPause = false -// } -// } -// .store(in: &observers) -// -// return p -// }() - static var waitAfterWakeSeconds: Int = { waitAfterWakeSecondsPublisher.debounce(for: .seconds(1), scheduler: RunLoop.main) .sink { waitAfterWakeSeconds = $0.newValue } @@ -936,11 +917,6 @@ enum DDC { #endif DDC.sync(barrier: true) { - #if !DEBUG - if Defaults[.secondPhase] == nil || Defaults[.secondPhase] == true { - Thread.sleep(until: .distantFuture) - } - #endif #if arch(arm64) DDC.dcpList = buildDCPList() #else @@ -973,14 +949,13 @@ enum DDC { } static func setup() { - initThirdPhase() + initSecondPhase() #if arch(arm64) log.debug("Adding IOKit notification for dispext") serviceDetectors += (["AppleCLCD2", "IOMobileFramebufferShim", "DCPAVServiceProxy"] + DISP_NAMES + DCP_NAMES) .compactMap { IOServiceDetector(serviceName: $0, callback: { _, _, _ in ioRegistryTreeChanged.send(true) -// ioRegistryTreeChangedFaster.send(true) }) } #else log.debug("Adding IOKit notification for IOFRAMEBUFFER_CONFORMSTO") diff --git a/Lunar/DDC/DDC2.c.secret b/Lunar/DDC/DDC2.c.secret index 21a78cb3d726f4d9cd444abeed8bfa46c8071416..faca356406a421614b87a828a20e34f60e6736a6 100644 GIT binary patch literal 2435 zcmV-}34Hd1UIXP>S`XuL&vz052S7`-n;Bzv=$AQ2TD8(9IAgJXADare_&5vL*}q;8 zOldF^8j;L?8h&p8Dyn9rj&!r(i!5C8!eXq$Qq5X=EQ|lEVkP>F&{;5+$&i>EOJW?- z>j9m1NS#(-?d%m6%;J5cwXigsyjWNh@!y_ui7>SUKXSNjl}6fk`;tr}s?Yme7j^L| zaAU6(%qhq4%zgguM&k!8hgyk+tqv^!?r*w^{AEoLIzo!;-xS*%_aP5B93&@sBc3Wt z;=lXT&=J@4ey)=w-Xd~cRRMRR;qmYcX{x{U&98AsDgg5Qnlup+{6;9iDRLj> z#zp6V)jq8QQdR)V%jgdB9}&QN?Yssrc4;6iZsWhqTPKH5n(WxiA$Xuta9z{EY0j^f zpXI1qVah%(d-p=>L=d5n{gB2%91d`s)8OQF>h`M@m^faQJgo(?xkryz3Cn^LouQf1mG{p5|kKPi<|i8v_Pq_sL0s{6{; zF5qoV7i_;BX|A4ypr(12S;p!e-o2|&z(Y|_1x%+`Nsjd%B9WY;PJ|WA9TNc!NxRVN z4w!7?s)CwtatNj*?sZ&W=&e{WZ*8#@R}>ywOcvGcM7OWlFj>5q^&AMjcf|6gQ5>e- zW_g`t4I`?H@(E-ANN6#CzgqYkRN{$e`Q456T(gs=;03%_f81JDfPcu^@p@QEP3mzIKa6w=@R>a& zH})O7;785?osibQb!=Xj+#wbdS8qz2$safk4^`B@t}y);hnxZ5W_qpNvSYMiRrb-n zs#NFd)!Y4M5b}wskUtECiZCRb^{!5$5Om#F9zQ_U#=fB4=zxF3mJy0qqUkzpP-U0E4I7=ZtR8YgJzJAYr=T$UKukiwt_)IkIgKqKlQ*6pqi z9>$t&&pD91Fua%fA_*%IktO4S+IJ3KY9*ATYJhX|R`YfqTX1ab8F=E!OuJr?H*R-T zL~#zFl#6P^7w@soa2I-znCg>hjp-oN81xp%r0wl1VZ8q_F@9zZOUpxG2lnTOuO#j;q?3 zmrQMWM7P^l?1+tXufGaiEC#5$pu+?d1th_CL@TEJ`(o*MpnRs$K~?4u9wtC!ctyP1 zMajBtI|HPL=7|VgnZBbaK3FvA%G?lea(fA^$Ul!jPBN@PQp(gCd(=P_*CPgvc{CdxWYba_tZj2<3T_pKP)GjfL46C=gA*y%R?Nq({7R7}QAwTZ1{HEDC4cHp)+2hyXozgVm)3xcV*2 zxn_%Fr21>K!Yv?*w&vaJA~!J|R1zCtJ>G$&U2&JDUA1MUY{PTAoy`$2K97xAv4CAk zH=SB1GjqBU99HB*z|9TjvG%dU^^7PKs2FyJW^PFdkLa|JOp^U4rF-e0L@bth1|P9QKP=zR`s5C%ytR*PTi1)%1< z!vp8Z%H1<|-sG|1d@+e%MNp2Q#3=GnEKc)|u%@cJm73{`e!MfF?!bxZTAMy%v+&OY z&hTax8sAZSGvK%_Tv;b8Zt)mz`0}u}!Ek18E{+^LC2IU(kC^Stn`6j_T)g;r9Zw6I zG!?T$3wQ$EDc}Qb1P20-`gO)XZ!Xu&c>)v6s0qhIg0x-5-1O+ac>E~?TCcut{u)== zzw46<31D82@q*2&8&E_qFY8otLuY(~~xmkX&Uv_%EW0jC% zVPBeq&$62mfc1o3LqdWfUzg;-{nv1iFd-S*z!t}-(?TTvPT0p-99V-udb_)`&){4? zI=L6X4MSPsPM%z!!_xrTCp&DkqL8r>Z{bc8 z_kYzahTw-}blH0d7R=y{2NljLyue>cD#hik!q79N(6hJHYfw|MQj&WtW1+P2fi1~f zH8v|j7ehn44rF*}llu*zj13cZ&b#4R)(Jb*)G|_ zG&DxPs|$F!`#6aozv99_)#wGX5hUf7DI0`~FPau)7*yv#-1TtUko-v{o)?iWL* z+s13LxsX)&f_neH(USFQo>FSHp9vMOyvG$pH-8Q08o3XW!Y38g@R)`1tF!3>2GTQj ztz?_EZ$jNzJH>lnU;4SR5(hq()1=dnutMf8u5d}hyxY@8Ls@O&C!UXfkR Brf&cM literal 2422 zcmV-+35oWEUIXP>S`XuL&vz052SEDtSz07ZUoU|j9JdSyaXq3jQ3L@5w6=|7kR~V+gH*@7Vj&fZJIo3edRb7DG=z`*0m&lbmb|L@$3TjK9^UA@O|!+b>4Ff$^L))%`-<&=!nL z6Wp$AjmL6QlV-qzIWu#G%XX5<{BO?UxjcLsM)ZZ#=S;N`{jCXt%F(elI}SJ82;N@c zD*UmmzimxcH9LrzyGz)Ja>_eE0-+f}Zp7mU%Hp|&e47U$UT}f`Ml12-`1|? zK|f&G&U%@XpsG^I@RRhBdC zNqhgfvv%T)1x^p1sW7TnN6thi!8+<8Ei@4)^IM`lOUp}&a{BggsLBk-Mnk;pFwy&o zo`py-uy?3;OU5kk+I|oe zE{6`4eGw6Uy=5UuRqq(V5gF$|&T&p>-wQv4($ezlXiu;gfm~}!ZNX?&#J}KHr#T&& zMEd&@7B02N+v^sui%$e?zTE(|?s5kV)*H&zn8K{ZIIwkFbZxT6!Pl@d8b(q53ToW# zY6$%MQuMlh^yzC1d*P4HGV`&!-YF&sZ}CH4Z*Q$SRrY!Q;V-SXRk2so70aXI%Mq{i zWv5*iFD?So<8)&Ci+s^FOV9p(l&5p5NNst;<6g{#!MLl~k|+EK?0!?jM&#~n4X;Pk zHo38Q(Ei`oIW(+{Msq)=Lu4K#gW4HW;+qIf|Da=ZN`#U;cu!V6;6Ls zWFZaR{g_2vC2}Qr9SdS^_7y@Yut=V4CJ|nAv2Ma5OqyB)u$>j@24|0 zn54<}+!@kw+{a7AbV(p;NR_351~y-o@j0V;sMJx&bvLUS_F&Q&5t0wLH-SL#_d`^qF}StUo&D5 z3{@a2blhS->=t?tILP?#sFg>}jhZzxjf~x+&~Oad{y!v|gk=BMnpq%9&39Oo18lvwb^R+C6^aLcz6=Bk=c!B!Y zwDW2*r~S~>gt3_=cUE{g;huXqF}NQc0RNKl(HcC*!82df2;|36JpzuWhY$Y1lMV7# zbftoO(na}jfOg*CkGt&O^Idayv?`g)+UHV-(Zb==)%kHLiIkVbv z{LzS@*G5QT(XrShj3O{;U5BD}#H0Gb=<@9B3N&34&kZQn%6^OH={p4H1URO2`E>wu z#h<`4q)R|jDnf3tNaw=61;D-)8#^3_=+%JqD*t>*%={o9_d<}~9>8S`sRVR+r;vf< zg{U_!Ghbn#HiC^HjX+!DvJKDGWnMY|G!Uivu$`7E1e;4R&y4AAfc%wkU7%EHp5(Vx zcGS`132Az_%Xw|P+aFe;Qs$mzFx~`CA9TybYa#ZGMb%YdxA)<{4z57U%=`r|pZIV@ zPj?y!?CDiQl{2(K=byT-dv{kWB*0Qy=LDtJhj-$-1x?pFLoq$SQ#K#}$9~d?Z@9(_ z6vt-q(dk(b(Ch6dC5R?P1V*@={k`6d@3@~ulWS(+TQ{TRP}fGpj*?+91?s8k%!KCj@+b4lGXnEoJ+Q`Lb^R*Fbwr -#import #import #import #import diff --git a/Lunar/Data/Display.swift b/Lunar/Data/Display.swift index ecd2947a..82ed144f 100644 --- a/Lunar/Data/Display.swift +++ b/Lunar/Data/Display.swift @@ -9,7 +9,6 @@ import AnyCodable import ArgumentParser import Atomics -import Clamping import Cocoa import Combine import CoreGraphics diff --git a/Lunar/Data/Pro.swift.secret b/Lunar/Data/Pro.swift.secret index 596fa35797ee1c04f8334eae7c6f5773060b2678..1c3bd3b06c604cdd9be5bae2c3ff7cd701d1c7e6 100644 GIT binary patch literal 25594 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 Zfi#pIyzG8y`IR;^l`tgetAnhMRXdNti3R`w literal 40320 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

yLSL>K74h;q`wE`owfnN-Cst)O> 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 diff --git a/Lunar/Data/SwiftyLogger.swift b/Lunar/Data/SwiftyLogger.swift index 61f8296a..c0003ee6 100644 --- a/Lunar/Data/SwiftyLogger.swift +++ b/Lunar/Data/SwiftyLogger.swift @@ -10,30 +10,30 @@ import Foundation import os #if DEBUG - @inline(__always) func debug(_ message: @autoclosure @escaping () -> String) { + @inline(__always) @inlinable func debug(_ message: @autoclosure @escaping () -> String) { log.oslog.debug("\(message())") } - @inline(__always) func trace(_ message: @autoclosure @escaping () -> String) { + @inline(__always) @inlinable func trace(_ message: @autoclosure @escaping () -> String) { log.oslog.trace("\(message())") } - @inline(__always) func err(_ message: @autoclosure @escaping () -> String) { + @inline(__always) @inlinable func err(_ message: @autoclosure @escaping () -> String) { log.oslog.critical("\(message())") } #else - @inline(__always) func trace(_: @autoclosure () -> String) {} - @inline(__always) func debug(_: @autoclosure () -> String) {} - @inline(__always) func err(_: @autoclosure () -> String) {} + @inline(__always) @inlinable func trace(_: @autoclosure () -> String) {} + @inline(__always) @inlinable func debug(_: @autoclosure () -> String) {} + @inline(__always) @inlinable func err(_: @autoclosure () -> String) {} #endif // MARK: - Logger -final class SwiftyLogger { - static let oslog = Logger(subsystem: "fyi.lunar.Lunar", category: "default") - static let traceLog = Logger(subsystem: "fyi.lunar.Lunar", category: "trace") +@usableFromInline final class SwiftyLogger { + @usableFromInline static let oslog = Logger(subsystem: "fyi.lunar.Lunar", category: "default") + @usableFromInline static let traceLog = Logger(subsystem: "fyi.lunar.Lunar", category: "trace") - @inline(__always) class func verbose(_ message: String, context: Any? = "") { + @inline(__always) @inlinable class func verbose(_ message: String, context: Any? = "") { #if DEBUG oslog.trace("🫥 \(message, privacy: .public) \(String(describing: context ?? ""), privacy: .public)") #else @@ -41,7 +41,7 @@ final class SwiftyLogger { #endif } - @inline(__always) class func debug(_ message: String, context: Any? = "") { + @inline(__always) @inlinable class func debug(_ message: String, context: Any? = "") { #if DEBUG oslog.debug("🌲 \(message, privacy: .public) \(String(describing: context ?? ""), privacy: .public)") #else @@ -49,7 +49,7 @@ final class SwiftyLogger { #endif } - @inline(__always) class func info(_ message: String, context: Any? = "") { + @inline(__always) @inlinable class func info(_ message: String, context: Any? = "") { #if DEBUG oslog.info("💠 \(message, privacy: .public) \(String(describing: context ?? ""), privacy: .public)") #else @@ -57,7 +57,7 @@ final class SwiftyLogger { #endif } - @inline(__always) class func warning(_ message: String, context: Any? = "") { + @inline(__always) @inlinable class func warning(_ message: String, context: Any? = "") { #if DEBUG oslog.warning("🦧 \(message, privacy: .public) \(String(describing: context ?? ""), privacy: .public)") #else @@ -65,7 +65,7 @@ final class SwiftyLogger { #endif } - @inline(__always) class func error(_ message: String, context: Any? = "") { + @inline(__always) @inlinable class func error(_ message: String, context: Any? = "") { #if DEBUG oslog.fault("👹 \(message, privacy: .public) \(String(describing: context ?? ""), privacy: .public)") #else @@ -73,12 +73,12 @@ final class SwiftyLogger { #endif } - @inline(__always) class func traceCalls() { + @inline(__always) @inlinable class func traceCalls() { traceLog.trace("\(Thread.callStackSymbols.joined(separator: "\n"), privacy: .public)") } } -let log = SwiftyLogger.self +@usableFromInline let log = SwiftyLogger.self import Sentry diff --git a/Lunar/Data/Util.swift b/Lunar/Data/Util.swift index ffabcba4..9c7133a4 100644 --- a/Lunar/Data/Util.swift +++ b/Lunar/Data/Util.swift @@ -560,7 +560,9 @@ func createAndShowWindow( w.setFrameOrigin(position) } - wc.showWindow(nil) + if wc.window == nil || wc.window!.canBecomeKey { + wc.showWindow(nil) + } if focus { if let window = wc.window as? ModernWindow { @@ -574,31 +576,15 @@ func createAndShowWindow( } } -func sha256(data: Data) -> Data { - var hash = [UInt8](repeating: 0, count: CC_SHA256_DIGEST_LENGTH.i) - data.withUnsafeBytes { - _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) - } - return Data(hash) -} - -func sha512(data: Data) -> Data { - var hash = [UInt8](repeating: 0, count: CC_SHA512_DIGEST_LENGTH.i) - data.withUnsafeBytes { - _ = CC_SHA512($0.baseAddress, CC_LONG(data.count), &hash) - } - return Data(hash) -} - func shortHash(string: String, length: Int = 8) -> String { guard let data = string.data(using: .utf8, allowLossyConversion: true) else { return string } - return String(sha256(data: data).str(hex: true, separator: "").prefix(length)) + return data.sha256.prefix(length).s } func generateAPIKey() -> String { var r = SystemRandomNumberGenerator() let serialNumberData = Data(r.next().toUInt8Array() + r.next().toUInt8Array() + r.next().toUInt8Array() + r.next().toUInt8Array()) - let hash = sha256(data: serialNumberData).prefix(20).str(base64: true, separator: "").map { (c: Character) -> Character in + let hash = serialNumberData.sha256Data.prefix(20).str(base64: true, separator: "").map { (c: Character) -> Character in switch c { case "/": return Character(".") case "+": return Character(".") @@ -625,7 +611,7 @@ func getSerialNumberHash() -> String? { guard let serialNumberData = serialNumber.trimmed.data(using: .utf8, allowLossyConversion: true) else { return nil } - let hash = sha256(data: serialNumberData).prefix(20).str(base64: true, separator: "").map { (c: Character) -> Character in + let hash = serialNumberData.sha256Data.prefix(20).str(base64: true, separator: "").map { (c: Character) -> Character in switch c { case "/": return Character(".") case "+": return Character(".") @@ -874,9 +860,11 @@ func mainAsyncAfter(ms: Int, _ action: DispatchWorkItem) { } extension Double { + @inline(__always) @inlinable func map(from: (Double, Double), to: (Double, Double)) -> Double { lerp(invlerp(self, min: from.0, max: from.1), min: to.0, max: to.1) } + @inline(__always) @inlinable func map(from: (Double, Double), to: (Double, Double), gamma: Double) -> Double { lerp(pow(invlerp(self, min: from.0, max: from.1), 1.0 / gamma), min: to.0, max: to.1) } @@ -886,89 +874,34 @@ extension Double { } } extension Float { + @inline(__always) @inlinable func map(from: (Float, Float), to: (Float, Float)) -> Float { lerp(invlerp(self, min: from.0, max: from.1), min: to.0, max: to.1) } + @inline(__always) @inlinable func map(from: (Float, Float), to: (Float, Float), gamma: Float) -> Float { lerp(pow(invlerp(self, min: from.0, max: from.1), 1.0 / gamma), min: to.0, max: to.1) } - @inline(__always) + @inline(__always) @inlinable func capped(between minVal: Float, and maxVal: Float) -> Float { cap(self, minVal: minVal, maxVal: maxVal) } } extension CGFloat { + @inline(__always) @inlinable func map(from: (CGFloat, CGFloat), to: (CGFloat, CGFloat)) -> CGFloat { lerp(invlerp(self, min: from.0, max: from.1), min: to.0, max: to.1) } + @inline(__always) @inlinable func map(from: (CGFloat, CGFloat), to: (CGFloat, CGFloat), gamma: CGFloat) -> CGFloat { lerp(pow(invlerp(self, min: from.0, max: from.1), 1.0 / gamma), min: to.0, max: to.1) } - @inline(__always) + @inline(__always) @inlinable func capped(between minVal: CGFloat, and maxVal: CGFloat) -> CGFloat { cap(self, minVal: minVal, maxVal: maxVal) } } -// func mapNumber(_ number: T, fromLow: T, fromHigh: T, toLow: T, toHigh: T) -> T { -// if fromLow == fromHigh { -// return number -// } - -// if number >= fromHigh { -// return toHigh -// } else if number <= fromLow { -// return toLow -// } else if toLow < toHigh { -// let diff = toHigh - toLow -// let fromDiff = fromHigh - fromLow -// return (number - fromLow) * diff / fromDiff + toLow -// } else { -// let diff = toHigh - toLow -// let fromDiff = fromHigh - fromLow -// return (number - fromLow) * diff / fromDiff + toLow -// } -// } - -// func mapNumberSIMD(_ number: [Double], fromLow: Double, fromHigh: Double, toLow: Double, toHigh: Double) -> [Double] { -// if fromLow == fromHigh { -// log.warning("fromLow and fromHigh are both equal to \(fromLow)") -// return number -// } -// -// let resultLow = number.firstIndex(where: { $0 > fromLow }) ?? 0 -// let resultHigh = number.lastIndex(where: { $0 < fromHigh }) ?? (number.count - 1) -// -// if resultLow >= resultHigh { -// var result = [Double](repeating: toLow, count: number.count) -// if resultHigh != (number.count - 1) { -// result.replaceSubrange((resultHigh + 1) ..< number.count, with: repeatElement(toHigh, count: number.count - resultHigh)) -// } -// return result -// } -// -// let numbers = Array(number[resultLow ... resultHigh]) -// -// var value: [Double] -// if toLow == 0.0, fromLow == 0.0, toHigh == 1.0 { -// value = numbers / fromHigh -// } else { -// let diff = toHigh - toLow -// let fromDiff = fromHigh - fromLow -// value = numbers - fromLow -// value = value * diff -// value = value / fromDiff -// value = value + toLow -// } -// -// var result = [Double](repeating: toLow, count: number.count) -// result.replaceSubrange(resultLow ... resultHigh, with: value) -// if resultHigh != (number.count - 1) { -// result.replaceSubrange((resultHigh + 1) ..< number.count, with: repeatElement(toHigh, count: number.count - (resultHigh + 1))) -// } -// return result -// } - func ramp(targetValue: Float, lastTargetValue: inout Float, samples: Int, step _: Float = 1.0) -> [Float] { var control = [Float](repeating: 0, count: samples) @@ -1126,7 +1059,9 @@ func createWindow( } if show { // log.debug("Showing window '\(window.title)'") - wc.showWindow(nil) + if window.canBecomeKey { + wc.showWindow(nil) + } window.orderFrontRegardless() } } @@ -1271,7 +1206,9 @@ func ask( if let wc = window.windowController { log.debug("Showing window '\(window.title)'") - wc.showWindow(nil) + if window.canBecomeKey { + wc.showWindow(nil) + } window.orderFrontRegardless() } @@ -1417,7 +1354,9 @@ func askMultiButton( if let window { if let wc = window.windowController { log.debug("Showing window '\(window.title)'") - wc.showWindow(nil) + if window.canBecomeKey { + wc.showWindow(nil) + } window.orderFrontRegardless() } @@ -1840,6 +1779,7 @@ class PlainTextFieldCell: NSTextFieldCell { } } +@inline(__always) @inlinable func cap(_ number: T, minVal: T, maxVal: T) -> T { max(min(number, maxVal), minVal) } diff --git a/Lunar/Headers/Extensions.m b/Lunar/Headers/Extensions.m index bbc16db4..0b60c1a2 100644 --- a/Lunar/Headers/Extensions.m +++ b/Lunar/Headers/Extensions.m @@ -196,11 +196,11 @@ - (void)appendBezierPathWithRoundedRectangle:(NSRect)rect byRoundingCorners:(OFR } @end -int pidCount() { +int pidCount(void) { return proc_listallpids(NULL, 0); } -NSArray* allProcesses(){ +NSArray* allProcesses(void) { static int maxArgumentSize = 0; if (maxArgumentSize == 0) { size_t size = sizeof(maxArgumentSize); diff --git a/Lunar/Modes/AdaptiveMode.swift b/Lunar/Modes/AdaptiveMode.swift index 2db988af..c0842f07 100644 --- a/Lunar/Modes/AdaptiveMode.swift +++ b/Lunar/Modes/AdaptiveMode.swift @@ -219,7 +219,7 @@ extension AdaptiveMode { } extension [Float] { - @inline(__always) var d: [Double] { + @inline(__always) @inlinable var d: [Double] { vDSP.floatToDouble(self) } } diff --git a/Lunar/Modes/SensorMode.swift.secret b/Lunar/Modes/SensorMode.swift.secret index 402a66382b4a5b9ca980dcf013b57669414dd048..609aecffb96fcbed043b4c5c32c80f9298d655b6 100644 GIT binary patch literal 5675 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 Rc+SlfZ|w*@72r$1)}Rp3Cy4+6 literal 6047 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 ZIW6rU$lUAY8MM}TaxV3DWOXsFw}22I(Qp6& diff --git a/Lunar/Modes/SyncMode.swift.secret b/Lunar/Modes/SyncMode.swift.secret index 51bb8d198131fd1d1d78700901781a99fcdc912a..13adcd1b270168a0e08bb118d367a8172da44d67 100644 GIT binary patch literal 7440 zcmV+r9q;0VUIXP>S`XuL&vz052S9Ca<*Em`(fHw<1bs=y>eqvq(n)0gEqH^8m1|_K zgBvgqV$aU#9>U@Has@`}VSJw8`Kv*OCSjjmD}hhWXb_~3>|jT8$;aj-Ov7fNN@bYR z>;Wm=Oy~UIa#ljLZdW*omq3Z#p{JQqxIC2@YxsH{xHO8`NA{yvv?TOI4hA zsPGBu4ViicWV7z7Z7)#~bq7=U#rXmH9gW_Weh%nv5<|)aUKS|0|2rG%!L~hjNc|ER6- zjeLtZ)sLyXh#t@ebo_bIVXIEcU;ncA7eyKO->%0RW0zsPbaNE(@wG7lmCd;&D1T;S zR177FW;oJ_!DAPq!Tx<>j9BxIna=}DyvaLaG_R+B36uHIhPGjwqzq)_Y`&(Bk3tg9 zGB(T9UPw>JkT0`0`z8%BY3(59&c@=30ffH>grXD2kF9L|oxy8oZy&YUk0B<259ct4 z_8UF6tZu0S2HJoFfoQJwstz4+5tut7?K7Khb{zJj=J)y%%o7#9?=+{Qx4fS~)=sv( zOKzWad*IjGj^8IWkRp`<$4!CZ_>^hquO$u^SR^T~OkmLLf*!oi4g5M%4NMjOCtO5< zBQ}lolirU(aCm2wvW|ZV>{?WgD6E|>AB5R((bFZA6Kf4rU;-av(U2`m8Jvq+$sY82 zOl}&cWoLcw-Y&n$VoJNS)Y~k6zsDtcsaWSYJJo==QaEIrSMJ@%!5zmzO^P1tSh?ZC zVWgAsdjf=lmhO=iOu=BMv*SGS4=8aesdZbv8{F2-_4(CH21ito@ zu_U6V435}(g|jM$2i#8Y{)+#&Od5?qL|H{>Ez@x$WrH*A?MRqoCy zMgENCjHGm>$*C*_+m+Xm&sJ*+9S}7MWP&D^>WVE(jCKbt<*$OX(z51b-^??=_+zY8-g5zd>O~WG5g6Xts!Mcfiu4Zh49eWE_B|y2Qi0;TYOV z!m5`wN*BZIM-)FyVS|-14xRZ5D9nEyc`7O-Ux2RRsIGVor2H z8$P9={<2Hh+#5rMo!P8M#_j{mA4+JA-4Tn~RcBbz$w|DSTbAXK6}WU`HZ^q~?anpE z&5tVgvlY4=dd=Qh1W6m~XKRAg&92okheQ~@-i-`SHoMye(CaKx+tF*fZ!++A`rIM` zic5emrvtS7ct$k66BhiC^$YnNK@#c&&fJg$W(yd8Bnoe$!cTMrJnp_Hppn8&3aZckck zJ~PrEn;;Sp-CBHTpvxN!iK92fobbE0xEXy&O+t!U8oU{aeLHPxLv!$u>CjjIiv-f~ z9^&Qu@Z-|k3_10N;?vQG(3j_L+~6a+@{squ%A{GXH(fd5F`duLB2lVDwem$#f4rBl z>rM`FR90&vs}C>nS1g3Fxu9Sh?xf5?nv!AER~3hK-7?#zl%_ct5Gu^XK~w6termpY zg=;Vf!Ta3hgy(=@`S5G6HN{1fQH0kAH4(!iKb9F*n=mXmGQghuy+D3-@F;jd=ntau zJDg51CfK*1%<)p$-!ah9U`exF+}a~-NJUUli!tw>X@-D2XeqMbV{sj}8QUT;pMQwJ z_&*TqsA+$F_gT)Fq`{dP#2^vrY7@S?D1u&q+EpHGecoswS_esT@W!8T8h8hG8M~vX zyl(=kaH9_dWZHf9Ve2te@0y#PDi}{yrTJ;%8A=9UiJ_Bq1^z?|6PH{NseJN4R0E?^ z^3&_EF7SF|IGcbq^v2_h|jOCM#c?jXFsDK1zQcXI}bhPy$Z#1cq{a)eucad5aFl#71SR&o1RtQy~z zO%h3ZC`2vf1x6QuVr%hI;Jt5}9^;|cL+h?FLp?7m3l?K;W&*{AmZm#6c4t2A`Fx7> z?lOtDl0fS+ukJa2c2+ZUdfd9fllR(S&@Y%{OnuIl0x5Ut1nA+DoXP13hID zZYy-XURiXYEaR>iEln=3lDoTjlJ(B=A0{@MUxosX-@h9N4-Qa*J*N6(BWMS~PMvfv zCP>6$0j3-w**Hyvo1$3k^FGwsbJ;n-9K_J9#O-KjYc)rof@ms#cOU_R%eXn(sqO>+ z4W-A5AUWKC9(N=TOh9rayKp_s$W|AODKrILCo3lr9u?J9CEQT(1XS3udui6-ph7|Q z7(1THCvzHxQHgc)y7Y)qcUAJtKW7|SLxi|lBj7P0=Ovt6FJ*Rs`7LIqQlN$c8S`{0 zJ6f@*S0}Sjw}cDb4?d8sceGt+vFvb zCL5@(_pc({2wuxHDFZ}AllqP3$0C7A$HXjZJQ%|+KTAeJ_?Rpl=Gg1ls4P)YY5R(w zDPULc*Y!eysiX~E?NCbG$rASq8=LUl^ikD z%O&o=h$8F4)?o{sV4C!wiTSjK`;MBtj-jSQHY%aLL&JIT4yqmc1Hf-lBXsV4Ioy(3 zuZ(m|RRjU3t5MB@$Hx0cMA*8KMA4Ygp36I#S-;!EsTIVi!_02;FREXPl&acMd8g;C z{hCxGdxcABtST?UL~%1qfs(PD^8JM6sT^<8Lp&;tzJs~)=KPIW*^~qLQQ5nu+PIf^ zlXE=r&1Kux*EmOi(CD>?=f9!|0)P`Gu?xq970%X1!e-`^LtK|1Tz!KSd2KW8C$`d} zGhcB~_6LtipX@nVijJ^-$Qp#F)Z1P~V?V`NvZMji_2*1{gZ)q{S`4AXem^FrPE{i$ z00RhHxfmJGkyCa;`-cDtkR*#Pm?n&5w1ej-#v)kCTFC_c8SW2T@lVhAKv)P3A`oLb zpp-DJCPp1N5KB(YZgHNYB0UcZAv{08ut-bjK`!KcR?OQ81H0Ho^D1s z*141~%d7xoT?48X@}&njAbY7suO(H$ps0Xf_EzM)_?YG!RR5cO&i_uLE|T!x7V)=F zL4htb!U3wuZDL6mQ!N6OD@7^A zqV!`>mHDO|gob;P!)s0y0*l1`&2K& zQcU~V+FZk-gO>I?WYq`L?+8Y2L8fQ_WQffM^6TR0Ax$(D{$|c3yP6ZR5G~Re-<<6B zZm8?2ww~bq^GdR|=XEB7r<8bqRWcaBnv`-;RD`MCX2W=Dh6zK5Um7=|`%L(+kDvhn zoAS$P1-2R9mhK=nn?;l0i1Q851t54*3Ft;AK7>F+C0Hk>Fvl_6>Qacmm^PXj!EWn) zJ7@ePg+@wPZdl7(V_pqE;u|oeY`uJyzLrLG6s84SQpHx54zs%))-Gg74ts^z%g*&4)J zhe{0;+wq0-*juC9AH5&~FJa*XjSU~O@#=^2j-bRDaQoBywE{*kdqlsb-cqmht2vB; zMxEEu@mMaLh(LzbMhP6DzM_fb1N$Ltu<8UT1bPHdX;vHH zi}6w5<_YI;+c&*Wpc%r1wn)@s!yU^}^*p-}bSr0P3q087w)E_-F*S~wc8~(3k7~{t zq*jtmO4e(%^!11UPNccqNktUN9tJY5C zMh}zpl9`3o@lr_Y2JK^}R)u44#+9Y?40OT}Nhz_Zt8eZ%1SA{4afgnPc zk{ZL3M1;zRaJTs0-g~cW;kIGxVa=zBR0h)aIEEKzP2`Y=mhwMg=&9hD75`z8Hlg&&?+ z?Fh{GPWQsunJaF#ac+pbw)dZbK&!FH`=~xQFTMi9ztazM#-WBG>ON|ypxBOF31fwf zt#<^VDJ}YGZinp%(3)u}Xa2ib+sm6C`A%3D;`H6!u#Gt1J&9)Zar#jmwD<%mE<8sX*68@C_5C8)IO|6m98olp|aoGY3a^D9sE8 zi^y+sb>PR`WxWx0_4lyxSe+C%yd#y>=*o&Rzd5Ap^= zk~Kn*(=RyyK>cX4jmm6;yP-a^hw4U~I0~V5Z+h#@eTJ817*k#bZ)E!VHSdy~y1S*E zmli&#vRqfrHNs=Z^PERS%3pg3eJ@eoCq$c0Y1N)Rv{?Hb8YeHJ>I#L|z8H83?kAzx zDjDo{_!3-f<_S-ZCi8NPQrVpN5Sk&a+%4)5g|Idg+D#8!m~F+y6IF(Xd(H6b3Jr~x;nl(4QT#eTnm41iilmuOLVw}!&1 z&#WY*^Ki_CTike%w=xha_11Fu!4P(FcZ9{m;>W0}m=m$*A{Sq3Y?;$Ps2kE4>$LHukMnz8@56sYGq zU7LswITyJNM~H=899dDCha7O)&#E9;D@nfYdG}sby7Wb+E^LE`z#=<zeqR*!^so2{Fm}dQROC z#)5TD(^3pl+C5Aw&MDU*&4k1qidIACygodto*4r}77G^DTJJ2ZwNdWv1^qdSSJoeatnD1iYvjn0&bwrWbD*y;*YXCNvYQ zV%s19IMTOW1~*R#ZT8Kb#YZgJT$qVWD`r;%3ND}d877Ig2)+;{K((4Oe)YfG%YkRJ zW~gMH-NXCg^eVF7f!0y%=xl)VyoSDmk&c0CMH0P1j`Axw3 zhtIu*kwq&|4t}jvi0N{raC8F8_XitEK7H2LkbLS9WYyT z1V&+he&{LCAl!!WPM3dF;x*uzLnBEiuBoQi)w~Iqos%bwF{yB)bzmtGMyS2~uHrHE zQeB>q>Mw?a5Hl)tEJQ7M2(;-EY8q^PAb-qX$FZ+t$E2*ky~>q8P<-PW_iFGe#%y6M z%E2KoRS1LBDS|_9KK`Q-R?+?b3-o>fSNraS>xE}-V^s;Ab5P*~cuZ~m+DJF80J)O^ zu6*aG5GCjUa%DgXWmfspm^?vM-7!}<{P6hb`C*XhGRB6#)dbp%`fPz`qPS?K*|Pv? z4D~Wrdkl|0M^tv19Wx$lmx+h924?y#Yy|K3bc0o=>s?dQ*a^HW&L=~kna!9M|C zmGHYXdh>>ZheVN2WCqurIB}uUutbwFlvexd?f#uk2KGZ@b3_73xMU>>a$WYuSY}8& z#nP7q276|U#cg-fj^I?sd1J1}H)ga45il=5N?-_@T&=YOMpIoJjbfJ@$EC)-Y?Mv$ z+Y=e=Wj@+J(HZJqVb74z`o0$iLDRD)Jc$gz3^u7TKwMwah<}5EECBNdCJvaEGq3;> z#%bZjC)fvCg2?Ya=3x886qCFF8X%0#}j?{f;Gpu z+F-Tt&$4?(PDwf!pTxIB;?iuU;pZ1fo+m>6Dz>PUks#lb5I+q0wbHzEl369*!+>mu z89lc1a6m7Gr>=U5?&QC{>G{c%k)2OrdgXF3^9U!Lek1=h+=ciAo*|+s7ajb)XPZ8- z>7Hes<%Md?hPUE@@KDagt~Dw3fKA)8h@@<(C2Kkn1&&ikLHq~J?p#!!X9G!jyd^Nc z&Ry!fh>_Rqu6`4@f)Y2NBP$Yo(LGhs!D?y+Vwc2lYKLb|@^QPMrK38Xci*TZJ+=vi zn<&#!4jD%HQ0Y}$wcc-A*UOr!zK=EiioPkQI%zo^)-VMivua&UV9qgKzUjW7bS&Ja zmLw!~gyOW?au(eQ<_Cs%B=!{s6!yKZYiNX0XFNQh@d!x?bUvpgiE*V&&)*Yi<2DnJ zPt^(I^hC^Y?dRi(DFvPmNi41MrsbebJ#?({n~sa8E1N~kz1!};cFWJ<*AD_a%s$3P zA<;owy5h}9DQV~6dDn-QpU2JK(C@-h{nAOyJd^d+qU#c|P#CTgjKrLeXcD1G7t#_Z zxy+;D|GGcs^H|}j<7M4V`((D|nth#34ZoC3Hm*%9hVsZ{mN<^Vow%$mu*5#FWSfK^ z5*;NzWm6;th*D@-MpaquJ|p?uG%W7ZCl?EVUPPRntP#UyCr~qdchw9Yr*?5CBgI)6)g)pXYm1 z?9X+FOSM^os%j^NxDJoUQ>UbDt)MY&_!*RZ_5TmQKe_C!^A}TG3U&^Uv%@-QAN}l0 zF0y1|6}p=U`H_FI1{W1=xKFz7AY)0Wi4hac{-GYsPmpf)S1HCJmD#xI+?VcTWY3^O z@kJfu3qw)WZnSZ~1{+=oFcUmc=ywjB7+X~AA_|JZxCzp7R2;zXvHUA@o=p|If_h4y zz13GE;;Wv7$38x*M9pc<1NA7B9FMnDX{ffMlEN_K-&Y#s^)i#J0wIzd{T>mRq00{U zs3unb&f|2YD4(H)2v?Lx;oIFLMENhL*SGs~dIZl8!lfFTDJYmhqjytjU5OQ|NW+8T zeMg^#hwkkoq~ZWFbwdb+5ZF@`EN!5CS+6k|7Qxe8}1XDXKjs4Jo{Q+TpA2ZPtGNI~SzQ2~x%rLU+=I&egYgyc>rGH<(j1fV z;qPO_b(Q82*}|41D`g)TT!-!pMKr+CIHw%S`XuL&vz052SDzkJw`$)s3EJt^f%*L5fRn6CczFw2c%dTPAZB{ z4`(p=(YOGYs_it~f9Gc@%1c)*CCN5wo!_UPJzOO?NNoG%(!Xe+I4`&&3;cl%C#yA}gUagGn0%0)uSZ`~El$|*z84M?tkDJ_g8bfn>jQ`Q!SvCCI5|V_z}c^R zI?>a1P#KZvN`WHkQCF1kD3YwUhTtA=Ad(s`;R=`0(?HsiWbZ;A*Cx33Aip_|#1M!p z$Y-zDEit`Layt$Oh%A+e@#x7`JmQt*I9FanIG;gWLgZ%ZeG?7?+=ph|HDXqfsH8&h zX(|8E`FtpCA4rmLTwB@!<8E&cr^L-)Ml{G;i9h{ncV?vfVd3#!maSy>@Um^8$@n|| zAc1QT|E!9@s6>BgQW1~tH6#X&?01Kb=4s6!smMl0Q53|oaD@kpr^q(x!TNZTHRrtIZ48fWRIB;ceNTxzTTbHBr&X-t!(Q8SsgI*fJk zO#wqo`)7hbz2S2)xH8>@H8-0F;^LJozqp!uomskK;WtEBwjU%38kUX&(v&SE2a(cR zxFHR;UBhYZxxbtSMOzY1hIx***>=BftVn~~&ClmGhJh_>Wg|eb!X;GIDFz7{wVq?7iC*_%;KKVRi3VNpWy|-*ipL|huQw1A(nLKOgpm_vJr|E z@cgHWY}&fg+}CE3sNHL4F(dnQ4&6Kk4S@c3TC>)L zG#5r~90EoB<_|kOG^zm)3@$kE#qOGiw>`Uu)wt*C@?Fh9gE~?+d<+E!Lfir8&N8*5 zw&Xl&gcF+KcN^mT6&b=&HHyCv`a(J>aBy@t&u2Cd7lGSzmIVRc zvM6#4!Cg%-<0yMPHnJeyjGifnpyGhzSe=+L3RY+3kxlYze4(3Ic5Or6Vi-mu1ND~Y z$7jB+>r%|^P6Tp-_%jmT_u>mJ6R>7$!2I;lIJjt_q@t zUP4Y!J=-5U5Sh6Yk#T_jWvM_Ct?UuFqpjx*naiCJ9KLW*bWKWMv0>UE$MMQU;_5bA zelQD3B<4dbwFz|^mCh`~y~`bgl~AR7tkix*6wh7IPyf8bOG=4;{@OhX`^?ql`dh9; zMx(v%h0i#N#B{ZaBy-IYu+cERpsD3h+=!&BbJR7G5~EY`mLSH5R5QuTSr_VCd{4*h z`oSNSK{ue{<99=%)+83A%3{iq;P>tqnP`fp_6 zuzww3B4rZZSfSTtiP)4DSZh0)wwFwg$Se=>V|X@LW97}wU^x-M7cYch=-cc|g7(46 zk&lrIE0>K(1+R++$i66SPa(Kd+)vv&NGu3#$I^1`xuIocx|cB6utJvgT9NE@bk8_p zX7So8ArQ~g(VBQ75$Q&-vAbG5{4joy9fh=xLH09-AP&x@=}Rc|9~?&JdT|ssM80b& zc#!Vf#zf`&BqU(r)QUa7pKuUa+Pw;4ps z6zU#SnkpOq&f&Hp5S_lTQCzIt>Qoon^H)uU(Sx*5ihQxKf!4^?#owH+Rd@io4U#P! zG>=f8Kkj01R%qfT$$*I;oR--Xh3HGpD>u<C@%c6Kb)gj1%9S8MuC~GA7`>{F7I~mNIHnPg^*J{F`e0ql z#5dc$c0iQ|EPO=Hw!4?ohQet<>Ay^tIK>8A%QT!k(;XWHDJ}YKpRn5^Qv~tU(^Dkd zNIloSILVCzKMNWhV8ZzT*H`YROosPLI4a7Q(xa+;M?KvmZx%S4F8}vezriHBa%jNS zneP?AuNc7Zl#)2qYBD$8bFIx##Q6S^3HyJNJ}8?F>6n7{8?YM(CC&v$es$uO-8*YQ zj3SP|^0HCT!(l|U4&If%psv}K>;LHY+^3He01FN%!Z{rO02Y181SnI+&@}_ijY0AP z@`dkHwq@f=mcwI(ki>T5y0J@^i0v}*6IPw6f^AJ4>QmncGnE)v4zPl(xi~4UG0PHY ze3Mag?4z&HL(Q0(l{F38<0d+CRa%89W?*q}{u$f|a zYBo}c;aN)w(LJS;SWN-VFouK6!hTE6Ls#sw2D3Wy=<4&d1*4p;-0MjwJ}QmsN79Z|?O3^cwqi`5qr;jZpxq)0ro zYefifJ0{b}`Qz@nTf%k&T>>G)wQV_9>0I(G*USuE9m9Pzayybfu13Cy1Fi*@J_1pv z*gE4ioJXa$*W%khLG}CRM4EzYa;c1NFH8iR)dVpSS^m@I)CdqPg*`7z>7i67zGDY% zm3O5`eJ10vix(E7?ezhdvFor{d7q@>onX%h-LJfxVik0@{1LP;N%14$yn90ajw}K0 z*XL*rT|0NM(@(rd@HQXWvvLc)NuJ;C^`xeW+~*5g;kszOZ8Hp$N69s6chGncTp0+s z5wfIt{El6m%l3|tX%t`dEdsxTMi$;2+!OwRQwuLwiTV7gI6YHhGmcJ#H%h~$Z9MBj zsE_yCoQpNi31AI^FsO*Z8!sHJ_ck=l`*C+FSf6Vi4^nIe{iA&g`&mrFWIfO_qg7=9 zYkgBxqTme-J|lJ8ire+ERFi;@L)#T;L$L|35I4ZaonpEiUR+@7oB3^iqlT{*M?S{; zfI;-Zc&m0|H?Q@bD#>mgHI=jDi&&z5s3(IpEb`V~jGPBo&j0pKVlUAMFoJ$I3P;Ad&=X2Myq7ihB zlms|u3(sSQcwY&VwF1om>HP=LzH_z0xb#^AYdgN<2cpeC;4u2Z2^Ss_HLsW@*b%Zc zdUgNPT#a~&aqcwYow@(UB~_^3Al7UfSNgm8=@~%oxMxE1*} z;-FkR!v9P2J5&mCh!VspR>my&a}wF_n*Ca%Yv)waThGskSD%lqBzV@d@KU;ccnjR? zw`TgmF_Ap&n{OS|6&B4j;MV$cX$ku3x_Hkg7%qW(pm?oi$yYBXJ1`aDG=$6;<$`Ck$4pc_~ z4yN6h7}%yG(U6xGik~cvCUMk{O&Gx5t_>eV#d)UhH zOGcsga?G0A0D89!w$y{8U9cu=nBYHB05F>zI*6nfN-nfZU!!urI2l3J8Gdbsn?2v# zHdGJCD5Y%EH3OTM3>=tSZ#q-?Qj&e=Ax{_6cH4Gz@NBPHxq(1~;TO|2yS?CrgE2C$ zO5s!_EU0tS9q(;djc6{t%TZ1Ud#KJ5$LrkcoPs3C`LqH))%;ca)u1g!m+-b;N{L51t`%=Q20nPrkLT5} z>YgMHth7#wiVSd@RRVsk&w&a%H9t<-QVU0b%0$JLg%4e9m`4dV_Oh_ia5qZCU(abF zz6{iSFa3NoWdsLWRgUr#P$TwQx7S+jYf$)f{?lW9!ILJaZ2{!_oA#&NdBnk>lw=?} zSQ5bC?l!8W_q)Gf#dIvx>_v;E<#658x#w!S=uo%9^^j2L8cUT*v0m*v_0FSYH|$Me zd28ms9%WG~kn75~!vIazitU9Hs_Lll?sx6>&Gmcv4QW{4dOq-gYz9ZoD_d#Mr<^tj z>bK7zKX=sK#3hSC0>Q!@B<8M9?9M)f0~k2fRJ<(U;lbVhK=TUDwBS@>qMUbaOLo}J zH^$SZD(A*unA=CZY$CKu3uS* zXCotr;Tlwvh?kLId`xOF4fjFF%eDEPO_}!~21%WbMd1XWso^K3{WL=!ZiM6nv{mjt z3DOoI08Y3nT(N>bXO5w?0*6GWZ~@7lDdutMP9W&l_a;z?$crMp$|eV*X{ez8<{K3S zw^;S<+>|yPNvSDPd*oi->2V5=2iANc4&OZU$kvM2%6RSTK{;2O^>V=5{fFesqSGp( zNsC4dUI&36-O9be3LB?qsDz@-jR*&gFO(meP+X!km@0Yc!9es_^bM}8-|{KR>=~iB zNwLH3^Hrne*qxUAcbyzjGEmFgUr69{3RRM2+&EKrDdFpB66@J?Im+u0 zP_aLKhrOQ@NRIuhu(zb6ZzgKlz$)ntCR2@kxuG+HAh}~d^DGle9&@7CyE;|1edm&k zD)EkK0YSOqFaDh=9{%8U`+bwmi6v(b!lmT4V_jTOMlUCh6eLPk@|9a z08w6S*{_I|4I#N17gGH#D+yIA){^CrV>a5LJ!?Wx;vkUNsh*?#9nFro@k|yK<>pYi>MTe!D$+HnG*NlKIK5LEl`aX14v+4#q_H ztn{!1tb4RTCcIe%%N=LBW2{jBnJUW6n32O|HIkEX#N|Dvx76OQP2XlDIto6P9zr{& zYT^agrVKIzuE_3E*J9%#qdB+6^&r9d8S}m;YdwX0Bh|U!DA-^yY6s4D!g+Fo_I`Om z9l0L06fY6^VV23p$x_=DaalVOW|L?t^9w#fIRfpnfMXP+Z$>L09AQ5RDNOhuuT){@ z#|t{}uy3YFIBsynQ>Md5F>kK=g9FU6*{Sqh^ucEXiV9;>82**=jA;)=U0#U6GpnyW7? z(3lJDQd5BL2W7&ejnk)kjk-VosjeE$#u1o0_0f3KTGSBy_vM0=(7ISZ&HYe;nZG^_ zJnTS%tho>2*N+Ii8~~~j51EilNo}p1P%wH0&isq+jcbGw^wtLZ^4lL*adSS_eSO~( zGAIf6ZUNDqGKo0(WI7wKl8eYn^yu@ZXdjDNpK*rm)SXG1^CWzhdRCJ986$C#$=Wxm zMHv%!D?dnv{OmRtmRTL#(sY=xeQXg##2@54Iqq;L#6-RRQF1~MV$)S}OgGGZq-7}# zsL-3NUXv|99UH&FO6sV176p-$5;>1OuF~SaPGt*ea;-+fVF8vv^-8nx;~Tj06#@#{V<`|{=@4Hn|4R-7<8&f$82@QM4bm}oZp_?eoQT9v9csqh7^`i z!O*_k3}4wWQzvys=i};9cc$Nq{(M6THBnCutg03p_>R@r__>VCH)YU$>JCU1YK&7F zR=ymYm*3nT&c@R9{Rc_qBR6!mREuaH`NH8KKA4s>t}6dxX$=qQlN8AZcz&#l3QRQe zJ=Q#w>)2XNM19Sf2rR}A}=3EI5u&U{)HNKJ1#MlA{L}Ry|A87HRPdhguBc8L(eiG680GwLW=I42}drB`$ot z^h#LDPZvjqN9O%2!_*4yERk|5YQ0XmNUW(0QfP%}W`2AH>4@mE&KchCPIjyou;28x zX`=(3lo|SZ<5Rd16NzgvM*e?@u5JgFKgf4?w^Fz$lV4$&-4@C~?9&7tGWp4ZMeF0e zxpA|f%bi<)53g+RqDi^{sE`U&<4lrmY|xcI33&V-+F$Z6Z{l`m(b!Mk`g(bX?1-Rf z*yv%D0=MX9Xu2f4g*pKy^Cyx;aDtFO%(t=NTR4H1+}8IFefub#olL;qkFZo)|DSi^ zV<^3z55oB|Hh^Q|Y#CW4*zE)4r)o*1PU*2Dr5AX*qjME6N$U8I@gOD#aTiqn3V&d6x(x6CPS! z6O~r{ZR-A$R1Z;2MXl@$fD^Mrg$mm2d)`Z{K%5Nxa;*~ADK@=z#nY@v6Las4MDA(v z?)Y^qttnk@nrfo)xZzqH$%q-)C;#nXw@+f0^)zvo!cx)l8aalcxVBk~<*zFHUfg!}}YNAkD{b4~y26genEH0=V z`GgU{4k`u(qc!RuzbFE>hW}-B{ci|VYZLk5s+}_cHu@qfJ5cz1tEGbcd1j<1c}UMm z8grVTJV6$GJVkeps`U0n(z~56NE)hM(`x3AC({1D_yE5m7%MXfnU+?>$fm~7zDihJ zQzDaG{{(8uTyZ9X^_w!`4><=4(!G-UpIfwHB9DuUYScwBhwbS5k+*4~XPJ=R-t#zZ zwIGg?CFp<5zlErvw1mjT_mYq)A%tv{I_tQlTc3$G2R|YT+@PeX1-B7QHQJ;JZD98X zUt`Cau99FZKc63xGVyG|VfTD<+s9btq=Cxc=<=~6jdIAB_{v;TJ_zcd?=B%uDWXi* z?E-_WE-*-SSwb`xu938U86moSDS%l1do|@Kr{Hf#`QT7SpyN3d$2=O4%;iXq^Ikdi zA#uh6GO#duv{_XapTNV*a}kwY1Z@fWYCRI6O|l=A!RY0zD4YzJFEbjI31wd+n;mzX zCozJKVIx+zta8$OJE)lZDR`Qy+^krd~?`^!jcZ>K6eQr3T>%$eRWT_W8i?k{$?y7P)L zcTwxTL48B6IIQrP^oT*MpJ-Z%ttE5n{>*fY3OYc(As($hBnk}sva}i!MI+q-Bf%va zdfgQDs^Z~1OoE+|H-Z*VWesB|`t@}p;tBt@%ixMnl06H!J<^8;9&`PR$KQO8*He}4 zeWs#(MwuRqY4HXy6kwwuxECK$dB)n{ttAr{%Npej1$xDyCO)EtE~fr?!>a_{*_d$W z?KD*V244_%fkKI~&z5i2bQSLu;<0*vK+`5FhD#H)22}0-3o#4LA&;oj$S6MDO>7(_X}H;jv~{J|1WLZ zvQLgvPEdO(D&#mOaU6HX#uA&zFl#E4^$ne$*lhEj+y!}Hs_W`8X0fFyyL8G2&5V{S zb+z#K39B70QUWdfZG@ysRbkv}WKI6DJm_^LOH!mBnP321dSLBCR15*VNLOI>8$YZy zVeeI`=LEhM`luGTIo77zi78|iQLWj&v1aKzU*N@P{PyMtyz)e83`sD*L(DY5^zGC2 z6x1SRxDCB>OM~y)`xJd>8}w!@if}Z}!@cWo+m;v4QJIDN?rs|LL10wFDI z6rCZ1^(g|U+!``8MS6t1i6+8a9@AuBt*7sDC6qjEkoo?pa?{n^YzkIREFP$G{Z1xn UE!R1gM@coo&)rxfu*5EC6m+fi`~Uy| diff --git a/Lunar/SwiftUIViews/AdvancedSettingsView.swift b/Lunar/SwiftUIViews/AdvancedSettingsView.swift new file mode 100644 index 00000000..bfbb72b8 --- /dev/null +++ b/Lunar/SwiftUIViews/AdvancedSettingsView.swift @@ -0,0 +1,308 @@ +import Defaults +import SwiftUI + +struct AdvancedSettingsView: View { + @ObservedObject var dc: DisplayController = DC + + @Default(.workaroundBuiltinDisplay) var workaroundBuiltinDisplay + @Default(.ddcSleepLonger) var ddcSleepLonger + @Default(.clamshellModeDetection) var clamshellModeDetection + @Default(.enableOrientationHotkeys) var enableOrientationHotkeys + @Default(.detectResponsiveness) var detectResponsiveness + @Default(.disableControllerVideo) var disableControllerVideo + @Default(.allowBlackOutOnSingleScreen) var allowBlackOutOnSingleScreen + @Default(.reapplyValuesAfterWake) var reapplyValuesAfterWake + @Default(.clockMode) var clockMode + @Default(.nonManualMode) var nonManualMode + @Default(.oldBlackOutMirroring) var oldBlackOutMirroring + @Default(.newBlackOutDisconnect) var newBlackOutDisconnect + + @Default(.refreshValues) var refreshValues + @Default(.gammaDisabledCompletely) var gammaDisabledCompletely + @Default(.waitAfterWakeSeconds) var waitAfterWakeSeconds + @Default(.delayDDCAfterWake) var delayDDCAfterWake + + @Default(.autoRestartOnFailedDDC) var autoRestartOnFailedDDC + @Default(.autoRestartOnFailedDDCSooner) var autoRestartOnFailedDDCSooner + @Default(.sleepInClamshellMode) var sleepInClamshellMode + @Default(.disableCliffDetection) var disableCliffDetection + @Default(.keyboardBacklightOffBlackout) var keyboardBacklightOffBlackout + + @State var sensorCheckerEnabled = !Defaults[.sensorHostname].isEmpty + + var body: some View { + ZStack { + Color.clear.frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading) { + Group { + #if arch(arm64) + if #available(macOS 13, *) { + SettingsToggle( + text: "Disable the Disconnect API in BlackOut", setting: !$newBlackOutDisconnect, + help: """ + BlackOut can use a hidden macOS API to disconnect the display entirely, + freeing up GPU resources and allowing for an easy reconnection when needed. + + If you're having trouble with how this works, you can switch to the old + method of mirroring the display to disable it. + + Note: Press ⌘ Command more than 8 times in a row to force connect all displays. + + In case the built-in MacBook display doesn't reconnect itself when it should, + close the laptop lid and reopen it to bring the display back. + + For external displays, disconnect and reconnect the cable to fix any issue. + """ + ) + } + #endif + + SettingsToggle( + text: "Allow BlackOut on single screen", setting: $allowBlackOutOnSingleScreen, + help: "Allows turning off a screen even if it's the only visible screen left" + ) + if Sysctl.isMacBook { + SettingsToggle( + text: "Turn off keyboard backlight in BlackOut", setting: $keyboardBacklightOffBlackout + ) + SettingsToggle( + text: "Force Sleep when the lid is closed", setting: $sleepInClamshellMode, + help: """ + When the MacBook is connected to a monitor that's also charging the Mac, + closing the lid will start Clamshell Mode. + + That system feature keeps the system awake to allow you to use the external + monitor with the lid closed. + + If you don't use that feature, enabling this option will disable Clamshell + Mode automatically when the lid is closed. + """ + ) + } + + #if !arch(arm64) + if #available(macOS 13, *) { + } else { + SettingsToggle( + text: "Switch to the old BlackOut mirroring system", setting: $oldBlackOutMirroring, + help: """ + Some setups will have trouble enabling mirroring with the new macOS 11+ API. + + You can try enabling this option if BlackOut is not working properly. + + Note: the old mirroring system can't handle complex mirror sets with dummies and virtual/wireless displays. + The best covered cases are "BlackOut built-in display" and "BlackOut only external displays". + """ + ) + } + #endif + Divider() + + SettingsToggle( + text: "Use workaround for built-in display", setting: $workaroundBuiltinDisplay, + help: """ + Forward brightness key events to the system instead of + changing built-in display brightness from Lunar. + + This setting might be needed to persist brightness + changes better on some specific older devices. + + Disables the following functions for the built-in display: + • Hotkey Step + • Auto XDR + • Sub-zero Dimming + """ + ) + if Sysctl.isMacBook { + SettingsToggle( + text: "Toggle Manual/Sync when the lid is closed/opened", + setting: $clamshellModeDetection + ) + } + SettingsToggle( + text: "Re-apply last brightness on screen wake", setting: $reapplyValuesAfterWake, + help: """ + On each screen wake/reconnection, Lunar will try to + re-apply previous brightness and contrast 3 times. + + Disable this if system appears slow on screen wake. + """ + ) + } + Divider() + Group { + SettingsToggle( + text: "Enable rotation hotkeys", + setting: $enableOrientationHotkeys, + help: """ + Pressing the following keys will change the + orientation for the display with the cursor on it: + + Ctrl+0: 0° + Ctrl+9: 90° + Ctrl+8: 180° + Ctrl+7: 270° + """ + ) + if dc.activeDisplayList.contains(where: \.hasDDC) { + SettingsToggle( + text: "Wait longer between DDC requests", setting: $ddcSleepLonger, + help: """ + Some monitors have a slower response time on DDC requests. + + This option might help reduce flicker in those cases. + """ + ) + SettingsToggle( + text: "Check for DDC responsiveness periodically", setting: $detectResponsiveness, + help: """ + Detects when DDC becomes unresponsive and presents + the choice to switch to Software Dimming. + """ + ) + } + if dc.activeDisplayList.contains(where: { $0.control is NetworkControl }) { + SettingsToggle( + text: "Disable Network Controller video ", setting: $disableControllerVideo, + help: """ + When using "Network Control" with a Raspberry Pi, it might be + helpful to disable the Pi desktop if you don't need it. + """ + ) + } + SettingsToggle( + text: "Check for network light sensors periodically", setting: $sensorCheckerEnabled, + help: """ + To enable "Sensor Mode", Lunar periodically checks if a wireless light + sensor is available using local DNS requests. You can disable this if + you never intend to use a wireless ambient light sensor. + """ + ) + Divider() + Group { + Text("EXPERIMENTAL!") + .foregroundColor(Colors.red) + .bold() + Text("Don't use unless really needed or asked by the developer") + .foregroundColor(Colors.red) + .font(.caption) + SettingsToggle( + text: "Disable usage of Gamma API completely", setting: $gammaDisabledCompletely, + help: """ + Experimental: for people running into macOS bugs like the color profile + being constantly reset, display turning to monochrome or HDR being disabled, + this could be a safe measure to ensure Lunar never touches the Gamma API of macOS. + + This will disable or cripple the following features: + + • XDR Brightness + • Facelight + • Blackout + • Software Dimming + • Sub-zero Dimming + """ + ) + if dc.activeDisplayList.contains(where: \.hasDDC) { + SettingsToggle( + text: "Auto restart Lunar when DDC fails", setting: $autoRestartOnFailedDDC, + help: """ + Experimental: for people running into macOS bugs where a monitor can no longer + be controlled. You might see a lock icon when brightness keys are pressed. + + To avoid jarring brightness changes, this will not restart the app + if any of the following features are in active use: + + • XDR Brightness + • Facelight + • Blackout + • Sub-zero Dimming + """ + ) + SettingsToggle( + text: "Avoid safety checks", setting: $autoRestartOnFailedDDCSooner, + help: """ + Don't wait for the detection of DDC fail to happen more than once, and restart + the app even if it could cause a jarring brightness change. + """ + ).padding(.leading) + + SettingsToggle( + text: "Delay DDC commands after wake", setting: $delayDDCAfterWake, + help: """ + Experimental: for people running into monitor bugs like the video signal being + lost or screen not waking up after system sleep, this could be a safe measure + to ensure Lunar doesn't send any DDC command until the monitor connection + is fully established. + + This will disable or cripple the following features: + + • Smooth transitions + • DDC responsiveness checker + • Re-applying color gain on wake + • Re-applying brightness/contrast on wake + """ + ) + HStack { + let secondsBinding = Binding( + get: { waitAfterWakeSeconds.f / 100 }, + set: { waitAfterWakeSeconds = ($0 * 100).i } + ) + BigSurSlider( + percentage: secondsBinding, + image: "clock.circle", + color: Colors.lightGray, + backgroundColor: Colors.grayMauve.opacity(0.1), + knobColor: Colors.lightGray, + showValue: .constant(true), + disabled: !$delayDDCAfterWake + ) + .padding(.leading) + + SwiftUI.Button("Reset") { waitAfterWakeSeconds = 30 } + .buttonStyle(FlatButton( + color: Colors.lightGray, + textColor: Colors.darkGray, + radius: 10, + verticalPadding: 3 + )) + .font(.system(size: 12, weight: .semibold, design: .monospaced)) + .disabled(!delayDDCAfterWake) + } + if delayDDCAfterWake { + Text("Lunar will wait \(waitAfterWakeSeconds) seconds before sending\nthe first DDC command after screen wake") + .font(.system(size: 10, weight: .semibold, design: .monospaced)) + .foregroundColor(.black.opacity(0.4)) + .frame(height: 28, alignment: .topLeading) + .fixedSize() + .lineLimit(2) + .padding(.leading, 20) + .padding(.top, -5) + } + } + if nonManualMode { + SettingsToggle( + text: "Disable cliff detection for auto-learning", setting: $disableCliffDetection, + help: """ + The Cliff Detection algorithm is a safe guard that avoids having Lunar learn + a very low brightness and a very high brightness very close together. + (e.g. 5% at 10lux, 100% at 20lux) + + That would cause constant transitioning between the two learnt brightnesses + and very high CPU usage when the ambient light changed continuously. + (which happens often when clouds are passing, or when in a moving vehicle) + + This will disable that safe guard. + """ + ) + } + } + } + Spacer() + Color.clear + }.frame(maxWidth: .infinity, alignment: .leading) + } + .onChange(of: sensorCheckerEnabled) { enabled in + Defaults[.sensorHostname] = enabled ? "lunarsensor.local" : "" + } + } +} diff --git a/Lunar/SwiftUIViews/AllDisplaysView.swift b/Lunar/SwiftUIViews/AllDisplaysView.swift new file mode 100644 index 00000000..9c502277 --- /dev/null +++ b/Lunar/SwiftUIViews/AllDisplaysView.swift @@ -0,0 +1,58 @@ +import Defaults +import SwiftUI + +struct AllDisplaysView: View { + @ObservedObject var display: Display = ALL_DISPLAYS + @Environment(\.colorScheme) var colorScheme + @Environment(\.colors) var colors + @Default(.showSliderValues) var showSliderValues + @Default(.mergeBrightnessContrast) var mergeBrightnessContrast + + @ViewBuilder var softwareSliders: some View { + if display.subzero { + BigSurSlider( + percentage: $display.softwareBrightness, + image: "moon.circle.fill", + color: Colors.subzero.opacity(0.7), + backgroundColor: Colors.subzero.opacity(colorScheme == .dark ? 0.1 : 0.2), + knobColor: Colors.subzero, + showValue: $showSliderValues + ) + } + } + + var body: some View { + VStack(spacing: 4) { + Text(display.name) + .font(.system(size: 22, weight: .black)) + .padding(.bottom, 6) + + if mergeBrightnessContrast { + BigSurSlider( + percentage: $display.preciseBrightnessContrast.f, + image: "sun.max.fill", + colorBinding: .constant(colors.accent), + backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), + showValue: $showSliderValues + ) + softwareSliders + } else { + BigSurSlider( + percentage: $display.preciseBrightness.f, + image: "sun.max.fill", + colorBinding: .constant(colors.accent), + backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), + showValue: $showSliderValues + ) + softwareSliders + BigSurSlider( + percentage: $display.preciseContrast.f, + image: "circle.righthalf.fill", + colorBinding: .constant(colors.accent), + backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), + showValue: $showSliderValues + ) + } + } + } +} diff --git a/Lunar/SwiftUIViews/BlackoutPopoverHeaderView.swift b/Lunar/SwiftUIViews/BlackoutPopoverHeaderView.swift new file mode 100644 index 00000000..4afdea4f --- /dev/null +++ b/Lunar/SwiftUIViews/BlackoutPopoverHeaderView.swift @@ -0,0 +1,25 @@ +import Defaults +import SwiftUI + +struct BlackoutPopoverHeaderView: View { + @Default(.neverShowBlackoutPopover) var neverShowBlackoutPopover + + var body: some View { + HStack { + Text("BlackOut") + .font(.system(size: 24, weight: .black)) + .foregroundColor(.white) + Spacer() + if lunarProActive || lunarProOnTrial { + Text("Click anywhere to hide") + .font(.system(size: 12, weight: .semibold, design: .monospaced)) + .foregroundColor(.white.opacity(0.8)) + } else { + SwiftUI.Button("Needs Lunar Pro") { + showCheckout() + appDelegate?.windowController?.window?.makeKeyAndOrderFront(nil) + }.buttonStyle(FlatButton(color: Colors.red, textColor: .white)) + } + } + } +} diff --git a/Lunar/SwiftUIViews/BlackoutPopoverRowView.swift b/Lunar/SwiftUIViews/BlackoutPopoverRowView.swift new file mode 100644 index 00000000..1e4606d1 --- /dev/null +++ b/Lunar/SwiftUIViews/BlackoutPopoverRowView.swift @@ -0,0 +1,48 @@ +import SwiftUI + +struct BlackoutPopoverRowView: View { + @State var modifiers: [String] = [] + @State var action: String + @State var hotkeyText = "" + @State var actionInfo = "" + + var body: some View { + HStack { + Text((modifiers + ["Click"]).joined(separator: " + ")).font(.system(size: 13, weight: .medium, design: .monospaced)) + .foregroundColor(.white.opacity(0.9)) + .padding(10) + .background( + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(Color.white.opacity(0.1)) + ).frame(width: 200, alignment: .leading) + HStack { + if !hotkeyText.isEmpty { + Text("or").font(.system(size: 12, weight: .semibold)).foregroundColor(.white.opacity(0.5)) + Text(hotkeyText).font(.system(size: 13, weight: .medium, design: .monospaced)) + .foregroundColor(.white.opacity(0.9)) + .kerning(3) + .padding(10) + .background( + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(Color.white.opacity(0.1)) + ) + } + }.frame(width: 100, alignment: .leading) + HStack { + Text(action) + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.white.opacity(0.9)) + if !actionInfo.isEmpty { + Text(actionInfo) + .font(.system(size: 12, weight: .medium, design: .monospaced)) + .foregroundColor(.white.opacity(0.7)) + } + } + .padding(10) + .background( + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(Color.white.opacity(0.1)) + ) + } + } +} diff --git a/Lunar/SwiftUIViews/BlackoutPopoverView.swift b/Lunar/SwiftUIViews/BlackoutPopoverView.swift new file mode 100644 index 00000000..00636d22 --- /dev/null +++ b/Lunar/SwiftUIViews/BlackoutPopoverView.swift @@ -0,0 +1,93 @@ +import Defaults +import SwiftUI + +struct BlackoutPopoverView: View { + @State var hasDDC: Bool + @Default(.hotkeys) var hotkeys + @Default(.newBlackOutDisconnect) var newBlackOutDisconnect + + var body: some View { + ZStack { + Color.black.brightness(0.02).scaleEffect(1.5) + VStack(alignment: .leading, spacing: 10) { + BlackoutPopoverHeaderView().padding(.bottom) + if DC.activeDisplayCount == 1 { + BlackoutPopoverRowView(action: "Make screen black", hotkeyText: hotkeyText(id: .blackOut), actionInfo: "(without disabling it)") + } else { + if newBlackOutDisconnect, #available(macOS 13, *) { + BlackoutPopoverRowView(action: "Disconnect screen", hotkeyText: hotkeyText(id: .blackOut), actionInfo: "(free up GPU)") + } else { + BlackoutPopoverRowView(action: "Soft power off", hotkeyText: hotkeyText(id: .blackOut), actionInfo: "(disables screen by mirroring)") + } + BlackoutPopoverRowView( + modifiers: ["Shift"], + action: "Make screen black", + hotkeyText: hotkeyText(id: .blackOutNoMirroring), + actionInfo: "(without disabling it)" + ) + BlackoutPopoverRowView( + modifiers: ["Option", "Shift"], + action: "Make other screens black", + hotkeyText: hotkeyText(id: .blackOutOthers), + actionInfo: "(keep this one visible)" + ) + + #if arch(arm64) + if #available(macOS 13, *) { + Divider().background(Color.white.opacity(0.2)) + if newBlackOutDisconnect { + BlackoutPopoverRowView(modifiers: ["Command"], action: "Soft power off", hotkeyText: "", actionInfo: "(disables screen by mirroring)") + .colorMultiply(Color.orange) + } else { + BlackoutPopoverRowView( + modifiers: ["Command"], + action: "Disconnect screen", + hotkeyText: "", + actionInfo: "(free up GPU)" + ) + .colorMultiply(Color.orange) + } + } + #endif + } + + if hasDDC { + BlackoutPopoverRowView( + modifiers: ["Option"], + action: "Hardware power off", + hotkeyText: hotkeyText(id: .blackOutPowerOff), + actionInfo: "(uses DDC)" + ) + .colorMultiply(Colors.red) + } + Divider().background(Color.white.opacity(0.2)) + BlackoutPopoverRowView(modifiers: ["Control"], action: "Show this help menu") + .colorMultiply(Colors.peach) + + HStack(spacing: 7) { + Text("Press") + Text("⌘ Command") + .padding(.vertical, 3) + .padding(.horizontal, 5) + .background(RoundedRectangle(cornerRadius: 3, style: .continuous).fill(Color.white)) + .foregroundColor(.black) + Text("more than 8 times in a row to force turn on all displays and reset BlackOut") + } + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.white.opacity(0.6)) + .padding(.vertical, 6) + .padding(.horizontal, 12) + .background(RoundedRectangle(cornerRadius: 8, style: .continuous).fill(Color.white.opacity(0.1))) + .colorMultiply(Colors.peach) + .padding(.top) + } + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + }.preferredColorScheme(.light) + } + + func hotkeyText(id: HotkeyIdentifier) -> String { + guard let h = hotkeys.first(where: { $0.identifier == id.rawValue }), h.isEnabled else { return "" } + return h.keyCombo.keyEquivalentModifierMaskString + h.keyCombo.keyEquivalent + } +} diff --git a/Lunar/SwiftUIViews/DisconnectedDisplayView.swift b/Lunar/SwiftUIViews/DisconnectedDisplayView.swift new file mode 100644 index 00000000..89aff01a --- /dev/null +++ b/Lunar/SwiftUIViews/DisconnectedDisplayView.swift @@ -0,0 +1,103 @@ +#if arch(arm64) + import Defaults + import SwiftUI + + @available(macOS 13, *) + struct ReconnectButtonView: View { + @State var display: CGDirectDisplayID + @State var hovering = false + @State var off = true + + var body: some View { + HStack(spacing: 2) { + SwiftUI.Button(action: { + off = false + DC.autoBlackoutPause = true + DC.en(display) + }) { + Image(systemName: "power").font(.system(size: 10, weight: .heavy)) + } + .buttonStyle(FlatButton( + color: off ? Color.gray : Colors.red, + circle: true, + horizontalPadding: 3, + verticalPadding: 3 + )) + .onHover { h in withAnimation { hovering = h } } + Text("Connect") + .font(.system(size: 10, weight: .semibold)) + .opacity(hovering ? 1 : 0) + } + .frame(width: 100, alignment: .leading) + } + } + + @available(macOS 13, *) + struct DisconnectedDisplayView: View { + @Environment(\.colors) var colors + + @State var id: CGDirectDisplayID + @State var name: String + @State var possibly = false + + @ObservedObject var display: Display + @Default(.autoBlackoutBuiltin) var autoBlackoutBuiltin + + var body: some View { + VStack(spacing: 1) { + HStack(alignment: .top, spacing: -10) { + Text(name) + .font(.system(size: 22, weight: .black)) + .padding(6) + .background(RoundedRectangle(cornerRadius: 6, style: .continuous).fill(colors.bg.primary.opacity(0.5))) + + ReconnectButtonView(display: id) + .offset(y: -8) + }.offset(x: 45) + + Text(possibly ? "Possibly disconnected" : "Disconnected") + .font(.system(size: 10, weight: .semibold, design: .rounded)) + + if display.id == id, !display.isSidecar, !display.isAirplay { + let binding = !display.isMacBook ? $display.keepDisconnected : Binding( + get: { autoBlackoutBuiltin }, + set: { + autoBlackoutBuiltin = $0 + display.keepDisconnected = $0 + } + ) + VStack { + SettingsToggle( + text: "Auto Disconnect", + setting: binding, + color: nil, + help: !display.isMacBook + ? """ + The display might come back on by itself after standby/wake or when + reconnecting the monitor cable. + + This option will automatically disconnect the display whenever that + happens, until you reconnect the display manually using the power button. + + Note: Press ⌘ Command more than 8 times in a row to force connect all displays. + """ + : """ + Turns off the built-in screen automatically when a monitor is connected and turns + it back on when the last monitor is disconnected. + + Keeps the screen disconnected between standby/wake or lid open/close states. + + Note: Press ⌘ Command more than 8 times in a row to force connect all displays. + """ + ) + .font(.system(size: 12, weight: .medium, design: .rounded)) + } + .padding(8) + .background(RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color.primary.opacity(0.05))) + .padding(.vertical, 3) + } + } + } + } + +#endif diff --git a/Lunar/SwiftUIViews/DisplayRowView.swift b/Lunar/SwiftUIViews/DisplayRowView.swift new file mode 100644 index 00000000..cbbfb40d --- /dev/null +++ b/Lunar/SwiftUIViews/DisplayRowView.swift @@ -0,0 +1,404 @@ +import Defaults +import SwiftUI + +struct DisplayRowView: View { + static var hoveringVolumeSliderTask: DispatchWorkItem? { + didSet { + oldValue?.cancel() + } + } + + @ObservedObject var display: Display + @Environment(\.colorScheme) var colorScheme + @Environment(\.colors) var colors + @Default(.showSliderValues) var showSliderValues + #if arch(arm64) + @Default(.syncNits) var syncNits + #endif + + @Default(.showInputInQuickActions) var showInputInQuickActions + @Default(.showPowerInQuickActions) var showPowerInQuickActions + @Default(.showXDRSelector) var showXDRSelector + @Default(.showRawValues) var showRawValues + @Default(.xdrTipShown) var xdrTipShown + @Default(.autoXdr) var autoXdr + @Default(.syncMode) var syncMode + + @State var showNeedsLunarPro = false + @State var showXDRTip = false + @State var showSubzero = false + @State var showXDR = false + + @State var hoveringVolumeSlider = false + + @Default(.autoBlackoutBuiltin) var autoBlackoutBuiltin + + @State var adaptiveStateText = "" + @State var adaptivePausedText = "Adaptive brightness paused" + + @State var editingMaxNits = false + @State var editingMinNits = false + + @State var hovering = false + + var softwareSliders: some View { + Group { + if display.enhanced { + BigSurSlider( + percentage: $display.xdrBrightness, + image: "sun.max.circle.fill", + color: Colors.xdr.opacity(0.7), + backgroundColor: Colors.xdr.opacity(colorScheme == .dark ? 0.1 : 0.2), + knobColor: Colors.xdr, + showValue: $showSliderValues, + beforeSettingPercentage: { _ in display.forceHideSoftwareOSD = true } + ) + } + if display.subzero { + BigSurSlider( + percentage: $display.softwareBrightness, + image: "moon.circle.fill", + color: Colors.subzero.opacity(0.7), + backgroundColor: Colors.subzero.opacity(colorScheme == .dark ? 0.1 : 0.2), + knobColor: Colors.subzero, + showValue: $showSliderValues, + beforeSettingPercentage: { _ in display.forceHideSoftwareOSD = true } + ) { _ in + guard display.adaptiveSubzero else { return } + + let lastDataPoint = datapointLock.around { DC.adaptiveMode.brightnessDataPoint.last } + display.insertBrightnessUserDataPoint(lastDataPoint, display.brightness.doubleValue, modeKey: DC.adaptiveModeKey) + } + } + } + } + + var sdrXdrSelector: some View { + HStack(spacing: 2) { + SwiftUI.Button("SDR") { + guard display.enhanced else { return } + withAnimation(.fastSpring) { display.enhanced = false } + } + .buttonStyle(PickerButton( + onColor: Color.black, offColor: .oneway { colors.invertedGray }, enumValue: $display.enhanced, onValue: false + )) + .font(.system(size: 11, weight: display.enhanced ? .semibold : .bold, design: .monospaced)) + + SwiftUI.Button("XDR") { + guard lunarProActive || lunarProOnTrial else { + showNeedsLunarPro = true + return + } + guard !display.enhanced else { return } + withAnimation(.fastSpring) { display.enhanced = true } + if !xdrTipShown, autoXdr { + xdrTipShown = true + mainAsyncAfter(ms: 2000) { + showXDRTip = true + } + } + } + .buttonStyle(PickerButton( + onColor: Color.black, offColor: .oneway { colors.invertedGray }, enumValue: $display.enhanced, onValue: true + )) + .font(.system(size: 11, weight: display.enhanced ? .bold : .semibold, design: .monospaced)) + .popover(isPresented: $showNeedsLunarPro) { NeedsLunarProView() } + .popover(isPresented: $showXDRTip) { XDRTipView() } + } + .padding(2) + .background(RoundedRectangle(cornerRadius: 8, style: .continuous).fill(colors.invertedGray)) + .padding(.bottom, 4) + } + + var inputSelector: some View { + Dropdown( + selection: $display.inputSource, + width: 150, + height: 20, + noValueText: "Video Input", + noValueImage: "input", + content: display.vendor == .lg ? .constant(VideoInputSource.mostUsed + [VideoInputSource.separator] + VideoInputSource.lgSpecific) : .constant(VideoInputSource.mostUsed) + ) + .frame(width: 150, height: 20, alignment: .center) + .padding(.vertical, 2) + .padding(.horizontal, 4) + .background( + RoundedRectangle( + cornerRadius: 8, + style: .continuous + ).fill(Color.primary.opacity(0.15)) + ) + .colorMultiply(colors.accent.blended(withFraction: 0.7, of: .white)) + } + + var rotationSelector: some View { + HStack { + rotationPicker(0).help("No rotation") + rotationPicker(90).help("90 degree rotation (vertical)") + rotationPicker(180).help("180 degree rotation (upside down)") + rotationPicker(270).help("270 degree rotation (vertical)") + } + } + + var disabledReason: String? { + if display.noControls { + return "No controls available" + } else if display.useOverlay { + if display.isInHardwareMirrorSet { + return "Overlay dimming disabled while mirroring" + } else if display.isIndependentDummy { + return "Overlay dimming disabled for dummy" + } + } + + return nil + } + + @ViewBuilder var appPresetAdaptivePaused: some View { + if (display.hasDDC && showInputInQuickActions) + || display.showOrientation + || display.appPreset != nil + || display.adaptivePaused + || showRawValues && (display.lastRawBrightness != nil || display.lastRawContrast != nil || display.lastRawVolume != nil) + || SWIFTUI_PREVIEW + { + VStack { + if (display.hasDDC && showInputInQuickActions) || SWIFTUI_PREVIEW { inputSelector } + if display.showOrientation || SWIFTUI_PREVIEW { rotationSelector } + if let app = display.appPreset { + SwiftUI.Button("App Preset: \(app.name)") { + app.runningApps?.first?.activate() + } + .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .secondary.opacity(0.8))) + .font(.system(size: 9, weight: .semibold)) + } + if display.adaptivePaused || SWIFTUI_PREVIEW { + SwiftUI.Button(adaptivePausedText) { display.adaptivePaused.toggle() } + .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .secondary.opacity(0.8))) + .font(.system(size: 9, weight: .semibold)) + .onHover { hovering in + adaptivePausedText = hovering ? "Resume adaptive brightness" : "Adaptive brightness paused" + } + } + + if showRawValues { + RawValuesView(display: display).frame(width: 220).padding(.vertical, 3) + } + } + .padding(8) + .background( + RoundedRectangle( + cornerRadius: 10, + style: .continuous + ).fill(Color.primary.opacity(0.05)) + ) + .padding(.vertical, 3) + } + } + + @ViewBuilder var volumeSlider: some View { + if display.hasDDC, display.showVolumeSlider, display.ddcEnabled || display.networkEnabled { + ZStack { + BigSurSlider( + percentage: $display.preciseVolume.f, + imageBinding: .oneway { display.audioMuted ? "speaker.slash.fill" : "speaker.2.fill" }, + colorBinding: .constant(colors.accent), + backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), + showValue: $showSliderValues, + disabled: $display.audioMuted, + enableText: "Unmute" + ) + if hoveringVolumeSlider, !display.audioMuted { + SwiftUI.Button("Mute") { + display.audioMuted = true + } + .buttonStyle(FlatButton( + color: Colors.red.opacity(0.7), + textColor: .white, + horizontalPadding: 6, + verticalPadding: 2 + )) + .font(.system(size: 10, weight: .medium, design: .rounded)) + .transition(.scale.animation(.fastSpring)) + .frame(maxWidth: .infinity, alignment: .center) + .offset(x: -120, y: 0) + } + }.onHover { hovering in + Self.hoveringVolumeSliderTask = mainAsyncAfter(ms: 150) { + hoveringVolumeSlider = hovering + } + } + } + } + + @ViewBuilder var sliders: some View { + if display.noDDCOrMergedBrightnessContrast { + let mergedLockBinding = Binding( + get: { display.lockedBrightness && display.lockedContrast }, + set: { display.lockedBrightness = $0 } + ) + HStack { + #if arch(arm64) + NitsTextField(nits: $display.minNits, placeholder: "min", display: display) + .opacity(hovering ? 1 : 0) + #endif + BigSurSlider( + percentage: $display.preciseBrightnessContrast.f, + image: "sun.max.fill", + colorBinding: .constant(colors.accent), + backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), + showValue: $showSliderValues, + disabled: mergedLockBinding, + enableText: "Unlock", + insideText: nitsText + ) + #if arch(arm64) + let maxNitsBinding = Binding( + get: { display.userMaxNits ?? display.maxNits }, + set: { + display.userMaxNits = nil + display.maxNits = $0 + } + ) + NitsTextField(nits: maxNitsBinding, placeholder: "max", display: display) + .opacity(hovering ? 1 : 0) + #endif + } + softwareSliders + } else { + BigSurSlider( + percentage: $display.preciseBrightness.f, + image: "sun.max.fill", + colorBinding: .constant(colors.accent), + backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), + showValue: $showSliderValues, + disabled: $display.lockedBrightness, + enableText: "Unlock", + insideText: nitsText + ) + softwareSliders + let contrastBinding: Binding = Binding( + get: { display.preciseContrast.f }, + set: { val in + display.withoutLockedContrast { + display.preciseContrast = val.d + } + } + ) + BigSurSlider( + percentage: contrastBinding, + image: "circle.righthalf.fill", + colorBinding: .constant(colors.accent), + backgroundColorBinding: .constant(colors.accent.opacity(colorScheme == .dark ? 0.1 : 0.4)), + showValue: $showSliderValues + ) + } + } + + var body: some View { + VStack(spacing: 4) { + let xdrSelectorShown = display.supportsEnhance && showXDRSelector && !display.blackOutEnabled + if showPowerInQuickActions, display.getPowerOffEnabled() { + HStack(alignment: .top, spacing: -10) { + Text(display.name) + .font(.system(size: 22, weight: .black)) + .padding(6) + .background(RoundedRectangle(cornerRadius: 6, style: .continuous).fill(colors.bg.primary.opacity(0.5))) + .padding(.bottom, xdrSelectorShown ? 0 : 6) + + PowerOffButtonView(display: display) + .offset(y: -8) + }.offset(x: 45) + + } else { + Text(display.name ?! "Unknown") + .font(.system(size: 22, weight: .black)) + .padding(.bottom, xdrSelectorShown ? 0 : 6) + } + + if let disabledReason { + Text(disabledReason).font(.system(size: 10, weight: .semibold, design: .rounded)) + } else if display.blackOutEnabled { + Text("Blacked Out").font(.system(size: 10, weight: .semibold, design: .rounded)) + + if display.isMacBook { + let binding = Binding( + get: { autoBlackoutBuiltin }, + set: { + autoBlackoutBuiltin = $0 + display.keepDisconnected = $0 + } + ) + + VStack { + SettingsToggle( + text: "Auto BlackOut", + setting: binding, + color: nil, + help: """ + Turns off the built-in screen automatically when a monitor is connected and turns + it back on when the last monitor is disconnected. + + Keeps the screen disconnected between standby/wake or lid open/close states. + + Note: Press ⌘ Command more than 8 times in a row to force connect all displays. + """ + ) + .font(.system(size: 12, weight: .medium, design: .rounded)) + } + .padding(8) + .background(RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color.primary.opacity(0.05))) + .padding(.vertical, 3) + } + } else { + if xdrSelectorShown { sdrXdrSelector } + + sliders + adaptiveState + volumeSlider + appPresetAdaptivePaused + } + }.onHover { h in withAnimation { hovering = h } } + } + + @ViewBuilder var adaptiveState: some View { + let systemAdaptive = display.systemAdaptiveBrightness + let key = DC.adaptiveModeKey + if !display.adaptive, !display.xdr, !display.blackout, !display.facelight, !display.subzero, (key == .sync && !display.isActiveSyncSource) || key == .location || key == .sensor || key == .clock { + SwiftUI.Button(adaptiveStateText.isEmpty ? (systemAdaptive ? "Adapted by the system" : "Adaptive brightness disabled") : adaptiveStateText) { + display.adaptive = true + } + .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .secondary.opacity(0.8))) + .font(.system(size: 9, weight: .semibold)) + .onHover { hovering in + adaptiveStateText = hovering ? "Adapt brightness with Lunar" : "" + } + } + } + + func nitsText() -> AnyView { + #if arch(arm64) + guard SyncMode.isUsingNits(), display.isNative, let nits = display.nits else { + return EmptyView().any + } + return VStack(spacing: -3) { + Text("\(nits.intround)") + Text("nits") + } + .font(.system(size: 8, weight: .bold, design: .rounded).leading(.tight)) + .foregroundColor((display.preciseBrightnessContrast < 0.25 && colorScheme == .dark) ? Colors.lightGray.opacity(0.6) : Colors.grayMauve.opacity(0.7)) + .any + #else + EmptyView().any + #endif + } + + func rotationPicker(_ degrees: Int) -> some View { + SwiftUI.Button("\(degrees)°") { + display.rotation = degrees + } + .buttonStyle(PickerButton(enumValue: $display.rotation, onValue: degrees)) + .font(.system(size: 12, weight: display.rotation == degrees ? .bold : .semibold, design: .monospaced)) + .help("No rotation") + } +} diff --git a/Lunar/SwiftUIViews/HDRSettingsView.swift b/Lunar/SwiftUIViews/HDRSettingsView.swift new file mode 100644 index 00000000..ca1730e4 --- /dev/null +++ b/Lunar/SwiftUIViews/HDRSettingsView.swift @@ -0,0 +1,222 @@ +import Defaults +import SwiftUI + +struct HDRSettingsView: View { + @ObservedObject var dc: DisplayController = DC + + @Default(.hdrWorkaround) var hdrWorkaround + @Default(.xdrContrast) var xdrContrast + @Default(.xdrContrastFactor) var xdrContrastFactor + @Default(.subzeroContrast) var subzeroContrast + @Default(.subzeroContrastFactor) var subzeroContrastFactor + @Default(.allowHDREnhanceBrightness) var allowHDREnhanceBrightness + @Default(.allowHDREnhanceContrast) var allowHDREnhanceContrast + + @Default(.autoXdr) var autoXdr + @Default(.autoSubzero) var autoSubzero + @Default(.disableNightShiftXDR) var disableNightShiftXDR + @Default(.enableDarkModeXDR) var enableDarkModeXDR + @Default(.autoXdrSensor) var autoXdrSensor + @Default(.autoXdrSensorShowOSD) var autoXdrSensorShowOSD + @Default(.autoXdrSensorLuxThreshold) var autoXdrSensorLuxThreshold + + var body: some View { + ZStack { + Color.clear.frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading) { + Group { + SettingsToggle( + text: "Run in HDR compatibility mode", setting: $hdrWorkaround, + help: """ + Because of a macOS bug, any app that uses the Gamma API will break HDR. + + This workaround tries to keep HDR working by periodically resetting Gamma changes. + + This will stop working in the following cases: + + • Using "Software Dimming" with the Gamma method on any display + • Having the f.lux app running + • Having the Gamma Control app running + • Using "XDR Brightness" + • Using "Sub-zero dimming" + """ + ) + SettingsToggle( + text: "Allow XDR on non-Apple HDR monitors", setting: $allowHDREnhanceBrightness.animation(.fastSpring), + help: """ + This should work for HDR monitors that have higher brightness LEDs. + Known issues: some monitors turn to grayscale/monochrome when XDR is enabled. + + In case of any issue, uncheck this and restart your computer to revert any changes. + """ + ) + + if DC.activeDisplayList.contains(where: \.supportsEnhance) { + SettingsToggle( + text: "Enhance contrast in XDR Brightness", setting: $xdrContrast, + help: """ + Improve readability in sunlight by increasing XDR contrast. + This option is especially useful when using apps with dark backgrounds. + + Note: works only when using a single display + """ + ) + HStack { + BigSurSlider( + percentage: $xdrContrastFactor, + image: "circle.lefthalf.filled", + color: Colors.lightGray, + backgroundColor: Colors.grayMauve.opacity(0.1), + knobColor: Colors.lightGray, + showValue: .constant(false), + disabled: !$xdrContrast + ) + .padding(.leading) + + SwiftUI.Button("Reset") { xdrContrastFactor = 0.3 } + .buttonStyle(FlatButton( + color: Colors.lightGray, + textColor: Colors.darkGray, + radius: 10, + verticalPadding: 3 + )) + .font(.system(size: 12, weight: .semibold, design: .monospaced)) + .disabled(!xdrContrast) + } + SettingsToggle(text: "Allow on non-Apple HDR monitors", setting: $allowHDREnhanceContrast.animation(.fastSpring)) + .padding(.leading) + .disabled(!xdrContrast) + } + if DC.activeDisplayList.contains(where: \.supportsGammaByDefault) { + SettingsToggle( + text: "Enhance contrast in Sub-zero Dimming", setting: $subzeroContrast, + help: """ + Improve readability in very low light by increasing contrast. + This option is especially useful when using apps with light backgrounds. + + Note: works only when using a single display + """ + ) + let binding = Binding( + get: { subzeroContrastFactor / 5.0 }, + set: { subzeroContrastFactor = $0 * 5.0 } + ) + HStack { + BigSurSlider( + percentage: binding, + image: "circle.lefthalf.filled", + color: Colors.lightGray, + backgroundColor: Colors.grayMauve.opacity(0.1), + knobColor: Colors.lightGray, + showValue: .constant(false), + disabled: !$subzeroContrast + ) + .padding(.leading) + + SwiftUI.Button("Reset") { subzeroContrastFactor = 1.75 } + .buttonStyle(FlatButton( + color: Colors.lightGray, + textColor: Colors.darkGray, + radius: 10, + verticalPadding: 3 + )) + .font(.system(size: 12, weight: .semibold, design: .monospaced)) + .disabled(!subzeroContrast) + } + } + } + Divider() + xdrSettings + Spacer() + Color.clear + }.frame(maxWidth: .infinity, alignment: .leading) + } + } + + var xdrSettings: some View { + Group { + SettingsToggle(text: "Disable Night Shift and f.lux when toggling XDR", setting: $disableNightShiftXDR.animation(.fastSpring)) + SettingsToggle( + text: "Enable Dark Mode when enabling XDR", setting: $enableDarkModeXDR.animation(.fastSpring), + help: """ + Use dark backgrounds with bright text for minimizing power usage and LED temperature while XDR is active. + + This works best in combination with the "Enhance contrast in XDR Brightness" setting. + """ + ) + SettingsToggle(text: "Toggle XDR Brightness when going over 100%", setting: $autoXdr.animation(.fastSpring)) + SettingsToggle( + text: "Toggle Sub-zero Dimming when going below 0%", + setting: $autoSubzero.animation(.fastSpring) + ) + + if Sysctl.isMacBook, DC.builtinDisplay?.supportsEnhance ?? false { + Divider().padding(.horizontal) + VStack(alignment: .leading, spacing: 2) { + SettingsToggle(text: "Toggle XDR Brightness based on ambient light", setting: $autoXdrSensor) + Text( + """ + XDR Brightness will be automatically enabled + when ambient light is above \(autoXdrSensorLuxThreshold.str(decimals: 0)) lux + """ + ) + .font(.system(size: 10, weight: .semibold, design: .monospaced)) + .foregroundColor(.black.opacity(0.4)) + .fixedSize() + .padding(.leading, 20) + } + HStack { + let luxBinding = Binding( + get: { powf(max(autoXdrSensorLuxThreshold - XDR_LUX_LEAST_NONZERO, 0) / XDR_MAX_LUX, 0.25) }, + set: { autoXdrSensorLuxThreshold = powf($0, 4) * XDR_MAX_LUX + XDR_LUX_LEAST_NONZERO } + ) + BigSurSlider( + percentage: luxBinding, + image: "sun.dust.fill", + color: Colors.lightGray, + backgroundColor: Colors.grayMauve.opacity(0.1), + knobColor: Colors.lightGray, + showValue: .constant(false), + disabled: !$autoXdrSensor, + mark: .oneway { powf(max(dc.internalSensorLux - XDR_LUX_LEAST_NONZERO, 0) / XDR_MAX_LUX, 0.25) } + ) + .padding(.leading) + + SwiftUI.Button("Reset") { autoXdrSensorLuxThreshold = XDR_DEFAULT_LUX } + .buttonStyle(FlatButton( + color: Colors.lightGray, + textColor: Colors.darkGray, + radius: 10, + verticalPadding: 3 + )) + .font(.system(size: 12, weight: .semibold, design: .monospaced)) + .disabled(!autoXdrSensor) + } + if autoXdrSensor { + ( + Text(dc.autoXdrSensorPausedReason ?? "Current ambient light: ") + .font(.system(size: 10, weight: .semibold, design: .monospaced)) + + Text(dc.autoXdrSensorPausedReason == nil ? "\(dc.internalSensorLux.str(decimals: 0)) lux" : "") + .font(.system(size: 10, weight: .heavy, design: .monospaced)) + ) + .foregroundColor(.black.opacity(0.4)) + .padding(.leading, 20) + } + VStack(alignment: .leading, spacing: 2) { + SettingsToggle(text: "Show OSD when toggling XDR automatically", setting: $autoXdrSensorShowOSD.animation(.fastSpring)) + ( + Text("Notifies you when XDR is activating and\n") + .font(.system(size: 10, weight: .semibold, design: .monospaced).leading(.tight)) + + Text("allows aborting AutoXDR by pressing ") + .font(.system(size: 10, weight: .semibold, design: .monospaced).leading(.tight)) + + Text("Esc") + .font(.system(size: 10, weight: .heavy, design: .monospaced).leading(.tight)) + ) + .foregroundColor(.black.opacity(0.4)) + .fixedSize() + .padding(.leading, 20) + }.padding(.leading) + } + } + } +} diff --git a/Lunar/SwiftUIViews/ManageButtonView.swift b/Lunar/SwiftUIViews/ManageButtonView.swift new file mode 100644 index 00000000..9d544ec9 --- /dev/null +++ b/Lunar/SwiftUIViews/ManageButtonView.swift @@ -0,0 +1,29 @@ +import SwiftUI + +struct ManageButtonView: View { + @State var display: Display + @State var hovering = false + @State var off = true + + var body: some View { + HStack(spacing: 2) { + SwiftUI.Button(action: { + off = false + display.unmanaged = false + }) { + Image(systemName: "power").font(.system(size: 10, weight: .heavy)) + } + .buttonStyle(FlatButton( + color: off ? Color.gray : Colors.red, + circle: true, + horizontalPadding: 3, + verticalPadding: 3 + )) + Text("Unignore") + .font(.system(size: 10, weight: .semibold)) + .opacity(hovering ? 1 : 0) + } + .onHover { h in withAnimation { hovering = h } } + .frame(width: 100, alignment: .leading) + } +} diff --git a/Lunar/SwiftUIViews/NeedsLunarProView.swift b/Lunar/SwiftUIViews/NeedsLunarProView.swift new file mode 100644 index 00000000..9334a220 --- /dev/null +++ b/Lunar/SwiftUIViews/NeedsLunarProView.swift @@ -0,0 +1,19 @@ +import SwiftUI + +struct NeedsLunarProView: View { + var body: some View { + PaddedPopoverView(background: Colors.red.brightness(0.1).any) { + HStack(spacing: 4) { + Text("Needs a") + .foregroundColor(.black.opacity(0.8)) + .font(.system(size: 16, weight: .semibold)) + SwiftUI.Button("Lunar Pro") { appDelegate!.getLunarPro(appDelegate!) } + .buttonStyle(FlatButton(color: .black.opacity(0.3), textColor: .white)) + .font(.system(size: 15, weight: .bold, design: .rounded)) + Text("licence") + .foregroundColor(.black.opacity(0.8)) + .font(.system(size: 16, weight: .semibold)) + } + } + } +} diff --git a/Lunar/SwiftUIViews/NitsTextField.swift b/Lunar/SwiftUIViews/NitsTextField.swift new file mode 100644 index 00000000..6482f974 --- /dev/null +++ b/Lunar/SwiftUIViews/NitsTextField.swift @@ -0,0 +1,60 @@ +#if arch(arm64) + import Combine + import Defaults + import SwiftUI + + struct NitsTextField: View { + @Binding var nits: Double + @State var placeholder: String + @ObservedObject var display: Display + @State var editing = false + + @Default(.syncMode) var syncMode + + var editPopover: some View { + PaddedPopoverView(background: Colors.peach.any) { + VStack { + Text("\(placeholder.titleCase())imum nits") + .font(.title.bold()) + Text("for \(display.name)") + + TextField("nits", value: $nits, formatter: NumberFormatter.shared(decimals: 0, padding: 0)) + .onReceive(Just(nits)) { _ in + display.nitsEditPublisher.send(true) + } + .textFieldStyle(PaddedTextFieldStyle(backgroundColor: .primary.opacity(0.1))) + .font(.system(size: 20, weight: .bold, design: .monospaced).leading(.tight)) + .lineLimit(1) + .frame(width: 70) + .multilineTextAlignment(.center) + .padding(.vertical) + + Text("Value estimated from monitor\nfirmware data and user input") + .font(.system(size: 12, weight: .medium, design: .rounded).leading(.tight)) + .foregroundColor(Colors.grayMauve.opacity(0.8)) + .multilineTextAlignment(.center) + } + } + } + + var body: some View { + if syncMode, SyncMode.isUsingNits() { + let disabled = display.isNative && (placeholder == "max" || display.isActiveSyncSource) + SwiftUI.Button(action: { editing = true }) { + VStack(spacing: -3) { + Text(nits.str(decimals: 0)) + .font(.system(size: 10, weight: .bold, design: .monospaced).leading(.tight)) + Text("nits") + .font(.system(size: 8, weight: .semibold, design: .rounded).leading(.tight)) + } + } + .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) + .frame(width: 50) + .popover(isPresented: $editing) { editPopover } + .disabled(disabled) + .help(disabled ? "Managed by the system" : "") + } + } + } + +#endif diff --git a/Lunar/SwiftUIViews/PaddedPopoverView.swift b/Lunar/SwiftUIViews/PaddedPopoverView.swift new file mode 100644 index 00000000..b9907645 --- /dev/null +++ b/Lunar/SwiftUIViews/PaddedPopoverView.swift @@ -0,0 +1,18 @@ +import SwiftUI + +struct PaddedPopoverView: View where Content: View { + @State var background: AnyView + + @ViewBuilder let content: Content + + var body: some View { + ZStack { + background.scaleEffect(1.5) + VStack(alignment: .leading, spacing: 10) { + content + } + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + }.preferredColorScheme(.light) + } +} diff --git a/Lunar/SwiftUIViews/PowerOffButtonView.swift b/Lunar/SwiftUIViews/PowerOffButtonView.swift new file mode 100644 index 00000000..40e96349 --- /dev/null +++ b/Lunar/SwiftUIViews/PowerOffButtonView.swift @@ -0,0 +1,127 @@ +import Defaults +import SwiftUI + +struct PowerOffButtonView: View { + @ObservedObject var display: Display + @ObservedObject var km = KM + @State var showPopover = false + @Default(.newBlackOutDisconnect) var newBlackOutDisconnect + @Default(.neverShowBlackoutPopover) var neverShowBlackoutPopover + @Default(.allowBlackOutOnSingleScreen) var allowBlackOutOnSingleScreen + + @State var hovering = false + @StateObject var poweringOff: ExpiringBool = false + + var actionText: String { + if km.controlKeyPressed { + if km.commandKeyPressed, !display.blackOutEnabled, DC.activeDisplayCount > 1 { + return "Ignore" + } + return "Show Help" + } + + if display.blackOutEnabled { + return "Power On" + } + + if allowBlackOutOnSingleScreen, DC.activeDisplayCount == 1 { + if km.optionKeyPressed { + return display.hasDDC ? "Power Off" : "Needs DDC" + } + return "Darken" + } + + if km.optionKeyPressed { + if km.shiftKeyPressed { + return "Focus" + } + return display.hasDDC ? "Power Off" : "Needs DDC" + } + + if km.shiftKeyPressed { + return "Darken" + } + + #if arch(arm64) + if #available(macOS 13, *), km.commandKeyPressed { + return newBlackOutDisconnect ? "BlackOut" : "Disconnect" + } + + return newBlackOutDisconnect ? "Disconnect" : "BlackOut" + #else + return "BlackOut" + #endif + } + + var color: Color { + if poweringOff.value || display.blackOutEnabled { + return Color.gray + } + + if km.controlKeyPressed { + return Color.orange + } + + if DC.activeDisplayCount == 1 { + return Colors.red + } + + if km.optionKeyPressed, !km.shiftKeyPressed, !display.hasDDC { + return Color.gray + } + return Colors.red + } + + var body: some View { + HStack(spacing: 2) { + SwiftUI.Button(action: { + if km.controlKeyPressed, km.commandKeyPressed, DC.activeDisplayCount > 1 { + display.unmanaged = true + return + } + + guard !KM.controlKeyPressed, + lunarProActive || lunarProOnTrial || (KM.optionKeyPressed && !KM.shiftKeyPressed) + else { + showPopover = true + return + } + + guard neverShowBlackoutPopover else { + showPopover = true + return + } + + poweringOff.set(true, expireAfter: 1) + if display.blackOutEnabled { + display.powerOn() + } else { + display.powerOff() + } + }) { + Image(systemName: "power").font(.system(size: 10, weight: .heavy)) + } + + .buttonStyle(FlatButton( + color: color, + circle: true, + horizontalPadding: 3, + verticalPadding: 3 + )) + .popover(isPresented: $showPopover) { + BlackoutPopoverView(hasDDC: display.hasDDC).onDisappear { + if !neverShowBlackoutPopover { + neverShowBlackoutPopover = true + } + } + } + .onHover { h in withAnimation { hovering = h } } + .disabled((km.optionKeyPressed && !km.shiftKeyPressed && !display.hasDDC) || poweringOff.value) + + Text(actionText) + .font(.system(size: 10, weight: .semibold)) + .opacity(hovering ? 1 : 0) + } + .frame(width: 100, alignment: .leading) + } +} diff --git a/Lunar/SwiftUIViews/PresetButtonView.swift b/Lunar/SwiftUIViews/PresetButtonView.swift new file mode 100644 index 00000000..ea372f6d --- /dev/null +++ b/Lunar/SwiftUIViews/PresetButtonView.swift @@ -0,0 +1,13 @@ +import SwiftUI + +struct PresetButtonView: View { + @State var percent: Int8 + + var body: some View { + SwiftUI.Button("\(percent)%") { + appDelegate!.setLightPercent(percent: percent) + } + .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) + .font(.system(size: 12, weight: .medium, design: .monospaced)) + } +} diff --git a/Lunar/SwiftUIViews/QuickActionsLayoutView.swift b/Lunar/SwiftUIViews/QuickActionsLayoutView.swift new file mode 100644 index 00000000..8312e080 --- /dev/null +++ b/Lunar/SwiftUIViews/QuickActionsLayoutView.swift @@ -0,0 +1,95 @@ +import Defaults +import SwiftUI + +struct QuickActionsLayoutView: View { + @ObservedObject var dc: DisplayController = DC + + @Default(.showSliderValues) var showSliderValues + @Default(.mergeBrightnessContrast) var mergeBrightnessContrast + @Default(.showVolumeSlider) var showVolumeSlider + @Default(.showRawValues) var showRawValues + @Default(.showBrightnessMenuBar) var showBrightnessMenuBar + @Default(.showOnlyExternalBrightnessMenuBar) var showOnlyExternalBrightnessMenuBar + @Default(.showOrientationInQuickActions) var showOrientationInQuickActions + @Default(.showInputInQuickActions) var showInputInQuickActions + @Default(.showPowerInQuickActions) var showPowerInQuickActions + @Default(.showStandardPresets) var showStandardPresets + @Default(.showCustomPresets) var showCustomPresets + @Default(.showXDRSelector) var showXDRSelector + @Default(.showHeaderOnHover) var showHeaderOnHover + @Default(.showFooterOnHover) var showFooterOnHover + @Default(.keepOptionsMenu) var keepOptionsMenu + + @Default(.alternateMenuBarIcon) var alternateMenuBarIcon + @Default(.hideMenuBarIcon) var hideMenuBarIcon + @Default(.showDockIcon) var showDockIcon + @Default(.moreGraphData) var moreGraphData + @Default(.infoMenuShown) var infoMenuShown + @Default(.adaptiveBrightnessMode) var adaptiveBrightnessMode + + var body: some View { + ZStack { + Color.clear.frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading) { + Group { + Group { + SettingsToggle(text: "Only show top buttons on hover", setting: $showHeaderOnHover.animation(.fastSpring)) + SettingsToggle(text: "Only show bottom buttons on hover", setting: $showFooterOnHover.animation(.fastSpring)) + SettingsToggle(text: "Save open-state for this menu", setting: $keepOptionsMenu.animation(.fastSpring)) + } + Divider() + Group { + SettingsToggle(text: "Show slider values", setting: $showSliderValues.animation(.fastSpring)) + if dc.activeDisplayList.contains(where: \.hasDDC) { + SettingsToggle(text: "Show volume slider", setting: $showVolumeSlider.animation(.fastSpring)) + SettingsToggle(text: "Show input source selector", setting: $showInputInQuickActions.animation(.fastSpring)) + } + SettingsToggle(text: "Show rotation selector", setting: $showOrientationInQuickActions.animation(.fastSpring)) + SettingsToggle(text: "Show power button", setting: $showPowerInQuickActions.animation(.fastSpring)) + } + Divider() + Group { + SettingsToggle(text: "Show standard presets", setting: $showStandardPresets.animation(.fastSpring)) + SettingsToggle(text: "Show custom presets", setting: $showCustomPresets.animation(.fastSpring)) + if dc.activeDisplayList.contains(where: \.supportsEnhance) { + SettingsToggle(text: "Show XDR Brightness buttons", setting: $showXDRSelector.animation(.fastSpring)) + } + } + } + if dc.activeDisplayList.contains(where: \.hasDDC) { + SettingsToggle(text: "Merge brightness and contrast", setting: $mergeBrightnessContrast.animation(.fastSpring)) + } + Divider() + Group { + if adaptiveBrightnessMode.hasUsefulInfo { + SettingsToggle(text: "Show useful adaptive info near mode selector", setting: $infoMenuShown.animation(.fastSpring)) + } + if dc.activeDisplayList.contains(where: \.hasDDC) { + SettingsToggle(text: "Show last raw values sent to the display", setting: $showRawValues.animation(.fastSpring)) + } + SettingsToggle(text: "Show brightness near menubar icon", setting: $showBrightnessMenuBar.animation(.fastSpring)) + SettingsToggle( + text: "Show only external monitor brightness", + setting: $showOnlyExternalBrightnessMenuBar.animation(.fastSpring) + ) + .padding(.leading) + .disabled(!showBrightnessMenuBar) + } + Divider() + Group { + SettingsToggle(text: "Hide menubar icon", setting: $hideMenuBarIcon) + SettingsToggle(text: "Alternate menubar icon", setting: $alternateMenuBarIcon) + SettingsToggle(text: "Show dock icon", setting: $showDockIcon) + SettingsToggle( + text: "Show more graph data", + setting: $moreGraphData, + help: "Renders values and data lines on the bottom graph of the Display Settings window" + ) + } + Spacer() + Color.clear + } + .frame(maxWidth: .infinity, alignment: .leading) + } + } +} diff --git a/Lunar/SwiftUIViews/QuickActionsMenuView.swift b/Lunar/SwiftUIViews/QuickActionsMenuView.swift new file mode 100644 index 00000000..7b75c043 --- /dev/null +++ b/Lunar/SwiftUIViews/QuickActionsMenuView.swift @@ -0,0 +1,620 @@ +import Defaults +import SwiftUI + +struct QuickActionsMenuView: View { + @Environment(\.colorScheme) var colorScheme + @Environment(\.colors) var colors + @EnvironmentObject var env: EnvState + @ObservedObject var dc: DisplayController = DC + @ObservedObject var um = UM + @Namespace var namespace + + @Default(.overrideAdaptiveMode) var overrideAdaptiveMode + @Default(.showStandardPresets) var showStandardPresets + @Default(.showCustomPresets) var showCustomPresets + @Default(.showHeaderOnHover) var showHeaderOnHover + @Default(.showFooterOnHover) var showFooterOnHover + @Default(.showOptionsMenu) var showOptionsMenu + + @Default(.menuBarClosed) var menuBarClosed + @Default(.menuDensity) var menuDensity + + @Default(.showBrightnessMenuBar) var showBrightnessMenuBar + @Default(.showOnlyExternalBrightnessMenuBar) var showOnlyExternalBrightnessMenuBar + @Default(.showAdditionalInfo) var showAdditionalInfo + @Default(.startAtLogin) var startAtLogin + + @State var displays: [Display] = DC.nonCursorDisplays + @State var cursorDisplay: Display? = DC.cursorDisplay + @State var sourceDisplay: Display? = DC.sourceDisplay + #if arch(arm64) + @State var disconnectedDisplays: [Display] = DC.possiblyDisconnectedDisplayList + @State var possiblyDisconnectedDisplays: [Display] = [] + #endif + @State var unmanagedDisplays: [Display] = DC.unmanagedDisplays + @State var adaptiveModes: [AdaptiveModeKey] = [.sensor, .sync, .location, .clock, .manual, .auto] + + @State var headerOpacity: CGFloat = 1.0 + @State var footerOpacity: CGFloat = 1.0 + @State var additionalInfoButtonOpacity: CGFloat = 0.3 + @State var headerIndicatorOpacity: CGFloat = 0.0 + @State var footerIndicatorOpacity: CGFloat = 0 + + @State var displayCount = DC.activeDisplayCount + + @ObservedObject var menuBarIcon: StatusItemButtonController + + @ObservedObject var km = KM + @ObservedObject var wm = WM + + var modeSelector: some View { + let titleBinding = Binding( + get: { overrideAdaptiveMode ? "⁣\(dc.adaptiveModeKey.name)⁣" : "Auto: \(dc.adaptiveModeKey.str)" }, set: { _ in } + ) + let imageBinding = Binding( + get: { overrideAdaptiveMode ? dc.adaptiveModeKey.image ?? "automode" : "automode" }, set: { _ in } + ) + + return Dropdown( + selection: $dc.adaptiveModeKey, + width: 140, + height: 20, + noValueText: "Adaptive Mode", + noValueImage: "automode", + content: $adaptiveModes, + title: titleBinding, + image: imageBinding, + validate: AdaptiveModeButton.validate(_:) + ) + .frame(width: 140, height: 20, alignment: .center) + .padding(.vertical, 2) + .padding(.horizontal, 4) + .background( + RoundedRectangle( + cornerRadius: 8, + style: .continuous + ).fill(Color.primary.opacity(0.15)) + ) + .colorMultiply(colors.accent.blended(withFraction: 0.7, of: .white)) + } + + var topRightButtons: some View { + Group { + SwiftUI.Button( + action: { showOptionsMenu.toggle() }, + label: { + HStack(spacing: 2) { + Image(systemName: "gear.circle.fill").font(.system(size: 12, weight: .semibold)) + Text("Settings").font(.system(size: 13, weight: .semibold)) + } + } + ) + .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) + .onChange(of: showBrightnessMenuBar) { _ in + let old = showOptionsMenu + showOptionsMenu = false + if old { mainAsyncAfter(ms: 500) { showOptionsMenu = true }} + } + .onChange(of: showOnlyExternalBrightnessMenuBar) { _ in + let old = showOptionsMenu + showOptionsMenu = false + if old { mainAsyncAfter(ms: 500) { showOptionsMenu = true }} + } + +// SwiftUI.Button( +// action: { +// guard let view = menuWindow?.contentViewController?.view else { return } +// appDelegate!.menu.popUp( +// positioning: nil, +// at: NSPoint( +// x: env +// .menuWidth + +// (showOptionsMenu ? MENU_HORIZONTAL_PADDING * 2 : OPTIONS_MENU_WIDTH / 2 + MENU_HORIZONTAL_PADDING / 2), +// y: 0 +// ), +// in: view +// ) +// }, +// label: { +// HStack(spacing: 2) { +// Image(systemName: "ellipsis.circle.fill").font(.system(size: 12, weight: .semibold)) +// Text("Menu").font(.system(size: 13, weight: .semibold)) +// } +// } +// ).buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) + } + } + + var standardPresets: some View { + HStack { + VStack(alignment: .center, spacing: -2) { + Text("Standard").font(.system(size: 10, weight: .bold)).opacity(0.7) + Text("Presets").font(.system(size: 12, weight: .heavy)).opacity(0.7) + } + Spacer() + PresetButtonView(percent: 0) + PresetButtonView(percent: 25) + PresetButtonView(percent: 50) + PresetButtonView(percent: 75) + PresetButtonView(percent: 100) + } + } + + var footer: some View { + Group { + let dynamicFooter = footerOpacity == 0.0 && showFooterOnHover + ZStack { + VStack(spacing: 5) { + HStack { + Toggle(um.newVersion != nil ? "" : "App info", isOn: $showAdditionalInfo.animation(.fastSpring)) + .toggleStyle(DetailToggleStyle(style: .circle)) + .foregroundColor(Color.secondary) + .font(.system(size: 12, weight: .semibold)) + .fixedSize() + + Spacer() + + if let version = um.newVersion { + SwiftUI.Button("v\(version) available") { appDelegate!.updater.checkForUpdates() } + .buttonStyle(FlatButton( + color: Colors.peach, + textColor: Colors.blackMauve, + horizontalPadding: 6, + verticalPadding: 3 + )) + .font(.system(size: 12, weight: .semibold)) + .lineLimit(1) + .minimumScaleFactor(.leastNonzeroMagnitude) + .scaledToFit() + } + + SwiftUI.Button("Display Settings") { appDelegate!.showPreferencesWindow(sender: nil) } + .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) + .font(.system(size: 12, weight: .medium, design: .rounded)) + .fixedSize() + + SwiftUI.Button("Restart") { appDelegate!.restartApp(appDelegate!) } + .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) + .font(.system(size: 12, weight: .medium, design: .rounded)) + .fixedSize() + + SwiftUI.Button("Quit") { NSApplication.shared.terminate(nil) } + .buttonStyle(FlatButton(color: .primary.opacity(0.1), textColor: .primary)) + .font(.system(size: 12, weight: .medium, design: .rounded)) + .fixedSize() + } + .padding(.bottom, showAdditionalInfo ? 0 : 7) + } + .padding(.horizontal, MENU_HORIZONTAL_PADDING / 2) + .opacity(showFooterOnHover ? footerOpacity : 1.0) + .contentShape(Rectangle()) + .onChange(of: showFooterOnHover) { showOnHover in + withAnimation(.fastTransition) { footerOpacity = showOnHover ? 0.0 : 1.0 } + } + Rectangle() + .fill(Color.primary.opacity(dynamicFooter ? footerIndicatorOpacity : 0.0)) + .frame(maxWidth: .infinity, maxHeight: dynamicFooter ? 20.0 : 0.0) + .onHover { hovering in + guard footerOpacity == 0.0, showFooterOnHover else { return } + if hovering { + withAnimation(.spring()) { + footerIndicatorOpacity = 0.1 + } + withAnimation(.easeOut.delay(0.5)) { + footerIndicatorOpacity = 0 + } + } + } + } + .frame(maxWidth: .infinity, maxHeight: footerOpacity == 0.0 ? 8 : nil) + .onHover { hovering in + guard showFooterOnHover else { + footerShowHideTask = nil + footerOpacity = 1.0 + return + } + + guard hovering else { + footerShowHideTask = mainAsyncAfter(ms: 500) { + withAnimation(.fastTransition) { + footerOpacity = 0.0 + footerIndicatorOpacity = 0.0 + } + } + return + } + footerShowHideTask = mainAsyncAfter(ms: 50) { + withAnimation(.fastTransition) { footerOpacity = 1.0 } + } + } + + if let appDelegate, showAdditionalInfo { + Divider().padding(.bottom, 5) + VStack(alignment: .leading, spacing: 6) { + HStack { + Toggle("Launch at login", isOn: $startAtLogin) + .toggleStyle(CheckboxToggleStyle(style: .circle)) + .foregroundColor(.primary) + .font(.system(size: 12, weight: .medium)) + Spacer() + SwiftUI.Button("Contact") { NSWorkspace.shared.open("https://lunar.fyi/contact".asURL()!) } + .buttonStyle(OutlineButton(thickness: 1, font: .system(size: 9, weight: .medium, design: .rounded))) + SwiftUI.Button("FAQ") { NSWorkspace.shared.open("https://lunar.fyi/faq".asURL()!) } + .buttonStyle(OutlineButton(thickness: 1, font: .system(size: 9, weight: .medium, design: .rounded))) + } + LicenseView() + VersionView(updater: appDelegate.updater) + MenuDensityView() + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 25) + .padding(.bottom, 10) + } + } + .frame(maxWidth: .infinity) + .onAppear { + if Defaults[.launchCount] == 1, !appInfoHiddenAfterLaunch { + appInfoHiddenAfterLaunch = true + additionInfoTask = mainAsyncAfter(ms: 1000) { + withAnimation(.spring()) { + showAdditionalInfo = true + } + } + } + if Defaults[.launchCount] == 2, !appInfoHiddenAfterLaunch { + appInfoHiddenAfterLaunch = true + additionInfoTask = mainAsyncAfter(ms: 1000) { + withAnimation(.spring()) { + showAdditionalInfo = false + } + } + } + } + } + + var header: some View { + let op = (showHeaderOnHover && !showOptionsMenu) ? headerOpacity : 1.0 + return ZStack { + HStack { + if !menuBarClosed { + modeSelector.fixedSize() + UsefulInfo().fixedSize() + } + Spacer() + topRightButtons.fixedSize() + } + .padding(.horizontal, 10) + .padding(.top, 10 * op) + .padding(.bottom, 10 * op) + .opacity(op) + + let dynamicHeader = headerOpacity == 0.0 && showHeaderOnHover + Rectangle() + .fill(Color.primary.opacity(dynamicHeader ? headerIndicatorOpacity : 0.0)) + .frame(maxWidth: .infinity, maxHeight: dynamicHeader ? 20.0 : 0.0) + .onHover { hovering in + guard headerOpacity == 0.0, showHeaderOnHover else { return } + if hovering { + withAnimation(.spring()) { + headerIndicatorOpacity = 0.1 + } + withAnimation(.easeOut.delay(0.5)) { + headerIndicatorOpacity = 0 + } + } + }.offset(x: 0, y: -6) + } + .background(Color.primary.opacity((colorScheme == .dark ? 0.03 : 0.05) * op)) + .padding(.bottom, 10 * op) + .onHover(perform: handleHeaderTransition(hovering:)) + .onChange(of: showOptionsMenu, perform: handleHeaderTransition(hovering:)) + } + + var content: some View { + Group { + header + + if dc.adaptiveModeKey == .sync, let d = sourceDisplay, d.isAllDisplays { + AllDisplaysView().padding(.bottom) + } + + if let d = cursorDisplay, !SWIFTUI_PREVIEW { + DisplayRowView(display: d).padding(.bottom) + } + + ForEach(displays) { d in + DisplayRowView(display: d).padding(.bottom) + } + + #if arch(arm64) + if #available(macOS 13, *) { + ForEach(disconnectedDisplays) { d in + if d.id != 1 || !dc.clamshell { + DisconnectedDisplayView(id: d.id, name: d.name, display: d).padding(.vertical, 7) + } + } + + ForEach(possiblyDisconnectedDisplays) { d in + DisconnectedDisplayView(id: d.id, name: d.name, possibly: true, display: d).padding(.vertical, 7) + } + + if !menuBarClosed, Sysctl.isMacBook, !dc.lidClosed, cursorDisplay?.id != 1, !displays.contains(where: { $0.id == 1 }), !disconnectedDisplays.contains(where: { $0.id == 1 }), + !(DC.builtinDisplays.first?.unmanaged ?? false) + { + DisconnectedDisplayView(id: 1, name: "Built-in", display: dc.displays[1] ?? GENERIC_DISPLAY).padding(.vertical, 7) + } + } + #endif + + ForEach(unmanagedDisplays) { d in + UnmanagedDisplayView(display: d).padding(.vertical, 7) + } + + if showStandardPresets || showCustomPresets { + VStack { + if showStandardPresets { standardPresets } + if showStandardPresets, showCustomPresets { Divider() } + if showCustomPresets { CustomPresetsView() } + } + .padding(.vertical, 6) + .padding(.horizontal, 10) + .background(RoundedRectangle(cornerRadius: 8, style: .continuous).fill(Color.primary.opacity(0.05))) + .padding(.horizontal, MENU_HORIZONTAL_PADDING) + } + } + } + + var body: some View { + let optionsMenuOverflow = showOptionsMenu ? isOptionsMenuOverflowing() : false + HStack(alignment: .top, spacing: 1) { + if optionsMenuOverflow { + optionsMenu.padding(.leading, 20) + .matchedGeometryEffect(id: "options-menu", in: namespace) + } + VStack { + content + footer + } + .frame(maxWidth: env.menuWidth, alignment: .center) + .scrollOnOverflow() + .frame(width: env.menuWidth, height: cap(env.menuHeight, minVal: 100, maxVal: env.menuMaxHeight - 50), alignment: .top) + .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) + .padding(.top, 0) + .background(bg(optionsMenuOverflow: optionsMenuOverflow), alignment: .top) + .onAppear { + displayHideTask = nil + setup() + } + .onChange(of: menuBarClosed) { closed in + setup(closed) + } + .onChange(of: dc.activeDisplayList) { _ in + mainAsyncAfter(ms: 10) { setup() } + } + .onChange(of: dc.sourceDisplay) { _ in + mainAsyncAfter(ms: 10) { setup() } + } + .onChange(of: dc.possiblyDisconnectedDisplayList) { disconnected in + mainAsyncAfter(ms: 10) { + setup() + let ids = disconnected.map(\.id) + displays = displays.filter { !ids.contains($0.id) } + } + } + .frame(maxWidth: .infinity, alignment: .center) + .onTapGesture { env.recording = false } + + .onChange(of: showStandardPresets, perform: setMenuWidth) + .onChange(of: showCustomPresets, perform: setMenuWidth) + .onChange(of: showHeaderOnHover, perform: setMenuWidth) + .onChange(of: showFooterOnHover, perform: setMenuWidth) + .onChange(of: showAdditionalInfo, perform: setMenuWidth) + .onChange(of: headerOpacity, perform: setMenuWidth) + .onChange(of: footerOpacity, perform: setMenuWidth) + .onChange(of: showOptionsMenu) { show in + guard !menuBarClosed else { return } + + setMenuWidth(show) + if !show, let menuWindow { + menuWindow.setContentSize(.zero) + } + appDelegate?.statusItemButtonController?.repositionWindow() + } + + if showOptionsMenu, !optionsMenuOverflow { + optionsMenu.padding(.trailing, 20) + .matchedGeometryEffect(id: "options-menu", in: namespace) + } + } + .frame(width: MENU_WIDTH + FULL_OPTIONS_MENU_WIDTH, height: env.menuMaxHeight, alignment: .top) + .padding(.horizontal, showOptionsMenu ? MENU_HORIZONTAL_PADDING * 2 : 0) + .contrast(wm.focused ? 1.0 : 0.8) + .brightness(wm.focused ? 0.0 : -0.1) + .saturation(wm.focused ? 1.0 : 0.7) + .allowsHitTesting(wm.focused) + } + + @ViewBuilder var optionsMenu: some View { + VStack(spacing: 10) { + HStack { + SwiftUI.Button("Layout") { + withAnimation(.fastSpring) { env.optionsTab = .layout } + } + .buttonStyle(PickerButton( + color: Colors.blackMauve.opacity(0.1), + onColor: Colors.blackMauve.opacity(0.4), + onTextColor: .white, + offTextColor: Colors.darkGray, + enumValue: $env.optionsTab, + onValue: .layout + )) + .font(.system(size: 12, weight: env.optionsTab == .layout ? .bold : .medium, design: .rounded)) + + SwiftUI.Button("Advanced") { + withAnimation(.fastSpring) { env.optionsTab = .advanced } + } + .buttonStyle(PickerButton( + color: Colors.blackMauve.opacity(0.1), + onColor: Colors.blackMauve.opacity(0.4), + onTextColor: .white, + offTextColor: Colors.darkGray, + enumValue: $env.optionsTab, + onValue: .advanced + )) + .font(.system(size: 12, weight: env.optionsTab == .advanced ? .bold : .medium, design: .rounded)) + + SwiftUI.Button("HDR") { + withAnimation(.fastSpring) { env.optionsTab = .hdr } + } + .buttonStyle(PickerButton( + color: Colors.blackMauve.opacity(0.1), + onColor: Colors.blackMauve.opacity(0.4), + onTextColor: .white, + offTextColor: Colors.darkGray, + enumValue: $env.optionsTab, + onValue: .hdr + )) + .font(.system(size: 12, weight: env.optionsTab == .hdr ? .bold : .medium, design: .rounded)) + }.frame(maxWidth: .infinity) + + switch env.optionsTab { + case .layout: + QuickActionsLayoutView().padding(10).foregroundColor(Colors.blackMauve) + case .advanced: + AdvancedSettingsView().padding(10).foregroundColor(Colors.blackMauve) + case .hdr: + HDRSettingsView().padding(10).foregroundColor(Colors.blackMauve) + } + + SwiftUI.Button("Reset \(km.optionKeyPressed ? "global" : (km.commandKeyPressed ? "display-specific" : "ALL")) settings") { + if km.optionKeyPressed { + DataStore.reset() + } else if km.commandKeyPressed { + appDelegate!.resetStates() + Defaults.reset(.displays) + dc.displays = [:] + } else { + resetAllSettings() + } + + mainAsyncAfter(ms: 300) { + restart() + } + } + .buttonStyle(FlatButton(color: Color.red.opacity(0.7), textColor: .white)) + .font(.system(size: 12, weight: .medium)) + .frame(maxWidth: .infinity, alignment: .center) + } + .padding(20) + .frame(width: OPTIONS_MENU_WIDTH, alignment: .center) + .fixedSize() + .background( + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(colorScheme == .dark ? Colors.sunYellow : Colors.lunarYellow) + .shadow(color: Colors.blackMauve.opacity(colorScheme == .dark ? 0.5 : 0.2), radius: 8, x: 0, y: 6) + ) + .padding(.bottom, 20) + .foregroundColor(Colors.blackMauve) + } + + func bg(optionsMenuOverflow _: Bool) -> some View { + ZStack { + VisualEffectBlur(material: .hudWindow, blendingMode: .behindWindow, state: .followsWindowActiveState) + .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) + RoundedRectangle(cornerRadius: 18, style: .continuous) + .fill(colorScheme == .dark ? Colors.blackMauve.opacity(0.4) : Color.white.opacity(0.6)) + } + .shadow(color: Colors.blackMauve.opacity(colorScheme == .dark ? 0.5 : 0.2), radius: 8, x: 0, y: 6) + } + + func isOptionsMenuOverflowing() -> Bool { + guard let screen = NSScreen.main else { return false } + return menuBarIcon.storedPosition.x + MENU_WIDTH + MENU_WIDTH / 2 + FULL_OPTIONS_MENU_WIDTH >= screen.visibleFrame.maxX + } + + func setMenuWidth(_: Any) { + #if arch(arm64) + withAnimation(.fastSpring) { + env.menuWidth = ( + showOptionsMenu || showStandardPresets || showCustomPresets + || !showHeaderOnHover || !showFooterOnHover + || showAdditionalInfo + || headerOpacity > 0 || footerOpacity > 0 + ) ? MENU_WIDTH : MENU_CLEAN_WIDTH + } + #else + env.menuWidth = ( + showOptionsMenu || showStandardPresets || showCustomPresets + || !showHeaderOnHover || !showFooterOnHover + || showAdditionalInfo + || headerOpacity > 0 || footerOpacity > 0 + ) ? MENU_WIDTH : MENU_CLEAN_WIDTH + #endif + if let menuWindow, let size = menuWindow.contentView?.frame.size, size != menuWindow.frame.size { + menuWindow.setContentSize(size) + } + } + + func handleHeaderTransition(hovering: Bool) { + guard !menuBarClosed else { return } + guard !showOptionsMenu else { + headerShowHideTask = nil + headerOpacity = 1.0 + return + } + + guard hovering else { + headerShowHideTask = mainAsyncAfter(ms: 500) { + withAnimation(.fastTransition) { headerOpacity = 0.0 } + } + return + } + headerShowHideTask = mainAsyncAfter(ms: 50) { + withAnimation(.fastTransition) { headerOpacity = 1.0 } + } + } + + func setup(_ closed: Bool? = nil) { + guard !(closed ?? menuBarClosed) else { + displayHideTask = mainAsyncAfter(ms: 2000) { + cursorDisplay = nil + displays = [] + displayCount = 0 + sourceDisplay = nil + #if arch(arm64) + disconnectedDisplays = [] + possiblyDisconnectedDisplays = [] + #endif + unmanagedDisplays = [] + } + return + } + + displayHideTask = nil + cursorDisplay = dc.cursorDisplay + displays = dc.nonCursorDisplays + displayCount = dc.activeDisplayCount + sourceDisplay = dc.sourceDisplay + #if arch(arm64) + disconnectedDisplays = dc.possiblyDisconnectedDisplayList + + let ids = disconnectedDisplays.map(\.id) + displays = displays.filter { !ids.contains($0.id) } + if let id = cursorDisplay?.id, ids.contains(id) { + cursorDisplay = nil + } + + let disconnectedSerials = disconnectedDisplays.map(\.serial) + possiblyDisconnectedDisplays = dc.displays.map(\.1).filter { d in + d.keepDisconnected && !(Sysctl.isMacBook && d.id == 1) && + dc.activeDisplaysBySerial[d.serial] == nil && + !disconnectedSerials.contains(d.serial) + } + #endif + unmanagedDisplays = dc.unmanagedDisplays + + if showHeaderOnHover { headerOpacity = 0.0 } + if showFooterOnHover { footerOpacity = 0.0 } + env.menuMaxHeight = (NSScreen.main?.visibleFrame.height ?? 600) - 50 + } +} diff --git a/Lunar/SwiftUIViews/QuickActionsView.swift b/Lunar/SwiftUIViews/QuickActionsView.swift new file mode 100644 index 00000000..e069818f --- /dev/null +++ b/Lunar/SwiftUIViews/QuickActionsView.swift @@ -0,0 +1,12 @@ +import SwiftUI + +struct QuickActionsView: View { + @Environment(\.colorScheme) var colorScheme + + var body: some View { + QuickActionsMenuView(menuBarIcon: appDelegate!.statusItemButtonController!) + .environmentObject(appDelegate!.env) + .colors(colorScheme == .dark ? .dark : .light) + .focusable(false) + } +} diff --git a/Lunar/SwiftUIViews/RawValuesView.swift b/Lunar/SwiftUIViews/RawValuesView.swift new file mode 100644 index 00000000..8f67d25f --- /dev/null +++ b/Lunar/SwiftUIViews/RawValuesView.swift @@ -0,0 +1,46 @@ +import SwiftUI + +struct RawValueView: View { + @Binding var value: Double? + @State var icon: String + @State var decimals: UInt8 = 0 + + var body: some View { + if let v = value?.str(decimals: decimals) { + HStack(spacing: 2) { + Image(systemName: icon) + Text(v) + } + .font(.system(size: 10, weight: .heavy, design: .monospaced)) + .padding(.horizontal, 4) + .padding(.vertical, 2) + .background(RoundedRectangle(cornerRadius: 5, style: .continuous).fill(Color.primary.opacity(0.07))) + } else { + EmptyView() + } + } +} + +struct RawValuesView: View { + @ObservedObject var display: Display + + var body: some View { + if display.lastRawBrightness != nil || display.lastRawContrast != nil || display.lastRawVolume != nil { + HStack(spacing: 0) { + Text("Raw Values").font(.system(size: 12, weight: .semibold, design: .monospaced)) + Spacer() + HStack(spacing: 4) { + RawValueView( + value: $display.lastRawBrightness, + icon: "sun.max.fill", + decimals: display.control is AppleNativeControl && (display.lastRawBrightness ?? 0 <= 1) ? 2 : 0 + ) + RawValueView(value: $display.lastRawContrast, icon: "circle.righthalf.fill") + RawValueView(value: $display.lastRawVolume, icon: "speaker.2.fill") + }.fixedSize() + }.foregroundColor(.secondary).padding(.horizontal, 3) + } else { + EmptyView() + } + } +} diff --git a/Lunar/SwiftUIViews/TextInputView.swift b/Lunar/SwiftUIViews/TextInputView.swift new file mode 100644 index 00000000..e4ece4f6 --- /dev/null +++ b/Lunar/SwiftUIViews/TextInputView.swift @@ -0,0 +1,17 @@ +import SwiftUI + +struct TextInputView: View { + @State var label: String + @State var placeholder: String + @Binding var data: String + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + if !label.isEmpty { + Text(label).font(.system(size: 12, weight: .semibold)) + } + TextField(placeholder, text: $data) + .textFieldStyle(PaddedTextFieldStyle()) + } + } +} diff --git a/Lunar/SwiftUIViews/UnmanagedDisplayView.swift b/Lunar/SwiftUIViews/UnmanagedDisplayView.swift new file mode 100644 index 00000000..303973f5 --- /dev/null +++ b/Lunar/SwiftUIViews/UnmanagedDisplayView.swift @@ -0,0 +1,22 @@ +import SwiftUI + +struct UnmanagedDisplayView: View { + @Environment(\.colors) var colors + + @ObservedObject var display: Display + + var body: some View { + VStack(spacing: 1) { + HStack(alignment: .top, spacing: -10) { + Text(display.name) + .font(.system(size: 22, weight: .black)) + .padding(6) + .background(RoundedRectangle(cornerRadius: 6, style: .continuous).fill(colors.bg.primary.opacity(0.5))) + + ManageButtonView(display: display) + .offset(y: -8) + }.offset(x: 45) + Text("Not managed").font(.system(size: 10, weight: .semibold, design: .rounded)) + } + } +} diff --git a/Lunar/SwiftUIViews/UsefulInfo.swift b/Lunar/SwiftUIViews/UsefulInfo.swift new file mode 100644 index 00000000..f2ef6cb0 --- /dev/null +++ b/Lunar/SwiftUIViews/UsefulInfo.swift @@ -0,0 +1,48 @@ +import Defaults +import SwiftUI + +struct UsefulInfo: View { + @Default(.infoMenuShown) var infoMenuShown + @Default(.adaptiveBrightnessMode) var adaptiveBrightnessMode + @ObservedObject var ami = AMI + + var usefulInfoText: (String, String)? { + guard infoMenuShown else { return nil } + + switch adaptiveBrightnessMode { + case .sync: + #if arch(arm64) + guard SyncMode.syncNits, let nits = ami.nits else { + return nil + } + return (nits.intround.s, "nits") + #else + return nil + #endif + case .sensor: + guard let lux = ami.lux else { + return nil + } + return (lux > 10 ? lux.intround.s : lux.str(decimals: 1), "lux") + case .location: + guard let elevation = ami.sunElevation else { + return nil + } + return ("\((elevation >= 10 || elevation <= -10) ? elevation.intround.s : elevation.str(decimals: 1))°", "sun") + default: + return nil + } + } + + var body: some View { + if let (t1, t2) = usefulInfoText { + VStack(alignment: .leading, spacing: -2) { + Text(t1) + .font(.system(size: 10, weight: .bold, design: .monospaced).leading(.tight)) + Text(t2) + .font(.system(size: 9, weight: .semibold, design: .rounded).leading(.tight)) + } + .foregroundColor(.secondary) + } + } +} diff --git a/Lunar/SwiftUIViews/ViewGeometry.swift b/Lunar/SwiftUIViews/ViewGeometry.swift new file mode 100644 index 00000000..1568296e --- /dev/null +++ b/Lunar/SwiftUIViews/ViewGeometry.swift @@ -0,0 +1,10 @@ +import SwiftUI + +struct ViewGeometry: View { + var body: some View { + GeometryReader { geometry in + Color.clear + .preference(key: ViewSizeKey.self, value: geometry.size) + } + } +} diff --git a/Lunar/Utils/Extensions.swift b/Lunar/Utils/Extensions.swift index 678a67aa..36d0b7a7 100644 --- a/Lunar/Utils/Extensions.swift +++ b/Lunar/Utils/Extensions.swift @@ -81,63 +81,63 @@ extension DispatchQueue { } extension BinaryInteger { - @inline(__always) var ns: NSNumber { + @inline(__always) @inlinable var ns: NSNumber { NSNumber(value: d) } - @inline(__always) var d: Double { + @inline(__always) @inlinable var d: Double { Double(self) } - @inline(__always) var cg: CGGammaValue { + @inline(__always) @inlinable var cg: CGGammaValue { CGGammaValue(self) } - @inline(__always) var f: Float { + @inline(__always) @inlinable var f: Float { Float(self) } - @inline(__always) var u: UInt { + @inline(__always) @inlinable var u: UInt { UInt(max(self, 0)) } - @inline(__always) var u8: UInt8 { + @inline(__always) @inlinable var u8: UInt8 { UInt8(max(self, 0)) } - @inline(__always) var u16: UInt16 { + @inline(__always) @inlinable var u16: UInt16 { UInt16(max(self, 0)) } - @inline(__always) var u32: UInt32 { + @inline(__always) @inlinable var u32: UInt32 { UInt32(max(self, 0)) } - @inline(__always) var u64: UInt64 { + @inline(__always) @inlinable var u64: UInt64 { UInt64(max(self, 0)) } - @inline(__always) var i: Int { + @inline(__always) @inlinable var i: Int { Int(self) } - @inline(__always) var i8: Int8 { + @inline(__always) @inlinable var i8: Int8 { Int8(self) } - @inline(__always) var i16: Int16 { + @inline(__always) @inlinable var i16: Int16 { Int16(self) } - @inline(__always) var i32: Int32 { + @inline(__always) @inlinable var i32: Int32 { Int32(cap(Int(self), minVal: Int(Int32.min), maxVal: Int(Int32.max))) } - @inline(__always) var i64: Int64 { + @inline(__always) @inlinable var i64: Int64 { Int64(self) } - @inline(__always) var s: String { + @inline(__always) @inlinable var s: String { String(self) } @@ -147,15 +147,15 @@ extension BinaryInteger { } extension Bool { - @inline(__always) var i: Int { + @inline(__always) @inlinable var i: Int { self ? 1 : 0 } - @inline(__always) var s: String { + @inline(__always) @inlinable var s: String { self ? "true" : "false" } - @inline(__always) var state: NSControl.StateValue { + @inline(__always) @inlinable var state: NSControl.StateValue { self ? .on : .off } } @@ -187,7 +187,7 @@ extension NSColor { } } -let CHARS_NOT_STRIPPED = Set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLKMNOPQRSTUVWXYZ1234567890+-=().!_") +@usableFromInline let CHARS_NOT_STRIPPED = Set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLKMNOPQRSTUVWXYZ1234567890+-=().!_") extension String { func parseHex(strict: Bool = false) -> Int? { guard !strict || starts(with: "0x") || starts(with: "x") || hasSuffix("h") else { return nil } @@ -209,61 +209,61 @@ extension String { return Int(sub, radix: 16) } - @inline(__always) var stripped: String { + @inline(__always) @inlinable var stripped: String { filter { CHARS_NOT_STRIPPED.contains($0) } } - @inline(__always) var trimmed: String { + @inline(__always) @inlinable var trimmed: String { trimmingCharacters(in: .whitespacesAndNewlines) } - @inline(__always) var d: Double? { + @inline(__always) @inlinable var d: Double? { Double(replacingOccurrences(of: ",", with: ".")) // NumberFormatter.shared.number(from: self)?.doubleValue } - @inline(__always) var f: Float? { + @inline(__always) @inlinable var f: Float? { Float(replacingOccurrences(of: ",", with: ".")) // NumberFormatter.shared.number(from: self)?.floatValue } - @inline(__always) var u: UInt? { + @inline(__always) @inlinable var u: UInt? { UInt(self) } - @inline(__always) var u8: UInt8? { + @inline(__always) @inlinable var u8: UInt8? { UInt8(self) } - @inline(__always) var u16: UInt16? { + @inline(__always) @inlinable var u16: UInt16? { UInt16(self) } - @inline(__always) var u32: UInt32? { + @inline(__always) @inlinable var u32: UInt32? { UInt32(self) } - @inline(__always) var u64: UInt64? { + @inline(__always) @inlinable var u64: UInt64? { UInt64(self) } - @inline(__always) var i: Int? { + @inline(__always) @inlinable var i: Int? { Int(self) } - @inline(__always) var i8: Int8? { + @inline(__always) @inlinable var i8: Int8? { Int8(self) } - @inline(__always) var i16: Int16? { + @inline(__always) @inlinable var i16: Int16? { Int16(self) } - @inline(__always) var i32: Int32? { + @inline(__always) @inlinable var i32: Int32? { Int32(self) } - @inline(__always) var i64: Int64? { + @inline(__always) @inlinable var i64: Int64? { Int64(self) } @@ -289,15 +289,15 @@ extension Substring.SubSequence { } extension String.SubSequence { - @inline(__always) var u32: UInt32? { + @inline(__always) @inlinable var u32: UInt32? { UInt32(self) } - @inline(__always) var i32: Int32? { + @inline(__always) @inlinable var i32: Int32? { Int32(self) } - @inline(__always) var d: Double? { + @inline(__always) @inlinable var d: Double? { Double(self) } } @@ -355,7 +355,7 @@ extension [UInt8] { } extension Double { - @inline(__always) func rounded(to scale: Int) -> Double { + @inline(__always) @inlinable func rounded(to scale: Int) -> Double { let behavior = NSDecimalNumberHandler( roundingMode: .plain, scale: scale.i16, @@ -370,48 +370,48 @@ extension Double { return roundedValue.doubleValue } - @inline(__always) var ns: NSNumber { + @inline(__always) @inlinable var ns: NSNumber { NSNumber(value: self) } - @inline(__always) var cg: CGGammaValue { + @inline(__always) @inlinable var cg: CGGammaValue { CGGammaValue(self) } - @inline(__always) var f: Float { + @inline(__always) @inlinable var f: Float { get { Float(self) } set { self = Double(newValue) } } - @inline(__always) var i: Int { + @inline(__always) @inlinable var i: Int { Int(self) } - @inline(__always) var u8: UInt8 { + @inline(__always) @inlinable var u8: UInt8 { UInt8(cap(intround, minVal: Int(UInt8.min), maxVal: Int(UInt8.max))) } - @inline(__always) var u16: UInt16 { + @inline(__always) @inlinable var u16: UInt16 { UInt16(cap(intround, minVal: Int(UInt16.min), maxVal: Int(UInt16.max))) } - @inline(__always) var u32: UInt32 { + @inline(__always) @inlinable var u32: UInt32 { UInt32(cap(intround, minVal: Int(UInt32.min), maxVal: Int(UInt32.max))) } - @inline(__always) var i8: Int8 { + @inline(__always) @inlinable var i8: Int8 { Int8(cap(intround, minVal: Int(Int8.min), maxVal: Int(Int8.max))) } - @inline(__always) var i16: Int16 { + @inline(__always) @inlinable var i16: Int16 { Int16(cap(intround, minVal: Int(Int16.min), maxVal: Int(Int16.max))) } - @inline(__always) var i32: Int32 { + @inline(__always) @inlinable var i32: Int32 { Int32(cap(intround, minVal: Int(Int32.min), maxVal: Int(Int32.max))) } - @inline(__always) var intround: Int { + @inline(__always) @inlinable var intround: Int { rounded().i } @@ -557,7 +557,7 @@ extension Dictionary { } extension Float { - @inline(__always) func rounded(to scale: Int) -> Float { + @inline(__always) @inlinable func rounded(to scale: Int) -> Float { let behavior = NSDecimalNumberHandler( roundingMode: .plain, scale: scale.i16, @@ -572,31 +572,31 @@ extension Float { return roundedValue.floatValue } - @inline(__always) var ns: NSNumber { + @inline(__always) @inlinable var ns: NSNumber { NSNumber(value: self) } - @inline(__always) var d: Double { + @inline(__always) @inlinable var d: Double { Double(self) } - @inline(__always) var i: Int { + @inline(__always) @inlinable var i: Int { Int(self) } - @inline(__always) var u8: UInt8 { + @inline(__always) @inlinable var u8: UInt8 { UInt8(cap(intround, minVal: Int(UInt8.min), maxVal: Int(UInt8.max))) } - @inline(__always) var u16: UInt16 { + @inline(__always) @inlinable var u16: UInt16 { UInt16(cap(intround, minVal: Int(UInt16.min), maxVal: Int(UInt16.max))) } - @inline(__always) var u32: UInt32 { + @inline(__always) @inlinable var u32: UInt32 { UInt32(cap(intround, minVal: Int(UInt32.min), maxVal: Int(UInt32.max))) } - @inline(__always) var intround: Int { + @inline(__always) @inlinable var intround: Int { rounded().i } @@ -610,31 +610,31 @@ extension Float { } extension CGFloat { - @inline(__always) var ns: NSNumber { + @inline(__always) @inlinable var ns: NSNumber { NSNumber(value: Float(self)) } - @inline(__always) var d: Double { + @inline(__always) @inlinable var d: Double { Double(self) } - @inline(__always) var i: Int { + @inline(__always) @inlinable var i: Int { Int(self) } - @inline(__always) var u8: UInt8 { + @inline(__always) @inlinable var u8: UInt8 { UInt8(cap(intround, minVal: Int(UInt8.min), maxVal: Int(UInt8.max))) } - @inline(__always) var u16: UInt16 { + @inline(__always) @inlinable var u16: UInt16 { UInt16(cap(intround, minVal: Int(UInt16.min), maxVal: Int(UInt16.max))) } - @inline(__always) var u32: UInt32 { + @inline(__always) @inlinable var u32: UInt32 { UInt32(cap(intround, minVal: Int(UInt32.min), maxVal: Int(UInt32.max))) } - @inline(__always) var intround: Int { + @inline(__always) @inlinable var intround: Int { rounded().i } @@ -920,13 +920,6 @@ extension MPDisplayMode { "\(width)×\(height)@\(refreshStringSafe)" } - @available(iOS 16, macOS 13, *) - var panelMode: PanelMode? { - guard let display, let screen = display.screen else { return nil } - - return PanelMode(screen: screen, id: (screen.id << 32) + modeNumber.i, title: title, subtitle: subtitle, image: imageSystemName) - } - var subtitle: String { let tags = tagStrings.without("hidpi") let tagsString = tags.isEmpty ? "" : " (\(tags.joined(separator: ", ")))" @@ -1896,3 +1889,38 @@ extension Binding { extension View { var any: AnyView { AnyView(self) } } + +import CryptoKit + +public extension Data { + var sha512: String { + Data(SHA512.hash(data: self).map { $0 }).str(hex: true, separator: "") + } + var sha256: String { + Data(SHA256.hash(data: self).map { $0 }).str(hex: true, separator: "") + } + var sha1: String { + Data(Insecure.SHA1.hash(data: self).map { $0 }).str(hex: true, separator: "") + } + var sha512Data: Data { + Data(SHA512.hash(data: self).map { $0 }) + } + var sha256Data: Data { + Data(SHA256.hash(data: self).map { $0 }) + } + var sha1Data: Data { + Data(Insecure.SHA1.hash(data: self).map { $0 }) + } +} + +extension String { + var sha512: String { + data(using: .utf8)!.sha512 + } + var sha256: String { + data(using: .utf8)!.sha256 + } + var sha1: String { + data(using: .utf8)!.sha1 + } +} diff --git a/Lunar/Views/ModernWindow.swift b/Lunar/Views/ModernWindow.swift index 3fc0e5f1..8d50dba7 100644 --- a/Lunar/Views/ModernWindow.swift +++ b/Lunar/Views/ModernWindow.swift @@ -261,7 +261,9 @@ final class ModernWindow: WAYWindow { setAutorecalculatesContentBorderThickness(false, for: NSRectEdge.minY) isOpaque = false backgroundColor = NSColor.clear - makeKeyAndOrderFront(self) + if canBecomeKey { + makeKeyAndOrderFront(self) + } orderFrontRegardless() } } diff --git a/Lunar/Views/OSDWindow.swift b/Lunar/Views/OSDWindow.swift index 6047fd11..092ae540 100644 --- a/Lunar/Views/OSDWindow.swift +++ b/Lunar/Views/OSDWindow.swift @@ -74,8 +74,10 @@ final class OSDWindow: NSWindow, NSWindowDelegate { } contentView?.superview?.alphaValue = 1 - wc.showWindow(nil) - makeKeyAndOrderFront(nil) + if canBecomeKey { + wc.showWindow(nil) + makeKeyAndOrderFront(nil) + } orderFrontRegardless() endFader = nil @@ -1064,8 +1066,10 @@ final class PanelWindow: NSWindow { guard !isVisible else { return } - wc.showWindow(nil) - makeKeyAndOrderFront(nil) + if canBecomeKey { + wc.showWindow(nil) + makeKeyAndOrderFront(nil) + } orderFrontRegardless() NSApp.activate(ignoringOtherApps: true) } diff --git a/Lunar/Views/ScrollViewIfNeeded.swift b/Lunar/Views/ScrollViewIfNeeded.swift index 43d9ba6f..9791b3a1 100644 --- a/Lunar/Views/ScrollViewIfNeeded.swift +++ b/Lunar/Views/ScrollViewIfNeeded.swift @@ -246,13 +246,13 @@ func swizzleScrollWheel(scrollView: AnyObject) { scrollWheelSwizIMP = method_getImplementation(scrollWheelSwiz!) } -import Introspect +@_spi(Advanced) import SwiftUIIntrospect extension View { @ViewBuilder func wrappedInScrollView(when condition: Bool, pauseScrolling: Bool = false) -> some View { if condition { ScrollView(.vertical, showsIndicators: false) { - self.introspectScrollView { s in + self.introspect(.scrollView, on: .macOS(.v11...)) { s in s.identifier = pauseScrolling ? SCROLL_VIEW_SWIZZLED_TAG : nil swizzleScrollWheel(scrollView: s) diff --git a/Lunar/required.swift.secret b/Lunar/required.swift.secret new file mode 100644 index 0000000000000000000000000000000000000000..219b1488f31d4ef504cf4000370b101b301b520d GIT binary patch literal 30338 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, property: Display.CodingKeys, value: String, skipMissingScreen: Bool = false) throws -> some IntentResult { +private func controlScreen(screen: IntentParameter, property: Display.CodingKeys, value: String, skipMissingScreen: Bool = false) throws -> some IntentResult { let scr = screen.wrappedValue guard !(scr.serial + scr.name).isEmpty else { if skipMissingScreen { @@ -499,7 +499,7 @@ func controlScreen(screen: IntentParameter, property: Display.CodingKeys // MARK: - SetBrightnessValue @available(iOS 16, macOS 13, *) -struct SetBrightnessIntent: AppIntent { +private struct SetBrightnessIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Set Brightness" @@ -529,7 +529,7 @@ struct SetBrightnessIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct SetContrastIntent: AppIntent { +private struct SetContrastIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Set Contrast" @@ -559,7 +559,7 @@ struct SetContrastIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct SetBrightnessContrastIntent: AppIntent { +private struct SetBrightnessContrastIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Set Brightness & Contrast" @@ -592,7 +592,7 @@ struct SetBrightnessContrastIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct SetSubZeroDimmingIntent: AppIntent { +private struct SetSubZeroDimmingIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Set Sub-zero Dimming" @@ -622,7 +622,7 @@ struct SetSubZeroDimmingIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct SetXDRBrightnessIntent: AppIntent { +private struct SetXDRBrightnessIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Set XDR Brightness" @@ -654,7 +654,7 @@ struct SetXDRBrightnessIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct SetVolumeIntent: AppIntent { +private struct SetVolumeIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Set Volume" @@ -713,7 +713,7 @@ enum ScreenToggleState: String, AppEnum, CaseDisplayRepresentable, TypeDisplayRe } @available(iOS 16, macOS 13, *) -struct ToggleAudioMuteIntent: AppIntent { +private struct ToggleAudioMuteIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Toggle Audio Mute" @@ -743,7 +743,7 @@ struct ToggleAudioMuteIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ToggleSystemAdaptiveBrightnessIntent: AppIntent { +private struct ToggleSystemAdaptiveBrightnessIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Toggle System Adaptive Brightness" @@ -773,7 +773,7 @@ struct ToggleSystemAdaptiveBrightnessIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ToggleHDRIntent: AppIntent { +private struct ToggleHDRIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Toggle HDR" @@ -803,7 +803,7 @@ struct ToggleHDRIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ToggleXDRIntent: AppIntent { +private struct ToggleXDRIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Toggle XDR Brightness" @@ -835,7 +835,7 @@ struct ToggleXDRIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ToggleSubZeroIntent: AppIntent { +private struct ToggleSubZeroIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Toggle Sub-zero Dimming" @@ -865,7 +865,7 @@ struct ToggleSubZeroIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ToggleFacelightIntent: AppIntent { +private struct ToggleFacelightIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Toggle Facelight" @@ -899,7 +899,7 @@ struct ToggleFacelightIntent: AppIntent { extension UnitDuration: @unchecked Sendable {} @available(iOS 16, macOS 13, *) -struct CleaningModeIntent: AppIntent { +private struct CleaningModeIntent: AppIntent { init() {} // swiftformat:disable all @@ -939,13 +939,13 @@ Press the ⌘ Command key more than 8 times in a row to force deactivation of th } } -var cleaningModeTask: DispatchWorkItem? -var swallowKeyboardEventTap: CFMachPort? -var swallowKeyboardRunLoopSource: CFRunLoopSource? -var swallowKeyboardRunLoop: CFRunLoop? -let swallowKeyboardQueue = DispatchQueue(label: "Cleaning Mode Runloop", attributes: []) +private var cleaningModeTask: DispatchWorkItem? +private var swallowKeyboardEventTap: CFMachPort? +private var swallowKeyboardRunLoopSource: CFRunLoopSource? +private var swallowKeyboardRunLoop: CFRunLoop? +private let swallowKeyboardQueue = DispatchQueue(label: "Cleaning Mode Runloop", attributes: []) -func swallowKeyboardEvents() { +private func swallowKeyboardEvents() { // creates a CGEventTap that will swallow all keyboard events and makes the keyboard acts as disabled let keyMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue) | (1 << NX_SYSDEFINED) | (1 << CGEventType.flagsChanged.rawValue) @@ -987,7 +987,7 @@ func swallowKeyboardEvents() { CGEvent.tapEnable(tap: swallowKeyboardEventTap, enable: true) } -func disableSwallowKeyboardEvents() { +private func disableSwallowKeyboardEvents() { if let source = swallowKeyboardRunLoopSource { CFRunLoopSourceInvalidate(source) swallowKeyboardRunLoopSource = nil @@ -1044,7 +1044,7 @@ func activateCleaningMode(deactivateAfter: TimeInterval? = 120, withoutSettingFl } @available(iOS 16, macOS 13, *) -struct PowerOffSoftwareIntent: AppIntent { +private struct PowerOffSoftwareIntent: AppIntent { init() {} // swiftformat:disable all @@ -1154,7 +1154,7 @@ Power off a screen by: } @available(iOS 16, macOS 13, *) -struct PowerOnSoftwareIntent: AppIntent { +private struct PowerOnSoftwareIntent: AppIntent { init() {} // swiftformat:disable all @@ -1212,7 +1212,7 @@ struct PowerOnSoftwareIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct SleepMacIntent: AppIntent { +private struct SleepMacIntent: AppIntent { init() {} // swiftformat:disable all @@ -1236,7 +1236,7 @@ struct SleepMacIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct DisconnectScreenIntent: AppIntent { +private struct DisconnectScreenIntent: AppIntent { init() {} // swiftformat:disable all @@ -1286,7 +1286,7 @@ To bring back the screen try any one of the following: } @available(iOS 16, macOS 13, *) -struct ReconnectScreenIntent: AppIntent { +private struct ReconnectScreenIntent: AppIntent { init() {} // swiftformat:disable all @@ -1350,7 +1350,7 @@ If the action fails, try any one of the following to bring back the screen: } @available(iOS 16, macOS 13, *) -func connectedScreen(_ s: Screen) async throws -> Screen? { +private func connectedScreen(_ s: Screen) async throws -> Screen? { for _ in 1 ... 30 { if let d = s.dynamicDisplay { return d.screen @@ -1362,7 +1362,7 @@ func connectedScreen(_ s: Screen) async throws -> Screen? { } @available(iOS 16, macOS 13, *) -struct ToggleScreenConnectionIntent: AppIntent { +private struct ToggleScreenConnectionIntent: AppIntent { init() {} // swiftformat:disable all @@ -1444,7 +1444,7 @@ If the reconnect action fails, try any one of the following to bring back the sc } @available(iOS 16, macOS 13, *) -struct ToggleBlackOutIntent: AppIntent { +private struct ToggleBlackOutIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Toggle Power (BlackOut)" @@ -1494,7 +1494,7 @@ struct ToggleBlackOutIntent: AppIntent { } @available(iOS 16, macOS 13, *) -enum ScreenRotationDegrees: Int, AppEnum, CaseDisplayRepresentable, TypeDisplayRepresentable { +private enum ScreenRotationDegrees: Int, AppEnum, CaseDisplayRepresentable, TypeDisplayRepresentable { case normal = 0 case portraitToLeft = 90 case upsideDown = 180 @@ -1511,7 +1511,7 @@ enum ScreenRotationDegrees: Int, AppEnum, CaseDisplayRepresentable, TypeDisplayR } @available(iOS 16, macOS 13, *) -struct RotateScreenIntent: AppIntent { +private struct RotateScreenIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Rotate Screen" @@ -1541,7 +1541,7 @@ struct RotateScreenIntent: AppIntent { } @available(iOS 16, macOS 13, *) -enum AdaptiveModeKeyForIntent: Int, AppEnum { +private enum AdaptiveModeKeyForIntent: Int, AppEnum { case location = 1 case sync = -1 case manual = 0 @@ -1572,7 +1572,7 @@ enum AdaptiveModeKeyForIntent: Int, AppEnum { } @available(iOS 16, macOS 13, *) -struct ChangeAdaptiveModeIntent: AppIntent { +private struct ChangeAdaptiveModeIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Change Adaptive Mode" @@ -1598,7 +1598,7 @@ struct ChangeAdaptiveModeIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ApplyPresetIntent: AppIntent { +private struct ApplyPresetIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Apply Lunar Preset" @@ -1644,7 +1644,7 @@ struct ApplyPresetIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct PowerOffIntent: AppIntent { +private struct PowerOffIntent: AppIntent { init() {} // swiftformat:disable all @@ -1677,7 +1677,7 @@ Note: a screen can't also be powered on using this method because a powered off } @available(iOS 16, macOS 13, *) -struct ResetColorGainIntent: AppIntent { +private struct ResetColorGainIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Reset Color Adjustments (in hardware)" @@ -1700,7 +1700,7 @@ struct ResetColorGainIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ResetColorGammaIntent: AppIntent { +private struct ResetColorGammaIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Reset Color Adjustments (in software)" @@ -1724,7 +1724,7 @@ struct ResetColorGammaIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ToggleGammaIntent: AppIntent { +private struct ToggleGammaIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Toggle Color Adjustments (in software)" @@ -1750,7 +1750,7 @@ struct ToggleGammaIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct SetInputIntent: AppIntent { +private struct SetInputIntent: AppIntent { init() {} // swiftformat:disable all @@ -1804,7 +1804,7 @@ Note: Not all inputs are supported by all monitors, and some monitors may use no } @available(iOS 16, macOS 13, *) -struct WriteDDCIntent: AppIntent { +private struct WriteDDCIntent: AppIntent { init() {} static var title: LocalizedStringResource = "DDC Write Value to Screen" @@ -1853,7 +1853,7 @@ struct WriteDDCIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ReadDDCIntent: AppIntent { +private struct ReadDDCIntent: AppIntent { init() {} // swiftformat:disable all @@ -1907,7 +1907,7 @@ Note: DDC reads rarely work and can return wrong values. } @available(iOS 16, macOS 13, *) -struct ControlScreenValueFloatNumeric: AppIntent { +private struct ControlScreenValueFloatNumeric: AppIntent { init() {} static var title: LocalizedStringResource = "Control a floating point Screen Value" @@ -1946,7 +1946,7 @@ struct ControlScreenValueFloatNumeric: AppIntent { } @available(iOS 16, macOS 13, *) -struct ControlScreenValueNumeric: AppIntent { +private struct ControlScreenValueNumeric: AppIntent { init() {} static var title: LocalizedStringResource = "Control an integer Screen Value" @@ -1985,7 +1985,7 @@ struct ControlScreenValueNumeric: AppIntent { } @available(iOS 16, macOS 13, *) -struct ControlScreenValueBool: AppIntent { +private struct ControlScreenValueBool: AppIntent { init() {} static var title: LocalizedStringResource = "Control a boolean Screen Value" @@ -2024,7 +2024,7 @@ struct ControlScreenValueBool: AppIntent { } @available(iOS 16, macOS 13, *) -struct AdjustSoftwareColorsIntent: AppIntent { +private struct AdjustSoftwareColorsIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Adjust Colors of a Screen (in software)" @@ -2102,7 +2102,7 @@ The blue component of the Gamma curve } @available(iOS 16, macOS 13, *) -struct AdjustHardwareColorsIntent: AppIntent { +private struct AdjustHardwareColorsIntent: AppIntent { init() {} // swiftformat:disable all @@ -2191,7 +2191,7 @@ Note: not all monitors support color gain control through DDC and value effect c } @available(iOS 16, macOS 13, *) -struct ReadHardwareColorsIntent: AppIntent { +private struct ReadHardwareColorsIntent: AppIntent { init() {} // swiftformat:disable all @@ -2240,7 +2240,7 @@ Note: very few monitors implement this functionality } @available(iOS 16, macOS 13, *) -struct ReadSoftwareColorsIntent: AppIntent { +private struct ReadSoftwareColorsIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Read Color Gamma Tables (in software)" @@ -2278,7 +2278,7 @@ struct ReadSoftwareColorsIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct MirrorSetIntent: AppIntent { +private struct MirrorSetIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Mirror Screens" @@ -2344,7 +2344,7 @@ struct MirrorSetIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct StopMirroringIntent: AppIntent { +private struct StopMirroringIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Stop Mirroring" @@ -2422,7 +2422,7 @@ struct StopMirroringIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct SetPanelModeIntent: AppIntent { +private struct SetPanelModeIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Change Screen Resolution" @@ -2452,7 +2452,7 @@ struct SetPanelModeIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct SetPanelPresetIntent: AppIntent { +private struct SetPanelPresetIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Change Screen Preset" @@ -2484,7 +2484,7 @@ struct SetPanelPresetIntent: AppIntent { } } -struct SortablePanelMode: Comparable, Equatable, Hashable { +private struct SortablePanelMode: Comparable, Equatable, Hashable { let isHiDPI: Bool let refreshRate: Int32 @@ -2494,7 +2494,7 @@ struct SortablePanelMode: Comparable, Equatable, Hashable { } @available(iOS 16, macOS 13, *) -extension MPDisplayPreset { +private extension MPDisplayPreset { var panelPreset: PanelPreset? { guard let screen = DC.activeDisplays[displayID]?.screen else { return nil } @@ -2516,7 +2516,7 @@ extension MPDisplayPreset { } @available(iOS 16, macOS 13, *) -struct PanelPreset: AppEntity { +private struct PanelPreset: AppEntity { struct PanelPresetQuery: EntityQuery { func entities(for identifiers: [Int]) async throws -> [PanelPreset] { await DisplayController.panelManager?.withLock { _ in @@ -2595,7 +2595,7 @@ struct PanelPreset: AppEntity { } @available(iOS 16, macOS 13, *) -extension MPDisplayMgr { +private extension MPDisplayMgr { func withLock(_ action: (MPDisplayMgr) -> T) async -> T? { while !tryLockAccess() { do { @@ -2611,6 +2611,15 @@ extension MPDisplayMgr { } } +private extension MPDisplayMode { + @available(iOS 16, macOS 13, *) + var panelMode: PanelMode? { + guard let display, let screen = display.screen else { return nil } + + return PanelMode(screen: screen, id: (screen.id << 32) + modeNumber.i, title: title, subtitle: subtitle, image: imageSystemName) + } +} + @available(iOS 16, macOS 13, *) struct PanelMode: AppEntity { struct PanelModeQuery: EntityQuery { @@ -2687,7 +2696,7 @@ struct PanelMode: AppEntity { } @available(iOS 16, macOS 13, *) -struct SwapMonitorsIntent: AppIntent { +private struct SwapMonitorsIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Swap Screen Positions" @@ -2725,7 +2734,7 @@ struct SwapMonitorsIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct MakeMonitorMainIntent: AppIntent { +private struct MakeMonitorMainIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Set Screen as Main" @@ -2763,7 +2772,7 @@ struct MakeMonitorMainIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct VerticalMonitorLayoutIntent: AppIntent { +private struct VerticalMonitorLayoutIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Arrange 2 Screens Vertically ■̳̲" @@ -2815,7 +2824,7 @@ struct VerticalMonitorLayoutIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct HorizontalMonitorLayoutIntent: AppIntent { +private struct HorizontalMonitorLayoutIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Arrange 2 Screens Horizontally ⫍⃮݄⫎⃯" @@ -2867,7 +2876,7 @@ struct HorizontalMonitorLayoutIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct ThreeAboveOneMonitorLayoutIntent: AppIntent { +private struct ThreeAboveOneMonitorLayoutIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Arrange 4 Screens in a 3-above-1 configuration ⫍⃮■̳̻⫎⃯" @@ -2937,7 +2946,7 @@ struct ThreeAboveOneMonitorLayoutIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct TwoAboveOneMonitorLayoutIntent: AppIntent { +private struct TwoAboveOneMonitorLayoutIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Arrange 3 Screens in a 2-above-1 configuration ⫍‗⫎" @@ -3020,7 +3029,7 @@ struct TwoAboveOneMonitorLayoutIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct HorizontalMonitorThreeLayoutIntent: AppIntent { +private struct HorizontalMonitorThreeLayoutIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Arrange 3 Screens Horizontally ⫍⃮▬̲⫎⃯" @@ -3103,7 +3112,7 @@ struct HorizontalMonitorThreeLayoutIntent: AppIntent { } @available(iOS 16, macOS 13, *) -struct FixMonitorArrangementIntent: AppIntent { +private struct FixMonitorArrangementIntent: AppIntent { init() {} static var title: LocalizedStringResource = "Fix monitor arrangement" @@ -3180,7 +3189,7 @@ Press `Esc` to cancel or `Enter` to partially arrange the monitors selected so f } @available(iOS 16, macOS 13, *) -struct ScreenColorGain: Equatable, Hashable, AppEntity { +private struct ScreenColorGain: Equatable, Hashable, AppEntity { init(red: Int, green: Int, blue: Int) { self.red = red self.green = green @@ -3228,7 +3237,7 @@ struct ScreenColorGain: Equatable, Hashable, AppEntity { } @available(iOS 16, macOS 13, *) -struct ScreenGammaTable: Equatable, Hashable, AppEntity { +private struct ScreenGammaTable: Equatable, Hashable, AppEntity { init(red: Double, green: Double, blue: Double) { self.red = red self.green = green @@ -3291,7 +3300,7 @@ extension Display.CodingKeys: EntityIdentifierConvertible { // MARK: - DisplayNumericPropertyQuery @available(iOS 16, macOS 13, *) -struct DisplayFloatNumericPropertyQuery: EntityQuery { +private struct DisplayFloatNumericPropertyQuery: EntityQuery { typealias Entity = DisplayProperty func suggestedEntities() async throws -> ItemCollection { @@ -3324,7 +3333,7 @@ struct DisplayFloatNumericPropertyQuery: EntityQuery { } @available(iOS 16, macOS 13, *) -struct DisplayNumericPropertyQuery: EntityQuery { +private struct DisplayNumericPropertyQuery: EntityQuery { typealias Entity = DisplayProperty func suggestedEntities() async throws -> ItemCollection { @@ -3361,7 +3370,7 @@ struct DisplayNumericPropertyQuery: EntityQuery { items: DisplayProperty.settingsNumeric.map { .init($0, title: "\($0.name)") } ) ItemSection( - title: "DDC dettings", + title: "DDC settings", items: DisplayProperty.ddcSettingsNumeric.map { .init($0, title: "\($0.name)") } ) } @@ -3371,7 +3380,7 @@ struct DisplayNumericPropertyQuery: EntityQuery { // MARK: - DisplayBoolPropertyQuery @available(iOS 16, macOS 13, *) -struct DisplayBoolPropertyQuery: EntityQuery { +private struct DisplayBoolPropertyQuery: EntityQuery { typealias Entity = DisplayProperty func suggestedEntities() async throws -> ItemCollection { @@ -3409,7 +3418,7 @@ struct DisplayBoolPropertyQuery: EntityQuery { // MARK: - DisplayProperty @available(iOS 16, macOS 13, *) -struct DisplayProperty: Equatable, Hashable, AppEntity, CustomStringConvertible { +private struct DisplayProperty: Equatable, Hashable, AppEntity, CustomStringConvertible { static var defaultQuery = DisplayFloatNumericPropertyQuery() static let commonNumeric: [DisplayProperty] = [ DisplayProperty(id: .brightness, name: "Brightness"), diff --git a/Makefile b/Makefile index fd63426f..afa419d6 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,6 @@ release: changelog cat ReleaseNotes/$(VERSION).md >> /tmp/release_file_$(VERSION).md gh release create v$(VERSION) -F /tmp/release_file_$(VERSION).md "Releases/Lunar-$(VERSION).dmg#Lunar.dmg" -sentry: export DWARF_DSYM_FOLDER_PATH="$(shell xcodebuild -scheme $(ENV) -configuration $(ENV) -showBuildSettings -json 2>/dev/null | jq -r .[0].buildSettings.DWARF_DSYM_FOLDER_PATH)" sentry: sentry-cli upload-dif --include-sources -o alin-panaitiu -p lunar --wait -- $(DERIVED_DATA_DIR)/Build/Products/Release/ @@ -76,15 +75,14 @@ print-% : ; @echo $* = $($*) dmg: SHELL=/usr/local/bin/fish dmg: - env CODESIGNING_FOLDER_PATH=(xcdir -s '$(ENV)' -c $(ENV))/Lunar.app ./bin/make-installer dmg + env CODESIGNING_FOLDER_PATH=(xcdir -s 'Lunar' -c $(ENV))/Lunar.app ./bin/make-installer dmg pack: SHELL=/usr/local/bin/fish pack: export SPARKLE_BIN_DIR="$$PWD/Frameworks/Sparkle/bin/" pack: - env CODESIGNING_FOLDER_PATH=(xcdir -s '$(ENV)' -c $(ENV))/Lunar.app PROJECT_DIR=$$PWD ./bin/pack + env CODESIGNING_FOLDER_PATH=(xcdir -s 'Lunar' -c $(ENV))/Lunar.app PROJECT_DIR=$$PWD ./bin/pack appcast: export SPARKLE_BIN_DIR="$$PWD/Frameworks/Sparkle/bin/" -appcast: VERSION=$(shell xcodebuild -scheme $(ENV) -configuration $(ENV) -workspace Lunar.xcworkspace -showBuildSettings -json 2>/dev/null | jq -r .[0].buildSettings.MARKETING_VERSION) appcast: Releases/Lunar-$(FULL_VERSION).html ifneq ($(DISABLE_APPCAST),1) rm Releases/Lunar.dmg || true @@ -104,22 +102,22 @@ endif endif -setversion: OLD_VERSION=$(shell xcodebuild -scheme $(ENV) -configuration $(ENV) -workspace Lunar.xcworkspace -showBuildSettings -json 2>/dev/null | jq -r .[0].buildSettings.MARKETING_VERSION) +setversion: OLD_VERSION=$(shell rg -o --no-filename 'MARKETING_VERSION = ([^;]+).+' -r '$$1' *.xcodeproj/project.pbxproj | head -1) setversion: ifneq (, $(FULL_VERSION)) rg -l 'VERSION = "?$(OLD_VERSION)"?' && sed -E -i .bkp 's/VERSION = "?$(OLD_VERSION)"?/VERSION = $(FULL_VERSION)/g' $$(rg -l 'VERSION = "?$(OLD_VERSION)"?') endif clean: - xcodebuild -scheme $(ENV) -configuration $(ENV) -workspace Lunar.xcworkspace ONLY_ACTIVE_ARCH=NO clean + xcodebuild -scheme Lunar -configuration $(ENV) -workspace Lunar.xcworkspace ONLY_ACTIVE_ARCH=NO clean build: BEAUTIFY=0 build: ONLY_ACTIVE_ARCH=NO build: setversion ifneq ($(BEAUTIFY),0) - xcodebuild -scheme $(ENV) -configuration $(ENV) -workspace Lunar.xcworkspace ONLY_ACTIVE_ARCH=$(ONLY_ACTIVE_ARCH) | xcbeautify + xcodebuild -scheme Lunar -configuration $(ENV) -workspace Lunar.xcworkspace ONLY_ACTIVE_ARCH=$(ONLY_ACTIVE_ARCH) | xcbeautify else - xcodebuild -scheme $(ENV) -configuration $(ENV) -workspace Lunar.xcworkspace ONLY_ACTIVE_ARCH=$(ONLY_ACTIVE_ARCH) + xcodebuild -scheme Lunar -configuration $(ENV) -workspace Lunar.xcworkspace ONLY_ACTIVE_ARCH=$(ONLY_ACTIVE_ARCH) endif ifneq ($(DISABLE_PACKING),1) make pack VERSION=$(VERSION) BETA=$(BETA) diff --git a/ReleaseNotes/6.2.6.md b/ReleaseNotes/6.2.6.md index bc933630..9b58cec7 100644 --- a/ReleaseNotes/6.2.6.md +++ b/ReleaseNotes/6.2.6.md @@ -11,6 +11,7 @@ ## Improvements +* Detect possible clamshell sleep and preemptively pause adaptive brightness to avoid flickering * Add **Skip action if screen is missing** for Shortcuts ![skip action if screen is missing checkbox](https://files.lunar.fyi/skip-action-if-screen-is-missing.png)