From e13c8a4d430a520e8e65b6a3ed30fc49294a3315 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Tue, 19 May 2026 13:51:17 +0800 Subject: [PATCH] {"schema":"decodex/commit/1","summary":"Focus Sparkle update alerts","authority":"manual"} --- .../RsnapNativeHostKit/NativeHostApp.swift | 3 +- .../NativeHostSoftwareUpdater.swift | 41 +++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/native/macos-host/Sources/RsnapNativeHostKit/NativeHostApp.swift b/native/macos-host/Sources/RsnapNativeHostKit/NativeHostApp.swift index cc7c600..97b6094 100644 --- a/native/macos-host/Sources/RsnapNativeHostKit/NativeHostApp.swift +++ b/native/macos-host/Sources/RsnapNativeHostKit/NativeHostApp.swift @@ -108,7 +108,7 @@ public final class NativeHostApplicationController: NSObject, NSApplicationDeleg private var selfCaptureRegistrationWindow: NSWindow? private var didBootstrap = false private var didPresentLaunchPermissionOnboarding = false - private let softwareUpdater = NativeHostSoftwareUpdater() + private lazy var softwareUpdater = NativeHostSoftwareUpdater() @objc public dynamic var window: NSWindow? private lazy var sessionController: CaptureSessionController = { let controller = CaptureSessionController(settingsStore: settingsStore) @@ -145,6 +145,7 @@ public final class NativeHostApplicationController: NSObject, NSApplicationDeleg ) Self.applyApplicationIcon() configureStatusItem() + _ = softwareUpdater configureGlobalHotKeys() showSelfCaptureRegistrationWindow() NotificationCenter.default.addObserver( diff --git a/native/macos-host/Sources/RsnapNativeHostKit/NativeHostSoftwareUpdater.swift b/native/macos-host/Sources/RsnapNativeHostKit/NativeHostSoftwareUpdater.swift index cfee00d..e43ab33 100644 --- a/native/macos-host/Sources/RsnapNativeHostKit/NativeHostSoftwareUpdater.swift +++ b/native/macos-host/Sources/RsnapNativeHostKit/NativeHostSoftwareUpdater.swift @@ -3,7 +3,7 @@ import Foundation import Sparkle @MainActor -final class NativeHostSoftwareUpdater { +final class NativeHostSoftwareUpdater: NSObject { enum Mode: String, CaseIterable { case off case check @@ -60,14 +60,15 @@ final class NativeHostSoftwareUpdater { host: "github.com", path: "/hack-ink/rsnap/releases/latest") - private let updaterController: SPUStandardUpdaterController? + private var updaterController: SPUStandardUpdaterController? - init() { + override init() { + super.init() if Self.hasSparkleConfiguration { let controller = SPUStandardUpdaterController( startingUpdater: true, updaterDelegate: nil, - userDriverDelegate: nil) + userDriverDelegate: self) updaterController = controller NativeHostTelemetry.lifecycleEvent("native_host.sparkle_updater_started") requestImmediateLaunchUpdateCheckIfEnabled(using: controller.updater) @@ -177,6 +178,11 @@ final class NativeHostSoftwareUpdater { return trimmed.isEmpty ? nil : trimmed } + private func showUpdateAlertInFront() { + NSApp.setActivationPolicy(.regular) + NSRunningApplication.current.activate(options: [.activateAllWindows]) + } + private static func httpsURL(host: String, path: String) -> URL { var components = URLComponents() components.scheme = "https" @@ -189,6 +195,33 @@ final class NativeHostSoftwareUpdater { } } +extension NativeHostSoftwareUpdater: @preconcurrency SPUStandardUserDriverDelegate { + var supportsGentleScheduledUpdateReminders: Bool { + true + } + + func standardUserDriverShouldHandleShowingScheduledUpdate( + _: SUAppcastItem, + andInImmediateFocus _: Bool + ) -> Bool { + true + } + + func standardUserDriverWillHandleShowingUpdate( + _ handleShowingUpdate: Bool, + forUpdate _: SUAppcastItem, + state _: SPUUserUpdateState + ) { + guard handleShowingUpdate else { + return + } + showUpdateAlertInFront() + NativeHostTelemetry.lifecycleEvent( + "native_host.sparkle_update_alert_focused", + detail: "source=standard_driver") + } +} + package enum SoftwareUpdateModeResolution { fileprivate static func mode( automaticallyChecksForUpdates: Bool,