kitty graphics support
This commit is contained in:
25
README.md
25
README.md
@@ -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
|
||||||
|
|||||||
63
src/main/java/dev/jlibghostty/KittyGraphics.java
Normal file
63
src/main/java/dev/jlibghostty/KittyGraphics.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/java/dev/jlibghostty/KittyImage.java
Normal file
31
src/main/java/dev/jlibghostty/KittyImage.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/main/java/dev/jlibghostty/KittyImageCompression.java
Normal file
25
src/main/java/dev/jlibghostty/KittyImageCompression.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/java/dev/jlibghostty/KittyImageFormat.java
Normal file
28
src/main/java/dev/jlibghostty/KittyImageFormat.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/dev/jlibghostty/KittyImageSnapshot.java
Normal file
20
src/main/java/dev/jlibghostty/KittyImageSnapshot.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/java/dev/jlibghostty/KittyPlacement.java
Normal file
21
src/main/java/dev/jlibghostty/KittyPlacement.java
Normal 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
|
||||||
|
) {
|
||||||
|
}
|
||||||
56
src/main/java/dev/jlibghostty/KittyPlacementIterator.java
Normal file
56
src/main/java/dev/jlibghostty/KittyPlacementIterator.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/java/dev/jlibghostty/KittyPlacementLayer.java
Normal file
18
src/main/java/dev/jlibghostty/KittyPlacementLayer.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/dev/jlibghostty/KittyRenderInfo.java
Normal file
16
src/main/java/dev/jlibghostty/KittyRenderInfo.java
Normal 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
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user