pretty good
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ devenv.local.yaml
|
|||||||
|
|
||||||
# pre-commit
|
# pre-commit
|
||||||
.pre-commit-config.yaml
|
.pre-commit-config.yaml
|
||||||
|
build
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -34,6 +34,8 @@ If `XDG_CONFIG_HOME` is unset, the fallback is:
|
|||||||
$HOME/.config/jprototerm/config.toml
|
$HOME/.config/jprototerm/config.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If no config file exists, jprototerm writes the default config on startup.
|
||||||
|
|
||||||
Example, also available in `config.example.toml`:
|
Example, also available in `config.example.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
@@ -60,6 +62,7 @@ toggle_floating = "ALT+F"
|
|||||||
new_floating = "ALT+SHIFT+F"
|
new_floating = "ALT+SHIFT+F"
|
||||||
next_floating = "ALT+F12"
|
next_floating = "ALT+F12"
|
||||||
close_pane = "ALT+X"
|
close_pane = "ALT+X"
|
||||||
|
open_font_selector = "ALT+T"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Defaults
|
## Defaults
|
||||||
@@ -69,5 +72,6 @@ close_pane = "ALT+X"
|
|||||||
- `Alt+Shift+f`: create a new floating pane
|
- `Alt+Shift+f`: create a new floating pane
|
||||||
- `Alt+F12`: cycle floating panes
|
- `Alt+F12`: cycle floating panes
|
||||||
- `Alt+x`: close the active floating pane
|
- `Alt+x`: close the active floating pane
|
||||||
|
- `Alt+t`: open the font selector
|
||||||
- Font default: `JetBrainsMono Nerd Font`
|
- Font default: `JetBrainsMono Nerd Font`
|
||||||
- Kitty graphics protocol parsing is enabled by default
|
- Kitty graphics protocol parsing is enabled by default
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -21,3 +21,4 @@ toggle_floating = "ALT+F"
|
|||||||
new_floating = "ALT+SHIFT+F"
|
new_floating = "ALT+SHIFT+F"
|
||||||
next_floating = "ALT+F12"
|
next_floating = "ALT+F12"
|
||||||
close_pane = "ALT+X"
|
close_pane = "ALT+X"
|
||||||
|
open_font_selector = "ALT+T"
|
||||||
|
|||||||
22
devenv.nix
22
devenv.nix
@@ -7,6 +7,7 @@ let
|
|||||||
"git+https://gitea.gregorlohaus.com/gregor/jlibghostty.git";
|
"git+https://gitea.gregorlohaus.com/gregor/jlibghostty.git";
|
||||||
|
|
||||||
jlib = jlibghostty.packages.${system}.jlibghostty;
|
jlib = jlibghostty.packages.${system}.jlibghostty;
|
||||||
|
hostNvidiaLibs = ".devenv/host-nvidia-libs";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages = [
|
packages = [
|
||||||
@@ -26,9 +27,10 @@ in
|
|||||||
pkgs.libGL
|
pkgs.libGL
|
||||||
pkgs.gtk3
|
pkgs.gtk3
|
||||||
pkgs.alsa-lib
|
pkgs.alsa-lib
|
||||||
|
pkgs.mesa-demos
|
||||||
];
|
];
|
||||||
|
|
||||||
env.LD_LIBRARY_PATH = lib.makeLibraryPath [
|
env.LD_LIBRARY_PATH = "${hostNvidiaLibs}:" + lib.makeLibraryPath [
|
||||||
pkgs.openjfx
|
pkgs.openjfx
|
||||||
|
|
||||||
pkgs.glib
|
pkgs.glib
|
||||||
@@ -42,5 +44,23 @@ in
|
|||||||
pkgs.gtk3
|
pkgs.gtk3
|
||||||
pkgs.alsa-lib
|
pkgs.alsa-lib
|
||||||
] + ":/usr/lib/x86_64-linux-gnu/nvidia/current";
|
] + ":/usr/lib/x86_64-linux-gnu/nvidia/current";
|
||||||
|
env.__GLX_VENDOR_LIBRARY_NAME = "nvidia";
|
||||||
|
env.__EGL_VENDOR_LIBRARY_FILENAMES = "/usr/share/glvnd/egl_vendor.d/10_nvidia.json";
|
||||||
env.JLIBGHOSTTY_MAVEN_REPO = "${jlib}/maven";
|
env.JLIBGHOSTTY_MAVEN_REPO = "${jlib}/maven";
|
||||||
|
|
||||||
|
enterShell = ''
|
||||||
|
mkdir -p ${hostNvidiaLibs}
|
||||||
|
for lib in \
|
||||||
|
/usr/lib/x86_64-linux-gnu/libnvidia*.so* \
|
||||||
|
/usr/lib/x86_64-linux-gnu/libGLX_nvidia.so* \
|
||||||
|
/usr/lib/x86_64-linux-gnu/libEGL_nvidia.so* \
|
||||||
|
/usr/lib/x86_64-linux-gnu/nvidia/current/libnvidia*.so* \
|
||||||
|
/usr/lib/x86_64-linux-gnu/nvidia/current/libGLX_nvidia.so* \
|
||||||
|
/usr/lib/x86_64-linux-gnu/nvidia/current/libEGL_nvidia.so*
|
||||||
|
do
|
||||||
|
if [ -e "$lib" ]; then
|
||||||
|
ln -sfn "$lib" ${hostNvidiaLibs}/"$(basename "$lib")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import io.github.wasabithumb.jtoml.value.primitive.TomlPrimitive;
|
|||||||
import io.github.wasabithumb.jtoml.value.table.TomlTable;
|
import io.github.wasabithumb.jtoml.value.table.TomlTable;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public record AppConfig(
|
public record AppConfig(
|
||||||
@@ -22,10 +26,23 @@ public record AppConfig(
|
|||||||
boolean kittyGraphics,
|
boolean kittyGraphics,
|
||||||
Map<String, KeyBinding> keybindings
|
Map<String, KeyBinding> keybindings
|
||||||
) {
|
) {
|
||||||
|
private static final List<String> KEYBINDING_KEYS = List.of(
|
||||||
|
"navigate_left",
|
||||||
|
"navigate_down",
|
||||||
|
"navigate_up",
|
||||||
|
"navigate_right",
|
||||||
|
"toggle_floating",
|
||||||
|
"new_floating",
|
||||||
|
"next_floating",
|
||||||
|
"close_pane",
|
||||||
|
"open_font_selector"
|
||||||
|
);
|
||||||
|
|
||||||
public static AppConfig load() {
|
public static AppConfig load() {
|
||||||
AppConfig defaults = defaults();
|
AppConfig defaults = defaults();
|
||||||
Path path = configPath();
|
Path path = configPath();
|
||||||
if (!Files.isRegularFile(path)) {
|
if (!Files.isRegularFile(path)) {
|
||||||
|
writeDefaultConfig(path, defaults);
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,16 +57,7 @@ public record AppConfig(
|
|||||||
doubleValue(document, "window.width", defaults.windowWidth),
|
doubleValue(document, "window.width", defaults.windowWidth),
|
||||||
doubleValue(document, "window.height", defaults.windowHeight),
|
doubleValue(document, "window.height", defaults.windowHeight),
|
||||||
booleanValue(document, "kitty_graphics.enabled", defaults.kittyGraphics),
|
booleanValue(document, "kitty_graphics.enabled", defaults.kittyGraphics),
|
||||||
Map.of(
|
keybindings(document, defaults)
|
||||||
"navigate_left", binding(document, "keybindings.navigate_left", defaults.keybindings.get("navigate_left")),
|
|
||||||
"navigate_down", binding(document, "keybindings.navigate_down", defaults.keybindings.get("navigate_down")),
|
|
||||||
"navigate_up", binding(document, "keybindings.navigate_up", defaults.keybindings.get("navigate_up")),
|
|
||||||
"navigate_right", binding(document, "keybindings.navigate_right", defaults.keybindings.get("navigate_right")),
|
|
||||||
"toggle_floating", binding(document, "keybindings.toggle_floating", defaults.keybindings.get("toggle_floating")),
|
|
||||||
"new_floating", binding(document, "keybindings.new_floating", defaults.keybindings.get("new_floating")),
|
|
||||||
"next_floating", binding(document, "keybindings.next_floating", defaults.keybindings.get("next_floating")),
|
|
||||||
"close_pane", binding(document, "keybindings.close_pane", defaults.keybindings.get("close_pane"))
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} catch (TomlException ex) {
|
} catch (TomlException ex) {
|
||||||
System.err.println("Could not parse " + path + ": " + ex.getMessage());
|
System.err.println("Could not parse " + path + ": " + ex.getMessage());
|
||||||
@@ -75,11 +83,30 @@ public record AppConfig(
|
|||||||
"toggle_floating", KeyBinding.parse("ALT+F"),
|
"toggle_floating", KeyBinding.parse("ALT+F"),
|
||||||
"new_floating", KeyBinding.parse("ALT+SHIFT+F"),
|
"new_floating", KeyBinding.parse("ALT+SHIFT+F"),
|
||||||
"next_floating", KeyBinding.parse("ALT+F12"),
|
"next_floating", KeyBinding.parse("ALT+F12"),
|
||||||
"close_pane", KeyBinding.parse("ALT+X")
|
"close_pane", KeyBinding.parse("ALT+X"),
|
||||||
|
"open_font_selector", KeyBinding.parse("ALT+T")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AppConfig withFont(String family, double size) {
|
||||||
|
return new AppConfig(
|
||||||
|
columns,
|
||||||
|
rows,
|
||||||
|
shell,
|
||||||
|
family,
|
||||||
|
size,
|
||||||
|
windowWidth,
|
||||||
|
windowHeight,
|
||||||
|
kittyGraphics,
|
||||||
|
keybindings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
save(configPath(), this);
|
||||||
|
}
|
||||||
|
|
||||||
public static Path configPath() {
|
public static Path configPath() {
|
||||||
String configHome = System.getenv("XDG_CONFIG_HOME");
|
String configHome = System.getenv("XDG_CONFIG_HOME");
|
||||||
if (configHome != null && !configHome.isBlank()) {
|
if (configHome != null && !configHome.isBlank()) {
|
||||||
@@ -92,6 +119,76 @@ public record AppConfig(
|
|||||||
return "/bin/bash";
|
return "/bin/bash";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, KeyBinding> keybindings(TomlTable table, AppConfig defaults) {
|
||||||
|
Map<String, KeyBinding> parsed = new LinkedHashMap<>();
|
||||||
|
for (String key : KEYBINDING_KEYS) {
|
||||||
|
parsed.put(key, binding(table, "keybindings." + key, defaults.keybindings.get(key)));
|
||||||
|
}
|
||||||
|
return Map.copyOf(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeDefaultConfig(Path path, AppConfig defaults) {
|
||||||
|
save(path, defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void save(Path path, AppConfig config) {
|
||||||
|
try {
|
||||||
|
Path parent = path.getParent();
|
||||||
|
if (parent != null) {
|
||||||
|
Files.createDirectories(parent);
|
||||||
|
}
|
||||||
|
Files.writeString(
|
||||||
|
path,
|
||||||
|
config.toToml(),
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.TRUNCATE_EXISTING,
|
||||||
|
StandardOpenOption.WRITE
|
||||||
|
);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.err.println("Could not write " + path + ": " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toToml() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("[terminal]\n");
|
||||||
|
builder.append("columns = ").append(columns).append('\n');
|
||||||
|
builder.append("rows = ").append(rows).append('\n');
|
||||||
|
builder.append("shell = ").append(quoted(shell)).append('\n');
|
||||||
|
builder.append("font_family = ").append(quoted(fontFamily)).append('\n');
|
||||||
|
builder.append("font_size = ").append(trimDouble(fontSize)).append("\n\n");
|
||||||
|
builder.append("[window]\n");
|
||||||
|
builder.append("width = ").append(trimDouble(windowWidth)).append('\n');
|
||||||
|
builder.append("height = ").append(trimDouble(windowHeight)).append("\n\n");
|
||||||
|
builder.append("[kitty_graphics]\n");
|
||||||
|
builder.append("enabled = ").append(kittyGraphics).append("\n\n");
|
||||||
|
builder.append("[keybindings]\n");
|
||||||
|
for (String key : KEYBINDING_KEYS) {
|
||||||
|
KeyBinding binding = keybindings.get(key);
|
||||||
|
if (binding != null) {
|
||||||
|
builder.append(key).append(" = ").append(quoted(binding.toString())).append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String quoted(String value) {
|
||||||
|
return "\"" + value
|
||||||
|
.replace("\\", "\\\\")
|
||||||
|
.replace("\"", "\\\"")
|
||||||
|
.replace("\n", "\\n")
|
||||||
|
.replace("\r", "\\r")
|
||||||
|
.replace("\t", "\\t")
|
||||||
|
+ "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String trimDouble(double value) {
|
||||||
|
if (value == Math.rint(value)) {
|
||||||
|
return Long.toString((long) value);
|
||||||
|
}
|
||||||
|
return Double.toString(value);
|
||||||
|
}
|
||||||
|
|
||||||
private static KeyBinding binding(TomlTable table, String key, KeyBinding fallback) {
|
private static KeyBinding binding(TomlTable table, String key, KeyBinding fallback) {
|
||||||
String value = stringValue(table, key, null);
|
String value = stringValue(table, key, null);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
|||||||
@@ -35,6 +35,22 @@ public record KeyBinding(boolean alt, boolean control, boolean shift, KeyCode co
|
|||||||
&& event.getCode() == code;
|
&& event.getCode() == code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (control) {
|
||||||
|
builder.append("CTRL+");
|
||||||
|
}
|
||||||
|
if (alt) {
|
||||||
|
builder.append("ALT+");
|
||||||
|
}
|
||||||
|
if (shift) {
|
||||||
|
builder.append("SHIFT+");
|
||||||
|
}
|
||||||
|
builder.append(code.getName().toUpperCase(Locale.ROOT).replace(' ', '_'));
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private static KeyCode keyCode(String token) {
|
private static KeyCode keyCode(String token) {
|
||||||
KeyCode alias = switch (token) {
|
KeyCode alias = switch (token) {
|
||||||
case "GRAVE", "BACKTICK", "BACK_QUOTE", "`" -> KeyCode.BACK_QUOTE;
|
case "GRAVE", "BACKTICK", "BACK_QUOTE", "`" -> KeyCode.BACK_QUOTE;
|
||||||
|
|||||||
@@ -3,19 +3,29 @@ package com.gregor.jprototerm;
|
|||||||
import javafx.animation.AnimationTimer;
|
import javafx.animation.AnimationTimer;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.Dialog;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Spinner;
|
||||||
|
import javafx.scene.control.SpinnerValueFactory;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
public final class Main extends Application {
|
public final class Main extends Application {
|
||||||
private TerminalWorkspace workspace;
|
private TerminalWorkspace workspace;
|
||||||
|
private TerminalCanvasView terminalView;
|
||||||
|
private AppConfig config;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) {
|
public void start(Stage stage) {
|
||||||
AppConfig config = AppConfig.load();
|
config = AppConfig.load();
|
||||||
|
|
||||||
workspace = new TerminalWorkspace(config);
|
workspace = new TerminalWorkspace(config);
|
||||||
TerminalCanvasView terminalView = new TerminalCanvasView(workspace, config);
|
terminalView = new TerminalCanvasView(workspace, config);
|
||||||
|
|
||||||
StackPane root = new StackPane(terminalView.canvas());
|
StackPane root = new StackPane(terminalView.canvas());
|
||||||
terminalView.canvas().widthProperty().bind(root.widthProperty());
|
terminalView.canvas().widthProperty().bind(root.widthProperty());
|
||||||
@@ -23,7 +33,7 @@ public final class Main extends Application {
|
|||||||
terminalView.canvas().setOnMousePressed(event -> terminalView.canvas().requestFocus());
|
terminalView.canvas().setOnMousePressed(event -> terminalView.canvas().requestFocus());
|
||||||
|
|
||||||
Scene scene = new Scene(root, config.windowWidth(), config.windowHeight());
|
Scene scene = new Scene(root, config.windowWidth(), config.windowHeight());
|
||||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> handlePressed(config, event));
|
scene.addEventFilter(KeyEvent.KEY_PRESSED, this::handlePressed);
|
||||||
scene.addEventFilter(KeyEvent.KEY_TYPED, event -> handleTyped(event));
|
scene.addEventFilter(KeyEvent.KEY_TYPED, event -> handleTyped(event));
|
||||||
|
|
||||||
new AnimationTimer() {
|
new AnimationTimer() {
|
||||||
@@ -42,7 +52,7 @@ public final class Main extends Application {
|
|||||||
terminalView.canvas().requestFocus();
|
terminalView.canvas().requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePressed(AppConfig config, KeyEvent event) {
|
private void handlePressed(KeyEvent event) {
|
||||||
if (config.keybindings().get("navigate_left").matches(event)) {
|
if (config.keybindings().get("navigate_left").matches(event)) {
|
||||||
workspace.navigate(Direction.LEFT);
|
workspace.navigate(Direction.LEFT);
|
||||||
event.consume();
|
event.consume();
|
||||||
@@ -67,6 +77,9 @@ public final class Main extends Application {
|
|||||||
} else if (config.keybindings().get("close_pane").matches(event)) {
|
} else if (config.keybindings().get("close_pane").matches(event)) {
|
||||||
workspace.closeActivePane();
|
workspace.closeActivePane();
|
||||||
event.consume();
|
event.consume();
|
||||||
|
} else if (config.keybindings().get("open_font_selector").matches(event)) {
|
||||||
|
openFontSelector();
|
||||||
|
event.consume();
|
||||||
} else {
|
} else {
|
||||||
String encoded = KeyEncoder.encode(event);
|
String encoded = KeyEncoder.encode(event);
|
||||||
if (encoded != null) {
|
if (encoded != null) {
|
||||||
@@ -88,6 +101,49 @@ public final class Main extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openFontSelector() {
|
||||||
|
Dialog<ButtonType> dialog = new Dialog<>();
|
||||||
|
dialog.setTitle("Font");
|
||||||
|
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
|
||||||
|
|
||||||
|
ComboBox<String> family = new ComboBox<>();
|
||||||
|
family.getItems().setAll(Font.getFamilies());
|
||||||
|
family.setEditable(true);
|
||||||
|
family.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
family.setValue(config.fontFamily());
|
||||||
|
|
||||||
|
Spinner<Double> size = new Spinner<>();
|
||||||
|
size.setEditable(true);
|
||||||
|
size.setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(6.0, 48.0, config.fontSize(), 0.5));
|
||||||
|
|
||||||
|
GridPane content = new GridPane();
|
||||||
|
content.setHgap(10.0);
|
||||||
|
content.setVgap(10.0);
|
||||||
|
content.add(new Label("Family"), 0, 0);
|
||||||
|
content.add(family, 1, 0);
|
||||||
|
content.add(new Label("Size"), 0, 1);
|
||||||
|
content.add(size, 1, 1);
|
||||||
|
dialog.getDialogPane().setContent(content);
|
||||||
|
|
||||||
|
dialog.showAndWait()
|
||||||
|
.filter(button -> button == ButtonType.OK)
|
||||||
|
.ifPresent(ignored -> {
|
||||||
|
String selectedFamily = family.getEditor().getText();
|
||||||
|
if (selectedFamily == null || selectedFamily.isBlank()) {
|
||||||
|
selectedFamily = family.getValue();
|
||||||
|
}
|
||||||
|
if (selectedFamily == null || selectedFamily.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double selectedSize = size.getValue();
|
||||||
|
config = config.withFont(selectedFamily.trim(), selectedSize);
|
||||||
|
config.save();
|
||||||
|
terminalView.setFont(config.fontFamily(), config.fontSize());
|
||||||
|
terminalView.canvas().requestFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.setProperty("prism.order", System.getProperty("prism.order", "es2,sw"));
|
System.setProperty("prism.order", System.getProperty("prism.order", "es2,sw"));
|
||||||
launch(Main.class, args);
|
launch(Main.class, args);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public final class ShellSession implements AutoCloseable {
|
|||||||
private final ExecutorService reader;
|
private final ExecutorService reader;
|
||||||
private volatile boolean closed;
|
private volatile boolean closed;
|
||||||
|
|
||||||
private ShellSession(PtyProcess process, TerminalPane pane) {
|
private ShellSession(PtyProcess process) {
|
||||||
this.process = process;
|
this.process = process;
|
||||||
this.stdin = process.getOutputStream();
|
this.stdin = process.getOutputStream();
|
||||||
this.reader = Executors.newSingleThreadExecutor(runnable -> {
|
this.reader = Executors.newSingleThreadExecutor(runnable -> {
|
||||||
@@ -27,7 +27,6 @@ public final class ShellSession implements AutoCloseable {
|
|||||||
thread.setDaemon(true);
|
thread.setDaemon(true);
|
||||||
return thread;
|
return thread;
|
||||||
});
|
});
|
||||||
reader.submit(() -> readOutput(pane));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShellSession start(String shell, TerminalPane pane, int columns, int rows) {
|
public static ShellSession start(String shell, TerminalPane pane, int columns, int rows) {
|
||||||
@@ -42,13 +41,17 @@ public final class ShellSession implements AutoCloseable {
|
|||||||
.setInitialRows(rows)
|
.setInitialRows(rows)
|
||||||
.setDirectory(System.getProperty("user.home"))
|
.setDirectory(System.getProperty("user.home"))
|
||||||
.start();
|
.start();
|
||||||
return new ShellSession(process, pane);
|
return new ShellSession(process);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
pane.write("failed to start shell: " + ex.getMessage() + "\r\n");
|
pane.write("failed to start shell: " + ex.getMessage() + "\r\n");
|
||||||
throw new IllegalStateException("Could not start shell " + shell, ex);
|
throw new IllegalStateException("Could not start shell " + shell, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void startReading(TerminalPane pane) {
|
||||||
|
reader.submit(() -> readOutput(pane));
|
||||||
|
}
|
||||||
|
|
||||||
public void resize(int columns, int rows) {
|
public void resize(int columns, int rows) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
return;
|
return;
|
||||||
@@ -57,11 +60,15 @@ public final class ShellSession implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void send(String text) {
|
public void send(String text) {
|
||||||
|
send(text.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(byte[] bytes) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
stdin.write(text.getBytes(StandardCharsets.UTF_8));
|
stdin.write(bytes);
|
||||||
stdin.flush();
|
stdin.flush();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
close();
|
close();
|
||||||
|
|||||||
@@ -32,10 +32,14 @@ public final class TerminalCanvasView {
|
|||||||
private final TerminalWorkspace workspace;
|
private final TerminalWorkspace workspace;
|
||||||
private final AppConfig config;
|
private final AppConfig config;
|
||||||
private final Map<Long, Image> kittyImageCache = new HashMap<>();
|
private final Map<Long, Image> kittyImageCache = new HashMap<>();
|
||||||
|
private String fontFamily;
|
||||||
|
private double fontSize;
|
||||||
|
|
||||||
public TerminalCanvasView(TerminalWorkspace workspace, AppConfig config) {
|
public TerminalCanvasView(TerminalWorkspace workspace, AppConfig config) {
|
||||||
this.workspace = workspace;
|
this.workspace = workspace;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
this.fontFamily = config.fontFamily();
|
||||||
|
this.fontSize = config.fontSize();
|
||||||
canvas.setFocusTraversable(true);
|
canvas.setFocusTraversable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +47,11 @@ public final class TerminalCanvasView {
|
|||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFont(String family, double size) {
|
||||||
|
this.fontFamily = family;
|
||||||
|
this.fontSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
public void render() {
|
public void render() {
|
||||||
double width = canvas.getWidth();
|
double width = canvas.getWidth();
|
||||||
double height = canvas.getHeight();
|
double height = canvas.getHeight();
|
||||||
@@ -75,7 +84,7 @@ public final class TerminalCanvasView {
|
|||||||
gc.setLineWidth(workspace.isActive(pane) ? 2.0 : 1.0);
|
gc.setLineWidth(workspace.isActive(pane) ? 2.0 : 1.0);
|
||||||
gc.strokeRect(pane.x() + 0.5, pane.y() + 0.5, pane.width() - 1.0, pane.height() - 1.0);
|
gc.strokeRect(pane.x() + 0.5, pane.y() + 0.5, pane.width() - 1.0, pane.height() - 1.0);
|
||||||
|
|
||||||
Font font = Font.font(config.fontFamily(), config.fontSize());
|
Font font = Font.font(fontFamily, fontSize);
|
||||||
gc.setFont(font);
|
gc.setFont(font);
|
||||||
|
|
||||||
FontMetrics metrics = measureFontMetrics(font);
|
FontMetrics metrics = measureFontMetrics(font);
|
||||||
@@ -105,15 +114,14 @@ public final class TerminalCanvasView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static FontMetrics measureFontMetrics(Font font) {
|
private static FontMetrics measureFontMetrics(Font font) {
|
||||||
Text text = new Text("Mg");
|
Text text = new Text("┃MgÅjy");
|
||||||
text.setFont(font);
|
text.setFont(font);
|
||||||
double textHeight = text.getLayoutBounds().getHeight();
|
double lineHeight = Math.max(1.0, text.getLayoutBounds().getHeight());
|
||||||
double lineHeight = Math.max(1.0, Math.ceil(textHeight * 1.2));
|
double baselineOffset = -text.getLayoutBounds().getMinY();
|
||||||
double baselineOffset = -text.getLayoutBounds().getMinY() + ((lineHeight - textHeight) / 2.0);
|
|
||||||
|
|
||||||
Text cell = new Text("M");
|
Text cell = new Text("M");
|
||||||
cell.setFont(font);
|
cell.setFont(font);
|
||||||
double cellWidth = Math.max(1.0, Math.ceil(cell.getLayoutBounds().getWidth()));
|
double cellWidth = Math.max(1.0, cell.getLayoutBounds().getWidth());
|
||||||
return new FontMetrics(cellWidth, lineHeight, baselineOffset);
|
return new FontMetrics(cellWidth, lineHeight, baselineOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import dev.jlibghostty.KittyGraphics;
|
|||||||
import dev.jlibghostty.RenderStateSnapshot;
|
import dev.jlibghostty.RenderStateSnapshot;
|
||||||
import dev.jlibghostty.Terminal;
|
import dev.jlibghostty.Terminal;
|
||||||
import dev.jlibghostty.TerminalOptions;
|
import dev.jlibghostty.TerminalOptions;
|
||||||
|
import dev.jlibghostty.DeviceAttributes;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
@@ -32,6 +33,7 @@ public final class TerminalPane implements AutoCloseable {
|
|||||||
|
|
||||||
public static TerminalPane create(int columns, int rows) {
|
public static TerminalPane create(int columns, int rows) {
|
||||||
Terminal terminal = Ghostty.open(TerminalOptions.of(columns, rows));
|
Terminal terminal = Ghostty.open(TerminalOptions.of(columns, rows));
|
||||||
|
terminal.setDeviceAttributesProvider(DeviceAttributes::xtermCompatible);
|
||||||
TerminalPane pane = new TerminalPane(terminal, columns, rows);
|
TerminalPane pane = new TerminalPane(terminal, columns, rows);
|
||||||
pane.refresh();
|
pane.refresh();
|
||||||
return pane;
|
return pane;
|
||||||
@@ -53,6 +55,13 @@ public final class TerminalPane implements AutoCloseable {
|
|||||||
|
|
||||||
public void attach(ShellSession session) {
|
public void attach(ShellSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
terminal.setPtyWriter(bytes -> {
|
||||||
|
ShellSession current = this.session;
|
||||||
|
if (current != null) {
|
||||||
|
current.send(bytes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
session.startReading(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(String text) {
|
public void send(String text) {
|
||||||
|
|||||||
Reference in New Issue
Block a user