aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/terminal/c/sys.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/terminal/c/sys.zig
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/terminal/c/sys.zig')
-rw-r--r--vendor/libghostty-vt/src/terminal/c/sys.zig136
1 files changed, 86 insertions, 50 deletions
diff --git a/vendor/libghostty-vt/src/terminal/c/sys.zig b/vendor/libghostty-vt/src/terminal/c/sys.zig
index c4b2b17f..f622e8fd 100644
--- a/vendor/libghostty-vt/src/terminal/c/sys.zig
+++ b/vendor/libghostty-vt/src/terminal/c/sys.zig
@@ -3,6 +3,7 @@ const builtin = @import("builtin");
const lib = @import("../lib.zig");
const CAllocator = lib.alloc.Allocator;
const terminal_sys = @import("../sys.zig");
+const stderr = @import("../../os/stderr.zig");
const Result = @import("result.zig").Result;
/// C: GhosttySysImage
@@ -22,6 +23,13 @@ pub const DecodePngFn = *const fn (
*Image,
) callconv(lib.calling_conv) bool;
+/// C: GhosttySysRandomSecureFn
+pub const RandomSecureFn = *const fn (
+ ?*anyopaque,
+ [*]u8,
+ usize,
+) callconv(lib.calling_conv) bool;
+
/// C: GhosttySysLogLevel
pub const LogLevel = enum(c_int) {
@"error" = 0,
@@ -54,12 +62,14 @@ pub const Option = enum(c_int) {
userdata = 0,
decode_png = 1,
log = 2,
+ random_secure = 3,
pub fn InType(comptime self: Option) type {
return switch (self) {
.userdata => ?*const anyopaque,
.decode_png => ?DecodePngFn,
.log => ?LogFn,
+ .random_secure => ?RandomSecureFn,
};
}
};
@@ -70,6 +80,7 @@ const Global = struct {
userdata: ?*anyopaque = null,
decode_png: ?DecodePngFn = null,
log: ?LogFn = null,
+ random_secure: ?RandomSecureFn = null,
};
/// Global state for the C sys interface.
@@ -86,7 +97,7 @@ fn decodePngWrapper(
const c_alloc = CAllocator.fromZig(&alloc);
var out: Image = undefined;
- if (!func(global.userdata, &c_alloc, data.ptr, data.len, &out)) return error.InvalidData;
+ if (!func(global.userdata, &c_alloc, lib.String.init(data).ptr, data.len, &out)) return error.InvalidData;
const result_data = out.data orelse return error.InvalidData;
@@ -97,12 +108,18 @@ fn decodePngWrapper(
};
}
+/// Zig-compatible wrapper that calls through to the stored C callback.
+fn randomSecureWrapper(buffer: []u8) terminal_sys.RandomSecureError!void {
+ const func = global.random_secure orelse return error.EntropyUnavailable;
+ if (!func(global.userdata, buffer.ptr, buffer.len)) return error.EntropyUnavailable;
+}
+
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 {
+ _ = std.enums.fromInt(Option, @intFromEnum(option)) orelse {
return .invalid_value;
};
}
@@ -126,6 +143,10 @@ fn setTyped(
terminal_sys.decode_png = if (value != null) &decodePngWrapper else null;
},
.log => global.log = value,
+ .random_secure => {
+ global.random_secure = value;
+ terminal_sys.random_secure = if (value != null) &randomSecureWrapper else null;
+ },
}
return .success;
}
@@ -146,37 +167,30 @@ fn emitLog(level: LogLevel, scope: []const u8, message: []const u8) void {
/// Emits logs in chunks. Almost all logs will be less than the chunk size
/// but this allows emitting larger logs without heap allocation.
const LogEmitter = struct {
+ const buffer_size = 2048;
+
c_level: LogLevel,
scope_text: []const u8,
- buf: [2048]u8 = undefined,
- pos: usize = 0,
-
- fn write(self: *@This(), bytes: []const u8) error{}!usize {
- var remaining = bytes;
- while (remaining.len > 0) {
- const space = self.buf.len - self.pos;
- if (space == 0) {
- self.flush();
- continue;
- }
-
- const n = @min(remaining.len, space);
- @memcpy(self.buf[self.pos..][0..n], remaining[0..n]);
- self.pos += n;
- remaining = remaining[n..];
- }
+ writer: std.Io.Writer,
- return bytes.len;
- }
-
- fn flush(self: *@This()) void {
- if (self.pos == 0) return;
- emitLog(
- self.c_level,
- self.scope_text,
- self.buf[0..self.pos],
- );
- self.pos = 0;
+ fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize {
+ switch (data.len) {
+ 0 => unreachable,
+ 1 => if (splat == 0) return 0,
+ else => {},
+ }
+ const self: *@This() = @fieldParentPtr("writer", w);
+ if (w.end > 0) {
+ emitLog(
+ self.c_level,
+ self.scope_text,
+ w.buffer[0..w.end],
+ );
+ }
+ const len: usize = @min(data[0].len, w.buffer.len);
+ @memcpy(w.buffer[0..len], data[0][0..len]);
+ w.end = len;
+ return len;
}
};
@@ -197,18 +211,20 @@ pub fn logFn(
const scope_text: []const u8 = if (scope == .default) "" else @tagName(scope);
const c_level = LogLevel.fromStd(level);
- var ctx: LogEmitter = .{
+ var log_emitter_buffer: [LogEmitter.buffer_size]u8 = undefined;
+ var log_emitter: LogEmitter = .{
.c_level = c_level,
.scope_text = scope_text,
+ .writer = .{
+ .buffer = &log_emitter_buffer,
+ .vtable = &.{
+ .drain = LogEmitter.drain,
+ },
+ },
};
- const writer: std.io.GenericWriter(
- *LogEmitter,
- error{},
- LogEmitter.write,
- ) = .{ .context = &ctx };
-
- nosuspend writer.print(format, args) catch {};
- ctx.flush();
+
+ nosuspend log_emitter.writer.print(format, args) catch {};
+ log_emitter.writer.flush() catch {};
}
/// Built-in log callback that writes to stderr.
@@ -216,8 +232,12 @@ pub fn logFn(
/// Formats each message as "[level](scope): message\n". Can be passed
/// directly to ghostty_sys_set(GHOSTTY_SYS_OPT_LOG, &ghostty_sys_log_stderr).
///
-/// Uses std.debug.lockStderrWriter for thread-safe, mutex-protected output.
-/// On freestanding/wasm targets this is a no-op (no stderr available).
+/// Each log line is emitted with a single raw write to stderr, which keeps
+/// concurrent log lines from interleaving. We intentionally avoid
+/// `std.debug.lockStderr` because it routes through `std.Options.debug_io`
+/// and would keep the entire `std.Io.Threaded` vtable alive in the binary
+/// (see `os/stderr.zig`). On freestanding/wasm targets this is a no-op
+/// (no stderr available).
pub fn logStderr(
_: ?*anyopaque,
level: LogLevel,
@@ -238,16 +258,32 @@ pub fn logStderr(
.debug => "debug",
};
- var buffer: [64]u8 = undefined;
- const writer = std.debug.lockStderrWriter(&buffer);
- defer std.debug.unlockStderrWriter();
- nosuspend {
- if (scope.len > 0) {
- writer.print("[{s}]({s}): {s}\n", .{ level_text, scope, message }) catch {};
- } else {
- writer.print("[{s}]: {s}\n", .{ level_text, message }) catch {};
- }
+ // Large enough for a full logFn chunk plus the level/scope prefix.
+ var buffer: [LogEmitter.buffer_size + 128]u8 = undefined;
+ const line: ?[]const u8 = if (scope.len > 0)
+ std.fmt.bufPrint(&buffer, "[{s}]({s}): {s}\n", .{ level_text, scope, message }) catch null
+ else
+ std.fmt.bufPrint(&buffer, "[{s}]: {s}\n", .{ level_text, message }) catch null;
+ if (line) |v| {
+ stderr.write(v);
+ return;
+ }
+
+ // The line didn't fit in our buffer (an embedder called us directly
+ // with a very large message). Fall back to writing the pieces
+ // separately; interleaving with other threads is possible here but
+ // this is a best-effort diagnostic path.
+ stderr.write("[");
+ stderr.write(level_text);
+ stderr.write("]");
+ if (scope.len > 0) {
+ stderr.write("(");
+ stderr.write(scope);
+ stderr.write(")");
}
+ stderr.write(": ");
+ stderr.write(message);
+ stderr.write("\n");
}
test "set decode_png with null clears" {