expose cursor from render state

This commit is contained in:
Gregor Lohaus
2026-05-27 19:47:28 +02:00
parent 0a875910a0
commit 0852e58086
5 changed files with 88 additions and 1 deletions

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

View File

@@ -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()
); );
} }

View File

@@ -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);
} }

View File

@@ -70,6 +70,14 @@ 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;
@@ -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<RenderRow> renderStateRows(MemorySegment state) { public List<RenderRow> renderStateRows(MemorySegment state) {
MemorySegment iterator = renderStateRowIteratorNew(); MemorySegment iterator = renderStateRowIteratorNew();
try { try {

View File

@@ -43,6 +43,14 @@ 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() renderSnapshot.renderRows().stream()
.flatMap(row -> row.cells().stream()) .flatMap(row -> row.cells().stream())
.forEach(cell -> { .forEach(cell -> {