readme update
This commit is contained in:
252
README.md
252
README.md
@@ -2,7 +2,31 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -15,7 +39,51 @@ The default Nix package builds:
|
||||
- `share/java/jlibghostty-0.1.0-SNAPSHOT.jar`
|
||||
- `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
|
||||
|
||||
@@ -23,6 +91,7 @@ After `nix build`, another Gradle project can consume the generated Maven reposi
|
||||
|
||||
```kotlin
|
||||
repositories {
|
||||
mavenCentral()
|
||||
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
|
||||
|
||||
`jlibghostty` ships GraalVM reachability metadata at:
|
||||
@@ -45,25 +121,19 @@ tasks.withType<JavaExec>().configureEach {
|
||||
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
|
||||
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:
|
||||
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
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, ... }:
|
||||
@@ -116,55 +186,133 @@ dependencies {
|
||||
}
|
||||
```
|
||||
|
||||
If the app runs on the module path, use:
|
||||
## Loading Native Libraries
|
||||
|
||||
```sh
|
||||
--enable-native-access=dev.jlibghostty
|
||||
```
|
||||
The library normally loads the bundled native `libghostty-vt` resource from the jar.
|
||||
|
||||
## External Native Library
|
||||
|
||||
The library normally loads the bundled native `libghostty-vt`. To override it:
|
||||
To override it with an external library:
|
||||
|
||||
```sh
|
||||
java -Djlibghostty.library.path=/path/to/libghostty-vt.so ...
|
||||
```
|
||||
|
||||
or set:
|
||||
or:
|
||||
|
||||
```sh
|
||||
export JLIBGHOSTTY_LIBRARY=/path/to/libghostty-vt.so
|
||||
```
|
||||
|
||||
## Example
|
||||
## Basic Terminal Usage
|
||||
|
||||
```java
|
||||
try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) {
|
||||
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
|
||||
|
||||
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`:
|
||||
`TerminalOptions.of(columns, rows)` defaults to `10_000` scrollback lines. Use the full constructor to choose a different limit:
|
||||
|
||||
```java
|
||||
MethodHandle handle = GhosttyNative.downcall(
|
||||
"ghostty_render_state_new",
|
||||
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS)
|
||||
);
|
||||
try (Terminal terminal = Ghostty.open(new TerminalOptions(80, 24, 50_000))) {
|
||||
terminal.write(outputBytes);
|
||||
}
|
||||
```
|
||||
|
||||
`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.
|
||||
## Render State
|
||||
|
||||
Build metadata and C struct layout JSON are exposed directly:
|
||||
Use `renderSnapshot()` for renderer-oriented state:
|
||||
|
||||
```java
|
||||
GhosttyBuildInfo info = Ghostty.buildInfo();
|
||||
String typeJson = Ghostty.typeJson();
|
||||
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);
|
||||
}
|
||||
```
|
||||
|
||||
Call `syncFromTerminal(terminal)` after terminal mode changes, or before encoding a batch of UI events.
|
||||
|
||||
## Kitty Graphics
|
||||
|
||||
Kitty graphics storage can be enabled and inspected:
|
||||
@@ -179,21 +327,47 @@ try (Terminal terminal = Ghostty.open(TerminalOptions.of(80, 24))) {
|
||||
terminal.write(kittyGraphicsSequenceBytes);
|
||||
|
||||
for (KittyPlacement placement : terminal.kittyGraphics().orElseThrow().placements()) {
|
||||
placement.image().ifPresent(image -> {
|
||||
// Hand image.data() and placement.renderInfo() to your renderer.
|
||||
});
|
||||
Optional<KittyImageSnapshot> image = placement.image();
|
||||
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.
|
||||
|
||||
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
|
||||
nix develop
|
||||
The static `Ghostty` facade exposes small helpers:
|
||||
|
||||
```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.
|
||||
|
||||
Reference in New Issue
Block a user