初始提交:terminalX 可运行态(M0/M1/M1.5 已真机验证)

- M0: libghostty SSH 终端(渲染/输入/连接)+ 白屏修复(OutputGate) + 会话状态机/自动重连
- M1: tsnet 用户态组网 + SSH-over-tsnet(fd 桥),shell 级真机验证;R5(Go+gvisor+C+++Swift 同进程) retire
- M1.5: tmux -CC 原生 tab(MVP)
- 结构: packages/(TXCore·TXTransport), apps/TerminalX, vendor/(libghostty-spm/libssh2/mbedtls/tsnet-bridge), artifacts/
- 文档: CLAUDE.md + docs/HANDOFF.md(新会话入口)
- 环境: 认证代理→依赖 vendor 本地化;Go 在 ~/.local/go;仅模拟器/未签名
- 待续: M2 mosh, tmux 多 pane, M4 安全(host key/SE)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kid
2026-07-24 10:20:46 +08:00
commit aa92d0e676
2761 changed files with 803505 additions and 0 deletions

106
vendor/libghostty-spm/Script/apply-patches.sh vendored Executable file
View File

@@ -0,0 +1,106 @@
#!/bin/zsh
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[-] malformed project structure"
exit 1
fi
SOURCE_DIR=${1:-}
PATCH_DIR=${2:-"$(pwd)/Patches/ghostty"}
if [ -z "$SOURCE_DIR" ]; then
echo "Usage: $0 <source_dir> [patch_dir]"
exit 1
fi
if [ ! -d "$SOURCE_DIR" ]; then
echo "[-] ghostty source directory not found: $SOURCE_DIR"
exit 1
fi
if [ ! -d "$PATCH_DIR" ]; then
echo "[+] no patches directory found: $PATCH_DIR"
exit 0
fi
apply_unified_patch() {
local patch_file="$1"
if [ -d "$SOURCE_DIR/.git" ] && command -v git >/dev/null 2>&1; then
if git -C "$SOURCE_DIR" apply --check --reverse "$patch_file" >/dev/null 2>&1; then
echo "[+] patch already applied: $(basename "$patch_file")"
return
fi
if ! git -C "$SOURCE_DIR" apply --check "$patch_file" >/dev/null 2>&1; then
echo "[-] failed to validate patch: $patch_file"
exit 1
fi
git -C "$SOURCE_DIR" apply "$patch_file"
echo "[+] applied patch: $(basename "$patch_file")"
return
fi
if patch -p1 -R --dry-run -d "$SOURCE_DIR" <"$patch_file" >/dev/null 2>&1; then
echo "[+] patch already applied: $(basename "$patch_file")"
return
fi
if ! patch -p1 --dry-run -d "$SOURCE_DIR" <"$patch_file" >/dev/null 2>&1; then
echo "[-] failed to validate patch: $patch_file"
exit 1
fi
patch -p1 -d "$SOURCE_DIR" <"$patch_file" >/dev/null
echo "[+] applied patch: $(basename "$patch_file")"
}
modern_host_io=false
if grep -q "ghostty_surface_foreground_pid" "$SOURCE_DIR/include/ghostty.h"; then
modern_host_io=true
fi
host_io_applied=false
if grep -q "GHOSTTY_SURFACE_IO_BACKEND_HOST_MANAGED" "$SOURCE_DIR/include/ghostty.h"; then
host_io_applied=true
fi
for patch_file in "$PATCH_DIR"/*; do
[ -e "$patch_file" ] || continue
patch_name=$(basename "$patch_file")
case "$patch_name" in
0002-host-managed-io.patch)
[ "$modern_host_io" = false ] || continue
if [ "$host_io_applied" = true ]; then
echo "[+] patch already applied: $patch_name"
continue
fi
apply_unified_patch "$patch_file"
;;
0002-host-managed-io-modern.patch)
[ "$modern_host_io" = true ] || continue
if [ "$host_io_applied" = true ]; then
echo "[+] patch already applied: $patch_name"
continue
fi
apply_unified_patch "$patch_file"
;;
*.md) ;;
*.patch)
apply_unified_patch "$patch_file"
;;
*.sh)
"$patch_file" "$SOURCE_DIR"
;;
*)
echo "[-] unsupported patch file: $patch_file"
exit 1
;;
esac
done

273
vendor/libghostty-spm/Script/build-ghostty.sh vendored Executable file
View File

@@ -0,0 +1,273 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[*] malformed project structure"
exit 1
fi
ROOT_DIR=$(pwd)
SOURCE_DIR=${1:-}
ZIG_TARGET=${2:-}
OUTPUT_DIR=${3:-}
ZIG_CPU=${ZIG_CPU:-}
ZIG_BUILD_EXTRA_ARGS=${ZIG_BUILD_EXTRA_ARGS:-}
if [ -z "$SOURCE_DIR" ] || [ -z "$ZIG_TARGET" ] || [ -z "$OUTPUT_DIR" ]; then
echo "Usage: $0 <source_dir> <zig_target> <output_dir>"
exit 1
fi
if [ ! -d "$SOURCE_DIR" ]; then
echo "[!] ghostty source directory not found: $SOURCE_DIR"
exit 1
fi
if [ ! -f "$SOURCE_DIR/include/ghostty.h" ]; then
echo "[!] ghostty header not found: $SOURCE_DIR/include/ghostty.h"
exit 1
fi
if ! command -v zig >/dev/null 2>&1; then
echo "[!] zig not found"
exit 1
fi
REQUESTED_TARGET="$ZIG_TARGET"
IS_ARM64E=0
APPLE_SDK=
APPLE_TARGET=
case "$REQUESTED_TARGET" in
arm64e-macos)
IS_ARM64E=1
ZIG_TARGET="aarch64-macos"
APPLE_SDK="macosx"
APPLE_TARGET="arm64e-apple-macosx13.0"
;;
arm64e-ios)
IS_ARM64E=1
ZIG_TARGET="aarch64-ios"
APPLE_SDK="iphoneos"
APPLE_TARGET="arm64e-apple-ios15.0"
;;
arm64e-ios-macabi)
IS_ARM64E=1
ZIG_TARGET="aarch64-ios-macabi"
APPLE_SDK="macosx"
APPLE_TARGET="arm64e-apple-ios15.0-macabi"
;;
esac
./Script/apply-patches.sh "$SOURCE_DIR"
CACHE_ROOT="${BUILD_CACHE_ROOT:-$ROOT_DIR/build/cache}"
GLOBAL_CACHE_DIR="${ZIG_GLOBAL_CACHE_DIR:-$CACHE_ROOT/zig-global}"
LOCAL_CACHE_DIR="$CACHE_ROOT/$REQUESTED_TARGET/zig-local"
MODULE_CACHE_DIR="${CLANG_MODULE_CACHE_ROOT:-$CACHE_ROOT/clang-module-cache}/$REQUESTED_TARGET"
if [ "$IS_ARM64E" -eq 1 ]; then
if ! command -v xcrun >/dev/null 2>&1; then
echo "[!] xcrun not found for arm64e build"
exit 1
fi
./Script/prepare-arm64e-source.sh "$SOURCE_DIR" "$GLOBAL_CACHE_DIR"
fi
echo "[*] building ghostty static library"
echo " target: $REQUESTED_TARGET"
echo " source: $SOURCE_DIR"
echo " output: $OUTPUT_DIR"
rm -rf "$OUTPUT_DIR" "$LOCAL_CACHE_DIR" "$MODULE_CACHE_DIR"
mkdir -p \
"$OUTPUT_DIR/lib" \
"$OUTPUT_DIR/include" \
"$GLOBAL_CACHE_DIR" \
"$LOCAL_CACHE_DIR" \
"$MODULE_CACHE_DIR"
rm -rf "$SOURCE_DIR/zig-out"
ZIG_BUILD_COMMAND=(
zig build
-Doptimize=${ZIG_OPTIMIZE:-ReleaseFast}
-Dapp-runtime=none
-Demit-exe=false
-Demit-xcframework=false
-Demit-macos-app=false
-Demit-docs=false
-Dsentry=false
-Dcustom-shaders=false
-Dinspector=false
-Dtarget="$ZIG_TARGET"
)
if [ -n "$ZIG_CPU" ]; then
ZIG_BUILD_COMMAND+=("-Dcpu=$ZIG_CPU")
fi
if [ -n "$ZIG_BUILD_EXTRA_ARGS" ]; then
# shellcheck disable=SC2206
EXTRA_ARGS=($ZIG_BUILD_EXTRA_ARGS)
ZIG_BUILD_COMMAND+=("${EXTRA_ARGS[@]}")
fi
(
cd "$SOURCE_DIR"
env \
CLANG_MODULE_CACHE_PATH="$MODULE_CACHE_DIR" \
ZIG_GLOBAL_CACHE_DIR="$GLOBAL_CACHE_DIR" \
ZIG_LOCAL_CACHE_DIR="$LOCAL_CACHE_DIR" \
"${ZIG_BUILD_COMMAND[@]}"
)
find_built_library() {
local preferred_name="$1"
find "$LOCAL_CACHE_DIR/o" -type f -name "$preferred_name" -print 2>/dev/null | sort | tail -n 1
}
LIBRARY_PATH=
if [ -f "$SOURCE_DIR/zig-out/lib/libghostty.a" ]; then
LIBRARY_PATH="$SOURCE_DIR/zig-out/lib/libghostty.a"
fi
if [ -z "$LIBRARY_PATH" ]; then
LIBRARY_PATH=$(find_built_library "libghostty-fat.a")
fi
if [ -z "$LIBRARY_PATH" ]; then
LIBRARY_PATH=$(find_built_library "libghostty.a")
fi
if [ -z "$LIBRARY_PATH" ]; then
echo "[!] failed to locate built libghostty archive in $LOCAL_CACHE_DIR"
if [[ "$ZIG_TARGET" == *macos* || "$ZIG_TARGET" == *ios* || "$ZIG_TARGET" == *tvos* || "$ZIG_TARGET" == *visionos* || "$ZIG_TARGET" == *watchos* ]]; then
echo "[!] note: upstream Ghostty does not install Darwin libghostty for app-runtime=none unless extra build wiring is triggered"
echo "[!] try again with ZIG_BUILD_EXTRA_ARGS='-Demit-xcframework=true' if you want to force Darwin libghostty build graph execution"
fi
find "$LOCAL_CACHE_DIR" -maxdepth 3 -type f | sort | tail -n 50
exit 1
fi
# Resolve std::__1::__libcpp_verbose_abort inside the archive: the Apple
# system libc++ only exports it since iOS 16.3 / macOS 13.3 / tvOS 16.3, and
# Zig's bundled libc++ headers reference it strongly (no availability markup),
# which crashes consumers at launch on older OS versions.
COMPAT_SOURCE="$ROOT_DIR/Script/support/libcxx-verbose-abort-compat.c"
COMPAT_OBJECT="$LOCAL_CACHE_DIR/libcxx-verbose-abort-compat.o"
zig cc -target "$ZIG_TARGET" -Os -fno-sanitize=undefined -c "$COMPAT_SOURCE" -o "$COMPAT_OBJECT"
if [ "$IS_ARM64E" -eq 1 ]; then
RAW_ARCHIVE="$LOCAL_CACHE_DIR/libghostty-arm64.a"
CONVERTED_ARCHIVE="$LOCAL_CACHE_DIR/libghostty-arm64e.a"
CORE_OBJECT="$LOCAL_CACHE_DIR/libghostty-arm64e-core.o"
ARM64E_COMPAT_OBJECT="$LOCAL_CACHE_DIR/arm64e-compat.o"
xcrun libtool -static -no_warning_for_no_symbols -o "$RAW_ARCHIVE" "$LIBRARY_PATH" "$COMPAT_OBJECT"
python3 "$ROOT_DIR/Script/convert-archive-arm64e.py" "$RAW_ARCHIVE" "$CONVERTED_ARCHIVE"
xcrun --sdk "$APPLE_SDK" clang \
-target "$APPLE_TARGET" \
-r \
-nostdlib \
"-Wl,-force_load,$CONVERTED_ARCHIVE" \
-Wl,-alias,_ghostty_app_new,_ghostty_arm64e_core_app_new \
-Wl,-unexported_symbol,_ghostty_app_new \
-Wl,-alias,_ghostty_surface_new,_ghostty_arm64e_core_surface_new \
-Wl,-unexported_symbol,_ghostty_surface_new \
-o "$CORE_OBJECT"
xcrun --sdk "$APPLE_SDK" clang \
-target "$APPLE_TARGET" \
-Os \
-fno-sanitize=undefined \
-I "$SOURCE_DIR/include" \
-c "$ROOT_DIR/Script/support/arm64e-compat.c" \
-o "$ARM64E_COMPAT_OBJECT"
xcrun libtool -static -no_warning_for_no_symbols \
-o "$OUTPUT_DIR/lib/libghostty.a" \
"$CORE_OBJECT" \
"$ARM64E_COMPAT_OBJECT"
if [ "$(lipo -archs "$OUTPUT_DIR/lib/libghostty.a")" != "arm64e" ]; then
echo "[!] built archive is not a single arm64e slice"
exit 1
fi
SYMBOLS=$(nm -gjU "$OUTPUT_DIR/lib/libghostty.a" | sort -u)
for symbol in \
_ghostty_app_new \
_ghostty_surface_new \
_ghostty_arm64e_core_app_new \
_ghostty_arm64e_core_surface_new \
_pa_pthread_cr \
_pa_qs; do
if ! grep -Fxq "$symbol" <<<"$SYMBOLS"; then
echo "[!] arm64e archive missing symbol: $symbol"
exit 1
fi
done
CORE_IMPORTS=$(nm -u "$CORE_OBJECT" | sort -u)
for symbol in _pa_pthread_cr _pa_qs; do
if ! grep -Fxq "$symbol" <<<"$CORE_IMPORTS"; then
echo "[!] arm64e core missing redirected import: $symbol"
exit 1
fi
done
for symbol in _pthread_create _qsort; do
if grep -Fxq "$symbol" <<<"$CORE_IMPORTS"; then
echo "[!] arm64e core contains unsigned callback import: $symbol"
exit 1
fi
done
echo "[*] built pointer-authenticated arm64e archive"
else
ARM64E_COMPAT_OBJECT="$LOCAL_CACHE_DIR/arm64e-compat.o"
case "$ZIG_TARGET" in
*-ios-simulator) COMPAT_SDK="iphonesimulator" ;;
*-ios-macabi | *-macos) COMPAT_SDK="macosx" ;;
*-ios) COMPAT_SDK="iphoneos" ;;
*-tvos-simulator) COMPAT_SDK="appletvsimulator" ;;
*-tvos) COMPAT_SDK="appletvos" ;;
*-visionos-simulator) COMPAT_SDK="xrsimulator" ;;
*-visionos) COMPAT_SDK="xros" ;;
*-watchos-simulator) COMPAT_SDK="watchsimulator" ;;
*-watchos) COMPAT_SDK="watchos" ;;
*)
echo "[!] unsupported compatibility target: $ZIG_TARGET"
exit 1
;;
esac
COMPAT_SDK_PATH=$(xcrun --sdk "$COMPAT_SDK" --show-sdk-path)
zig cc \
-target "$ZIG_TARGET" \
-Os \
-fno-sanitize=undefined \
-isystem "$COMPAT_SDK_PATH/usr/include" \
-I "$SOURCE_DIR/include" \
-c "$ROOT_DIR/Script/support/arm64e-compat.c" \
-o "$ARM64E_COMPAT_OBJECT"
xcrun libtool -static -no_warning_for_no_symbols \
-o "$OUTPUT_DIR/lib/libghostty.a" \
"$LIBRARY_PATH" \
"$COMPAT_OBJECT" \
"$ARM64E_COMPAT_OBJECT"
echo "[*] appended compatibility objects"
fi
cp "$SOURCE_DIR/include/ghostty.h" "$OUTPUT_DIR/include/ghostty.h"
cat >"$OUTPUT_DIR/include/module.modulemap" <<'EOF'
module libghostty {
umbrella header "ghostty.h"
export *
}
EOF
echo "[*] built archive: $OUTPUT_DIR/lib/libghostty.a"

View File

@@ -0,0 +1,32 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[*] malformed project structure"
exit 1
fi
XCFRAMEWORK_PATH_ZIP=${1:-}
DOWNLOAD_URL=${2:-}
if [ -z "$XCFRAMEWORK_PATH_ZIP" ] || [ -z "$DOWNLOAD_URL" ]; then
echo "Usage: $0 <xcframework_zip> <download_url>"
exit 1
fi
if [ ! -f "$XCFRAMEWORK_PATH_ZIP" ]; then
echo "[!] $XCFRAMEWORK_PATH_ZIP not found"
exit 1
fi
SHA256SUM=$(shasum -a 256 "$XCFRAMEWORK_PATH_ZIP" | awk '{print $1}')
echo "[*] SHA256: $SHA256SUM"
PACKAGE_MANIFEST=$(cat Package.swift.template)
PACKAGE_MANIFEST=${PACKAGE_MANIFEST/__DOWNLOAD_URL__/$DOWNLOAD_URL}
PACKAGE_MANIFEST=${PACKAGE_MANIFEST/__CHECKSUM__/$SHA256SUM}
echo "$PACKAGE_MANIFEST" >Package.swift

126
vendor/libghostty-spm/Script/build-platform.sh vendored Executable file
View File

@@ -0,0 +1,126 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[*] malformed project structure"
exit 1
fi
SOURCE_DIR=${1:-}
PLATFORM_GROUP=${2:-}
OUTPUT_DIR=${3:-}
if [ -z "$SOURCE_DIR" ] || [ -z "$PLATFORM_GROUP" ] || [ -z "$OUTPUT_DIR" ]; then
echo "Usage: $0 <source_dir> <platform_group> <output_dir>"
exit 1
fi
if [ ! -d "$SOURCE_DIR" ]; then
echo "[!] ghostty source directory not found: $SOURCE_DIR"
exit 1
fi
build_variant() {
local variant_name="$1"
shift
local variant_dir="$OUTPUT_DIR/$variant_name"
local intermediate_dir="$OUTPUT_DIR/.intermediates/$variant_name"
local first_headers=
local libraries=()
local target_spec=
local target=
local cpu=
rm -rf "$variant_dir" "$intermediate_dir"
mkdir -p "$variant_dir/lib" "$variant_dir/include" "$intermediate_dir"
for target_spec in "$@"; do
target=${target_spec%%@*}
cpu=
if [ "$target_spec" != "$target" ]; then
cpu=${target_spec#*@}
fi
local target_dir="$intermediate_dir/$target"
if [ -n "$cpu" ]; then
env ZIG_CPU="$cpu" ./Script/build-ghostty.sh "$SOURCE_DIR" "$target" "$target_dir"
else
./Script/build-ghostty.sh "$SOURCE_DIR" "$target" "$target_dir"
fi
libraries+=("$target_dir/lib/libghostty.a")
if [ -z "$first_headers" ]; then
first_headers="$target_dir/include"
fi
done
if [ -z "$first_headers" ]; then
echo "[!] no libraries were built for variant: $variant_name"
exit 1
fi
cp -R "$first_headers/." "$variant_dir/include/"
if [ "${#libraries[@]}" -eq 1 ]; then
cp "${libraries[0]}" "$variant_dir/lib/libghostty.a"
else
lipo -create "${libraries[@]}" -output "$variant_dir/lib/libghostty.a"
fi
echo "[*] assembled variant: $variant_name"
}
mkdir -p "$OUTPUT_DIR"
case "$PLATFORM_GROUP" in
macos)
build_variant "macosx" \
"aarch64-macos" \
"arm64e-macos@apple_a12" \
"x86_64-macos"
;;
ios)
build_variant "iphoneos" \
"aarch64-ios" \
"arm64e-ios@apple_a12"
build_variant "iphonesimulator" \
"aarch64-ios-simulator@apple_a17" \
"x86_64-ios-simulator"
;;
maccatalyst)
build_variant "maccatalyst" \
"aarch64-ios-macabi@apple_a17" \
"arm64e-ios-macabi@apple_a17" \
"x86_64-ios-macabi"
;;
tvos)
build_variant "appletvos" \
"aarch64-tvos"
build_variant "appletvsimulator" \
"aarch64-tvos-simulator@apple_a17" \
"x86_64-tvos-simulator"
;;
visionos)
build_variant "xros" \
"aarch64-visionos"
build_variant "xrsimulator" \
"aarch64-visionos-simulator@apple_a17" \
"x86_64-visionos-simulator"
;;
watchos)
build_variant "watchos" \
"aarch64-watchos"
build_variant "watchsimulator" \
"aarch64-watchos-simulator@apple_a17" \
"x86_64-watchos-simulator"
;;
*)
echo "[!] unknown platform group: $PLATFORM_GROUP"
exit 1
;;
esac
echo "[*] built platform group: $PLATFORM_GROUP"

130
vendor/libghostty-spm/Script/build.sh vendored Executable file
View File

@@ -0,0 +1,130 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[*] malformed project structure"
exit 1
fi
usage() {
cat <<'EOF'
Usage: ./build.sh [options]
Options:
--source <path> Use an existing Ghostty checkout.
--ref <tag-or-commit> Checkout the given ref in the source checkout.
--platforms <csv> Build platform groups. Default:
macos,ios,maccatalyst
--download-url <url> Generate Package.swift from Package.swift.template.
--skip-tests Skip local xcodebuild and swift test verification.
-h, --help Show this help.
Notes:
- Default source path is ./References/ghostty-upstream
- This builds real per-target static archives, then assembles
BinaryTarget/GhosttyKit.xcframework and build/GhosttyKit.xcframework.zip
- Upstream Ghostty patches from ./Patches/ghostty are applied automatically
- Current verified groups: macos, ios, maccatalyst
- Current upstream Ghostty crashes for: tvos, visionos, watchos
EOF
}
ROOT_DIR=$(pwd)
SOURCE_DIR="$ROOT_DIR/References/ghostty-upstream"
PLATFORMS="macos,ios,maccatalyst"
DOWNLOAD_URL=${DOWNLOAD_URL:-}
GHOSTTY_REF=
SKIP_TESTS=0
while [ $# -gt 0 ]; do
case "$1" in
--source)
SOURCE_DIR="$2"
shift 2
;;
--ref)
GHOSTTY_REF="$2"
shift 2
;;
--platforms)
PLATFORMS="$2"
shift 2
;;
--download-url)
DOWNLOAD_URL="$2"
shift 2
;;
--skip-tests)
SKIP_TESTS=1
shift
;;
-h | --help)
usage
exit 0
;;
*)
echo "[!] unknown argument: $1"
usage
exit 1
;;
esac
done
if ! command -v zig >/dev/null 2>&1; then
echo "[!] zig not found"
exit 1
fi
if [ ! -d "$SOURCE_DIR" ]; then
echo "[*] ghostty source not found, cloning into $SOURCE_DIR"
mkdir -p "$(dirname "$SOURCE_DIR")"
git clone https://github.com/ghostty-org/ghostty "$SOURCE_DIR"
fi
if [ -n "$GHOSTTY_REF" ]; then
echo "[*] checking out ghostty ref: $GHOSTTY_REF"
git -C "$SOURCE_DIR" fetch --tags origin
git -C "$SOURCE_DIR" checkout "$GHOSTTY_REF"
fi
ARTIFACTS_DIR="$ROOT_DIR/build/artifacts"
XCFRAMEWORK_PATH="$ROOT_DIR/BinaryTarget/GhosttyKit.xcframework"
XCFRAMEWORK_ZIP="$ROOT_DIR/build/GhosttyKit.xcframework.zip"
rm -rf "$ARTIFACTS_DIR" "$XCFRAMEWORK_PATH" "$XCFRAMEWORK_ZIP"
mkdir -p "$ARTIFACTS_DIR" "$(dirname "$XCFRAMEWORK_PATH")"
echo "[*] zig version: $(zig version)"
echo "[*] ghostty source: $SOURCE_DIR"
echo "[*] platform groups: $PLATFORMS"
OLD_IFS=$IFS
IFS=','
set -- $PLATFORMS
IFS=$OLD_IFS
for platform_group in "$@"; do
platform_group=$(echo "$platform_group" | xargs)
[ -n "$platform_group" ] || continue
./Script/build-platform.sh "$SOURCE_DIR" "$platform_group" "$ARTIFACTS_DIR"
done
./Script/merge-xcframework.sh \
"$ARTIFACTS_DIR" \
"$XCFRAMEWORK_PATH" \
"$XCFRAMEWORK_ZIP"
if [ -n "$DOWNLOAD_URL" ]; then
./Script/build-manifest.sh "$XCFRAMEWORK_ZIP" "$DOWNLOAD_URL"
fi
if [ "$SKIP_TESTS" -eq 0 ]; then
./Script/test.sh
swift test
fi
echo "[*] xcframework: $XCFRAMEWORK_PATH"
echo "[*] zip: $XCFRAMEWORK_ZIP"

View File

@@ -0,0 +1,194 @@
#!/usr/bin/env python3
import os
import pathlib
import struct
import sys
AR_MAGIC = b"!<arch>\n"
AR_HEADER_SIZE = 60
MACHO64_LITTLE_ENDIAN_MAGIC = b"\xcf\xfa\xed\xfe"
CPU_TYPE_ARM64 = 0x0100000C
CPU_SUBTYPE_ARM64_ALL = 0
CPU_SUBTYPE_ARM64E = 0x80000002
LC_SYMTAB = 0x2
N_TYPE_MASK = 0x0E
N_UNDF = 0x0
NLIST64_SIZE = 16
UNDEFINED_SYMBOL_RENAMES = {
b"_pthread_create": b"_pa_pthread_cr",
b"_qsort": b"_pa_qs",
}
def fail(message: str) -> None:
raise SystemExit(f"[-] {message}")
def parse_decimal(raw: bytes, field: str) -> int:
try:
return int(raw.decode("ascii").strip())
except ValueError:
fail(f"invalid {field}: {raw!r}")
def rewrite_undefined_symbols(
data: bytearray,
object_start: int,
member_end: int,
) -> dict[bytes, int]:
object_size = member_end - object_start
if object_size < 32:
fail(f"truncated Mach-O header at offset {object_start}")
command_count, command_bytes = struct.unpack_from("<II", data, object_start + 16)
command_start = object_start + 32
command_end = command_start + command_bytes
if command_end > member_end:
fail(f"Mach-O load commands cross member boundary at offset {object_start}")
symtab = None
cursor = command_start
for _ in range(command_count):
if cursor + 8 > command_end:
fail(f"truncated Mach-O load command at offset {cursor}")
command, command_size = struct.unpack_from("<II", data, cursor)
if command_size < 8 or cursor + command_size > command_end:
fail(f"invalid Mach-O load command size at offset {cursor}")
if command == LC_SYMTAB:
if command_size < 24 or symtab is not None:
fail(f"invalid Mach-O symbol table command at offset {cursor}")
symtab = struct.unpack_from("<IIII", data, cursor + 8)
cursor += command_size
if cursor != command_end:
fail(f"Mach-O load command sizes do not match header at offset {object_start}")
if symtab is None:
return {name: 0 for name in UNDEFINED_SYMBOL_RENAMES}
symbol_offset, symbol_count, string_offset, string_size = symtab
symbol_bytes = symbol_count * NLIST64_SIZE
if symbol_offset + symbol_bytes > object_size:
fail(f"Mach-O symbol table crosses member boundary at offset {object_start}")
if string_offset + string_size > object_size:
fail(f"Mach-O string table crosses member boundary at offset {object_start}")
string_start = object_start + string_offset
string_end = string_start + string_size
renamed = {name: 0 for name in UNDEFINED_SYMBOL_RENAMES}
for index in range(symbol_count):
entry = object_start + symbol_offset + index * NLIST64_SIZE
string_index = struct.unpack_from("<I", data, entry)[0]
symbol_type = data[entry + 4]
if string_index == 0 or symbol_type & N_TYPE_MASK != N_UNDF:
continue
name_start = string_start + string_index
if name_start >= string_end:
fail(f"Mach-O symbol name crosses string table at offset {entry}")
name_end = data.find(0, name_start, string_end)
if name_end < 0:
fail(f"unterminated Mach-O symbol name at offset {name_start}")
name = bytes(data[name_start:name_end])
replacement = UNDEFINED_SYMBOL_RENAMES.get(name)
if replacement is None:
continue
if len(replacement) > len(name):
fail(f"replacement symbol is longer than source: {name!r}")
replacement_size = len(name) + 1
data[name_start : name_start + replacement_size] = replacement.ljust(
replacement_size,
b"\0",
)
renamed[name] += 1
return renamed
def convert_archive(source: pathlib.Path, destination: pathlib.Path) -> int:
data = bytearray(source.read_bytes())
if not data.startswith(AR_MAGIC):
fail(f"not a static archive: {source}")
cursor = len(AR_MAGIC)
patched_objects = 0
renamed_symbols = {name: 0 for name in UNDEFINED_SYMBOL_RENAMES}
while cursor < len(data):
if cursor + AR_HEADER_SIZE > len(data):
fail(f"truncated archive member header at offset {cursor}")
header = data[cursor : cursor + AR_HEADER_SIZE]
if header[58:60] != b"`\n":
fail(f"invalid archive member header at offset {cursor}")
member_size = parse_decimal(header[48:58], "archive member size")
member_start = cursor + AR_HEADER_SIZE
member_end = member_start + member_size
if member_end > len(data):
fail(f"archive member crosses file boundary at offset {cursor}")
object_start = member_start
member_name = header[:16].decode("ascii", errors="replace").strip()
if member_name.startswith("#1/"):
name_size = parse_decimal(member_name[3:].encode("ascii"), "extended member name size")
object_start += name_size
if object_start > member_end:
fail(f"extended member name crosses member boundary at offset {cursor}")
if data[object_start : object_start + 4] == MACHO64_LITTLE_ENDIAN_MAGIC:
if object_start + 12 > member_end:
fail(f"truncated Mach-O header at offset {object_start}")
cpu_type, cpu_subtype = struct.unpack_from("<II", data, object_start + 4)
if cpu_type != CPU_TYPE_ARM64:
fail(f"unexpected Mach-O CPU type 0x{cpu_type:08x} at offset {object_start}")
if cpu_subtype not in {CPU_SUBTYPE_ARM64_ALL, CPU_SUBTYPE_ARM64E}:
fail(f"unexpected ARM64 CPU subtype 0x{cpu_subtype:08x} at offset {object_start}")
struct.pack_into("<I", data, object_start + 8, CPU_SUBTYPE_ARM64E)
member_renames = rewrite_undefined_symbols(data, object_start, member_end)
for name, count in member_renames.items():
renamed_symbols[name] += count
patched_objects += 1
cursor = member_end + (member_size & 1)
if cursor != len(data):
fail(f"archive alignment crosses file boundary: {source}")
if patched_objects == 0:
fail(f"archive contains no ARM64 Mach-O objects: {source}")
destination.parent.mkdir(parents=True, exist_ok=True)
temporary = destination.with_name(f".{destination.name}.{os.getpid()}.tmp")
temporary.write_bytes(data)
os.replace(temporary, destination)
for name, replacement in UNDEFINED_SYMBOL_RENAMES.items():
print(
f"[+] redirected {renamed_symbols[name]} {name.decode()} imports "
f"to {replacement.decode()}"
)
return patched_objects
def main() -> None:
if len(sys.argv) != 3:
fail(f"usage: {sys.argv[0]} <arm64_archive> <arm64e_archive>")
source = pathlib.Path(sys.argv[1])
destination = pathlib.Path(sys.argv[2])
if not source.is_file():
fail(f"archive not found: {source}")
if source.resolve() == destination.resolve():
fail("source and destination must differ")
count = convert_archive(source, destination)
print(f"[+] converted {count} Mach-O objects to arm64e: {destination}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,195 @@
#!/bin/zsh
set -euo pipefail
# Downloads all Ghostty theme files from iTerm2-Color-Schemes and generates
# Swift source files for the GhosttyTheme library.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
OUTPUT_DIR="$ROOT_DIR/Sources/GhosttyTheme/Themes"
TEMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TEMP_DIR"' EXIT
mkdir -p "$OUTPUT_DIR"
# Fetch the list of theme files from GitHub API
echo "[+] fetching theme list from github api"
curl -sL "https://api.github.com/repos/mbadolato/iTerm2-Color-Schemes/contents/ghostty" \
| python3 -c "
import json, sys
data = json.load(sys.stdin)
for item in data:
if item['type'] == 'file':
print(item['name'])
" > "$TEMP_DIR/theme_list.txt"
THEME_COUNT=$(wc -l < "$TEMP_DIR/theme_list.txt" | tr -d ' ')
echo "[+] found $THEME_COUNT themes"
# Download all themes
echo "[+] downloading themes"
mkdir -p "$TEMP_DIR/themes"
while IFS= read -r name; do
encoded_name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$name'))")
curl -sL "https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/ghostty/$encoded_name" \
-o "$TEMP_DIR/themes/$name" &
# Limit concurrent downloads
if (( $(jobs -r | wc -l) >= 20 )); then
wait -n
fi
done < "$TEMP_DIR/theme_list.txt"
wait
echo "[+] downloaded all themes"
# Generate Swift files using Python
echo "[+] generating swift source files"
python3 - "$TEMP_DIR/themes" "$OUTPUT_DIR" "$TEMP_DIR/theme_list.txt" << 'PYEOF'
import os, sys, re, string
themes_dir = sys.argv[1]
output_dir = sys.argv[2]
list_file = sys.argv[3]
with open(list_file) as f:
theme_names = [line.strip() for line in f if line.strip()]
def parse_theme(path):
result = {}
palette = {}
with open(path) as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' not in line:
continue
key, _, value = line.partition('=')
key = key.strip()
value = value.strip()
if key == 'palette':
# format: N=#RRGGBB or N=RRGGBB
idx_str, _, color = value.partition('=')
idx = int(idx_str.strip())
color = color.strip().lstrip('#')
palette[idx] = color
else:
# strip # from color values
clean_val = value.lstrip('#')
result[key] = clean_val
result['_palette'] = palette
return result
def swift_identifier(name):
"""Convert theme name to a valid Swift identifier."""
# Replace + with Plus before stripping
ident = name.replace('+', 'Plus')
# Replace non-alphanumeric with underscore
ident = re.sub(r'[^a-zA-Z0-9]', '_', ident)
# Collapse multiple underscores
ident = re.sub(r'_+', '_', ident)
# Strip leading/trailing underscores
ident = ident.strip('_')
# Prefix with underscore if starts with digit
if ident and ident[0].isdigit():
ident = '_' + ident
# camelCase: lowercase first char
if ident:
ident = ident[0].lower() + ident[1:]
return ident
def swift_string(s):
return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"'
def optional_field(data, ghostty_key, swift_type="String"):
val = data.get(ghostty_key)
if val:
return swift_string(val)
return "nil"
# Group themes by first letter
groups = {}
all_themes = []
for name in sorted(theme_names, key=str.lower):
path = os.path.join(themes_dir, name)
if not os.path.isfile(path):
continue
try:
data = parse_theme(path)
except Exception as e:
print(f" [-] skipping {name}: {e}", file=sys.stderr)
continue
bg = data.get('background')
fg = data.get('foreground')
if not bg or not fg:
print(f" [-] skipping {name}: missing background/foreground", file=sys.stderr)
continue
ident = swift_identifier(name)
first_char = name[0].upper()
if first_char not in string.ascii_uppercase:
first_char = 'Symbols'
palette = data.get('_palette', {})
palette_str = '['
for i in sorted(palette.keys()):
palette_str += f'{i}: {swift_string(palette[i])}, '
if palette_str.endswith(', '):
palette_str = palette_str[:-2]
palette_str += ']'
entry = {
'name': name,
'ident': ident,
'background': swift_string(bg),
'foreground': swift_string(fg),
'cursorColor': optional_field(data, 'cursor-color'),
'cursorText': optional_field(data, 'cursor-text'),
'selectionBackground': optional_field(data, 'selection-background'),
'selectionForeground': optional_field(data, 'selection-foreground'),
'palette': palette_str,
'group': first_char,
}
groups.setdefault(first_char, []).append(entry)
all_themes.append(entry)
# Write per-group Swift files
for group, entries in sorted(groups.items()):
filename = f"Themes_{group}.swift"
filepath = os.path.join(output_dir, filename)
with open(filepath, 'w') as f:
f.write("// Auto-generated by Script/generate-themes.sh — do not edit\n\n")
f.write("public extension GhosttyThemeDefinition {\n")
for e in entries:
f.write(f" static let {e['ident']} = GhosttyThemeDefinition(\n")
f.write(f" name: {swift_string(e['name'])},\n")
f.write(f" background: {e['background']},\n")
f.write(f" foreground: {e['foreground']},\n")
f.write(f" cursorColor: {e['cursorColor']},\n")
f.write(f" cursorText: {e['cursorText']},\n")
f.write(f" selectionBackground: {e['selectionBackground']},\n")
f.write(f" selectionForeground: {e['selectionForeground']},\n")
f.write(f" palette: {e['palette']}\n")
f.write(f" )\n\n")
f.write("}\n")
print(f" [+] wrote {filename} ({len(entries)} themes)")
# Write catalog file
catalog_path = os.path.join(output_dir, "ThemeCatalog_Generated.swift")
with open(catalog_path, 'w') as f:
f.write("// Auto-generated by Script/generate-themes.sh — do not edit\n\n")
f.write("public extension GhosttyThemeCatalog {\n")
f.write(" static let allThemes: [GhosttyThemeDefinition] = [\n")
for e in all_themes:
f.write(f" .{e['ident']},\n")
f.write(" ]\n")
f.write("}\n")
print(f" [+] wrote ThemeCatalog_Generated.swift ({len(all_themes)} themes)")
print(f"[+] generated {len(all_themes)} themes total")
PYEOF
echo "[+] done"

View File

@@ -0,0 +1,93 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[*] malformed project structure"
exit 1
fi
ARTIFACTS_DIR=${1:-}
OUTPUT_XCFRAMEWORK=${2:-}
OUTPUT_ZIP=${3:-}
if [ -z "$ARTIFACTS_DIR" ] || [ -z "$OUTPUT_XCFRAMEWORK" ]; then
echo "Usage: $0 <artifacts_dir> <output_xcframework> [output_zip]"
exit 1
fi
if [ ! -d "$ARTIFACTS_DIR" ]; then
echo "[!] artifacts directory not found: $ARTIFACTS_DIR"
exit 1
fi
XCFRAMEWORK_COMMAND=()
FOUND_VARIANTS=0
verify_headers() {
local header_dir="$1"
if [ ! -f "$header_dir/ghostty.h" ]; then
echo "[!] missing header: $header_dir/ghostty.h"
exit 1
fi
if [ ! -f "$header_dir/module.modulemap" ]; then
echo "[!] missing module map: $header_dir/module.modulemap"
exit 1
fi
}
add_library() {
local variant_dir="$1"
local library_path="$variant_dir/lib/libghostty.a"
local header_dir="$variant_dir/include"
if [ ! -f "$library_path" ]; then
echo "[!] missing library: $library_path"
exit 1
fi
if [ ! -d "$header_dir" ]; then
echo "[!] missing headers: $header_dir"
exit 1
fi
verify_headers "$header_dir"
XCFRAMEWORK_COMMAND+=("-library" "$library_path" "-headers" "$header_dir")
FOUND_VARIANTS=$((FOUND_VARIANTS + 1))
}
for variant_dir in "$ARTIFACTS_DIR"/*; do
[ -d "$variant_dir" ] || continue
[ "$(basename "$variant_dir")" = ".intermediates" ] && continue
add_library "$variant_dir"
done
if [ "$FOUND_VARIANTS" -eq 0 ]; then
echo "[!] no staged variants found in $ARTIFACTS_DIR"
exit 1
fi
mkdir -p "$(dirname "$OUTPUT_XCFRAMEWORK")"
rm -rf "$OUTPUT_XCFRAMEWORK"
echo "[*] creating static-library xcframework: $OUTPUT_XCFRAMEWORK"
xcodebuild -create-xcframework \
-output "$OUTPUT_XCFRAMEWORK" \
"${XCFRAMEWORK_COMMAND[@]}"
./Script/verify-xcframework.sh "$OUTPUT_XCFRAMEWORK"
if [ -n "$OUTPUT_ZIP" ]; then
mkdir -p "$(dirname "$OUTPUT_ZIP")"
rm -f "$OUTPUT_ZIP"
(
cd "$(dirname "$OUTPUT_XCFRAMEWORK")"
ditto -c -k --sequesterRsrc --keepParent "$(basename "$OUTPUT_XCFRAMEWORK")" "$(basename "$OUTPUT_ZIP")"
)
mv "$(dirname "$OUTPUT_XCFRAMEWORK")/$(basename "$OUTPUT_ZIP")" "$OUTPUT_ZIP"
./Script/verify-xcframework.sh "$OUTPUT_ZIP"
echo "[*] packed xcframework zip: $OUTPUT_ZIP"
fi

View File

@@ -0,0 +1,53 @@
#!/bin/zsh
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[-] malformed project structure"
exit 1
fi
SOURCE_DIR=${1:-}
GLOBAL_CACHE_DIR=${2:-}
if [ -z "$SOURCE_DIR" ] || [ -z "$GLOBAL_CACHE_DIR" ]; then
echo "Usage: $0 <source_dir> <zig_global_cache_dir>"
exit 1
fi
if [ ! -d "$SOURCE_DIR" ]; then
echo "[-] ghostty source directory not found: $SOURCE_DIR"
exit 1
fi
ZIG_OBJC_URL="https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz"
ZIG_OBJC_HASH="zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK"
VENDOR_DIR="$SOURCE_DIR/.libghostty-spm/zig_objc"
VENDOR_PATCH="$(pwd)/Patches/arm64e/zig-objc/0001-arm64e-boundaries.patch"
VENDOR_PATCH_MARKER="$VENDOR_DIR/.libghostty-spm-arm64e-patched"
./Script/apply-patches.sh "$SOURCE_DIR" "$(pwd)/Patches/arm64e/ghostty"
if [ ! -f "$VENDOR_DIR/src/block.zig" ]; then
fetched_hash=$(zig fetch --global-cache-dir "$GLOBAL_CACHE_DIR" "$ZIG_OBJC_URL")
if [ "$fetched_hash" != "$ZIG_OBJC_HASH" ]; then
echo "[-] zig-objc hash mismatch: expected $ZIG_OBJC_HASH, got $fetched_hash"
exit 1
fi
mkdir -p "$(dirname "$VENDOR_DIR")"
cp -R "$GLOBAL_CACHE_DIR/p/$ZIG_OBJC_HASH" "$VENDOR_DIR"
echo "[+] vendored pinned zig-objc for arm64e"
fi
if [ ! -f "$VENDOR_PATCH_MARKER" ]; then
patch -p1 -f -d "$VENDOR_DIR" < "$VENDOR_PATCH" >/dev/null
touch "$VENDOR_PATCH_MARKER"
echo "[+] applied zig-objc arm64e patch"
else
echo "[+] zig-objc arm64e patch already applied"
fi
echo "[+] prepared arm64e pointer-authentication boundaries"

View File

@@ -0,0 +1,152 @@
#include "ghostty.h"
#include <stdbool.h>
#include <stdint.h>
#if defined(__arm64e__)
#include <pthread.h>
#include <ptrauth.h>
#include <stdatomic.h>
#include <stdlib.h>
enum {
ghostty_block_has_copy_dispose = 1 << 25,
ghostty_objc_isa_discriminator = 0x6AE1,
};
typedef struct {
uintptr_t reserved;
uintptr_t size;
void *copy_helper;
void *dispose_helper;
} ghostty_block_descriptor_s;
typedef struct {
void *isa;
int flags;
int reserved;
void *invoke;
ghostty_block_descriptor_s *descriptor;
} ghostty_block_s;
static atomic_flag ghostty_descriptor_lock = ATOMIC_FLAG_INIT;
static void *ghostty_sign_function_pointer(void *pointer,
uintptr_t discriminator) {
void *raw = ptrauth_strip(pointer, ptrauth_key_function_pointer);
return ptrauth_sign_unauthenticated(
raw, ptrauth_key_function_pointer, discriminator);
}
static void *ghostty_sign_objc_isa_pointer(void *pointer,
uintptr_t discriminator) {
void *raw = ptrauth_strip(pointer, ptrauth_key_objc_isa_pointer);
return ptrauth_sign_unauthenticated(
raw, ptrauth_key_objc_isa_pointer, discriminator);
}
static void ghostty_prepare_block_descriptor(ghostty_block_descriptor_s *descriptor) {
while (atomic_flag_test_and_set_explicit(&ghostty_descriptor_lock,
memory_order_acquire)) {
}
descriptor->copy_helper = ghostty_sign_function_pointer(
descriptor->copy_helper,
(uintptr_t)&descriptor->copy_helper);
descriptor->dispose_helper = ghostty_sign_function_pointer(
descriptor->dispose_helper,
(uintptr_t)&descriptor->dispose_helper);
atomic_flag_clear_explicit(&ghostty_descriptor_lock, memory_order_release);
}
const void *ghostty_arm64e_sign_function(const void *pointer) {
return ghostty_sign_function_pointer((void *)pointer, 0);
}
void ghostty_arm64e_prepare_block(void *raw_block) {
ghostty_block_s *block = raw_block;
if ((block->flags & ghostty_block_has_copy_dispose) != 0) {
ghostty_prepare_block_descriptor(block->descriptor);
}
block->isa = ghostty_sign_objc_isa_pointer(
block->isa,
ptrauth_blend_discriminator(&block->isa, ghostty_objc_isa_discriminator));
block->invoke = ghostty_sign_function_pointer(
block->invoke,
(uintptr_t)&block->invoke);
#if __has_feature(ptrauth_signed_block_descriptors)
block->descriptor = ptrauth_sign_unauthenticated(
ptrauth_strip(block->descriptor, ptrauth_key_block_descriptor_pointer),
ptrauth_key_block_descriptor_pointer,
ptrauth_blend_discriminator(&block->descriptor, 0xC0BB));
#endif
}
__attribute__((visibility("hidden"))) int pa_pthread_cr(
pthread_t *thread,
const pthread_attr_t *attributes,
void *(*start_routine)(void *),
void *argument) {
start_routine = ghostty_sign_function_pointer((void *)start_routine, 0);
return pthread_create(thread, attributes, start_routine, argument);
}
__attribute__((visibility("hidden"))) void pa_qs(
void *base,
size_t count,
size_t size,
int (*comparator)(const void *, const void *)) {
comparator = ghostty_sign_function_pointer((void *)comparator, 0);
qsort(base, count, size, comparator);
}
extern ghostty_app_t ghostty_arm64e_core_app_new(
const ghostty_runtime_config_s *,
ghostty_config_t);
extern ghostty_surface_t ghostty_arm64e_core_surface_new(
ghostty_app_t,
const ghostty_surface_config_s *);
GHOSTTY_API ghostty_app_t ghostty_app_new(
const ghostty_runtime_config_s *config,
ghostty_config_t ghostty_config) {
ghostty_runtime_config_s raw = *config;
raw.wakeup_cb = ptrauth_strip(raw.wakeup_cb, ptrauth_key_function_pointer);
raw.action_cb = ptrauth_strip(raw.action_cb, ptrauth_key_function_pointer);
raw.read_clipboard_cb =
ptrauth_strip(raw.read_clipboard_cb, ptrauth_key_function_pointer);
raw.confirm_read_clipboard_cb =
ptrauth_strip(raw.confirm_read_clipboard_cb, ptrauth_key_function_pointer);
raw.write_clipboard_cb =
ptrauth_strip(raw.write_clipboard_cb, ptrauth_key_function_pointer);
raw.close_surface_cb =
ptrauth_strip(raw.close_surface_cb, ptrauth_key_function_pointer);
return ghostty_arm64e_core_app_new(&raw, ghostty_config);
}
GHOSTTY_API ghostty_surface_t ghostty_surface_new(
ghostty_app_t app,
const ghostty_surface_config_s *config) {
ghostty_surface_config_s raw = *config;
raw.receive_buffer =
ptrauth_strip(raw.receive_buffer, ptrauth_key_function_pointer);
raw.receive_resize =
ptrauth_strip(raw.receive_resize, ptrauth_key_function_pointer);
return ghostty_arm64e_core_surface_new(app, &raw);
}
#else
const void *ghostty_arm64e_sign_function(const void *pointer) {
return pointer;
}
void ghostty_arm64e_prepare_block(void *block) {
(void)block;
}
#endif

View File

@@ -0,0 +1,18 @@
// libc++'s std::__1::__libcpp_verbose_abort is only exported by the Apple
// system libc++.1.dylib since iOS 16.3 / macOS 13.3 / tvOS 16.3. Zig's
// bundled libc++ headers reference it unconditionally (no Apple availability
// markup), so apps linking libghostty.a crash at launch on older OS versions
// with dyld "Symbol missing". Shipping the definition inside the archive
// resolves the reference at static link time instead.
//
// Header-free on purpose: zig cc has no Apple sysroot in this build.
__attribute__((noreturn)) void abort(void);
__attribute__((visibility("hidden"), noreturn))
void libghostty_libcpp_verbose_abort(const char *format, ...) __asm__("__ZNSt3__122__libcpp_verbose_abortEPKcz");
void libghostty_libcpp_verbose_abort(const char *format, ...) {
(void)format;
abort();
}

80
vendor/libghostty-spm/Script/tag-release.sh vendored Executable file
View File

@@ -0,0 +1,80 @@
#!/bin/zsh
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[-] malformed project structure"
exit 1
fi
usage() {
cat <<'EOF'
Usage: ./Script/tag-release.sh [options]
Options:
--push push the new tag to origin after creating it
--suffix <value> override the ci-style release suffix
-h, --help show this help
Notes:
- release tags follow the ci pattern: 1.0.<suffix>
- this script tags swift source releases only
- this script does not create storage.* tags or update binaries
EOF
}
PUSH_TAG=0
RELEASE_SUFFIX=
while [ $# -gt 0 ]; do
case "$1" in
--push)
PUSH_TAG=1
shift
;;
--suffix)
RELEASE_SUFFIX="$2"
shift 2
;;
-h | --help)
usage
exit 0
;;
*)
echo "[-] unknown argument: $1"
usage
exit 1
;;
esac
done
if [ -z "$RELEASE_SUFFIX" ]; then
RELEASE_SUFFIX=$(date -u +%s)
fi
RELEASE_TAG="1.0.$RELEASE_SUFFIX"
RELEASE_DATE=$(date -u +%Y-%m-%d)
HEAD_SHA=$(git rev-parse --short=12 HEAD)
if git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
echo "[-] tag already exists: $RELEASE_TAG"
exit 1
fi
TAG_MESSAGE=$(cat <<EOF
Swift source release $RELEASE_DATE
Tag: $RELEASE_TAG
Commit: $HEAD_SHA
EOF
)
git tag -a "$RELEASE_TAG" -m "$TAG_MESSAGE"
echo "[+] created tag $RELEASE_TAG for $HEAD_SHA on $RELEASE_DATE"
if [ "$PUSH_TAG" -eq 1 ]; then
git push origin "$RELEASE_TAG"
echo "[+] pushed tag $RELEASE_TAG"
fi

View File

@@ -0,0 +1,288 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[*] malformed project structure"
exit 1
fi
XCFRAMEWORK_PATH=${1:-BinaryTarget/GhosttyKit.xcframework}
if [ ! -d "$XCFRAMEWORK_PATH" ]; then
echo "[!] xcframework not found: $XCFRAMEWORK_PATH"
exit 1
fi
format_output() {
if command -v xcbeautify >/dev/null 2>&1; then
xcbeautify
else
cat
fi
}
WORK_DIR=$(mktemp -d)
trap 'rm -rf "$WORK_DIR"' EXIT
mkdir -p "$WORK_DIR/Consumer/BinaryTarget" "$WORK_DIR/Consumer/Sources/Consumer"
ditto "$XCFRAMEWORK_PATH" "$WORK_DIR/Consumer/BinaryTarget/GhosttyKit.xcframework"
cat >"$WORK_DIR/Consumer/Package.swift" <<'EOF'
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "Consumer",
platforms: [
.iOS(.v15),
.macOS(.v13),
.macCatalyst(.v15),
],
products: [
.library(name: "Consumer", targets: ["Consumer"]),
],
targets: [
.binaryTarget(
name: "libghostty",
path: "BinaryTarget/GhosttyKit.xcframework"
),
.target(
name: "Consumer",
dependencies: ["libghostty"],
linkerSettings: [
.linkedLibrary("c++"),
.linkedFramework("Carbon", .when(platforms: [.macOS])),
]
),
]
)
EOF
cat >"$WORK_DIR/Consumer/Sources/Consumer/Consumer.swift" <<'EOF'
import libghostty
public func passThroughPlatform(_ platform: ghostty_platform_e) -> ghostty_platform_e {
platform
}
EOF
ARM64E_LINK_SOURCE="$WORK_DIR/arm64e-link.c"
cat >"$ARM64E_LINK_SOURCE" <<'EOF'
#include "ghostty.h"
#include <pthread.h>
#include <ptrauth.h>
#include <stdlib.h>
extern int pa_pthread_cr(
pthread_t *,
const pthread_attr_t *,
void *(*)(void *),
void *);
extern void pa_qs(
void *,
size_t,
size_t,
int (*)(const void *, const void *));
static void wakeup(void *userdata) {
(void)userdata;
}
static void *thread_main(void *context) {
int *value = context;
*value = 42;
return NULL;
}
static int compare_ints(const void *lhs, const void *rhs) {
const int left = *(const int *)lhs;
const int right = *(const int *)rhs;
return (left > right) - (left < right);
}
int main(void) {
if (ghostty_init(0, NULL) != GHOSTTY_SUCCESS) {
return 1;
}
ghostty_config_t config = ghostty_config_new();
if (config == NULL) {
return 2;
}
ghostty_config_finalize(config);
ghostty_runtime_config_s runtime = {0};
runtime.wakeup_cb = wakeup;
ghostty_app_t app = ghostty_app_new(&runtime, config);
if (app == NULL) {
ghostty_config_free(config);
return 3;
}
int thread_value = 0;
pthread_t thread;
void *(*raw_thread_main)(void *) =
ptrauth_strip(&thread_main, ptrauth_key_function_pointer);
if (pa_pthread_cr(&thread, NULL, raw_thread_main, &thread_value) != 0) {
return 4;
}
if (pthread_join(thread, NULL) != 0 || thread_value != 42) {
return 5;
}
int values[] = {3, 1, 2};
int (*raw_compare)(const void *, const void *) =
ptrauth_strip(&compare_ints, ptrauth_key_function_pointer);
pa_qs(values, 3, sizeof(values[0]), raw_compare);
if (values[0] != 1 || values[1] != 2 || values[2] != 3) {
return 6;
}
ghostty_app_free(app);
ghostty_config_free(config);
return 0;
}
EOF
find_slice_paths() {
local platform="$1"
local platform_variant="$2"
python3 - "$XCFRAMEWORK_PATH" "$platform" "$platform_variant" <<'PY'
import os
import plistlib
import sys
xcframework, expected_platform, expected_variant = sys.argv[1:]
expected_variant = expected_variant or None
with open(os.path.join(xcframework, "Info.plist"), "rb") as handle:
libraries = plistlib.load(handle)["AvailableLibraries"]
for library in libraries:
if library.get("SupportedPlatform") != expected_platform:
continue
if library.get("SupportedPlatformVariant") != expected_variant:
continue
root = os.path.join(xcframework, library["LibraryIdentifier"])
print(os.path.join(root, library["LibraryPath"]))
print(os.path.join(root, library["HeadersPath"]))
break
else:
raise SystemExit(
f"[!] xcframework slice not found: {expected_platform}/{expected_variant}"
)
PY
}
link_arm64e() {
local name="$1"
local sdk="$2"
local target="$3"
local platform="$4"
local platform_variant="$5"
local slice_paths
local library_path
local headers_path
local output_path="$WORK_DIR/arm64e-link-$name"
local command
slice_paths=$(find_slice_paths "$platform" "$platform_variant")
library_path=${slice_paths%%$'\n'*}
headers_path=${slice_paths#*$'\n'}
command=(
xcrun --sdk "$sdk" clang
-target "$target"
"$ARM64E_LINK_SOURCE"
-I "$headers_path"
"-Wl,-force_load,$library_path"
-lc++
-framework Foundation
-framework CoreFoundation
-framework CoreGraphics
-framework CoreText
-framework CoreVideo
-framework QuartzCore
-framework IOSurface
-framework Metal
-framework MetalKit
)
case "$name" in
macos)
command+=(-framework Carbon)
;;
ios)
command+=(-framework UIKit)
;;
maccatalyst)
local sdk_path
sdk_path=$(xcrun --sdk macosx --show-sdk-path)
command+=(
-F "$sdk_path/System/iOSSupport/System/Library/Frameworks"
-L "$sdk_path/System/iOSSupport/usr/lib"
-framework UIKit
)
;;
esac
command+=(-o "$output_path")
echo "[*] full link platform=$name architecture=arm64e"
"${command[@]}"
if [ "$(lipo -archs "$output_path")" != "arm64e" ]; then
echo "[!] linked executable is not a single arm64e slice: $output_path"
exit 1
fi
if [ "$name" = "macos" ] && [ "$(uname -m)" = "arm64" ]; then
echo "[*] runtime smoke platform=macos architecture=arm64e"
"$output_path"
fi
}
test_build() {
local destination="$1"
local architecture=${2:-}
local command=(
xcodebuild
-scheme Consumer
-destination "$destination"
-derivedDataPath "$WORK_DIR/DerivedData"
-packageCachePath "$WORK_DIR/PackageCache"
)
if [ -n "$architecture" ]; then
command+=("ARCHS=$architecture" "ONLY_ACTIVE_ARCH=YES")
fi
command+=(build)
echo "[*] consumer build destination=$destination architecture=${architecture:-default}"
"${command[@]}" 2>&1 | format_output
local exit_code=${PIPESTATUS[0]}
if [ "$exit_code" -ne 0 ]; then
echo "[!] consumer build failed destination=$destination"
exit "$exit_code"
fi
}
(
cd "$WORK_DIR/Consumer"
test_build "generic/platform=macOS"
test_build "generic/platform=macOS,variant=Mac Catalyst"
test_build "generic/platform=iOS"
test_build "generic/platform=iOS Simulator"
test_build "generic/platform=macOS" "arm64e"
test_build "generic/platform=macOS,variant=Mac Catalyst" "arm64e"
test_build "generic/platform=iOS" "arm64e"
)
link_arm64e "macos" "macosx" "arm64e-apple-macosx13.0" "macos" ""
link_arm64e "ios" "iphoneos" "arm64e-apple-ios15.0" "ios" ""
link_arm64e "maccatalyst" "macosx" "arm64e-apple-ios15.0-macabi" "ios" "maccatalyst"
echo "[*] xcframework consumer tests passed"

56
vendor/libghostty-spm/Script/test.sh vendored Executable file
View File

@@ -0,0 +1,56 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
export CLANG_MODULE_CACHE_PATH="${CLANG_MODULE_CACHE_PATH:-/tmp/clang-module-cache}"
export SWIFTPM_MODULECACHE_OVERRIDE="${SWIFTPM_MODULECACHE_OVERRIDE:-/tmp/swiftpm-module-cache}"
format_output() {
if command -v xcbeautify >/dev/null 2>&1; then
xcbeautify
else
cat
fi
}
test_build() {
local scheme="$1"
local destination="$2"
local architecture=${3:-}
local command=(
xcodebuild
-scheme "$scheme"
-destination "$destination"
)
if [ -n "$architecture" ]; then
command+=("ARCHS=$architecture" "ONLY_ACTIVE_ARCH=YES")
fi
command+=(build)
echo "[*] build scheme=$scheme destination=$destination architecture=${architecture:-default}"
"${command[@]}" 2>&1 | format_output
local exit_code=${PIPESTATUS[0]}
if [ "$exit_code" -ne 0 ]; then
echo "[!] failed scheme=$scheme destination=$destination"
exit "$exit_code"
fi
}
test_build "GhosttyKit" "generic/platform=macOS"
test_build "GhosttyKit" "generic/platform=iOS"
test_build "GhosttyKit" "generic/platform=iOS Simulator"
test_build "GhosttyTerminal" "generic/platform=macOS"
test_build "GhosttyTerminal" "generic/platform=macOS,variant=Mac Catalyst"
test_build "GhosttyTerminal" "generic/platform=iOS"
test_build "GhosttyTerminal" "generic/platform=iOS Simulator"
test_build "GhosttyKit" "generic/platform=macOS" "arm64e"
test_build "GhosttyKit" "generic/platform=macOS,variant=Mac Catalyst" "arm64e"
test_build "GhosttyKit" "generic/platform=iOS" "arm64e"
test_build "GhosttyTerminal" "generic/platform=macOS" "arm64e"
test_build "GhosttyTerminal" "generic/platform=macOS,variant=Mac Catalyst" "arm64e"
test_build "GhosttyTerminal" "generic/platform=iOS" "arm64e"
echo "[*] all tests passed"

View File

@@ -0,0 +1,73 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[*] malformed project structure"
exit 1
fi
PACKAGE_TAG=${1:-}
STORAGE_TAG=${2:-}
ASSET_NAME=${3:-GhosttyKit.xcframework.zip}
if [ -z "$PACKAGE_TAG" ] || [ -z "$STORAGE_TAG" ]; then
echo "Usage: $0 <package_tag> <storage_tag> [asset_name]"
exit 1
fi
if ! command -v gh >/dev/null 2>&1; then
echo "[!] gh not found"
exit 1
fi
git fetch --tags origin
package_commit=$(git rev-parse "refs/tags/$PACKAGE_TAG")
storage_commit=$(git rev-parse "refs/tags/$STORAGE_TAG")
if [ "$package_commit" != "$storage_commit" ]; then
echo "[!] package tag and storage tag point at different commits"
echo " $PACKAGE_TAG: $package_commit"
echo " $STORAGE_TAG: $storage_commit"
exit 1
fi
manifest=$(git show "$PACKAGE_TAG:Package.swift")
download_url=$(printf '%s\n' "$manifest" | python3 -c 'import re, sys; text=sys.stdin.read(); urls=re.findall(r"url:\s*\"([^\"]+)\"", text); matches=[url for url in urls if "/releases/download/" in url and url.endswith("/GhosttyKit.xcframework.zip")]; print(matches[0] if matches else "", end="")')
checksum=$(printf '%s\n' "$manifest" | python3 -c 'import re, sys; text=sys.stdin.read(); match=re.search(r"checksum:\s*\"([0-9a-f]{64})\"", text); print(match.group(1) if match else "", end="")')
if [ -z "$download_url" ] || [ -z "$checksum" ]; then
echo "[!] failed to read binary target URL/checksum from Package.swift at $PACKAGE_TAG"
exit 1
fi
expected_url="https://github.com/Lakr233/libghostty-spm/releases/download/$STORAGE_TAG/$ASSET_NAME"
if [ "$download_url" != "$expected_url" ]; then
echo "[!] Package.swift download URL does not match storage release"
echo " expected: $expected_url"
echo " actual: $download_url"
exit 1
fi
asset_digest=$(
gh release view "$STORAGE_TAG" \
--json assets \
--jq ".assets[] | select(.name == \"$ASSET_NAME\") | .digest"
)
if [ -z "$asset_digest" ]; then
echo "[!] release asset not found: $STORAGE_TAG/$ASSET_NAME"
exit 1
fi
if [ "$asset_digest" != "sha256:$checksum" ]; then
echo "[!] release asset digest does not match Package.swift checksum"
echo " asset: $asset_digest"
echo " manifest: sha256:$checksum"
exit 1
fi
echo "[*] release verified: $PACKAGE_TAG -> $STORAGE_TAG/$ASSET_NAME"

View File

@@ -0,0 +1,125 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -f .root ]; then
echo "[*] malformed project structure"
exit 1
fi
INPUT_PATH=${1:-}
if [ -z "$INPUT_PATH" ]; then
echo "Usage: $0 <xcframework_or_zip>"
exit 1
fi
if [ ! -e "$INPUT_PATH" ]; then
echo "[!] not found: $INPUT_PATH"
exit 1
fi
TEMP_DIR=
XCFRAMEWORK_PATH="$INPUT_PATH"
cleanup() {
if [ -n "$TEMP_DIR" ]; then
rm -rf "$TEMP_DIR"
fi
}
trap cleanup EXIT
if [[ "$INPUT_PATH" == *.zip ]]; then
TEMP_DIR=$(mktemp -d)
ditto -x -k "$INPUT_PATH" "$TEMP_DIR"
XCFRAMEWORK_PATH=$(find "$TEMP_DIR" -maxdepth 1 -name "*.xcframework" -type d | head -1)
fi
if [ -z "$XCFRAMEWORK_PATH" ] || [ ! -d "$XCFRAMEWORK_PATH" ]; then
echo "[!] xcframework not found in input: $INPUT_PATH"
exit 1
fi
python3 - "$XCFRAMEWORK_PATH" <<'PY'
import os
import plistlib
import subprocess
import sys
xcframework = sys.argv[1]
info_path = os.path.join(xcframework, "Info.plist")
if not os.path.isfile(info_path):
raise SystemExit(f"[!] missing xcframework Info.plist: {info_path}")
for root, dirs, _ in os.walk(xcframework):
for directory in dirs:
if directory.endswith(".framework"):
raise SystemExit(f"[!] framework wrapper is not allowed in static-library xcframework: {os.path.join(root, directory)}")
with open(info_path, "rb") as handle:
info = plistlib.load(handle)
libraries = info.get("AvailableLibraries")
if not isinstance(libraries, list) or not libraries:
raise SystemExit("[!] AvailableLibraries is empty or missing")
for library in libraries:
identifier = library.get("LibraryIdentifier")
library_path = library.get("LibraryPath")
binary_path = library.get("BinaryPath")
headers_path = library.get("HeadersPath")
platform = library.get("SupportedPlatform")
platform_variant = library.get("SupportedPlatformVariant")
declared_architectures = set(library.get("SupportedArchitectures", []))
if not identifier:
raise SystemExit("[!] library entry missing LibraryIdentifier")
if platform not in {"ios", "macos", "tvos", "watchos", "xros"}:
raise SystemExit(f"[!] unsupported platform in {identifier}: {platform}")
if library_path != "libghostty.a" or binary_path != "libghostty.a":
raise SystemExit(f"[!] {identifier} must reference libghostty.a, got LibraryPath={library_path!r} BinaryPath={binary_path!r}")
if headers_path != "Headers":
raise SystemExit(f"[!] {identifier} must use HeadersPath=Headers, got {headers_path!r}")
variant_dir = os.path.join(xcframework, identifier)
archive_path = os.path.join(variant_dir, library_path)
headers_dir = os.path.join(variant_dir, headers_path)
header_path = os.path.join(headers_dir, "ghostty.h")
modulemap_path = os.path.join(headers_dir, "module.modulemap")
for required_path in (archive_path, header_path, modulemap_path):
if not os.path.exists(required_path):
raise SystemExit(f"[!] {identifier} missing required file: {required_path}")
actual_architectures = set(
subprocess.check_output(["lipo", "-archs", archive_path], text=True).split()
)
if actual_architectures != declared_architectures:
raise SystemExit(
f"[!] {identifier} architecture metadata mismatch: "
f"plist={sorted(declared_architectures)} archive={sorted(actual_architectures)}"
)
expected_architectures = {
("macos", None): {"arm64", "arm64e", "x86_64"},
("ios", None): {"arm64", "arm64e"},
("ios", "maccatalyst"): {"arm64", "arm64e", "x86_64"},
("ios", "simulator"): {"arm64", "x86_64"},
}.get((platform, platform_variant))
if expected_architectures is not None and actual_architectures != expected_architectures:
raise SystemExit(
f"[!] {identifier} architectures must be {sorted(expected_architectures)}, "
f"got {sorted(actual_architectures)}"
)
with open(modulemap_path, "r", encoding="utf-8") as handle:
modulemap = handle.read()
if "framework module libghostty" in modulemap:
raise SystemExit(f"[!] {identifier} module map must not declare a framework module")
if "module libghostty" not in modulemap:
raise SystemExit(f"[!] {identifier} module map does not declare module libghostty")
print(f"[*] verified static-library xcframework layout and architectures: {xcframework}")
PY