diff --git a/electrum/gui/qml/components/ImportAddressesKeysDialog.qml b/electrum/gui/qml/components/ImportAddressesKeysDialog.qml index e8c96b850..e84d5e7e7 100644 --- a/electrum/gui/qml/components/ImportAddressesKeysDialog.qml +++ b/electrum/gui/qml/components/ImportAddressesKeysDialog.qml @@ -91,11 +91,11 @@ ElDialog { ? qsTr('Scan another address') : qsTr('Scan another private key') }) - dialog.onFound.connect(function() { - if (verify(dialog.scanData)) { + dialog.onFoundText.connect(function(data) { + if (verify(data)) { if (import_ta.text != '') import_ta.text = import_ta.text + ',\n' - import_ta.text = import_ta.text + dialog.scanData + import_ta.text = import_ta.text + data } dialog.close() }) diff --git a/electrum/gui/qml/components/ImportChannelBackupDialog.qml b/electrum/gui/qml/components/ImportChannelBackupDialog.qml index 3a86360ac..4de22020e 100644 --- a/electrum/gui/qml/components/ImportChannelBackupDialog.qml +++ b/electrum/gui/qml/components/ImportChannelBackupDialog.qml @@ -62,8 +62,8 @@ ElDialog { var dialog = app.scanDialog.createObject(app, { hint: qsTr('Scan a channel backup') }) - dialog.onFound.connect(function() { - channelbackup_ta.text = dialog.scanData + dialog.onFoundText.connect(function(data) { + channelbackup_ta.text = data dialog.close() }) dialog.open() diff --git a/electrum/gui/qml/components/OpenChannelDialog.qml b/electrum/gui/qml/components/OpenChannelDialog.qml index a7849aeb9..97d709b52 100644 --- a/electrum/gui/qml/components/OpenChannelDialog.qml +++ b/electrum/gui/qml/components/OpenChannelDialog.qml @@ -124,9 +124,9 @@ ElDialog { var dialog = app.scanDialog.createObject(app, { hint: qsTr('Scan a node-id or a connect string') }) - dialog.onFound.connect(function() { - if (channelopener.validateConnectString(dialog.scanData)) { - channelopener.connectStr = dialog.scanData + dialog.onFoundText.connect(function(data) { + if (channelopener.validateConnectString(data)) { + channelopener.connectStr = data node.text = channelopener.connectStr } else { var errdialog = app.messageDialog.createObject(app, { diff --git a/electrum/gui/qml/components/ScanDialog.qml b/electrum/gui/qml/components/ScanDialog.qml index f85cd82ac..d8e4ed93a 100644 --- a/electrum/gui/qml/components/ScanDialog.qml +++ b/electrum/gui/qml/components/ScanDialog.qml @@ -2,17 +2,19 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import org.electrum + import "controls" // currently not used on android, kept for future use when qt6 camera stops crashing ElDialog { id: scanDialog - property string scanData property string error property string hint - signal found + signal foundText(data: string) + signal foundBinary(data: Bytes) width: parent.width height: parent.height @@ -35,9 +37,8 @@ ElDialog { Layout.fillWidth: true Layout.fillHeight: true hint: scanDialog.hint - onFound: { - scanDialog.scanData = scanData - scanDialog.found() + onFoundText: (data) => { + scanDialog.foundText(data) } } diff --git a/electrum/gui/qml/components/SendDialog.qml b/electrum/gui/qml/components/SendDialog.qml index e8d529843..7564e4b4f 100644 --- a/electrum/gui/qml/components/SendDialog.qml +++ b/electrum/gui/qml/components/SendDialog.qml @@ -59,7 +59,10 @@ ElDialog { hint: Daemon.currentWallet.isLightning ? qsTr('Scan an Invoice, an Address, an LNURL-pay, a PSBT or a Channel Backup') : qsTr('Scan an Invoice, an Address, an LNURL-pay or a PSBT') - onFound: dialog.dispatch(scanData) + + onFoundText: (data) => { + dialog.dispatch(data) + } } ButtonContainer { diff --git a/electrum/gui/qml/components/SweepDialog.qml b/electrum/gui/qml/components/SweepDialog.qml index da06de745..849a70356 100644 --- a/electrum/gui/qml/components/SweepDialog.qml +++ b/electrum/gui/qml/components/SweepDialog.qml @@ -117,9 +117,9 @@ ElDialog { var dialog = app.scanDialog.createObject(app, { hint: qsTr('Scan a private key') }) - dialog.onFound.connect(function() { - if (verifyPrivateKey(dialog.scanData)) - addPrivateKey(dialog.scanData) + dialog.onFoundText.connect(function(data) { + if (verifyPrivateKey(data)) + addPrivateKey(data) dialog.close() }) dialog.open() diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml index 3ebb5b72b..b044d5189 100644 --- a/electrum/gui/qml/components/WalletMainView.qml +++ b/electrum/gui/qml/components/WalletMainView.qml @@ -47,8 +47,7 @@ Item { ? qsTr('Scan an Invoice, an Address, an LNURL-pay, a PSBT or a Channel Backup') : qsTr('Scan an Invoice, an Address, an LNURL-pay or a PSBT') }) - scanner.onFound.connect(function() { - var data = scanner.scanData + scanner.onFoundText.connect(function(data) { data = data.trim() if (bitcoin.isRawTx(data)) { app.stack.push(Qt.resolvedUrl('TxDetails.qml'), { rawtx: data }) diff --git a/electrum/gui/qml/components/controls/QRScan.qml b/electrum/gui/qml/components/controls/QRScan.qml index 66ce7c17f..b25d0e163 100644 --- a/electrum/gui/qml/components/controls/QRScan.qml +++ b/electrum/gui/qml/components/controls/QRScan.qml @@ -10,14 +10,12 @@ Item { property bool active: false property string url - property string scanData property string hint - signal found + signal foundText(data: string) function restart() { console.log('qrscan.restart') - scanData = '' qr.reset() start() } @@ -153,8 +151,7 @@ Item { function onDataChanged() { console.log('QR DATA: ' + qr.data) scanner.active = false - scanner.scanData = qr.data - scanner.found() + scanner.foundText(qr.data) } } diff --git a/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml index 039082cb9..11f04eb04 100644 --- a/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml +++ b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml @@ -166,9 +166,9 @@ WizardComponent { ? qsTr('Scan a cosigner master public key') : qsTr('Scan a master key') }) - dialog.onFound.connect(function() { - if (verifyMasterKey(dialog.scanData)) - masterkey_ta.text = dialog.scanData + dialog.onFoundText.connect(function(data) { + if (verifyMasterKey(data)) + masterkey_ta.text = data else masterkey_ta.text = '' dialog.close() diff --git a/electrum/gui/qml/components/wizard/WCImport.qml b/electrum/gui/qml/components/wizard/WCImport.qml index b185a1c0d..fbf402a8b 100644 --- a/electrum/gui/qml/components/wizard/WCImport.qml +++ b/electrum/gui/qml/components/wizard/WCImport.qml @@ -76,11 +76,11 @@ WizardComponent { ? qsTr('Scan another private key') : qsTr('Scan a private key or an address') }) - dialog.onFound.connect(function() { - if (verify(dialog.scanData)) { + dialog.onFoundText.connect(function(data) { + if (verify(data)) { if (import_ta.text != '') import_ta.text = import_ta.text + '\n' - import_ta.text = import_ta.text + dialog.scanData + import_ta.text = import_ta.text + data } dialog.close() }) diff --git a/electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java b/electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java index ff7b22d13..58eeeb81e 100644 --- a/electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java +++ b/electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java @@ -21,6 +21,8 @@ import androidx.core.app.ActivityCompat; import java.util.Arrays; import de.markusfisch.android.barcodescannerview.widget.BarcodeScannerView; +import de.markusfisch.android.zxingcpp.ZxingCpp.Result; +import de.markusfisch.android.zxingcpp.ZxingCpp.ContentType; import org.electrum.electrum.res.R; // package set in build.gradle @@ -29,7 +31,7 @@ public class SimpleScannerActivity extends Activity { private static final int MY_PERMISSIONS_CAMERA = 1002; private BarcodeScannerView mScannerView = null; - final String TAG = "org.electrum.SimpleScannerActivity"; + final String TAG = "org.electrum.qr.SimpleScannerActivity"; private boolean mAlreadyRequestedPermissions = false; @@ -60,7 +62,7 @@ public class SimpleScannerActivity extends Activity { Toast.makeText(SimpleScannerActivity.this, "Clipboard contents too large.", Toast.LENGTH_SHORT).show(); return; } - SimpleScannerActivity.this.setResultAndClose(clipboardText); + SimpleScannerActivity.this.setResultAndClose(null, clipboardText); } else { Toast.makeText(SimpleScannerActivity.this, "Clipboard is empty.", Toast.LENGTH_SHORT).show(); } @@ -96,7 +98,7 @@ public class SimpleScannerActivity extends Activity { contentFrame.addView(mScannerView); mScannerView.setOnBarcodeListener(result -> { // Handle the scan result - this.setResultAndClose(result.getText()); + this.setResultAndClose(result, null); // Return false to stop scanning after first result return false; }); @@ -104,9 +106,22 @@ public class SimpleScannerActivity extends Activity { mScannerView.openAsync(); // Start camera on resume } - private void setResultAndClose(String resultText) { + private void setResultAndClose(Result scanResult, String textOnly) { Intent resultIntent = new Intent(); - resultIntent.putExtra("text", resultText); + if (textOnly != null) { + Log.v(TAG, "clipboard contentType TEXT"); + resultIntent.putExtra("text", textOnly); + } else if (scanResult != null) { + if (scanResult.getContentType() == ContentType.TEXT) { + Log.v(TAG, "scanResult contentType TEXT"); + resultIntent.putExtra("text", scanResult.getText()); + } else if (scanResult.getContentType() == ContentType.BINARY) { + Log.v(TAG, "scanResult contentType BINARY"); + resultIntent.putExtra("binary", scanResult.getRawBytes()); + } else { + Log.v(TAG, "scanresult contenttype unknown"); + } + } setResult(Activity.RESULT_OK, resultIntent); this.finish(); } diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index 349f89e5c..45e9feaf2 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -34,7 +34,7 @@ from .qefx import QEFX from .qetxfinalizer import QETxFinalizer, QETxRbfFeeBumper, QETxCpfpFeeBumper, QETxCanceller, QETxSweepFinalizer, FeeSlider from .qeinvoice import QEInvoice, QEInvoiceParser from .qerequestdetails import QERequestDetails -from .qetypes import QEAmount +from .qetypes import QEAmount, QEBytes from .qeaddressdetails import QEAddressDetails from .qetxdetails import QETxDetails from .qechannelopener import QEChannelOpener @@ -426,6 +426,7 @@ class ElectrumQmlApplication(QGuiApplication): # TODO QT6 order of declaration is important now? qmlRegisterType(QEAmount, 'org.electrum', 1, 0, 'Amount') + qmlRegisterType(QEBytes, 'org.electrum', 1, 0, 'Bytes') qmlRegisterType(QENewWalletWizard, 'org.electrum', 1, 0, 'QNewWalletWizard') qmlRegisterType(QETermsOfUseWizard, 'org.electrum', 1, 0, 'QTermsOfUseWizard') qmlRegisterType(QEServerConnectWizard, 'org.electrum', 1, 0, 'QServerConnectWizard') diff --git a/electrum/gui/qml/qeqrscanner.py b/electrum/gui/qml/qeqrscanner.py index a301cdaa9..297479ba8 100644 --- a/electrum/gui/qml/qeqrscanner.py +++ b/electrum/gui/qml/qeqrscanner.py @@ -3,8 +3,8 @@ import os from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, Qt from PyQt6.QtGui import QGuiApplication -from electrum.util import send_exception_to_crash_reporter, UserFacingException -from electrum.simple_config import SimpleConfig +from electrum.gui.qml.qetypes import QEBytes +from electrum.util import send_exception_to_crash_reporter from electrum.logging import get_logger from electrum.i18n import _ @@ -21,14 +21,14 @@ if 'ANDROID_DATA' in os.environ: class QEQRScanner(QObject): _logger = get_logger(__name__) - found = pyqtSignal() + foundText = pyqtSignal(str) + foundBinary = pyqtSignal(QEBytes) finished = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self._hint = _("Scan a QR code.") - self._scan_data = "" # decoded qr code result self.finished.connect(self._unbind, Qt.ConnectionType.QueuedConnection) self.destroyed.connect(lambda: self.on_destroy()) @@ -44,14 +44,6 @@ class QEQRScanner(QObject): def hint(self, v: str): self._hint = v - @pyqtProperty(str) - def scanData(self): - return self._scan_data - - @scanData.setter - def scanData(self, v: str): - self._scan_data = v - @pyqtSlot() def open(self): if 'ANDROID_DATA' not in os.environ: @@ -67,9 +59,11 @@ class QEQRScanner(QObject): def on_qr_activity_result(self, requestCode, resultCode, intent): try: if resultCode == -1: # RESULT_OK: - contents = intent.getStringExtra(jString("text")) - self.scanData = contents - self.found.emit() + if (contents := intent.getStringExtra(jString("text"))) is not None: + self.foundText.emit(contents) + if (contents := intent.getByteArrayExtra(jString("binary"))) is not None: + self._binary_content = QEBytes(bytes(contents.tolist())) + self.foundBinary.emit(self._binary_content) except Exception as e: # exc would otherwise get lost send_exception_to_crash_reporter(e) finally: @@ -82,23 +76,6 @@ class QEQRScanner(QObject): def _scan_qr_non_android(self): data = QGuiApplication.clipboard().text() - self.scanData = data - self.found.emit() + self.foundText.emit(data) self.finished.emit() return - # from electrum import qrscanner - # from .qeapp import ElectrumQmlApplication - # daemon = ElectrumQmlApplication._daemon - # config = daemon.config # type: SimpleConfig - # try: - # video_dev = config.get_video_device() - # data = qrscanner.scan_barcode(video_dev) - # if data is not None: - # self.scanData = data - # self.found.emit() - # except UserFacingException as e: - # self._logger.warning(f'camera error: {e!r}') - # #self.show_error(e) - # except Exception as e: - # self._logger.exception('camera error') - # #self.show_error(repr(e)) diff --git a/electrum/gui/qml/qetypes.py b/electrum/gui/qml/qetypes.py index f304476e4..090dae9a6 100644 --- a/electrum/gui/qml/qetypes.py +++ b/electrum/gui/qml/qetypes.py @@ -116,3 +116,27 @@ class QEAmount(QObject): def __repr__(self): return f"" + + +class QEBytes(QObject): + def __init__(self, data: bytes = None, *, parent=None): + super().__init__(parent) + self.data = data + + @property + def data(self): + return self._data + + @data.setter + def data(self, _data): + self._data = _data + + @pyqtProperty(bool) + def isEmpty(self): + return self._data is None or self._data == bytes() + + def __str__(self): + return f'{self._data}' + + def __repr__(self): + return f""