diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-10 21:51:57 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 21:51:57 +0300 |
| commit | 425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch) | |
| tree | e52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/os/open.zig | |
| parent | 90e947a6e26e02f10ec7f1694c4eb5b78a86406b (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.zig | 100 |
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 {}; } |
