├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── maven-build-all-installer.yml │ └── maven-package.yml ├── .gitignore ├── LICENSE ├── README.md ├── app-icon.icns ├── app-icon.ico ├── app-icon.png ├── docs ├── apple-sign-notarize.md ├── qna.md └── sample-run.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── changenode │ │ ├── BaseApplication.java │ │ ├── Log.java │ │ ├── Plugin.java │ │ └── plugin │ │ ├── DarkMode.java │ │ ├── DesktopIntegration.java │ │ ├── FileDrop.java │ │ ├── HelloWorld.java │ │ ├── LogFile.java │ │ └── StandardMenus.java └── resources │ ├── build-info │ └── build-info.properties │ ├── hello.txt │ └── version.txt └── packaging ├── add-launch-to-msi.js ├── linux-jpackage.txt ├── osx-jpackage.txt └── windows-jpackage.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: wiverson 4 | -------------------------------------------------------------------------------- /.github/workflows/maven-build-all-installer.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Build All Installers 5 | 6 | on: 7 | # push: 8 | # branches: [ main ] 9 | # pull_request: 10 | # branches: [ main ] 11 | workflow_dispatch: 12 | branches: [ main ] 13 | 14 | env: 15 | DEV_IDENTITY: BXPZTQZ35S # Your Apple Dev identity, something like BXPZTQZ35S 16 | PRIMARY_BUNDLE_ID: com.example.App # Unique to your app, often the launcher class 17 | 18 | jobs: 19 | build: 20 | strategy: 21 | matrix: 22 | os: [ubuntu-latest, windows-latest, macos-latest] 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - name: Download Wix 26 | uses: i3h/download-release-asset@v1 27 | if: matrix.os == 'windows-latest' 28 | with: 29 | owner: wixtoolset 30 | repo: wix3 31 | tag: wix3112rtm 32 | file: wix311-binaries.zip 33 | - name: Decompress Wix 34 | uses: DuckSoft/extract-7z-action@v1.0 35 | if: matrix.os == 'windows-latest' 36 | with: 37 | pathSource: wix311-binaries.zip 38 | pathTarget: ./target/wix 39 | - name: Add Wix to Path 40 | run: echo "$HOME/target/wix" >> $GITHUB_PATH 41 | if: matrix.os == 'windows-latest' 42 | - uses: actions/checkout@v2 43 | - name: Set up JDK 17 44 | uses: actions/setup-java@v2 45 | with: 46 | java-version: 18.0.2 47 | distribution: 'liberica' 48 | java-package: jdk+fx 49 | cache: 'maven' 50 | - name: "Build with Maven" 51 | if: matrix.os != 'macos-latest' 52 | run: mvn -B clean install --file pom.xml 53 | - name: "Build with Maven (macOS No Signing)" 54 | env: 55 | MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} 56 | MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} 57 | if: ${{ env.MACOS_CERTIFICATE == null && matrix.os == 'macos-latest' }} 58 | run: mvn -B clean install --file pom.xml 59 | - name: "Build with Maven (macOS Signed)" 60 | env: 61 | MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} 62 | MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} 63 | if: ${{ env.MACOS_CERTIFICATE != null && matrix.os == 'macos-latest' }} 64 | run: | 65 | echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12 66 | security create-keychain -p temppass build.keychain 67 | security default-keychain -s build.keychain 68 | security unlock-keychain -p temppass build.keychain 69 | security import certificate.p12 -k build.keychain -P $MACOS_CERTIFICATE_PWD -T /usr/bin/codesign 70 | security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k temppass build.keychain 71 | export IDENTITY=$(security find-identity -v) 72 | echo $(security find-identity -v) 73 | mvn -B clean install -Pmacos-sign --file pom.xml 74 | - name: "Codesign DMG" 75 | env: 76 | MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} 77 | MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} 78 | if: ${{ env.MACOS_CERTIFICATE != null && matrix.os == 'macos-latest' }} 79 | run: | 80 | export DMG_PATH=$(ls ./target/*.dmg) 81 | /usr/bin/codesign --deep --force -s ${{ env.DEV_IDENTITY}} $DMG_PATH -v 82 | echo DMG_PATH=$DMG_PATH >> $GITHUB_ENV 83 | echo ${{ env.DMG_PATH }} 84 | - name: "Notarize DMG" 85 | env: 86 | APP_EMAIL: ${{ secrets.APP_EMAIL }} 87 | APP_PASS: ${{ secrets.APP_PASS }} 88 | if: ${{ env.APP_EMAIL != null && matrix.os == 'macos-latest' }} 89 | uses: devbotsxyz/xcode-notarize@v1 90 | with: 91 | product-path: ${{ env.DMG_PATH }} 92 | primary-bundle-id: ${{ env.PRIMARY_BUNDLE_ID }} 93 | appstore-connect-username: ${{ secrets.APP_EMAIL }} 94 | appstore-connect-password: ${{ secrets.APP_PASS }} 95 | - name: "Staple DMG" 96 | env: 97 | APP_EMAIL: ${{ secrets.APP_EMAIL }} 98 | APP_PASS: ${{ secrets.APP_PASS }} 99 | if: ${{ env.APP_EMAIL != null && matrix.os == 'macos-latest' }} 100 | uses: devbotsxyz/xcode-staple@v1 101 | with: 102 | product-path: ${{ env.DMG_PATH }} 103 | - name: Update Automatic Release 104 | uses: marvinpinto/action-automatic-releases@latest 105 | with: 106 | repo_token: "${{ secrets.GITHUB_TOKEN}}" 107 | automatic_release_tag: ${{ matrix.os }} 108 | prerelease: true 109 | title: ${{ matrix.os }} Development Build 110 | files: | 111 | ${{ env.DMG_PATH }} 112 | ./target/*.msi 113 | ./target/*.dmg 114 | ./target/*.deb 115 | -------------------------------------------------------------------------------- /.github/workflows/maven-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Maven Basic Package 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: macos-latest 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | - name: Set up JDK 21 | uses: actions/setup-java@v3 22 | with: 23 | java-version: 18.0.2 24 | distribution: 'liberica' 25 | java-package: jdk+fx 26 | cache: 'maven' 27 | - name: Build with Maven 28 | run: mvn -B clean package --file pom.xml 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | # Maven 25 | target/ 26 | 27 | # IntelliJ project files 28 | *.iml 29 | *.iws 30 | *.ipr 31 | .idea/ 32 | 33 | # OS 34 | .DS_Store 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java + Maven + GitHub Actions = Native Desktop Apps 2 | 3 | [JavaFX](https://openjfx.io) or Swing + [jpackage](https://docs.oracle.com/en/java/javase/18/docs/specs/man/jpackage.html) + 4 | [Maven](http://maven.apache.org) template project for generating native desktop applications. 5 | 6 | # Goals 7 | 8 | 1. Build nice, small cross-platform JavaFX or Swing desktop apps with native installers! 9 | 2. Just use Maven - no shell scripts required! 10 | 3. Use standard Maven dependency system to manage dependencies. 11 | 4. Generate [MacOS (.dmg), Windows (.msi) and Unix (e.g. deb/rpm)](https://github.com/wiverson/maven-jpackage-template/releases) 12 | installers/packages in the cloud 13 | with [GitHub Actions](https://github.com/wiverson/maven-jpackage-template/tree/main/.github/workflows). 14 | 15 | Out of the box, this template generates "Hello World" installers - 30-40mb .dmg, .msi and .deb files. Check out the 16 | example builds in 17 | [releases](https://github.com/wiverson/maven-jpackage-template/releases). 18 | 19 | If you are on MacOS, you notice the MacOS builds are not signed. Current versions of MacOS will report 20 | installers downloaded via browsers as damaged/unopenable. You can [clear this flag via the command-line](docs/apple-sign-notarize.md). As this is not a reasonable solution for end users, a GitHub Action is included to notarize, sign, and staple 21 | MacOS installers, but the secrets aren't set up for this repository by default. You will need an Apple Developer account 22 | to get this working. [More information on MacOS signing](docs/apple-sign-notarize.md). 23 | 24 | # Overview 25 | 26 | This template uses a [Maven plugin](https://github.com/wiverson/jtoolprovider-plugin) to generate a custom JVM and 27 | installer package for a JavaFX application. It can easily be adapted to work with Swing instead. 28 | 29 | Check out the [announcements and recent updates](https://github.com/wiverson/maven-jpackage-template/discussions/categories/announcements). 30 | 31 | ## Requirements 32 | 33 | - [Java 18](https://bell-sw.com/pages/downloads/#/java-18-current). 34 | - If you are using JavaFX, use an SDK that has JavaFX bundled: 35 | - [Liberica with JavaFX](https://bell-sw.com/pages/downloads/#/java-18-current) 36 | - [Azul Zulu with JavaFX](https://www.azul.com/downloads/?version=java-18-sts&package=jdk-fx) 37 | - If you are using Swing, pretty much any Java 17 or 18 JDK will work. 38 | - [Maven](https://maven.apache.org/). 39 | - On MacOS XCode is required. 40 | - On Windows the free [WiX Toolset](https://wixtoolset.org/) is required. 41 | 42 | ## Installation 43 | 44 | If you are on Windows, you will need to install Java, Maven, and Wix manually. 45 | 46 | If you are on MacOS or Linux, you can use [SDKMAN!](https://sdkman.io/) to simplify installing Java and Maven. Once 47 | SDKMAN! is installed, you can run the following to install Liberica or Azul Zulu and Maven. 48 | 49 | ```bash 50 | sdk install java 18.0.2.fx-librca 51 | # or 52 | sdk install java 18.0.2.fx-zulu 53 | sdk current java 54 | sdk install maven 55 | ``` 56 | 57 | ## Installation Verification 58 | 59 | 1. Verify that Java is installed by opening a fresh Terminal/Command Prompt and enter `java --version`. As of this 60 | writing, the Java version should be 18.0.2 or later. 61 | 2. Verify that Maven is installed with `mvn --version`. Maven should be version 3.8.6 or later. 62 | 3. Install platform-specific tools. 63 | 1. **MacOS only:** Verify that XCode is installed & license accepted by a) launching it and b) 64 | running `sudo xcodebuild -license`. 65 | 2. **Windows only:** Install [Wix 3 binaries](https://github.com/wixtoolset/wix3/releases/). 66 | 4. Clone/download this project. 67 | 5. Run `mvn clean install` from the root of the project to generate the `target\TestApp.dmg` or `target\TestApp.msi` 68 | installers. 69 | - The generated installer will include a version number in the file name. 70 | - For reference, here is a complete run log for [a successful run](docs/sample-run.md). 71 | 72 | Because these builds use stripped down JVM images, the 73 | [generated installers are in the 30-40mb range](https://github.com/wiverson/maven-jpackage-template/releases). 74 | 75 | On MacOS you should [add signing to avoid error messages](https://github.com/wiverson/maven-jpackage-template/issues/49) 76 | related to the security system(s). 77 | 78 | To [re]generate an installer, run... 79 | 80 | `mvn clean install` 81 | 82 | To do everything up until the actual installer generation (including generating the custom JVM)... 83 | 84 | `mvn clean package` 85 | 86 | To generate reports, include to check if you are using the current version[s] of your dependencies, run... 87 | 88 | `mvn site` 89 | 90 | ...and open target/site/index.html to see the generated reports. 91 | 92 | ## Key Features 93 | 94 | Here are few cool things in this template: 95 | 96 | - Only uses Java and Maven. No shell scripts required. 97 | - Includes sample [GitHub Actions](https://github.com/wiverson/maven-jpackage-template/tree/main/.github/workflows) to 98 | build MacOS, Windows and Linux installers. These GitHub Actions are configured to use the Liberica JDK 18 with 99 | JavaFX to simplify the build process. If you prefer to use Azul Zulu, modify the distribution name to `distribution: 'zulu'` as described on the [Usage description of setup-java](https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Zulu) 100 | - Demonstrates setting the application icon 101 | - Builds a .dmg on MacOS, .msi on Windows, and .deb on Linux, but can be easily tweaked to generate other jpackage 102 | supported installers (e.g. .pkg) 103 | - Includes a JavaFX demo to simplify getting started. 104 | - Just delete the JavaFX stuff if you are using Swing 105 | - Template includes several examples of JavaFX / native desktop integration 106 | - Drag & drop with Finder / Explorer 107 | - Change the Dock icon dynamically on MacOS 108 | - Menu on the top for MacOS, in the window itself on Windows 109 | - Request user attention (bouncing dock icon) on MacOS 110 | - Removing the code and the demonstration dependencies gets a "Hello World" build size closer to 30mb than 111 | 40mb. 112 | - Java + Java modules are used to build a trimmed 113 | JVM ([a few thoughts on Java modules](https://changenode.com/articles/fomo-java-modules)) 114 | - The user application uses ordinary Maven dependencies and classpath to run the application 115 | - Nice illustration of how to use jlink to build a slim JVM, point jpackage at that JVM and still use the ordinary 116 | Maven managed classpath for the application 117 | 118 | Once you get started, you might find these lists of tutorials, tools, libraries for 119 | [JavaFX](https://gist.github.com/wiverson/6c7f49819016cece906f0e8cea195ea2) 120 | and general [Java desktop integration](https://gist.github.com/wiverson/e9dfd73ca9a9a222b2d0a3d68ae3f129) helpful. 121 | 122 | ### Version Numbering 123 | 124 | Usually you want a "marketing version" of an app as released to customers, and a "developer version" for use in internal 125 | testing. For example, to the end user it's just "Windows 11" but there are countless build numbers for all the 126 | different versions of Windows 11. 127 | 128 | The end-user value is set in the pom.xml as `app.version`. This value is updated to use a GitHub environment variable 129 | when the installers are run on GitHub. 130 | 131 | If you look in the `src/main/resources` you will see a version.txt file. This file has information in it that will 132 | be useful for creating a developer build UI. You might want to convert this to a properties file or a JSON file and 133 | display the information in your about UI. 134 | 135 | Most projects will want to set up a coherent versioning strategy to manage both the user visible and development 136 | build version numbers. This is usually project specific. 137 | 138 | ### Does this work with Apple Silicon aka M1/M2? 139 | 140 | Yes, although as of this writing I don't believe there are GitHub Action runners that support M1. But building locally 141 | on my M1/M2 systems works great and generates native Apple Silicon builds. 142 | 143 | ### Does this support macOS signing, notarization, and stapling? 144 | 145 | Yes, there is a GitHub Action and a Maven profile to assist with setting all of this up 146 | for macOS applications. 147 | 148 | For more information, see 149 | the [documentation on getting MacOS signing/notarization/stapling](/docs/apple-sign-notarize.md) set 150 | up. 151 | 152 | To get this working, you will need to: 153 | 154 | 1. You need to sign up for an Apple Developer account. 155 | 2. Add [four GitHub Secrets based on information from Apple]((/docs/apple-sign-notarize.md)). 156 | 3. Update the [build all installer GitHub Action yaml](https://github.com/wiverson/maven-jpackage-template/blob/6d4ef8a80a562f2d49ec41204927d07aa8990d25/.github/workflows/maven-build-all-installer.yml#L14) 157 | 4. Update the [pom.xml](https://github.com/wiverson/maven-jpackage-template/blob/6d4ef8a80a562f2d49ec41204927d07aa8990d25/pom.xml#L331). 158 | 159 | ### What about Linux? 160 | 161 | The JavaFX builds include several other architectures, including aarch64 and arm32. In theory, 162 | you should be able to add those just like the other builds. Haven't tested it though, as I only use Linux for 163 | server-side stuff. Feel free to post in 164 | the [discussion](https://github.com/wiverson/maven-jpackage-template/discussions) section and 165 | also check the [Q&A](docs/qna.md) if you are using Linux. 166 | 167 | ### Can I Use this with Swing instead of JavaFX? 168 | 169 | tl;dr absolutely. 170 | 171 | Just delete the JavaFX stuff, including the JavaFX modules declarations in `pom.xml` and add a Swing main class instead. 172 | If you are reasonably familiar with Maven this shouldn't be very hard to do. 173 | 174 | I *highly* recommend the [FlatLaf](https://www.formdev.com/flatlaf/) as a must for working with Swing in 2022. That 175 | look-and-feel plus designers such as 176 | the [IntelliJ GUI Designer](https://www.jetbrains.com/help/idea/gui-designer-basics.html) 177 | or [JFormDesigner](https://www.formdev.com/jformdesigner/) can work very well, arguably with an easier learning curve 178 | than JavaFX. 179 | 180 | Suggested changes to the pom.xml for Swing: 181 | 182 | 1. Remove the javafx modules from the jvm.modules property 183 | 2. Remove the javafx.version property. 184 | 3. Remove the three org.openjfx dependencies 185 | 4. Remove the configuration/excludeGroupIds section from the maven-dependency-plugin 186 | 5. Remove javafx-maven-plugin from the plugins list 187 | 6. Remove the modulePath delcaration from the jtoolprovider-plugin execution/configuration 188 | 189 | # Debugging 190 | 191 | 1. If the built app fails to run, make sure the JavaFX app runs as expected first by using the `mvn javafx:run` command. 192 | This will run the app in development mode locally, and you should see standard System.out debug lines appear in your 193 | console. 194 | - Many flavors of Linux fail to run here for a variety of reasons. Head over to 195 | the [discussions](https://github.com/wiverson/maven-jpackage-template/discussions) or perhaps consider your 196 | [consulting budget](https://changenode.com) or 197 | a [JavaFX support contract from Gluon](https://gluonhq.com/services/javafx-support/). 198 | 2. Check the Maven build logs (of course). 199 | 3. By default, the app will generate debug*****.log files containing the output from System.out. You can look at the 200 | main method of `BaseApplication.java` to see how this is done. For a production app, you would want to place these 201 | logs in the correct OS specific location. On a Unix machine you can `tail -f` the log normally. 202 | 203 | # Help 204 | 205 | Problems? Make sure everything is installed and working right! 206 | 207 | - Compiler not recognizing the --release option? Probably on an old JDK. 208 | - Can't find jdeps or jpackage? Probably on an old JDK. 209 | - Unrecognized option: --add-modules jdk.incubator.jpackage 210 | - Could be a left-over MAVEN_OPTS setting when you switched from Java 15 to Java 16/17 211 | - If you are still on Java 15, you may not have 212 | [MAVEN_OPTS set correctly](https://github.com/wiverson/maven-jpackage-template/issues/2). 213 | - No certificate found matching [Developer ID Application: Company Name, Inc. (BXPXTXC35S)] using keychain [] -> Update the Developer ID info at the top of your build all installers and also in the macOS signing profile in the pom.xml. 214 | 215 | - Getting errors about not being able to find JavaFX classes in your IDE? Make 216 | sure your IDE is pointing to the right JDK. For example, MacOS IntelliJ -> select 217 | File, Project Structure and make sure you have Liberica with JavaFX selected. 218 | 219 | If you need consulting support, feel free to reach out at [ChangeNode.com](https://changenode.com/). I've helped several 220 | companies with Swing and JavaFX clean up/modernize their old apps to include updated look & feels, add MacOS 221 | sign/staple/notarization, or even in a few cases helped port the app to Spring Boot. 222 | 223 | # Q&A 224 | 225 | If you are using the template, browsing the [Q&A](docs/qna.md) is highly recommended. 226 | -------------------------------------------------------------------------------- /app-icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiverson/maven-jpackage-template/37139516ddcf3e4d5919f59711a4eb0478a099bd/app-icon.icns -------------------------------------------------------------------------------- /app-icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiverson/maven-jpackage-template/37139516ddcf3e4d5919f59711a4eb0478a099bd/app-icon.ico -------------------------------------------------------------------------------- /app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiverson/maven-jpackage-template/37139516ddcf3e4d5919f59711a4eb0478a099bd/app-icon.png -------------------------------------------------------------------------------- /docs/apple-sign-notarize.md: -------------------------------------------------------------------------------- 1 | # macOS Application Signing, Notarization, and Stapling 2 | 3 | So, you just want to build and release a macOS application. You have managed 4 | to use this template with JavaFX or Swing to produce a DMG with a nice application, 5 | but when you download the application and try to run it you get an error 6 | from macOS saying that the application is damaged and unrunnable. What is going on? 7 | 8 | ## Signing 9 | 10 | When you download a disk image or app on macOS, it checks to see if the app 11 | has been digitally signed. If not, macOS adds a quarantine flag to the dmg (and the 12 | bundled app). Then, when you go to run the application macOS notices the quarantine 13 | flag and then refuses to run. You can't work around this quarantine flag with a 14 | right-click and open. As far as I know there is no GUI for working around a quarantine 15 | flag - you have to use the Terminal. 16 | 17 | You can verify if the quarantine flag is set with this command: 18 | 19 | xattr MyFancyApp.app 20 | 21 | You can remove the quarantine flag with this command: 22 | 23 | sudo xattr -r -d com.apple.quarantine MyFancyApp.app 24 | 25 | Needless to say, that's not acceptable for end users. 26 | 27 | To sign the application, you need to run a suite of command-line tools that does 28 | the Right Thing(tm) for signing all of the application assets, including the various 29 | bundled libraries. Fortunately, jpackage will do this for us if we pass along the 30 | right arguments and have our keychain set up properly for command-line use. 31 | 32 | With a signed application, you can download the file, but macOS will initially 33 | still flag the application as sketchy and will initially tell users that the 34 | application can't be opened. You can work around this by right-clicking on the 35 | MyFancyApp.app and selecting open - this will bring up the dialog with the option 36 | to Open the file. 37 | 38 | ## Notarization and Stapling 39 | 40 | In order to allow the application to actually run nicely for an end user - as in, 41 | the first time the user runs the app macOS will still point out that it was downloaded 42 | from the Internet, but offer an Open button without having to right-click on the app, 43 | you have to notarize the app. 44 | 45 | Notarization is simply sending the entire dmg to Apple to analyze (run a virus checker?) 46 | and then getting back a digital certificate. 47 | 48 | It appears that if you want you can notarize the dmg, and then if the user goes to 49 | run the app macOS will then go online to verify the signature of the dmg against 50 | a database of notarized apps. The only problem with is that it requires macOS to hit 51 | the online database, which is a bummer for a variety of reasons. 52 | 53 | Instead, if we have gone so far as to notarize the app, we should go ahead and staple 54 | (just another word for attach) the notarization certificate directly to the dmg. 55 | As long as the local copy of macOS doesn't have the notarization on a block-list, 56 | now even an offline user can go ahead and install the application. 57 | 58 | ## How Does This Template Deal With This? 59 | 60 | This template includes a complete working example of how to get all of this 61 | stuff working via a GitHub Action. 62 | 63 | You will need to set several GitHub Secrets for the project, based on the 64 | Apple ID and Developer information you have set up with Apple. 65 | 66 | You can find the GitHub Action for building a [signed, notarized macOS application](https://github.com/wiverson/maven-jpackage-template/blob/main/.github/workflows/maven-build-installer-macos.yml). 67 | That action includes documentation on several secrets you will need to set, based 68 | on values from Apple. 69 | 70 | You can find more information on how to how to find and set these variables 71 | [in this article](https://localazy.com/blog/how-to-automatically-sign-macos-apps-using-github-actions) 72 | summarizing the steps. 73 | 74 | In addition to setting the values in the GitHub Action, you will also need 75 | to set a `` value in your pom.xml file. 76 | 77 | ## A Brief Note on GitHub Actions 78 | 79 | If you are using GitHub Actions on a private repository you have to (at least eventually) 80 | pay for minutes. As of this writing the minutes are x1 for Linux builds, x2 for Windows, 81 | and x10 for macOS. Apple can easily take 5-10 minutes to notarize a build, which means 82 | that if it uses 10 minutes that's x10 = 100 minutes of GitHub Action build time for purposes 83 | of billing. 84 | 85 | ## Ugh... Help Please 86 | 87 | If you need help setting this up, [feel free to reach out](https://changenode.com/contact) 88 | for consulting support, or you can post in the [discussion forum](https://github.com/wiverson/maven-jpackage-template/discussions) for this template. 89 | -------------------------------------------------------------------------------- /docs/qna.md: -------------------------------------------------------------------------------- 1 | # Q&A 2 | 3 | ## Can you give me a few more details about how this works? 4 | 5 | Maven plugins are used to copy all the project dependencies into a folder, generate a slimmed-down JVM, and then 6 | generate a platform-specific installer. 7 | The [pom.xml](https://github.com/wiverson/maven-jpackage-template/blob/main/pom.xml) 8 | is heavily commented! 9 | 10 | ## What are all the folders in the target directory? 11 | 12 | Here are the folders unique to this project: 13 | 14 | - `dependency` - all of the jar files declared by your Maven project, including the jar containing your code 15 | - `installer-work` - the platform specific working files generated by jpackage. 16 | - [Wix Toolset](https://wixtoolset.org) files on Windows 17 | - [Script Editor](https://support.apple.com/guide/script-editor/welcome/mac) on macOS. 18 | - `jvm-image` - Platform-specific, trimmed down JVM. 19 | - `packaging` - Platform-specific commands, filtered by Maven and fed to jpackage. 20 | 21 | Standard Maven folders: 22 | 23 | - `classes` - generated by the javac compiler, contains your compiled code. 24 | - `generated-sources` - side-effect of the javac compiler 25 | - `maven-archiver` - side-effect of Maven execution. 26 | - `maven-status` - side-effect of Maven compiler 27 | 28 | ## Linux Tips 29 | 30 | There are a LOT of different flavors of Linux out there. I've provided the Ubuntu build more as an example of how the 31 | GitHub Action works, but I can't diagnose or trouble-shoot your Linux build (unless it's a consulting engagement). Feel 32 | free to post these in [discussions](https://github.com/wiverson/maven-jpackage-template/discussions)! 33 | 34 | I will note, however, that much of the Linux trouble I have seen comes from some of the included integration 35 | demonstrations. Try commenting out the loading of demo plugins in `BaseApplication.java` - specifically the loop that 36 | loads the plugins. 37 | 38 | In theory, the Exception handler in the plugin loader code should catch the exceptions. In practice, on a few flavors of 39 | Linux something dies with a native exception that takes it all down. 40 | 41 | I get more support/issues for Linux builds than anything else, often for distros I've never heard of... which is cool 42 | but not something I'm really set up to deal with (short of paid consulting). That said, every Linux support issue so 43 | far has been resolved pretty easily by folks posting Maven or application log files in 44 | the [discussion group](https://github.com/wiverson/maven-jpackage-template/discussions). No promises, but go forth and 45 | post! 46 | 47 | The current GitHub Workflow for the Linux build runs on a GitHub Ubuntu instance, and by default it generates a 48 | amd64.deb file. jpackage supports other distribution formats, including rpm, so if you want a different packaging format 49 | you can tweak the 50 | [GitHub Action for the Ubuntu build](https://github.com/wiverson/maven-jpackage-template/blob/main/.github/workflows/maven-build-installer-unix.yml) 51 | and 52 | the [jpackage command for Unix](https://github.com/wiverson/maven-jpackage-template/blob/main/src/packaging/unix-jpackage.txt) 53 | to generate whatever you need. As long as you can find the right combination of configuration flags for 54 | [jpackage](https://docs.oracle.com/en/java/javase/15/docs/specs/man/jpackage.html) and can set up the GitHub Actions 55 | runner to match, you can generate whatever packaging you might need. If you need, you could set up several combinations 56 | of Maven profile and GitHub Action to generate as many different builds as you wish to support. For example, you could 57 | support generating macOS .dmg and .pkg files, Windows .msi and .exe, Linux .deb and .rpm in several different binary 58 | formats. 59 | 60 | ## Custom JDK & JavaFX Builds 61 | 62 | This template now uses the Liberica JDK build which includes JavaFX. This simplifies the developer experience and 63 | configuration over the old version of this template. I've created [a branch](https://github.com/wiverson/maven-jpackage-template/tree/jdk-17-custom-javafx) for those that wish to use the Java 17 + 64 | bundled JavaFX template, but going forward I think the Liberica build is preferable for most JavaFX developers. 65 | 66 | One nice side-effect is that I can eventually include a demo in this template illustrating the use of the JavaFX 67 | web module - basically an embedded web browser, similar to how Electron embeds Chromium to allow web developers 68 | to create desktop applications using browser technology. GitHub templates have an (undocumented?) limit 69 | 70 | ## Windows Tips 71 | 72 | A: First, make sure you **set a custom Windows installer UUID for your project**, as described in 73 | the [pom.xml](https://github.com/wiverson/maven-jpackage-template/blob/main/pom.xml)! 74 | 75 | This UUID is used to uniquely identify the app as YOUR app by the Windows installer system, and is critical for allowing 76 | users to seamlessly upgrade. You can quickly [grab a UUID of your own](https://www.uuidgenerator.net/) and pop that 77 | value in instead. By default jpackage will generate a UUID automatically, but this automatic UUID is easily regenerated 78 | with minor changes to your application, breaking the Windows installer upgrade chain. 79 | 80 | The [Windows GitHub workflow](https://github.com/wiverson/maven-jpackage-template/blob/main/.github/workflows/maven-build-installer-windows.yml) 81 | for this project downloads the Wix Installer toolkit and adds it to the path to automatically build the Windows 82 | installer. On your local dev machine just install [WiX Toolset](https://wixtoolset.org/) 83 | locally instead - it'll be a lot faster. 84 | 85 | ## Can I generate macOS installers on Windows, or Windows installers on macOS? Or macOS/Windows on Linux? 86 | 87 | [Not locally](https://openjdk.java.net/jeps/392), but this project uses GitHub workflows to generate 88 | [macOS](https://github.com/wiverson/maven-jpackage-template/blob/main/.github/workflows/maven-build-installer.yml) 89 | and 90 | [Windows](https://github.com/wiverson/maven-jpackage-template/blob/main/.github/workflows/maven-build-installer-windows.yml) ( 91 | and Linux) 92 | installers in the cloud regardless of your development platform. This means that (for example) 93 | you could do your dev work on Linux and rely on the GitHub Actions to generate macOS and Windows builds. If you need 94 | help, reach out to [ChangeNode.com](https://changenode.com/). 95 | 96 | You still should (of course) do platform specific testing on your apps, but that's a different topic. 97 | 98 | ## Does this support auto-updating, crash reporting, or analytics? 99 | 100 | No. If that is something you need help setting up, feel free to [reach out for help](https://changenode.com/). 101 | 102 | Some developers just ping the [GitHub Releases API](https://docs.github.com/en/rest/releases) or even just a file on 103 | a web server and present an in-app UI notification to let users know if there is a new version available. 104 | 105 | You might want to check out [Sentry](https://docs.sentry.io/platforms/java/) for crash reporting. This is a busy 106 | space and things move fast. 107 | 108 | ## I'd rather use shell scripts. 109 | 110 | Ok. Check out [JPackageScriptFX](https://github.com/dlemmermann/JPackageScriptFX) - the original shell scripts used 111 | as a reference when I initially started work on this project. 112 | 113 | ## Any pointers for working with JavaFX? 114 | 115 | Sure - here's 116 | my [personal list of cool JavaFX resources](https://gist.github.com/wiverson/6c7f49819016cece906f0e8cea195ea2), which 117 | includes links to a few other big lists of resources. 118 | 119 | ## Any Maven tips? 120 | 121 | If you are not familiar with the standard Maven build lifecycle, you are highly encouraged to review the documentation 122 | ["Introduction to the Build Lifecycle"](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html) 123 | to understand how Maven builds work. 124 | 125 | The project also uses os-activated, platform-specific 126 | [profiles](https://maven.apache.org/guides/introduction/introduction-to-profiles.html) for configuration - really cool 127 | for setting up platform-specific stuff. 128 | 129 | ## Wait, didn't this project use to try to completely modularize the application first? 130 | 131 | Correct. 132 | 133 | There were two previous versions of this template, and both strategies were discarded as impractical in the real world. 134 | 135 | The first approach was to **try to create a shaded jar** (a single jar containing all of the project dependencies) and 136 | then use jdeps to create a module-info.java and add it to the shaded jar. This failed to account for things like 137 | multi-release jars and jars with service declarations. While it worked for trivial applications, it fell apart with more 138 | complex real world usage. In particular, attempting to integrate Spring Boot into the application caused many issues to 139 | appear. 140 | 141 | The second attempt took a more sophisticated approach - **using jdeps to automatically modularize all of the project 142 | dependencies.** 143 | A new [`collect-modules` goal](https://github.com/wiverson/jtoolprovider-plugin/blob/main/collect-modules-doc.md) 144 | was added. In brief, the plugin would walk through the entire Maven dependency tree and sort all of the declared 145 | dependencies into folders. The dependencies that already were modularized (both basic and multi-release jars) went into 146 | one folder, and ordinary non-modularized jars went into another. The plugin would then attempt to use jdeps to 147 | automatically generate module-info.java and add the compiled module-info.classes into each jar. 148 | 149 | Unfortunately, this also failed. There were numerous errors, such a circular references between libraries (e.g. slf4j 150 | and logback). Some jars would only work when jdeps generated open module-info.java files, and others would only work 151 | when jdeps generated module-info.java files with package level exports. Many, many jars would have large numbers of 152 | packages exposed, which mean that the resulting module-info.java files contained many, many entries. The error messages 153 | and the resolution for these messages were very, very confusing. The terminology around modules is unfortunately very 154 | inaccessible to a typical Java developer - for example, a "static" declaration in a Java module-info.java is used to 155 | denote a concept similar to the Maven notion of "provided" - but unfortunately jdeps fails to run if a needed module is 156 | declared as a transitive reference. Even if it's optional. 157 | 158 | In the end, even with the plugin, the error messages and the resolution of those messages is just simply not something a 159 | typical Java developer can be expected to understand or fix. 160 | 161 | Which brings us back to this project. The end result is effective the same - just change 162 | the `javafx.media,javafx.controls,javafx.fxml,java.logging` declaration in the pom.xml to 163 | specify the JVM modules you need and just skip all trying to modularize your app. 164 | 165 | For now, I would consider the creation and use of module-info.java to effective be a system programming interface for 166 | the JDK itself and system-level modules such as JavaFX itself, where the entire dependency tree is very carefully 167 | enforced - and likely includes native code. For ordinary developers, just enjoy the benefits of a trimmed down custom 168 | JVM and don't worry about it. After all the challenges working with 169 | [(incorrectly written!) module-info.java files](https://github.com/sormuras/modules/tree/main/doc/suspicious) 170 | I found just in the Spring Boot dependency graph, I would highly suggest that maintainers for ordinary, non-native Java 171 | libraries remove their existing module-info.java files. 172 | 173 | It's a pity, as I think that if jdeps, jlink, and jpackage were set up to me more user-friendly, I think it would be a 174 | very interesting system that might lead to slimmed down, cloud-friendly applications. For me, the acid test for Java 175 | module adoption is probably Spring Boot. It's very, very popular. From a technical standpoint, it uses technologies such 176 | as reflection that are often notoriously tricky when working with static compilation heavily. An end user should be able 177 | to simply start working with Spring Boot and at at most a few commands to their build process. Clearly projects such 178 | as [Spring Native](https://github.com/spring-projects-experimental/spring-native) 179 | shows there is an interest in this space. 180 | 181 | -------------------------------------------------------------------------------- /docs/sample-run.md: -------------------------------------------------------------------------------- 1 | 2 | For reference, here is the complete output from a successful run of the project. 3 | 4 | Note that on my (reasonably modern) Windows 10 laptop the line... 5 | 6 | `[INFO] --- jtoolprovider-plugin:1.0.23:java-tool (jpackage-win-installer) @ maven-jpackage-template ---` 7 | 8 | ...can take a little while to run. 9 | 10 | Here is a sample execution of `mvn clean install` 11 | 12 | ``` 13 | [INFO] Scanning for projects... 14 | [INFO] 15 | [INFO] ---------------< com.changenode:maven-jpackage-template >--------------- 16 | [INFO] Building maven-jpackage-template 1.0-SNAPSHOT 17 | [INFO] --------------------------------[ jar ]--------------------------------- 18 | [INFO] 19 | [INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ maven-jpackage-template --- 20 | [INFO] Deleting /Users/wiverson/src/sample-project/target 21 | [INFO] 22 | [INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ maven-jpackage-template --- 23 | [INFO] Using 'UTF-8' encoding to copy filtered resources. 24 | [INFO] Using 'UTF-8' encoding to copy filtered properties files. 25 | [INFO] Copying 3 resources to /Users/wiverson/src/sample-project/target/packaging 26 | [INFO] 27 | [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ maven-jpackage-template --- 28 | [INFO] Changes detected - recompiling the module! 29 | [INFO] Compiling 9 source files to /Users/wiverson/src/sample-project/target/classes 30 | [INFO] 31 | [INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ maven-jpackage-template --- 32 | [INFO] Using 'UTF-8' encoding to copy filtered resources. 33 | [INFO] Using 'UTF-8' encoding to copy filtered properties files. 34 | [INFO] skip non existing resourceDirectory /Users/wiverson/src/sample-project/src/test/resources 35 | [INFO] 36 | [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ maven-jpackage-template --- 37 | [INFO] No sources to compile 38 | [INFO] 39 | [INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ maven-jpackage-template --- 40 | [INFO] No tests to run. 41 | [INFO] 42 | [INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ maven-jpackage-template --- 43 | [INFO] Building jar: /Users/wiverson/src/sample-project/target/dependency/maven-jpackage-template-1.0-SNAPSHOT.jar 44 | [INFO] 45 | [INFO] --- maven-dependency-plugin:3.1.2:copy-dependencies (copy-dependencies) @ maven-jpackage-template --- 46 | [INFO] Copying javafaker-1.0.2.jar to /Users/wiverson/src/sample-project/target/dependency/javafaker-1.0.2.jar 47 | [INFO] Copying commons-lang3-3.5.jar to /Users/wiverson/src/sample-project/target/dependency/commons-lang3-3.5.jar 48 | [INFO] Copying snakeyaml-1.23-android.jar to /Users/wiverson/src/sample-project/target/dependency/snakeyaml-1.23-android.jar 49 | [INFO] Copying generex-1.0.2.jar to /Users/wiverson/src/sample-project/target/dependency/generex-1.0.2.jar 50 | [INFO] Copying automaton-1.11-8.jar to /Users/wiverson/src/sample-project/target/dependency/automaton-1.11-8.jar 51 | [INFO] 52 | [INFO] --- jtoolprovider-plugin:1.0.34:java-tool (jlink) @ maven-jpackage-template --- 53 | [INFO] 54 | [INFO] --- maven-install-plugin:3.0.0-M1:install (default-install) @ maven-jpackage-template --- 55 | [INFO] Skipping artifact installation 56 | [INFO] 57 | [INFO] --- jtoolprovider-plugin:1.0.34:java-tool (jpackage) @ maven-jpackage-template --- 58 | [INFO] ------------------------------------------------------------------------ 59 | [INFO] BUILD SUCCESS 60 | [INFO] ------------------------------------------------------------------------ 61 | [INFO] Total time: 20.392 s 62 | [INFO] Finished at: 2021-03-16T14:34:34-07:00 63 | [INFO] ------------------------------------------------------------------------ 64 | ``` 65 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.changenode 8 | maven-jpackage-template 9 | 1.1-SNAPSHOT 10 | 11 | 12 | Sample JavaFX Application 13 | 14 | 15 | 16 | com.changenode.BaseApplication 17 | 19 | TestApp 20 | 25 | 26 | 1.0.0 27 | 28 | 29 | 31 | javafx.media,javafx.controls,javafx.fxml,java.logging 32 | 33 | 34 | UTF-8 35 | 36 | 18 37 | 18 38 | 39 | 42 | aaaaaaaa-0000-aaaa-aaaa-aaaaaaaaaaaa 43 | 44 | Sample 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | com.github.javafaker 57 | javafaker 58 | 1.0.2 59 | 60 | 61 | 62 | 63 | 64 | io.github.mkpaz 65 | atlantafx-base 66 | 1.1.0 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | kr.motd.maven 76 | os-maven-plugin 77 | 1.7.0 78 | 79 | 80 | 81 | 82 | 84 | ${project.basedir}/src/packaging 85 | true 86 | ${project.build.directory}/packaging 87 | 88 | 89 | 90 | ${project.basedir}/src/main/resources 91 | true 92 | 93 | 94 | ${project.basedir}/src/main/resources/build-info/ 95 | true 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-install-plugin 103 | 104 | 105 | true 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-dependency-plugin 111 | 112 | 113 | 115 | copy-dependencies 116 | package 117 | 118 | copy-dependencies 119 | 120 | 121 | 122 | compile 123 | 125 | org.openjfx 126 | 127 | 128 | 129 | 130 | 131 | 133 | org.apache.maven.plugins 134 | maven-jar-plugin 135 | 136 | ${project.build.directory}/dependency 137 | 138 | 139 | 140 | 141 | org.openjfx 142 | javafx-maven-plugin 143 | 0.0.8 144 | 145 | ${main-class} 146 | 147 | 148 | 149 | 150 | io.github.wiverson 151 | jtoolprovider-plugin 152 | 1.0.34 153 | 154 | 156 | 157 | jlink 158 | package 159 | 160 | java-tool 161 | 162 | 163 | jlink 164 | ${project.build.directory}/jvm-image/ 165 | ${jvm.modules} 166 | ${project.build.directory}/jvm-image 167 | 168 | --strip-native-commands 169 | --no-header-files 170 | --strip-debug 171 | --no-man-pages 172 | --compress=2 173 | 174 | 175 | 176 | 183 | 184 | jpackage 185 | install 186 | 187 | java-tool 188 | 189 | 190 | jpackage 191 | true 192 | true 193 | true 194 | ${project.build.directory}/installer-work 195 | @${project.build.directory}/packaging/${os.detected.name}-jpackage.txt 196 | 197 | 198 | 199 | 200 | 201 | org.apache.maven.plugins 202 | maven-compiler-plugin 203 | 204 | ${maven.compiler.source} 205 | ${maven.compiler.target} 206 | 207 | 208 | 209 | 210 | 211 | 212 | org.apache.maven.plugins 213 | maven-clean-plugin 214 | 3.2.0 215 | 216 | 217 | org.apache.maven.plugins 218 | maven-compiler-plugin 219 | 3.10.1 220 | 221 | 222 | org.apache.maven.plugins 223 | maven-dependency-plugin 224 | 3.3.0 225 | 226 | 227 | org.apache.maven.plugins 228 | maven-install-plugin 229 | 3.0.0-M1 230 | 231 | 232 | org.apache.maven.plugins 233 | maven-jar-plugin 234 | 3.2.2 235 | 236 | 237 | org.apache.maven.plugins 238 | maven-project-info-reports-plugin 239 | 3.3.0 240 | 241 | 242 | org.apache.maven.plugins 243 | maven-resources-plugin 244 | 3.2.0 245 | 246 | 247 | 248 | org.apache.maven.plugins 249 | maven-site-plugin 250 | 4.0.0-M1 251 | 252 | 253 | org.apache.maven.plugins 254 | maven-surefire-plugin 255 | 3.0.0-M7 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | org.apache.maven.plugins 265 | maven-project-info-reports-plugin 266 | 3.3.0 267 | 268 | 269 | 270 | 273 | 274 | org.codehaus.mojo 275 | versions-maven-plugin 276 | 2.8.1 277 | 278 | 279 | 280 | dependency-updates-report 281 | plugin-updates-report 282 | property-updates-report 283 | 284 | 285 | 286 | 287 | false 288 | 289 | 290 | 291 | 292 | 293 | 294 | GitHub Run 295 | 296 | 297 | env.GITHUB_ACTIONS 298 | 299 | 300 | 301 | 302 | 1.1.${env.GITHUB_RUN_NUMBER} 303 | 304 | 305 | 306 | Local Development Run 307 | 308 | 309 | !env.GITHUB_ACTIONS 310 | 311 | 312 | 313 | 1.1.0 314 | 315 | 316 | 317 | 318 | 322 | 323 | 324 | macos-sign 325 | 326 | false 327 | 328 | 329 | --mac-sign 330 | 331 | --mac-signing-key-user-name "Company Name, Inc. (BXPXTXC35S)" 332 | 333 | 334 | 335 | 336 | windows-active 337 | 338 | 339 | windows 340 | 341 | 342 | 343 | 344 | 345 | 348 | org.codehaus.mojo 349 | exec-maven-plugin 350 | 3.0.0 351 | 352 | 353 | install 354 | add-launch-to-msi 355 | 356 | exec 357 | 358 | 359 | 360 | 361 | cscript 362 | ${project.build.directory}/msi-result.log 363 | ${project.build.directory} 364 | 365 | ${project.build.directory}/packaging/add-launch-to-msi.js 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | -------------------------------------------------------------------------------- /src/main/java/com/changenode/BaseApplication.java: -------------------------------------------------------------------------------- 1 | package com.changenode; 2 | 3 | import atlantafx.base.theme.PrimerDark; 4 | import atlantafx.base.theme.PrimerLight; 5 | import com.changenode.plugin.*; 6 | import javafx.application.Application; 7 | import javafx.geometry.Insets; 8 | import javafx.scene.Scene; 9 | import javafx.scene.control.Label; 10 | import javafx.scene.control.MenuBar; 11 | import javafx.scene.control.TextArea; 12 | import javafx.scene.control.ToolBar; 13 | import javafx.scene.layout.BorderPane; 14 | import javafx.scene.layout.VBox; 15 | import javafx.stage.Stage; 16 | 17 | import java.io.*; 18 | 19 | import static javax.swing.filechooser.FileSystemView.getFileSystemView; 20 | 21 | public class BaseApplication extends Application implements Log { 22 | 23 | public static File outputFile; 24 | /** 25 | * This is the very simple "registry" for the various demonstration features of this application. 26 | */ 27 | private final Plugin[] plugins = new Plugin[]{new StandardMenus(), new HelloWorld(), new FileDrop(), 28 | new DesktopIntegration(), new LogFile(), new DarkMode()}; 29 | 30 | private TextArea textArea; 31 | private Label statusLabel; 32 | 33 | public static void main(String[] args) { 34 | /* 35 | * Route the debugging output for this application to a log file in your "default" directory. 36 | * */ 37 | try { 38 | outputFile = File.createTempFile("debug", ".log", getFileSystemView().getDefaultDirectory()); 39 | PrintStream output = new PrintStream(new BufferedOutputStream(new FileOutputStream(outputFile)), true); 40 | System.setOut(output); 41 | System.setErr(output); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | 46 | launch(args); 47 | } 48 | 49 | public void log(String s) { 50 | textArea.appendText(s); 51 | textArea.appendText(System.lineSeparator()); 52 | statusLabel.setText(s); 53 | } 54 | 55 | @Override 56 | public void start(Stage stage) { 57 | 58 | Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet()); 59 | 60 | BorderPane borderPane = new BorderPane(); 61 | 62 | VBox topElements = new VBox(); 63 | 64 | MenuBar menuBar = new MenuBar(); 65 | topElements.getChildren().add(menuBar); 66 | 67 | ToolBar toolbar = new ToolBar(); 68 | topElements.getChildren().add(toolbar); 69 | 70 | textArea = new TextArea(); 71 | textArea.setWrapText(true); 72 | 73 | statusLabel = new Label(); 74 | statusLabel.setPadding(new Insets(5.0f, 5.0f, 5.0f, 5.0f)); 75 | statusLabel.setMaxWidth(Double.MAX_VALUE); 76 | 77 | borderPane.setTop(topElements); 78 | borderPane.setBottom(statusLabel); 79 | borderPane.setCenter(textArea); 80 | 81 | Scene scene = new Scene(borderPane, 800, 600); 82 | 83 | stage.setTitle("Hello World"); 84 | stage.setScene(scene); 85 | 86 | for (Plugin plugin : plugins) { 87 | try { 88 | plugin.setup(stage, textArea, toolbar, this, menuBar); 89 | } catch (Exception e) { 90 | System.err.println("Unable to start plugin"); 91 | System.err.println(plugin.getClass().getName()); 92 | e.printStackTrace(); 93 | log("Unable to start plugin"); 94 | log(plugin.getClass().getName()); 95 | log(e.getMessage()); 96 | } 97 | } 98 | 99 | statusLabel.setText("Ready."); 100 | 101 | stage.show(); 102 | //put window to front to avoid it to be hide behind other. 103 | stage.setAlwaysOnTop(true); 104 | stage.requestFocus(); 105 | stage.toFront(); 106 | stage.setAlwaysOnTop(false); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/changenode/Log.java: -------------------------------------------------------------------------------- 1 | package com.changenode; 2 | 3 | public interface Log { 4 | void log(String s); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/changenode/Plugin.java: -------------------------------------------------------------------------------- 1 | package com.changenode; 2 | 3 | import javafx.scene.control.MenuBar; 4 | import javafx.scene.control.TextArea; 5 | import javafx.scene.control.ToolBar; 6 | import javafx.stage.Stage; 7 | 8 | /** 9 | * This is a very basic, leaky example of a plugin interface 10 | */ 11 | public interface Plugin { 12 | 13 | void setup(Stage stage, TextArea textArea, ToolBar toolBar, Log log, MenuBar menuBar); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/changenode/plugin/DarkMode.java: -------------------------------------------------------------------------------- 1 | package com.changenode.plugin; 2 | 3 | import atlantafx.base.theme.PrimerDark; 4 | import atlantafx.base.theme.PrimerLight; 5 | import com.changenode.Log; 6 | import com.changenode.Plugin; 7 | import javafx.application.Application; 8 | import javafx.scene.Scene; 9 | import javafx.scene.control.Button; 10 | import javafx.scene.control.MenuBar; 11 | import javafx.scene.control.TextArea; 12 | import javafx.scene.control.ToolBar; 13 | import javafx.stage.Stage; 14 | 15 | public class DarkMode implements Plugin { 16 | 17 | private boolean isDark; 18 | 19 | private Scene scene; 20 | private Button toggleDark; 21 | 22 | @Override 23 | public void setup(Stage stage, TextArea textArea, ToolBar toolBar, Log log, MenuBar menuBar) { 24 | scene = stage.getScene(); 25 | toggleDark = new Button(); 26 | toggleDark.setText("Light"); 27 | toggleDark.setOnAction(e -> toggleDark()); 28 | toggleDark.setFocusTraversable(false); 29 | toolBar.getItems().add(toggleDark); 30 | } 31 | 32 | private void toggleDark() { 33 | if (isDark) { 34 | // This is how to set a light style w/the default JavaFX CSS 35 | // scene.getRoot().setStyle(""); 36 | Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet()); 37 | toggleDark.setText("Light"); 38 | } else { 39 | // This is how to set a dark style w/the default JavaFX CSS. 40 | // scene.getRoot().setStyle("-fx-base:#25292D;"); 41 | Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet()); 42 | toggleDark.setText("Dark"); 43 | } 44 | isDark = !isDark; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/changenode/plugin/DesktopIntegration.java: -------------------------------------------------------------------------------- 1 | package com.changenode.plugin; 2 | 3 | import com.changenode.Log; 4 | import com.changenode.Plugin; 5 | import javafx.concurrent.Task; 6 | import javafx.scene.control.Menu; 7 | import javafx.scene.control.MenuBar; 8 | import javafx.scene.control.MenuItem; 9 | import javafx.scene.control.TextArea; 10 | import javafx.scene.control.*; 11 | import javafx.scene.input.KeyCode; 12 | import javafx.stage.Stage; 13 | 14 | import java.awt.*; 15 | import java.awt.Taskbar.Feature; 16 | import java.awt.image.BufferedImage; 17 | 18 | import static com.changenode.plugin.StandardMenus.Configure; 19 | import static java.awt.Taskbar.getTaskbar; 20 | import static java.awt.Taskbar.isTaskbarSupported; 21 | 22 | public class DesktopIntegration implements Plugin { 23 | 24 | int currentIconProgress = 1; 25 | 26 | Image defaultIcon; 27 | Image redCircleIcon; 28 | 29 | public Menu extraDesktopIntegration(Log log) { 30 | if (!isTaskbarSupported()) 31 | return null; 32 | 33 | log.log(""); 34 | log.log("Desktop integration flags for this platform include:"); 35 | 36 | for (Feature feature : Feature.values()) { 37 | log.log(" " + feature.name() + " " + getTaskbar().isSupported(feature)); 38 | } 39 | 40 | if (getTaskbar().isSupported(Feature.ICON_IMAGE)) { 41 | defaultIcon = getTaskbar().getIconImage(); 42 | 43 | BufferedImage bufferedImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB); 44 | Graphics2D graphics2D = bufferedImage.createGraphics(); 45 | graphics2D.setColor(Color.red); 46 | graphics2D.fillOval(0, 0, 256, 256); 47 | graphics2D.dispose(); 48 | 49 | redCircleIcon = bufferedImage; 50 | 51 | } 52 | MenuItem useCustomIcon = Configure("Use Custom App Icon", x -> getTaskbar().setIconImage(redCircleIcon), null); 53 | MenuItem useDefaultAppIcon = Configure("Use Default App Icon", x -> getTaskbar().setIconImage(defaultIcon), null); 54 | useCustomIcon.setDisable(!getTaskbar().isSupported(Feature.ICON_IMAGE)); 55 | useDefaultAppIcon.setDisable(!getTaskbar().isSupported(Feature.ICON_IMAGE)); 56 | 57 | Menu desktopIntegration = new Menu("Desktop"); 58 | 59 | MenuItem setIconBadge = Configure("Set Badge", x -> getTaskbar().setIconBadge("1"), null); 60 | MenuItem removeIconBadge = Configure("Remove Badge", x -> getTaskbar().setIconBadge("1"), null); 61 | 62 | setIconBadge.setDisable(!getTaskbar().isSupported(Feature.ICON_BADGE_TEXT)); 63 | removeIconBadge.setDisable(!getTaskbar().isSupported(Feature.ICON_BADGE_TEXT)); 64 | 65 | 66 | MenuItem addProgress = Configure("Add Icon Progress", x -> getTaskbar().setProgressValue(currentIconProgress++), KeyCode.R); 67 | MenuItem clearProgress = Configure("Clear Icon Progress", x -> { 68 | currentIconProgress = -1; 69 | getTaskbar().setProgressValue(currentIconProgress++); 70 | }, null); 71 | addProgress.setDisable(!getTaskbar().isSupported(Feature.PROGRESS_VALUE)); 72 | clearProgress.setDisable(!getTaskbar().isSupported(Feature.PROGRESS_VALUE)); 73 | 74 | MenuItem requestUserAttention = Configure("Request User Attention (5s)", x -> requestUserAttention(), null); 75 | 76 | requestUserAttention.setDisable(!getTaskbar().isSupported(Feature.USER_ATTENTION)); 77 | 78 | desktopIntegration.getItems().addAll(setIconBadge, removeIconBadge, addProgress, clearProgress, useCustomIcon, useDefaultAppIcon, requestUserAttention); 79 | 80 | return desktopIntegration; 81 | } 82 | 83 | private void requestUserAttention() { 84 | 85 | Task task = new Task() { 86 | 87 | @Override 88 | public Void call() { 89 | try { 90 | Thread.sleep(5000); 91 | } catch (InterruptedException e) { 92 | e.printStackTrace(); 93 | } 94 | getTaskbar().requestUserAttention(true, true); 95 | return null; 96 | } 97 | }; 98 | 99 | new Thread(task).start(); 100 | } 101 | 102 | @Override 103 | public void setup(Stage stage, TextArea textArea, ToolBar toolBar, Log log, MenuBar menuBar) { 104 | menuBar.getMenus().add(extraDesktopIntegration(log)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/changenode/plugin/FileDrop.java: -------------------------------------------------------------------------------- 1 | package com.changenode.plugin; 2 | 3 | import com.changenode.Log; 4 | import com.changenode.Plugin; 5 | import javafx.geometry.Insets; 6 | import javafx.scene.control.MenuBar; 7 | import javafx.scene.control.TextArea; 8 | import javafx.scene.control.ToolBar; 9 | import javafx.scene.input.Dragboard; 10 | import javafx.scene.input.TransferMode; 11 | import javafx.scene.layout.Background; 12 | import javafx.scene.layout.BackgroundFill; 13 | import javafx.scene.layout.CornerRadii; 14 | import javafx.scene.paint.Color; 15 | import javafx.stage.Stage; 16 | 17 | import java.io.File; 18 | 19 | public class FileDrop implements Plugin { 20 | 21 | public void setupFileDropTarget(TextArea textArea, Log log) { 22 | textArea.setOnDragOver(event -> { 23 | if (event.getGestureSource() != textArea && event.getDragboard().hasFiles()) { 24 | event.acceptTransferModes(TransferMode.COPY_OR_MOVE); 25 | } 26 | event.consume(); 27 | 28 | }); 29 | 30 | textArea.setOnDragEntered(event -> textArea.setBackground( 31 | new Background(new BackgroundFill(Color.CORNFLOWERBLUE, CornerRadii.EMPTY, Insets.EMPTY)))); 32 | 33 | textArea.setOnDragExited(event -> textArea.setBackground( 34 | new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)))); 35 | 36 | textArea.setOnDragDropped(event -> { 37 | Dragboard db = event.getDragboard(); 38 | boolean success = false; 39 | if (db.hasFiles()) { 40 | 41 | for (File file : db.getFiles()) { 42 | log.log(file.getAbsolutePath()); 43 | } 44 | 45 | success = true; 46 | } 47 | /* let the source know whether the information was successfully transferred and used */ 48 | event.setDropCompleted(success); 49 | 50 | event.consume(); 51 | }); 52 | } 53 | 54 | @Override 55 | public void setup(Stage stage, TextArea textArea, ToolBar toolBar, Log log, MenuBar menuBar) { 56 | log.log("Try dragging one or more files and/or directories here from another application."); 57 | setupFileDropTarget(textArea, log); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/changenode/plugin/HelloWorld.java: -------------------------------------------------------------------------------- 1 | package com.changenode.plugin; 2 | 3 | import com.changenode.Log; 4 | import com.changenode.Plugin; 5 | import javafx.scene.control.Button; 6 | import javafx.scene.control.MenuBar; 7 | import javafx.scene.control.TextArea; 8 | import javafx.scene.control.ToolBar; 9 | import javafx.stage.Stage; 10 | 11 | public class HelloWorld implements Plugin { 12 | 13 | Button button; 14 | 15 | @Override 16 | public void setup(Stage stage, TextArea textArea, ToolBar toolBar, Log log, MenuBar menuBar) { 17 | button = new Button(); 18 | button.setText("Hello World"); 19 | button.setOnAction(event -> log.log("Hello World! " + java.util.Calendar.getInstance().getTime())); 20 | button.setFocusTraversable(false); 21 | 22 | toolBar.getItems().add(button); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/changenode/plugin/LogFile.java: -------------------------------------------------------------------------------- 1 | package com.changenode.plugin; 2 | 3 | import com.changenode.BaseApplication; 4 | import com.changenode.Log; 5 | import com.changenode.Plugin; 6 | import javafx.scene.control.*; 7 | import javafx.stage.Stage; 8 | 9 | import static java.awt.Desktop.getDesktop; 10 | import static java.lang.System.out; 11 | import static java.util.Calendar.getInstance; 12 | 13 | public class LogFile implements Plugin { 14 | @Override 15 | public void setup(Stage stage, TextArea textArea, ToolBar toolBar, Log log, MenuBar menuBar) { 16 | 17 | Menu menu = new Menu("Debug"); 18 | MenuItem findDebugLog = new MenuItem("Find Debug Log"); 19 | findDebugLog.setOnAction(e -> showDebugLog()); 20 | 21 | MenuItem writeHelloWorldToLog = new MenuItem("Write Hello World to Log"); 22 | writeHelloWorldToLog.setOnAction(e -> out.println("Hello World! " + getInstance().getTime())); 23 | 24 | menu.getItems().addAll(findDebugLog, writeHelloWorldToLog); 25 | menuBar.getMenus().add(menu); 26 | } 27 | 28 | private void showDebugLog() { 29 | getDesktop().browseFileDirectory(BaseApplication.outputFile); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/changenode/plugin/StandardMenus.java: -------------------------------------------------------------------------------- 1 | package com.changenode.plugin; 2 | 3 | import com.changenode.Log; 4 | import com.changenode.Plugin; 5 | import javafx.application.Platform; 6 | import javafx.event.ActionEvent; 7 | import javafx.event.EventHandler; 8 | import javafx.scene.control.*; 9 | import javafx.scene.input.KeyCode; 10 | import javafx.scene.input.KeyCodeCombination; 11 | import javafx.scene.input.KeyCombination; 12 | import javafx.stage.FileChooser; 13 | import javafx.stage.Stage; 14 | 15 | import java.io.File; 16 | 17 | import static java.lang.System.getProperty; 18 | 19 | public class StandardMenus implements Plugin { 20 | 21 | private Stage stage; 22 | private MenuBar menuBar; 23 | private Log output; 24 | 25 | public static boolean isMac() { 26 | return getProperty("os.name").contains("Mac"); 27 | } 28 | 29 | public static MenuItem Configure(String name, EventHandler action, KeyCode keyCode) { 30 | MenuItem item = new MenuItem(name); 31 | item.setOnAction(action); 32 | if (keyCode != null) 33 | item.setAccelerator(new KeyCodeCombination(keyCode, KeyCombination.SHORTCUT_DOWN)); 34 | return item; 35 | } 36 | 37 | private void openFileDialog() { 38 | FileChooser fileChooser = new FileChooser(); 39 | fileChooser.setTitle("Open File"); 40 | File file = fileChooser.showOpenDialog(stage); 41 | if (file != null) { 42 | output.log(file.getAbsolutePath()); 43 | } else { 44 | output.log("Open File cancelled."); 45 | } 46 | } 47 | 48 | public void standardMenus() { 49 | 50 | Menu file = new Menu("File"); 51 | MenuItem newFile = Configure("New", x -> output.log("File -> New"), KeyCode.N); 52 | MenuItem open = Configure("Open...", x -> openFileDialog(), KeyCode.O); 53 | 54 | file.getItems().addAll(newFile, open); 55 | 56 | if (!isMac()) { 57 | MenuItem quit = Configure("Quit", x -> Platform.exit(), KeyCode.Q); 58 | file.getItems().add(quit); 59 | } else { 60 | menuBar.setUseSystemMenuBar(true); 61 | } 62 | 63 | Menu edit = new Menu("Edit"); 64 | MenuItem undo = Configure("Undo", x -> output.log("Undo"), KeyCode.Z); 65 | MenuItem redo = Configure("Redo", x -> output.log("Redo"), KeyCode.R); 66 | SeparatorMenuItem editSeparator = new SeparatorMenuItem(); 67 | MenuItem cut = Configure("Cut", x -> output.log("Cut"), KeyCode.X); 68 | MenuItem copy = Configure("Copy", x -> output.log("Copy"), KeyCode.C); 69 | MenuItem paste = Configure("Paste", x -> output.log("Paste"), KeyCode.V); 70 | 71 | edit.getItems().addAll(undo, redo, editSeparator, cut, copy, paste); 72 | 73 | menuBar.getMenus().addAll(file, edit); 74 | } 75 | 76 | @Override 77 | public void setup(Stage stage, TextArea textArea, ToolBar toolBar, Log log, MenuBar menuBar) { 78 | this.menuBar = menuBar; 79 | this.output = log; 80 | this.stage = stage; 81 | 82 | standardMenus(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/resources/build-info/build-info.properties: -------------------------------------------------------------------------------- 1 | build.architecture=${os.detected.classifier} 2 | build.timestamp=${maven.build.timestamp} 3 | -------------------------------------------------------------------------------- /src/main/resources/hello.txt: -------------------------------------------------------------------------------- 1 | A simple hello file that is copied into the application. 2 | -------------------------------------------------------------------------------- /src/main/resources/version.txt: -------------------------------------------------------------------------------- 1 | ${maven.build.timestamp} 2 | ${env.GITHUB_RUN_NUMBER} 3 | ${env.GITHUB_RUN_ID} 4 | ${env.GITHUB_RUN_ATTEMPT} 5 | ${env.GITHUB_WORKFLOW} 6 | ${env.RUNNER_ARCH} -------------------------------------------------------------------------------- /src/packaging/add-launch-to-msi.js: -------------------------------------------------------------------------------- 1 | // run with command 2 | // cscript add-change.js 3 | var installer = WScript.CreateObject("WindowsInstaller.Installer"); 4 | var database = installer.OpenDatabase("${app.name}-${app.version}.msi", 1); 5 | var sql 6 | var view 7 | 8 | var file = FindFileIdentifier(database, "${app.name}.exe"); 9 | 10 | try { 11 | sql = "INSERT INTO `CustomAction` (`Action`,`Type`,`Source`) VALUES ('ExecuteAfterFinalize','2258','" + file + "')" 12 | WScript.StdErr.WriteLine(sql); 13 | view = database.OpenView(sql); 14 | view.Execute(); 15 | view.Close(); 16 | 17 | sql = "INSERT INTO `InstallExecuteSequence` (`Action`,`Condition`,`Sequence`) VALUES ('ExecuteAfterFinalize','NOT Installed','6700')" 18 | WScript.StdErr.WriteLine(sql); 19 | view = database.OpenView(sql); 20 | view.Execute(); 21 | view.Close(); 22 | WScript.StdErr.WriteLine("Committing changes"); 23 | database.Commit(); 24 | } catch (e) { 25 | WScript.StdErr.WriteLine(e); 26 | WScript.Quit(1); 27 | } 28 | 29 | // Finds file id and component id of file 30 | function FindFileIdentifier(database, fileName) { 31 | var sql 32 | var view 33 | var record 34 | 35 | // First, try to find the exact file name 36 | sql = "SELECT `File`, `Component_` FROM `File` WHERE `FileName`='" + fileName + "'"; 37 | view = database.OpenView(sql); 38 | view.Execute(); 39 | record = view.Fetch(); 40 | if (record) { 41 | var value = record.StringData(1); 42 | componentId = record.StringData(2) 43 | view.Close(); 44 | return value; 45 | } 46 | view.Close(); 47 | 48 | // The file may be in SFN|LFN format. Look for a filename in this case next 49 | sql = "SELECT `File`, `Component_`, `FileName` FROM `File`"; 50 | view = database.OpenView(sql); 51 | view.Execute(); 52 | record = view.Fetch(); 53 | while (record) { 54 | if (StringEndsWith(record.StringData(3), "|" + fileName)) { 55 | componentId = record.StringData(2); 56 | var value = record.StringData(1); 57 | view.Close(); 58 | return value; 59 | } 60 | 61 | record = view.Fetch(); 62 | } 63 | view.Close(); 64 | 65 | } 66 | 67 | function StringEndsWith(str, value) { 68 | if (str.length < value.length) 69 | return false; 70 | 71 | return (str.indexOf(value, str.length - value.length) != -1); 72 | } -------------------------------------------------------------------------------- /src/packaging/linux-jpackage.txt: -------------------------------------------------------------------------------- 1 | --name ${app.name} 2 | --icon "${project.basedir}/app-icon.png" 3 | --dest "${project.build.directory}" 4 | --main-jar ${project.build.finalName}.jar 5 | --main-class ${main-class} 6 | --input "${project.build.directory}/dependency" 7 | --app-version ${app.version} 8 | --runtime-image "${project.build.directory}/jvm-image" 9 | --temp "${project.build.directory}/installer-work" 10 | -------------------------------------------------------------------------------- /src/packaging/osx-jpackage.txt: -------------------------------------------------------------------------------- 1 | --name ${app.name} 2 | --icon "${project.basedir}/app-icon.icns" 3 | --dest "${project.build.directory}" 4 | --main-jar ${project.build.finalName}.jar 5 | --main-class ${main-class} 6 | --input "${project.build.directory}/dependency" 7 | --app-version ${app.version} 8 | --runtime-image "${project.build.directory}/jvm-image" 9 | --temp "${project.build.directory}/installer-work" 10 | ${macos.sign} 11 | ${macos.sign.identity} 12 | -------------------------------------------------------------------------------- /src/packaging/windows-jpackage.txt: -------------------------------------------------------------------------------- 1 | --type msi 2 | --name ${app.name} 3 | --win-menu 4 | --win-menu-group ${windows.vendor} 5 | --vendor ${windows.vendor} 6 | --icon "${project.basedir}/app-icon.ico" 7 | --dest "${project.build.directory}" 8 | --main-jar ${project.build.finalName}.jar 9 | --main-class ${main-class} 10 | --input "${project.build.directory}/dependency" 11 | --app-version ${app.version} 12 | --runtime-image "${project.build.directory}/jvm-image" 13 | --temp "${project.build.directory}/installer-work" 14 | --win-upgrade-uuid ${windows.upgrade.uuid} 15 | --description "${project.description}" 16 | --copyright "(C) ${windows.vendor}" 17 | --------------------------------------------------------------------------------