Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ dependencies {
// instead of rendering. 0.64.8 has the fix - see vsch/flexmark-java@0.64.6..0.64.8.
implementation("com.vladsch.flexmark:flexmark-all:0.64.8")

implementation("org.jsoup:jsoup:1.15.4") {
because("AboutViewController strips HTML tags for the plain-text fallback on platforms "
+ "without a working javafx.web. Already on the classpath transitively via "
+ "flexmark-all's html2md converter - pinned to that same version so the resolved "
+ "graph is unchanged, but declared because we now use it directly.")
}

implementation("org.hildan.fxgson:fx-gson:5.0.0") {
because("de-/serialization of launcher properties to JSON")
}
Expand Down
8 changes: 7 additions & 1 deletion buildres/scripts/windowsStartScript.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ goto fail
:execute
@rem Setup the command line

set CLASSPATH=$classpath
@rem Use a wildcard instead of Gradle's $classpath (every jar listed individually with its full
@rem absolute path) - with this many dependencies that line easily exceeds cmd.exe's 8191-character
@rem line length limit once APP_HOME is substituted in, especially on the "windows-arm64" build,
@rem which - unlike Windows64 - has no launch4j .exe wrapper (that one already used lib/* via
@rem `classpath.set(listOf("lib/*"))` in build.gradle.kts) and so relies entirely on this template.
@rem See: launcher crashes with "The input line is too long." on startup.
set CLASSPATH=%APP_HOME%/lib/*
<% if ( mainClassName.startsWith('--module ') ) { %>set MODULE_PATH=$modulePath<% } %>

@rem Execute ${applicationName}
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/org/terasology/launcher/platform/Platform.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public enum Platform {

WINDOWS_X64(OS.WINDOWS, Arch.X64),
WINDOWS_ARM64(OS.WINDOWS, Arch.ARM64),
LINUX_X64(OS.LINUX, Arch.X64),
MACOS_X64(OS.MAC, Arch.X64),
MACOS_ARM64(OS.MAC, Arch.ARM64);
Expand Down Expand Up @@ -41,6 +42,38 @@ public boolean isWindows() {
return os == OS.WINDOWS;
}

/**
* Whether OpenJFX ships a working WebKit native library ({@code javafx.web}, backing
* {@link javafx.scene.web.WebView}) for this platform.
* <p>
* {@code javafx.application.Platform.isSupported(ConditionalFeature.WEB)} can't be trusted for
* this: it only reports whether the javafx.web module was compiled in, not whether its native
* library actually works on this OS/arch. Vendor JREs for platforms upstream OpenJFX doesn't
* support - like Windows/aarch64, see https://bugs.openjdk.org/browse/JDK-8314064, whose PR
* explicitly excludes javafx.web - still bundle a javafx.web module (for API compatibility)
* that reports itself as supported and then crashes with an UnsatisfiedLinkError the moment a
* WebView is constructed.
*/
public boolean supportsWebView() {
return this != WINDOWS_ARM64;
}

/**
* {@link #supportsWebView()} for the platform this JVM is currently running on.
* <p>
* Falls back to {@code false} when the platform can't be identified at all: the plain-text view
* renders everywhere, so an unrecognized platform is better served by it than by a WebView that
* may not load. In practice this is unreachable - startup (see {@code LauncherInitTask}) already
* resolves the platform before any view is built.
*/
public static boolean currentSupportsWebView() {
try {
return getPlatform().supportsWebView();
} catch (UnsupportedPlatformException e) {
return false;
}
}

@Override
public String toString() {
return "OS '" + os + "', arch '" + arch + "'";
Expand Down Expand Up @@ -83,6 +116,8 @@ public static Platform getPlatform() throws UnsupportedPlatformException {
public static Platform fromOsAndArch(OS os, Arch arch) throws UnsupportedPlatformException {
if (os.equals(OS.WINDOWS) && arch.equals(Arch.X64)) {
return WINDOWS_X64;
} else if (os.equals(OS.WINDOWS) && arch.equals(Arch.ARM64)) {
return WINDOWS_ARM64;
} else if (os.equals(OS.LINUX) && arch.equals(Arch.X64)) {
return LINUX_X64;
} else if (os.equals(OS.MAC) && arch.equals(Arch.X64)) {
Expand Down
86 changes: 70 additions & 16 deletions src/main/java/org/terasology/launcher/ui/AboutViewController.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
import com.vladsch.flexmark.util.data.MutableDataSet;
import javafx.fxml.FXML;
import javafx.scene.control.Accordion;
import javafx.scene.control.TextArea;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.AnchorPane;
import javafx.scene.web.WebView;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.launcher.platform.Platform;
import org.terasology.launcher.util.I18N;
import org.terasology.launcher.util.UnicodeEmojiImages;

Expand All @@ -38,6 +41,12 @@
* Controller for the <b>About</b> section in the tab view.
* <p>
* Presents static content which is compiled from Markdown and HTML documents.
* <p>
* Rendered as HTML in a {@code WebView} where {@link org.terasology.launcher.platform.Platform#supportsWebView()}
* says it's available; on platforms without a working WebKit native library (notably
* Windows/aarch64 - see https://bugs.openjdk.org/browse/JDK-8314064, whose javafx.web is still
* unimplemented there) this falls back to a plain-text view instead (HTML documents have their
* tags stripped via Jsoup so they're still readable).
*/
public class AboutViewController {

Expand All @@ -50,6 +59,8 @@ public class AboutViewController {

private static final Charset UTF_8 = Charset.forName("UTF-8");

private static final boolean WEB_SUPPORTED = Platform.currentSupportsWebView();

@FXML
private Accordion aboutInfoAccordion;

Expand Down Expand Up @@ -99,15 +110,12 @@ public void update() {

private Optional<TitledPane> createPaneFor(URL url) {
return createViewFor(url)
.map(view -> {
view.getStylesheets().add(I18N.getFXMLUrl("css_webview").toExternalForm());
view.setContextMenuEnabled(false);
return view;
})
.map(view -> {
final AnchorPane pane = new AnchorPane();
AnchorPane.setBottomAnchor(view, 0.0);
AnchorPane.setTopAnchor(view, 0.0);
AnchorPane.setLeftAnchor(view, 0.0);
AnchorPane.setRightAnchor(view, 0.0);
pane.getChildren().add(view);
return pane;
})
Expand All @@ -119,15 +127,24 @@ private Optional<TitledPane> createPaneFor(URL url) {
});
}

private Optional<WebView> createViewFor(URL url) {
return switch (Files.getFileExtension(url.getFile().toLowerCase(Locale.ROOT))) {
case "md", "markdown" -> renderMarkdown(url);
case "htm", "html" -> renderHtml(url);
default -> renderUnknown(url);
private Optional<javafx.scene.Node> createViewFor(URL url) {
String extension = Files.getFileExtension(url.getFile().toLowerCase(Locale.ROOT));
if (WEB_SUPPORTED) {
return switch (extension) {
case "md", "markdown" -> renderMarkdown(url);
case "htm", "html" -> renderHtml(url);
default -> renderUnknown(url);
};
}
return switch (extension) {
case "htm", "html" -> renderHtmlAsText(url);
default -> renderPlainText(url);
};
}

private Optional<WebView> renderMarkdown(URL url) {
// -- WebView-based rendering (default, where javafx.web is supported) --

private Optional<javafx.scene.Node> renderMarkdown(URL url) {
WebView view = null;
try (InputStream input = url.openStream()) {
view = new WebView();
Expand All @@ -141,16 +158,16 @@ private Optional<WebView> renderMarkdown(URL url) {
} catch (IOException e) {
logger.warn("Could not render markdown file: {}", url);
}
return Optional.ofNullable(view);
return finishWebView(view);
}

private Optional<WebView> renderHtml(URL url) {
private Optional<javafx.scene.Node> renderHtml(URL url) {
final WebView view = new WebView();
view.getEngine().load(url.toExternalForm());
return Optional.of(view);
return finishWebView(view);
}

private Optional<WebView> renderUnknown(URL url) {
private Optional<javafx.scene.Node> renderUnknown(URL url) {
WebView view = null;
try (Reader isr = new InputStreamReader(url.openStream(), UTF_8);
BufferedReader br = new BufferedReader(isr)) {
Expand All @@ -168,6 +185,43 @@ private Optional<WebView> renderUnknown(URL url) {
} catch (IOException e) {
logger.warn("Could not render file: {}", url);
}
return Optional.ofNullable(view);
return finishWebView(view);
}

private Optional<javafx.scene.Node> finishWebView(WebView view) {
if (view == null) {
return Optional.empty();
}
view.getStylesheets().add(I18N.getFXMLUrl("css_webview").toExternalForm());
view.setContextMenuEnabled(false);
return Optional.of(view);
}

// -- Plain-text fallback (platforms without javafx.web, e.g. Windows/aarch64) --

private Optional<javafx.scene.Node> renderHtmlAsText(URL url) {
try (InputStream input = url.openStream()) {
String html = new String(input.readAllBytes(), UTF_8);
return Optional.of(makeTextArea(Jsoup.parse(html).text()));
} catch (IOException e) {
logger.warn("Could not render file: {}", url);
return Optional.empty();
}
}

private Optional<javafx.scene.Node> renderPlainText(URL url) {
try (InputStream input = url.openStream()) {
return Optional.of(makeTextArea(new String(input.readAllBytes(), UTF_8)));
} catch (IOException e) {
logger.warn("Could not render file: {}", url);
return Optional.empty();
}
}

private TextArea makeTextArea(String content) {
final TextArea textArea = new TextArea(content);
textArea.setEditable(false);
textArea.setWrapText(true);
return textArea;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,25 @@
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.MutableDataSet;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.effect.BlendMode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.web.WebView;
import org.terasology.launcher.platform.Platform;
import org.terasology.launcher.util.I18N;
import org.terasology.launcher.util.UnicodeEmojiImages;

import java.util.Arrays;

public class ChangelogViewController {

private static final double PREF_HEIGHT = 336.0;

@FXML
private WebView changelogView;
private AnchorPane changelogPane;

private WebView webView;
private TextArea textArea;

private HtmlRenderer renderer;
private Parser parser;
Expand All @@ -41,15 +49,47 @@ public ChangelogViewController() {
renderer = HtmlRenderer.builder(options).build();
}

/**
* Builds either a {@code WebView} (to render the changelog as HTML) or, on platforms where
* OpenJFX doesn't ship a WebKit native library (notably Windows/aarch64 - see
* https://bugs.openjdk.org/browse/JDK-8314064, whose javafx.web is still unimplemented there),
* a plain-text fallback. Constructing a {@code WebView} at all triggers WebKit's native library
* load, so this can't be a static FXML node - it has to be conditional.
*/
@FXML
public void initialize() {
final javafx.scene.Node view;
if (Platform.currentSupportsWebView()) {
webView = new WebView();
webView.setPrefHeight(PREF_HEIGHT);
view = webView;
} else {
textArea = new TextArea();
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setPrefHeight(PREF_HEIGHT);
view = textArea;
}
AnchorPane.setTopAnchor(view, 8.0);
AnchorPane.setBottomAnchor(view, 8.0);
AnchorPane.setLeftAnchor(view, 8.0);
AnchorPane.setRightAnchor(view, 8.0);
changelogPane.getChildren().add(view);
}

/**
* Update the displayed changelog based on the selected package.
*
* @param changes list of changes
*/
void update(final String changes) {
changelogView.getEngine().loadContent(makeHtml(changes));
changelogView.setBlendMode(BlendMode.LIGHTEN);
changelogView.getEngine().setUserStyleSheetLocation(I18N.getFXMLUrl("css_webview").toExternalForm());
if (webView != null) {
webView.getEngine().loadContent(makeHtml(changes));
webView.setBlendMode(BlendMode.LIGHTEN);
webView.getEngine().setUserStyleSheetLocation(I18N.getFXMLUrl("css_webview").toExternalForm());
} else {
textArea.setText(changes);
}
}

private String makeHtml(final String changes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@

<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.web.WebView?>
<AnchorPane fx:controller="org.terasology.launcher.ui.ChangelogViewController"
<AnchorPane fx:id="changelogPane" fx:controller="org.terasology.launcher.ui.ChangelogViewController"
xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
prefHeight="180.0" prefWidth="200.0">
<children>
<WebView fx:id="changelogView" prefHeight="336.0" AnchorPane.bottomAnchor="8.0" AnchorPane.leftAnchor="8.0"
AnchorPane.rightAnchor="8.0" AnchorPane.topAnchor="8.0"/>
</children>
<padding>
<Insets top="8.0" bottom="8.0"/>
</padding>
Expand Down
Loading