diff --git a/DynamicIsland.xcodeproj/project.pbxproj b/DynamicIsland.xcodeproj/project.pbxproj
index 1234a33..1abb11f 100644
--- a/DynamicIsland.xcodeproj/project.pbxproj
+++ b/DynamicIsland.xcodeproj/project.pbxproj
@@ -433,7 +433,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 8;
+ CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_ASSET_PATHS = "\"DynamicIsland/Preview Content\"";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = 9Y64TRM77N;
@@ -505,7 +505,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 8;
+ CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_ASSET_PATHS = "\"DynamicIsland/Preview Content\"";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = 9Y64TRM77N;
diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings
index 13c3eed..94d116b 100644
--- a/DynamicIsland/Localizable.xcstrings
+++ b/DynamicIsland/Localizable.xcstrings
@@ -9907,9 +9907,13 @@
}
}
}
+ },
+ "Enable \"Idle Animation\" to customize animations" : {
+
},
"Enable \"Show cool face animation while inactivity\" to customize animations" : {
"comment" : "A message displayed below the section header in the IdleAnimationsSettingsSection, explaining that users can customize animations by enabling this feature.",
+ "extractionState" : "stale",
"isCommentAutoGenerated" : true,
"localizations" : {
"pt-BR" : {
diff --git a/DynamicIsland/components/AnimatedFace.swift b/DynamicIsland/components/AnimatedFace.swift
deleted file mode 100644
index 66d4b65..0000000
--- a/DynamicIsland/components/AnimatedFace.swift
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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
-
-struct MinimalFaceFeatures: View {
- @State private var isBlinking = false
- @State var height:CGFloat = 20;
- @State var width:CGFloat = 30;
-
- var body: some View {
- VStack(spacing: 4) { // Adjusted spacing to fit within 30x30
- // Eyes
- HStack(spacing: 4) { // Adjusted spacing to fit within 30x30
- Eye(isBlinking: $isBlinking)
- Eye(isBlinking: $isBlinking)
- }
-
- // Nose and mouth combined
- VStack(spacing: 2) { // Adjusted spacing to fit within 30x30
- // Nose
- RoundedRectangle(cornerRadius: 2)
- .fill(Color.white)
- .frame(width: 3, height: 4)
-
- // Mouth (happy)
- GeometryReader { geometry in
- Path { path in
- let width = geometry.size.width
- let height = geometry.size.height
- path.move(to: CGPoint(x: 0, y: height / 2))
- path.addQuadCurve(to: CGPoint(x: width, y: height / 2), control: CGPoint(x: width / 2, y: height))
- }
- .stroke(Color.white, lineWidth: 2)
- }
- .frame(width: 14, height: 10)
- }
- }
- .frame(width: self.width, height: self.height) // Maximum size of face
- .onAppear {
- startBlinking()
- }
- }
-
- func startBlinking() {
- Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in
- withAnimation(.spring(duration: 0.2)) {
- isBlinking = true
- }
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
- withAnimation(.spring(duration: 0.2)) {
- isBlinking = false
- }
- }
- }
- }
-}
-
-struct Eye: View {
- @Binding var isBlinking: Bool
-
- var body: some View {
- RoundedRectangle(cornerRadius: 10)
- .fill(Color.white)
- .frame(width: 4, height: isBlinking ? 1 : 4)
- .frame(maxWidth: 15, maxHeight: 15) // Adjusted max size
- .animation(.easeInOut(duration: 0.1), value: isBlinking)
- }
-}
-
-struct MinimalFaceFeatures_Previews: PreviewProvider {
- static var previews: some View {
- ZStack {
- Color.black
- MinimalFaceFeatures()
- }
- .previewLayout(.fixed(width: 60, height: 60)) // Adjusted preview size for better visibility
- }
-}
diff --git a/DynamicIsland/components/EmptyState.swift b/DynamicIsland/components/EmptyState.swift
index c372a66..1da3b4e 100644
--- a/DynamicIsland/components/EmptyState.swift
+++ b/DynamicIsland/components/EmptyState.swift
@@ -28,8 +28,10 @@ struct EmptyStateView: View {
var body: some View {
HStack {
- MinimalFaceFeatures(
- height: 70, width: 80)
+ Image(systemName: "music.note")
+ .font(.system(size: 24))
+ .foregroundColor(.gray)
+ .frame(width: 80, height: 70)
Text(message)
.font(.system(size:14))
.foregroundColor(.gray)
diff --git a/DynamicIsland/components/IdleAnimationView.swift b/DynamicIsland/components/IdleAnimationView.swift
index 935c714..41015a9 100644
--- a/DynamicIsland/components/IdleAnimationView.swift
+++ b/DynamicIsland/components/IdleAnimationView.swift
@@ -31,8 +31,8 @@ struct IdleAnimationView: View {
AnimationContentView(animation: animation)
.id("\(animation.id)-\(overrides[animation.id.uuidString]?.hashValue ?? 0)") // Force recreation when override changes
} else {
- // Fallback to original face if nothing selected
- MinimalFaceFeatures(height: 20, width: 30)
+ // No animation selected
+ EmptyView()
}
}
}
@@ -85,20 +85,6 @@ private struct AnimationContentView: View {
.padding(.bottom, config.paddingBottom)
.frame(width: config.expandWithAnimation ? nil : 30, height: 20)
.clipped()
-
- case .builtInFace:
- // Apply transforms to built-in face too!
- MinimalFaceFeatures(height: 20, width: 30)
- .frame(
- width: config.cropWidth * config.scale,
- height: config.cropHeight * config.scale
- )
- .offset(x: config.offsetX, y: config.offsetY)
- .rotationEffect(.degrees(config.rotation))
- .opacity(config.opacity)
- .padding(.bottom, config.paddingBottom)
- .frame(width: config.expandWithAnimation ? nil : 30, height: 20)
- .clipped()
}
}
}
diff --git a/DynamicIsland/components/Settings/AnimationEditorView.swift b/DynamicIsland/components/Settings/AnimationEditorView.swift
index 1f6ebf1..9480841 100644
--- a/DynamicIsland/components/Settings/AnimationEditorView.swift
+++ b/DynamicIsland/components/Settings/AnimationEditorView.swift
@@ -170,16 +170,11 @@ struct AnimationEditorView: View {
.frame(width: 30 * previewScale, height: 20 * previewScale)
Group {
- if let existing = existingAnimation, case .builtInFace = existing.source {
- MinimalFaceFeatures(height: 20, width: 30)
- .scaleEffect(previewScale)
- } else {
- LottieView(state: LUStateData(
- type: .loadedFrom(sourceURL),
- speed: speed,
- loopMode: loopMode.lottieLoopMode
- ))
- }
+ LottieView(state: LUStateData(
+ type: .loadedFrom(sourceURL),
+ speed: speed,
+ loopMode: loopMode.lottieLoopMode
+ ))
}
.frame(width: cropWidth * scale * previewScale, height: cropHeight * scale * previewScale)
.offset(x: offsetX * previewScale, y: offsetY * previewScale)
diff --git a/DynamicIsland/components/Settings/IdleAnimationsSettingsSection.swift b/DynamicIsland/components/Settings/IdleAnimationsSettingsSection.swift
index be45da7..fa587bc 100644
--- a/DynamicIsland/components/Settings/IdleAnimationsSettingsSection.swift
+++ b/DynamicIsland/components/Settings/IdleAnimationsSettingsSection.swift
@@ -87,12 +87,6 @@ struct IdleAnimationsSettingsSection: View {
print("🔧 [Edit] Lottie URL: \(url)")
editorSourceURL = url
editorIsRemote = true
-
- case .builtInFace:
- print("🔧 [Edit] Built-in face - using dummy URL")
- // Use a dummy URL for built-in face (editor only needs URL for preview)
- editorSourceURL = URL(string: "builtin://face")!
- editorIsRemote = false
}
// Show editor with slight delay to ensure state is set
@@ -118,7 +112,7 @@ struct IdleAnimationsSettingsSection: View {
}
.frame(height: 140)
} else {
- Text("Enable \"Show cool face animation while inactivity\" to customize animations")
+ Text("Enable \"Idle Animation\" to customize animations")
.font(.caption)
.foregroundStyle(.secondary)
.padding(.vertical, 8)
@@ -388,10 +382,6 @@ struct AnimationPreview: View {
loopMode: .loop
))
.frame(width: 60, height: 40)
-
- case .builtInFace:
- MinimalFaceFeatures(height: 40, width: 60)
- .scaleEffect(2.0) // Scale up the face for preview
}
}
}
diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift
index 718ccc2..ae7a5ec 100644
--- a/DynamicIsland/components/Settings/SettingsView.swift
+++ b/DynamicIsland/components/Settings/SettingsView.swift
@@ -807,7 +807,7 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .appearance, title: "Slider color", keywords: ["slider", "accent"], highlightID: SettingsTab.appearance.highlightID(for: "Slider color")),
SettingsSearchEntry(tab: .appearance, title: "Enable Dynamic mirror", keywords: ["mirror", "reflection"], highlightID: SettingsTab.appearance.highlightID(for: "Enable Dynamic mirror")),
SettingsSearchEntry(tab: .appearance, title: "Mirror shape", keywords: ["mirror shape", "circle", "rectangle"], highlightID: SettingsTab.appearance.highlightID(for: "Mirror shape")),
- SettingsSearchEntry(tab: .appearance, title: "Show cool face animation while inactivity", keywords: ["face animation", "idle"], highlightID: SettingsTab.appearance.highlightID(for: "Show cool face animation while inactivity")),
+ SettingsSearchEntry(tab: .appearance, title: "Idle Animation", keywords: ["face animation", "idle", "cool face"], highlightID: SettingsTab.appearance.highlightID(for: "Idle Animation")),
SettingsSearchEntry(tab: .appearance, title: "App icon", keywords: ["app icon", "custom icon"], highlightID: SettingsTab.appearance.highlightID(for: "App icon")),
// Lock Screen
@@ -3951,8 +3951,8 @@ struct Appearance: View {
.tag(MirrorShapeEnum.rectangle)
}
.settingsHighlight(id: highlightID("Mirror shape"))
- Defaults.Toggle("Show cool face animation while inactivity", key: .showNotHumanFace)
- .settingsHighlight(id: highlightID("Show cool face animation while inactivity"))
+ Defaults.Toggle("Idle Animation", key: .showNotHumanFace)
+ .settingsHighlight(id: highlightID("Idle Animation"))
} header: {
HStack {
Text("Additional features")
diff --git a/DynamicIsland/managers/IdleAnimationManager.swift b/DynamicIsland/managers/IdleAnimationManager.swift
index 3bb02cc..ed1d4f6 100644
--- a/DynamicIsland/managers/IdleAnimationManager.swift
+++ b/DynamicIsland/managers/IdleAnimationManager.swift
@@ -39,19 +39,10 @@ class IdleAnimationManager {
// MARK: - Initialization
- /// Load bundled animations from the LottieAnimations folder and built-in face
+ /// Load bundled animations from the LottieAnimations folder
func initializeDefaultAnimations() {
var animations: [CustomIdleAnimation] = []
- // Add built-in face as first option
- let builtInFace = CustomIdleAnimation(
- name: "Classic Face",
- source: .builtInFace,
- speed: 1.0,
- isBuiltIn: true
- )
- animations.append(builtInFace)
-
// Load bundled Lottie files
if let bundledAnimations = loadBundledAnimations() {
animations.append(contentsOf: bundledAnimations)
@@ -60,14 +51,21 @@ class IdleAnimationManager {
// Get existing animations
var existing = Defaults[.customIdleAnimations]
+ // Remove any legacy builtInFace entries that may be stored from older versions
+ existing.removeAll { animation in
+ if case .lottieFile(let url) = animation.source, url.absoluteString == "builtin://face" {
+ return true
+ }
+ return false
+ }
+
if existing.isEmpty {
// First launch - set everything
Defaults[.customIdleAnimations] = animations
- Defaults[.selectedIdleAnimation] = builtInFace
+ Defaults[.selectedIdleAnimation] = animations.first
print("✅ [IdleAnimationManager] First launch: Initialized with \(animations.count) animations")
} else {
// Subsequent launch - ensure all bundled animations are present
- let existingIDs = Set(existing.map { $0.id })
let existingNames = Set(existing.filter { $0.isBuiltIn }.map { $0.name })
// Add any missing bundled animations
@@ -79,7 +77,23 @@ class IdleAnimationManager {
}
Defaults[.customIdleAnimations] = existing
- print("✅ [IdleAnimationManager] Subsequent launch: \(existing.count) total animations (\(animations.count - 1) bundled + built-in face)")
+
+ // If current selection was the old built-in face (no longer valid), select first available
+ if let selected = Defaults[.selectedIdleAnimation] {
+ let isValid: Bool
+ switch selected.source {
+ case .lottieFile(let url):
+ isValid = url.absoluteString != "builtin://face"
+ case .lottieURL:
+ isValid = true
+ }
+ if !isValid {
+ Defaults[.selectedIdleAnimation] = existing.first
+ print("🔄 [IdleAnimationManager] Migrated selection from legacy face to: \(existing.first?.name ?? "nil")")
+ }
+ }
+
+ print("✅ [IdleAnimationManager] Subsequent launch: \(existing.count) total animations")
}
}
diff --git a/DynamicIsland/managers/StatsManager.swift b/DynamicIsland/managers/StatsManager.swift
index 8bcef43..7abe47e 100644
--- a/DynamicIsland/managers/StatsManager.swift
+++ b/DynamicIsland/managers/StatsManager.swift
@@ -414,14 +414,20 @@ class StatsManager: ObservableObject {
private var delayedStopTimer: Timer?
private var delayedStartTimer: Timer?
private let maxHistoryPoints = 30
+ /// Cached host port to avoid leaking Mach send rights.
+ /// Every call to `mach_host_self()` acquires a new send right that must be
+ /// explicitly deallocated; caching it once prevents port exhaustion over time.
+ private let hostPort: mach_port_t = mach_host_self()
private let totalPhysicalMemory: UInt64 = {
var stats = host_basic_info()
var count = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size)
+ let initHostPort = mach_host_self()
let result = withUnsafeMutablePointer(to: &stats) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
- host_info(mach_host_self(), HOST_BASIC_INFO, $0, &count)
+ host_info(initHostPort, HOST_BASIC_INFO, $0, &count)
}
}
+ mach_port_deallocate(mach_task_self_, initHostPort)
if result == KERN_SUCCESS {
return UInt64(stats.max_mem)
}
@@ -786,7 +792,7 @@ class StatsManager: ObservableObject {
let result = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
- host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &count)
+ host_statistics(hostPort, HOST_CPU_LOAD_INFO, $0, &count)
}
}
@@ -858,7 +864,7 @@ class StatsManager: ObservableObject {
let vmResult = withUnsafeMutablePointer(to: &vmStatistics) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
- host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &size)
+ host_statistics64(hostPort, HOST_VM_INFO64, $0, &size)
}
}
@@ -978,7 +984,7 @@ class StatsManager: ObservableObject {
var cpuInfo: processor_info_array_t?
var numCpuInfo: mach_msg_type_number_t = 0
var numCPUsU: natural_t = 0
- let result = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo)
+ let result = host_processor_info(hostPort, PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo)
guard result == KERN_SUCCESS, let cpuInfo else {
return cpuCoreUsage
}
diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift
index 01cb6d0..8ae20b6 100644
--- a/DynamicIsland/models/Constants.swift
+++ b/DynamicIsland/models/Constants.swift
@@ -128,13 +128,11 @@ enum AnimationLoopMode: String, Codable, CaseIterable {
enum AnimationSource: Codable, Hashable, Equatable {
case lottieFile(URL) // Local file (in app support or bundle)
case lottieURL(URL) // Remote URL
- case builtInFace // Original MinimalFaceFeatures
var displayType: String {
switch self {
case .lottieFile: return "Local"
case .lottieURL: return "Remote"
- case .builtInFace: return "Built-in"
}
}
}