From e24960f455febe40bedc77bce232dc11a97e9d14 Mon Sep 17 00:00:00 2001 From: Nathan Nutter Date: Sat, 3 Oct 2026 18:41:57 -0500 Subject: [PATCH] Add Open at Login toggle to status-item menu Uses SMAppService.mainApp so the right-click menu shows a checkable Open at Login item; failures post a notification instead of failing silently. --- README.md | 3 ++- Sources/Timber/TimberApp.swift | 23 +++++++++++++++++++++++ Sources/Timber/TimberLoginItem.swift | 20 ++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 Sources/Timber/TimberLoginItem.swift diff --git a/README.md b/README.md index f7335cc..818cb6d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ managed Git worktrees. Requires the `timber` CLI and an Xcode toolchain. - Failures post a macOS notification; never silently. - Keyboard: Up/Down move, Return activates, Esc closes form / clears filter, Ctrl+U clears. -- Right-click (or Ctrl-click) the menubar icon for a Quit menu; +- Right-click (or Ctrl-click) the menubar icon for an Open at Login toggle + and Quit; left-click toggles the popover. Notes on parity: enumeration is implemented natively with FileManager diff --git a/Sources/Timber/TimberApp.swift b/Sources/Timber/TimberApp.swift index 428661b..8ea1db6 100644 --- a/Sources/Timber/TimberApp.swift +++ b/Sources/Timber/TimberApp.swift @@ -210,6 +210,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate { guard let button = statusItem?.button, let popover else { return } popover.performClose(nil) let menu = NSMenu() + let loginItem = NSMenuItem( + title: "Open at Login", + action: #selector(toggleOpenAtLogin(_:)), + keyEquivalent: "" + ) + loginItem.target = self + loginItem.state = TimberLoginItem.isEnabled ? .on : .off + menu.addItem(loginItem) + menu.addItem(.separator()) menu.addItem(NSMenuItem( title: "Quit Timber", action: #selector(NSApplication.terminate(_:)), @@ -217,6 +226,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate { )) menu.popUp(positioning: nil, at: NSPoint(x: 0, y: button.bounds.height), in: button) } + + @objc private func toggleOpenAtLogin(_ sender: NSMenuItem) { + let enabled = sender.state != .on + do { + try TimberLoginItem.setEnabled(enabled) + sender.state = enabled ? .on : .off + } catch { + TimberCommand.notify( + subject: enabled ? "open at login" : "remove from login", + detail: error.localizedDescription, + failure: true + ) + } + } } // Note: there is deliberately no SwiftUI App scene here. The entry point diff --git a/Sources/Timber/TimberLoginItem.swift b/Sources/Timber/TimberLoginItem.swift new file mode 100644 index 0000000..1bfa2d3 --- /dev/null +++ b/Sources/Timber/TimberLoginItem.swift @@ -0,0 +1,20 @@ +import Foundation +import ServiceManagement + +/// Login-item wrapper around SMAppService.mainApp so the status-item menu +/// can offer an "Open at Login" toggle. Thin on purpose: the only +/// behavior is reading the current status and (un)registering, with +/// errors propagated so callers can notify instead of failing silently. +enum TimberLoginItem { + static var isEnabled: Bool { + SMAppService.mainApp.status == .enabled + } + + static func setEnabled(_ enabled: Bool) throws { + if enabled { + try SMAppService.mainApp.register() + } else { + try SMAppService.mainApp.unregister() + } + } +}