allow selection of webcam from QR scan dialog

This commit is contained in:
Craig Raw 2021-07-05 12:33:31 +02:00
parent 2f153686dd
commit ada45ee75b
6 changed files with 126 additions and 17 deletions

View file

@ -232,7 +232,7 @@ public class QRDisplayDialog extends Dialog<UR> {
if(useLegacyEncoding) { if(useLegacyEncoding) {
legacy.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE)); legacy.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE));
} else { } else {
legacy.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE)); legacy.setGraphic(getGlyph(FontAwesome5.Glyph.BAN));
} }
} }

View file

@ -1,9 +1,6 @@
package com.sparrowwallet.sparrow.control; package com.sparrowwallet.sparrow.control;
import com.github.sarxos.webcam.WebcamEvent; import com.github.sarxos.webcam.*;
import com.github.sarxos.webcam.WebcamListener;
import com.github.sarxos.webcam.WebcamResolution;
import com.github.sarxos.webcam.WebcamUpdater;
import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.OutputDescriptor; import com.sparrowwallet.drongo.OutputDescriptor;
@ -41,6 +38,7 @@ import javafx.scene.Node;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.util.Duration; import javafx.util.Duration;
import javafx.util.StringConverter;
import org.controlsfx.glyphfont.Glyph; import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.tools.Borders; import org.controlsfx.tools.Borders;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -74,6 +72,8 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0); private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0);
private final ObjectProperty<WebcamDevice> webcamDeviceProperty = new SimpleObjectProperty<>();
public QRScanDialog() { public QRScanDialog() {
this.decoder = new URDecoder(); this.decoder = new URDecoder();
this.legacyDecoder = new LegacyURDecoder(); this.legacyDecoder = new LegacyURDecoder();
@ -82,7 +82,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
webcamResolutionProperty.set(WebcamResolution.HD); 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.setPeriod(Duration.millis(SCAN_PERIOD_MILLIS));
webcamService.setRestartOnFailure(false); webcamService.setRestartOnFailure(false);
WebcamView webcamView = new WebcamView(webcamService); WebcamView webcamView = new WebcamView(webcamService);
@ -104,6 +104,15 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
if(percentComplete.get() <= 0.0) { if(percentComplete.get() <= 0.0) {
Platform.runLater(() -> percentComplete.set(newValue ? 0.0 : -1.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); VBox vBox = new VBox(20);
@ -134,6 +143,12 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
} }
webcamService.cancel(); webcamService.cancel();
}); });
webcamDeviceProperty.addListener((observable, oldValue, newValue) -> {
Config.get().setWebcamDevice(newValue.getName());
if(!Objects.equals(webcamService.getDevice(), newValue)) {
webcamService.cancel();
}
});
setOnCloseRequest(event -> { setOnCloseRequest(event -> {
boolean isHdCapture = (webcamResolutionProperty.get() == WebcamResolution.HD); boolean isHdCapture = (webcamResolutionProperty.get() == WebcamResolution.HD);
@ -146,7 +161,8 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); 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); 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.setPrefWidth(646);
dialogPane.setPrefHeight(webcamResolutionProperty.get() == WebcamResolution.HD ? 490 : 590); dialogPane.setPrefHeight(webcamResolutionProperty.get() == WebcamResolution.HD ? 490 : 590);
AppServices.moveToActiveWindowScreen(this); AppServices.moveToActiveWindowScreen(this);
@ -520,6 +536,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
public void webcamClosed(WebcamEvent webcamEvent) { public void webcamClosed(WebcamEvent webcamEvent) {
if(webcamResolutionProperty.get() != null) { if(webcamResolutionProperty.get() != null) {
webcamService.setResolution(webcamResolutionProperty.get()); webcamService.setResolution(webcamResolutionProperty.get());
webcamService.setDevice(webcamDeviceProperty.get());
Platform.runLater(() -> { Platform.runLater(() -> {
if(!webcamService.isRunning()) { if(!webcamService.isRunning()) {
webcamService.reset(); webcamService.reset();
@ -558,10 +575,31 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
}); });
button = hd; button = hd;
} else if(buttonType.getButtonData() == ButtonBar.ButtonData.HELP_2) {
ComboBox<WebcamDevice> 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 { } else {
button = super.createButton(buttonType); button = super.createButton(buttonType);
} }
if(button instanceof Region) {
((Region)button).setMaxWidth(140);
}
button.disableProperty().bind(webcamService.openingProperty()); button.disableProperty().bind(webcamService.openingProperty());
return button; return button;
} }
@ -570,7 +608,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
if(isHd) { if(isHd) {
hd.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE)); hd.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE));
} else { } else {
hd.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE)); hd.setGraphic(getGlyph(FontAwesome5.Glyph.BAN));
} }
} }

View file

@ -14,6 +14,7 @@ import java.awt.image.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Iterator; import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -312,6 +313,23 @@ public class WebcamScanDevice implements WebcamDevice, WebcamDevice.BufferAccess
return this.fps; 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 { static {
DIMENSIONS = new Dimension[]{WebcamResolution.QQVGA.getSize(), WebcamResolution.QVGA.getSize(), WebcamResolution.VGA.getSize()}; DIMENSIONS = new Dimension[]{WebcamResolution.QQVGA.getSize(), WebcamResolution.QVGA.getSize(), WebcamResolution.VGA.getSize()};
BAND_OFFSETS = new int[]{0, 1, 2}; BAND_OFFSETS = new int[]{0, 1, 2};

View file

@ -3,16 +3,19 @@ package com.sparrowwallet.sparrow.control;
import com.github.sarxos.webcam.WebcamDevice; import com.github.sarxos.webcam.WebcamDevice;
import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice; import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice;
import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver; import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class WebcamScanDriver extends WebcamDefaultDriver { public class WebcamScanDriver extends WebcamDefaultDriver {
private List<WebcamDevice> foundScanDevices; private static final ObservableList<WebcamDevice> webcamDevices = FXCollections.observableArrayList();
private static boolean rescan;
@Override @Override
public List<WebcamDevice> getDevices() { public List<WebcamDevice> getDevices() {
if(foundScanDevices == null || foundScanDevices.isEmpty()) { if(rescan || webcamDevices.isEmpty()) {
List<WebcamDevice> devices = super.getDevices(); List<WebcamDevice> devices = super.getDevices();
List<WebcamDevice> scanDevices = new ArrayList<>(); List<WebcamDevice> scanDevices = new ArrayList<>();
for(WebcamDevice device : devices) { for(WebcamDevice device : devices) {
@ -20,9 +23,20 @@ public class WebcamScanDriver extends WebcamDefaultDriver {
scanDevices.add(new WebcamScanDevice(defaultDevice.getDeviceRef())); scanDevices.add(new WebcamScanDevice(defaultDevice.getDeviceRef()));
} }
foundScanDevices = scanDevices; List<WebcamDevice> newDevices = new ArrayList<>(scanDevices);
newDevices.removeAll(webcamDevices);
webcamDevices.addAll(newDevices);
webcamDevices.removeIf(device -> !scanDevices.contains(device));
} }
return foundScanDevices; return webcamDevices;
}
public static ObservableList<WebcamDevice> getFoundDevices() {
return webcamDevices;
}
public static void rescan() {
rescan = true;
} }
} }

View file

@ -1,13 +1,11 @@
package com.sparrowwallet.sparrow.control; package com.sparrowwallet.sparrow.control;
import com.github.sarxos.webcam.Webcam; import com.github.sarxos.webcam.*;
import com.github.sarxos.webcam.WebcamListener;
import com.github.sarxos.webcam.WebcamResolution;
import com.github.sarxos.webcam.WebcamUpdater;
import com.google.zxing.*; import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer; import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader; import com.google.zxing.qrcode.QRCodeReader;
import com.sparrowwallet.sparrow.io.Config;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
@ -24,6 +22,7 @@ import java.util.concurrent.TimeUnit;
public class WebcamService extends ScheduledService<Image> { public class WebcamService extends ScheduledService<Image> {
private WebcamResolution resolution; private WebcamResolution resolution;
private WebcamDevice device;
private final WebcamListener listener; private final WebcamListener listener;
private final WebcamUpdater.DelayCalculator delayCalculator; private final WebcamUpdater.DelayCalculator delayCalculator;
private final BooleanProperty opening = new SimpleBooleanProperty(false); private final BooleanProperty opening = new SimpleBooleanProperty(false);
@ -40,8 +39,9 @@ public class WebcamService extends ScheduledService<Image> {
Webcam.setDriver(new WebcamScanDriver()); 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.resolution = resolution;
this.device = device;
this.listener = listener; this.listener = listener;
this.delayCalculator = delayCalculator; this.delayCalculator = delayCalculator;
this.lastQrSampleTime = System.currentTimeMillis(); this.lastQrSampleTime = System.currentTimeMillis();
@ -61,6 +61,23 @@ public class WebcamService extends ScheduledService<Image> {
} }
cam = webcams.get(0); 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.setCustomViewSizes(resolution.getSize());
cam.setViewSize(resolution.getSize()); cam.setViewSize(resolution.getSize());
if(!Arrays.asList(cam.getWebcamListeners()).contains(listener)) { if(!Arrays.asList(cam.getWebcamListeners()).contains(listener)) {
@ -73,6 +90,10 @@ public class WebcamService extends ScheduledService<Image> {
} }
BufferedImage bimg = cam.getImage(); BufferedImage bimg = cam.getImage();
if(bimg == null) {
return null;
}
Image image = SwingFXUtils.toFXImage(bimg, null); Image image = SwingFXUtils.toFXImage(bimg, null);
updateValue(image); updateValue(image);
@ -136,6 +157,14 @@ public class WebcamService extends ScheduledService<Image> {
this.resolution = resolution; this.resolution = resolution;
} }
public WebcamDevice getDevice() {
return device;
}
public void setDevice(WebcamDevice device) {
this.device = device;
}
public boolean isOpening() { public boolean isOpening() {
return opening.get(); return opening.get();
} }

View file

@ -46,6 +46,7 @@ public class Config {
private Integer keyDerivationPeriod; private Integer keyDerivationPeriod;
private File hwi; private File hwi;
private Boolean hdCapture; private Boolean hdCapture;
private String webcamDevice;
private ServerType serverType; private ServerType serverType;
private String publicElectrumServer; private String publicElectrumServer;
private String coreServer; private String coreServer;
@ -307,6 +308,15 @@ public class Config {
flush(); flush();
} }
public String getWebcamDevice() {
return webcamDevice;
}
public void setWebcamDevice(String webcamDevice) {
this.webcamDevice = webcamDevice;
flush();
}
public ServerType getServerType() { public ServerType getServerType() {
return serverType; return serverType;
} }