From 39fe7e07533c5f6770dee66a33a53eb152f69a01 Mon Sep 17 00:00:00 2001 From: TheEveEye Date: Sun, 13 Jul 2025 13:27:48 +0200 Subject: [PATCH] Add launch at login option --- Overview/OverviewAppDelegate.swift | 2 + Overview/Settings/LaunchSettingsKeys.swift | 12 ++++ .../Settings/Services/DiagnosticService.swift | 4 +- .../Settings/Services/LoginItemService.swift | 60 +++++++++++++++++++ Overview/Settings/SettingsManager.swift | 5 +- .../Window/Settings/WindowSettingsTab.swift | 5 ++ 6 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 Overview/Settings/LaunchSettingsKeys.swift create mode 100644 Overview/Settings/Services/LoginItemService.swift diff --git a/Overview/OverviewAppDelegate.swift b/Overview/OverviewAppDelegate.swift index 20e7d2b2..6a9be257 100644 --- a/Overview/OverviewAppDelegate.swift +++ b/Overview/OverviewAppDelegate.swift @@ -7,6 +7,7 @@ The application delegate managing global state coordination and window management. */ +import Defaults import Sparkle import SwiftUI @@ -61,6 +62,7 @@ final class OverviewAppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { logger.debug("Application finished launching") NSApp.setActivationPolicy(.accessory) + LoginItemService.shared.setLaunchAtLogin(enabled: Defaults[.launchAtLogin]) NotificationCenter.default.addObserver( self, diff --git a/Overview/Settings/LaunchSettingsKeys.swift b/Overview/Settings/LaunchSettingsKeys.swift new file mode 100644 index 00000000..2d748a18 --- /dev/null +++ b/Overview/Settings/LaunchSettingsKeys.swift @@ -0,0 +1,12 @@ +/* + Settings/LaunchSettingsKeys.swift + Overview + + Created by William Pierce on 3/19/25. +*/ + +import Defaults + +extension Defaults.Keys { + static let launchAtLogin = Key("launchAtLogin", default: false) +} diff --git a/Overview/Settings/Services/DiagnosticService.swift b/Overview/Settings/Services/DiagnosticService.swift index 7257cbff..68480aaf 100644 --- a/Overview/Settings/Services/DiagnosticService.swift +++ b/Overview/Settings/Services/DiagnosticService.swift @@ -205,7 +205,8 @@ final class DiagnosticService { createOnLaunch: Defaults[.createOnLaunch], closeWithSource: Defaults[.closeOnCaptureStop], saveWindowsOnQuit: Defaults[.saveWindowsOnQuit], - restoreWindowsOnLaunch: Defaults[.restoreWindowsOnLaunch] + restoreWindowsOnLaunch: Defaults[.restoreWindowsOnLaunch], + launchAtLogin: Defaults[.launchAtLogin] ), overlay: OverlaySettings( focusBorder: FocusBorderSettings( @@ -513,6 +514,7 @@ struct WindowSettings: Codable { let closeWithSource: Bool let saveWindowsOnQuit: Bool let restoreWindowsOnLaunch: Bool + let launchAtLogin: Bool } struct OverlaySettings: Codable { diff --git a/Overview/Settings/Services/LoginItemService.swift b/Overview/Settings/Services/LoginItemService.swift new file mode 100644 index 00000000..3ae8be25 --- /dev/null +++ b/Overview/Settings/Services/LoginItemService.swift @@ -0,0 +1,60 @@ +/* + Settings/Services/LoginItemService.swift + Overview + + Created by William Pierce on 3/19/25. + + Manages enabling or disabling Overview as a login item by + creating or removing a LaunchAgent in the user's Library. +*/ + +import Foundation + +@MainActor +final class LoginItemService: ObservableObject { + static let shared = LoginItemService() + private let logger = AppLogger.settings + + private init() {} + + func setLaunchAtLogin(enabled: Bool) { + do { + if enabled { + try createLaunchAgent() + logger.info("Enabled launch at login") + } else { + try removeLaunchAgent() + logger.info("Disabled launch at login") + } + } catch { + logger.logError(error, context: "Updating launch at login") + } + } + + private var agentURL: URL { + let bundleID = Bundle.main.bundleIdentifier ?? "com.overview" + return FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent("Library/LaunchAgents") + .appendingPathComponent("\(bundleID).launchAgent.plist") + } + + private func createLaunchAgent() throws { + let directory = agentURL.deletingLastPathComponent() + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + + let contents: [String: Any] = [ + "Label": Bundle.main.bundleIdentifier ?? "com.overview", + "ProgramArguments": [Bundle.main.bundlePath], + "RunAtLoad": true + ] + + let data = try PropertyListSerialization.data(fromPropertyList: contents, format: .xml, options: 0) + try data.write(to: agentURL, options: .atomic) + } + + private func removeLaunchAgent() throws { + if FileManager.default.fileExists(atPath: agentURL.path) { + try FileManager.default.removeItem(at: agentURL) + } + } +} diff --git a/Overview/Settings/SettingsManager.swift b/Overview/Settings/SettingsManager.swift index 643ef26d..69f24d98 100644 --- a/Overview/Settings/SettingsManager.swift +++ b/Overview/Settings/SettingsManager.swift @@ -53,7 +53,8 @@ final class SettingsManager: ObservableObject { .closeOnCaptureStop, .assignPreviewsToAllDesktops, .saveWindowsOnQuit, - .restoreWindowsOnLaunch + .restoreWindowsOnLaunch, + .launchAtLogin ) /// Reset Overlay settings @@ -89,6 +90,8 @@ final class SettingsManager: ObservableObject { .appFilterNames ) + LoginItemService.shared.setLaunchAtLogin(enabled: Defaults[.launchAtLogin]) + logger.info("Settings reset completed successfully") } diff --git a/Overview/Window/Settings/WindowSettingsTab.swift b/Overview/Window/Settings/WindowSettingsTab.swift index 4045b392..c2f40fc5 100644 --- a/Overview/Window/Settings/WindowSettingsTab.swift +++ b/Overview/Window/Settings/WindowSettingsTab.swift @@ -18,6 +18,7 @@ struct WindowSettingsTab: View { @Default(.windowOpacity) private var windowOpacity @Default(.defaultWindowWidth) private var defaultWindowWidth @Default(.defaultWindowHeight) private var defaultWindowHeight + @Default(.launchAtLogin) private var launchAtLogin var body: some View { Form { @@ -126,6 +127,10 @@ struct WindowSettingsTab: View { Defaults.Toggle("Save window positions on quit", key: .saveWindowsOnQuit) Defaults.Toggle( "Restore window positions on launch", key: .restoreWindowsOnLaunch) + Toggle("Launch Overview on login", isOn: $launchAtLogin) + .onChange(of: launchAtLogin) { newValue in + LoginItemService.shared.setLaunchAtLogin(enabled: newValue) + } } } }