mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-06-07 12:24:24 +00:00
libghostty: C APIs for Kitty Graphics inspection (#12145)
This adds a C API for inspecting Kitty graphics image storage, images,
and placements from a terminal instance.
I think this is enough of the API surface area for a renderer to draw
images. But I'll have to add it to Ghostling to be sure.
## Example
```c
#include <stdint.h>
#include <stdio.h>
#include <ghostty/vt.h>
/* After creating a terminal and transmitting a Kitty graphics image... */
/* Get the kitty graphics storage from the terminal. */
GhosttyKittyGraphics graphics = NULL;
ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS, &graphics);
/* Iterate over all placements. */
GhosttyKittyGraphicsPlacementIterator iter = NULL;
ghostty_kitty_graphics_placement_iterator_new(NULL, &iter);
ghostty_kitty_graphics_get(graphics,
GHOSTTY_KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR, &iter);
while (ghostty_kitty_graphics_placement_next(iter)) {
uint32_t image_id = 0;
ghostty_kitty_graphics_placement_get(iter,
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID, &image_id);
/* Look up the image and query its properties. */
GhosttyKittyGraphicsImage image = ghostty_kitty_graphics_image(graphics, image_id);
uint32_t width = 0, height = 0;
GhosttyKittyImageFormat format = 0;
ghostty_kitty_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_WIDTH, &width);
ghostty_kitty_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_HEIGHT, &height);
ghostty_kitty_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_FORMAT, &format);
printf("image %u: %ux%u format=%d\n", image_id, width, height, format);
/* Compute rendered pixel size and grid size. */
uint32_t px_w, px_h, cols, rows;
ghostty_kitty_graphics_placement_pixel_size(iter, image, terminal, &px_w, &px_h);
ghostty_kitty_graphics_placement_grid_size(iter, image, terminal, &cols, &rows);
printf(" rendered: %ux%u px, %ux%u cells\n", px_w, px_h, cols, rows);
}
ghostty_kitty_graphics_placement_iterator_free(iter);
```
## API
### Functions
| Function | Description |
|----------|-------------|
| `ghostty_kitty_graphics_get` | Query data from a kitty graphics
storage (e.g. placement iterator) |
| `ghostty_kitty_graphics_image` | Look up an image by its image ID |
| `ghostty_kitty_graphics_image_get` | Query image properties (ID,
dimensions, format, compression, pixel data) |
| `ghostty_kitty_graphics_placement_iterator_new` | Create a new
placement iterator |
| `ghostty_kitty_graphics_placement_iterator_free` | Free a placement
iterator |
| `ghostty_kitty_graphics_placement_next` | Advance the iterator to the
next placement |
| `ghostty_kitty_graphics_placement_get` | Query placement properties
(image ID, offsets, source rect, z-index, etc.) |
| `ghostty_kitty_graphics_placement_rect` | Compute the bounding grid
rectangle for a placement |
| `ghostty_kitty_graphics_placement_pixel_size` | Compute the rendered
pixel dimensions of a placement |
| `ghostty_kitty_graphics_placement_grid_size` | Compute the grid cell
dimensions of a placement |
### Types
| Type | Description |
|------|-------------|
| `GhosttyKittyGraphics` | Opaque handle to image storage (borrowed from
terminal) |
| `GhosttyKittyGraphicsImage` | Opaque handle to a single image |
| `GhosttyKittyGraphicsPlacementIterator` | Opaque handle to a placement
iterator |
| `GhosttyKittyGraphicsData` | Enum for `ghostty_kitty_graphics_get`
data kinds |
| `GhosttyKittyGraphicsImageData` | Enum for `ghostty_kitty_image_get`
data kinds |
| `GhosttyKittyGraphicsPlacementData` | Enum for
`ghostty_kitty_graphics_placement_get` data kinds |
| `GhosttyKittyImageFormat` | Image pixel format (RGB, RGBA, PNG, gray,
gray+alpha) |
| `GhosttyKittyImageCompression` | Image compression (none, zlib) |
This commit is contained in:
@@ -82,6 +82,9 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Set cell pixel dimensions so kitty graphics can compute grid sizes. */
|
||||
ghostty_terminal_resize(terminal, 80, 24, 8, 16);
|
||||
|
||||
/* Set a storage limit to enable Kitty graphics. */
|
||||
uint64_t storage_limit = 64 * 1024 * 1024; /* 64 MiB */
|
||||
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT,
|
||||
@@ -113,6 +116,83 @@ int main() {
|
||||
|
||||
printf("PNG decode calls: %d\n", decode_count);
|
||||
|
||||
/* Query the kitty graphics storage to verify the image was stored. */
|
||||
GhosttyKittyGraphics graphics = NULL;
|
||||
if (ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS,
|
||||
&graphics) != GHOSTTY_SUCCESS || !graphics) {
|
||||
fprintf(stderr, "Failed to get kitty graphics storage\n");
|
||||
return 1;
|
||||
}
|
||||
printf("\nKitty graphics storage is available.\n");
|
||||
|
||||
/* Iterate placements to find the image ID. */
|
||||
GhosttyKittyGraphicsPlacementIterator iter = NULL;
|
||||
if (ghostty_kitty_graphics_placement_iterator_new(NULL, &iter) != GHOSTTY_SUCCESS) {
|
||||
fprintf(stderr, "Failed to create placement iterator\n");
|
||||
return 1;
|
||||
}
|
||||
if (ghostty_kitty_graphics_get(graphics,
|
||||
GHOSTTY_KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR, &iter) != GHOSTTY_SUCCESS) {
|
||||
fprintf(stderr, "Failed to get placement iterator\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int placement_count = 0;
|
||||
while (ghostty_kitty_graphics_placement_next(iter)) {
|
||||
placement_count++;
|
||||
uint32_t image_id = 0;
|
||||
uint32_t placement_id = 0;
|
||||
bool is_virtual = false;
|
||||
int32_t z = 0;
|
||||
|
||||
ghostty_kitty_graphics_placement_get(iter,
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID, &image_id);
|
||||
ghostty_kitty_graphics_placement_get(iter,
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_PLACEMENT_ID, &placement_id);
|
||||
ghostty_kitty_graphics_placement_get(iter,
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IS_VIRTUAL, &is_virtual);
|
||||
ghostty_kitty_graphics_placement_get(iter,
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z, &z);
|
||||
|
||||
printf(" placement #%d: image_id=%u placement_id=%u virtual=%s z=%d\n",
|
||||
placement_count, image_id, placement_id,
|
||||
is_virtual ? "true" : "false", z);
|
||||
|
||||
/* Look up the image and print its properties. */
|
||||
GhosttyKittyGraphicsImage image =
|
||||
ghostty_kitty_graphics_image(graphics, image_id);
|
||||
if (!image) {
|
||||
fprintf(stderr, "Failed to look up image %u\n", image_id);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t width = 0, height = 0, number = 0;
|
||||
GhosttyKittyImageFormat format = 0;
|
||||
size_t data_len = 0;
|
||||
|
||||
ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_NUMBER, &number);
|
||||
ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_WIDTH, &width);
|
||||
ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_HEIGHT, &height);
|
||||
ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_FORMAT, &format);
|
||||
ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN, &data_len);
|
||||
|
||||
printf(" image: number=%u size=%ux%u format=%d data_len=%zu\n",
|
||||
number, width, height, format, data_len);
|
||||
|
||||
/* Compute the rendered pixel size and grid size. */
|
||||
uint32_t px_w = 0, px_h = 0, cols = 0, rows = 0;
|
||||
if (ghostty_kitty_graphics_placement_pixel_size(iter, image, terminal,
|
||||
&px_w, &px_h) == GHOSTTY_SUCCESS) {
|
||||
printf(" rendered pixel size: %ux%u\n", px_w, px_h);
|
||||
}
|
||||
if (ghostty_kitty_graphics_placement_grid_size(iter, image, terminal,
|
||||
&cols, &rows) == GHOSTTY_SUCCESS) {
|
||||
printf(" grid size: %u cols x %u rows\n", cols, rows);
|
||||
}
|
||||
}
|
||||
printf("Total placements: %d\n", placement_count);
|
||||
ghostty_kitty_graphics_placement_iterator_free(iter);
|
||||
|
||||
/* Clean up. */
|
||||
ghostty_terminal_free(terminal);
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ extern "C" {
|
||||
#include <ghostty/vt/style.h>
|
||||
#include <ghostty/vt/sys.h>
|
||||
#include <ghostty/vt/key.h>
|
||||
#include <ghostty/vt/kitty_graphics.h>
|
||||
#include <ghostty/vt/modes.h>
|
||||
#include <ghostty/vt/mouse.h>
|
||||
#include <ghostty/vt/paste.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.
|
||||
*
|
||||
|
||||
424
include/ghostty/vt/kitty_graphics.h
Normal file
424
include/ghostty/vt/kitty_graphics.h
Normal file
@@ -0,0 +1,424 @@
|
||||
/**
|
||||
* @file kitty_graphics.h
|
||||
*
|
||||
* Kitty graphics protocol image storage.
|
||||
*/
|
||||
|
||||
#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/selection.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup kitty_graphics Kitty Graphics
|
||||
*
|
||||
* Opaque handle to the Kitty graphics image storage associated with a
|
||||
* terminal screen, and an iterator for inspecting placements.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Pixel format of a Kitty graphics image.
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef enum {
|
||||
GHOSTTY_KITTY_IMAGE_FORMAT_RGB = 0,
|
||||
GHOSTTY_KITTY_IMAGE_FORMAT_RGBA = 1,
|
||||
GHOSTTY_KITTY_IMAGE_FORMAT_PNG = 2,
|
||||
GHOSTTY_KITTY_IMAGE_FORMAT_GRAY_ALPHA = 3,
|
||||
GHOSTTY_KITTY_IMAGE_FORMAT_GRAY = 4,
|
||||
} GhosttyKittyImageFormat;
|
||||
|
||||
/**
|
||||
* Compression of a Kitty graphics image.
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef enum {
|
||||
GHOSTTY_KITTY_IMAGE_COMPRESSION_NONE = 0,
|
||||
GHOSTTY_KITTY_IMAGE_COMPRESSION_ZLIB_DEFLATE = 1,
|
||||
} GhosttyKittyImageCompression;
|
||||
|
||||
/**
|
||||
* Queryable data kinds for ghostty_kitty_graphics_image_get().
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef enum {
|
||||
/** Invalid / sentinel value. */
|
||||
GHOSTTY_KITTY_IMAGE_DATA_INVALID = 0,
|
||||
|
||||
/**
|
||||
* The image ID.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_ID = 1,
|
||||
|
||||
/**
|
||||
* The image number.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_NUMBER = 2,
|
||||
|
||||
/**
|
||||
* Image width in pixels.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_WIDTH = 3,
|
||||
|
||||
/**
|
||||
* Image height in pixels.
|
||||
*
|
||||
* Output type: uint32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_HEIGHT = 4,
|
||||
|
||||
/**
|
||||
* Pixel format of the image.
|
||||
*
|
||||
* Output type: GhosttyKittyImageFormat *
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_FORMAT = 5,
|
||||
|
||||
/**
|
||||
* Compression of the image.
|
||||
*
|
||||
* Output type: GhosttyKittyImageCompression *
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_COMPRESSION = 6,
|
||||
|
||||
/**
|
||||
* Borrowed pointer to the raw pixel data. Valid as long as the
|
||||
* underlying terminal is not mutated.
|
||||
*
|
||||
* Output type: const uint8_t **
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_DATA_PTR = 7,
|
||||
|
||||
/**
|
||||
* Length of the raw pixel data in bytes.
|
||||
*
|
||||
* Output type: size_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN = 8,
|
||||
} GhosttyKittyGraphicsImageData;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Look up a Kitty graphics image by its image ID.
|
||||
*
|
||||
* Returns NULL if no image with the given ID exists or if Kitty graphics
|
||||
* are disabled at build time.
|
||||
*
|
||||
* @param graphics The kitty graphics handle
|
||||
* @param image_id The image ID to look up
|
||||
* @return An opaque image handle, or NULL if not found
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API GhosttyKittyGraphicsImage ghostty_kitty_graphics_image(
|
||||
GhosttyKittyGraphics graphics,
|
||||
uint32_t image_id);
|
||||
|
||||
/**
|
||||
* Get data from a Kitty graphics image.
|
||||
*
|
||||
* The output pointer must be of the appropriate type for the requested
|
||||
* data kind.
|
||||
*
|
||||
* @param image The image 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
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_kitty_graphics_image_get(
|
||||
GhosttyKittyGraphicsImage image,
|
||||
GhosttyKittyGraphicsImageData 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Compute the rendered pixel size of the current placement.
|
||||
*
|
||||
* Takes into account the placement's source rectangle, specified
|
||||
* columns/rows, and aspect ratio to calculate the final rendered
|
||||
* pixel dimensions.
|
||||
*
|
||||
* @param iterator The placement iterator positioned on a placement
|
||||
* @param image The image handle for this placement's image
|
||||
* @param terminal The terminal handle
|
||||
* @param[out] out_width On success, receives the width in pixels
|
||||
* @param[out] out_height On success, receives the height in pixels
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if any handle
|
||||
* is NULL or the iterator is not positioned, GHOSTTY_NO_VALUE when
|
||||
* Kitty graphics are disabled
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_pixel_size(
|
||||
GhosttyKittyGraphicsPlacementIterator iterator,
|
||||
GhosttyKittyGraphicsImage image,
|
||||
GhosttyTerminal terminal,
|
||||
uint32_t* out_width,
|
||||
uint32_t* out_height);
|
||||
|
||||
/**
|
||||
* Compute the grid cell size of the current placement.
|
||||
*
|
||||
* Returns the number of columns and rows that the placement occupies
|
||||
* in the terminal grid. If the placement specifies explicit columns
|
||||
* and rows, those are returned directly; otherwise they are calculated
|
||||
* from the pixel size and cell dimensions.
|
||||
*
|
||||
* @param iterator The placement iterator positioned on a placement
|
||||
* @param image The image handle for this placement's image
|
||||
* @param terminal The terminal handle
|
||||
* @param[out] out_cols On success, receives the number of columns
|
||||
* @param[out] out_rows On success, receives the number of rows
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if any handle
|
||||
* is NULL or the iterator is not positioned, GHOSTTY_NO_VALUE when
|
||||
* Kitty graphics are disabled
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_grid_size(
|
||||
GhosttyKittyGraphicsPlacementIterator iterator,
|
||||
GhosttyKittyGraphicsImage image,
|
||||
GhosttyTerminal terminal,
|
||||
uint32_t* out_cols,
|
||||
uint32_t* out_rows);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GHOSTTY_VT_KITTY_GRAPHICS_H */
|
||||
@@ -13,26 +13,6 @@
|
||||
#include <ghostty/vt/types.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <ghostty/vt/modes.h>
|
||||
#include <ghostty/vt/size_report.h>
|
||||
#include <ghostty/vt/grid_ref.h>
|
||||
#include <ghostty/vt/kitty_graphics.h>
|
||||
#include <ghostty/vt/screen.h>
|
||||
#include <ghostty/vt/point.h>
|
||||
#include <ghostty/vt/style.h>
|
||||
@@ -154,13 +155,6 @@ extern "C" {
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opaque handle to a terminal instance.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef struct GhosttyTerminalImpl* GhosttyTerminal;
|
||||
|
||||
/**
|
||||
* Terminal initialization options.
|
||||
*
|
||||
@@ -839,6 +833,19 @@ typedef enum {
|
||||
* Output type: bool *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_SHARED_MEM = 29,
|
||||
|
||||
/**
|
||||
* The Kitty graphics image storage for the active screen.
|
||||
*
|
||||
* Returns a borrowed pointer to the image storage. The pointer is valid
|
||||
* until the next mutating terminal call (e.g. ghostty_terminal_vt_write()
|
||||
* or ghostty_terminal_reset()).
|
||||
*
|
||||
* Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
|
||||
*
|
||||
* Output type: GhosttyKittyGraphics *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS = 30,
|
||||
} GhosttyTerminalData;
|
||||
|
||||
/**
|
||||
@@ -1057,6 +1064,39 @@ GHOSTTY_API GhosttyResult ghostty_terminal_grid_ref(GhosttyTerminal terminal,
|
||||
GhosttyPoint point,
|
||||
GhosttyGridRef *out_ref);
|
||||
|
||||
/**
|
||||
* Convert a grid reference back to a point in the given coordinate system.
|
||||
*
|
||||
* This is the inverse of ghostty_terminal_grid_ref(): given a grid reference,
|
||||
* it returns the x/y coordinates in the requested coordinate system (active,
|
||||
* viewport, screen, or history).
|
||||
*
|
||||
* The grid reference must have been obtained from the same terminal instance.
|
||||
* Like all grid references, it is only valid until the next mutating terminal
|
||||
* call.
|
||||
*
|
||||
* Not every grid reference is representable in every coordinate system. For
|
||||
* example, a cell in scrollback history cannot be expressed in active
|
||||
* coordinates, and a cell that has scrolled off the visible area cannot be
|
||||
* expressed in viewport coordinates. In these cases, the function returns
|
||||
* GHOSTTY_NO_VALUE.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param ref Pointer to the grid reference to convert
|
||||
* @param tag The target coordinate system
|
||||
* @param[out] out On success, set to the coordinate in the requested system (may be NULL)
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
|
||||
* or ref is NULL/invalid, GHOSTTY_NO_VALUE if the ref falls outside
|
||||
* the requested coordinate system
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_terminal_point_from_grid_ref(
|
||||
GhosttyTerminal terminal,
|
||||
const GhosttyGridRef *ref,
|
||||
GhosttyPointTag tag,
|
||||
GhosttyPointCoordinate *out);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -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).
|
||||
*
|
||||
|
||||
@@ -233,6 +233,17 @@ 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.terminal_point_from_grid_ref, .{ .name = "ghostty_terminal_point_from_grid_ref" });
|
||||
@export(&c.kitty_graphics_get, .{ .name = "ghostty_kitty_graphics_get" });
|
||||
@export(&c.kitty_graphics_image, .{ .name = "ghostty_kitty_graphics_image" });
|
||||
@export(&c.kitty_graphics_image_get, .{ .name = "ghostty_kitty_graphics_image_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.kitty_graphics_placement_rect, .{ .name = "ghostty_kitty_graphics_placement_rect" });
|
||||
@export(&c.kitty_graphics_placement_pixel_size, .{ .name = "ghostty_kitty_graphics_placement_pixel_size" });
|
||||
@export(&c.kitty_graphics_placement_grid_size, .{ .name = "ghostty_kitty_graphics_placement_grid_size" });
|
||||
@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" });
|
||||
|
||||
@@ -426,7 +426,7 @@ pub const State = struct {
|
||||
|
||||
// Calculate the dimensions of our image, taking in to
|
||||
// account the rows / columns specified by the placement.
|
||||
const dest_size = p.calculatedSize(image.*, t);
|
||||
const dest_size = p.pixelSize(image.*, t);
|
||||
|
||||
// Calculate the source rectangle
|
||||
const source_x = @min(image.width, p.source_x);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
786
src/terminal/c/kitty_graphics.zig
Normal file
786
src/terminal/c/kitty_graphics.zig
Normal file
@@ -0,0 +1,786 @@
|
||||
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
|
||||
pub const KittyGraphics = if (build_options.kitty_graphics)
|
||||
*kitty_storage.ImageStorage
|
||||
else
|
||||
*anyopaque;
|
||||
|
||||
/// C: GhosttyKittyGraphicsImage
|
||||
pub const ImageHandle = if (build_options.kitty_graphics)
|
||||
?*const Image
|
||||
else
|
||||
?*const anyopaque;
|
||||
|
||||
/// C: GhosttyKittyGraphicsPlacementIterator
|
||||
pub const PlacementIterator = if (build_options.kitty_graphics)
|
||||
?*PlacementIteratorWrapper
|
||||
else
|
||||
?*anyopaque;
|
||||
|
||||
const PlacementMap = if (build_options.kitty_graphics)
|
||||
std.AutoHashMapUnmanaged(
|
||||
kitty_storage.ImageStorage.PlacementKey,
|
||||
kitty_storage.ImageStorage.Placement,
|
||||
)
|
||||
else
|
||||
void;
|
||||
|
||||
const PlacementIteratorWrapper = if (build_options.kitty_graphics)
|
||||
struct {
|
||||
alloc: std.mem.Allocator,
|
||||
inner: PlacementMap.Iterator = undefined,
|
||||
entry: ?PlacementMap.Entry = null,
|
||||
}
|
||||
else
|
||||
void;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// C: GhosttyKittyImageFormat
|
||||
pub const ImageFormat = kitty_cmd.Transmission.Format;
|
||||
|
||||
/// C: GhosttyKittyImageCompression
|
||||
pub const ImageCompression = kitty_cmd.Transmission.Compression;
|
||||
|
||||
/// C: GhosttyKittyGraphicsImageData
|
||||
pub const ImageData = enum(c_int) {
|
||||
invalid = 0,
|
||||
id = 1,
|
||||
number = 2,
|
||||
width = 3,
|
||||
height = 4,
|
||||
format = 5,
|
||||
compression = 6,
|
||||
data_ptr = 7,
|
||||
data_len = 8,
|
||||
|
||||
pub fn OutType(comptime self: ImageData) type {
|
||||
return switch (self) {
|
||||
.invalid => void,
|
||||
.id, .number, .width, .height => u32,
|
||||
.format => ImageFormat,
|
||||
.compression => ImageCompression,
|
||||
.data_ptr => [*]const u8,
|
||||
.data_len => usize,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn image_get_handle(
|
||||
graphics_: KittyGraphics,
|
||||
image_id: u32,
|
||||
) callconv(lib.calling_conv) ImageHandle {
|
||||
if (comptime !build_options.kitty_graphics) return null;
|
||||
|
||||
const storage = graphics_;
|
||||
return storage.images.getPtr(image_id);
|
||||
}
|
||||
|
||||
pub fn image_get(
|
||||
image_: ImageHandle,
|
||||
data: ImageData,
|
||||
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| imageGetTyped(
|
||||
image_,
|
||||
comptime_data,
|
||||
@ptrCast(@alignCast(out)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn imageGetTyped(
|
||||
image_: ImageHandle,
|
||||
comptime data: ImageData,
|
||||
out: *data.OutType(),
|
||||
) Result {
|
||||
const image = image_ orelse return .invalid_value;
|
||||
|
||||
switch (data) {
|
||||
.invalid => return .invalid_value,
|
||||
.id => out.* = image.id,
|
||||
.number => out.* = image.number,
|
||||
.width => out.* = image.width,
|
||||
.height => out.* = image.height,
|
||||
.format => out.* = image.format,
|
||||
.compression => out.* = image.compression,
|
||||
.data_ptr => out.* = image.data.ptr,
|
||||
.data_len => out.* = image.data.len,
|
||||
}
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn placement_iterator_new(
|
||||
alloc_: ?*const CAllocator,
|
||||
out: *PlacementIterator,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
if (comptime !build_options.kitty_graphics) {
|
||||
out.* = null;
|
||||
return .no_value;
|
||||
}
|
||||
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 {
|
||||
if (comptime !build_options.kitty_graphics) return;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn placement_pixel_size(
|
||||
iter_: PlacementIterator,
|
||||
image_: ImageHandle,
|
||||
terminal_: terminal_c.Terminal,
|
||||
out_width: *u32,
|
||||
out_height: *u32,
|
||||
) 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 s = entry.value_ptr.pixelSize(image.*, wrapper.terminal);
|
||||
|
||||
out_width.* = s.width;
|
||||
out_height.* = s.height;
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn placement_grid_size(
|
||||
iter_: PlacementIterator,
|
||||
image_: ImageHandle,
|
||||
terminal_: terminal_c.Terminal,
|
||||
out_cols: *u32,
|
||||
out_rows: *u32,
|
||||
) 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 s = entry.value_ptr.gridSize(image.*, wrapper.terminal);
|
||||
|
||||
out_cols.* = s.cols;
|
||||
out_rows.* = s.rows;
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
test "image_get_handle returns null for missing id" {
|
||||
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),
|
||||
));
|
||||
|
||||
try testing.expectEqual(@as(ImageHandle, null), image_get_handle(graphics, 999));
|
||||
}
|
||||
|
||||
test "image_get_handle and image_get with transmitted image" {
|
||||
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 a 1x2 RGB image with image_id=1.
|
||||
const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2;////////\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 id: u32 = undefined;
|
||||
try testing.expectEqual(Result.success, image_get(img, .id, @ptrCast(&id)));
|
||||
try testing.expectEqual(1, id);
|
||||
|
||||
var w: u32 = undefined;
|
||||
try testing.expectEqual(Result.success, image_get(img, .width, @ptrCast(&w)));
|
||||
try testing.expectEqual(1, w);
|
||||
|
||||
var h: u32 = undefined;
|
||||
try testing.expectEqual(Result.success, image_get(img, .height, @ptrCast(&h)));
|
||||
try testing.expectEqual(2, h);
|
||||
|
||||
var fmt: ImageFormat = undefined;
|
||||
try testing.expectEqual(Result.success, image_get(img, .format, @ptrCast(&fmt)));
|
||||
try testing.expectEqual(.rgb, fmt);
|
||||
|
||||
var comp: ImageCompression = undefined;
|
||||
try testing.expectEqual(Result.success, image_get(img, .compression, @ptrCast(&comp)));
|
||||
try testing.expectEqual(.none, comp);
|
||||
|
||||
var data_len: usize = undefined;
|
||||
try testing.expectEqual(Result.success, image_get(img, .data_len, @ptrCast(&data_len)));
|
||||
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 "placement_pixel_size 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);
|
||||
|
||||
// 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 with c=10,r=1.
|
||||
// 10 cols * 10px = 100px width, 1 row * 20px = 20px height.
|
||||
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 w: u32 = undefined;
|
||||
var h: u32 = undefined;
|
||||
try testing.expectEqual(Result.success, placement_pixel_size(iter, img, t, &w, &h));
|
||||
|
||||
try testing.expectEqual(100, w);
|
||||
try testing.expectEqual(20, h);
|
||||
}
|
||||
|
||||
test "placement_pixel_size null args return invalid_value" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var w: u32 = undefined;
|
||||
var h: u32 = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, placement_pixel_size(null, null, null, &w, &h));
|
||||
}
|
||||
|
||||
test "placement_grid_size 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);
|
||||
|
||||
// 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 with c=10,r=1.
|
||||
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 cols: u32 = undefined;
|
||||
var rows: u32 = undefined;
|
||||
try testing.expectEqual(Result.success, placement_grid_size(iter, img, t, &cols, &rows));
|
||||
|
||||
try testing.expectEqual(10, cols);
|
||||
try testing.expectEqual(1, rows);
|
||||
}
|
||||
|
||||
test "placement_grid_size null args return invalid_value" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var cols: u32 = undefined;
|
||||
var rows: u32 = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, placement_grid_size(null, null, null, &cols, &rows));
|
||||
}
|
||||
|
||||
test "image_get on null returns invalid_value" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var id: u32 = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, image_get(null, .id, @ptrCast(&id)));
|
||||
}
|
||||
@@ -8,6 +8,17 @@ pub const color = @import("color.zig");
|
||||
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_image = kitty_graphics.image_get_handle;
|
||||
pub const kitty_graphics_image_get = kitty_graphics.image_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 kitty_graphics_placement_rect = kitty_graphics.placement_rect;
|
||||
pub const kitty_graphics_placement_pixel_size = kitty_graphics.placement_pixel_size;
|
||||
pub const kitty_graphics_placement_grid_size = kitty_graphics.placement_grid_size;
|
||||
pub const types = @import("types.zig");
|
||||
pub const modes = @import("modes.zig");
|
||||
pub const osc = @import("osc.zig");
|
||||
@@ -146,6 +157,7 @@ pub const terminal_mode_get = terminal.mode_get;
|
||||
pub const terminal_mode_set = terminal.mode_set;
|
||||
pub const terminal_get = terminal.get;
|
||||
pub const terminal_grid_ref = terminal.grid_ref;
|
||||
pub const terminal_point_from_grid_ref = terminal.point_from_grid_ref;
|
||||
|
||||
pub const type_json = types.get_json;
|
||||
|
||||
@@ -161,6 +173,7 @@ test {
|
||||
_ = cell;
|
||||
_ = color;
|
||||
_ = grid_ref;
|
||||
_ = kitty_graphics;
|
||||
_ = row;
|
||||
_ = focus;
|
||||
_ = formatter;
|
||||
|
||||
@@ -8,6 +8,7 @@ const Stream = @import("../stream_terminal.zig").Stream;
|
||||
const ScreenSet = @import("../ScreenSet.zig");
|
||||
const PageList = @import("../PageList.zig");
|
||||
const kitty = @import("../kitty/key.zig");
|
||||
const kitty_gfx_c = @import("kitty_graphics.zig");
|
||||
const modes = @import("../modes.zig");
|
||||
const point = @import("../point.zig");
|
||||
const size = @import("../size.zig");
|
||||
@@ -515,6 +516,9 @@ pub fn mode_set(
|
||||
return .success;
|
||||
}
|
||||
|
||||
/// C: GhosttyKittyGraphics
|
||||
pub const KittyGraphics = kitty_gfx_c.KittyGraphics;
|
||||
|
||||
/// C: GhosttyTerminalScreen
|
||||
pub const TerminalScreen = ScreenSet.Key;
|
||||
|
||||
@@ -553,6 +557,7 @@ pub const TerminalData = enum(c_int) {
|
||||
kitty_image_medium_file = 27,
|
||||
kitty_image_medium_temp_file = 28,
|
||||
kitty_image_medium_shared_mem = 29,
|
||||
kitty_graphics = 30,
|
||||
|
||||
/// Output type expected for querying the data of the given kind.
|
||||
pub fn OutType(comptime self: TerminalData) type {
|
||||
@@ -580,6 +585,7 @@ pub const TerminalData = enum(c_int) {
|
||||
.kitty_image_medium_temp_file,
|
||||
.kitty_image_medium_shared_mem,
|
||||
=> bool,
|
||||
.kitty_graphics => KittyGraphics,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -664,6 +670,10 @@ fn getTyped(
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
out.* = t.screens.active.kitty_images.image_limits.shared_memory;
|
||||
},
|
||||
.kitty_graphics => {
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
out.* = &t.screens.active.kitty_images;
|
||||
},
|
||||
}
|
||||
|
||||
return .success;
|
||||
@@ -687,6 +697,20 @@ pub fn grid_ref(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn point_from_grid_ref(
|
||||
terminal_: Terminal,
|
||||
ref: *const grid_ref_c.CGridRef,
|
||||
tag: point.Tag,
|
||||
out: ?*point.Coordinate,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal;
|
||||
const p = ref.toPin() orelse return .invalid_value;
|
||||
const pt = t.screens.active.pages.pointFromPin(tag, p) orelse
|
||||
return .no_value;
|
||||
if (out) |o| o.* = pt.coord();
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn free(terminal_: Terminal) callconv(lib.calling_conv) void {
|
||||
const wrapper = terminal_ orelse return;
|
||||
const t = wrapper.terminal;
|
||||
@@ -1251,6 +1275,102 @@ test "grid_ref null terminal" {
|
||||
}, &out_ref));
|
||||
}
|
||||
|
||||
test "point_from_grid_ref roundtrip active" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
vt_write(t, "Hello", 5);
|
||||
|
||||
// Get a grid ref at (2, 0) in active coords
|
||||
var ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, grid_ref(t, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 2, .y = 0 } },
|
||||
}, &ref));
|
||||
|
||||
// Convert back to active coords
|
||||
var coord: point.Coordinate = undefined;
|
||||
try testing.expectEqual(Result.success, point_from_grid_ref(t, &ref, .active, &coord));
|
||||
try testing.expectEqual(@as(size.CellCountInt, 2), coord.x);
|
||||
try testing.expectEqual(@as(u32, 0), coord.y);
|
||||
}
|
||||
|
||||
test "point_from_grid_ref roundtrip viewport" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
vt_write(t, "Hello", 5);
|
||||
|
||||
var ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, grid_ref(t, .{
|
||||
.tag = .viewport,
|
||||
.value = .{ .viewport = .{ .x = 0, .y = 0 } },
|
||||
}, &ref));
|
||||
|
||||
var coord: point.Coordinate = undefined;
|
||||
try testing.expectEqual(Result.success, point_from_grid_ref(t, &ref, .viewport, &coord));
|
||||
try testing.expectEqual(@as(size.CellCountInt, 0), coord.x);
|
||||
try testing.expectEqual(@as(u32, 0), coord.y);
|
||||
}
|
||||
|
||||
test "point_from_grid_ref history ref to active returns no_value" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 4, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
// Write enough lines to push content into scrollback
|
||||
for (0..10) |_| {
|
||||
vt_write(t, "line\n", 5);
|
||||
}
|
||||
|
||||
// Get a ref to the first line (now in scrollback)
|
||||
var ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, grid_ref(t, .{
|
||||
.tag = .screen,
|
||||
.value = .{ .screen = .{ .x = 0, .y = 0 } },
|
||||
}, &ref));
|
||||
|
||||
// Should succeed for screen coords
|
||||
var coord: point.Coordinate = undefined;
|
||||
try testing.expectEqual(Result.success, point_from_grid_ref(t, &ref, .screen, &coord));
|
||||
try testing.expectEqual(@as(u32, 0), coord.y);
|
||||
|
||||
// Should fail for active coords (it's in scrollback)
|
||||
try testing.expectEqual(Result.no_value, point_from_grid_ref(t, &ref, .active, &coord));
|
||||
}
|
||||
|
||||
test "point_from_grid_ref null terminal" {
|
||||
var ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.invalid_value, point_from_grid_ref(null, &ref, .active, null));
|
||||
}
|
||||
|
||||
test "point_from_grid_ref null node" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
const ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.invalid_value, point_from_grid_ref(t, &ref, .active, null));
|
||||
}
|
||||
|
||||
test "set write_pty callback" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
@@ -3,6 +3,7 @@ const assert = @import("../../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const simd = @import("../../simd/main.zig");
|
||||
const lib = @import("../lib.zig");
|
||||
|
||||
const log = std.log.scoped(.kitty_gfx);
|
||||
|
||||
@@ -394,39 +395,38 @@ pub const Transmission = struct {
|
||||
compression: Compression = .none, // o
|
||||
more_chunks: bool = false, // m
|
||||
|
||||
pub const Format = enum {
|
||||
rgb, // 24
|
||||
rgba, // 32
|
||||
png, // 100
|
||||
|
||||
pub const Format = lib.Enum(lib.target, &.{
|
||||
"rgb", // 24
|
||||
"rgba", // 32
|
||||
"png", // 100
|
||||
// The following are not supported directly via the protocol
|
||||
// but they are formats that a png may decode to that we
|
||||
// support.
|
||||
gray_alpha,
|
||||
gray,
|
||||
"gray_alpha",
|
||||
"gray",
|
||||
});
|
||||
|
||||
pub fn bpp(self: Format) u8 {
|
||||
return switch (self) {
|
||||
.gray => 1,
|
||||
.gray_alpha => 2,
|
||||
.rgb => 3,
|
||||
.rgba => 4,
|
||||
.png => unreachable, // Must be validated before
|
||||
};
|
||||
}
|
||||
};
|
||||
pub const Medium = lib.Enum(lib.target, &.{
|
||||
"direct", // d
|
||||
"file", // f
|
||||
"temporary_file", // t
|
||||
"shared_memory", // s
|
||||
});
|
||||
|
||||
pub const Medium = enum {
|
||||
direct, // d
|
||||
file, // f
|
||||
temporary_file, // t
|
||||
shared_memory, // s
|
||||
};
|
||||
pub const Compression = lib.Enum(lib.target, &.{
|
||||
"none",
|
||||
"zlib_deflate", // z
|
||||
});
|
||||
|
||||
pub const Compression = enum {
|
||||
none,
|
||||
zlib_deflate, // z
|
||||
};
|
||||
pub fn formatBpp(format: Format) u8 {
|
||||
return switch (format) {
|
||||
.gray => 1,
|
||||
.gray_alpha => 2,
|
||||
.rgb => 3,
|
||||
.rgba => 4,
|
||||
.png => unreachable, // Must be validated before
|
||||
};
|
||||
}
|
||||
|
||||
fn parse(kv: KV) !Transmission {
|
||||
var result: Transmission = .{};
|
||||
|
||||
@@ -202,8 +202,8 @@ pub const LoadingImage = struct {
|
||||
.png => stat_size,
|
||||
|
||||
// For these formats we have a size we must have.
|
||||
.gray, .gray_alpha, .rgb, .rgba => |f| size: {
|
||||
const bpp = f.bpp();
|
||||
.gray, .gray_alpha, .rgb, .rgba => size: {
|
||||
const bpp = command.Transmission.formatBpp(self.image.format);
|
||||
break :size self.image.width * self.image.height * bpp;
|
||||
},
|
||||
};
|
||||
@@ -390,7 +390,7 @@ pub const LoadingImage = struct {
|
||||
if (img.width > max_dimension or img.height > max_dimension) return error.DimensionsTooLarge;
|
||||
|
||||
// Data length must be what we expect
|
||||
const bpp = img.format.bpp();
|
||||
const bpp = command.Transmission.formatBpp(img.format);
|
||||
const expected_len = img.width * img.height * bpp;
|
||||
const actual_len = self.data.items.len;
|
||||
if (actual_len != expected_len) {
|
||||
|
||||
@@ -662,9 +662,10 @@ pub const ImageStorage = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the size of this placement's image in pixels,
|
||||
/// taking in to account the specified rows and columns.
|
||||
pub fn calculatedSize(
|
||||
/// Returns the size of this placement's image in pixels,
|
||||
/// taking into account the source rectangle, specified
|
||||
/// rows/columns, and aspect ratio.
|
||||
pub fn pixelSize(
|
||||
self: Placement,
|
||||
image: Image,
|
||||
t: *const terminal.Terminal,
|
||||
@@ -759,7 +760,7 @@ pub const ImageStorage = struct {
|
||||
|
||||
// Otherwise we calculate the pixel size, divide by
|
||||
// cell size, and round up to the nearest integer.
|
||||
const calc_size = self.calculatedSize(image, t);
|
||||
const calc_size = self.pixelSize(image, t);
|
||||
return .{
|
||||
.cols = std.math.divCeil(
|
||||
u32,
|
||||
@@ -1338,7 +1339,7 @@ test "storage: aspect ratio calculation when only columns or rows specified" {
|
||||
// that's 100px width. 100px * (9 / 16) = 56.25, which should round
|
||||
// to a height of 56px.
|
||||
|
||||
const calc_size = placement.calculatedSize(image, &t);
|
||||
const calc_size = placement.pixelSize(image, &t);
|
||||
try testing.expectEqual(@as(u32, 100), calc_size.width);
|
||||
try testing.expectEqual(@as(u32, 56), calc_size.height);
|
||||
}
|
||||
@@ -1356,7 +1357,7 @@ test "storage: aspect ratio calculation when only columns or rows specified" {
|
||||
// 100px height. 100px * (16 / 9) = 177.77..., which should round to
|
||||
// a width of 178px.
|
||||
|
||||
const calc_size = placement.calculatedSize(image, &t);
|
||||
const calc_size = placement.pixelSize(image, &t);
|
||||
try testing.expectEqual(@as(u32, 178), calc_size.width);
|
||||
try testing.expectEqual(@as(u32, 100), calc_size.height);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user