diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index 44f5ca0..89cb503 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -32,7 +32,6 @@ 504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; }; 504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; }; 50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; }; - 50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; }; 50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; }; 50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; }; 50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; }; @@ -194,7 +193,6 @@ 504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = ""; }; 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = ""; }; 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = ""; }; - 50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = ""; }; 5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = ""; }; 50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50617D8223FCE48E0099B055 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; @@ -448,7 +446,6 @@ 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */, 5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */, 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */, - 50571E0424393D1500F76F6C /* LaunchAgentController.swift */, ); path = Controllers; sourceTree = ""; @@ -707,7 +704,6 @@ 5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */, 50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */, 50153E20250AFCB200525160 /* UpdateView.swift in Sources */, - 50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */, 5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */, 50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */, 50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */, diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 4c3dff1..c13dadd 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -7,7 +7,7 @@ import Brief @main struct Secretive: App { - @Environment(\.agentStatusChecker) var agentStatusChecker + @Environment(\.agentLaunchController) var agentLaunchController @Environment(\.justUpdatedChecker) var justUpdatedChecker @SceneBuilder var body: some Scene { @@ -15,14 +15,15 @@ struct Secretive: App { ContentView() .environment(EnvironmentValues._secretStoreList) .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in - @AppStorage("defaultsHasRunSetup") var hasRunSetup = false - guard hasRunSetup else { return } - agentStatusChecker.check() - if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild { - // Relaunch the agent, since it'll be running from earlier update still - reinstallAgent() - } else if !agentStatusChecker.running && !agentStatusChecker.developmentBuild { - forceLaunchAgent() + Task { + @AppStorage("defaultsHasRunSetup") var hasRunSetup = false + guard hasRunSetup else { return } + agentLaunchController.check() + guard !agentLaunchController.developmentBuild else { return } + if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running { + // Relaunch the agent, since it'll be running from earlier update still + try await agentLaunchController.forceLaunch() + } } } } @@ -79,30 +80,6 @@ extension Secretive { } -extension Secretive { - - private func reinstallAgent() { - Task { - _ = await LaunchAgentController().install() - try? await Task.sleep(for: .seconds(1)) - agentStatusChecker.check() - if !agentStatusChecker.running { - forceLaunchAgent() - } - } - } - - private func forceLaunchAgent() { - // We've run setup, we didn't just update, launchd is just not doing it's thing. - // Force a launch directly. - Task { - _ = await LaunchAgentController().forceLaunch() - agentStatusChecker.check() - } - } - -} - private enum Constants { static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")! } @@ -121,8 +98,8 @@ extension EnvironmentValues { return list }() - private static let _agentStatusChecker = AgentStatusChecker() - @Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker + private static let _agentLaunchController = AgentLaunchController() + @Entry var agentLaunchController: any AgentLaunchControllerProtocol = _agentLaunchController private static let _updater: any UpdaterProtocol = { @AppStorage("defaultsHasRunSetup") var hasRunSetup = false return Updater(checkOnLaunch: hasRunSetup) diff --git a/Sources/Secretive/Controllers/AgentStatusChecker.swift b/Sources/Secretive/Controllers/AgentStatusChecker.swift index b7327a6..63962ac 100644 --- a/Sources/Secretive/Controllers/AgentStatusChecker.swift +++ b/Sources/Secretive/Controllers/AgentStatusChecker.swift @@ -2,18 +2,25 @@ import Foundation import AppKit import SecretKit import Observation +import OSLog +import ServiceManagement -@MainActor protocol AgentStatusCheckerProtocol: Observable, Sendable { +@MainActor protocol AgentLaunchControllerProtocol: Observable, Sendable { var running: Bool { get } var developmentBuild: Bool { get } var process: NSRunningApplication? { get } func check() + func install() async throws + func uninstall() async throws + func forceLaunch() async throws } -@Observable @MainActor final class AgentStatusChecker: AgentStatusCheckerProtocol { +@Observable @MainActor final class AgentLaunchController: AgentLaunchControllerProtocol { var running: Bool = false var process: NSRunningApplication? = nil + private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController") + private let service = SMAppService.loginItem(identifier: Bundle.agentBundleID) nonisolated init() { Task { @MainActor in @@ -49,6 +56,47 @@ import Observation Bundle.main.bundleURL.isXcodeURL } + func install() async throws { + logger.debug("Installing agent") + try? await service.unregister() + // This is definitely a bit of a "seems to work better" thing but: + // Seems to more reliably hit if these are on separate runloops, otherwise it seems like it sometimes doesn't kill old + // and start new? + try await Task.sleep(for: .seconds(1)) + try service.register() + try await Task.sleep(for: .seconds(1)) + check() + } + + func uninstall() async throws { + logger.debug("Uninstalling agent") + try await Task.sleep(for: .seconds(1)) + try await service.unregister() + try await Task.sleep(for: .seconds(1)) + check() + } + + func forceLaunch() async throws { + logger.debug("Agent is not running, attempting to force launch by reinstalling") + try await install() + if running { + logger.debug("Agent successfully force launched by reinstalling") + return + } + logger.debug("Agent is not running, attempting to force launch by launching directly") + let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app") + let config = NSWorkspace.OpenConfiguration() + config.activates = false + do { + try await NSWorkspace.shared.openApplication(at: url, configuration: config) + logger.debug("Agent force launched") + try await Task.sleep(for: .seconds(1)) + } catch { + logger.error("Error force launching \(error.localizedDescription)") + } + check() + } + } extension URL { diff --git a/Sources/Secretive/Controllers/LaunchAgentController.swift b/Sources/Secretive/Controllers/LaunchAgentController.swift deleted file mode 100644 index 308c381..0000000 --- a/Sources/Secretive/Controllers/LaunchAgentController.swift +++ /dev/null @@ -1,65 +0,0 @@ -import Foundation -import ServiceManagement -import AppKit -import OSLog -import SecretKit - -struct LaunchAgentController { - - private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController") - - func install() async -> Bool { - logger.debug("Installing agent") - _ = setEnabled(false) - // This is definitely a bit of a "seems to work better" thing but: - // Seems to more reliably hit if these are on separate runloops, otherwise it seems like it sometimes doesn't kill old - // and start new? - try? await Task.sleep(for: .seconds(1)) - let result = await MainActor.run { - setEnabled(true) - } - try? await Task.sleep(for: .seconds(1)) - return result - } - - func uninstall() async -> Bool { - logger.debug("Uninstalling agent") - try? await Task.sleep(for: .seconds(1)) - let result = await MainActor.run { - setEnabled(false) - } - try? await Task.sleep(for: .seconds(1)) - return result - } - - func forceLaunch() async -> Bool { - logger.debug("Agent is not running, attempting to force launch") - let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app") - let config = NSWorkspace.OpenConfiguration() - config.activates = false - do { - try await NSWorkspace.shared.openApplication(at: url, configuration: config) - logger.debug("Agent force launched") - try? await Task.sleep(for: .seconds(1)) - return true - } catch { - logger.error("Error force launching \(error.localizedDescription)") - return false - } - } - - private func setEnabled(_ enabled: Bool) -> Bool { - let service = SMAppService.loginItem(identifier: Bundle.agentBundleID) - do { - if enabled { - try service.register() - } else { - try service.unregister() - } - return true - } catch { - return false - } - } - -} diff --git a/Sources/Secretive/Preview Content/PreviewAgentStatusChecker.swift b/Sources/Secretive/Preview Content/PreviewAgentStatusChecker.swift index e9799e9..97df864 100644 --- a/Sources/Secretive/Preview Content/PreviewAgentStatusChecker.swift +++ b/Sources/Secretive/Preview Content/PreviewAgentStatusChecker.swift @@ -1,7 +1,7 @@ import Foundation import AppKit -class PreviewAgentStatusChecker: AgentStatusCheckerProtocol { +class PreviewAgentLaunchController: AgentLaunchControllerProtocol { let running: Bool let process: NSRunningApplication? @@ -15,4 +15,13 @@ class PreviewAgentStatusChecker: AgentStatusCheckerProtocol { func check() { } + func install() async throws { + } + + func uninstall() async throws { + } + + func forceLaunch() async throws { + } + } diff --git a/Sources/Secretive/Views/Configuration/SetupView.swift b/Sources/Secretive/Views/Configuration/SetupView.swift index df55855..7616b56 100644 --- a/Sources/Secretive/Views/Configuration/SetupView.swift +++ b/Sources/Secretive/Views/Configuration/SetupView.swift @@ -3,6 +3,7 @@ import SwiftUI struct SetupView: View { @Environment(\.dismiss) private var dismiss + @Environment(\.agentLaunchController) private var agentLaunchController @Binding var setupComplete: Bool @State var showingIntegrations = false @@ -31,7 +32,7 @@ struct SetupView: View { ) { installed = true Task { - await LaunchAgentController().install() + try? await agentLaunchController.install() } } } diff --git a/Sources/Secretive/Views/Views/AgentStatusView.swift b/Sources/Secretive/Views/Views/AgentStatusView.swift index 50b50c9..3377772 100644 --- a/Sources/Secretive/Views/Views/AgentStatusView.swift +++ b/Sources/Secretive/Views/Views/AgentStatusView.swift @@ -2,10 +2,10 @@ import SwiftUI struct AgentStatusView: View { - @Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol + @Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol var body: some View { - if agentStatusChecker.running { + if agentLaunchController.running { AgentRunningView() } else { AgentNotRunningView() @@ -14,12 +14,12 @@ struct AgentStatusView: View { } struct AgentRunningView: View { - @Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol + @Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol var body: some View { Form { Section { - if let process = agentStatusChecker.process { + if let process = agentLaunchController.process { ConfigurationItemView( title: .agentDetailsLocationTitle, value: process.bundleURL!.path(), @@ -53,19 +53,13 @@ struct AgentRunningView: View { Menu(.agentDetailsRestartAgentButton) { Button(.agentDetailsDisableAgentButton) { Task { - _ = await LaunchAgentController() + try? await agentLaunchController .uninstall() - agentStatusChecker.check() } } } primaryAction: { Task { - let controller = LaunchAgentController() - let installed = await controller.install() - if !installed { - _ = await controller.forceLaunch() - } - agentStatusChecker.check() + try? await agentLaunchController.forceLaunch() } } } @@ -82,7 +76,7 @@ struct AgentRunningView: View { struct AgentNotRunningView: View { - @Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol + @Environment(\.agentLaunchController) private var agentLaunchController @State var triedRestart = false @State var loading = false @@ -103,15 +97,10 @@ struct AgentNotRunningView: View { guard !loading else { return } loading = true Task { - let controller = LaunchAgentController() - let installed = await controller.install() - if !installed { - _ = await controller.forceLaunch() - } - agentStatusChecker.check() + try await agentLaunchController.forceLaunch() loading = false - if !agentStatusChecker.running { + if !agentLaunchController.running { triedRestart = true } } @@ -145,9 +134,9 @@ struct AgentNotRunningView: View { //#Preview { // AgentStatusView() -// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: false)) +// .environment(\.agentLaunchController, PreviewAgentLaunchController(running: false)) //} //#Preview { // AgentStatusView() -// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: true, process: .current)) +// .environment(\.agentLaunchController, PreviewAgentLaunchController(running: true, process: .current)) //} diff --git a/Sources/Secretive/Views/Views/ContentView.swift b/Sources/Secretive/Views/Views/ContentView.swift index 7c395df..c44f0da 100644 --- a/Sources/Secretive/Views/Views/ContentView.swift +++ b/Sources/Secretive/Views/Views/ContentView.swift @@ -14,7 +14,7 @@ struct ContentView: View { @Environment(\.openWindow) private var openWindow @Environment(\.secretStoreList) private var storeList @Environment(\.updater) private var updater - @Environment(\.agentStatusChecker) private var agentStatusChecker + @Environment(\.agentLaunchController) private var agentLaunchController @AppStorage("defaultsHasRunSetup") private var hasRunSetup = false @State private var showingCreation = false @@ -127,7 +127,7 @@ extension ContentView { showingAgentInfo = true }, label: { HStack { - if agentStatusChecker.running { + if agentLaunchController.running { Text(.agentRunningNoticeTitle) .font(.headline) .foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white) @@ -145,8 +145,8 @@ extension ContentView { }) .buttonStyle( ToolbarStatusButtonStyle( - lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75), - darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5), + lightColor: agentLaunchController.running ? .black.opacity(0.05) : .red.opacity(0.75), + darkColor: agentLaunchController.running ? .white.opacity(0.05) : .red.opacity(0.5), ) ) .popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {