import Foundation import AppKit /// Central registry of all user-configurable notch settings. enum NotchSettings { enum Keys { // General static let showOnAllDisplays = "showOnAllDisplays" static let openNotchOnHover = "openNotchOnHover" static let minimumHoverDuration = "minimumHoverDuration" static let showMenuBarIcon = "showMenuBarIcon" static let launchAtLogin = "launchAtLogin" // Sizing — closed state static let notchHeight = "notchHeight" static let nonNotchHeight = "nonNotchHeight" static let notchHeightMode = "notchHeightMode" static let nonNotchHeightMode = "nonNotchHeightMode" // Sizing — open state static let openWidth = "openWidth" static let openHeight = "openHeight" // Appearance static let enableShadow = "enableShadow" static let shadowRadius = "shadowRadius" static let shadowOpacity = "shadowOpacity" static let cornerRadiusScaling = "cornerRadiusScaling" static let notchOpacity = "notchOpacity" static let blurRadius = "blurRadius" // Animation static let openSpringResponse = "openSpringResponse" static let openSpringDamping = "openSpringDamping" static let closeSpringResponse = "closeSpringResponse" static let closeSpringDamping = "closeSpringDamping" static let hoverSpringResponse = "hoverSpringResponse" static let hoverSpringDamping = "hoverSpringDamping" // Behavior static let enableGestures = "enableGestures" static let gestureSensitivity = "gestureSensitivity" // Terminal static let terminalFontSize = "terminalFontSize" static let terminalShell = "terminalShell" static let terminalTheme = "terminalTheme" static let terminalSizePresets = "terminalSizePresets" // Hotkeys — each stores a HotkeyBinding JSON string static let hotkeyToggle = "hotkey_toggle" static let hotkeyNewTab = "hotkey_newTab" static let hotkeyCloseTab = "hotkey_closeTab" static let hotkeyNextTab = "hotkey_nextTab" static let hotkeyPreviousTab = "hotkey_previousTab" static let hotkeyDetachTab = "hotkey_detachTab" } enum Defaults { static let showOnAllDisplays: Bool = true static let openNotchOnHover: Bool = true static let minimumHoverDuration: Double = 0.3 static let showMenuBarIcon: Bool = true static let launchAtLogin: Bool = false static let notchHeight: Double = 32 static let nonNotchHeight: Double = 32 static let notchHeightMode: Int = 0 static let nonNotchHeightMode: Int = 1 static let openWidth: Double = 640 static let openHeight: Double = 350 static let enableShadow: Bool = true static let shadowRadius: Double = 6 static let shadowOpacity: Double = 0.5 static let cornerRadiusScaling: Bool = true static let notchOpacity: Double = 1.0 static let blurRadius: Double = 0 static let openSpringResponse: Double = 0.42 static let openSpringDamping: Double = 0.8 static let closeSpringResponse: Double = 0.45 static let closeSpringDamping: Double = 1.0 static let hoverSpringResponse: Double = 0.38 static let hoverSpringDamping: Double = 0.8 static let enableGestures: Bool = true static let gestureSensitivity: Double = 0.5 static let terminalFontSize: Double = 13 static let terminalShell: String = "" static let terminalTheme: String = TerminalTheme.terminalApp.rawValue static let terminalSizePresets: String = TerminalSizePresetStore.defaultPresetsJSON() // Default hotkey bindings as JSON static let hotkeyToggle: String = HotkeyBinding.cmdReturn.toJSON() static let hotkeyNewTab: String = HotkeyBinding.cmdT.toJSON() static let hotkeyCloseTab: String = HotkeyBinding.cmdW.toJSON() static let hotkeyNextTab: String = HotkeyBinding.cmdShiftRB.toJSON() static let hotkeyPreviousTab: String = HotkeyBinding.cmdShiftLB.toJSON() static let hotkeyDetachTab: String = HotkeyBinding.cmdD.toJSON() } static func registerDefaults() { UserDefaults.standard.register(defaults: [ Keys.showOnAllDisplays: Defaults.showOnAllDisplays, Keys.openNotchOnHover: Defaults.openNotchOnHover, Keys.minimumHoverDuration: Defaults.minimumHoverDuration, Keys.showMenuBarIcon: Defaults.showMenuBarIcon, Keys.launchAtLogin: Defaults.launchAtLogin, Keys.notchHeight: Defaults.notchHeight, Keys.nonNotchHeight: Defaults.nonNotchHeight, Keys.notchHeightMode: Defaults.notchHeightMode, Keys.nonNotchHeightMode: Defaults.nonNotchHeightMode, Keys.openWidth: Defaults.openWidth, Keys.openHeight: Defaults.openHeight, Keys.enableShadow: Defaults.enableShadow, Keys.shadowRadius: Defaults.shadowRadius, Keys.shadowOpacity: Defaults.shadowOpacity, Keys.cornerRadiusScaling: Defaults.cornerRadiusScaling, Keys.notchOpacity: Defaults.notchOpacity, Keys.blurRadius: Defaults.blurRadius, Keys.openSpringResponse: Defaults.openSpringResponse, Keys.openSpringDamping: Defaults.openSpringDamping, Keys.closeSpringResponse: Defaults.closeSpringResponse, Keys.closeSpringDamping: Defaults.closeSpringDamping, Keys.hoverSpringResponse: Defaults.hoverSpringResponse, Keys.hoverSpringDamping: Defaults.hoverSpringDamping, Keys.enableGestures: Defaults.enableGestures, Keys.gestureSensitivity: Defaults.gestureSensitivity, Keys.terminalFontSize: Defaults.terminalFontSize, Keys.terminalShell: Defaults.terminalShell, Keys.terminalTheme: Defaults.terminalTheme, Keys.terminalSizePresets: Defaults.terminalSizePresets, Keys.hotkeyToggle: Defaults.hotkeyToggle, Keys.hotkeyNewTab: Defaults.hotkeyNewTab, Keys.hotkeyCloseTab: Defaults.hotkeyCloseTab, Keys.hotkeyNextTab: Defaults.hotkeyNextTab, Keys.hotkeyPreviousTab: Defaults.hotkeyPreviousTab, Keys.hotkeyDetachTab: Defaults.hotkeyDetachTab, ]) } } enum NotchHeightMode: Int, CaseIterable, Identifiable { case matchRealNotchSize = 0 case matchMenuBar = 1 case custom = 2 var id: Int { rawValue } var label: String { switch self { case .matchRealNotchSize: return "Match Notch" case .matchMenuBar: return "Match Menu Bar" case .custom: return "Custom" } } } enum NonNotchHeightMode: Int, CaseIterable, Identifiable { case matchMenuBar = 1 case custom = 2 var id: Int { rawValue } var label: String { switch self { case .matchMenuBar: return "Match Menu Bar" case .custom: return "Custom" } } } struct TerminalSizePreset: Codable, Equatable, Identifiable { var id: UUID var name: String var width: Double var height: Double var hotkey: HotkeyBinding? init( id: UUID = UUID(), name: String, width: Double, height: Double, hotkey: HotkeyBinding? = nil ) { self.id = id self.name = name self.width = width self.height = height self.hotkey = hotkey } var size: CGSize { CGSize(width: width, height: height) } } enum TerminalSizePresetStore { static func load() -> [TerminalSizePreset] { let defaults = UserDefaults.standard guard let json = defaults.string(forKey: NotchSettings.Keys.terminalSizePresets), let data = json.data(using: .utf8), let presets = try? JSONDecoder().decode([TerminalSizePreset].self, from: data) else { return defaultPresets() } return presets } static func save(_ presets: [TerminalSizePreset]) { guard let data = try? JSONEncoder().encode(presets), let json = String(data: data, encoding: .utf8) else { return } UserDefaults.standard.set(json, forKey: NotchSettings.Keys.terminalSizePresets) } static func reset() { save(defaultPresets()) } static func loadDefaults() -> [TerminalSizePreset] { defaultPresets() } static func defaultPresetsJSON() -> String { guard let data = try? JSONEncoder().encode(defaultPresets()), let json = String(data: data, encoding: .utf8) else { return "[]" } return json } static func suggestedHotkey(for presets: [TerminalSizePreset]) -> HotkeyBinding? { let used = Set(presets.compactMap(\.hotkey)) for digit in 1...9 { guard let candidate = HotkeyBinding.cmdShiftDigit(digit) else { continue } if !used.contains(candidate) { return candidate } } return nil } private static func defaultPresets() -> [TerminalSizePreset] { [ TerminalSizePreset(name: "Compact", width: 480, height: 220, hotkey: HotkeyBinding.cmdShiftDigit(1)), TerminalSizePreset(name: "Default", width: 640, height: 350, hotkey: HotkeyBinding.cmdShiftDigit(2)), TerminalSizePreset(name: "Large", width: 900, height: 500, hotkey: HotkeyBinding.cmdShiftDigit(3)), ] } }