From b7b53e56bc9bfab5eefb49bd1f0404555a57bb47 Mon Sep 17 00:00:00 2001 From: Jonas Lundqvist Date: Wed, 1 Jun 2022 18:38:13 +0200 Subject: [PATCH] Qt PayToEdit: add option to scan QR code from screen(shot) this ports the following commits: https://github.com/Electron-Cash/Electron-Cash/commit/448376e4410951f12f53ce42b59bdfe856afc66b https://github.com/Electron-Cash/Electron-Cash/commit/6053f6f696c3f65a93a84e570454e8a1d8c5f490 --- electrum/gui/qt/qrreader/__init__.py | 19 +++++++++++-- electrum/gui/qt/qrtextedit.py | 8 ++++-- electrum/gui/qt/util.py | 40 ++++++++++++++++++++++++---- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/electrum/gui/qt/qrreader/__init__.py b/electrum/gui/qt/qrreader/__init__.py index 260f8ecaa..8e39f21f3 100644 --- a/electrum/gui/qt/qrreader/__init__.py +++ b/electrum/gui/qt/qrreader/__init__.py @@ -22,14 +22,15 @@ # Note: this module is safe to import on all platforms. import sys -from typing import Callable, Optional, TYPE_CHECKING, Mapping +from typing import Callable, Optional, TYPE_CHECKING, Mapping, Sequence from PyQt5.QtWidgets import QMessageBox, QWidget +from PyQt5.QtGui import QImage from electrum.i18n import _ from electrum.util import UserFacingException from electrum.logging import get_logger -from electrum.qrreader import MissingQrDetectionLib +from electrum.qrreader import get_qr_reader, QrCodeResult, MissingQrDetectionLib from electrum.gui.qt.util import MessageBoxMixin, custom_message_box @@ -47,12 +48,26 @@ def scan_qrcode( config: 'SimpleConfig', callback: Callable[[bool, str, Optional[str]], None], ) -> None: + """Scans QR code using camera.""" if sys.platform == 'darwin' or sys.platform in ('windows', 'win32'): _scan_qrcode_using_qtmultimedia(parent=parent, config=config, callback=callback) else: # desktop Linux and similar _scan_qrcode_using_zbar(parent=parent, config=config, callback=callback) +def scan_qr_from_image(image: QImage) -> Sequence[QrCodeResult]: + """Might raise exception: MissingQrDetectionLib.""" + qr_reader = get_qr_reader() + image_y800 = image.convertToFormat(QImage.Format_Grayscale8) + res = qr_reader.read_qr_code( + image_y800.constBits().__int__(), image_y800.byteCount(), + image_y800.bytesPerLine(), + image_y800.width(), + image_y800.height() + ) + return res + + def find_system_cameras() -> Mapping[str, str]: """Returns a camera_description -> camera_path map.""" if sys.platform == 'darwin' or sys.platform in ('windows', 'win32'): diff --git a/electrum/gui/qt/qrtextedit.py b/electrum/gui/qt/qrtextedit.py index 7a23ef5ee..3cd404658 100644 --- a/electrum/gui/qt/qrtextedit.py +++ b/electrum/gui/qt/qrtextedit.py @@ -30,7 +30,9 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin): def contextMenuEvent(self, e): m = self.createStandardContextMenu() - m.addAction(_("Read QR code"), self.on_qr_input_btn) + m.addSeparator() + m.addAction(_("Read QR code from camera"), self.on_qr_from_camera_input_btn) + m.addAction(_("Read QR code from screen"), self.on_qr_from_screenshot_input_btn) m.exec_(e.globalPos()) @@ -46,6 +48,8 @@ class ScanShowQRTextEdit(ButtonsTextEdit, MessageBoxMixin): def contextMenuEvent(self, e): m = self.createStandardContextMenu() - m.addAction(_("Read QR code"), self.on_qr_input_btn) + m.addSeparator() + m.addAction(_("Read QR code from camera"), self.on_qr_from_camera_input_btn) + m.addAction(_("Read QR code from screen"), self.on_qr_from_screenshot_input_btn) m.addAction(_("Show as QR code"), self.on_qr_show_btn) m.exec_(e.globalPos()) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 1b3b6635f..f256442f7 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -30,6 +30,7 @@ from electrum.i18n import _, languages from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path from electrum.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED from electrum.logging import Logger +from electrum.qrreader import MissingQrDetectionLib if TYPE_CHECKING: from .main_window import ElectrumWindow @@ -879,7 +880,7 @@ class OverlayControlMixin: # The old code positioned the items the other way around, so we just insert at position 0 instead self.overlay_layout.insertWidget(0, widget) - def addButton(self, icon_name: str, on_click, tooltip: str) -> QAbstractButton: + def addButton(self, icon_name: str, on_click, tooltip: str) -> QPushButton: button = QPushButton(self.overlay_widget) button.setToolTip(tooltip) button.setIcon(read_QIcon(icon_name)) @@ -943,7 +944,7 @@ class OverlayControlMixin: ): if setText is None: setText = self.setText - def qr_input(): + def qr_from_camera_input() -> None: def cb(success: bool, error: str, data): if not success: if error: @@ -960,10 +961,39 @@ class OverlayControlMixin: from .qrreader import scan_qrcode scan_qrcode(parent=self, config=config, callback=cb) + def qr_from_screenshot_input() -> None: + from .qrreader import scan_qr_from_image + scanned_qr = None + for screen in QApplication.instance().screens(): + try: + scan_result = scan_qr_from_image(screen.grabWindow(0).toImage()) + except MissingQrDetectionLib as e: + show_error(_("Unable to scan image.") + "\n" + repr(e)) + return + if len(scan_result) > 0: + if (scanned_qr is not None) or len(scan_result) > 1: + show_error(_("More than one QR code was found on the screen.")) + return + scanned_qr = scan_result + if scanned_qr is None: + show_error(_("No QR code was found on the screen.")) + return + data = scanned_qr[0].data + if allow_multi: + new_text = self.text() + data + '\n' + else: + new_text = data + setText(new_text) + icon = "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png" - self.addButton(icon, qr_input, _("Read QR code")) - # side-effect: we export this method: - self.on_qr_input_btn = qr_input + btn = self.addButton(icon, lambda: None, _("Read QR code")) + menu = QMenu() + menu.addAction(_("Read QR code from camera"), qr_from_camera_input) + menu.addAction(_("Read QR code from screen"), qr_from_screenshot_input) + btn.setMenu(menu) + # side-effect: we export these methods: + self.on_qr_from_camera_input_btn = qr_from_camera_input + self.on_qr_from_screenshot_input_btn = qr_from_screenshot_input def add_file_input_button( self,