terminal/kitty-gfx: start terminal state, can load and add images

This commit is contained in:
Mitchell Hashimoto
2023-08-20 15:55:44 -07:00
parent f82899bd58
commit 80c7f09a36
5 changed files with 106 additions and 16 deletions

View File

@ -865,6 +865,9 @@ selection: ?Selection = null,
/// The kitty keyboard settings. /// The kitty keyboard settings.
kitty_keyboard: kitty.KeyFlagStack = .{}, kitty_keyboard: kitty.KeyFlagStack = .{},
/// Kitty graphics protocol state.
kitty_images: kitty.graphics.ImageStorage = .{},
/// Initialize a new screen. /// Initialize a new screen.
pub fn init( pub fn init(
alloc: Allocator, alloc: Allocator,
@ -889,6 +892,7 @@ pub fn init(
} }
pub fn deinit(self: *Screen) void { pub fn deinit(self: *Screen) void {
self.kitty_images.deinit(self.alloc);
self.storage.deinit(self.alloc); self.storage.deinit(self.alloc);
self.deinitGraphemes(); self.deinitGraphemes();
} }

View File

@ -6,3 +6,4 @@
pub usingnamespace @import("graphics_command.zig"); pub usingnamespace @import("graphics_command.zig");
pub usingnamespace @import("graphics_exec.zig"); pub usingnamespace @import("graphics_exec.zig");
pub usingnamespace @import("graphics_image.zig"); pub usingnamespace @import("graphics_image.zig");
pub usingnamespace @import("graphics_storage.zig");

View File

@ -22,13 +22,19 @@ pub fn execute(
buf: []u8, buf: []u8,
cmd: *Command, cmd: *Command,
) ?Response { ) ?Response {
_ = terminal;
_ = buf; _ = buf;
const resp_: ?Response = switch (cmd.control) { const resp_: ?Response = switch (cmd.control) {
.query => query(alloc, cmd), .query => query(alloc, cmd),
.transmit => transmit(alloc, cmd), .transmit => transmit(alloc, terminal, cmd),
else => .{ .message = "ERROR: unimplemented action" }, .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 // Handle the quiet settings
@ -42,7 +48,6 @@ pub fn execute(
return null; return null;
} }
/// Execute a "query" command. /// Execute a "query" command.
/// ///
/// This command is used to attempt to load an image and respond with /// 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. /// 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 = .{ var result: Response = .{
.id = t.image_id, .id = t.image_id,
.image_number = t.image_number, .image_number = t.image_number,
.placement_id = t.placement_id, .placement_id = t.placement_id,
}; };
// Load our image. This will also validate all the metadata. var img = loadAndAddImage(alloc, terminal, cmd) catch |err| {
var img = Image.load(alloc, cmd) catch |err| {
encodeError(&result, err); encodeError(&result, err);
return result; return result;
}; };
errdefer img.deinit(alloc);
// Store our image
// TODO
img.deinit(alloc); img.deinit(alloc);
return result; 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. /// Encode an error code into a message for a response.
fn encodeError(r: *Response, err: EncodeableError) void { fn encodeError(r: *Response, err: EncodeableError) void {
switch (err) { switch (err) {
error.OutOfMemory => r.message = "ENOMEM: out of memory",
error.InvalidData => r.message = "EINVAL: invalid data", error.InvalidData => r.message = "EINVAL: invalid data",
error.UnsupportedFormat => r.message = "EINVAL: unsupported format", error.UnsupportedFormat => r.message = "EINVAL: unsupported format",
error.DimensionsRequired => r.message = "EINVAL: dimensions required", error.DimensionsRequired => r.message = "EINVAL: dimensions required",

View File

@ -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);
}
};

View File

@ -1089,14 +1089,16 @@ const StreamHandler = struct {
.kitty => |*kitty_cmd| { .kitty => |*kitty_cmd| {
var partial_buf: [512]u8 = undefined; var partial_buf: [512]u8 = undefined;
if (self.terminal.kittyGraphics(self.alloc, &partial_buf, kitty_cmd)) |resp| { 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: [1024]u8 = undefined;
var buf_stream = std.io.fixedBufferStream(&buf); var buf_stream = std.io.fixedBufferStream(&buf);
try resp.encode(buf_stream.writer()); try resp.encode(buf_stream.writer());
// The "2" here is for the leading and trailing ESC
const final = buf_stream.getWritten(); const final = buf_stream.getWritten();
if (final.len > 2) { 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)); self.messageWriter(try termio.Message.writeReq(self.alloc, final));
} }
} }