diff --git a/src/main/java/com/sparrowwallet/sparrow/control/QRDisplayDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/QRDisplayDialog.java index a06ac1de..f06e9ad2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/QRDisplayDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/QRDisplayDialog.java @@ -232,7 +232,7 @@ public class QRDisplayDialog extends Dialog { if(useLegacyEncoding) { legacy.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE)); } else { - legacy.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE)); + legacy.setGraphic(getGlyph(FontAwesome5.Glyph.BAN)); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java index 6670a9d3..c4ca7383 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java @@ -1,9 +1,6 @@ package com.sparrowwallet.sparrow.control; -import com.github.sarxos.webcam.WebcamEvent; -import com.github.sarxos.webcam.WebcamListener; -import com.github.sarxos.webcam.WebcamResolution; -import com.github.sarxos.webcam.WebcamUpdater; +import com.github.sarxos.webcam.*; import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.OutputDescriptor; @@ -41,6 +38,7 @@ import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.layout.*; import javafx.util.Duration; +import javafx.util.StringConverter; import org.controlsfx.glyphfont.Glyph; import org.controlsfx.tools.Borders; import org.slf4j.Logger; @@ -74,6 +72,8 @@ public class QRScanDialog extends Dialog { private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0); + private final ObjectProperty webcamDeviceProperty = new SimpleObjectProperty<>(); + public QRScanDialog() { this.decoder = new URDecoder(); this.legacyDecoder = new LegacyURDecoder(); @@ -82,7 +82,7 @@ public class QRScanDialog extends Dialog { webcamResolutionProperty.set(WebcamResolution.HD); } - this.webcamService = new WebcamService(webcamResolutionProperty.get(), new QRScanListener(), new ScanDelayCalculator()); + this.webcamService = new WebcamService(webcamResolutionProperty.get(), null, new QRScanListener(), new ScanDelayCalculator()); webcamService.setPeriod(Duration.millis(SCAN_PERIOD_MILLIS)); webcamService.setRestartOnFailure(false); WebcamView webcamView = new WebcamView(webcamService); @@ -104,6 +104,15 @@ public class QRScanDialog extends Dialog { if(percentComplete.get() <= 0.0) { Platform.runLater(() -> percentComplete.set(newValue ? 0.0 : -1.0)); } + Platform.runLater(() -> { + if(Config.get().getWebcamDevice() != null && webcamDeviceProperty.get() == null) { + for(WebcamDevice device : WebcamScanDriver.getFoundDevices()) { + if(device.getName().equals(Config.get().getWebcamDevice())) { + webcamDeviceProperty.set(device); + } + } + } + }); }); VBox vBox = new VBox(20); @@ -134,6 +143,12 @@ public class QRScanDialog extends Dialog { } webcamService.cancel(); }); + webcamDeviceProperty.addListener((observable, oldValue, newValue) -> { + Config.get().setWebcamDevice(newValue.getName()); + if(!Objects.equals(webcamService.getDevice(), newValue)) { + webcamService.cancel(); + } + }); setOnCloseRequest(event -> { boolean isHdCapture = (webcamResolutionProperty.get() == WebcamResolution.HD); @@ -146,7 +161,8 @@ public class QRScanDialog extends Dialog { final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); final ButtonType hdButtonType = new javafx.scene.control.ButtonType("Use HD Capture", ButtonBar.ButtonData.LEFT); - dialogPane.getButtonTypes().addAll(hdButtonType, cancelButtonType); + final ButtonType camButtonType = new javafx.scene.control.ButtonType("Default Camera", ButtonBar.ButtonData.HELP_2); + dialogPane.getButtonTypes().addAll(hdButtonType, camButtonType, cancelButtonType); dialogPane.setPrefWidth(646); dialogPane.setPrefHeight(webcamResolutionProperty.get() == WebcamResolution.HD ? 490 : 590); AppServices.moveToActiveWindowScreen(this); @@ -520,6 +536,7 @@ public class QRScanDialog extends Dialog { public void webcamClosed(WebcamEvent webcamEvent) { if(webcamResolutionProperty.get() != null) { webcamService.setResolution(webcamResolutionProperty.get()); + webcamService.setDevice(webcamDeviceProperty.get()); Platform.runLater(() -> { if(!webcamService.isRunning()) { webcamService.reset(); @@ -558,10 +575,31 @@ public class QRScanDialog extends Dialog { }); button = hd; + } else if(buttonType.getButtonData() == ButtonBar.ButtonData.HELP_2) { + ComboBox devicesCombo = new ComboBox<>(WebcamScanDriver.getFoundDevices()); + devicesCombo.setConverter(new StringConverter<>() { + @Override + public String toString(WebcamDevice device) { + return device instanceof WebcamScanDevice ? ((WebcamScanDevice)device).getDeviceName() : "Default Camera"; + } + + @Override + public WebcamDevice fromString(String string) { + throw new UnsupportedOperationException(); + } + }); + devicesCombo.valueProperty().bindBidirectional(webcamDeviceProperty); + ButtonBar.setButtonData(devicesCombo, ButtonBar.ButtonData.LEFT); + + button = devicesCombo; } else { button = super.createButton(buttonType); } + if(button instanceof Region) { + ((Region)button).setMaxWidth(140); + } + button.disableProperty().bind(webcamService.openingProperty()); return button; } @@ -570,7 +608,7 @@ public class QRScanDialog extends Dialog { if(isHd) { hd.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE)); } else { - hd.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE)); + hd.setGraphic(getGlyph(FontAwesome5.Glyph.BAN)); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDevice.java b/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDevice.java index 9359ba61..5e0592b3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDevice.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDevice.java @@ -14,6 +14,7 @@ import java.awt.image.*; import java.nio.ByteBuffer; import java.util.Hashtable; import java.util.Iterator; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -312,6 +313,23 @@ public class WebcamScanDevice implements WebcamDevice, WebcamDevice.BufferAccess return this.fps; } + @Override + public boolean equals(Object o) { + if(this == o) { + return true; + } + if(o == null || getClass() != o.getClass()) { + return false; + } + WebcamScanDevice that = (WebcamScanDevice) o; + return Objects.equals(fullname, that.fullname); + } + + @Override + public int hashCode() { + return Objects.hash(fullname); + } + static { DIMENSIONS = new Dimension[]{WebcamResolution.QQVGA.getSize(), WebcamResolution.QVGA.getSize(), WebcamResolution.VGA.getSize()}; BAND_OFFSETS = new int[]{0, 1, 2}; diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDriver.java b/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDriver.java index e31b3f66..736be43c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDriver.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDriver.java @@ -3,16 +3,19 @@ package com.sparrowwallet.sparrow.control; import com.github.sarxos.webcam.WebcamDevice; import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice; import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import java.util.ArrayList; import java.util.List; public class WebcamScanDriver extends WebcamDefaultDriver { - private List foundScanDevices; + private static final ObservableList webcamDevices = FXCollections.observableArrayList(); + private static boolean rescan; @Override public List getDevices() { - if(foundScanDevices == null || foundScanDevices.isEmpty()) { + if(rescan || webcamDevices.isEmpty()) { List devices = super.getDevices(); List scanDevices = new ArrayList<>(); for(WebcamDevice device : devices) { @@ -20,9 +23,20 @@ public class WebcamScanDriver extends WebcamDefaultDriver { scanDevices.add(new WebcamScanDevice(defaultDevice.getDeviceRef())); } - foundScanDevices = scanDevices; + List newDevices = new ArrayList<>(scanDevices); + newDevices.removeAll(webcamDevices); + webcamDevices.addAll(newDevices); + webcamDevices.removeIf(device -> !scanDevices.contains(device)); } - return foundScanDevices; + return webcamDevices; + } + + public static ObservableList getFoundDevices() { + return webcamDevices; + } + + public static void rescan() { + rescan = true; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java b/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java index 472ce74f..4d06f754 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java @@ -1,13 +1,11 @@ package com.sparrowwallet.sparrow.control; -import com.github.sarxos.webcam.Webcam; -import com.github.sarxos.webcam.WebcamListener; -import com.github.sarxos.webcam.WebcamResolution; -import com.github.sarxos.webcam.WebcamUpdater; +import com.github.sarxos.webcam.*; import com.google.zxing.*; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; import com.google.zxing.qrcode.QRCodeReader; +import com.sparrowwallet.sparrow.io.Config; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -24,6 +22,7 @@ import java.util.concurrent.TimeUnit; public class WebcamService extends ScheduledService { private WebcamResolution resolution; + private WebcamDevice device; private final WebcamListener listener; private final WebcamUpdater.DelayCalculator delayCalculator; private final BooleanProperty opening = new SimpleBooleanProperty(false); @@ -40,8 +39,9 @@ public class WebcamService extends ScheduledService { Webcam.setDriver(new WebcamScanDriver()); } - public WebcamService(WebcamResolution resolution, WebcamListener listener, WebcamUpdater.DelayCalculator delayCalculator) { + public WebcamService(WebcamResolution resolution, WebcamDevice device, WebcamListener listener, WebcamUpdater.DelayCalculator delayCalculator) { this.resolution = resolution; + this.device = device; this.listener = listener; this.delayCalculator = delayCalculator; this.lastQrSampleTime = System.currentTimeMillis(); @@ -61,6 +61,23 @@ public class WebcamService extends ScheduledService { } cam = webcams.get(0); + + if(device != null) { + for(Webcam webcam : webcams) { + if(webcam.getDevice().getName().equals(device.getName())) { + cam = webcam; + } + } + } else if(Config.get().getWebcamDevice() != null) { + for(Webcam webcam : webcams) { + if(webcam.getDevice().getName().equals(Config.get().getWebcamDevice())) { + cam = webcam; + } + } + } + + device = cam.getDevice(); + cam.setCustomViewSizes(resolution.getSize()); cam.setViewSize(resolution.getSize()); if(!Arrays.asList(cam.getWebcamListeners()).contains(listener)) { @@ -73,6 +90,10 @@ public class WebcamService extends ScheduledService { } BufferedImage bimg = cam.getImage(); + if(bimg == null) { + return null; + } + Image image = SwingFXUtils.toFXImage(bimg, null); updateValue(image); @@ -136,6 +157,14 @@ public class WebcamService extends ScheduledService { this.resolution = resolution; } + public WebcamDevice getDevice() { + return device; + } + + public void setDevice(WebcamDevice device) { + this.device = device; + } + public boolean isOpening() { return opening.get(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index 4587fe88..42e74f21 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -46,6 +46,7 @@ public class Config { private Integer keyDerivationPeriod; private File hwi; private Boolean hdCapture; + private String webcamDevice; private ServerType serverType; private String publicElectrumServer; private String coreServer; @@ -307,6 +308,15 @@ public class Config { flush(); } + public String getWebcamDevice() { + return webcamDevice; + } + + public void setWebcamDevice(String webcamDevice) { + this.webcamDevice = webcamDevice; + flush(); + } + public ServerType getServerType() { return serverType; }