Add quit and relaunch logic to macOS FMAs (#37670)

This pull request enhances the macOS app installation process by
improving how running applications are handled during install and
update, and also updates the metadata and scripts for Docker Desktop.
The main improvements are the introduction of quit/relaunch logic for
pkg-based FMAs, and the renaming and updating of Docker Desktop’s
identifiers and scripts.

**App install/relaunch improvements:**

* Added new shell functions `quit_and_track_application` and
`relaunch_application` to the generated install scripts. These functions
ensure that if an app (or pkg) is running before installation, it is
quit and then automatically relaunched after installation, preserving
user state. The logic tracks whether the app was running via an
environment variable.
[[1]](diffhunk://#diff-a9df2db484fcbb560d62c43f94c4bcc2d26dcf68066c9e7cc2bffad6f124ce97L22-R41)
[[2]](diffhunk://#diff-a9df2db484fcbb560d62c43f94c4bcc2d26dcf68066c9e7cc2bffad6f124ce97R53-R59)
[[3]](diffhunk://#diff-a9df2db484fcbb560d62c43f94c4bcc2d26dcf68066c9e7cc2bffad6f124ce97R72-R73)
[[4]](diffhunk://#diff-a9df2db484fcbb560d62c43f94c4bcc2d26dcf68066c9e7cc2bffad6f124ce97R571-R648)
* Removed the previous simpler `quit_application` logic from the install
script generation, as the new functions supersede it.

**Docker Desktop metadata and script updates:**

* Renamed the Docker Desktop input and updated its `slug` and
`unique_identifier` to match the new bundle identifier
(`com.electron.dockerdesktop`), reflecting the current packaging.
* Updated the output app metadata in `apps.json` to use the new slug and
unique identifier for Docker Desktop.
* Added a new output file for Docker Desktop
(`docker-desktop/darwin.json`) with the updated install and uninstall
scripts, including the new quit/relaunch logic and references.
This commit is contained in:
Allen Houchins
2026-01-05 22:31:55 -06:00
committed by GitHub
parent 67347a652a
commit 7d527ca23c
6 changed files with 131 additions and 20 deletions
+100 -10
View File
@@ -19,16 +19,26 @@ func installScriptForApp(app inputApp, cask *brewCask) (string, error) {
sb.Extract(app.InstallerFormat)
var includeQuitFunc bool
// Add quit/relaunch functions if we have App or Pkg artifacts
var needsQuitRelaunch bool
for _, artifact := range cask.Artifacts {
if len(artifact.App) > 0 || len(artifact.Pkg) > 0 {
needsQuitRelaunch = true
break
}
}
if needsQuitRelaunch {
sb.AddFunction("quit_and_track_application", quitAndTrackApplicationFunc)
sb.AddFunction("relaunch_application", relaunchApplicationFunc)
}
for _, artifact := range cask.Artifacts {
switch {
case len(artifact.App) > 0:
sb.Write("# copy to the applications folder")
sb.Writef("quit_application '%s'", app.UniqueIdentifier)
if cask.Token == "docker" {
sb.Writef("quit_application 'com.electron.dockerdesktop'")
}
includeQuitFunc = true
// Quit the app before installing if it's running, and track state for relaunch
sb.Writef("quit_and_track_application '%s'", app.UniqueIdentifier)
for _, appItem := range artifact.App {
// Only process string values (skip objects with target, those are handled by custom scripts)
if appItem.String == "" {
@@ -40,9 +50,13 @@ func installScriptForApp(app inputApp, cask *brewCask) (string, error) {
fi`, appPath)
sb.Copy(appPath, "$APPDIR")
}
// Relaunch the app if it was running before installation
sb.Writef("relaunch_application '%s'", app.UniqueIdentifier)
case len(artifact.Pkg) > 0:
sb.Write("# install pkg files")
// Quit the app before installing if it's running, and track state for relaunch
sb.Writef("quit_and_track_application '%s'", app.UniqueIdentifier)
switch len(artifact.Pkg) {
case 1:
if err := sb.InstallPkg(artifact.Pkg[0].String); err != nil {
@@ -55,6 +69,8 @@ fi`, appPath)
default:
return "", fmt.Errorf("application %s has unknown directive format for pkg", app.Token)
}
// Relaunch the app if it was running before installation
sb.Writef("relaunch_application '%s'", app.UniqueIdentifier)
case len(artifact.Binary) > 0:
if len(artifact.Binary) == 2 {
@@ -69,10 +85,6 @@ fi`, appPath)
}
}
if includeQuitFunc {
sb.AddFunction("quit_application", quitApplicationFunc)
}
return sb.String(), nil
}
@@ -556,6 +568,84 @@ const quitApplicationFunc = `quit_application() {
}
`
// quitAndTrackApplicationFunc quits a running application and tracks whether it was running
// so it can be relaunched after installation. Sets APP_WAS_RUNNING_<bundle_id> environment variable.
const quitAndTrackApplicationFunc = `quit_and_track_application() {
local bundle_id="$1"
local var_name="APP_WAS_RUNNING_$(echo "$bundle_id" | tr '.-' '__')"
local timeout_duration=10
# check if the application is running
if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then
eval "export $var_name=0"
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
eval "export $var_name=0"
return
fi
# App was running, mark it for relaunch
eval "export $var_name=1"
echo "Application '$bundle_id' was running; will relaunch after installation."
echo "Quitting application '$bundle_id'..."
# try to quit the application within the timeout period
local quit_success=false
SECONDS=0
while (( SECONDS < timeout_duration )); do
if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then
if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then
echo "Application '$bundle_id' quit successfully."
quit_success=true
break
fi
fi
sleep 1
done
if [[ "$quit_success" = false ]]; then
echo "Application '$bundle_id' did not quit."
fi
}
`
// relaunchApplicationFunc relaunches an application if it was running before installation.
// Checks the APP_WAS_RUNNING_<bundle_id> environment variable set by quitAndTrackApplicationFunc.
const relaunchApplicationFunc = `relaunch_application() {
local bundle_id="$1"
local var_name="APP_WAS_RUNNING_$(echo "$bundle_id" | tr '.-' '__')"
local was_running
# Check if the app was running before installation
eval "was_running=\$$var_name"
if [[ "$was_running" != "1" ]]; then
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
echo "Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'."
return
fi
echo "Relaunching application '$bundle_id'..."
# Try to launch the application
if osascript -e "tell application id \"$bundle_id\" to activate" >/dev/null 2>&1; then
echo "Application '$bundle_id' relaunched successfully."
else
echo "Failed to relaunch application '$bundle_id'."
fi
}
`
const trashFunc = `trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -1,7 +1,7 @@
{
"name": "Docker Desktop",
"slug": "docker/darwin",
"unique_identifier": "com.docker.docker",
"slug": "docker-desktop/darwin",
"unique_identifier": "com.electron.dockerdesktop",
"token": "docker-desktop",
"installer_format": "dmg",
"default_categories": ["Developer tools"]
+2 -2
View File
@@ -570,9 +570,9 @@
},
{
"name": "Docker Desktop",
"slug": "docker/darwin",
"slug": "docker-desktop/darwin",
"platform": "darwin",
"unique_identifier": "com.docker.docker",
"unique_identifier": "com.electron.dockerdesktop",
"description": "Docker Desktop provides a seamless environment for building, sharing, and running containerized applications and microservices."
},
{
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB