aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/os
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/os
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/os')
-rw-r--r--vendor/libghostty-vt/src/os/TempDir.zig83
-rw-r--r--vendor/libghostty-vt/src/os/args.zig130
-rw-r--r--vendor/libghostty-vt/src/os/cf_release_thread.zig7
-rw-r--r--vendor/libghostty-vt/src/os/cgroup.zig9
-rw-r--r--vendor/libghostty-vt/src/os/desktop.zig97
-rw-r--r--vendor/libghostty-vt/src/os/edit.zig69
-rw-r--r--vendor/libghostty-vt/src/os/env.zig178
-rw-r--r--vendor/libghostty-vt/src/os/file.zig11
-rw-r--r--vendor/libghostty-vt/src/os/flatpak.zig173
-rw-r--r--vendor/libghostty-vt/src/os/homedir.zig92
-rw-r--r--vendor/libghostty-vt/src/os/hostname.zig90
-rw-r--r--vendor/libghostty-vt/src/os/i18n.zig17
-rw-r--r--vendor/libghostty-vt/src/os/i18n_locales.zig3
-rw-r--r--vendor/libghostty-vt/src/os/kernel_info.zig13
-rw-r--r--vendor/libghostty-vt/src/os/locale.zig59
-rw-r--r--vendor/libghostty-vt/src/os/mach.zig9
-rw-r--r--vendor/libghostty-vt/src/os/macos.zig17
-rw-r--r--vendor/libghostty-vt/src/os/main.zig19
-rw-r--r--vendor/libghostty-vt/src/os/open.zig100
-rw-r--r--vendor/libghostty-vt/src/os/passwd.zig7
-rw-r--r--vendor/libghostty-vt/src/os/path.zig51
-rw-r--r--vendor/libghostty-vt/src/os/pipe.zig7
-rw-r--r--vendor/libghostty-vt/src/os/resourcesdir.zig35
-rw-r--r--vendor/libghostty-vt/src/os/stderr.zig60
-rw-r--r--vendor/libghostty-vt/src/os/systemd.zig28
-rw-r--r--vendor/libghostty-vt/src/os/wasm/log.zig16
-rw-r--r--vendor/libghostty-vt/src/os/windows.zig449
-rw-r--r--vendor/libghostty-vt/src/os/xdg.zig127
28 files changed, 1010 insertions, 946 deletions
diff --git a/vendor/libghostty-vt/src/os/TempDir.zig b/vendor/libghostty-vt/src/os/TempDir.zig
index c3d344d9..c25589a2 100644
--- a/vendor/libghostty-vt/src/os/TempDir.zig
+++ b/vendor/libghostty-vt/src/os/TempDir.zig
@@ -3,8 +3,9 @@
const TempDir = @This();
const std = @import("std");
-const Dir = std.fs.Dir;
+const Dir = std.Io.Dir;
const file = @import("file.zig");
+const global = @import("../global.zig");
const log = std.log.scoped(.tempdir);
@@ -27,10 +28,10 @@ pub fn init() !TempDir {
var tmp_path_buf: [file.random_basename_len:0]u8 = undefined;
const dir = dir: {
- const cwd = std.fs.cwd();
- const tmp_dir = try file.allocTmpDir(std.heap.page_allocator);
+ const cwd = std.Io.Dir.cwd();
+ const tmp_dir = try file.allocTmpDir(std.heap.page_allocator, global.environ());
defer file.freeTmpDir(std.heap.page_allocator, tmp_dir);
- break :dir try cwd.openDir(tmp_dir, .{});
+ break :dir try cwd.openDir(global.io(), tmp_dir, .{});
};
// We now loop forever until we can find a directory that we can create.
@@ -38,13 +39,13 @@ pub fn init() !TempDir {
const tmp_path = try file.randomBasename(&tmp_path_buf);
tmp_path_buf[tmp_path.len] = 0;
- dir.makeDir(tmp_path) catch |err| switch (err) {
+ dir.createDir(global.io(), tmp_path, .default_dir) catch |err| switch (err) {
error.PathAlreadyExists => continue,
else => |e| return e,
};
return TempDir{
- .dir = try dir.openDir(tmp_path, .{}),
+ .dir = try dir.openDir(global.io(), tmp_path, .{}),
.parent = dir,
.name_buf = tmp_path_buf,
};
@@ -60,25 +61,69 @@ pub fn name(self: *TempDir) []const u8 {
/// Finish with the temporary directory. This deletes all contents in the
/// directory.
pub fn deinit(self: *TempDir) void {
- self.dir.close();
- self.parent.deleteTree(self.name()) catch |err|
- log.err("error deleting temp dir err={}", .{err});
+ self.close(.delete);
+}
+
+pub const CloseMode = enum { delete, retain };
+
+/// Close the directory handles, optionally retaining the temporary directory
+/// and its contents on disk.
+pub fn close(self: *TempDir, mode: CloseMode) void {
+ self.dir.close(global.io());
+ switch (mode) {
+ .delete => self.parent.deleteTree(global.io(), self.name()) catch |err|
+ log.err("error deleting temp dir err={}", .{err}),
+ .retain => {},
+ }
+ self.parent.close(global.io());
}
test {
const testing = std.testing;
- var td = try init();
- errdefer td.deinit();
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ var path_len: usize = undefined;
+ {
+ var td = try init();
+ errdefer td.deinit();
- const nameval = td.name();
- try testing.expect(nameval.len > 0);
+ const nameval = td.name();
+ try testing.expect(nameval.len > 0);
+
+ // Can open a new handle to it proves it exists.
+ var dir = try td.parent.openDir(testing.io, nameval, .{});
+ dir.close(testing.io);
+
+ path_len = try td.dir.realPath(testing.io, &path_buf);
+
+ // Don't check the raw handles after close: descriptor numbers can be
+ // reused immediately, so a valid number does not imply that we leaked it.
+ td.deinit();
+ }
- // Can open a new handle to it proves it exists.
- var dir = try td.parent.openDir(nameval, .{});
- dir.close();
+ try testing.expectError(
+ error.FileNotFound,
+ Dir.openDirAbsolute(testing.io, path_buf[0..path_len], .{}),
+ );
+}
+
+test "close retains temporary directory" {
+ const testing = std.testing;
+
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ var path_len: usize = undefined;
+ {
+ var td = try init();
+ errdefer td.deinit();
+
+ path_len = try td.dir.realPath(testing.io, &path_buf);
+
+ // Don't check the raw handles after close: descriptor numbers can be
+ // reused immediately, so a valid number does not imply that we leaked it.
+ td.close(.retain);
+ }
+ defer Dir.deleteDirAbsolute(testing.io, path_buf[0..path_len]) catch {};
- // Should be deleted after we deinit
- td.deinit();
- try testing.expectError(error.FileNotFound, td.parent.openDir(nameval, .{}));
+ var dir = try Dir.openDirAbsolute(testing.io, path_buf[0..path_len], .{});
+ dir.close(testing.io);
}
diff --git a/vendor/libghostty-vt/src/os/args.zig b/vendor/libghostty-vt/src/os/args.zig
deleted file mode 100644
index 9ef5bba4..00000000
--- a/vendor/libghostty-vt/src/os/args.zig
+++ /dev/null
@@ -1,130 +0,0 @@
-const std = @import("std");
-const builtin = @import("builtin");
-const Allocator = std.mem.Allocator;
-const objc = @import("objc");
-const macos = @import("macos");
-
-/// Returns an iterator over the command line arguments. This may or may
-/// not allocate depending on the platform.
-///
-/// For Zig-aware readers: this is the same as std.process.argsWithAllocator
-/// but handles macOS using NSProcessInfo instead of libc argc/argv.
-pub fn iterator(allocator: Allocator) ArgIterator.InitError!ArgIterator {
- //if (true) return try std.process.argsWithAllocator(allocator);
- return .initWithAllocator(allocator);
-}
-
-/// Duck-typed to std.process.ArgIterator
-pub const ArgIterator = switch (builtin.os.tag) {
- .macos => IteratorMacOS,
- else => std.process.ArgIterator,
-};
-
-/// This is an ArgIterator (duck-typed for std.process.ArgIterator) for
-/// NSApplicationMain-based applications on macOS. It uses NSProcessInfo to
-/// get the command line arguments since libc argc/argv pointers are not
-/// valid.
-///
-/// I believe this should work for all macOS applications even if
-/// NSApplicationMain is not used, but I haven't tested that so I'm not
-/// sure. If/when libghostty is ever used outside of NSApplicationMain
-/// then we can revisit this.
-const IteratorMacOS = struct {
- alloc: Allocator,
- index: usize,
- count: usize,
- buf: [:0]u8,
- args: objc.Object,
-
- pub const InitError = Allocator.Error;
-
- pub fn initWithAllocator(alloc: Allocator) InitError!IteratorMacOS {
- const NSProcessInfo = objc.getClass("NSProcessInfo").?;
- const info = NSProcessInfo.msgSend(objc.Object, objc.sel("processInfo"), .{});
- const args = info.getProperty(objc.Object, "arguments");
- errdefer args.release();
-
- // Determine our maximum length so we can allocate the buffer to
- // fit all values.
- var max: usize = 0;
- const count: usize = @intCast(args.getProperty(c_ulong, "count"));
- for (0..count) |i| {
- const nsstr = args.msgSend(
- objc.Object,
- objc.sel("objectAtIndex:"),
- .{@as(c_ulong, @intCast(i))},
- );
-
- const maxlen: usize = @intCast(nsstr.msgSend(
- c_ulong,
- objc.sel("maximumLengthOfBytesUsingEncoding:"),
- .{@as(c_ulong, 4)},
- ));
-
- max = @max(max, maxlen);
- }
-
- // Allocate our buffer. We add 1 for the null terminator.
- const buf = try alloc.allocSentinel(u8, max, 0);
- errdefer alloc.free(buf);
-
- return .{
- .alloc = alloc,
- .index = 0,
- .count = count,
- .buf = buf,
- .args = args,
- };
- }
-
- pub fn deinit(self: *IteratorMacOS) void {
- self.alloc.free(self.buf);
-
- // Note: we don't release self.args because it is a pointer copy
- // not a retained object.
- }
-
- pub fn next(self: *IteratorMacOS) ?[:0]const u8 {
- if (self.index == self.count) return null;
-
- // NSString. No release because not a copy.
- const nsstr = self.args.msgSend(
- objc.Object,
- objc.sel("objectAtIndex:"),
- .{@as(c_ulong, @intCast(self.index))},
- );
- self.index += 1;
-
- // Convert to string using getCString. Our buffer should always
- // be big enough because we precomputed the maximum length.
- if (!nsstr.msgSend(
- bool,
- objc.sel("getCString:maxLength:encoding:"),
- .{
- @as([*]u8, @ptrCast(self.buf.ptr)),
- @as(c_ulong, @intCast(self.buf.len)),
- @as(c_ulong, 4), // NSUTF8StringEncoding
- },
- )) {
- // This should never happen... if it does, we just return empty.
- return "";
- }
-
- return std.mem.sliceTo(self.buf, 0);
- }
-
- pub fn skip(self: *IteratorMacOS) bool {
- if (self.index == self.count) return false;
- self.index += 1;
- return true;
- }
-};
-
-test "args" {
- const testing = std.testing;
- const alloc = testing.allocator;
-
- var iter = try iterator(alloc);
- defer iter.deinit();
- try testing.expect(iter.next().?.len > 0);
-}
diff --git a/vendor/libghostty-vt/src/os/cf_release_thread.zig b/vendor/libghostty-vt/src/os/cf_release_thread.zig
index 445dc486..e93120b3 100644
--- a/vendor/libghostty-vt/src/os/cf_release_thread.zig
+++ b/vendor/libghostty-vt/src/os/cf_release_thread.zig
@@ -9,7 +9,8 @@ const builtin = @import("builtin");
const macos = @import("macos");
const internal_os = @import("../os/main.zig");
-const xev = @import("../global.zig").xev;
+const global = @import("../global.zig");
+const xev = global.xev;
const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue;
const Allocator = std.mem.Allocator;
@@ -142,11 +143,11 @@ fn threadMain_(self: *Thread) !void {
fn drainMailbox(self: *Thread) !void {
// If we're draining, we just drain the mailbox and return.
if (self.flags.drain) {
- while (self.mailbox.pop()) |_| {}
+ while (self.mailbox.pop(global.io())) |_| {}
return;
}
- while (self.mailbox.pop()) |message| {
+ while (self.mailbox.pop(global.io())) |message| {
// log.debug("mailbox message={}", .{message});
switch (message) {
.release => |msg| {
diff --git a/vendor/libghostty-vt/src/os/cgroup.zig b/vendor/libghostty-vt/src/os/cgroup.zig
index 9e68a50f..f5eacc6c 100644
--- a/vendor/libghostty-vt/src/os/cgroup.zig
+++ b/vendor/libghostty-vt/src/os/cgroup.zig
@@ -1,4 +1,5 @@
const std = @import("std");
+const global = @import("../global.zig");
const log = std.log.scoped(.@"linux-cgroup");
@@ -11,16 +12,16 @@ pub fn current(buf: []u8, pid: u32) ?[]const u8 {
// 0::/user.slice/user-1000.slice/session-1.scope
// The cgroup path is the third field.
const path = std.fmt.bufPrint(&path_buf, "/proc/{}/cgroup", .{pid}) catch return null;
- const file = std.fs.openFileAbsolute(path, .{}) catch return null;
- defer file.close();
+ const file = std.Io.Dir.openFileAbsolute(global.io(), path, .{}) catch return null;
+ defer file.close(global.io());
var read_buf: [64]u8 = undefined;
- var file_reader = file.reader(&read_buf);
+ var file_reader = file.reader(global.io(), &read_buf);
const reader = &file_reader.interface;
const len = reader.readSliceShort(buf) catch return null;
const contents = buf[0..len];
// Find the last ':'
const idx = std.mem.lastIndexOfScalar(u8, contents, ':') orelse return null;
- return std.mem.trimRight(u8, contents[idx + 1 ..], " \r\n");
+ return std.mem.trimEnd(u8, contents[idx + 1 ..], " \r\n");
}
diff --git a/vendor/libghostty-vt/src/os/desktop.zig b/vendor/libghostty-vt/src/os/desktop.zig
index 93bfb74b..60ec51a5 100644
--- a/vendor/libghostty-vt/src/os/desktop.zig
+++ b/vendor/libghostty-vt/src/os/desktop.zig
@@ -1,7 +1,7 @@
const std = @import("std");
const builtin = @import("builtin");
const build_config = @import("../build_config.zig");
-const posix = std.posix;
+const global = @import("../global.zig");
const c = @cImport({
@cInclude("unistd.h");
@@ -26,7 +26,7 @@ pub fn launchedFromDesktop() bool {
// was launched from the desktop.
if (build_config.artifact == .lib) lib: {
const env = "GHOSTTY_MAC_LAUNCH_SOURCE";
- const source = posix.getenv(env) orelse break :lib;
+ const source = global.environ().getPosix(env) orelse break :lib;
// Source can be "app", "cli", or "zig_run". We assume
// its the desktop only if its "app". We may want to do
@@ -44,7 +44,7 @@ pub fn launchedFromDesktop() bool {
// another terminal was launched from a desktop file and then launches
// Ghostty and Ghostty inherits the env.
.linux, .freebsd => ul: {
- const gio_pid_str = posix.getenv("GIO_LAUNCHED_DESKTOP_FILE_PID") orelse
+ const gio_pid_str = global.environ().getPosix("GIO_LAUNCHED_DESKTOP_FILE_PID") orelse
break :ul false;
const pid = c.getpid();
@@ -80,7 +80,7 @@ pub const DesktopEnvironment = enum {
/// Detect what desktop environment we are running under. This is mainly used
/// on Linux and BSD to enable or disable certain features but there may be more uses in
/// the future.
-pub fn desktopEnvironment() DesktopEnvironment {
+pub fn desktopEnvironment(environ_map: *const std.process.Environ.Map) DesktopEnvironment {
return switch (comptime builtin.os.tag) {
.macos => .macos,
.windows => .windows,
@@ -89,7 +89,7 @@ pub fn desktopEnvironment() DesktopEnvironment {
// Use $XDG_SESSION_DESKTOP to determine what DE we are using on Linux
// https://www.freedesktop.org/software/systemd/man/latest/pam_systemd.html#desktop=
- if (posix.getenv("XDG_SESSION_DESKTOP")) |sd| {
+ if (environ_map.get("XDG_SESSION_DESKTOP")) |sd| {
if (std.ascii.eqlIgnoreCase("gnome", sd)) break :de .gnome;
if (std.ascii.eqlIgnoreCase("gnome-xorg", sd)) break :de .gnome;
}
@@ -99,7 +99,7 @@ pub fn desktopEnvironment() DesktopEnvironment {
// colon-separated list of up to three desktop names, although we
// only look at the first.
// https://specifications.freedesktop.org/desktop-entry-spec/latest/recognized-keys.html
- if (posix.getenv("XDG_CURRENT_DESKTOP")) |cd| {
+ if (environ_map.get("XDG_CURRENT_DESKTOP")) |cd| {
var cd_it = std.mem.splitScalar(u8, cd, ':');
const cd_first = cd_it.first();
if (std.ascii.eqlIgnoreCase(cd_first, "gnome")) break :de .gnome;
@@ -114,46 +114,55 @@ pub fn desktopEnvironment() DesktopEnvironment {
test "desktop environment" {
const testing = std.testing;
+ // Always run these tests with a blank evironment map so that we don't get any
+ // failures from values in the "real" evironment leaking in.
switch (builtin.os.tag) {
- .macos => try testing.expectEqual(.macos, desktopEnvironment()),
- .windows => try testing.expectEqual(.windows, desktopEnvironment()),
+ inline .macos, .windows => |tag| {
+ var environ_map: std.process.Environ.Map = .init(testing.allocator);
+ defer environ_map.deinit();
+ try testing.expectEqual(@tagName(tag), @tagName(desktopEnvironment(&environ_map)));
+ },
.linux, .freebsd => {
- const getenv = std.posix.getenv;
- const setenv = @import("env.zig").setenv;
- const unsetenv = @import("env.zig").unsetenv;
-
- const xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP");
- defer if (xdg_current_desktop) |v| {
- _ = setenv("XDG_CURRENT_DESKTOP", v);
- } else {
- _ = unsetenv("XDG_CURRENT_DESKTOP");
- };
- _ = unsetenv("XDG_CURRENT_DESKTOP");
-
- const xdg_session_desktop = getenv("XDG_SESSION_DESKTOP");
- defer if (xdg_session_desktop) |v| {
- _ = setenv("XDG_SESSION_DESKTOP", v);
- } else {
- _ = unsetenv("XDG_SESSION_DESKTOP");
- };
- _ = unsetenv("XDG_SESSION_DESKTOP");
-
- _ = setenv("XDG_SESSION_DESKTOP", "gnome");
- try testing.expectEqual(.gnome, desktopEnvironment());
- _ = setenv("XDG_SESSION_DESKTOP", "gnome-xorg");
- try testing.expectEqual(.gnome, desktopEnvironment());
- _ = setenv("XDG_SESSION_DESKTOP", "foobar");
- try testing.expectEqual(.other, desktopEnvironment());
-
- _ = unsetenv("XDG_SESSION_DESKTOP");
- try testing.expectEqual(.other, desktopEnvironment());
-
- _ = setenv("XDG_CURRENT_DESKTOP", "GNOME");
- try testing.expectEqual(.gnome, desktopEnvironment());
- _ = setenv("XDG_CURRENT_DESKTOP", "FOOBAR");
- try testing.expectEqual(.other, desktopEnvironment());
- _ = unsetenv("XDG_CURRENT_DESKTOP");
- try testing.expectEqual(.other, desktopEnvironment());
+ {
+ var environ_map: std.process.Environ.Map = .init(testing.allocator);
+ defer environ_map.deinit();
+ try environ_map.put("XDG_SESSION_DESKTOP", "gnome");
+ try testing.expectEqual(.gnome, desktopEnvironment(&environ_map));
+ }
+
+ {
+ var environ_map: std.process.Environ.Map = .init(testing.allocator);
+ defer environ_map.deinit();
+ try environ_map.put("XDG_SESSION_DESKTOP", "gnome-xorg");
+ try testing.expectEqual(.gnome, desktopEnvironment(&environ_map));
+ }
+
+ {
+ var environ_map: std.process.Environ.Map = .init(testing.allocator);
+ defer environ_map.deinit();
+ try environ_map.put("XDG_SESSION_DESKTOP", "foobar");
+ try testing.expectEqual(.other, desktopEnvironment(&environ_map));
+ }
+
+ {
+ var environ_map: std.process.Environ.Map = .init(testing.allocator);
+ defer environ_map.deinit();
+ try environ_map.put("XDG_CURRENT_DESKTOP", "GNOME");
+ try testing.expectEqual(.gnome, desktopEnvironment(&environ_map));
+ }
+
+ {
+ var environ_map: std.process.Environ.Map = .init(testing.allocator);
+ defer environ_map.deinit();
+ try environ_map.put("XDG_CURRENT_DESKTOP", "FOOBAR");
+ try testing.expectEqual(.other, desktopEnvironment(&environ_map));
+ }
+
+ {
+ var environ_map: std.process.Environ.Map = .init(testing.allocator);
+ defer environ_map.deinit();
+ try testing.expectEqual(.other, desktopEnvironment(&environ_map));
+ }
},
else => try testing.expectEqual(.other, DesktopEnvironment()),
}
diff --git a/vendor/libghostty-vt/src/os/edit.zig b/vendor/libghostty-vt/src/os/edit.zig
new file mode 100644
index 00000000..3e14bcaa
--- /dev/null
+++ b/vendor/libghostty-vt/src/os/edit.zig
@@ -0,0 +1,69 @@
+const std = @import("std");
+
+const global = @import("../global.zig");
+const ShellEscapeWriter = @import("shell.zig").ShellEscapeWriter;
+
+const log = std.log.scoped(.os_edit);
+
+const Options = struct {
+ default_editor: enum {
+ /// Return an error if $EDITOR or $VISUAL is not defined.
+ failure,
+ /// Default to "vi" if $EDITOR or $VISUAL is not defined.
+ vi,
+ /// Default to "nano" if $EDITOR or $VISUAL is not defined.
+ nano,
+ } = .vi,
+
+ pub const default: Options = .{};
+};
+
+pub const Error = error{NoEditorConfigured};
+
+pub fn getConfigEditCommand(alloc: std.mem.Allocator, path: []const u8, options: Options) (Error || std.Io.Writer.Error || std.mem.Allocator.Error)![:0]const u8 {
+ const editor: []const u8 = editor: {
+ // VISUAL vs. EDITOR: https://unix.stackexchange.com/questions/4859/visual-vs-editor-what-s-the-difference
+ v: {
+ if (global.environ().containsUnempty(alloc, "VISUAL") catch break :v) {
+ break :editor global.environ().getAlloc(alloc, "VISUAL") catch break :v;
+ }
+ }
+
+ e: {
+ if (global.environ().containsUnempty(alloc, "EDITOR") catch break :e) {
+ break :editor global.environ().getAlloc(alloc, "EDITOR") catch break :e;
+ }
+ }
+
+ switch (options.default_editor) {
+ .failure => {
+ log.warn("$EDITOR or $VISUAL must be set to open config in a new window", .{});
+ return error.NoEditorConfigured;
+ },
+ .vi => {
+ log.warn("$EDITOR or $VISUAL not set, falling back to vi", .{});
+ break :editor try alloc.dupe(u8, "vi");
+ },
+ .nano => {
+ log.warn("$EDITOR or $VISUAL not set, falling back to nano", .{});
+ break :editor try alloc.dupe(u8, "nano");
+ },
+ }
+ };
+ defer alloc.free(editor);
+
+ var buffer: std.Io.Writer.Allocating = .init(alloc);
+ defer buffer.deinit();
+ const writer = &buffer.writer;
+
+ try writer.writeAll(editor);
+ try writer.writeByte(' ');
+ {
+ var sh: ShellEscapeWriter = .init(writer);
+ try sh.writer.writeAll(path);
+ try sh.writer.flush();
+ }
+ try writer.flush();
+
+ return try buffer.toOwnedSliceSentinel(0);
+}
diff --git a/vendor/libghostty-vt/src/os/env.zig b/vendor/libghostty-vt/src/os/env.zig
deleted file mode 100644
index 8f01a14d..00000000
--- a/vendor/libghostty-vt/src/os/env.zig
+++ /dev/null
@@ -1,178 +0,0 @@
-const std = @import("std");
-const builtin = @import("builtin");
-const Allocator = std.mem.Allocator;
-const posix = std.posix;
-const isFlatpak = @import("flatpak.zig").isFlatpak;
-
-pub const Error = Allocator.Error;
-
-/// Get the environment map.
-pub fn getEnvMap(alloc: Allocator) !std.process.EnvMap {
- return if (isFlatpak())
- std.process.EnvMap.init(alloc)
- else
- try std.process.getEnvMap(alloc);
-}
-
-/// Append a value to an environment variable such as PATH.
-/// The returned value is always allocated so it must be freed.
-pub fn appendEnv(
- alloc: Allocator,
- current: []const u8,
- value: []const u8,
-) Error![]u8 {
- // If there is no prior value, we return it as-is
- if (current.len == 0) return try alloc.dupe(u8, value);
-
- // Otherwise we must prefix.
- return try appendEnvAlways(alloc, current, value);
-}
-
-/// Always append value to environment, even when it is empty.
-/// This is useful because some env vars (like MANPATH) want there
-/// to be an empty prefix to preserve existing values.
-///
-/// The returned value is always allocated so it must be freed.
-pub fn appendEnvAlways(
- alloc: Allocator,
- current: []const u8,
- value: []const u8,
-) Error![]u8 {
- return try std.fmt.allocPrint(alloc, "{s}{c}{s}", .{
- current,
- std.fs.path.delimiter,
- value,
- });
-}
-
-/// Prepend a value to an environment variable such as PATH.
-/// The returned value is always allocated so it must be freed.
-pub fn prependEnv(
- alloc: Allocator,
- current: []const u8,
- value: []const u8,
-) Error![]u8 {
- // If there is no prior value, we return it as-is
- if (current.len == 0) return try alloc.dupe(u8, value);
-
- return try std.fmt.allocPrint(alloc, "{s}{c}{s}", .{
- value,
- std.fs.path.delimiter,
- current,
- });
-}
-
-/// The result of getenv, with a shared deinit to properly handle allocation
-/// on Windows.
-pub const GetEnvResult = struct {
- value: []const u8,
-
- pub fn deinit(self: GetEnvResult, alloc: Allocator) void {
- switch (builtin.os.tag) {
- .windows => alloc.free(self.value),
- else => {},
- }
- }
-};
-
-/// Gets the value of an environment variable, or null if not found.
-/// This will allocate on Windows but not on other platforms. The returned
-/// value should have deinit called to do the proper cleanup no matter what
-/// platform you are on.
-pub fn getenv(alloc: Allocator, key: []const u8) Error!?GetEnvResult {
- return switch (builtin.os.tag) {
- // Non-Windows doesn't need to allocate
- else => if (posix.getenv(key)) |v| .{ .value = v } else null,
-
- // Windows needs to allocate
- .windows => if (std.process.getEnvVarOwned(alloc, key)) |v| .{
- .value = v,
- } else |err| switch (err) {
- error.EnvironmentVariableNotFound => null,
- error.InvalidWtf8 => null,
- else => |e| e,
- },
- };
-}
-
-/// Gets the value of an environment variable. Returns null if not found or the
-/// value is empty. This will allocate on Windows but not on other platforms.
-/// The returned value should have deinit called to do the proper cleanup no
-/// matter what platform you are on.
-pub fn getenvNotEmpty(alloc: Allocator, key: []const u8) !?GetEnvResult {
- const result_ = try getenv(alloc, key);
- if (result_) |result| {
- if (result.value.len == 0) {
- result.deinit(alloc);
- return null;
- }
- }
- return result_;
-}
-
-pub fn setenv(key: [:0]const u8, value: [:0]const u8) c_int {
- return switch (builtin.os.tag) {
- .windows => c._putenv_s(key.ptr, value.ptr),
- else => c.setenv(key.ptr, value.ptr, 1),
- };
-}
-
-pub fn unsetenv(key: [:0]const u8) c_int {
- return switch (builtin.os.tag) {
- .windows => c._putenv_s(key.ptr, ""),
- else => c.unsetenv(key.ptr),
- };
-}
-
-const c = struct {
- // POSIX
- extern "c" fn setenv(name: ?[*]const u8, value: ?[*]const u8, overwrite: c_int) c_int;
- extern "c" fn unsetenv(name: ?[*]const u8) c_int;
-
- // Windows
- extern "c" fn _putenv_s(varname: ?[*]const u8, value_string: ?[*]const u8) c_int;
-};
-
-test "appendEnv empty" {
- const testing = std.testing;
- const alloc = testing.allocator;
-
- const result = try appendEnv(alloc, "", "foo");
- defer alloc.free(result);
- try testing.expectEqualStrings(result, "foo");
-}
-
-test "appendEnv existing" {
- const testing = std.testing;
- const alloc = testing.allocator;
-
- const result = try appendEnv(alloc, "a:b", "foo");
- defer alloc.free(result);
- if (builtin.os.tag == .windows) {
- try testing.expectEqualStrings(result, "a:b;foo");
- } else {
- try testing.expectEqualStrings(result, "a:b:foo");
- }
-}
-
-test "prependEnv empty" {
- const testing = std.testing;
- const alloc = testing.allocator;
-
- const result = try prependEnv(alloc, "", "foo");
- defer alloc.free(result);
- try testing.expectEqualStrings(result, "foo");
-}
-
-test "prependEnv existing" {
- const testing = std.testing;
- const alloc = testing.allocator;
-
- const result = try prependEnv(alloc, "a:b", "foo");
- defer alloc.free(result);
- if (builtin.os.tag == .windows) {
- try testing.expectEqualStrings(result, "foo;a:b");
- } else {
- try testing.expectEqualStrings(result, "foo:a:b");
- }
-}
diff --git a/vendor/libghostty-vt/src/os/file.zig b/vendor/libghostty-vt/src/os/file.zig
index 6007ae49..3c170b46 100644
--- a/vendor/libghostty-vt/src/os/file.zig
+++ b/vendor/libghostty-vt/src/os/file.zig
@@ -1,5 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
+const global = @import("../global.zig");
const posix = std.posix;
const windows = @import("windows.zig");
@@ -63,7 +64,7 @@ pub fn restoreMaxFiles(lim: rlimit) void {
/// POSIX this returns `$TMPDIR`/`$TMP` (or `"/tmp"` as a fallback)
/// without allocating. Always pair with `freeTmpDir` to release any
/// allocation.
-pub fn allocTmpDir(allocator: std.mem.Allocator) std.mem.Allocator.Error![]const u8 {
+pub fn allocTmpDir(allocator: std.mem.Allocator, environ: std.process.Environ) std.mem.Allocator.Error![]const u8 {
if (builtin.os.tag == .windows) {
// GetTempPathW guarantees the result fits in MAX_PATH+1.
var buf: [windows.MAX_PATH + 1:0]u16 = undefined;
@@ -81,7 +82,7 @@ pub fn allocTmpDir(allocator: std.mem.Allocator) std.mem.Allocator.Error![]const
}
return allocator.dupe(u8, "C:\\Windows\\Temp");
}
- const tmpdir = posix.getenv("TMPDIR") orelse posix.getenv("TMP") orelse return "/tmp";
+ const tmpdir = environ.getPosix("TMPDIR") orelse environ.getPosix("TMP") orelse return "/tmp";
return std.mem.trimEnd(u8, tmpdir, &.{std.fs.path.sep});
}
@@ -107,7 +108,7 @@ pub const random_basename_len = b64_encoder.calcSize(random_basename_bytes);
pub fn randomBasename(buf: []u8) RandomBasenameError![]const u8 {
if (buf.len < random_basename_len) return error.BufferTooSmall;
var rand_buf: [random_basename_bytes]u8 = undefined;
- std.crypto.random.bytes(&rand_buf);
+ global.io().random(&rand_buf);
return b64_encoder.encode(buf[0..random_basename_len], &rand_buf);
}
@@ -121,14 +122,12 @@ pub fn randomTmpPath(
allocator: std.mem.Allocator,
prefix: []const u8,
) std.mem.Allocator.Error![]u8 {
- const tmp_dir = try allocTmpDir(allocator);
- defer freeTmpDir(allocator, tmp_dir);
var name_buf: [random_basename_len]u8 = undefined;
const basename = randomBasename(&name_buf) catch unreachable;
return std.fmt.allocPrint(
allocator,
"{s}{c}{s}{s}",
- .{ tmp_dir, std.fs.path.sep, prefix, basename },
+ .{ global.tmpDirPath(), std.fs.path.sep, prefix, basename },
);
}
diff --git a/vendor/libghostty-vt/src/os/flatpak.zig b/vendor/libghostty-vt/src/os/flatpak.zig
index 44f820ad..15c619de 100644
--- a/vendor/libghostty-vt/src/os/flatpak.zig
+++ b/vendor/libghostty-vt/src/os/flatpak.zig
@@ -2,7 +2,8 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
const posix = std.posix;
-const xev = @import("../global.zig").xev;
+const global = @import("../global.zig");
+const xev = global.xev;
const log = std.log.scoped(.flatpak);
@@ -10,7 +11,10 @@ const log = std.log.scoped(.flatpak);
pub fn isFlatpak() bool {
// If we're not on Linux then we'll make this comptime false.
if (comptime builtin.os.tag != .linux) return false;
- return if (std.fs.accessAbsolute("/.flatpak-info", .{})) true else |_| false;
+ return if (std.Io.Dir.accessAbsolute(global.io(), "/.flatpak-info", .{}))
+ true
+ else |_|
+ false;
}
/// A struct to help execute commands on the host via the
@@ -28,11 +32,8 @@ pub fn isFlatpak() bool {
/// Requires GIO, GLib to be available and linked.
pub const FlatpakHostCommand = struct {
const fd_t = posix.fd_t;
- const EnvMap = std.process.EnvMap;
- const c = @cImport({
- @cInclude("gio/gio.h");
- @cInclude("gio/gunixfdlist.h");
- });
+ const EnvMap = std.process.Environ.Map;
+ const gio_c = @import("gio_c");
/// Flags for HostCommand method
///
/// Ref: https://docs.flatpak.org/en/latest/libflatpak-api-reference.html#gdbus-method-org-freedesktop-Flatpak-Development.HostCommand
@@ -65,8 +66,8 @@ pub const FlatpakHostCommand = struct {
/// State of the process. This is updated by the dedicated thread it
/// runs in and is protected by the given lock and condition variable.
state: State = .{ .init = {} },
- state_mutex: std.Thread.Mutex = .{},
- state_cv: std.Thread.Condition = .{},
+ state_mutex: std.Io.Mutex = .init,
+ state_cv: std.Io.Condition = .init,
/// State the process is in. This can't be inspected directly, you
/// must use getters on the struct to get access.
@@ -84,8 +85,8 @@ pub const FlatpakHostCommand = struct {
pid: u32,
loop_xev: ?*xev.Loop,
completion: ?*Completion,
- subscription: c.guint,
- loop: *c.GMainLoop,
+ subscription: gio_c.guint,
+ loop: *gio_c.GMainLoop,
},
/// Process exited
@@ -120,14 +121,14 @@ pub const FlatpakHostCommand = struct {
/// Precondition: The self pointer MUST be stable.
pub fn spawn(self: *FlatpakHostCommand, alloc: Allocator) !u32 {
const thread = try std.Thread.spawn(.{}, threadMain, .{ self, alloc });
- thread.setName("flatpak-host-command") catch {};
+ thread.setName(global.io(), "flatpak-host-command") catch {};
// We don't track this thread, it will terminate on its own on command exit
thread.detach();
// Wait for the process to start or error.
- self.state_mutex.lock();
- defer self.state_mutex.unlock();
- while (self.state == .init) self.state_cv.wait(&self.state_mutex);
+ self.state_mutex.lockUncancelable(global.io());
+ defer self.state_mutex.unlock(global.io());
+ while (self.state == .init) self.state_cv.waitUncancelable(global.io(), &self.state_mutex);
return switch (self.state) {
.init => unreachable,
@@ -140,8 +141,8 @@ pub const FlatpakHostCommand = struct {
/// Wait for the process to end and return the exit status. This
/// can only be called ONCE. Once this returns, the state is reset.
pub fn wait(self: *FlatpakHostCommand) !u8 {
- self.state_mutex.lock();
- defer self.state_mutex.unlock();
+ self.state_mutex.lockUncancelable(global.io());
+ defer self.state_mutex.unlock(global.io());
while (true) {
switch (self.state) {
@@ -150,12 +151,12 @@ pub const FlatpakHostCommand = struct {
.started => {},
.exited => |v| {
self.state = .{ .init = {} };
- self.state_cv.broadcast();
+ self.state_cv.broadcast(global.io());
return v.status;
},
}
- self.state_cv.wait(&self.state_mutex);
+ self.state_cv.waitUncancelable(global.io(), &self.state_mutex);
}
}
@@ -174,8 +175,8 @@ pub const FlatpakHostCommand = struct {
r: WaitError!u8,
) void,
) void {
- self.state_mutex.lock();
- defer self.state_mutex.unlock();
+ self.state_mutex.lockUncancelable(global.io());
+ defer self.state_mutex.unlock(global.io());
completion.* = .{
.callback = (struct {
@@ -234,8 +235,8 @@ pub const FlatpakHostCommand = struct {
/// command is not in the started state.
pub fn signal(self: *FlatpakHostCommand, sig: u8, pg: bool) !void {
const pid = pid: {
- self.state_mutex.lock();
- defer self.state_mutex.unlock();
+ self.state_mutex.lockUncancelable(global.io());
+ defer self.state_mutex.unlock(global.io());
switch (self.state) {
.started => |v| break :pid v.pid,
else => return,
@@ -243,29 +244,29 @@ pub const FlatpakHostCommand = struct {
};
// Get our bus connection.
- var g_err: ?*c.GError = null;
- defer if (g_err) |ptr| c.g_error_free(ptr);
- const bus = c.g_bus_get_sync(c.G_BUS_TYPE_SESSION, null, &g_err) orelse {
+ var g_err: ?*gio_c.GError = null;
+ defer if (g_err) |ptr| gio_c.g_error_free(ptr);
+ const bus = gio_c.g_bus_get_sync(gio_c.G_BUS_TYPE_SESSION, null, &g_err) orelse {
log.warn("signal error getting bus: {s}", .{g_err.?.*.message});
return Error.FlatpakSetupFail;
};
- defer c.g_object_unref(bus);
+ defer gio_c.g_object_unref(bus);
- const reply = c.g_dbus_connection_call_sync(
+ const reply = gio_c.g_dbus_connection_call_sync(
bus,
"org.freedesktop.Flatpak",
"/org/freedesktop/Flatpak/Development",
"org.freedesktop.Flatpak.Development",
"HostCommandSignal",
- c.g_variant_new(
+ gio_c.g_variant_new(
"(uub)",
pid,
sig,
@as(c_int, @intCast(@intFromBool(pg))),
),
- c.G_VARIANT_TYPE("()"),
- c.G_DBUS_CALL_FLAGS_NONE,
- c.G_MAXINT,
+ gio_c.G_VARIANT_TYPE("()"),
+ gio_c.G_DBUS_CALL_FLAGS_NONE,
+ gio_c.G_MAXINT,
null,
&g_err,
);
@@ -273,31 +274,31 @@ pub const FlatpakHostCommand = struct {
log.warn("signal send error: {s}", .{g_err.?.*.message});
return;
}
- defer c.g_variant_unref(reply);
+ defer gio_c.g_variant_unref(reply);
}
fn threadMain(self: *FlatpakHostCommand, alloc: Allocator) void {
// Create a new thread-local context so that all our sources go
// to this context and we can run our loop correctly.
- const ctx = c.g_main_context_new();
- defer c.g_main_context_unref(ctx);
- c.g_main_context_push_thread_default(ctx);
- defer c.g_main_context_pop_thread_default(ctx);
+ const ctx = gio_c.g_main_context_new();
+ defer gio_c.g_main_context_unref(ctx);
+ gio_c.g_main_context_push_thread_default(ctx);
+ defer gio_c.g_main_context_pop_thread_default(ctx);
// Get our loop for the current thread
- const loop = c.g_main_loop_new(ctx, 1).?;
- defer c.g_main_loop_unref(loop);
+ const loop = gio_c.g_main_loop_new(ctx, 1).?;
+ defer gio_c.g_main_loop_unref(loop);
// Get our bus connection. This has to remain active until we exit
// the thread otherwise our signals won't be called.
- var g_err: ?*c.GError = null;
- defer if (g_err) |ptr| c.g_error_free(ptr);
- const bus = c.g_bus_get_sync(c.G_BUS_TYPE_SESSION, null, &g_err) orelse {
+ var g_err: ?*gio_c.GError = null;
+ defer if (g_err) |ptr| gio_c.g_error_free(ptr);
+ const bus = gio_c.g_bus_get_sync(gio_c.G_BUS_TYPE_SESSION, null, &g_err) orelse {
log.warn("spawn error getting bus: {s}", .{g_err.?.*.message});
self.updateState(.{ .err = {} });
return;
};
- defer c.g_object_unref(bus);
+ defer gio_c.g_object_unref(bus);
// Spawn the command first. This will setup all our IO.
self.start(alloc, bus, loop) catch |err| {
@@ -307,7 +308,7 @@ pub const FlatpakHostCommand = struct {
};
// Run the event loop. It quits in the exit callback.
- c.g_main_loop_run(loop);
+ gio_c.g_main_loop_run(loop);
}
/// Start the command. This will start the host command and set the
@@ -318,47 +319,47 @@ pub const FlatpakHostCommand = struct {
fn start(
self: *FlatpakHostCommand,
alloc: Allocator,
- bus: *c.GDBusConnection,
- loop: *c.GMainLoop,
+ bus: *gio_c.GDBusConnection,
+ loop: *gio_c.GMainLoop,
) !void {
- var err: ?*c.GError = null;
- defer if (err) |ptr| c.g_error_free(ptr);
+ var err: ?*gio_c.GError = null;
+ defer if (err) |ptr| gio_c.g_error_free(ptr);
var arena_allocator = std.heap.ArenaAllocator.init(alloc);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
// Our list of file descriptors that we need to send to the process.
- const fd_list = c.g_unix_fd_list_new();
- defer c.g_object_unref(fd_list);
- if (c.g_unix_fd_list_append(fd_list, self.stdin, &err) < 0) {
+ const fd_list = gio_c.g_unix_fd_list_new();
+ defer gio_c.g_object_unref(fd_list);
+ if (gio_c.g_unix_fd_list_append(fd_list, self.stdin, &err) < 0) {
log.warn("error adding fd: {s}", .{err.?.*.message});
return Error.FlatpakSetupFail;
}
- if (c.g_unix_fd_list_append(fd_list, self.stdout, &err) < 0) {
+ if (gio_c.g_unix_fd_list_append(fd_list, self.stdout, &err) < 0) {
log.warn("error adding fd: {s}", .{err.?.*.message});
return Error.FlatpakSetupFail;
}
- if (c.g_unix_fd_list_append(fd_list, self.stderr, &err) < 0) {
+ if (gio_c.g_unix_fd_list_append(fd_list, self.stderr, &err) < 0) {
log.warn("error adding fd: {s}", .{err.?.*.message});
return Error.FlatpakSetupFail;
}
// Build our arguments for the file descriptors.
- const fd_builder = c.g_variant_builder_new(c.G_VARIANT_TYPE("a{uh}"));
- defer c.g_variant_builder_unref(fd_builder);
- c.g_variant_builder_add(fd_builder, "{uh}", @as(c_int, 0), self.stdin);
- c.g_variant_builder_add(fd_builder, "{uh}", @as(c_int, 1), self.stdout);
- c.g_variant_builder_add(fd_builder, "{uh}", @as(c_int, 2), self.stderr);
+ const fd_builder = gio_c.g_variant_builder_new(gio_c.G_VARIANT_TYPE("a{uh}"));
+ defer gio_c.g_variant_builder_unref(fd_builder);
+ gio_c.g_variant_builder_add(fd_builder, "{uh}", @as(c_int, 0), self.stdin);
+ gio_c.g_variant_builder_add(fd_builder, "{uh}", @as(c_int, 1), self.stdout);
+ gio_c.g_variant_builder_add(fd_builder, "{uh}", @as(c_int, 2), self.stderr);
// Build our env vars
- const env_builder = c.g_variant_builder_new(c.G_VARIANT_TYPE("a{ss}"));
- defer c.g_variant_builder_unref(env_builder);
+ const env_builder = gio_c.g_variant_builder_new(gio_c.G_VARIANT_TYPE("a{ss}"));
+ defer gio_c.g_variant_builder_unref(env_builder);
if (self.env) |env| {
var it = env.iterator();
while (it.next()) |pair| {
const key = try arena.dupeZ(u8, pair.key_ptr.*);
const value = try arena.dupeZ(u8, pair.value_ptr.*);
- c.g_variant_builder_add(env_builder, "{ss}", key.ptr, value.ptr);
+ gio_c.g_variant_builder_add(env_builder, "{ss}", key.ptr, value.ptr);
}
}
@@ -373,26 +374,26 @@ pub const FlatpakHostCommand = struct {
// Get the cwd in case we don't have ours set. A small optimization
// would be to do this only if we need it but this isn't a
// common code path.
- const g_cwd = c.g_get_current_dir();
- defer c.g_free(g_cwd);
+ const g_cwd = gio_c.g_get_current_dir();
+ defer gio_c.g_free(g_cwd);
// Terminate session if Ghostty drops off the bus (e.g. due to crashes)
const flags: Flags = .{ .watch_bus = true };
// The params for our RPC call
- const params = c.g_variant_new(
+ const params = gio_c.g_variant_new(
"(^ay^aay@a{uh}@a{ss}u)",
@as(*const anyopaque, if (self.cwd) |*cwd| cwd.ptr else g_cwd),
args.ptr,
- c.g_variant_builder_end(fd_builder),
- c.g_variant_builder_end(env_builder),
+ gio_c.g_variant_builder_end(fd_builder),
+ gio_c.g_variant_builder_end(env_builder),
@as(c_uint, @bitCast(flags)),
);
- _ = c.g_variant_ref_sink(params); // take ownership
- defer c.g_variant_unref(params);
+ _ = gio_c.g_variant_ref_sink(params); // take ownership
+ defer gio_c.g_variant_unref(params);
// Subscribe to exit notifications
- const subscription_id = c.g_dbus_connection_signal_subscribe(
+ const subscription_id = gio_c.g_dbus_connection_signal_subscribe(
bus,
"org.freedesktop.Flatpak",
"org.freedesktop.Flatpak.Development",
@@ -404,19 +405,19 @@ pub const FlatpakHostCommand = struct {
self,
null,
);
- errdefer c.g_dbus_connection_signal_unsubscribe(bus, subscription_id);
+ errdefer gio_c.g_dbus_connection_signal_unsubscribe(bus, subscription_id);
// Go!
- const reply = c.g_dbus_connection_call_with_unix_fd_list_sync(
+ const reply = gio_c.g_dbus_connection_call_with_unix_fd_list_sync(
bus,
"org.freedesktop.Flatpak",
"/org/freedesktop/Flatpak/Development",
"org.freedesktop.Flatpak.Development",
"HostCommand",
params,
- c.G_VARIANT_TYPE("(u)"),
- c.G_DBUS_CALL_FLAGS_NONE,
- c.G_MAXINT,
+ gio_c.G_VARIANT_TYPE("(u)"),
+ gio_c.G_DBUS_CALL_FLAGS_NONE,
+ gio_c.G_MAXINT,
fd_list,
null,
null,
@@ -425,10 +426,10 @@ pub const FlatpakHostCommand = struct {
log.warn("Flatpak.HostCommand failed: {s}", .{err.?.*.message});
return Error.FlatpakRPCFail;
};
- defer c.g_variant_unref(reply);
+ defer gio_c.g_variant_unref(reply);
var pid: u32 = 0;
- c.g_variant_get(reply, "(u)", &pid);
+ gio_c.g_variant_get(reply, "(u)", &pid);
log.debug("HostCommand started pid={} subscription={}", .{
pid,
subscription_id,
@@ -447,31 +448,31 @@ pub const FlatpakHostCommand = struct {
/// Helper to update the state and notify waiters via the cv.
fn updateState(self: *FlatpakHostCommand, state: State) void {
- self.state_mutex.lock();
- defer self.state_mutex.unlock();
- defer self.state_cv.broadcast();
+ self.state_mutex.lockUncancelable(global.io());
+ defer self.state_mutex.unlock(global.io());
+ defer self.state_cv.broadcast(global.io());
self.state = state;
}
fn onExit(
- bus: ?*c.GDBusConnection,
+ bus: ?*gio_c.GDBusConnection,
_: [*c]const u8,
_: [*c]const u8,
_: [*c]const u8,
_: [*c]const u8,
- params: ?*c.GVariant,
+ params: ?*gio_c.GVariant,
ud: ?*anyopaque,
) callconv(.c) void {
const self = @as(*FlatpakHostCommand, @ptrCast(@alignCast(ud)));
const state = state: {
- self.state_mutex.lock();
- defer self.state_mutex.unlock();
+ self.state_mutex.lockUncancelable(global.io());
+ defer self.state_mutex.unlock(global.io());
break :state self.state.started;
};
var pid: u32 = 0;
var exit_status_raw: u32 = 0;
- c.g_variant_get(params.?, "(uu)", &pid, &exit_status_raw);
+ gio_c.g_variant_get(params.?, "(uu)", &pid, &exit_status_raw);
if (state.pid != pid) return;
const exit_status = posix.W.EXITSTATUS(exit_status_raw);
@@ -510,10 +511,10 @@ pub const FlatpakHostCommand = struct {
log.debug("HostCommand exited pid={} status={}", .{ pid, exit_status });
// We're done now, so we can unsubscribe
- c.g_dbus_connection_signal_unsubscribe(bus.?, state.subscription);
+ gio_c.g_dbus_connection_signal_unsubscribe(bus.?, state.subscription);
// We are also done with our loop so we can exit.
- c.g_main_loop_quit(state.loop);
+ gio_c.g_main_loop_quit(state.loop);
}
fn noopCallback(_: ?*anyopaque, _: *xev.Loop, _: *Completion, _: WaitError!u8) void {}
diff --git a/vendor/libghostty-vt/src/os/homedir.zig b/vendor/libghostty-vt/src/os/homedir.zig
index 9e0420f4..8da9640a 100644
--- a/vendor/libghostty-vt/src/os/homedir.zig
+++ b/vendor/libghostty-vt/src/os/homedir.zig
@@ -1,7 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const passwd = @import("passwd.zig");
-const posix = std.posix;
const objc = @import("objc");
const Error = error{
@@ -11,10 +10,10 @@ const Error = error{
/// Determine the home directory for the currently executing user. This
/// is generally an expensive process so the value should be cached.
-pub inline fn home(buf: []u8) !?[]const u8 {
+pub inline fn home(io: std.Io, environ_map: *const std.process.Environ.Map, buf: []u8) !?[]const u8 {
return switch (builtin.os.tag) {
- .linux, .freebsd, .macos => try homeUnix(buf),
- .windows => try homeWindows(buf),
+ .linux, .freebsd, .macos => try homeUnix(io, environ_map, buf),
+ .windows => homeWindows(environ_map, buf) catch return error.BufferTooSmall,
// iOS doesn't have a user-writable home directory
.ios => null,
@@ -23,9 +22,9 @@ pub inline fn home(buf: []u8) !?[]const u8 {
};
}
-fn homeUnix(buf: []u8) !?[]const u8 {
+fn homeUnix(io: std.Io, environ_map: *const std.process.Environ.Map, buf: []u8) !?[]const u8 {
// First: if we have a HOME env var, then we use that.
- if (posix.getenv("HOME")) |result| {
+ if (environ_map.get("HOME")) |result| {
if (buf.len < result.len) return Error.BufferTooSmall;
@memcpy(buf[0..result.len], result);
return buf[0..result.len];
@@ -60,13 +59,14 @@ fn homeUnix(buf: []u8) !?[]const u8 {
// If all else fails, have the shell tell us...
fba.reset();
- const run = try std.process.Child.run(.{
- .allocator = fba.allocator(),
+ const run = try std.process.run(fba.allocator(), io, .{
.argv = &[_][]const u8{ "/bin/sh", "-c", "cd && pwd" },
- .max_output_bytes = fba.buffer.len / 2,
+ .environ_map = environ_map,
+ .stdout_limit = .limited(fba.buffer.len / 2),
+ .stderr_limit = .limited(fba.buffer.len / 2),
});
- if (run.term == .Exited and run.term.Exited == 0) {
+ if (run.term == .exited and run.term.exited == 0) {
const result = trimSpace(run.stdout);
if (buf.len < result.len) return Error.BufferTooSmall;
@memcpy(buf[0..result.len], result);
@@ -76,33 +76,11 @@ fn homeUnix(buf: []u8) !?[]const u8 {
return null;
}
-fn homeWindows(buf: []u8) !?[]const u8 {
- const drive_len = blk: {
- var fba_instance = std.heap.FixedBufferAllocator.init(buf);
- const fba = fba_instance.allocator();
- const drive = std.process.getEnvVarOwned(fba, "HOMEDRIVE") catch |err| switch (err) {
- error.OutOfMemory => return Error.BufferTooSmall,
- error.InvalidWtf8, error.EnvironmentVariableNotFound => return null,
- };
- // could shift the contents if this ever happens
- if (drive.ptr != buf.ptr) @panic("codebug");
- break :blk drive.len;
- };
-
- const path_len = blk: {
- const path_buf = buf[drive_len..];
- var fba_instance = std.heap.FixedBufferAllocator.init(buf[drive_len..]);
- const fba = fba_instance.allocator();
- const homepath = std.process.getEnvVarOwned(fba, "HOMEPATH") catch |err| switch (err) {
- error.OutOfMemory => return Error.BufferTooSmall,
- error.InvalidWtf8, error.EnvironmentVariableNotFound => return null,
- };
- // could shift the contents if this ever happens
- if (homepath.ptr != path_buf.ptr) @panic("codebug");
- break :blk homepath.len;
- };
-
- return buf[0 .. drive_len + path_len];
+fn homeWindows(environ_map: *const std.process.Environ.Map, buf: []u8) !?[]const u8 {
+ var writer: std.Io.Writer = .fixed(buf);
+ _ = try writer.write(environ_map.get("HOMEDRIVE") orelse return null);
+ _ = try writer.write(environ_map.get("HOMEPATH") orelse return null);
+ return writer.buffered();
}
fn trimSpace(input: []const u8) []const u8 {
@@ -119,9 +97,14 @@ pub const ExpandError = error{
///
/// Errors if `home` fails or if the size of the expanded path is larger
/// than `buf.len`.
-pub fn expandHome(path: []const u8, buf: []u8) ExpandError![]const u8 {
+pub fn expandHome(
+ io: std.Io,
+ environ_map: *const std.process.Environ.Map,
+ path: []const u8,
+ buf: []u8,
+) ExpandError![]const u8 {
return switch (builtin.os.tag) {
- .linux, .freebsd, .macos => try expandHomeUnix(path, buf),
+ .linux, .freebsd, .macos => try expandHomeUnix(io, environ_map, path, buf),
// `~/` is not an idiom generally used on Windows
.windows => return path,
@@ -133,9 +116,14 @@ pub fn expandHome(path: []const u8, buf: []u8) ExpandError![]const u8 {
};
}
-fn expandHomeUnix(path: []const u8, buf: []u8) ExpandError![]const u8 {
+fn expandHomeUnix(
+ io: std.Io,
+ environ_map: *const std.process.Environ.Map,
+ path: []const u8,
+ buf: []u8,
+) ExpandError![]const u8 {
if (!std.mem.startsWith(u8, path, "~/")) return path;
- const home_dir: []const u8 = if (home(buf)) |home_|
+ const home_dir: []const u8 = if (home(io, environ_map, buf)) |home_|
home_ orelse return error.HomeDetectionFailed
else |_|
return error.HomeDetectionFailed;
@@ -152,28 +140,33 @@ test "expandHomeUnix" {
if (builtin.os.tag == .windows) return error.SkipZigTest;
const testing = std.testing;
+ const io = testing.io;
const allocator = testing.allocator;
+ var environ_map = try testing.environ.createMap(testing.allocator);
+ defer environ_map.deinit();
var buf: [std.fs.max_path_bytes]u8 = undefined;
- const home_dir = try expandHomeUnix("~/", &buf);
+ const home_dir = try expandHomeUnix(io, &environ_map, "~/", &buf);
// Joining the home directory `~` with the path `/`
// the result should end with a separator here. (e.g. `/home/user/`)
try testing.expect(home_dir[home_dir.len - 1] == std.fs.path.sep);
- const downloads = try expandHomeUnix("~/Downloads/shader.glsl", &buf);
+ const downloads = try expandHomeUnix(io, &environ_map, "~/Downloads/shader.glsl", &buf);
const expected_downloads = try std.mem.concat(allocator, u8, &[_][]const u8{ home_dir, "Downloads/shader.glsl" });
defer allocator.free(expected_downloads);
try testing.expectEqualStrings(expected_downloads, downloads);
- try testing.expectEqualStrings("~", try expandHomeUnix("~", &buf));
- try testing.expectEqualStrings("~abc/", try expandHomeUnix("~abc/", &buf));
- try testing.expectEqualStrings("/home/user", try expandHomeUnix("/home/user", &buf));
- try testing.expectEqualStrings("", try expandHomeUnix("", &buf));
+ try testing.expectEqualStrings("~", try expandHomeUnix(io, &environ_map, "~", &buf));
+ try testing.expectEqualStrings("~abc/", try expandHomeUnix(io, &environ_map, "~abc/", &buf));
+ try testing.expectEqualStrings("/home/user", try expandHomeUnix(io, &environ_map, "/home/user", &buf));
+ try testing.expectEqualStrings("", try expandHomeUnix(io, &environ_map, "", &buf));
// Expect an error if the buffer is large enough to hold the home directory,
// but not the expanded path
var small_buf = try allocator.alloc(u8, home_dir.len);
defer allocator.free(small_buf);
try testing.expectError(error.BufferTooSmall, expandHomeUnix(
+ io,
+ &environ_map,
"~/Downloads",
small_buf[0..],
));
@@ -181,9 +174,12 @@ test "expandHomeUnix" {
test {
const testing = std.testing;
+ const io = testing.io;
+ var environ_map = try testing.environ.createMap(testing.allocator);
+ defer environ_map.deinit();
var buf: [1024]u8 = undefined;
- const result = try home(&buf);
+ const result = try home(io, &environ_map, &buf);
try testing.expect(result != null);
try testing.expect(result.?.len > 0);
}
diff --git a/vendor/libghostty-vt/src/os/hostname.zig b/vendor/libghostty-vt/src/os/hostname.zig
index af9148fb..969b4baa 100644
--- a/vendor/libghostty-vt/src/os/hostname.zig
+++ b/vendor/libghostty-vt/src/os/hostname.zig
@@ -7,91 +7,6 @@ pub const LocalHostnameValidationError = error{
Unexpected,
};
-/// Validates a hostname according to [RFC 1123](https://www.rfc-editor.org/rfc/rfc1123)
-///
-/// std.net.isValidHostname is (currently) too generous. It considers strings like
-/// ".example.com", "exa..mple.com", and "-example.com" to be valid hostnames, which
-/// is incorrect.
-pub fn isValid(hostname: []const u8) bool {
- if (hostname.len == 0) return false;
- if (hostname[0] == '.') return false;
-
- // Ignore trailing dot (FQDN). It doesn't count toward our length.
- const end = if (hostname[hostname.len - 1] == '.') end: {
- if (hostname.len == 1) return false;
- break :end hostname.len - 1;
- } else hostname.len;
-
- if (end > 253) return false;
-
- // Hostnames are divided into dot-separated "labels", which:
- //
- // - Start with a letter or digit
- // - Can contain letters, digits, or hyphens
- // - Must end with a letter or digit
- // - Have a minimum of 1 character and a maximum of 63
- var label_start: usize = 0;
- var label_len: usize = 0;
- for (hostname[0..end], 0..) |c, i| {
- switch (c) {
- '.' => {
- if (label_len == 0 or label_len > 63) return false;
- if (!std.ascii.isAlphanumeric(hostname[label_start])) return false;
- if (!std.ascii.isAlphanumeric(hostname[i - 1])) return false;
-
- label_start = i + 1;
- label_len = 0;
- },
- '-' => {
- label_len += 1;
- },
- else => {
- if (!std.ascii.isAlphanumeric(c)) return false;
- label_len += 1;
- },
- }
- }
-
- // Validate the final label
- if (label_len == 0 or label_len > 63) return false;
- if (!std.ascii.isAlphanumeric(hostname[label_start])) return false;
- if (!std.ascii.isAlphanumeric(hostname[end - 1])) return false;
-
- return true;
-}
-
-test isValid {
- const testing = std.testing;
-
- // Valid hostnames
- try testing.expect(isValid("example"));
- try testing.expect(isValid("example.com"));
- try testing.expect(isValid("www.example.com"));
- try testing.expect(isValid("sub.domain.example.com"));
- try testing.expect(isValid("example.com."));
- try testing.expect(isValid("host-name.example.com."));
- try testing.expect(isValid("123.example.com."));
- try testing.expect(isValid("a-b.com"));
- try testing.expect(isValid("a.b.c.d.e.f.g"));
- try testing.expect(isValid("127.0.0.1")); // Also a valid hostname
- try testing.expect(isValid("a" ** 63 ++ ".com")); // Label exactly 63 chars (valid)
- try testing.expect(isValid("a." ** 126 ++ "a")); // Total length 253 (valid)
-
- // Invalid hostnames
- try testing.expect(!isValid(""));
- try testing.expect(!isValid(".example.com"));
- try testing.expect(!isValid("example.com.."));
- try testing.expect(!isValid("host..domain"));
- try testing.expect(!isValid("-hostname"));
- try testing.expect(!isValid("hostname-"));
- try testing.expect(!isValid("a.-.b"));
- try testing.expect(!isValid("host_name.com"));
- try testing.expect(!isValid("."));
- try testing.expect(!isValid(".."));
- try testing.expect(!isValid("a" ** 64 ++ ".com")); // Label length 64 (too long)
- try testing.expect(!isValid("a." ** 126 ++ "ab")); // Total length 254 (too long)
-}
-
/// Checks if a hostname is local to the current machine. This matches
/// both "localhost" and the current hostname of the machine (as returned
/// by `gethostname`).
@@ -105,7 +20,7 @@ pub fn isLocal(hostname: []const u8) LocalHostnameValidationError!bool {
const windows = @import("windows.zig");
var buf: [256:0]u8 = undefined;
var nSize: windows.DWORD = buf.len;
- if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == 0) return false;
+ if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == windows.FALSE) return false;
const ourHostname = buf[0..nSize];
return std.mem.eql(u8, hostname, ourHostname);
},
@@ -127,7 +42,8 @@ test "isLocal returns true when hostname is local" {
const windows = @import("windows.zig");
var buf: [256:0]u8 = undefined;
var nSize: windows.DWORD = buf.len;
- if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == 0) return error.GetComputerNameFailed;
+ if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == windows.FALSE)
+ return error.GetComputerNameFailed;
const localHostname = buf[0..nSize];
try std.testing.expect(try isLocal(localHostname));
},
diff --git a/vendor/libghostty-vt/src/os/i18n.zig b/vendor/libghostty-vt/src/os/i18n.zig
index 0f7790fa..20d3c1b9 100644
--- a/vendor/libghostty-vt/src/os/i18n.zig
+++ b/vendor/libghostty-vt/src/os/i18n.zig
@@ -62,11 +62,21 @@ pub fn initGlobalDomain() error{OutOfMemory}!void {
}
/// Translate a message for the Ghostty domain.
+///
+/// If this is called at comptime, the direct msgid is returned without
+/// translation. This still allows the string to be marked for translation
+/// while remaining usable in comptime contexts.
pub fn _(msgid: [*:0]const u8) [*:0]const u8 {
if (comptime !build_config.i18n) return msgid;
+ if (@inComptime()) return msgid;
return dgettext(build_config.bundle_id, msgid);
}
+/// Mark a string for translation without translating it immediately.
+pub fn N_(msgid: [:0]const u8) [:0]const u8 {
+ return msgid;
+}
+
/// Canonicalize a locale name from a platform-specific value to
/// a POSIX-compliant value. This is a thin layer over the unexported
/// gnulib-lib function in gettext that does this already.
@@ -191,3 +201,10 @@ test "canonicalizeLocale darwin" {
// underscores. We should parse them out before calling this function.
try testing.expectEqualStrings("en_US.UTF_8", try canonicalizeLocale(&buf, "en_US.UTF-8"));
}
+
+test "_ returns msgid at comptime" {
+ const testing = std.testing;
+
+ const msgid = comptime @"_"("Ghostty");
+ try testing.expectEqualStrings("Ghostty", std.mem.span(msgid));
+}
diff --git a/vendor/libghostty-vt/src/os/i18n_locales.zig b/vendor/libghostty-vt/src/os/i18n_locales.zig
index 5c1ee063..9df96ad8 100644
--- a/vendor/libghostty-vt/src/os/i18n_locales.zig
+++ b/vendor/libghostty-vt/src/os/i18n_locales.zig
@@ -59,4 +59,7 @@ pub const locales = [_][:0]const u8{
"kk",
"be",
"eu",
+ "da",
+ "sr",
+ "sr@latin",
};
diff --git a/vendor/libghostty-vt/src/os/kernel_info.zig b/vendor/libghostty-vt/src/os/kernel_info.zig
index e57cc304..60e6b49f 100644
--- a/vendor/libghostty-vt/src/os/kernel_info.zig
+++ b/vendor/libghostty-vt/src/os/kernel_info.zig
@@ -1,15 +1,20 @@
const std = @import("std");
const builtin = @import("builtin");
+const global = @import("../global.zig");
pub fn getKernelInfo(alloc: std.mem.Allocator) ?[]const u8 {
if (comptime builtin.os.tag != .linux) return null;
const path = "/proc/sys/kernel/osrelease";
- var file = std.fs.openFileAbsolute(path, .{}) catch return null;
- defer file.close();
+ var file = std.Io.Dir.openFileAbsolute(global.io(), path, .{}) catch return null;
+ defer file.close(global.io());
// 128 bytes should be enough to hold the kernel information
- const kernel_info = file.readToEndAlloc(alloc, 128) catch return null;
- defer alloc.free(kernel_info);
+ var kernel_info_buf: [128]u8 = undefined;
+ const kernel_info = kernel_info_buf[0 .. file.readPositionalAll(
+ global.io(),
+ &kernel_info_buf,
+ 0,
+ ) catch return null];
return alloc.dupe(u8, std.mem.trim(u8, kernel_info, &std.ascii.whitespace)) catch return null;
}
diff --git a/vendor/libghostty-vt/src/os/locale.zig b/vendor/libghostty-vt/src/os/locale.zig
index faba3bd2..ceb3c883 100644
--- a/vendor/libghostty-vt/src/os/locale.zig
+++ b/vendor/libghostty-vt/src/os/locale.zig
@@ -9,13 +9,10 @@ const i18n = internal_os.i18n;
const log = std.log.scoped(.os_locale);
/// Ensure that the locale is set.
-pub fn ensureLocale(alloc: std.mem.Allocator) !void {
- assert(builtin.link_libc);
-
- // Get our LANG env var. We use this many times but we also need
- // the original value later.
- const lang = try internal_os.getenv(alloc, "LANG");
- defer if (lang) |v| v.deinit(alloc);
+pub fn ensureLocale() !void {
+ // This does a lot of in-process mutation of the environment and can't be
+ // run in a test as a result.
+ assert(!builtin.is_test);
// On macOS, pre-populate the LANG env var with system preferences.
// When launching the .app, LANG is not set so we must query it from the
@@ -23,7 +20,8 @@ pub fn ensureLocale(alloc: std.mem.Allocator) !void {
// process.
if (comptime builtin.target.os.tag.isDarwin()) {
// Set the lang if it is not set or if its empty.
- if (lang == null or lang.?.value.len == 0) {
+ const lang = std.posix.system.getenv("LANG");
+ if (lang == null or lang.?[0] == 0) {
setLangFromCocoa();
}
}
@@ -34,16 +32,29 @@ pub fn ensureLocale(alloc: std.mem.Allocator) !void {
return;
}
+ if (builtin.os.tag == .windows) {
+ // Exit early for Windows.
+ //
+ // NOTE: There currently is no official Windows version of Ghostty,
+ // either by way of an official Windows port or through use of
+ // libghostty-internal. As such this function is likely unused on those
+ // platforms, so this serves as a TODO stub to support more robust
+ // locale support on Windows. setlocale on Windows sets an
+ // "implementation-defined native environment" with an empty string, so
+ // we are currently just doing best-effort for now.
+ log.info("TODO: setlocale failed on Windows, implement better fallbacks", .{});
+ return;
+ }
+
// setlocale failed. This is probably because the LANG env var is
// invalid. Try to set it without the LANG var set to use the system
// default.
- if ((try internal_os.getenv(alloc, "LANG"))) |old_lang| {
- defer old_lang.deinit(alloc);
- if (old_lang.value.len > 0) {
+ if (std.posix.system.getenv("LANG")) |lang| {
+ if (lang[0] != 0) {
// We don't need to do both of these things but we do them
// both to be sure that lang is either empty or unset completely.
- _ = internal_os.setenv("LANG", "");
- _ = internal_os.unsetenv("LANG");
+ _ = setenv("LANG", "", 1);
+ _ = unsetenv("LANG");
if (setlocale(LC_ALL, "")) |v| {
log.info("setlocale after unset lang result={s}", .{v});
@@ -59,7 +70,7 @@ pub fn ensureLocale(alloc: std.mem.Allocator) !void {
// Failure again... fallback to en_US.UTF-8
log.warn("setlocale failed with LANG and system default. Falling back to en_US.UTF-8", .{});
if (setlocale(LC_ALL, "en_US.UTF-8")) |v| {
- _ = internal_os.setenv("LANG", "en_US.UTF-8");
+ _ = setenv("LANG", "en_US.UTF-8", 1);
log.info("setlocale default result={s}", .{v});
return;
} else log.warn("setlocale failed even with the fallback, uncertain results", .{});
@@ -106,7 +117,7 @@ fn setLangFromCocoa() void {
log.info("detected system locale={s}", .{env_value});
// Set it onto our environment
- if (internal_os.setenv("LANG", env_value) < 0) {
+ if (setenv("LANG", @ptrCast(env_value), 1) < 0) {
log.warn("error setting locale env var", .{});
return;
}
@@ -129,7 +140,7 @@ fn setLangFromCocoa() void {
"setting LANGUAGE from preferred languages value={s}",
.{pref},
);
- _ = internal_os.setenv("LANGUAGE", pref);
+ _ = setenv("LANGUAGE", @ptrCast(pref), 1);
}
}
@@ -156,8 +167,7 @@ fn preferredLanguageFromCocoa(
buf: []u8,
NSLocale: objc.Class,
) error{NoSpaceLeft}!?[:0]const u8 {
- var fbs = std.io.fixedBufferStream(buf);
- const writer = fbs.writer();
+ var writer: std.Io.Writer = .fixed(buf);
// We need to get our app's preferred languages. These may not
// match the system locale (NSLocale.currentLocale).
@@ -180,7 +190,7 @@ fn preferredLanguageFromCocoa(
};
// Append our separator if we have any previous languages
- if (fbs.pos > 0) {
+ if (writer.end > 0) {
_ = writer.writeByte(':') catch
return error.NoSpaceLeft;
}
@@ -188,10 +198,10 @@ fn preferredLanguageFromCocoa(
// Apple languages are in BCP-47 format, and we need to
// canonicalize them to the POSIX format.
const canon = try i18n.canonicalizeLocale(
- fbs.buffer[fbs.pos..],
+ writer.buffer[writer.end..],
c_str,
);
- fbs.seekBy(@intCast(canon.len)) catch unreachable;
+ writer.end += canon.len;
// The canonicalized locale never contains the encoding and
// all of our translations require UTF-8 so we add that.
@@ -199,14 +209,14 @@ fn preferredLanguageFromCocoa(
}
// If we had no preferred languages then we return nothing.
- if (fbs.pos == 0) return null;
+ if (writer.end == 0) return null;
// Null terminate it
_ = writer.writeByte(0) catch return error.NoSpaceLeft;
// Get our slice, this won't be null terminated so we have to
// reslice it with the null terminator.
- const slice = fbs.getWritten();
+ const slice = writer.buffered();
return slice[0 .. slice.len - 1 :0];
}
@@ -217,3 +227,6 @@ const locale_t = c.locale_t;
const setlocale = c.setlocale;
const newlocale = c.newlocale;
const freelocale = c.freelocale;
+
+extern "c" fn setenv(name: ?[*]const u8, value: ?[*]const u8, overwrite: c_int) c_int;
+extern "c" fn unsetenv(name: ?[*]const u8) c_int;
diff --git a/vendor/libghostty-vt/src/os/mach.zig b/vendor/libghostty-vt/src/os/mach.zig
index 4477bd12..5f5a3ed8 100644
--- a/vendor/libghostty-vt/src/os/mach.zig
+++ b/vendor/libghostty-vt/src/os/mach.zig
@@ -46,6 +46,9 @@ pub fn taggedPageAllocator(tag: VMTag) Allocator {
/// This is based heavily on the Zig 0.15.2 PageAllocator implementation,
/// with only the posix implementation. Zig 0.15.2 is MIT licensed.
const TaggedPageAllocator = struct {
+ // Static local next_mmap_addr_hint since this has been removed from stdlib now
+ var next_mmap_addr_hint: ?[*]align(std.heap.page_size_min) u8 = null;
+
pub const vtable: Allocator.VTable = .{
.alloc = alloc,
.resize = resize,
@@ -92,11 +95,11 @@ const TaggedPageAllocator = struct {
aligned_len
else
mem.alignForward(usize, aligned_len + max_drop_len, page_size);
- const hint = @atomicLoad(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, .unordered);
+ const hint = @atomicLoad(@TypeOf(next_mmap_addr_hint), &next_mmap_addr_hint, .unordered);
const slice = std.posix.mmap(
hint,
overalloc_len,
- std.posix.PROT.READ | std.posix.PROT.WRITE,
+ .{ .READ = true, .WRITE = true },
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
tag.make(),
0,
@@ -110,7 +113,7 @@ const TaggedPageAllocator = struct {
const remaining_len = overalloc_len - drop_len;
if (remaining_len > aligned_len) std.posix.munmap(@alignCast(result_ptr[aligned_len..remaining_len]));
const new_hint: [*]align(std.heap.page_size_min) u8 = @alignCast(result_ptr + aligned_len);
- _ = @cmpxchgStrong(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, hint, new_hint, .monotonic, .monotonic);
+ _ = @cmpxchgStrong(@TypeOf(next_mmap_addr_hint), &next_mmap_addr_hint, hint, new_hint, .monotonic, .monotonic);
return result_ptr;
}
diff --git a/vendor/libghostty-vt/src/os/macos.zig b/vendor/libghostty-vt/src/os/macos.zig
index fcd1c3e5..b59882d7 100644
--- a/vendor/libghostty-vt/src/os/macos.zig
+++ b/vendor/libghostty-vt/src/os/macos.zig
@@ -28,7 +28,7 @@ pub fn appSupportDir(
return try commonDir(
alloc,
.NSApplicationSupportDirectory,
- &.{ build_config.bundle_id, sub_path },
+ sub_path,
);
}
@@ -43,7 +43,7 @@ pub fn cacheDir(
return try commonDir(
alloc,
.NSCachesDirectory,
- &.{ build_config.bundle_id, sub_path },
+ sub_path,
);
}
@@ -110,7 +110,7 @@ pub const NSSearchPathDomainMask = enum(c_ulong) {
fn commonDir(
alloc: Allocator,
directory: NSSearchPathDirectory,
- sub_paths: []const []const u8,
+ sub_path: []const u8,
) (error{AppleAPIFailed} || Allocator.Error)![]const u8 {
comptime assert(builtin.target.os.tag.isDarwin());
@@ -140,13 +140,10 @@ fn commonDir(
return error.AppleAPIFailed;
const base_dir = std.mem.sliceTo(c_str, 0);
- // Create a new array with base_dir as the first element
- var paths = try alloc.alloc([]const u8, sub_paths.len + 1);
- paths[0] = base_dir;
- @memcpy(paths[1..], sub_paths);
- defer alloc.free(paths);
-
- return try std.fs.path.join(alloc, paths);
+ return try std.fs.path.join(
+ alloc,
+ &.{ base_dir, build_config.bundle_id, sub_path },
+ );
}
test "cacheDir paths" {
diff --git a/vendor/libghostty-vt/src/os/main.zig b/vendor/libghostty-vt/src/os/main.zig
index b3018274..05f2a45a 100644
--- a/vendor/libghostty-vt/src/os/main.zig
+++ b/vendor/libghostty-vt/src/os/main.zig
@@ -1,12 +1,11 @@
//! The "os" package contains utilities for interfacing with the operating
-//! system. These aren't restricted to syscalls or low-level operations, but
+//! system. These aren't restricted to syscalls or low-level operations, butos/main.zig
//! also OS-specific features and conventions.
const builtin = @import("builtin");
const dbus = @import("dbus.zig");
const desktop = @import("desktop.zig");
-const env = @import("env.zig");
const file = @import("file.zig");
const flatpak = @import("flatpak.zig");
const homedir = @import("homedir.zig");
@@ -19,8 +18,8 @@ const systemd = @import("systemd.zig");
const kernel_info = @import("kernel_info.zig");
// Namespaces
-pub const args = @import("args.zig");
pub const cgroup = @import("cgroup.zig");
+pub const edit = @import("edit.zig");
pub const hostname = @import("hostname.zig");
pub const i18n = @import("i18n.zig");
pub const mach = @import("mach.zig");
@@ -30,19 +29,12 @@ pub const xdg = @import("xdg.zig");
pub const windows = @import("windows.zig");
pub const macos = @import("macos.zig");
pub const shell = @import("shell.zig");
+pub const stderr = @import("stderr.zig");
pub const uri = @import("uri.zig");
// Functions and types
pub const CFReleaseThread = @import("cf_release_thread.zig");
pub const TempDir = @import("TempDir.zig");
-pub const GetEnvResult = env.GetEnvResult;
-pub const getEnvMap = env.getEnvMap;
-pub const appendEnv = env.appendEnv;
-pub const appendEnvAlways = env.appendEnvAlways;
-pub const prependEnv = env.prependEnv;
-pub const getenv = env.getenv;
-pub const setenv = env.setenv;
-pub const unsetenv = env.unsetenv;
pub const launchedFromDesktop = desktop.launchedFromDesktop;
pub const launchedByDbusActivation = dbus.launchedByDbusActivation;
pub const launchedBySystemd = systemd.launchedBySystemd;
@@ -50,8 +42,6 @@ pub const desktopEnvironment = desktop.desktopEnvironment;
pub const rlimit = file.rlimit;
pub const fixMaxFiles = file.fixMaxFiles;
pub const restoreMaxFiles = file.restoreMaxFiles;
-pub const allocTmpDir = file.allocTmpDir;
-pub const freeTmpDir = file.freeTmpDir;
pub const randomTmpPath = file.randomTmpPath;
pub const isFlatpak = flatpak.isFlatpak;
pub const FlatpakHostCommand = flatpak.FlatpakHostCommand;
@@ -66,9 +56,12 @@ pub const resourcesDir = resourcesdir.resourcesDir;
pub const ResourcesDir = resourcesdir.ResourcesDir;
pub const ShellEscapeWriter = shell.ShellEscapeWriter;
pub const getKernelInfo = kernel_info.getKernelInfo;
+pub const getConfigEditCommand = edit.getConfigEditCommand;
test {
_ = file;
+ _ = stderr;
+ _ = edit;
_ = i18n;
_ = path;
_ = uri;
diff --git a/vendor/libghostty-vt/src/os/open.zig b/vendor/libghostty-vt/src/os/open.zig
index 86c3d76f..e7574618 100644
--- a/vendor/libghostty-vt/src/os/open.zig
+++ b/vendor/libghostty-vt/src/os/open.zig
@@ -3,6 +3,7 @@ const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const build_config = @import("../build_config.zig");
const apprt = @import("../apprt.zig");
+const global = @import("../global.zig");
const log = std.log.scoped(.@"os-open");
@@ -15,74 +16,81 @@ const log = std.log.scoped(.@"os-open");
/// way to open URLs. If you are implementing an apprt for Ghostty, you should
/// consider doing something special-cased for your platform.
pub fn open(
- alloc: Allocator,
kind: apprt.action.OpenUrl.Kind,
url: []const u8,
) !void {
- var exe: std.process.Child = switch (builtin.os.tag) {
- .linux, .freebsd => .init(
- &.{ "xdg-open", url },
- alloc,
- ),
-
- .windows => .init(
- &.{ "rundll32", "url.dll,FileProtocolHandler", url },
- alloc,
- ),
-
- .macos => .init(
- switch (kind) {
- .text => &.{ "open", "-t", url },
- .html, .unknown => &.{ "open", url },
- },
- alloc,
- ),
+ // On macOS, the apprt handles OSC 8 targets before this fallback. Ghostty's
+ // native apprt applies its allowlist, confirmation, and file safety policy.
+ // If a macOS embedder declines the action, fail closed rather than bypassing
+ // that policy by handing producer-controlled terminal output to `open`.
+ if (comptime builtin.os.tag == .macos) {
+ if (kind == .osc8) return error.UnsafeOSC8Link;
+ }
+ var spawn_opts: std.process.SpawnOptions = switch (builtin.os.tag) {
+ .linux, .freebsd => .{ .argv = &.{ "xdg-open", url } },
+ .windows => .{ .argv = &.{ "rundll32", "url.dll,FileProtocolHandler", url } },
+ .macos => switch (kind) {
+ .text => .{ .argv = &.{ "open", "-t", url } },
+ .html, .unknown => .{ .argv = &.{ "open", url } },
+ .osc8 => unreachable,
+ },
.ios => return error.Unimplemented,
else => @compileError("unsupported OS"),
};
-
// Ignore anything from stdout. This must be set before spawning the
// process.
- exe.stdout_behavior = .Ignore;
+ spawn_opts.stdout = .ignore;
// Pipe stderr so we can log the stderr from the command. This must be set
// before spawning the process.
- exe.stderr_behavior = .Pipe;
+ spawn_opts.stderr = .pipe;
- // In the snap on Linux the launcher exports LD_LIBRARY_PATH pointing at
- // the snap's bundled libraries. Leaking this into child process can can be
- // problematic, so let's drop it from the env
- var snap_env: std.process.EnvMap = if (comptime build_config.snap) blk: {
- var env = try std.process.getEnvMap(alloc);
- env.remove("LD_LIBRARY_PATH");
- break :blk env;
- } else undefined;
- defer if (comptime build_config.snap) snap_env.deinit();
- if (comptime build_config.snap) exe.env_map = &snap_env;
+ const exe = if (comptime build_config.snap) local_env: {
+ // In the snap on Linux the launcher exports LD_LIBRARY_PATH
+ // pointing at the snap's bundled libraries. Leaking this into
+ // child process can can be problematic, so let's drop it from the
+ // env.
+ //
+ // Note that `spawn` copies the passed in `Environ.Map` into a
+ // fresh `Environ` block, so this is safe to release immediately
+ // after spawn.
+ var environ_map = try global.environMap();
+ defer environ_map.deinit();
+ _ = environ_map.orderedRemove("LD_LIBRARY_PATH");
+ spawn_opts.environ_map = &environ_map;
+ break :local_env try std.process.spawn(global.io(), spawn_opts);
+ } else
+ // Non-snap releases don't need to alter the env.
+ try std.process.spawn(global.io(), spawn_opts);
- // Spawn the process on our same thread so we can detect failure
- // quickly.
- try exe.spawn();
-
- // Create a thread that handles collecting output and reaping the process.
- // This is done in a separate thread because SOME open implementations block
- // and some do not. It's easier to just spawn a thread to handle this so
- // that we never block.
- const thread = try std.Thread.spawn(.{}, openThread, .{exe});
+ const thread = try std.Thread.spawn(.{}, openThread, .{ global.io(), exe });
thread.detach();
}
-fn openThread(exe_: std.process.Child) void {
+test "macOS OSC 8 links have no generic opener fallback" {
+ if (builtin.os.tag != .macos) return error.SkipZigTest;
+
+ try std.testing.expectError(
+ error.UnsafeOSC8Link,
+ open(.osc8, "file:///tmp/payload.command"),
+ );
+}
+
+fn openThread(io: std.Io, exe_: std.process.Child) void {
// Copy the exe so it is non-const. This is necessary because wait()
// requires a mutable reference and we can't have one as a thread
// param.
var exe = exe_;
if (exe.stderr) |stderr| {
var buffer: [256]u8 = undefined;
- var stream = stderr.readerStreaming(&buffer);
+ var stream = stderr.readerStreaming(io, &buffer);
const reader = &stream.interface;
while (true) {
- const line = reader.takeDelimiterExclusive('\n') catch |outer| switch (outer) {
+ // Read inclusively so the delimiter is consumed:
+ // takeDelimiterExclusive leaves the '\n' buffered, so once the
+ // child writes a line this loop would receive an empty slice
+ // forever, pinning a core and spamming empty warnings.
+ const line = reader.takeDelimiterInclusive('\n') catch |outer| switch (outer) {
error.EndOfStream => break,
error.ReadFailed => break,
error.StreamTooLong => reader.take(buffer.len) catch |inner| switch (inner) {
@@ -90,8 +98,8 @@ fn openThread(exe_: std.process.Child) void {
error.EndOfStream => break,
},
};
- log.warn("open stderr={s}", .{line});
+ log.warn("open stderr={s}", .{std.mem.trimEnd(u8, line, "\n")});
}
}
- _ = exe.wait() catch {};
+ _ = exe.wait(io) catch {};
}
diff --git a/vendor/libghostty-vt/src/os/passwd.zig b/vendor/libghostty-vt/src/os/passwd.zig
index e9bbff06..7c16e54e 100644
--- a/vendor/libghostty-vt/src/os/passwd.zig
+++ b/vendor/libghostty-vt/src/os/passwd.zig
@@ -5,6 +5,7 @@ const build_config = @import("../build_config.zig");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const posix = std.posix;
+const compat_fd = @import("../lib/compat/fd.zig");
const log = std.log.scoped(.passwd);
@@ -89,11 +90,11 @@ pub fn get(alloc: Allocator) !Entry {
// Once started, we can close the child side. We do this after
// wait right now but that is fine too. This lets us read the
// parent and detect EOF.
- _ = posix.close(pty.slave);
+ _ = compat_fd.close(pty.slave);
// Read all of our output
const output = output: {
- var output: std.ArrayListUnmanaged(u8) = .{};
+ var output: std.ArrayListUnmanaged(u8) = .empty;
while (true) {
const n = posix.read(pty.master, &buf) catch |err| {
switch (err) {
@@ -116,7 +117,7 @@ pub fn get(alloc: Allocator) !Entry {
};
// Shell and home are the last two entries
- var it = std.mem.splitBackwardsScalar(u8, std.mem.trimRight(u8, output, " \r\n"), ':');
+ var it = std.mem.splitBackwardsScalar(u8, std.mem.trimEnd(u8, output, " \r\n"), ':');
result.shell = if (it.next()) |v| try alloc.dupeZ(u8, v) else null;
result.home = if (it.next()) |v| try alloc.dupeZ(u8, v) else null;
return result;
diff --git a/vendor/libghostty-vt/src/os/path.zig b/vendor/libghostty-vt/src/os/path.zig
index 730ae692..ba1a63f7 100644
--- a/vendor/libghostty-vt/src/os/path.zig
+++ b/vendor/libghostty-vt/src/os/path.zig
@@ -6,22 +6,19 @@ const testing = std.testing;
/// Search for "cmd" in the PATH and return the absolute path. This will
/// always allocate if there is a non-null result. The caller must free the
/// resulting value.
-pub fn expand(alloc: Allocator, cmd: []const u8) !?[]u8 {
+pub fn expand(
+ io: std.Io,
+ alloc: Allocator,
+ environ_map: *const std.process.Environ.Map,
+ cmd: []const u8,
+) !?[]u8 {
// If the command already contains a slash, then we return it as-is
// because it is assumed to be absolute or relative.
if (std.mem.indexOfScalar(u8, cmd, '/') != null) {
return try alloc.dupe(u8, cmd);
}
- const PATH = switch (builtin.os.tag) {
- .windows => blk: {
- const win_path = std.process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse return null;
- const path = try std.unicode.utf16LeToUtf8Alloc(alloc, win_path);
- break :blk path;
- },
- else => std.posix.getenvZ("PATH") orelse return null,
- };
- defer if (builtin.os.tag == .windows) alloc.free(PATH);
+ const PATH = environ_map.get("PATH") orelse return null;
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
var it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter);
@@ -39,7 +36,8 @@ pub fn expand(alloc: Allocator, cmd: []const u8) !?[]u8 {
const full_path = path_buf[0..path_len :0];
// Stat it
- const f = std.fs.cwd().openFile(
+ const f = std.Io.Dir.cwd().openFile(
+ io,
full_path,
.{},
) catch |err| switch (err) {
@@ -52,9 +50,9 @@ pub fn expand(alloc: Allocator, cmd: []const u8) !?[]u8 {
},
else => return err,
};
- defer f.close();
- const stat = try f.stat();
- if (stat.kind != .directory and isExecutable(stat.mode)) {
+ defer f.close(io);
+ const stat = try f.stat(io);
+ if (stat.kind != .directory and isExecutable(stat.permissions)) {
return try alloc.dupe(u8, full_path);
}
}
@@ -64,26 +62,39 @@ pub fn expand(alloc: Allocator, cmd: []const u8) !?[]u8 {
return null;
}
-fn isExecutable(mode: std.fs.File.Mode) bool {
- if (builtin.os.tag == .windows) return true;
- return mode & 0o0111 != 0;
+fn isExecutable(perms: std.Io.File.Permissions) bool {
+ return switch (builtin.os.tag) {
+ .windows => true,
+ else => posix: {
+ break :posix switch (std.posix.mode_t) {
+ u0 => true,
+ else => perms.toMode() & 0o0111 != 0,
+ };
+ },
+ };
}
// `uname -n` is the *nix equivalent of `hostname.exe` on Windows
test "expand: hostname" {
+ var environ_map = try testing.environ.createMap(testing.allocator);
+ defer environ_map.deinit();
const executable = if (builtin.os.tag == .windows) "hostname.exe" else "uname";
- const path = (try expand(testing.allocator, executable)).?;
+ const path = (try expand(testing.io, testing.allocator, &environ_map, executable)).?;
defer testing.allocator.free(path);
try testing.expect(path.len > executable.len);
}
test "expand: does not exist" {
- const path = try expand(testing.allocator, "thisreallyprobablydoesntexist123");
+ var environ_map = try testing.environ.createMap(testing.allocator);
+ defer environ_map.deinit();
+ const path = try expand(testing.io, testing.allocator, &environ_map, "thisreallyprobablydoesntexist123");
try testing.expect(path == null);
}
test "expand: slash" {
- const path = (try expand(testing.allocator, "foo/env")).?;
+ var environ_map = try testing.environ.createMap(testing.allocator);
+ defer environ_map.deinit();
+ const path = (try expand(testing.io, testing.allocator, &environ_map, "foo/env")).?;
defer testing.allocator.free(path);
try testing.expect(path.len == 7);
}
diff --git a/vendor/libghostty-vt/src/os/pipe.zig b/vendor/libghostty-vt/src/os/pipe.zig
index 2cb7bd4a..0310fb3e 100644
--- a/vendor/libghostty-vt/src/os/pipe.zig
+++ b/vendor/libghostty-vt/src/os/pipe.zig
@@ -2,17 +2,18 @@ const std = @import("std");
const builtin = @import("builtin");
const windows = @import("windows.zig");
const posix = std.posix;
+const compat_fd = @import("../lib/compat/fd.zig");
/// pipe() that works on Windows and POSIX. For POSIX systems, this sets
/// CLOEXEC on the file descriptors.
pub fn pipe() ![2]posix.fd_t {
switch (builtin.os.tag) {
- else => return try posix.pipe2(.{ .CLOEXEC = true }),
+ else => return compat_fd.pipe2(.{ .CLOEXEC = true }),
.windows => {
var read: windows.HANDLE = undefined;
var write: windows.HANDLE = undefined;
- if (windows.exp.kernel32.CreatePipe(&read, &write, null, 0) == 0) {
- return windows.unexpectedError(windows.kernel32.GetLastError());
+ if (windows.exp.kernel32.CreatePipe(&read, &write, null, 0) == windows.FALSE) {
+ return windows.unexpectedError(windows.GetLastError());
}
return .{ read, write };
diff --git a/vendor/libghostty-vt/src/os/resourcesdir.zig b/vendor/libghostty-vt/src/os/resourcesdir.zig
index 278de44f..ef8b5a28 100644
--- a/vendor/libghostty-vt/src/os/resourcesdir.zig
+++ b/vendor/libghostty-vt/src/os/resourcesdir.zig
@@ -1,6 +1,7 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
+const global = @import("../global.zig");
pub const ResourcesDir = struct {
/// Avoid accessing these directly, use the app() and host() methods instead.
@@ -16,7 +17,7 @@ pub const ResourcesDir = struct {
/// Get the directory to the bundled resources directory accessible
/// by the application.
- pub fn app(self: *ResourcesDir) ?[]const u8 {
+ pub fn app(self: *const ResourcesDir) ?[]const u8 {
return self.app_path;
}
@@ -26,7 +27,7 @@ pub const ResourcesDir = struct {
/// itself.
///
/// In non-sandboxed environment, this should be the same as app().
- pub fn host(self: *ResourcesDir) ?[]const u8 {
+ pub fn host(self: *const ResourcesDir) ?[]const u8 {
return self.host_path orelse self.app_path;
}
};
@@ -47,13 +48,13 @@ pub fn resourcesDir(alloc: Allocator) !ResourcesDir {
//
// Note: we ALWAYS want to allocate here because the result is always
// freed, do not try to use internal_os.getenv or posix getenv.
- if (comptime builtin.mode != .Debug) {
- if (std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
- if (dir.len > 0) return .{ .app_path = dir };
- } else |err| switch (err) {
- error.EnvironmentVariableNotFound => {},
+ if (comptime builtin.mode != .Debug) env: {
+ const dir = global.environ().getAlloc(alloc, "GHOSTTY_RESOURCES_DIR") catch |err| switch (err) {
+ error.EnvironmentVariableMissing => break :env,
else => return err,
- }
+ };
+
+ if (dir.len > 0) return .{ .app_path = dir };
}
// This is the sentinel value we look for in the path to know
@@ -67,7 +68,10 @@ pub fn resourcesDir(alloc: Allocator) !ResourcesDir {
// Get the path to our running binary
var exe_buf: [std.fs.max_path_bytes]u8 = undefined;
- var exe: []const u8 = std.fs.selfExePath(&exe_buf) catch return .{};
+ var exe: []const u8 = exe_buf[0 .. std.process.executablePath(
+ global.io(),
+ &exe_buf,
+ ) catch return .{}];
// We have an exe path! Climb the tree looking for the terminfo
// bundle as we expect it.
@@ -78,7 +82,12 @@ pub fn resourcesDir(alloc: Allocator) !ResourcesDir {
// On MacOS, we look for the app bundle path.
if (comptime builtin.target.os.tag.isDarwin()) {
inline for (sentinels) |sentinel| {
- if (try maybeDir(&dir_buf, dir, "Contents/Resources", sentinel)) |v| {
+ if (try maybeDir(
+ &dir_buf,
+ dir,
+ "Contents/Resources",
+ sentinel,
+ )) |v| {
return .{ .app_path = try std.fs.path.join(alloc, &.{ v, "ghostty" }) };
}
}
@@ -102,10 +111,10 @@ pub fn resourcesDir(alloc: Allocator) !ResourcesDir {
// If terminfo detection failed in debug builds (somehow),
// fallback and use the provided resources dir.
if (comptime builtin.mode == .Debug) {
- if (std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
+ if (global.environ().getAlloc(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
if (dir.len > 0) return .{ .app_path = dir };
} else |err| switch (err) {
- error.EnvironmentVariableNotFound => {},
+ error.InvalidWtf8, error.EnvironmentVariableMissing => {},
else => return err,
}
}
@@ -127,7 +136,7 @@ pub fn maybeDir(
) !?[]const u8 {
const path = try std.fmt.bufPrint(buf, "{s}/{s}/{s}", .{ base, sub, suffix });
- if (std.fs.accessAbsolute(path, .{})) {
+ if (std.Io.Dir.accessAbsolute(global.io(), path, .{})) {
const len = path.len - suffix.len - 1;
return buf[0..len];
} else |_| {
diff --git a/vendor/libghostty-vt/src/os/stderr.zig b/vendor/libghostty-vt/src/os/stderr.zig
new file mode 100644
index 00000000..201bc438
--- /dev/null
+++ b/vendor/libghostty-vt/src/os/stderr.zig
@@ -0,0 +1,60 @@
+//! Raw, best-effort stderr writing that bypasses `std.Io` entirely.
+//!
+//! `std.debug.lockStderr` (and everything layered on top of it) routes
+//! through `std.Options.debug_io`, whose default implementation is
+//! `std.Io.Threaded`. This keeps ~70KB of unreachable code in binaries.
+
+const std = @import("std");
+const builtin = @import("builtin");
+
+/// Write bytes to stderr using the most primitive mechanism available
+/// for the target. This is best-effort: errors are ignored, since this
+/// is only used for diagnostics (logging and panic messages).
+///
+/// Freestanding targets (e.g. wasm) have no stderr, so this is a no-op
+/// there.
+pub fn write(bytes: []const u8) void {
+ switch (builtin.os.tag) {
+ .freestanding, .other => {},
+
+ .windows => {
+ const windows = std.os.windows;
+ const handle = windows.peb().ProcessParameters.hStdError;
+ var iosb: windows.IO_STATUS_BLOCK = undefined;
+ _ = windows.ntdll.NtWriteFile(
+ handle,
+ null, // event
+ null, // APC routine
+ null, // APC context
+ &iosb,
+ bytes.ptr,
+ @intCast(bytes.len),
+ null, // byte offset
+ null, // key
+ );
+ },
+
+ else => {
+ const posix = std.posix;
+ var i: usize = 0;
+ while (i < bytes.len) {
+ const rc = posix.system.write(
+ posix.STDERR_FILENO,
+ bytes[i..].ptr,
+ bytes.len - i,
+ );
+ switch (posix.errno(rc)) {
+ .SUCCESS => i += @as(usize, @intCast(rc)),
+ .INTR => continue,
+ else => return,
+ }
+ }
+ },
+ }
+}
+
+test write {
+ // Smoke test: must not crash. We can't assert on the output without
+ // capturing stderr, which isn't worth the complexity here.
+ write("");
+}
diff --git a/vendor/libghostty-vt/src/os/systemd.zig b/vendor/libghostty-vt/src/os/systemd.zig
index 1a9a10ee..5815648a 100644
--- a/vendor/libghostty-vt/src/os/systemd.zig
+++ b/vendor/libghostty-vt/src/os/systemd.zig
@@ -1,5 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
+const global = @import("../global.zig");
const log = std.log.scoped(.systemd);
@@ -16,8 +17,8 @@ pub fn launchedBySystemd() bool {
// `JOURNAL_STREAM` (v231+) environment variables. If these
// environment variables are not present we were not launched by
// systemd.
- if (std.posix.getenv("INVOCATION_ID") == null) break :linux false;
- if (std.posix.getenv("JOURNAL_STREAM") == null) break :linux false;
+ if (global.environ().getPosix("INVOCATION_ID") == null) break :linux false;
+ if (global.environ().getPosix("JOURNAL_STREAM") == null) break :linux false;
// If `INVOCATION_ID` and `JOURNAL_STREAM` are present, check to make sure
// that our parent process is actually `systemd`, not some other terminal
@@ -33,7 +34,7 @@ pub fn launchedBySystemd() bool {
log.err("unable to format comm path for pid {d}", .{ppid});
break :linux false;
};
- const comm_file = std.fs.openFileAbsolute(comm_path, .{ .mode = .read_only }) catch {
+ const comm_file = std.Io.Dir.openFileAbsolute(global.io(), comm_path, .{ .mode = .read_only }) catch {
log.err("unable to open '{s}' for reading", .{comm_path});
break :linux false;
};
@@ -91,7 +92,7 @@ pub const notify = struct {
if (comptime builtin.os.tag != .linux) return;
// Get the socket address that should receive notifications.
- const socket_path = std.posix.getenv("NOTIFY_SOCKET") orelse return;
+ const socket_path = global.environ().getPosix("NOTIFY_SOCKET") orelse return;
// If the socket address is an empty string return.
if (socket_path.len == 0) return;
@@ -121,7 +122,7 @@ pub const notify = struct {
std.os.linux.SOCK.DGRAM | std.os.linux.SOCK.CLOEXEC,
0,
);
- switch (std.os.linux.E.init(rc)) {
+ switch (linuxErrnoFromSyscall(rc)) {
.SUCCESS => break :socket @intCast(rc),
else => |e| {
log.warn("creating socket failed: {s}", .{@tagName(e)});
@@ -138,7 +139,7 @@ pub const notify = struct {
&socket_address,
@offsetOf(std.os.linux.sockaddr.un, "path") + socket_address.path.len,
);
- switch (std.os.linux.E.init(rc)) {
+ switch (linuxErrnoFromSyscall(rc)) {
.SUCCESS => break :connect,
else => |e| {
log.warn("unable to connect to notify socket: {s}", .{@tagName(e)});
@@ -149,7 +150,7 @@ pub const notify = struct {
write: {
const rc = std.os.linux.write(socket, message.ptr, message.len);
- switch (std.os.linux.E.init(rc)) {
+ switch (linuxErrnoFromSyscall(rc)) {
.SUCCESS => {
const written = rc;
if (written < message.len) {
@@ -180,12 +181,7 @@ pub const notify = struct {
pub fn reloading() void {
if (comptime builtin.os.tag != .linux) return;
- const ts = std.posix.clock_gettime(.MONOTONIC) catch |err| {
- log.err("unable to get MONOTONIC clock: {}", .{err});
- return;
- };
-
- const now = ts.sec * std.time.us_per_s + @divFloor(ts.nsec, std.time.ns_per_us);
+ const now = std.Io.Timestamp.now(global.io(), .awake).toMicroseconds();
var buffer: [64]u8 = undefined;
const message = std.fmt.bufPrint(&buffer, "RELOADING=1\nMONOTONIC_USEC={d}", .{now}) catch |err| {
@@ -196,3 +192,9 @@ pub const notify = struct {
send(message);
}
};
+
+fn linuxErrnoFromSyscall(r: usize) std.os.linux.E {
+ const signed_r: isize = @bitCast(r);
+ const int = if (signed_r > -4096 and signed_r < 0) -signed_r else 0;
+ return @enumFromInt(int);
+}
diff --git a/vendor/libghostty-vt/src/os/wasm/log.zig b/vendor/libghostty-vt/src/os/wasm/log.zig
index faa885c6..5c5a8f66 100644
--- a/vendor/libghostty-vt/src/os/wasm/log.zig
+++ b/vendor/libghostty-vt/src/os/wasm/log.zig
@@ -4,6 +4,22 @@ const wasm = @import("../wasm.zig");
// Use the correct implementation
pub const log = Freestanding.log;
+/// A log function that discards everything. Since the format string and
+/// scope are comptime parameters, using this compiles out all logging
+/// machinery: call sites, format strings, and the std.fmt code they
+/// reference (~220KB in a ReleaseFast wasm build).
+pub fn noop(
+ comptime level: std.log.Level,
+ comptime scope: @TypeOf(.EnumLiteral),
+ comptime format: []const u8,
+ args: anytype,
+) void {
+ _ = level;
+ _ = scope;
+ _ = format;
+ _ = args;
+}
+
/// Freestanding implementation calls an extern "log" function.
pub const Freestanding = struct {
// The function std.log will call.
diff --git a/vendor/libghostty-vt/src/os/windows.zig b/vendor/libghostty-vt/src/os/windows.zig
index 6e452fb7..30b011a2 100644
--- a/vendor/libghostty-vt/src/os/windows.zig
+++ b/vendor/libghostty-vt/src/os/windows.zig
@@ -1,139 +1,378 @@
const std = @import("std");
const windows = std.os.windows;
-// Export any constants or functions we need from the Windows API so
-// we can just import one file.
-pub const kernel32 = windows.kernel32;
-pub const unexpectedError = windows.unexpectedError;
-pub const OpenFile = windows.OpenFile;
-pub const CloseHandle = windows.CloseHandle;
+// NOTE: The Windows part of the Zig stdlib is currently in the process of
+// having most of its features removed, with the ultimate goal of switching to
+// serve as a support for higher-level functionality offered in places like
+// `std.Io` only. As such this file serves as a "bridge" between type
+// information (mostly coming from stdlib) and manually-defined constants and
+// external functions.
+
+// Utility functions
pub const GetCurrentProcessId = windows.GetCurrentProcessId;
-pub const SetHandleInformation = windows.SetHandleInformation;
+pub const GetLastError = windows.GetLastError;
+pub const unexpectedError = windows.unexpectedError;
+pub const unexpectedStatus = windows.unexpectedStatus;
+
+// Primitive types
+pub const BOOL = windows.BOOL;
+pub const COORD = windows.COORD;
pub const DWORD = windows.DWORD;
-pub const FILE_ATTRIBUTE_NORMAL = windows.FILE_ATTRIBUTE_NORMAL;
-pub const FILE_FLAG_OVERLAPPED = windows.FILE_FLAG_OVERLAPPED;
-pub const FILE_SHARE_READ = windows.FILE_SHARE_READ;
-pub const GENERIC_READ = windows.GENERIC_READ;
+pub const DWORD_PTR = windows.DWORD_PTR;
pub const HANDLE = windows.HANDLE;
-pub const HANDLE_FLAG_INHERIT = windows.HANDLE_FLAG_INHERIT;
-pub const INFINITE = windows.INFINITE;
-pub const INVALID_HANDLE_VALUE = windows.INVALID_HANDLE_VALUE;
-pub const MAX_PATH = windows.MAX_PATH;
-pub const OPEN_EXISTING = windows.OPEN_EXISTING;
-pub const PIPE_ACCESS_OUTBOUND = windows.PIPE_ACCESS_OUTBOUND;
-pub const PIPE_TYPE_BYTE = windows.PIPE_TYPE_BYTE;
-pub const PROCESS_INFORMATION = windows.PROCESS_INFORMATION;
-pub const S_OK = windows.S_OK;
+pub const HINSTANCE = windows.HINSTANCE;
+pub const HPCON = windows.LPVOID;
+pub const HRESULT = c_long;
+pub const LARGE_INTEGER = windows.LARGE_INTEGER;
+pub const LPCWSTR = windows.LPCWSTR;
+pub const LPSTR = windows.LPSTR;
+pub const LPVOID = windows.LPVOID;
+pub const LPWSTR = windows.LPWSTR;
+pub const PVOID = windows.PVOID;
+pub const SIZE_T = windows.SIZE_T;
+pub const UINT = windows.UINT;
+pub const ULONG = windows.ULONG;
+pub const ULONG_PTR = windows.ULONG_PTR;
+pub const UNICODE_STRING = windows.UNICODE_STRING;
+
+// Structs and opaque types
+pub const LPPROC_THREAD_ATTRIBUTE_LIST = ?*anyopaque;
pub const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES;
-pub const STARTUPINFOW = windows.STARTUPINFOW;
pub const STARTF_USESTDHANDLES = windows.STARTF_USESTDHANDLES;
-pub const SYNCHRONIZE = windows.SYNCHRONIZE;
-pub const WAIT_FAILED = windows.WAIT_FAILED;
-pub const FALSE = windows.FALSE;
-pub const TRUE = windows.TRUE;
+pub const STARTUPINFOW = windows.STARTUPINFOW;
-pub const exp = struct {
- pub const HPCON = windows.LPVOID;
+pub const OVERLAPPED = extern struct {
+ Internal: ULONG_PTR,
+ InternalHigh: ULONG_PTR,
+ DUMMYUNIONNAME: extern union {
+ DUMMYSTRUCTNAME: extern struct {
+ Offset: DWORD,
+ OffsetHigh: DWORD,
+ },
+ Pointer: ?PVOID,
+ },
+ hEvent: ?HANDLE,
+};
+pub const PROCESS_INFORMATION = extern struct {
+ hProcess: HANDLE,
+ hThread: HANDLE,
+ dwProcessId: DWORD,
+ dwThreadId: DWORD,
+};
+pub const STARTUPINFOEX = extern struct {
+ StartupInfo: windows.STARTUPINFOW,
+ lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
+};
+
+// Well-known constant values
+pub const INFINITE = 4294967295;
+pub const INVALID_HANDLE_VALUE = windows.INVALID_HANDLE_VALUE;
+pub const MAX_PATH = windows.MAX_PATH;
+pub const FALSE: windows.BOOL = .fromBool(false);
+pub const TRUE: windows.BOOL = .fromBool(true);
- pub const CREATE_UNICODE_ENVIRONMENT = 0x00000400;
- pub const EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
- pub const LPPROC_THREAD_ATTRIBUTE_LIST = ?*anyopaque;
- pub const FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;
+// Bit-field and enum constant values
+pub const CREATE_UNICODE_ENVIRONMENT = 0x00000400;
+pub const DELETE = 0x00010000;
+pub const ERROR_SUCCESS = 0;
+pub const EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
+pub const FILE_ATTRIBUTE_NORMAL = 0x80;
+pub const FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;
+pub const FILE_FLAG_OVERLAPPED = 0x40000000;
+/// GetFinalPathNameByHandleW dwFlags: normalized name with a DOS drive
+/// letter (both are the zero flag values).
+pub const FILE_NAME_NORMALIZED = 0x0;
+pub const FILE_NON_DIRECTORY_FILE = 0x00000040;
+/// NtCreateFile CreateDisposition: open an existing file, never create.
+/// Distinct from the Win32 CreateFileW value OPEN_EXISTING.
+pub const FILE_OPEN = 0x00000001;
+pub const FILE_OPEN_REPARSE_POINT = 0x00200000;
+pub const FILE_SHARE_DELETE = 0x00000004;
+pub const FILE_SHARE_READ = 0x00000001;
+pub const FILE_SHARE_WRITE = 0x00000002;
+pub const FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
+pub const GENERIC_READ = 0x80000000;
+pub const HANDLE_FLAG_INHERIT = 0x00000001;
+pub const MEM_COMMIT = 0x1000;
+pub const MEM_RELEASE = 0x8000;
+pub const MEM_RESERVE = 0x2000;
+pub const OPEN_EXISTING = 3; // Known as FILE_OPEN in Windows docs
+pub const PAGE_READWRITE = 0x04;
+pub const PIPE_ACCESS_OUTBOUND = 0x00000002;
+pub const PIPE_TYPE_BYTE = 0x00000000;
+pub const PROC_THREAD_ATTRIBUTE_ADDITIVE = 0x00040000;
+pub const PROC_THREAD_ATTRIBUTE_INPUT = 0x00020000;
+pub const PROC_THREAD_ATTRIBUTE_NUMBER = 0x0000FFFF;
+pub const PROC_THREAD_ATTRIBUTE_THREAD = 0x00010000;
+pub const S_OK = 0;
+pub const SYNCHRONIZE = 0x00100000;
+pub const VOLUME_NAME_DOS = 0x0;
+pub const WAIT_FAILED = 0xFFFFFFFF;
- pub const STATUS_PENDING = 0x00000103;
- pub const STILL_ACTIVE = STATUS_PENDING;
+/// IOCTL that fills the output buffer with bytes from the kernel CSPRNG
+/// behind `\Device\CNG` (what ProcessPrng and BCryptGenRandom draw from).
+pub const IOCTL_KSEC_GEN_RANDOM: CTL_CODE = windows.IOCTL.KSEC.GEN_RANDOM;
- pub const STARTUPINFOEX = extern struct {
- StartupInfo: windows.STARTUPINFOW,
- lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
- };
+pub const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = ProcThreadAttributeValue(
+ .ProcThreadAttributePseudoConsole,
+ false,
+ true,
+ false,
+);
+
+// Types needed for ntdll calls
+pub const ACCESS_MASK = windows.ACCESS_MASK;
+pub const CTL_CODE = windows.CTL_CODE;
+pub const FILE_ALL_INFORMATION = windows.FILE.ALL_INFORMATION;
+pub const FILE_ATTRIBUTE_TAG_INFORMATION = windows.FILE.ATTRIBUTE_TAG_INFO;
+pub const FILE_DISPOSITION_INFORMATION = windows.FILE.DISPOSITION.INFORMATION;
+pub const FILE_DISPOSITION_INFORMATION_EX = windows.FILE.DISPOSITION.INFORMATION.EX;
+pub const FILE_INFORMATION_CLASS = windows.FILE.INFORMATION_CLASS;
+pub const FILE_POSITION_INFORMATION = windows.FILE.POSITION_INFORMATION;
+pub const FILE_STANDARD_INFORMATION = windows.FILE.STANDARD_INFORMATION;
+pub const IO_STATUS_BLOCK = windows.IO_STATUS_BLOCK;
+pub const NTSTATUS = windows.NTSTATUS;
+pub const OBJECT_ATTRIBUTES = windows.OBJECT.ATTRIBUTES;
+// Exported functions by library
+pub const exp = struct {
pub const kernel32 = struct {
pub extern "kernel32" fn CreatePipe(
- hReadPipe: *windows.HANDLE,
- hWritePipe: *windows.HANDLE,
- lpPipeAttributes: ?*const windows.SECURITY_ATTRIBUTES,
- nSize: windows.DWORD,
- ) callconv(.winapi) windows.BOOL;
+ hReadPipe: *HANDLE,
+ hWritePipe: *HANDLE,
+ lpPipeAttributes: ?*const SECURITY_ATTRIBUTES,
+ nSize: DWORD,
+ ) callconv(.winapi) BOOL;
pub extern "kernel32" fn CreatePseudoConsole(
- size: windows.COORD,
- hInput: windows.HANDLE,
- hOutput: windows.HANDLE,
- dwFlags: windows.DWORD,
+ size: COORD,
+ hInput: HANDLE,
+ hOutput: HANDLE,
+ dwFlags: DWORD,
phPC: *HPCON,
- ) callconv(.winapi) windows.HRESULT;
- pub extern "kernel32" fn ResizePseudoConsole(hPC: HPCON, size: windows.COORD) callconv(.winapi) windows.HRESULT;
+ ) callconv(.winapi) HRESULT;
+ pub extern "kernel32" fn ResizePseudoConsole(
+ hPC: HPCON,
+ size: COORD,
+ ) callconv(.winapi) HRESULT;
pub extern "kernel32" fn ClosePseudoConsole(hPC: HPCON) callconv(.winapi) void;
pub extern "kernel32" fn InitializeProcThreadAttributeList(
lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
- dwAttributeCount: windows.DWORD,
- dwFlags: windows.DWORD,
- lpSize: *windows.SIZE_T,
- ) callconv(.winapi) windows.BOOL;
+ dwAttributeCount: DWORD,
+ dwFlags: DWORD,
+ lpSize: *SIZE_T,
+ ) callconv(.winapi) BOOL;
pub extern "kernel32" fn UpdateProcThreadAttribute(
lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
- dwFlags: windows.DWORD,
- Attribute: windows.DWORD_PTR,
- lpValue: windows.PVOID,
- cbSize: windows.SIZE_T,
- lpPreviousValue: ?windows.PVOID,
- lpReturnSize: ?*windows.SIZE_T,
- ) callconv(.winapi) windows.BOOL;
+ dwFlags: DWORD,
+ Attribute: DWORD_PTR,
+ lpValue: PVOID,
+ cbSize: SIZE_T,
+ lpPreviousValue: ?PVOID,
+ lpReturnSize: ?*SIZE_T,
+ ) callconv(.winapi) BOOL;
pub extern "kernel32" fn PeekNamedPipe(
- hNamedPipe: windows.HANDLE,
- lpBuffer: ?windows.LPVOID,
- nBufferSize: windows.DWORD,
- lpBytesRead: ?*windows.DWORD,
- lpTotalBytesAvail: ?*windows.DWORD,
- lpBytesLeftThisMessage: ?*windows.DWORD,
- ) callconv(.winapi) windows.BOOL;
+ hNamedPipe: HANDLE,
+ lpBuffer: ?LPVOID,
+ nBufferSize: DWORD,
+ lpBytesRead: ?*DWORD,
+ lpTotalBytesAvail: ?*DWORD,
+ lpBytesLeftThisMessage: ?*DWORD,
+ ) callconv(.winapi) BOOL;
// Duplicated here because lpCommandLine is not marked optional in zig std
pub extern "kernel32" fn CreateProcessW(
- lpApplicationName: ?windows.LPWSTR,
- lpCommandLine: ?windows.LPWSTR,
- lpProcessAttributes: ?*windows.SECURITY_ATTRIBUTES,
- lpThreadAttributes: ?*windows.SECURITY_ATTRIBUTES,
- bInheritHandles: windows.BOOL,
- dwCreationFlags: windows.DWORD,
+ lpApplicationName: ?LPWSTR,
+ lpCommandLine: ?LPWSTR,
+ lpProcessAttributes: ?*SECURITY_ATTRIBUTES,
+ lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
+ bInheritHandles: BOOL,
+ dwCreationFlags: DWORD,
lpEnvironment: ?*anyopaque,
- lpCurrentDirectory: ?windows.LPWSTR,
- lpStartupInfo: *windows.STARTUPINFOW,
- lpProcessInformation: *windows.PROCESS_INFORMATION,
- ) callconv(.winapi) windows.BOOL;
+ lpCurrentDirectory: ?LPWSTR,
+ lpStartupInfo: *STARTUPINFOW,
+ lpProcessInformation: *PROCESS_INFORMATION,
+ ) callconv(.winapi) BOOL;
/// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcomputernamea
pub extern "kernel32" fn GetComputerNameA(
- lpBuffer: windows.LPSTR,
- nSize: *windows.DWORD,
- ) callconv(.winapi) windows.BOOL;
+ lpBuffer: LPSTR,
+ nSize: *DWORD,
+ ) callconv(.winapi) BOOL;
/// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw
pub extern "kernel32" fn GetTempPathW(
- nBufferLength: windows.DWORD,
- lpBuffer: windows.LPWSTR,
- ) callconv(.winapi) windows.DWORD;
+ nBufferLength: DWORD,
+ lpBuffer: LPWSTR,
+ ) callconv(.winapi) DWORD;
+ pub extern "kernel32" fn SetHandleInformation(
+ hObject: HANDLE,
+ dwMask: DWORD,
+ dwFlags: DWORD,
+ ) callconv(.winapi) BOOL;
+ pub extern "kernel32" fn CreateFileW(
+ lpFileName: LPCWSTR,
+ dwDesiredAccess: DWORD,
+ dwShareMode: DWORD,
+ lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
+ dwCreationDisposition: DWORD,
+ dwFlagsAndAttributes: DWORD,
+ hTemplateFile: ?HANDLE,
+ ) callconv(.winapi) HANDLE;
+ pub extern "kernel32" fn CreateNamedPipeW(
+ lpName: LPCWSTR,
+ dwOpenMode: DWORD,
+ dwPipeMode: DWORD,
+ nMaxInstances: DWORD,
+ nOutBufferSize: DWORD,
+ nInBufferSize: DWORD,
+ nDefaultTimeOut: DWORD,
+ lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES,
+ ) callconv(.winapi) HANDLE;
+ pub extern "kernel32" fn CloseHandle(
+ hObject: HANDLE,
+ ) callconv(.winapi) BOOL;
+ pub extern "kernel32" fn VirtualAlloc(
+ lpAddress: ?LPVOID,
+ dwSize: SIZE_T,
+ flAllocationType: DWORD,
+ flProtect: DWORD,
+ ) callconv(.winapi) ?LPVOID;
+ pub extern "kernel32" fn VirtualFree(
+ lpAddress: ?LPVOID,
+ dwSize: SIZE_T,
+ dwFreeType: DWORD,
+ ) callconv(.winapi) BOOL;
+ /// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-discardvirtualmemory
+ pub extern "kernel32" fn DiscardVirtualMemory(
+ VirtualAddress: PVOID,
+ Size: SIZE_T,
+ ) callconv(.winapi) DWORD;
+ pub extern "kernel32" fn WaitForSingleObject(
+ hHandle: HANDLE,
+ dwMilliseconds: DWORD,
+ ) callconv(.winapi) DWORD;
+ pub extern "kernel32" fn GetExitCodeProcess(
+ hProcess: HANDLE,
+ lpExitCode: *DWORD,
+ ) callconv(.winapi) BOOL;
+ pub extern "kernel32" fn TerminateProcess(
+ hProcess: HANDLE,
+ uExitCode: UINT,
+ ) callconv(.winapi) BOOL;
+ pub extern "kernel32" fn CancelIoEx(
+ hFile: HANDLE,
+ lpOverlapped: ?*OVERLAPPED,
+ ) callconv(.winapi) BOOL;
+ pub extern "kernel32" fn ReadFile(
+ hFile: HANDLE,
+ lpBuffer: LPVOID,
+ nNumberOfBytesToRead: DWORD,
+ lpNumberOfBytesRead: ?*DWORD,
+ lpOverlapped: ?*OVERLAPPED,
+ ) callconv(.winapi) BOOL;
+ /// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew
+ pub extern "kernel32" fn GetFinalPathNameByHandleW(
+ hFile: HANDLE,
+ lpszFilePath: [*]u16,
+ cchFilePath: DWORD,
+ dwFlags: DWORD,
+ ) callconv(.winapi) DWORD;
};
-
- pub const PROC_THREAD_ATTRIBUTE_NUMBER = 0x0000FFFF;
- pub const PROC_THREAD_ATTRIBUTE_THREAD = 0x00010000;
- pub const PROC_THREAD_ATTRIBUTE_INPUT = 0x00020000;
- pub const PROC_THREAD_ATTRIBUTE_ADDITIVE = 0x00040000;
-
- pub const ProcThreadAttributeNumber = enum(windows.DWORD) {
- ProcThreadAttributePseudoConsole = 22,
- _,
+ pub const ntdll = struct {
+ pub extern "ntdll" fn NtCreateFile(
+ FileHandle: *HANDLE,
+ DesiredAccess: ACCESS_MASK,
+ ObjectAttributes: *OBJECT_ATTRIBUTES,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+ AllocationSize: ?*LARGE_INTEGER,
+ FileAttributes: ULONG,
+ ShareAccess: ULONG,
+ CreateDisposition: ULONG,
+ CreateOptions: ULONG,
+ EaBuffer: ?*anyopaque,
+ EaLength: ULONG,
+ ) callconv(.winapi) NTSTATUS;
+ pub extern "ntdll" fn NtOpenFile(
+ FileHandle: *HANDLE,
+ DesiredAccess: ACCESS_MASK,
+ ObjectAttributes: *OBJECT_ATTRIBUTES,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+ ShareAccess: ULONG,
+ OpenOptions: ULONG,
+ ) callconv(.winapi) NTSTATUS;
+ pub extern "ntdll" fn NtClose(Handle: HANDLE) callconv(.winapi) NTSTATUS;
+ pub extern "ntdll" fn NtReadFile(
+ FileHandle: HANDLE,
+ Event: ?HANDLE,
+ ApcRoutine: ?*const anyopaque,
+ ApcContext: ?*anyopaque,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+ Buffer: *anyopaque,
+ Length: ULONG,
+ ByteOffset: ?*const LARGE_INTEGER,
+ Key: ?*const ULONG,
+ ) callconv(.winapi) NTSTATUS;
+ pub extern "ntdll" fn NtQueryInformationFile(
+ FileHandle: HANDLE,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+ FileInformation: *anyopaque,
+ Length: ULONG,
+ FileInformationClass: FILE_INFORMATION_CLASS,
+ ) callconv(.winapi) NTSTATUS;
+ pub extern "ntdll" fn NtSetInformationFile(
+ FileHandle: HANDLE,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+ FileInformation: *anyopaque,
+ Length: ULONG,
+ FileInformationClass: FILE_INFORMATION_CLASS,
+ ) callconv(.winapi) NTSTATUS;
+ pub extern "ntdll" fn NtDeviceIoControlFile(
+ FileHandle: HANDLE,
+ Event: ?HANDLE,
+ ApcRoutine: ?*const anyopaque,
+ ApcContext: ?*anyopaque,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+ IoControlCode: CTL_CODE,
+ InputBuffer: ?*const anyopaque,
+ InputBufferLength: ULONG,
+ OutputBuffer: ?*anyopaque,
+ OutputBufferLength: ULONG,
+ ) callconv(.winapi) NTSTATUS;
+ /// Resolves a Win32 path against the process working directory and
+ /// canonicalizes it, keeping any `\\?\` or `\\.\` prefix. Returns
+ /// the byte length written excluding the terminator, the required
+ /// byte length if the buffer is too small, or 0 on failure.
+ pub extern "ntdll" fn RtlGetFullPathName_U(
+ FileName: [*:0]const u16,
+ BufferByteLength: ULONG,
+ Buffer: [*]u16,
+ ShortName: ?*[*:0]const u16,
+ ) callconv(.winapi) ULONG;
+ /// The futex primitives that kernel32's WaitOnAddress and
+ /// WakeByAddress* forward to. Timeout is a relative (negative)
+ /// interval in 100ns units, null to wait forever.
+ pub extern "ntdll" fn RtlWaitOnAddress(
+ Address: *const anyopaque,
+ CompareAddress: *const anyopaque,
+ AddressSize: SIZE_T,
+ Timeout: ?*const LARGE_INTEGER,
+ ) callconv(.winapi) NTSTATUS;
+ pub extern "ntdll" fn RtlWakeAddressSingle(Address: *const anyopaque) callconv(.winapi) void;
+ pub extern "ntdll" fn RtlWakeAddressAll(Address: *const anyopaque) callconv(.winapi) void;
};
+};
- /// Corresponds to the ProcThreadAttributeValue define in WinBase.h
- pub fn ProcThreadAttributeValue(
- comptime attribute: ProcThreadAttributeNumber,
- comptime thread: bool,
- comptime input: bool,
- comptime additive: bool,
- ) windows.DWORD {
- return (@intFromEnum(attribute) & PROC_THREAD_ATTRIBUTE_NUMBER) |
- (if (thread) PROC_THREAD_ATTRIBUTE_THREAD else 0) |
- (if (input) PROC_THREAD_ATTRIBUTE_INPUT else 0) |
- (if (additive) PROC_THREAD_ATTRIBUTE_ADDITIVE else 0);
- }
-
- pub const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = ProcThreadAttributeValue(.ProcThreadAttributePseudoConsole, false, true, false);
+pub const ProcThreadAttributeNumber = enum(DWORD) {
+ ProcThreadAttributePseudoConsole = 22,
+ _,
};
+
+/// Corresponds to the ProcThreadAttributeValue define in WinBase.h
+pub fn ProcThreadAttributeValue(
+ comptime attribute: ProcThreadAttributeNumber,
+ comptime thread: bool,
+ comptime input: bool,
+ comptime additive: bool,
+) DWORD {
+ return (@intFromEnum(attribute) & PROC_THREAD_ATTRIBUTE_NUMBER) |
+ (if (thread) PROC_THREAD_ATTRIBUTE_THREAD else 0) |
+ (if (input) PROC_THREAD_ATTRIBUTE_INPUT else 0) |
+ (if (additive) PROC_THREAD_ATTRIBUTE_ADDITIVE else 0);
+}
diff --git a/vendor/libghostty-vt/src/os/xdg.zig b/vendor/libghostty-vt/src/os/xdg.zig
index f2a495af..92029586 100644
--- a/vendor/libghostty-vt/src/os/xdg.zig
+++ b/vendor/libghostty-vt/src/os/xdg.zig
@@ -6,7 +6,6 @@ const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const posix = std.posix;
const homedir = @import("homedir.zig");
-const env_os = @import("env.zig");
pub const Options = struct {
/// Subdirectories to join to the base. This avoids extra allocations
@@ -20,8 +19,8 @@ pub const Options = struct {
};
/// Get the XDG user config directory. The returned value is allocated.
-pub fn config(alloc: Allocator, opts: Options) ![]u8 {
- return try dir(alloc, opts, .{
+pub fn config(io: std.Io, alloc: Allocator, environ_map: *const std.process.Environ.Map, opts: Options) ![]u8 {
+ return try dir(io, alloc, environ_map, opts, .{
.env = "XDG_CONFIG_HOME",
.windows_env = "LOCALAPPDATA",
.default_subdir = ".config",
@@ -29,8 +28,8 @@ pub fn config(alloc: Allocator, opts: Options) ![]u8 {
}
/// Get the XDG cache directory. The returned value is allocated.
-pub fn cache(alloc: Allocator, opts: Options) ![]u8 {
- return try dir(alloc, opts, .{
+pub fn cache(io: std.Io, alloc: Allocator, environ_map: *const std.process.Environ.Map, opts: Options) ![]u8 {
+ return try dir(io, alloc, environ_map, opts, .{
.env = "XDG_CACHE_HOME",
.windows_env = "LOCALAPPDATA",
.default_subdir = ".cache",
@@ -38,8 +37,8 @@ pub fn cache(alloc: Allocator, opts: Options) ![]u8 {
}
/// Get the XDG state directory. The returned value is allocated.
-pub fn state(alloc: Allocator, opts: Options) ![]u8 {
- return try dir(alloc, opts, .{
+pub fn state(io: std.Io, alloc: Allocator, environ_map: *const std.process.Environ.Map, opts: Options) ![]u8 {
+ return try dir(io, alloc, environ_map, opts, .{
.env = "XDG_STATE_HOME",
.windows_env = "LOCALAPPDATA",
.default_subdir = ".local/state",
@@ -54,7 +53,9 @@ const InternalOptions = struct {
/// Unified helper to get XDG directories that follow a common pattern.
fn dir(
+ io: std.Io,
alloc: Allocator,
+ environ_map: *const std.process.Environ.Map,
opts: Options,
internal_opts: InternalOptions,
) ![]u8 {
@@ -67,30 +68,28 @@ fn dir(
});
}
- // First check the env var. On Windows we have to allocate so this tracks
- // both whether we have the env var and whether we own it.
- // on Windows we treat `LOCALAPPDATA` as a fallback for `XDG_CONFIG_HOME`
- const env_ = try env_os.getenvNotEmpty(alloc, internal_opts.env) orelse switch (builtin.os.tag) {
- else => null,
- .windows => try env_os.getenvNotEmpty(alloc, internal_opts.windows_env),
+ // First check the env var. On Windows we treat `LOCALAPPDATA` as a
+ // fallback for `XDG_CONFIG_HOME`
+ const env = switch (builtin.os.tag) {
+ .windows => environ_map.get(internal_opts.env) orelse environ_map.get(internal_opts.windows_env) orelse "",
+ else => environ_map.get(internal_opts.env) orelse "",
};
- defer if (env_) |env| env.deinit(alloc);
- if (env_) |env| {
+ if (env.len > 0) {
// If we have a subdir, then we use the env as-is to avoid a copy.
if (opts.subdir) |subdir| {
return try std.fs.path.join(alloc, &[_][]const u8{
- env.value,
+ env,
subdir,
});
}
- return try alloc.dupe(u8, env.value);
+ return try alloc.dupe(u8, env);
}
// Get our home dir
var buf: [1024]u8 = undefined;
- if (try homedir.home(&buf)) |home| {
+ if (try homedir.home(io, environ_map, &buf)) |home| {
return try std.fs.path.join(alloc, &[_][]const u8{
home,
internal_opts.default_subdir,
@@ -120,10 +119,13 @@ pub fn parseTerminalExec(argv: []const [*:0]const u8) ?[]const [*:0]const u8 {
test {
const testing = std.testing;
+ const io = testing.io;
const alloc = testing.allocator;
+ var environ_map = try testing.environ.createMap(alloc);
+ defer environ_map.deinit();
{
- const value = try config(alloc, .{});
+ const value = try config(io, alloc, &environ_map, .{});
defer alloc.free(value);
try testing.expect(value.len > 0);
}
@@ -131,14 +133,17 @@ test {
test "cache directory paths" {
const testing = std.testing;
+ const io = testing.io;
const alloc = testing.allocator;
const mock_home = if (builtin.os.tag == .windows) "C:\\Users\\test" else "/Users/test";
+ var environ_map = try testing.environ.createMap(alloc);
+ defer environ_map.deinit();
// Test when XDG_CACHE_HOME is not set
{
// Test base path
{
- const cache_path = try cache(alloc, .{ .home = mock_home });
+ const cache_path = try cache(io, alloc, &environ_map, .{ .home = mock_home });
defer alloc.free(cache_path);
const expected = try std.fs.path.join(alloc, &.{ mock_home, ".cache" });
defer alloc.free(expected);
@@ -147,7 +152,7 @@ test "cache directory paths" {
// Test with subdir
{
- const cache_path = try cache(alloc, .{
+ const cache_path = try cache(io, alloc, &environ_map, .{
.home = mock_home,
.subdir = "ghostty",
});
@@ -162,26 +167,12 @@ test "cache directory paths" {
test "fallback when xdg env empty" {
if (builtin.os.tag == .windows) return error.SkipZigTest;
+ const io = std.testing.io;
const alloc = std.testing.allocator;
- const saved_home = home: {
- const home = std.posix.getenv("HOME") orelse break :home null;
- break :home try alloc.dupeZ(u8, home);
- };
- defer env: {
- const home = saved_home orelse {
- _ = env_os.unsetenv("HOME");
- break :env;
- };
- _ = env_os.setenv("HOME", home);
- std.testing.allocator.free(home);
- }
- const temp_home = "/tmp/ghostty-test-home";
- _ = env_os.setenv("HOME", temp_home);
-
const DirCase = struct {
name: [:0]const u8,
- func: fn (Allocator, Options) anyerror![]u8,
+ func: fn (std.Io, Allocator, *std.process.Environ.Map, Options) anyerror![]u8,
default_subdir: []const u8,
};
@@ -192,19 +183,10 @@ test "fallback when xdg env empty" {
};
inline for (cases) |case| {
- // Save and restore each environment variable
- const saved_env = blk: {
- const value = std.posix.getenv(case.name) orelse break :blk null;
- break :blk try alloc.dupeZ(u8, value);
- };
- defer env: {
- const value = saved_env orelse {
- _ = env_os.unsetenv(case.name);
- break :env;
- };
- _ = env_os.setenv(case.name, value);
- alloc.free(value);
- }
+ var environ_map = try std.testing.environ.createMap(alloc);
+ defer environ_map.deinit();
+ const temp_home = "/tmp/ghostty-test-home";
+ try environ_map.put("HOME", temp_home);
const expected = try std.fs.path.join(alloc, &[_][]const u8{
temp_home,
@@ -213,8 +195,8 @@ test "fallback when xdg env empty" {
defer alloc.free(expected);
// Test with empty string - should fallback to home
- _ = env_os.setenv(case.name, "");
- const actual = try case.func(alloc, .{});
+ try environ_map.put(case.name, "");
+ const actual = try case.func(io, alloc, &environ_map, .{});
defer alloc.free(actual);
try std.testing.expectEqualStrings(expected, actual);
@@ -224,28 +206,12 @@ test "fallback when xdg env empty" {
test "fallback when xdg env empty and subdir" {
if (builtin.os.tag == .windows) return error.SkipZigTest;
- const env = @import("env.zig");
+ const io = std.testing.io;
const alloc = std.testing.allocator;
- const saved_home = home: {
- const home = std.posix.getenv("HOME") orelse break :home null;
- break :home try alloc.dupeZ(u8, home);
- };
- defer env: {
- const home = saved_home orelse {
- _ = env.unsetenv("HOME");
- break :env;
- };
- _ = env.setenv("HOME", home);
- std.testing.allocator.free(home);
- }
-
- const temp_home = "/tmp/ghostty-test-home";
- _ = env.setenv("HOME", temp_home);
-
const DirCase = struct {
name: [:0]const u8,
- func: fn (Allocator, Options) anyerror![]u8,
+ func: fn (std.Io, Allocator, *const std.process.Environ.Map, Options) anyerror![]u8,
default_subdir: []const u8,
};
@@ -256,19 +222,10 @@ test "fallback when xdg env empty and subdir" {
};
inline for (cases) |case| {
- // Save and restore each environment variable
- const saved_env = blk: {
- const value = std.posix.getenv(case.name) orelse break :blk null;
- break :blk try alloc.dupeZ(u8, value);
- };
- defer env: {
- const value = saved_env orelse {
- _ = env.unsetenv(case.name);
- break :env;
- };
- _ = env.setenv(case.name, value);
- alloc.free(value);
- }
+ var environ_map = try std.testing.environ.createMap(alloc);
+ defer environ_map.deinit();
+ const temp_home = "/tmp/ghostty-test-home";
+ try environ_map.put("HOME", temp_home);
const expected = try std.fs.path.join(alloc, &[_][]const u8{
temp_home,
@@ -278,8 +235,8 @@ test "fallback when xdg env empty and subdir" {
defer alloc.free(expected);
// Test with empty string - should fallback to home
- _ = env.setenv(case.name, "");
- const actual = try case.func(alloc, .{ .subdir = "ghostty" });
+ try environ_map.put(case.name, "");
+ const actual = try case.func(io, alloc, &environ_map, .{ .subdir = "ghostty" });
defer alloc.free(actual);
try std.testing.expectEqualStrings(expected, actual);