readme update

This commit is contained in:
Gregor Lohaus
2026-05-28 13:48:30 +02:00
parent 39468748fd
commit c90ed9f9a5
3 changed files with 215 additions and 39 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ result
result-* result-*
*.class *.class
*.jar *.jar
.devenv

1
.ignore Normal file
View File

@@ -0,0 +1 @@
.devenv

258
README.md
View File

@@ -2,7 +2,31 @@
Java FFM bindings for Ghostty's `libghostty-vt`. Java FFM bindings for Ghostty's `libghostty-vt`.
This targets Java 22+ and uses `java.lang.foreign`, not JNI. The public API is intentionally small while Ghostty's C API is still marked unstable upstream. This project targets Java 22+ and uses `java.lang.foreign`, not JNI. The build is Nix-first, but the output is a normal Maven repository that can be consumed from Gradle.
Ghostty's C API is still evolving upstream, so the ergonomic Java API is intentionally focused on the parts needed to embed the VT parser and renderer state in Java applications. The raw native symbol surface is still available through `GhosttyNative`.
## Current Coverage
The public Java API currently wraps:
- Terminal creation, resize, reset, VT input, snapshots, and scrollback viewport movement.
- Terminal text formatting as plain text, VT text, or HTML.
- PTY output callbacks for terminal-generated responses such as DSR and DA.
- Device attributes callbacks.
- Render-state snapshots, including rows, cells, codepoints, fg/bg colors, selection state, cursor state, and cursor viewport position.
- Kitty graphics image and placement inspection, including image data, placement render info, and decoded unicode-placeholder metadata on `RenderCell`.
- Mouse event encoding through Ghostty's mouse encoder.
- Paste safety and paste encoding.
- Focus, mode-report, and size-report encoding.
- Build metadata and Ghostty type/layout JSON.
- Raw exported symbol lookup/downcalls through `GhosttyNative`.
Notable gaps:
- PNG decode callbacks via `ghostty_sys_set(GHOSTTY_SYS_OPT_DECODE_PNG, ...)` are not wrapped yet.
- Some lower-level grid, selection, OSC, SGR, style, and sys APIs are only available through `GhosttyNative`.
- The wrapper follows the pinned `libghostty-vt` ABI from the Nix flake. Recheck layouts when updating Ghostty.
## Build ## Build
@@ -15,7 +39,51 @@ The default Nix package builds:
- `share/java/jlibghostty-0.1.0-SNAPSHOT.jar` - `share/java/jlibghostty-0.1.0-SNAPSHOT.jar`
- `maven/dev/jlibghostty/jlibghostty/0.1.0-SNAPSHOT/...` - `maven/dev/jlibghostty/jlibghostty/0.1.0-SNAPSHOT/...`
The jar contains the host platform `libghostty-vt` under `dev/jlibghostty/native/<platform>/`. The jar contains the host platform `libghostty-vt` under:
```text
dev/jlibghostty/native/<platform>/
```
Supported Nix systems are:
- `x86_64-linux`
- `aarch64-linux`
- `x86_64-darwin`
- `aarch64-darwin`
Because this is a flake build, new source files must be tracked by git before Nix sees them:
```sh
git add src/main/java src/test/java README.md
nix build
```
## Development Shell
```sh
nix develop
```
The shell provides Java, Gradle, and `JLIBGHOSTTY_LIBRARY` pointing at the Nix-built `libghostty-vt`.
For a local Gradle build outside Nix:
```sh
./gradlew build -PghosttyNativeLib="$JLIBGHOSTTY_LIBRARY"
```
Runtime native access is required when running on the classpath:
```sh
--enable-native-access=ALL-UNNAMED
```
If your app runs on the module path, use:
```sh
--enable-native-access=dev.jlibghostty
```
## Gradle Consumer ## Gradle Consumer
@@ -23,6 +91,7 @@ After `nix build`, another Gradle project can consume the generated Maven reposi
```kotlin ```kotlin
repositories { repositories {
mavenCentral()
maven { maven {
url = uri("/home/anon/Dev/jlibghostty/result/maven") url = uri("/home/anon/Dev/jlibghostty/result/maven")
} }
@@ -37,6 +106,13 @@ tasks.withType<JavaExec>().configureEach {
} }
``` ```
There is also a small consumer example in `examples/gradle-consumer`. After `nix build`:
```sh
cd examples/gradle-consumer
JLIBGHOSTTY_MAVEN_REPO="$PWD/../../result/maven" gradle run
```
## GraalVM Native Image Consumer ## GraalVM Native Image Consumer
`jlibghostty` ships GraalVM reachability metadata at: `jlibghostty` ships GraalVM reachability metadata at:
@@ -45,25 +121,19 @@ tasks.withType<JavaExec>().configureEach {
META-INF/native-image/dev.jlibghostty/jlibghostty/reachability-metadata.json META-INF/native-image/dev.jlibghostty/jlibghostty/reachability-metadata.json
``` ```
That metadata registers the FFM downcalls used by this library and includes the bundled native library resource. GraalVM 25 enables Native Image FFM support by default, but the build still needs native access: That metadata registers the FFM downcalls/upcalls used by this library and includes the bundled native library resource. GraalVM 25 enables Native Image FFM support by default, but the build still needs native access:
```sh ```sh
native-image --enable-native-access=ALL-UNNAMED ... native-image --enable-native-access=ALL-UNNAMED ...
``` ```
If your app uses the module path, prefer:
```sh
native-image --enable-native-access=dev.jlibghostty ...
```
For a Nix-built downstream project, the usual shape is: For a Nix-built downstream project, the usual shape is:
```nix ```nix
{ {
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
jlibghostty.url = "path:/home/anon/Dev/jlibghostty"; jlibghostty.url = "git+https://gitea.gregorlohaus.com/gregor/jlibghostty";
}; };
outputs = { nixpkgs, jlibghostty, ... }: outputs = { nixpkgs, jlibghostty, ... }:
@@ -116,54 +186,132 @@ dependencies {
} }
``` ```
If the app runs on the module path, use: ## Loading Native Libraries
```sh The library normally loads the bundled native `libghostty-vt` resource from the jar.
--enable-native-access=dev.jlibghostty
```
## External Native Library To override it with an external library:
The library normally loads the bundled native `libghostty-vt`. To override it:
```sh ```sh
java -Djlibghostty.library.path=/path/to/libghostty-vt.so ... java -Djlibghostty.library.path=/path/to/libghostty-vt.so ...
``` ```
or set: or:
```sh ```sh
export JLIBGHOSTTY_LIBRARY=/path/to/libghostty-vt.so export JLIBGHOSTTY_LIBRARY=/path/to/libghostty-vt.so
``` ```
## Example ## Basic Terminal Usage
```java ```java
try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) { try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) {
terminal.write("hello\r\n"); terminal.write("hello\r\n");
System.out.println(terminal.snapshot());
TerminalSnapshot snapshot = terminal.snapshot();
String plainText = terminal.text();
String vtText = terminal.vtText();
String html = terminal.html();
System.out.println(snapshot);
System.out.println(plainText);
} }
``` ```
## Full Native Surface `TerminalOptions.of(columns, rows)` defaults to `10_000` scrollback lines. Use the full constructor to choose a different limit:
The public Java API covers the common terminal, paste, build-info, focus, mode-report, size-report, and Kitty graphics paths. For libghostty-vt APIs that do not yet have ergonomic Java wrappers, use `GhosttyNative`:
```java ```java
MethodHandle handle = GhosttyNative.downcall( try (Terminal terminal = Ghostty.open(new TerminalOptions(80, 24, 50_000))) {
"ghostty_render_state_new", terminal.write(outputBytes);
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS) }
```
## Render State
Use `renderSnapshot()` for renderer-oriented state:
```java
try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) {
terminal.write("hello\r\n");
RenderStateSnapshot snapshot = terminal.renderSnapshot();
for (RenderRow row : snapshot.renderRows()) {
for (RenderCell cell : row.cells()) {
int column = cell.column();
int[] codepoints = cell.codepoints();
Optional<RenderColor> fg = cell.foreground();
Optional<RenderColor> bg = cell.background();
boolean selected = cell.selected();
}
}
}
```
`RenderStateSnapshot` also exposes cursor visual style, cursor visibility/blinking, password-input state, and cursor viewport coordinates. If `cursorViewportHasValue()` is false, the cursor viewport coordinates are `-1`.
Cells can include Kitty unicode-placeholder metadata:
```java
cell.kittyPlaceholder().ifPresent(placeholder -> {
long imageId = placeholder.imageId();
long placementId = placeholder.placementId();
long sourceRow = placeholder.sourceRow();
long sourceColumn = placeholder.sourceColumn();
});
```
That is the cell-to-image mapping needed for exact virtual Kitty placement rendering.
## Scrollback Viewport
Scrollback is stored by `libghostty-vt` according to `TerminalOptions.maxScrollback()`. Move the viewport with:
```java
terminal.scrollViewport(ScrollViewport.delta(-3)); // up 3 rows
terminal.scrollViewport(ScrollViewport.delta(3)); // down 3 rows
terminal.scrollViewport(ScrollViewport.top());
terminal.scrollViewport(ScrollViewport.bottom());
```
After scrolling, `terminal.renderSnapshot()` renders the current viewport.
## PTY Responses And Device Attributes
Install a PTY writer if the embedded terminal should answer terminal queries back to the child process:
```java
try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) {
terminal.setPtyWriter(bytes -> shellSession.send(bytes));
terminal.setDeviceAttributesProvider(DeviceAttributes::xtermCompatible);
terminal.write(childOutputBytes);
}
```
This lets libghostty handle query semantics such as DSR and DA instead of duplicating VT parsing in Java.
## Mouse Input
`MouseEncoder` converts UI mouse events into terminal input bytes according to the mouse modes currently enabled in the terminal:
```java
try (MouseEncoder mouse = new MouseEncoder()) {
mouse.syncFromTerminal(terminal);
mouse.setSize(MouseEncoderSize.of(widthPx, heightPx, cellWidthPx, cellHeightPx));
KeyModifiers mods = KeyModifiers.of(
event.isShiftDown(),
event.isControlDown(),
event.isAltDown(),
event.isMetaDown()
); );
byte[] bytes = mouse.encode(MouseInput.press(MouseButton.LEFT, event.getX(), event.getY(), mods));
shellSession.send(bytes);
}
``` ```
`GhosttyNative.symbolNames()` lists every exported symbol found in the current public headers. Some entries are target-specific, such as WASM helpers, so use `GhosttyNative.findSymbol(name)` when probing optional symbols. Call `syncFromTerminal(terminal)` after terminal mode changes, or before encoding a batch of UI events.
Build metadata and C struct layout JSON are exposed directly:
```java
GhosttyBuildInfo info = Ghostty.buildInfo();
String typeJson = Ghostty.typeJson();
```
## Kitty Graphics ## Kitty Graphics
@@ -179,21 +327,47 @@ try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) {
terminal.write(kittyGraphicsSequenceBytes); terminal.write(kittyGraphicsSequenceBytes);
for (KittyPlacement placement : terminal.kittyGraphics().orElseThrow().placements()) { for (KittyPlacement placement : terminal.kittyGraphics().orElseThrow().placements()) {
placement.image().ifPresent(image -> { Optional<KittyImageSnapshot> image = placement.image();
// Hand image.data() and placement.renderInfo() to your renderer. Optional<KittyRenderInfo> renderInfo = placement.renderInfo();
});
} }
} }
``` ```
The Kitty handles returned by `libghostty-vt` are borrowed from the terminal and are invalidated by mutating terminal calls. The Java API returns snapshots for images and placements to make renderer handoff simpler. The Kitty handles returned by `libghostty-vt` are borrowed from the terminal and are invalidated by mutating terminal calls. The Java API returns snapshots for images and placements to make renderer handoff simpler.
PNG decode callbacks from `ghostty_sys_set(GHOSTTY_SYS_OPT_DECODE_PNG, ...)` are not exposed yet. Raw Kitty image formats can be inspected; PNG image ingestion will need a Java callback bridge or a small native helper that allocates decoded RGBA data through Ghostty's allocator. For virtual Kitty placements, combine `KittyGraphics.placements()` with `RenderCell.kittyPlaceholder()` when you need exact per-cell placeholder mapping.
## Development Shell ## Utility Encoders
```sh The static `Ghostty` facade exposes small helpers:
nix develop
```java
boolean safe = Ghostty.pasteIsSafe("hello");
String paste = Ghostty.encodePaste("hello", true);
String focus = Ghostty.encodeFocus(FocusEvent.GAINED);
String mode = Ghostty.encodeModeReport(25, ModeReportState.SET);
String size = Ghostty.encodeSizeReport(
SizeReportStyle.CSI_18_T,
new SizeReportSize(24, 80, 8, 16)
);
``` ```
The shell provides Java, Gradle, and `JLIBGHOSTTY_LIBRARY` pointing at the Nix-built `libghostty-vt`. Build metadata and C struct layout JSON are exposed directly:
```java
GhosttyBuildInfo info = Ghostty.buildInfo();
String typeJson = Ghostty.typeJson();
```
## Raw Native Surface
For libghostty-vt APIs that do not yet have ergonomic Java wrappers, use `GhosttyNative`:
```java
MethodHandle handle = GhosttyNative.downcall(
"ghostty_render_state_new",
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS)
);
```
`GhosttyNative.symbolNames()` lists exported symbols from the current public headers. Some entries are target-specific, such as WASM helpers, so use `GhosttyNative.findSymbol(name)` when probing optional symbols.