tray: let Sparkle's install quit through the window-close interception - #451
Merged
Merged
Conversation
Sparkle's installer quits the app with a plain quit event, which the window-close interception cancelled whenever the window was on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UpdaterController raises the flag in updater(_:willInstallUpdate:), which Sparkle calls just before asking its installer to quit the app, and clears it when the update cycle finishes. AppDelegate passes it to QuitReason.shouldTerminate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 40 minutes. View limit detailsLimit details: You’ve used the included review currently available. Your 85 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (4)
Comment |
An installing flag that outlives a failed install no longer lets a plain Cmd-Q or Dock quit through; the quit must also come from org.sparkle-project.Sparkle.Updater. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ames AppDelegate resolves the quit event's sender PID to a bundle id and logs the decision while an update installs. UpdaterController names both hooked SPUUpdaterDelegate selectors so a Sparkle rename fails the build instead of silently dropping the hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Install and Relaunch now works on the first click when the mattstack window is open. Before this, the app turned Sparkle's quit into a window close, and the update only installed once a quit came in with the window already closed.
Root cause
To install, Sparkle's installer sends the app an ordinary quit event (
NSRunningApplication.terminate) with nokAEQuitReason.applicationShouldTerminatehands every quit toQuitReason.shouldTerminate. That function cancels any quit that isn't from the tray menu or the OS while the window is on screen. So the app closed its window and cancelled Sparkle's quit. Sparkle kept waiting, and each retry of Install and Relaunch hit the same interception.Fix
QuitReason.shouldTerminatetakes two new inputs:updateInstallingandsenderBundleIdentifier. The app terminates when an update is installing and the quit came from Sparkle's installer agent (org.sparkle-project.Sparkle.Updater, as in the bundledUpdater.appInfo.plist). A Cmd-Q, which has no AppleEvent, or a Dock quit still closes the window, even if the flag outlives a failed install.AppDelegatereads the quit event'skeySenderPIDAttrand gets the sender's bundle id fromNSRunningApplication(processIdentifier:).Updater.appis started withSMJobSubmit, but itsmaincallsNSApplication.sharedApplication, so it registers with LaunchServices. Today's unified log showslaunchservicesd CHECKIN ... org.sparkle-project.Sparkle.Updaterfor the installer run. While an update is installing, every quit logsquit during update installwith the sender and the decision.UpdaterControllersets the flag inupdater(_:willInstallUpdate:)and clears it inupdater(_:didFinishUpdateCycleFor:error:).AppDelegategets the flag through a callback and doesn't readupdater, because touching that lazy property would start Sparkle on a copy that's only quitting.Why
willInstallUpdate(checked against the Sparkle 2.10.0 source):SPUInstallerDriver.installWithToolAndRelaunchcallsinstallerWillFinishInstallationAndRelaunch, which firesupdater(_:willInstallUpdate:)and thenupdaterWillRelaunchApplication(_:). It does this on the main thread, before it sendsSPUResumeInstallationToStage2. The installer only sends the quit after stage 2 (AppInstaller.performStage2InstallationthensendTerminationSignal), so the flag is always set before the quit arrives.willInstallUpdatefires whether or not Sparkle relaunches the app.updaterWillRelaunchApplicationonly fires on a relaunch._notifiedDelegateInstallationWillFinish) and doesn't call it again when Install and Relaunch is retried. That's why the flag stays set until the update cycle ends and isn't reset after each quit.installWithToolAndRelaunch: the Install and Relaunch button, the retry button, a resumed update, and theimmediateInstallationBlockfromwillInstallUpdateOnQuit. Plain install-on-quit sends no quit from Sparkle. It waits for the app to exit.Verification
New checks: with the window on screen, Sparkle's installer quitting during an install terminates the app. With the window on screen, the window still closes for any of these:
Each new check failed before its fix.
swift build --package-path rt-trayis green.swift run --package-path rt-tray mattstack-checkspasses 528 checks with 0 failures.UpdaterControllernames both hookedSPUUpdaterDelegateselectors with#selector, so a Sparkle rename fails the build. I checked that a misspelled selector does fail to typecheck against the framework.Not run: a real Sparkle install in the app. That needs a signed build and an appcast.
🤖 Generated with Claude Code