stacking
This commit is contained in:
18
.classpath
Normal file
18
.classpath
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="bin/main" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="main"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="main,test"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="bin/main" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="main"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="main,test"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21/"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||||
|
<classpathentry kind="output" path="bin/default"/>
|
||||||
|
</classpath>
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1 +1,12 @@
|
|||||||
result
|
result
|
||||||
|
|
||||||
|
# Devenv
|
||||||
|
.devenv*
|
||||||
|
devenv.local.nix
|
||||||
|
devenv.local.yaml
|
||||||
|
|
||||||
|
# direnv
|
||||||
|
.direnv
|
||||||
|
|
||||||
|
# pre-commit
|
||||||
|
.pre-commit-config.yaml
|
||||||
|
|||||||
BIN
.gradle/8.14.4/fileChanges/last-build.bin
Normal file
BIN
.gradle/8.14.4/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
.gradle/8.14.4/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/8.14.4/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
0
.gradle/8.14.4/gc.properties
Normal file
0
.gradle/8.14.4/gc.properties
Normal file
BIN
.gradle/8.9/checksums/checksums.lock
Normal file
BIN
.gradle/8.9/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
.gradle/8.9/checksums/md5-checksums.bin
Normal file
BIN
.gradle/8.9/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/8.9/checksums/sha1-checksums.bin
Normal file
BIN
.gradle/8.9/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
0
.gradle/8.9/dependencies-accessors/gc.properties
Normal file
0
.gradle/8.9/dependencies-accessors/gc.properties
Normal file
BIN
.gradle/8.9/executionHistory/executionHistory.lock
Normal file
BIN
.gradle/8.9/executionHistory/executionHistory.lock
Normal file
Binary file not shown.
BIN
.gradle/8.9/fileChanges/last-build.bin
Normal file
BIN
.gradle/8.9/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
.gradle/8.9/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/8.9/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
0
.gradle/8.9/gc.properties
Normal file
0
.gradle/8.9/gc.properties
Normal file
BIN
.gradle/9.4.1/checksums/checksums.lock
Normal file
BIN
.gradle/9.4.1/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
.gradle/9.4.1/checksums/md5-checksums.bin
Normal file
BIN
.gradle/9.4.1/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/9.4.1/checksums/sha1-checksums.bin
Normal file
BIN
.gradle/9.4.1/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/9.4.1/executionHistory/executionHistory.bin
Normal file
BIN
.gradle/9.4.1/executionHistory/executionHistory.bin
Normal file
Binary file not shown.
BIN
.gradle/9.4.1/executionHistory/executionHistory.lock
Normal file
BIN
.gradle/9.4.1/executionHistory/executionHistory.lock
Normal file
Binary file not shown.
BIN
.gradle/9.4.1/fileChanges/last-build.bin
Normal file
BIN
.gradle/9.4.1/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
.gradle/9.4.1/fileHashes/fileHashes.bin
Normal file
BIN
.gradle/9.4.1/fileHashes/fileHashes.bin
Normal file
Binary file not shown.
BIN
.gradle/9.4.1/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/9.4.1/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
BIN
.gradle/9.4.1/fileHashes/resourceHashesCache.bin
Normal file
BIN
.gradle/9.4.1/fileHashes/resourceHashesCache.bin
Normal file
Binary file not shown.
0
.gradle/9.4.1/gc.properties
Normal file
0
.gradle/9.4.1/gc.properties
Normal file
BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
Binary file not shown.
2
.gradle/buildOutputCleanup/cache.properties
Normal file
2
.gradle/buildOutputCleanup/cache.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#Wed May 27 23:44:22 CEST 2026
|
||||||
|
gradle.version=9.4.1
|
||||||
BIN
.gradle/buildOutputCleanup/outputFiles.bin
Normal file
BIN
.gradle/buildOutputCleanup/outputFiles.bin
Normal file
Binary file not shown.
BIN
.gradle/file-system.probe
Normal file
BIN
.gradle/file-system.probe
Normal file
Binary file not shown.
0
.gradle/vcs-1/gc.properties
Normal file
0
.gradle/vcs-1/gc.properties
Normal file
34
.project
Normal file
34
.project
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>jprototerm</name>
|
||||||
|
<comment>Project jprototerm created by Buildship.</comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
<filteredResources>
|
||||||
|
<filter>
|
||||||
|
<id>1779917652126</id>
|
||||||
|
<name></name>
|
||||||
|
<type>30</type>
|
||||||
|
<matcher>
|
||||||
|
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||||
|
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||||
|
</matcher>
|
||||||
|
</filter>
|
||||||
|
</filteredResources>
|
||||||
|
</projectDescription>
|
||||||
13
.settings/org.eclipse.buildship.core.prefs
Normal file
13
.settings/org.eclipse.buildship.core.prefs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
arguments=--init-script /home/anon/.eclipse/1927926929_linux_gtk_x86_64/configuration/org.eclipse.osgi/58/0/.cp/gradle/init/init.gradle
|
||||||
|
auto.sync=false
|
||||||
|
build.scans.enabled=false
|
||||||
|
connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(8.9))
|
||||||
|
connection.project.dir=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
gradle.user.home=
|
||||||
|
java.home=/nix/store/c3pl7bqrx3d2rc3dh98z6yaj0mv1p52g-openjdk-21.0.10+7/lib/openjdk
|
||||||
|
jvm.arguments=
|
||||||
|
offline.mode=false
|
||||||
|
override.workspace.settings=true
|
||||||
|
show.console.view=true
|
||||||
|
show.executions.view=true
|
||||||
16
README.md
16
README.md
@@ -8,17 +8,17 @@ JavaFX canvas terminal prototype using `jlibghostty` for terminal emulation, Nix
|
|||||||
nix build
|
nix build
|
||||||
```
|
```
|
||||||
|
|
||||||
The package build compiles with Nix-provided OpenJFX 25, `jlibghostty`, JToml, and GraalVM Native Image directly so it does not depend on Gradle plugin resolution inside the Nix sandbox.
|
The package build uses GluonFX through Gradle so JavaFX native-image metadata is generated by the toolchain that is designed for it. In a strict pure Nix sandbox, Gradle dependencies must be vendored first with `gradle2nix` or a checked-in Maven/Gradle cache.
|
||||||
|
|
||||||
For development:
|
For development:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nix develop
|
nix develop
|
||||||
gradle -PjlibghosttyMavenRepo="$JLIBGHOSTTY_MAVEN_REPO" run
|
gradle -PjlibghosttyMavenRepo="$JLIBGHOSTTY_MAVEN_REPO" run
|
||||||
gradle -PjlibghosttyMavenRepo="$JLIBGHOSTTY_MAVEN_REPO" nativeCompile
|
gradle -PjlibghosttyMavenRepo="$JLIBGHOSTTY_MAVEN_REPO" -Pgluonfx.mainClassName=com.gregor.jprototerm.Main nativeExecutable
|
||||||
```
|
```
|
||||||
|
|
||||||
The Gradle project is kept for interactive development and IDE import.
|
The Gradle project is the source of truth for native JavaFX builds.
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
@@ -57,11 +57,17 @@ navigate_down = "ALT+J"
|
|||||||
navigate_up = "ALT+K"
|
navigate_up = "ALT+K"
|
||||||
navigate_right = "ALT+L"
|
navigate_right = "ALT+L"
|
||||||
toggle_floating = "ALT+F"
|
toggle_floating = "ALT+F"
|
||||||
|
new_floating = "ALT+SHIFT+F"
|
||||||
|
next_floating = "ALT+F12"
|
||||||
|
close_pane = "ALT+X"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Defaults
|
## Defaults
|
||||||
|
|
||||||
- `Alt+h/j/k/l`: navigate panes
|
- `Alt+h/j/k/l`: navigate panes
|
||||||
- `Alt+f`: open or close a floating pane
|
- `Alt+f`: show or hide all floating panes
|
||||||
- Font default: `Symbols Nerd Font Mono`
|
- `Alt+Shift+f`: create a new floating pane
|
||||||
|
- `Alt+F12`: cycle floating panes
|
||||||
|
- `Alt+x`: close the active floating pane
|
||||||
|
- Font default: `JetBrainsMono Nerd Font`
|
||||||
- Kitty graphics protocol parsing is enabled by default
|
- Kitty graphics protocol parsing is enabled by default
|
||||||
|
|||||||
31
build.gradle
Normal file
31
build.gradle
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
plugins {
|
||||||
|
id 'application'
|
||||||
|
id 'org.openjfx.javafxplugin' version '0.1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
|
||||||
|
maven {
|
||||||
|
url = uri(System.getenv("JLIBGHOSTTY_MAVEN_REPO"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'io.github.wasabithumb:jtoml:1.5.2'
|
||||||
|
implementation 'dev.jlibghostty:jlibghostty:0.1.0-SNAPSHOT'
|
||||||
|
implementation 'org.jetbrains.pty4j:pty4j:0.13.11'
|
||||||
|
}
|
||||||
|
|
||||||
|
javafx {
|
||||||
|
version = '22'
|
||||||
|
modules = [ 'javafx.controls', 'javafx.fxml' ]
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass = 'com.gregor.jprototerm.Main'
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
jvmArgs += ['--enable-native-access=ALL-UNNAMED']
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
plugins {
|
|
||||||
application
|
|
||||||
id("org.openjfx.javafxplugin") version "0.1.0"
|
|
||||||
id("com.gluonhq.gluonfx-gradle-plugin") version "1.0.28"
|
|
||||||
}
|
|
||||||
|
|
||||||
group = "com.gregor"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("dev.jlibghostty:jlibghostty:0.1.0-SNAPSHOT")
|
|
||||||
implementation("io.github.wasabithumb:jtoml:1.5.2")
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
toolchain {
|
|
||||||
languageVersion.set(JavaLanguageVersion.of(25))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
application {
|
|
||||||
mainClass.set("com.gregor.jprototerm.Main")
|
|
||||||
}
|
|
||||||
|
|
||||||
javafx {
|
|
||||||
version = "25"
|
|
||||||
modules = listOf("javafx.controls", "javafx.graphics")
|
|
||||||
}
|
|
||||||
|
|
||||||
gluonfx {
|
|
||||||
mainClassName = "com.gregor.jprototerm.Main"
|
|
||||||
}
|
|
||||||
BIN
build/classes/java/main/com/gregor/jprototerm/AppConfig.class
Normal file
BIN
build/classes/java/main/com/gregor/jprototerm/AppConfig.class
Normal file
Binary file not shown.
BIN
build/classes/java/main/com/gregor/jprototerm/Direction.class
Normal file
BIN
build/classes/java/main/com/gregor/jprototerm/Direction.class
Normal file
Binary file not shown.
BIN
build/classes/java/main/com/gregor/jprototerm/KeyBinding.class
Normal file
BIN
build/classes/java/main/com/gregor/jprototerm/KeyBinding.class
Normal file
Binary file not shown.
BIN
build/classes/java/main/com/gregor/jprototerm/KeyEncoder$1.class
Normal file
BIN
build/classes/java/main/com/gregor/jprototerm/KeyEncoder$1.class
Normal file
Binary file not shown.
BIN
build/classes/java/main/com/gregor/jprototerm/KeyEncoder.class
Normal file
BIN
build/classes/java/main/com/gregor/jprototerm/KeyEncoder.class
Normal file
Binary file not shown.
BIN
build/classes/java/main/com/gregor/jprototerm/Main$1.class
Normal file
BIN
build/classes/java/main/com/gregor/jprototerm/Main$1.class
Normal file
Binary file not shown.
BIN
build/classes/java/main/com/gregor/jprototerm/Main.class
Normal file
BIN
build/classes/java/main/com/gregor/jprototerm/Main.class
Normal file
Binary file not shown.
BIN
build/classes/java/main/com/gregor/jprototerm/ShellSession.class
Normal file
BIN
build/classes/java/main/com/gregor/jprototerm/ShellSession.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/classes/java/main/com/gregor/jprototerm/TerminalPane.class
Normal file
BIN
build/classes/java/main/com/gregor/jprototerm/TerminalPane.class
Normal file
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.
BIN
build/tmp/compileJava/previous-compilation-data.bin
Normal file
BIN
build/tmp/compileJava/previous-compilation-data.bin
Normal file
Binary file not shown.
@@ -18,3 +18,6 @@ navigate_down = "ALT+J"
|
|||||||
navigate_up = "ALT+K"
|
navigate_up = "ALT+K"
|
||||||
navigate_right = "ALT+L"
|
navigate_right = "ALT+L"
|
||||||
toggle_floating = "ALT+F"
|
toggle_floating = "ALT+F"
|
||||||
|
new_floating = "ALT+SHIFT+F"
|
||||||
|
next_floating = "ALT+F12"
|
||||||
|
close_pane = "ALT+X"
|
||||||
|
|||||||
65
devenv.lock
Normal file
65
devenv.lock
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"devenv": {
|
||||||
|
"locked": {
|
||||||
|
"dir": "src/modules",
|
||||||
|
"lastModified": 1779749056,
|
||||||
|
"narHash": "sha256-AtocdrunzuxTvSDn+82RntEhrs6TicM6Z4/zNQS9KKg=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"rev": "099ac65fcef79e88127bdc06adbd1ea94255274a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"dir": "src/modules",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-src": "nixpkgs-src"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778507786,
|
||||||
|
"narHash": "sha256-HzSQCKMsMr8r55LwM1JuzIOB+8bzk0FEv6sItKvsfoY=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv-nixpkgs",
|
||||||
|
"rev": "8f24a228a782e24576b155d1e39f0d914b380691",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"ref": "rolling",
|
||||||
|
"repo": "devenv-nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778274207,
|
||||||
|
"narHash": "sha256-I4puXmX1iovcCHZlRmztO3vW0mAbbRvq4F8wgIMQ1MM=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "b3da656039dc7a6240f27b2ef8cc6a3ef3bccae7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"devenv": "devenv",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
46
devenv.nix
Normal file
46
devenv.nix
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{ pkgs, lib, config, inputs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
|
|
||||||
|
jlibghostty = builtins.getFlake
|
||||||
|
"git+https://gitea.gregorlohaus.com/gregor/jlibghostty.git";
|
||||||
|
|
||||||
|
jlib = jlibghostty.packages.${system}.jlibghostty;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages = [
|
||||||
|
pkgs.git
|
||||||
|
pkgs.gradle_9
|
||||||
|
pkgs.jdk25
|
||||||
|
pkgs.jdt-language-server
|
||||||
|
pkgs.openjfx
|
||||||
|
|
||||||
|
pkgs.glib
|
||||||
|
pkgs.xorg.libXxf86vm
|
||||||
|
pkgs.xorg.libXrender
|
||||||
|
pkgs.xorg.libXtst
|
||||||
|
pkgs.xorg.libXi
|
||||||
|
pkgs.xorg.libXrandr
|
||||||
|
|
||||||
|
pkgs.libGL
|
||||||
|
pkgs.gtk3
|
||||||
|
pkgs.alsa-lib
|
||||||
|
];
|
||||||
|
|
||||||
|
env.LD_LIBRARY_PATH = lib.makeLibraryPath [
|
||||||
|
pkgs.openjfx
|
||||||
|
|
||||||
|
pkgs.glib
|
||||||
|
pkgs.xorg.libXxf86vm
|
||||||
|
pkgs.xorg.libXrender
|
||||||
|
pkgs.xorg.libXtst
|
||||||
|
pkgs.xorg.libXi
|
||||||
|
pkgs.xorg.libXrandr
|
||||||
|
|
||||||
|
pkgs.libGL
|
||||||
|
pkgs.gtk3
|
||||||
|
pkgs.alsa-lib
|
||||||
|
] + ":/usr/lib/x86_64-linux-gnu/nvidia/current";
|
||||||
|
env.JLIBGHOSTTY_MAVEN_REPO = "${jlib}/maven";
|
||||||
|
}
|
||||||
18
devenv.yaml
Normal file
18
devenv.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
|
||||||
|
inputs:
|
||||||
|
nixpkgs:
|
||||||
|
url: github:cachix/devenv-nixpkgs/rolling
|
||||||
|
|
||||||
|
# If you're using non-OSS software, you can set allowUnfree to true.
|
||||||
|
# allowUnfree: true
|
||||||
|
|
||||||
|
# If you're not willing to allow unsupported packages:
|
||||||
|
# allowUnsupportedSystem: false
|
||||||
|
|
||||||
|
# If you're willing to use a package that's vulnerable
|
||||||
|
# permittedInsecurePackages:
|
||||||
|
# - "openssl-1.1.1w"
|
||||||
|
|
||||||
|
# If you have more than one devenv you can merge them
|
||||||
|
#imports:
|
||||||
|
# - ./backend
|
||||||
274
flake.lock
generated
274
flake.lock
generated
@@ -1,274 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-compat": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1761588595,
|
|
||||||
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ghostty": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"home-manager": "home-manager",
|
|
||||||
"nixpkgs": [
|
|
||||||
"jlibghostty",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"systems": "systems",
|
|
||||||
"zig": "zig",
|
|
||||||
"zon2nix": "zon2nix"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1779812402,
|
|
||||||
"narHash": "sha256-gozJEyJHbaAyrbzODKeWJhxpUrGK6m4DIPDogfjz2BU=",
|
|
||||||
"owner": "ghostty-org",
|
|
||||||
"repo": "ghostty",
|
|
||||||
"rev": "2e5ad917eb4e325a3dbb161c3f41208a8cd35e44",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "ghostty-org",
|
|
||||||
"repo": "ghostty",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home-manager": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"jlibghostty",
|
|
||||||
"ghostty",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1770586272,
|
|
||||||
"narHash": "sha256-Ucci8mu8QfxwzyfER2DQDbvW9t1BnTUJhBmY7ybralo=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"rev": "b1f916ba052341edc1f80d4b2399f1092a4873ca",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"javafx-base": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"narHash": "sha256-96fttJUts/rFwKB7u5G8NWkK2NjJ3a6eIKbe1RTWkmM=",
|
|
||||||
"type": "file",
|
|
||||||
"url": "https://repo.maven.apache.org/maven2/org/openjfx/javafx-base/25/javafx-base-25-linux.jar"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "file",
|
|
||||||
"url": "https://repo.maven.apache.org/maven2/org/openjfx/javafx-base/25/javafx-base-25-linux.jar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"javafx-controls": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"narHash": "sha256-2Cdc2/hPOjJmQidDjXu9vnlwAuawLn0cg/tLhzFfkUs=",
|
|
||||||
"type": "file",
|
|
||||||
"url": "https://repo.maven.apache.org/maven2/org/openjfx/javafx-controls/25/javafx-controls-25-linux.jar"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "file",
|
|
||||||
"url": "https://repo.maven.apache.org/maven2/org/openjfx/javafx-controls/25/javafx-controls-25-linux.jar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"javafx-graphics": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"narHash": "sha256-w01IhRAQzcfTvwkqIQkjrI8ZPXT0VTEeijfzbqp3G0k=",
|
|
||||||
"type": "file",
|
|
||||||
"url": "https://repo.maven.apache.org/maven2/org/openjfx/javafx-graphics/25/javafx-graphics-25-linux.jar"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "file",
|
|
||||||
"url": "https://repo.maven.apache.org/maven2/org/openjfx/javafx-graphics/25/javafx-graphics-25-linux.jar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"jlibghostty": {
|
|
||||||
"inputs": {
|
|
||||||
"ghostty": "ghostty",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1779889299,
|
|
||||||
"narHash": "sha256-B82MyhTvlfeszdcuM3F8YDSZYaxUom+m59oQKSoWjmQ=",
|
|
||||||
"ref": "refs/heads/main",
|
|
||||||
"rev": "eea43843002f8fae4fa4cb1c46b64339124bf6b2",
|
|
||||||
"revCount": 6,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitea.gregorlohaus.com/gregor/jlibghostty.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitea.gregorlohaus.com/gregor/jlibghostty.git"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"jtoml-all": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"narHash": "sha256-KWrUaDVmnWzdkQxjgPFFNl8DOEvkCqWW3OmXU2sZHKw=",
|
|
||||||
"type": "file",
|
|
||||||
"url": "https://repo.maven.apache.org/maven2/io/github/wasabithumb/jtoml-all/1.5.2/jtoml-all-1.5.2.jar"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "file",
|
|
||||||
"url": "https://repo.maven.apache.org/maven2/io/github/wasabithumb/jtoml-all/1.5.2/jtoml-all-1.5.2.jar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1779560665,
|
|
||||||
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1779560665,
|
|
||||||
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"javafx-base": "javafx-base",
|
|
||||||
"javafx-controls": "javafx-controls",
|
|
||||||
"javafx-graphics": "javafx-graphics",
|
|
||||||
"jlibghostty": "jlibghostty",
|
|
||||||
"jtoml-all": "jtoml-all",
|
|
||||||
"nixpkgs": "nixpkgs_2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zig": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": [
|
|
||||||
"jlibghostty",
|
|
||||||
"ghostty",
|
|
||||||
"flake-compat"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"jlibghostty",
|
|
||||||
"ghostty",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"systems": [
|
|
||||||
"jlibghostty",
|
|
||||||
"ghostty",
|
|
||||||
"systems"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1776789209,
|
|
||||||
"narHash": "sha256-G6B7Q4TXn7MZ1mB+f9rymjsYF5PLWoSvmbxijb/99bw=",
|
|
||||||
"owner": "mitchellh",
|
|
||||||
"repo": "zig-overlay",
|
|
||||||
"rev": "14fe971844e841297ddd2ce9783d6892b467af39",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "mitchellh",
|
|
||||||
"repo": "zig-overlay",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zig_2": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"jlibghostty",
|
|
||||||
"ghostty",
|
|
||||||
"zon2nix",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1777234348,
|
|
||||||
"narHash": "sha256-fKw44a4qbUuI5eTG8k0gPbqMV5TOrjYF35PBzsYgd2U=",
|
|
||||||
"ref": "refs/heads/main",
|
|
||||||
"rev": "2c781c0609ecda600ab98f98cca417bbd981bd53",
|
|
||||||
"revCount": 1677,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://codeberg.org/jcollie/zig-overlay.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://codeberg.org/jcollie/zig-overlay.git"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zon2nix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"jlibghostty",
|
|
||||||
"ghostty",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"zig": "zig_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1777314365,
|
|
||||||
"narHash": "sha256-eLxQaD0wc96Neqkln8wHS0rNq/chPODifFkhwrwilEU=",
|
|
||||||
"owner": "jcollie",
|
|
||||||
"repo": "zon2nix",
|
|
||||||
"rev": "a5a1d412ad1ab6305511997bbc92b3a9dd6cb784",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "jcollie",
|
|
||||||
"ref": "main",
|
|
||||||
"repo": "zon2nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
182
flake.nix
182
flake.nix
@@ -1,182 +0,0 @@
|
|||||||
{
|
|
||||||
description = "JavaFX terminal using jlibghostty and GraalVM Native Image";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
||||||
jlibghostty.url = "git+https://gitea.gregorlohaus.com/gregor/jlibghostty.git";
|
|
||||||
|
|
||||||
jtoml-all = {
|
|
||||||
url = "https://repo.maven.apache.org/maven2/io/github/wasabithumb/jtoml-all/1.5.2/jtoml-all-1.5.2.jar";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
javafx-base = {
|
|
||||||
url = "https://repo.maven.apache.org/maven2/org/openjfx/javafx-base/25/javafx-base-25-linux.jar";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
javafx-controls = {
|
|
||||||
url = "https://repo.maven.apache.org/maven2/org/openjfx/javafx-controls/25/javafx-controls-25-linux.jar";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
javafx-graphics = {
|
|
||||||
url = "https://repo.maven.apache.org/maven2/org/openjfx/javafx-graphics/25/javafx-graphics-25-linux.jar";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = {
|
|
||||||
self,
|
|
||||||
nixpkgs,
|
|
||||||
jlibghostty,
|
|
||||||
jtoml-all,
|
|
||||||
javafx-base,
|
|
||||||
javafx-controls,
|
|
||||||
javafx-graphics
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
system = "x86_64-linux";
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
|
|
||||||
jlib = jlibghostty.packages.${system}.jlibghostty;
|
|
||||||
graalvm = pkgs.graalvmPackages.graalvm-ce;
|
|
||||||
gradle = if pkgs ? gradle_9 then pkgs.gradle_9 else pkgs.gradle;
|
|
||||||
openjfx = pkgs.javaPackages.openjfx25;
|
|
||||||
javafxNativeLibraryPath = pkgs.lib.concatStringsSep ":" [
|
|
||||||
"${openjfx}/modules_libs/javafx.base"
|
|
||||||
"${openjfx}/modules_libs/javafx.graphics"
|
|
||||||
"${openjfx}/modules_libs/javafx.media"
|
|
||||||
];
|
|
||||||
x11 = name: oldName: pkgs.${name} or pkgs.xorg.${oldName};
|
|
||||||
mesaDrivers = pkgs.mesa;
|
|
||||||
runtimeLibraryPath = pkgs.lib.makeLibraryPath ([
|
|
||||||
openjfx
|
|
||||||
jlib
|
|
||||||
pkgs.gtk3
|
|
||||||
pkgs.glib
|
|
||||||
pkgs.pango
|
|
||||||
pkgs.cairo
|
|
||||||
pkgs.gdk-pixbuf
|
|
||||||
pkgs.harfbuzz
|
|
||||||
pkgs.freetype
|
|
||||||
pkgs.fontconfig
|
|
||||||
pkgs.libxkbcommon
|
|
||||||
pkgs.zlib
|
|
||||||
pkgs.stdenv.cc.cc.lib
|
|
||||||
pkgs.libglvnd
|
|
||||||
(x11 "libx11" "libX11")
|
|
||||||
(x11 "libxext" "libXext")
|
|
||||||
(x11 "libxrender" "libXrender")
|
|
||||||
(x11 "libxtst" "libXtst")
|
|
||||||
(x11 "libxi" "libXi")
|
|
||||||
(x11 "libxcursor" "libXcursor")
|
|
||||||
(x11 "libxrandr" "libXrandr")
|
|
||||||
(x11 "libxinerama" "libXinerama")
|
|
||||||
(x11 "libxcb" "libxcb")
|
|
||||||
]
|
|
||||||
++ pkgs.lib.optionals (pkgs ? atk) [ pkgs.atk ]
|
|
||||||
++ pkgs.lib.optionals (pkgs ? libxxf86vm || pkgs.xorg ? libXxf86vm) [ (x11 "libxxf86vm" "libXxf86vm") ]
|
|
||||||
++ pkgs.lib.optionals (pkgs ? libGL) [ pkgs.libGL ]
|
|
||||||
++ pkgs.lib.optionals (pkgs ? mesa) [ pkgs.mesa ]);
|
|
||||||
openglDriverPath = pkgs.lib.concatStringsSep ":" [
|
|
||||||
"/run/opengl-driver/lib"
|
|
||||||
"/run/opengl-driver-32/lib"
|
|
||||||
"${mesaDrivers}/lib"
|
|
||||||
];
|
|
||||||
driDriverPath = pkgs.lib.concatStringsSep ":" [
|
|
||||||
"/run/opengl-driver/lib/dri"
|
|
||||||
"/run/opengl-driver-32/lib/dri"
|
|
||||||
"${mesaDrivers}/lib/dri"
|
|
||||||
];
|
|
||||||
in {
|
|
||||||
packages.${system}.default = pkgs.stdenvNoCC.mkDerivation {
|
|
||||||
pname = "jprototerm";
|
|
||||||
version = "0.1.0";
|
|
||||||
src = ./.;
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
graalvm
|
|
||||||
pkgs.makeWrapper
|
|
||||||
];
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
runHook preBuild
|
|
||||||
|
|
||||||
mkdir -p build/classes build/native-image build/lib build/javafx-modules
|
|
||||||
|
|
||||||
find src/main/java -name '*.java' | sort > build/sources.txt
|
|
||||||
cp ${jtoml-all} build/lib/jtoml-all.jar
|
|
||||||
cp ${javafx-base} build/javafx-modules/javafx-base.jar
|
|
||||||
cp ${javafx-controls} build/javafx-modules/javafx-controls.jar
|
|
||||||
cp ${javafx-graphics} build/javafx-modules/javafx-graphics.jar
|
|
||||||
javafx_module_path="build/javafx-modules"
|
|
||||||
|
|
||||||
jlib_classpath="$(
|
|
||||||
find ${jlib}/maven -type f -name '*.jar' \
|
|
||||||
! -name '*-sources.jar' \
|
|
||||||
! -name '*-javadoc.jar' \
|
|
||||||
| sort \
|
|
||||||
| paste -sd: -
|
|
||||||
)"
|
|
||||||
app_classpath="build/classes:build/lib/jtoml-all.jar:$jlib_classpath:build/javafx-modules/javafx-base.jar:build/javafx-modules/javafx-controls.jar:build/javafx-modules/javafx-graphics.jar"
|
|
||||||
|
|
||||||
javac \
|
|
||||||
--release 25 \
|
|
||||||
--module-path "$javafx_module_path" \
|
|
||||||
--add-modules javafx.controls,javafx.graphics \
|
|
||||||
-cp "build/lib/jtoml-all.jar:$jlib_classpath" \
|
|
||||||
-d build/classes \
|
|
||||||
@build/sources.txt
|
|
||||||
|
|
||||||
if [ -d src/main/resources ]; then
|
|
||||||
cp -R src/main/resources/. build/classes/
|
|
||||||
fi
|
|
||||||
|
|
||||||
native-image \
|
|
||||||
--no-fallback \
|
|
||||||
--enable-native-access=javafx.graphics \
|
|
||||||
-Djava.library.path=${javafxNativeLibraryPath} \
|
|
||||||
--module-path "$javafx_module_path" \
|
|
||||||
--add-modules javafx.controls,javafx.graphics \
|
|
||||||
-cp "$app_classpath" \
|
|
||||||
-H:Class=com.gregor.jprototerm.Main \
|
|
||||||
-o build/native-image/jprototerm
|
|
||||||
|
|
||||||
runHook postBuild
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
mkdir -p $out/bin
|
|
||||||
cp build/native-image/jprototerm $out/bin/jprototerm
|
|
||||||
|
|
||||||
wrapProgram $out/bin/jprototerm \
|
|
||||||
--set GDK_BACKEND x11 \
|
|
||||||
--set LIBGL_DRIVERS_PATH ${driDriverPath} \
|
|
||||||
--set JAVA_TOOL_OPTIONS "-Dprism.order=es2,sw -Dprism.verbose=true" \
|
|
||||||
--add-flags "-Djava.library.path=${javafxNativeLibraryPath}" \
|
|
||||||
--add-flags "-Dprism.order=es2,sw" \
|
|
||||||
--add-flags "-Dprism.verbose=true" \
|
|
||||||
--prefix LD_LIBRARY_PATH : ${javafxNativeLibraryPath}:${runtimeLibraryPath}:${openglDriverPath} \
|
|
||||||
--prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.util-linux pkgs.bash ]}
|
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
devShells.${system}.default = pkgs.mkShell {
|
|
||||||
packages = [
|
|
||||||
graalvm
|
|
||||||
gradle
|
|
||||||
pkgs.util-linux
|
|
||||||
];
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
export JLIBGHOSTTY_MAVEN_REPO=${jlib}/maven
|
|
||||||
echo "Use: gradle -PjlibghosttyMavenRepo=$JLIBGHOSTTY_MAVEN_REPO run"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
pluginManagement {
|
|
||||||
repositories {
|
|
||||||
gradlePluginPortal()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
url = uri(
|
|
||||||
providers.gradleProperty("jlibghosttyMavenRepo")
|
|
||||||
.orElse("../jlibghostty/result/maven")
|
|
||||||
.get()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.name = "jprototerm"
|
|
||||||
@@ -45,7 +45,10 @@ public record AppConfig(
|
|||||||
"navigate_down", binding(document, "keybindings.navigate_down", defaults.keybindings.get("navigate_down")),
|
"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_up", binding(document, "keybindings.navigate_up", defaults.keybindings.get("navigate_up")),
|
||||||
"navigate_right", binding(document, "keybindings.navigate_right", defaults.keybindings.get("navigate_right")),
|
"navigate_right", binding(document, "keybindings.navigate_right", defaults.keybindings.get("navigate_right")),
|
||||||
"toggle_floating", binding(document, "keybindings.toggle_floating", defaults.keybindings.get("toggle_floating"))
|
"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) {
|
||||||
@@ -59,7 +62,7 @@ public record AppConfig(
|
|||||||
100,
|
100,
|
||||||
30,
|
30,
|
||||||
defaultShell(),
|
defaultShell(),
|
||||||
"Symbols Nerd Font Mono",
|
"JetBrainsMono Nerd Font",
|
||||||
15.0,
|
15.0,
|
||||||
1200.0,
|
1200.0,
|
||||||
760.0,
|
760.0,
|
||||||
@@ -69,7 +72,10 @@ public record AppConfig(
|
|||||||
"navigate_down", KeyBinding.parse("ALT+J"),
|
"navigate_down", KeyBinding.parse("ALT+J"),
|
||||||
"navigate_up", KeyBinding.parse("ALT+K"),
|
"navigate_up", KeyBinding.parse("ALT+K"),
|
||||||
"navigate_right", KeyBinding.parse("ALT+L"),
|
"navigate_right", KeyBinding.parse("ALT+L"),
|
||||||
"toggle_floating", KeyBinding.parse("ALT+F")
|
"toggle_floating", KeyBinding.parse("ALT+F"),
|
||||||
|
"new_floating", KeyBinding.parse("ALT+SHIFT+F"),
|
||||||
|
"next_floating", KeyBinding.parse("ALT+F12"),
|
||||||
|
"close_pane", KeyBinding.parse("ALT+X")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -83,8 +89,7 @@ public record AppConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String defaultShell() {
|
private static String defaultShell() {
|
||||||
String shell = System.getenv("SHELL");
|
return "/bin/bash";
|
||||||
return shell == null || shell.isBlank() ? "/bin/sh" : shell;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyBinding binding(TomlTable table, String key, KeyBinding fallback) {
|
private static KeyBinding binding(TomlTable table, String key, KeyBinding fallback) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public record KeyBinding(boolean alt, boolean control, boolean shift, KeyCode co
|
|||||||
case "ALT", "META" -> alt = true;
|
case "ALT", "META" -> alt = true;
|
||||||
case "CTRL", "CONTROL" -> control = true;
|
case "CTRL", "CONTROL" -> control = true;
|
||||||
case "SHIFT" -> shift = true;
|
case "SHIFT" -> shift = true;
|
||||||
default -> code = KeyCode.getKeyCode(token);
|
default -> code = keyCode(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,4 +34,19 @@ public record KeyBinding(boolean alt, boolean control, boolean shift, KeyCode co
|
|||||||
&& event.isShiftDown() == shift
|
&& event.isShiftDown() == shift
|
||||||
&& event.getCode() == code;
|
&& event.getCode() == code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static KeyCode keyCode(String token) {
|
||||||
|
KeyCode alias = switch (token) {
|
||||||
|
case "GRAVE", "BACKTICK", "BACK_QUOTE", "`" -> KeyCode.BACK_QUOTE;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
if (alias != null) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return KeyCode.valueOf(token);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
return KeyCode.getKeyCode(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,20 @@ final class KeyEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String encode(KeyEvent event) {
|
static String encode(KeyEvent event) {
|
||||||
|
if (event.isControlDown() && !event.isAltDown() && !event.isMetaDown()) {
|
||||||
|
String control = controlSequence(event);
|
||||||
|
if (control != null) {
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.isAltDown() && !event.isControlDown() && !event.isMetaDown()) {
|
||||||
|
String alt = altSequence(event);
|
||||||
|
if (alt != null) {
|
||||||
|
return alt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
KeyCode code = event.getCode();
|
KeyCode code = event.getCode();
|
||||||
return switch (code) {
|
return switch (code) {
|
||||||
case ENTER -> "\r";
|
case ENTER -> "\r";
|
||||||
@@ -23,7 +37,43 @@ final class KeyEncoder {
|
|||||||
case DELETE -> "\u001b[3~";
|
case DELETE -> "\u001b[3~";
|
||||||
case PAGE_UP -> "\u001b[5~";
|
case PAGE_UP -> "\u001b[5~";
|
||||||
case PAGE_DOWN -> "\u001b[6~";
|
case PAGE_DOWN -> "\u001b[6~";
|
||||||
|
case F1 -> "\u001bOP";
|
||||||
|
case F2 -> "\u001bOQ";
|
||||||
|
case F3 -> "\u001bOR";
|
||||||
|
case F4 -> "\u001bOS";
|
||||||
|
case F5 -> "\u001b[15~";
|
||||||
|
case F6 -> "\u001b[17~";
|
||||||
|
case F7 -> "\u001b[18~";
|
||||||
|
case F8 -> "\u001b[19~";
|
||||||
|
case F9 -> "\u001b[20~";
|
||||||
|
case F10 -> "\u001b[21~";
|
||||||
|
case F11 -> "\u001b[23~";
|
||||||
|
case F12 -> "\u001b[24~";
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String controlSequence(KeyEvent event) {
|
||||||
|
KeyCode code = event.getCode();
|
||||||
|
if (code.isLetterKey()) {
|
||||||
|
return String.valueOf((char) (Character.toUpperCase(code.getName().charAt(0)) - '@'));
|
||||||
|
}
|
||||||
|
return switch (code) {
|
||||||
|
case SPACE -> "\u0000";
|
||||||
|
case OPEN_BRACKET -> "\u001b";
|
||||||
|
case BACK_SLASH -> "\u001c";
|
||||||
|
case CLOSE_BRACKET -> "\u001d";
|
||||||
|
case DIGIT6 -> "\u001e";
|
||||||
|
case MINUS -> "\u001f";
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String altSequence(KeyEvent event) {
|
||||||
|
KeyCode code = event.getCode();
|
||||||
|
if (code.isLetterKey() || code.isDigitKey()) {
|
||||||
|
return "\u001b" + code.getName().toLowerCase();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
package com.gregor.jprototerm;
|
|
||||||
|
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public final class KittyGraphicsRegistry {
|
|
||||||
private final boolean enabled;
|
|
||||||
private final StringBuilder stream = new StringBuilder();
|
|
||||||
private final Map<Integer, StringBuilder> chunks = new HashMap<>();
|
|
||||||
private final List<Placement> placements = new ArrayList<>();
|
|
||||||
|
|
||||||
public KittyGraphicsRegistry(boolean enabled) {
|
|
||||||
this.enabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void accept(String text) {
|
|
||||||
if (!enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stream.append(text);
|
|
||||||
parseBufferedCommands();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void draw(GraphicsContext gc, double originX, double originY, double cellWidth, double lineHeight) {
|
|
||||||
if (!enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Placement placement : placements) {
|
|
||||||
double x = originX + placement.column * cellWidth;
|
|
||||||
double y = originY + placement.row * lineHeight;
|
|
||||||
double width = placement.columns <= 0 ? placement.image.getWidth() : placement.columns * cellWidth;
|
|
||||||
double height = placement.rows <= 0 ? placement.image.getHeight() : placement.rows * lineHeight;
|
|
||||||
gc.drawImage(placement.image, x, y, width, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void clear() {
|
|
||||||
chunks.clear();
|
|
||||||
placements.clear();
|
|
||||||
stream.setLength(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseBufferedCommands() {
|
|
||||||
int start;
|
|
||||||
while ((start = stream.indexOf("\u001b_G")) >= 0) {
|
|
||||||
int end = commandEnd(start + 3);
|
|
||||||
if (end < 0) {
|
|
||||||
if (start > 0) {
|
|
||||||
stream.delete(0, start);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String command = stream.substring(start + 3, end);
|
|
||||||
handleCommand(command);
|
|
||||||
stream.delete(0, end + terminatorLength(end));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream.length() > 16384) {
|
|
||||||
stream.delete(0, stream.length() - 4096);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int commandEnd(int from) {
|
|
||||||
int bell = stream.indexOf("\u0007", from);
|
|
||||||
int st = stream.indexOf("\u001b\\", from);
|
|
||||||
if (bell < 0) {
|
|
||||||
return st;
|
|
||||||
}
|
|
||||||
if (st < 0) {
|
|
||||||
return bell;
|
|
||||||
}
|
|
||||||
return Math.min(bell, st);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int terminatorLength(int end) {
|
|
||||||
return stream.charAt(end) == '\u0007' ? 1 : 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleCommand(String command) {
|
|
||||||
int separator = command.indexOf(';');
|
|
||||||
if (separator < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> control = parseControl(command.substring(0, separator));
|
|
||||||
String payload = command.substring(separator + 1).replace("\n", "").replace("\r", "");
|
|
||||||
|
|
||||||
int id = intControl(control, "i", 1);
|
|
||||||
boolean more = intControl(control, "m", 0) == 1;
|
|
||||||
chunks.computeIfAbsent(id, ignored -> new StringBuilder()).append(payload);
|
|
||||||
if (more) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String data = chunks.remove(id).toString();
|
|
||||||
try {
|
|
||||||
byte[] bytes = Base64.getDecoder().decode(data);
|
|
||||||
Image image = new Image(new ByteArrayInputStream(bytes));
|
|
||||||
if (!image.isError()) {
|
|
||||||
placements.add(new Placement(
|
|
||||||
image,
|
|
||||||
intControl(control, "x", 0),
|
|
||||||
intControl(control, "y", 0),
|
|
||||||
intControl(control, "c", 0),
|
|
||||||
intControl(control, "r", 0)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} catch (IllegalArgumentException ignored) {
|
|
||||||
chunks.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, String> parseControl(String text) {
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
for (String part : text.split(",")) {
|
|
||||||
int equals = part.indexOf('=');
|
|
||||||
if (equals > 0) {
|
|
||||||
result.put(part.substring(0, equals), part.substring(equals + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int intControl(Map<String, String> control, String key, int fallback) {
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(control.getOrDefault(key, String.valueOf(fallback)));
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private record Placement(Image image, int column, int row, int columns, int rows) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,7 @@ public final class Main extends Application {
|
|||||||
StackPane root = new StackPane(terminalView.canvas());
|
StackPane root = new StackPane(terminalView.canvas());
|
||||||
terminalView.canvas().widthProperty().bind(root.widthProperty());
|
terminalView.canvas().widthProperty().bind(root.widthProperty());
|
||||||
terminalView.canvas().heightProperty().bind(root.heightProperty());
|
terminalView.canvas().heightProperty().bind(root.heightProperty());
|
||||||
|
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, event -> handlePressed(config, event));
|
||||||
@@ -38,6 +39,7 @@ public final class Main extends Application {
|
|||||||
workspace.close();
|
workspace.close();
|
||||||
});
|
});
|
||||||
stage.show();
|
stage.show();
|
||||||
|
terminalView.canvas().requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePressed(AppConfig config, KeyEvent event) {
|
private void handlePressed(AppConfig config, KeyEvent event) {
|
||||||
@@ -56,6 +58,15 @@ public final class Main extends Application {
|
|||||||
} else if (config.keybindings().get("toggle_floating").matches(event)) {
|
} else if (config.keybindings().get("toggle_floating").matches(event)) {
|
||||||
workspace.toggleFloating();
|
workspace.toggleFloating();
|
||||||
event.consume();
|
event.consume();
|
||||||
|
} else if (config.keybindings().get("new_floating").matches(event)) {
|
||||||
|
workspace.createFloatingPane();
|
||||||
|
event.consume();
|
||||||
|
} else if (config.keybindings().get("next_floating").matches(event)) {
|
||||||
|
workspace.nextFloatingPane();
|
||||||
|
event.consume();
|
||||||
|
} else if (config.keybindings().get("close_pane").matches(event)) {
|
||||||
|
workspace.closeActivePane();
|
||||||
|
event.consume();
|
||||||
} else {
|
} else {
|
||||||
String encoded = KeyEncoder.encode(event);
|
String encoded = KeyEncoder.encode(event);
|
||||||
if (encoded != null) {
|
if (encoded != null) {
|
||||||
@@ -78,8 +89,7 @@ public final class Main extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.setProperty("prism.order", "es2,sw");
|
System.setProperty("prism.order", System.getProperty("prism.order", "es2,sw"));
|
||||||
System.setProperty("prism.verbose", "true");
|
|
||||||
launch(Main.class, args);
|
launch(Main.class, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
package com.gregor.jprototerm;
|
package com.gregor.jprototerm;
|
||||||
|
|
||||||
|
import com.pty4j.PtyProcess;
|
||||||
|
import com.pty4j.PtyProcessBuilder;
|
||||||
|
import com.pty4j.WinSize;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
public final class ShellSession implements AutoCloseable {
|
public final class ShellSession implements AutoCloseable {
|
||||||
private final Process process;
|
private final PtyProcess process;
|
||||||
private final OutputStream stdin;
|
private final OutputStream stdin;
|
||||||
private final ExecutorService reader;
|
private final ExecutorService reader;
|
||||||
private volatile boolean closed;
|
private volatile boolean closed;
|
||||||
|
|
||||||
private ShellSession(Process process, TerminalPane pane, KittyGraphicsRegistry graphicsRegistry) {
|
private ShellSession(PtyProcess process, TerminalPane pane) {
|
||||||
this.process = process;
|
this.process = process;
|
||||||
this.stdin = process.getOutputStream();
|
this.stdin = process.getOutputStream();
|
||||||
this.reader = Executors.newSingleThreadExecutor(runnable -> {
|
this.reader = Executors.newSingleThreadExecutor(runnable -> {
|
||||||
@@ -22,27 +27,35 @@ public final class ShellSession implements AutoCloseable {
|
|||||||
thread.setDaemon(true);
|
thread.setDaemon(true);
|
||||||
return thread;
|
return thread;
|
||||||
});
|
});
|
||||||
reader.submit(() -> readOutput(pane, graphicsRegistry));
|
reader.submit(() -> readOutput(pane));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShellSession start(String shell, TerminalPane pane, KittyGraphicsRegistry graphicsRegistry) {
|
public static ShellSession start(String shell, TerminalPane pane, int columns, int rows) {
|
||||||
try {
|
try {
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder(
|
Map<String, String> environment = new HashMap<>(System.getenv());
|
||||||
"script",
|
environment.put("TERM", "xterm-kitty");
|
||||||
"-qfec",
|
environment.put("COLORTERM", "truecolor");
|
||||||
shell + " -i",
|
|
||||||
"/dev/null"
|
PtyProcess process = new PtyProcessBuilder(new String[] {shell, "-i"})
|
||||||
).redirectErrorStream(true);
|
.setEnvironment(environment)
|
||||||
processBuilder.environment().put("TERM", "xterm-kitty");
|
.setInitialColumns(columns)
|
||||||
processBuilder.environment().put("COLORTERM", "truecolor");
|
.setInitialRows(rows)
|
||||||
Process process = processBuilder.start();
|
.setDirectory(System.getProperty("user.home"))
|
||||||
return new ShellSession(process, pane, graphicsRegistry);
|
.start();
|
||||||
|
return new ShellSession(process, pane);
|
||||||
} 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 resize(int columns, int rows) {
|
||||||
|
if (closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
process.setWinSize(new WinSize(columns, rows));
|
||||||
|
}
|
||||||
|
|
||||||
public void send(String text) {
|
public void send(String text) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
return;
|
return;
|
||||||
@@ -55,17 +68,17 @@ public final class ShellSession implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readOutput(TerminalPane pane, KittyGraphicsRegistry graphicsRegistry) {
|
private void readOutput(TerminalPane pane) {
|
||||||
byte[] buffer = new byte[8192];
|
byte[] buffer = new byte[8192];
|
||||||
try {
|
try {
|
||||||
int read;
|
int read;
|
||||||
while ((read = process.getInputStream().read(buffer)) != -1) {
|
while ((read = process.getInputStream().read(buffer)) != -1) {
|
||||||
String text = new String(buffer, 0, read, StandardCharsets.UTF_8);
|
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
graphicsRegistry.accept(text);
|
byte[] bytes = new byte[read];
|
||||||
|
System.arraycopy(buffer, 0, bytes, 0, read);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
pane.write(text);
|
pane.write(bytes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,37 @@
|
|||||||
package com.gregor.jprototerm;
|
package com.gregor.jprototerm;
|
||||||
|
|
||||||
|
import dev.jlibghostty.KittyImageCompression;
|
||||||
|
import dev.jlibghostty.KittyImageFormat;
|
||||||
|
import dev.jlibghostty.KittyImageSnapshot;
|
||||||
|
import dev.jlibghostty.KittyPlacement;
|
||||||
|
import dev.jlibghostty.KittyRenderInfo;
|
||||||
|
import dev.jlibghostty.RenderCell;
|
||||||
|
import dev.jlibghostty.RenderColor;
|
||||||
|
import dev.jlibghostty.RenderCursorStyle;
|
||||||
|
import dev.jlibghostty.RenderRow;
|
||||||
|
import dev.jlibghostty.RenderStateSnapshot;
|
||||||
import javafx.scene.canvas.Canvas;
|
import javafx.scene.canvas.Canvas;
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.PixelFormat;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import javafx.scene.text.FontSmoothingType;
|
import javafx.scene.text.FontSmoothingType;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public final class TerminalCanvasView {
|
public final class TerminalCanvasView {
|
||||||
|
private static final Color DEFAULT_FOREGROUND = Color.rgb(225, 229, 235);
|
||||||
|
private static final Color SELECTED_BACKGROUND = Color.rgb(52, 92, 140);
|
||||||
|
|
||||||
private final Canvas canvas = new Canvas();
|
private final Canvas canvas = new Canvas();
|
||||||
private final TerminalWorkspace workspace;
|
private final TerminalWorkspace workspace;
|
||||||
private final AppConfig config;
|
private final AppConfig config;
|
||||||
|
private final Map<Long, Image> kittyImageCache = new HashMap<>();
|
||||||
|
|
||||||
public TerminalCanvasView(TerminalWorkspace workspace, AppConfig config) {
|
public TerminalCanvasView(TerminalWorkspace workspace, AppConfig config) {
|
||||||
this.workspace = workspace;
|
this.workspace = workspace;
|
||||||
@@ -29,7 +51,7 @@ public final class TerminalCanvasView {
|
|||||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||||
gc.setFill(Color.rgb(16, 16, 18));
|
gc.setFill(Color.rgb(16, 16, 18));
|
||||||
gc.fillRect(0, 0, width, height);
|
gc.fillRect(0, 0, width, height);
|
||||||
gc.setFontSmoothingType(FontSmoothingType.GRAY);
|
gc.setFontSmoothingType(FontSmoothingType.LCD);
|
||||||
|
|
||||||
for (TerminalPane pane : workspace.panes()) {
|
for (TerminalPane pane : workspace.panes()) {
|
||||||
drawPane(gc, pane);
|
drawPane(gc, pane);
|
||||||
@@ -55,20 +77,172 @@ public final class TerminalCanvasView {
|
|||||||
|
|
||||||
Font font = Font.font(config.fontFamily(), config.fontSize());
|
Font font = Font.font(config.fontFamily(), config.fontSize());
|
||||||
gc.setFont(font);
|
gc.setFont(font);
|
||||||
gc.setFill(Color.rgb(225, 229, 235));
|
|
||||||
|
|
||||||
double lineHeight = Math.ceil(config.fontSize() * 1.35);
|
FontMetrics metrics = measureFontMetrics(font);
|
||||||
|
int columns = Math.max(1, (int) ((pane.width() - 24.0) / metrics.cellWidth));
|
||||||
|
int rows = Math.max(1, (int) ((pane.height() - 24.0) / metrics.lineHeight));
|
||||||
|
pane.resize(columns, rows, (int) Math.round(metrics.cellWidth), (int) Math.round(metrics.lineHeight));
|
||||||
|
|
||||||
double left = pane.x() + 12.0;
|
double left = pane.x() + 12.0;
|
||||||
double baseline = pane.y() + 18.0;
|
double top = pane.y() + 12.0;
|
||||||
int maxLines = Math.max(1, (int) ((pane.height() - 24.0) / lineHeight));
|
double baseline = top + metrics.baselineOffset;
|
||||||
|
|
||||||
String[] lines = pane.snapshotText().split("\\R", -1);
|
RenderStateSnapshot snapshot = pane.renderSnapshot();
|
||||||
int start = Math.max(0, lines.length - maxLines);
|
if (snapshot != null) {
|
||||||
for (int i = start; i < lines.length; i++) {
|
for (RenderRow row : snapshot.renderRows()) {
|
||||||
gc.fillText(lines[i], left, baseline + ((i - start) * lineHeight));
|
drawRow(gc, row, left, top, baseline, metrics.cellWidth, metrics.lineHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pane.graphicsRegistry().draw(gc, pane.x() + 12.0, pane.y() + 12.0, config.fontSize() * 0.62, lineHeight);
|
if (snapshot != null) {
|
||||||
|
drawCursor(gc, snapshot, left, top, metrics.cellWidth, metrics.lineHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.kittyGraphics()) {
|
||||||
|
drawKittyGraphics(gc, pane, left, top, metrics.cellWidth, metrics.lineHeight);
|
||||||
|
}
|
||||||
gc.restore();
|
gc.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static FontMetrics measureFontMetrics(Font font) {
|
||||||
|
Text text = new Text("Mg");
|
||||||
|
text.setFont(font);
|
||||||
|
double textHeight = text.getLayoutBounds().getHeight();
|
||||||
|
double lineHeight = Math.max(1.0, Math.ceil(textHeight * 1.2));
|
||||||
|
double baselineOffset = -text.getLayoutBounds().getMinY() + ((lineHeight - textHeight) / 2.0);
|
||||||
|
|
||||||
|
Text cell = new Text("M");
|
||||||
|
cell.setFont(font);
|
||||||
|
double cellWidth = Math.max(1.0, Math.ceil(cell.getLayoutBounds().getWidth()));
|
||||||
|
return new FontMetrics(cellWidth, lineHeight, baselineOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void drawRow(
|
||||||
|
GraphicsContext gc,
|
||||||
|
RenderRow row,
|
||||||
|
double left,
|
||||||
|
double top,
|
||||||
|
double baseline,
|
||||||
|
double cellWidth,
|
||||||
|
double lineHeight
|
||||||
|
) {
|
||||||
|
for (RenderCell cell : row.cells()) {
|
||||||
|
double x = left + (cell.column() * cellWidth);
|
||||||
|
double cellTop = top + (row.row() * lineHeight);
|
||||||
|
cell.background().ifPresent(background -> {
|
||||||
|
gc.setFill(toFxColor(background));
|
||||||
|
gc.fillRect(x, cellTop, cellWidth, lineHeight);
|
||||||
|
});
|
||||||
|
if (cell.selected()) {
|
||||||
|
gc.setFill(SELECTED_BACKGROUND);
|
||||||
|
gc.fillRect(x, cellTop, cellWidth, lineHeight);
|
||||||
|
}
|
||||||
|
if (cell.codepoints().length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double y = baseline + (row.row() * lineHeight);
|
||||||
|
Color foreground = cell.foreground().map(TerminalCanvasView::toFxColor).orElse(DEFAULT_FOREGROUND);
|
||||||
|
gc.setFill(foreground);
|
||||||
|
gc.fillText(cell.text(), x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color toFxColor(RenderColor color) {
|
||||||
|
return Color.rgb(color.red(), color.green(), color.blue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void drawCursor(GraphicsContext gc, RenderStateSnapshot snapshot, double left, double top, double cellWidth, double lineHeight) {
|
||||||
|
if (!snapshot.cursorVisible() || !snapshot.cursorViewportHasValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double x = left + (snapshot.cursorViewportX() * cellWidth);
|
||||||
|
double y = top + (snapshot.cursorViewportY() * lineHeight);
|
||||||
|
gc.setStroke(Color.rgb(225, 229, 235));
|
||||||
|
gc.setFill(Color.rgb(225, 229, 235, 0.28));
|
||||||
|
gc.setLineWidth(1.5);
|
||||||
|
|
||||||
|
RenderCursorStyle style = snapshot.cursorStyle();
|
||||||
|
if (style == RenderCursorStyle.BAR) {
|
||||||
|
gc.strokeLine(x + 0.5, y + 2.0, x + 0.5, y + lineHeight - 2.0);
|
||||||
|
} else if (style == RenderCursorStyle.UNDERLINE) {
|
||||||
|
gc.strokeLine(x + 1.0, y + lineHeight - 2.0, x + cellWidth - 1.0, y + lineHeight - 2.0);
|
||||||
|
} else if (style == RenderCursorStyle.BLOCK) {
|
||||||
|
gc.fillRect(x + 0.5, y + 1.0, Math.max(1.0, cellWidth - 1.0), Math.max(1.0, lineHeight - 2.0));
|
||||||
|
} else {
|
||||||
|
gc.strokeRect(x + 0.5, y + 1.0, Math.max(1.0, cellWidth - 1.0), Math.max(1.0, lineHeight - 2.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawKittyGraphics(GraphicsContext gc, TerminalPane pane, double originX, double originY, double cellWidth, double lineHeight) {
|
||||||
|
pane.kittyGraphics().ifPresent(graphics -> {
|
||||||
|
for (KittyPlacement placement : graphics.placements()) {
|
||||||
|
Image image = imageFor(placement);
|
||||||
|
if (image == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
KittyRenderInfo renderInfo = placement.renderInfo().orElse(null);
|
||||||
|
double x = originX;
|
||||||
|
double y = originY;
|
||||||
|
double width = image.getWidth();
|
||||||
|
double height = image.getHeight();
|
||||||
|
|
||||||
|
if (renderInfo != null) {
|
||||||
|
x += renderInfo.viewportColumn() * cellWidth;
|
||||||
|
y += renderInfo.viewportRow() * lineHeight;
|
||||||
|
width = renderInfo.gridColumns() > 0 ? renderInfo.gridColumns() * cellWidth : renderInfo.pixelWidth();
|
||||||
|
height = renderInfo.gridRows() > 0 ? renderInfo.gridRows() * lineHeight : renderInfo.pixelHeight();
|
||||||
|
} else {
|
||||||
|
width = placement.columns() > 0 ? placement.columns() * cellWidth : width;
|
||||||
|
height = placement.rows() > 0 ? placement.rows() * lineHeight : height;
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.drawImage(image, x + placement.xOffset(), y + placement.yOffset(), width, height);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image imageFor(KittyPlacement placement) {
|
||||||
|
return placement.image()
|
||||||
|
.map(snapshot -> kittyImageCache.computeIfAbsent(snapshot.id(), ignored -> decodeImage(snapshot)))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image decodeImage(KittyImageSnapshot snapshot) {
|
||||||
|
if (snapshot.compression() != KittyImageCompression.NONE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.format() == KittyImageFormat.PNG) {
|
||||||
|
return new Image(new ByteArrayInputStream(snapshot.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = Math.toIntExact(snapshot.width());
|
||||||
|
int height = Math.toIntExact(snapshot.height());
|
||||||
|
WritableImage image = new WritableImage(width, height);
|
||||||
|
byte[] data = snapshot.data();
|
||||||
|
|
||||||
|
if (snapshot.format() == KittyImageFormat.RGBA) {
|
||||||
|
image.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), rgbaToBgra(data), 0, width * 4);
|
||||||
|
} else if (snapshot.format() == KittyImageFormat.RGB) {
|
||||||
|
image.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getByteRgbInstance(), data, 0, width * 3);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] rgbaToBgra(byte[] rgba) {
|
||||||
|
byte[] bgra = new byte[rgba.length];
|
||||||
|
for (int i = 0; i + 3 < rgba.length; i += 4) {
|
||||||
|
bgra[i] = rgba[i + 2];
|
||||||
|
bgra[i + 1] = rgba[i + 1];
|
||||||
|
bgra[i + 2] = rgba[i];
|
||||||
|
bgra[i + 3] = rgba[i + 3];
|
||||||
|
}
|
||||||
|
return bgra;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record FontMetrics(double cellWidth, double lineHeight, double baselineOffset) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
package com.gregor.jprototerm;
|
package com.gregor.jprototerm;
|
||||||
|
|
||||||
import dev.jlibghostty.Ghostty;
|
import dev.jlibghostty.Ghostty;
|
||||||
|
import dev.jlibghostty.KittyGraphics;
|
||||||
|
import dev.jlibghostty.RenderStateSnapshot;
|
||||||
import dev.jlibghostty.Terminal;
|
import dev.jlibghostty.Terminal;
|
||||||
import dev.jlibghostty.TerminalOptions;
|
import dev.jlibghostty.TerminalOptions;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
public final class TerminalPane implements AutoCloseable {
|
public final class TerminalPane implements AutoCloseable {
|
||||||
private final Terminal terminal;
|
private final Terminal terminal;
|
||||||
private final KittyGraphicsRegistry graphicsRegistry;
|
private final AtomicReference<RenderStateSnapshot> renderSnapshot = new AtomicReference<>();
|
||||||
private final AtomicReference<String> snapshotText = new AtomicReference<>("");
|
|
||||||
private ShellSession session;
|
private ShellSession session;
|
||||||
private boolean floating;
|
private boolean floating;
|
||||||
|
private boolean visible = true;
|
||||||
private double x;
|
private double x;
|
||||||
private double y;
|
private double y;
|
||||||
private double width;
|
private double width;
|
||||||
private double height;
|
private double height;
|
||||||
|
private int columns;
|
||||||
|
private int rows;
|
||||||
|
private int pixelWidth;
|
||||||
|
private int pixelHeight;
|
||||||
|
|
||||||
private TerminalPane(Terminal terminal, KittyGraphicsRegistry graphicsRegistry) {
|
private TerminalPane(Terminal terminal, int columns, int rows) {
|
||||||
this.terminal = terminal;
|
this.terminal = terminal;
|
||||||
this.graphicsRegistry = graphicsRegistry;
|
this.columns = columns;
|
||||||
|
this.rows = rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TerminalPane create(int columns, int rows, boolean kittyGraphics) {
|
public static TerminalPane create(int columns, int rows) {
|
||||||
Terminal terminal = Ghostty.open(TerminalOptions.of(columns, rows));
|
Terminal terminal = Ghostty.open(TerminalOptions.of(columns, rows));
|
||||||
TerminalPane pane = new TerminalPane(terminal, new KittyGraphicsRegistry(kittyGraphics));
|
TerminalPane pane = new TerminalPane(terminal, columns, rows);
|
||||||
pane.refresh();
|
pane.refresh();
|
||||||
return pane;
|
return pane;
|
||||||
}
|
}
|
||||||
@@ -36,6 +44,13 @@ public final class TerminalPane implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void write(byte[] bytes) {
|
||||||
|
synchronized (terminal) {
|
||||||
|
terminal.write(bytes);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void attach(ShellSession session) {
|
public void attach(ShellSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
@@ -46,12 +61,14 @@ public final class TerminalPane implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String snapshotText() {
|
public RenderStateSnapshot renderSnapshot() {
|
||||||
return snapshotText.get();
|
return renderSnapshot.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public KittyGraphicsRegistry graphicsRegistry() {
|
public Optional<KittyGraphics> kittyGraphics() {
|
||||||
return graphicsRegistry;
|
synchronized (terminal) {
|
||||||
|
return terminal.kittyGraphics();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean floating() {
|
public boolean floating() {
|
||||||
@@ -62,6 +79,14 @@ public final class TerminalPane implements AutoCloseable {
|
|||||||
this.floating = floating;
|
this.floating = floating;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean visible() {
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVisible(boolean visible) {
|
||||||
|
this.visible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
public double x() {
|
public double x() {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
@@ -85,8 +110,29 @@ public final class TerminalPane implements AutoCloseable {
|
|||||||
this.height = height;
|
this.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void resize(int columns, int rows, int pixelWidth, int pixelHeight) {
|
||||||
|
if (columns <= 0 || rows <= 0 || pixelWidth <= 0 || pixelHeight <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.columns == columns && this.rows == rows && this.pixelWidth == pixelWidth && this.pixelHeight == pixelHeight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (terminal) {
|
||||||
|
terminal.resize(columns, rows, pixelWidth, pixelHeight);
|
||||||
|
if (session != null) {
|
||||||
|
session.resize(columns, rows);
|
||||||
|
}
|
||||||
|
this.columns = columns;
|
||||||
|
this.rows = rows;
|
||||||
|
this.pixelWidth = pixelWidth;
|
||||||
|
this.pixelHeight = pixelHeight;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
snapshotText.set(String.valueOf(terminal.snapshot()));
|
renderSnapshot.set(terminal.renderSnapshot());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public final class TerminalWorkspace implements AutoCloseable {
|
|||||||
private final AppConfig config;
|
private final AppConfig config;
|
||||||
private final List<TerminalPane> panes = new ArrayList<>();
|
private final List<TerminalPane> panes = new ArrayList<>();
|
||||||
private int activeIndex;
|
private int activeIndex;
|
||||||
|
private int hiddenFloatingFocusIndex = -1;
|
||||||
|
|
||||||
public TerminalWorkspace(AppConfig config) {
|
public TerminalWorkspace(AppConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
@@ -19,7 +20,18 @@ public final class TerminalWorkspace implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<TerminalPane> panes() {
|
public List<TerminalPane> panes() {
|
||||||
return List.copyOf(panes);
|
List<TerminalPane> visible = panes.stream().filter(TerminalPane::visible).toList();
|
||||||
|
TerminalPane active = activePane();
|
||||||
|
if (!active.visible() || !active.floating()) {
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TerminalPane> ordered = new ArrayList<>(visible.size());
|
||||||
|
visible.stream()
|
||||||
|
.filter(pane -> pane != active)
|
||||||
|
.forEach(ordered::add);
|
||||||
|
ordered.add(active);
|
||||||
|
return List.copyOf(ordered);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isActive(TerminalPane pane) {
|
public boolean isActive(TerminalPane pane) {
|
||||||
@@ -27,20 +39,29 @@ public final class TerminalWorkspace implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void layout(double width, double height) {
|
public void layout(double width, double height) {
|
||||||
List<TerminalPane> tiled = panes.stream().filter(pane -> !pane.floating()).toList();
|
List<TerminalPane> tiled = panes.stream()
|
||||||
|
.filter(TerminalPane::visible)
|
||||||
|
.filter(pane -> !pane.floating())
|
||||||
|
.toList();
|
||||||
int tileCount = Math.max(1, tiled.size());
|
int tileCount = Math.max(1, tiled.size());
|
||||||
double tileWidth = width / tileCount;
|
double tileWidth = width / tileCount;
|
||||||
for (int i = 0; i < tiled.size(); i++) {
|
for (int i = 0; i < tiled.size(); i++) {
|
||||||
tiled.get(i).bounds(i * tileWidth, 0, tileWidth, height);
|
tiled.get(i).bounds(i * tileWidth, 0, tileWidth, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TerminalPane pane : panes) {
|
List<TerminalPane> floating = panes.stream()
|
||||||
if (pane.floating()) {
|
.filter(TerminalPane::visible)
|
||||||
|
.filter(TerminalPane::floating)
|
||||||
|
.toList();
|
||||||
|
for (int i = 0; i < floating.size(); i++) {
|
||||||
|
TerminalPane pane = floating.get(i);
|
||||||
|
if (pane.visible() && pane.floating()) {
|
||||||
double floatingWidth = Math.max(420, width * 0.58);
|
double floatingWidth = Math.max(420, width * 0.58);
|
||||||
double floatingHeight = Math.max(260, height * 0.58);
|
double floatingHeight = Math.max(260, height * 0.58);
|
||||||
|
double offset = i * 28.0;
|
||||||
pane.bounds(
|
pane.bounds(
|
||||||
(width - floatingWidth) / 2.0,
|
Math.min(width - floatingWidth - 12.0, ((width - floatingWidth) / 2.0) + offset),
|
||||||
(height - floatingHeight) / 2.0,
|
Math.min(height - floatingHeight - 12.0, ((height - floatingHeight) / 2.0) + offset),
|
||||||
floatingWidth,
|
floatingWidth,
|
||||||
floatingHeight
|
floatingHeight
|
||||||
);
|
);
|
||||||
@@ -50,7 +71,12 @@ public final class TerminalWorkspace implements AutoCloseable {
|
|||||||
|
|
||||||
public void navigate(Direction direction) {
|
public void navigate(Direction direction) {
|
||||||
TerminalPane current = activePane();
|
TerminalPane current = activePane();
|
||||||
|
if (current.floating() && navigateFloatingStack(direction)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
panes.stream()
|
panes.stream()
|
||||||
|
.filter(TerminalPane::visible)
|
||||||
.filter(pane -> pane != current)
|
.filter(pane -> pane != current)
|
||||||
.filter(pane -> directionFilter(direction, current, pane))
|
.filter(pane -> directionFilter(direction, current, pane))
|
||||||
.min(Comparator.comparingDouble(pane -> distance(current, pane)))
|
.min(Comparator.comparingDouble(pane -> distance(current, pane)))
|
||||||
@@ -58,23 +84,160 @@ public final class TerminalWorkspace implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void toggleFloating() {
|
public void toggleFloating() {
|
||||||
TerminalPane active = activePane();
|
List<TerminalPane> floating = panes.stream()
|
||||||
if (active.floating()) {
|
.filter(TerminalPane::floating)
|
||||||
panes.remove(activeIndex);
|
.toList();
|
||||||
active.close();
|
if (floating.isEmpty()) {
|
||||||
activeIndex = Math.max(0, activeIndex - 1);
|
createFloatingPane();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean anyVisible = floating.stream().anyMatch(TerminalPane::visible);
|
||||||
|
if (anyVisible) {
|
||||||
|
TerminalPane active = activePane();
|
||||||
|
hiddenFloatingFocusIndex = active.floating() ? activeIndex : firstVisibleFloatingIndex();
|
||||||
|
floating.forEach(pane -> pane.setVisible(false));
|
||||||
|
activeIndex = firstVisibleNonFloatingIndex();
|
||||||
|
} else {
|
||||||
|
floating.forEach(pane -> pane.setVisible(true));
|
||||||
|
activeIndex = visibleIndexOrFallback(hiddenFloatingFocusIndex, panes.indexOf(floating.get(floating.size() - 1)));
|
||||||
|
hiddenFloatingFocusIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createFloatingPane() {
|
||||||
TerminalPane pane = openPane(true);
|
TerminalPane pane = openPane(true);
|
||||||
panes.add(pane);
|
panes.add(pane);
|
||||||
activeIndex = panes.size() - 1;
|
activeIndex = panes.size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void nextFloatingPane() {
|
||||||
|
TerminalPane next = nextFloatingAfter(activeIndex);
|
||||||
|
next.setVisible(true);
|
||||||
|
activeIndex = panes.indexOf(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeActivePane() {
|
||||||
|
TerminalPane active = activePane();
|
||||||
|
if (!active.floating() || panes.stream().filter(pane -> !pane.floating()).count() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int removed = activeIndex;
|
||||||
|
int previous = previousVisibleIndex(removed);
|
||||||
|
panes.remove(removed);
|
||||||
|
active.close();
|
||||||
|
activeIndex = adjustIndexAfterRemoval(previous, removed);
|
||||||
|
hiddenFloatingFocusIndex = adjustHiddenFocusAfterRemoval(hiddenFloatingFocusIndex, removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalPane nextFloatingAfter(int index) {
|
||||||
|
for (int i = index + 1; i < panes.size(); i++) {
|
||||||
|
TerminalPane pane = panes.get(i);
|
||||||
|
if (pane.floating()) {
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i <= index && i < panes.size(); i++) {
|
||||||
|
TerminalPane pane = panes.get(i);
|
||||||
|
if (pane.floating()) {
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createAndReturnFloatingPane();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalPane createAndReturnFloatingPane() {
|
||||||
|
TerminalPane pane = openPane(true);
|
||||||
|
panes.add(pane);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean navigateFloatingStack(Direction direction) {
|
||||||
|
List<TerminalPane> floating = panes.stream()
|
||||||
|
.filter(TerminalPane::visible)
|
||||||
|
.filter(TerminalPane::floating)
|
||||||
|
.toList();
|
||||||
|
if (floating.size() < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int current = floating.indexOf(activePane());
|
||||||
|
if (current < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int next = switch (direction) {
|
||||||
|
case LEFT, UP -> current - 1;
|
||||||
|
case DOWN, RIGHT -> current + 1;
|
||||||
|
};
|
||||||
|
if (next < 0 || next >= floating.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeIndex = panes.indexOf(floating.get(next));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int firstVisibleFloatingIndex() {
|
||||||
|
for (int i = 0; i < panes.size(); i++) {
|
||||||
|
TerminalPane pane = panes.get(i);
|
||||||
|
if (pane.visible() && pane.floating()) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int firstVisibleNonFloatingIndex() {
|
||||||
|
for (int i = 0; i < panes.size(); i++) {
|
||||||
|
TerminalPane pane = panes.get(i);
|
||||||
|
if (pane.visible() && !pane.floating()) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int previousVisibleIndex(int index) {
|
||||||
|
for (int i = index - 1; i >= 0; i--) {
|
||||||
|
if (panes.get(i).visible()) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = index + 1; i < panes.size(); i++) {
|
||||||
|
if (panes.get(i).visible()) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstVisibleNonFloatingIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int visibleIndexOrFallback(int index, int fallback) {
|
||||||
|
if (index >= 0 && index < panes.size() && panes.get(index).visible()) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int adjustIndexAfterRemoval(int index, int removedIndex) {
|
||||||
|
if (index < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return index > removedIndex ? index - 1 : index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int adjustHiddenFocusAfterRemoval(int index, int removedIndex) {
|
||||||
|
if (index < 0 || index == removedIndex) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return index > removedIndex ? index - 1 : index;
|
||||||
|
}
|
||||||
|
|
||||||
private TerminalPane openPane(boolean floating) {
|
private TerminalPane openPane(boolean floating) {
|
||||||
TerminalPane pane = TerminalPane.create(config.columns(), config.rows(), config.kittyGraphics());
|
TerminalPane pane = TerminalPane.create(config.columns(), config.rows());
|
||||||
pane.setFloating(floating);
|
pane.setFloating(floating);
|
||||||
pane.attach(ShellSession.start(config.shell(), pane, pane.graphicsRegistry()));
|
pane.attach(ShellSession.start(config.shell(), pane, config.columns(), config.rows()));
|
||||||
return pane;
|
return pane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "java.lang.Object",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.lang.String",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.lang.Runnable",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.lang.Thread",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.nio.Buffer",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.nio.ByteBuffer",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.nio.DirectByteBuffer",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.nio.IntBuffer",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.nio.FloatBuffer",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.nio.LongBuffer",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "java.nio.ShortBuffer",
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.gtk.GtkApplication",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.gtk.GtkWindow",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.gtk.GtkView",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.Application",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.Window",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.View",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.Screen",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.Pixels",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.Clipboard",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.Cursor",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.CommonDialogs",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicMethods": true,
|
|
||||||
"allDeclaredMethods": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "com.gregor.jprototerm.Main",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicConstructors": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.javafx.tk.quantum.QuantumToolkit",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicConstructors": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.gtk.GtkPlatformFactory",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicConstructors": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.glass.ui.gtk.GtkApplication",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicConstructors": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.prism.es2.ES2Pipeline",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicConstructors": true,
|
|
||||||
"allDeclaredMethods": true,
|
|
||||||
"allPublicMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.prism.es2.X11GLFactory",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicConstructors": true,
|
|
||||||
"allDeclaredMethods": true,
|
|
||||||
"allPublicMethods": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "com.sun.prism.sw.SWPipeline",
|
|
||||||
"allDeclaredConstructors": true,
|
|
||||||
"allPublicConstructors": true,
|
|
||||||
"allDeclaredMethods": true,
|
|
||||||
"allPublicMethods": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"resources": [
|
|
||||||
{ "pattern": ".*\\.css$" },
|
|
||||||
{ "pattern": ".*\\.toml$" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user