Compare commits
2 Commits
91e9ce5bb4
...
0852e58086
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0852e58086 | ||
|
|
0a875910a0 |
@@ -1,8 +1,18 @@
|
|||||||
package dev.jlibghostty;
|
package dev.jlibghostty;
|
||||||
|
|
||||||
public record RenderCell(int column, int[] codepoints, boolean selected) {
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public record RenderCell(
|
||||||
|
int column,
|
||||||
|
int[] codepoints,
|
||||||
|
Optional<RenderColor> foreground,
|
||||||
|
Optional<RenderColor> background,
|
||||||
|
boolean selected
|
||||||
|
) {
|
||||||
public RenderCell {
|
public RenderCell {
|
||||||
codepoints = codepoints.clone();
|
codepoints = codepoints.clone();
|
||||||
|
foreground = foreground == null ? Optional.empty() : foreground;
|
||||||
|
background = background == null ? Optional.empty() : background;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
16
src/main/java/dev/jlibghostty/RenderColor.java
Normal file
16
src/main/java/dev/jlibghostty/RenderColor.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package dev.jlibghostty;
|
||||||
|
|
||||||
|
public record RenderColor(int red, int green, int blue) {
|
||||||
|
public RenderColor {
|
||||||
|
red = checkChannel("red", red);
|
||||||
|
green = checkChannel("green", green);
|
||||||
|
blue = checkChannel("blue", blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int checkChannel(String name, int value) {
|
||||||
|
if (value < 0 || value > 255) {
|
||||||
|
throw new IllegalArgumentException(name + " must be between 0 and 255");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/dev/jlibghostty/RenderCursorStyle.java
Normal file
27
src/main/java/dev/jlibghostty/RenderCursorStyle.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package dev.jlibghostty;
|
||||||
|
|
||||||
|
public enum RenderCursorStyle {
|
||||||
|
BAR(0),
|
||||||
|
BLOCK(1),
|
||||||
|
UNDERLINE(2),
|
||||||
|
BLOCK_HOLLOW(3);
|
||||||
|
|
||||||
|
private final int nativeValue;
|
||||||
|
|
||||||
|
RenderCursorStyle(int nativeValue) {
|
||||||
|
this.nativeValue = nativeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nativeValue() {
|
||||||
|
return nativeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RenderCursorStyle fromNative(int value) {
|
||||||
|
for (RenderCursorStyle style : values()) {
|
||||||
|
if (style.nativeValue == value) {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown render cursor style: " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,10 +24,30 @@ public final class RenderState implements AutoCloseable {
|
|||||||
|
|
||||||
public RenderStateSnapshot snapshot() {
|
public RenderStateSnapshot snapshot() {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
|
boolean cursorViewportHasValue = library.renderStateGetBoolean(
|
||||||
|
handle,
|
||||||
|
GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE
|
||||||
|
);
|
||||||
|
|
||||||
return new RenderStateSnapshot(
|
return new RenderStateSnapshot(
|
||||||
library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_COLS),
|
library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_COLS),
|
||||||
library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_ROWS),
|
library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_ROWS),
|
||||||
library.renderStateGetI32(handle, GhosttyLibrary.RENDER_STATE_DATA_DIRTY),
|
library.renderStateGetI32(handle, GhosttyLibrary.RENDER_STATE_DATA_DIRTY),
|
||||||
|
RenderCursorStyle.fromNative(
|
||||||
|
library.renderStateGetI32(handle, GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VISUAL_STYLE)
|
||||||
|
),
|
||||||
|
library.renderStateGetBoolean(handle, GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VISIBLE),
|
||||||
|
library.renderStateGetBoolean(handle, GhosttyLibrary.RENDER_STATE_DATA_CURSOR_BLINKING),
|
||||||
|
library.renderStateGetBoolean(handle, GhosttyLibrary.RENDER_STATE_DATA_CURSOR_PASSWORD_INPUT),
|
||||||
|
cursorViewportHasValue,
|
||||||
|
cursorViewportHasValue
|
||||||
|
? library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VIEWPORT_X)
|
||||||
|
: -1,
|
||||||
|
cursorViewportHasValue
|
||||||
|
? library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VIEWPORT_Y)
|
||||||
|
: -1,
|
||||||
|
cursorViewportHasValue
|
||||||
|
&& library.renderStateGetBoolean(handle, GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL),
|
||||||
rows()
|
rows()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,20 @@ package dev.jlibghostty;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public record RenderStateSnapshot(int columns, int rows, int dirty, List<RenderRow> renderRows) {
|
public record RenderStateSnapshot(
|
||||||
|
int columns,
|
||||||
|
int rows,
|
||||||
|
int dirty,
|
||||||
|
RenderCursorStyle cursorStyle,
|
||||||
|
boolean cursorVisible,
|
||||||
|
boolean cursorBlinking,
|
||||||
|
boolean cursorPasswordInput,
|
||||||
|
boolean cursorViewportHasValue,
|
||||||
|
int cursorViewportX,
|
||||||
|
int cursorViewportY,
|
||||||
|
boolean cursorViewportWideTail,
|
||||||
|
List<RenderRow> renderRows
|
||||||
|
) {
|
||||||
public RenderStateSnapshot {
|
public RenderStateSnapshot {
|
||||||
renderRows = List.copyOf(renderRows);
|
renderRows = List.copyOf(renderRows);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import dev.jlibghostty.GhosttyBuildInfo;
|
|||||||
import dev.jlibghostty.GhosttyException;
|
import dev.jlibghostty.GhosttyException;
|
||||||
import dev.jlibghostty.OptimizeMode;
|
import dev.jlibghostty.OptimizeMode;
|
||||||
import dev.jlibghostty.RenderCell;
|
import dev.jlibghostty.RenderCell;
|
||||||
|
import dev.jlibghostty.RenderColor;
|
||||||
import dev.jlibghostty.RenderRow;
|
import dev.jlibghostty.RenderRow;
|
||||||
import dev.jlibghostty.SizeReportSize;
|
import dev.jlibghostty.SizeReportSize;
|
||||||
import dev.jlibghostty.TerminalOptions;
|
import dev.jlibghostty.TerminalOptions;
|
||||||
@@ -22,6 +23,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
|
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
|
||||||
|
|
||||||
@@ -68,15 +70,26 @@ public final class GhosttyLibrary {
|
|||||||
public static final int RENDER_STATE_DATA_ROWS = 2;
|
public static final int RENDER_STATE_DATA_ROWS = 2;
|
||||||
public static final int RENDER_STATE_DATA_DIRTY = 3;
|
public static final int RENDER_STATE_DATA_DIRTY = 3;
|
||||||
public static final int RENDER_STATE_DATA_ROW_ITERATOR = 4;
|
public static final int RENDER_STATE_DATA_ROW_ITERATOR = 4;
|
||||||
|
public static final int RENDER_STATE_DATA_CURSOR_VISUAL_STYLE = 10;
|
||||||
|
public static final int RENDER_STATE_DATA_CURSOR_VISIBLE = 11;
|
||||||
|
public static final int RENDER_STATE_DATA_CURSOR_BLINKING = 12;
|
||||||
|
public static final int RENDER_STATE_DATA_CURSOR_PASSWORD_INPUT = 13;
|
||||||
|
public static final int RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE = 14;
|
||||||
|
public static final int RENDER_STATE_DATA_CURSOR_VIEWPORT_X = 15;
|
||||||
|
public static final int RENDER_STATE_DATA_CURSOR_VIEWPORT_Y = 16;
|
||||||
|
public static final int RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL = 17;
|
||||||
|
|
||||||
public static final int RENDER_STATE_ROW_DATA_DIRTY = 1;
|
public static final int RENDER_STATE_ROW_DATA_DIRTY = 1;
|
||||||
public static final int RENDER_STATE_ROW_DATA_CELLS = 3;
|
public static final int RENDER_STATE_ROW_DATA_CELLS = 3;
|
||||||
|
|
||||||
public static final int RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN = 3;
|
public static final int RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN = 3;
|
||||||
public static final int RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF = 4;
|
public static final int RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF = 4;
|
||||||
|
public static final int RENDER_STATE_ROW_CELLS_DATA_BG_COLOR = 5;
|
||||||
|
public static final int RENDER_STATE_ROW_CELLS_DATA_FG_COLOR = 6;
|
||||||
public static final int RENDER_STATE_ROW_CELLS_DATA_SELECTED = 7;
|
public static final int RENDER_STATE_ROW_CELLS_DATA_SELECTED = 7;
|
||||||
|
|
||||||
private static final int GHOSTTY_SUCCESS = 0;
|
private static final int GHOSTTY_SUCCESS = 0;
|
||||||
|
private static final int GHOSTTY_INVALID_VALUE = -2;
|
||||||
private static final int GHOSTTY_OUT_OF_SPACE = -3;
|
private static final int GHOSTTY_OUT_OF_SPACE = -3;
|
||||||
private static final int GHOSTTY_NO_VALUE = -4;
|
private static final int GHOSTTY_NO_VALUE = -4;
|
||||||
|
|
||||||
@@ -622,6 +635,17 @@ public final class GhosttyLibrary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean renderStateGetBoolean(MemorySegment state, int key) {
|
||||||
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
|
MemorySegment out = arena.allocate(C_BOOL);
|
||||||
|
int result = (int) renderStateGet.invoke(state, key, out);
|
||||||
|
checkResult("ghostty_render_state_get", result);
|
||||||
|
return out.get(C_BOOL, 0);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<RenderRow> renderStateRows(MemorySegment state) {
|
public List<RenderRow> renderStateRows(MemorySegment state) {
|
||||||
MemorySegment iterator = renderStateRowIteratorNew();
|
MemorySegment iterator = renderStateRowIteratorNew();
|
||||||
try {
|
try {
|
||||||
@@ -703,6 +727,8 @@ public final class GhosttyLibrary {
|
|||||||
result.add(new RenderCell(
|
result.add(new RenderCell(
|
||||||
column,
|
column,
|
||||||
renderStateRowCellGraphemes(cells),
|
renderStateRowCellGraphemes(cells),
|
||||||
|
renderStateRowCellColor(cells, RENDER_STATE_ROW_CELLS_DATA_FG_COLOR),
|
||||||
|
renderStateRowCellColor(cells, RENDER_STATE_ROW_CELLS_DATA_BG_COLOR),
|
||||||
renderStateRowCellsGetBoolean(cells, RENDER_STATE_ROW_CELLS_DATA_SELECTED)
|
renderStateRowCellsGetBoolean(cells, RENDER_STATE_ROW_CELLS_DATA_SELECTED)
|
||||||
));
|
));
|
||||||
column++;
|
column++;
|
||||||
@@ -799,6 +825,24 @@ public final class GhosttyLibrary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<RenderColor> renderStateRowCellColor(MemorySegment cells, int key) {
|
||||||
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
|
MemorySegment out = arena.allocate(3, 1);
|
||||||
|
int result = (int) renderStateRowCellsGet.invoke(cells, key, out);
|
||||||
|
if (result == GHOSTTY_INVALID_VALUE) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
checkResult("ghostty_render_state_row_cells_get", result);
|
||||||
|
return Optional.of(new RenderColor(
|
||||||
|
Byte.toUnsignedInt(out.get(JAVA_BYTE, 0)),
|
||||||
|
Byte.toUnsignedInt(out.get(JAVA_BYTE, 1)),
|
||||||
|
Byte.toUnsignedInt(out.get(JAVA_BYTE, 2))
|
||||||
|
));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void kittyGraphicsPopulatePlacementIterator(MemorySegment graphics, MemorySegment iterator) {
|
public void kittyGraphicsPopulatePlacementIterator(MemorySegment graphics, MemorySegment iterator) {
|
||||||
try (Arena arena = Arena.ofConfined()) {
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
MemorySegment out = arena.allocate(C_POINTER);
|
MemorySegment out = arena.allocate(C_POINTER);
|
||||||
|
|||||||
@@ -43,6 +43,20 @@ public final class GhosttySmokeTest {
|
|||||||
if (!renderedHello) {
|
if (!renderedHello) {
|
||||||
throw new AssertionError("render state should contain written text");
|
throw new AssertionError("render state should contain written text");
|
||||||
}
|
}
|
||||||
|
if (renderSnapshot.cursorViewportHasValue()) {
|
||||||
|
if (renderSnapshot.cursorViewportX() < 0 || renderSnapshot.cursorViewportX() >= renderSnapshot.columns()
|
||||||
|
|| renderSnapshot.cursorViewportY() < 0 || renderSnapshot.cursorViewportY() >= renderSnapshot.rows()) {
|
||||||
|
throw new AssertionError("cursor viewport position out of bounds: " + renderSnapshot);
|
||||||
|
}
|
||||||
|
} else if (renderSnapshot.cursorViewportX() != -1 || renderSnapshot.cursorViewportY() != -1) {
|
||||||
|
throw new AssertionError("cursor viewport position should be -1 when absent: " + renderSnapshot);
|
||||||
|
}
|
||||||
|
renderSnapshot.renderRows().stream()
|
||||||
|
.flatMap(row -> row.cells().stream())
|
||||||
|
.forEach(cell -> {
|
||||||
|
cell.foreground().ifPresent(GhosttySmokeTest::assertColor);
|
||||||
|
cell.background().ifPresent(GhosttySmokeTest::assertColor);
|
||||||
|
});
|
||||||
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);
|
||||||
@@ -50,4 +64,12 @@ public final class GhosttySmokeTest {
|
|||||||
terminal.kittyGraphics().ifPresent(KittyGraphics::placements);
|
terminal.kittyGraphics().ifPresent(KittyGraphics::placements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void assertColor(RenderColor color) {
|
||||||
|
if (color.red() < 0 || color.red() > 255
|
||||||
|
|| color.green() < 0 || color.green() > 255
|
||||||
|
|| color.blue() < 0 || color.blue() > 255) {
|
||||||
|
throw new AssertionError("invalid color: " + color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user