mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
vt: add ghostty_terminal_set for configuring effects callbacks
Add a typed option setter ghostty_terminal_set() following the existing setopt pattern used by the key encoder and render state APIs. This is the first step toward exposing stream_terminal Handler.Effects through the C API. The initial implementation includes a write_pty callback and a shared userdata pointer. The write_pty callback is invoked synchronously during ghostty_terminal_vt_write() when the terminal needs to send a response back to the pty, such as DECRQM mode reports or device status responses. Trampolines are always installed at terminal creation time and no-op when no C callback is set, so callers can configure callbacks at any point without reinitializing the stream. The C callback state is grouped into an internal Effects struct on the TerminalWrapper to simplify adding more callbacks in the future.
This commit is contained in:
@@ -134,6 +134,52 @@ typedef struct {
|
||||
uint64_t len;
|
||||
} GhosttyTerminalScrollbar;
|
||||
|
||||
/**
|
||||
* Callback function type for write_pty.
|
||||
*
|
||||
* Called when the terminal needs to write data back to the pty, for
|
||||
* example in response to a device status report or mode query. The
|
||||
* data is only valid for the duration of the call; callers must copy
|
||||
* it if it needs to persist.
|
||||
*
|
||||
* @param terminal The terminal handle
|
||||
* @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
|
||||
* @param data Pointer to the response bytes
|
||||
* @param len Length of the response in bytes
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef void (*GhosttyTerminalWritePtyFn)(GhosttyTerminal terminal,
|
||||
void* userdata,
|
||||
const uint8_t* data,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* Terminal option identifiers.
|
||||
*
|
||||
* These values are used with ghostty_terminal_set() to configure
|
||||
* terminal callbacks and associated state.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* Opaque userdata pointer passed to all callbacks.
|
||||
*
|
||||
* Input type: void**
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_USERDATA = 0,
|
||||
|
||||
/**
|
||||
* Callback invoked when the terminal needs to write data back
|
||||
* to the pty (e.g. in response to a DECRQM query or device
|
||||
* status report). Set to NULL to ignore such sequences.
|
||||
*
|
||||
* Input type: GhosttyTerminalWritePtyFn*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_WRITE_PTY = 1,
|
||||
} GhosttyTerminalOption;
|
||||
|
||||
/**
|
||||
* Terminal data types.
|
||||
*
|
||||
@@ -290,15 +336,36 @@ GhosttyResult ghostty_terminal_resize(GhosttyTerminal terminal,
|
||||
uint16_t cols,
|
||||
uint16_t rows);
|
||||
|
||||
/**
|
||||
* Set an option on the terminal.
|
||||
*
|
||||
* Configures terminal callbacks and associated state such as the
|
||||
* write_pty callback and userdata pointer. A NULL value pointer
|
||||
* clears the option to its default (NULL/disabled).
|
||||
*
|
||||
* Callbacks are invoked synchronously during ghostty_terminal_vt_write().
|
||||
* Callbacks must not call ghostty_terminal_vt_write() on the same
|
||||
* terminal (no reentrancy).
|
||||
*
|
||||
* @param terminal The terminal handle (may be NULL, in which case this is a no-op)
|
||||
* @param option The option to set
|
||||
* @param value Pointer to the value to set (type depends on the option),
|
||||
* or NULL to clear the option
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
void ghostty_terminal_set(GhosttyTerminal terminal,
|
||||
GhosttyTerminalOption option,
|
||||
const void* value);
|
||||
|
||||
/**
|
||||
* Write VT-encoded data to the terminal for processing.
|
||||
*
|
||||
* Feeds raw bytes through the terminal's VT stream parser, updating
|
||||
* terminal state accordingly. Only read-only sequences are processed;
|
||||
* sequences that require output (queries) are ignored.
|
||||
*
|
||||
* In the future, a callback-based API will be added to allow handling
|
||||
* of output or side effect sequences.
|
||||
* terminal state accordingly. By default, sequences that require output
|
||||
* (queries, device status reports) are silently ignored. Use
|
||||
* ghostty_terminal_set() with GHOSTTY_TERMINAL_OPT_WRITE_PTY to install
|
||||
* a callback that receives response data.
|
||||
*
|
||||
* This never fails. Any erroneous input or errors in processing the
|
||||
* input are logged internally but do not cause this function to fail
|
||||
|
||||
@@ -206,6 +206,7 @@ comptime {
|
||||
@export(&c.terminal_free, .{ .name = "ghostty_terminal_free" });
|
||||
@export(&c.terminal_reset, .{ .name = "ghostty_terminal_reset" });
|
||||
@export(&c.terminal_resize, .{ .name = "ghostty_terminal_resize" });
|
||||
@export(&c.terminal_set, .{ .name = "ghostty_terminal_set" });
|
||||
@export(&c.terminal_vt_write, .{ .name = "ghostty_terminal_vt_write" });
|
||||
@export(&c.terminal_scroll_viewport, .{ .name = "ghostty_terminal_scroll_viewport" });
|
||||
@export(&c.terminal_mode_get, .{ .name = "ghostty_terminal_mode_get" });
|
||||
|
||||
@@ -132,6 +132,7 @@ pub const terminal_new = terminal.new;
|
||||
pub const terminal_free = terminal.free;
|
||||
pub const terminal_reset = terminal.reset;
|
||||
pub const terminal_resize = terminal.resize;
|
||||
pub const terminal_set = terminal.set;
|
||||
pub const terminal_vt_write = terminal.vt_write;
|
||||
pub const terminal_scroll_viewport = terminal.scroll_viewport;
|
||||
pub const terminal_mode_get = terminal.mode_get;
|
||||
|
||||
@@ -16,14 +16,35 @@ const grid_ref_c = @import("grid_ref.zig");
|
||||
const style_c = @import("style.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
const Handler = @import("../stream_terminal.zig").Handler;
|
||||
|
||||
const log = std.log.scoped(.terminal_c);
|
||||
|
||||
/// C function pointer type for the write_pty callback.
|
||||
pub const CWritePtyFn = *const fn (Terminal, ?*anyopaque, [*]const u8, usize) callconv(.c) void;
|
||||
|
||||
/// Wrapper around ZigTerminal that tracks additional state for C API usage,
|
||||
/// such as the persistent VT stream needed to handle escape sequences split
|
||||
/// across multiple vt_write calls.
|
||||
const TerminalWrapper = struct {
|
||||
terminal: *ZigTerminal,
|
||||
stream: Stream,
|
||||
effects: Effects = .{},
|
||||
};
|
||||
|
||||
/// C callback state for terminal effects. Trampolines are always
|
||||
/// installed on the stream handler; they check these fields and
|
||||
/// no-op when the corresponding callback is null.
|
||||
const Effects = struct {
|
||||
userdata: ?*anyopaque = null,
|
||||
write_pty: ?CWritePtyFn = null,
|
||||
|
||||
fn writePtyTrampoline(handler: *Handler, data: [:0]const u8) void {
|
||||
const stream_ptr: *Stream = @fieldParentPtr("handler", handler);
|
||||
const wrapper: *TerminalWrapper = @fieldParentPtr("stream", stream_ptr);
|
||||
const func = wrapper.effects.write_pty orelse return;
|
||||
func(@ptrCast(wrapper), wrapper.effects.userdata, data.ptr, data.len);
|
||||
}
|
||||
};
|
||||
|
||||
/// C: GhosttyTerminal
|
||||
@@ -80,8 +101,10 @@ fn new_(
|
||||
});
|
||||
errdefer t.deinit(alloc);
|
||||
|
||||
// Setup our stream
|
||||
const handler: Stream.Handler = t.vtHandler();
|
||||
// Setup our stream with trampolines always installed so that
|
||||
// setting C callbacks at any time takes effect immediately.
|
||||
var handler: Stream.Handler = t.vtHandler();
|
||||
handler.effects.write_pty = &Effects.writePtyTrampoline;
|
||||
|
||||
wrapper.* = .{
|
||||
.terminal = t,
|
||||
@@ -100,6 +123,53 @@ pub fn vt_write(
|
||||
wrapper.stream.nextSlice(ptr[0..len]);
|
||||
}
|
||||
|
||||
/// C: GhosttyTerminalOption
|
||||
pub const Option = enum(c_int) {
|
||||
userdata = 0,
|
||||
write_pty = 1,
|
||||
|
||||
/// Input type expected for setting the option.
|
||||
pub fn InType(comptime self: Option) type {
|
||||
return switch (self) {
|
||||
.userdata => ?*anyopaque,
|
||||
.write_pty => ?CWritePtyFn,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn set(
|
||||
terminal_: Terminal,
|
||||
option: Option,
|
||||
value: ?*const anyopaque,
|
||||
) callconv(.c) void {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(Option, @intFromEnum(option)) catch {
|
||||
log.warn("terminal_set invalid option value={d}", .{@intFromEnum(option)});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
return switch (option) {
|
||||
inline else => |comptime_option| setTyped(
|
||||
terminal_,
|
||||
comptime_option,
|
||||
@ptrCast(@alignCast(value)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn setTyped(
|
||||
terminal_: Terminal,
|
||||
comptime option: Option,
|
||||
value: ?*const option.InType(),
|
||||
) void {
|
||||
const wrapper = terminal_ orelse return;
|
||||
switch (option) {
|
||||
.userdata => wrapper.effects.userdata = if (value) |v| v.* else null,
|
||||
.write_pty => wrapper.effects.write_pty = if (value) |v| v.* else null,
|
||||
}
|
||||
}
|
||||
|
||||
/// C: GhosttyTerminalScrollViewport
|
||||
pub const ScrollViewport = ZigTerminal.ScrollViewport.C;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user