From 7414959604f23c352a872c9f06fdc93e8b3e6804 Mon Sep 17 00:00:00 2001 From: Kristaps Kaupe Date: Wed, 14 Oct 2020 19:55:20 +0300 Subject: [PATCH] QR code support for BIP78 payjoin receiver in Qt GUI --- requirements/gui.txt | 2 +- scripts/joinmarket-qt.py | 24 ++---------------------- scripts/qtsupport.py | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/requirements/gui.txt b/requirements/gui.txt index 9b7879b..d4dfe8c 100644 --- a/requirements/gui.txt +++ b/requirements/gui.txt @@ -2,5 +2,5 @@ pywin32; platform_system == "Windows" PySide2 PyQt5==5.14.2 -qrcode[pil] +qrcode https://github.com/sunu/qt5reactor/archive/58410aaead2185e9917ae9cac9c50fe7b70e4a60.zip#egg=qt5reactor diff --git a/scripts/joinmarket-qt.py b/scripts/joinmarket-qt.py index fcb23b5..62d8f11 100755 --- a/scripts/joinmarket-qt.py +++ b/scripts/joinmarket-qt.py @@ -22,7 +22,6 @@ Some widgets copied and modified from https://github.com/spesmilo/electrum import sys, datetime, os, logging import platform, json, threading, time -import qrcode from optparse import OptionParser from PySide2 import QtCore @@ -31,8 +30,6 @@ from PySide2.QtGui import * from PySide2.QtWidgets import * -from PIL.ImageQt import ImageQt - if platform.system() == 'Windows': MONOSPACE_FONT = 'Lucida Console' elif platform.system() == 'Darwin': @@ -82,7 +79,7 @@ from qtsupport import ScheduleWizard, TumbleRestartWizard, config_tips,\ config_types, QtHandler, XStream, Buttons, OkButton, CancelButton,\ PasswordDialog, MyTreeWidget, JMQtMessageBox, BLUE_FG,\ donation_more_message, BitcoinAmountEdit, JMIntValidator,\ - ReceiveBIP78Dialog + ReceiveBIP78Dialog, QRCodePopup from twisted.internet import task @@ -1302,23 +1299,6 @@ class CoinsTab(QWidget): lambda: app.clipboard().setText(txid)) menu.exec_(self.cTW.viewport().mapToGlobal(position)) -class BitcoinQRCodePopup(QDialog): - - def __init__(self, parent, address): - super().__init__(parent) - self.address = address - self.setWindowTitle(address) - img = qrcode.make('bitcoin:' + address) - self.imageLabel = QLabel() - self.imageLabel.setPixmap(QPixmap.fromImage(ImageQt(img))) - layout = QVBoxLayout() - layout.addWidget(self.imageLabel) - self.setLayout(layout) - self.initUI() - - def initUI(self): - self.show() - class JMWalletTab(QWidget): @@ -1385,7 +1365,7 @@ class JMWalletTab(QWidget): menu.exec_(self.walletTree.viewport().mapToGlobal(position)) def openQRCodePopup(self, address): - popup = BitcoinQRCodePopup(self, address) + popup = QRCodePopup(self, address, btc.encode_bip21_uri(address, {})) popup.show() def updateWalletInfo(self, walletinfo=None): diff --git a/scripts/qtsupport.py b/scripts/qtsupport.py index 030d0b4..479b8e0 100644 --- a/scripts/qtsupport.py +++ b/scripts/qtsupport.py @@ -17,12 +17,15 @@ Qt files for the wizard for initiating a tumbler run. You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import math, re, logging, string +import math, logging, qrcode, re, string +from io import BytesIO from PySide2 import QtCore + from PySide2.QtGui import * from PySide2.QtWidgets import * from jmbitcoin.amount import amount_to_sat, btc_to_sat, sat_to_btc +from jmbitcoin.bip21 import decode_bip21_uri from jmclient import (jm_single, validate_address, get_tumble_schedule) @@ -943,6 +946,28 @@ class CopyOnClickLineEdit(QLineEdit): "URI copied to clipboard", mbtype="info") self.was_copied = True + +class QRCodePopup(QDialog): + + def __init__(self, parent, title, data): + super().__init__(parent) + self.setWindowTitle(title) + buf = BytesIO() + img = qrcode.make(data) + img.save(buf, "PNG") + self.imageLabel = QLabel() + qt_pixmap = QPixmap() + qt_pixmap.loadFromData(buf.getvalue(), "PNG") + self.imageLabel.setPixmap(qt_pixmap) + layout = QVBoxLayout() + layout.addWidget(self.imageLabel) + self.setLayout(layout) + self.initUI() + + def initUI(self): + self.show() + + class ReceiveBIP78Dialog(QDialog): parameter_names = ['Amount to receive', 'Mixdepth'] @@ -1015,6 +1040,7 @@ class ReceiveBIP78Dialog(QDialog): # Give user indication that they # can quit without cancelling: self.close_btn.setVisible(True) + self.qr_btn.setVisible(False) self.btnbox.button(QDialogButtonBox.Cancel).setDisabled(True) def start_generate(self): @@ -1080,12 +1106,22 @@ class ReceiveBIP78Dialog(QDialog): self.close_btn = self.btnbox.addButton("C&lose", QDialogButtonBox.AcceptRole) self.close_btn.setVisible(False) + self.qr_btn = self.btnbox.addButton("Show &QR code", + QDialogButtonBox.ActionRole) layout.addWidget(self.btnbox, i+4, 0) # note that we don't use a standard 'Close' button because # it is also associated with 'rejection' (and we don't use "OK" because # concept doesn't quite fit here: self.btnbox.rejected.connect(self.shutdown_actions) self.generate_btn.clicked.connect(self.start_generate) + self.qr_btn.clicked.connect(self.open_qr_code_popup) # does not trigger cancel_fn callback: self.close_btn.clicked.connect(self.close) return layout + + def open_qr_code_popup(self): + bip21_uri = self.bip21_widget.text() + if bip21_uri: + parsed_uri = decode_bip21_uri(bip21_uri) + popup = QRCodePopup(self, parsed_uri['address'], bip21_uri) + popup.show()