Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
02b130c
perf(shelf): bound thumbnail cache with NSCache (count + cost limits)
mehmetefeaytas Jul 23, 2026
ecf38cf
perf(audio): fix HAL property-listener leak + reuse volume/mute eleme…
mehmetefeaytas Jul 23, 2026
32799c8
perf(stats): fix observer-id bug, ps throttle, single getifaddrs, laz…
mehmetefeaytas Jul 23, 2026
0571428
fix(media): publish playbackState on the main actor; reuse static ISO…
mehmetefeaytas Jul 23, 2026
16c8620
perf(phase-1): low-risk isolated fixes (HAL leak, thumbnail NSCache, …
mehmetefeaytas Jul 23, 2026
62b88ce
perf(energy): add central ActivityGate (sleep/thermal/low-power) for …
mehmetefeaytas Jul 23, 2026
e04b2aa
merge: ActivityGate (S2-a)
mehmetefeaytas Jul 23, 2026
fb83ccf
perf(bluetooth): make observer primary, 30s gated watchdog poll, skip…
mehmetefeaytas Jul 23, 2026
c230144
perf(polling): brightness event-driven/windowed, clipboard >=1s + gat…
mehmetefeaytas Jul 23, 2026
f94632e
merge: bluetooth polling (S2-b)
mehmetefeaytas Jul 23, 2026
b4a8c23
merge: brightness/clipboard polling (S2-c)
mehmetefeaytas Jul 23, 2026
f47cc42
perf(bg): DND FS-events, gated AX ticker, conditional media-key tap (…
mehmetefeaytas Jul 23, 2026
1230c28
docs(bluetooth): clarify sleep-gate uses thread-safe CGDisplayIsAslee…
mehmetefeaytas Jul 23, 2026
e1e0d1e
perf(visualizer): drive animation with TimelineView/visibility gating…
mehmetefeaytas Jul 23, 2026
7473d51
perf(lockscreen/llm): cache weather hosting view, memoize next event,…
mehmetefeaytas Jul 23, 2026
3b7ae7d
perf(media/shelf): lazy now-playing subprocess; move icon/preview ren…
mehmetefeaytas Jul 23, 2026
cd8b827
perf(stats): collect system metrics off the main actor; throttle GPU/…
mehmetefeaytas Jul 23, 2026
ffbd68f
perf(phase-3): main-thread offload + render (StatsManager actor, Time…
mehmetefeaytas Jul 23, 2026
e137d6c
perf(ui): cache screen lookups + extract independent leaf subviews to…
mehmetefeaytas Jul 23, 2026
c1b4398
perf(startup): lazy feature-manager init + defer non-critical launch …
mehmetefeaytas Jul 23, 2026
045fb5d
perf(phase-4): scope ContentView re-renders (screen cache + stats de-…
mehmetefeaytas Jul 23, 2026
3193e1d
fix(perf-phase-1): address CodeRabbit review
mehmetefeaytas Jul 23, 2026
6b27bc4
merge: phase-1 CodeRabbit fixes into phase-2
mehmetefeaytas Jul 23, 2026
b473e05
fix(perf-phase-2): address CodeRabbit review
mehmetefeaytas Jul 23, 2026
e4e9477
merge: phase-1+2 CodeRabbit fixes into phase-3
mehmetefeaytas Jul 23, 2026
28ad5df
fix(perf-phase-3): address CodeRabbit review
mehmetefeaytas Jul 23, 2026
dd4f5bc
merge: phase-1/2/3 CodeRabbit fixes into phase-4
mehmetefeaytas Jul 23, 2026
54e159b
perf(startup): load app icon off-main in deferred launch block
mehmetefeaytas Jul 23, 2026
129c64a
perf(icons): invalidate cached app icons when the bundle changes
mehmetefeaytas Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion DynamicIsland/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ struct ContentView: View {
@ObservedObject var timerManager = TimerManager.shared
@ObservedObject var reminderManager = ReminderLiveActivityManager.shared
@ObservedObject var batteryModel = BatteryStatusViewModel.shared
@ObservedObject var statsManager = StatsManager.shared
// NOTE: Intentionally NOT @ObservedObject. `StatsManager` publishes every ~1s,
// and observing it here invalidated the entire ContentView body on every tick.
// ContentView only uses it for imperative `updateMonitoringState(...)` calls and
// for `statsRowCount()` (which reads @Default flags, not published stats values).
// The live stats UI (`NotchStatsView`) owns its own @ObservedObject StatsManager,
// so reactivity for the stats display is preserved and now scoped to that leaf.
let statsManager = StatsManager.shared
@ObservedObject var recordingManager = ScreenRecordingManager.shared
@ObservedObject var privacyManager = PrivacyIndicatorManager.shared
@ObservedObject var doNotDisturbManager = DoNotDisturbManager.shared
Expand Down Expand Up @@ -212,6 +218,11 @@ struct ContentView: View {
@State private var hiddenEdgeHoverPollingTask: Task<Void, Never>?
@State private var isHoveringClosedMusicWaveformControl: Bool = false

/// Cached result of the `NSScreen.screens` notch scan (see `isNonNotchScreen`).
/// Defaults to `true`, matching the original "screen not found" fallback, and
/// is refreshed on appear / screen-parameter change / selected-screen change.
@State private var cachedIsNonNotchScreen: Bool = true

@State private var gestureProgress: CGFloat = .zero
@State private var skipGestureActiveDirection: MusicManager.SkipDirection?
@State private var isMusicControlWindowVisible = false
Expand Down Expand Up @@ -384,13 +395,34 @@ struct ContentView: View {
}

/// Whether the current screen lacks a physical notch.
///
/// Reads a cached value instead of scanning `NSScreen.screens` on every body
/// evaluation. The cache is recomputed only when its inputs can change:
/// on appear, when the screen configuration changes
/// (`didChangeScreenParametersNotification`), or when the selected screen
/// name changes. Behaviour is identical to the previous inline scan.
private var isNonNotchScreen: Bool {
cachedIsNonNotchScreen
}

/// Performs the actual `NSScreen.screens` scan that backs `isNonNotchScreen`.
/// Kept behaviour-identical to the original computed property (including the
/// `true` fallback when no matching screen is found).
private func computeIsNonNotchScreen() -> Bool {
guard let screen = NSScreen.screens.first(where: { $0.localizedName == currentScreenName }) else {
return true
}
return screen.safeAreaInsets.top <= 0
}

/// Refreshes `cachedIsNonNotchScreen` if the scan result has changed.
private func refreshIsNonNotchScreenCache() {
let value = computeIsNonNotchScreen()
if value != cachedIsNonNotchScreen {
cachedIsNonNotchScreen = value
}
}

/// Whether the global sneak peek is visible on this specific screen.
private var isSneakPeekVisibleOnCurrentScreen: Bool {
guard coordinator.sneakPeek.show else { return false }
Expand Down Expand Up @@ -741,6 +773,7 @@ struct ContentView: View {
private func installPrimaryRootLifecycleHandlers<Content: View>(on view: Content) -> some View {
view
.onAppear {
refreshIsNonNotchScreenCache()
isMusicControlWindowSuppressed = vm.notchState != .closed
|| lockScreenManager.isLocked
|| isMusicHUDDeferredAfterUnlock
Expand Down Expand Up @@ -858,6 +891,12 @@ struct ContentView: View {
enqueueMusicControlWindowSync(forceRefresh: true)
}
}
.onChange(of: currentScreenName) { _, _ in
refreshIsNonNotchScreenCache()
}
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didChangeScreenParametersNotification)) { _ in
refreshIsNonNotchScreenCache()
}
.onDisappear {
performViewTeardown()
}
Expand Down
66 changes: 50 additions & 16 deletions DynamicIsland/DynamicIslandApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,23 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
var whatsNewWindow: NSWindow?
var timer: Timer?
let calendarManager = CalendarManager.shared
let webcamManager = WebcamManager.shared
let dndManager = DoNotDisturbManager.shared // NEW: DND detection
let bluetoothAudioManager = BluetoothAudioManager.shared // NEW: Bluetooth audio detection
let idleAnimationManager = IdleAnimationManager.shared // NEW: Custom idle animations
let downloadManager = DownloadManager.shared // NEW: Chromium downloads detection
let lockScreenPanelManager = LockScreenPanelManager.shared // NEW: Lock screen music panel
let mediaControlsStateCoordinator = MediaControlsStateCoordinator.shared
let systemTimerBridge = SystemTimerBridge.shared
let extensionXPCServiceHost = ExtensionXPCServiceHost.shared
let extensionRPCServer = ExtensionRPCServer.shared
// Feature-dependent managers are lazily initialized so their (sometimes
// observer/poller-heavy) `.shared` init does not run synchronously when the
// AppDelegate object is allocated (before applicationDidFinishLaunching).
// Each is either kept alive by an external owner that is touched during
// launch (ContentView / vm / ReminderLiveActivityManager), gated behind its
// feature flag, or explicitly touched from the deferred launch block below.
lazy var calendarManager = CalendarManager.shared // owned by ReminderLiveActivityManager (touched at launch)
let webcamManager = WebcamManager.shared // eager: consumed by createDynamicIslandWindow during launch; also owned by vm
lazy var dndManager = DoNotDisturbManager.shared // NEW: DND detection (gated by enableDoNotDisturbDetection; owned by ContentView)
lazy var bluetoothAudioManager = BluetoothAudioManager.shared // NEW: Bluetooth audio detection (deferred touch)
lazy var idleAnimationManager = IdleAnimationManager.shared // NEW: Custom idle animations (touched via deferred initializeDefaultAnimations)
lazy var downloadManager = DownloadManager.shared // NEW: Chromium downloads detection (owned by ContentView)
lazy var lockScreenPanelManager = LockScreenPanelManager.shared // NEW: Lock screen music panel (deferred touch)
let mediaControlsStateCoordinator = MediaControlsStateCoordinator.shared // eager: settings-consistency glue with no other owner; cheap init
lazy var systemTimerBridge = SystemTimerBridge.shared // deferred touch (no external owner; init hosts mirrorSystemTimer observer)
let extensionXPCServiceHost = ExtensionXPCServiceHost.shared // eager: start()/stop() lifecycle driven from launch/terminate
let extensionRPCServer = ExtensionRPCServer.shared // eager: start()/stop() lifecycle driven from launch/terminate
var closeNotchWorkItem: DispatchWorkItem?
private var previousScreens: [NSScreen]?
private var onboardingWindowController: NSWindowController?
Expand Down Expand Up @@ -611,10 +617,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
.store(in: &cancellables)

// Initialize idle animations (load bundled + built-in face)
idleAnimationManager.initializeDefaultAnimations()

applySelectedAppIcon()
// Idle-animation loading (disk I/O) and app-icon application (disk I/O)
// are deferred to the post-first-frame block at the end of this method.
installTopMenuItemsIfNeeded()

Defaults.publisher(.focusMonitoringMode, options: [])
Expand Down Expand Up @@ -926,8 +930,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if coordinator.firstLaunch && !AppRuntimeEnvironment.isUITesting {
DispatchQueue.main.async {
self.showOnboardingWindow()
self.playWelcomeSound()
}
playWelcomeSound()
}

previousScreens = NSScreen.screens
Expand All @@ -945,6 +949,36 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let timerWidgetManager = LockScreenTimerWidgetManager.shared
timerWidgetManager.handleLockStateChange(isLocked: LockScreenManager.shared.currentLockStatus)

// Defer non-critical launch work until after the first window/notch is on
// screen. This runs on the next main-runloop turn, once the synchronous
// launch path (window creation above) has completed.
DispatchQueue.main.async { [weak self] in
guard let self else { return }

// Idle animations: resolves bundled resource URLs and reconciles the
// Defaults keys that back the animation picker. Kept on the main actor
// because those Defaults writes have UI observers — and it only resolves
// bundle URLs (no file-content reads), so it stays cheap here.
self.idleAnimationManager.initializeDefaultAnimations()

// App icon: the image file read is the one real disk hit on this path,
// so load it off-main (mirrors ModelPricingManager.loadInitialPricing)
// and publish applicationIconImage back on the main actor.
Task.detached(priority: .utility) {
let image = loadSelectedAppIconImage()
await MainActor.run {
if let image { NSApp.applicationIconImage = image }
}
}

// Instantiate always-on managers whose init only registers
// observers/pollers. They have no other owner touched during launch,
// so they must be materialized here to preserve behavior — just one
// runloop later than before, off the synchronous launch path.
_ = self.bluetoothAudioManager
_ = self.systemTimerBridge
_ = self.lockScreenPanelManager
}
}

private func installTopMenuItemsIfNeeded() {
Expand Down
35 changes: 24 additions & 11 deletions DynamicIsland/MediaControllers/AmazonMusicController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ class FilteredNowPlayingController: ObservableObject, MediaControllerProtocol {
/// True only after a stream line explicitly identified the selected app as the now playing source.
private var targetSessionActive = false

/// Reused across every stream update to avoid re-allocating a formatter
/// on each (1+/second) now playing payload.
private static let iso8601Formatter = ISO8601DateFormatter()

init?(bundleIdentifier: String, controllerName: String) {
self.targetBundleIdentifier = bundleIdentifier
self.controllerName = controllerName
Expand Down Expand Up @@ -136,14 +140,18 @@ class FilteredNowPlayingController: ObservableObject, MediaControllerProtocol {
}

func toggleShuffle() async {
MRMediaRemoteSetShuffleModeFunction(playbackState.isShuffled ? 1 : 3)
playbackState.isShuffled.toggle()
await MainActor.run {
MRMediaRemoteSetShuffleModeFunction(playbackState.isShuffled ? 1 : 3)
playbackState.isShuffled.toggle()
}
}

func toggleRepeat() async {
let newRepeatMode = (playbackState.repeatMode == .off) ? 3 : (playbackState.repeatMode.rawValue - 1)
playbackState.repeatMode = RepeatMode(rawValue: newRepeatMode) ?? .off
MRMediaRemoteSetRepeatModeFunction(newRepeatMode)
await MainActor.run {
let newRepeatMode = (playbackState.repeatMode == .off) ? 3 : (playbackState.repeatMode.rawValue - 1)
playbackState.repeatMode = RepeatMode(rawValue: newRepeatMode) ?? .off
MRMediaRemoteSetRepeatModeFunction(newRepeatMode)
}
}

private func setupNowPlayingObserver() async {
Expand Down Expand Up @@ -214,9 +222,12 @@ class FilteredNowPlayingController: ObservableObject, MediaControllerProtocol {
return state
}

private func applyIdleBecauseDifferentSource() {
private func applyIdleBecauseDifferentSource() async {
targetSessionActive = false
playbackState = Self.makeIdlePlaybackState(bundleIdentifier: targetBundleIdentifier)
let idleState = Self.makeIdlePlaybackState(bundleIdentifier: targetBundleIdentifier)
await MainActor.run { [weak self] in
self?.playbackState = idleState
}
}

private func handleAdapterUpdate(_ update: NowPlayingUpdate) async {
Expand All @@ -233,12 +244,12 @@ class FilteredNowPlayingController: ObservableObject, MediaControllerProtocol {

if let source = explicitSource {
if source != targetBundleIdentifier {
applyIdleBecauseDifferentSource()
await applyIdleBecauseDifferentSource()
return
}
targetSessionActive = true
} else if !diff {
applyIdleBecauseDifferentSource()
await applyIdleBecauseDifferentSource()
return
} else if !targetSessionActive {
return
Expand Down Expand Up @@ -276,7 +287,7 @@ class FilteredNowPlayingController: ObservableObject, MediaControllerProtocol {
}

if let dateString = payload.timestamp,
let date = ISO8601DateFormatter().date(from: dateString) {
let date = Self.iso8601Formatter.date(from: dateString) {
newPlaybackState.lastUpdated = date
} else if !diff {
newPlaybackState.lastUpdated = Date()
Expand All @@ -288,7 +299,9 @@ class FilteredNowPlayingController: ObservableObject, MediaControllerProtocol {
newPlaybackState.isPlaying = payload.playing ?? (diff ? self.playbackState.isPlaying : false)
newPlaybackState.bundleIdentifier = targetBundleIdentifier

self.playbackState = newPlaybackState
await MainActor.run { [weak self] in
self?.playbackState = newPlaybackState
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion DynamicIsland/MediaControllers/AppleMusicController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,10 @@ class AppleMusicController: MediaControllerProtocol {
}

updatedState.lastUpdated = Date()
self.playbackState = updatedState
let finalState = updatedState
await MainActor.run { [weak self] in
self?.playbackState = finalState
}
}

// MARK: - Private Methods
Expand Down
Loading