vt: expose terminal default colors via C API

Add set/get support for foreground, background, cursor, and palette
default colors through ghostty_terminal_set and ghostty_terminal_get.

Four new set options (COLOR_FOREGROUND, COLOR_BACKGROUND, COLOR_CURSOR,
COLOR_PALETTE) write directly to the terminal color defaults. Passing
NULL clears the value for RGB colors or resets the palette to the
built-in default. All set operations mark the palette dirty flag for
the renderer.

Eight new get data types retrieve either the effective color (override
or default, via DynamicRGB.get) or the default color only (ignoring
any OSC overrides). Effective getters for RGB colors return the new
NO_VALUE result code when no color is configured. The palette getters
return the current or original palette respectively.

Adds the GHOSTTY_NO_VALUE result code for cases where a queried value
is simply not configured, distinct from GHOSTTY_INVALID_VALUE which
indicates a caller error.
This commit is contained in:
Mitchell Hashimoto
2026-03-26 09:37:55 -07:00
parent 0752320d3b
commit 945920a186
11 changed files with 674 additions and 7 deletions

View File

@@ -0,0 +1,18 @@
# Example: `ghostty-vt` Terminal Colors
This contains a simple example of how to set default terminal colors,
read effective and default color values, and observe how OSC overrides
layer on top of defaults using the `ghostty-vt` C library.
This uses a `build.zig` and `Zig` to build the C program so that we
can reuse a lot of our build logic and depend directly on our source
tree, but Ghostty emits a standard C library that can be used with any
C tooling.
## Usage
Run the program:
```shell-session
zig build run
```

View File

@@ -0,0 +1,42 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const run_step = b.step("run", "Run the app");
const exe_mod = b.createModule(.{
.target = target,
.optimize = optimize,
});
exe_mod.addCSourceFiles(.{
.root = b.path("src"),
.files = &.{"main.c"},
});
// You'll want to use a lazy dependency here so that ghostty is only
// downloaded if you actually need it.
if (b.lazyDependency("ghostty", .{
// Setting simd to false will force a pure static build that
// doesn't even require libc, but it has a significant performance
// penalty. If your embedding app requires libc anyway, you should
// always keep simd enabled.
// .simd = false,
})) |dep| {
exe_mod.linkLibrary(dep.artifact("ghostty-vt"));
}
// Exe
const exe = b.addExecutable(.{
.name = "c_vt_colors",
.root_module = exe_mod,
});
b.installArtifact(exe);
// Run
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
run_step.dependOn(&run_cmd.step);
}

View File

@@ -0,0 +1,24 @@
.{
.name = .c_vt_colors,
.version = "0.0.0",
.fingerprint = 0xe7ec4247f16d4fce,
.minimum_zig_version = "0.15.1",
.dependencies = .{
// Ghostty dependency. In reality, you'd probably use a URL-based
// dependency like the one showed (and commented out) below this one.
// We use a path dependency here for simplicity and to ensure our
// examples always test against the source they're bundled with.
.ghostty = .{ .path = "../../" },
// Example of what a URL-based dependency looks like:
// .ghostty = .{
// .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz",
// .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s",
// },
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

View File

@@ -0,0 +1,121 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <ghostty/vt.h>
//! [colors-set-defaults]
/// Set up a dark color theme with custom palette entries.
void set_color_theme(GhosttyTerminal terminal) {
// Set default foreground (light gray) and background (dark)
GhosttyColorRgb fg = { .r = 0xDD, .g = 0xDD, .b = 0xDD };
GhosttyColorRgb bg = { .r = 0x1E, .g = 0x1E, .b = 0x2E };
GhosttyColorRgb cursor = { .r = 0xF5, .g = 0xE0, .b = 0xDC };
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_COLOR_FOREGROUND, &fg);
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_COLOR_BACKGROUND, &bg);
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_COLOR_CURSOR, &cursor);
// Set a custom palette — start from the built-in default and override
// the first 8 entries with a custom dark theme.
GhosttyColorRgb palette[256];
ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_COLOR_PALETTE, palette);
palette[GHOSTTY_COLOR_NAMED_BLACK] = (GhosttyColorRgb){ 0x45, 0x47, 0x5A };
palette[GHOSTTY_COLOR_NAMED_RED] = (GhosttyColorRgb){ 0xF3, 0x8B, 0xA8 };
palette[GHOSTTY_COLOR_NAMED_GREEN] = (GhosttyColorRgb){ 0xA6, 0xE3, 0xA1 };
palette[GHOSTTY_COLOR_NAMED_YELLOW] = (GhosttyColorRgb){ 0xF9, 0xE2, 0xAF };
palette[GHOSTTY_COLOR_NAMED_BLUE] = (GhosttyColorRgb){ 0x89, 0xB4, 0xFA };
palette[GHOSTTY_COLOR_NAMED_MAGENTA] = (GhosttyColorRgb){ 0xF5, 0xC2, 0xE7 };
palette[GHOSTTY_COLOR_NAMED_CYAN] = (GhosttyColorRgb){ 0x94, 0xE2, 0xD5 };
palette[GHOSTTY_COLOR_NAMED_WHITE] = (GhosttyColorRgb){ 0xBA, 0xC2, 0xDE };
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_COLOR_PALETTE, palette);
}
//! [colors-set-defaults]
//! [colors-read]
/// Print the effective and default values for a color, showing how
/// OSC overrides layer on top of defaults.
void print_color(GhosttyTerminal terminal,
const char* name,
GhosttyTerminalData effective_data,
GhosttyTerminalData default_data) {
GhosttyColorRgb color;
GhosttyResult res = ghostty_terminal_get(terminal, effective_data, &color);
if (res == GHOSTTY_SUCCESS) {
printf(" %-12s effective: #%02X%02X%02X", name, color.r, color.g, color.b);
} else {
printf(" %-12s effective: (not set)", name);
}
res = ghostty_terminal_get(terminal, default_data, &color);
if (res == GHOSTTY_SUCCESS) {
printf(" default: #%02X%02X%02X\n", color.r, color.g, color.b);
} else {
printf(" default: (not set)\n");
}
}
void print_all_colors(GhosttyTerminal terminal, const char* label) {
printf("%s:\n", label);
print_color(terminal, "foreground",
GHOSTTY_TERMINAL_DATA_COLOR_FOREGROUND,
GHOSTTY_TERMINAL_DATA_COLOR_FOREGROUND_DEFAULT);
print_color(terminal, "background",
GHOSTTY_TERMINAL_DATA_COLOR_BACKGROUND,
GHOSTTY_TERMINAL_DATA_COLOR_BACKGROUND_DEFAULT);
print_color(terminal, "cursor",
GHOSTTY_TERMINAL_DATA_COLOR_CURSOR,
GHOSTTY_TERMINAL_DATA_COLOR_CURSOR_DEFAULT);
// Show palette index 0 (black) as an example
GhosttyColorRgb palette[256];
ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_COLOR_PALETTE, palette);
printf(" %-12s effective: #%02X%02X%02X", "palette[0]",
palette[0].r, palette[0].g, palette[0].b);
ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_COLOR_PALETTE_DEFAULT,
palette);
printf(" default: #%02X%02X%02X\n", palette[0].r, palette[0].g, palette[0].b);
}
//! [colors-read]
//! [colors-main]
int main() {
// Create a terminal
GhosttyTerminal terminal = NULL;
GhosttyTerminalOptions opts = {
.cols = 80,
.rows = 24,
.max_scrollback = 0,
};
if (ghostty_terminal_new(NULL, &terminal, opts) != GHOSTTY_SUCCESS) {
fprintf(stderr, "Failed to create terminal\n");
return 1;
}
// Before setting any colors, everything is unset
print_all_colors(terminal, "Before setting defaults");
// Set our color theme defaults
set_color_theme(terminal);
print_all_colors(terminal, "\nAfter setting defaults");
// Simulate an OSC override (e.g. a program running inside the
// terminal changes the foreground via OSC 10)
const char* osc_fg = "\x1B]10;rgb:FF/00/00\x1B\\";
ghostty_terminal_vt_write(terminal, (const uint8_t*)osc_fg,
strlen(osc_fg));
print_all_colors(terminal, "\nAfter OSC foreground override");
// Clear the foreground default — the OSC override is still active
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_COLOR_FOREGROUND, NULL);
print_all_colors(terminal, "\nAfter clearing foreground default");
ghostty_terminal_free(terminal);
return 0;
}
//! [colors-main]