qt: qrreader: keep both old and new toolchain; try to abstract it away
This commit is contained in:
@@ -53,7 +53,7 @@ datas += collect_data_files('bitbox02')
|
||||
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
|
||||
a = Analysis([home+'run_electrum',
|
||||
home+'electrum/gui/qt/main_window.py',
|
||||
home+'electrum/gui/qt/qrreader/camera_dialog.py',
|
||||
home+'electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py',
|
||||
home+'electrum/gui/text.py',
|
||||
home+'electrum/util.py',
|
||||
home+'electrum/wallet.py',
|
||||
|
||||
@@ -47,35 +47,30 @@ info "Building $pkgname..."
|
||||
if ! [ -r config.status ] ; then
|
||||
if [ "$BUILD_TYPE" = "wine" ] ; then
|
||||
# windows target
|
||||
./configure \
|
||||
$AUTOCONF_FLAGS \
|
||||
--prefix="$here/$pkgname/dist" \
|
||||
AUTOCONF_FLAGS="$AUTOCONF_FLAGS \
|
||||
--with-x=no \
|
||||
--enable-pthread=no \
|
||||
--enable-doc=no \
|
||||
--enable-video=yes \
|
||||
--with-directshow=yes \
|
||||
--with-jpeg=no \
|
||||
--with-python=no \
|
||||
--with-gtk=no \
|
||||
--with-qt=no \
|
||||
--with-java=no \
|
||||
--with-imagemagick=no \
|
||||
--with-dbus=no \
|
||||
--enable-codes=qrcode \
|
||||
--disable-dependency-tracking \
|
||||
--disable-static \
|
||||
--enable-shared || fail "Could not configure $pkgname. Please make sure you have a C compiler installed and try again."
|
||||
--with-directshow=yes \
|
||||
--disable-dependency-tracking"
|
||||
elif [ $(uname) == "Darwin" ]; then
|
||||
# macos target
|
||||
AUTOCONF_FLAGS="$AUTOCONF_FLAGS \
|
||||
--with-x=no \
|
||||
--enable-video=no \
|
||||
--with-jpeg=no"
|
||||
else
|
||||
# linux target
|
||||
AUTOCONF_FLAGS="$AUTOCONF_FLAGS \
|
||||
--with-x=yes \
|
||||
--enable-video=yes \
|
||||
--with-jpeg=yes"
|
||||
fi
|
||||
./configure \
|
||||
$AUTOCONF_FLAGS \
|
||||
--prefix="$here/$pkgname/dist" \
|
||||
--with-x=yes \
|
||||
--enable-pthread=no \
|
||||
--enable-doc=no \
|
||||
--enable-video=yes \
|
||||
--with-jpeg=yes \
|
||||
--with-python=no \
|
||||
--with-gtk=no \
|
||||
--with-qt=no \
|
||||
@@ -86,7 +81,6 @@ info "Building $pkgname..."
|
||||
--disable-static \
|
||||
--enable-shared || fail "Could not configure $pkgname. Please make sure you have a C compiler installed and try again."
|
||||
fi
|
||||
fi
|
||||
make -j4 || fail "Could not build $pkgname"
|
||||
make install || fail "Could not install $pkgname"
|
||||
. "$here/$pkgname/dist/lib/libzbar.la"
|
||||
|
||||
@@ -96,7 +96,7 @@ binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
|
||||
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
|
||||
a = Analysis([electrum+ MAIN_SCRIPT,
|
||||
electrum+'electrum/gui/qt/main_window.py',
|
||||
electrum+'electrum/gui/qt/qrreader/camera_dialog.py',
|
||||
electrum+'electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py',
|
||||
electrum+'electrum/gui/text.py',
|
||||
electrum+'electrum/util.py',
|
||||
electrum+'electrum/wallet.py',
|
||||
|
||||
@@ -275,28 +275,6 @@ class ElectrumGui(Logger):
|
||||
network_updated_signal_obj=self.network_updated_signal_obj)
|
||||
self.network_dialog.show()
|
||||
|
||||
@staticmethod
|
||||
def warn_if_cant_import_qrreader(parent, *, show_warning=True) -> bool:
|
||||
"""Checks it QR reading from camera is possible. It can fail on a
|
||||
system lacking QtMultimedia. This can be removed in the future when
|
||||
we are unlikely to encounter Qt5 installations that are missing
|
||||
QtMultimedia
|
||||
"""
|
||||
try:
|
||||
from .qrreader import QrReaderCameraDialog
|
||||
except ImportError as e:
|
||||
if show_warning:
|
||||
icon = QMessageBox.Warning
|
||||
title = _("QR Reader Error")
|
||||
message = _("QR reader failed to load. This may happen if "
|
||||
"you are using an older version of PyQt5.") + "\n\n" + str(e)
|
||||
if isinstance(parent, MessageBoxMixin):
|
||||
parent.msg_box(title=title, text=message, icon=icon, parent=None)
|
||||
else:
|
||||
custom_message_box(title=title, text=message, icon=icon, parent=parent)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _create_window_for_wallet(self, wallet):
|
||||
w = ElectrumWindow(self, wallet)
|
||||
self.windows.append(w)
|
||||
|
||||
@@ -103,6 +103,7 @@ from .channels_list import ChannelsList
|
||||
from .confirm_tx_dialog import ConfirmTxDialog
|
||||
from .transaction_dialog import PreviewTxDialog
|
||||
from .rbf_dialog import BumpFeeDialog, DSCancelDialog
|
||||
from .qrreader import scan_qrcode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ElectrumGui
|
||||
@@ -2820,26 +2821,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
self.show_error("failed to import backup" + '\n' + str(e))
|
||||
return
|
||||
|
||||
# Due to the asynchronous nature of the qr reader we need to keep the
|
||||
# dialog instance as member variable to prevent reentrancy/multiple ones
|
||||
# from being presented at once.
|
||||
_qr_dialog = None
|
||||
|
||||
def read_tx_from_qrcode(self):
|
||||
if self._qr_dialog:
|
||||
self.logger.warning("QR dialog is already presented, ignoring.")
|
||||
return
|
||||
if self.gui_object.warn_if_cant_import_qrreader(self):
|
||||
return
|
||||
from .qrreader import QrReaderCameraDialog, CameraError, MissingQrDetectionLib
|
||||
self._qr_dialog = None
|
||||
try:
|
||||
self._qr_dialog = QrReaderCameraDialog(parent=self.top_level_window(), config=self.config)
|
||||
|
||||
def _on_qr_reader_finished(success: bool, error: str, data):
|
||||
if self._qr_dialog:
|
||||
self._qr_dialog.deleteLater()
|
||||
self._qr_dialog = None
|
||||
def cb(success: bool, error: str, data):
|
||||
if not success:
|
||||
if error:
|
||||
self.show_error(error)
|
||||
@@ -2859,15 +2842,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
return
|
||||
self.show_transaction(tx)
|
||||
|
||||
self._qr_dialog.qr_finished.connect(_on_qr_reader_finished)
|
||||
self._qr_dialog.start_scan(self.config.get_video_device())
|
||||
except (MissingQrDetectionLib, CameraError) as e:
|
||||
self._qr_dialog = None
|
||||
self.show_error(str(e))
|
||||
except Exception as e:
|
||||
self.logger.exception('camera error')
|
||||
self._qr_dialog = None
|
||||
self.show_error(repr(e))
|
||||
scan_qrcode(parent=self.top_level_window(), config=self.config, callback=cb)
|
||||
|
||||
def read_tx_from_file(self) -> Optional[Transaction]:
|
||||
fileName = getOpenFileName(
|
||||
|
||||
@@ -1,30 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2021 The Electrum developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
||||
#
|
||||
# Electron Cash - lightweight Bitcoin client
|
||||
# Copyright (C) 2019 Axel Gembe <derago@gmail.com>
|
||||
# We have two toolchains to scan qr codes:
|
||||
# 1. access camera via QtMultimedia, take picture, feed picture to zbar
|
||||
# 2. let zbar handle whole flow (including accessing the camera)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
# notes:
|
||||
# - zbar needs to be compiled with platform-dependent extra config options to be able
|
||||
# to access the camera
|
||||
# - zbar fails to access the camera on macOS
|
||||
# - qtmultimedia seems to support more cameras on Windows than zbar
|
||||
# - qtmultimedia is often not packaged with PyQt5
|
||||
# in particular, on debian, you need both "python3-pyqt5" and "python3-pyqt5.qtmultimedia"
|
||||
# - older versions of qtmultimedia don't seem to work reliably
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
# Considering the above, we use QtMultimedia for Windows and macOS, as there
|
||||
# most users run our binaries where we can make sure the packaged versions work well.
|
||||
# On Linux where many people run from source, we use zbar.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# Note: this module is safe to import on all platforms.
|
||||
|
||||
import sys
|
||||
from typing import Callable, Optional, TYPE_CHECKING, Mapping
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox, QWidget
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import UserFacingException
|
||||
from electrum.logging import get_logger
|
||||
|
||||
from electrum.gui.qt.util import MessageBoxMixin, custom_message_box
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum.simple_config import SimpleConfig
|
||||
|
||||
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
|
||||
def scan_qrcode(
|
||||
*,
|
||||
parent: Optional[QWidget],
|
||||
config: 'SimpleConfig',
|
||||
callback: Callable[[bool, str, Optional[str]], None],
|
||||
) -> None:
|
||||
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 find_system_cameras() -> Mapping[str, str]:
|
||||
"""Returns a camera_description -> camera_path map."""
|
||||
if sys.platform == 'darwin' or sys.platform in ('windows', 'win32'):
|
||||
try:
|
||||
from .qtmultimedia import find_system_cameras
|
||||
except ImportError as e:
|
||||
return {}
|
||||
else:
|
||||
return find_system_cameras()
|
||||
else: # desktop Linux and similar
|
||||
from electrum import qrscanner
|
||||
return qrscanner.find_system_cameras()
|
||||
|
||||
|
||||
# --- Internals below (not part of external API)
|
||||
|
||||
def _scan_qrcode_using_zbar(
|
||||
*,
|
||||
parent: Optional[QWidget],
|
||||
config: 'SimpleConfig',
|
||||
callback: Callable[[bool, str, Optional[str]], None],
|
||||
) -> None:
|
||||
from electrum import qrscanner
|
||||
data = None
|
||||
try:
|
||||
data = qrscanner.scan_barcode(config.get_video_device())
|
||||
except UserFacingException as e:
|
||||
success = False
|
||||
error = str(e)
|
||||
except BaseException as e:
|
||||
_logger.exception('camera error')
|
||||
success = False
|
||||
error = repr(e)
|
||||
else:
|
||||
success = True
|
||||
error = ""
|
||||
callback(success, error, data)
|
||||
|
||||
|
||||
# Use a global to prevent multiple QR dialogs created simultaneously
|
||||
_qr_dialog = None
|
||||
|
||||
|
||||
def _scan_qrcode_using_qtmultimedia(
|
||||
*,
|
||||
parent: Optional[QWidget],
|
||||
config: 'SimpleConfig',
|
||||
callback: Callable[[bool, str, Optional[str]], None],
|
||||
) -> None:
|
||||
try:
|
||||
from .qtmultimedia import QrReaderCameraDialog, CameraError, MissingQrDetectionLib
|
||||
except ImportError as e:
|
||||
icon = QMessageBox.Warning
|
||||
title = _("QR Reader Error")
|
||||
message = _("QR reader failed to load. This may happen if "
|
||||
"you are using an older version of PyQt5.") + "\n\n" + str(e)
|
||||
if isinstance(parent, MessageBoxMixin):
|
||||
parent.msg_box(title=title, text=message, icon=icon, parent=None)
|
||||
else:
|
||||
custom_message_box(title=title, text=message, icon=icon, parent=parent)
|
||||
return
|
||||
|
||||
global _qr_dialog
|
||||
if _qr_dialog:
|
||||
_logger.warning("QR dialog is already presented, ignoring.")
|
||||
return
|
||||
_qr_dialog = None
|
||||
try:
|
||||
_qr_dialog = QrReaderCameraDialog(parent=parent, config=config)
|
||||
|
||||
def _on_qr_reader_finished(success: bool, error: str, data):
|
||||
global _qr_dialog
|
||||
if _qr_dialog:
|
||||
_qr_dialog.deleteLater()
|
||||
_qr_dialog = None
|
||||
callback(success, error, data)
|
||||
|
||||
_qr_dialog.qr_finished.connect(_on_qr_reader_finished)
|
||||
_qr_dialog.start_scan(config.get_video_device())
|
||||
except (MissingQrDetectionLib, CameraError) as e:
|
||||
_qr_dialog = None
|
||||
callback(False, str(e), None)
|
||||
except Exception as e:
|
||||
_logger.exception('camera error')
|
||||
_qr_dialog = None
|
||||
callback(False, repr(e), None)
|
||||
|
||||
from .camera_dialog import (QrReaderCameraDialog, CameraError, NoCamerasFound,
|
||||
NoCameraResolutionsFound, MissingQrDetectionLib)
|
||||
from .validator import (QrReaderValidatorResult, AbstractQrReaderValidator,
|
||||
QrReaderValidatorCounting, QrReaderValidatorColorizing,
|
||||
QrReaderValidatorStrong, QrReaderValidatorCounted)
|
||||
|
||||
39
electrum/gui/qt/qrreader/qtmultimedia/__init__.py
Normal file
39
electrum/gui/qt/qrreader/qtmultimedia/__init__.py
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Electron Cash - lightweight Bitcoin client
|
||||
# Copyright (C) 2019 Axel Gembe <derago@gmail.com>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from typing import Mapping
|
||||
|
||||
from .camera_dialog import (QrReaderCameraDialog, CameraError, NoCamerasFound,
|
||||
NoCameraResolutionsFound, MissingQrDetectionLib)
|
||||
from .validator import (QrReaderValidatorResult, AbstractQrReaderValidator,
|
||||
QrReaderValidatorCounting, QrReaderValidatorColorizing,
|
||||
QrReaderValidatorStrong, QrReaderValidatorCounted)
|
||||
|
||||
|
||||
def find_system_cameras() -> Mapping[str, str]:
|
||||
"""Returns a camera_description -> camera_path map."""
|
||||
from PyQt5.QtMultimedia import QCameraInfo
|
||||
system_cameras = QCameraInfo.availableCameras()
|
||||
return {cam.description(): cam.deviceName() for cam in system_cameras}
|
||||
@@ -39,12 +39,14 @@ from electrum.i18n import _
|
||||
from electrum.qrreader import get_qr_reader, QrCodeResult
|
||||
from electrum.logging import Logger
|
||||
|
||||
from electrum.gui.qt.util import MessageBoxMixin, FixedAspectRatioLayout, ImageGraphicsEffect
|
||||
|
||||
from .video_widget import QrReaderVideoWidget
|
||||
from .video_overlay import QrReaderVideoOverlay
|
||||
from .video_surface import QrReaderVideoSurface
|
||||
from .crop_blur_effect import QrReaderCropBlurEffect
|
||||
from .validator import AbstractQrReaderValidator, QrReaderValidatorCounted, QrReaderValidatorResult
|
||||
from ..util import MessageBoxMixin, FixedAspectRatioLayout, ImageGraphicsEffect
|
||||
|
||||
|
||||
class CameraError(RuntimeError):
|
||||
''' Base class of the camera-related error conditions. '''
|
||||
@@ -7,6 +7,7 @@ from electrum.util import UserFacingException
|
||||
from electrum.logging import Logger
|
||||
|
||||
from .util import ButtonsTextEdit, MessageBoxMixin, ColorScheme, getOpenFileName
|
||||
from .qrreader import scan_qrcode
|
||||
|
||||
|
||||
class ShowQRTextEdit(ButtonsTextEdit):
|
||||
@@ -72,26 +73,8 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin, Logger):
|
||||
else:
|
||||
self.setText(data)
|
||||
|
||||
# Due to the asynchronous nature of the qr reader we need to keep the
|
||||
# dialog instance as member variable to prevent reentrancy/multiple ones
|
||||
# from being presented at once.
|
||||
qr_dialog = None
|
||||
|
||||
def qr_input(self, *, callback=None) -> None:
|
||||
if self.qr_dialog:
|
||||
self.logger.warning("QR dialog is already presented, ignoring.")
|
||||
return
|
||||
from . import ElectrumGui
|
||||
if ElectrumGui.warn_if_cant_import_qrreader(self):
|
||||
return
|
||||
from .qrreader import QrReaderCameraDialog, CameraError, MissingQrDetectionLib
|
||||
try:
|
||||
self.qr_dialog = QrReaderCameraDialog(parent=self.top_level_window(), config=self.config)
|
||||
|
||||
def _on_qr_reader_finished(success: bool, error: str, data):
|
||||
if self.qr_dialog:
|
||||
self.qr_dialog.deleteLater()
|
||||
self.qr_dialog = None
|
||||
def cb(success: bool, error: str, data):
|
||||
if not success:
|
||||
if error:
|
||||
self.show_error(error)
|
||||
@@ -106,15 +89,7 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin, Logger):
|
||||
if callback and success:
|
||||
callback(data)
|
||||
|
||||
self.qr_dialog.qr_finished.connect(_on_qr_reader_finished)
|
||||
self.qr_dialog.start_scan(self.config.get_video_device())
|
||||
except (MissingQrDetectionLib, CameraError) as e:
|
||||
self.qr_dialog = None
|
||||
self.show_error(str(e))
|
||||
except Exception as e:
|
||||
self.logger.exception('camera error')
|
||||
self.qr_dialog = None
|
||||
self.show_error(repr(e))
|
||||
scan_qrcode(parent=self.top_level_window(), config=self.config, callback=cb)
|
||||
|
||||
def contextMenuEvent(self, e):
|
||||
m = self.createStandardContextMenu()
|
||||
|
||||
@@ -220,18 +220,10 @@ class SettingsDialog(WindowModalDialog):
|
||||
msg = (_("For scanning QR codes.") + "\n"
|
||||
+ _("Install the zbar package to enable this."))
|
||||
qr_label = HelpLabel(_('Video Device') + ':', msg)
|
||||
system_cameras = []
|
||||
try:
|
||||
from PyQt5.QtMultimedia import QCameraInfo
|
||||
system_cameras = QCameraInfo.availableCameras()
|
||||
except ImportError as e:
|
||||
# Older Qt or missing libs -- disable GUI control and inform user why
|
||||
qr_combo.setEnabled(False)
|
||||
qr_label.setEnabled(False)
|
||||
qr_combo.setToolTip(_("Unable to probe for cameras on this system. QtMultimedia is likely missing."))
|
||||
qr_label.setToolTip(qr_combo.toolTip())
|
||||
for cam in system_cameras:
|
||||
qr_combo.addItem(cam.description(), cam.deviceName())
|
||||
from .qrreader import find_system_cameras
|
||||
system_cameras = find_system_cameras()
|
||||
for cam_desc, cam_path in system_cameras.items():
|
||||
qr_combo.addItem(cam_desc, cam_path)
|
||||
index = qr_combo.findData(self.config.get("video_device"))
|
||||
qr_combo.setCurrentIndex(index)
|
||||
on_video_device = lambda x: self.config.set_key("video_device", qr_combo.itemData(x), True)
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
from typing import Optional
|
||||
from typing import Optional, Mapping
|
||||
|
||||
from .util import UserFacingException
|
||||
from .i18n import _
|
||||
@@ -82,7 +82,7 @@ def scan_barcode(device='', timeout=-1, display=True, threaded=False) -> Optiona
|
||||
return data.decode('utf8')
|
||||
|
||||
|
||||
def _find_system_cameras():
|
||||
def find_system_cameras() -> Mapping[str, str]:
|
||||
device_root = "/sys/class/video4linux"
|
||||
devices = {} # Name -> device
|
||||
if os.path.exists(device_root):
|
||||
|
||||
Reference in New Issue
Block a user