mitchellh.com/3mkqkqquj4d22

7 min read Original article ↗

Configure Feed

Select the types of activity you want to include in your feed.

Configure Feed

Select the types of activity you want to include in your feed.

41 2 0

Clone this repository

https://tangled.org/mitchellh.com/3mkqkqquj4d22 https://tangled.org/did:plc:s6vfrnebhxb6gqrmlspmbmr7

git@knot.mitchellh.com:mitchellh.com/3mkqkqquj4d22 git@knot.mitchellh.com:did:plc:s6vfrnebhxb6gqrmlspmbmr7

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz

commits 41

Add human-friendly text serialization to the input enum types via
the standard encoding.TextMarshaler and encoding.TextUnmarshaler
interfaces. This makes them work transparently with encoding/json,
TOML, YAML, XML, and flag.TextVar.

Each type gains:

- String() string returning the canonical snake_case name
(e.g. "key_a", "arrow_down", "shift+ctrl", "middle",
"gained").
- MarshalText() ([]byte, error) returning the same bytes as
String. Mods(0) marshals to an empty byte slice.
- UnmarshalText([]byte) error mutating the receiver.
- A top-level ParseKey, ParseMods, ParseMouseButton, and
ParseFocusEvent function as an ergonomic alternative to the
pointer-receiver UnmarshalText.

The Key and MouseButton names follow the upstream Zig source
naming. Mods is rendered as a stable "+"-joined list of
individual flag names; UnmarshalText accepts either "+" or ","
as separators and supports the upstream aliases (cmd/command for
super, opt/option for alt, control for ctrl).

add String/FromString for Key, Mods, MouseButton, FocusEvent

Add human-friendly string conversion to the input enum types so they
can be serialized to and parsed from text formats like JSON or
configuration files.

Each type gains a String() method returning the canonical
snake_case name (e.g. "key_a", "arrow_down", "shift+ctrl",
"middle", "gained") and a pointer-receiver FromString method
that mutates the receiver. A top-level NewKeyFromString,
NewModsFromString, NewMouseButtonFromString, and
NewFocusEventFromString constructor is also provided as a more
ergonomic alternative.

Extensive notes on concurrency

Document and test cross-compilation

One of the common stigmas of cgo in Go projects is that it makes
cross-compilation overly difficult. Its more difficult than pure Go projects,
to be sure, but with the right shape of libraries and build scripts, it
can be made to work well. libghostty is a good example of this.

libghostty only depends on libc and the Zig compiler (tool, not language)
as a drop-in replacement for c/c++ compilation means we can easily
cross-compile!

This commit adds documentation, tests, and examples on how to do this.

terminal: fix checkptr failure under -race for cgo.Handle userdata

Converting a cgo.Handle (uintptr) directly to unsafe.Pointer in
NewTerminal triggered a checkptr "bad pointer value" panic when
running tests with -race. The handle is an opaque integer, not a
real Go pointer, so checkptr incorrectly rejects it.

Extract the conversion into a small handleToPointer helper
annotated with //go:nocheckptr to suppress the false positive.

terminal: pass *Terminal as first parameter to effect callbacks

Previously, effect callback function types (WritePtyFn, BellFn,
TitleChangedFn, etc.) did not receive the terminal that triggered
them. This forced callers using the functional option pattern to
pre-declare a var and use a split assignment so closures could
capture it, as seen in the effects example.

All effect callback types now take *Terminal as their first
parameter. The C trampolines already recovered the *Terminal from
userdata, so they now simply forward it to the Go handler. This
lets callers define callbacks inline in NewTerminal without any
pre-declaration workaround.

encoding: add key, mouse, and focus encoding bindings

Add Go bindings for the upstream key, mouse, and focus encoding
APIs from key/, mouse/, and focus.h headers.

Key encoding provides KeyEvent (action, key, mods, UTF-8 text,
composing state, unshifted codepoint) and KeyEncoder with setopt
methods for cursor key application mode, Kitty keyboard protocol
flags, macOS option-as-alt, and other terminal modes. The encoder
also supports syncing options from a Terminal instance.

Mouse encoding provides MouseEvent (action, button, mods, position)
and MouseEncoder with tracking mode, format, size geometry, and
terminal state syncing. The encoder supports X10, UTF-8, SGR, URxvt,
and SGR-Pixels protocols.

Focus encoding exposes FocusEncode as a standalone function producing
CSI I (gained) or CSI O (lost) sequences.

KittyKeyFlags moves from terminal.go to key_encoder.go since it is
part of the key encoding API and used by both the encoder and the
terminal.

Update libghostty to not depend on libc++

sys/png: move SysDecodePng to separate subpackage

Fixes #6

The root libghostty package previously imported image/png for
SysDecodePng. Because image/png registers itself via an init()
function, the Go linker cannot eliminate it from binaries that
never call SysDecodePng. Every consumer paid the cost.

Move the built-in PNG decoder into sys/png so users opt in
explicitly. The root package no longer imports image or image/png.
The kitty_graphics_test.go file inlines a local test helper to
avoid the import cycle that would result from an internal test
package importing its own subpackage.

bind ghostty_alloc/ghostty_free and replace C.malloc/C.free

Add alloc.go with exported Alloc and Free functions wrapping the
upstream ghostty_alloc() and ghostty_free() from allocator.h. These
use the default (NULL) allocator, matching how the rest of the
bindings pass nil for the allocator parameter.

This is important because it lets us not explicitly depend on any libc
functionality.

bind upstream get_multi APIs, replace sized-struct Info()

Update the pinned ghostty commit to pick up the new _get_multi C
APIs added across all getter types (terminal, render state, row,
cell, screen, kitty graphics image, kitty graphics placement).

The existing Info() methods on KittyGraphicsImage and
KittyGraphicsPlacementIterator previously used sized-struct C
types (GhosttyKittyGraphicsImageInfo, etc.) initialized via
GHOSTTY_INIT_SIZED. These are replaced by get_multi calls that
fetch each field individually through typed pointers, eliminating
struct ABI concerns (padding, alignment, field ordering) at the
cgo boundary. The Go-side convenience structs remain unchanged.

Each type also gains a public GetMulti method that exposes the
raw get_multi API for callers who want to batch arbitrary subsets
of queries into a single cgo crossing.

A shared cValuesArray helper in get_multi.go solves the cgo
pointer-passing rule for void**: the array of output pointers is
allocated in C heap memory, populated from Go, passed to C, then
freed.

kitty_graphics: add batch info and render info bindings

Go Libghostty Bindings#

Go bindings for libghostty-vt.

This project uses cgo but libghostty-vt only depends on libc, so it is very easy to static link and very easy to cross-compile. The bindings default to static linking for this reason.

WARNING

I'm not promising any API stability yet. This is a new project and the API may change as necessary. The underlying functionality is very stable, but the Go API is still being designed.

Example#

package main

import (
 "fmt"
 "log"

 "go.mitchellh.com/libghostty"
)

func main() {
 term, err := libghostty.NewTerminal(libghostty.WithSize(80, 24))
 if err != nil {
  log.Fatal(err)
 }
 defer term.Close()

 // Feed VT data — bold green "world", then plain text.
 fmt.Fprintf(term, "Hello, \033[1;32mworld\033[0m!\r\n")

 // Format the terminal contents as plain text.
 f, err := libghostty.NewFormatter(term,
  libghostty.WithFormatterFormat(libghostty.FormatterFormatPlain),
  libghostty.WithFormatterTrim(true),
 )
 if err != nil {
  log.Fatal(err)
 }
 defer f.Close()

 output, _ := f.FormatString()
 fmt.Println(output) // Hello, world!
}

More examples are in the examples/ directory.

Usage#

Add the module to your Go project:

go get go.mitchellh.com/libghostty

This is a cgo package that links libghostty-vt via pkg-config. By default it links statically. Before building your project, you need the library installed. Either install it system-wide or set PKG_CONFIG_PATH to point to a local checkout:

export PKG_CONFIG_PATH=/path/to/libghostty-vt/share/pkgconfig

To link dynamically instead (requires the shared library at runtime, so you'll also need to set the library path):

go build -tags dynamic

See the Ghostty docs for building libghostty-vt from source.

Cross-Compilation#

Because libghostty-vt only depends on libc, cross-compilation is straightforward using Zig as the C compiler. Zig is already required to build libghostty-vt, so no extra tooling is needed. You don't need to write any Zig code, we're just using Zig as a C/C++ compiler.

First, build libghostty-vt for your target (from the ghostty source tree):

zig build -Demit-lib-vt -Dtarget=x86_64-linux-gnu --prefix /tmp/ghostty-linux-amd64

Then cross-compile your Go project with zig cc:

CGO_ENABLED=1 \
GOOS=linux GOARCH=amd64 \
CC="zig cc -target x86_64-linux-gnu" \
CXX="zig c++ -target x86_64-linux-gnu" \
CGO_CFLAGS="-I/tmp/ghostty-linux-amd64/include -DGHOSTTY_STATIC" \
CGO_LDFLAGS="-L/tmp/ghostty-linux-amd64/lib -lghostty-vt" \
go build ./...

Supported targets include x86_64-linux-gnu, aarch64-linux-gnu, x86_64-macos, aarch64-macos, x86_64-windows-gnu, and aarch64-windows-gnu.

If you are using ghostty's CMake integration via FetchContent, the ghostty_vt_add_target() function handles the zig build for you:

FetchContent_MakeAvailable(ghostty)
ghostty_vt_add_target(NAME linux-amd64 ZIG_TARGET x86_64-linux-gnu)

See the ghostty CMakeLists.txt for full documentation of ghostty_vt_add_target().

Development#

CMake fetches and builds libghostty-vt automatically. CMake is only required and used for development of this module. For actual downstream usage, you can get libghostty-vt available however you like (e.g. system package, local checkout, etc.).

You need Zig and CMake on your PATH.

make build
make test

# If in a Nix dev shell:
go build
go test

If you use the Nix dev shell (nix develop), go build and go test work directly — the shell configures all paths automatically.