mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 11:35:48 +00:00
libghostty: expose Kitty image configs, decode png callback from C API (#12144)
This exposes the APIs necessary to enable Kitty image protocol parsing and state from the C API. * You can now set the PNG decoder via the `ghostty_sys_set` API. * You can set Kitty image configs via `ghostty_terminal_set` API. * An example showing this working has been added. * **You cannot yet query Kitty images for metadata or rendering.** I'm going to follow that up in a separate PR.
This commit is contained in:
18
example/c-vt-kitty-graphics/README.md
Normal file
18
example/c-vt-kitty-graphics/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Example: `ghostty-vt` Kitty Graphics Protocol
|
||||
|
||||
This contains a simple example of how to use the system interface
|
||||
(`ghostty_sys_set`) to install a PNG decoder callback, then send
|
||||
a Kitty Graphics Protocol image via `ghostty_terminal_vt_write`.
|
||||
|
||||
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
|
||||
```
|
||||
42
example/c-vt-kitty-graphics/build.zig
Normal file
42
example/c-vt-kitty-graphics/build.zig
Normal 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_kitty_graphics",
|
||||
.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);
|
||||
}
|
||||
24
example/c-vt-kitty-graphics/build.zig.zon
Normal file
24
example/c-vt-kitty-graphics/build.zig.zon
Normal file
@@ -0,0 +1,24 @@
|
||||
.{
|
||||
.name = .c_vt_kitty_graphics,
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0x432d40ecc8f15589,
|
||||
.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",
|
||||
},
|
||||
}
|
||||
125
example/c-vt-kitty-graphics/src/main.c
Normal file
125
example/c-vt-kitty-graphics/src/main.c
Normal file
@@ -0,0 +1,125 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ghostty/vt.h>
|
||||
|
||||
//! [kitty-graphics-decode-png]
|
||||
/**
|
||||
* Minimal PNG decoder callback for the sys interface.
|
||||
*
|
||||
* A real implementation would use a PNG library (libpng, stb_image, etc.)
|
||||
* to decode the PNG data. This example uses a hardcoded 1x1 red pixel
|
||||
* since we know exactly what image we're sending.
|
||||
*
|
||||
* WARNING: This is only an example for providing a callback, it DOES NOT
|
||||
* actually decode the PNG it is passed. It hardcodes a response.
|
||||
*/
|
||||
bool decode_png(void* userdata,
|
||||
const GhosttyAllocator* allocator,
|
||||
const uint8_t* data,
|
||||
size_t data_len,
|
||||
GhosttySysImage* out) {
|
||||
int* count = (int*)userdata;
|
||||
(*count)++;
|
||||
printf(" decode_png called (size=%zu, call #%d)\n", data_len, *count);
|
||||
|
||||
/* Allocate RGBA pixel data through the provided allocator. */
|
||||
const size_t pixel_len = 4; /* 1x1 RGBA */
|
||||
uint8_t* pixels = ghostty_alloc(allocator, pixel_len);
|
||||
if (!pixels) return false;
|
||||
|
||||
/* Fill with red (R=255, G=0, B=0, A=255). */
|
||||
pixels[0] = 255;
|
||||
pixels[1] = 0;
|
||||
pixels[2] = 0;
|
||||
pixels[3] = 255;
|
||||
|
||||
out->width = 1;
|
||||
out->height = 1;
|
||||
out->data = pixels;
|
||||
out->data_len = pixel_len;
|
||||
return true;
|
||||
}
|
||||
//! [kitty-graphics-decode-png]
|
||||
|
||||
//! [kitty-graphics-write-pty]
|
||||
/**
|
||||
* write_pty callback to capture terminal responses.
|
||||
*
|
||||
* The Kitty graphics protocol sends an APC response back to the pty
|
||||
* when an image is loaded (unless suppressed with q=2).
|
||||
*/
|
||||
void on_write_pty(GhosttyTerminal terminal,
|
||||
void* userdata,
|
||||
const uint8_t* data,
|
||||
size_t len) {
|
||||
(void)terminal;
|
||||
(void)userdata;
|
||||
printf(" response (%zu bytes): ", len);
|
||||
fwrite(data, 1, len, stdout);
|
||||
printf("\n");
|
||||
}
|
||||
//! [kitty-graphics-write-pty]
|
||||
|
||||
//! [kitty-graphics-main]
|
||||
int main() {
|
||||
/* Install the PNG decoder via the sys interface. */
|
||||
int decode_count = 0;
|
||||
ghostty_sys_set(GHOSTTY_SYS_OPT_USERDATA, &decode_count);
|
||||
ghostty_sys_set(GHOSTTY_SYS_OPT_DECODE_PNG, (const void*)decode_png);
|
||||
|
||||
/* Create a terminal with Kitty graphics enabled. */
|
||||
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;
|
||||
}
|
||||
|
||||
/* Set a storage limit to enable Kitty graphics. */
|
||||
uint64_t storage_limit = 64 * 1024 * 1024; /* 64 MiB */
|
||||
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT,
|
||||
&storage_limit);
|
||||
|
||||
/* Install write_pty to see the protocol response. */
|
||||
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_WRITE_PTY,
|
||||
(const void*)on_write_pty);
|
||||
|
||||
/*
|
||||
* Send a Kitty graphics command with an inline 1x1 PNG image.
|
||||
*
|
||||
* The escape sequence is:
|
||||
* ESC _G a=T,f=100,q=1; <base64 PNG data> ESC \
|
||||
*
|
||||
* Where:
|
||||
* a=T — transmit and display
|
||||
* f=100 — PNG format
|
||||
* q=1 — request a response (q=0 would suppress it)
|
||||
*/
|
||||
printf("Sending Kitty graphics PNG image:\n");
|
||||
const char* kitty_cmd =
|
||||
"\x1b_Ga=T,f=100,q=1;"
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAA"
|
||||
"DUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg=="
|
||||
"\x1b\\";
|
||||
ghostty_terminal_vt_write(terminal, (const uint8_t*)kitty_cmd,
|
||||
strlen(kitty_cmd));
|
||||
|
||||
printf("PNG decode calls: %d\n", decode_count);
|
||||
|
||||
/* Clean up. */
|
||||
ghostty_terminal_free(terminal);
|
||||
|
||||
/* Clear the sys callbacks. */
|
||||
ghostty_sys_set(GHOSTTY_SYS_OPT_DECODE_PNG, NULL);
|
||||
ghostty_sys_set(GHOSTTY_SYS_OPT_USERDATA, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
//! [kitty-graphics-main]
|
||||
@@ -98,6 +98,11 @@
|
||||
* grid refs to inspect cell codepoints, row wrap state, and cell styles.
|
||||
*/
|
||||
|
||||
/** @example c-vt-kitty-graphics/src/main.c
|
||||
* This example demonstrates how to use the system interface to install a
|
||||
* PNG decoder callback and send a Kitty Graphics Protocol image.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_H
|
||||
#define GHOSTTY_VT_H
|
||||
|
||||
@@ -118,6 +123,7 @@ extern "C" {
|
||||
#include <ghostty/vt/osc.h>
|
||||
#include <ghostty/vt/sgr.h>
|
||||
#include <ghostty/vt/style.h>
|
||||
#include <ghostty/vt/sys.h>
|
||||
#include <ghostty/vt/key.h>
|
||||
#include <ghostty/vt/modes.h>
|
||||
#include <ghostty/vt/mouse.h>
|
||||
|
||||
133
include/ghostty/vt/sys.h
Normal file
133
include/ghostty/vt/sys.h
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @file sys.h
|
||||
*
|
||||
* System interface - runtime-swappable implementations for external dependencies.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_SYS_H
|
||||
#define GHOSTTY_VT_SYS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
|
||||
/** @defgroup sys System Interface
|
||||
*
|
||||
* Runtime-swappable function pointers for operations that depend on
|
||||
* external implementations (e.g. image decoding).
|
||||
*
|
||||
* These are process-global settings that must be configured at startup
|
||||
* before any terminal functionality that depends on them is used.
|
||||
* Setting these enables various optional features of the terminal. For
|
||||
* example, setting a PNG decoder enables PNG image support in the Kitty
|
||||
* Graphics Protocol.
|
||||
*
|
||||
* Use ghostty_sys_set() with a `GhosttySysOption` to install or clear
|
||||
* an implementation. Passing NULL as the value clears the implementation
|
||||
* and disables the corresponding feature.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ### Defining a PNG decode callback
|
||||
* @snippet c-vt-kitty-graphics/src/main.c kitty-graphics-decode-png
|
||||
*
|
||||
* ### Installing the callback and sending a PNG image
|
||||
* @snippet c-vt-kitty-graphics/src/main.c kitty-graphics-main
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Result of decoding an image.
|
||||
*
|
||||
* The `data` buffer must be allocated through the allocator provided to
|
||||
* the decode callback. The library takes ownership and will free it
|
||||
* with the same allocator.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Image width in pixels. */
|
||||
uint32_t width;
|
||||
|
||||
/** Image height in pixels. */
|
||||
uint32_t height;
|
||||
|
||||
/** Pointer to the decoded RGBA pixel data. */
|
||||
uint8_t* data;
|
||||
|
||||
/** Length of the pixel data in bytes. */
|
||||
size_t data_len;
|
||||
} GhosttySysImage;
|
||||
|
||||
/**
|
||||
* Callback type for PNG decoding.
|
||||
*
|
||||
* Decodes raw PNG data into RGBA pixels. The output pixel data must be
|
||||
* allocated through the provided allocator. The library takes ownership
|
||||
* of the buffer and will free it with the same allocator.
|
||||
*
|
||||
* @param userdata The userdata pointer set via GHOSTTY_SYS_OPT_USERDATA
|
||||
* @param allocator The allocator to use for the output pixel buffer
|
||||
* @param data Pointer to the raw PNG data
|
||||
* @param data_len Length of the raw PNG data in bytes
|
||||
* @param[out] out On success, filled with the decoded image
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
typedef bool (*GhosttySysDecodePngFn)(
|
||||
void* userdata,
|
||||
const GhosttyAllocator* allocator,
|
||||
const uint8_t* data,
|
||||
size_t data_len,
|
||||
GhosttySysImage* out);
|
||||
|
||||
/**
|
||||
* System option identifiers for ghostty_sys_set().
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* Set the userdata pointer passed to all sys callbacks.
|
||||
*
|
||||
* Input type: void* (or NULL)
|
||||
*/
|
||||
GHOSTTY_SYS_OPT_USERDATA = 0,
|
||||
|
||||
/**
|
||||
* Set the PNG decode function.
|
||||
*
|
||||
* When set, the terminal can accept PNG images via the Kitty
|
||||
* Graphics Protocol. When cleared (NULL value), PNG decoding is
|
||||
* unsupported and PNG image data will be rejected.
|
||||
*
|
||||
* Input type: GhosttySysDecodePngFn (function pointer, or NULL)
|
||||
*/
|
||||
GHOSTTY_SYS_OPT_DECODE_PNG = 1,
|
||||
} GhosttySysOption;
|
||||
|
||||
/**
|
||||
* Set a system-level option.
|
||||
*
|
||||
* Configures a process-global implementation function. These should be
|
||||
* set once at startup before using any terminal functionality that
|
||||
* depends on them.
|
||||
*
|
||||
* @param option The option to set
|
||||
* @param value Pointer to the value (type depends on the option),
|
||||
* or NULL to clear it
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the
|
||||
* option is not recognized
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_sys_set(GhosttySysOption option,
|
||||
const void* value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif /* GHOSTTY_VT_SYS_H */
|
||||
@@ -534,6 +534,49 @@ typedef enum {
|
||||
* Input type: GhosttyColorRgb[256]*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_COLOR_PALETTE = 14,
|
||||
|
||||
/**
|
||||
* Set the Kitty image storage limit in bytes.
|
||||
*
|
||||
* Applied to all initialized screens (primary and alternate).
|
||||
* A value of zero disables the Kitty graphics protocol entirely,
|
||||
* deleting all stored images and placements. A NULL value pointer
|
||||
* is equivalent to zero (disables). Has no effect when Kitty graphics
|
||||
* are disabled at build time.
|
||||
*
|
||||
* Input type: uint64_t*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT = 15,
|
||||
|
||||
/**
|
||||
* Enable or disable Kitty image loading via the file medium.
|
||||
*
|
||||
* A NULL value pointer is a no-op. Has no effect when Kitty graphics
|
||||
* are disabled at build time.
|
||||
*
|
||||
* Input type: bool*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_FILE = 16,
|
||||
|
||||
/**
|
||||
* Enable or disable Kitty image loading via the temporary file medium.
|
||||
*
|
||||
* A NULL value pointer is a no-op. Has no effect when Kitty graphics
|
||||
* are disabled at build time.
|
||||
*
|
||||
* Input type: bool*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_TEMP_FILE = 17,
|
||||
|
||||
/**
|
||||
* Enable or disable Kitty image loading via the shared memory medium.
|
||||
*
|
||||
* A NULL value pointer is a no-op. Has no effect when Kitty graphics
|
||||
* are disabled at build time.
|
||||
*
|
||||
* Input type: bool*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_SHARED_MEM = 18,
|
||||
} GhosttyTerminalOption;
|
||||
|
||||
/**
|
||||
@@ -756,6 +799,46 @@ typedef enum {
|
||||
* Output type: GhosttyColorRgb[256] *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_COLOR_PALETTE_DEFAULT = 25,
|
||||
|
||||
/**
|
||||
* The Kitty image storage limit in bytes for the active screen.
|
||||
*
|
||||
* A value of zero means the Kitty graphics protocol is disabled.
|
||||
* Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
|
||||
*
|
||||
* Output type: uint64_t *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_STORAGE_LIMIT = 26,
|
||||
|
||||
/**
|
||||
* Whether the file medium is enabled for Kitty image loading on the
|
||||
* active screen.
|
||||
*
|
||||
* Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
|
||||
*
|
||||
* Output type: bool *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_FILE = 27,
|
||||
|
||||
/**
|
||||
* Whether the temporary file medium is enabled for Kitty image loading
|
||||
* on the active screen.
|
||||
*
|
||||
* Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
|
||||
*
|
||||
* Output type: bool *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_TEMP_FILE = 28,
|
||||
|
||||
/**
|
||||
* Whether the shared memory medium is enabled for Kitty image loading
|
||||
* on the active screen.
|
||||
*
|
||||
* Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
|
||||
*
|
||||
* Output type: bool *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_SHARED_MEM = 29,
|
||||
} GhosttyTerminalData;
|
||||
|
||||
/**
|
||||
|
||||
@@ -189,6 +189,7 @@ comptime {
|
||||
@export(&c.size_report_encode, .{ .name = "ghostty_size_report_encode" });
|
||||
@export(&c.style_default, .{ .name = "ghostty_style_default" });
|
||||
@export(&c.style_is_default, .{ .name = "ghostty_style_is_default" });
|
||||
@export(&c.sys_set, .{ .name = "ghostty_sys_set" });
|
||||
@export(&c.cell_get, .{ .name = "ghostty_cell_get" });
|
||||
@export(&c.row_get, .{ .name = "ghostty_row_get" });
|
||||
@export(&c.color_rgb_get, .{ .name = "ghostty_color_rgb_get" });
|
||||
|
||||
@@ -22,6 +22,7 @@ pub const row = @import("row.zig");
|
||||
pub const sgr = @import("sgr.zig");
|
||||
pub const size_report = @import("size_report.zig");
|
||||
pub const style = @import("style.zig");
|
||||
pub const sys = @import("sys.zig");
|
||||
pub const terminal = @import("terminal.zig");
|
||||
|
||||
// The full C API, unexported.
|
||||
@@ -132,6 +133,8 @@ pub const row_get = row.get;
|
||||
pub const style_default = style.default_style;
|
||||
pub const style_is_default = style.style_is_default;
|
||||
|
||||
pub const sys_set = sys.set;
|
||||
|
||||
pub const terminal_new = terminal.new;
|
||||
pub const terminal_free = terminal.free;
|
||||
pub const terminal_reset = terminal.reset;
|
||||
@@ -173,6 +176,7 @@ test {
|
||||
_ = sgr;
|
||||
_ = size_report;
|
||||
_ = style;
|
||||
_ = sys;
|
||||
_ = terminal;
|
||||
_ = types;
|
||||
|
||||
|
||||
137
src/terminal/c/sys.zig
Normal file
137
src/terminal/c/sys.zig
Normal file
@@ -0,0 +1,137 @@
|
||||
const std = @import("std");
|
||||
const lib = @import("../lib.zig");
|
||||
const CAllocator = lib.alloc.Allocator;
|
||||
const terminal_sys = @import("../sys.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
/// C: GhosttySysImage
|
||||
pub const Image = extern struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
data: ?[*]u8,
|
||||
data_len: usize,
|
||||
};
|
||||
|
||||
/// C: GhosttySysDecodePngFn
|
||||
pub const DecodePngFn = *const fn (
|
||||
?*anyopaque,
|
||||
*const CAllocator,
|
||||
[*]const u8,
|
||||
usize,
|
||||
*Image,
|
||||
) callconv(lib.calling_conv) bool;
|
||||
|
||||
/// C: GhosttySysOption
|
||||
pub const Option = enum(c_int) {
|
||||
userdata = 0,
|
||||
decode_png = 1,
|
||||
|
||||
pub fn InType(comptime self: Option) type {
|
||||
return switch (self) {
|
||||
.userdata => ?*const anyopaque,
|
||||
.decode_png => ?DecodePngFn,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Global state for the sys interface so we can call through to the C
|
||||
/// callbacks from Zig.
|
||||
const Global = struct {
|
||||
userdata: ?*anyopaque = null,
|
||||
decode_png: ?DecodePngFn = null,
|
||||
};
|
||||
|
||||
/// Global state for the C sys interface.
|
||||
var global: Global = .{};
|
||||
|
||||
/// Zig-compatible wrapper that calls through to the stored C callback.
|
||||
/// The C callback allocates the pixel data through the provided allocator,
|
||||
/// so we can take ownership directly.
|
||||
fn decodePngWrapper(
|
||||
alloc: std.mem.Allocator,
|
||||
data: []const u8,
|
||||
) terminal_sys.DecodeError!terminal_sys.Image {
|
||||
const func = global.decode_png orelse return error.InvalidData;
|
||||
|
||||
const c_alloc = CAllocator.fromZig(&alloc);
|
||||
var out: Image = undefined;
|
||||
if (!func(global.userdata, &c_alloc, data.ptr, data.len, &out)) return error.InvalidData;
|
||||
|
||||
const result_data = out.data orelse return error.InvalidData;
|
||||
|
||||
return .{
|
||||
.width = out.width,
|
||||
.height = out.height,
|
||||
.data = result_data[0..out.data_len],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set(
|
||||
option: Option,
|
||||
value: ?*const anyopaque,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(Option, @intFromEnum(option)) catch {
|
||||
return .invalid_value;
|
||||
};
|
||||
}
|
||||
|
||||
return switch (option) {
|
||||
inline else => |comptime_option| setTyped(
|
||||
comptime_option,
|
||||
@ptrCast(@alignCast(value)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn setTyped(
|
||||
comptime option: Option,
|
||||
value: option.InType(),
|
||||
) Result {
|
||||
switch (option) {
|
||||
.userdata => global.userdata = @constCast(value),
|
||||
.decode_png => {
|
||||
global.decode_png = value;
|
||||
terminal_sys.decode_png = if (value != null) &decodePngWrapper else null;
|
||||
},
|
||||
}
|
||||
return .success;
|
||||
}
|
||||
|
||||
test "set decode_png with null clears" {
|
||||
// Start from a known state.
|
||||
global.decode_png = null;
|
||||
terminal_sys.decode_png = null;
|
||||
|
||||
try std.testing.expectEqual(Result.success, set(.decode_png, null));
|
||||
try std.testing.expect(terminal_sys.decode_png == null);
|
||||
}
|
||||
|
||||
test "set decode_png installs wrapper" {
|
||||
const S = struct {
|
||||
fn decode(_: ?*anyopaque, _: *const CAllocator, _: [*]const u8, _: usize, out: *Image) callconv(lib.calling_conv) bool {
|
||||
out.* = .{ .width = 1, .height = 1, .data = null, .data_len = 0 };
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
try std.testing.expectEqual(Result.success, set(
|
||||
.decode_png,
|
||||
@ptrCast(&S.decode),
|
||||
));
|
||||
try std.testing.expect(terminal_sys.decode_png != null);
|
||||
|
||||
// Clear it again.
|
||||
try std.testing.expectEqual(Result.success, set(.decode_png, null));
|
||||
try std.testing.expect(terminal_sys.decode_png == null);
|
||||
}
|
||||
|
||||
test "set userdata" {
|
||||
var data: u32 = 42;
|
||||
try std.testing.expectEqual(Result.success, set(.userdata, @ptrCast(&data)));
|
||||
try std.testing.expect(global.userdata == @as(?*anyopaque, @ptrCast(&data)));
|
||||
|
||||
// Clear it.
|
||||
try std.testing.expectEqual(Result.success, set(.userdata, null));
|
||||
try std.testing.expect(global.userdata == null);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const build_options = @import("terminal_options");
|
||||
const lib = @import("../lib.zig");
|
||||
const CAllocator = lib.alloc.Allocator;
|
||||
const ZigTerminal = @import("../Terminal.zig");
|
||||
@@ -304,6 +305,10 @@ pub const Option = enum(c_int) {
|
||||
color_background = 12,
|
||||
color_cursor = 13,
|
||||
color_palette = 14,
|
||||
kitty_image_storage_limit = 15,
|
||||
kitty_image_medium_file = 16,
|
||||
kitty_image_medium_temp_file = 17,
|
||||
kitty_image_medium_shared_mem = 18,
|
||||
|
||||
/// Input type expected for setting the option.
|
||||
pub fn InType(comptime self: Option) type {
|
||||
@@ -320,6 +325,11 @@ pub const Option = enum(c_int) {
|
||||
.title, .pwd => ?*const lib.String,
|
||||
.color_foreground, .color_background, .color_cursor => ?*const color.RGB.C,
|
||||
.color_palette => ?*const color.PaletteC,
|
||||
.kitty_image_storage_limit => ?*const u64,
|
||||
.kitty_image_medium_file,
|
||||
.kitty_image_medium_temp_file,
|
||||
.kitty_image_medium_shared_mem,
|
||||
=> ?*const bool,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -388,6 +398,32 @@ fn setTyped(
|
||||
);
|
||||
wrapper.terminal.flags.dirty.palette = true;
|
||||
},
|
||||
.kitty_image_storage_limit => {
|
||||
if (comptime !build_options.kitty_graphics) return .success;
|
||||
const limit: usize = if (value) |v| @intCast(v.*) else 0;
|
||||
var it = wrapper.terminal.screens.all.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const screen = entry.value.*;
|
||||
screen.kitty_images.setLimit(screen.alloc, screen, limit) catch return .out_of_memory;
|
||||
}
|
||||
},
|
||||
.kitty_image_medium_file,
|
||||
.kitty_image_medium_temp_file,
|
||||
.kitty_image_medium_shared_mem,
|
||||
=> {
|
||||
if (comptime !build_options.kitty_graphics) return .success;
|
||||
const val = (value orelse return .success).*;
|
||||
var it = wrapper.terminal.screens.all.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const screen = entry.value.*;
|
||||
switch (option) {
|
||||
.kitty_image_medium_file => screen.kitty_images.image_limits.file = val,
|
||||
.kitty_image_medium_temp_file => screen.kitty_images.image_limits.temporary_file = val,
|
||||
.kitty_image_medium_shared_mem => screen.kitty_images.image_limits.shared_memory = val,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
return .success;
|
||||
}
|
||||
@@ -513,6 +549,10 @@ pub const TerminalData = enum(c_int) {
|
||||
color_background_default = 23,
|
||||
color_cursor_default = 24,
|
||||
color_palette_default = 25,
|
||||
kitty_image_storage_limit = 26,
|
||||
kitty_image_medium_file = 27,
|
||||
kitty_image_medium_temp_file = 28,
|
||||
kitty_image_medium_shared_mem = 29,
|
||||
|
||||
/// Output type expected for querying the data of the given kind.
|
||||
pub fn OutType(comptime self: TerminalData) type {
|
||||
@@ -535,6 +575,11 @@ pub const TerminalData = enum(c_int) {
|
||||
.color_cursor_default,
|
||||
=> color.RGB.C,
|
||||
.color_palette, .color_palette_default => color.PaletteC,
|
||||
.kitty_image_storage_limit => u64,
|
||||
.kitty_image_medium_file,
|
||||
.kitty_image_medium_temp_file,
|
||||
.kitty_image_medium_shared_mem,
|
||||
=> bool,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -603,6 +648,22 @@ fn getTyped(
|
||||
.color_cursor_default => out.* = (t.colors.cursor.default orelse return .no_value).cval(),
|
||||
.color_palette => out.* = color.paletteCval(&t.colors.palette.current),
|
||||
.color_palette_default => out.* = color.paletteCval(&t.colors.palette.original),
|
||||
.kitty_image_storage_limit => {
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
out.* = @intCast(t.screens.active.kitty_images.total_limit);
|
||||
},
|
||||
.kitty_image_medium_file => {
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
out.* = t.screens.active.kitty_images.image_limits.file;
|
||||
},
|
||||
.kitty_image_medium_temp_file => {
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
out.* = t.screens.active.kitty_images.image_limits.temporary_file;
|
||||
},
|
||||
.kitty_image_medium_shared_mem => {
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
out.* = t.screens.active.kitty_images.image_limits.shared_memory;
|
||||
},
|
||||
}
|
||||
|
||||
return .success;
|
||||
|
||||
Reference in New Issue
Block a user