terminal: make wuffs runtime-swappable, enable Kitty graphics for libvt (#12117)

This enables Kitty Graphics for `libghostty-vt` for the Zig API (C to
come next).

First, a note on security: by default, Kitty graphics will only allow
images transferred via the _direct_ medium (directly via the pty) and
will not allow file or shared memory based images. libghostty-vt
consumers need to manually opt-in via terminal init options or
`terminal.setKittyGraphicsLoadingLimits` to enable file-based things.
**This is so we're as secure as possible by default.**

Second, for PNG decoding, embedders must now set a global
runtime-callback at `ghostty.sys.decode_png`. If this is not set, PNG
formatted images are rejected. If this is set, then we'll use this to
decode and embedders can use any decoder they want.

There is no C API exposed yet to set this, so this is only for Zig to
start.

## Examples (Zig)

### Configuring Allowed Formats

```zig
var term = try Terminal.init(alloc, .{
    .cols = 80,
    .rows = 24,
    // Only allow direct (inline) image data, no file/shm access.
    // This is the default so you don't need to specify it.
    .kitty_image_loading_limits = .direct,
});
```

```zig
var term = try Terminal.init(alloc, .{
    .cols = 80,
    .rows = 24,
    // Allow all transmission mediums: direct, file, temporary file, shared memory.
    .kitty_image_loading_limits = .all,
});
```

```zig
var term = try Terminal.init(alloc, .{
    .cols = 80,
    .rows = 24,
    .kitty_image_loading_limits = .{
        .file = true,
        .temporary_file = true,
        .shared_memory = false,
    },
});
```

### Iterate all images

```zig
var it = term.screens.active.kitty_images.images.iterator();
while (it.next()) |kv| {
    const img = kv.value_ptr;
    std.debug.print("id={} {}x{} format={} bytes={}\n", .{
        img.id, img.width, img.height, img.format, img.data.len,
    });
}
```

### Delete all images

```zig
term.screens.active.kitty_images.delete(alloc, &term, .{ .all = true });
```
This commit is contained in:
Mitchell Hashimoto
2026-04-06 06:57:14 -07:00
committed by GitHub
11 changed files with 399 additions and 45 deletions

View File

@@ -19,6 +19,24 @@ const builtin = @import("builtin");
// or are too Ghostty-internal.
const terminal = @import("terminal/main.zig");
/// System interface for the terminal package.
///
/// This module provides runtime-swappable function pointers for operations
/// that depend on external implementations. Embedders can use this to
/// provide or override default behaviors. These must be set at startup
/// before any terminal functionality is used.
///
/// This lets libghostty-vt have no runtime dependencies on external
/// libraries, while still allowing rich functionality that may require
/// external libraries (e.g. image decoding or regular expresssions).
///
/// Setting these will enable various features of the terminal package.
/// For example, setting a PNG decoder will enable support for PNG images in
/// the Kitty Graphics Protocol.
///
/// Additional functionality will be added here over time as needed.
pub const sys = terminal.sys;
pub const apc = terminal.apc;
pub const dcs = terminal.dcs;
pub const osc = terminal.osc;

View File

@@ -255,7 +255,16 @@ pub const Options = struct {
/// The total storage limit for Kitty images in bytes for this
/// screen. Kitty image storage is per-screen.
kitty_image_storage_limit: usize = 320 * 1000 * 1000, // 320MB
kitty_image_storage_limit: usize = switch (build_options.artifact) {
.ghostty => 320 * 1000 * 1000, // 320MB
.lib => 10 * 1000 * 1000, // 10MB
},
/// The limits for what medium types are allowed for Kitty image loading.
kitty_image_loading_limits: if (build_options.kitty_graphics)
kitty.graphics.LoadingImage.Limits
else
void = if (build_options.kitty_graphics) .direct else {},
/// A simple, default terminal. If you rely on specific dimensions or
/// scrollback (or lack of) then do not use this directly. This is just
@@ -313,6 +322,7 @@ pub fn init(
&result,
opts.kitty_image_storage_limit,
) catch unreachable;
result.kitty_images.image_limits = opts.kitty_image_loading_limits;
}
return result;

View File

@@ -191,6 +191,26 @@ pub const Options = struct {
/// The default mode state. When the terminal gets a reset, it
/// will revert back to this state.
default_modes: modespkg.ModePacked = .{},
/// The total storage limit for Kitty images in bytes. Has no effect
/// if kitty images are disabled at build-time.
kitty_image_storage_limit: usize = switch (build_options.artifact) {
.ghostty => 320 * 1000 * 1000, // 320MB
// libghostty we start with a much lower limit since this is an
// embedded library and we want to be more conservative with memory
// usage by default.
.lib => 10 * 1000 * 1000, // 10MB
},
/// The limits for what medium types are allowed for Kitty image loading.
/// Has no effect if kitty images are disabled otherwise. For example,
// if no `sys.decode_png` hook is specified, png formats are disabled
// no matter what.
kitty_image_loading_limits: if (build_options.kitty_graphics)
kitty.graphics.LoadingImage.Limits
else
void = if (build_options.kitty_graphics) .direct else {},
};
/// Initialize a new terminal.
@@ -205,6 +225,8 @@ pub fn init(
.cols = cols,
.rows = rows,
.max_scrollback = opts.max_scrollback,
.kitty_image_storage_limit = opts.kitty_image_storage_limit,
.kitty_image_loading_limits = opts.kitty_image_loading_limits,
});
errdefer screen_set.deinit(alloc);
@@ -2693,6 +2715,34 @@ pub fn kittyGraphics(
return kitty.graphics.execute(alloc, self, cmd);
}
/// Set the storage size limit for Kitty graphics across all screens.
pub fn setKittyGraphicsSizeLimit(
self: *Terminal,
alloc: Allocator,
limit: usize,
) !void {
if (comptime !build_options.kitty_graphics) return;
var it = self.screens.all.iterator();
while (it.next()) |entry| {
const screen: *Screen = entry.value.*;
try screen.kitty_images.setLimit(alloc, screen, limit);
}
}
/// Set the allowed medium types for Kitty graphics image loading
/// across all screens.
pub fn setKittyGraphicsLoadingLimits(
self: *Terminal,
limits: kitty.graphics.LoadingImage.Limits,
) void {
if (comptime !build_options.kitty_graphics) return;
var it = self.screens.all.iterator();
while (it.next()) |entry| {
const screen: *Screen = entry.value.*;
screen.kitty_images.image_limits = limits;
}
}
/// Set a style attribute.
pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void {
try self.screens.active.setAttribute(attr);
@@ -2941,12 +2991,15 @@ pub fn switchScreen(self: *Terminal, key: ScreenSet.Key) !?*Screen {
.alternate => 0,
},
// Inherit our Kitty image storage limit from the primary
// Inherit our Kitty image settings from the primary
// screen if we have to initialize.
.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 {},
},
);
};

View File

@@ -47,8 +47,23 @@ pub const Options = struct {
opts.addOption(bool, "simd", self.simd);
opts.addOption(bool, "slow_runtime_safety", self.slow_runtime_safety);
// Kitty graphics is almost always true. This used to be conditional on
// some other factors but we've since generalized the implementation
// to support optional PNG decoding, OS capabilities like filesystems,
// etc. So its safe to always enable it and just have the
// implementation deal with unsupported features as needed.
//
// We disable it on wasm32-freestanding because we at the least
// require the ability to get timestamps and there is no way to
// do that with freestanding targets.
const target = m.resolved_target.?.result;
opts.addOption(
bool,
"kitty_graphics",
!(target.cpu.arch == .wasm32 and target.os.tag == .freestanding),
);
// These are synthesized based on other options.
opts.addOption(bool, "kitty_graphics", self.oniguruma);
opts.addOption(bool, "tmux_control_mode", self.oniguruma);
// Version information.

View File

@@ -25,6 +25,7 @@ pub const unicode = @import("graphics_unicode.zig");
pub const Command = command.Command;
pub const CommandParser = command.Parser;
pub const Image = image.Image;
pub const LoadingImage = image.LoadingImage;
pub const ImageStorage = storage.ImageStorage;
pub const RenderPlacement = render.Placement;
pub const Response = command.Response;

View File

@@ -44,7 +44,7 @@ pub fn execute(
var quiet = cmd.quiet;
const resp_: ?Response = switch (cmd.control) {
.query => query(alloc, cmd),
.query => query(alloc, terminal, cmd),
.display => display(alloc, terminal, cmd),
.delete => delete(alloc, terminal, cmd),
@@ -94,7 +94,11 @@ pub fn execute(
/// This command is used to attempt to load an image and respond with
/// success/error but does not persist any of the command to the terminal
/// state.
fn query(alloc: Allocator, cmd: *const Command) Response {
fn query(
alloc: Allocator,
terminal: *const Terminal,
cmd: *const Command,
) Response {
const t = cmd.control.query;
// Query requires image ID. We can't actually send a response without
@@ -112,7 +116,8 @@ fn query(alloc: Allocator, cmd: *const Command) Response {
};
// Attempt to load the image. If we cannot, then set an appropriate error.
var loading = LoadingImage.init(alloc, cmd) catch |err| {
const storage = &terminal.screens.active.kitty_images;
var loading = LoadingImage.init(alloc, cmd, storage.image_limits) catch |err| {
encodeError(&result, err);
return result;
};
@@ -322,7 +327,7 @@ fn loadAndAddImage(
}
break :loading loading.*;
} else try .init(alloc, cmd);
} else try .init(alloc, cmd, storage.image_limits);
// We only want to deinit on error. If we're chunking, then we don't
// want to deinit at all. If we're not chunking, then we'll deinit

View File

@@ -8,7 +8,7 @@ const posix = std.posix;
const fastmem = @import("../../fastmem.zig");
const command = @import("graphics_command.zig");
const PageList = @import("../PageList.zig");
const wuffs = @import("wuffs");
const sys = @import("../sys.zig");
const temp_dir = struct {
const TempDir = @import("../../os/TempDir.zig");
@@ -44,10 +44,38 @@ pub const LoadingImage = struct {
/// used if q isn't set on subsequent chunks.
quiet: command.Command.Quiet,
/// The limits of the Kitty Graphics protocol we should allow.
///
/// This can be used to restrict the type of images and other
/// parameters for resource or security reasons. Note that depending
/// on how libghostty is compiled, some of these may be fully unsupported
/// and ignored (e.g. "file" on wasm32-freestanding).
pub const Limits = packed struct {
file: bool,
temporary_file: bool,
shared_memory: bool,
pub const all: Limits = .{
.file = true,
.temporary_file = true,
.shared_memory = true,
};
pub const direct: Limits = .{
.file = false,
.temporary_file = false,
.shared_memory = false,
};
};
/// Initialize a chunked immage from the first image transmission.
/// If this is a multi-chunk image, this should only be the FIRST
/// chunk.
pub fn init(alloc: Allocator, cmd: *const command.Command) !LoadingImage {
pub fn init(
alloc: Allocator,
cmd: *const command.Command,
limits: Limits,
) !LoadingImage {
// Build our initial image from the properties sent via the control.
// These can be overwritten by the data loading process. For example,
// PNG loading sets the width/height from the data.
@@ -72,6 +100,26 @@ pub const LoadingImage = struct {
return result;
}
// Verify our capabilities and limits allow this.
{
// Special case if we don't support decoding PNGs and the format
// is a PNG we can save a lot of memory/effort buffering the
// data but failing up front.
if (t.format == .png and
sys.decode_png == null)
{
return error.UnsupportedMedium;
}
// Verify the medium is allowed
switch (t.medium) {
.direct => unreachable,
.file => if (!limits.file) return error.UnsupportedMedium,
.temporary_file => if (!limits.temporary_file) return error.UnsupportedMedium,
.shared_memory => if (!limits.shared_memory) return error.UnsupportedMedium,
}
}
// Otherwise, the payload data is guaranteed to be a path.
if (comptime builtin.os.tag != .windows) {
@@ -426,13 +474,14 @@ pub const LoadingImage = struct {
fn decodePng(self: *LoadingImage, alloc: Allocator) !void {
assert(self.image.format == .png);
const result = wuffs.png.decode(
const decode_png_fn = sys.decode_png orelse
return error.UnsupportedFormat;
const result = decode_png_fn(
alloc,
self.data.items,
) catch |err| switch (err) {
error.WuffsError => return error.InvalidData,
error.InvalidData => return error.InvalidData,
error.OutOfMemory => return error.OutOfMemory,
error.Overflow => return error.InvalidData,
};
defer alloc.free(result.data);
@@ -522,7 +571,7 @@ test "image load with invalid RGB data" {
.data = try alloc.dupe(u8, "AAAA"),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .direct);
defer loading.deinit(alloc);
}
@@ -540,7 +589,7 @@ test "image load with image too wide" {
.data = try alloc.dupe(u8, "AAAA"),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .direct);
defer loading.deinit(alloc);
try testing.expectError(error.DimensionsTooLarge, loading.complete(alloc));
}
@@ -559,7 +608,7 @@ test "image load with image too tall" {
.data = try alloc.dupe(u8, "AAAA"),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .direct);
defer loading.deinit(alloc);
try testing.expectError(error.DimensionsTooLarge, loading.complete(alloc));
}
@@ -583,7 +632,7 @@ test "image load: rgb, zlib compressed, direct" {
),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .direct);
defer loading.deinit(alloc);
var img = try loading.complete(alloc);
defer img.deinit(alloc);
@@ -611,7 +660,7 @@ test "image load: rgb, not compressed, direct" {
),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .direct);
defer loading.deinit(alloc);
var img = try loading.complete(alloc);
defer img.deinit(alloc);
@@ -640,7 +689,7 @@ test "image load: rgb, zlib compressed, direct, chunked" {
.data = try alloc.dupe(u8, data[0..1024]),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .direct);
defer loading.deinit(alloc);
// Read our remaining chunks
@@ -676,7 +725,7 @@ test "image load: rgb, zlib compressed, direct, chunked with zero initial chunk"
} },
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .direct);
defer loading.deinit(alloc);
// Read our remaining chunks
@@ -720,7 +769,7 @@ test "image load: temporary file without correct path" {
.data = try alloc.dupe(u8, path),
};
defer cmd.deinit(alloc);
try testing.expectError(error.TemporaryFileNotNamedCorrectly, LoadingImage.init(alloc, &cmd));
try testing.expectError(error.TemporaryFileNotNamedCorrectly, LoadingImage.init(alloc, &cmd, .all));
// Temporary file should still be there
try tmp_dir.dir.access(path, .{});
@@ -753,7 +802,7 @@ test "image load: rgb, not compressed, temporary file" {
.data = try alloc.dupe(u8, path),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .all);
defer loading.deinit(alloc);
var img = try loading.complete(alloc);
defer img.deinit(alloc);
@@ -790,7 +839,7 @@ test "image load: rgb, not compressed, regular file" {
.data = try alloc.dupe(u8, path),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .all);
defer loading.deinit(alloc);
var img = try loading.complete(alloc);
defer img.deinit(alloc);
@@ -799,6 +848,8 @@ test "image load: rgb, not compressed, regular file" {
}
test "image load: png, not compressed, regular file" {
if (sys.decode_png == null) return error.SkipZigTest;
const testing = std.testing;
const alloc = testing.allocator;
@@ -825,7 +876,7 @@ test "image load: png, not compressed, regular file" {
.data = try alloc.dupe(u8, path),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd);
var loading = try LoadingImage.init(alloc, &cmd, .all);
defer loading.deinit(alloc);
var img = try loading.complete(alloc);
defer img.deinit(alloc);
@@ -833,3 +884,161 @@ test "image load: png, not compressed, regular file" {
try testing.expect(img.format == .rgba);
try tmp_dir.dir.access(path, .{});
}
test "limits: direct medium always allowed" {
const testing = std.testing;
const alloc = testing.allocator;
var cmd: command.Command = .{
.control = .{ .transmit = .{
.format = .rgb,
.medium = .direct,
.width = 1,
.height = 1,
.image_id = 31,
} },
.data = try alloc.dupe(u8, "AAAA"),
};
defer cmd.deinit(alloc);
// Direct medium should work even with the most restrictive limits
var loading = try LoadingImage.init(alloc, &cmd, .direct);
defer loading.deinit(alloc);
}
test "limits: file medium blocked by limits" {
const testing = std.testing;
const alloc = testing.allocator;
var tmp_dir = try temp_dir.TempDir.init();
defer tmp_dir.deinit();
const data = @embedFile("testdata/image-rgb-none-20x15-2147483647-raw.data");
try tmp_dir.dir.writeFile(.{
.sub_path = "image.data",
.data = data,
});
var buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try tmp_dir.dir.realpath("image.data", &buf);
var cmd: command.Command = .{
.control = .{ .transmit = .{
.format = .rgb,
.medium = .file,
.compression = .none,
.width = 20,
.height = 15,
.image_id = 31,
} },
.data = try alloc.dupe(u8, path),
};
defer cmd.deinit(alloc);
try testing.expectError(error.UnsupportedMedium, LoadingImage.init(alloc, &cmd, .direct));
}
test "limits: file medium allowed by limits" {
const testing = std.testing;
const alloc = testing.allocator;
var tmp_dir = try temp_dir.TempDir.init();
defer tmp_dir.deinit();
const data = @embedFile("testdata/image-rgb-none-20x15-2147483647-raw.data");
try tmp_dir.dir.writeFile(.{
.sub_path = "image.data",
.data = data,
});
var buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try tmp_dir.dir.realpath("image.data", &buf);
var cmd: command.Command = .{
.control = .{ .transmit = .{
.format = .rgb,
.medium = .file,
.compression = .none,
.width = 20,
.height = 15,
.image_id = 31,
} },
.data = try alloc.dupe(u8, path),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd, .{
.file = true,
.temporary_file = false,
.shared_memory = false,
});
defer loading.deinit(alloc);
}
test "limits: temporary file medium blocked by limits" {
const testing = std.testing;
const alloc = testing.allocator;
var tmp_dir = try temp_dir.TempDir.init();
defer tmp_dir.deinit();
const data = @embedFile("testdata/image-rgb-none-20x15-2147483647-raw.data");
try tmp_dir.dir.writeFile(.{
.sub_path = "tty-graphics-protocol-image.data",
.data = data,
});
var buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try tmp_dir.dir.realpath("tty-graphics-protocol-image.data", &buf);
var cmd: command.Command = .{
.control = .{ .transmit = .{
.format = .rgb,
.medium = .temporary_file,
.compression = .none,
.width = 20,
.height = 15,
.image_id = 31,
} },
.data = try alloc.dupe(u8, path),
};
defer cmd.deinit(alloc);
try testing.expectError(error.UnsupportedMedium, LoadingImage.init(alloc, &cmd, .{
.file = true,
.temporary_file = false,
.shared_memory = true,
}));
// File should still exist since we blocked before reading
try tmp_dir.dir.access("tty-graphics-protocol-image.data", .{});
}
test "limits: temporary file medium allowed by limits" {
const testing = std.testing;
const alloc = testing.allocator;
var tmp_dir = try temp_dir.TempDir.init();
defer tmp_dir.deinit();
const data = @embedFile("testdata/image-rgb-none-20x15-2147483647-raw.data");
try tmp_dir.dir.writeFile(.{
.sub_path = "tty-graphics-protocol-image.data",
.data = data,
});
var buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try tmp_dir.dir.realpath("tty-graphics-protocol-image.data", &buf);
var cmd: command.Command = .{
.control = .{ .transmit = .{
.format = .rgb,
.medium = .temporary_file,
.compression = .none,
.width = 20,
.height = 15,
.image_id = 31,
} },
.data = try alloc.dupe(u8, path),
};
defer cmd.deinit(alloc);
var loading = try LoadingImage.init(alloc, &cmd, .{
.file = false,
.temporary_file = true,
.shared_memory = false,
});
defer loading.deinit(alloc);
}

View File

@@ -51,6 +51,9 @@ pub const ImageStorage = struct {
/// Non-null if there is an in-progress loading image.
loading: ?*LoadingImage = null,
/// The limits of what medium types are allowed for image loading.
image_limits: LoadingImage.Limits = .direct,
/// The total bytes of image data that have been loaded and the limit.
/// If the limit is reached, the oldest images will be evicted to make
/// space. Unused images take priority.
@@ -89,8 +92,9 @@ pub const ImageStorage = struct {
) !void {
// Special case disabling by quickly deleting all
if (limit == 0) {
const image_limits = self.image_limits;
self.deinit(alloc, s);
self.* = .{};
self.* = .{ .image_limits = image_limits };
}
// If we re lowering our limit, check if we need to evict.

View File

@@ -23,6 +23,7 @@ pub const search = @import("search.zig");
pub const sgr = @import("sgr.zig");
pub const size = @import("size.zig");
pub const size_report = @import("size_report.zig");
pub const sys = @import("sys.zig");
pub const tmux = if (options.tmux_control_mode) @import("tmux.zig") else struct {};
pub const x11_color = @import("x11_color.zig");

54
src/terminal/sys.zig Normal file
View File

@@ -0,0 +1,54 @@
//! System interface for the terminal package.
//!
//! This provides runtime-swappable function pointers for operations that
//! depend on external implementations (e.g. image decoding). Each function
//! pointer is initialized with a default implementation if available.
//!
//! This exists so that the terminal package doesn't have hard dependencies
//! on specific libraries and enables embedders of the terminal package to
//! swap out implementations as needed at startup to provide their own
//! implementations.
const std = @import("std");
const Allocator = std.mem.Allocator;
const build_options = @import("terminal_options");
/// Decode PNG data into RGBA pixels. If null, PNG decoding is unsupported
/// and the exact semantics are up to callers. For example, the Kitty Graphics
/// Protocol will work but cannot accept PNG images.
pub var decode_png: ?DecodePngFn = png: {
if (build_options.artifact == .lib) break :png null;
break :png &decodePngWuffs;
};
pub const DecodeError = Allocator.Error || error{InvalidData};
pub const DecodePngFn = *const fn (Allocator, []const u8) DecodeError!Image;
/// The result of decoding an image. The caller owns the returned data
/// and must free it with the same allocator that was passed to the
/// decode function.
pub const Image = struct {
width: u32,
height: u32,
data: []u8,
};
fn decodePngWuffs(
alloc: Allocator,
data: []const u8,
) DecodeError!Image {
const wuffs = @import("wuffs");
const result = wuffs.png.decode(
alloc,
data,
) catch |err| switch (err) {
error.WuffsError => return error.InvalidData,
error.OutOfMemory => return error.OutOfMemory,
error.Overflow => return error.InvalidData,
};
return .{
.width = result.width,
.height = result.height,
.data = result.data,
};
}

View File

@@ -255,21 +255,12 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
},
.palette = .init(opts.config.palette),
},
.kitty_image_storage_limit = opts.config.image_storage_limit,
.kitty_image_loading_limits = .all,
};
});
errdefer term.deinit(alloc);
// Set the image size limits
var it = term.screens.all.iterator();
while (it.next()) |entry| {
const screen: *terminalpkg.Screen = entry.value.*;
try screen.kitty_images.setLimit(
alloc,
screen,
opts.config.image_storage_limit,
);
}
// Set our default cursor style
term.screens.active.cursor.cursor_style = opts.config.cursor_style;
@@ -463,16 +454,9 @@ pub fn changeConfig(self: *Termio, td: *ThreadData, config: *DerivedConfig) !voi
break :cursor color.toTerminalRGB() orelse break :cursor null;
};
// Set the image size limits
var it = self.terminal.screens.all.iterator();
while (it.next()) |entry| {
const screen: *terminalpkg.Screen = entry.value.*;
try screen.kitty_images.setLimit(
self.alloc,
screen,
config.image_storage_limit,
);
}
// Set the image limits
try self.terminal.setKittyGraphicsSizeLimit(self.alloc, config.image_storage_limit);
self.terminal.setKittyGraphicsLoadingLimits(.all);
}
/// Resize the terminal.