diff --git a/include/ghostty/vt/formatter.h b/include/ghostty/vt/formatter.h index 19f6664c3..9eacc6409 100644 --- a/include/ghostty/vt/formatter.h +++ b/include/ghostty/vt/formatter.h @@ -107,13 +107,6 @@ typedef struct { GhosttyFormatterScreenExtra screen; } GhosttyFormatterTerminalExtra; -/** - * Opaque handle to a formatter instance. - * - * @ingroup formatter - */ -typedef struct GhosttyFormatterImpl* GhosttyFormatter; - /** * Options for creating a terminal formatter. * diff --git a/include/ghostty/vt/kitty_graphics.h b/include/ghostty/vt/kitty_graphics.h index 359e17ddc..25f3128e1 100644 --- a/include/ghostty/vt/kitty_graphics.h +++ b/include/ghostty/vt/kitty_graphics.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #ifdef __cplusplus @@ -24,36 +25,6 @@ extern "C" { * @{ */ -/** - * Opaque handle to a Kitty graphics image storage. - * - * Obtained via ghostty_terminal_get() with - * GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS. The pointer is borrowed from - * the terminal and remains valid until the next mutating terminal call - * (e.g. ghostty_terminal_vt_write() or ghostty_terminal_reset()). - * - * @ingroup kitty_graphics - */ -typedef struct GhosttyKittyGraphicsImpl* GhosttyKittyGraphics; - -/** - * Opaque handle to a Kitty graphics image. - * - * Obtained via ghostty_kitty_graphics_image() with an image ID. The - * pointer is borrowed from the storage and remains valid until the next - * mutating terminal call. - * - * @ingroup kitty_graphics - */ -typedef const struct GhosttyKittyGraphicsImageImpl* GhosttyKittyGraphicsImage; - -/** - * Opaque handle to a Kitty graphics placement iterator. - * - * @ingroup kitty_graphics - */ -typedef struct GhosttyKittyGraphicsPlacementIteratorImpl* GhosttyKittyGraphicsPlacementIterator; - /** * Queryable data kinds for ghostty_kitty_graphics_get(). * @@ -369,6 +340,30 @@ GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_get( GhosttyKittyGraphicsPlacementData data, void* out); +/** + * Compute the grid rectangle occupied by the current placement. + * + * Uses the placement's pin, the image dimensions, and the terminal's + * cell/pixel geometry to calculate the bounding rectangle. Virtual + * placements (unicode placeholders) return GHOSTTY_NO_VALUE. + * + * @param terminal The terminal handle + * @param image The image handle for this placement's image + * @param iterator The placement iterator positioned on a placement + * @param[out] out_selection On success, receives the bounding rectangle + * as a selection with rectangle=true + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if any handle + * is NULL or the iterator is not positioned, GHOSTTY_NO_VALUE for + * virtual placements or when Kitty graphics are disabled + * + * @ingroup kitty_graphics + */ +GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_rect( + GhosttyKittyGraphicsPlacementIterator iterator, + GhosttyKittyGraphicsImage image, + GhosttyTerminal terminal, + GhosttySelection* out_selection); + /** @} */ #ifdef __cplusplus diff --git a/include/ghostty/vt/osc.h b/include/ghostty/vt/osc.h index c86498090..e17a8a182 100644 --- a/include/ghostty/vt/osc.h +++ b/include/ghostty/vt/osc.h @@ -13,26 +13,6 @@ #include #include -/** - * Opaque handle to an OSC parser instance. - * - * This handle represents an OSC (Operating System Command) parser that can - * be used to parse the contents of OSC sequences. - * - * @ingroup osc - */ -typedef struct GhosttyOscParserImpl *GhosttyOscParser; - -/** - * Opaque handle to a single OSC command. - * - * This handle represents a parsed OSC (Operating System Command) command. - * The command can be queried for its type and associated data. - * - * @ingroup osc - */ -typedef struct GhosttyOscCommandImpl *GhosttyOscCommand; - /** @defgroup osc OSC Parser * * OSC (Operating System Command) sequence parser and command handling. diff --git a/include/ghostty/vt/render.h b/include/ghostty/vt/render.h index 163a4e1d4..b15be4902 100644 --- a/include/ghostty/vt/render.h +++ b/include/ghostty/vt/render.h @@ -81,27 +81,6 @@ extern "C" { * @{ */ -/** - * Opaque handle to a render state instance. - * - * @ingroup render - */ -typedef struct GhosttyRenderStateImpl* GhosttyRenderState; - -/** - * Opaque handle to a render-state row iterator. - * - * @ingroup render - */ -typedef struct GhosttyRenderStateRowIteratorImpl* GhosttyRenderStateRowIterator; - -/** - * Opaque handle to render-state row cells. - * - * @ingroup render - */ -typedef struct GhosttyRenderStateRowCellsImpl* GhosttyRenderStateRowCells; - /** * Dirty state of a render state after update. * diff --git a/include/ghostty/vt/sgr.h b/include/ghostty/vt/sgr.h index 01ea3a359..b093bc9ff 100644 --- a/include/ghostty/vt/sgr.h +++ b/include/ghostty/vt/sgr.h @@ -47,16 +47,6 @@ extern "C" { #endif -/** - * Opaque handle to an SGR parser instance. - * - * This handle represents an SGR (Select Graphic Rendition) parser that can - * be used to parse SGR sequences and extract individual text attributes. - * - * @ingroup sgr - */ -typedef struct GhosttySgrParserImpl* GhosttySgrParser; - /** * SGR attribute tags. * diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index ff3f60ae1..73db8d6d1 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -155,13 +155,6 @@ extern "C" { * @{ */ -/** - * Opaque handle to a terminal instance. - * - * @ingroup terminal - */ -typedef struct GhosttyTerminalImpl* GhosttyTerminal; - /** * Terminal initialization options. * diff --git a/include/ghostty/vt/types.h b/include/ghostty/vt/types.h index 8f0be7760..0fe37e3b2 100644 --- a/include/ghostty/vt/types.h +++ b/include/ghostty/vt/types.h @@ -48,6 +48,105 @@ typedef enum { GHOSTTY_NO_VALUE = -4, } GhosttyResult; +/* ---- Opaque handles ---- */ + +/** + * Opaque handle to a terminal instance. + * + * @ingroup terminal + */ +typedef struct GhosttyTerminalImpl* GhosttyTerminal; + +/** + * Opaque handle to a Kitty graphics image storage. + * + * Obtained via ghostty_terminal_get() with + * GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS. The pointer is borrowed from + * the terminal and remains valid until the next mutating terminal call + * (e.g. ghostty_terminal_vt_write() or ghostty_terminal_reset()). + * + * @ingroup kitty_graphics + */ +typedef struct GhosttyKittyGraphicsImpl* GhosttyKittyGraphics; + +/** + * Opaque handle to a Kitty graphics image. + * + * Obtained via ghostty_kitty_graphics_image() with an image ID. The + * pointer is borrowed from the storage and remains valid until the next + * mutating terminal call. + * + * @ingroup kitty_graphics + */ +typedef const struct GhosttyKittyGraphicsImageImpl* GhosttyKittyGraphicsImage; + +/** + * Opaque handle to a Kitty graphics placement iterator. + * + * @ingroup kitty_graphics + */ +typedef struct GhosttyKittyGraphicsPlacementIteratorImpl* GhosttyKittyGraphicsPlacementIterator; + +/** + * Opaque handle to a render state instance. + * + * @ingroup render + */ +typedef struct GhosttyRenderStateImpl* GhosttyRenderState; + +/** + * Opaque handle to a render-state row iterator. + * + * @ingroup render + */ +typedef struct GhosttyRenderStateRowIteratorImpl* GhosttyRenderStateRowIterator; + +/** + * Opaque handle to render-state row cells. + * + * @ingroup render + */ +typedef struct GhosttyRenderStateRowCellsImpl* GhosttyRenderStateRowCells; + +/** + * Opaque handle to an SGR parser instance. + * + * This handle represents an SGR (Select Graphic Rendition) parser that can + * be used to parse SGR sequences and extract individual text attributes. + * + * @ingroup sgr + */ +typedef struct GhosttySgrParserImpl* GhosttySgrParser; + +/** + * Opaque handle to a formatter instance. + * + * @ingroup formatter + */ +typedef struct GhosttyFormatterImpl* GhosttyFormatter; + +/** + * Opaque handle to an OSC parser instance. + * + * This handle represents an OSC (Operating System Command) parser that can + * be used to parse the contents of OSC sequences. + * + * @ingroup osc + */ +typedef struct GhosttyOscParserImpl* GhosttyOscParser; + +/** + * Opaque handle to a single OSC command. + * + * This handle represents a parsed OSC (Operating System Command) command. + * The command can be queried for its type and associated data. + * + * @ingroup osc + */ +typedef struct GhosttyOscCommandImpl* GhosttyOscCommand; + +/* ---- Common value types ---- */ + /** * A borrowed byte string (pointer + length). * diff --git a/src/lib_vt.zig b/src/lib_vt.zig index da113e4f7..9098f9dbc 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -240,6 +240,7 @@ comptime { @export(&c.kitty_graphics_placement_iterator_free, .{ .name = "ghostty_kitty_graphics_placement_iterator_free" }); @export(&c.kitty_graphics_placement_next, .{ .name = "ghostty_kitty_graphics_placement_next" }); @export(&c.kitty_graphics_placement_get, .{ .name = "ghostty_kitty_graphics_placement_get" }); + @export(&c.kitty_graphics_placement_rect, .{ .name = "ghostty_kitty_graphics_placement_rect" }); @export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" }); @export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" }); @export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" }); diff --git a/src/terminal/c/kitty_graphics.zig b/src/terminal/c/kitty_graphics.zig index ecfc574c2..70ad5f818 100644 --- a/src/terminal/c/kitty_graphics.zig +++ b/src/terminal/c/kitty_graphics.zig @@ -1,10 +1,14 @@ const std = @import("std"); +const testing = std.testing; const build_options = @import("terminal_options"); const lib = @import("../lib.zig"); const CAllocator = lib.alloc.Allocator; const kitty_storage = @import("../kitty/graphics_storage.zig"); const kitty_cmd = @import("../kitty/graphics_command.zig"); const Image = @import("../kitty/graphics_image.zig").Image; +const grid_ref = @import("grid_ref.zig"); +const selection_c = @import("selection.zig"); +const terminal_c = @import("terminal.zig"); const Result = @import("result.zig").Result; /// C: GhosttyKittyGraphics @@ -267,8 +271,31 @@ fn placementGetTyped( return .success; } -const testing = std.testing; -const terminal_c = @import("terminal.zig"); +pub fn placement_rect( + iter_: PlacementIterator, + image_: ImageHandle, + terminal_: terminal_c.Terminal, + out: *selection_c.CSelection, +) callconv(lib.calling_conv) Result { + if (comptime !build_options.kitty_graphics) return .no_value; + + const wrapper = terminal_ orelse return .invalid_value; + const image = image_ orelse return .invalid_value; + const iter = iter_ orelse return .invalid_value; + const entry = iter.entry orelse return .invalid_value; + const r = entry.value_ptr.rect( + image.*, + wrapper.terminal, + ) orelse return .no_value; + + out.* = .{ + .start = grid_ref.CGridRef.fromPin(r.top_left), + .end = grid_ref.CGridRef.fromPin(r.bottom_right), + .rectangle = true, + }; + + return .success; +} test "placement_iterator new/free" { var iter: PlacementIterator = null; @@ -525,6 +552,67 @@ test "image_get_handle and image_get with transmitted image" { try testing.expect(data_len > 0); } +test "placement_rect with transmit and display" { + if (comptime !build_options.kitty_graphics) return error.SkipZigTest; + + var t: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &t, + .{ .cols = 80, .rows = 24, .max_scrollback = 0 }, + )); + defer terminal_c.free(t); + + // Set cell size so grid calculations are deterministic. + // 80 cols * 10px = 800px, 24 rows * 20px = 480px. + try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20)); + + // Transmit and display a 1x2 RGB image at cursor (0,0). + // c=10,r=1 => 10 columns, 1 row. + const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2,c=10,r=1;////////\x1b\\"; + terminal_c.vt_write(t, cmd.ptr, cmd.len); + + var graphics: KittyGraphics = undefined; + try testing.expectEqual(Result.success, terminal_c.get( + t, + .kitty_graphics, + @ptrCast(&graphics), + )); + + const img = image_get_handle(graphics, 1); + try testing.expect(img != null); + + var iter: PlacementIterator = null; + try testing.expectEqual(Result.success, placement_iterator_new( + &lib.alloc.test_allocator, + &iter, + )); + defer placement_iterator_free(iter); + + try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter))); + try testing.expect(placement_iterator_next(iter)); + + var sel: selection_c.CSelection = undefined; + try testing.expectEqual(Result.success, placement_rect(iter, img, t, &sel)); + + // Placement starts at cursor origin (0,0). + try testing.expectEqual(0, sel.start.x); + try testing.expectEqual(0, sel.start.y); + + // 10 columns wide, 1 row tall => bottom-right is (9, 0). + try testing.expectEqual(9, sel.end.x); + try testing.expectEqual(0, sel.end.y); + + try testing.expect(sel.rectangle); +} + +test "placement_rect null args return invalid_value" { + if (comptime !build_options.kitty_graphics) return error.SkipZigTest; + + var sel: selection_c.CSelection = undefined; + try testing.expectEqual(Result.invalid_value, placement_rect(null, null, null, &sel)); +} + test "image_get on null returns invalid_value" { if (comptime !build_options.kitty_graphics) return error.SkipZigTest; diff --git a/src/terminal/c/main.zig b/src/terminal/c/main.zig index c6e11c5e8..76c471dd0 100644 --- a/src/terminal/c/main.zig +++ b/src/terminal/c/main.zig @@ -16,6 +16,7 @@ pub const kitty_graphics_placement_iterator_new = kitty_graphics.placement_itera pub const kitty_graphics_placement_iterator_free = kitty_graphics.placement_iterator_free; pub const kitty_graphics_placement_next = kitty_graphics.placement_iterator_next; pub const kitty_graphics_placement_get = kitty_graphics.placement_get; +pub const kitty_graphics_placement_rect = kitty_graphics.placement_rect; pub const types = @import("types.zig"); pub const modes = @import("modes.zig"); pub const osc = @import("osc.zig");