Skip to content

Instantly share code, notes, and snippets.

@gnattu
Created December 14, 2025 14:44
Show Gist options
  • Select an option

  • Save gnattu/34e6a52c2105501e0766c0246ab43537 to your computer and use it in GitHub Desktop.

Select an option

Save gnattu/34e6a52c2105501e0766c0246ab43537 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
usage() {
cat <<'EOF'
extract-dmg-layout.sh — extract Finder layout settings from an existing DMG.
This mounts the DMG, opens the disk in Finder, and queries:
- window position/size
- icon size / text size
- background picture path (if set)
- icon positions for *.app and the Applications link (if present)
Usage:
scripts/extract-dmg-layout.sh --dmg /path/Old.dmg [--export-dir dist/dmg-layout]
Options:
--dmg PATH Path to .dmg file (required)
--export-dir DIR Where to copy the extracted background image (default: dist/dmg-layout)
-h, --help Show help
Output:
Prints a ready-to-paste create-dmg snippet using the extracted values.
EOF
}
DMG_PATH=""
EXPORT_DIR="dist/dmg-layout"
while [[ $# -gt 0 ]]; do
case "$1" in
--dmg) DMG_PATH="${2:-}"; shift 2 ;;
--export-dir) EXPORT_DIR="${2:-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*)
echo "Unknown arg: $1" >&2
usage >&2
exit 2 ;;
esac
done
if [[ -z "$DMG_PATH" ]]; then
echo "Missing required --dmg" >&2
usage >&2
exit 2
fi
if [[ ! -f "$DMG_PATH" || "${DMG_PATH##*.}" != "dmg" ]]; then
echo "--dmg must point to a .dmg file: $DMG_PATH" >&2
exit 2
fi
for tool in hdiutil osascript; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "Missing required tool: $tool" >&2
exit 127
fi
done
TMP_ROOT="$(mktemp -d)"
cleanup() {
set +e
if [[ -n "${MOUNTPOINT:-}" ]]; then
hdiutil detach "$MOUNTPOINT" >/dev/null 2>&1 || hdiutil detach -force "$MOUNTPOINT" >/dev/null 2>&1
fi
rm -rf "$TMP_ROOT"
}
trap cleanup EXIT
ATTACH_OUT="$(hdiutil attach -nobrowse -readonly -noverify "$DMG_PATH")"
MOUNTPOINT="$(printf '%s\n' "$ATTACH_OUT" | awk '/\/Volumes\// {print $NF; exit}')"
if [[ -z "$MOUNTPOINT" || ! -d "$MOUNTPOINT" ]]; then
echo "Failed to find mountpoint for DMG." >&2
echo "hdiutil output:" >&2
printf '%s\n' "$ATTACH_OUT" >&2
exit 1
fi
VOLNAME="$(basename "$MOUNTPOINT")"
APP_PATH="$(find "$MOUNTPOINT" -maxdepth 1 -name '*.app' -print -quit || true)"
APP_NAME=""
if [[ -n "$APP_PATH" ]]; then
APP_NAME="$(basename "$APP_PATH")"
fi
if [[ -z "$APP_NAME" ]]; then
echo "No .app found at DMG root: $MOUNTPOINT" >&2
echo "Contents:" >&2
ls -la "$MOUNTPOINT" >&2
exit 1
fi
RESULT="$(
osascript <<EOF
on _toText(v)
try
return v as text
on error
return ""
end try
end _toText
on run
set volName to "${VOLNAME}"
set appName to "${APP_NAME}"
tell application "Finder"
activate
tell disk volName
open
delay 0.5
set w to container window
set b to bounds of w
set leftX to item 1 of b
set topY to item 2 of b
set rightX to item 3 of b
set bottomY to item 4 of b
set winW to (rightX - leftX)
set winH to (bottomY - topY)
set vo to icon view options of w
set iconSize to icon size of vo
set textSize to text size of vo
set bgPosix to ""
try
set bgAlias to background picture of vo
set bgPosix to POSIX path of (bgAlias as alias)
end try
set appX to ""
set appY to ""
try
set p to position of item appName of w
set appX to item 1 of p
set appY to item 2 of p
end try
set appsX to ""
set appsY to ""
try
set p2 to position of item "Applications" of w
set appsX to item 1 of p2
set appsY to item 2 of p2
end try
close w
set outText to ""
set outText to outText & "win_x=" & my _toText(leftX) & linefeed
set outText to outText & "win_y=" & my _toText(topY) & linefeed
set outText to outText & "win_w=" & my _toText(winW) & linefeed
set outText to outText & "win_h=" & my _toText(winH) & linefeed
set outText to outText & "icon_size=" & my _toText(iconSize) & linefeed
set outText to outText & "text_size=" & my _toText(textSize) & linefeed
set outText to outText & "bg_path=" & my _toText(bgPosix) & linefeed
set outText to outText & "app_x=" & my _toText(appX) & linefeed
set outText to outText & "app_y=" & my _toText(appY) & linefeed
set outText to outText & "apps_x=" & my _toText(appsX) & linefeed
set outText to outText & "apps_y=" & my _toText(appsY) & linefeed
return outText
end tell
end tell
end run
EOF
)"
WIN_X=""; WIN_Y=""; WIN_W=""; WIN_H=""; ICON_SIZE=""; TEXT_SIZE=""; BG_PATH=""; APP_X=""; APP_Y=""; APPS_X=""; APPS_Y=""
while IFS='=' read -r k v; do
[[ -z "${k:-}" ]] && continue
case "$k" in
win_x) WIN_X="$v" ;;
win_y) WIN_Y="$v" ;;
win_w) WIN_W="$v" ;;
win_h) WIN_H="$v" ;;
icon_size) ICON_SIZE="$v" ;;
text_size) TEXT_SIZE="$v" ;;
bg_path) BG_PATH="$v" ;;
app_x) APP_X="$v" ;;
app_y) APP_Y="$v" ;;
apps_x) APPS_X="$v" ;;
apps_y) APPS_Y="$v" ;;
esac
done <<<"$RESULT"
sanitize_num() {
# keep digits, optional leading '-', and optional '.'; strip commas/spaces/locale artifacts
local in="${1:-}"
in="${in//,/}"
in="${in// /}"
printf '%s' "$in" | tr -cd '0-9.-' | sed 's/^\.\+//'
}
WIN_X="$(sanitize_num "$WIN_X")"
WIN_Y="$(sanitize_num "$WIN_Y")"
WIN_W="$(sanitize_num "$WIN_W")"
WIN_H="$(sanitize_num "$WIN_H")"
ICON_SIZE="$(sanitize_num "$ICON_SIZE")"
TEXT_SIZE="$(sanitize_num "$TEXT_SIZE")"
APP_X="$(sanitize_num "$APP_X")"
APP_Y="$(sanitize_num "$APP_Y")"
APPS_X="$(sanitize_num "$APPS_X")"
APPS_Y="$(sanitize_num "$APPS_Y")"
mkdir -p "$EXPORT_DIR"
BACKGROUND_ARG=()
if [[ -n "$BG_PATH" && -f "$BG_PATH" ]]; then
BG_BASENAME="$(basename "$BG_PATH")"
BG_OUT="$EXPORT_DIR/$BG_BASENAME"
cp -f "$BG_PATH" "$BG_OUT"
BACKGROUND_ARG=(--background "$BG_OUT")
fi
if [[ ${#BACKGROUND_ARG[@]} -eq 0 ]]; then
# Common case: background image lives in the DMG at /.background/*
BG_FALLBACK="$(find "$MOUNTPOINT" -maxdepth 2 -path '*/.background/*' -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.gif' \) -print -quit 2>/dev/null || true)"
if [[ -n "$BG_FALLBACK" && -f "$BG_FALLBACK" ]]; then
BG_BASENAME="$(basename "$BG_FALLBACK")"
BG_OUT="$EXPORT_DIR/$BG_BASENAME"
cp -f "$BG_FALLBACK" "$BG_OUT"
BACKGROUND_ARG=(--background "$BG_OUT")
BG_PATH="$BG_FALLBACK"
fi
fi
echo "Mounted volume: $VOLNAME"
echo "Detected app: $APP_NAME"
[[ -n "$BG_PATH" ]] && echo "Background: $BG_PATH" || echo "Background: (none detected)"
echo
echo "create-dmg flags:"
echo " --volname \"$VOLNAME\""
echo " --window-pos $WIN_X $WIN_Y"
echo " --window-size $WIN_W $WIN_H"
echo " --icon-size $ICON_SIZE"
echo " --text-size $TEXT_SIZE"
if [[ ${#BACKGROUND_ARG[@]} -gt 0 ]]; then
echo " --background \"${BACKGROUND_ARG[1]}\""
fi
if [[ -n "$APP_X" && -n "$APP_Y" ]]; then
echo " --icon \"$APP_NAME\" $APP_X $APP_Y"
fi
if [[ -n "$APPS_X" && -n "$APPS_Y" ]]; then
echo " --app-drop-link $APPS_X $APPS_Y"
fi
echo
echo "Example:"
echo "create-dmg \\"
echo " --volname \"$VOLNAME\" \\"
echo " --window-pos $WIN_X $WIN_Y \\"
echo " --window-size $WIN_W $WIN_H \\"
echo " --icon-size $ICON_SIZE \\"
echo " --text-size $TEXT_SIZE \\"
if [[ ${#BACKGROUND_ARG[@]} -gt 0 ]]; then
echo " --background \"${BACKGROUND_ARG[1]}\" \\"
fi
if [[ -n "$APP_X" && -n "$APP_Y" ]]; then
echo " --icon \"$APP_NAME\" $APP_X $APP_Y \\"
fi
if [[ -n "$APPS_X" && -n "$APPS_Y" ]]; then
echo " --app-drop-link $APPS_X $APPS_Y \\"
fi
echo " out.dmg /path/to/source_folder"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment