feat: add launch-at-login settings and Sunset styling

This commit is contained in:
2026-10-12 13:59:40 +02:00
parent 37f6cab256
commit fa4cf9c154
2 changed files with 89 additions and 6 deletions
+85 -4
View File
@@ -1,5 +1,6 @@
import AppKit
import ConjureKit
import ServiceManagement
@MainActor
final class SettingsWindowController: NSWindowController, NSWindowDelegate {
@@ -8,16 +9,24 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
private let clearButton = NSButton(title: "Clear", target: nil, action: nil)
private let statusLabel = NSTextField(wrappingLabelWithString: "")
private let errorLabel = NSTextField(wrappingLabelWithString: "")
private let loginToggle = NSButton(checkboxWithTitle: "Launch at login", target: nil, action: nil)
private let loginStatus = NSTextField(wrappingLabelWithString: "")
private let loginSettings = NSButton(title: "Open Login Items Settings…", target: nil, action: nil)
init(shortcut: GlobalShortcut) {
self.shortcut = shortcut
let window = ShortcutSettingsWindow(
contentRect: NSRect(x: 0, y: 0, width: 580, height: 520),
contentRect: NSRect(x: 0, y: 0, width: 620, height: 640),
styleMask: [.titled, .closable, .miniaturizable],
backing: .buffered,
defer: false
)
window.title = "Conjure Settings"
window.appearance = NSAppearance(named: .darkAqua)
window.titlebarAppearsTransparent = true
window.backgroundColor = .clear
window.isOpaque = false
window.isMovableByWindowBackground = true
window.isReleasedWhenClosed = false
window.center()
super.init(window: window)
@@ -43,6 +52,10 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
window?.makeKeyAndOrderFront(nil)
}
func windowDidBecomeKey(_ notification: Notification) {
refreshLogin()
}
func windowDidResignKey(_ notification: Notification) {
cancelRecording()
}
@@ -94,15 +107,44 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
)
keyboardSettings.bezelStyle = .rounded
loginToggle.target = self
loginToggle.action = #selector(toggleLogin)
loginSettings.target = self
loginSettings.action = #selector(openLoginSettings)
loginSettings.bezelStyle = .rounded
loginStatus.setAccessibilityLabel("Launch at login status")
heading.font = .systemFont(ofSize: 20, weight: .semibold)
heading.textColor = SunsetPalette.text
for label in [instructions, statusLabel, spotlight, loginStatus] {
label.textColor = SunsetPalette.secondaryText
}
for button in [recorder, commandSpaceButton, clearButton, keyboardSettings, loginToggle, loginSettings] {
button.contentTintColor = SunsetPalette.accent
}
let loginHeading = NSTextField(labelWithString: "Startup")
loginHeading.font = .systemFont(ofSize: 20, weight: .semibold)
loginHeading.textColor = SunsetPalette.text
let divider = NSBox()
divider.boxType = .separator
let stack = NSStackView(views: [
heading, controls, instructions, statusLabel, errorLabel, spotlight, keyboardSettings
heading, controls, instructions, statusLabel, errorLabel, spotlight, keyboardSettings,
divider, loginHeading, loginToggle, loginStatus, loginSettings
])
stack.orientation = .vertical
stack.alignment = .leading
stack.spacing = 12
stack.translatesAutoresizingMaskIntoConstraints = false
let content = NSView()
let content = NSVisualEffectView()
content.material = .hudWindow
content.blendingMode = .behindWindow
content.state = .active
window.contentView = content
let background = LauncherBackground(frame: content.bounds)
background.autoresizingMask = [.width, .height]
background.setAccessibilityElement(false)
content.addSubview(background)
content.addSubview(stack)
NSLayoutConstraint.activate([
stack.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 24),
@@ -111,12 +153,51 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
stack.bottomAnchor.constraint(lessThanOrEqualTo: content.bottomAnchor, constant: -24),
recorder.widthAnchor.constraint(greaterThanOrEqualToConstant: 190)
])
for label in [instructions, statusLabel, errorLabel, spotlight] {
for label in [instructions, statusLabel, errorLabel, spotlight, loginStatus] {
label.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
}
}
private func refreshLogin() {
let status = SMAppService.mainApp.status
loginToggle.state = (status == .enabled || status == .requiresApproval) ? .on : .off
loginSettings.isHidden = status != .requiresApproval
switch status {
case .enabled:
loginStatus.stringValue = "Conjure starts in the background when you log in."
case .requiresApproval:
loginStatus.stringValue = "Allow Conjure in System Settings → General → Login Items & Extensions."
case .notRegistered:
loginStatus.stringValue = "Start Conjure automatically so your global shortcut is available."
case .notFound:
loginStatus.stringValue = "Install Conjure in Applications before enabling launch at login."
@unknown default:
loginStatus.stringValue = "Launch at login status is unavailable."
}
}
@objc private func toggleLogin() {
cancelRecording()
do {
if loginToggle.state == .on {
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
refreshLogin()
} catch {
refreshLogin()
loginStatus.stringValue = "Could not change launch at login: \(error.localizedDescription)"
}
}
@objc private func openLoginSettings() {
cancelRecording()
SMAppService.openSystemSettingsLoginItems()
}
private func refresh() {
refreshLogin()
let name = shortcut.shortcut?.displayName
recorder.title = recorder.isRecording ? "Type shortcut…" : (name ?? "Record Shortcut…")
recorder.setAccessibilityValue(name ?? "Not set")