PluginBench
Skill
Review
Audit score 70

ghostling-libghostty-terminal

aradotso/trending-skills

Build minimal terminal emulators with libghostty-vt's VT parsing and render API plus Raylib.

What is ghostling-libghostty-terminal?

Ghostling demonstrates how to embed Ghostty's libghostty-vt C library—a zero-dependency VT parser and terminal state engine—into a custom terminal emulator. Use this skill when you need to integrate terminal emulation into an application, understand libghostty-vt's render-state API, or build a minimal terminal from scratch.

  • Parse VT escape sequences with SIMD optimization and handle Unicode graphemes
  • Manage terminal state including cursor position, styles, scrollback, and text reflow
  • Query render state to identify changed cells and their colors, attributes, and codepoints
  • Send keyboard and mouse events encoded as proper escape sequences to a PTY
  • Resize the terminal and reflow text automatically
  • Scroll the viewport and access cursor shape and visibility

How to install ghostling-libghostty-terminal

npx skills add https://github.com/aradotso/trending-skills --skill ghostling-libghostty-terminal
Prerequisites
  • CMake 3.19 or later
  • Ninja build tool
  • C compiler (clang or gcc)
  • Zig 0.15.x on PATH
  • macOS Xcode Command Line Tools or Xcode (for macOS builds)
Claude Code
Cursor
Windsurf
Cline

How to use ghostling-libghostty-terminal

  1. 1.Clone the ghostling repository and navigate to its directory
  2. 2.Run `cmake -B build -G Ninja` to configure the project (CMake auto-fetches libghostty-vt and Raylib)
  3. 3.Run `cmake --build build` to compile; use `-DCMAKE_BUILD_TYPE=Release` for optimized builds
  4. 4.Execute `./build/ghostling` to launch the terminal emulator
  5. 5.Study `main.c` to see the complete implementation: PTY setup, libghostty-vt initialization, render-state iteration, and Raylib drawing

Use cases

Good for
  • Embed a terminal emulator in a GUI application or game engine
  • Build a custom terminal with specialized rendering (GPU, retro, or minimal UI)
  • Integrate Kitty keyboard protocol and mouse tracking into a terminal frontend
  • Understand how to wire a VT parser to any 2D or GPU renderer
  • Create a WASM-based terminal by leveraging libghostty-vt's zero-dependency design
Who it's for
  • C/C++ developers building terminal applications or embedded systems
  • Graphics programmers integrating terminal emulation with custom renderers
  • Systems engineers exploring Ghostty's architecture and VT parsing
  • Anyone implementing a terminal frontend without relying on existing terminal emulators

ghostling-libghostty-terminal FAQ

What does libghostty-vt NOT provide?

libghostty-vt provides only VT parsing and terminal state; you must implement windowing, rendering, PTY management, tabs, splits, and configuration yourself.

Why are debug builds slow?

Ghostty's safety and correctness assertions are enabled in debug mode. Always use Release builds (`-DCMAKE_BUILD_TYPE=Release`) for benchmarking and production.

How do I handle PTY communication?

libghostty-vt has no PTY management. You own the PTY setup using POSIX `openpty()`, `fork()`, and `dup2()` to connect a shell process; feed raw bytes from the PTY into `ghostty_terminal_write()` and write encoded keyboard/mouse events back.

How do I render cells efficiently?

Use the render-state API (`ghostty_render_state_begin()`, `ghostty_render_state_next_cell()`, `ghostty_render_state_end()`) to iterate only changed cells and their attributes (color, bold, italic, reverse), then draw with your renderer.

Can I use libghostty-vt in WebAssembly?

Yes; libghostty-vt has zero dependencies (not even libc) and is WASM-compatible, though you must provide your own PTY emulation or input simulation in the browser.

Full instructions (SKILL.md)

Source of truth, from aradotso/trending-skills.


name: ghostling-libghostty-terminal description: Build minimal terminal emulators using the libghostty-vt C API with Raylib for windowing and rendering triggers:

  • build a terminal emulator with libghostty
  • use libghostty-vt C API
  • embed terminal emulation in my app
  • ghostling terminal example
  • integrate ghostty terminal library
  • libghostty render state API
  • minimal terminal emulator in C
  • ghostty libghostty-vt bindings

Ghostling — libghostty Terminal Emulator

Skill by ara.so — Daily 2026 Skills collection.

Ghostling is a minimal viable terminal emulator built on libghostty-vt, the embeddable C library extracted from Ghostty. It uses Raylib for windowing/rendering and lives in a single C file. The project demonstrates how to wire libghostty-vt's VT parsing, terminal state, and render-state API to any 2D or GPU renderer.

What libghostty-vt Provides

  • VT sequence parsing (SIMD-optimized)
  • Terminal state: cursor, styles, text reflow, scrollback
  • Render state management (what cells changed and how to draw them)
  • Unicode / multi-codepoint grapheme handling
  • Kitty keyboard protocol, mouse tracking, focus reporting
  • Zero dependencies (not even libc) — WASM-compatible

libghostty-vt does NOT provide: windowing, rendering, PTY management, tabs, splits, or configuration.

Requirements

ToolVersion
CMake3.19+
Ninjaany
C compilerclang/gcc
Zig0.15.x (on PATH)
macOSXcode CLT or Xcode

Build & Run

# Clone
git clone https://github.com/ghostty-org/ghostling.git
cd ghostling

# Debug build (slow — safety checks enabled)
cmake -B build -G Ninja
cmake --build build
./build/ghostling

# Release build (optimized)
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/ghostling

After first configure, only the build step is needed:

cmake --build build

Warning: Debug builds are very slow due to Ghostty's safety/correctness assertions. Always benchmark with Release builds.

Project Structure

ghostling/
├── main.c          # Entire terminal implementation (single file)
├── CMakeLists.txt  # Build config; fetches libghostty-vt + Raylib
└── demo.gif

Core libghostty-vt API Patterns

All implementation lives in main.c. Below are the key patterns extracted from it.

1. Initialize the Terminal

#include "ghostty.h"  // provided by libghostty-vt via CMake

// Create terminal with cols x rows cells
ghostty_terminal_t *terminal = ghostty_terminal_new(
    &(ghostty_terminal_config_t){
        .cols = cols,
        .rows = rows,
    }
);
if (!terminal) { /* handle error */ }

2. Write Data from PTY into the Terminal

// Read from PTY fd, feed raw bytes to libghostty-vt
ssize_t n = read(pty_fd, buf, sizeof(buf));
if (n > 0) {
    ghostty_terminal_write(terminal, buf, (size_t)n);
}

3. Send Keyboard Input

// libghostty-vt encodes the correct escape sequences
ghostty_key_event_t ev = {
    .key       = GHOSTTY_KEY_A,          // key enum
    .mods      = GHOSTTY_MODS_CTRL,      // modifier flags
    .action    = GHOSTTY_ACTION_PRESS,
    .composing = false,
};

uint8_t out[64];
size_t  out_len = 0;
ghostty_terminal_key(terminal, &ev, out, sizeof(out), &out_len);

// Write encoded bytes to PTY
if (out_len > 0) write(pty_fd, out, out_len);

4. Send Mouse Events

ghostty_mouse_event_t mev = {
    .x      = cell_col,   // cell column
    .y      = cell_row,   // cell row
    .button = GHOSTTY_MOUSE_LEFT,
    .action = GHOSTTY_MOUSE_PRESS,
    .mods   = GHOSTTY_MODS_NONE,
};

uint8_t out[64];
size_t  out_len = 0;
ghostty_terminal_mouse(terminal, &mev, out, sizeof(out), &out_len);
if (out_len > 0) write(pty_fd, out, out_len);

5. Resize the Terminal

ghostty_terminal_resize(terminal, new_cols, new_rows);
// libghostty-vt handles text reflow automatically
// Send SIGWINCH to the child process after this
struct winsize ws = { .ws_col = new_cols, .ws_row = new_rows };
ioctl(pty_fd, TIOCSWINSZ, &ws);
kill(child_pid, SIGWINCH);

6. Render: Walk the Render State

The render state API tells you exactly which cells changed and how to draw them — no need to redraw everything every frame.

// Get render state handle
ghostty_render_state_t *rs = ghostty_terminal_render_state(terminal);

// Begin a render pass (snapshot current state)
ghostty_render_state_begin(rs);

// Iterate dirty cells
ghostty_render_cell_iter_t iter = {0};
ghostty_render_cell_t      cell;

while (ghostty_render_state_next_cell(rs, &iter, &cell)) {
    // cell.col, cell.row      — grid position
    // cell.codepoint          — Unicode codepoint (0 = empty)
    // cell.fg.r/g/b           — foreground RGB
    // cell.bg.r/g/b           — background RGB
    // cell.attrs.bold         — bold flag
    // cell.attrs.italic       — italic flag
    // cell.attrs.reverse      — reverse video

    // Example: draw with Raylib
    Color fg = { cell.fg.r, cell.fg.g, cell.fg.b, 255 };
    Color bg = { cell.bg.r, cell.bg.g, cell.bg.b, 255 };

    Rectangle rect = {
        .x      = cell.col * cell_width,
        .y      = cell.row * cell_height,
        .width  = cell_width,
        .height = cell_height,
    };
    DrawRectangleRec(rect, bg);

    if (cell.codepoint != 0) {
        char glyph[8] = {0};
        // encode codepoint to UTF-8 yourself or use a helper
        encode_utf8(cell.codepoint, glyph);
        DrawText(glyph, (int)rect.x, (int)rect.y, font_size, fg);
    }
}

// End render pass (marks cells as clean)
ghostty_render_state_end(rs);

7. Scrollback

// Scroll viewport up/down by N rows
ghostty_terminal_scroll(terminal, -3);  // scroll up 3
ghostty_terminal_scroll(terminal,  3);  // scroll down 3

// Scroll to bottom
ghostty_terminal_scroll_bottom(terminal);

8. Cursor Position

ghostty_cursor_t cursor;
ghostty_terminal_cursor(terminal, &cursor);
// cursor.col, cursor.row  — cell position
// cursor.visible          — bool
// cursor.shape            — GHOSTTY_CURSOR_BLOCK, _UNDERLINE, _BAR

9. Cleanup

ghostty_terminal_free(terminal);

PTY Setup (POSIX)

libghostty-vt has no PTY management — you own this:

#include <pty.h>   // openpty
#include <unistd.h>
#include <stdlib.h>

int master_fd, slave_fd;
struct winsize ws = { .ws_row = rows, .ws_col = cols,
                      .ws_xpixel = 0, .ws_ypixel = 0 };
openpty(&master_fd, &slave_fd, NULL, NULL, &ws);

pid_t child = fork();
if (child == 0) {
    // Child: become session leader, attach slave PTY
    setsid();
    ioctl(slave_fd, TIOCSCTTY, 0);
    dup2(slave_fd, STDIN_FILENO);
    dup2(slave_fd, STDOUT_FILENO);
    dup2(slave_fd, STDERR_FILENO);
    close(master_fd);
    close(slave_fd);
    char *shell = getenv("SHELL");
    if (!shell) shell = "/bin/sh";
    execl(shell, shell, NULL);
    _exit(1);
}
// Parent: use master_fd for read/write
close(slave_fd);

CMakeLists.txt Pattern

The project fetches libghostty-vt automatically via CMake FetchContent:

cmake_minimum_required(VERSION 3.19)
project(ghostling C)

include(FetchContent)

# libghostty-vt
FetchContent_Declare(
    libghostty
    URL https://release.files.ghostty.org/tip/libghostty-vt-<platform>.tar.gz
)
FetchContent_MakeAvailable(libghostty)

# Raylib
FetchContent_Declare(
    raylib
    GIT_REPOSITORY https://github.com/raysan5/raylib.git
    GIT_TAG 5.0
)
FetchContent_MakeAvailable(raylib)

add_executable(ghostling main.c)
target_link_libraries(ghostling PRIVATE ghostty-vt raylib)

Key Enums & Constants

// Keys
GHOSTTY_KEY_A … GHOSTTY_KEY_Z
GHOSTTY_KEY_UP, GHOSTTY_KEY_DOWN, GHOSTTY_KEY_LEFT, GHOSTTY_KEY_RIGHT
GHOSTTY_KEY_ENTER, GHOSTTY_KEY_BACKSPACE, GHOSTTY_KEY_ESCAPE, GHOSTTY_KEY_TAB
GHOSTTY_KEY_F1 … GHOSTTY_KEY_F12

// Modifiers (bitmask)
GHOSTTY_MODS_NONE
GHOSTTY_MODS_SHIFT
GHOSTTY_MODS_CTRL
GHOSTTY_MODS_ALT
GHOSTTY_MODS_SUPER

// Mouse buttons
GHOSTTY_MOUSE_LEFT, GHOSTTY_MOUSE_RIGHT, GHOSTTY_MOUSE_MIDDLE
GHOSTTY_MOUSE_WHEEL_UP, GHOSTTY_MOUSE_WHEEL_DOWN

// Cursor shapes
GHOSTTY_CURSOR_BLOCK, GHOSTTY_CURSOR_UNDERLINE, GHOSTTY_CURSOR_BAR

Common Patterns

Non-blocking PTY Read Loop

// Set master_fd non-blocking
fcntl(master_fd, F_SETFL, O_NONBLOCK);

// In your main loop:
uint8_t buf[4096];
ssize_t n;
while ((n = read(master_fd, buf, sizeof(buf))) > 0) {
    ghostty_terminal_write(terminal, buf, (size_t)n);
}
// EAGAIN means no data available — not an error

Raylib Key → ghostty_key_t Mapping

ghostty_key_t raylib_key_to_ghostty(int rl_key) {
    switch (rl_key) {
        case KEY_A: return GHOSTTY_KEY_A;
        case KEY_ENTER: return GHOSTTY_KEY_ENTER;
        case KEY_BACKSPACE: return GHOSTTY_KEY_BACKSPACE;
        case KEY_UP: return GHOSTTY_KEY_UP;
        case KEY_DOWN: return GHOSTTY_KEY_DOWN;
        // ... etc
        default: return GHOSTTY_KEY_INVALID;
    }
}

Scrollbar Rendering

int total_rows    = ghostty_terminal_total_rows(terminal);
int viewport_rows = rows; // your grid height
int scroll_offset = ghostty_terminal_scroll_offset(terminal);

float bar_h      = (float)viewport_rows / total_rows * window_height;
float bar_y      = (float)scroll_offset / total_rows * window_height;
DrawRectangle(window_width - SCROLLBAR_W, (int)bar_y,
              SCROLLBAR_W, (int)bar_h, GRAY);

Troubleshooting

ProblemFix
Build fails: zig not foundInstall Zig 0.15.x and ensure it's on $PATH
Debug build extremely slowUse Release: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
Terminal renders garbageVerify you're calling ghostty_render_state_begin before iterating cells and ghostty_render_state_end after
Child process not getting resizeCall ioctl(pty_fd, TIOCSWINSZ, &ws) AND kill(child_pid, SIGWINCH) after ghostty_terminal_resize
Kitty keyboard protocol brokenKnown upstream Raylib/GLFW limitation — libghostty-vt supports it correctly but needs richer input events
Colors look wrongCheck cell.fg/cell.bg — libghostty-vt resolves palette to RGB, use those values directly
ghostty_terminal_write crashesEnsure buffer passed is valid and len > 0; never pass NULL

What libghostty-vt Will NOT Do For You

You must implement these yourself:

  • PTY creation and process management
  • Window creation and event loop
  • Font loading and glyph rendering
  • Clipboard read/write (OSC 52 bytes are provided, handling is yours)
  • Tabs, splits, multiple windows
  • Configuration UI

API Reference

Full libghostty-vt C API docs: https://libghostty.tip.ghostty.org/group__render.html