From e2fe6bf74b4afb876bbcfed4ec38ab65e37c956e Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 8 Aug 2024 15:28:31 -0500 Subject: [PATCH 1/2] 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 From c114979ee38d7e20c53aefcab92afe6d0da28262 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 8 Aug 2024 14:35:30 -0700 Subject: [PATCH 2/2] terminal/kitty: minor stylistic changes to shm --- src/terminal/kitty/graphics_image.zig | 92 +++++++++++++++------------ 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/src/terminal/kitty/graphics_image.zig b/src/terminal/kitty/graphics_image.zig index 6b675857c..8035c094a 100644 --- a/src/terminal/kitty/graphics_image.zig +++ b/src/terminal/kitty/graphics_image.zig @@ -102,53 +102,61 @@ pub const LoadingImage = struct { t: command.Transmission, path: []const u8, ) !void { - 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; + // windows is currently unsupported, does it support shm? + if (comptime builtin.target.os.tag == .windows) { + return error.UnsupportedMedium; + } - const pathz = try alloc.dupeZ(u8, path); - defer alloc.free(pathz); + // libc is required for shm_open + if (comptime !builtin.link_libc) { + return error.UnsupportedMedium; + } - 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); + // Since we're only supporting posix then max_path_bytes should + // be enough to stack allocate the path. + var buf: [std.fs.max_path_bytes]u8 = undefined; + const pathz = std.fmt.bufPrintZ(&buf, "{s}", .{path}) catch return error.InvalidData; - 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; - }, - } + const fd = std.c.shm_open(pathz, @as(c_int, @bitCast(std.c.O{ .ACCMODE = .RDONLY })), 0); + switch (std.posix.errno(fd)) { + .SUCCESS => {}, + else => |err| { + log.warn("unable to open shared memory {s}: {}", .{ path, err }); + return error.InvalidData; }, } + 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]); } /// Reads the data from a temporary file and returns it. This allocates