/* * Atoll (DynamicIsland) * Copyright (C) 2024-2026 Atoll Contributors * * Originally from boring.notch project * Modified and adapted for Atoll (DynamicIsland) * See NOTICE for details. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import SwiftUI import Defaults import Lottie import Foundation private let availableDirectories = FileManager .default .urls(for: .documentDirectory, in: .userDomainMask) let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let bundleIdentifier = Bundle.main.bundleIdentifier! let appVersion = "\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "") (\(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""))" let temporaryDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! let spacing: CGFloat = 16 struct CustomVisualizer: Codable, Hashable, Equatable, Defaults.Serializable { let UUID: UUID var name: String var url: URL var speed: CGFloat = 1.0 } struct CustomAppIcon: Codable, Hashable, Equatable, Defaults.Serializable, Identifiable { let id: UUID var name: String var fileName: String var addedAt: Date init(id: UUID = UUID(), name: String, fileName: String, addedAt: Date = .now) { self.id = id self.name = name self.fileName = fileName self.addedAt = addedAt } static let iconDirectory: URL = { let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! let dir = base.appendingPathComponent("DynamicIsland", isDirectory: true) .appendingPathComponent("AppIcons", isDirectory: true) try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) return dir }() var fileURL: URL { Self.iconDirectory.appendingPathComponent(fileName) } } // MARK: - Custom Idle Animation Models struct CustomIdleAnimation: Codable, Hashable, Equatable, Defaults.Serializable, Identifiable { let id: UUID var name: String var source: AnimationSource var speed: CGFloat = 1.0 var isBuiltIn: Bool = false // Track if it's bundled vs user-added init(id: UUID = UUID(), name: String, source: AnimationSource, speed: CGFloat = 1.0, isBuiltIn: Bool = false) { self.id = id self.name = name self.source = source self.speed = speed self.isBuiltIn = isBuiltIn } /// Get the effective transform config (override or default) func getTransformConfig() -> AnimationTransformConfig { let override = Defaults[.animationTransformOverrides][id.uuidString] if let override = override { print("📋 [CustomIdleAnimation] Found override for '\(name)': \(override)") } else { print("📋 [CustomIdleAnimation] No override for '\(name)', using default") } return override ?? .default } } struct AnimationTransformConfig: Codable, Hashable, Equatable, Defaults.Serializable { var scale: CGFloat = 1.0 var offsetX: CGFloat = 0 var offsetY: CGFloat = 0 var cropWidth: CGFloat = 30 var cropHeight: CGFloat = 20 var rotation: CGFloat = 0 var opacity: CGFloat = 1.0 var paddingBottom: CGFloat = 0 // Allow adjustment to fill notch from bottom var expandWithAnimation: Bool = false // Whether notch should expand horizontally with animation var loopMode: AnimationLoopMode = .loop // Loop mode for animation static let `default` = AnimationTransformConfig() } enum AnimationLoopMode: String, Codable, CaseIterable { case loop = "Loop" case playOnce = "Play Once" case autoReverse = "Auto Reverse" var lottieLoopMode: LottieLoopMode { switch self { case .loop: return .loop case .playOnce: return .playOnce case .autoReverse: return .autoReverse } } } enum AnimationSource: Codable, Hashable, Equatable { case lottieFile(URL) // Local file (in app support or bundle) case lottieURL(URL) // Remote URL var displayType: String { switch self { case .lottieFile: return "Local" case .lottieURL: return "Remote" } } } // MARK: - Extension Authorization Models enum ExtensionPermissionScope: String, CaseIterable, Codable, Defaults.Serializable { case liveActivities case lockScreenWidgets case notchExperiences case fileSharing var displayName: String { switch self { case .liveActivities: return "Live Activities" case .lockScreenWidgets: return "Lock Screen Widgets" case .notchExperiences: return "Notch Experiences" case .fileSharing: return "File Sharing" } } } enum ExtensionAuthorizationStatus: String, CaseIterable, Codable, Defaults.Serializable { case pending case authorized case denied case revoked var isActive: Bool { switch self { case .authorized: return true case .pending, .denied, .revoked: return false } } } struct ExtensionAuthorizationEntry: Codable, Defaults.Serializable, Identifiable, Hashable { let bundleIdentifier: String var appName: String var status: ExtensionAuthorizationStatus var allowedScopes: Set var requestedAt: Date var grantedAt: Date? var lastActivityAt: Date? var lastDeniedReason: String? var notes: String? var id: String { bundleIdentifier } init( bundleIdentifier: String, appName: String, status: ExtensionAuthorizationStatus, allowedScopes: Set = Set(ExtensionPermissionScope.allCases), requestedAt: Date = .now, grantedAt: Date? = nil, lastActivityAt: Date? = nil, lastDeniedReason: String? = nil, notes: String? = nil ) { self.bundleIdentifier = bundleIdentifier self.appName = appName self.status = status self.allowedScopes = allowedScopes self.requestedAt = requestedAt self.grantedAt = grantedAt self.lastActivityAt = lastActivityAt self.lastDeniedReason = lastDeniedReason self.notes = notes } var isAuthorized: Bool { status.isActive } } struct ExtensionRateLimitRecord: Codable, Defaults.Serializable, Hashable, Identifiable { let bundleIdentifier: String var activityTimestamps: [Date] var widgetTimestamps: [Date] var notchExperienceTimestamps: [Date] var id: String { bundleIdentifier } init( bundleIdentifier: String, activityTimestamps: [Date] = [], widgetTimestamps: [Date] = [], notchExperienceTimestamps: [Date] = [] ) { self.bundleIdentifier = bundleIdentifier self.activityTimestamps = activityTimestamps self.widgetTimestamps = widgetTimestamps self.notchExperienceTimestamps = notchExperienceTimestamps } private enum CodingKeys: String, CodingKey { case bundleIdentifier case activityTimestamps case widgetTimestamps case notchExperienceTimestamps } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) bundleIdentifier = try container.decode(String.self, forKey: .bundleIdentifier) activityTimestamps = try container.decodeIfPresent([Date].self, forKey: .activityTimestamps) ?? [] widgetTimestamps = try container.decodeIfPresent([Date].self, forKey: .widgetTimestamps) ?? [] notchExperienceTimestamps = try container.decodeIfPresent([Date].self, forKey: .notchExperienceTimestamps) ?? [] } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(bundleIdentifier, forKey: .bundleIdentifier) try container.encode(activityTimestamps, forKey: .activityTimestamps) try container.encode(widgetTimestamps, forKey: .widgetTimestamps) try container.encode(notchExperienceTimestamps, forKey: .notchExperienceTimestamps) } } enum CalendarSelectionState: Codable, Defaults.Serializable { case all case selected(Set) } enum FantasticalViewStyle: String, CaseIterable, Codable, Defaults.Serializable { case mini = "mini" case calendar = "calendar" var displayName: String { switch self { case .mini: return "Mini View" case .calendar: return "Full Calendar" } } } enum ThirdPartyCalendarApp: String, CaseIterable, Codable, Defaults.Serializable, Identifiable { case fantastical = "fantastical" case notionCalendar = "notionCalendar" var id: String { rawValue } var displayName: String { switch self { case .fantastical: return "Fantastical" case .notionCalendar: return "Notion Calendar" } } /// Bundle identifiers to try when looking up the app icon (first match wins). var bundleIdentifiers: [String] { switch self { case .fantastical: return ["com.flexibits.fantastical2.mac", "com.flexibits.fantastical"] case .notionCalendar: return ["com.cron.electron"] } } var fallbackIconName: String { switch self { case .fantastical: return "calendar.badge.clock" case .notionCalendar: return "calendar.badge.plus" } } var fallbackIconColor: Color { switch self { case .fantastical: return .red case .notionCalendar: return .blue } } } enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable { case popover = "popover" // Traditional popover attached to button case panel = "panel" // Floating panel near notch case separateTab = "separateTab" // Separate tab in Dynamic Island var displayName: String { switch self { case .popover: return String(localized: "Popover") case .panel: return String(localized: "Panel") case .separateTab: return String(localized: "Separate Tab") } } var description: String { switch self { case .popover: return "Shows clipboard as a dropdown attached to the clipboard button" case .panel: return "Shows clipboard in a floating panel near the notch" case .separateTab: return "Shows copied items in a separate tab within the Dynamic Island (merges with Notes if enabled)" } } } enum ScreenAssistantDisplayMode: String, CaseIterable, Codable, Defaults.Serializable { case popover = "popover" // Traditional popover attached to button case panel = "panel" // Floating panel near notch var displayName: String { switch self { case .popover: return String(localized: "Popover") case .panel: return String(localized: "Panel") } } var description: String { switch self { case .popover: return String(localized: "Shows screen assistant as a dropdown attached to the AI button") case .panel: return String(localized: "Shows screen assistant in a floating panel near the notch") } } } enum ColorPickerDisplayMode: String, CaseIterable, Codable, Defaults.Serializable { case popover = "popover" // Traditional popover attached to button case panel = "panel" // Floating panel near notch var displayName: String { switch self { case .popover: return "Popover" case .panel: return "Panel" } } var description: String { switch self { case .popover: return "Shows color picker as a dropdown attached to the color picker button" case .panel: return "Shows color picker in a floating panel near the notch" } } } enum ThirdPartyDDCProvider: String, CaseIterable, Codable, Defaults.Serializable, Identifiable { case betterDisplay case lunar var id: String { rawValue } var displayName: String { switch self { case .betterDisplay: return "BetterDisplay" case .lunar: return "Lunar" } } /// Bundle identifiers to try when looking up the app icon. var bundleIdentifiers: [String] { switch self { case .betterDisplay: return ["pro.betterdisplay.BetterDisplay"] case .lunar: return ["fyi.lunar.Lunar"] } } } enum HideNotchOption: String, Defaults.Serializable { case always case nowPlayingOnly case never } // Define notification names at file scope extension Notification.Name { static let mediaControllerChanged = Notification.Name("mediaControllerChanged") } // Media controller types for selection in settings enum MediaControllerType: String, CaseIterable, Identifiable, Defaults.Serializable { case nowPlaying = "Now Playing" case appleMusic = "Apple Music" case spotify = "Spotify" case youtubeMusic = "Youtube Music" case amazonMusic = "Amazon Music" var id: String { self.rawValue } var localizedName: String { switch self { case .nowPlaying: return String(localized: "Now Playing") case .appleMusic: return String(localized: "Apple Music") case .spotify: return String(localized: "Spotify") case .youtubeMusic: return String(localized: "Youtube Music") case .amazonMusic: return String(localized: "Amazon Music") } } } // Sneak peek styles for selection in settings enum SneakPeekStyle: String, CaseIterable, Identifiable, Defaults.Serializable { case standard = "Default" case inline = "Inline" var id: String { self.rawValue } var LocalizedName : String { switch self { case .standard: return String(localized: "Default") case .inline: return String(localized: "Inline") } } } enum CapsLockIndicatorTintMode: String, CaseIterable, Identifiable, Defaults.Serializable { case green case accent case white var id: String { rawValue } var displayName: String { switch self { case .green: return String(localized: "Green") case .accent: return String(localized: "Accent") case .white: return String(localized: "White") } } var color: Color { switch self { case .green: return .green case .accent: return .accentColor case .white: return .white } } } enum ProgressBarStyle: String, CaseIterable, Identifiable, Defaults.Serializable { case hierarchical = "Hierarchical" case gradient = "Gradient" case segmented = "Segmented" var id: String { self.rawValue } } enum MusicAuxiliaryControl: String, CaseIterable, Identifiable, Defaults.Serializable { case shuffle case repeatMode case mediaOutput case lyrics var id: String { rawValue } var displayName: String { switch self { case .shuffle: return "Shuffle" case .repeatMode: return "Repeat" case .mediaOutput: return "Media Output" case .lyrics: return "Lyrics" } } var symbolName: String { switch self { case .shuffle: return "shuffle" case .repeatMode: return "repeat" case .mediaOutput: return "laptopcomputer" case .lyrics: return "quote.bubble" } } static func alternative( excluding control: MusicAuxiliaryControl, preferring candidate: MusicAuxiliaryControl? = nil ) -> MusicAuxiliaryControl { if let candidate, candidate != control { return candidate } return allCases.first { $0 != control } ?? .shuffle } } enum MusicSkipBehavior: String, CaseIterable, Identifiable, Defaults.Serializable { case track case tenSecond var id: String { rawValue } var displayName: String { switch self { case .track: return String(localized: "Track Skip") case .tenSecond: return String(localized: "±10 Seconds") } } var description: String { switch self { case .track: return String(localized: "Standard previous/next track controls") case .tenSecond: return String(localized: "Skip forward or backward by ten seconds") } } } enum TimerIconColorMode: String, CaseIterable, Identifiable, Defaults.Serializable { case adaptive = "Adaptive" case solid = "Solid" var id: String { rawValue } var displayName: String { switch self { case .adaptive: return String(localized:"Adaptive gradient") case .solid: return String(localized:"Solid colour") } } } enum TimerProgressStyle: String, CaseIterable, Identifiable, Defaults.Serializable { case bar = "Bar" case ring = "Ring" var id: String { rawValue } var localizedName: String { switch self { case .bar: return String(localized:"Bar") case .ring: return String(localized:"Ring") } } } enum FocusMonitoringMode: String, CaseIterable, Identifiable, Defaults.Serializable { case withoutDevTools = "withoutDevTools" case useDevTools = "useDevTools" var id: String { rawValue } var displayName: String { switch self { case .withoutDevTools: return "Use without DevTools" case .useDevTools: return "Use DevTools" } } } enum ReminderPresentationStyle: String, CaseIterable, Identifiable, Defaults.Serializable { case ringCountdown = "Ring" case digital = "Digital" case minutes = "Minutes" var id: String { rawValue } var displayName: String { switch self { case .ringCountdown: return String(localized: "Ring") case .digital: return String(localized: "Digital") case .minutes: return String(localized: "Minutes") } } } // AI Model types for screen assistant enum AIModelProvider: String, CaseIterable, Identifiable, Defaults.Serializable { case gemini = "Gemini" case openai = "OpenAI GPT" case claude = "Claude" case local = "Local Model" var id: String { self.rawValue } var displayName: String { return self.rawValue } var description: String { switch self { case .gemini: return "Google's Gemini AI with multimodal capabilities" case .openai: return "OpenAI's GPT models with advanced reasoning" case .claude: return "Anthropic's Claude with strong analytical skills" case .local: return "Local AI model (Ollama or similar)" } } var supportedModels: [AIModel] { switch self { case .gemini: return [ // Gemini 2.5 Models (Latest) AIModel(id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", supportsThinking: true), AIModel(id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", supportsThinking: true), AIModel(id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash-Lite", supportsThinking: false), AIModel(id: "gemini-2.5-flash-live", name: "Gemini 2.5 Flash Live", supportsThinking: false), AIModel(id: "gemini-2.5-flash-native-audio", name: "Gemini 2.5 Flash Native Audio", supportsThinking: true), // Gemini 2.0 Models AIModel(id: "gemini-2.0-flash", name: "Gemini 2.0 Flash", supportsThinking: false), AIModel(id: "gemini-2.0-flash-lite", name: "Gemini 2.0 Flash-Lite", supportsThinking: false), AIModel(id: "gemini-2.0-flash-live", name: "Gemini 2.0 Flash Live", supportsThinking: false), // Legacy 1.5 Models (for compatibility) AIModel(id: "gemini-1.5-pro", name: "Gemini 1.5 Pro", supportsThinking: false), AIModel(id: "gemini-1.5-flash", name: "Gemini 1.5 Flash", supportsThinking: false) ] case .openai: return [ AIModel(id: "gpt-4o", name: "GPT-4o", supportsThinking: false), AIModel(id: "gpt-4o-mini", name: "GPT-4o Mini", supportsThinking: false), AIModel(id: "o1-preview", name: "o1 Preview", supportsThinking: true), AIModel(id: "o1-mini", name: "o1 Mini", supportsThinking: true) ] case .claude: return [ AIModel(id: "claude-3-5-sonnet", name: "Claude 3.5 Sonnet", supportsThinking: false), AIModel(id: "claude-3-haiku", name: "Claude 3 Haiku", supportsThinking: false) ] case .local: return [ AIModel(id: "llama3.2", name: "Llama 3.2", supportsThinking: false), AIModel(id: "qwen2.5", name: "Qwen 2.5", supportsThinking: false) ] } } } struct AIModel: Codable, Identifiable, Defaults.Serializable { let id: String let name: String let supportsThinking: Bool var displayName: String { return name + (supportsThinking ? " (Thinking)" : "") } } struct NoteItem: Codable, Identifiable, Defaults.Serializable, Hashable { var id: UUID = UUID() var title: String var content: String var creationDate: Date var colorIndex: Int // 0: Yellow, 1: Blue, 2: Red, 3: Green var isPinned: Bool = false var imageFileName: String? = nil // Store filename instead of raw data // Internal property for migration private enum CodingKeys: String, CodingKey { case id, title, content, creationDate, colorIndex, isPinned, imageFileName, imageData } init(id: UUID = UUID(), title: String, content: String, creationDate: Date, colorIndex: Int, isPinned: Bool = false, imageFileName: String? = nil) { self.id = id self.title = title self.content = content self.creationDate = creationDate self.colorIndex = colorIndex self.isPinned = isPinned self.imageFileName = imageFileName } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(UUID.self, forKey: .id) title = try container.decode(String.self, forKey: .title) content = try container.decode(String.self, forKey: .content) creationDate = try container.decode(Date.self, forKey: .creationDate) colorIndex = try container.decode(Int.self, forKey: .colorIndex) isPinned = try container.decode(Bool.self, forKey: .isPinned) // Migration logic: if imageData exists but imageFileName doesn't, save it to disk if let data = try container.decodeIfPresent(Data.self, forKey: .imageData) { let fileName = "note_image_\(id.uuidString).png" let fileURL = NoteItem.noteImageDataDirectory.appendingPathComponent(fileName) try? data.write(to: fileURL) imageFileName = fileName } else { imageFileName = try container.decodeIfPresent(String.self, forKey: .imageFileName) } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(title, forKey: .title) try container.encode(content, forKey: .content) try container.encode(creationDate, forKey: .creationDate) try container.encode(colorIndex, forKey: .colorIndex) try container.encode(isPinned, forKey: .isPinned) try container.encode(imageFileName, forKey: .imageFileName) } static let colors: [Color] = [.yellow, .blue, .red, .green, .purple, .orange] // Directory for storing note image files static let noteImageDataDirectory: URL = { let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let notesDir = documentsPath.appendingPathComponent("NoteImages") try? FileManager.default.createDirectory(at: notesDir, withIntermediateDirectories: true) return notesDir }() var color: Color { if colorIndex >= 0 && colorIndex < NoteItem.colors.count { return NoteItem.colors[colorIndex] } return .yellow } // Helper to get image data from file func getImageData() -> Data? { guard let fileName = imageFileName else { return nil } let fileURL = NoteItem.noteImageDataDirectory.appendingPathComponent(fileName) return try? Data(contentsOf: fileURL) } } extension Defaults.Keys { // MARK: General static let menubarIcon = Key("menubarIcon", default: true) static let showOnAllDisplays = Key("showOnAllDisplays", default: false) static let automaticallySwitchDisplay = Key("automaticallySwitchDisplay", default: true) static let releaseName = Key("releaseName", default: "Seychelles") static let hideDynamicIslandFromScreenCapture = Key("hideDynamicIslandFromScreenCapture", default: false) // MARK: Behavior static let minimumHoverDuration = Key("minimumHoverDuration", default: 0.3) static let enableHaptics = Key("enableHaptics", default: true) static let openNotchOnHover = Key("openNotchOnHover", default: true) static let extendHoverArea = Key("extendHoverArea", default: false) static let externalDisplayStyle = Key( "externalDisplayStyle", default: .notch ) static let hideNonNotchUntilHover = Key("hideNonNotchUntilHover", default: false) static let notchHeightMode = Key( "notchHeightMode", default: WindowHeightMode.matchRealNotchSize ) static let nonNotchHeightMode = Key( "nonNotchHeightMode", default: WindowHeightMode.matchMenuBar ) static let nonNotchHeight = Key("nonNotchHeight", default: 32) static let notchHeight = Key("notchHeight", default: 32) static let openNotchWidth = Key("openNotchWidth", default: 640) //static let openLastTabByDefault = Key("openLastTabByDefault", default: false) // MARK: Appearance static let showEmojis = Key("showEmojis", default: false) //static let alwaysShowTabs = Key("alwaysShowTabs", default: true) static let showMirror = Key("showMirror", default: false) static let mirrorShape = Key("mirrorShape", default: MirrorShapeEnum.rectangle) static let settingsIconInNotch = Key("settingsIconInNotch", default: true) static let lightingEffect = Key("lightingEffect", default: true) static let accentColor = Key("accentColor", default: Color.blue) static let enableShadow = Key("enableShadow", default: true) static let cornerRadiusScaling = Key("cornerRadiusScaling", default: true) static let useModernCloseAnimation = Key("useModernCloseAnimation", default: true) static let showNotHumanFace = Key("showNotHumanFace", default: false) static let customIdleAnimations = Key<[CustomIdleAnimation]>("customIdleAnimations", default: []) static let selectedIdleAnimation = Key("selectedIdleAnimation", default: nil) static let animationTransformOverrides = Key<[String: AnimationTransformConfig]>("animationTransformOverrides", default: [:]) static let tileShowLabels = Key("tileShowLabels", default: false) static let showCalendar = Key("showCalendar", default: true) static let hideCompletedReminders = Key("hideCompletedReminders", default: true) static let hideAllDayEvents = Key("hideAllDayEvents", default: false) static let sliderColor = Key( "sliderUseAlbumArtColor", default: SliderColorEnum.white ) static let playerColorTinting = Key("playerColorTinting", default: true) static let useMusicVisualizer = Key("useMusicVisualizer", default: true) static let customVisualizers = Key<[CustomVisualizer]>("customVisualizers", default: []) static let selectedVisualizer = Key("selectedVisualizer", default: nil) static let customAppIcons = Key<[CustomAppIcon]>("customAppIcons", default: []) static let selectedAppIconID = Key("selectedAppIconID", default: nil) // MARK: Gestures static let enableGestures = Key("enableGestures", default: true) static let closeGestureEnabled = Key("closeGestureEnabled", default: true) static let gestureSensitivity = Key("gestureSensitivity", default: 200.0) static let enableHorizontalMusicGestures = Key("enableHorizontalMusicGestures", default: true) static let musicGestureBehavior = Key("musicGestureBehavior", default: .track) static let reverseSwipeGestures = Key("reverseSwipeGestures", default: false) static let reverseScrollGestures = Key("reverseScrollGestures", default: false) // MARK: Media playback static let coloredSpectrogram = Key("coloredSpectrogram", default: true) static let enableRealTimeWaveform = Key("enableRealTimeWaveform", default: false) static let enableSneakPeek = Key("enableSneakPeek", default: false) static let sneakPeekStyles = Key("sneakPeekStyles", default: .standard) static let showSneakPeekOnTrackChange = Key("showSneakPeekOnTrackChange", default: true) static let enableFullscreenMediaDetection = Key("enableFullscreenMediaDetection", default: true) static let parallaxEffectIntensity = Key("parallaxEffectIntensity", default: 6.0) static let waitInterval = Key("waitInterval", default: 3) static let showShuffleAndRepeat = Key("showShuffleAndRepeat", default: true) static let showMediaOutputControl = Key("showMediaOutputControl", default: true) static let musicAuxLeftControl = Key("musicAuxLeftControl", default: .shuffle) static let musicAuxRightControl = Key("musicAuxRightControl", default: .repeatMode) static let didMigrateMusicAuxControls = Key("didMigrateMusicAuxControls", default: false) static let musicControlSlots = Key<[MusicControlButton]>("musicControlSlots", default: MusicControlButton.defaultLayout) static let didMigrateMusicControlSlots = Key("didMigrateMusicControlSlots", default: false) static let musicSkipBehavior = Key("musicSkipBehavior", default: .track) static let musicControlWindowEnabled = Key("musicControlWindowEnabled", default: false) static let showStandardMediaControls = Key("showStandardMediaControls", default: true) static let autoHideInactiveNotchMediaPlayer = Key("autoHideInactiveNotchMediaPlayer", default: true) static let cachedMusicLiveActivityPreference = Key("cachedMusicLiveActivityPreference", default: nil) static let cachedLockScreenMediaWidgetPreference = Key("cachedLockScreenMediaWidgetPreference", default: nil) static let cachedMusicControlWindowPreference = Key("cachedMusicControlWindowPreference", default: nil) // Enable lock screen media widget (shows the standalone panel when screen is locked) static let enableLockScreenMediaWidget = Key("enableLockScreenMediaWidget", default: true) static let enableLockScreenWeatherWidget = Key("enableLockScreenWeatherWidget", default: true) static let enableLockScreenFocusWidget = Key("enableLockScreenFocusWidget", default: true) static let enableLockScreenReminderWidget = Key("enableLockScreenReminderWidget", default: true) static let enableLockScreenTimerWidget = Key("enableLockScreenTimerWidget", default: true) static let lockScreenWeatherRefreshInterval = Key("lockScreenWeatherRefreshInterval", default: 30 * 60) static let lockScreenWeatherShowsLocation = Key("lockScreenWeatherShowsLocation", default: true) static let lockScreenWeatherShowsSunrise = Key("lockScreenWeatherShowsSunrise", default: true) static let lockScreenWeatherWidgetStyle = Key("lockScreenWeatherWidgetStyle", default: .inline) static let lockScreenWeatherTemperatureUnit = Key("lockScreenWeatherTemperatureUnit", default: .celsius) static let lockScreenWeatherShowsAQI = Key("lockScreenWeatherShowsAQI", default: true) static let lockScreenWeatherAQIScale = Key("lockScreenWeatherAQIScale", default: .us) static let lockScreenWeatherUsesGaugeTint = Key("lockScreenWeatherUsesGaugeTint", default: false) static let lockScreenWeatherProviderSource = Key("lockScreenWeatherProviderSource", default: .openMeteo) static let lockScreenWeatherVerticalOffset = Key("lockScreenWeatherVerticalOffset", default: 0) static let lockScreenMusicVerticalOffset = Key("lockScreenMusicVerticalOffset", default: 0) static let lockScreenMusicPanelWidth = Key("lockScreenMusicPanelWidth", default: 350) static let lockScreenMusicAlbumParallaxEnabled = Key("lockScreenMusicAlbumParallaxEnabled", default: false) static let lockScreenTimerVerticalOffset = Key("lockScreenTimerVerticalOffset", default: 0) static let lockScreenTimerWidgetWidth = Key("lockScreenTimerWidgetWidth", default: 350) static let lockScreenGlassStyle = Key("lockScreenGlassStyle", default: .liquid) static let lockScreenGlassCustomizationMode = Key( "lockScreenGlassCustomizationMode", default: .standard ) static let lockScreenTimerGlassStyle = Key("lockScreenTimerGlassStyle", default: .frosted) static let lockScreenTimerGlassCustomizationMode = Key( "lockScreenTimerGlassCustomizationMode", default: .standard ) static let lockScreenMusicLiquidGlassVariant = Key( "lockScreenMusicLiquidGlassVariant", default: .defaultVariant ) static let lockScreenTimerLiquidGlassVariant = Key( "lockScreenTimerLiquidGlassVariant", default: .defaultVariant ) static let lockScreenShowAppIcon = Key("lockScreenShowAppIcon", default: false) static let lockScreenPanelShowsBorder = Key("lockScreenPanelShowsBorder", default: false) static let lockScreenPanelUsesBlur = Key("lockScreenPanelUsesBlur", default: true) static let lockScreenMusicMergedAirPlayOutput = Key("lockScreenMusicMergedAirPlayOutput", default: true) static let lockScreenTimerWidgetUsesBlur = Key("lockScreenTimerWidgetUsesBlur", default: false) static let lockScreenReminderChipStyle = Key("lockScreenReminderChipStyle", default: .eventColor) static let lockScreenReminderWidgetHorizontalAlignment = Key("lockScreenReminderWidgetHorizontalAlignment", default: "center") static let lockScreenReminderWidgetVerticalOffset = Key("lockScreenReminderWidgetVerticalOffset", default: 0) static let lockScreenShowCalendarEvent = Key("lockScreenShowCalendarEvent", default: true) static let lockScreenCalendarEventLookaheadWindow = Key("lockScreenCalendarEventLookaheadWindow", default: "3h") static let lockScreenCalendarSelectionMode = Key("lockScreenCalendarSelectionMode", default: "all") static let lockScreenSelectedCalendarIDs = Key>("lockScreenSelectedCalendarIDs", default: []) static let lockScreenShowCalendarCountdown = Key("lockScreenShowCalendarCountdown", default: true) static let lockScreenShowCalendarEventEntireDuration = Key("lockScreenShowCalendarEventEntireDuration", default: true) static let lockScreenShowCalendarEventAfterStartEnabled = Key("lockScreenShowCalendarEventAfterStartEnabled", default: false) static let lockScreenShowCalendarEventAfterStartWindow = Key("lockScreenShowCalendarEventAfterStartWindow", default: "5m") static let lockScreenShowCalendarTimeRemaining = Key("lockScreenShowCalendarTimeRemaining", default: true) static let lockScreenShowCalendarStartTimeAfterBegins = Key("lockScreenShowCalendarStartTimeAfterBegins", default: true) static let lockScreenWeatherWidgetRowOrder = Key("lockScreenWeatherWidgetRowOrder", default: "weather_calendar_focus") // MARK: Third-party Calendar Integration static let enableThirdPartyCalendarApp = Key("enableThirdPartyCalendarApp", default: false) static let selectedCalendarApp = Key("selectedCalendarApp", default: .fantastical) static let fantasticalDefaultView = Key("fantasticalDefaultView", default: .mini) // MARK: Battery static let showPowerStatusNotifications = Key("showPowerStatusNotifications", default: true) static let showBatteryIndicator = Key("showBatteryIndicator", default: BatteryActivityManager.shared.hasBattery()) static let showBatteryPercentage = Key("showBatteryPercentage", default: true) static let showPowerStatusIcons = Key("showPowerStatusIcons", default: true) static let playLowBatteryAlertSound = Key("playLowBatteryAlertSound", default: true) static let lockScreenBatteryShowsBatteryGauge = Key( "lockScreenWeatherShowsBatteryGauge", default: BatteryActivityManager.shared.hasBattery() ) static let lockScreenBatteryUsesLaptopSymbol = Key("lockScreenWeatherBatteryUsesLaptopSymbol", default: true) static let lockScreenBatteryShowsCharging = Key("lockScreenWeatherShowsCharging", default: true) static let lockScreenBatteryShowsChargingPercentage = Key("lockScreenWeatherShowsChargingPercentage", default: true) static let lockScreenBatteryShowsBluetooth = Key("lockScreenWeatherShowsBluetooth", default: true) // MARK: Downloads static let enableDownloadListener = Key("enableDownloadListener", default: true) static let enableSafariDownloads = Key("enableSafariDownloads", default: true) static let selectedDownloadIndicatorStyle = Key("selectedDownloadIndicatorStyle", default: DownloadIndicatorStyle.progress) static let selectedDownloadIconStyle = Key("selectedDownloadIconStyle", default: DownloadIconStyle.onlyAppIcon) // MARK: HUD static let inlineHUD = Key("inlineHUD", default: true) static let progressBarStyle = Key("progressBarStyle", default: .hierarchical) // Legacy support - keeping for backward compatibility static let enableGradient = Key("enableGradient", default: false) static let systemEventIndicatorShadow = Key("systemEventIndicatorShadow", default: false) static let systemEventIndicatorUseAccent = Key("systemEventIndicatorUseAccent", default: false) static let showProgressPercentages = Key("showProgressPercentages", default: true) // MARK: Shelf static let dynamicShelf = Key("dynamicShelf", default: true) static let openShelfByDefault = Key("openShelfByDefault", default: true) static let quickShareProvider = Key("quickShareProvider", default: "AirDrop") static let localSendSelectedDeviceID = Key("localSendSelectedDeviceID", default: "") static let localSendDevicePickerGlassMode = Key("localSendDevicePickerGlassMode", default: .standard) static let localSendDevicePickerLiquidGlassVariant = Key("localSendDevicePickerLiquidGlassVariant", default: .v11) static let copyOnDrag = Key("copyOnDrag", default: false) static let autoRemoveShelfItems = Key("autoRemoveShelfItems", default: false) static let expandedDragDetection = Key("expandedDragDetection", default: true) // MARK: Calendar static let calendarSelectionState = Key("calendarSelectionState", default: .all) static let showFullEventTitles = Key("showFullEventTitles", default: false) static let autoScrollToNextEvent = Key("autoScrollToNextEvent", default: true) // MARK: Fullscreen Media Detection static let alwaysHideInFullscreen = Key("alwaysHideInFullscreen", default: false) static let hideNotchOption = Key("hideNotchOption", default: .nowPlayingOnly) // MARK: Wobble Animation static let enableWobbleAnimation = Key("enableWobbleAnimation", default: false) // MARK: Media Controller static let mediaController = Key("mediaController", default: defaultMediaController) // MARK: Bluetooth Audio Devices static let showBluetoothDeviceConnections = Key("showBluetoothDeviceConnections", default: true) static let useColorCodedBatteryDisplay = Key("useColorCodedBatteryDisplay", default: true) static let useColorCodedVolumeDisplay = Key("useColorCodedVolumeDisplay", default: true) static let useSmoothColorGradient = Key("useSmoothColorGradient", default: true) static let useCircularBluetoothBatteryIndicator = Key("useCircularBluetoothBatteryIndicator", default: true) static let showBluetoothBatteryPercentageText = Key("showBluetoothBatteryPercentageText", default: false) static let showBluetoothDeviceNameMarquee = Key("showBluetoothDeviceNameMarquee", default: false) static let useBluetoothHUD3DIcon = Key("useBluetoothHUD3DIcon", default: true) // MARK: Stats Feature static let enableStatsFeature = Key("enableStatsFeature", default: false) static let autoStartStatsMonitoring = Key("autoStartStatsMonitoring", default: true) static let statsStopWhenNotchCloses = Key("statsStopWhenNotchCloses", default: true) static let statsUpdateInterval = Key("statsUpdateInterval", default: 1.0) static let showCpuGraph = Key("showCpuGraph", default: true) static let showMemoryGraph = Key("showMemoryGraph", default: true) static let showGpuGraph = Key("showGpuGraph", default: true) static let showNetworkGraph = Key("showNetworkGraph", default: false) static let showDiskGraph = Key("showDiskGraph", default: false) static let cpuTemperatureUnit = Key("cpuTemperatureUnit", default: .celsius) // MARK: Terminal Feature static let enableTerminalFeature = Key("enableTerminalFeature", default: false) static let terminalShellPath = Key("terminalShellPath", default: "/bin/zsh") static let terminalFontFamily = Key("terminalFontFamily", default: "") static let terminalFontSize = Key("terminalFontSize", default: 12.0) static let terminalOpacity = Key("terminalOpacity", default: 1.0) static let terminalMaxHeightFraction = Key("terminalMaxHeightFraction", default: 0.4) static let terminalCursorStyle = Key("terminalCursorStyle", default: "blinkBlock") static let terminalScrollbackLines = Key("terminalScrollbackLines", default: 1000) static let terminalOptionAsMeta = Key("terminalOptionAsMeta", default: true) static let terminalMouseReporting = Key("terminalMouseReporting", default: true) static let terminalBoldAsBright = Key("terminalBoldAsBright", default: true) static let terminalBackgroundColor = Key("terminalBackgroundColor", default: .black) static let terminalForegroundColor = Key("terminalForegroundColor", default: .white) static let terminalCursorColor = Key("terminalCursorColor", default: Color(.selectedControlColor)) static let terminalStickyMode = Key("terminalStickyMode", default: false) // MARK: Timer Feature static let enableTimerFeature = Key("enableTimerFeature", default: true) static let timerDisplayMode = Key("timerDisplayMode", default: .tab) static let timerPresets = Key<[TimerPreset]>("timerPresets", default: TimerPreset.defaultPresets) static let showTimerPresetsInNotchTab = Key("showTimerPresetsInNotchTab", default: true) static let timerIconColorMode = Key("timerIconColorMode", default: .adaptive) static let timerSolidColor = Key("timerSolidColor", default: .blue) static let timerShowsCountdown = Key("timerShowsCountdown", default: true) static let timerShowsLabel = Key("timerShowsLabel", default: false) static let timerShowsProgress = Key("timerShowsProgress", default: true) static let timerProgressStyle = Key("timerProgressStyle", default: .bar) static let mirrorSystemTimer = Key("mirrorSystemTimer", default: true) // MARK: Reminder Live Activity static let enableReminderLiveActivity = Key("enableReminderLiveActivity", default: true) static let reminderPresentationStyle = Key("reminderPresentationStyle", default: .ringCountdown) static let reminderLeadTime = Key("reminderLeadTime", default: 5) static let reminderSneakPeekDuration = Key("reminderSneakPeekDuration", default: 5) static let timerControlWindowEnabled = Key("timerControlWindowEnabled", default: true) // MARK: ColorPicker Feature static let enableColorPickerFeature = Key("enableColorPickerFeature", default: true) static let showColorFormats = Key("showColorFormats", default: true) static let colorPickerDisplayMode = Key("colorPickerDisplayMode", default: .panel) static let colorHistorySize = Key("colorHistorySize", default: 10) static let showColorPickerIcon = Key("showColorPickerIcon", default: true) // MARK: Clipboard Feature static let enableClipboardManager = Key("enableClipboardManager", default: true) static let clipboardHistorySize = Key("clipboardHistorySize", default: 3) static let showClipboardIcon = Key("showClipboardIcon", default: true) static let clipboardDisplayMode = Key("clipboardDisplayMode", default: .panel) // MARK: Screen Assistant Feature static let enableScreenAssistant = Key("enableScreenAssistant", default: true) static let screenAssistantDisplayMode = Key("screenAssistantDisplayMode", default: .panel) static let geminiApiKey = Key("geminiApiKey", default: "") static let openaiApiKey = Key("openaiApiKey", default: "") static let claudeApiKey = Key("claudeApiKey", default: "") static let selectedAIProvider = Key("selectedAIProvider", default: .gemini) static let selectedAIModel = Key("selectedAIModel", default: nil) static let enableThinkingMode = Key("enableThinkingMode", default: false) static let localModelEndpoint = Key("localModelEndpoint", default: "http://localhost:11434") // MARK: Third-Party Extensions static let enableThirdPartyExtensions = Key("enableThirdPartyExtensions", default: true) static let enableExtensionLiveActivities = Key("enableExtensionLiveActivities", default: true) static let enableExtensionLockScreenWidgets = Key("enableExtensionLockScreenWidgets", default: true) static let enableExtensionNotchExperiences = Key("enableExtensionNotchExperiences", default: true) static let enableExtensionNotchTabs = Key("enableExtensionNotchTabs", default: true) static let enableExtensionNotchMinimalisticOverrides = Key("enableExtensionNotchMinimalisticOverrides", default: true) static let enableExtensionNotchInteractiveWebViews = Key("enableExtensionNotchInteractiveWebViews", default: true) static let extensionAuthorizationEntries = Key<[ExtensionAuthorizationEntry]>("extensionAuthorizationEntries", default: []) static let extensionRateLimitRecords = Key<[ExtensionRateLimitRecord]>("extensionRateLimitRecords", default: []) static let extensionDiagnosticsLoggingEnabled = Key("extensionDiagnosticsLoggingEnabled", default: true) static let extensionLiveActivityCapacity = Key("extensionLiveActivityCapacity", default: 4) static let extensionLockScreenWidgetCapacity = Key("extensionLockScreenWidgetCapacity", default: 4) static let extensionNotchExperienceCapacity = Key("extensionNotchExperienceCapacity", default: 2) static let enableExtensionFileSharing = Key("enableExtensionFileSharing", default: true) // MARK: Keyboard Shortcuts static let enableShortcuts = Key("enableShortcuts", default: true) // MARK: System HUD Feature static let enableSystemHUD = Key("enableSystemHUD", default: true) static let enableVolumeHUD = Key("enableVolumeHUD", default: true) static let enableBrightnessHUD = Key("enableBrightnessHUD", default: true) static let enableKeyboardBacklightHUD = Key("enableKeyboardBacklightHUD", default: true) static let systemHUDSensitivity = Key("systemHUDSensitivity", default: 5) static let playVolumeChangeFeedback = Key("playVolumeChangeFeedback", default: false) // MARK: Custom OSD Window Feature static let enableCustomOSD = Key("enableCustomOSD", default: false) static let enableVerticalHUD = Key("enableVerticalHUD", default: false) static let enableCircularHUD = Key("enableCircularHUD", default: false) static let verticalHUDPosition = Key("verticalHUDPosition", default: "right") // "left" or "right" // Vertical HUD Customization static let verticalHUDShowValue = Key("verticalHUDShowValue", default: true) static let verticalHUDInteractive = Key("verticalHUDInteractive", default: true) static let verticalHUDHeight = Key("verticalHUDHeight", default: 160) static let verticalHUDWidth = Key("verticalHUDWidth", default: 36) static let verticalHUDPadding = Key("verticalHUDPadding", default: 24) static let verticalHUDUseAccentColor = Key("verticalHUDUseAccentColor", default: false) static let verticalHUDMaterial = Key("verticalHUDMaterial", default: .frosted) static let verticalHUDLiquidGlassCustomizationMode = Key( "verticalHUDLiquidGlassCustomizationMode", default: .standard ) static let verticalHUDLiquidGlassVariant = Key( "verticalHUDLiquidGlassVariant", default: .defaultVariant ) // Circular HUD Customization static let circularHUDShowValue = Key("circularHUDShowValue", default: true) static let circularHUDSize = Key("circularHUDSize", default: 65) static let circularHUDStrokeWidth = Key("circularHUDStrokeWidth", default: 4) static let circularHUDUseAccentColor = Key("circularHUDUseAccentColor", default: true) // MARK: Third-Party DDC Integration static let enableThirdPartyDDCIntegration = Key("enableThirdPartyDDCIntegration", default: false) static let thirdPartyDDCProvider = Key("thirdPartyDDCProvider", default: .betterDisplay) static let enableExternalVolumeControlListener = Key("enableExternalVolumeControlListener", default: false) static let didMigrateThirdPartyDDCIntegration = Key("didMigrateThirdPartyDDCIntegration", default: false) // Legacy keys retained for migration/backward compatibility static let enableBetterDisplayIntegration = Key("enableBetterDisplayIntegration", default: false) static let enableLunarIntegration = Key("enableLunarIntegration", default: false) static let hasSeenOSDAlphaWarning = Key("hasSeenOSDAlphaWarning", default: false) static let enableOSDVolume = Key("enableOSDVolume", default: true) static let enableOSDBrightness = Key("enableOSDBrightness", default: true) static let enableOSDKeyboardBacklight = Key("enableOSDKeyboardBacklight", default: true) static let osdMaterial = Key("osdMaterial", default: .frosted) static let osdLiquidGlassCustomizationMode = Key( "osdLiquidGlassCustomizationMode", default: .standard ) static let osdLiquidGlassVariant = Key( "osdLiquidGlassVariant", default: .defaultVariant ) static let osdIconColorStyle = Key("osdIconColorStyle", default: .white) // MARK: Screen Recording Detection Feature static let enableScreenRecordingDetection = Key("enableScreenRecordingDetection", default: true) static let showRecordingIndicator = Key("showRecordingIndicator", default: true) // Polling removed - now uses event-driven private API detection (CGSIsScreenWatcherPresent) // static let enableScreenRecordingPolling = Key("enableScreenRecordingPolling", default: false) // MARK: Focus / Do Not Disturb Detection static let enableDoNotDisturbDetection = Key("enableDoNotDisturbDetection", default: true) static let focusMonitoringMode = Key("focusMonitoringMode", default: .withoutDevTools) static let showDoNotDisturbIndicator = Key("showDoNotDisturbIndicator", default: true) static let showDoNotDisturbLabel = Key("showDoNotDisturbLabel", default: true) static let focusIndicatorNonPersistent = Key("focusIndicatorNonPersistent", default: false) // MARK: Privacy Indicators (Camera & Microphone Detection) static let enableCameraDetection = Key("enableCameraDetection", default: true) static let enableMicrophoneDetection = Key("enableMicrophoneDetection", default: true) // MARK: Lock Screen Features static let enableLockScreenLiveActivity = Key("enableLockScreenLiveActivity", default: true) static let enableLockSounds = Key("enableLockSounds", default: true) // MARK: Caps Lock Indicator static let enableCapsLockIndicator = Key("enableCapsLockIndicator", default: true) static let capsLockIndicatorUseGreenColor = Key("capsLockIndicatorUseGreenColor", default: false) // Legacy toggle static let capsLockIndicatorTintMode = Key("capsLockIndicatorTintMode", default: .white) static let didMigrateCapsLockTintMode = Key("didMigrateCapsLockTintMode", default: false) static let showCapsLockLabel = Key("showCapsLockLabel", default: false) // MARK: ImageService static let didClearLegacyURLCacheV1 = Key("didClearLegacyURLCacheV1", default: false) // MARK: Minimalistic UI Mode static let enableMinimalisticUI = Key("enableMinimalisticUI", default: false) // MARK: Lyrics Feature static let enableLyrics = Key("enableLyrics", default: false) // MARK: Notes Feature static let enableNotes = Key("enableNotes", default: false) static let enableNotePinning = Key("enableNotePinning", default: true) static let enableNoteSearch = Key("enableNoteSearch", default: false) static let enableNoteColorFiltering = Key("enableNoteColorFiltering", default: false) static let enableCreateFromClipboard = Key("enableCreateFromClipboard", default: true) static let enableNoteCharCount = Key("enableNoteCharCount", default: true) static let savedNotes = Key<[NoteItem]>("savedNotes", default: []) // Helper to determine the default media controller based on macOS version static var defaultMediaController: MediaControllerType { if #available(macOS 15.4, *) { return .appleMusic } else { return .nowPlaying } } // Migration helper to convert from legacy enableGradient Boolean to new ProgressBarStyle enum static func migrateProgressBarStyle() { // Check if migration is needed by seeing if the old Boolean was set to gradient let wasGradientEnabled = Defaults[.enableGradient] // Only migrate if we're still using the default hierarchical value but gradient was enabled if wasGradientEnabled && Defaults[.progressBarStyle] == .hierarchical { Defaults[.progressBarStyle] = .gradient } } static func migrateMusicAuxControls() { if Defaults[.didMigrateMusicAuxControls] == false { if Defaults[.showMediaOutputControl] { Defaults[.musicAuxRightControl] = .mediaOutput } Defaults[.didMigrateMusicAuxControls] = true } normalizeMusicAuxControls() } static func migrateCapsLockTintMode() { guard Defaults[.didMigrateCapsLockTintMode] == false else { return } let legacyGreen = Defaults[.capsLockIndicatorUseGreenColor] Defaults[.capsLockIndicatorTintMode] = legacyGreen ? .green : .white Defaults[.didMigrateCapsLockTintMode] = true } static func migrateMusicControlSlots() { guard Defaults[.didMigrateMusicControlSlots] == false else { return } let allowMediaOutput = Defaults[.showMediaOutputControl] let baseLayout: [MusicControlButton] if Defaults[.showShuffleAndRepeat] { var slots = MusicControlButton.defaultLayout let left = MusicControlButton(auxiliaryControl: Defaults[.musicAuxLeftControl]) let right = MusicControlButton(auxiliaryControl: Defaults[.musicAuxRightControl]) slots[0] = left slots[4] = right baseLayout = slots } else { baseLayout = MusicControlButton.minimalLayout } Defaults[.musicControlSlots] = baseLayout.normalized(allowingMediaOutput: allowMediaOutput) Defaults[.didMigrateMusicControlSlots] = true } static func migrateThirdPartyDDCIntegration() { if Defaults[.didMigrateThirdPartyDDCIntegration] == false { let legacyBetterDisplayEnabled = Defaults[.enableBetterDisplayIntegration] let legacyLunarEnabled = Defaults[.enableLunarIntegration] if legacyBetterDisplayEnabled || legacyLunarEnabled { Defaults[.enableThirdPartyDDCIntegration] = true Defaults[.thirdPartyDDCProvider] = (legacyLunarEnabled && !legacyBetterDisplayEnabled) ? .lunar : .betterDisplay } Defaults[.didMigrateThirdPartyDDCIntegration] = true } syncLegacyThirdPartyDDCKeys() } static func syncLegacyThirdPartyDDCKeys() { let isIntegrationEnabled = Defaults[.enableThirdPartyDDCIntegration] let selectedProvider = Defaults[.thirdPartyDDCProvider] Defaults[.enableBetterDisplayIntegration] = isIntegrationEnabled && selectedProvider == .betterDisplay Defaults[.enableLunarIntegration] = isIntegrationEnabled && selectedProvider == .lunar } private static func normalizeMusicAuxControls() { guard Defaults[.musicAuxLeftControl] == Defaults[.musicAuxRightControl] else { return } let current = Defaults[.musicAuxLeftControl] let fallback = MusicAuxiliaryControl.alternative(excluding: current) Defaults[.musicAuxRightControl] = fallback } }