SomberNight
4 years ago
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 |
|||
# 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. |
|||
|
|||
from .camera_dialog import (QrReaderCameraDialog, CameraError, NoCamerasFound, |
|||
NoCameraResolutionsFound, MissingQrDetectionLib) |
|||
from .validator import (QrReaderValidatorResult, AbstractQrReaderValidator, |
|||
QrReaderValidatorCounting, QrReaderValidatorColorizing, |
|||
QrReaderValidatorStrong, QrReaderValidatorCounted) |
|||
# 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) |
|||
|
|||
|
@ -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