mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
terminal/kitty-gfx: start terminal state, can load and add images
This commit is contained in:
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
@ -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",
|
||||||
|
37
src/terminal/kitty/graphics_storage.zig
Normal file
37
src/terminal/kitty/graphics_storage.zig
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user