mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 11:35:48 +00:00
libghostty: add kitty graphics placement iterator API
Add a C API for iterating over Kitty graphics placements via the new GhosttyKittyGraphics opaque handle. The API follows the same pattern as the render state row iterator: allocate an iterator with ghostty_kitty_graphics_placement_iterator_new, populate it from a graphics handle via ghostty_kitty_graphics_get with the PLACEMENT_ITERATOR data kind, advance with ghostty_kitty_graphics_placement_next, and query per-placement fields with ghostty_kitty_graphics_placement_get.
This commit is contained in:
@@ -7,6 +7,11 @@
|
||||
#ifndef GHOSTTY_VT_KITTY_GRAPHICS_H
|
||||
#define GHOSTTY_VT_KITTY_GRAPHICS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -14,7 +19,7 @@ extern "C" {
|
||||
/** @defgroup kitty_graphics Kitty Graphics
|
||||
*
|
||||
* Opaque handle to the Kitty graphics image storage associated with a
|
||||
* terminal screen.
|
||||
* terminal screen, and an iterator for inspecting placements.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
@@ -31,6 +36,204 @@ extern "C" {
|
||||
*/
|
||||
typedef struct GhosttyKittyGraphicsImpl* GhosttyKittyGraphics;
|
||||
|
||||
/**
|
||||
* Opaque handle to a Kitty graphics placement iterator.
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef struct GhosttyKittyGraphicsPlacementIteratorImpl* GhosttyKittyGraphicsPlacementIterator;
|
||||
|
||||
/**
|
||||
* Queryable data kinds for ghostty_kitty_graphics_get().
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef enum {
|
||||
/** Invalid / sentinel value. */
|
||||
GHOSTTY_KITTY_GRAPHICS_DATA_INVALID = 0,
|
||||
|
||||
/**
|
||||
* Populate a pre-allocated placement iterator with placement data from
|
||||
* the storage. Iterator data is only valid as long as the underlying
|
||||
* terminal is not mutated.
|
||||
*
|
||||
* Output type: GhosttyKittyGraphicsPlacementIterator *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR = 1,
|
||||
} GhosttyKittyGraphicsData;
|
||||
|
||||
/**
|
||||
* Queryable data kinds for ghostty_kitty_graphics_placement_get().
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef enum {
|
||||
/** Invalid / sentinel value. */
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INVALID = 0,
|
||||
|
||||
/**
|
||||
* The image ID this placement belongs to.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID = 1,
|
||||
|
||||
/**
|
||||
* The placement ID.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_PLACEMENT_ID = 2,
|
||||
|
||||
/**
|
||||
* Whether this is a virtual placement (unicode placeholder).
|
||||
*
|
||||
* Output type: bool *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IS_VIRTUAL = 3,
|
||||
|
||||
/**
|
||||
* Pixel offset from the left edge of the cell.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_X_OFFSET = 4,
|
||||
|
||||
/**
|
||||
* Pixel offset from the top edge of the cell.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Y_OFFSET = 5,
|
||||
|
||||
/**
|
||||
* Source rectangle x origin in pixels.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_X = 6,
|
||||
|
||||
/**
|
||||
* Source rectangle y origin in pixels.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_Y = 7,
|
||||
|
||||
/**
|
||||
* Source rectangle width in pixels (0 = full image width).
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_WIDTH = 8,
|
||||
|
||||
/**
|
||||
* Source rectangle height in pixels (0 = full image height).
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_HEIGHT = 9,
|
||||
|
||||
/**
|
||||
* Number of columns this placement occupies.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_COLUMNS = 10,
|
||||
|
||||
/**
|
||||
* Number of rows this placement occupies.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_ROWS = 11,
|
||||
|
||||
/**
|
||||
* Z-index for this placement.
|
||||
*
|
||||
* Output type: int32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z = 12,
|
||||
} GhosttyKittyGraphicsPlacementData;
|
||||
|
||||
/**
|
||||
* Get data from a kitty graphics storage instance.
|
||||
*
|
||||
* The output pointer must be of the appropriate type for the requested
|
||||
* data kind.
|
||||
*
|
||||
* Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
|
||||
*
|
||||
* @param graphics The kitty graphics handle
|
||||
* @param data The type of data to extract
|
||||
* @param[out] out Pointer to store the extracted data
|
||||
* @return GHOSTTY_SUCCESS on success
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_kitty_graphics_get(
|
||||
GhosttyKittyGraphics graphics,
|
||||
GhosttyKittyGraphicsData data,
|
||||
void* out);
|
||||
|
||||
/**
|
||||
* Create a new placement iterator instance.
|
||||
*
|
||||
* All fields except the allocator are left undefined until populated
|
||||
* via ghostty_kitty_graphics_get() with
|
||||
* GHOSTTY_KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR.
|
||||
*
|
||||
* @param allocator Pointer to allocator, or NULL to use the default allocator
|
||||
* @param[out] out_iterator On success, receives the created iterator handle
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
|
||||
* failure
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_iterator_new(
|
||||
const GhosttyAllocator* allocator,
|
||||
GhosttyKittyGraphicsPlacementIterator* out_iterator);
|
||||
|
||||
/**
|
||||
* Free a placement iterator.
|
||||
*
|
||||
* @param iterator The iterator handle to free (may be NULL)
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API void ghostty_kitty_graphics_placement_iterator_free(
|
||||
GhosttyKittyGraphicsPlacementIterator iterator);
|
||||
|
||||
/**
|
||||
* Advance the placement iterator to the next placement.
|
||||
*
|
||||
* @param iterator The iterator handle (may be NULL)
|
||||
* @return true if advanced to the next placement, false if at the end
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API bool ghostty_kitty_graphics_placement_next(
|
||||
GhosttyKittyGraphicsPlacementIterator iterator);
|
||||
|
||||
/**
|
||||
* Get data from the current placement in a placement iterator.
|
||||
*
|
||||
* Call ghostty_kitty_graphics_placement_next() at least once before
|
||||
* calling this function.
|
||||
*
|
||||
* @param iterator The iterator handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param data The data kind to query
|
||||
* @param[out] out Pointer to receive the queried value
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the
|
||||
* iterator is NULL or not positioned on a placement
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_get(
|
||||
GhosttyKittyGraphicsPlacementIterator iterator,
|
||||
GhosttyKittyGraphicsPlacementData data,
|
||||
void* out);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -233,6 +233,11 @@ comptime {
|
||||
@export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" });
|
||||
@export(&c.terminal_get, .{ .name = "ghostty_terminal_get" });
|
||||
@export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" });
|
||||
@export(&c.kitty_graphics_get, .{ .name = "ghostty_kitty_graphics_get" });
|
||||
@export(&c.kitty_graphics_placement_iterator_new, .{ .name = "ghostty_kitty_graphics_placement_iterator_new" });
|
||||
@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.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" });
|
||||
|
||||
@@ -5,7 +5,12 @@
|
||||
via `lib.TaggedUnion`.
|
||||
- Any functions must be updated all the way through from here to
|
||||
`src/terminal/c/main.zig` to `src/lib_vt.zig` and the headers
|
||||
in `include/ghostty/vt.h`.
|
||||
in `include/ghostty/vt.h`. Specifically:
|
||||
1. Define the function in `src/terminal/c/<module>.zig`.
|
||||
2. Re-export it via a `pub const` in `src/terminal/c/main.zig`.
|
||||
3. Add an `@export` call in `src/lib_vt.zig` with the
|
||||
`ghostty_` prefixed symbol name.
|
||||
4. Declare it in the corresponding header under `include/ghostty/vt/`.
|
||||
- In `include/ghostty/vt.h`, always sort the header contents by:
|
||||
(1) macros, (2) forward declarations, (3) types, (4) functions
|
||||
|
||||
|
||||
@@ -1,8 +1,364 @@
|
||||
const std = @import("std");
|
||||
const build_options = @import("terminal_options");
|
||||
const lib = @import("../lib.zig");
|
||||
const CAllocator = lib.alloc.Allocator;
|
||||
const kitty_gfx = @import("../kitty/graphics_storage.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
/// C: GhosttyKittyGraphics
|
||||
pub const KittyGraphics = if (build_options.kitty_graphics)
|
||||
*kitty_gfx.ImageStorage
|
||||
else
|
||||
*anyopaque;
|
||||
|
||||
/// C: GhosttyKittyGraphicsPlacementIterator
|
||||
pub const PlacementIterator = ?*PlacementIteratorWrapper;
|
||||
|
||||
const PlacementMap = std.AutoHashMapUnmanaged(
|
||||
kitty_gfx.ImageStorage.PlacementKey,
|
||||
kitty_gfx.ImageStorage.Placement,
|
||||
);
|
||||
|
||||
const PlacementIteratorWrapper = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
inner: PlacementMap.Iterator = undefined,
|
||||
entry: ?PlacementMap.Entry = null,
|
||||
};
|
||||
|
||||
/// C: GhosttyKittyGraphicsData
|
||||
pub const Data = enum(c_int) {
|
||||
invalid = 0,
|
||||
placement_iterator = 1,
|
||||
|
||||
pub fn OutType(comptime self: Data) type {
|
||||
return switch (self) {
|
||||
.invalid => void,
|
||||
.placement_iterator => PlacementIterator,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// C: GhosttyKittyGraphicsPlacementData
|
||||
pub const PlacementData = enum(c_int) {
|
||||
invalid = 0,
|
||||
image_id = 1,
|
||||
placement_id = 2,
|
||||
is_virtual = 3,
|
||||
x_offset = 4,
|
||||
y_offset = 5,
|
||||
source_x = 6,
|
||||
source_y = 7,
|
||||
source_width = 8,
|
||||
source_height = 9,
|
||||
columns = 10,
|
||||
rows = 11,
|
||||
z = 12,
|
||||
|
||||
pub fn OutType(comptime self: PlacementData) type {
|
||||
return switch (self) {
|
||||
.invalid => void,
|
||||
.image_id, .placement_id => u32,
|
||||
.is_virtual => bool,
|
||||
.x_offset,
|
||||
.y_offset,
|
||||
.source_x,
|
||||
.source_y,
|
||||
.source_width,
|
||||
.source_height,
|
||||
.columns,
|
||||
.rows,
|
||||
=> u32,
|
||||
.z => i32,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn get(
|
||||
graphics_: KittyGraphics,
|
||||
data: Data,
|
||||
out: ?*anyopaque,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| getTyped(
|
||||
graphics_,
|
||||
comptime_data,
|
||||
@ptrCast(@alignCast(out)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn getTyped(
|
||||
graphics_: KittyGraphics,
|
||||
comptime data: Data,
|
||||
out: *data.OutType(),
|
||||
) Result {
|
||||
const storage = graphics_;
|
||||
switch (data) {
|
||||
.invalid => return .invalid_value,
|
||||
.placement_iterator => {
|
||||
const it = out.* orelse return .invalid_value;
|
||||
it.* = .{
|
||||
.alloc = it.alloc,
|
||||
.inner = storage.placements.iterator(),
|
||||
};
|
||||
},
|
||||
}
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn placement_iterator_new(
|
||||
alloc_: ?*const CAllocator,
|
||||
out: *PlacementIterator,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const alloc = lib.alloc.default(alloc_);
|
||||
const ptr = alloc.create(PlacementIteratorWrapper) catch {
|
||||
out.* = null;
|
||||
return .out_of_memory;
|
||||
};
|
||||
ptr.* = .{ .alloc = alloc };
|
||||
out.* = ptr;
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn placement_iterator_free(iter_: PlacementIterator) callconv(lib.calling_conv) void {
|
||||
const iter = iter_ orelse return;
|
||||
iter.alloc.destroy(iter);
|
||||
}
|
||||
|
||||
pub fn placement_iterator_next(iter_: PlacementIterator) callconv(lib.calling_conv) bool {
|
||||
if (comptime !build_options.kitty_graphics) return false;
|
||||
|
||||
const iter = iter_ orelse return false;
|
||||
iter.entry = iter.inner.next() orelse return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn placement_get(
|
||||
iter_: PlacementIterator,
|
||||
data: PlacementData,
|
||||
out: ?*anyopaque,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| placementGetTyped(
|
||||
iter_,
|
||||
comptime_data,
|
||||
@ptrCast(@alignCast(out)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn placementGetTyped(
|
||||
iter_: PlacementIterator,
|
||||
comptime data: PlacementData,
|
||||
out: *data.OutType(),
|
||||
) Result {
|
||||
const iter = iter_ orelse return .invalid_value;
|
||||
const entry = iter.entry orelse return .invalid_value;
|
||||
|
||||
switch (data) {
|
||||
.invalid => return .invalid_value,
|
||||
.image_id => out.* = entry.key_ptr.image_id,
|
||||
.placement_id => out.* = entry.key_ptr.placement_id.id,
|
||||
.is_virtual => out.* = entry.value_ptr.location == .virtual,
|
||||
.x_offset => out.* = entry.value_ptr.x_offset,
|
||||
.y_offset => out.* = entry.value_ptr.y_offset,
|
||||
.source_x => out.* = entry.value_ptr.source_x,
|
||||
.source_y => out.* = entry.value_ptr.source_y,
|
||||
.source_width => out.* = entry.value_ptr.source_width,
|
||||
.source_height => out.* = entry.value_ptr.source_height,
|
||||
.columns => out.* = entry.value_ptr.columns,
|
||||
.rows => out.* = entry.value_ptr.rows,
|
||||
.z => out.* = entry.value_ptr.z,
|
||||
}
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
const testing = std.testing;
|
||||
const terminal_c = @import("terminal.zig");
|
||||
|
||||
test "placement_iterator new/free" {
|
||||
var iter: PlacementIterator = null;
|
||||
try testing.expectEqual(Result.success, placement_iterator_new(
|
||||
&lib.alloc.test_allocator,
|
||||
&iter,
|
||||
));
|
||||
try testing.expect(iter != null);
|
||||
placement_iterator_free(iter);
|
||||
}
|
||||
|
||||
test "placement_iterator free null" {
|
||||
placement_iterator_free(null);
|
||||
}
|
||||
|
||||
test "placement_iterator next on empty storage" {
|
||||
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);
|
||||
|
||||
var graphics: KittyGraphics = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.get(
|
||||
t,
|
||||
.kitty_graphics,
|
||||
@ptrCast(&graphics),
|
||||
));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
test "placement_iterator get before next returns invalid" {
|
||||
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);
|
||||
|
||||
var graphics: KittyGraphics = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.get(
|
||||
t,
|
||||
.kitty_graphics,
|
||||
@ptrCast(&graphics),
|
||||
));
|
||||
|
||||
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)));
|
||||
|
||||
var image_id: u32 = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, placement_get(iter, .image_id, @ptrCast(&image_id)));
|
||||
}
|
||||
|
||||
test "placement_iterator 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);
|
||||
|
||||
// Transmit and display a 1x2 RGB image (image_id=1, placement_id=1).
|
||||
// a=T (transmit+display), t=d (direct), f=24 (RGB), i=1, p=1
|
||||
// s=1,v=2 (1x2 pixels), c=10,r=1 (10 cols, 1 row)
|
||||
// //////// = 8 base64 chars = 6 bytes = 1*2*3 RGB bytes
|
||||
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),
|
||||
));
|
||||
|
||||
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)));
|
||||
|
||||
// Should have exactly one placement.
|
||||
try testing.expect(placement_iterator_next(iter));
|
||||
|
||||
var image_id: u32 = undefined;
|
||||
try testing.expectEqual(Result.success, placement_get(iter, .image_id, @ptrCast(&image_id)));
|
||||
try testing.expectEqual(1, image_id);
|
||||
|
||||
var placement_id: u32 = undefined;
|
||||
try testing.expectEqual(Result.success, placement_get(iter, .placement_id, @ptrCast(&placement_id)));
|
||||
try testing.expectEqual(1, placement_id);
|
||||
|
||||
var is_virtual: bool = undefined;
|
||||
try testing.expectEqual(Result.success, placement_get(iter, .is_virtual, @ptrCast(&is_virtual)));
|
||||
try testing.expect(!is_virtual);
|
||||
|
||||
// No more placements.
|
||||
try testing.expect(!placement_iterator_next(iter));
|
||||
}
|
||||
|
||||
test "placement_iterator with multiple placements" {
|
||||
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);
|
||||
|
||||
// Transmit image 1 then display it twice with different placement IDs.
|
||||
const transmit = "\x1b_Ga=t,t=d,f=24,i=1,s=1,v=2;////////\x1b\\";
|
||||
const display1 = "\x1b_Ga=p,i=1,p=1,c=10,r=1;\x1b\\";
|
||||
const display2 = "\x1b_Ga=p,i=1,p=2,c=5,r=1;\x1b\\";
|
||||
terminal_c.vt_write(t, transmit.ptr, transmit.len);
|
||||
terminal_c.vt_write(t, display1.ptr, display1.len);
|
||||
terminal_c.vt_write(t, display2.ptr, display2.len);
|
||||
|
||||
var graphics: KittyGraphics = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.get(
|
||||
t,
|
||||
.kitty_graphics,
|
||||
@ptrCast(&graphics),
|
||||
));
|
||||
|
||||
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)));
|
||||
|
||||
// Count placements and collect image IDs.
|
||||
var count: usize = 0;
|
||||
var seen_p1 = false;
|
||||
var seen_p2 = false;
|
||||
while (placement_iterator_next(iter)) {
|
||||
count += 1;
|
||||
|
||||
var image_id: u32 = undefined;
|
||||
try testing.expectEqual(Result.success, placement_get(iter, .image_id, @ptrCast(&image_id)));
|
||||
try testing.expectEqual(1, image_id);
|
||||
|
||||
var placement_id: u32 = undefined;
|
||||
try testing.expectEqual(Result.success, placement_get(iter, .placement_id, @ptrCast(&placement_id)));
|
||||
if (placement_id == 1) seen_p1 = true;
|
||||
if (placement_id == 2) seen_p2 = true;
|
||||
}
|
||||
|
||||
try testing.expectEqual(2, count);
|
||||
try testing.expect(seen_p1);
|
||||
try testing.expect(seen_p2);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ pub const focus = @import("focus.zig");
|
||||
pub const formatter = @import("formatter.zig");
|
||||
pub const grid_ref = @import("grid_ref.zig");
|
||||
pub const kitty_graphics = @import("kitty_graphics.zig");
|
||||
pub const kitty_graphics_get = kitty_graphics.get;
|
||||
pub const kitty_graphics_placement_iterator_new = kitty_graphics.placement_iterator_new;
|
||||
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 types = @import("types.zig");
|
||||
pub const modes = @import("modes.zig");
|
||||
pub const osc = @import("osc.zig");
|
||||
|
||||
Reference in New Issue
Block a user