From e2fe6bf74b4afb876bbcfed4ec38ab65e37c956e Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 8 Aug 2024 15:28:31 -0500 Subject: [PATCH] kitty graphics: add support for shared memory transfer medium Adds support for using shared memory to transfer images between the CLI and Ghostty using the Kitty image protocol. This should be the fastest way to transfer images if the CLI program and Ghostty are running on the same system. Works for single image transfer using `kitten icat`: ``` kitten icat --transfer-mode=memory images/icons/icon_256x256.png ``` However trying to play a movie with `mpv` fails in Ghostty (although it works in Kitty): ``` mpv --vo=kitty --vo-kitty-use-shm=yes --profile=sw-fast --really-quiet video.mp4 ``` `mpv` appears to be sending frames using the normal image transfer commands but always setting `more_chunks` to `true` which results in an image never being shown by Ghostty. Shared memory transfer on Windows remains to be implemented. --- src/terminal/kitty/graphics_image.zig | 64 ++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/src/terminal/kitty/graphics_image.zig b/src/terminal/kitty/graphics_image.zig index 83ae69f7d..6b675857c 100644 --- a/src/terminal/kitty/graphics_image.zig +++ b/src/terminal/kitty/graphics_image.zig @@ -75,9 +75,13 @@ pub const LoadingImage = struct { } var abs_buf: [std.fs.max_path_bytes]u8 = undefined; - const path = posix.realpath(cmd.data, &abs_buf) catch |err| { - log.warn("failed to get absolute path: {}", .{err}); - return error.InvalidData; + const path = switch (t.medium) { + .direct => unreachable, // handled above + .file, .temporary_file => posix.realpath(cmd.data, &abs_buf) catch |err| { + log.warn("failed to get absolute path: {}", .{err}); + return error.InvalidData; + }, + .shared_memory => cmd.data, }; // Depending on the medium, load the data from the path. @@ -98,15 +102,53 @@ pub const LoadingImage = struct { t: command.Transmission, path: []const u8, ) !void { - // We require libc for this for shm_open - if (comptime !builtin.link_libc) return error.UnsupportedMedium; + switch (builtin.target.os.tag) { + .windows => { + // TODO: support shared memory on windows + return error.UnsupportedMedium; + }, + else => { + // libc is required for shm_open + if (comptime !builtin.link_libc) return error.UnsupportedMedium; - // Todo: support shared memory - _ = self; - _ = alloc; - _ = t; - _ = path; - return error.UnsupportedMedium; + const pathz = try alloc.dupeZ(u8, path); + defer alloc.free(pathz); + + const fd = std.c.shm_open(pathz, @as(c_int, @bitCast(std.c.O{ .ACCMODE = .RDONLY })), 0); + switch (std.posix.errno(fd)) { + .SUCCESS => { + defer _ = std.c.close(fd); + defer _ = std.c.shm_unlink(pathz); + + const stat = std.posix.fstat(fd) catch |err| { + log.warn("unable to fstat shared memory {s}: {}", .{ path, err }); + return error.InvalidData; + }; + + if (stat.size <= 0) return error.InvalidData; + + const size: usize = @intCast(stat.size); + + const map = std.posix.mmap(null, size, std.c.PROT.READ, std.c.MAP{ .TYPE = .SHARED }, fd, 0) catch |err| { + log.warn("unable to mmap shared memory {s}: {}", .{ path, err }); + return error.InvalidData; + }; + defer std.posix.munmap(map); + + const start: usize = @intCast(t.offset); + const end: usize = if (t.size > 0) @min(@as(usize, @intCast(t.offset)) + @as(usize, @intCast(t.size)), size) else size; + + assert(self.data.items.len == 0); + try self.data.appendSlice(alloc, map[start..end]); + }, + + else => |err| { + log.warn("unable to open shared memory {s}: {}", .{ path, err }); + return error.InvalidData; + }, + } + }, + } } /// Reads the data from a temporary file and returns it. This allocates