Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions Sources/ProcessBarMonitor/LegacyLaunchAgentCleaner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Foundation

struct LegacyLaunchAgentCleanupResult {
let removed: Bool
let messageKey: String?
let details: String?
}

enum LegacyLaunchAgentCleaner {
static let legacyLabel = "ai.openclaw.ProcessBarMonitor"

static func cleanupIfNeeded(
fileManager: FileManager = .default,
processRunner: ((String, [String]) throws -> Int32)? = nil
) -> LegacyLaunchAgentCleanupResult {
let plistPath = (NSHomeDirectory() as NSString).appendingPathComponent("Library/LaunchAgents/\(legacyLabel).plist")
guard fileManager.fileExists(atPath: plistPath) else {
return LegacyLaunchAgentCleanupResult(removed: false, messageKey: nil, details: nil)
}

let runner = processRunner ?? runProcess
do {
_ = try? runner("/bin/launchctl", ["bootout", "gui/\(getuid())", plistPath])
try fileManager.removeItem(atPath: plistPath)
return LegacyLaunchAgentCleanupResult(
removed: true,
messageKey: "status.legacy_launch_agent_removed",
details: nil
)
} catch {
return LegacyLaunchAgentCleanupResult(
removed: false,
messageKey: "status.legacy_launch_agent_remove_failed",
details: error.localizedDescription
)
}
}

private static func runProcess(_ launchPath: String, _ arguments: [String]) throws -> Int32 {
let process = Process()
process.executableURL = URL(fileURLWithPath: launchPath)
process.arguments = arguments
try process.run()
process.waitUntilExit()
return process.terminationStatus
}
}
11 changes: 11 additions & 0 deletions Sources/ProcessBarMonitor/MonitorViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ final class MonitorViewModel: ObservableObject {

func start() {
guard refreshTask == nil else { return }

let legacyCleanup = LegacyLaunchAgentCleaner.cleanupIfNeeded()
if let messageKey = legacyCleanup.messageKey {
if let details = legacyCleanup.details {
statusMessage = L10n.format(messageKey, details)
} else {
statusMessage = L10n.string(messageKey)
}
}
launchAtLogin.refreshState()

refreshTask = Task { [weak self] in
await self?.refresh(forceProcesses: true)
while !Task.isCancelled {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"status.failed_to_load_top_apps" = "Failed to load top apps: %@";
"status.launch_at_login_enabled" = "Launch at login enabled.";
"status.launch_at_login_disabled" = "Launch at login disabled.";
"status.legacy_launch_agent_removed" = "Removed a leftover legacy login LaunchAgent so the app will no longer start twice.";
"status.legacy_launch_agent_remove_failed" = "Found a leftover legacy login LaunchAgent but could not remove it automatically: %@";
"thermal.nominal" = "Nominal";
"thermal.fair" = "Fair";
"thermal.serious" = "Serious";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"status.failed_to_load_top_apps" = "加载 Top Apps 失败:%@";
"status.launch_at_login_enabled" = "已启用开机启动。";
"status.launch_at_login_disabled" = "已关闭开机启动。";
"status.legacy_launch_agent_removed" = "已清理遗留的旧版开机启动项,应用之后不会再因此重复启动。";
"status.legacy_launch_agent_remove_failed" = "发现遗留的旧版开机启动项,但自动清理失败:%@";
"thermal.nominal" = "正常";
"thermal.fair" = "一般";
"thermal.serious" = "较高";
Expand Down
Loading