mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 21:40:29 +00:00
libghostty: expose sys interface to C API
The terminal sys module provides runtime-swappable function pointers for operations that depend on external implementations (e.g. PNG decoding). This exposes that functionality through the C API via a ghostty_sys_set() function, modeled after the ghostty_terminal_set() enum-based option pattern. Embedders can install a PNG decode callback to enable Kitty Graphics Protocol PNG support. The callback receives a userdata pointer (set via GHOSTTY_SYS_OPT_USERDATA) and a GhosttyAllocator that must be used to allocate the returned pixel data, since the library takes ownership of the buffer. Passing NULL clears the callback and disables the feature.
This commit is contained in:
@@ -118,6 +118,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>
|
||||
|
||||
125
include/ghostty/vt/sys.h
Normal file
125
include/ghostty/vt/sys.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#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 */
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user