feat: add launch-at-login settings and Sunset styling
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user