From 21e96b543a07373d322b5f55015ce210125dd6db Mon Sep 17 00:00:00 2001 From: Lorenzo Nuvoletta Date: Sun, 30 Aug 2026 10:59:41 -0600 Subject: [PATCH] Fix lock action to sleep displays --- Code Awake Tests/AppLogicTests.swift | 20 ++++++++++++++ Code Awake/AppLogic.swift | 22 +++++++++++++++ Code Awake/Code_AwakeApp.swift | 40 ++++++++++++++++++---------- Code Awake/WelcomeView.swift | 2 +- README.md | 2 +- 5 files changed, 70 insertions(+), 16 deletions(-) diff --git a/Code Awake Tests/AppLogicTests.swift b/Code Awake Tests/AppLogicTests.swift index c2c577a..04f537f 100644 --- a/Code Awake Tests/AppLogicTests.swift +++ b/Code Awake Tests/AppLogicTests.swift @@ -9,6 +9,26 @@ import XCTest @testable import Code_Awake final class AppLogicTests: XCTestCase { + func testLockAndDisplaySleepPolicyLocksBeforeSleepingDisplays() { + XCTAssertEqual( + LockAndDisplaySleepPolicy().commands, + [ + SystemToolCommand( + executablePath: "/usr/bin/open", + arguments: ["-a", "ScreenSaverEngine"] + ), + SystemToolCommand( + executablePath: "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession", + arguments: ["-suspend"] + ), + SystemToolCommand( + executablePath: "/usr/bin/pmset", + arguments: ["displaysleepnow"] + ) + ] + ) + } + func testAwakeAssertionPolicyLetsMacOSLockAndDisplaySleepWhenEnabled() { let policy = AwakeAssertionPolicy() diff --git a/Code Awake/AppLogic.swift b/Code Awake/AppLogic.swift index ef42ad3..2ec8821 100644 --- a/Code Awake/AppLogic.swift +++ b/Code Awake/AppLogic.swift @@ -12,6 +12,28 @@ struct AwakePowerAssertion: Equatable { let reason: String } +struct SystemToolCommand: Equatable { + let executablePath: String + let arguments: [String] +} + +struct LockAndDisplaySleepPolicy { + let commands = [ + SystemToolCommand( + executablePath: "/usr/bin/open", + arguments: ["-a", "ScreenSaverEngine"] + ), + SystemToolCommand( + executablePath: "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession", + arguments: ["-suspend"] + ), + SystemToolCommand( + executablePath: "/usr/bin/pmset", + arguments: ["displaysleepnow"] + ) + ] +} + struct AwakeAssertionPolicy { private let systemAssertions = [ AwakePowerAssertion( diff --git a/Code Awake/Code_AwakeApp.swift b/Code Awake/Code_AwakeApp.swift index bc017dd..c14fad4 100644 --- a/Code Awake/Code_AwakeApp.swift +++ b/Code Awake/Code_AwakeApp.swift @@ -40,7 +40,7 @@ struct Code_AwakeApp: App { awakeController: awakeController, launchAtLoginController: launchAtLoginController, updateAction: checkForUpdatesManually, - lockScreenAction: lockScreenNow, + lockScreenAction: lockScreenAndSleepDisplaysNow, donateAction: openDonatePage, quitAction: { awakeController.shutdown() @@ -98,24 +98,36 @@ struct Code_AwakeApp: App { NSWorkspace.shared.open(url) } - private func lockScreenNow() { - launchSystemTool("/usr/bin/open", arguments: ["-a", "ScreenSaverEngine"]) - launchSystemTool( - "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession", - arguments: ["-suspend"] - ) + private func lockScreenAndSleepDisplaysNow() { + runSystemTools(LockAndDisplaySleepPolicy().commands[...]) } - private func launchSystemTool(_ path: String, arguments: [String]) { - guard FileManager.default.isExecutableFile(atPath: path) else { + private func runSystemTools(_ commands: ArraySlice) { + guard let command = commands.first else { + return + } + + let remainingCommands = commands.dropFirst() + + guard FileManager.default.isExecutableFile(atPath: command.executablePath) else { + runSystemTools(remainingCommands) return } let process = Process() - process.executableURL = URL(fileURLWithPath: path) - process.arguments = arguments + process.executableURL = URL(fileURLWithPath: command.executablePath) + process.arguments = command.arguments + process.terminationHandler = { _ in + DispatchQueue.main.async { + runSystemTools(remainingCommands) + } + } - try? process.run() + do { + try process.run() + } catch { + runSystemTools(remainingCommands) + } } } @@ -155,7 +167,7 @@ private struct CodeAwakeMenuPanel: View { toggleAction: { awakeController.setAllowLockAndSleepEnabled(!awakeController.allowLockAndSleepEnabled) }, - tooltip: "Allow macOS to lock the screen or sleep while Code Awake is active.", + tooltip: "Use the normal macOS lock and display-sleep schedule while Code Awake keeps the Mac running.", lockAction: lockScreenAction ) @@ -399,7 +411,7 @@ private struct MenuLockSleepRow: View { .clipShape(Circle()) } .buttonStyle(.plain) - .help("Lock your screen now.") + .help("Lock the screen and turn displays off now.") .transition(.scale.combined(with: .opacity)) } diff --git a/Code Awake/WelcomeView.swift b/Code Awake/WelcomeView.swift index e3fc056..8f154db 100644 --- a/Code Awake/WelcomeView.swift +++ b/Code Awake/WelcomeView.swift @@ -128,7 +128,7 @@ struct WelcomeView: View { UseCaseRow( icon: "lock", title: "Optional Lock & Sleep", - detail: "Choose between keeping the display/session awake or letting macOS lock and sleep the display while work continues." + detail: "Choose between keeping the display/session awake or following the normal macOS lock and display-sleep schedule while work continues." ) UseCaseRow( diff --git a/README.md b/README.md index a700ede..051c9b7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The app uses public Apple power-management APIs and Sparkle for direct-distribut | Feature | Details | | --- | --- | | Keep Mac Awake | Prevents idle system sleep and keeps network client sessions active. | -| Lock & Sleep | Off keeps display sleep and lock away, then dims supported displays to 10% after one minute. On lets macOS lock and display-sleep timing work while the Mac stays awake. | +| Lock & Sleep | Off keeps display sleep and lock away, then dims supported displays to 10% after one minute. On follows the normal macOS lock and display-sleep schedule while the Mac stays awake. Its lock button locks the screen and sleeps displays immediately. | | Closed Lid Workflows | Best-effort support when macOS allows closed-lid wake behavior, such as supported clamshell conditions. | | Auto Turn Off | Optional timer for turning awake mode off automatically. | | Launch at Login | Starts Code Awake with macOS so the menu bar control is ready. |