From bb183a701d7eceb2c4a169b152c8195fea0f6747 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Fri, 9 Jan 2026 12:32:00 +0530 Subject: [PATCH] BugFix: Display Lock and Unlock Lag --- .../LockScreenDisplayContextProvider.swift | 83 +++++++++++++++++++ .../LockScreenLiveActivityWindowManager.swift | 2 +- .../managers/LockScreenManager.swift | 2 + .../managers/LockScreenPanelManager.swift | 10 ++- ...LockScreenReminderWidgetPanelManager.swift | 8 +- .../LockScreenTimerWidgetManager.swift | 8 +- .../managers/LockScreenWeatherManager.swift | 29 +++++-- .../LockScreenWeatherPanelManager.swift | 8 +- 8 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 DynamicIsland/managers/LockScreenDisplayContextProvider.swift diff --git a/DynamicIsland/managers/LockScreenDisplayContextProvider.swift b/DynamicIsland/managers/LockScreenDisplayContextProvider.swift new file mode 100644 index 0000000..933f172 --- /dev/null +++ b/DynamicIsland/managers/LockScreenDisplayContextProvider.swift @@ -0,0 +1,83 @@ +import AppKit + +struct LockScreenDisplayContext { + let screen: NSScreen + let frame: NSRect + let identifier: String +} + +@MainActor +final class LockScreenDisplayContextProvider { + static let shared = LockScreenDisplayContextProvider() + + private(set) var context: LockScreenDisplayContext? + private var screenChangeObserver: NSObjectProtocol? + private var workspaceObservers: [NSObjectProtocol] = [] + + private init() { + refresh(reason: "init") + registerObservers() + } + + deinit { + if let screenChangeObserver { + NotificationCenter.default.removeObserver(screenChangeObserver) + } + + let workspaceCenter = NSWorkspace.shared.notificationCenter + workspaceObservers.forEach { workspaceCenter.removeObserver($0) } + } + + @discardableResult + func refresh(reason: String) -> LockScreenDisplayContext? { + guard let screen = NSScreen.main ?? NSScreen.screens.first else { + context = nil + return nil + } + + let snapshot = LockScreenDisplayContext( + screen: screen, + frame: screen.frame, + identifier: screen.localizedName + ) + + context = snapshot + return snapshot + } + + func contextSnapshot() -> LockScreenDisplayContext? { + if let context { + return context + } + return refresh(reason: "snapshot-miss") + } + + private func registerObservers() { + screenChangeObserver = NotificationCenter.default.addObserver( + forName: NSApplication.didChangeScreenParametersNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.refresh(reason: "screen-parameters") + } + + let workspaceCenter = NSWorkspace.shared.notificationCenter + let wakeObserver = workspaceCenter.addObserver( + forName: NSWorkspace.screensDidWakeNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.refresh(reason: "screens-did-wake") + } + + let spaceObserver = workspaceCenter.addObserver( + forName: NSWorkspace.activeSpaceDidChangeNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.refresh(reason: "space-changed") + } + + workspaceObservers = [wakeObserver, spaceObserver] + } +} diff --git a/DynamicIsland/managers/LockScreenLiveActivityWindowManager.swift b/DynamicIsland/managers/LockScreenLiveActivityWindowManager.swift index dda84e4..3aacfc1 100644 --- a/DynamicIsland/managers/LockScreenLiveActivityWindowManager.swift +++ b/DynamicIsland/managers/LockScreenLiveActivityWindowManager.swift @@ -83,7 +83,7 @@ class LockScreenLiveActivityWindowManager { } private func lockContext() -> (notchSize: CGSize, screen: NSScreen)? { - guard let screen = NSScreen.main else { + guard let screen = LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main else { print("[\(timestamp())] LockScreenLiveActivityWindowManager: no main screen available") return nil } diff --git a/DynamicIsland/managers/LockScreenManager.swift b/DynamicIsland/managers/LockScreenManager.swift index c8dad66..f578dae 100644 --- a/DynamicIsland/managers/LockScreenManager.swift +++ b/DynamicIsland/managers/LockScreenManager.swift @@ -87,6 +87,7 @@ class LockScreenManager: ObservableObject { print("[\(timestamp())] LockScreenManager: 🔒 Screen LOCKED event received") Logger.log("LockScreenManager: Screen locked", category: .lifecycle) LockSoundPlayer.shared.playLockChime() + LockScreenDisplayContextProvider.shared.refresh(reason: "screen-locked") // Update state SYNCHRONOUSLY without Task/await to avoid any delay lastUpdated = Date() @@ -134,6 +135,7 @@ class LockScreenManager: ObservableObject { print("[\(timestamp())] LockScreenManager: 🔓 Screen UNLOCKED event received") Logger.log("LockScreenManager: Screen unlocked", category: .lifecycle) LockSoundPlayer.shared.playUnlockChime() + LockScreenDisplayContextProvider.shared.refresh(reason: "screen-unlocked") lastUpdated = Date() updateIdleState(locked: false) isLocked = false diff --git a/DynamicIsland/managers/LockScreenPanelManager.swift b/DynamicIsland/managers/LockScreenPanelManager.swift index ff2572d..4c2283a 100644 --- a/DynamicIsland/managers/LockScreenPanelManager.swift +++ b/DynamicIsland/managers/LockScreenPanelManager.swift @@ -74,7 +74,7 @@ class LockScreenPanelManager { return } - guard let screen = NSScreen.main else { + guard let screen = currentScreen() else { print("[\(timestamp())] LockScreenPanelManager: no main screen available") return } @@ -188,7 +188,7 @@ class LockScreenPanelManager { } func applyOffsetAdjustment(animated: Bool = true) { - guard let screen = NSScreen.main else { return } + guard let screen = currentScreen() else { return } let screenFrame = screen.frame let newCollapsed = collapsedFrame(for: screenFrame) collapsedFrame = newCollapsed @@ -225,7 +225,7 @@ class LockScreenPanelManager { private func handleScreenGeometryChange(reason: String) { guard let window = panelWindow else { return } guard window.isVisible || panelAnimator.isPresented else { return } - guard let screen = NSScreen.main else { return } + guard let screen = currentScreen() else { return } let screenFrame = screen.frame collapsedFrame = collapsedFrame(for: screenFrame) @@ -245,4 +245,8 @@ class LockScreenPanelManager { let originY = baseOriginY + defaultLowering + clampedOffset return NSRect(x: originX, y: originY, width: collapsedSize.width, height: collapsedSize.height) } + + private func currentScreen() -> NSScreen? { + LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main + } } diff --git a/DynamicIsland/managers/LockScreenReminderWidgetPanelManager.swift b/DynamicIsland/managers/LockScreenReminderWidgetPanelManager.swift index 7b5d0f4..db3d69c 100644 --- a/DynamicIsland/managers/LockScreenReminderWidgetPanelManager.swift +++ b/DynamicIsland/managers/LockScreenReminderWidgetPanelManager.swift @@ -35,7 +35,7 @@ final class LockScreenReminderWidgetPanelManager { } func refreshPosition(animated: Bool) { - guard let window, window.isVisible, let screen = NSScreen.main else { return } + guard let window, window.isVisible, let screen = currentScreen() else { return } let target = frame(for: window.frame.size, on: screen) if animated { NSAnimationContext.runAnimationGroup { context in @@ -49,7 +49,7 @@ final class LockScreenReminderWidgetPanelManager { } private func render(snapshot: LockScreenReminderWidgetSnapshot, makeVisible: Bool) { - guard let screen = NSScreen.main else { return } + guard let screen = currentScreen() else { return } if !makeVisible, window == nil { return } @@ -161,5 +161,9 @@ final class LockScreenReminderWidgetPanelManager { refreshPosition(animated: false) print("LockScreenReminderWidgetPanelManager: realigned window due to \(reason)") } + + private func currentScreen() -> NSScreen? { + LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main + } } diff --git a/DynamicIsland/managers/LockScreenTimerWidgetManager.swift b/DynamicIsland/managers/LockScreenTimerWidgetManager.swift index ccfb6e4..62cda9d 100644 --- a/DynamicIsland/managers/LockScreenTimerWidgetManager.swift +++ b/DynamicIsland/managers/LockScreenTimerWidgetManager.swift @@ -110,7 +110,7 @@ final class LockScreenTimerWidgetPanelManager { } func showWidget() { - guard let screen = NSScreen.main else { return } + guard let screen = currentScreen() else { return } let window = ensureWindow() let frame = targetFrame(on: screen) window.setFrame(frame, display: true) @@ -150,7 +150,7 @@ final class LockScreenTimerWidgetPanelManager { func refreshPosition(animated: Bool) { - guard let window, window.isVisible, let screen = NSScreen.main else { return } + guard let window, window.isVisible, let screen = currentScreen() else { return } let frame = targetFrame(on: screen) if animated { NSAnimationContext.runAnimationGroup { context in @@ -279,4 +279,8 @@ final class LockScreenTimerWidgetPanelManager { refreshPosition(animated: false) print("LockScreenTimerWidgetPanelManager: realigned window due to \(reason)") } + + private func currentScreen() -> NSScreen? { + LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main + } } diff --git a/DynamicIsland/managers/LockScreenWeatherManager.swift b/DynamicIsland/managers/LockScreenWeatherManager.swift index e238f7b..1a1287e 100644 --- a/DynamicIsland/managers/LockScreenWeatherManager.swift +++ b/DynamicIsland/managers/LockScreenWeatherManager.swift @@ -35,19 +35,32 @@ final class LockScreenWeatherManager: ObservableObject { } locationProvider.prepareAuthorization() + let existingSnapshot = snapshot + let hadSnapshot = existingSnapshot != nil + var cachedSnapshot = existingSnapshot + + if cachedSnapshot == nil, let payload = latestWeatherPayload { + let derivedSnapshot = makeSnapshot(from: payload) + snapshot = derivedSnapshot + cachedSnapshot = derivedSnapshot + } + + if let cachedSnapshot { + deliver(cachedSnapshot, forceShow: true) + } + + let needsForceShowOnRefresh = cachedSnapshot == nil + let shouldForceRefresh = !hadSnapshot Task { @MainActor [weak self] in guard let self else { return } - let snapshot = await self.refresh(force: self.snapshot == nil) + let refreshedSnapshot = await self.refresh(force: shouldForceRefresh) - guard LockScreenManager.shared.currentLockStatus else { - LockScreenWeatherPanelManager.shared.hide() - return - } + guard LockScreenManager.shared.currentLockStatus else { return } - if let snapshot { - self.deliver(snapshot, forceShow: true) - } else { + if let refreshedSnapshot { + self.deliver(refreshedSnapshot, forceShow: needsForceShowOnRefresh) + } else if needsForceShowOnRefresh { LockScreenWeatherPanelManager.shared.hide() } } diff --git a/DynamicIsland/managers/LockScreenWeatherPanelManager.swift b/DynamicIsland/managers/LockScreenWeatherPanelManager.swift index 9ae3c08..7c38fcb 100644 --- a/DynamicIsland/managers/LockScreenWeatherPanelManager.swift +++ b/DynamicIsland/managers/LockScreenWeatherPanelManager.swift @@ -38,7 +38,7 @@ final class LockScreenWeatherPanelManager { } private func render(snapshot: LockScreenWeatherSnapshot, makeVisible: Bool) { - guard let screen = NSScreen.main else { return } + guard let screen = currentScreen() else { return } if !makeVisible, window == nil { return } @@ -111,7 +111,7 @@ final class LockScreenWeatherPanelManager { func refreshPositionForOffsets(animated: Bool = true) { guard let window, let snapshot = lastSnapshot else { return } - guard let screen = NSScreen.main else { return } + guard let screen = currentScreen() else { return } let size = lastContentSize ?? window.frame.size let targetFrame = frame(for: size, snapshot: snapshot, on: screen) latestFrame = targetFrame @@ -153,4 +153,8 @@ final class LockScreenWeatherPanelManager { refreshPositionForOffsets(animated: false) print("LockScreenWeatherPanelManager: realigned window due to \(reason)") } + + private func currentScreen() -> NSScreen? { + LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main + } }