diff --git a/README.md b/README.md index 4cd5dae..3fcbe37 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,22 @@ GUI version of CLI [multiwallet](https://twitter.com/mflaxman/status/13215030367 ## Install +### Pillow (for QR Codes) + +Mac: +``` +$ brew install libtiff libjpeg webp little-cms2 +``` + +Ubuntu: +``` +$ sudo apt-get install python3-dev python3-setuptools apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk libharfbuzz-dev libfribidi-dev libxcb1-dev +``` + +For more see instructions [here](https://pillow.readthedocs.io/en/latest/installation.html) + +### Multiwallet + #### Easy Downloadable binary link here (MacOS only): diff --git a/multiwallet_gui/helper.py b/multiwallet_gui/helper.py index 4c2784e..4caeef5 100644 --- a/multiwallet_gui/helper.py +++ b/multiwallet_gui/helper.py @@ -1,4 +1,35 @@ -from PyQt5.QtWidgets import QMessageBox +import qrcode +import re + +from io import BytesIO +from PyQt5.QtWidgets import QMessageBox, QDialog, QVBoxLayout, QLabel +from PyQt5.QtGui import QPixmap, QIcon + + +def strip_html(data): + # https://stackoverflow.com/questions/3398852/using-python-remove-html-tags-formatting-from-a-string + p = re.compile(r"<.*?>") + return p.sub("", data) + + +def create_qr_icon(): + return QIcon("multiwallet_gui/images/qr.png") + + +def create_qt_pixmap_qr(text): + """ + How to use this: + label.setText("") + label.setPixmap(create_qt_pixmap_qr(text="foo")) + + https://stackoverflow.com/a/58251630/1754586 + """ + buf = BytesIO() + img = qrcode.make(text) + img.save(buf, "PNG") + qt_pixmap = QPixmap() + qt_pixmap.loadFromData(buf.getvalue(), "PNG") + return qt_pixmap def _clean_submisission(string): @@ -20,6 +51,45 @@ def _msgbox_err(main_text=None, informative_text=None, detailed_text=None): msg.exec_() +class QRPopup(QDialog): + def __init__(self, window_title, qr_text): + super().__init__() + + self.setWindowTitle(window_title) + + self.setMaximumHeight(16777215) + self.setMaximumWidth(16777215) + + self.qr_text = qr_text + + self.vbox = QVBoxLayout() + + self.labelImage = QLabel() + self.labelImage.setMaximumHeight(16777215) + self.labelImage.setMaximumWidth(16777215) + + self._set_pixmap() + + self.vbox.addWidget(self.labelImage) + self.setLayout(self.vbox) + + self.show() + + def _set_pixmap(self): + self.pixmap = create_qt_pixmap_qr(text=self.qr_text).scaledToHeight( + self.height() * 0.9 + ) + self.labelImage.setPixmap(self.pixmap) + + def resizeEvent(self, event): + self.pixmap = self._set_pixmap() + + +def qr_dialog(qr_text, window_title): + dialog = QRPopup(qr_text=qr_text, window_title=strip_html(window_title)) + return dialog.exec_() + + def _is_libsec_enabled(): # TODO: move to buidl try: diff --git a/multiwallet_gui/images/qr.png b/multiwallet_gui/images/qr.png new file mode 100644 index 0000000..6cd9cc0 Binary files /dev/null and b/multiwallet_gui/images/qr.png differ diff --git a/multiwallet_gui/seedpicker.py b/multiwallet_gui/seedpicker.py index e753a7d..ef35e01 100644 --- a/multiwallet_gui/seedpicker.py +++ b/multiwallet_gui/seedpicker.py @@ -4,8 +4,10 @@ BITCOIN_NETWORK_TOOLTIP, BITCOIN_TESTNET_TOOLTIP, BITCOIN_MAINNET_TOOLTIP, + create_qr_icon, _clean_submisission, _msgbox_err, + qr_dialog, ) from PyQt5.QtWidgets import ( @@ -73,7 +75,6 @@ def __init__(self): self.testnet_button.setChecked(True) self.firstWordsSubmitButton = QPushButton("Calculate Full Seed") - self.firstWordsSubmitButton.setText("Calculate Full Seed") self.firstWordsSubmitButton.clicked.connect(self.process_submit) self.privResultsLabel = QLabel("") @@ -88,9 +89,14 @@ def __init__(self): self.pubResultsLabel.setToolTip( "For export to your online computer and eventaully other hardware wallets. This represents your bitcoin public keys, which are neccesary-but-not-sufficient to spend your bitcoin." ) - self.pubResultsEdit = QPlainTextEdit("") - self.pubResultsEdit.setReadOnly(True) - self.pubResultsEdit.setHidden(True) + self.pubResultsROEdit = QPlainTextEdit("") + self.pubResultsROEdit.setReadOnly(True) + self.pubResultsROEdit.setHidden(True) + + self.qrButton = QPushButton() + self.qrButton.setText("QR") + self.qrButton.setHidden(True) + self.qrButton.clicked.connect(self.make_qr_popup) for widget in ( self.firstWordsLabel, @@ -102,7 +108,8 @@ def __init__(self): self.privResultsLabel, self.privResultsEdit, self.pubResultsLabel, - self.pubResultsEdit, + self.pubResultsROEdit, + self.qrButton, ): self.layout.addWidget(widget) @@ -116,9 +123,11 @@ def process_submit(self): self.privResultsEdit.clear() self.privResultsEdit.setHidden(True) self.privResultsLabel.setText("") - self.pubResultsEdit.clear() - self.pubResultsEdit.setHidden(True) + self.pubResultsROEdit.clear() + self.pubResultsROEdit.setHidden(True) self.pubResultsLabel.setText("") + self.qrButton.setHidden(True) + self.qrButton.setText("") # TODO: why setText and not hide? first_words = _clean_submisission(self.firstWordsEdit.toPlainText()) @@ -182,8 +191,19 @@ def process_submit(self): self.privResultsEdit.setHidden(False) self.privResultsEdit.appendPlainText("\n".join(priv_to_display)) - self.pubResultsLabel.setText( + pubkey_results_text = ( f"PUBLIC KEY INFO - {'Testnet' if self.IS_TESTNET else 'Mainnet'}" ) - self.pubResultsEdit.setHidden(False) - self.pubResultsEdit.appendPlainText("\n".join(pub_to_display)) + self.pubResultsLabel.setText(pubkey_results_text) + self.pubResultsROEdit.setHidden(False) + self.pubResultsROEdit.appendPlainText("\n".join(pub_to_display)) + + self.qrButton.setHidden(False) + self.qrButton.setText("QR") + self.qrButton.setIcon(create_qr_icon()) + + def make_qr_popup(self): + qr_dialog( + qr_text=self.pubResultsROEdit.toPlainText(), + window_title=self.pubResultsLabel.text(), + ) diff --git a/requirements.txt b/requirements.txt index 8341c87..1724d12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ -buidl==0.2.1 +buidl==0.2.2 PyQt5==5.15.1 +qrcode==6.1 +Pillow==8.0.1 diff --git a/setup.py b/setup.py index afb62ac..a842264 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name="multiwallet", - version="0.3.6", + version="0.3.7", author="Michael Flaxman", author_email="multiwallet@michaelflaxman.com", description="Stateless multisig bitcoin wallet",