diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs index 97aed87..ec93094 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ b/.settings/org.eclipse.buildship.core.prefs @@ -1,7 +1,7 @@ -arguments=--init-script /home/anon/.eclipse/1927926929_linux_gtk_x86_64/configuration/org.eclipse.osgi/58/0/.cp/gradle/init/init.gradle +arguments=--init-script /home/anon/Src/eclipse.jdt.ls/org.eclipse.jdt.ls.product/target/repository/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.gradle.distribution=GRADLE_DISTRIBUTION(LOCAL_INSTALLATION(/home/anon/.sdkman/candidates/gradle/current)) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= diff --git a/README.md b/README.md index 4baf7e1..1ed93d3 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,45 @@ # jprototerm -JavaFX canvas terminal prototype using `jlibghostty` for terminal emulation, Nix for the build environment, and GluonFX/GraalVM Native Image for the Linux binary. +JavaFX canvas terminal prototype using `jlibghostty` for terminal emulation and Nix for +the build environment. It builds a plain JavaFX application (JDK 25, JavaFX 25 via Gradle) +packaged as a Nix derivation — no GraalVM/GluonFX native image. ## Build ```sh nix build +./result/bin/jprototerm ``` -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. +Install it into a profile (works on NixOS and on a plain Debian box with Nix installed): + +```sh +nix profile add . +jprototerm +``` + +The flake bundles everything the app needs — the JDK 25 runtime, the Maven JavaFX modules +and their native libraries, and the gtk/glib/freetype/X11 libraries they load — **except** +the system OpenGL/graphics drivers. `libGL` is supplied by the host at runtime through a GL +shim in the launcher wrapper, so the same closure runs against NixOS, Mesa, or vendor +(e.g. NVIDIA) GPU drivers. + +Gradle dependencies are vendored in `deps.json` for the pure Nix sandbox. Regenerate it +after changing dependencies in `build.gradle` (the update script writes `deps.json` in the +current directory): + +```sh +$(nix build .#gradleDepsUpdateScript --no-link --print-out-paths) +``` For development: ```sh nix develop -gradle -PjlibghosttyMavenRepo="$JLIBGHOSTTY_MAVEN_REPO" run -gradle -PjlibghosttyMavenRepo="$JLIBGHOSTTY_MAVEN_REPO" -Pgluonfx.mainClassName=com.gregor.jprototerm.Main nativeExecutable +gradle run ``` -The Gradle project is the source of truth for native JavaFX builds. +The Gradle project is the source of truth for the JavaFX build. ## Config diff --git a/build.gradle b/build.gradle index 9e4581f..f5c0725 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,6 @@ plugins { id 'application' id 'org.openjfx.javafxplugin' version '0.1.0' - // id 'org.graalvm.buildtools.native' version '1.1.1' - id 'com.gluonhq.gluonfx-gradle-plugin' version '1.0.28' } repositories { @@ -19,20 +17,10 @@ dependencies { } javafx { - version = '22' + version = '25' modules = [ 'javafx.controls', 'javafx.fxml' ] } -gluonfx { - compilerArgs += [ - '--features=com.gregor.jprototerm.PtyForeignRegistrationFeature', - '-H:+UnlockExperimentalVMOptions', - '-H:+ForeignAPISupport', - '--enable-native-access=ALL-UNNAMED', - "-H:ConfigurationFileDirectories=${file('src/main/resources/META-INF/native-image/com.gregor/jprototerm').absolutePath}".toString() - ] -} - application { mainClass = 'com.gregor.jprototerm.Main' } diff --git a/deps.json b/deps.json index 8a92c6f..fcc4e4c 100644 --- a/deps.json +++ b/deps.json @@ -2,19 +2,6 @@ "!comment": "This is a nixpkgs Gradle dependency lockfile. For more details, refer to the Gradle section in the nixpkgs manual.", "!version": 1, "https://plugins.gradle.org/m2": { - "com/gluonhq#gluonfx-gradle-plugin/1.0.28": { - "jar": "sha256-uPpVnf9fWsdtMHkJZ1Mq7b/wa7kPhgobruE6k4T5ocE=", - "module": "sha256-si+dMCY5W/aA0W4x0Ef6xQfELR3vp3J53PFhn4EEMzk=", - "pom": "sha256-IAx/ryC66kqOVYmZtpK+jowA2AlfOaQjb5jgezrtDq0=" - }, - "com/gluonhq#substrate/0.0.68": { - "jar": "sha256-ZOchzSN8IPSQVnItGAvo3T6OG93G0lqU+BmemdFa6lE=", - "module": "sha256-Fe+E0f9bIizfOxyAwx85EhS7d+PHTFP3dVxIjjXiqVI=", - "pom": "sha256-pZ/ETzHMpevrtsXKycZDvlUKel5OLYwIlPzx3VhUwPQ=" - }, - "com/gluonhq/gluonfx-gradle-plugin#com.gluonhq.gluonfx-gradle-plugin.gradle.plugin/1.0.28": { - "pom": "sha256-kJ4KJdl1tThityoqOgZgWCk1DwjwOatuwKu+pqM2fVY=" - }, "com/google/code/findbugs#jsr305/3.0.2": { "jar": "sha256-dmrSoHg/JoeWLIrXTO7MOKKLn3Ki0IXuQ4t4E+ko0Mc=", "pom": "sha256-GYidvfGyVLJgGl7mRbgUepdGRIgil2hMeYr+XWPXjf4=" @@ -23,95 +10,10 @@ "jar": "sha256-a0aS+ROiGx+2Axae54uo8+SrKvnXYq+cqIt5EmwcCtE=", "pom": "sha256-hGDJUBJ8o1mHZhYeOLT/jWO01p+4MQoW4As1E1ABDBE=" }, - "com/google/javascript#closure-compiler-main/v20210601": { - "pom": "sha256-5ndSQ8/yIPoBYQRJYMgINYm0RNBT8+UBh5RgadeelPc=" - }, - "com/google/javascript#closure-compiler-parent/v20210601": { - "pom": "sha256-fUctzCy/bEb70rPLjMbXvzoQQSJFcvz5wYM/0FwGMSw=" - }, - "com/google/javascript#closure-compiler/v20210601": { - "jar": "sha256-ZPFhxlo9ukLJpPWnnbM1rA51f8ukhs15KCBaxMJY7wg=", - "pom": "sha256-oG+6EUi6PBunTk69Xm47vQFxnUyJ1x/2spmAc1P5pnE=" - }, - "com/googlecode/plist#dd-plist/1.23": { - "jar": "sha256-PXlPKAjQ6Qx+yIlljoQ/kqJEFL1N8Y7PnxR4cmrxI4w=", - "pom": "sha256-irwhpc7IBIMPGpXg6ePumjP+vuugYUoKC39r1Q2sNoM=" - }, "kr/motd/maven#os-maven-plugin/1.7.1": { "jar": "sha256-9Hru+Ggh5SsrGHWJeL0EXwPXIikuMudHCCEixiKJUuA=", "pom": "sha256-S3WABEIrljPdMY8p54Tx0YC9ilkgzVCvGTCGH21qVHY=" }, - "net/java#jvnet-parent/3": { - "pom": "sha256-MPV4nvo53b+WCVqto/wSYMRWH68vcUaGcXyy3FBJR1o=" - }, - "org/apidesign#bck2brwsr/0.53": { - "pom": "sha256-4gloFSkouKZ2Knb3dv0+hATBY21FyUrQwIEo6Ldf84g=" - }, - "org/apidesign/bck2brwsr#aot/0.53": { - "jar": "sha256-3AYD3veZGDwAba3Km3tDtQBpvKvqVcdOwOyVnSGNrDo=", - "pom": "sha256-y8SBpobq+EucjgQZYORkL1LQldq7Qz7aDzYTKx4WVJs=" - }, - "org/apidesign/bck2brwsr#core/0.53": { - "jar": "sha256-Vx3cekElg279+ZloL2R2hq7o4PXAv1SSPuLq85CKBJA=", - "pom": "sha256-tv4AFJHrOGMb58k2MeoRKS5JJd/TTH1WSa5r31cyYxA=" - }, - "org/apidesign/bck2brwsr#rt/0.53": { - "pom": "sha256-djjJvzf/apO+0fXQd8BF3Gwa/8kNYJnEe0HFkFQA17g=" - }, - "org/apidesign/bck2brwsr#vm4brwsr/0.53": { - "jar": "sha256-40KkVFiKss+K1vy9ZAc0Zu72koER7uks/N9KkgUewYc=", - "pom": "sha256-Pu28aImG2vOir62p5SiF7TCRT0mDTsZcj8Oz0vyIkZc=" - }, - "org/bouncycastle#bcpkix-jdk15on/1.49": { - "jar": "sha256-RbdLj4RoUMIDmezT0GqHGgMApxmvPD1wxiGypu9wGMQ=", - "pom": "sha256-goBjpMwZV6CBydovX/HPKxTL6U8dliP4N+PqC832Nko=" - }, - "org/bouncycastle#bcprov-jdk15on/1.49": { - "jar": "sha256-lrS41G445aCLXubuwjxZUdlb2sNcCPk16Zm7vmsVup0=", - "pom": "sha256-2NH0XGsa+Sh+U7Hk7Z3OgPE/gM9jXKxbFsOnXxErGI4=" - }, - "org/graalvm/compiler#compiler/21.1.0": { - "jar": "sha256-J4rM6driWApOTY4mrucCNAYO5zE+SYssIbBp+VdIgAM=", - "pom": "sha256-29CpNDLziJ22QBzV3kn5+Zo+2N03tjqw+Vt0paolKe8=" - }, - "org/graalvm/nativeimage#objectfile/21.1.0": { - "jar": "sha256-GdR196ACgcJHimtLFSS4IBPtpNzouUZFoV+BKzd51+c=", - "pom": "sha256-k/ScoLxtNJd+KjQO5G5LR9r3mw7jijlgIzGx6U/oSwE=" - }, - "org/graalvm/nativeimage#pointsto/21.1.0": { - "jar": "sha256-wD2RSGqNOxFoEvpuzHXtEolCY8EjpLcM4gm9chQ0Lv8=", - "pom": "sha256-5Pq2drpWcEkosAg4UjNjv8kU1Z0QadOa0/uWkRYZ874=" - }, - "org/graalvm/nativeimage#svm-hosted-native-darwin-amd64/21.1.0": { - "pom": "sha256-wu8bdhdbxPrxshx3NXbYALoepTWdDy7D9Iyry5IKt1M=" - }, - "org/graalvm/nativeimage#svm-hosted-native-linux-amd64/21.1.0": { - "pom": "sha256-n6slhAlQ6gRkkClPvEwLxHkBuIsK+Ygs4ZigNVSs0i4=" - }, - "org/graalvm/nativeimage#svm-hosted-native-windows-amd64/21.1.0": { - "pom": "sha256-QAHwMgf+vnTMPY7b64oGMzJci3Nv9zvzGqaqdn+ih/E=" - }, - "org/graalvm/nativeimage#svm/21.1.0": { - "jar": "sha256-7mVFI+odQ02FQ7aO4yq0WpFYEUlpyMMrRftqaKtepIw=", - "pom": "sha256-Tc1jANLepe57NKSQEJg3oZAvt05vVXsqEbgjonmpA6E=" - }, - "org/graalvm/nativeimage/svm-hosted-native-darwin-amd64/21.1.0/svm-hosted-native-darwin-amd64-21.1.0": { - "tar.gz": "sha256-cRv2xfIXJSO9dk1zm70KdvkqzzyS1PgwuAWMfPwGBw0=" - }, - "org/graalvm/nativeimage/svm-hosted-native-linux-amd64/21.1.0/svm-hosted-native-linux-amd64-21.1.0": { - "tar.gz": "sha256-AmGma5Lf1uxxMYb1LOsvtHXyPEZfxnsOaORGdt0dhMA=" - }, - "org/graalvm/nativeimage/svm-hosted-native-windows-amd64/21.1.0/svm-hosted-native-windows-amd64-21.1.0": { - "tar.gz": "sha256-mk6t0oPU1iowP+pkYoNlXBmoob7DMJP2WHphCDWlS8Y=" - }, - "org/graalvm/sdk#graal-sdk/21.1.0": { - "jar": "sha256-I3XHwVkNToh7um4BwZkRh9E4BM+HTgNeR7ttW55/FBE=", - "pom": "sha256-Bfwh8sGsVPPlpN2kDvK5yMo4RwdIvxcyc75w9sGZudY=" - }, - "org/graalvm/truffle#truffle-api/21.1.0": { - "jar": "sha256-4IrvulJD4FTe4bwfX5EW6ZcZkXp03fxzntDEj3SSLro=", - "pom": "sha256-fC/b6jkSX8c923Iz8j5WRhjDQ+raeKllJNfELM7JhEU=" - }, "org/openjfx#javafx-plugin/0.1.0": { "jar": "sha256-Xq7sB5m0QGRrDKTP2iGaMttr4rpXktAyoNpKOlw4j6s=", "module": "sha256-rf+3RA0kntF8BJOD1nBp+UU7F3gncMAFtoKkNBbYNmE=", @@ -120,13 +22,6 @@ "org/openjfx/javafxplugin#org.openjfx.javafxplugin.gradle.plugin/0.1.0": { "pom": "sha256-1tASf/Q2PQAXPDV6mByec+/wPDCl0Ohq2CtgVPrvqEE=" }, - "org/ow2#ow2/1.5": { - "pom": "sha256-D4obEW52C4/mOJxRuE5LB6cPwRCC1Pk25FO1g91QtDs=" - }, - "org/ow2/asm#asm/9.2": { - "jar": "sha256-udT+TXGTjfOIOfDspCqqpkz4sxPWeNoDbwyzyhmbR/U=", - "pom": "sha256-37EqGyJL8Bvh/WBAIEZviUJBvLZF3M45Xt2M1vilDfQ=" - }, "org/sonatype/oss#oss-parent/7": { "pom": "sha256-tR+IZ8kranIkmVV/w6H96ne9+e9XRyL+kM5DailVlFQ=" }, @@ -150,68 +45,32 @@ "module": "sha256-TRoE8nqf0ULuQ4J1/u2+voUNf421lTOJ1SajE07F8/Y=", "pom": "sha256-sG4IDPD+ItRgyQcfDiLqdd+wCd40JHcSLocA+jWX1p0=" }, - "net/java/dev/jna#jna-platform/5.14.0": { - "jar": "sha256-rkys6zhAcwwlN/m3+1WgG6ulgChrQSKVFIi87lWMJEk=", - "pom": "sha256-bLoOBPnuyxZIYAB5O7J+EDsPTQSF4FVOK0wK7RPS7RY=" + "org/openjfx#javafx-base/25": { + "pom": "sha256-XFYpcqK673qkB7J9Wc4XOl6lCht7dRgEO3/I92/v5Tc=" }, - "net/java/dev/jna#jna/5.14.0": { - "jar": "sha256-NO0eHyf6iWvKUNvE6ZzzcylnzsOHp6DV40hsCWc/6MY=", - "pom": "sha256-4E4llRUB3yWtx7Hc22xTNzyUiXuE0+FJISknY+4Hrj0=" + "org/openjfx#javafx-base/25/linux": { + "jar": "sha256-MkJZRruLjbBxfPovsuAOIc1InzW5ZitvrKGLYVpKlmk=" }, - "org/jetbrains#annotations/13.0": { - "jar": "sha256-rOKhDcji1f00kl7KwD5JiLLA+FFlDJS4zvSbob0RFHg=", - "pom": "sha256-llrrK+3/NpgZvd4b96CzuJuCR91pyIuGN112Fju4w5c=" + "org/openjfx#javafx-controls/25": { + "pom": "sha256-74cad6gX7nuDrKWKKe6yv5h2AvRseKHRXEYAgzpq1uM=" }, - "org/jetbrains#annotations/24.0.1": { - "jar": "sha256-YWZtvOfkLmyFtDwE/PuCk6Idy1WzyA6GknDOQsAaazU=", - "pom": "sha256-mb7eKcAzHBlS7uBL+ZeN5TWpDJfi3v/6XgCTNRcZJbA=" + "org/openjfx#javafx-controls/25/linux": { + "jar": "sha256-NzVeTZHGfoj9mBX2AeasW1Xd3p9em5P8j0qgRXfmkdM=" }, - "org/jetbrains/kotlin#kotlin-stdlib/2.1.21": { - "jar": "sha256-JjvcZ54fYgEtt7CReWJ5ttcc829Hl6mP8azgWDXyAcg=", - "module": "sha256-DTc1BD1ot6WvtE0n3mX4Cp0rIIRicJwu27SP1bOVrYo=", - "pom": "sha256-xVJAhso+SaZ5ht+P3M1mA3uvXzxaNYt2+d1gm+57tjg=" + "org/openjfx#javafx-fxml/25": { + "pom": "sha256-RopsFNQeVHnwNK4v4FPwyJEpfqJoo8dtf/047zyrsio=" }, - "org/jetbrains/pty4j#pty4j/0.13.11": { - "jar": "sha256-AynuVSDzsBd6Cyve+sPExpzJ05FeTyEKi/Ob56s4h8g=", - "module": "sha256-eTWa/9ZV8g2eFDU5aDuke3OhqAOLLtP3uX3p3/RELhI=", - "pom": "sha256-NQ/chKePdmts52rZbY22ZL/3rjreQGLJKTblTCu4+S4=" + "org/openjfx#javafx-fxml/25/linux": { + "jar": "sha256-OUrjL2TBIsFPvRDvSb3efbsFVpt6uOf58XDIGSS5Wis=" }, - "org/openjfx#javafx-base/22": { - "pom": "sha256-XPr4AKyei29O5+mOaRZvN0PAo+xyLhx8idLaEzmgQIE=" + "org/openjfx#javafx-graphics/25": { + "pom": "sha256-zB2jY7Id7uvymRWBk9qmIB+USw+Setv13DhL62qDOfQ=" }, - "org/openjfx#javafx-base/22/linux": { - "jar": "sha256-guS1JYZGAz4MvYzoYKBZWGDIwNeOrfXKWQ9FNps7PQ8=" + "org/openjfx#javafx-graphics/25/linux": { + "jar": "sha256-PlGLwX7lWFWaKsWKX3/UUmFRCNnVCI9lsTvuk5nDcis=" }, - "org/openjfx#javafx-controls/22": { - "pom": "sha256-8AZSRCn/IPdz+tfjwXq9/qkkzMeqGamSoeQZPl/kCA8=" - }, - "org/openjfx#javafx-controls/22/linux": { - "jar": "sha256-4E5POGe18PR56yKJsDgwPOn7TR3vImvJHCkYlpvzW2s=" - }, - "org/openjfx#javafx-fxml/22": { - "pom": "sha256-N2R+Lpsq+3hcJKu2GjjD5p02av/TUyv6J+jIrm92tPI=" - }, - "org/openjfx#javafx-fxml/22/linux": { - "jar": "sha256-K5X/HQ+4GUIrIU2Xs423Bc2alyMK41P0kr3UiJujqdY=" - }, - "org/openjfx#javafx-graphics/22": { - "pom": "sha256-snXDI9Pbr1/anOegMkG/JEvfeJ5GFON+pMPjww4P858=" - }, - "org/openjfx#javafx-graphics/22/linux": { - "jar": "sha256-ACrpnQ8ML2Clh72fkRaP7lvZ2AJ4H0eXV1lWz0icJLs=" - }, - "org/openjfx#javafx/22": { - "pom": "sha256-ZO+kxQXukz+S0/4hqQ7RVKKs1Dhif6hKHYG7jRZWN58=" - }, - "org/slf4j#slf4j-api/2.0.13": { - "jar": "sha256-58KkjoUVuh9J+mN9V7Ti9ZCz9b2XQHrGmcOqXvsSBKk=", - "pom": "sha256-UYBc/agMoqyCBBuQbZhl056YI+NYoO62I3nf7UdcFXE=" - }, - "org/slf4j#slf4j-bom/2.0.13": { - "pom": "sha256-evJy16c44rmHY3kf/diWBA6L6ymKiP1gYhRAeXbNMQo=" - }, - "org/slf4j#slf4j-parent/2.0.13": { - "pom": "sha256-Z/rP1R8Gk1zqhWFaBHddcNgL/QOtDzdnA1H5IO0LtYo=" + "org/openjfx#javafx/25": { + "pom": "sha256-55IzCPyt1/LGiwcgQfR9jnNVIj2EZVnutceA3EuivxM=" } } } diff --git a/flake.lock b/flake.lock index f074b84..3b3ca30 100644 --- a/flake.lock +++ b/flake.lock @@ -70,11 +70,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1780043504, - "narHash": "sha256-AyyfTWcVLfq6e707oi95y/yWp3PnaBkeb6jYE1BspjI=", + "lastModified": 1780050576, + "narHash": "sha256-u06xuO3QnLDpajIOZwDdhwI0HGzMuXG7x1pR+4Zb+RA=", "ref": "refs/heads/main", - "rev": "482305a1af38e84f075e75984923c4c885cabcc5", - "revCount": 16, + "rev": "d558d554b360a76d03c2fc09d327e3ec4aade878", + "revCount": 17, "type": "git", "url": "https://gitea.gregorlohaus.com/gregor/jlibghostty.git" }, diff --git a/flake.nix b/flake.nix index a8de7a7..33e182c 100644 --- a/flake.nix +++ b/flake.nix @@ -11,6 +11,33 @@ let supportedSystems = [ "x86_64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + + # Everything the JavaFX natives (and jlibghostty) dlopen at runtime, EXCEPT the + # system OpenGL/graphics drivers. libGL is intentionally left out: it is supplied + # by the host at runtime via the GL shim in the wrapper below, so the same closure + # works on NixOS and on a plain Debian box with vendor GPU drivers installed. + runtimeLibsFor = pkgs: ghosttyVt: [ + pkgs.glib + pkgs.gtk3 + pkgs.pango + pkgs.cairo + pkgs.gdk-pixbuf + pkgs.harfbuzz + pkgs.freetype + pkgs.fontconfig.lib + pkgs.libx11 + pkgs.libxext + pkgs.libxrender + pkgs.libxtst + pkgs.libxi + pkgs.libxcursor + pkgs.libxrandr + pkgs.libxinerama + pkgs.libxcb + pkgs.libxxf86vm + pkgs.zlib + ghosttyVt + ]; in { packages = forAllSystems (system: let @@ -19,218 +46,73 @@ jlib = jlibghostty.packages.${system}.jlibghostty; ghosttyVt = ghostty.packages.${system}.libghostty-vt; - javafxStaticSdkZip = pkgs.fetchurl { - url = "https://download2.gluonhq.com/substrate/javafxstaticsdk/openjfx-21-ea+11.3-linux-x86_64-static.zip"; - hash = "sha256-ovoByMPwhvU54mtxGYyrgLxDLNn0tA3XUK3rtnGfAAM="; - }; + runtimeLibs = runtimeLibsFor pkgs ghosttyVt; - patchedSubstrateJar = - let - substrateJar = pkgs.fetchurl { - url = "https://plugins.gradle.org/m2/com/gluonhq/substrate/0.0.68/substrate-0.0.68.jar"; - hash = "sha256-ZOchzSN8IPSQVnItGAvo3T6OG93G0lqU+BmemdFa6lE="; - }; - in - pkgs.runCommand "substrate-0.0.68-patched.jar" - { - nativeBuildInputs = [ - pkgs.perl - pkgs.unzip - pkgs.zip - ]; - } - '' - mkdir work - cd work - unzip -q ${substrateJar} - - # Gluon Substrate hardcodes /usr/bin/pkg-config. Keep the - # replacement string the same byte length to avoid rewriting - # the Java class constant pool structure. - perl -0pi -e 's|/usr/bin/pkg-config|/tmp/nix/pkg-config|g' \ - com/gluonhq/substrate/util/linux/LinuxLinkerFlags.class - - if grep -a -q "/usr/bin/pkg-config" com/gluonhq/substrate/util/linux/LinuxLinkerFlags.class; then - echo "Failed to patch hardcoded pkg-config path in Substrate" >&2 - exit 1 - fi - - zip -qr "$out" . - ''; - - gluonGraalvm = pkgs.stdenv.mkDerivation { - pname = "graalvm-java23-gluon"; - version = "23+25.1-dev-2409082136"; - - src = pkgs.fetchurl { - url = "https://github.com/gluonhq/graal/releases/download/gluon-23%2B25.1-dev-2409082136/graalvm-java23-linux-amd64-gluon-23+25.1-dev.tar.gz"; - hash = "sha256-/NyMutn3pT4ZKL2pkzPdBZghxg0ERK5VJ2bFQF0VBfU="; - }; - - nativeBuildInputs = [ - pkgs.autoPatchelfHook - ]; - - buildInputs = [ - pkgs.stdenv.cc.cc.lib - pkgs.zlib - pkgs.freetype - pkgs.fontconfig - pkgs.alsa-lib - pkgs.glib - pkgs.gtk3 - pkgs.pango - pkgs.libx11 - pkgs.libxext - pkgs.libxrender - pkgs.libxtst - pkgs.libxi - pkgs.libxrandr - pkgs.libxinerama - pkgs.libxcb - ]; - - installPhase = '' - runHook preInstall - - mkdir -p "$out" - cp -R . "$out/" - - # The GluonFX Gradle plugin expects these two static libraries - # directly under linux-amd64, but this tarball keeps them one - # level deeper under linux-amd64/glibc. - ln -sfn ./glibc/libjvm.a "$out/lib/svm/clibraries/linux-amd64/libjvm.a" - ln -sfn ./glibc/liblibchelper.a "$out/lib/svm/clibraries/linux-amd64/liblibchelper.a" - - runHook postInstall - ''; - }; - - runtimeLibs = [ - pkgs.glib - pkgs.gtk3 - pkgs.pango - pkgs.alsa-lib - pkgs.ffmpeg.dev - pkgs.ffmpeg.lib - pkgs.freetype - pkgs.fontconfig - pkgs.libx11 - pkgs.libx11.dev - pkgs.libxext - pkgs.libxrender - pkgs.libxtst - pkgs.libxi - pkgs.libxcursor - pkgs.libxrandr - pkgs.libxinerama - pkgs.libxcb - pkgs.libxxf86vm - pkgs.zlib - pkgs.zlib.dev - ghosttyVt - ]; jprototerm = pkgs.stdenv.mkDerivation (finalAttrs: { pname = "jprototerm"; version = "0.1.0"; src = ./.; nativeBuildInputs = [ - gluonGraalvm + pkgs.jdk25 pkgs.gradle_9 pkgs.makeWrapper - pkgs.patchelf - pkgs.pkg-config ]; buildInputs = runtimeLibs; - baseMitmCache = pkgs.gradle_9.fetchDeps { + mitmCache = pkgs.gradle_9.fetchDeps { pkg = finalAttrs.finalPackage; data = ./deps.json; - silent = false; useBwrap = false; }; - mitmCache = pkgs.runCommand "jprototerm-deps-patched" - { - passthru.updateScript = finalAttrs.baseMitmCache.updateScript; - } - '' - mkdir -p "$out" - cp -a ${finalAttrs.baseMitmCache}/. "$out/" - chmod -R u+w "$out" - - substratePath="$out/https/plugins.gradle.org/m2/com/gluonhq/substrate/0.0.68/substrate-0.0.68.jar" - if [ ! -e "$substratePath" ]; then - echo "Could not find substrate jar in Gradle MITM cache: $substratePath" >&2 - find "$out" -path '*substrate*' -print >&2 - exit 1 - fi - - rm "$substratePath" - ln -s ${patchedSubstrateJar} "$substratePath" - ''; - - gradleBuildTask = "nativeBuild"; - gradleUpdateTask = "nixDownloadDeps"; + # Builds build/install/jprototerm/{bin,lib} with every runtime jar, including + # the maven javafx-*-linux jars that carry the platform natives. + gradleBuildTask = "installDist"; gradleFlags = [ "--no-build-cache" "--stacktrace" - "--info" - "-Dorg.gradle.java.home=${gluonGraalvm}" + "-Dorg.gradle.java.home=${pkgs.jdk25}" ]; - GRAALVM_HOME = "${gluonGraalvm}"; - JAVA_HOME = "${gluonGraalvm}"; + JAVA_HOME = "${pkgs.jdk25}"; JLIBGHOSTTY_MAVEN_REPO = "${jlib}/maven"; - preConfigure = '' - export HOME="$TMPDIR/home" - export GRADLE_OPTS="-Duser.home=$HOME ''${GRADLE_OPTS:-}" - mkdir -p /tmp/nix - ln -sfn ${pkgs.pkg-config}/bin/pkg-config /tmp/nix/pkg-config - for gluonHome in "$HOME/.gluon" /build/.gluon; do - if mkdir -p "$gluonHome/substrate" 2>/dev/null; then - cp -f ${javafxStaticSdkZip} "$gluonHome/substrate/openjfx-21-ea+11.3-linux-x86_64-static.zip" - chmod u+w "$gluonHome/substrate/openjfx-21-ea+11.3-linux-x86_64-static.zip" - fi - done - ''; - preBuild = '' export HOME="$TMPDIR/home" export GRADLE_OPTS="-Duser.home=$HOME ''${GRADLE_OPTS:-}" - mkdir -p /tmp/nix - ln -sfn ${pkgs.pkg-config}/bin/pkg-config /tmp/nix/pkg-config - for gluonHome in "$HOME/.gluon" /build/.gluon; do - if mkdir -p "$gluonHome/substrate" 2>/dev/null; then - cp -f ${javafxStaticSdkZip} "$gluonHome/substrate/openjfx-21-ea+11.3-linux-x86_64-static.zip" - chmod u+w "$gluonHome/substrate/openjfx-21-ea+11.3-linux-x86_64-static.zip" - fi - done - export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}:$LD_LIBRARY_PATH" ''; preGradleUpdate = '' export HOME="$TMPDIR/home" - export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}:$LD_LIBRARY_PATH" ''; installPhase = '' runHook preInstall - mkdir -p "$out/bin" - binary="$(find build/gluonfx -path '*/gvm/*' -prune -o -type f -perm -0100 -print | head -n1)" - if [ -z "$binary" ]; then - echo "Could not find native executable under build/gluonfx" >&2 - find build/gluonfx -type f -perm -0100 >&2 || true - exit 1 - fi + mkdir -p "$out/share/jprototerm" + cp -a build/install/jprototerm/lib "$out/share/jprototerm/lib" - cp "$binary" "$out/bin/jprototerm" + # JavaFX is a set of proper modular jars: put them on the module path and + # keep the application + plain dependency jars on the classpath, so the two + # worlds do not collide. + mkdir -p "$out/share/jprototerm/javafx" + mv "$out/share/jprototerm/lib"/javafx-*.jar "$out/share/jprototerm/javafx/" - wrapProgram "$out/bin/jprototerm" \ + # Build an explicit colon-separated classpath. A "lib/*" glob would be + # expanded by the wrapper's shell before java sees it, breaking -cp. + classpath="" + for jar in "$out"/share/jprototerm/lib/*.jar; do + classpath="$classpath''${classpath:+:}$jar" + done + + makeWrapper "${pkgs.jdk25}/bin/java" "$out/bin/jprototerm" \ + --add-flags "--enable-native-access=ALL-UNNAMED,javafx.graphics" \ + --add-flags "--module-path $out/share/jprototerm/javafx" \ + --add-flags "--add-modules javafx.controls,javafx.fxml" \ + --add-flags "-cp $classpath" \ + --add-flags "com.gregor.jprototerm.Main" \ --prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath runtimeLibs}" \ --run 'glShimDir="''${XDG_RUNTIME_DIR:-/tmp}/jprototerm-gl"; mkdir -p "$glShimDir"; for lib in /lib/x86_64-linux-gnu/libGL.so.1 /lib/x86_64-linux-gnu/libGLX.so.0 /lib/x86_64-linux-gnu/libGLdispatch.so.0 /usr/lib/x86_64-linux-gnu/libGLX_nvidia.so* /usr/lib/x86_64-linux-gnu/libEGL_nvidia.so* /usr/lib/x86_64-linux-gnu/libnvidia*.so* /usr/lib/x86_64-linux-gnu/nvidia/current/lib*.so*; do [ -e "$lib" ] && ln -sfn "$lib" "$glShimDir/$(basename "$lib")"; done; export LD_LIBRARY_PATH="$glShimDir''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"; export __GLX_VENDOR_LIBRARY_NAME="''${__GLX_VENDOR_LIBRARY_NAME:-nvidia}"; if [ -e /usr/share/glvnd/egl_vendor.d/10_nvidia.json ]; then export __EGL_VENDOR_LIBRARY_FILENAMES="''${__EGL_VENDOR_LIBRARY_FILENAMES:-/usr/share/glvnd/egl_vendor.d/10_nvidia.json}"; fi' \ --set JLIBGHOSTTY_LIBRARY "${ghosttyVt}/lib/libghostty-vt.so" \ @@ -238,13 +120,6 @@ runHook postInstall ''; - - postFixup = '' - binary="$out/bin/.jprototerm-wrapped" - currentRpath="$(patchelf --print-rpath "$binary" || true)" - filteredRpath="$(printf '%s' "$currentRpath" | tr ':' '\n' | grep -v 'libglvnd' | paste -sd: -)" - patchelf --set-rpath "$filteredRpath" "$binary" - ''; }); in { default = jprototerm; @@ -256,35 +131,12 @@ pkgs = import nixpkgs { inherit system; }; jlib = jlibghostty.packages.${system}.jlibghostty; ghosttyVt = ghostty.packages.${system}.libghostty-vt; - runtimeLibs = [ - pkgs.glib - pkgs.gtk3 - pkgs.pango - pkgs.alsa-lib - pkgs.ffmpeg.dev - pkgs.ffmpeg.lib - pkgs.freetype - pkgs.fontconfig - pkgs.libx11 - pkgs.libx11.dev - pkgs.libxext - pkgs.libxrender - pkgs.libxtst - pkgs.libxi - pkgs.libxcursor - pkgs.libxrandr - pkgs.libxinerama - pkgs.libxcb - pkgs.libxxf86vm - pkgs.zlib - pkgs.zlib.dev - ghosttyVt - ]; + runtimeLibs = runtimeLibsFor pkgs ghosttyVt; in { default = pkgs.mkShell { packages = [ pkgs.gradle_9 - pkgs.jdk23 + pkgs.jdk25 pkgs.jdt-language-server ] ++ runtimeLibs; diff --git a/src/main/java/com/gregor/jprototerm/LinuxPty.java b/src/main/java/com/gregor/jprototerm/LinuxPty.java index a871af0..28e9d42 100644 --- a/src/main/java/com/gregor/jprototerm/LinuxPty.java +++ b/src/main/java/com/gregor/jprototerm/LinuxPty.java @@ -16,19 +16,16 @@ import java.util.Map; /** * A Linux PTY backed by libc via the Foreign Function & Memory API. * - *
This replaces pty4j (which loads a JNA JNI shim that does not initialise under a - * GraalVM native image). It uses {@code posix_openpt}/{@code posix_spawnp} rather than - * {@code fork}/{@code forkpty}: doing work between {@code fork} and {@code exec} inside a - * multithreaded JVM is unsafe (only async-signal-safe calls are permitted), whereas - * {@code posix_spawn} performs the dangerous part in libc with no Java on the stack. + *
This replaces pty4j (which loads a JNA JNI shim). It uses + * {@code posix_openpt}/{@code posix_spawnp} rather than {@code fork}/{@code forkpty}: + * doing work between {@code fork} and {@code exec} inside a multithreaded JVM is unsafe + * (only async-signal-safe calls are permitted), whereas {@code posix_spawn} performs the + * dangerous part in libc with no Java on the stack. * *
The child gets a fresh session via {@code POSIX_SPAWN_SETSID}; it then opens the slave * PTY itself (as fd 0, without {@code O_NOCTTY}) so the slave becomes its controlling * terminal. glibc applies attribute flags (the setsid) before file actions, so the open * happens in the new session. - * - *
FFM downcall descriptors are registered for native image by - * {@link PtyForeignRegistrationFeature}; keep the two in sync. */ public final class LinuxPty implements AutoCloseable { static final Linker LINKER = Linker.nativeLinker(); @@ -40,7 +37,7 @@ public final class LinuxPty implements AutoCloseable { static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long"); static final ValueLayout.OfLong C_SIZE_T = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("size_t"); - // Function descriptors. Mirrored in PtyForeignRegistrationFeature. + // Function descriptors. static final FunctionDescriptor FD_INT_INT = FunctionDescriptor.of(C_INT, C_INT); static final FunctionDescriptor FD_PTSNAME_R = FunctionDescriptor.of(C_INT, C_INT, C_POINTER, C_SIZE_T); static final FunctionDescriptor FD_RW = FunctionDescriptor.of(C_LONG, C_INT, C_POINTER, C_SIZE_T); diff --git a/src/main/java/com/gregor/jprototerm/PtyForeignRegistrationFeature.java b/src/main/java/com/gregor/jprototerm/PtyForeignRegistrationFeature.java deleted file mode 100644 index 44ff49a..0000000 --- a/src/main/java/com/gregor/jprototerm/PtyForeignRegistrationFeature.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.gregor.jprototerm; - -import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.RuntimeForeignAccess; - -import java.lang.foreign.Linker; - -/** - * Registers the FFM downcall descriptors used by {@link LinuxPty} for GraalVM releases that - * do not consume {@code foreign.downcalls} from reachability-metadata.json. Mirrors - * jlibghostty's {@code GhosttyForeignRegistrationFeature}. - * - *
Wired in via {@code --features=com.gregor.jprototerm.PtyForeignRegistrationFeature} - * in the gluonfx compiler args. - */ -public final class PtyForeignRegistrationFeature implements Feature { - @Override - public void duringSetup(DuringSetupAccess access) { - downcall(LinuxPty.FD_INT_INT); // posix_openpt / grantpt / unlockpt / close - downcall(LinuxPty.FD_PTSNAME_R); // ptsname_r - downcall(LinuxPty.FD_RW); // read / write - downcall(LinuxPty.FD_KILL); // kill - downcall(LinuxPty.FD_WAITPID); // waitpid - downcall(LinuxPty.FD_SPAWN); // posix_spawnp - downcall(LinuxPty.FD_FA_INIT); // *_init / *_destroy - downcall(LinuxPty.FD_FA_ADDCLOSE); // posix_spawn_file_actions_addclose - downcall(LinuxPty.FD_FA_ADDDUP2); // posix_spawn_file_actions_adddup2 - downcall(LinuxPty.FD_FA_ADDOPEN); // posix_spawn_file_actions_addopen - downcall(LinuxPty.FD_FA_ADDCHDIR); // posix_spawn_file_actions_addchdir_np - downcall(LinuxPty.FD_ATTR_SETFLAGS); // posix_spawnattr_setflags - - // ioctl(int, unsigned long, ...) is variadic; register with the same linker option. - RuntimeForeignAccess.registerForDowncall(LinuxPty.FD_IOCTL, Linker.Option.firstVariadicArg(2)); - } - - private static void downcall(java.lang.foreign.FunctionDescriptor descriptor) { - RuntimeForeignAccess.registerForDowncall(descriptor); - } -} diff --git a/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java b/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java index e76a959..60d29be 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java +++ b/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java @@ -121,13 +121,24 @@ public final class TerminalCanvasView { String cacheKey = paneCacheKey(pane, metrics); int imageWidth = Math.max(1, (int) Math.ceil(pane.width())); int imageHeight = Math.max(1, (int) Math.ceil(pane.height())); - if (cache.image == null || cache.canvas == null || cache.imageWidth != imageWidth || cache.imageHeight != imageHeight || !cacheKey.equals(cache.key)) { + + // Allocate the offscreen buffers only when the pane size changes. Reallocating a + // full-pane Canvas + WritableImage on every content change churns ~20 MB per frame, + // which the native image's serial GC turns into Full-GC frame drops. + if (cache.canvas == null || cache.image == null || cache.imageWidth != imageWidth || cache.imageHeight != imageHeight) { cache.canvas = new Canvas(imageWidth, imageHeight); - drawPaneContent(cache.canvas.getGraphicsContext2D(), pane, font, metrics, 0.0, 0.0, imageWidth, imageHeight, true); cache.image = new WritableImage(imageWidth, imageHeight); - cache.canvas.snapshot(null, cache.image); cache.imageWidth = imageWidth; cache.imageHeight = imageHeight; + cache.key = null; + } + + // Redraw and re-snapshot into the existing buffers only when content changed. + if (!cacheKey.equals(cache.key)) { + GraphicsContext cacheGc = cache.canvas.getGraphicsContext2D(); + cacheGc.clearRect(0, 0, imageWidth, imageHeight); + drawPaneContent(cacheGc, pane, font, metrics, 0.0, 0.0, imageWidth, imageHeight, true); + cache.canvas.snapshot(null, cache.image); cache.key = cacheKey; } diff --git a/src/main/resources/.gitkeep b/src/main/resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/META-INF/native-image/com.gregor/jprototerm/reachability-metadata.json b/src/main/resources/META-INF/native-image/com.gregor/jprototerm/reachability-metadata.json deleted file mode 100644 index d02ef58..0000000 --- a/src/main/resources/META-INF/native-image/com.gregor/jprototerm/reachability-metadata.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "resources": [] -} 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 bf2f940..0000000 --- a/src/main/resources/META-INF/native-image/com.gregor/jprototerm/resource-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "resources": { - "includes": [] - } -}