diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 3f0bf3ce..5acac7cd 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -1602,6 +1602,29 @@ public class AppController implements Initializable { tabLabel.getGraphic().getStyleClass().remove("failure"); } + private void setTorIcon() { + TorStatusLabel torStatusLabel = null; + for(Node node : statusBar.getRightItems()) { + if(node instanceof TorStatusLabel) { + torStatusLabel = (TorStatusLabel)node; + } + } + + if(!AppServices.isUsingProxy()) { + if(torStatusLabel != null) { + torStatusLabel.update(); + statusBar.getRightItems().removeAll(torStatusLabel); + } + } else { + if(torStatusLabel == null) { + torStatusLabel = new TorStatusLabel(); + statusBar.getRightItems().add(Math.max(statusBar.getRightItems().size() - 2, 0), torStatusLabel); + } else { + torStatusLabel.update(); + } + } + } + @Subscribe public void themeChanged(ThemeChangedEvent event) { String darkCss = getClass().getResource("darktheme.css").toExternalForm(); @@ -1859,6 +1882,7 @@ public class AppController implements Initializable { String status = CONNECTION_FAILED_PREFIX + event.getMessage(); statusUpdated(new StatusEvent(status)); serverToggleStopAnimation(); + setTorIcon(); } @Subscribe @@ -1867,6 +1891,7 @@ public class AppController implements Initializable { statusUpdated(new StatusEvent(status)); setServerToggleTooltip(event.getBlockHeight()); serverToggleStopAnimation(); + setTorIcon(); } @Subscribe @@ -1993,18 +2018,21 @@ public class AppController implements Initializable { public void torBootStatus(TorBootStatusEvent event) { serverToggle.setDisable(true); statusUpdated(new StatusEvent(event.getStatus(), 120)); + setTorIcon(); } @Subscribe public void torFailedStatus(TorFailedStatusEvent event) { serverToggle.setDisable(false); statusUpdated(new StatusEvent(event.getStatus())); + setTorIcon(); } @Subscribe public void torReadyStatus(TorReadyStatusEvent event) { serverToggle.setDisable(false); statusUpdated(new StatusEvent(event.getStatus())); + setTorIcon(); } @Subscribe diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index afa3471c..f3765802 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -119,6 +119,11 @@ public class AppServices { public void changed(ObservableValue observable, Boolean oldValue, Boolean online) { if(online) { if(Config.get().requiresInternalTor() && !isTorRunning()) { + if(torService.getState() == Worker.State.SCHEDULED) { + torService.cancel(); + torService.reset(); + } + torService.start(); } else { restartServices(); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TorStatusLabel.java b/src/main/java/com/sparrowwallet/sparrow/control/TorStatusLabel.java new file mode 100644 index 00000000..4511b6f0 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/TorStatusLabel.java @@ -0,0 +1,88 @@ +package com.sparrowwallet.sparrow.control; + +import com.google.common.net.HostAndPort; +import com.sparrowwallet.sparrow.AppServices; +import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; +import com.sparrowwallet.sparrow.io.Config; +import javafx.concurrent.ScheduledService; +import javafx.concurrent.Task; +import javafx.geometry.Insets; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.util.Duration; +import org.controlsfx.glyphfont.Glyph; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.Socket; + +public class TorStatusLabel extends Label { + private static final Logger log = LoggerFactory.getLogger(TorStatusLabel.class); + + private final TorConnectionTest torConnectionTest = new TorConnectionTest(); + + public TorStatusLabel() { + getStyleClass().add("tor-status"); + setPadding(new Insets(1, 0, 0, 3)); + setGraphic(getIcon()); + update(); + } + + public void update() { + boolean proxyInUse = AppServices.isUsingProxy(); + boolean internal = AppServices.isTorRunning(); + + if(!proxyInUse || internal) { + torConnectionTest.cancel(); + if(internal) { + setTooltip(new Tooltip("Internal Tor proxy enabled")); + } + } else if(!torConnectionTest.isRunning()) { + torConnectionTest.setPeriod(Duration.seconds(20.0)); + torConnectionTest.setOnSucceeded(workerStateEvent -> { + getStyleClass().remove("failure"); + setTooltip(new Tooltip("External Tor proxy enabled")); + }); + torConnectionTest.setOnFailed(workerStateEvent -> { + if(!getStyleClass().contains("failure")) { + getStyleClass().add("failure"); + } + setTooltip(new Tooltip("External Tor proxy error: " + workerStateEvent.getSource().getException().getMessage())); + log.warn("Failed to connect to external Tor proxy: " + workerStateEvent.getSource().getException().getMessage()); + }); + torConnectionTest.start(); + } + } + + private Node getIcon() { + Glyph adjust = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ADJUST); + adjust.setFontSize(15); + adjust.setRotate(180); + + Glyph bullseye = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.BULLSEYE); + bullseye.setFontSize(14); + + Group group = new Group(); + group.getChildren().addAll(adjust, bullseye); + + return group; + } + + private static class TorConnectionTest extends ScheduledService { + @Override + protected Task createTask() { + return new Task<>() { + protected Void call() throws IOException { + HostAndPort proxyHostAndPort = HostAndPort.fromString(Config.get().getProxyServer()); + Socket socket = new Socket(proxyHostAndPort.getHost(), proxyHostAndPort.getPort()); + socket.close(); + + return null; + } + }; + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java index 9be28b1c..af17d16b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java +++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java @@ -15,6 +15,7 @@ public class FontAwesome5 extends GlyphFont { * The individual glyphs offered by the FontAwesome5 font. */ public static enum Glyph implements INamedCharacter { + ADJUST('\uf042'), ARROW_CIRCLE_DOWN('\uf0ab'), ANGLE_DOUBLE_RIGHT('\uf101'), ARROW_DOWN('\uf063'), @@ -22,6 +23,7 @@ public class FontAwesome5 extends GlyphFont { BAN('\uf05e'), BIOHAZARD('\uf780'), BTC('\uf15a'), + BULLSEYE('\uf140'), CAMERA('\uf030'), CHECK_CIRCLE('\uf058'), CIRCLE('\uf111'), diff --git a/src/main/resources/com/sparrowwallet/sparrow/app.css b/src/main/resources/com/sparrowwallet/sparrow/app.css index 8c0f42c2..0ddd025a 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/app.css +++ b/src/main/resources/com/sparrowwallet/sparrow/app.css @@ -24,6 +24,10 @@ -fx-text-fill: rgb(202, 18, 67); } +.tor-status.failure .glyph-font { + -fx-text-fill: rgb(202, 18, 67); +} + .master-only { -fx-tab-max-height: 0; } diff --git a/src/main/resources/com/sparrowwallet/sparrow/darktheme.css b/src/main/resources/com/sparrowwallet/sparrow/darktheme.css index 4e89dd08..5fc0e1c3 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/darktheme.css +++ b/src/main/resources/com/sparrowwallet/sparrow/darktheme.css @@ -87,6 +87,10 @@ -fx-fill: #e06c75; } +.status-bar .tor-status.failure .glyph-font { + -fx-text-fill: #e06c75; +} + .root .titled-description-pane .status-error .text, .root .titled-description-pane .description-error .text { -fx-fill: #e06c75; }