mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 12:49:03 +00:00
terminal: init_single_threaded for C API
The C API is assumed to be single-threaded per VT instance. Additionally, using fully-threaded I/O instances registers signal handlers, and would do a pair of registrations once per instance, which could easily get out of hand (and is not really what we intend anyway). init_single_threaded does not register signal handlers, so it does not have this issue, and matches the execution model of the C VT API (single-threaded/not thread-safe within a single VT instance). This also fixes an initialization issue with the threaded I/O instance in general (needs allocation as the memory location would have gone out of scope before).
This commit is contained in:
@@ -39,44 +39,16 @@ const log = std.log.scoped(.terminal_c);
|
||||
/// such as the persistent VT stream needed to handle escape sequences split
|
||||
/// across multiple vt_write calls.
|
||||
const TerminalWrapper = struct {
|
||||
// Shim for the I/O interface that passes freestanding calls to std.Io.failing.
|
||||
const Io = switch (builtin.os.tag) {
|
||||
.freestanding => struct {
|
||||
fn init() @This() {
|
||||
return .{};
|
||||
}
|
||||
|
||||
fn deinit(self: *@This()) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
fn io(self: *@This()) std.Io {
|
||||
_ = self;
|
||||
return std.Io.failing;
|
||||
}
|
||||
},
|
||||
else => struct {
|
||||
threaded: std.Io.Threaded,
|
||||
|
||||
fn init(alloc: std.mem.Allocator, argv0: std.Io.Threaded.Argv0) @This() {
|
||||
return .{ .threaded = .init(alloc, .{ .argv0 = argv0 }) };
|
||||
}
|
||||
|
||||
fn deinit(self: *@This()) void {
|
||||
self.threaded.deinit();
|
||||
}
|
||||
|
||||
fn io(self: *@This()) std.Io {
|
||||
return self.threaded.io();
|
||||
}
|
||||
},
|
||||
};
|
||||
const IoImpl = if (builtin.os.tag != .freestanding) *std.Io.Threaded else void;
|
||||
|
||||
terminal: *ZigTerminal,
|
||||
/// We need to keep an I/O instance here as part of the terminal since we
|
||||
/// have no way of taking it in the C API. This is set up in `new` and
|
||||
/// destroyed on `free`.
|
||||
io_impl: Io,
|
||||
///
|
||||
/// This is set to null on freestanding platforms, which get std.Io.failing
|
||||
/// instead.
|
||||
io_impl: IoImpl,
|
||||
/// We also need to store a temp dir path for some operations (e.g., kitty
|
||||
/// graphics). This provides stable storage for the API calls.
|
||||
tmp_dir_path: [max_path_bytes]u8,
|
||||
@@ -378,34 +350,24 @@ fn new_(
|
||||
return error.OutOfMemory;
|
||||
errdefer alloc.destroy(wrapper);
|
||||
|
||||
// Since I/O implementations are out of scope for C, we just create a
|
||||
// standard threaded one off of our allocator. Note that due to the fact
|
||||
// that easy access to the OS environment is not something that can be
|
||||
// guaranteed we *always* create an empty environment here.
|
||||
const argv0 = "libghostty-vt";
|
||||
const argv0_windows = argv0_windows: {
|
||||
var buf: [std.unicode.calcUtf16LeLen(argv0) catch unreachable]u16 = undefined;
|
||||
_ = std.unicode.utf8ToUtf16Le(&buf, argv0) catch unreachable;
|
||||
break :argv0_windows buf;
|
||||
};
|
||||
|
||||
var io_impl: TerminalWrapper.Io = switch (builtin.os.tag) {
|
||||
.freestanding => .init(),
|
||||
else => .init(
|
||||
alloc,
|
||||
.init(.{ .vector = switch (builtin.os.tag) {
|
||||
.windows => &argv0_windows,
|
||||
else => &.{argv0},
|
||||
} }),
|
||||
),
|
||||
};
|
||||
const has_nonfailing_io = builtin.os.tag != .freestanding;
|
||||
const io_impl: TerminalWrapper.IoImpl = if (has_nonfailing_io) io_impl: {
|
||||
const ptr = try alloc.create(std.Io.Threaded);
|
||||
ptr.* = .init_single_threaded;
|
||||
break :io_impl ptr;
|
||||
} else {};
|
||||
errdefer if (has_nonfailing_io) alloc.destroy(io_impl);
|
||||
|
||||
// Setup our terminal
|
||||
t.* = try .init(io_impl.io(), alloc, .{
|
||||
.cols = opts.cols,
|
||||
.rows = opts.rows,
|
||||
.max_scrollback = opts.max_scrollback,
|
||||
});
|
||||
t.* = try .init(
|
||||
if (has_nonfailing_io) io_impl.io() else std.Io.failing,
|
||||
alloc,
|
||||
.{
|
||||
.cols = opts.cols,
|
||||
.rows = opts.rows,
|
||||
.max_scrollback = opts.max_scrollback,
|
||||
},
|
||||
);
|
||||
errdefer t.deinit(alloc);
|
||||
|
||||
// libghostty-vt embedders don't necessarily install Ghostty's shell
|
||||
@@ -1051,7 +1013,11 @@ pub fn free(terminal_: Terminal) callconv(lib.calling_conv) void {
|
||||
wrapper.tracked_grid_refs.deinit(alloc);
|
||||
wrapper.stream.deinit();
|
||||
t.deinit(alloc);
|
||||
wrapper.io_impl.deinit();
|
||||
if (builtin.os.tag != .freestanding) {
|
||||
// Deinit is always safe to call, even for single-threaded instances
|
||||
wrapper.io_impl.deinit();
|
||||
alloc.destroy(wrapper.io_impl);
|
||||
}
|
||||
alloc.destroy(t);
|
||||
alloc.destroy(wrapper);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user