From 80c7f09a36bee3d2e2d9598de7838d402329234d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 20 Aug 2023 15:55:44 -0700 Subject: [PATCH] terminal/kitty-gfx: start terminal state, can load and add images --- src/terminal/Screen.zig | 4 ++ src/terminal/kitty/graphics.zig | 1 + src/terminal/kitty/graphics_exec.zig | 72 ++++++++++++++++++++----- src/terminal/kitty/graphics_storage.zig | 37 +++++++++++++ src/termio/Exec.zig | 8 +-- 5 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 src/terminal/kitty/graphics_storage.zig diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 8da118584..e53d1830c 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -865,6 +865,9 @@ selection: ?Selection = null, /// The kitty keyboard settings. kitty_keyboard: kitty.KeyFlagStack = .{}, +/// Kitty graphics protocol state. +kitty_images: kitty.graphics.ImageStorage = .{}, + /// Initialize a new screen. pub fn init( alloc: Allocator, @@ -889,6 +892,7 @@ pub fn init( } pub fn deinit(self: *Screen) void { + self.kitty_images.deinit(self.alloc); self.storage.deinit(self.alloc); self.deinitGraphemes(); } diff --git a/src/terminal/kitty/graphics.zig b/src/terminal/kitty/graphics.zig index cc84707a4..300ad21cf 100644 --- a/src/terminal/kitty/graphics.zig +++ b/src/terminal/kitty/graphics.zig @@ -6,3 +6,4 @@ pub usingnamespace @import("graphics_command.zig"); pub usingnamespace @import("graphics_exec.zig"); pub usingnamespace @import("graphics_image.zig"); +pub usingnamespace @import("graphics_storage.zig"); diff --git a/src/terminal/kitty/graphics_exec.zig b/src/terminal/kitty/graphics_exec.zig index aaf7a922c..1c88aa5a0 100644 --- a/src/terminal/kitty/graphics_exec.zig +++ b/src/terminal/kitty/graphics_exec.zig @@ -22,13 +22,19 @@ pub fn execute( buf: []u8, cmd: *Command, ) ?Response { - _ = terminal; _ = buf; const resp_: ?Response = switch (cmd.control) { .query => query(alloc, cmd), - .transmit => transmit(alloc, cmd), - else => .{ .message = "ERROR: unimplemented action" }, + .transmit => transmit(alloc, terminal, cmd), + .transmit_and_display => transmitAndDisplay(alloc, terminal, cmd), + .display => display(alloc, terminal, cmd), + + .delete, + .transmit_animation_frame, + .control_animation, + .compose_animation, + => .{ .message = "ERROR: unimplemented action" }, }; // Handle the quiet settings @@ -42,7 +48,6 @@ pub fn execute( return null; } - /// Execute a "query" command. /// /// This command is used to attempt to load an image and respond with @@ -76,33 +81,74 @@ fn query(alloc: Allocator, cmd: *Command) Response { } /// Transmit image data. -fn transmit(alloc: Allocator, cmd: *Command) Response { - const t = cmd.control.transmit; +/// +/// This loads the image, validates it, and puts it into the terminal +/// screen storage. It does not display the image. +fn transmit( + alloc: Allocator, + terminal: *Terminal, + cmd: *Command, +) Response { + const t = cmd.transmission().?; var result: Response = .{ .id = t.image_id, .image_number = t.image_number, .placement_id = t.placement_id, }; - // Load our image. This will also validate all the metadata. - var img = Image.load(alloc, cmd) catch |err| { + var img = loadAndAddImage(alloc, terminal, cmd) catch |err| { encodeError(&result, err); return result; }; - errdefer img.deinit(alloc); - - // Store our image - // TODO img.deinit(alloc); return result; } -const EncodeableError = Image.Error; +/// Display a previously transmitted image. +fn display( + alloc: Allocator, + terminal: *Terminal, + cmd: *Command, +) Response { + _ = alloc; + _ = terminal; + _ = cmd; + return .{}; +} + +/// A combination of transmit and display. Nothing special. +fn transmitAndDisplay( + alloc: Allocator, + terminal: *Terminal, + cmd: *Command, +) Response { + const resp = transmit(alloc, terminal, cmd); + if (!resp.ok()) return resp; + return display(alloc, terminal, cmd); +} + +fn loadAndAddImage( + alloc: Allocator, + terminal: *Terminal, + cmd: *Command, +) !Image { + // Load the image + var img = try Image.load(alloc, cmd); + errdefer img.deinit(alloc); + + // Store our image + try terminal.screen.kitty_images.add(alloc, img); + + return img; +} + +const EncodeableError = Image.Error || Allocator.Error; /// Encode an error code into a message for a response. fn encodeError(r: *Response, err: EncodeableError) void { switch (err) { + error.OutOfMemory => r.message = "ENOMEM: out of memory", error.InvalidData => r.message = "EINVAL: invalid data", error.UnsupportedFormat => r.message = "EINVAL: unsupported format", error.DimensionsRequired => r.message = "EINVAL: dimensions required", diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig new file mode 100644 index 000000000..eb3affe35 --- /dev/null +++ b/src/terminal/kitty/graphics_storage.zig @@ -0,0 +1,37 @@ +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; + +const Image = @import("graphics_image.zig").Image; +const command = @import("graphics_command.zig"); +const Command = command.Command; + +/// An image storage is associated with a terminal screen (i.e. main +/// screen, alt screen) and contains all the transmitted images and +/// placements. +pub const ImageStorage = struct { + /// The hash map type used to store our images. The key is the image ID + /// and the value is the image itself. + /// + /// Note that the image ID is optional when transmitting images, in + /// which case the image ID is always 0. + const HashMap = std.AutoHashMapUnmanaged(u32, Image); + + /// The set of images that are currently known. + images: HashMap = .{}, + + /// Add an already-loaded image to the storage. This will automatically + /// free any existing image with the same ID. + pub fn add(self: *ImageStorage, alloc: Allocator, img: Image) !void { + const gop = try self.images.getOrPut(alloc, img.id); + if (gop.found_existing) gop.value_ptr.deinit(alloc); + gop.value_ptr.* = img; + } + + pub fn deinit(self: *ImageStorage, alloc: Allocator) void { + var it = self.images.iterator(); + while (it.next()) |kv| kv.value_ptr.deinit(alloc); + self.images.deinit(alloc); + } +}; diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 69f5d2617..d744514d0 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1089,14 +1089,16 @@ const StreamHandler = struct { .kitty => |*kitty_cmd| { var partial_buf: [512]u8 = undefined; if (self.terminal.kittyGraphics(self.alloc, &partial_buf, kitty_cmd)) |resp| { + if (!resp.ok()) { + log.warn("erroneous kitty graphics response: {s}", .{resp.message}); + } + var buf: [1024]u8 = undefined; var buf_stream = std.io.fixedBufferStream(&buf); try resp.encode(buf_stream.writer()); - - // The "2" here is for the leading and trailing ESC const final = buf_stream.getWritten(); if (final.len > 2) { - log.warn("kitty graphics response: {s}", .{final[1 .. final.len - 1]}); + //log.warn("kitty graphics response: {s}", .{final[1 .. final.len - 1]}); self.messageWriter(try termio.Message.writeReq(self.alloc, final)); } }