401ddd6f98
- ghostty-template/Ghostty.app: lokal gebautes Ghostty v1.3.1 mit DockTilePlugin-Patch (eigenes Dock-Icon ueber Contents/Resources/DockIcon.png) - make-ghostty-app.sh: erzeugt pro Projekt eine App; Slots fuer icon/workdir/command/theme/bg/fg/font/font-size - rebuild-template.sh: baut Template lokal neu (nur Xcode, kein Zig/VM), gepinnt auf vendor/libghostty v1.3.1 - ghostty-template/dock-icon.patch: der DockTilePlugin-Patch - icons/robotunits.png - enthaelt zudem die vorausgehende MiniTerm/libghostty-Integration
161 lines
5.7 KiB
Bash
Executable File
161 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# make-app.sh — build a standalone macOS .app from the MiniTerm binary.
|
|
#
|
|
# Each generated .app has its OWN bundle id, so macOS treats it as a separate
|
|
# application: separate Dock icon, separate ⌘-Tab entry. Run this script once
|
|
# per app you want.
|
|
#
|
|
# Usage:
|
|
# ./make-app.sh \
|
|
# --name "Claude · Projekt A" \
|
|
# --bundle-id com.yourname.claude.projekta \
|
|
# --workdir ~/code/project-a \
|
|
# --command "claude" \
|
|
# [--font-size 13] \
|
|
# [--font "JetBrains Mono"] \
|
|
# [--icon ./icons/projekta.png] \ # .png/.jpg (auto-converted) or .icns
|
|
# [--out ~/Applications] \
|
|
# [--close-on-exit] # close the window when the command exits
|
|
#
|
|
# Build the binary fresh? The script runs `swift build -c release` automatically
|
|
# the first time and reuses the result for further apps.
|
|
|
|
set -euo pipefail
|
|
|
|
# ---- defaults -------------------------------------------------------------
|
|
NAME=""
|
|
BUNDLE_ID=""
|
|
WORKDIR=""
|
|
COMMAND=""
|
|
FONT_SIZE="13"
|
|
FONT_NAME="Menlo"
|
|
ICON=""
|
|
OUTDIR="$HOME/Applications"
|
|
CLOSE_ON_EXIT="false"
|
|
|
|
# ---- parse args -----------------------------------------------------------
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) NAME="$2"; shift 2 ;;
|
|
--bundle-id) BUNDLE_ID="$2"; shift 2 ;;
|
|
--workdir) WORKDIR="$2"; shift 2 ;;
|
|
--command) COMMAND="$2"; shift 2 ;;
|
|
--font-size) FONT_SIZE="$2"; shift 2 ;;
|
|
--font) FONT_NAME="$2"; shift 2 ;;
|
|
--icon) ICON="$2"; shift 2 ;;
|
|
--out) OUTDIR="$2"; shift 2 ;;
|
|
--close-on-exit) CLOSE_ON_EXIT="true"; shift ;;
|
|
-h|--help)
|
|
grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$NAME" || -z "$BUNDLE_ID" ]]; then
|
|
echo "Error: --name and --bundle-id are required." >&2
|
|
echo "Run './make-app.sh --help' for usage." >&2
|
|
exit 1
|
|
fi
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
cd "$SCRIPT_DIR"
|
|
|
|
# ---- build binary once ----------------------------------------------------
|
|
BIN=".build/release/MiniTerm"
|
|
if [[ ! -f "$BIN" ]]; then
|
|
echo "==> Building MiniTerm (release)…"
|
|
swift build -c release
|
|
fi
|
|
|
|
# Keep workdir portable across machines/users: store paths under the current
|
|
# home directory as a literal "~", so each app expands it per-user at runtime
|
|
# (Swift's expandingTildeInPath). Note the shell may have already expanded a "~"
|
|
# you typed on the command line into an absolute $HOME path, so we normalize that
|
|
# back to "~" too. Paths outside $HOME stay absolute (genuinely machine-specific).
|
|
case "$WORKDIR" in
|
|
"$HOME") WORKDIR="~" ;;
|
|
"$HOME"/*) WORKDIR="~/${WORKDIR#"$HOME"/}" ;;
|
|
esac
|
|
|
|
# ---- assemble the .app bundle ---------------------------------------------
|
|
APP="$OUTDIR/$NAME.app"
|
|
echo "==> Creating $APP"
|
|
rm -rf "$APP"
|
|
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
|
|
|
cp "$BIN" "$APP/Contents/MacOS/MiniTerm"
|
|
chmod +x "$APP/Contents/MacOS/MiniTerm"
|
|
|
|
# Build an .icns from a square source image (PNG/JPG/…) using macOS tools.
|
|
make_icns_from_image() {
|
|
local src="$1" dest="$2"
|
|
local iconset
|
|
iconset="$(mktemp -d)/icon.iconset"
|
|
mkdir -p "$iconset"
|
|
# All sizes Apple expects in an .iconset
|
|
local sizes=(16 32 128 256 512)
|
|
for s in "${sizes[@]}"; do
|
|
sips -z "$s" "$s" "$src" --out "$iconset/icon_${s}x${s}.png" >/dev/null
|
|
sips -z "$((s*2))" "$((s*2))" "$src" --out "$iconset/icon_${s}x${s}@2x.png" >/dev/null
|
|
done
|
|
iconutil -c icns "$iconset" -o "$dest"
|
|
rm -rf "$(dirname "$iconset")"
|
|
}
|
|
|
|
ICON_KEY=""
|
|
if [[ -n "$ICON" ]]; then
|
|
if [[ ! -f "$ICON" ]]; then
|
|
echo " (warning: --icon file not found: $ICON; skipping icon)" >&2
|
|
elif [[ "$ICON" == *.icns ]]; then
|
|
cp "$ICON" "$APP/Contents/Resources/AppIcon.icns"
|
|
ICON_KEY="<key>CFBundleIconFile</key><string>AppIcon</string>"
|
|
elif command -v sips >/dev/null 2>&1 && command -v iconutil >/dev/null 2>&1; then
|
|
echo "==> Converting $ICON to .icns…"
|
|
if make_icns_from_image "$ICON" "$APP/Contents/Resources/AppIcon.icns"; then
|
|
ICON_KEY="<key>CFBundleIconFile</key><string>AppIcon</string>"
|
|
else
|
|
echo " (warning: icon conversion failed; building without icon)" >&2
|
|
fi
|
|
else
|
|
echo " (warning: sips/iconutil not available; pass a .icns file instead)" >&2
|
|
fi
|
|
fi
|
|
|
|
cat > "$APP/Contents/Info.plist" <<PLIST
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>CFBundleName</key><string>${NAME}</string>
|
|
<key>CFBundleDisplayName</key><string>${NAME}</string>
|
|
<key>CFBundleExecutable</key><string>MiniTerm</string>
|
|
<key>CFBundleIdentifier</key><string>${BUNDLE_ID}</string>
|
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
<key>CFBundleVersion</key><string>1</string>
|
|
<key>CFBundleShortVersionString</key><string>1.0</string>
|
|
<key>LSMinimumSystemVersion</key><string>13.0</string>
|
|
<key>NSHighResolutionCapable</key><true/>
|
|
${ICON_KEY}
|
|
<key>MTCommand</key><string>${COMMAND}</string>
|
|
<key>MTWorkingDirectory</key><string>${WORKDIR}</string>
|
|
<key>MTWindowTitle</key><string>${NAME}</string>
|
|
<key>MTFontSize</key><integer>${FONT_SIZE}</integer>
|
|
<key>MTFontName</key><string>${FONT_NAME}</string>
|
|
<key>MTCloseOnExit</key><${CLOSE_ON_EXIT}/>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
|
|
# ---- ad-hoc codesign (helps on Apple Silicon / Gatekeeper) ----------------
|
|
if command -v codesign >/dev/null 2>&1; then
|
|
codesign --force --deep --sign - "$APP" >/dev/null 2>&1 \
|
|
&& echo "==> Ad-hoc signed." \
|
|
|| echo " (codesign skipped/failed — app still runs)"
|
|
fi
|
|
|
|
echo "==> Done: $APP"
|
|
echo " Command : ${COMMAND:-<plain shell>}"
|
|
echo " Workdir : ${WORKDIR:-<home>}"
|
|
echo " Font : ${FONT_NAME} ${FONT_SIZE}pt"
|