Skip to content

Commit

Permalink
fix(footer): Use java.awt.Desktop as fallback for HostServices (#500)
Browse files Browse the repository at this point in the history
This introduces a wrapper around HostServices/Desktop, the `HostServicesWrapper`. This abstraction hides what technology is used to open links in the system browser.

Co-authored-by: jdrueckert <[email protected]>
  • Loading branch information
skaldarnar and jdrueckert authored Mar 14, 2020
1 parent e854102 commit e751116
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 31 deletions.
36 changes: 23 additions & 13 deletions src/main/java/org/terasology/launcher/TerasologyLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.terasology.launcher.gui.javafx.ApplicationController;
import org.terasology.launcher.log.TempLogFilePropertyDefiner;
import org.terasology.launcher.util.BundleUtils;
import org.terasology.launcher.util.HostServicesWrapper;
import org.terasology.launcher.util.Languages;
import org.terasology.launcher.util.LauncherStartFailedException;
import org.terasology.launcher.version.TerasologyLauncherVersionInfo;
Expand All @@ -68,12 +69,32 @@ public final class TerasologyLauncher extends Application {
private ProgressBar loadProgress;
private Label progressText;
private Stage mainStage;
private HostServices hostServices;
private HostServicesWrapper hostServices;

public static void main(String[] args) {
launch(args);
}

/**
* Initialize the host service wrapper by attempting to use the JavaFX {@link HostServices}.
* @return the configured host service wrapper
*/
private HostServicesWrapper initHostServices() {
HostServices hs;
try {
// This may throw an exception on a different thread, but we cannot catch it here o.O
// In addition, `hostServices` will be initialized, but disfunctional.
// Thus, we have no idea whether we can use the services or not...
hs = getHostServices();
// poor man's check: this will throw a NPE if the internal `hostServices.delegate` is not initialized
hs.getCodeBase();
} catch (NullPointerException e) {
logger.warn("Host Services not available - won't be able to open hyperlinks in the system browser.");
hs = null;
}
return new HostServicesWrapper(hs);
}

@Override
public void init() {
ImageView splash = new ImageView(BundleUtils.getFxImage("splash"));
Expand All @@ -85,18 +106,7 @@ public void init() {
progressText.setAlignment(Pos.CENTER);
splashLayout.getStylesheets().add(BundleUtils.getStylesheet("css_splash"));
splashLayout.setEffect(new DropShadow());

try {
// This may throw an exception on a different thread, but we cannot catch it here o.O
// In addition, `hostServices` will be initialized, but disfunctional.
// Thus, we have no idea whether we can use the services or not...
hostServices = getHostServices();
// poor man's check: this will throw a NPE if the internal `hostServices.deleagte` is not initialized
hostServices.getCodeBase();
} catch (NullPointerException e) {
logger.warn("Host Services not available - won't be able to open hyperlinks in the system browser.");
hostServices = null;
}
hostServices = initHostServices();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package org.terasology.launcher.gui.javafx;

import javafx.animation.Transition;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
Expand Down Expand Up @@ -62,6 +61,7 @@
import org.terasology.launcher.util.BundleUtils;
import org.terasology.launcher.util.DownloadException;
import org.terasology.launcher.util.GuiUtils;
import org.terasology.launcher.util.HostServicesWrapper;
import org.terasology.launcher.util.Languages;
import org.terasology.launcher.util.ProgressListener;

Expand Down Expand Up @@ -376,7 +376,7 @@ protected void deleteAction() {
}

public void update(final Path newLauncherDirectory, final Path newDownloadDirectory, final Path newTempDirectory, final BaseLauncherSettings newLauncherSettings,
final PackageManager newPackageManager, final Stage newStage, final HostServices hostServices) {
final PackageManager newPackageManager, final Stage newStage, final HostServicesWrapper hostServices) {
this.launcherDirectory = newLauncherDirectory;
this.downloadDirectory = newDownloadDirectory;
this.tempDirectory = newTempDirectory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package org.terasology.launcher.gui.javafx;

import javafx.animation.Transition;
import javafx.application.HostServices;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyProperty;
import javafx.beans.property.SimpleObjectProperty;
Expand All @@ -30,9 +29,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.launcher.util.BundleUtils;
import org.terasology.launcher.util.HostServicesWrapper;
import org.terasology.launcher.version.TerasologyLauncherVersionInfo;

import java.net.URI;
import java.util.Optional;

public class FooterController {
Expand All @@ -43,7 +42,7 @@ public class FooterController {
private Button warningButton;
@FXML
private Label versionInfo;
private HostServices hostServices;
private HostServicesWrapper hostServices;
private Property<Optional<Warning>> warningProperty;

public FooterController() {
Expand All @@ -59,7 +58,7 @@ private void updateLabels() {
}
}

public void setHostServices(HostServices hostServices) {
public void setHostServices(HostServicesWrapper hostServices) {
this.hostServices = hostServices;
}

Expand Down Expand Up @@ -99,32 +98,32 @@ protected void handleSocialButtonMouseReleased(MouseEvent event) {

@FXML
protected void openFacebook() {
openUri(BundleUtils.getURI("terasology_facebook"));
hostServices.tryOpenUri(BundleUtils.getURI("terasology_facebook"));
}

@FXML
protected void openGithub() {
openUri(BundleUtils.getURI("terasology_github"));
hostServices.tryOpenUri(BundleUtils.getURI("terasology_github"));
}

@FXML
protected void openDiscord() {
openUri(BundleUtils.getURI("terasology_discord"));
hostServices.tryOpenUri(BundleUtils.getURI("terasology_discord"));
}

@FXML
protected void openReddit() {
openUri(BundleUtils.getURI("terasology_reddit"));
hostServices.tryOpenUri(BundleUtils.getURI("terasology_reddit"));
}

@FXML
protected void openTwitter() {
openUri(BundleUtils.getURI("terasology_twitter"));
hostServices.tryOpenUri(BundleUtils.getURI("terasology_twitter"));
}

@FXML
protected void openYoutube() {
openUri(BundleUtils.getURI("terasology_youtube"));
hostServices.tryOpenUri(BundleUtils.getURI("terasology_youtube"));
}

@FXML
Expand All @@ -133,12 +132,6 @@ protected void openLogs() {
//contentTabPane.getSelectionModel().select(2);
}

private void openUri(URI uri) {
if (uri != null && hostServices != null) {
hostServices.showDocument(uri.toString());
}
}

private void updateWarningButton(Optional<Warning> warning) {
warningButton.setVisible(warning.isPresent());
warning.ifPresent(w -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.terasology.launcher.util;

import javafx.application.HostServices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Desktop;
import java.io.IOException;
import java.net.URI;

/**
* Provide capabilities to interact with the hosting system (OS).
* <p>
* For instance, this allows to open a URL in the default browser on the system.
* <p>
* This abstraction is necessary as {@link javafx.application.HostServices} may not be available and we want to use
* {@link java.awt.Desktop} as a fallback solution.
*/
public class HostServicesWrapper {

private static final Logger logger = LoggerFactory.getLogger(HostServicesWrapper.class);

private final Desktop desktop;
private final HostServices hostServices;

/**
* Create a new {@link javafx.application.HostServices} wrapper.
* <p>
* If the given host service instance is {@code null}
*
* @param hostServices a JavaFX host services instance or null
*/
public HostServicesWrapper(HostServices hostServices) {
this.hostServices = hostServices;

if (hostServices == null && Desktop.isDesktopSupported()) {
Desktop d = Desktop.getDesktop();
if (d.isSupported(Desktop.Action.BROWSE)) {
desktop = d;
} else {
desktop = null;
}
} else {
desktop = null;
}
}

/**
* Attempt to open the given URI with the default browser.
* <p>
* The preferred way to open the URI is via the {@link javafx.application.HostServices}, as a fallback
* {@link java.awt.Desktop} may be used internally.
* <p>
* In case the URI cannot be opened (e.g., because neither HostServices nor Desktop are available) this method will
* fail silently.
*
* @param uri the URI to open
*/
public void tryOpenUri(URI uri) {
if (hostServices != null) {
hostServices.showDocument(uri.toString());
} else if (desktop != null) {
try {
desktop.browse(uri);
} catch (IOException e) {
logger.warn("Unable to open URI with 'Browse' action", e);
}
}
}
}

0 comments on commit e751116

Please sign in to comment.