diff --git a/src/main/java/dev/jlibghostty/RenderCell.java b/src/main/java/dev/jlibghostty/RenderCell.java index c16a969..64683a1 100644 --- a/src/main/java/dev/jlibghostty/RenderCell.java +++ b/src/main/java/dev/jlibghostty/RenderCell.java @@ -1,8 +1,18 @@ package dev.jlibghostty; -public record RenderCell(int column, int[] codepoints, boolean selected) { +import java.util.Optional; + +public record RenderCell( + int column, + int[] codepoints, + Optional foreground, + Optional background, + boolean selected +) { public RenderCell { codepoints = codepoints.clone(); + foreground = foreground == null ? Optional.empty() : foreground; + background = background == null ? Optional.empty() : background; } @Override diff --git a/src/main/java/dev/jlibghostty/RenderColor.java b/src/main/java/dev/jlibghostty/RenderColor.java new file mode 100644 index 0000000..6c6cc04 --- /dev/null +++ b/src/main/java/dev/jlibghostty/RenderColor.java @@ -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; + } +} diff --git a/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java b/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java index 88007c3..1014f61 100644 --- a/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java +++ b/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java @@ -4,6 +4,7 @@ import dev.jlibghostty.GhosttyBuildInfo; import dev.jlibghostty.GhosttyException; import dev.jlibghostty.OptimizeMode; import dev.jlibghostty.RenderCell; +import dev.jlibghostty.RenderColor; import dev.jlibghostty.RenderRow; import dev.jlibghostty.SizeReportSize; import dev.jlibghostty.TerminalOptions; @@ -22,6 +23,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; 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_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; 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_NO_VALUE = -4; @@ -703,6 +708,8 @@ public final class GhosttyLibrary { result.add(new RenderCell( column, 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) )); column++; @@ -799,6 +806,24 @@ public final class GhosttyLibrary { } } + private Optional 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) { try (Arena arena = Arena.ofConfined()) { MemorySegment out = arena.allocate(C_POINTER); diff --git a/src/test/java/dev/jlibghostty/GhosttySmokeTest.java b/src/test/java/dev/jlibghostty/GhosttySmokeTest.java index e379ad5..f64b8d8 100644 --- a/src/test/java/dev/jlibghostty/GhosttySmokeTest.java +++ b/src/test/java/dev/jlibghostty/GhosttySmokeTest.java @@ -43,6 +43,12 @@ public final class GhosttySmokeTest { if (!renderedHello) { 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(); if (snapshot.columns() != 80 || snapshot.rows() != 24) { throw new AssertionError("unexpected terminal size: " + snapshot); @@ -50,4 +56,12 @@ public final class GhosttySmokeTest { 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); + } + } }