mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-10-04 18:20:56 +00:00
109 lines
3.5 KiB
Swift
109 lines
3.5 KiB
Swift
import Foundation
|
|
import AppKit
|
|
import SecretKit
|
|
import Observation
|
|
import OSLog
|
|
import ServiceManagement
|
|
|
|
@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 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
|
|
check()
|
|
}
|
|
}
|
|
|
|
func check() {
|
|
process = instanceSecretAgentProcess
|
|
running = process != nil
|
|
}
|
|
|
|
// All processes, including ones from older versions, etc
|
|
var allSecretAgentProcesses: [NSRunningApplication] {
|
|
NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.agentBundleID)
|
|
}
|
|
|
|
// The process corresponding to this instance of Secretive
|
|
var instanceSecretAgentProcess: NSRunningApplication? {
|
|
// FIXME: CHECK VERSION
|
|
let agents = allSecretAgentProcesses
|
|
for agent in agents {
|
|
guard let url = agent.bundleURL else { continue }
|
|
if url.absoluteString.hasPrefix(Bundle.main.bundleURL.absoluteString) || (url.isXcodeURL && developmentBuild) {
|
|
return agent
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Whether Secretive is being run in an Xcode environment.
|
|
var developmentBuild: Bool {
|
|
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 {
|
|
|
|
var isXcodeURL: Bool {
|
|
absoluteString.contains("/Library/Developer/Xcode")
|
|
}
|
|
|
|
}
|