expose color from render state

This commit is contained in:
Gregor Lohaus
2026-05-27 19:44:03 +02:00
parent 91e9ce5bb4
commit 0a875910a0
4 changed files with 66 additions and 1 deletions

View File

@@ -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

View 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;
}
}

View File

@@ -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;
@@ -74,9 +76,12 @@ public final class GhosttyLibrary {
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;
@@ -703,6 +708,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 +806,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);

View File

@@ -43,6 +43,12 @@ 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");
} }
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 +56,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);
}
}
} }