diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..be88c88 --- /dev/null +++ b/.classpath @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index b2be92b..ed72e43 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,12 @@ result + +# Devenv +.devenv* +devenv.local.nix +devenv.local.yaml + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/.gradle/8.14.4/fileChanges/last-build.bin b/.gradle/8.14.4/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/8.14.4/fileChanges/last-build.bin differ diff --git a/.gradle/8.14.4/fileHashes/fileHashes.lock b/.gradle/8.14.4/fileHashes/fileHashes.lock new file mode 100644 index 0000000..71936ad Binary files /dev/null and b/.gradle/8.14.4/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.14.4/gc.properties b/.gradle/8.14.4/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/8.9/checksums/checksums.lock b/.gradle/8.9/checksums/checksums.lock new file mode 100644 index 0000000..6b42c4e Binary files /dev/null and b/.gradle/8.9/checksums/checksums.lock differ diff --git a/.gradle/8.9/checksums/md5-checksums.bin b/.gradle/8.9/checksums/md5-checksums.bin new file mode 100644 index 0000000..f5f7e88 Binary files /dev/null and b/.gradle/8.9/checksums/md5-checksums.bin differ diff --git a/.gradle/8.9/checksums/sha1-checksums.bin b/.gradle/8.9/checksums/sha1-checksums.bin new file mode 100644 index 0000000..33d1488 Binary files /dev/null and b/.gradle/8.9/checksums/sha1-checksums.bin differ diff --git a/.gradle/8.9/dependencies-accessors/gc.properties b/.gradle/8.9/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/8.9/executionHistory/executionHistory.lock b/.gradle/8.9/executionHistory/executionHistory.lock new file mode 100644 index 0000000..8afbfe1 Binary files /dev/null and b/.gradle/8.9/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.9/fileChanges/last-build.bin b/.gradle/8.9/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/8.9/fileChanges/last-build.bin differ diff --git a/.gradle/8.9/fileHashes/fileHashes.lock b/.gradle/8.9/fileHashes/fileHashes.lock new file mode 100644 index 0000000..1d4ce21 Binary files /dev/null and b/.gradle/8.9/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.9/gc.properties b/.gradle/8.9/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/9.4.1/checksums/checksums.lock b/.gradle/9.4.1/checksums/checksums.lock new file mode 100644 index 0000000..98fc7a5 Binary files /dev/null and b/.gradle/9.4.1/checksums/checksums.lock differ diff --git a/.gradle/9.4.1/checksums/md5-checksums.bin b/.gradle/9.4.1/checksums/md5-checksums.bin new file mode 100644 index 0000000..b01b8c5 Binary files /dev/null and b/.gradle/9.4.1/checksums/md5-checksums.bin differ diff --git a/.gradle/9.4.1/checksums/sha1-checksums.bin b/.gradle/9.4.1/checksums/sha1-checksums.bin new file mode 100644 index 0000000..8651f4c Binary files /dev/null and b/.gradle/9.4.1/checksums/sha1-checksums.bin differ diff --git a/.gradle/9.4.1/executionHistory/executionHistory.bin b/.gradle/9.4.1/executionHistory/executionHistory.bin new file mode 100644 index 0000000..1a9b6d6 Binary files /dev/null and b/.gradle/9.4.1/executionHistory/executionHistory.bin differ diff --git a/.gradle/9.4.1/executionHistory/executionHistory.lock b/.gradle/9.4.1/executionHistory/executionHistory.lock new file mode 100644 index 0000000..ac2b076 Binary files /dev/null and b/.gradle/9.4.1/executionHistory/executionHistory.lock differ diff --git a/.gradle/9.4.1/fileChanges/last-build.bin b/.gradle/9.4.1/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/9.4.1/fileChanges/last-build.bin differ diff --git a/.gradle/9.4.1/fileHashes/fileHashes.bin b/.gradle/9.4.1/fileHashes/fileHashes.bin new file mode 100644 index 0000000..2e15714 Binary files /dev/null and b/.gradle/9.4.1/fileHashes/fileHashes.bin differ diff --git a/.gradle/9.4.1/fileHashes/fileHashes.lock b/.gradle/9.4.1/fileHashes/fileHashes.lock new file mode 100644 index 0000000..a2cbede Binary files /dev/null and b/.gradle/9.4.1/fileHashes/fileHashes.lock differ diff --git a/.gradle/9.4.1/fileHashes/resourceHashesCache.bin b/.gradle/9.4.1/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..48d03dd Binary files /dev/null and b/.gradle/9.4.1/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/9.4.1/gc.properties b/.gradle/9.4.1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..3413bb3 Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..8ef438f --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Wed May 27 23:44:22 CEST 2026 +gradle.version=9.4.1 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..f25149a Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe new file mode 100644 index 0000000..676e00e Binary files /dev/null and b/.gradle/file-system.probe differ diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.project b/.project new file mode 100644 index 0000000..250acc4 --- /dev/null +++ b/.project @@ -0,0 +1,34 @@ + + + jprototerm + Project jprototerm created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1779917652126 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..97aed87 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -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 diff --git a/README.md b/README.md index 23d2aa0..43dd93f 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,17 @@ JavaFX canvas terminal prototype using `jlibghostty` for terminal emulation, Nix 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: ```sh nix develop 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 @@ -57,11 +57,17 @@ navigate_down = "ALT+J" navigate_up = "ALT+K" navigate_right = "ALT+L" toggle_floating = "ALT+F" +new_floating = "ALT+SHIFT+F" +next_floating = "ALT+F12" +close_pane = "ALT+X" ``` ## Defaults - `Alt+h/j/k/l`: navigate panes -- `Alt+f`: open or close a floating pane -- Font default: `Symbols Nerd Font Mono` +- `Alt+f`: show or hide all floating panes +- `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 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3635554 --- /dev/null +++ b/build.gradle @@ -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'] +} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 72e980d..0000000 --- a/build.gradle.kts +++ /dev/null @@ -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" -} diff --git a/build/classes/java/main/com/gregor/jprototerm/AppConfig.class b/build/classes/java/main/com/gregor/jprototerm/AppConfig.class new file mode 100644 index 0000000..24abfbb Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/AppConfig.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/Direction.class b/build/classes/java/main/com/gregor/jprototerm/Direction.class new file mode 100644 index 0000000..e540aa0 Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/Direction.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/KeyBinding.class b/build/classes/java/main/com/gregor/jprototerm/KeyBinding.class new file mode 100644 index 0000000..26acc65 Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/KeyBinding.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/KeyEncoder$1.class b/build/classes/java/main/com/gregor/jprototerm/KeyEncoder$1.class new file mode 100644 index 0000000..09505a7 Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/KeyEncoder$1.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/KeyEncoder.class b/build/classes/java/main/com/gregor/jprototerm/KeyEncoder.class new file mode 100644 index 0000000..56c0172 Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/KeyEncoder.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/Main$1.class b/build/classes/java/main/com/gregor/jprototerm/Main$1.class new file mode 100644 index 0000000..068e051 Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/Main$1.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/Main.class b/build/classes/java/main/com/gregor/jprototerm/Main.class new file mode 100644 index 0000000..72439f5 Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/Main.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/ShellSession.class b/build/classes/java/main/com/gregor/jprototerm/ShellSession.class new file mode 100644 index 0000000..9a4d193 Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/ShellSession.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/TerminalCanvasView$FontMetrics.class b/build/classes/java/main/com/gregor/jprototerm/TerminalCanvasView$FontMetrics.class new file mode 100644 index 0000000..37dff2d Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/TerminalCanvasView$FontMetrics.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/TerminalCanvasView.class b/build/classes/java/main/com/gregor/jprototerm/TerminalCanvasView.class new file mode 100644 index 0000000..de9420e Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/TerminalCanvasView.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/TerminalPane.class b/build/classes/java/main/com/gregor/jprototerm/TerminalPane.class new file mode 100644 index 0000000..2b79a1b Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/TerminalPane.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/TerminalWorkspace$1.class b/build/classes/java/main/com/gregor/jprototerm/TerminalWorkspace$1.class new file mode 100644 index 0000000..2bd6d75 Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/TerminalWorkspace$1.class differ diff --git a/build/classes/java/main/com/gregor/jprototerm/TerminalWorkspace.class b/build/classes/java/main/com/gregor/jprototerm/TerminalWorkspace.class new file mode 100644 index 0000000..b81a68f Binary files /dev/null and b/build/classes/java/main/com/gregor/jprototerm/TerminalWorkspace.class differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/Main$1.class.uniqueId5 b/build/tmp/compileJava/compileTransaction/stash-dir/Main$1.class.uniqueId5 new file mode 100644 index 0000000..068e051 Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/Main$1.class.uniqueId5 differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/Main.class.uniqueId3 b/build/tmp/compileJava/compileTransaction/stash-dir/Main.class.uniqueId3 new file mode 100644 index 0000000..72439f5 Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/Main.class.uniqueId3 differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/TerminalCanvasView$FontMetrics.class.uniqueId4 b/build/tmp/compileJava/compileTransaction/stash-dir/TerminalCanvasView$FontMetrics.class.uniqueId4 new file mode 100644 index 0000000..37dff2d Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/TerminalCanvasView$FontMetrics.class.uniqueId4 differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/TerminalCanvasView.class.uniqueId1 b/build/tmp/compileJava/compileTransaction/stash-dir/TerminalCanvasView.class.uniqueId1 new file mode 100644 index 0000000..de9420e Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/TerminalCanvasView.class.uniqueId1 differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/TerminalWorkspace$1.class.uniqueId2 b/build/tmp/compileJava/compileTransaction/stash-dir/TerminalWorkspace$1.class.uniqueId2 new file mode 100644 index 0000000..cae4c9b Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/TerminalWorkspace$1.class.uniqueId2 differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/TerminalWorkspace.class.uniqueId0 b/build/tmp/compileJava/compileTransaction/stash-dir/TerminalWorkspace.class.uniqueId0 new file mode 100644 index 0000000..9774dde Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/TerminalWorkspace.class.uniqueId0 differ diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000..adba47d Binary files /dev/null and b/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/config.example.toml b/config.example.toml index f6f5df2..cb450ee 100644 --- a/config.example.toml +++ b/config.example.toml @@ -18,3 +18,6 @@ navigate_down = "ALT+J" navigate_up = "ALT+K" navigate_right = "ALT+L" toggle_floating = "ALT+F" +new_floating = "ALT+SHIFT+F" +next_floating = "ALT+F12" +close_pane = "ALT+X" diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..5834f80 --- /dev/null +++ b/devenv.lock @@ -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 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..78379c6 --- /dev/null +++ b/devenv.nix @@ -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"; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..4da5c66 --- /dev/null +++ b/devenv.yaml @@ -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 diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 14956f4..0000000 --- a/flake.lock +++ /dev/null @@ -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 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 5e081d8..0000000 --- a/flake.nix +++ /dev/null @@ -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" - ''; - }; - }; -} diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index 9ea7c35..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -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" diff --git a/src/main/java/com/gregor/jprototerm/AppConfig.java b/src/main/java/com/gregor/jprototerm/AppConfig.java index 983f12b..cd07328 100644 --- a/src/main/java/com/gregor/jprototerm/AppConfig.java +++ b/src/main/java/com/gregor/jprototerm/AppConfig.java @@ -45,7 +45,10 @@ public record AppConfig( "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")) + "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) { @@ -59,7 +62,7 @@ public record AppConfig( 100, 30, defaultShell(), - "Symbols Nerd Font Mono", + "JetBrainsMono Nerd Font", 15.0, 1200.0, 760.0, @@ -69,7 +72,10 @@ public record AppConfig( "navigate_down", KeyBinding.parse("ALT+J"), "navigate_up", KeyBinding.parse("ALT+K"), "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() { - String shell = System.getenv("SHELL"); - return shell == null || shell.isBlank() ? "/bin/sh" : shell; + return "/bin/bash"; } private static KeyBinding binding(TomlTable table, String key, KeyBinding fallback) { diff --git a/src/main/java/com/gregor/jprototerm/KeyBinding.java b/src/main/java/com/gregor/jprototerm/KeyBinding.java index 996521d..1c0ebe9 100644 --- a/src/main/java/com/gregor/jprototerm/KeyBinding.java +++ b/src/main/java/com/gregor/jprototerm/KeyBinding.java @@ -18,7 +18,7 @@ public record KeyBinding(boolean alt, boolean control, boolean shift, KeyCode co case "ALT", "META" -> alt = true; case "CTRL", "CONTROL" -> control = 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.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); + } + } } diff --git a/src/main/java/com/gregor/jprototerm/KeyEncoder.java b/src/main/java/com/gregor/jprototerm/KeyEncoder.java index 795274c..765b1b3 100644 --- a/src/main/java/com/gregor/jprototerm/KeyEncoder.java +++ b/src/main/java/com/gregor/jprototerm/KeyEncoder.java @@ -8,6 +8,20 @@ final class KeyEncoder { } 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(); return switch (code) { case ENTER -> "\r"; @@ -23,7 +37,43 @@ final class KeyEncoder { case DELETE -> "\u001b[3~"; case PAGE_UP -> "\u001b[5~"; 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; }; } + + 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; + } } diff --git a/src/main/java/com/gregor/jprototerm/KittyGraphicsRegistry.java b/src/main/java/com/gregor/jprototerm/KittyGraphicsRegistry.java deleted file mode 100644 index e35ab9e..0000000 --- a/src/main/java/com/gregor/jprototerm/KittyGraphicsRegistry.java +++ /dev/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 chunks = new HashMap<>(); - private final List 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 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 parseControl(String text) { - Map 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 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) { - } -} diff --git a/src/main/java/com/gregor/jprototerm/Main.java b/src/main/java/com/gregor/jprototerm/Main.java index adae33a..911c412 100644 --- a/src/main/java/com/gregor/jprototerm/Main.java +++ b/src/main/java/com/gregor/jprototerm/Main.java @@ -20,6 +20,7 @@ public final class Main extends Application { StackPane root = new StackPane(terminalView.canvas()); terminalView.canvas().widthProperty().bind(root.widthProperty()); terminalView.canvas().heightProperty().bind(root.heightProperty()); + terminalView.canvas().setOnMousePressed(event -> terminalView.canvas().requestFocus()); Scene scene = new Scene(root, config.windowWidth(), config.windowHeight()); scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> handlePressed(config, event)); @@ -38,6 +39,7 @@ public final class Main extends Application { workspace.close(); }); stage.show(); + terminalView.canvas().requestFocus(); } 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)) { workspace.toggleFloating(); 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 { String encoded = KeyEncoder.encode(event); if (encoded != null) { @@ -78,8 +89,7 @@ public final class Main extends Application { } public static void main(String[] args) { - System.setProperty("prism.order", "es2,sw"); - System.setProperty("prism.verbose", "true"); + System.setProperty("prism.order", System.getProperty("prism.order", "es2,sw")); launch(Main.class, args); } } diff --git a/src/main/java/com/gregor/jprototerm/ShellSession.java b/src/main/java/com/gregor/jprototerm/ShellSession.java index 7f38311..64101cd 100644 --- a/src/main/java/com/gregor/jprototerm/ShellSession.java +++ b/src/main/java/com/gregor/jprototerm/ShellSession.java @@ -1,20 +1,25 @@ package com.gregor.jprototerm; +import com.pty4j.PtyProcess; +import com.pty4j.PtyProcessBuilder; +import com.pty4j.WinSize; import javafx.application.Platform; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public final class ShellSession implements AutoCloseable { - private final Process process; + private final PtyProcess process; private final OutputStream stdin; private final ExecutorService reader; private volatile boolean closed; - private ShellSession(Process process, TerminalPane pane, KittyGraphicsRegistry graphicsRegistry) { + private ShellSession(PtyProcess process, TerminalPane pane) { this.process = process; this.stdin = process.getOutputStream(); this.reader = Executors.newSingleThreadExecutor(runnable -> { @@ -22,27 +27,35 @@ public final class ShellSession implements AutoCloseable { thread.setDaemon(true); 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 { - ProcessBuilder processBuilder = new ProcessBuilder( - "script", - "-qfec", - shell + " -i", - "/dev/null" - ).redirectErrorStream(true); - processBuilder.environment().put("TERM", "xterm-kitty"); - processBuilder.environment().put("COLORTERM", "truecolor"); - Process process = processBuilder.start(); - return new ShellSession(process, pane, graphicsRegistry); + Map environment = new HashMap<>(System.getenv()); + environment.put("TERM", "xterm-kitty"); + environment.put("COLORTERM", "truecolor"); + + PtyProcess process = new PtyProcessBuilder(new String[] {shell, "-i"}) + .setEnvironment(environment) + .setInitialColumns(columns) + .setInitialRows(rows) + .setDirectory(System.getProperty("user.home")) + .start(); + return new ShellSession(process, pane); } catch (IOException ex) { pane.write("failed to start shell: " + ex.getMessage() + "\r\n"); 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) { if (closed) { 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]; try { int read; while ((read = process.getInputStream().read(buffer)) != -1) { - String text = new String(buffer, 0, read, StandardCharsets.UTF_8); if (!closed) { - graphicsRegistry.accept(text); + byte[] bytes = new byte[read]; + System.arraycopy(buffer, 0, bytes, 0, read); Platform.runLater(() -> { if (!closed) { - pane.write(text); + pane.write(bytes); } }); } diff --git a/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java b/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java index 24ecc70..cf03466 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java +++ b/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java @@ -1,15 +1,37 @@ 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.GraphicsContext; +import javafx.scene.image.Image; +import javafx.scene.image.PixelFormat; +import javafx.scene.image.WritableImage; import javafx.scene.paint.Color; import javafx.scene.text.Font; 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 { + 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 TerminalWorkspace workspace; private final AppConfig config; + private final Map kittyImageCache = new HashMap<>(); public TerminalCanvasView(TerminalWorkspace workspace, AppConfig config) { this.workspace = workspace; @@ -29,7 +51,7 @@ public final class TerminalCanvasView { GraphicsContext gc = canvas.getGraphicsContext2D(); gc.setFill(Color.rgb(16, 16, 18)); gc.fillRect(0, 0, width, height); - gc.setFontSmoothingType(FontSmoothingType.GRAY); + gc.setFontSmoothingType(FontSmoothingType.LCD); for (TerminalPane pane : workspace.panes()) { drawPane(gc, pane); @@ -55,20 +77,172 @@ public final class TerminalCanvasView { Font font = Font.font(config.fontFamily(), config.fontSize()); 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 baseline = pane.y() + 18.0; - int maxLines = Math.max(1, (int) ((pane.height() - 24.0) / lineHeight)); + double top = pane.y() + 12.0; + double baseline = top + metrics.baselineOffset; - String[] lines = pane.snapshotText().split("\\R", -1); - int start = Math.max(0, lines.length - maxLines); - for (int i = start; i < lines.length; i++) { - gc.fillText(lines[i], left, baseline + ((i - start) * lineHeight)); + RenderStateSnapshot snapshot = pane.renderSnapshot(); + if (snapshot != null) { + for (RenderRow row : snapshot.renderRows()) { + 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(); } + + 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) { + } } diff --git a/src/main/java/com/gregor/jprototerm/TerminalPane.java b/src/main/java/com/gregor/jprototerm/TerminalPane.java index 6c41cf0..b2b35d0 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalPane.java +++ b/src/main/java/com/gregor/jprototerm/TerminalPane.java @@ -1,30 +1,38 @@ package com.gregor.jprototerm; import dev.jlibghostty.Ghostty; +import dev.jlibghostty.KittyGraphics; +import dev.jlibghostty.RenderStateSnapshot; import dev.jlibghostty.Terminal; import dev.jlibghostty.TerminalOptions; +import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; public final class TerminalPane implements AutoCloseable { private final Terminal terminal; - private final KittyGraphicsRegistry graphicsRegistry; - private final AtomicReference snapshotText = new AtomicReference<>(""); + private final AtomicReference renderSnapshot = new AtomicReference<>(); private ShellSession session; private boolean floating; + private boolean visible = true; private double x; private double y; private double width; 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.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)); - TerminalPane pane = new TerminalPane(terminal, new KittyGraphicsRegistry(kittyGraphics)); + TerminalPane pane = new TerminalPane(terminal, columns, rows); pane.refresh(); 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) { this.session = session; } @@ -46,12 +61,14 @@ public final class TerminalPane implements AutoCloseable { } } - public String snapshotText() { - return snapshotText.get(); + public RenderStateSnapshot renderSnapshot() { + return renderSnapshot.get(); } - public KittyGraphicsRegistry graphicsRegistry() { - return graphicsRegistry; + public Optional kittyGraphics() { + synchronized (terminal) { + return terminal.kittyGraphics(); + } } public boolean floating() { @@ -62,6 +79,14 @@ public final class TerminalPane implements AutoCloseable { this.floating = floating; } + public boolean visible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + public double x() { return x; } @@ -85,8 +110,29 @@ public final class TerminalPane implements AutoCloseable { 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() { - snapshotText.set(String.valueOf(terminal.snapshot())); + renderSnapshot.set(terminal.renderSnapshot()); } @Override diff --git a/src/main/java/com/gregor/jprototerm/TerminalWorkspace.java b/src/main/java/com/gregor/jprototerm/TerminalWorkspace.java index fc174c3..770dd45 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalWorkspace.java +++ b/src/main/java/com/gregor/jprototerm/TerminalWorkspace.java @@ -8,6 +8,7 @@ public final class TerminalWorkspace implements AutoCloseable { private final AppConfig config; private final List panes = new ArrayList<>(); private int activeIndex; + private int hiddenFloatingFocusIndex = -1; public TerminalWorkspace(AppConfig config) { this.config = config; @@ -19,7 +20,18 @@ public final class TerminalWorkspace implements AutoCloseable { } public List panes() { - return List.copyOf(panes); + List visible = panes.stream().filter(TerminalPane::visible).toList(); + TerminalPane active = activePane(); + if (!active.visible() || !active.floating()) { + return visible; + } + + List 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) { @@ -27,20 +39,29 @@ public final class TerminalWorkspace implements AutoCloseable { } public void layout(double width, double height) { - List tiled = panes.stream().filter(pane -> !pane.floating()).toList(); + List tiled = panes.stream() + .filter(TerminalPane::visible) + .filter(pane -> !pane.floating()) + .toList(); int tileCount = Math.max(1, tiled.size()); double tileWidth = width / tileCount; for (int i = 0; i < tiled.size(); i++) { tiled.get(i).bounds(i * tileWidth, 0, tileWidth, height); } - for (TerminalPane pane : panes) { - if (pane.floating()) { + List floating = panes.stream() + .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 floatingHeight = Math.max(260, height * 0.58); + double offset = i * 28.0; pane.bounds( - (width - floatingWidth) / 2.0, - (height - floatingHeight) / 2.0, + Math.min(width - floatingWidth - 12.0, ((width - floatingWidth) / 2.0) + offset), + Math.min(height - floatingHeight - 12.0, ((height - floatingHeight) / 2.0) + offset), floatingWidth, floatingHeight ); @@ -50,7 +71,12 @@ public final class TerminalWorkspace implements AutoCloseable { public void navigate(Direction direction) { TerminalPane current = activePane(); + if (current.floating() && navigateFloatingStack(direction)) { + return; + } + panes.stream() + .filter(TerminalPane::visible) .filter(pane -> pane != current) .filter(pane -> directionFilter(direction, current, pane)) .min(Comparator.comparingDouble(pane -> distance(current, pane))) @@ -58,23 +84,160 @@ public final class TerminalWorkspace implements AutoCloseable { } public void toggleFloating() { - TerminalPane active = activePane(); - if (active.floating()) { - panes.remove(activeIndex); - active.close(); - activeIndex = Math.max(0, activeIndex - 1); + List floating = panes.stream() + .filter(TerminalPane::floating) + .toList(); + if (floating.isEmpty()) { + createFloatingPane(); 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); panes.add(pane); 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 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) { - TerminalPane pane = TerminalPane.create(config.columns(), config.rows(), config.kittyGraphics()); + TerminalPane pane = TerminalPane.create(config.columns(), config.rows()); 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; } diff --git a/src/main/resources/META-INF/native-image/com.gregor/jprototerm/jni-config.json b/src/main/resources/META-INF/native-image/com.gregor/jprototerm/jni-config.json deleted file mode 100644 index 79fe92b..0000000 --- a/src/main/resources/META-INF/native-image/com.gregor/jprototerm/jni-config.json +++ /dev/null @@ -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 - } -] diff --git a/src/main/resources/META-INF/native-image/com.gregor/jprototerm/reflect-config.json b/src/main/resources/META-INF/native-image/com.gregor/jprototerm/reflect-config.json deleted file mode 100644 index 2d05928..0000000 --- a/src/main/resources/META-INF/native-image/com.gregor/jprototerm/reflect-config.json +++ /dev/null @@ -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 - } -] diff --git a/src/main/resources/META-INF/native-image/com.gregor/jprototerm/resource-config.json b/src/main/resources/META-INF/native-image/com.gregor/jprototerm/resource-config.json deleted file mode 100644 index f127be5..0000000 --- a/src/main/resources/META-INF/native-image/com.gregor/jprototerm/resource-config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "resources": [ - { "pattern": ".*\\.css$" }, - { "pattern": ".*\\.toml$" } - ] -}