kitty graphics support

This commit is contained in:
Gregor Lohaus
2026-05-27 14:33:41 +02:00
parent a03bc2ec48
commit 5285fb68c9
14 changed files with 651 additions and 0 deletions

View File

@@ -145,6 +145,31 @@ try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) {
} }
``` ```
## Kitty Graphics
Kitty graphics storage can be enabled and inspected:
```java
try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) {
terminal.setKittyImageStorageLimit(64 * 1024 * 1024);
terminal.setKittyImageMediumFile(true);
terminal.setKittyImageMediumTemporaryFile(true);
terminal.setKittyImageMediumSharedMemory(true);
terminal.write(kittyGraphicsSequenceBytes);
for (KittyPlacement placement : terminal.kittyGraphics().orElseThrow().placements()) {
placement.image().ifPresent(image -> {
// Hand image.data() and placement.renderInfo() to your renderer.
});
}
}
```
The Kitty handles returned by `libghostty-vt` are borrowed from the terminal and are invalidated by mutating terminal calls. The Java API returns snapshots for images and placements to make renderer handoff simpler.
PNG decode callbacks from `ghostty_sys_set(GHOSTTY_SYS_OPT_DECODE_PNG, ...)` are not exposed yet. Raw Kitty image formats can be inspected; PNG image ingestion will need a Java callback bridge or a small native helper that allocates decoded RGBA data through Ghostty's allocator.
## Development Shell ## Development Shell
```sh ```sh

View File

@@ -0,0 +1,63 @@
package dev.jlibghostty;
import dev.jlibghostty.internal.GhosttyLibrary;
import java.lang.foreign.MemorySegment;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public final class KittyGraphics {
private final GhosttyLibrary library;
private final MemorySegment terminal;
private final MemorySegment graphics;
KittyGraphics(GhosttyLibrary library, MemorySegment terminal, MemorySegment graphics) {
this.library = library;
this.terminal = terminal;
this.graphics = graphics;
}
public Optional<KittyImage> image(long imageId) {
MemorySegment image = library.kittyGraphicsImage(graphics, imageId);
if (image.address() == 0) {
return Optional.empty();
}
return Optional.of(new KittyImage(library, image));
}
public List<KittyPlacement> placements() {
return placements(KittyPlacementLayer.ALL);
}
public List<KittyPlacement> placements(KittyPlacementLayer layer) {
try (KittyPlacementIterator iterator = KittyPlacementIterator.open(library, graphics, layer)) {
List<KittyPlacement> placements = new ArrayList<>();
while (iterator.next()) {
long imageId = iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID);
Optional<KittyImage> image = image(imageId);
KittyRenderInfo renderInfo = image
.flatMap(value -> library.kittyPlacementRenderInfo(iterator.handle(), value.handle(), terminal))
.orElse(null);
placements.add(new KittyPlacement(
imageId,
iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_PLACEMENT_ID),
iterator.getBoolean(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_IS_VIRTUAL),
iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_X_OFFSET),
iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_Y_OFFSET),
iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_X),
iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_Y),
iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_WIDTH),
iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_HEIGHT),
iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_COLUMNS),
iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_ROWS),
iterator.getI32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_Z),
image.map(KittyImage::snapshot),
Optional.ofNullable(renderInfo)
));
}
return List.copyOf(placements);
}
}
}

View File

@@ -0,0 +1,31 @@
package dev.jlibghostty;
import dev.jlibghostty.internal.GhosttyLibrary;
import java.lang.foreign.MemorySegment;
public final class KittyImage {
private final GhosttyLibrary library;
private final MemorySegment handle;
KittyImage(GhosttyLibrary library, MemorySegment handle) {
this.library = library;
this.handle = handle;
}
public KittyImageSnapshot snapshot() {
return new KittyImageSnapshot(
library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_ID),
library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_NUMBER),
library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_WIDTH),
library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_HEIGHT),
KittyImageFormat.fromNative(library.kittyImageGetI32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_FORMAT)),
KittyImageCompression.fromNative(library.kittyImageGetI32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_COMPRESSION)),
library.kittyImageData(handle)
);
}
MemorySegment handle() {
return handle;
}
}

View File

@@ -0,0 +1,25 @@
package dev.jlibghostty;
public enum KittyImageCompression {
NONE(0),
ZLIB_DEFLATE(1);
private final int nativeValue;
KittyImageCompression(int nativeValue) {
this.nativeValue = nativeValue;
}
public int nativeValue() {
return nativeValue;
}
static KittyImageCompression fromNative(int value) {
for (KittyImageCompression compression : values()) {
if (compression.nativeValue == value) {
return compression;
}
}
throw new IllegalArgumentException("Unknown Kitty image compression: " + value);
}
}

View File

@@ -0,0 +1,28 @@
package dev.jlibghostty;
public enum KittyImageFormat {
RGB(0),
RGBA(1),
PNG(2),
GRAY_ALPHA(3),
GRAY(4);
private final int nativeValue;
KittyImageFormat(int nativeValue) {
this.nativeValue = nativeValue;
}
public int nativeValue() {
return nativeValue;
}
static KittyImageFormat fromNative(int value) {
for (KittyImageFormat format : values()) {
if (format.nativeValue == value) {
return format;
}
}
throw new IllegalArgumentException("Unknown Kitty image format: " + value);
}
}

View File

@@ -0,0 +1,20 @@
package dev.jlibghostty;
public record KittyImageSnapshot(
long id,
long number,
long width,
long height,
KittyImageFormat format,
KittyImageCompression compression,
byte[] data
) {
public KittyImageSnapshot {
data = data.clone();
}
@Override
public byte[] data() {
return data.clone();
}
}

View File

@@ -0,0 +1,21 @@
package dev.jlibghostty;
import java.util.Optional;
public record KittyPlacement(
long imageId,
long placementId,
boolean virtual,
long xOffset,
long yOffset,
long sourceX,
long sourceY,
long sourceWidth,
long sourceHeight,
long columns,
long rows,
int z,
Optional<KittyImageSnapshot> image,
Optional<KittyRenderInfo> renderInfo
) {
}

View File

@@ -0,0 +1,56 @@
package dev.jlibghostty;
import dev.jlibghostty.internal.GhosttyLibrary;
import java.lang.foreign.MemorySegment;
final class KittyPlacementIterator implements AutoCloseable {
private final GhosttyLibrary library;
private final MemorySegment handle;
private boolean closed;
private KittyPlacementIterator(GhosttyLibrary library, MemorySegment handle) {
this.library = library;
this.handle = handle;
}
static KittyPlacementIterator open(GhosttyLibrary library, MemorySegment graphics, KittyPlacementLayer layer) {
MemorySegment handle = library.kittyPlacementIteratorNew();
try {
library.kittyGraphicsPopulatePlacementIterator(graphics, handle);
library.kittyPlacementIteratorSetLayer(handle, layer.nativeValue());
return new KittyPlacementIterator(library, handle);
} catch (RuntimeException | Error e) {
library.kittyPlacementIteratorFree(handle);
throw e;
}
}
boolean next() {
return library.kittyPlacementNext(handle);
}
long getU32(int key) {
return library.kittyPlacementGetU32(handle, key);
}
int getI32(int key) {
return library.kittyPlacementGetI32(handle, key);
}
boolean getBoolean(int key) {
return library.kittyPlacementGetBoolean(handle, key);
}
MemorySegment handle() {
return handle;
}
@Override
public void close() {
if (!closed) {
closed = true;
library.kittyPlacementIteratorFree(handle);
}
}
}

View File

@@ -0,0 +1,18 @@
package dev.jlibghostty;
public enum KittyPlacementLayer {
ALL(0),
BELOW_BACKGROUND(1),
BELOW_TEXT(2),
ABOVE_TEXT(3);
private final int nativeValue;
KittyPlacementLayer(int nativeValue) {
this.nativeValue = nativeValue;
}
int nativeValue() {
return nativeValue;
}
}

View File

@@ -0,0 +1,16 @@
package dev.jlibghostty;
public record KittyRenderInfo(
long pixelWidth,
long pixelHeight,
long gridColumns,
long gridRows,
int viewportColumn,
int viewportRow,
boolean viewportVisible,
long sourceX,
long sourceY,
long sourceWidth,
long sourceHeight
) {
}

View File

@@ -4,6 +4,7 @@ import dev.jlibghostty.internal.GhosttyLibrary;
import java.lang.foreign.MemorySegment; import java.lang.foreign.MemorySegment;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public final class Terminal implements AutoCloseable { public final class Terminal implements AutoCloseable {
@@ -40,6 +41,38 @@ public final class Terminal implements AutoCloseable {
library.terminalResize(handle, columns, rows, cellWidthPx, cellHeightPx); library.terminalResize(handle, columns, rows, cellWidthPx, cellHeightPx);
} }
public void setKittyImageStorageLimit(long bytes) {
ensureOpen();
library.terminalSetU64(handle, GhosttyLibrary.TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT, bytes);
}
public void setKittyImageMediumFile(boolean enabled) {
ensureOpen();
library.terminalSetBoolean(handle, GhosttyLibrary.TERMINAL_OPT_KITTY_IMAGE_MEDIUM_FILE, enabled);
}
public void setKittyImageMediumTemporaryFile(boolean enabled) {
ensureOpen();
library.terminalSetBoolean(handle, GhosttyLibrary.TERMINAL_OPT_KITTY_IMAGE_MEDIUM_TEMP_FILE, enabled);
}
public void setKittyImageMediumSharedMemory(boolean enabled) {
ensureOpen();
library.terminalSetBoolean(handle, GhosttyLibrary.TERMINAL_OPT_KITTY_IMAGE_MEDIUM_SHARED_MEM, enabled);
}
public Optional<KittyGraphics> kittyGraphics() {
ensureOpen();
MemorySegment graphics = library.terminalGetPointerOrNull(
handle,
GhosttyLibrary.TERMINAL_DATA_KITTY_GRAPHICS
);
if (graphics.address() == 0) {
return Optional.empty();
}
return Optional.of(new KittyGraphics(library, handle, graphics));
}
public TerminalSnapshot snapshot() { public TerminalSnapshot snapshot() {
ensureOpen(); ensureOpen();
return new TerminalSnapshot( return new TerminalSnapshot(

View File

@@ -26,6 +26,36 @@ public final class GhosttyLibrary {
public static final int TERMINAL_DATA_CURSOR_VISIBLE = 7; public static final int TERMINAL_DATA_CURSOR_VISIBLE = 7;
public static final int TERMINAL_DATA_TITLE = 12; public static final int TERMINAL_DATA_TITLE = 12;
public static final int TERMINAL_DATA_PWD = 13; public static final int TERMINAL_DATA_PWD = 13;
public static final int TERMINAL_DATA_KITTY_GRAPHICS = 30;
public static final int TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT = 15;
public static final int TERMINAL_OPT_KITTY_IMAGE_MEDIUM_FILE = 16;
public static final int TERMINAL_OPT_KITTY_IMAGE_MEDIUM_TEMP_FILE = 17;
public static final int TERMINAL_OPT_KITTY_IMAGE_MEDIUM_SHARED_MEM = 18;
public static final int KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR = 1;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID = 1;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_PLACEMENT_ID = 2;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_IS_VIRTUAL = 3;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_X_OFFSET = 4;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_Y_OFFSET = 5;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_X = 6;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_Y = 7;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_WIDTH = 8;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_HEIGHT = 9;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_COLUMNS = 10;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_ROWS = 11;
public static final int KITTY_GRAPHICS_PLACEMENT_DATA_Z = 12;
public static final int KITTY_IMAGE_DATA_ID = 1;
public static final int KITTY_IMAGE_DATA_NUMBER = 2;
public static final int KITTY_IMAGE_DATA_WIDTH = 3;
public static final int KITTY_IMAGE_DATA_HEIGHT = 4;
public static final int KITTY_IMAGE_DATA_FORMAT = 5;
public static final int KITTY_IMAGE_DATA_COMPRESSION = 6;
public static final int KITTY_IMAGE_DATA_DATA_PTR = 7;
public static final int KITTY_IMAGE_DATA_DATA_LEN = 8;
private static final int GHOSTTY_SUCCESS = 0; private static final int GHOSTTY_SUCCESS = 0;
private static final int GHOSTTY_OUT_OF_SPACE = -3; private static final int GHOSTTY_OUT_OF_SPACE = -3;
@@ -36,6 +66,7 @@ public final class GhosttyLibrary {
private static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) LINKER.canonicalLayouts().get("bool"); private static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) LINKER.canonicalLayouts().get("bool");
private static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) LINKER.canonicalLayouts().get("short"); private static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) LINKER.canonicalLayouts().get("short");
private static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) LINKER.canonicalLayouts().get("int"); private static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) LINKER.canonicalLayouts().get("int");
private static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long long");
private static final ValueLayout.OfLong C_SIZE_T = sizeTLayout(); private static final ValueLayout.OfLong C_SIZE_T = sizeTLayout();
private static final GroupLayout TERMINAL_OPTIONS = MemoryLayout.structLayout( private static final GroupLayout TERMINAL_OPTIONS = MemoryLayout.structLayout(
@@ -50,14 +81,41 @@ public final class GhosttyLibrary {
C_SIZE_T.withName("len") C_SIZE_T.withName("len")
); );
private static final GroupLayout KITTY_RENDER_INFO = MemoryLayout.structLayout(
C_SIZE_T.withName("size"),
C_INT.withName("pixel_width"),
C_INT.withName("pixel_height"),
C_INT.withName("grid_cols"),
C_INT.withName("grid_rows"),
C_INT.withName("viewport_col"),
C_INT.withName("viewport_row"),
C_BOOL.withName("viewport_visible"),
MemoryLayout.paddingLayout(3),
C_INT.withName("source_x"),
C_INT.withName("source_y"),
C_INT.withName("source_width"),
C_INT.withName("source_height"),
MemoryLayout.paddingLayout(4)
);
private final MethodHandle terminalNew; private final MethodHandle terminalNew;
private final MethodHandle terminalFree; private final MethodHandle terminalFree;
private final MethodHandle terminalReset; private final MethodHandle terminalReset;
private final MethodHandle terminalResize; private final MethodHandle terminalResize;
private final MethodHandle terminalVtWrite; private final MethodHandle terminalVtWrite;
private final MethodHandle terminalSet;
private final MethodHandle terminalGet; private final MethodHandle terminalGet;
private final MethodHandle pasteIsSafe; private final MethodHandle pasteIsSafe;
private final MethodHandle pasteEncode; private final MethodHandle pasteEncode;
private final MethodHandle kittyGraphicsGet;
private final MethodHandle kittyGraphicsImage;
private final MethodHandle kittyGraphicsImageGet;
private final MethodHandle kittyGraphicsPlacementIteratorNew;
private final MethodHandle kittyGraphicsPlacementIteratorFree;
private final MethodHandle kittyGraphicsPlacementIteratorSet;
private final MethodHandle kittyGraphicsPlacementNext;
private final MethodHandle kittyGraphicsPlacementGet;
private final MethodHandle kittyGraphicsPlacementRenderInfo;
private GhosttyLibrary(Path libraryPath) { private GhosttyLibrary(Path libraryPath) {
try { try {
@@ -73,12 +131,32 @@ public final class GhosttyLibrary {
FunctionDescriptor.of(C_INT, C_POINTER, C_SHORT, C_SHORT, C_INT, C_INT)); FunctionDescriptor.of(C_INT, C_POINTER, C_SHORT, C_SHORT, C_INT, C_INT));
terminalVtWrite = downcall(symbols, "ghostty_terminal_vt_write", terminalVtWrite = downcall(symbols, "ghostty_terminal_vt_write",
FunctionDescriptor.ofVoid(C_POINTER, C_POINTER, C_SIZE_T)); FunctionDescriptor.ofVoid(C_POINTER, C_POINTER, C_SIZE_T));
terminalSet = downcall(symbols, "ghostty_terminal_set",
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
terminalGet = downcall(symbols, "ghostty_terminal_get", terminalGet = downcall(symbols, "ghostty_terminal_get",
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER)); FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
pasteIsSafe = downcall(symbols, "ghostty_paste_is_safe", pasteIsSafe = downcall(symbols, "ghostty_paste_is_safe",
FunctionDescriptor.of(C_BOOL, C_POINTER, C_SIZE_T)); FunctionDescriptor.of(C_BOOL, C_POINTER, C_SIZE_T));
pasteEncode = downcall(symbols, "ghostty_paste_encode", pasteEncode = downcall(symbols, "ghostty_paste_encode",
FunctionDescriptor.of(C_INT, C_POINTER, C_SIZE_T, C_BOOL, C_POINTER, C_SIZE_T, C_POINTER)); FunctionDescriptor.of(C_INT, C_POINTER, C_SIZE_T, C_BOOL, C_POINTER, C_SIZE_T, C_POINTER));
kittyGraphicsGet = downcall(symbols, "ghostty_kitty_graphics_get",
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
kittyGraphicsImage = downcall(symbols, "ghostty_kitty_graphics_image",
FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT));
kittyGraphicsImageGet = downcall(symbols, "ghostty_kitty_graphics_image_get",
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
kittyGraphicsPlacementIteratorNew = downcall(symbols, "ghostty_kitty_graphics_placement_iterator_new",
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER));
kittyGraphicsPlacementIteratorFree = downcall(symbols, "ghostty_kitty_graphics_placement_iterator_free",
FunctionDescriptor.ofVoid(C_POINTER));
kittyGraphicsPlacementIteratorSet = downcall(symbols, "ghostty_kitty_graphics_placement_iterator_set",
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
kittyGraphicsPlacementNext = downcall(symbols, "ghostty_kitty_graphics_placement_next",
FunctionDescriptor.of(C_BOOL, C_POINTER));
kittyGraphicsPlacementGet = downcall(symbols, "ghostty_kitty_graphics_placement_get",
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
kittyGraphicsPlacementRenderInfo = downcall(symbols, "ghostty_kitty_graphics_placement_render_info",
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_POINTER));
} catch (IllegalCallerException e) { } catch (IllegalCallerException e) {
throw new IllegalStateException( throw new IllegalStateException(
"FFM native access is disabled. Run with --enable-native-access=dev.jlibghostty " "FFM native access is disabled. Run with --enable-native-access=dev.jlibghostty "
@@ -159,6 +237,28 @@ public final class GhosttyLibrary {
} }
} }
public void terminalSetU64(MemorySegment terminal, int key, long value) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment nativeValue = arena.allocate(C_LONG_LONG);
nativeValue.set(C_LONG_LONG, 0, value);
int result = (int) terminalSet.invoke(terminal, key, nativeValue);
checkResult("ghostty_terminal_set", result);
} catch (Throwable t) {
rethrow(t);
}
}
public void terminalSetBoolean(MemorySegment terminal, int key, boolean value) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment nativeValue = arena.allocate(C_BOOL);
nativeValue.set(C_BOOL, 0, value);
int result = (int) terminalSet.invoke(terminal, key, nativeValue);
checkResult("ghostty_terminal_set", result);
} catch (Throwable t) {
rethrow(t);
}
}
public int terminalGetU16(MemorySegment terminal, int key) { public int terminalGetU16(MemorySegment terminal, int key) {
try (Arena arena = Arena.ofConfined()) { try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(C_SHORT); MemorySegment out = arena.allocate(C_SHORT);
@@ -203,6 +303,20 @@ public final class GhosttyLibrary {
} }
} }
public MemorySegment terminalGetPointerOrNull(MemorySegment terminal, int key) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(C_POINTER);
int result = (int) terminalGet.invoke(terminal, key, out);
if (result == GHOSTTY_NO_VALUE) {
return MemorySegment.NULL;
}
checkResult("ghostty_terminal_get", result);
return out.get(C_POINTER, 0);
} catch (Throwable t) {
return rethrow(t);
}
}
public boolean pasteIsSafe(byte[] data) { public boolean pasteIsSafe(byte[] data) {
if (data.length == 0) { if (data.length == 0) {
return true; return true;
@@ -261,6 +375,163 @@ public final class GhosttyLibrary {
} }
} }
public void kittyGraphicsPopulatePlacementIterator(MemorySegment graphics, MemorySegment iterator) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(C_POINTER);
out.set(C_POINTER, 0, iterator);
int result = (int) kittyGraphicsGet.invoke(graphics, KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR, out);
checkResult("ghostty_kitty_graphics_get", result);
} catch (Throwable t) {
rethrow(t);
}
}
public MemorySegment kittyGraphicsImage(MemorySegment graphics, long imageId) {
try {
return (MemorySegment) kittyGraphicsImage.invoke(graphics, (int) imageId);
} catch (Throwable t) {
return rethrow(t);
}
}
public long kittyImageGetU32(MemorySegment image, int key) {
return Integer.toUnsignedLong(kittyImageGetI32(image, key));
}
public int kittyImageGetI32(MemorySegment image, int key) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(C_INT);
int result = (int) kittyGraphicsImageGet.invoke(image, key, out);
checkResult("ghostty_kitty_graphics_image_get", result);
return out.get(C_INT, 0);
} catch (Throwable t) {
return rethrow(t);
}
}
public byte[] kittyImageData(MemorySegment image) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment outPtr = arena.allocate(C_POINTER);
MemorySegment outLen = arena.allocate(C_SIZE_T);
int result = (int) kittyGraphicsImageGet.invoke(image, KITTY_IMAGE_DATA_DATA_PTR, outPtr);
checkResult("ghostty_kitty_graphics_image_get", result);
result = (int) kittyGraphicsImageGet.invoke(image, KITTY_IMAGE_DATA_DATA_LEN, outLen);
checkResult("ghostty_kitty_graphics_image_get", result);
MemorySegment ptr = outPtr.get(C_POINTER, 0);
long len = outLen.get(C_SIZE_T, 0);
if (ptr.address() == 0 || len == 0) {
return new byte[0];
}
return ptr.reinterpret(len).toArray(JAVA_BYTE);
} catch (Throwable t) {
return rethrow(t);
}
}
public MemorySegment kittyPlacementIteratorNew() {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(C_POINTER);
int result = (int) kittyGraphicsPlacementIteratorNew.invoke(MemorySegment.NULL, out);
checkResult("ghostty_kitty_graphics_placement_iterator_new", result);
MemorySegment iterator = out.get(C_POINTER, 0);
if (iterator.address() == 0) {
throw new IllegalStateException("ghostty_kitty_graphics_placement_iterator_new returned null");
}
return iterator;
} catch (Throwable t) {
return rethrow(t);
}
}
public void kittyPlacementIteratorFree(MemorySegment iterator) {
try {
kittyGraphicsPlacementIteratorFree.invoke(iterator);
} catch (Throwable t) {
rethrow(t);
}
}
public void kittyPlacementIteratorSetLayer(MemorySegment iterator, int layer) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment nativeLayer = arena.allocate(C_INT);
nativeLayer.set(C_INT, 0, layer);
int result = (int) kittyGraphicsPlacementIteratorSet.invoke(iterator, 0, nativeLayer);
checkResult("ghostty_kitty_graphics_placement_iterator_set", result);
} catch (Throwable t) {
rethrow(t);
}
}
public boolean kittyPlacementNext(MemorySegment iterator) {
try {
return (boolean) kittyGraphicsPlacementNext.invoke(iterator);
} catch (Throwable t) {
return rethrow(t);
}
}
public long kittyPlacementGetU32(MemorySegment iterator, int key) {
return Integer.toUnsignedLong(kittyPlacementGetI32(iterator, key));
}
public int kittyPlacementGetI32(MemorySegment iterator, int key) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(C_INT);
int result = (int) kittyGraphicsPlacementGet.invoke(iterator, key, out);
checkResult("ghostty_kitty_graphics_placement_get", result);
return out.get(C_INT, 0);
} catch (Throwable t) {
return rethrow(t);
}
}
public boolean kittyPlacementGetBoolean(MemorySegment iterator, int key) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(C_BOOL);
int result = (int) kittyGraphicsPlacementGet.invoke(iterator, key, out);
checkResult("ghostty_kitty_graphics_placement_get", result);
return out.get(C_BOOL, 0);
} catch (Throwable t) {
return rethrow(t);
}
}
public java.util.Optional<dev.jlibghostty.KittyRenderInfo> kittyPlacementRenderInfo(
MemorySegment iterator,
MemorySegment image,
MemorySegment terminal
) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(KITTY_RENDER_INFO);
out.set(C_SIZE_T, 0, KITTY_RENDER_INFO.byteSize());
int result = (int) kittyGraphicsPlacementRenderInfo.invoke(iterator, image, terminal, out);
if (result == GHOSTTY_NO_VALUE) {
return java.util.Optional.empty();
}
checkResult("ghostty_kitty_graphics_placement_render_info", result);
return java.util.Optional.of(new dev.jlibghostty.KittyRenderInfo(
Integer.toUnsignedLong(out.get(C_INT, 8)),
Integer.toUnsignedLong(out.get(C_INT, 12)),
Integer.toUnsignedLong(out.get(C_INT, 16)),
Integer.toUnsignedLong(out.get(C_INT, 20)),
out.get(C_INT, 24),
out.get(C_INT, 28),
out.get(C_BOOL, 32),
Integer.toUnsignedLong(out.get(C_INT, 36)),
Integer.toUnsignedLong(out.get(C_INT, 40)),
Integer.toUnsignedLong(out.get(C_INT, 44)),
Integer.toUnsignedLong(out.get(C_INT, 48))
));
} catch (Throwable t) {
return rethrow(t);
}
}
private static MethodHandle downcall(SymbolLookup symbols, String name, FunctionDescriptor descriptor) { private static MethodHandle downcall(SymbolLookup symbols, String name, FunctionDescriptor descriptor) {
MemorySegment symbol = symbols.find(name) MemorySegment symbol = symbols.find(name)
.orElseThrow(() -> new UnsatisfiedLinkError("Missing libghostty-vt symbol: " + name)); .orElseThrow(() -> new UnsatisfiedLinkError("Missing libghostty-vt symbol: " + name));

View File

@@ -46,6 +46,14 @@
"void*" "void*"
] ]
}, },
{
"returnType": "int",
"parameterTypes": [
"void*",
"int",
"void*"
]
},
{ {
"returnType": "bool", "returnType": "bool",
"parameterTypes": [ "parameterTypes": [
@@ -63,6 +71,35 @@
"size_t", "size_t",
"void*" "void*"
] ]
},
{
"returnType": "void*",
"parameterTypes": [
"void*",
"int"
]
},
{
"returnType": "int",
"parameterTypes": [
"void*",
"void*"
]
},
{
"returnType": "bool",
"parameterTypes": [
"void*"
]
},
{
"returnType": "int",
"parameterTypes": [
"void*",
"void*",
"void*",
"void*"
]
} }
] ]
} }

View File

@@ -16,11 +16,18 @@ public final class GhosttySmokeTest {
} }
try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) { try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) {
terminal.setKittyImageStorageLimit(1024 * 1024);
terminal.setKittyImageMediumFile(true);
terminal.write("hello\r\n"); terminal.write("hello\r\n");
TerminalSnapshot snapshot = terminal.snapshot(); TerminalSnapshot snapshot = terminal.snapshot();
if (snapshot.columns() != 80 || snapshot.rows() != 24) { if (snapshot.columns() != 80 || snapshot.rows() != 24) {
throw new AssertionError("unexpected terminal size: " + snapshot); throw new AssertionError("unexpected terminal size: " + snapshot);
} }
KittyGraphics graphics = terminal.kittyGraphics()
.orElseThrow(() -> new AssertionError("expected kitty graphics handle"));
if (graphics.placements().size() != 1) {
throw new AssertionError("expected one kitty placement");
}
} }
} }
} }