17 changed files with 251 additions and 184 deletions
@ -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 |
# We have two toolchains to scan qr codes: |
||||||
# Copyright (C) 2019 Axel Gembe <derago@gmail.com> |
# 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 |
# notes: |
||||||
# obtaining a copy of this software and associated documentation files |
# - zbar needs to be compiled with platform-dependent extra config options to be able |
||||||
# (the "Software"), to deal in the Software without restriction, |
# to access the camera |
||||||
# including without limitation the rights to use, copy, modify, merge, |
# - zbar fails to access the camera on macOS |
||||||
# publish, distribute, sublicense, and/or sell copies of the Software, |
# - qtmultimedia seems to support more cameras on Windows than zbar |
||||||
# and to permit persons to whom the Software is furnished to do so, |
# - qtmultimedia is often not packaged with PyQt5 |
||||||
# subject to the following conditions: |
# 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 |
# Considering the above, we use QtMultimedia for Windows and macOS, as there |
||||||
# included in all copies or substantial portions of the Software. |
# 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, |
# Note: this module is safe to import on all platforms. |
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
import sys |
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
from typing import Callable, Optional, TYPE_CHECKING, Mapping |
||||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
from PyQt5.QtWidgets import QMessageBox, QWidget |
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||||
# SOFTWARE. |
from electrum.i18n import _ |
||||||
|
from electrum.util import UserFacingException |
||||||
from .camera_dialog import (QrReaderCameraDialog, CameraError, NoCamerasFound, |
from electrum.logging import get_logger |
||||||
NoCameraResolutionsFound, MissingQrDetectionLib) |
|
||||||
from .validator import (QrReaderValidatorResult, AbstractQrReaderValidator, |
from electrum.gui.qt.util import MessageBoxMixin, custom_message_box |
||||||
QrReaderValidatorCounting, QrReaderValidatorColorizing, |
|
||||||
QrReaderValidatorStrong, QrReaderValidatorCounted) |
|
||||||
|
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) |
||||||
|
|
||||||
|
|||||||
@ -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} |
||||||
Loading…
Reference in new issue