Skip to content

Commit

Permalink
Add a simple JavaFX installer to the launcher. (#1292)
Browse files Browse the repository at this point in the history
* Add javafx installer to launcher.

* Remove unused imports.

* Add dropdowns to select which computer configuration to use. Add update site functionality.

* Clean up some of the code for the installer. Simplify the error message.

* Add a printout for the detected computer configuration.

* Fix license name typo.

* Bump launcher version. Add link to gluon site for javafx.

* Fix help link for the error pop-up.

* Verify sha256 of downloaded javafx zip files before unpacking them.

Co-authored-by: Maik Marschner <[email protected]>
  • Loading branch information
ThatRedox and leMaik authored Jan 7, 2023
1 parent 902f456 commit 1393227
Show file tree
Hide file tree
Showing 4 changed files with 566 additions and 69 deletions.
72 changes: 3 additions & 69 deletions launcher/src/se/llbit/chunky/launcher/ChunkyLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,10 @@
import se.llbit.chunky.launcher.ui.FirstTimeSetupDialog;
import se.llbit.chunky.resources.SettingsDirectory;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.io.*;
import java.net.*;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
Expand All @@ -53,7 +46,7 @@
*/
public class ChunkyLauncher {

public static final Semver LAUNCHER_VERSION = new Semver("1.13.3");
public static final Semver LAUNCHER_VERSION = new Semver("1.14.0");
public static final int LAUNCHER_SETTINGS_REVISION = 1;

/**
Expand All @@ -68,8 +61,8 @@ protected static void launchFailure(String command) {
public static void main(String[] args) throws FileNotFoundException {
boolean retryIfMissingJavafx = true;

final LauncherSettings settings = new LauncherSettings();
try {
final LauncherSettings settings = new LauncherSettings();
settings.load();

// Currently, there's nothing that changed from previous launcher settings revisions.
Expand Down Expand Up @@ -257,7 +250,7 @@ else if (!settings.javaOptions.contains(args[i + 1]))
// Javafx error
if (retryIfMissingJavafx)
JavaFxLocator.retryWithJavafx(args);
showJavafxError();
JavaFxInstaller.launch(settings, args);
}
e.printStackTrace(System.err);
}
Expand Down Expand Up @@ -387,63 +380,4 @@ public static String prettyPrintSize(int size) {
return String.format("%.1f %s", fSize, unit);
}
}

private static void showJavafxError() {
String[] errorMessages = new String[]{
"Error: Java cannot find JavaFX.",
"If you are using a JVM for Java 11 or later, " +
"JavaFX is no longer shipped alongside and must be installed separately.",
"If you already have JavaFX installed, you need to run Chunky with the command:",
"java --module-path <path/to/JavaFX/lib> --add-modules javafx.controls,javafx.fxml -jar <path/to/ChunkyLauncher.jar>"
};
String faqLink = "https://chunky.lemaik.de/java11";
String faqMessage = "Check out this page for more information on how to use Chunky with JavaFX";
if (!GraphicsEnvironment.isHeadless()) {
JTextField faqLabel;
if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
faqLabel = new JTextField(faqMessage);
Font font = faqLabel.getFont();
Map attributes = font.getAttributes();
attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
faqLabel.setFont(font.deriveFont(attributes));
faqLabel.setForeground(Color.BLUE.darker());
faqLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
faqLabel.setEditable(false);
faqLabel.setBackground(null);
faqLabel.setBorder(null);
faqLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
try {
Desktop.getDesktop().browse(new URI(faqLink));
} catch (IOException | URISyntaxException ioException) {
ioException.printStackTrace();
}
}
});
} else {
faqLabel = new JTextField(String.format("%s: %s", faqMessage, faqLink));
faqLabel.setEditable(false);
faqLabel.setBackground(null);
faqLabel.setBorder(null);
faqLabel.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
}
Object[] dialogContent = {
Arrays.stream(errorMessages).map(msg -> {
JTextField field = new JTextField(msg);
field.setEditable(false);
field.setBackground(null);
field.setBorder(null);
field.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
return field;
}).toArray(),
faqLabel
};
JOptionPane.showMessageDialog(null, dialogContent, "Cannot find JavaFX", JOptionPane.ERROR_MESSAGE);
}
for (String message : errorMessages) {
System.err.println(message);
}
System.err.printf("%s: %s\n", faqMessage, faqLink);
}
}
138 changes: 138 additions & 0 deletions launcher/src/se/llbit/chunky/launcher/JavaFxDownloads.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* Copyright (c) 2022 Chunky contributors
*
* This file is part of Chunky.
*
* Chunky is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Chunky is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Chunky. If not, see <http://www.gnu.org/licenses/>.
*/

package se.llbit.chunky.launcher;

import se.llbit.json.JsonArray;
import se.llbit.json.JsonObject;
import se.llbit.json.JsonParser;
import se.llbit.json.JsonValue;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.regex.Pattern;

public class JavaFxDownloads {
public static class SyntaxException extends Exception {
public SyntaxException(String message) {
super(message);
}

public SyntaxException(JsonParser.SyntaxError e) {
super("Json syntax error:\n" + e.getMessage());
}
}

public static class Os {
public final String name;
public final Arch[] archs;
private final Pattern regex;

Os(JsonObject obj) throws SyntaxException {
name = getChecked(obj, "name");
this.regex = Pattern.compile(getChecked(obj, "regex"));

ArrayList<Arch> archs = new ArrayList<>();
JsonValue archsVal = obj.get("archs");
if (!archsVal.isArray()) throw new SyntaxException("OS object field archs must be array.");
for (JsonValue arch : archsVal.asArray().elements) {
if (!arch.isObject()) throw new SyntaxException("OS object field archs must be array of architecture objects");
archs.add(new Arch(arch.asObject()));
}
this.archs = archs.toArray(new Arch[0]);
}

public boolean doesMatch(String os) {
return regex.matcher(os).matches();
}
}

public static class Arch {
public final String name;
public final URL url;
public final String sha256;
private final Pattern regex;

Arch(JsonObject obj) throws SyntaxException {
name = getChecked(obj, "name");
regex = Pattern.compile(getChecked(obj, "regex"));
sha256 = getChecked(obj, "sha256");
try {
url = new URL(getChecked(obj, "url"));
} catch (MalformedURLException e) {
throw new SyntaxException("Architecture object has invalid URL");
}
}

public boolean doesMatch(String arch) {
return regex.matcher(arch).matches();
}
}

private static String getChecked(JsonObject obj, String field) throws SyntaxException {
String o = obj.get(field).asString(null);
if (o == null) throw new SyntaxException("Missing field: " + field);
return o;
}

/**
* Parse a json object for the download list.
*/
public static Os[] parse(JsonArray objs) throws SyntaxException {
ArrayList<Os> out = new ArrayList<>();
for (JsonValue value : objs.elements) {
if (!value.isObject()) throw new SyntaxException("Expecting array of OS objects.");
out.add(new Os(value.asObject()));
}
return out.toArray(new Os[0]);
}

/**
* Parse an input stream for the download list.
*/
public static Os[] parse(InputStream is) throws SyntaxException, IOException {
try (JsonParser parser = new JsonParser(is)) {
JsonValue value = parser.parse();
if (!value.isArray()) throw new SyntaxException("Expecting array of OS objects.");
return parse(value.asArray());
} catch (JsonParser.SyntaxError e) {
throw new SyntaxException(e);
}
}

/**
* Fet the download list from a URL.
*/
public static Os[] fetch(URL url) throws SyntaxException, IOException {
// Follow redirects
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_MOVED_PERM ||
responseCode == HttpURLConnection.HTTP_MOVED_TEMP ||
responseCode == HttpURLConnection.HTTP_SEE_OTHER) {
return fetch(new URL(conn.getHeaderField("Location")));
}

try (InputStream is = url.openStream()) {
return parse(is);
}
}
}
Loading

0 comments on commit 1393227

Please sign in to comment.