-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Warn before Sparkle update relaunch kills terminal sessions #1986
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fa7c166
53e3591
bb5efc4
d481640
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1994,6 +1994,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let display: SessionDisplaySnapshot? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private struct UpdateRelaunchTerminalSummary { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let workspaceCount: Int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let terminalSessionCount: Int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let runningCommandCount: Int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let remoteTerminalSessionCount: Int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static let persistedWindowGeometryDefaultsKey = "cmux.session.lastWindowGeometry.v1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| weak var tabManager: TabManager? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -5889,6 +5896,59 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateController.attemptUpdate() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func confirmUpdateRelaunchIfNeeded() -> Bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard let summary = updateRelaunchTerminalSummary() else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UpdateLogStore.shared.append("update relaunch warning skipped (no active terminal sessions)") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UpdateLogStore.shared.append( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "update relaunch warning shown " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "(workspaces=\(summary.workspaceCount), sessions=\(summary.terminalSessionCount), " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "running=\(summary.runningCommandCount), remote=\(summary.remoteTerminalSessionCount))" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let alert = NSAlert() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert.alertStyle = .warning | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert.messageText = String( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| localized: "update.relaunchWarning.title", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defaultValue: "Update Will Close Terminal Sessions" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert.informativeText = String( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| localized: "update.relaunchWarning.message", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defaultValue: """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| This update will relaunch cmux and terminate in-app terminal sessions. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Workspaces affected: \(summary.workspaceCount) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Terminal sessions affected: \(summary.terminalSessionCount) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Running commands detected: \(summary.runningCommandCount) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Remote / SSH terminal sessions: \(summary.remoteTerminalSessionCount) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Shells, SSH connections, and agents attached to these PTYs will be terminated. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+5917
to
+5928
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use plural-aware localized strings for the count lines. This packs four counted labels into one fixed-plural paragraph, so translators cannot independently handle Based on learnings: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert.addButton(withTitle: String(localized: "common.restartNow", defaultValue: "Restart Now")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert.addButton(withTitle: String(localized: "common.later", defaultValue: "Later")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let updateNowButton = alert.buttons.first { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateNowButton.keyEquivalent = "\r" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateNowButton.keyEquivalentModifierMask = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert.window.defaultButtonCell = updateNowButton.cell as? NSButtonCell | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert.window.initialFirstResponder = updateNowButton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let deferButton = alert.buttons.dropFirst().first { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deferButton.keyEquivalent = "\u{1b}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if NSApp.activationPolicy() == .regular { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NSApp.activate(ignoringOtherApps: true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let confirmed = alert.runModal() == .alertFirstButtonReturn | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UpdateLogStore.shared.append("update relaunch warning result=\(confirmed ? "confirm" : "defer")") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return confirmed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func isCmuxCLIInstalledInPATH() -> Bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CmuxCLIPathInstaller().isInstalled() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -5958,6 +6018,61 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private func updateRelaunchTerminalSummary() -> UpdateRelaunchTerminalSummary? { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var seenManagers = Set<ObjectIdentifier>() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var managers: [TabManager] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for context in mainWindowContexts.values { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let managerId = ObjectIdentifier(context.tabManager) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard seenManagers.insert(managerId).inserted else { continue } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| managers.append(context.tabManager) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if managers.isEmpty, let tabManager { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| managers.append(tabManager) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+6021
to
+6033
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not hide the active This only appends ♻️ Suggested adjustment for context in mainWindowContexts.values {
let managerId = ObjectIdentifier(context.tabManager)
guard seenManagers.insert(managerId).inserted else { continue }
managers.append(context.tabManager)
}
- if managers.isEmpty, let tabManager {
- managers.append(tabManager)
+ if let tabManager {
+ let managerId = ObjectIdentifier(tabManager)
+ if seenManagers.insert(managerId).inserted {
+ managers.append(tabManager)
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var workspaceCount = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var terminalSessionCount = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var runningCommandCount = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var remoteTerminalSessionCount = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for manager in managers { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for workspace in manager.tabs { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var workspaceHasTerminalSession = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (panelId, panel) in workspace.panels { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard let terminalPanel = panel as? TerminalPanel else { continue } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspaceHasTerminalSession = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| terminalSessionCount += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if workspace.panelNeedsConfirmClose( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| panelId: panelId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fallbackNeedsConfirmClose: terminalPanel.needsConfirmClose() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runningCommandCount += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+6049
to
+6053
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a busy-session count, not a command count. This increments once per terminal panel based on 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+6048
to
+6055
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The counter increments once per Consider renaming the field and label to |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if workspace.isRemoteTerminalSurface(panelId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| remoteTerminalSessionCount += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if workspaceHasTerminalSession { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspaceCount += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard terminalSessionCount > 0 else { return nil } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return UpdateRelaunchTerminalSummary( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspaceCount: workspaceCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| terminalSessionCount: terminalSessionCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runningCommandCount: runningCommandCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| remoteTerminalSessionCount: remoteTerminalSessionCount | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @objc func restartSocketListener(_ sender: Any?) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard tabManager != nil else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NSSound.beep() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,7 @@ class UpdateDriver: NSObject, SPUUserDriver { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private var lastFeedURLString: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private var pendingUserInitiatedCheckPresentation: UpdateUserInitiatedCheckPresentation? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private var activeUserInitiatedCheckPresentation: UpdateUserInitiatedCheckPresentation? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var confirmUpdateRelaunchIfNeededHandler: (() -> Bool)? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| init(viewModel: UpdateViewModel, hostBundle: Bundle) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewModel = viewModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -71,17 +72,21 @@ class UpdateDriver: NSObject, SPUUserDriver { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state: SPUUserUpdateState, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply: @escaping @Sendable (SPUUserUpdateChoice) -> Void) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UpdateLogStore.shared.append("show update found: \(appcastItem.displayVersionString)") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let forwardedReply: @Sendable (SPUUserUpdateChoice) -> Void = { [weak self] choice in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if choice != .install { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self?.finishUserInitiatedCheckPresentation() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply(choice) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let guardedReply = shouldGuardInstallChoice(for: state) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? makeGuardedUpdateInstallChoiceReply(forwardedReply) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : forwardedReply | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if usesStandardPresentation { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clearCustomStateForStandardPresentation() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| standard.showUpdateFound(with: appcastItem, state: state) { [weak self] choice in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if choice != .install { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self?.finishUserInitiatedCheckPresentation() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply(choice) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| standard.showUpdateFound(with: appcastItem, state: state, reply: guardedReply) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setStateAfterMinimumCheckDelay(.updateAvailable(.init(appcastItem: appcastItem, reply: reply))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setStateAfterMinimumCheckDelay(.updateAvailable(.init(appcastItem: appcastItem, reply: guardedReply))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func showUpdateReleaseNotes(with downloadData: SPUDownloadData) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -208,21 +213,35 @@ class UpdateDriver: NSObject, SPUUserDriver { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func showReady(toInstallAndRelaunch reply: @escaping @Sendable (SPUUserUpdateChoice) -> Void) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UpdateLogStore.shared.append("show ready to install") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let forwardedReply: @Sendable (SPUUserUpdateChoice) -> Void = { [weak self] choice in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if choice != .install { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self?.finishUserInitiatedCheckPresentation() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply(choice) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let guardedReply = makeGuardedUpdateInstallChoiceReply(forwardedReply) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if usesStandardPresentation { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| standard.showReady(toInstallAndRelaunch: reply) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| standard.showReady(toInstallAndRelaunch: guardedReply) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply(.install) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guardedReply(.install) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func showInstallingUpdate(withApplicationTerminated applicationTerminated: Bool, retryTerminatingApplication: @escaping () -> Void) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UpdateLogStore.shared.append("show installing update") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let guardedRetryTerminatingApplication = makeGuardedUpdateRelaunchAction( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applicationTerminated: applicationTerminated, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| action: retryTerminatingApplication | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if usesStandardPresentation { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| standard.showInstallingUpdate(withApplicationTerminated: applicationTerminated, retryTerminatingApplication: retryTerminatingApplication) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| standard.showInstallingUpdate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| withApplicationTerminated: applicationTerminated, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retryTerminatingApplication: guardedRetryTerminatingApplication | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setState(.installing(.init( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retryTerminatingApplication: retryTerminatingApplication, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retryTerminatingApplication: guardedRetryTerminatingApplication, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dismiss: { [weak viewModel] in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel?.state = .idle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -457,6 +476,63 @@ class UpdateDriver: NSObject, SPUUserDriver { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private func shouldGuardInstallChoice(for state: SPUUserUpdateState) -> Bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state.stage != .notDownloaded | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @MainActor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private func confirmUpdateRelaunchIfNeeded() -> Bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| confirmUpdateRelaunchIfNeededHandler?() ?? AppDelegate.shared?.confirmUpdateRelaunchIfNeeded() ?? true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func makeGuardedUpdateInstallChoiceReply( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ reply: @escaping @Sendable (SPUUserUpdateChoice) -> Void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> @Sendable (SPUUserUpdateChoice) -> Void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { [weak self] choice in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard choice == .install else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply(choice) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Task { @MainActor [weak self] in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let confirmed = self?.confirmUpdateRelaunchIfNeeded() ?? true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard confirmed else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self?.recordDeferredUpdateRelaunch() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply(.dismiss) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply(.install) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func makeGuardedUpdateRelaunchAction( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applicationTerminated: Bool, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| action: @escaping () -> Void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> () -> Void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { [weak self] in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Task { @MainActor [weak self] in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if applicationTerminated { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| action() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let confirmed = self?.confirmUpdateRelaunchIfNeeded() ?? true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard confirmed else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self?.recordDeferredUpdateRelaunch() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| action() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+514
to
+529
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The outer closure captures
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private func recordDeferredUpdateRelaunch() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UpdateLogStore.shared.append("update relaunch deferred due to active terminal sessions") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private func runOnMain(_ action: @escaping () -> Void) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if Thread.isMainThread { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| action() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MainActorannotationconfirmUpdateRelaunchIfNeeded()callsNSAlert.runModal()(which must run on the main thread) and reads main-thread-only state viaupdateRelaunchTerminalSummary()(mainWindowContexts,tabManager). The current call site wraps it inTask { @MainActor }correctly, but without the annotation the compiler won't prevent future callers from invoking this method off-actor.