From 0852e580860bc5035edccff39607f13764eec6f8 Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Wed, 27 May 2026 19:47:28 +0200 Subject: [PATCH] expose cursor from render state --- .../dev/jlibghostty/RenderCursorStyle.java | 27 +++++++++++++++++++ .../java/dev/jlibghostty/RenderState.java | 20 ++++++++++++++ .../dev/jlibghostty/RenderStateSnapshot.java | 15 ++++++++++- .../jlibghostty/internal/GhosttyLibrary.java | 19 +++++++++++++ .../dev/jlibghostty/GhosttySmokeTest.java | 8 ++++++ 5 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dev/jlibghostty/RenderCursorStyle.java diff --git a/src/main/java/dev/jlibghostty/RenderCursorStyle.java b/src/main/java/dev/jlibghostty/RenderCursorStyle.java new file mode 100644 index 0000000..d2f6198 --- /dev/null +++ b/src/main/java/dev/jlibghostty/RenderCursorStyle.java @@ -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); + } +} diff --git a/src/main/java/dev/jlibghostty/RenderState.java b/src/main/java/dev/jlibghostty/RenderState.java index 49e2d86..41c6331 100644 --- a/src/main/java/dev/jlibghostty/RenderState.java +++ b/src/main/java/dev/jlibghostty/RenderState.java @@ -24,10 +24,30 @@ public final class RenderState implements AutoCloseable { public RenderStateSnapshot snapshot() { ensureOpen(); + boolean cursorViewportHasValue = library.renderStateGetBoolean( + handle, + GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE + ); + return new RenderStateSnapshot( library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_COLS), library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_ROWS), 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() ); } diff --git a/src/main/java/dev/jlibghostty/RenderStateSnapshot.java b/src/main/java/dev/jlibghostty/RenderStateSnapshot.java index 69685d1..6eb55a1 100644 --- a/src/main/java/dev/jlibghostty/RenderStateSnapshot.java +++ b/src/main/java/dev/jlibghostty/RenderStateSnapshot.java @@ -2,7 +2,20 @@ package dev.jlibghostty; import java.util.List; -public record RenderStateSnapshot(int columns, int rows, int dirty, List 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 renderRows +) { public RenderStateSnapshot { renderRows = List.copyOf(renderRows); } diff --git a/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java b/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java index 1014f61..0191422 100644 --- a/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java +++ b/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java @@ -70,6 +70,14 @@ public final class GhosttyLibrary { 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_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_CELLS = 3; @@ -627,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 renderStateRows(MemorySegment state) { MemorySegment iterator = renderStateRowIteratorNew(); try { diff --git a/src/test/java/dev/jlibghostty/GhosttySmokeTest.java b/src/test/java/dev/jlibghostty/GhosttySmokeTest.java index f64b8d8..b5576f6 100644 --- a/src/test/java/dev/jlibghostty/GhosttySmokeTest.java +++ b/src/test/java/dev/jlibghostty/GhosttySmokeTest.java @@ -43,6 +43,14 @@ public final class GhosttySmokeTest { if (!renderedHello) { 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 -> {