libghostty als zweite Terminal-Engine integriert (Default ghostty, SwiftTerm Fallback)

- vendor/libghostty: vorgebaute macOS-libghostty.a (arm64) + Header + Build-Patches + README
- Sources/CGhostty: C-Modul der libghostty-C-API
- Sources/MiniTerm/Ghostty.swift: GhosttyApp + GhosttySurfaceView (Surface, Input, Größe, Fokus)
- main.swift: Engine-Wahl per Info.plist MTEngine
- Package.swift: CGhostty-Target + Link gegen libghostty + Frameworks
- STATUS.md: Stand, Build-Weg (macOS-15-VM), offene Punkte
This commit is contained in:
marcus.hinz
2026-06-21 21:40:00 +02:00
parent 51cb14a782
commit c8f5714b1e
22 changed files with 4970 additions and 11 deletions
+20 -1
View File
@@ -12,9 +12,28 @@ let package = Package(
.package(url: "https://github.com/migueldeicaza/SwiftTerm.git", branch: "main")
],
targets: [
// C-Modul für die libghostty-C-API (Header in Sources/CGhostty/include).
.target(name: "CGhostty"),
.executableTarget(
name: "MiniTerm",
dependencies: ["SwiftTerm"]
dependencies: ["SwiftTerm", "CGhostty"],
linkerSettings: [
// Vorgebaute statische libghostty (siehe vendor/libghostty/README.md).
// Pfad relativ zum Paketwurzelverzeichnis (so ruft make-app.sh `swift build`).
.unsafeFlags(["-L", "vendor/libghostty/lib", "-lghostty"]),
// Frameworks, die libghostty (Metal-Renderer, Fonts, Surface) braucht.
.linkedFramework("AppKit"),
.linkedFramework("Metal"),
.linkedFramework("MetalKit"),
.linkedFramework("QuartzCore"),
.linkedFramework("CoreText"),
.linkedFramework("CoreGraphics"),
.linkedFramework("CoreVideo"),
.linkedFramework("IOSurface"),
.linkedFramework("Carbon"),
.linkedLibrary("c++"),
]
)
]
)
+54
View File
@@ -0,0 +1,54 @@
# Stand: libghostty-Integration in MiniTerm
Stand 2026-06-21. MiniTerm baut pro Aufruf eigenständige macOS-`.app`s (eigene
Bundle-ID → eigenes Dock-Icon/⌘-Tab). Bisher SwiftTerm als Terminal. Ziel:
SwiftTerm durch **libghostty** (GPU/Metal-Renderer von Ghostty) ersetzen.
## Erreicht
- **libghostty gebaut** (volle macOS-Variante mit Metal-Renderer, Surface-/App-API),
liegt als `vendor/libghostty/` (lib/`libghostty.a` arm64 ~135 MB, `include/`,
`patches/`, `README.md`). Quelle Ghostty **v1.3.1** + Zig **0.15.2**.
- **In MiniTerm integriert, kompiliert, linkt, läuft** — Test-`.app` startet ohne
Crash mit GPU-gerenderter Login-Shell. Mausrad/Scrollback funktioniert.
## Warum der Build aufwändig war
- Zig 0.15.2 linkt auf dem **Host (macOS 26.5)** kein natives Binary (libSystem-
Inkompatibilität mit dem 26er-SDK; Auto-Erkennung von `26.x` scheitert).
Neuere Zig (0.16/0.17) linken, bauen aber Ghostty 1.3.1 nicht (API-Drift).
- Lösung: Build in **macOS-15-VM** (tart, cirruslabs-Image, Apple-Silicon-Host)
mit aktivem **CLT (15.5-SDK)** → Zig linkt. `metal`-Shader-Compiler nur per
`DEVELOPER_DIR` aus dem installierten Xcode geholt.
- 3 Build-Patches an Ghostty (in `vendor/libghostty/patches/…patch`):
build.zig (macOS-`libghostty.a`-Install frei, shared `.so` raus),
GhosttyXCFramework.zig (nur macOS-Slice, kein iOS), MetallibStep.zig
(metal via Xcode-`DEVELOPER_DIR`).
- libghostty.a aus VM herauskopiert; Host-Swift linkt sie problemlos (Cross-SDK
unkritisch). VM + Scratch danach abgeräumt.
## Neue/geänderte Dateien
- `Sources/CGhostty/` — C-Modul (`include/ghostty.h` + `module.modulemap`,
leeres `shim.c`) für die libghostty-C-API.
- `Sources/MiniTerm/Ghostty.swift``GhosttyApp` (einmalig `ghostty_init`,
Config laden/finalize, `ghostty_runtime_config_s`-Callbacks, `ghostty_app_tick`
via wakeup) + `GhosttySurfaceView` (NSView, `ghostty_surface_new` mit
nsview-Zeiger; Größe/Content-Scale/Fokus; Tastatur inkl. Shift+Enter→
`ghostty_surface_text`; Maus/Scroll).
- `Sources/MiniTerm/main.swift` — Engine-Wahl per Info.plist **`MTEngine`**
(Default `ghostty`, `swiftterm` = Fallback, auch wenn libghostty-Init scheitert).
- `Package.swift` — CGhostty-Target + linkerSettings:
`-L vendor/libghostty/lib -lghostty` + Frameworks AppKit/Metal/MetalKit/
QuartzCore/CoreText/CoreGraphics/CoreVideo/IOSurface/Carbon, `-lc++`.
## Offen / bewusst weggelassen
- **Kein Scrollbalken** — der ist in Ghostty.app host-seitig (`SurfaceScrollView`),
nicht in libghostty; unsere View bildet ihn nicht nach (Scrollback per Mausrad
geht). Nachrüstbar.
- **Clipboard-Callbacks no-op** — kein ⌘C/⌘V über libghostty.
- **Font-Family** wird nicht gesetzt (nur `font_size`; Family ginge nur über eine
Ghostty-Config-Datei via `ghostty_config_load_file`).
- **arm64-only** (kein x86_64-Slice).
- Visuelle Vollprüfung (Tippen/Resize/Reflow) durch Marcus noch offen.
## Reproduzieren ohne VM
`vendor/libghostty/lib/libghostty.a` + `include/` genügen zum Bauen. Neu bauen
nur nötig bei Ghostty-Update → Rezept in `vendor/libghostty/README.md`.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
module CGhostty {
header "ghostty.h"
export *
}
+2
View File
@@ -0,0 +1,2 @@
// Header-only C-Target für die libghostty-C-API (CGhostty-Modul).
// Bewusst leer; die Bibliothek selbst kommt als vendor/libghostty/lib/libghostty.a.
+295
View File
@@ -0,0 +1,295 @@
import AppKit
import CGhostty
// MARK: - Hilfen
/// Ruft `body` mit einem C-String auf (oder nil, wenn der String nil ist).
/// Der Zeiger ist nur innerhalb von `body` gültig.
private func withOptionalCString<R>(_ s: String?, _ body: (UnsafePointer<CChar>?) -> R) -> R {
if let s = s { return s.withCString { body($0) } }
return body(nil)
}
// MARK: - libghostty-App
/// Kapselt die globale libghostty-App: einmalige Initialisierung, geladene
/// Konfiguration, Runtime-Callbacks und der Event-Tick. Eine Instanz pro Prozess.
final class GhosttyApp {
private(set) var app: ghostty_app_t!
private(set) var config: ghostty_config_t!
private static var didInit = false
init?() {
if !GhosttyApp.didInit {
_ = ghostty_init(UInt(CommandLine.argc), CommandLine.unsafeArgv)
GhosttyApp.didInit = true
}
guard let cfg = ghostty_config_new() else { return nil }
ghostty_config_load_default_files(cfg)
ghostty_config_load_recursive_files(cfg)
ghostty_config_finalize(cfg)
self.config = cfg
var runtime = ghostty_runtime_config_s(
userdata: Unmanaged.passUnretained(self).toOpaque(),
supports_selection_clipboard: false,
wakeup_cb: { userdata in
guard let userdata = userdata else { return }
let me = Unmanaged<GhosttyApp>.fromOpaque(userdata).takeUnretainedValue()
// 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
},
read_clipboard_cb: { _, _, _ in
return false
},
confirm_read_clipboard_cb: { _, _, _, _ in },
write_clipboard_cb: { _, _, _, _, _ in },
close_surface_cb: { _, _ in
DispatchQueue.main.async { NSApp.terminate(nil) }
}
)
guard let app = ghostty_app_new(&runtime, cfg) else {
ghostty_config_free(cfg)
return nil
}
self.app = app
ghostty_app_set_focus(app, NSApp.isActive)
}
func tick() {
guard let app = app else { return }
ghostty_app_tick(app)
}
func setFocus(_ focused: Bool) {
guard let app = app else { return }
ghostty_app_set_focus(app, focused)
}
deinit {
if let app = app { ghostty_app_free(app) }
if let config = config { ghostty_config_free(config) }
}
}
// MARK: - Surface-View
/// NSView, in die libghostty per Metal rendert. libghostty erzeugt seinen
/// CAMetalLayer selbst aus dem übergebenen NSView-Zeiger; wir leiten Größe,
/// Skalierung, Fokus und Eingaben weiter.
final class GhosttySurfaceView: NSView {
private let ghostty: GhosttyApp
private let appConfig: AppConfig
private var surface: ghostty_surface_t!
init(ghostty: GhosttyApp, appConfig: AppConfig, frame: NSRect) {
self.ghostty = ghostty
self.appConfig = appConfig
super.init(frame: frame)
wantsLayer = true
layerContentsRedrawPolicy = .duringViewResize
createSurface()
}
required init?(coder: NSCoder) { fatalError("init(coder:) not supported") }
override var acceptsFirstResponder: Bool { true }
override var isFlipped: Bool { true }
// MARK: Surface-Lifecycle
private func createSurface() {
var c = ghostty_surface_config_new()
c.platform_tag = GHOSTTY_PLATFORM_MACOS
c.platform = ghostty_platform_u(
macos: ghostty_platform_macos_s(
nsview: Unmanaged.passUnretained(self).toOpaque()))
c.userdata = Unmanaged.passUnretained(self).toOpaque()
c.scale_factor = Double(window?.backingScaleFactor
?? NSScreen.main?.backingScaleFactor ?? 2.0)
c.font_size = Float(appConfig.fontSize)
c.context = GHOSTTY_SURFACE_CONTEXT_WINDOW
withOptionalCString(surfaceCommand()) { cmd in
withOptionalCString(appConfig.workingDirectory) { wd in
c.command = cmd
c.working_directory = wd
self.surface = ghostty_surface_new(self.ghostty.app, &c)
}
}
}
/// 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.
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"
return "\(shell) -lc '\(cmd); exec \(shell) -l'"
}
// MARK: Größe / Skalierung
override func setFrameSize(_ newSize: NSSize) {
super.setFrameSize(newSize)
updateSurfaceSize()
}
override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
updateSurfaceSize()
if let window = window {
ghostty_surface_set_content_scale(surface,
Double(window.backingScaleFactor),
Double(window.backingScaleFactor))
}
}
override func viewDidChangeBackingProperties() {
super.viewDidChangeBackingProperties()
guard let surface = surface, let window = window else { return }
ghostty_surface_set_content_scale(surface,
Double(window.backingScaleFactor),
Double(window.backingScaleFactor))
updateSurfaceSize()
}
private func updateSurfaceSize() {
guard let surface = surface else { return }
let backing = convertToBacking(bounds).size
let w = UInt32(max(0, backing.width))
let h = UInt32(max(0, backing.height))
ghostty_surface_set_size(surface, w, h)
}
// MARK: Fokus
override func becomeFirstResponder() -> Bool {
let ok = super.becomeFirstResponder()
if let surface = surface { ghostty_surface_set_focus(surface, true) }
return ok
}
override func resignFirstResponder() -> Bool {
let ok = super.resignFirstResponder()
if let surface = surface { ghostty_surface_set_focus(surface, false) }
return ok
}
// MARK: Tastatur
override func keyDown(with event: NSEvent) {
// Shift+Return / Shift+Enter (Numpad): Zeilenvorschub statt Submit,
// damit CLIs wie Claude Code mehrzeilige Eingaben erlauben.
let isReturn = (event.keyCode == 36 || event.keyCode == 76)
let mods = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
if isReturn && mods == .shift {
"\n".withCString { ghostty_surface_text(surface, $0, UInt(strlen($0))) }
return
}
sendKey(event, action: event.isARepeat ? GHOSTTY_ACTION_REPEAT : GHOSTTY_ACTION_PRESS)
}
override func keyUp(with event: NSEvent) {
sendKey(event, action: GHOSTTY_ACTION_RELEASE)
}
override func flagsChanged(with event: NSEvent) {
var k = ghostty_input_key_s()
k.action = GHOSTTY_ACTION_PRESS
k.mods = Self.translateMods(event.modifierFlags)
k.keycode = UInt32(event.keyCode)
_ = ghostty_surface_key(surface, k)
}
private func sendKey(_ event: NSEvent, action: ghostty_input_action_e) {
guard let surface = surface else { return }
var k = ghostty_input_key_s()
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 ?? ""
text.withCString { ptr in
k.text = ptr
_ = ghostty_surface_key(surface, k)
}
}
private static func translateMods(_ flags: NSEvent.ModifierFlags) -> ghostty_input_mods_e {
var m: UInt32 = GHOSTTY_MODS_NONE.rawValue
if flags.contains(.shift) { m |= GHOSTTY_MODS_SHIFT.rawValue }
if flags.contains(.control) { m |= GHOSTTY_MODS_CTRL.rawValue }
if flags.contains(.option) { m |= GHOSTTY_MODS_ALT.rawValue }
if flags.contains(.command) { m |= GHOSTTY_MODS_SUPER.rawValue }
if flags.contains(.capsLock) { m |= GHOSTTY_MODS_CAPS.rawValue }
return ghostty_input_mods_e(rawValue: m)
}
// MARK: Maus
private func mousePos(_ event: NSEvent) -> (Double, Double) {
let p = convert(event.locationInWindow, from: nil)
return (Double(p.x), Double(p.y))
}
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) }
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 }
let state: ghostty_input_mouse_state_e = (a == .press) ? GHOSTTY_MOUSE_PRESS : GHOSTTY_MOUSE_RELEASE
_ = ghostty_surface_mouse_button(surface, state, button, Self.translateMods(event.modifierFlags))
}
override func mouseMoved(with event: NSEvent) { mouseMove(event) }
override func mouseDragged(with event: NSEvent) { mouseMove(event) }
override func rightMouseDragged(with event: NSEvent) { mouseMove(event) }
override func otherMouseDragged(with event: NSEvent) { mouseMove(event) }
private func mouseMove(_ event: NSEvent) {
guard let surface = surface else { return }
let (x, y) = mousePos(event)
ghostty_surface_mouse_pos(surface, x, y, Self.translateMods(event.modifierFlags))
}
override func scrollWheel(with event: NSEvent) {
guard let surface = surface else { return }
// scroll_mods: einfacher Modus, 0 = keine Präzision/Momentum-Flags.
ghostty_surface_mouse_scroll(surface,
Double(event.scrollingDeltaX),
Double(event.scrollingDeltaY),
0)
}
override func updateTrackingAreas() {
super.updateTrackingAreas()
trackingAreas.forEach { removeTrackingArea($0) }
let area = NSTrackingArea(
rect: bounds,
options: [.activeInKeyWindow, .inVisibleRect, .mouseMoved, .mouseEnteredAndExited],
owner: self,
userInfo: nil)
addTrackingArea(area)
}
deinit {
if let surface = surface { ghostty_surface_free(surface) }
}
}
+39 -10
View File
@@ -20,6 +20,7 @@ struct AppConfig {
let fontSize: CGFloat
let fontName: String
let windowTitle: String
let engine: String // "ghostty" (Default) oder "swiftterm"
static func load() -> AppConfig {
let info = Bundle.main.infoDictionary ?? [:]
@@ -27,6 +28,9 @@ struct AppConfig {
let cmd = ((info["MTCommand"] as? String) ?? "")
.trimmingCharacters(in: .whitespacesAndNewlines)
let engine = ((info["MTEngine"] as? String) ?? "ghostty")
.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
var workdir: String? = nil
if let raw = info["MTWorkingDirectory"] as? String,
!raw.trimmingCharacters(in: .whitespaces).isEmpty {
@@ -46,7 +50,8 @@ struct AppConfig {
workingDirectory: workdir,
fontSize: CGFloat(size),
fontName: fontName.isEmpty ? "Menlo" : fontName,
windowTitle: title)
windowTitle: title,
engine: engine == "swiftterm" ? "swiftterm" : "ghostty")
}
}
@@ -55,30 +60,54 @@ final class AppDelegate: NSObject, NSApplicationDelegate, LocalProcessTerminalVi
private var window: NSWindow!
private var terminalView: LocalProcessTerminalView!
private var keyMonitor: Any?
private var ghosttyApp: GhosttyApp?
private var ghosttyView: GhosttySurfaceView?
private let config = AppConfig.load()
func applicationDidFinishLaunching(_ notification: Notification) {
let frame = NSRect(x: 0, y: 0, width: 900, height: 560)
terminalView = LocalProcessTerminalView(frame: frame)
terminalView.processDelegate = self
terminalView.font = resolvedFont()
installKeyMonitor()
window = NSWindow(
contentRect: frame,
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false)
window.title = config.windowTitle
window.contentView = terminalView
window.setFrameAutosaveName("MiniTermMain")
window.center()
window.makeKeyAndOrderFront(nil)
window.makeFirstResponder(terminalView)
// Engine wählen. libghostty (GPU-Renderer) ist Default; SwiftTerm bleibt
// als Fallback (auch wenn die libghostty-Init scheitert).
if config.engine == "ghostty", let contentView = setupGhostty(frame: frame) {
window.contentView = contentView
window.makeKeyAndOrderFront(nil)
window.makeFirstResponder(contentView)
} else {
setupSwiftTerm(frame: frame)
}
setupMenu()
}
private func setupGhostty(frame: NSRect) -> NSView? {
guard let gapp = GhosttyApp() else {
NSLog("libghostty-Init fehlgeschlagen Fallback auf SwiftTerm")
return nil
}
ghosttyApp = gapp
let view = GhosttySurfaceView(ghostty: gapp, appConfig: config, frame: frame)
ghosttyView = view
return view
}
private func setupSwiftTerm(frame: NSRect) {
terminalView = LocalProcessTerminalView(frame: frame)
terminalView.processDelegate = self
terminalView.font = resolvedFont()
installKeyMonitor()
window.contentView = terminalView
window.makeKeyAndOrderFront(nil)
window.makeFirstResponder(terminalView)
launchProcess()
}
+40
View File
@@ -0,0 +1,40 @@
# libghostty (volle macOS-Variante, mit Metal-Renderer)
Einbettbare Ghostty-Kernbibliothek inkl. Surface-/App-API und Metal-Renderer —
dieselbe, die Ghostty.app auf macOS nutzt. Zum Linken in die Swift-Hülle
(`Sources/MiniTerm`) als Ersatz/Ergänzung zu SwiftTerm.
## Inhalt
- `lib/libghostty.a` — statische Lib, **arm64**, ReleaseFast (~135 MB)
- `include/` — C-Header (`ghostty.h` = volle API; `ghostty/vt*` = VT-only-API)
- `patches/ghostty-v1.3.1-libghostty-clt-build.patch` — Build-Patches (s. u.)
## Zentrale API (in `include/ghostty.h`)
`ghostty_init`, `ghostty_app_new`, `ghostty_surface_new`, `ghostty_surface_draw`,
`ghostty_surface_key`, `ghostty_surface_free`, …
## Quelle / Build
- Ghostty **v1.3.1**, Zig **0.15.2**.
- Gebaut in einer **macOS-15-VM** (tart, cirruslabs `macos-sequoia-xcode`), weil
Zig 0.15.2 auf dem Host (macOS 26.5) kein natives Binary linkt (libSystem-
Inkompatibilität mit dem 26er-SDK; reine Auto-Erkennung von `26.x` scheitert).
- In der VM aktiv: **Command Line Tools** (macOS-15.5-SDK) → Zig linkt sauber.
Der Metal-Shader-Compiler (`metal`) wird nur für den Shader-Schritt per
`DEVELOPER_DIR` aus dem installierten Xcode geholt (s. Patch `MetallibStep.zig`).
Build-Aufruf in der VM:
sudo xcode-select -s /Library/Developer/CommandLineTools
cd ghostty && git apply ghostty-v1.3.1-libghostty-clt-build.patch
zig build -Doptimize=ReleaseFast -Demit-xcframework=false
# Ergebnis: zig-out/lib/libghostty.a, zig-out/include/
## Patches (warum)
- `build.zig`: Auf Darwin den macOS-`libghostty.a`-Install freischalten
(Original klammert ihn per `if (!isDarwin())` aus, baut sonst nur ein
xcframework). Shared-`.so`-Install entfernt (Dynamic-Link schlug fehl,
wird nicht gebraucht).
- `GhosttyXCFramework.zig`: Nur die native macOS-Slice konstruieren —
iOS/iOS-Simulator/Universal raus (CLT hat kein iOS-SDK, brauchen wir nicht).
- `MetallibStep.zig`: `metal`/`metallib` über `DEVELOPER_DIR=<Xcode>` aufrufen,
damit der Shader-Compiler trotz aktivem CLT gefunden wird.
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
/**
* @file vt.h
*
* libghostty-vt - Virtual terminal emulator library
*
* This library provides functionality for parsing and handling terminal
* escape sequences as well as maintaining terminal state such as styles,
* cursor position, screen, scrollback, and more.
*
* WARNING: This is an incomplete, work-in-progress API. It is not yet
* stable and is definitely going to change.
*/
/**
* @mainpage libghostty-vt - Virtual Terminal Emulator Library
*
* libghostty-vt is a C library which implements a modern terminal emulator,
* extracted from the [Ghostty](https://ghostty.org) terminal emulator.
*
* libghostty-vt contains the logic for handling the core parts of a terminal
* emulator: parsing terminal escape sequences, maintaining terminal state,
* encoding input events, etc. It can handle scrollback, line wrapping,
* reflow on resize, and more.
*
* @warning This library is currently in development and the API is not yet stable.
* Breaking changes are expected in future versions. Use with caution in production code.
*
* @section groups_sec API Reference
*
* The API is organized into the following groups:
* - @ref key "Key Encoding" - Encode key events into terminal sequences
* - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences
* - @ref sgr "SGR Parser" - Parse SGR (Select Graphic Rendition) sequences
* - @ref paste "Paste Utilities" - Validate paste data safety
* - @ref allocator "Memory Management" - Memory management and custom allocators
* - @ref wasm "WebAssembly Utilities" - WebAssembly convenience functions
*
* @section examples_sec Examples
*
* Complete working examples:
* - @ref c-vt/src/main.c - OSC parser example
* - @ref c-vt-key-encode/src/main.c - Key encoding example
* - @ref c-vt-paste/src/main.c - Paste safety check example
* - @ref c-vt-sgr/src/main.c - SGR parser example
*
*/
/** @example c-vt/src/main.c
* This example demonstrates how to use the OSC parser to parse an OSC sequence,
* extract command information, and retrieve command-specific data like window titles.
*/
/** @example c-vt-key-encode/src/main.c
* This example demonstrates how to use the key encoder to convert key events
* into terminal escape sequences using the Kitty keyboard protocol.
*/
/** @example c-vt-paste/src/main.c
* This example demonstrates how to use the paste utilities to check if
* paste data is safe before sending it to the terminal.
*/
/** @example c-vt-sgr/src/main.c
* This example demonstrates how to use the SGR parser to parse terminal
* styling sequences and extract text attributes like colors and underline styles.
*/
#ifndef GHOSTTY_VT_H
#define GHOSTTY_VT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <ghostty/vt/result.h>
#include <ghostty/vt/allocator.h>
#include <ghostty/vt/osc.h>
#include <ghostty/vt/sgr.h>
#include <ghostty/vt/key.h>
#include <ghostty/vt/paste.h>
#include <ghostty/vt/wasm.h>
#ifdef __cplusplus
}
#endif
#endif /* GHOSTTY_VT_H */
+196
View File
@@ -0,0 +1,196 @@
/**
* @file allocator.h
*
* Memory management interface for libghostty-vt.
*/
#ifndef GHOSTTY_VT_ALLOCATOR_H
#define GHOSTTY_VT_ALLOCATOR_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/** @defgroup allocator Memory Management
*
* libghostty-vt does require memory allocation for various operations,
* but is resilient to allocation failures and will gracefully handle
* out-of-memory situations by returning error codes.
*
* The exact memory management semantics are documented in the relevant
* functions and data structures.
*
* libghostty-vt uses explicit memory allocation via an allocator
* interface provided by GhosttyAllocator. The interface is based on the
* [Zig](https://ziglang.org) allocator interface, since this has been
* shown to be a flexible and powerful interface in practice and enables
* a wide variety of allocation strategies.
*
* **For the common case, you can pass NULL as the allocator for any
* function that accepts one,** and libghostty will use a default allocator.
* The default allocator will be libc malloc/free if libc is linked.
* Otherwise, a custom allocator is used (currently Zig's SMP allocator)
* that doesn't require any external dependencies.
*
* ## Basic Usage
*
* For simple use cases, you can ignore this interface entirely by passing NULL
* as the allocator parameter to functions that accept one. This will use the
* default allocator (typically libc malloc/free, if libc is linked, but
* we provide our own default allocator if libc isn't linked).
*
* To use a custom allocator:
* 1. Implement the GhosttyAllocatorVtable function pointers
* 2. Create a GhosttyAllocator struct with your vtable and context
* 3. Pass the allocator to functions that accept one
*
* @{
*/
/**
* Function table for custom memory allocator operations.
*
* This vtable defines the interface for a custom memory allocator. All
* function pointers must be valid and non-NULL.
*
* @ingroup allocator
*
* If you're not going to use a custom allocator, you can ignore all of
* this. All functions that take an allocator pointer allow NULL to use a
* default allocator.
*
* The interface is based on the Zig allocator interface. I'll say up front
* that it is easy to look at this interface and think "wow, this is really
* overcomplicated". The reason for this complexity is well thought out by
* the Zig folks, and it enables a diverse set of allocation strategies
* as shown by the Zig ecosystem. As a consolation, please note that many
* of the arguments are only needed for advanced use cases and can be
* safely ignored in simple implementations. For example, if you look at
* the Zig implementation of the libc allocator in `lib/std/heap.zig`
* (search for CAllocator), you'll see it is very simple.
*
* We chose to align with the Zig allocator interface because:
*
* 1. It is a proven interface that serves a wide variety of use cases
* in the real world via the Zig ecosystem. It's shown to work.
*
* 2. Our core implementation itself is Zig, and this lets us very
* cheaply and easily convert between C and Zig allocators.
*
* NOTE(mitchellh): In the future, we can have default implementations of
* resize/remap and allow those to be null.
*/
typedef struct {
/**
* Return a pointer to `len` bytes with specified `alignment`, or return
* `NULL` indicating the allocation failed.
*
* @param ctx The allocator context
* @param len Number of bytes to allocate
* @param alignment Required alignment for the allocation. Guaranteed to
* be a power of two between 1 and 16 inclusive.
* @param ret_addr First return address of the allocation call stack (0 if not provided)
* @return Pointer to allocated memory, or NULL if allocation failed
*/
void* (*alloc)(void *ctx, size_t len, uint8_t alignment, uintptr_t ret_addr);
/**
* Attempt to expand or shrink memory in place.
*
* `memory_len` must equal the length requested from the most recent
* successful call to `alloc`, `resize`, or `remap`. `alignment` must
* equal the same value that was passed as the `alignment` parameter to
* the original `alloc` call.
*
* `new_len` must be greater than zero.
*
* @param ctx The allocator context
* @param memory Pointer to the memory block to resize
* @param memory_len Current size of the memory block
* @param alignment Alignment (must match original allocation)
* @param new_len New requested size
* @param ret_addr First return address of the allocation call stack (0 if not provided)
* @return true if resize was successful in-place, false if relocation would be required
*/
bool (*resize)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, size_t new_len, uintptr_t ret_addr);
/**
* Attempt to expand or shrink memory, allowing relocation.
*
* `memory_len` must equal the length requested from the most recent
* successful call to `alloc`, `resize`, or `remap`. `alignment` must
* equal the same value that was passed as the `alignment` parameter to
* the original `alloc` call.
*
* A non-`NULL` return value indicates the resize was successful. The
* allocation may have same address, or may have been relocated. In either
* case, the allocation now has size of `new_len`. A `NULL` return value
* indicates that the resize would be equivalent to allocating new memory,
* copying the bytes from the old memory, and then freeing the old memory.
* In such case, it is more efficient for the caller to perform the copy.
*
* `new_len` must be greater than zero.
*
* @param ctx The allocator context
* @param memory Pointer to the memory block to remap
* @param memory_len Current size of the memory block
* @param alignment Alignment (must match original allocation)
* @param new_len New requested size
* @param ret_addr First return address of the allocation call stack (0 if not provided)
* @return Pointer to resized memory (may be relocated), or NULL if manual copy is needed
*/
void* (*remap)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, size_t new_len, uintptr_t ret_addr);
/**
* Free and invalidate a region of memory.
*
* `memory_len` must equal the length requested from the most recent
* successful call to `alloc`, `resize`, or `remap`. `alignment` must
* equal the same value that was passed as the `alignment` parameter to
* the original `alloc` call.
*
* @param ctx The allocator context
* @param memory Pointer to the memory block to free
* @param memory_len Size of the memory block
* @param alignment Alignment (must match original allocation)
* @param ret_addr First return address of the allocation call stack (0 if not provided)
*/
void (*free)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, uintptr_t ret_addr);
} GhosttyAllocatorVtable;
/**
* Custom memory allocator.
*
* For functions that take an allocator pointer, a NULL pointer indicates
* that the default allocator should be used. The default allocator will
* be libc malloc/free if we're linking to libc. If libc isn't linked,
* a custom allocator is used (currently Zig's SMP allocator).
*
* @ingroup allocator
*
* Usage example:
* @code
* GhosttyAllocator allocator = {
* .vtable = &my_allocator_vtable,
* .ctx = my_allocator_state
* };
* @endcode
*/
typedef struct GhosttyAllocator {
/**
* Opaque context pointer passed to all vtable functions.
* This allows the allocator implementation to maintain state
* or reference external resources needed for memory management.
*/
void *ctx;
/**
* Pointer to the allocator's vtable containing function pointers
* for memory operations (alloc, resize, remap, free).
*/
const GhosttyAllocatorVtable *vtable;
} GhosttyAllocator;
/** @} */
#endif /* GHOSTTY_VT_ALLOCATOR_H */
+96
View File
@@ -0,0 +1,96 @@
/**
* @file color.h
*
* Color types and utilities.
*/
#ifndef GHOSTTY_VT_COLOR_H
#define GHOSTTY_VT_COLOR_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* RGB color value.
*
* @ingroup sgr
*/
typedef struct {
uint8_t r; /**< Red component (0-255) */
uint8_t g; /**< Green component (0-255) */
uint8_t b; /**< Blue component (0-255) */
} GhosttyColorRgb;
/**
* Palette color index (0-255).
*
* @ingroup sgr
*/
typedef uint8_t GhosttyColorPaletteIndex;
/** @addtogroup sgr
* @{
*/
/** Black color (0) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BLACK 0
/** Red color (1) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_RED 1
/** Green color (2) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_GREEN 2
/** Yellow color (3) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_YELLOW 3
/** Blue color (4) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BLUE 4
/** Magenta color (5) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_MAGENTA 5
/** Cyan color (6) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_CYAN 6
/** White color (7) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_WHITE 7
/** Bright black color (8) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BRIGHT_BLACK 8
/** Bright red color (9) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BRIGHT_RED 9
/** Bright green color (10) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BRIGHT_GREEN 10
/** Bright yellow color (11) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BRIGHT_YELLOW 11
/** Bright blue color (12) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BRIGHT_BLUE 12
/** Bright magenta color (13) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BRIGHT_MAGENTA 13
/** Bright cyan color (14) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BRIGHT_CYAN 14
/** Bright white color (15) @ingroup sgr */
#define GHOSTTY_COLOR_NAMED_BRIGHT_WHITE 15
/** @} */
/**
* Get the RGB color components.
*
* This function extracts the individual red, green, and blue components
* from a GhosttyColorRgb value. Primarily useful in WebAssembly environments
* where accessing struct fields directly is difficult.
*
* @param color The RGB color value
* @param r Pointer to store the red component (0-255)
* @param g Pointer to store the green component (0-255)
* @param b Pointer to store the blue component (0-255)
*
* @ingroup sgr
*/
void ghostty_color_rgb_get(GhosttyColorRgb color,
uint8_t* r,
uint8_t* g,
uint8_t* b);
#ifdef __cplusplus
}
#endif
#endif /* GHOSTTY_VT_COLOR_H */
+80
View File
@@ -0,0 +1,80 @@
/**
* @file key.h
*
* Key encoding module - encode key events into terminal escape sequences.
*/
#ifndef GHOSTTY_VT_KEY_H
#define GHOSTTY_VT_KEY_H
/** @defgroup key Key Encoding
*
* Utilities for encoding key events into terminal escape sequences,
* supporting both legacy encoding as well as Kitty Keyboard Protocol.
*
* ## Basic Usage
*
* 1. Create an encoder instance with ghostty_key_encoder_new()
* 2. Configure encoder options with ghostty_key_encoder_setopt().
* 3. For each key event:
* - Create a key event with ghostty_key_event_new()
* - Set event properties (action, key, modifiers, etc.)
* - Encode with ghostty_key_encoder_encode()
* - Free the event with ghostty_key_event_free()
* - Note: You can also reuse the same key event multiple times by
* changing its properties.
* 4. Free the encoder with ghostty_key_encoder_free() when done
*
* ## Example
*
* @code{.c}
* #include <assert.h>
* #include <stdio.h>
* #include <ghostty/vt.h>
*
* int main() {
* // Create encoder
* GhosttyKeyEncoder encoder;
* GhosttyResult result = ghostty_key_encoder_new(NULL, &encoder);
* assert(result == GHOSTTY_SUCCESS);
*
* // Enable Kitty keyboard protocol with all features
* ghostty_key_encoder_setopt(encoder, GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS,
* &(uint8_t){GHOSTTY_KITTY_KEY_ALL});
*
* // Create and configure key event for Ctrl+C press
* GhosttyKeyEvent event;
* result = ghostty_key_event_new(NULL, &event);
* assert(result == GHOSTTY_SUCCESS);
* ghostty_key_event_set_action(event, GHOSTTY_KEY_ACTION_PRESS);
* ghostty_key_event_set_key(event, GHOSTTY_KEY_C);
* ghostty_key_event_set_mods(event, GHOSTTY_MODS_CTRL);
*
* // Encode the key event
* char buf[128];
* size_t written = 0;
* result = ghostty_key_encoder_encode(encoder, event, buf, sizeof(buf), &written);
* assert(result == GHOSTTY_SUCCESS);
*
* // Use the encoded sequence (e.g., write to terminal)
* fwrite(buf, 1, written, stdout);
*
* // Cleanup
* ghostty_key_event_free(event);
* ghostty_key_encoder_free(encoder);
* return 0;
* }
* @endcode
*
* For a complete working example, see example/c-vt-key-encode in the
* repository.
*
* @{
*/
#include <ghostty/vt/key/event.h>
#include <ghostty/vt/key/encoder.h>
/** @} */
#endif /* GHOSTTY_VT_KEY_H */
+221
View File
@@ -0,0 +1,221 @@
/**
* @file encoder.h
*
* Key event encoding to terminal escape sequences.
*/
#ifndef GHOSTTY_VT_KEY_ENCODER_H
#define GHOSTTY_VT_KEY_ENCODER_H
#include <stddef.h>
#include <stdint.h>
#include <ghostty/vt/result.h>
#include <ghostty/vt/allocator.h>
#include <ghostty/vt/key/event.h>
/**
* Opaque handle to a key encoder instance.
*
* This handle represents a key encoder that converts key events into terminal
* escape sequences.
*
* @ingroup key
*/
typedef struct GhosttyKeyEncoder *GhosttyKeyEncoder;
/**
* Kitty keyboard protocol flags.
*
* Bitflags representing the various modes of the Kitty keyboard protocol.
* These can be combined using bitwise OR operations. Valid values all
* start with `GHOSTTY_KITTY_KEY_`.
*
* @ingroup key
*/
typedef uint8_t GhosttyKittyKeyFlags;
/** Kitty keyboard protocol disabled (all flags off) */
#define GHOSTTY_KITTY_KEY_DISABLED 0
/** Disambiguate escape codes */
#define GHOSTTY_KITTY_KEY_DISAMBIGUATE (1 << 0)
/** Report key press and release events */
#define GHOSTTY_KITTY_KEY_REPORT_EVENTS (1 << 1)
/** Report alternate key codes */
#define GHOSTTY_KITTY_KEY_REPORT_ALTERNATES (1 << 2)
/** Report all key events including those normally handled by the terminal */
#define GHOSTTY_KITTY_KEY_REPORT_ALL (1 << 3)
/** Report associated text with key events */
#define GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED (1 << 4)
/** All Kitty keyboard protocol flags enabled */
#define GHOSTTY_KITTY_KEY_ALL (GHOSTTY_KITTY_KEY_DISAMBIGUATE | GHOSTTY_KITTY_KEY_REPORT_EVENTS | GHOSTTY_KITTY_KEY_REPORT_ALTERNATES | GHOSTTY_KITTY_KEY_REPORT_ALL | GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED)
/**
* macOS option key behavior.
*
* Determines whether the "option" key on macOS is treated as "alt" or not.
* See the Ghostty `macos-option-as-alt` configuration option for more details.
*
* @ingroup key
*/
typedef enum {
/** Option key is not treated as alt */
GHOSTTY_OPTION_AS_ALT_FALSE = 0,
/** Option key is treated as alt */
GHOSTTY_OPTION_AS_ALT_TRUE = 1,
/** Only left option key is treated as alt */
GHOSTTY_OPTION_AS_ALT_LEFT = 2,
/** Only right option key is treated as alt */
GHOSTTY_OPTION_AS_ALT_RIGHT = 3,
} GhosttyOptionAsAlt;
/**
* Key encoder option identifiers.
*
* These values are used with ghostty_key_encoder_setopt() to configure
* the behavior of the key encoder.
*
* @ingroup key
*/
typedef enum {
/** Terminal DEC mode 1: cursor key application mode (value: bool) */
GHOSTTY_KEY_ENCODER_OPT_CURSOR_KEY_APPLICATION = 0,
/** Terminal DEC mode 66: keypad key application mode (value: bool) */
GHOSTTY_KEY_ENCODER_OPT_KEYPAD_KEY_APPLICATION = 1,
/** Terminal DEC mode 1035: ignore keypad with numlock (value: bool) */
GHOSTTY_KEY_ENCODER_OPT_IGNORE_KEYPAD_WITH_NUMLOCK = 2,
/** Terminal DEC mode 1036: alt sends escape prefix (value: bool) */
GHOSTTY_KEY_ENCODER_OPT_ALT_ESC_PREFIX = 3,
/** xterm modifyOtherKeys mode 2 (value: bool) */
GHOSTTY_KEY_ENCODER_OPT_MODIFY_OTHER_KEYS_STATE_2 = 4,
/** Kitty keyboard protocol flags (value: GhosttyKittyKeyFlags bitmask) */
GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS = 5,
/** macOS option-as-alt setting (value: GhosttyOptionAsAlt) */
GHOSTTY_KEY_ENCODER_OPT_MACOS_OPTION_AS_ALT = 6,
} GhosttyKeyEncoderOption;
/**
* Create a new key encoder instance.
*
* Creates a new key encoder with default options. The encoder can be configured
* using ghostty_key_encoder_setopt() and must be freed using
* ghostty_key_encoder_free() when no longer needed.
*
* @param allocator Pointer to the allocator to use for memory management, or NULL to use the default allocator
* @param encoder Pointer to store the created encoder handle
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup key
*/
GhosttyResult ghostty_key_encoder_new(const GhosttyAllocator *allocator, GhosttyKeyEncoder *encoder);
/**
* Free a key encoder instance.
*
* Releases all resources associated with the key encoder. After this call,
* the encoder handle becomes invalid and must not be used.
*
* @param encoder The encoder handle to free (may be NULL)
*
* @ingroup key
*/
void ghostty_key_encoder_free(GhosttyKeyEncoder encoder);
/**
* Set an option on the key encoder.
*
* Configures the behavior of the key encoder. Options control various aspects
* of encoding such as terminal modes (cursor key application mode, keypad mode),
* protocol selection (Kitty keyboard protocol flags), and platform-specific
* behaviors (macOS option-as-alt).
*
* A null pointer value does nothing. It does not reset the value to the
* default. The setopt call will do nothing.
*
* @param encoder The encoder handle, must not be NULL
* @param option The option to set
* @param value Pointer to the value to set (type depends on the option)
*
* @ingroup key
*/
void ghostty_key_encoder_setopt(GhosttyKeyEncoder encoder, GhosttyKeyEncoderOption option, const void *value);
/**
* Encode a key event into a terminal escape sequence.
*
* Converts a key event into the appropriate terminal escape sequence based on
* the encoder's current options. The sequence is written to the provided buffer.
*
* Not all key events produce output. For example, unmodified modifier keys
* typically don't generate escape sequences. Check the out_len parameter to
* determine if any data was written.
*
* If the output buffer is too small, this function returns GHOSTTY_OUT_OF_MEMORY
* and out_len will contain the required buffer size. The caller can then
* allocate a larger buffer and call the function again.
*
* @param encoder The encoder handle, must not be NULL
* @param event The key event to encode, must not be NULL
* @param out_buf Buffer to write the encoded sequence to
* @param out_buf_size Size of the output buffer in bytes
* @param out_len Pointer to store the number of bytes written (may be NULL)
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY if buffer too small, or other error code
*
* ## Example: Calculate required buffer size
*
* @code{.c}
* // Query the required size with a NULL buffer (always returns OUT_OF_MEMORY)
* size_t required = 0;
* GhosttyResult result = ghostty_key_encoder_encode(encoder, event, NULL, 0, &required);
* assert(result == GHOSTTY_OUT_OF_MEMORY);
*
* // Allocate buffer of required size
* char *buf = malloc(required);
*
* // Encode with properly sized buffer
* size_t written = 0;
* result = ghostty_key_encoder_encode(encoder, event, buf, required, &written);
* assert(result == GHOSTTY_SUCCESS);
*
* // Use the encoded sequence...
*
* free(buf);
* @endcode
*
* ## Example: Direct encoding with static buffer
*
* @code{.c}
* // Most escape sequences are short, so a static buffer often suffices
* char buf[128];
* size_t written = 0;
* GhosttyResult result = ghostty_key_encoder_encode(encoder, event, buf, sizeof(buf), &written);
*
* if (result == GHOSTTY_SUCCESS) {
* // Write the encoded sequence to the terminal
* write(pty_fd, buf, written);
* } else if (result == GHOSTTY_OUT_OF_MEMORY) {
* // Buffer too small, written contains required size
* char *dynamic_buf = malloc(written);
* result = ghostty_key_encoder_encode(encoder, event, dynamic_buf, written, &written);
* assert(result == GHOSTTY_SUCCESS);
* write(pty_fd, dynamic_buf, written);
* free(dynamic_buf);
* }
* @endcode
*
* @ingroup key
*/
GhosttyResult ghostty_key_encoder_encode(GhosttyKeyEncoder encoder, GhosttyKeyEvent event, char *out_buf, size_t out_buf_size, size_t *out_len);
#endif /* GHOSTTY_VT_KEY_ENCODER_H */
+474
View File
@@ -0,0 +1,474 @@
/**
* @file event.h
*
* Key event representation and manipulation.
*/
#ifndef GHOSTTY_VT_KEY_EVENT_H
#define GHOSTTY_VT_KEY_EVENT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <ghostty/vt/result.h>
#include <ghostty/vt/allocator.h>
/**
* Opaque handle to a key event.
*
* This handle represents a keyboard input event containing information about
* the physical key pressed, modifiers, and generated text.
*
* @ingroup key
*/
typedef struct GhosttyKeyEvent *GhosttyKeyEvent;
/**
* Keyboard input event types.
*
* @ingroup key
*/
typedef enum {
/** Key was released */
GHOSTTY_KEY_ACTION_RELEASE = 0,
/** Key was pressed */
GHOSTTY_KEY_ACTION_PRESS = 1,
/** Key is being repeated (held down) */
GHOSTTY_KEY_ACTION_REPEAT = 2,
} GhosttyKeyAction;
/**
* Keyboard modifier keys bitmask.
*
* A bitmask representing all keyboard modifiers. This tracks which modifier keys
* are pressed and, where supported by the platform, which side (left or right)
* of each modifier is active.
*
* Use the GHOSTTY_MODS_* constants to test and set individual modifiers.
*
* Modifier side bits are only meaningful when the corresponding modifier bit is set.
* Not all platforms support distinguishing between left and right modifier
* keys and Ghostty is built to expect that some platforms may not provide this
* information.
*
* @ingroup key
*/
typedef uint16_t GhosttyMods;
/** Shift key is pressed */
#define GHOSTTY_MODS_SHIFT (1 << 0)
/** Control key is pressed */
#define GHOSTTY_MODS_CTRL (1 << 1)
/** Alt/Option key is pressed */
#define GHOSTTY_MODS_ALT (1 << 2)
/** Super/Command/Windows key is pressed */
#define GHOSTTY_MODS_SUPER (1 << 3)
/** Caps Lock is active */
#define GHOSTTY_MODS_CAPS_LOCK (1 << 4)
/** Num Lock is active */
#define GHOSTTY_MODS_NUM_LOCK (1 << 5)
/**
* Right shift is pressed (0 = left, 1 = right).
* Only meaningful when GHOSTTY_MODS_SHIFT is set.
*/
#define GHOSTTY_MODS_SHIFT_SIDE (1 << 6)
/**
* Right ctrl is pressed (0 = left, 1 = right).
* Only meaningful when GHOSTTY_MODS_CTRL is set.
*/
#define GHOSTTY_MODS_CTRL_SIDE (1 << 7)
/**
* Right alt is pressed (0 = left, 1 = right).
* Only meaningful when GHOSTTY_MODS_ALT is set.
*/
#define GHOSTTY_MODS_ALT_SIDE (1 << 8)
/**
* Right super is pressed (0 = left, 1 = right).
* Only meaningful when GHOSTTY_MODS_SUPER is set.
*/
#define GHOSTTY_MODS_SUPER_SIDE (1 << 9)
/**
* Physical key codes.
*
* The set of key codes that Ghostty is aware of. These represent physical keys
* on the keyboard and are layout-independent. For example, the "a" key on a US
* keyboard is the same as the "ф" key on a Russian keyboard, but both will
* report the same key_a value.
*
* Layout-dependent strings are provided separately as UTF-8 text and are produced
* by the platform. These values are based on the W3C UI Events KeyboardEvent code
* standard. See: https://www.w3.org/TR/uievents-code
*
* @ingroup key
*/
typedef enum {
GHOSTTY_KEY_UNIDENTIFIED = 0,
// Writing System Keys (W3C § 3.1.1)
GHOSTTY_KEY_BACKQUOTE,
GHOSTTY_KEY_BACKSLASH,
GHOSTTY_KEY_BRACKET_LEFT,
GHOSTTY_KEY_BRACKET_RIGHT,
GHOSTTY_KEY_COMMA,
GHOSTTY_KEY_DIGIT_0,
GHOSTTY_KEY_DIGIT_1,
GHOSTTY_KEY_DIGIT_2,
GHOSTTY_KEY_DIGIT_3,
GHOSTTY_KEY_DIGIT_4,
GHOSTTY_KEY_DIGIT_5,
GHOSTTY_KEY_DIGIT_6,
GHOSTTY_KEY_DIGIT_7,
GHOSTTY_KEY_DIGIT_8,
GHOSTTY_KEY_DIGIT_9,
GHOSTTY_KEY_EQUAL,
GHOSTTY_KEY_INTL_BACKSLASH,
GHOSTTY_KEY_INTL_RO,
GHOSTTY_KEY_INTL_YEN,
GHOSTTY_KEY_A,
GHOSTTY_KEY_B,
GHOSTTY_KEY_C,
GHOSTTY_KEY_D,
GHOSTTY_KEY_E,
GHOSTTY_KEY_F,
GHOSTTY_KEY_G,
GHOSTTY_KEY_H,
GHOSTTY_KEY_I,
GHOSTTY_KEY_J,
GHOSTTY_KEY_K,
GHOSTTY_KEY_L,
GHOSTTY_KEY_M,
GHOSTTY_KEY_N,
GHOSTTY_KEY_O,
GHOSTTY_KEY_P,
GHOSTTY_KEY_Q,
GHOSTTY_KEY_R,
GHOSTTY_KEY_S,
GHOSTTY_KEY_T,
GHOSTTY_KEY_U,
GHOSTTY_KEY_V,
GHOSTTY_KEY_W,
GHOSTTY_KEY_X,
GHOSTTY_KEY_Y,
GHOSTTY_KEY_Z,
GHOSTTY_KEY_MINUS,
GHOSTTY_KEY_PERIOD,
GHOSTTY_KEY_QUOTE,
GHOSTTY_KEY_SEMICOLON,
GHOSTTY_KEY_SLASH,
// Functional Keys (W3C § 3.1.2)
GHOSTTY_KEY_ALT_LEFT,
GHOSTTY_KEY_ALT_RIGHT,
GHOSTTY_KEY_BACKSPACE,
GHOSTTY_KEY_CAPS_LOCK,
GHOSTTY_KEY_CONTEXT_MENU,
GHOSTTY_KEY_CONTROL_LEFT,
GHOSTTY_KEY_CONTROL_RIGHT,
GHOSTTY_KEY_ENTER,
GHOSTTY_KEY_META_LEFT,
GHOSTTY_KEY_META_RIGHT,
GHOSTTY_KEY_SHIFT_LEFT,
GHOSTTY_KEY_SHIFT_RIGHT,
GHOSTTY_KEY_SPACE,
GHOSTTY_KEY_TAB,
GHOSTTY_KEY_CONVERT,
GHOSTTY_KEY_KANA_MODE,
GHOSTTY_KEY_NON_CONVERT,
// Control Pad Section (W3C § 3.2)
GHOSTTY_KEY_DELETE,
GHOSTTY_KEY_END,
GHOSTTY_KEY_HELP,
GHOSTTY_KEY_HOME,
GHOSTTY_KEY_INSERT,
GHOSTTY_KEY_PAGE_DOWN,
GHOSTTY_KEY_PAGE_UP,
// Arrow Pad Section (W3C § 3.3)
GHOSTTY_KEY_ARROW_DOWN,
GHOSTTY_KEY_ARROW_LEFT,
GHOSTTY_KEY_ARROW_RIGHT,
GHOSTTY_KEY_ARROW_UP,
// Numpad Section (W3C § 3.4)
GHOSTTY_KEY_NUM_LOCK,
GHOSTTY_KEY_NUMPAD_0,
GHOSTTY_KEY_NUMPAD_1,
GHOSTTY_KEY_NUMPAD_2,
GHOSTTY_KEY_NUMPAD_3,
GHOSTTY_KEY_NUMPAD_4,
GHOSTTY_KEY_NUMPAD_5,
GHOSTTY_KEY_NUMPAD_6,
GHOSTTY_KEY_NUMPAD_7,
GHOSTTY_KEY_NUMPAD_8,
GHOSTTY_KEY_NUMPAD_9,
GHOSTTY_KEY_NUMPAD_ADD,
GHOSTTY_KEY_NUMPAD_BACKSPACE,
GHOSTTY_KEY_NUMPAD_CLEAR,
GHOSTTY_KEY_NUMPAD_CLEAR_ENTRY,
GHOSTTY_KEY_NUMPAD_COMMA,
GHOSTTY_KEY_NUMPAD_DECIMAL,
GHOSTTY_KEY_NUMPAD_DIVIDE,
GHOSTTY_KEY_NUMPAD_ENTER,
GHOSTTY_KEY_NUMPAD_EQUAL,
GHOSTTY_KEY_NUMPAD_MEMORY_ADD,
GHOSTTY_KEY_NUMPAD_MEMORY_CLEAR,
GHOSTTY_KEY_NUMPAD_MEMORY_RECALL,
GHOSTTY_KEY_NUMPAD_MEMORY_STORE,
GHOSTTY_KEY_NUMPAD_MEMORY_SUBTRACT,
GHOSTTY_KEY_NUMPAD_MULTIPLY,
GHOSTTY_KEY_NUMPAD_PAREN_LEFT,
GHOSTTY_KEY_NUMPAD_PAREN_RIGHT,
GHOSTTY_KEY_NUMPAD_SUBTRACT,
GHOSTTY_KEY_NUMPAD_SEPARATOR,
GHOSTTY_KEY_NUMPAD_UP,
GHOSTTY_KEY_NUMPAD_DOWN,
GHOSTTY_KEY_NUMPAD_RIGHT,
GHOSTTY_KEY_NUMPAD_LEFT,
GHOSTTY_KEY_NUMPAD_BEGIN,
GHOSTTY_KEY_NUMPAD_HOME,
GHOSTTY_KEY_NUMPAD_END,
GHOSTTY_KEY_NUMPAD_INSERT,
GHOSTTY_KEY_NUMPAD_DELETE,
GHOSTTY_KEY_NUMPAD_PAGE_UP,
GHOSTTY_KEY_NUMPAD_PAGE_DOWN,
// Function Section (W3C § 3.5)
GHOSTTY_KEY_ESCAPE,
GHOSTTY_KEY_F1,
GHOSTTY_KEY_F2,
GHOSTTY_KEY_F3,
GHOSTTY_KEY_F4,
GHOSTTY_KEY_F5,
GHOSTTY_KEY_F6,
GHOSTTY_KEY_F7,
GHOSTTY_KEY_F8,
GHOSTTY_KEY_F9,
GHOSTTY_KEY_F10,
GHOSTTY_KEY_F11,
GHOSTTY_KEY_F12,
GHOSTTY_KEY_F13,
GHOSTTY_KEY_F14,
GHOSTTY_KEY_F15,
GHOSTTY_KEY_F16,
GHOSTTY_KEY_F17,
GHOSTTY_KEY_F18,
GHOSTTY_KEY_F19,
GHOSTTY_KEY_F20,
GHOSTTY_KEY_F21,
GHOSTTY_KEY_F22,
GHOSTTY_KEY_F23,
GHOSTTY_KEY_F24,
GHOSTTY_KEY_F25,
GHOSTTY_KEY_FN,
GHOSTTY_KEY_FN_LOCK,
GHOSTTY_KEY_PRINT_SCREEN,
GHOSTTY_KEY_SCROLL_LOCK,
GHOSTTY_KEY_PAUSE,
// Media Keys (W3C § 3.6)
GHOSTTY_KEY_BROWSER_BACK,
GHOSTTY_KEY_BROWSER_FAVORITES,
GHOSTTY_KEY_BROWSER_FORWARD,
GHOSTTY_KEY_BROWSER_HOME,
GHOSTTY_KEY_BROWSER_REFRESH,
GHOSTTY_KEY_BROWSER_SEARCH,
GHOSTTY_KEY_BROWSER_STOP,
GHOSTTY_KEY_EJECT,
GHOSTTY_KEY_LAUNCH_APP_1,
GHOSTTY_KEY_LAUNCH_APP_2,
GHOSTTY_KEY_LAUNCH_MAIL,
GHOSTTY_KEY_MEDIA_PLAY_PAUSE,
GHOSTTY_KEY_MEDIA_SELECT,
GHOSTTY_KEY_MEDIA_STOP,
GHOSTTY_KEY_MEDIA_TRACK_NEXT,
GHOSTTY_KEY_MEDIA_TRACK_PREVIOUS,
GHOSTTY_KEY_POWER,
GHOSTTY_KEY_SLEEP,
GHOSTTY_KEY_AUDIO_VOLUME_DOWN,
GHOSTTY_KEY_AUDIO_VOLUME_MUTE,
GHOSTTY_KEY_AUDIO_VOLUME_UP,
GHOSTTY_KEY_WAKE_UP,
// Legacy, Non-standard, and Special Keys (W3C § 3.7)
GHOSTTY_KEY_COPY,
GHOSTTY_KEY_CUT,
GHOSTTY_KEY_PASTE,
} GhosttyKey;
/**
* Create a new key event instance.
*
* Creates a new key event with default values. The event must be freed using
* ghostty_key_event_free() when no longer needed.
*
* @param allocator Pointer to the allocator to use for memory management, or NULL to use the default allocator
* @param event Pointer to store the created key event handle
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup key
*/
GhosttyResult ghostty_key_event_new(const GhosttyAllocator *allocator, GhosttyKeyEvent *event);
/**
* Free a key event instance.
*
* Releases all resources associated with the key event. After this call,
* the event handle becomes invalid and must not be used.
*
* @param event The key event handle to free (may be NULL)
*
* @ingroup key
*/
void ghostty_key_event_free(GhosttyKeyEvent event);
/**
* Set the key action (press, release, repeat).
*
* @param event The key event handle, must not be NULL
* @param action The action to set
*
* @ingroup key
*/
void ghostty_key_event_set_action(GhosttyKeyEvent event, GhosttyKeyAction action);
/**
* Get the key action (press, release, repeat).
*
* @param event The key event handle, must not be NULL
* @return The key action
*
* @ingroup key
*/
GhosttyKeyAction ghostty_key_event_get_action(GhosttyKeyEvent event);
/**
* Set the physical key code.
*
* @param event The key event handle, must not be NULL
* @param key The physical key code to set
*
* @ingroup key
*/
void ghostty_key_event_set_key(GhosttyKeyEvent event, GhosttyKey key);
/**
* Get the physical key code.
*
* @param event The key event handle, must not be NULL
* @return The physical key code
*
* @ingroup key
*/
GhosttyKey ghostty_key_event_get_key(GhosttyKeyEvent event);
/**
* Set the modifier keys bitmask.
*
* @param event The key event handle, must not be NULL
* @param mods The modifier keys bitmask to set
*
* @ingroup key
*/
void ghostty_key_event_set_mods(GhosttyKeyEvent event, GhosttyMods mods);
/**
* Get the modifier keys bitmask.
*
* @param event The key event handle, must not be NULL
* @return The modifier keys bitmask
*
* @ingroup key
*/
GhosttyMods ghostty_key_event_get_mods(GhosttyKeyEvent event);
/**
* Set the consumed modifiers bitmask.
*
* @param event The key event handle, must not be NULL
* @param consumed_mods The consumed modifiers bitmask to set
*
* @ingroup key
*/
void ghostty_key_event_set_consumed_mods(GhosttyKeyEvent event, GhosttyMods consumed_mods);
/**
* Get the consumed modifiers bitmask.
*
* @param event The key event handle, must not be NULL
* @return The consumed modifiers bitmask
*
* @ingroup key
*/
GhosttyMods ghostty_key_event_get_consumed_mods(GhosttyKeyEvent event);
/**
* Set whether the key event is part of a composition sequence.
*
* @param event The key event handle, must not be NULL
* @param composing Whether the key event is part of a composition sequence
*
* @ingroup key
*/
void ghostty_key_event_set_composing(GhosttyKeyEvent event, bool composing);
/**
* Get whether the key event is part of a composition sequence.
*
* @param event The key event handle, must not be NULL
* @return Whether the key event is part of a composition sequence
*
* @ingroup key
*/
bool ghostty_key_event_get_composing(GhosttyKeyEvent event);
/**
* Set the UTF-8 text generated by the key event.
*
* The key event does NOT take ownership of the text pointer. The caller
* must ensure the string remains valid for the lifetime needed by the event.
*
* @param event The key event handle, must not be NULL
* @param utf8 The UTF-8 text to set (or NULL for empty)
* @param len Length of the UTF-8 text in bytes
*
* @ingroup key
*/
void ghostty_key_event_set_utf8(GhosttyKeyEvent event, const char *utf8, size_t len);
/**
* Get the UTF-8 text generated by the key event.
*
* The returned pointer is valid until the event is freed or the UTF-8 text is modified.
*
* @param event The key event handle, must not be NULL
* @param len Pointer to store the length of the UTF-8 text in bytes (may be NULL)
* @return The UTF-8 text (or NULL for empty)
*
* @ingroup key
*/
const char *ghostty_key_event_get_utf8(GhosttyKeyEvent event, size_t *len);
/**
* Set the unshifted Unicode codepoint.
*
* @param event The key event handle, must not be NULL
* @param codepoint The unshifted Unicode codepoint to set
*
* @ingroup key
*/
void ghostty_key_event_set_unshifted_codepoint(GhosttyKeyEvent event, uint32_t codepoint);
/**
* Get the unshifted Unicode codepoint.
*
* @param event The key event handle, must not be NULL
* @return The unshifted Unicode codepoint
*
* @ingroup key
*/
uint32_t ghostty_key_event_get_unshifted_codepoint(GhosttyKeyEvent event);
#endif /* GHOSTTY_VT_KEY_EVENT_H */
+233
View File
@@ -0,0 +1,233 @@
/**
* @file osc.h
*
* OSC (Operating System Command) sequence parser and command handling.
*/
#ifndef GHOSTTY_VT_OSC_H
#define GHOSTTY_VT_OSC_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <ghostty/vt/result.h>
#include <ghostty/vt/allocator.h>
/**
* Opaque handle to an OSC parser instance.
*
* This handle represents an OSC (Operating System Command) parser that can
* be used to parse the contents of OSC sequences.
*
* @ingroup osc
*/
typedef struct GhosttyOscParser *GhosttyOscParser;
/**
* Opaque handle to a single OSC command.
*
* This handle represents a parsed OSC (Operating System Command) command.
* The command can be queried for its type and associated data.
*
* @ingroup osc
*/
typedef struct GhosttyOscCommand *GhosttyOscCommand;
/** @defgroup osc OSC Parser
*
* OSC (Operating System Command) sequence parser and command handling.
*
* The parser operates in a streaming fashion, processing input byte-by-byte
* to handle OSC sequences that may arrive in fragments across multiple reads.
* This interface makes it easy to integrate into most environments and avoids
* over-allocating buffers.
*
* ## Basic Usage
*
* 1. Create a parser instance with ghostty_osc_new()
* 2. Feed bytes to the parser using ghostty_osc_next()
* 3. Finalize parsing with ghostty_osc_end() to get the command
* 4. Query command type and extract data using ghostty_osc_command_type()
* and ghostty_osc_command_data()
* 5. Free the parser with ghostty_osc_free() when done
*
* @{
*/
/**
* OSC command types.
*
* @ingroup osc
*/
typedef enum {
GHOSTTY_OSC_COMMAND_INVALID = 0,
GHOSTTY_OSC_COMMAND_CHANGE_WINDOW_TITLE = 1,
GHOSTTY_OSC_COMMAND_CHANGE_WINDOW_ICON = 2,
GHOSTTY_OSC_COMMAND_SEMANTIC_PROMPT = 3,
GHOSTTY_OSC_COMMAND_CLIPBOARD_CONTENTS = 4,
GHOSTTY_OSC_COMMAND_REPORT_PWD = 5,
GHOSTTY_OSC_COMMAND_MOUSE_SHAPE = 6,
GHOSTTY_OSC_COMMAND_COLOR_OPERATION = 7,
GHOSTTY_OSC_COMMAND_KITTY_COLOR_PROTOCOL = 8,
GHOSTTY_OSC_COMMAND_SHOW_DESKTOP_NOTIFICATION = 9,
GHOSTTY_OSC_COMMAND_HYPERLINK_START = 10,
GHOSTTY_OSC_COMMAND_HYPERLINK_END = 11,
GHOSTTY_OSC_COMMAND_CONEMU_SLEEP = 12,
GHOSTTY_OSC_COMMAND_CONEMU_SHOW_MESSAGE_BOX = 13,
GHOSTTY_OSC_COMMAND_CONEMU_CHANGE_TAB_TITLE = 14,
GHOSTTY_OSC_COMMAND_CONEMU_PROGRESS_REPORT = 15,
GHOSTTY_OSC_COMMAND_CONEMU_WAIT_INPUT = 16,
GHOSTTY_OSC_COMMAND_CONEMU_GUIMACRO = 17,
GHOSTTY_OSC_COMMAND_CONEMU_RUN_PROCESS = 18,
GHOSTTY_OSC_COMMAND_CONEMU_OUTPUT_ENVIRONMENT_VARIABLE = 19,
GHOSTTY_OSC_COMMAND_CONEMU_XTERM_EMULATION = 20,
GHOSTTY_OSC_COMMAND_CONEMU_COMMENT = 21,
GHOSTTY_OSC_COMMAND_KITTY_TEXT_SIZING = 22,
} GhosttyOscCommandType;
/**
* OSC command data types.
*
* These values specify what type of data to extract from an OSC command
* using `ghostty_osc_command_data`.
*
* @ingroup osc
*/
typedef enum {
/** Invalid data type. Never results in any data extraction. */
GHOSTTY_OSC_DATA_INVALID = 0,
/**
* Window title string data.
*
* Valid for: GHOSTTY_OSC_COMMAND_CHANGE_WINDOW_TITLE
*
* Output type: const char ** (pointer to null-terminated string)
*
* Lifetime: Valid until the next call to any ghostty_osc_* function with
* the same parser instance. Memory is owned by the parser.
*/
GHOSTTY_OSC_DATA_CHANGE_WINDOW_TITLE_STR = 1,
} GhosttyOscCommandData;
/**
* Create a new OSC parser instance.
*
* Creates a new OSC (Operating System Command) parser using the provided
* allocator. The parser must be freed using ghostty_vt_osc_free() when
* no longer needed.
*
* @param allocator Pointer to the allocator to use for memory management, or NULL to use the default allocator
* @param parser Pointer to store the created parser handle
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup osc
*/
GhosttyResult ghostty_osc_new(const GhosttyAllocator *allocator, GhosttyOscParser *parser);
/**
* Free an OSC parser instance.
*
* Releases all resources associated with the OSC parser. After this call,
* the parser handle becomes invalid and must not be used.
*
* @param parser The parser handle to free (may be NULL)
*
* @ingroup osc
*/
void ghostty_osc_free(GhosttyOscParser parser);
/**
* Reset an OSC parser instance to its initial state.
*
* Resets the parser state, clearing any partially parsed OSC sequences
* and returning the parser to its initial state. This is useful for
* reusing a parser instance or recovering from parse errors.
*
* @param parser The parser handle to reset, must not be null.
*
* @ingroup osc
*/
void ghostty_osc_reset(GhosttyOscParser parser);
/**
* Parse the next byte in an OSC sequence.
*
* Processes a single byte as part of an OSC sequence. The parser maintains
* internal state to track the progress through the sequence. Call this
* function for each byte in the sequence data.
*
* When finished pumping the parser with bytes, call ghostty_osc_end
* to get the final result.
*
* @param parser The parser handle, must not be null.
* @param byte The next byte to parse
*
* @ingroup osc
*/
void ghostty_osc_next(GhosttyOscParser parser, uint8_t byte);
/**
* Finalize OSC parsing and retrieve the parsed command.
*
* Call this function after feeding all bytes of an OSC sequence to the parser
* using ghostty_osc_next() with the exception of the terminating character
* (ESC or ST). This function finalizes the parsing process and returns the
* parsed OSC command.
*
* The return value is never NULL. Invalid commands will return a command
* with type GHOSTTY_OSC_COMMAND_INVALID.
*
* The terminator parameter specifies the byte that terminated the OSC sequence
* (typically 0x07 for BEL or 0x5C for ST after ESC). This information is
* preserved in the parsed command so that responses can use the same terminator
* format for better compatibility with the calling program. For commands that
* do not require a response, this parameter is ignored and the resulting
* command will not retain the terminator information.
*
* The returned command handle is valid until the next call to any
* `ghostty_osc_*` function with the same parser instance with the exception
* of command introspection functions such as `ghostty_osc_command_type`.
*
* @param parser The parser handle, must not be null.
* @param terminator The terminating byte of the OSC sequence (0x07 for BEL, 0x5C for ST)
* @return Handle to the parsed OSC command
*
* @ingroup osc
*/
GhosttyOscCommand ghostty_osc_end(GhosttyOscParser parser, uint8_t terminator);
/**
* Get the type of an OSC command.
*
* Returns the type identifier for the given OSC command. This can be used
* to determine what kind of command was parsed and what data might be
* available from it.
*
* @param command The OSC command handle to query (may be NULL)
* @return The command type, or GHOSTTY_OSC_COMMAND_INVALID if command is NULL
*
* @ingroup osc
*/
GhosttyOscCommandType ghostty_osc_command_type(GhosttyOscCommand command);
/**
* Extract data from an OSC command.
*
* Extracts typed data from the given OSC command based on the specified
* data type. The output pointer must be of the appropriate type for the
* requested data kind. Valid command types, output types, and memory
* safety information are documented in the `GhosttyOscCommandData` enum.
*
* @param command The OSC command handle to query (may be NULL)
* @param data The type of data to extract
* @param out Pointer to store the extracted data (type depends on data parameter)
* @return true if data extraction was successful, false otherwise
*
* @ingroup osc
*/
bool ghostty_osc_command_data(GhosttyOscCommand command, GhosttyOscCommandData data, void *out);
/** @} */
#endif /* GHOSTTY_VT_OSC_H */
+75
View File
@@ -0,0 +1,75 @@
/**
* @file paste.h
*
* Paste utilities - validate and encode paste data for terminal input.
*/
#ifndef GHOSTTY_VT_PASTE_H
#define GHOSTTY_VT_PASTE_H
/** @defgroup paste Paste Utilities
*
* Utilities for validating paste data safety.
*
* ## Basic Usage
*
* Use ghostty_paste_is_safe() to check if paste data contains potentially
* dangerous sequences before sending it to the terminal.
*
* ## Example
*
* @code{.c}
* #include <stdio.h>
* #include <string.h>
* #include <ghostty/vt.h>
*
* int main() {
* const char* safe_data = "hello world";
* const char* unsafe_data = "rm -rf /\n";
*
* if (ghostty_paste_is_safe(safe_data, strlen(safe_data))) {
* printf("Safe to paste\n");
* }
*
* if (!ghostty_paste_is_safe(unsafe_data, strlen(unsafe_data))) {
* printf("Unsafe! Contains newline\n");
* }
*
* return 0;
* }
* @endcode
*
* @{
*/
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Check if paste data is safe to paste into the terminal.
*
* Data is considered unsafe if it contains:
* - Newlines (`\n`) which can inject commands
* - The bracketed paste end sequence (`\x1b[201~`) which can be used
* to exit bracketed paste mode and inject commands
*
* This check is conservative and considers data unsafe regardless of
* current terminal state.
*
* @param data The paste data to check (must not be NULL)
* @param len The length of the data in bytes
* @return true if the data is safe to paste, false otherwise
*/
bool ghostty_paste_is_safe(const char* data, size_t len);
#ifdef __cplusplus
}
#endif
/** @} */
#endif /* GHOSTTY_VT_PASTE_H */
+22
View File
@@ -0,0 +1,22 @@
/**
* @file result.h
*
* Result codes for libghostty-vt operations.
*/
#ifndef GHOSTTY_VT_RESULT_H
#define GHOSTTY_VT_RESULT_H
/**
* Result codes for libghostty-vt operations.
*/
typedef enum {
/** Operation completed successfully */
GHOSTTY_SUCCESS = 0,
/** Operation failed due to failed allocation */
GHOSTTY_OUT_OF_MEMORY = -1,
/** Operation failed due to invalid value */
GHOSTTY_INVALID_VALUE = -2,
} GhosttyResult;
#endif /* GHOSTTY_VT_RESULT_H */
+393
View File
@@ -0,0 +1,393 @@
/**
* @file sgr.h
*
* SGR (Select Graphic Rendition) attribute parsing and handling.
*/
#ifndef GHOSTTY_VT_SGR_H
#define GHOSTTY_VT_SGR_H
/** @defgroup sgr SGR Parser
*
* SGR (Select Graphic Rendition) attribute parser.
*
* SGR sequences are the syntax used to set styling attributes such as
* bold, italic, underline, and colors for text in terminal emulators.
* For example, you may be familiar with sequences like `ESC[1;31m`. The
* `1;31` is the SGR attribute list.
*
* The parser processes SGR parameters from CSI sequences (e.g., `ESC[1;31m`)
* and returns individual text attributes like bold, italic, colors, etc.
* It supports both semicolon (`;`) and colon (`:`) separators, possibly mixed,
* and handles various color formats including 8-color, 16-color, 256-color,
* X11 named colors, and RGB in multiple formats.
*
* ## Basic Usage
*
* 1. Create a parser instance with ghostty_sgr_new()
* 2. Set SGR parameters with ghostty_sgr_set_params()
* 3. Iterate through attributes using ghostty_sgr_next()
* 4. Free the parser with ghostty_sgr_free() when done
*
* ## Example
*
* @code{.c}
* #include <assert.h>
* #include <stdio.h>
* #include <ghostty/vt.h>
*
* int main() {
* // Create parser
* GhosttySgrParser parser;
* GhosttyResult result = ghostty_sgr_new(NULL, &parser);
* assert(result == GHOSTTY_SUCCESS);
*
* // Parse "bold, red foreground" sequence: ESC[1;31m
* uint16_t params[] = {1, 31};
* result = ghostty_sgr_set_params(parser, params, NULL, 2);
* assert(result == GHOSTTY_SUCCESS);
*
* // Iterate through attributes
* GhosttySgrAttribute attr;
* while (ghostty_sgr_next(parser, &attr)) {
* switch (attr.tag) {
* case GHOSTTY_SGR_ATTR_BOLD:
* printf("Bold enabled\n");
* break;
* case GHOSTTY_SGR_ATTR_FG_8:
* printf("Foreground color: %d\n", attr.value.fg_8);
* break;
* default:
* break;
* }
* }
*
* // Cleanup
* ghostty_sgr_free(parser);
* return 0;
* }
* @endcode
*
* @{
*/
#include <ghostty/vt/allocator.h>
#include <ghostty/vt/color.h>
#include <ghostty/vt/result.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Opaque handle to an SGR parser instance.
*
* This handle represents an SGR (Select Graphic Rendition) parser that can
* be used to parse SGR sequences and extract individual text attributes.
*
* @ingroup sgr
*/
typedef struct GhosttySgrParser* GhosttySgrParser;
/**
* SGR attribute tags.
*
* These values identify the type of an SGR attribute in a tagged union.
* Use the tag to determine which field in the attribute value union to access.
*
* @ingroup sgr
*/
typedef enum {
GHOSTTY_SGR_ATTR_UNSET = 0,
GHOSTTY_SGR_ATTR_UNKNOWN = 1,
GHOSTTY_SGR_ATTR_BOLD = 2,
GHOSTTY_SGR_ATTR_RESET_BOLD = 3,
GHOSTTY_SGR_ATTR_ITALIC = 4,
GHOSTTY_SGR_ATTR_RESET_ITALIC = 5,
GHOSTTY_SGR_ATTR_FAINT = 6,
GHOSTTY_SGR_ATTR_UNDERLINE = 7,
GHOSTTY_SGR_ATTR_UNDERLINE_COLOR = 8,
GHOSTTY_SGR_ATTR_UNDERLINE_COLOR_256 = 9,
GHOSTTY_SGR_ATTR_RESET_UNDERLINE_COLOR = 10,
GHOSTTY_SGR_ATTR_OVERLINE = 11,
GHOSTTY_SGR_ATTR_RESET_OVERLINE = 12,
GHOSTTY_SGR_ATTR_BLINK = 13,
GHOSTTY_SGR_ATTR_RESET_BLINK = 14,
GHOSTTY_SGR_ATTR_INVERSE = 15,
GHOSTTY_SGR_ATTR_RESET_INVERSE = 16,
GHOSTTY_SGR_ATTR_INVISIBLE = 17,
GHOSTTY_SGR_ATTR_RESET_INVISIBLE = 18,
GHOSTTY_SGR_ATTR_STRIKETHROUGH = 19,
GHOSTTY_SGR_ATTR_RESET_STRIKETHROUGH = 20,
GHOSTTY_SGR_ATTR_DIRECT_COLOR_FG = 21,
GHOSTTY_SGR_ATTR_DIRECT_COLOR_BG = 22,
GHOSTTY_SGR_ATTR_BG_8 = 23,
GHOSTTY_SGR_ATTR_FG_8 = 24,
GHOSTTY_SGR_ATTR_RESET_FG = 25,
GHOSTTY_SGR_ATTR_RESET_BG = 26,
GHOSTTY_SGR_ATTR_BRIGHT_BG_8 = 27,
GHOSTTY_SGR_ATTR_BRIGHT_FG_8 = 28,
GHOSTTY_SGR_ATTR_BG_256 = 29,
GHOSTTY_SGR_ATTR_FG_256 = 30,
} GhosttySgrAttributeTag;
/**
* Underline style types.
*
* @ingroup sgr
*/
typedef enum {
GHOSTTY_SGR_UNDERLINE_NONE = 0,
GHOSTTY_SGR_UNDERLINE_SINGLE = 1,
GHOSTTY_SGR_UNDERLINE_DOUBLE = 2,
GHOSTTY_SGR_UNDERLINE_CURLY = 3,
GHOSTTY_SGR_UNDERLINE_DOTTED = 4,
GHOSTTY_SGR_UNDERLINE_DASHED = 5,
} GhosttySgrUnderline;
/**
* Unknown SGR attribute data.
*
* Contains the full parameter list and the partial list where parsing
* encountered an unknown or invalid sequence.
*
* @ingroup sgr
*/
typedef struct {
const uint16_t* full_ptr;
size_t full_len;
const uint16_t* partial_ptr;
size_t partial_len;
} GhosttySgrUnknown;
/**
* SGR attribute value union.
*
* This union contains all possible attribute values. Use the tag field
* to determine which union member is active. Attributes without associated
* data (like bold, italic) don't use the union value.
*
* @ingroup sgr
*/
typedef union {
GhosttySgrUnknown unknown;
GhosttySgrUnderline underline;
GhosttyColorRgb underline_color;
GhosttyColorPaletteIndex underline_color_256;
GhosttyColorRgb direct_color_fg;
GhosttyColorRgb direct_color_bg;
GhosttyColorPaletteIndex bg_8;
GhosttyColorPaletteIndex fg_8;
GhosttyColorPaletteIndex bright_bg_8;
GhosttyColorPaletteIndex bright_fg_8;
GhosttyColorPaletteIndex bg_256;
GhosttyColorPaletteIndex fg_256;
uint64_t _padding[8];
} GhosttySgrAttributeValue;
/**
* SGR attribute (tagged union).
*
* A complete SGR attribute with both its type tag and associated value.
* Always check the tag field to determine which value union member is valid.
*
* Attributes without associated data (e.g., GHOSTTY_SGR_ATTR_BOLD) can be
* identified by tag alone; the value union is not used for these and
* the memory in the value field is undefined.
*
* @ingroup sgr
*/
typedef struct {
GhosttySgrAttributeTag tag;
GhosttySgrAttributeValue value;
} GhosttySgrAttribute;
/**
* Create a new SGR parser instance.
*
* Creates a new SGR (Select Graphic Rendition) parser using the provided
* allocator. The parser must be freed using ghostty_sgr_free() when
* no longer needed.
*
* @param allocator Pointer to the allocator to use for memory management, or
* NULL to use the default allocator
* @param parser Pointer to store the created parser handle
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup sgr
*/
GhosttyResult ghostty_sgr_new(const GhosttyAllocator* allocator,
GhosttySgrParser* parser);
/**
* Free an SGR parser instance.
*
* Releases all resources associated with the SGR parser. After this call,
* the parser handle becomes invalid and must not be used. This includes
* any attributes previously returned by ghostty_sgr_next().
*
* @param parser The parser handle to free (may be NULL)
*
* @ingroup sgr
*/
void ghostty_sgr_free(GhosttySgrParser parser);
/**
* Reset an SGR parser instance to the beginning of the parameter list.
*
* Resets the parser's iteration state without clearing the parameters.
* After calling this, ghostty_sgr_next() will start from the beginning
* of the parameter list again.
*
* @param parser The parser handle to reset, must not be NULL
*
* @ingroup sgr
*/
void ghostty_sgr_reset(GhosttySgrParser parser);
/**
* Set SGR parameters for parsing.
*
* Sets the SGR parameter list to parse. Parameters are the numeric values
* from a CSI SGR sequence (e.g., for `ESC[1;31m`, params would be {1, 31}).
*
* The separators array optionally specifies the separator type for each
* parameter position. Each byte should be either ';' for semicolon or ':'
* for colon. This is needed for certain color formats that use colon
* separators (e.g., `ESC[4:3m` for curly underline). Any invalid separator
* values are treated as semicolons. The separators array must have the same
* length as the params array, if it is not NULL.
*
* If separators is NULL, all parameters are assumed to be semicolon-separated.
*
* This function makes an internal copy of the parameter and separator data,
* so the caller can safely free or modify the input arrays after this call.
*
* After calling this function, the parser is automatically reset and ready
* to iterate from the beginning.
*
* @param parser The parser handle, must not be NULL
* @param params Array of SGR parameter values
* @param separators Optional array of separator characters (';' or ':'), or
* NULL
* @param len Number of parameters (and separators if provided)
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup sgr
*/
GhosttyResult ghostty_sgr_set_params(GhosttySgrParser parser,
const uint16_t* params,
const char* separators,
size_t len);
/**
* Get the next SGR attribute.
*
* Parses and returns the next attribute from the parameter list.
* Call this function repeatedly until it returns false to process
* all attributes in the sequence.
*
* @param parser The parser handle, must not be NULL
* @param attr Pointer to store the next attribute
* @return true if an attribute was returned, false if no more attributes
*
* @ingroup sgr
*/
bool ghostty_sgr_next(GhosttySgrParser parser, GhosttySgrAttribute* attr);
/**
* Get the full parameter list from an unknown SGR attribute.
*
* This function retrieves the full parameter list that was provided to the
* parser when an unknown attribute was encountered. Primarily useful in
* WebAssembly environments where accessing struct fields directly is difficult.
*
* @param unknown The unknown attribute data
* @param ptr Pointer to store the pointer to the parameter array (may be NULL)
* @return The length of the full parameter array
*
* @ingroup sgr
*/
size_t ghostty_sgr_unknown_full(GhosttySgrUnknown unknown,
const uint16_t** ptr);
/**
* Get the partial parameter list from an unknown SGR attribute.
*
* This function retrieves the partial parameter list where parsing stopped
* when an unknown attribute was encountered. Primarily useful in WebAssembly
* environments where accessing struct fields directly is difficult.
*
* @param unknown The unknown attribute data
* @param ptr Pointer to store the pointer to the parameter array (may be NULL)
* @return The length of the partial parameter array
*
* @ingroup sgr
*/
size_t ghostty_sgr_unknown_partial(GhosttySgrUnknown unknown,
const uint16_t** ptr);
/**
* Get the tag from an SGR attribute.
*
* This function extracts the tag that identifies which type of attribute
* this is. Primarily useful in WebAssembly environments where accessing
* struct fields directly is difficult.
*
* @param attr The SGR attribute
* @return The attribute tag
*
* @ingroup sgr
*/
GhosttySgrAttributeTag ghostty_sgr_attribute_tag(GhosttySgrAttribute attr);
/**
* Get the value from an SGR attribute.
*
* This function returns a pointer to the value union from an SGR attribute. Use
* the tag to determine which field of the union is valid. Primarily useful in
* WebAssembly environments where accessing struct fields directly is difficult.
*
* @param attr Pointer to the SGR attribute
* @return Pointer to the attribute value union
*
* @ingroup sgr
*/
GhosttySgrAttributeValue* ghostty_sgr_attribute_value(
GhosttySgrAttribute* attr);
#ifdef __wasm__
/**
* Allocate memory for an SGR attribute (WebAssembly only).
*
* This is a convenience function for WebAssembly environments to allocate
* memory for an SGR attribute structure that can be passed to ghostty_sgr_next.
*
* @return Pointer to the allocated attribute structure
*
* @ingroup wasm
*/
GhosttySgrAttribute* ghostty_wasm_alloc_sgr_attribute(void);
/**
* Free memory for an SGR attribute (WebAssembly only).
*
* Frees memory allocated by ghostty_wasm_alloc_sgr_attribute.
*
* @param attr Pointer to the attribute structure to free
*
* @ingroup wasm
*/
void ghostty_wasm_free_sgr_attribute(GhosttySgrAttribute* attr);
#endif
#ifdef __cplusplus
}
#endif
/** @} */
#endif /* GHOSTTY_VT_SGR_H */
+159
View File
@@ -0,0 +1,159 @@
/**
* @file wasm.h
*
* WebAssembly utility functions for libghostty-vt.
*/
#ifndef GHOSTTY_VT_WASM_H
#define GHOSTTY_VT_WASM_H
#ifdef __wasm__
#include <stddef.h>
#include <stdint.h>
/** @defgroup wasm WebAssembly Utilities
*
* Convenience functions for allocating various types in WebAssembly builds.
* **These are only available the libghostty-vt wasm module.**
*
* Ghostty relies on pointers to various types for ABI compatibility, and
* creating those pointers in Wasm can be tedious. These functions provide
* a purely additive set of utilities that simplify memory management in
* Wasm environments without changing the core C library API.
*
* @note These functions always use the default allocator. If you need
* custom allocation strategies, you should allocate types manually using
* your custom allocator. This is a very rare use case in the WebAssembly
* world so these are optimized for simplicity.
*
* ## Example Usage
*
* Here's a simple example of using the Wasm utilities with the key encoder:
*
* @code
* const { exports } = wasmInstance;
* const view = new DataView(wasmMemory.buffer);
*
* // Create key encoder
* const encoderPtr = exports.ghostty_wasm_alloc_opaque();
* exports.ghostty_key_encoder_new(null, encoderPtr);
* const encoder = view.getUint32(encoder, true);
*
* // Configure encoder with Kitty protocol flags
* const flagsPtr = exports.ghostty_wasm_alloc_u8();
* view.setUint8(flagsPtr, 0x1F);
* exports.ghostty_key_encoder_setopt(encoder, 5, flagsPtr);
*
* // Allocate output buffer and size pointer
* const bufferSize = 32;
* const bufPtr = exports.ghostty_wasm_alloc_u8_array(bufferSize);
* const writtenPtr = exports.ghostty_wasm_alloc_usize();
*
* // Encode the key event
* exports.ghostty_key_encoder_encode(
* encoder, eventPtr, bufPtr, bufferSize, writtenPtr
* );
*
* // Read encoded output
* const bytesWritten = view.getUint32(writtenPtr, true);
* const encoded = new Uint8Array(wasmMemory.buffer, bufPtr, bytesWritten);
* @endcode
*
* @remark The code above is pretty ugly! This is the lowest level interface
* to the libghostty-vt Wasm module. In practice, this should be wrapped
* in a higher-level API that abstracts away all this.
*
* @{
*/
/**
* Allocate an opaque pointer. This can be used for any opaque pointer
* types such as GhosttyKeyEncoder, GhosttyKeyEvent, etc.
*
* @return Pointer to allocated opaque pointer, or NULL if allocation failed
* @ingroup wasm
*/
void** ghostty_wasm_alloc_opaque(void);
/**
* Free an opaque pointer allocated by ghostty_wasm_alloc_opaque().
*
* @param ptr Pointer to free, or NULL (NULL is safely ignored)
* @ingroup wasm
*/
void ghostty_wasm_free_opaque(void **ptr);
/**
* Allocate an array of uint8_t values.
*
* @param len Number of uint8_t elements to allocate
* @return Pointer to allocated array, or NULL if allocation failed
* @ingroup wasm
*/
uint8_t* ghostty_wasm_alloc_u8_array(size_t len);
/**
* Free an array allocated by ghostty_wasm_alloc_u8_array().
*
* @param ptr Pointer to the array to free, or NULL (NULL is safely ignored)
* @param len Length of the array (must match the length passed to alloc)
* @ingroup wasm
*/
void ghostty_wasm_free_u8_array(uint8_t *ptr, size_t len);
/**
* Allocate an array of uint16_t values.
*
* @param len Number of uint16_t elements to allocate
* @return Pointer to allocated array, or NULL if allocation failed
* @ingroup wasm
*/
uint16_t* ghostty_wasm_alloc_u16_array(size_t len);
/**
* Free an array allocated by ghostty_wasm_alloc_u16_array().
*
* @param ptr Pointer to the array to free, or NULL (NULL is safely ignored)
* @param len Length of the array (must match the length passed to alloc)
* @ingroup wasm
*/
void ghostty_wasm_free_u16_array(uint16_t *ptr, size_t len);
/**
* Allocate a single uint8_t value.
*
* @return Pointer to allocated uint8_t, or NULL if allocation failed
* @ingroup wasm
*/
uint8_t* ghostty_wasm_alloc_u8(void);
/**
* Free a uint8_t allocated by ghostty_wasm_alloc_u8().
*
* @param ptr Pointer to free, or NULL (NULL is safely ignored)
* @ingroup wasm
*/
void ghostty_wasm_free_u8(uint8_t *ptr);
/**
* Allocate a single size_t value.
*
* @return Pointer to allocated size_t, or NULL if allocation failed
* @ingroup wasm
*/
size_t* ghostty_wasm_alloc_usize(void);
/**
* Free a size_t allocated by ghostty_wasm_alloc_usize().
*
* @param ptr Pointer to free, or NULL (NULL is safely ignored)
* @ingroup wasm
*/
void ghostty_wasm_free_usize(size_t *ptr);
/** @} */
#endif /* __wasm__ */
#endif /* GHOSTTY_VT_WASM_H */
Binary file not shown.
@@ -0,0 +1,124 @@
diff --git a/build.zig b/build.zig
index f9d861b..93d0780 100644
--- a/build.zig
+++ b/build.zig
@@ -138,9 +138,8 @@ pub fn build(b: *std.Build) !void {
// We shouldn't have this guard but we don't currently
// build on macOS this way ironically so we need to fix that.
- if (!config.target.result.os.tag.isDarwin()) {
+ if (true) {
libghostty_shared.installHeader(); // Only need one header
- libghostty_shared.install("libghostty.so");
libghostty_static.install("libghostty.a");
}
}
diff --git a/src/build/GhosttyXCFramework.zig b/src/build/GhosttyXCFramework.zig
index 3afeb90..ef075b3 100644
--- a/src/build/GhosttyXCFramework.zig
+++ b/src/build/GhosttyXCFramework.zig
@@ -15,74 +15,24 @@ pub fn init(
deps: *const SharedDeps,
target: Target,
) !GhosttyXCFramework {
- // Universal macOS build
- const macos_universal = try GhosttyLib.initMacOSUniversal(b, deps);
-
- // Native macOS build
+ // PATCH: Nur die native macOS-Slice bauen. iOS/iOS-Simulator/Universal
+ // entfernt, weil die VM nur Command Line Tools hat (kein iOS-SDK) und wir
+ // für die macOS-only MiniTerm-Hülle ohnehin nur libghostty.a brauchen.
const macos_native = try GhosttyLib.initStatic(b, &try deps.retarget(
b,
Config.genericMacOSTarget(b, null),
));
- // iOS
- const ios = try GhosttyLib.initStatic(b, &try deps.retarget(
- b,
- b.resolveTargetQuery(.{
- .cpu_arch = .aarch64,
- .os_tag = .ios,
- .os_version_min = Config.osVersionMin(.ios),
- .abi = null,
- }),
- ));
-
- // iOS Simulator
- const ios_sim = try GhosttyLib.initStatic(b, &try deps.retarget(
- b,
- b.resolveTargetQuery(.{
- .cpu_arch = .aarch64,
- .os_tag = .ios,
- .os_version_min = Config.osVersionMin(.ios),
- .abi = .simulator,
-
- // We force the Apple CPU model because the simulator
- // doesn't support the generic CPU model as of Zig 0.14 due
- // to missing "altnzcv" instructions, which is false. This
- // surely can't be right but we can fix this if/when we get
- // back to running simulator builds.
- .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.apple_a17 },
- }),
- ));
-
// The xcframework wraps our ghostty library so that we can link
// it to the final app built with Swift.
const xcframework = XCFrameworkStep.create(b, .{
.name = "GhosttyKit",
.out_path = "macos/GhosttyKit.xcframework",
- .libraries = switch (target) {
- .universal => &.{
- .{
- .library = macos_universal.output,
- .headers = b.path("include"),
- .dsym = macos_universal.dsym,
- },
- .{
- .library = ios.output,
- .headers = b.path("include"),
- .dsym = ios.dsym,
- },
- .{
- .library = ios_sim.output,
- .headers = b.path("include"),
- .dsym = ios_sim.dsym,
- },
- },
-
- .native => &.{.{
- .library = macos_native.output,
- .headers = b.path("include"),
- .dsym = macos_native.dsym,
- }},
- },
+ .libraries = &.{.{
+ .library = macos_native.output,
+ .headers = b.path("include"),
+ .dsym = macos_native.dsym,
+ }},
});
return .{
diff --git a/src/build/MetallibStep.zig b/src/build/MetallibStep.zig
index fcf3055..f4f5bc9 100644
--- a/src/build/MetallibStep.zig
+++ b/src/build/MetallibStep.zig
@@ -55,7 +55,7 @@ pub fn create(b: *std.Build, opts: Options) ?*MetallibStep {
b,
b.fmt("metal {s}", .{opts.name}),
);
- run_ir.addArgs(&.{ "/usr/bin/xcrun", "-sdk", sdk, "metal", "-o" });
+ run_ir.addArgs(&.{ "/usr/bin/env", "DEVELOPER_DIR=/Applications/Xcode_26.4.1.app/Contents/Developer", "/usr/bin/xcrun", "-sdk", sdk, "metal", "-o" });
const output_ir = run_ir.addOutputFileArg(b.fmt("{s}.ir", .{opts.name}));
run_ir.addArgs(&.{"-c"});
for (opts.sources) |source| run_ir.addFileArg(source);
@@ -70,7 +70,7 @@ pub fn create(b: *std.Build, opts: Options) ?*MetallibStep {
b,
b.fmt("metallib {s}", .{opts.name}),
);
- run_lib.addArgs(&.{ "/usr/bin/xcrun", "-sdk", sdk, "metallib", "-o" });
+ run_lib.addArgs(&.{ "/usr/bin/env", "DEVELOPER_DIR=/Applications/Xcode_26.4.1.app/Contents/Developer", "/usr/bin/xcrun", "-sdk", sdk, "metallib", "-o" });
const output_lib = run_lib.addOutputFileArg(b.fmt("{s}.metallib", .{opts.name}));
run_lib.addFileArg(output_ir);
run_lib.step.dependOn(&run_ir.step);