From 401ddd6f982bb06b62b53e5ae561a3a5acf0655b Mon Sep 17 00:00:00 2001 From: "marcus.hinz" Date: Wed, 24 Jun 2026 18:50:53 +0200 Subject: [PATCH] Ghostty-per-Projekt-Builder: Template + make/rebuild-Skripte - 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 --- .gitignore | 2 + Sources/MiniTerm/Ghostty.swift | 127 ++++++++++++++++++++++++++++--- Sources/MiniTerm/main.swift | 12 ++- ghostty-template/dock-icon.patch | 20 +++++ icons/robotunits.png | Bin 0 -> 29711 bytes make-app.sh | 6 +- make-ghostty-app.sh | 111 +++++++++++++++++++++++++++ rebuild-template.sh | 89 ++++++++++++++++++++++ 8 files changed, 353 insertions(+), 14 deletions(-) create mode 100644 ghostty-template/dock-icon.patch create mode 100644 icons/robotunits.png create mode 100755 make-ghostty-app.sh create mode 100755 rebuild-template.sh diff --git a/.gitignore b/.gitignore index 163a629..8a7925e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .build/ *.app/ +.ghostty-build/ +.DS_Store diff --git a/Sources/MiniTerm/Ghostty.swift b/Sources/MiniTerm/Ghostty.swift index f7573ff..ead12ac 100644 --- a/Sources/MiniTerm/Ghostty.swift +++ b/Sources/MiniTerm/Ghostty.swift @@ -41,15 +41,46 @@ final class GhosttyApp { // libghostty signalisiert Arbeit -> Tick auf dem Main-Thread. DispatchQueue.main.async { me.tick() } }, - action_cb: { _, _, _ in - // Minimal: keine App-Aktionen (neue Fenster, Tabs, …) behandeln. - return false + action_cb: { _, _, action in + // Minimal: die meisten App-Aktionen (neue Fenster, Tabs, …) nicht + // behandeln. Endet der Befehl (closeOnExit) bzw. die Shell, meldet + // libghostty das über diese Aktionen – dann die App beenden, sonst + // bliebe das Fenster mit toter Surface offen. + switch action.tag { + case GHOSTTY_ACTION_CLOSE_WINDOW, + GHOSTTY_ACTION_QUIT, + GHOSTTY_ACTION_SHOW_CHILD_EXITED: + DispatchQueue.main.async { NSApp.terminate(nil) } + return true + default: + return false + } }, - read_clipboard_cb: { _, _, _ in - return false + read_clipboard_cb: { userdata, _, state in + // libghostty will den Zwischenablage-Inhalt (Paste). Das userdata + // ist die SURFACE-userdata (= GhosttySurfaceView, in createSurface + // gesetzt). Aus dem System-Pasteboard lesen und die Anfrage + // abschließen. confirmed: true = direkt einfügen (kein + // Unsafe-Paste-Bestätigungsdialog). + guard let userdata = userdata else { return false } + let view = Unmanaged.fromOpaque(userdata).takeUnretainedValue() + guard let surface = view.surface else { return false } + let s = NSPasteboard.general.string(forType: .string) ?? "" + s.withCString { + ghostty_surface_complete_clipboard_request(surface, $0, state, true) + } + return true }, confirm_read_clipboard_cb: { _, _, _, _ in }, - write_clipboard_cb: { _, _, _, _, _ in }, + write_clipboard_cb: { _, _, content, len, _ in + // libghostty schreibt in die Zwischenablage (⌘C-Keybind, OSC 52). + // Ersten Eintrag mit Textinhalt ins System-Pasteboard übernehmen. + guard let content = content, len > 0, + let data = content.pointee.data else { return } + let s = String(cString: data) + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(s, forType: .string) + }, close_surface_cb: { _, _ in DispatchQueue.main.async { NSApp.terminate(nil) } } @@ -87,7 +118,9 @@ final class GhosttyApp { final class GhosttySurfaceView: NSView { private let ghostty: GhosttyApp private let appConfig: AppConfig - private var surface: ghostty_surface_t! + // fileprivate, damit die Clipboard-Callbacks in GhosttyApp (gleiche Datei) + // die Surface dieser View erreichen. + fileprivate var surface: ghostty_surface_t! init(ghostty: GhosttyApp, appConfig: AppConfig, frame: NSRect) { self.ghostty = ghostty @@ -129,12 +162,16 @@ final class GhosttySurfaceView: NSView { /// Login-Shell-Verhalten wie in der SwiftTerm-Variante: leeres MTCommand => /// nil (libghostty startet selbst eine Login-Shell). Sonst Befehl ausführen /// und danach in eine interaktive Login-Shell übergehen, damit das Fenster - /// offen bleibt. + /// offen bleibt. Bei closeOnExit endet der Prozess mit dem Befehl; libghostty + /// schließt die Surface (close_surface_cb -> NSApp.terminate). private func surfaceCommand() -> String? { let cmd = appConfig.command.trimmingCharacters(in: .whitespacesAndNewlines) if cmd.isEmpty { return nil } let shell = ProcessInfo.processInfo.environment["SHELL"].flatMap { $0.isEmpty ? nil : $0 } ?? "/bin/zsh" + if appConfig.closeOnExit { + return "\(shell) -lc '\(cmd)'" + } return "\(shell) -lc '\(cmd); exec \(shell) -l'" } @@ -218,9 +255,22 @@ final class GhosttySurfaceView: NSView { k.action = action k.mods = Self.translateMods(event.modifierFlags) k.keycode = UInt32(event.keyCode) - k.unshifted_codepoint = (event.charactersIgnoringModifiers?.unicodeScalars.first?.value) ?? 0 - let text = event.characters ?? "" + // Funktionstasten (Pfeile, F-Tasten, Home/End/Page, …) liefern unter + // AppKit Codepoints aus der Private Use Area (0xF700–0xF8FF). Diese NICHT + // als Text/Codepoint übergeben – sonst sendet libghostty diese Zeichen + // wörtlich statt der passenden Escape-Sequenz aus dem keycode. + func isPUAFunctionKey(_ s: String?) -> Bool { + guard let v = s?.unicodeScalars.first?.value else { return false } + return (0xF700...0xF8FF).contains(v) + } + + let unshifted = event.charactersIgnoringModifiers + k.unshifted_codepoint = isPUAFunctionKey(unshifted) + ? 0 : (unshifted?.unicodeScalars.first?.value ?? 0) + + let chars = event.characters ?? "" + let text = isPUAFunctionKey(chars) ? "" : chars text.withCString { ptr in k.text = ptr _ = ghostty_surface_key(surface, k) @@ -246,11 +296,24 @@ final class GhosttySurfaceView: NSView { override func mouseDown(with event: NSEvent) { mouseButton(event, .press, GHOSTTY_MOUSE_LEFT) } override func mouseUp(with event: NSEvent) { mouseButton(event, .release, GHOSTTY_MOUSE_LEFT) } - override func rightMouseDown(with event: NSEvent) { mouseButton(event, .press, GHOSTTY_MOUSE_RIGHT) } - override func rightMouseUp(with event: NSEvent) { mouseButton(event, .release, GHOSTTY_MOUSE_RIGHT) } override func otherMouseDown(with event: NSEvent) { mouseButton(event, .press, GHOSTTY_MOUSE_MIDDLE) } override func otherMouseUp(with event: NSEvent) { mouseButton(event, .release, GHOSTTY_MOUSE_MIDDLE) } + // Rechte Maustaste: Shift -> Kontextmenü; Selektion vorhanden -> kopieren; + // sonst -> einfügen (PuTTY/urxvt-Verhalten). Der Klick geht nicht an die + // Surface, das Maus-Reporting per Rechtsklick entfällt damit bewusst. + override func rightMouseDown(with event: NSEvent) { + let mods = event.modifierFlags.intersection(.deviceIndependentFlagsMask) + if mods.contains(.shift) { + showContextMenu(event) + } else if let surface = surface, ghostty_surface_has_selection(surface) { + copy(nil) + } else { + paste(nil) + } + } + override func rightMouseUp(with event: NSEvent) {} + private enum MouseAction { case press, release } private func mouseButton(_ event: NSEvent, _ a: MouseAction, _ button: ghostty_input_mouse_button_e) { guard let surface = surface else { return } @@ -289,6 +352,46 @@ final class GhosttySurfaceView: NSView { addTrackingArea(area) } + // MARK: Zwischenablage + + // Markierten Text der Surface in die System-Zwischenablage kopieren. Der + // Selektor heißt `copy(_:)`, damit der Edit-Menü-Eintrag (⌘C) greift. + @objc func copy(_ sender: Any?) { + guard let surface = surface, ghostty_surface_has_selection(surface) else { return } + var t = ghostty_text_s() + guard ghostty_surface_read_selection(surface, &t), let cstr = t.text else { return } + let s = String(cString: cstr) + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(s, forType: .string) + ghostty_surface_free_text(surface, &t) + } + + // Zwischenablage einfügen. Über die Binding-Aktion, damit Bracketed-Paste + // erhalten bleibt (mehrzeilige Eingaben). Selektor `paste(_:)` für ⌘V. + @objc func paste(_ sender: Any?) { + guard let surface = surface else { return } + let action = "paste_from_clipboard" + _ = action.withCString { + ghostty_surface_binding_action(surface, $0, UInt(strlen($0))) + } + } + + private func showContextMenu(_ event: NSEvent) { + let menu = NSMenu() + let hasSelection = surface.map { ghostty_surface_has_selection($0) } ?? false + + let copyItem = NSMenuItem(title: "Kopieren", action: #selector(copy(_:)), keyEquivalent: "") + copyItem.target = self + copyItem.isEnabled = hasSelection + menu.addItem(copyItem) + + let pasteItem = NSMenuItem(title: "Einfügen", action: #selector(paste(_:)), keyEquivalent: "") + pasteItem.target = self + menu.addItem(pasteItem) + + NSMenu.popUpContextMenu(menu, with: event, for: self) + } + deinit { if let surface = surface { ghostty_surface_free(surface) } } diff --git a/Sources/MiniTerm/main.swift b/Sources/MiniTerm/main.swift index 0d20388..a9e0948 100644 --- a/Sources/MiniTerm/main.swift +++ b/Sources/MiniTerm/main.swift @@ -13,6 +13,8 @@ import SwiftTerm // MTFontName String Font family name (default "Menlo"). Falls back to // the system monospaced font if the name is missing. // MTWindowTitle String Window title (default = CFBundleName). +// MTCloseOnExit Bool Close the window when the command exits (default +// false = drop back into an interactive login shell). // struct AppConfig { let command: String @@ -21,6 +23,7 @@ struct AppConfig { let fontName: String let windowTitle: String let engine: String // "ghostty" (Default) oder "swiftterm" + let closeOnExit: Bool // true = Fenster schließt, wenn der Befehl endet static func load() -> AppConfig { let info = Bundle.main.infoDictionary ?? [:] @@ -46,12 +49,15 @@ struct AppConfig { ?? (info["CFBundleName"] as? String) ?? "Terminal" + let closeOnExit = (info["MTCloseOnExit"] as? NSNumber)?.boolValue ?? false + return AppConfig(command: cmd, workingDirectory: workdir, fontSize: CGFloat(size), fontName: fontName.isEmpty ? "Menlo" : fontName, windowTitle: title, - engine: engine == "swiftterm" ? "swiftterm" : "ghostty") + engine: engine == "swiftterm" ? "swiftterm" : "ghostty", + closeOnExit: closeOnExit) } } @@ -120,6 +126,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, LocalProcessTerminalVi let args: [String] if config.command.isEmpty { args = ["-l"] + } else if config.closeOnExit { + // Run the command and let the process end with it; the window + // closes via processTerminated(). + args = ["-l", "-c", config.command] } else { // Run the requested command, then hand control to an interactive // login shell so the window stays open after the command exits. diff --git a/ghostty-template/dock-icon.patch b/ghostty-template/dock-icon.patch new file mode 100644 index 0000000..624825b --- /dev/null +++ b/ghostty-template/dock-icon.patch @@ -0,0 +1,20 @@ +diff --git a/macos/Sources/Features/Custom App Icon/DockTilePlugin.swift b/macos/Sources/Features/Custom App Icon/DockTilePlugin.swift +index 990cd8b..6cf7031 100644 +--- a/macos/Sources/Features/Custom App Icon/DockTilePlugin.swift ++++ b/macos/Sources/Features/Custom App Icon/DockTilePlugin.swift +@@ -51,6 +51,15 @@ class DockTilePlugin: NSObject, NSDockTilePlugIn { + return + } + ++ // Custom per-project dock icon: if the enclosing app bundle ships a ++ // Resources/DockIcon.png, use it verbatim and skip the ghost logic. ++ if let appURL = ghosttyAppURL, ++ let img = NSImage(contentsOf: appURL.appendingPathComponent("Contents/Resources/DockIcon.png")) { ++ dockTile.setIcon(img) ++ iconChangeObserver = nil ++ return ++ } ++ + // Try to restore the previous icon on launch. + iconDidChange(ghosttyUserDefaults.appIcon, dockTile: dockTile) + diff --git a/icons/robotunits.png b/icons/robotunits.png new file mode 100644 index 0000000000000000000000000000000000000000..c2d864b7e5bc1a8f547e06a31c16aa0cc83ef4d8 GIT binary patch literal 29711 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 literal 0 HcmV?d00001 diff --git a/make-app.sh b/make-app.sh index 29d5d3f..c4e2a31 100755 --- a/make-app.sh +++ b/make-app.sh @@ -15,7 +15,8 @@ # [--font-size 13] \ # [--font "JetBrains Mono"] \ # [--icon ./icons/projekta.png] \ # .png/.jpg (auto-converted) or .icns -# [--out ~/Applications] +# [--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. @@ -31,6 +32,7 @@ FONT_SIZE="13" FONT_NAME="Menlo" ICON="" OUTDIR="$HOME/Applications" +CLOSE_ON_EXIT="false" # ---- parse args ----------------------------------------------------------- while [[ $# -gt 0 ]]; do @@ -43,6 +45,7 @@ while [[ $# -gt 0 ]]; do --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 ;; @@ -139,6 +142,7 @@ cat > "$APP/Contents/Info.plist" <MTWindowTitle${NAME} MTFontSize${FONT_SIZE} MTFontName${FONT_NAME} + MTCloseOnExit<${CLOSE_ON_EXIT}/> PLIST diff --git a/make-ghostty-app.sh b/make-ghostty-app.sh new file mode 100755 index 0000000..6d27308 --- /dev/null +++ b/make-ghostty-app.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# Erzeugt aus dem gepatchten Ghostty-Template eine eigenständige, pro-Projekt +# konfigurierte macOS-App: eigene Bundle-ID (eigenes Dock-Icon + ⌘-Tab), +# eigenes Icon, eigene Ghostty-Config (working-directory, command, Farben, Font). +# +# Beispiel: +# ./make-ghostty-app.sh misc \ +# --icon ~/claude-projects/misc/misc-icon.png \ +# --workdir ~/claude-projects/misc \ +# --command ~/.local/bin/claude \ +# --bg 2b0a3d --fg ffd000 --font "Courier New" --font-size 18 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEMPLATE="$SCRIPT_DIR/ghostty-template/Ghostty.app" +PB=/usr/libexec/PlistBuddy + +# --- Defaults --- +NAME="" +BUNDLE_ID="" +ICON="" +WORKDIR="" +COMMAND_LINE="" +THEME="" +BG="" +FG="" +FONT="" +FONT_SIZE="" +OUT_DIR="$HOME/Applications" +CFG_BASE="$HOME/.config-ghostty" + +usage() { grep '^#' "$0" | sed 's/^# \?//'; exit 1; } + +[[ $# -ge 1 ]] || usage +NAME="$1"; shift +while [[ $# -gt 0 ]]; do + case "$1" in + --id) BUNDLE_ID="$2"; shift 2;; + --icon) ICON="$2"; shift 2;; + --workdir) WORKDIR="$2"; shift 2;; + --command) COMMAND_LINE="$2"; shift 2;; + --theme) THEME="$2"; shift 2;; + --bg) BG="$2"; shift 2;; + --fg) FG="$2"; shift 2;; + --font) FONT="$2"; shift 2;; + --font-size) FONT_SIZE="$2"; shift 2;; + --out) OUT_DIR="$2"; shift 2;; + *) echo "Unbekannte Option: $1" >&2; usage;; + esac +done + +[[ -d "$TEMPLATE" ]] || { echo "Template fehlt: $TEMPLATE (erst rebuild-template.sh laufen lassen)" >&2; exit 1; } +: "${BUNDLE_ID:=com.mitchellh.ghostty.$NAME}" +APP="$OUT_DIR/Ghostty-$NAME.app" +CFG="$CFG_BASE/$NAME" + +# Pfade expandieren (~) +WORKDIR="${WORKDIR/#\~/$HOME}" +ICON="${ICON/#\~/$HOME}" +COMMAND_LINE="${COMMAND_LINE/#\~/$HOME}" + +echo ">> Baue $APP (id=$BUNDLE_ID)" + +# --- 1. Template kopieren --- +mkdir -p "$OUT_DIR" +pkill -9 -f "$APP/Contents/MacOS" 2>/dev/null || true +sleep 1 +rm -rf "$APP" +cp -R "$TEMPLATE" "$APP" + +# --- 2. Icon (PNG -> .icns für statisch + PNG fürs Dock-Tile) --- +if [[ -n "$ICON" ]]; then + [[ -f "$ICON" ]] || { echo "Icon nicht gefunden: $ICON" >&2; exit 1; } + TMP_ISET="$(mktemp -d)/icon.iconset"; mkdir -p "$TMP_ISET" + for s in 16 32 128 256 512; do + sips -z $s $s "$ICON" --out "$TMP_ISET/icon_${s}x${s}.png" >/dev/null + sips -z $((s*2)) $((s*2)) "$ICON" --out "$TMP_ISET/icon_${s}x${s}@2x.png" >/dev/null + done + iconutil -c icns "$TMP_ISET" -o "$APP/Contents/Resources/Ghostty.icns" + cp "$ICON" "$APP/Contents/Resources/DockIcon.png" # laufendes Dock-Tile (Plugin-Patch) + rm -rf "$(dirname "$TMP_ISET")" +fi + +# --- 3. Info.plist: Identität + Config-Pfad --- +PL="$APP/Contents/Info.plist" +$PB -c "Set :CFBundleIdentifier $BUNDLE_ID" "$PL" +$PB -c "Set :CFBundleName Ghostty-$NAME" "$PL" 2>/dev/null || $PB -c "Add :CFBundleName string Ghostty-$NAME" "$PL" +$PB -c "Delete :CFBundleIconName" "$PL" 2>/dev/null || true # sonst gewinnt Assets.car (Geist) +$PB -c "Delete :LSEnvironment" "$PL" 2>/dev/null || true +$PB -c "Add :LSEnvironment dict" "$PL" +$PB -c "Add :LSEnvironment:XDG_CONFIG_HOME string $CFG" "$PL" + +# --- 4. Ghostty-Config schreiben --- +mkdir -p "$CFG/ghostty" +{ + [[ -n "$WORKDIR" ]] && echo "working-directory = $WORKDIR" + [[ -n "$COMMAND_LINE" ]] && echo "command = $COMMAND_LINE" + [[ -n "$COMMAND_LINE" ]] && echo "wait-after-command = true" + [[ -n "$THEME" ]] && echo "theme = $THEME" + [[ -n "$BG" ]] && echo "background = $BG" + [[ -n "$FG" ]] && echo "foreground = $FG" + [[ -n "$FONT" ]] && echo "font-family = $FONT" + [[ -n "$FONT_SIZE" ]] && echo "font-size = $FONT_SIZE" +} > "$CFG/ghostty/config" + +# --- 5. ad-hoc signieren --- +codesign --force --deep --sign - "$APP" >/dev/null + +echo ">> Fertig: $APP" +echo " Config: $CFG/ghostty/config" +echo " Starten: open -n \"$APP\"" diff --git a/rebuild-template.sh b/rebuild-template.sh new file mode 100755 index 0000000..90d0b2e --- /dev/null +++ b/rebuild-template.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# Baut das gepatchte Ghostty-Template (ghostty-template/Ghostty.app) lokal neu — +# OHNE VM, OHNE Zig. Der kritische Zig-Kern liegt bereits als +# vendor/libghostty/lib/libghostty.a vor; hier laeuft nur noch reines Xcode. +# +# Voraussetzungen: +# - Volles Xcode (CommandLineTools reichen NICHT). Per `xcodes` installierbar. +# - vendor/libghostty/lib/libghostty.a (arm64) + vendor/libghostty/include +# - Installierte /Applications/Ghostty.app (liefert die Shell-Ressourcen) +# +# WICHTIG: libghostty.a ist auf Ghostty v1.3.1 gepinnt. Ein Versionssprung +# erfordert einen NEUEN libghostty.a-Build (Zig, nur in macOS-15-VM moeglich — +# siehe vendor/libghostty/README.md). Dieses Skript baut nur das App-Bundle neu. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +GHOSTTY_VERSION="${1:-v1.3.1}" +VENDOR="$SCRIPT_DIR/vendor/libghostty" +BUILD="$SCRIPT_DIR/.ghostty-build" +SRC="$BUILD/ghostty-src" +INSTALLED="/Applications/Ghostty.app" + +# --- Xcode (volles) finden --- +find_xcode() { + local p + p="$(xcode-select -p 2>/dev/null || true)" + [[ "$p" == *Xcode*/Contents/Developer ]] && { echo "$p"; return; } + for c in /Applications/Xcode*.app \ + "$HOME/Library/Application Support/com.robotsandpencils.xcodes/Xcode.app"; do + [[ -d "$c/Contents/Developer" ]] && { echo "$c/Contents/Developer"; return; } + done + return 1 +} +DEVELOPER_DIR="$(find_xcode)" || { echo "Volles Xcode nicht gefunden." >&2; exit 1; } +export DEVELOPER_DIR +echo ">> Xcode: $(xcodebuild -version | head -1)" + +# --- Vorbedingungen --- +[[ -f "$VENDOR/lib/libghostty.a" ]] || { echo "fehlt: $VENDOR/lib/libghostty.a" >&2; exit 1; } +[[ -d "$INSTALLED/Contents/Resources" ]] || { echo "fehlt: $INSTALLED (Shell-Ressourcen)" >&2; exit 1; } + +# --- 1. Source holen --- +mkdir -p "$BUILD" +if [[ ! -d "$SRC/.git" ]]; then + rm -rf "$SRC" + git clone --depth 1 --branch "$GHOSTTY_VERSION" https://github.com/ghostty-org/ghostty.git "$SRC" +else + git -C "$SRC" fetch --depth 1 origin "$GHOSTTY_VERSION" + git -C "$SRC" checkout -f FETCH_HEAD +fi + +# --- 2. Patch anwenden (idempotent) --- +cd "$SRC" +git checkout -- "macos/Sources/Features/Custom App Icon/DockTilePlugin.swift" 2>/dev/null || true +git apply "$SCRIPT_DIR/ghostty-template/dock-icon.patch" +echo ">> Patch angewandt" + +# --- 3. xcframework aus libghostty.a (Header MIT module.modulemap aus src/include) --- +OUT_XCF="$SRC/macos/GhosttyKit.xcframework" +rm -rf "$OUT_XCF" +xcodebuild -create-xcframework \ + -library "$VENDOR/lib/libghostty.a" \ + -headers "$SRC/include" \ + -output "$OUT_XCF" >/dev/null +echo ">> GhosttyKit.xcframework verpackt" + +# --- 4. Shell-Ressourcen einspeisen (sonst CpResource-Fehler) --- +SHARE="$SRC/zig-out/share" +mkdir -p "$SHARE" +for n in zsh vim terminfo man nvim locale fish ghostty bat bash-completion; do + rm -rf "$SHARE/$n" + cp -R "$INSTALLED/Contents/Resources/$n" "$SHARE/$n" +done +echo ">> Shell-Ressourcen eingespeist" + +# --- 5. App bauen (arm64, ReleaseLocal, ohne Signing) --- +cd "$SRC/macos" +xcodebuild -project Ghostty.xcodeproj -target Ghostty -configuration ReleaseLocal \ + SYMROOT="$BUILD/out" ARCHS=arm64 ONLY_ACTIVE_ARCH=YES \ + CLANG_MODULE_CACHE_PATH="$BUILD/modulecache" \ + CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" \ + build >/dev/null +echo ">> App gebaut" + +# --- 6. Template ersetzen --- +DST="$SCRIPT_DIR/ghostty-template/Ghostty.app" +rm -rf "$DST" +cp -R "$BUILD/out/ReleaseLocal/Ghostty.app" "$DST" +echo ">> Template aktualisiert: $DST"