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
This commit is contained in:
@@ -1,2 +1,4 @@
|
||||
.build/
|
||||
*.app/
|
||||
.ghostty-build/
|
||||
.DS_Store
|
||||
|
||||
+115
-12
@@ -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<GhosttySurfaceView>.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) }
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
+5
-1
@@ -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" <<PLIST
|
||||
<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
|
||||
|
||||
Executable
+111
@@ -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\""
|
||||
Executable
+89
@@ -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"
|
||||
Reference in New Issue
Block a user