mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
display address on device, device support improvements
This commit is contained in:
parent
8aed206a34
commit
84d08fea18
18 changed files with 530 additions and 150 deletions
|
@ -113,6 +113,8 @@ public class AppController implements Initializable {
|
|||
|
||||
private ElectrumServer.ConnectionService connectionService;
|
||||
|
||||
private Hwi.ScheduledEnumerateService deviceEnumerateService;
|
||||
|
||||
private static Integer currentBlockHeight;
|
||||
|
||||
public static boolean showTxHexProperty;
|
||||
|
@ -291,6 +293,25 @@ public class AppController implements Initializable {
|
|||
return ratesService;
|
||||
}
|
||||
|
||||
private Hwi.ScheduledEnumerateService createDeviceEnumerateService() {
|
||||
Hwi.ScheduledEnumerateService enumerateService = new Hwi.ScheduledEnumerateService(null);
|
||||
enumerateService.setPeriod(new Duration(ENUMERATE_HW_PERIOD));
|
||||
enumerateService.setOnSucceeded(workerStateEvent -> {
|
||||
List<Device> devices = enumerateService.getValue();
|
||||
|
||||
//Null devices are returned if the app is currently prompting for a pin. Otherwise, the enumerate clears the pin screen
|
||||
if(devices != null) {
|
||||
//If another instance of HWI is currently accessing the usb interface, HWI returns empty device models. Ignore this run if that happens
|
||||
List<Device> validDevices = devices.stream().filter(device -> device.getModel() != null).collect(Collectors.toList());
|
||||
if(validDevices.size() == devices.size()) {
|
||||
Platform.runLater(() -> EventManager.get().post(new UsbDeviceEvent(devices)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return enumerateService;
|
||||
}
|
||||
|
||||
public void setApplication(MainApp application) {
|
||||
this.application = application;
|
||||
}
|
||||
|
@ -509,7 +530,7 @@ public class AppController implements Initializable {
|
|||
}
|
||||
|
||||
public static List<Device> getDevices() {
|
||||
return devices;
|
||||
return devices == null ? new ArrayList<>() : devices;
|
||||
}
|
||||
|
||||
public Map<Wallet, Storage> getOpenWallets() {
|
||||
|
@ -739,24 +760,6 @@ public class AppController implements Initializable {
|
|||
EventManager.get().register(walletForm);
|
||||
controller.setWalletForm(walletForm);
|
||||
|
||||
if(!storage.getWalletFile().exists() || wallet.containsSource(KeystoreSource.HW_USB)) {
|
||||
Hwi.ScheduledEnumerateService enumerateService = new Hwi.ScheduledEnumerateService(null);
|
||||
enumerateService.setPeriod(new Duration(ENUMERATE_HW_PERIOD));
|
||||
enumerateService.setOnSucceeded(workerStateEvent -> {
|
||||
List<Device> devices = enumerateService.getValue();
|
||||
|
||||
//Null devices are returned if the app is currently prompting for a pin. Otherwise, the enumerate clears the pin screen
|
||||
if(devices != null) {
|
||||
//If another instance of HWI is currently accessing the usb interface, HWI returns empty device models. Ignore this run if that happens
|
||||
List<Device> validDevices = devices.stream().filter(device -> device.getModel() != null).collect(Collectors.toList());
|
||||
if(validDevices.size() == devices.size()) {
|
||||
EventManager.get().post(new UsbDeviceEvent(devices));
|
||||
}
|
||||
}
|
||||
});
|
||||
enumerateService.start();
|
||||
}
|
||||
|
||||
tabs.getTabs().add(tab);
|
||||
return tab;
|
||||
} catch(IOException e) {
|
||||
|
@ -1080,6 +1083,36 @@ public class AppController implements Initializable {
|
|||
fiatCurrencyExchangeRate = event.getCurrencyRate();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void openWallets(OpenWalletsEvent event) {
|
||||
boolean usbWallet = false;
|
||||
for(Map.Entry<Wallet, Storage> entry : event.getWalletsMap().entrySet()) {
|
||||
Wallet wallet = entry.getKey();
|
||||
Storage storage = entry.getValue();
|
||||
|
||||
if(!storage.getWalletFile().exists() || wallet.containsSource(KeystoreSource.HW_USB)) {
|
||||
usbWallet = true;
|
||||
|
||||
if(deviceEnumerateService == null) {
|
||||
deviceEnumerateService = createDeviceEnumerateService();
|
||||
}
|
||||
|
||||
if(deviceEnumerateService.getState() == Worker.State.CANCELLED) {
|
||||
deviceEnumerateService.reset();
|
||||
}
|
||||
|
||||
if(!deviceEnumerateService.isRunning()) {
|
||||
deviceEnumerateService.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!usbWallet && deviceEnumerateService != null && deviceEnumerateService.isRunning()) {
|
||||
deviceEnumerateService.cancel();
|
||||
EventManager.get().post(new UsbDeviceEvent(Collections.emptyList()));
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void requestOpenWallets(RequestOpenWalletsEvent event) {
|
||||
EventManager.get().post(new OpenWalletsEvent(getOpenWallets()));
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.AddressDisplayedEvent;
|
||||
import com.sparrowwallet.sparrow.io.Device;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DeviceAddressDialog extends DeviceDialog<String> {
|
||||
private final Wallet wallet;
|
||||
private final KeyDerivation keyDerivation;
|
||||
|
||||
public DeviceAddressDialog(List<Device> devices, Wallet wallet, KeyDerivation keyDerivation) {
|
||||
super(devices);
|
||||
this.wallet = wallet;
|
||||
this.keyDerivation = keyDerivation;
|
||||
|
||||
EventManager.get().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DevicePane getDevicePane(Device device) {
|
||||
return new DevicePane(wallet, keyDerivation, device);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void addressDisplayed(AddressDisplayedEvent event) {
|
||||
EventManager.get().unregister(this);
|
||||
setResult(event.getAddress());
|
||||
this.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.sparrow.AppController;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.UsbDeviceEvent;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
||||
import com.sparrowwallet.sparrow.io.Device;
|
||||
import com.sparrowwallet.sparrow.io.Hwi;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class DeviceDialog<R> extends Dialog<R> {
|
||||
private final List<Device> operationDevices;
|
||||
private final Accordion deviceAccordion;
|
||||
private final VBox scanBox;
|
||||
private final Label scanLabel;
|
||||
|
||||
public DeviceDialog() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public DeviceDialog(List<Device> operationDevices) {
|
||||
this.operationDevices = operationDevices;
|
||||
|
||||
final DialogPane dialogPane = getDialogPane();
|
||||
dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm());
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
dialogPane.setContent(stackPane);
|
||||
|
||||
AnchorPane anchorPane = new AnchorPane();
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setPrefHeight(280);
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
anchorPane.getChildren().add(scrollPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
AnchorPane.setLeftAnchor(scrollPane, 0.0);
|
||||
AnchorPane.setRightAnchor(scrollPane, 0.0);
|
||||
|
||||
deviceAccordion = new Accordion();
|
||||
scrollPane.setContent(deviceAccordion);
|
||||
|
||||
scanBox = new VBox();
|
||||
scanBox.setSpacing(30);
|
||||
scanBox.setAlignment(Pos.CENTER);
|
||||
Glyph usb = new Glyph(FontAwesome5Brands.FONT_NAME, FontAwesome5Brands.Glyph.USB);
|
||||
usb.setFontSize(50);
|
||||
scanLabel = new Label("Connect Hardware Wallet");
|
||||
Button button = new Button("Scan...");
|
||||
button.setPrefSize(120, 60);
|
||||
button.setOnAction(event -> {
|
||||
scan();
|
||||
});
|
||||
scanBox.getChildren().addAll(usb, scanLabel, button);
|
||||
scanBox.managedProperty().bind(scanBox.visibleProperty());
|
||||
|
||||
stackPane.getChildren().addAll(anchorPane, scanBox);
|
||||
|
||||
List<Device> devices = AppController.getDevices();
|
||||
if(devices == null || devices.isEmpty()) {
|
||||
scanBox.setVisible(true);
|
||||
} else {
|
||||
Platform.runLater(() -> setDevices(devices));
|
||||
scanBox.setVisible(false);
|
||||
}
|
||||
|
||||
final ButtonType rescanButtonType = new javafx.scene.control.ButtonType("Rescan", ButtonBar.ButtonData.NO);
|
||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
dialogPane.getButtonTypes().addAll(rescanButtonType, cancelButtonType);
|
||||
|
||||
Button rescanButton = (Button) dialogPane.lookupButton(rescanButtonType);
|
||||
rescanButton.managedProperty().bind(rescanButton.visibleProperty());
|
||||
rescanButton.visibleProperty().bind(scanBox.visibleProperty().not());
|
||||
rescanButton.addEventFilter(ActionEvent.ACTION, event -> {
|
||||
scan();
|
||||
event.consume();
|
||||
});
|
||||
|
||||
dialogPane.setPrefWidth(500);
|
||||
dialogPane.setPrefHeight(360);
|
||||
}
|
||||
|
||||
private void scan() {
|
||||
Hwi.EnumerateService enumerateService = new Hwi.EnumerateService(null);
|
||||
enumerateService.setOnSucceeded(workerStateEvent -> {
|
||||
List<Device> devices = enumerateService.getValue();
|
||||
setDevices(devices);
|
||||
Platform.runLater(() -> EventManager.get().post(new UsbDeviceEvent(devices)));
|
||||
});
|
||||
enumerateService.setOnFailed(workerStateEvent -> {
|
||||
scanBox.setVisible(true);
|
||||
scanLabel.setText(workerStateEvent.getSource().getException().getMessage());
|
||||
});
|
||||
enumerateService.start();
|
||||
}
|
||||
|
||||
protected void setDevices(List<Device> devices) {
|
||||
List<Device> dialogDevices = devices;
|
||||
if(operationDevices != null && dialogDevices.containsAll(operationDevices)) {
|
||||
dialogDevices = operationDevices;
|
||||
}
|
||||
|
||||
deviceAccordion.getPanes().clear();
|
||||
|
||||
if(dialogDevices.isEmpty()) {
|
||||
scanBox.setVisible(true);
|
||||
scanLabel.setText("No devices found");
|
||||
} else {
|
||||
scanBox.setVisible(false);
|
||||
for(Device device : dialogDevices) {
|
||||
DevicePane devicePane = getDevicePane(device);
|
||||
deviceAccordion.getPanes().add(devicePane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract DevicePane getDevicePane(Device device);
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.sparrowwallet.drongo.wallet.Keystore;
|
|||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.AddressDisplayedEvent;
|
||||
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||
import com.sparrowwallet.sparrow.event.PSBTSignedEvent;
|
||||
import com.sparrowwallet.sparrow.io.Device;
|
||||
|
@ -35,6 +36,7 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
private final DeviceOperation deviceOperation;
|
||||
private final Wallet wallet;
|
||||
private final PSBT psbt;
|
||||
private final KeyDerivation keyDerivation;
|
||||
private final Device device;
|
||||
|
||||
private CustomPasswordField pinField;
|
||||
|
@ -43,14 +45,16 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
private Button setPassphraseButton;
|
||||
private SplitMenuButton importButton;
|
||||
private Button signButton;
|
||||
private Button displayAddressButton;
|
||||
|
||||
private final SimpleStringProperty passphrase = new SimpleStringProperty("");
|
||||
|
||||
public DevicePane(DeviceOperation deviceOperation, Wallet wallet, Device device) {
|
||||
public DevicePane(Wallet wallet, Device device) {
|
||||
super(device.getModel().toDisplayString(), "", "", "image/" + device.getType() + ".png");
|
||||
this.deviceOperation = deviceOperation;
|
||||
this.deviceOperation = DeviceOperation.IMPORT;
|
||||
this.wallet = wallet;
|
||||
this.psbt = null;
|
||||
this.keyDerivation = null;
|
||||
this.device = device;
|
||||
|
||||
setDefaultStatus();
|
||||
|
@ -70,11 +74,12 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
buttonBox.getChildren().addAll(setPassphraseButton, importButton);
|
||||
}
|
||||
|
||||
public DevicePane(DeviceOperation deviceOperation, PSBT psbt, Device device) {
|
||||
public DevicePane(PSBT psbt, Device device) {
|
||||
super(device.getModel().toDisplayString(), "", "", "image/" + device.getType() + ".png");
|
||||
this.deviceOperation = deviceOperation;
|
||||
this.deviceOperation = DeviceOperation.SIGN;
|
||||
this.wallet = null;
|
||||
this.psbt = psbt;
|
||||
this.keyDerivation = null;
|
||||
this.device = device;
|
||||
|
||||
setDefaultStatus();
|
||||
|
@ -94,6 +99,31 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
buttonBox.getChildren().addAll(setPassphraseButton, signButton);
|
||||
}
|
||||
|
||||
public DevicePane(Wallet wallet, KeyDerivation keyDerivation, Device device) {
|
||||
super(device.getModel().toDisplayString(), "", "", "image/" + device.getType() + ".png");
|
||||
this.deviceOperation = DeviceOperation.DISPLAY_ADDRESS;
|
||||
this.wallet = wallet;
|
||||
this.psbt = null;
|
||||
this.keyDerivation = keyDerivation;
|
||||
this.device = device;
|
||||
|
||||
setDefaultStatus();
|
||||
showHideLink.setVisible(false);
|
||||
|
||||
createSetPassphraseButton();
|
||||
createDisplayAddressButton();
|
||||
|
||||
if (device.getNeedsPinSent() != null && device.getNeedsPinSent()) {
|
||||
unlockButton.setVisible(true);
|
||||
} else if(device.getNeedsPassphraseSent() != null && device.getNeedsPassphraseSent()) {
|
||||
setPassphraseButton.setVisible(true);
|
||||
} else {
|
||||
showOperationButton();
|
||||
}
|
||||
|
||||
buttonBox.getChildren().addAll(setPassphraseButton, displayAddressButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Control createButton() {
|
||||
createUnlockButton();
|
||||
|
@ -153,6 +183,7 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
signButton = new Button("Sign");
|
||||
signButton.setDefaultButton(true);
|
||||
signButton.setAlignment(Pos.CENTER_RIGHT);
|
||||
signButton.setMinWidth(44);
|
||||
signButton.setOnAction(event -> {
|
||||
signButton.setDisable(true);
|
||||
sign();
|
||||
|
@ -161,6 +192,22 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
signButton.setVisible(false);
|
||||
}
|
||||
|
||||
private void createDisplayAddressButton() {
|
||||
displayAddressButton = new Button("Display Address");
|
||||
displayAddressButton.setDefaultButton(true);
|
||||
displayAddressButton.setAlignment(Pos.CENTER_RIGHT);
|
||||
displayAddressButton.setOnAction(event -> {
|
||||
displayAddressButton.setDisable(true);
|
||||
displayAddress();
|
||||
});
|
||||
displayAddressButton.managedProperty().bind(displayAddressButton.visibleProperty());
|
||||
displayAddressButton.setVisible(false);
|
||||
|
||||
if(device.getFingerprint() != null && !device.getFingerprint().equals(keyDerivation.getMasterFingerprint())) {
|
||||
displayAddressButton.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void unlock(Device device) {
|
||||
if(device.getModel().equals(WalletModel.TREZOR_1)) {
|
||||
promptPin();
|
||||
|
@ -375,6 +422,20 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
signPSBTService.start();
|
||||
}
|
||||
|
||||
private void displayAddress() {
|
||||
Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(device, passphrase.get(), wallet.getScriptType(), keyDerivation.getDerivationPath());
|
||||
displayAddressService.setOnSucceeded(successEvent -> {
|
||||
String address = displayAddressService.getValue();
|
||||
EventManager.get().post(new AddressDisplayedEvent(address));
|
||||
});
|
||||
displayAddressService.setOnFailed(failedEvent -> {
|
||||
setError(displayAddressService.getException().getMessage(), null);
|
||||
displayAddressButton.setDisable(false);
|
||||
});
|
||||
setDescription("Check device for address");
|
||||
displayAddressService.start();
|
||||
}
|
||||
|
||||
private void showOperationButton() {
|
||||
if(deviceOperation.equals(DeviceOperation.IMPORT)) {
|
||||
importButton.setVisible(true);
|
||||
|
@ -384,6 +445,9 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
} else if(deviceOperation.equals(DeviceOperation.SIGN)) {
|
||||
signButton.setVisible(true);
|
||||
showHideLink.setVisible(false);
|
||||
} else if(deviceOperation.equals(DeviceOperation.DISPLAY_ADDRESS)) {
|
||||
displayAddressButton.setVisible(true);
|
||||
showHideLink.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,6 +488,6 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
}
|
||||
|
||||
public enum DeviceOperation {
|
||||
IMPORT, SIGN;
|
||||
IMPORT, SIGN, DISPLAY_ADDRESS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,124 +2,31 @@ package com.sparrowwallet.sparrow.control;
|
|||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||
import com.sparrowwallet.sparrow.AppController;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.PSBTSignedEvent;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
||||
import com.sparrowwallet.sparrow.io.Device;
|
||||
import com.sparrowwallet.sparrow.io.Hwi;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DeviceSignDialog extends Dialog<PSBT> {
|
||||
public class DeviceSignDialog extends DeviceDialog<PSBT> {
|
||||
private final PSBT psbt;
|
||||
private final Accordion deviceAccordion;
|
||||
private final VBox scanBox;
|
||||
private final Label scanLabel;
|
||||
|
||||
public DeviceSignDialog(PSBT psbt) {
|
||||
public DeviceSignDialog(List<Device> devices, PSBT psbt) {
|
||||
super(devices);
|
||||
this.psbt = psbt;
|
||||
|
||||
EventManager.get().register(this);
|
||||
|
||||
final DialogPane dialogPane = getDialogPane();
|
||||
dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm());
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
dialogPane.setContent(stackPane);
|
||||
|
||||
AnchorPane anchorPane = new AnchorPane();
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setPrefHeight(280);
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
anchorPane.getChildren().add(scrollPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
AnchorPane.setLeftAnchor(scrollPane, 0.0);
|
||||
AnchorPane.setRightAnchor(scrollPane, 0.0);
|
||||
|
||||
deviceAccordion = new Accordion();
|
||||
scrollPane.setContent(deviceAccordion);
|
||||
|
||||
scanBox = new VBox();
|
||||
scanBox.setSpacing(30);
|
||||
scanBox.setAlignment(Pos.CENTER);
|
||||
Glyph usb = new Glyph(FontAwesome5Brands.FONT_NAME, FontAwesome5Brands.Glyph.USB);
|
||||
usb.setFontSize(50);
|
||||
scanLabel = new Label("Connect Hardware Wallet");
|
||||
Button button = new Button("Scan...");
|
||||
button.setPrefSize(120, 60);
|
||||
button.setOnAction(event -> {
|
||||
scan();
|
||||
});
|
||||
scanBox.getChildren().addAll(usb, scanLabel, button);
|
||||
scanBox.managedProperty().bind(scanBox.visibleProperty());
|
||||
|
||||
stackPane.getChildren().addAll(anchorPane, scanBox);
|
||||
|
||||
List<Device> devices = AppController.getDevices();
|
||||
if(devices == null || devices.isEmpty()) {
|
||||
scanBox.setVisible(true);
|
||||
} else {
|
||||
setDevices(devices);
|
||||
scanBox.setVisible(false);
|
||||
setResultConverter(dialogButton -> dialogButton.getButtonData().isCancelButton() ? null : psbt);
|
||||
}
|
||||
|
||||
final ButtonType rescanButtonType = new javafx.scene.control.ButtonType("Rescan", ButtonBar.ButtonData.NO);
|
||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
dialogPane.getButtonTypes().addAll(rescanButtonType, cancelButtonType);
|
||||
|
||||
Button rescanButton = (Button) dialogPane.lookupButton(rescanButtonType);
|
||||
rescanButton.managedProperty().bind(rescanButton.visibleProperty());
|
||||
rescanButton.visibleProperty().bind(scanBox.visibleProperty().not());
|
||||
rescanButton.addEventFilter(ActionEvent.ACTION, event -> {
|
||||
scan();
|
||||
event.consume();
|
||||
});
|
||||
|
||||
dialogPane.setPrefWidth(500);
|
||||
dialogPane.setPrefHeight(360);
|
||||
|
||||
setResultConverter(dialogButton -> dialogButton != cancelButtonType ? psbt : null);
|
||||
}
|
||||
|
||||
private void scan() {
|
||||
Hwi.EnumerateService enumerateService = new Hwi.EnumerateService(null);
|
||||
enumerateService.setOnSucceeded(workerStateEvent -> {
|
||||
List<Device> devices = enumerateService.getValue();
|
||||
setDevices(devices);
|
||||
});
|
||||
enumerateService.setOnFailed(workerStateEvent -> {
|
||||
scanBox.setVisible(true);
|
||||
scanLabel.setText(workerStateEvent.getSource().getException().getMessage());
|
||||
});
|
||||
enumerateService.start();
|
||||
}
|
||||
|
||||
private void setDevices(List<Device> devices) {
|
||||
deviceAccordion.getPanes().clear();
|
||||
|
||||
if(devices.isEmpty()) {
|
||||
scanBox.setVisible(true);
|
||||
scanLabel.setText("No devices found");
|
||||
} else {
|
||||
scanBox.setVisible(false);
|
||||
for(Device device : devices) {
|
||||
DevicePane devicePane = new DevicePane(DevicePane.DeviceOperation.SIGN, psbt, device);
|
||||
deviceAccordion.getPanes().add(devicePane);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected DevicePane getDevicePane(Device device) {
|
||||
return new DevicePane(psbt, device);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void psbtSigned(PSBTSignedEvent event) {
|
||||
if(psbt == event.getPsbt()) {
|
||||
EventManager.get().unregister(this);
|
||||
setResult(event.getSignedPsbt());
|
||||
this.close();
|
||||
}
|
||||
|
|
|
@ -47,8 +47,6 @@ public class QRDisplayDialog extends Dialog<UR> {
|
|||
this.ur = ur;
|
||||
this.encoder = new UREncoder(ur, MAX_FRAGMENT_LENGTH, MIN_FRAGMENT_LENGTH, 0);
|
||||
|
||||
EventManager.get().register(this);
|
||||
|
||||
final DialogPane dialogPane = getDialogPane();
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
|
|
|
@ -64,6 +64,7 @@ public class WalletExportDialog extends Dialog<Wallet> {
|
|||
|
||||
@Subscribe
|
||||
public void walletExported(WalletExportEvent event) {
|
||||
EventManager.get().unregister(this);
|
||||
wallet = event.getWallet();
|
||||
setResult(wallet);
|
||||
this.close();
|
||||
|
|
|
@ -51,6 +51,7 @@ public class WalletImportDialog extends Dialog<Wallet> {
|
|||
|
||||
@Subscribe
|
||||
public void walletImported(WalletImportEvent event) {
|
||||
EventManager.get().unregister(this);
|
||||
wallet = event.getWallet();
|
||||
setResult(wallet);
|
||||
this.close();
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
/**
|
||||
* This event is used by the DeviceAddressDialog to indicate that a USB device has displayed an address
|
||||
*
|
||||
*/
|
||||
public class AddressDisplayedEvent {
|
||||
private final String address;
|
||||
|
||||
public AddressDisplayedEvent(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ import com.sparrowwallet.sparrow.io.Device;
|
|||
import java.util.List;
|
||||
|
||||
public class UsbDeviceEvent {
|
||||
private List<Device> devices;
|
||||
private final List<Device> devices;
|
||||
|
||||
public UsbDeviceEvent(List<Device> devices) {
|
||||
this.devices = devices;
|
||||
|
|
|
@ -2,6 +2,8 @@ package com.sparrowwallet.sparrow.io;
|
|||
|
||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Device {
|
||||
private String type;
|
||||
private String path;
|
||||
|
@ -61,4 +63,22 @@ public class Device {
|
|||
public String toString() {
|
||||
return getModel() + ":" + getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(this == o) {
|
||||
return true;
|
||||
}
|
||||
if(o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Device device = (Device) o;
|
||||
return Objects.equals(type, device.type) &&
|
||||
Objects.equals(path, device.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package com.sparrowwallet.sparrow.io;
|
||||
|
||||
public class DisplayAddressException extends Exception {
|
||||
public DisplayAddressException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public DisplayAddressException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DisplayAddressException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DisplayAddressException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.io;
|
|||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.gson.*;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
|
@ -20,9 +21,8 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
@ -88,6 +88,37 @@ public class Hwi {
|
|||
}
|
||||
}
|
||||
|
||||
public String displayAddress(Device device, String passphrase, ScriptType scriptType, String derivationPath) throws DisplayAddressException {
|
||||
try {
|
||||
if(!List.of(ScriptType.P2PKH, ScriptType.P2SH_P2WPKH, ScriptType.P2WPKH).contains(scriptType)) {
|
||||
throw new IllegalArgumentException("Cannot display address for script type " + scriptType + ": Only single sig types supported");
|
||||
}
|
||||
|
||||
String type = null;
|
||||
if(scriptType == ScriptType.P2SH_P2WPKH) {
|
||||
type = "--sh_wpkh";
|
||||
} else if(scriptType == ScriptType.P2WPKH) {
|
||||
type = "--wpkh";
|
||||
}
|
||||
|
||||
String output;
|
||||
if(passphrase != null && device.getModel().equals(WalletModel.TREZOR_1)) {
|
||||
output = execute(getDeviceCommand(device, passphrase, Command.DISPLAY_ADDRESS, "--path", derivationPath, type));
|
||||
} else {
|
||||
output = execute(getDeviceCommand(device, Command.DISPLAY_ADDRESS, "--path", derivationPath, type));
|
||||
}
|
||||
|
||||
JsonObject result = JsonParser.parseString(output).getAsJsonObject();
|
||||
if(result.get("address") != null) {
|
||||
return result.get("address").getAsString();
|
||||
} else {
|
||||
throw new DisplayAddressException("Could not retrieve address");
|
||||
}
|
||||
} catch(IOException e) {
|
||||
throw new DisplayAddressException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PSBT signPSBT(Device device, String passphrase, PSBT psbt) throws SignTransactionException {
|
||||
try {
|
||||
String psbtBase64 = psbt.toBase64String();
|
||||
|
@ -251,12 +282,16 @@ public class Hwi {
|
|||
return List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString());
|
||||
}
|
||||
|
||||
private List<String> getDeviceCommand(Device device, Command command, String data) throws IOException {
|
||||
return List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString(), data);
|
||||
private List<String> getDeviceCommand(Device device, Command command, String... commandData) throws IOException {
|
||||
List<String> elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString()));
|
||||
elements.addAll(Arrays.stream(commandData).filter(Objects::nonNull).collect(Collectors.toList()));
|
||||
return elements;
|
||||
}
|
||||
|
||||
private List<String> getDeviceCommand(Device device, String passphrase, Command command, String data) throws IOException {
|
||||
return List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), "--password", passphrase, command.toString(), data);
|
||||
private List<String> getDeviceCommand(Device device, String passphrase, Command command, String... commandData) throws IOException {
|
||||
List<String> elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), "--password", passphrase, command.toString()));
|
||||
elements.addAll(Arrays.stream(commandData).filter(Objects::nonNull).collect(Collectors.toList()));
|
||||
return elements;
|
||||
}
|
||||
|
||||
public static class EnumerateService extends Service<List<Device>> {
|
||||
|
@ -337,6 +372,30 @@ public class Hwi {
|
|||
}
|
||||
}
|
||||
|
||||
public static class DisplayAddressService extends Service<String> {
|
||||
private final Device device;
|
||||
private final String passphrase;
|
||||
private final ScriptType scriptType;
|
||||
private final String derivationPath;
|
||||
|
||||
public DisplayAddressService(Device device, String passphrase, ScriptType scriptType, String derivationPath) {
|
||||
this.device = device;
|
||||
this.passphrase = passphrase;
|
||||
this.scriptType = scriptType;
|
||||
this.derivationPath = derivationPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<String> createTask() {
|
||||
return new Task<>() {
|
||||
protected String call() throws DisplayAddressException {
|
||||
Hwi hwi = new Hwi();
|
||||
return hwi.displayAddress(device, passphrase, scriptType, derivationPath);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetXpubService extends Service<String> {
|
||||
private final Device device;
|
||||
private final String passphrase;
|
||||
|
@ -406,6 +465,7 @@ public class Hwi {
|
|||
ENUMERATE("enumerate", true),
|
||||
PROMPT_PIN("promptpin", true),
|
||||
SEND_PIN("sendpin", false),
|
||||
DISPLAY_ADDRESS("displayaddress", true),
|
||||
GET_XPUB("getxpub", true),
|
||||
SIGN_TX("signtx", true);
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ public class HwUsbDevicesController extends KeystoreImportDetailController {
|
|||
|
||||
public void initializeView(List<Device> devices) {
|
||||
for(Device device : devices) {
|
||||
DevicePane devicePane = new DevicePane(DevicePane.DeviceOperation.IMPORT, getMasterController().getWallet(), device);
|
||||
DevicePane devicePane = new DevicePane(getMasterController().getWallet(), device);
|
||||
deviceAccordion.getPanes().add(devicePane);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package com.sparrowwallet.sparrow.keystoreimport;
|
||||
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.UsbDeviceEvent;
|
||||
import com.sparrowwallet.sparrow.io.Device;
|
||||
import com.sparrowwallet.sparrow.io.Hwi;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
|
@ -33,6 +36,7 @@ public class HwUsbScanController extends KeystoreImportDetailController {
|
|||
} else {
|
||||
getMasterController().showUsbDevices(devices);
|
||||
}
|
||||
Platform.runLater(() -> EventManager.get().post(new UsbDeviceEvent(devices)));
|
||||
});
|
||||
enumerateService.setOnFailed(workerStateEvent -> {
|
||||
getMasterController().showUsbError(enumerateService.getException().getMessage());
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.sparrowwallet.sparrow.EventManager;
|
|||
import com.sparrowwallet.sparrow.control.*;
|
||||
import com.sparrowwallet.sparrow.event.*;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
||||
import com.sparrowwallet.sparrow.io.Device;
|
||||
import com.sparrowwallet.sparrow.io.ElectrumServer;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.wallet.TransactionEntry;
|
||||
|
@ -627,15 +628,21 @@ public class HeadersController extends TransactionFormController implements Init
|
|||
}
|
||||
|
||||
private void signUsbKeystores() {
|
||||
if(headersForm.getSigningWallet().getKeystores().stream().noneMatch(keystore -> keystore.getSource().equals(KeystoreSource.HW_USB))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(headersForm.getPsbt().isSigned()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceSignDialog dlg = new DeviceSignDialog(headersForm.getPsbt());
|
||||
List<String> fingerprints = headersForm.getSigningWallet().getKeystores().stream().map(keystore -> keystore.getKeyDerivation().getMasterFingerprint()).collect(Collectors.toList());
|
||||
List<Device> signingDevices = AppController.getDevices().stream().filter(device -> fingerprints.contains(device.getFingerprint())).collect(Collectors.toList());
|
||||
if(signingDevices.isEmpty() && headersForm.getSigningWallet().getKeystores().stream().noneMatch(keystore -> keystore.getSource().equals(KeystoreSource.HW_USB))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(signingDevices.isEmpty()) {
|
||||
signingDevices = AppController.getDevices().stream().filter(device -> device.getNeedsPinSent() || device.getNeedsPassphraseSent()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
DeviceSignDialog dlg = new DeviceSignDialog(signingDevices.isEmpty() ? null : signingDevices, headersForm.getPsbt());
|
||||
Optional<PSBT> optionalSignedPsbt = dlg.showAndWait();
|
||||
if(optionalSignedPsbt.isPresent()) {
|
||||
PSBT signedPsbt = optionalSignedPsbt.get();
|
||||
|
|
|
@ -6,20 +6,28 @@ import com.google.zxing.client.j2se.MatrixToImageConfig;
|
|||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.AppController;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
||||
import com.sparrowwallet.sparrow.control.CopyableTextField;
|
||||
import com.sparrowwallet.sparrow.control.ScriptArea;
|
||||
import com.sparrowwallet.sparrow.control.*;
|
||||
import com.sparrowwallet.sparrow.event.ReceiveToEvent;
|
||||
import com.sparrowwallet.sparrow.event.UsbDeviceEvent;
|
||||
import com.sparrowwallet.sparrow.event.WalletHistoryChangedEvent;
|
||||
import com.sparrowwallet.sparrow.event.WalletNodesChangedEvent;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.io.Device;
|
||||
import com.sparrowwallet.sparrow.io.Hwi;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.image.Image;
|
||||
|
@ -34,8 +42,10 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.net.URL;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ReceiveController extends WalletFormController implements Initializable {
|
||||
private static final Logger log = LoggerFactory.getLogger(ReceiveController.class);
|
||||
|
@ -63,6 +73,9 @@ public class ReceiveController extends WalletFormController implements Initializ
|
|||
@FXML
|
||||
private CodeArea outputDescriptor;
|
||||
|
||||
@FXML
|
||||
private Button displayAddress;
|
||||
|
||||
private NodeEntry currentEntry;
|
||||
|
||||
@Override
|
||||
|
@ -73,6 +86,9 @@ public class ReceiveController extends WalletFormController implements Initializ
|
|||
@Override
|
||||
public void initializeView() {
|
||||
initializeScriptField(scriptPubKeyArea);
|
||||
|
||||
displayAddress.managedProperty().bind(displayAddress.visibleProperty());
|
||||
displayAddress.setVisible(false);
|
||||
}
|
||||
|
||||
public void setNodeEntry(NodeEntry nodeEntry) {
|
||||
|
@ -97,6 +113,8 @@ public class ReceiveController extends WalletFormController implements Initializ
|
|||
|
||||
outputDescriptor.clear();
|
||||
outputDescriptor.appendText(nodeEntry.getOutputDescriptor());
|
||||
|
||||
updateDisplayAddress(AppController.getDevices());
|
||||
}
|
||||
|
||||
private void updateLastUsed() {
|
||||
|
@ -115,6 +133,33 @@ public class ReceiveController extends WalletFormController implements Initializ
|
|||
}
|
||||
}
|
||||
|
||||
private void updateDisplayAddress(List<Device> devices) {
|
||||
//Can only display address for single sig wallets. See https://github.com/bitcoin-core/HWI/issues/224
|
||||
Wallet wallet = getWalletForm().getWallet();
|
||||
if(wallet.getPolicyType().equals(PolicyType.SINGLE)) {
|
||||
List<Device> addressDevices = devices.stream().filter(device -> wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint().equals(device.getFingerprint())).collect(Collectors.toList());
|
||||
if(addressDevices.isEmpty()) {
|
||||
addressDevices = devices.stream().filter(device -> device.getNeedsPinSent() || device.getNeedsPassphraseSent()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if(!addressDevices.isEmpty()) {
|
||||
if(currentEntry != null) {
|
||||
displayAddress.setVisible(true);
|
||||
}
|
||||
|
||||
displayAddress.setUserData(addressDevices);
|
||||
return;
|
||||
} else if(currentEntry != null && wallet.getKeystores().stream().anyMatch(keystore -> keystore.getSource().equals(KeystoreSource.HW_USB))) {
|
||||
displayAddress.setVisible(true);
|
||||
displayAddress.setUserData(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
displayAddress.setVisible(false);
|
||||
displayAddress.setUserData(null);
|
||||
}
|
||||
|
||||
private Image getQrCode(String address) {
|
||||
try {
|
||||
QRCodeWriter qrCodeWriter = new QRCodeWriter();
|
||||
|
@ -137,6 +182,36 @@ public class ReceiveController extends WalletFormController implements Initializ
|
|||
setNodeEntry(freshEntry);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void displayAddress(ActionEvent event) {
|
||||
Wallet wallet = getWalletForm().getWallet();
|
||||
if(wallet.getPolicyType() == PolicyType.SINGLE && currentEntry != null) {
|
||||
Keystore keystore = wallet.getKeystores().get(0);
|
||||
KeyDerivation fullDerivation = keystore.getKeyDerivation().extend(currentEntry.getNode().getDerivation());
|
||||
|
||||
List<Device> possibleDevices = (List<Device>)displayAddress.getUserData();
|
||||
if(possibleDevices != null && !possibleDevices.isEmpty()) {
|
||||
if(possibleDevices.size() > 1 || possibleDevices.get(0).getNeedsPinSent() || possibleDevices.get(0).getNeedsPassphraseSent()) {
|
||||
DeviceAddressDialog dlg = new DeviceAddressDialog(possibleDevices.size() == 1 ? List.of(possibleDevices.get(0)) : null, wallet, fullDerivation);
|
||||
dlg.showAndWait();
|
||||
} else {
|
||||
Device actualDevice = possibleDevices.get(0);
|
||||
Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(actualDevice, "", wallet.getScriptType(), fullDerivation.getDerivationPath());
|
||||
displayAddressService.setOnFailed(failedEvent -> {
|
||||
Platform.runLater(() -> {
|
||||
DeviceAddressDialog dlg = new DeviceAddressDialog(null, wallet, fullDerivation);
|
||||
dlg.showAndWait();
|
||||
});
|
||||
});
|
||||
displayAddressService.start();
|
||||
}
|
||||
} else {
|
||||
DeviceAddressDialog dlg = new DeviceAddressDialog(null, wallet, fullDerivation);
|
||||
dlg.showAndWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if(currentEntry != null) {
|
||||
label.textProperty().unbindBidirectional(currentEntry.labelProperty());
|
||||
|
@ -189,4 +264,9 @@ public class ReceiveController extends WalletFormController implements Initializ
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void usbDevicesFound(UsbDeviceEvent event) {
|
||||
updateDisplayAddress(event.getDevices());
|
||||
}
|
||||
}
|
|
@ -86,11 +86,19 @@
|
|||
<padding>
|
||||
<Insets left="25.0" right="25.0" bottom="25.0" />
|
||||
</padding>
|
||||
<HBox AnchorPane.rightAnchor="10">
|
||||
<Button fx:id="displayAddress" graphicTextGap="5" text="Display Address" AnchorPane.rightAnchor="10" onAction="#displayAddress">
|
||||
<graphic>
|
||||
<Glyph fontFamily="Font Awesome 5 Brands Regular" fontSize="12" icon="USB" />
|
||||
</graphic>
|
||||
</Button>
|
||||
<Region HBox.hgrow="ALWAYS" style="-fx-min-width: 20px" />
|
||||
<Button fx:id="nextAddress" graphicTextGap="5" text="Get Next Address" defaultButton="true" AnchorPane.rightAnchor="10" onAction="#getNewAddress">
|
||||
<graphic>
|
||||
<Glyph fontFamily="FontAwesome" icon="ARROW_DOWN" fontSize="12" />
|
||||
</graphic>
|
||||
</Button>
|
||||
</HBox>
|
||||
</AnchorPane>
|
||||
</bottom>
|
||||
</BorderPane>
|
Loading…
Reference in a new issue