fix some 0.16 translation regressions

This commit is contained in:
Mitchell Hashimoto
2026-07-22 08:04:27 -07:00
parent a77c706a18
commit b988efcfe5
9 changed files with 136 additions and 43 deletions

View File

@@ -785,12 +785,13 @@ typedef enum GHOSTTY_ENUM_TYPED {
GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_FILE = 16,
/**
* Enable or disable Kitty image loading via the temporary file medium.
* Enable Kitty image loading via the temporary file medium, restricted to
* the provided directory. The string data is copied into the terminal.
*
* A NULL value pointer is a no-op. Has no effect when Kitty graphics
* are disabled at build time.
* A NULL value pointer disables the temporary file medium. Has no effect
* when Kitty graphics are disabled at build time.
*
* Input type: bool*
* Input type: GhosttyString*
*/
GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_TEMP_FILE = 17,
@@ -1139,12 +1140,13 @@ typedef enum GHOSTTY_ENUM_TYPED {
GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_FILE = 27,
/**
* Whether the temporary file medium is enabled for Kitty image loading
* on the active screen.
* The directory allowed for Kitty image loading via the temporary file
* medium on the active screen. The string is empty when the medium is
* disabled.
*
* Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
*
* Output type: bool *
* Output type: GhosttyString *
*/
GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_TEMP_FILE = 28,

View File

@@ -236,12 +236,12 @@ fn startPosix(self: *Command, arena: Allocator) !void {
var path_expanded_buf: [std.fs.max_path_bytes]u8 = undefined;
const PATH = global.environ().getPosix("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
var it = std.mem.tokenizeScalar(u8, PATH, ':');
var err: posix.system.E = undefined;
var err: posix.system.E = .NOENT;
var seen_eacces = false;
while (it.next()) |search_path| {
const path_len = search_path.len + file_slice.len + 1;
if (path_expanded_buf.len < path_len + 1) return error.NameTooLong;
if (path_expanded_buf.len < path_len + 1) break :execve .NAMETOOLONG;
@memcpy(path_expanded_buf[0..search_path.len], search_path);
path_expanded_buf[search_path.len] = '/';
@memcpy(path_expanded_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);

View File

@@ -1593,7 +1593,10 @@ pub const Surface = extern struct {
pub fn defaultTermioEnv(self: *Self) !std.process.Environ.Map {
const app = Application.default();
const alloc = app.allocator();
var env = try global.environMap();
var env = if (internal_os.isFlatpak())
std.process.Environ.Map.init(alloc)
else
try global.environMap();
errdefer env.deinit();
if (app.savedLanguage()) |language| {

View File

@@ -34,6 +34,7 @@ pub const SVG = struct {
const offset = try compat_reader.readerInt(&reader, u32, .big);
// Seek to the offset to get our document list
if (offset > data.len) return error.EndOfStream;
reader.seek = offset;
// Get our document records along with the start/end glyph range.
@@ -108,3 +109,13 @@ test "SVG" {
try testing.expectEqual(11482, svg.end_glyph_id);
try testing.expect(svg.hasGlyph(11482));
}
test "SVG document list offset past end" {
const testing = std.testing;
const table = [_]u8{
0, 0, // Version
0, 0, 0, 7, // Document list offset, one byte past end
};
try testing.expectError(error.EndOfStream, SVG.init(&table));
}

View File

@@ -4,27 +4,53 @@ const builtin = @import("builtin");
const std = @import("std");
const global = @import("../../global.zig");
pub const ReadToEndAllocError = error{ FileTooBig, BytesReadMismatch } ||
std.Io.File.StatError ||
pub const ReadToEndAllocError = error{FileTooBig} ||
std.Io.File.ReadStreamingError ||
std.mem.Allocator.Error;
/// This is a much simpler `readToEndAlloc` that just pre-allocates the memory
/// for the file ahead of time, and errors out if the size is larger than
/// `max_bytes`.
/// Read the file from its current position through end-of-stream, returning
/// `error.FileTooBig` if the result exceeds `max_bytes`.
///
/// Caller owns the memory.
pub fn readToEndAlloc(file: std.Io.File, alloc: std.mem.Allocator, max_bytes: usize) ReadToEndAllocError![]u8 {
const size = (try file.stat(global.io())).size;
if (size > max_bytes) {
return error.FileTooBig;
var read_buf: [4096]u8 = undefined;
var result: std.ArrayList(u8) = .empty;
defer result.deinit(alloc);
while (true) {
const n = file.readStreaming(global.io(), &.{&read_buf}) catch |err| switch (err) {
error.EndOfStream => break,
else => |e| return e,
};
if (n == 0) continue;
if (n > max_bytes - result.items.len) return error.FileTooBig;
try result.appendSlice(alloc, read_buf[0..n]);
}
const buf = try alloc.alloc(u8, size);
errdefer alloc.free(buf);
if (try file.readStreaming(global.io(), &.{buf}) != size) {
return error.BytesReadMismatch;
}
return buf;
return result.toOwnedSlice(alloc);
}
test "readToEndAlloc reads through EOF and permits exact limit" {
const testing = std.testing;
const contents = "hello, world";
var tmp_dir = testing.tmpDir(.{});
defer tmp_dir.cleanup();
try tmp_dir.dir.writeFile(testing.io, .{
.sub_path = "data",
.data = contents,
});
const file = try tmp_dir.dir.openFile(testing.io, "data", .{});
defer file.close(testing.io);
const result = try readToEndAlloc(file, testing.allocator, contents.len);
defer testing.allocator.free(result);
try testing.expectEqualStrings(contents, result);
const too_big_file = try tmp_dir.dir.openFile(testing.io, "data", .{});
defer too_big_file.close(testing.io);
try testing.expectError(
error.FileTooBig,
readToEndAlloc(too_big_file, testing.allocator, contents.len - 1),
);
}

View File

@@ -3816,18 +3816,23 @@ pub fn resize(
// we just go back to the primary screen. Not great, but best
// we can do.
tw.check(.alternate_screen_init) catch break :alt;
const replacement = self.screens.getInit(alt.io, alloc, .alternate, .{
.cols = opts.cols,
.rows = opts.rows,
.max_scrollback = 0,
.kitty_image_storage_limit = if (comptime build_options.kitty_graphics)
primary.kitty_images.total_limit
else
0,
.kitty_image_loading_limits = if (comptime build_options.kitty_graphics)
primary.kitty_images.image_limits
else {},
}) catch |init_err| {
const replacement = self.screens.getInit(
self.io(),
alloc,
.alternate,
.{
.cols = opts.cols,
.rows = opts.rows,
.max_scrollback = 0,
.kitty_image_storage_limit = if (comptime build_options.kitty_graphics)
primary.kitty_images.total_limit
else
0,
.kitty_image_loading_limits = if (comptime build_options.kitty_graphics)
primary.kitty_images.image_limits
else {},
},
) catch |init_err| {
log.warn(
"alternate screen replacement failed, falling back to primary err={}",
.{init_err},

View File

@@ -150,7 +150,7 @@ pub const LoadingImage = struct {
const path = switch (t.medium) {
.direct => unreachable, // handled above
.file, .temporary_file => path: {
const len = std.Io.Dir.realPathFileAbsolute(
const len = std.Io.Dir.cwd().realPathFile(
io,
cmd.data,
&abs_buf,
@@ -364,7 +364,7 @@ pub const LoadingImage = struct {
// The temporary dir is sometimes a symlink. On macOS for
// example /tmp is /private/var/...
var buf: [std.fs.max_path_bytes]u8 = undefined;
const real_dir = buf[0 .. std.Io.Dir.realPathFileAbsolute(
const real_dir = buf[0 .. std.Io.Dir.cwd().realPathFile(
io,
dir,
&buf,
@@ -912,6 +912,46 @@ test "image load: rgb, not compressed, regular file" {
try tmp_dir.dir.access(testing.io, path, .{});
}
test "image load: rgb, not compressed, relative regular file" {
const testing = std.testing;
const alloc = testing.allocator;
const io = testing.io;
var tmp_dir = testing.tmpDir(.{});
defer tmp_dir.cleanup();
const data = @embedFile("testdata/image-rgb-none-20x15-2147483647-raw.data");
try tmp_dir.dir.writeFile(testing.io, .{
.sub_path = "image.data",
.data = data,
});
var cmd: command.Command = .{
.control = .{ .transmit = .{
.format = .rgb,
.medium = .file,
.compression = .none,
.width = 20,
.height = 15,
.image_id = 31,
} },
.data = try std.fmt.allocPrint(
alloc,
".zig-cache/tmp/{s}/image.data",
.{tmp_dir.sub_path},
),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(io, alloc, &cmd, .{
.file = true,
.temporary_file = .disabled,
.shared_memory = false,
});
defer loading.deinit(alloc);
var img = try loading.complete(alloc);
defer img.deinit(alloc);
try testing.expect(img.compression == .none);
}
test "image load: png, not compressed, regular file" {
if (sys.decode_png == null) return error.SkipZigTest;

View File

@@ -1196,7 +1196,11 @@ const Subprocess = struct {
// The gist is that it lets us detect when children
// are still alive without blocking so that we can
// kill them again.
const res_pid = posix.system.waitpid(pid, null, std.c.W.NOHANG);
const res_pid = while (true) {
const rc = posix.system.waitpid(pid, null, std.c.W.NOHANG);
if (posix.errno(rc) == .INTR) continue;
break rc;
};
log.debug("waitpid result={}", .{res_pid});
if (res_pid != 0) break;
try std.Io.sleep(global.io(), .fromMilliseconds(10), .awake);

View File

@@ -658,9 +658,11 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
// HEAVY read load, we don't want to send a ton of these so we
// use a timer under the covers
const now = std.Io.Timestamp.now(global.io(), .awake);
if (self.last_cursor_reset) |last| cursor_reset: {
if (last.durationTo(now).toMilliseconds() <= 500) {
break :cursor_reset;
cursor_reset: {
if (self.last_cursor_reset) |last| {
if (last.durationTo(now).toMilliseconds() <= 500) {
break :cursor_reset;
}
}
self.last_cursor_reset = now;