aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/os/open.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/os/open.zig
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/os/open.zig')
-rw-r--r--vendor/libghostty-vt/src/os/open.zig100
1 files changed, 54 insertions, 46 deletions
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 {};
}