Zum Inhalt

native/macos/KuroMetalApp/LoginItem.swift

// LoginItem — enable/disable a per-user LaunchAgent that runs this app at login.
// Two flavours: the idle-watcher `--agent` (start-at-login, honours an explicit Quit)
// and the `--wallpaper` desktop background (KeepAlive so it restarts if it dies).
// Classic launchctl approach (reliable + inspectable).

import Foundation

enum LoginItem {
    static let agentLabel = "com.kuro.screensaver.agent"
    static let wallpaperLabel = "com.kuro.screensaver.wallpaper"

    // ── generic ────────────────────────────────────────────────────────────
    static func plistURL(_ label: String) -> URL {
        FileManager.default.homeDirectoryForCurrentUser
            .appendingPathComponent("Library/LaunchAgents/\(label).plist")
    }

    static func isEnabled(_ label: String) -> Bool {
        FileManager.default.fileExists(atPath: plistURL(label).path)
    }

    /// Write (+ optionally start now) a login agent. `bootstrapNow == false` only
    /// installs the plist (it loads at the next login) — used when the caller already
    /// hosts the process in-proc and just wants login persistence (no double launch).
    static func enable(_ label: String, args: [String], keepAlive: Bool, bootstrapNow: Bool = true) {
        let exe = Bundle.main.executablePath ?? CommandLine.arguments[0]
        let plist: [String: Any] = [
            "Label": label,
            "ProgramArguments": [exe] + args,
            "RunAtLoad": true,
            "KeepAlive": keepAlive,
            "ProcessType": "Interactive",
        ]
        let url = plistURL(label)
        try? FileManager.default.createDirectory(
            at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
        if let data = try? PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) {
            try? data.write(to: url)
        }
        launchctl("bootout", "gui/\(getuid())/\(label)")          // clear any running instance
        if bootstrapNow { launchctl("bootstrap", "gui/\(getuid())", url.path) }
    }

    static func disable(_ label: String) {
        launchctl("bootout", "gui/\(getuid())/\(label)")
        try? FileManager.default.removeItem(at: plistURL(label))
    }

    // ── agent (idle watcher) — existing callers ────────────────────────────
    static var isEnabled: Bool { isEnabled(agentLabel) }
    static func enable() { enable(agentLabel, args: ["--agent"], keepAlive: false) }
    static func disable() { disable(agentLabel) }

    // ── wallpaper (desktop background) ─────────────────────────────────────
    static var wallpaperEnabled: Bool { isEnabled(wallpaperLabel) }
    static func enableWallpaper(bootstrapNow: Bool = true) { enable(wallpaperLabel, args: ["--wallpaper"], keepAlive: true, bootstrapNow: bootstrapNow) }
    static func disableWallpaper() { disable(wallpaperLabel) }

    private static func launchctl(_ args: String...) {
        let t = Process()
        t.executableURL = URL(fileURLWithPath: "/bin/launchctl")
        t.arguments = args
        try? t.run(); t.waitUntilExit()
    }
}