scrollback, mouse
This commit is contained in:
37
src/main/java/dev/jlibghostty/KeyModifiers.java
Normal file
37
src/main/java/dev/jlibghostty/KeyModifiers.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.jlibghostty;
|
||||||
|
|
||||||
|
public record KeyModifiers(int mask) {
|
||||||
|
public static final int SHIFT = 1 << 0;
|
||||||
|
public static final int CTRL = 1 << 1;
|
||||||
|
public static final int ALT = 1 << 2;
|
||||||
|
public static final int SUPER = 1 << 3;
|
||||||
|
public static final int CAPS_LOCK = 1 << 4;
|
||||||
|
public static final int NUM_LOCK = 1 << 5;
|
||||||
|
|
||||||
|
public KeyModifiers {
|
||||||
|
if (mask < 0 || mask > 0xffff) {
|
||||||
|
throw new IllegalArgumentException("modifier mask must fit in uint16_t");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyModifiers none() {
|
||||||
|
return new KeyModifiers(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyModifiers of(boolean shift, boolean ctrl, boolean alt, boolean superKey) {
|
||||||
|
int mask = 0;
|
||||||
|
if (shift) {
|
||||||
|
mask |= SHIFT;
|
||||||
|
}
|
||||||
|
if (ctrl) {
|
||||||
|
mask |= CTRL;
|
||||||
|
}
|
||||||
|
if (alt) {
|
||||||
|
mask |= ALT;
|
||||||
|
}
|
||||||
|
if (superKey) {
|
||||||
|
mask |= SUPER;
|
||||||
|
}
|
||||||
|
return new KeyModifiers(mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/dev/jlibghostty/MouseAction.java
Normal file
17
src/main/java/dev/jlibghostty/MouseAction.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package dev.jlibghostty;
|
||||||
|
|
||||||
|
public enum MouseAction {
|
||||||
|
PRESS(0),
|
||||||
|
RELEASE(1),
|
||||||
|
MOTION(2);
|
||||||
|
|
||||||
|
private final int nativeValue;
|
||||||
|
|
||||||
|
MouseAction(int nativeValue) {
|
||||||
|
this.nativeValue = nativeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nativeValue() {
|
||||||
|
return nativeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/main/java/dev/jlibghostty/MouseButton.java
Normal file
26
src/main/java/dev/jlibghostty/MouseButton.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package dev.jlibghostty;
|
||||||
|
|
||||||
|
public enum MouseButton {
|
||||||
|
UNKNOWN(0),
|
||||||
|
LEFT(1),
|
||||||
|
RIGHT(2),
|
||||||
|
MIDDLE(3),
|
||||||
|
FOUR(4),
|
||||||
|
FIVE(5),
|
||||||
|
SIX(6),
|
||||||
|
SEVEN(7),
|
||||||
|
EIGHT(8),
|
||||||
|
NINE(9),
|
||||||
|
TEN(10),
|
||||||
|
ELEVEN(11);
|
||||||
|
|
||||||
|
private final int nativeValue;
|
||||||
|
|
||||||
|
MouseButton(int nativeValue) {
|
||||||
|
this.nativeValue = nativeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nativeValue() {
|
||||||
|
return nativeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/main/java/dev/jlibghostty/MouseEncoder.java
Normal file
61
src/main/java/dev/jlibghostty/MouseEncoder.java
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package dev.jlibghostty;
|
||||||
|
|
||||||
|
import dev.jlibghostty.internal.GhosttyLibrary;
|
||||||
|
|
||||||
|
import java.lang.foreign.MemorySegment;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public final class MouseEncoder implements AutoCloseable {
|
||||||
|
private final GhosttyLibrary library;
|
||||||
|
private final MemorySegment handle;
|
||||||
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
|
|
||||||
|
public MouseEncoder() {
|
||||||
|
library = GhosttyLibrary.loadDefault();
|
||||||
|
handle = library.mouseEncoderNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncFromTerminal(Terminal terminal) {
|
||||||
|
ensureOpen();
|
||||||
|
terminal.ensureOpenForPackage();
|
||||||
|
library.mouseEncoderSetOptFromTerminal(handle, terminal.handleForPackage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(MouseEncoderSize size) {
|
||||||
|
ensureOpen();
|
||||||
|
library.mouseEncoderSetSize(handle, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnyButtonPressed(boolean pressed) {
|
||||||
|
ensureOpen();
|
||||||
|
library.mouseEncoderSetBoolean(handle, GhosttyLibrary.MOUSE_ENCODER_OPT_ANY_BUTTON_PRESSED, pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTrackLastCell(boolean enabled) {
|
||||||
|
ensureOpen();
|
||||||
|
library.mouseEncoderSetBoolean(handle, GhosttyLibrary.MOUSE_ENCODER_OPT_TRACK_LAST_CELL, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
ensureOpen();
|
||||||
|
library.mouseEncoderReset(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encode(MouseInput input) {
|
||||||
|
ensureOpen();
|
||||||
|
return library.mouseEncoderEncode(handle, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (closed.compareAndSet(false, true)) {
|
||||||
|
library.mouseEncoderFree(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureOpen() {
|
||||||
|
if (closed.get()) {
|
||||||
|
throw new IllegalStateException("MouseEncoder is closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/main/java/dev/jlibghostty/MouseEncoderSize.java
Normal file
36
src/main/java/dev/jlibghostty/MouseEncoderSize.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package dev.jlibghostty;
|
||||||
|
|
||||||
|
public record MouseEncoderSize(
|
||||||
|
long screenWidth,
|
||||||
|
long screenHeight,
|
||||||
|
long cellWidth,
|
||||||
|
long cellHeight,
|
||||||
|
long paddingTop,
|
||||||
|
long paddingBottom,
|
||||||
|
long paddingRight,
|
||||||
|
long paddingLeft
|
||||||
|
) {
|
||||||
|
public MouseEncoderSize {
|
||||||
|
checkU32("screenWidth", screenWidth);
|
||||||
|
checkU32("screenHeight", screenHeight);
|
||||||
|
checkU32("cellWidth", cellWidth);
|
||||||
|
checkU32("cellHeight", cellHeight);
|
||||||
|
checkU32("paddingTop", paddingTop);
|
||||||
|
checkU32("paddingBottom", paddingBottom);
|
||||||
|
checkU32("paddingRight", paddingRight);
|
||||||
|
checkU32("paddingLeft", paddingLeft);
|
||||||
|
if (cellWidth == 0 || cellHeight == 0) {
|
||||||
|
throw new IllegalArgumentException("cell dimensions must be non-zero");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MouseEncoderSize of(long screenWidth, long screenHeight, long cellWidth, long cellHeight) {
|
||||||
|
return new MouseEncoderSize(screenWidth, screenHeight, cellWidth, cellHeight, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkU32(String name, long value) {
|
||||||
|
if (value < 0 || value > 0xffffffffL) {
|
||||||
|
throw new IllegalArgumentException(name + " must fit in uint32_t");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/java/dev/jlibghostty/MouseInput.java
Normal file
32
src/main/java/dev/jlibghostty/MouseInput.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package dev.jlibghostty;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public record MouseInput(
|
||||||
|
MouseAction action,
|
||||||
|
Optional<MouseButton> button,
|
||||||
|
KeyModifiers modifiers,
|
||||||
|
double x,
|
||||||
|
double y
|
||||||
|
) {
|
||||||
|
public MouseInput {
|
||||||
|
button = button == null ? Optional.empty() : button;
|
||||||
|
modifiers = modifiers == null ? KeyModifiers.none() : modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MouseInput press(MouseButton button, double x, double y, KeyModifiers modifiers) {
|
||||||
|
return new MouseInput(MouseAction.PRESS, Optional.of(button), modifiers, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MouseInput release(MouseButton button, double x, double y, KeyModifiers modifiers) {
|
||||||
|
return new MouseInput(MouseAction.RELEASE, Optional.of(button), modifiers, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MouseInput motion(double x, double y, KeyModifiers modifiers) {
|
||||||
|
return new MouseInput(MouseAction.MOTION, Optional.empty(), modifiers, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MouseInput drag(MouseButton button, double x, double y, KeyModifiers modifiers) {
|
||||||
|
return new MouseInput(MouseAction.MOTION, Optional.of(button), modifiers, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/main/java/dev/jlibghostty/ScrollViewport.java
Normal file
47
src/main/java/dev/jlibghostty/ScrollViewport.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package dev.jlibghostty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes how libghostty should move the terminal viewport through scrollback.
|
||||||
|
*/
|
||||||
|
public record ScrollViewport(Type type, long delta) {
|
||||||
|
public ScrollViewport {
|
||||||
|
if (type == null) {
|
||||||
|
throw new NullPointerException("type");
|
||||||
|
}
|
||||||
|
if (type != Type.DELTA && delta != 0) {
|
||||||
|
throw new IllegalArgumentException("delta is only valid for DELTA scroll viewport behavior");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScrollViewport top() {
|
||||||
|
return new ScrollViewport(Type.TOP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScrollViewport bottom() {
|
||||||
|
return new ScrollViewport(Type.BOTTOM, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScrollViewport delta(long rows) {
|
||||||
|
return new ScrollViewport(Type.DELTA, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nativeTag() {
|
||||||
|
return type.nativeValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
TOP(0),
|
||||||
|
BOTTOM(1),
|
||||||
|
DELTA(2);
|
||||||
|
|
||||||
|
private final int nativeValue;
|
||||||
|
|
||||||
|
Type(int nativeValue) {
|
||||||
|
this.nativeValue = nativeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nativeValue() {
|
||||||
|
return nativeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,6 +102,11 @@ public final class Terminal implements AutoCloseable {
|
|||||||
library.terminalResize(handle, columns, rows, cellWidthPx, cellHeightPx);
|
library.terminalResize(handle, columns, rows, cellWidthPx, cellHeightPx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void scrollViewport(ScrollViewport behavior) {
|
||||||
|
ensureOpen();
|
||||||
|
library.terminalScrollViewport(handle, behavior);
|
||||||
|
}
|
||||||
|
|
||||||
public void setKittyImageStorageLimit(long bytes) {
|
public void setKittyImageStorageLimit(long bytes) {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
library.terminalSetU64(handle, GhosttyLibrary.TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT, bytes);
|
library.terminalSetU64(handle, GhosttyLibrary.TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT, bytes);
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import dev.jlibghostty.GhosttyBuildInfo;
|
|||||||
import dev.jlibghostty.GhosttyException;
|
import dev.jlibghostty.GhosttyException;
|
||||||
import dev.jlibghostty.DeviceAttributes;
|
import dev.jlibghostty.DeviceAttributes;
|
||||||
import dev.jlibghostty.DeviceAttributesProvider;
|
import dev.jlibghostty.DeviceAttributesProvider;
|
||||||
|
import dev.jlibghostty.MouseEncoderSize;
|
||||||
|
import dev.jlibghostty.MouseInput;
|
||||||
import dev.jlibghostty.OptimizeMode;
|
import dev.jlibghostty.OptimizeMode;
|
||||||
import dev.jlibghostty.PtyWriter;
|
import dev.jlibghostty.PtyWriter;
|
||||||
import dev.jlibghostty.RenderCell;
|
import dev.jlibghostty.RenderCell;
|
||||||
import dev.jlibghostty.RenderColor;
|
import dev.jlibghostty.RenderColor;
|
||||||
import dev.jlibghostty.RenderRow;
|
import dev.jlibghostty.RenderRow;
|
||||||
|
import dev.jlibghostty.ScrollViewport;
|
||||||
import dev.jlibghostty.SizeReportSize;
|
import dev.jlibghostty.SizeReportSize;
|
||||||
import dev.jlibghostty.TerminalOptions;
|
import dev.jlibghostty.TerminalOptions;
|
||||||
|
|
||||||
@@ -95,6 +98,10 @@ public final class GhosttyLibrary {
|
|||||||
public static final int RENDER_STATE_ROW_CELLS_DATA_FG_COLOR = 6;
|
public static final int RENDER_STATE_ROW_CELLS_DATA_FG_COLOR = 6;
|
||||||
public static final int RENDER_STATE_ROW_CELLS_DATA_SELECTED = 7;
|
public static final int RENDER_STATE_ROW_CELLS_DATA_SELECTED = 7;
|
||||||
|
|
||||||
|
public static final int MOUSE_ENCODER_OPT_SIZE = 2;
|
||||||
|
public static final int MOUSE_ENCODER_OPT_ANY_BUTTON_PRESSED = 3;
|
||||||
|
public static final int MOUSE_ENCODER_OPT_TRACK_LAST_CELL = 4;
|
||||||
|
|
||||||
private static final int GHOSTTY_SUCCESS = 0;
|
private static final int GHOSTTY_SUCCESS = 0;
|
||||||
private static final int GHOSTTY_INVALID_VALUE = -2;
|
private static final int GHOSTTY_INVALID_VALUE = -2;
|
||||||
private static final int GHOSTTY_OUT_OF_SPACE = -3;
|
private static final int GHOSTTY_OUT_OF_SPACE = -3;
|
||||||
@@ -146,6 +153,32 @@ public final class GhosttyLibrary {
|
|||||||
C_INT.withName("cell_height")
|
C_INT.withName("cell_height")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private static final GroupLayout SCROLL_VIEWPORT = MemoryLayout.structLayout(
|
||||||
|
C_INT.withName("tag"),
|
||||||
|
MemoryLayout.paddingLayout(4),
|
||||||
|
MemoryLayout.structLayout(
|
||||||
|
C_LONG_LONG.withName("delta"),
|
||||||
|
C_LONG_LONG.withName("padding")
|
||||||
|
).withName("value")
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final GroupLayout MOUSE_POSITION = MemoryLayout.structLayout(
|
||||||
|
ValueLayout.JAVA_FLOAT.withName("x"),
|
||||||
|
ValueLayout.JAVA_FLOAT.withName("y")
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final GroupLayout MOUSE_ENCODER_SIZE = MemoryLayout.structLayout(
|
||||||
|
C_SIZE_T.withName("size"),
|
||||||
|
C_INT.withName("screen_width"),
|
||||||
|
C_INT.withName("screen_height"),
|
||||||
|
C_INT.withName("cell_width"),
|
||||||
|
C_INT.withName("cell_height"),
|
||||||
|
C_INT.withName("padding_top"),
|
||||||
|
C_INT.withName("padding_bottom"),
|
||||||
|
C_INT.withName("padding_right"),
|
||||||
|
C_INT.withName("padding_left")
|
||||||
|
);
|
||||||
|
|
||||||
private static final GroupLayout KITTY_RENDER_INFO = MemoryLayout.structLayout(
|
private static final GroupLayout KITTY_RENDER_INFO = MemoryLayout.structLayout(
|
||||||
C_SIZE_T.withName("size"),
|
C_SIZE_T.withName("size"),
|
||||||
C_INT.withName("pixel_width"),
|
C_INT.withName("pixel_width"),
|
||||||
@@ -200,6 +233,7 @@ public final class GhosttyLibrary {
|
|||||||
private final MethodHandle terminalFree;
|
private final MethodHandle terminalFree;
|
||||||
private final MethodHandle terminalReset;
|
private final MethodHandle terminalReset;
|
||||||
private final MethodHandle terminalResize;
|
private final MethodHandle terminalResize;
|
||||||
|
private final MethodHandle terminalScrollViewport;
|
||||||
private final MethodHandle terminalVtWrite;
|
private final MethodHandle terminalVtWrite;
|
||||||
private final MethodHandle terminalSet;
|
private final MethodHandle terminalSet;
|
||||||
private final MethodHandle terminalGet;
|
private final MethodHandle terminalGet;
|
||||||
@@ -225,6 +259,19 @@ public final class GhosttyLibrary {
|
|||||||
private final MethodHandle renderStateRowCellsFree;
|
private final MethodHandle renderStateRowCellsFree;
|
||||||
private final MethodHandle renderStateRowCellsNext;
|
private final MethodHandle renderStateRowCellsNext;
|
||||||
private final MethodHandle renderStateRowCellsGet;
|
private final MethodHandle renderStateRowCellsGet;
|
||||||
|
private final MethodHandle mouseEventNew;
|
||||||
|
private final MethodHandle mouseEventFree;
|
||||||
|
private final MethodHandle mouseEventSetAction;
|
||||||
|
private final MethodHandle mouseEventSetButton;
|
||||||
|
private final MethodHandle mouseEventClearButton;
|
||||||
|
private final MethodHandle mouseEventSetMods;
|
||||||
|
private final MethodHandle mouseEventSetPosition;
|
||||||
|
private final MethodHandle mouseEncoderNew;
|
||||||
|
private final MethodHandle mouseEncoderFree;
|
||||||
|
private final MethodHandle mouseEncoderSetOpt;
|
||||||
|
private final MethodHandle mouseEncoderSetOptFromTerminal;
|
||||||
|
private final MethodHandle mouseEncoderReset;
|
||||||
|
private final MethodHandle mouseEncoderEncode;
|
||||||
private final MethodHandle kittyGraphicsGet;
|
private final MethodHandle kittyGraphicsGet;
|
||||||
private final MethodHandle kittyGraphicsImage;
|
private final MethodHandle kittyGraphicsImage;
|
||||||
private final MethodHandle kittyGraphicsImageGet;
|
private final MethodHandle kittyGraphicsImageGet;
|
||||||
@@ -247,6 +294,8 @@ public final class GhosttyLibrary {
|
|||||||
FunctionDescriptor.ofVoid(C_POINTER));
|
FunctionDescriptor.ofVoid(C_POINTER));
|
||||||
terminalResize = downcall(symbols, "ghostty_terminal_resize",
|
terminalResize = downcall(symbols, "ghostty_terminal_resize",
|
||||||
FunctionDescriptor.of(C_INT, C_POINTER, C_SHORT, C_SHORT, C_INT, C_INT));
|
FunctionDescriptor.of(C_INT, C_POINTER, C_SHORT, C_SHORT, C_INT, C_INT));
|
||||||
|
terminalScrollViewport = downcall(symbols, "ghostty_terminal_scroll_viewport",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER, SCROLL_VIEWPORT));
|
||||||
terminalVtWrite = downcall(symbols, "ghostty_terminal_vt_write",
|
terminalVtWrite = downcall(symbols, "ghostty_terminal_vt_write",
|
||||||
FunctionDescriptor.ofVoid(C_POINTER, C_POINTER, C_SIZE_T));
|
FunctionDescriptor.ofVoid(C_POINTER, C_POINTER, C_SIZE_T));
|
||||||
terminalSet = downcall(symbols, "ghostty_terminal_set",
|
terminalSet = downcall(symbols, "ghostty_terminal_set",
|
||||||
@@ -297,6 +346,32 @@ public final class GhosttyLibrary {
|
|||||||
FunctionDescriptor.of(C_BOOL, C_POINTER));
|
FunctionDescriptor.of(C_BOOL, C_POINTER));
|
||||||
renderStateRowCellsGet = downcall(symbols, "ghostty_render_state_row_cells_get",
|
renderStateRowCellsGet = downcall(symbols, "ghostty_render_state_row_cells_get",
|
||||||
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
|
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
|
||||||
|
mouseEventNew = downcall(symbols, "ghostty_mouse_event_new",
|
||||||
|
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER));
|
||||||
|
mouseEventFree = downcall(symbols, "ghostty_mouse_event_free",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER));
|
||||||
|
mouseEventSetAction = downcall(symbols, "ghostty_mouse_event_set_action",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER, C_INT));
|
||||||
|
mouseEventSetButton = downcall(symbols, "ghostty_mouse_event_set_button",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER, C_INT));
|
||||||
|
mouseEventClearButton = downcall(symbols, "ghostty_mouse_event_clear_button",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER));
|
||||||
|
mouseEventSetMods = downcall(symbols, "ghostty_mouse_event_set_mods",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER, C_SHORT));
|
||||||
|
mouseEventSetPosition = downcall(symbols, "ghostty_mouse_event_set_position",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER, MOUSE_POSITION));
|
||||||
|
mouseEncoderNew = downcall(symbols, "ghostty_mouse_encoder_new",
|
||||||
|
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER));
|
||||||
|
mouseEncoderFree = downcall(symbols, "ghostty_mouse_encoder_free",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER));
|
||||||
|
mouseEncoderSetOpt = downcall(symbols, "ghostty_mouse_encoder_setopt",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER, C_INT, C_POINTER));
|
||||||
|
mouseEncoderSetOptFromTerminal = downcall(symbols, "ghostty_mouse_encoder_setopt_from_terminal",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER, C_POINTER));
|
||||||
|
mouseEncoderReset = downcall(symbols, "ghostty_mouse_encoder_reset",
|
||||||
|
FunctionDescriptor.ofVoid(C_POINTER));
|
||||||
|
mouseEncoderEncode = downcall(symbols, "ghostty_mouse_encoder_encode",
|
||||||
|
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_SIZE_T, C_POINTER));
|
||||||
kittyGraphicsGet = downcall(symbols, "ghostty_kitty_graphics_get",
|
kittyGraphicsGet = downcall(symbols, "ghostty_kitty_graphics_get",
|
||||||
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
|
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
|
||||||
kittyGraphicsImage = downcall(symbols, "ghostty_kitty_graphics_image",
|
kittyGraphicsImage = downcall(symbols, "ghostty_kitty_graphics_image",
|
||||||
@@ -381,6 +456,18 @@ public final class GhosttyLibrary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void terminalScrollViewport(MemorySegment terminal, ScrollViewport behavior) {
|
||||||
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
|
MemorySegment nativeBehavior = arena.allocate(SCROLL_VIEWPORT);
|
||||||
|
nativeBehavior.set(C_INT, 0, behavior.nativeTag());
|
||||||
|
nativeBehavior.set(C_LONG_LONG, 8, behavior.delta());
|
||||||
|
nativeBehavior.set(C_LONG_LONG, 16, 0);
|
||||||
|
terminalScrollViewport.invoke(terminal, nativeBehavior);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void terminalWrite(MemorySegment terminal, byte[] data) {
|
public void terminalWrite(MemorySegment terminal, byte[] data) {
|
||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
return;
|
return;
|
||||||
@@ -867,6 +954,129 @@ public final class GhosttyLibrary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MemorySegment mouseEncoderNew() {
|
||||||
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
|
MemorySegment out = arena.allocate(C_POINTER);
|
||||||
|
int result = (int) mouseEncoderNew.invoke(MemorySegment.NULL, out);
|
||||||
|
checkResult("ghostty_mouse_encoder_new", result);
|
||||||
|
|
||||||
|
MemorySegment encoder = out.get(C_POINTER, 0);
|
||||||
|
if (encoder.address() == 0) {
|
||||||
|
throw new IllegalStateException("ghostty_mouse_encoder_new returned null");
|
||||||
|
}
|
||||||
|
return encoder;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseEncoderFree(MemorySegment encoder) {
|
||||||
|
try {
|
||||||
|
mouseEncoderFree.invoke(encoder);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseEncoderSetOptFromTerminal(MemorySegment encoder, MemorySegment terminal) {
|
||||||
|
try {
|
||||||
|
mouseEncoderSetOptFromTerminal.invoke(encoder, terminal);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseEncoderSetSize(MemorySegment encoder, MouseEncoderSize size) {
|
||||||
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
|
MemorySegment nativeSize = arena.allocate(MOUSE_ENCODER_SIZE);
|
||||||
|
nativeSize.set(C_SIZE_T, 0, MOUSE_ENCODER_SIZE.byteSize());
|
||||||
|
nativeSize.set(C_INT, 8, (int) size.screenWidth());
|
||||||
|
nativeSize.set(C_INT, 12, (int) size.screenHeight());
|
||||||
|
nativeSize.set(C_INT, 16, (int) size.cellWidth());
|
||||||
|
nativeSize.set(C_INT, 20, (int) size.cellHeight());
|
||||||
|
nativeSize.set(C_INT, 24, (int) size.paddingTop());
|
||||||
|
nativeSize.set(C_INT, 28, (int) size.paddingBottom());
|
||||||
|
nativeSize.set(C_INT, 32, (int) size.paddingRight());
|
||||||
|
nativeSize.set(C_INT, 36, (int) size.paddingLeft());
|
||||||
|
mouseEncoderSetOpt.invoke(encoder, MOUSE_ENCODER_OPT_SIZE, nativeSize);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseEncoderSetBoolean(MemorySegment encoder, int option, boolean value) {
|
||||||
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
|
MemorySegment nativeValue = arena.allocate(C_BOOL);
|
||||||
|
nativeValue.set(C_BOOL, 0, value);
|
||||||
|
mouseEncoderSetOpt.invoke(encoder, option, nativeValue);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseEncoderReset(MemorySegment encoder) {
|
||||||
|
try {
|
||||||
|
mouseEncoderReset.invoke(encoder);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] mouseEncoderEncode(MemorySegment encoder, MouseInput input) {
|
||||||
|
MemorySegment event = mouseEventNew();
|
||||||
|
try {
|
||||||
|
configureMouseEvent(event, input);
|
||||||
|
return encodeBuffer("ghostty_mouse_encoder_encode", (arena, out, outLen, outWritten) ->
|
||||||
|
(int) mouseEncoderEncode.invoke(encoder, event, out, outLen, outWritten)
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
mouseEventFree(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemorySegment mouseEventNew() {
|
||||||
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
|
MemorySegment out = arena.allocate(C_POINTER);
|
||||||
|
int result = (int) mouseEventNew.invoke(MemorySegment.NULL, out);
|
||||||
|
checkResult("ghostty_mouse_event_new", result);
|
||||||
|
|
||||||
|
MemorySegment event = out.get(C_POINTER, 0);
|
||||||
|
if (event.address() == 0) {
|
||||||
|
throw new IllegalStateException("ghostty_mouse_event_new returned null");
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mouseEventFree(MemorySegment event) {
|
||||||
|
try {
|
||||||
|
mouseEventFree.invoke(event);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureMouseEvent(MemorySegment event, MouseInput input) {
|
||||||
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
|
mouseEventSetAction.invoke(event, input.action().nativeValue());
|
||||||
|
if (input.button().isPresent()) {
|
||||||
|
mouseEventSetButton.invoke(event, input.button().orElseThrow().nativeValue());
|
||||||
|
} else {
|
||||||
|
mouseEventClearButton.invoke(event);
|
||||||
|
}
|
||||||
|
mouseEventSetMods.invoke(event, (short) input.modifiers().mask());
|
||||||
|
|
||||||
|
MemorySegment position = arena.allocate(MOUSE_POSITION);
|
||||||
|
position.set(ValueLayout.JAVA_FLOAT, 0, (float) input.x());
|
||||||
|
position.set(ValueLayout.JAVA_FLOAT, 4, (float) input.y());
|
||||||
|
mouseEventSetPosition.invoke(event, position);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
rethrow(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<RenderColor> renderStateRowCellColor(MemorySegment cells, int key) {
|
private Optional<RenderColor> renderStateRowCellColor(MemorySegment cells, int key) {
|
||||||
try (Arena arena = Arena.ofConfined()) {
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
MemorySegment out = arena.allocate(3, 1);
|
MemorySegment out = arena.allocate(3, 1);
|
||||||
|
|||||||
@@ -57,6 +57,13 @@
|
|||||||
"size_t"
|
"size_t"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"returnType": "void",
|
||||||
|
"parameterTypes": [
|
||||||
|
"void*",
|
||||||
|
"struct(int, padding(4), struct(long long, long long))"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"returnType": "int",
|
"returnType": "int",
|
||||||
"parameterTypes": [
|
"parameterTypes": [
|
||||||
@@ -177,6 +184,35 @@
|
|||||||
"void*",
|
"void*",
|
||||||
"void*"
|
"void*"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"returnType": "void",
|
||||||
|
"parameterTypes": [
|
||||||
|
"void*",
|
||||||
|
"int"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"returnType": "void",
|
||||||
|
"parameterTypes": [
|
||||||
|
"void*",
|
||||||
|
"short"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"returnType": "void",
|
||||||
|
"parameterTypes": [
|
||||||
|
"void*",
|
||||||
|
"struct(float, float)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"returnType": "void",
|
||||||
|
"parameterTypes": [
|
||||||
|
"void*",
|
||||||
|
"int",
|
||||||
|
"void*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,21 +39,46 @@ public final class GhosttySmokeTest {
|
|||||||
terminal.setDeviceAttributesProvider(DeviceAttributes::xtermCompatible);
|
terminal.setDeviceAttributesProvider(DeviceAttributes::xtermCompatible);
|
||||||
terminal.setKittyImageStorageLimit(1024 * 1024);
|
terminal.setKittyImageStorageLimit(1024 * 1024);
|
||||||
terminal.setKittyImageMediumFile(true);
|
terminal.setKittyImageMediumFile(true);
|
||||||
terminal.write("hello\r\n\u001b[5n\u001b[6n\u001b[c");
|
terminal.write("hello\r\n\u001b[?1000h\u001b[?1006h\u001b[5n\u001b[6n\u001b[c");
|
||||||
String responses = ptyResponses.toString(StandardCharsets.UTF_8);
|
String responses = ptyResponses.toString(StandardCharsets.UTF_8);
|
||||||
if (!responses.contains("\u001b[0n")
|
if (!responses.contains("\u001b[0n")
|
||||||
|| !responses.contains("\u001b[")
|
|| !responses.contains("\u001b[")
|
||||||
|| !responses.contains("c")) {
|
|| !responses.contains("c")) {
|
||||||
throw new AssertionError("expected PTY query responses, got: " + responses);
|
throw new AssertionError("expected PTY query responses, got: " + responses);
|
||||||
}
|
}
|
||||||
|
try (MouseEncoder mouseEncoder = new MouseEncoder()) {
|
||||||
|
mouseEncoder.syncFromTerminal(terminal);
|
||||||
|
mouseEncoder.setSize(MouseEncoderSize.of(640, 384, 8, 16));
|
||||||
|
byte[] mouseBytes = mouseEncoder.encode(MouseInput.press(
|
||||||
|
MouseButton.LEFT,
|
||||||
|
12,
|
||||||
|
20,
|
||||||
|
KeyModifiers.none()
|
||||||
|
));
|
||||||
|
if (mouseBytes.length == 0) {
|
||||||
|
throw new AssertionError("mouse encoder should emit bytes when tracking is enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!terminal.text().contains("hello")) {
|
if (!terminal.text().contains("hello")) {
|
||||||
throw new AssertionError("formatted terminal text should contain written text: " + terminal.text());
|
throw new AssertionError("formatted terminal text should contain written text: " + terminal.text());
|
||||||
}
|
}
|
||||||
|
terminal.write(scrollbackFixture());
|
||||||
|
terminal.scrollViewport(ScrollViewport.delta(-10));
|
||||||
|
String scrolledText = renderText(terminal.renderSnapshot());
|
||||||
|
if (scrolledText.contains("scroll-line-39")) {
|
||||||
|
throw new AssertionError("scrolled viewport should move away from bottom");
|
||||||
|
}
|
||||||
|
terminal.scrollViewport(ScrollViewport.bottom());
|
||||||
|
if (!renderText(terminal.renderSnapshot()).contains("scroll-line-39")) {
|
||||||
|
throw new AssertionError("bottom viewport should show latest text");
|
||||||
|
}
|
||||||
|
terminal.scrollViewport(ScrollViewport.top());
|
||||||
|
terminal.scrollViewport(ScrollViewport.bottom());
|
||||||
RenderStateSnapshot renderSnapshot = terminal.renderSnapshot();
|
RenderStateSnapshot renderSnapshot = terminal.renderSnapshot();
|
||||||
boolean renderedHello = renderSnapshot.renderRows().stream()
|
boolean renderedLatestLine = renderSnapshot.renderRows().stream()
|
||||||
.anyMatch(row -> row.text().contains("hello"));
|
.anyMatch(row -> row.text().contains("scroll-line-39"));
|
||||||
if (!renderedHello) {
|
if (!renderedLatestLine) {
|
||||||
throw new AssertionError("render state should contain written text");
|
throw new AssertionError("render state should contain latest written text");
|
||||||
}
|
}
|
||||||
if (renderSnapshot.cursorViewportHasValue()) {
|
if (renderSnapshot.cursorViewportHasValue()) {
|
||||||
if (renderSnapshot.cursorViewportX() < 0 || renderSnapshot.cursorViewportX() >= renderSnapshot.columns()
|
if (renderSnapshot.cursorViewportX() < 0 || renderSnapshot.cursorViewportX() >= renderSnapshot.columns()
|
||||||
@@ -84,4 +109,20 @@ public final class GhosttySmokeTest {
|
|||||||
throw new AssertionError("invalid color: " + color);
|
throw new AssertionError("invalid color: " + color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String renderText(RenderStateSnapshot snapshot) {
|
||||||
|
StringBuilder text = new StringBuilder();
|
||||||
|
for (RenderRow row : snapshot.renderRows()) {
|
||||||
|
text.append(row.text()).append('\n');
|
||||||
|
}
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String scrollbackFixture() {
|
||||||
|
StringBuilder text = new StringBuilder();
|
||||||
|
for (int i = 0; i < 40; i++) {
|
||||||
|
text.append("scroll-line-").append(i).append("\r\n");
|
||||||
|
}
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user