qt gui: qrreader: macos: add runtime requesting of camera permission
- we were already
- statically declaring "NSCameraUsageDescription" in the Info.plist
- this used to be enough in the past
- codesigning with an entitlements.plist that declares "com.apple.security.device.camera"
- I believe this is required for notarization to pass for an app that declares "NSCameraUsageDescription".
- previously this was enough to access the camera IIRC
- in any case, if the user goes into "System Preferences">"Security & Privacy", they can manually modify permissions there
- now with this commit, we on-demand trigger at runtime the OS permission prompt
- making it much easier for users to actually use the camera
- note: if you run via the Terminal, e.g. `$ $HOME/Desktop/Electrum.app/Contents/MacOS/run_electrum`,
then it will be the Terminal app that requires the camera permission. If you run by double-clicking Electrum.app,
then Electrum.app will be the "app" that requires the camera permission.
- `$ tccutil reset Camera` can be used to clear permissions for all apps (back to the Qt::PermissionStatus::Undetermined state)
ref https://doc.qt.io/qt-6.5/qcoreapplication.html#requestPermission-1
This commit is contained in:
@@ -97,7 +97,7 @@ from .update_checker import UpdateCheck, UpdateCheckThread
|
||||
from .channels_list import ChannelsList
|
||||
from .confirm_tx_dialog import ConfirmTxDialog
|
||||
from .rbf_dialog import BumpFeeDialog, DSCancelDialog
|
||||
from .qrreader import scan_qrcode
|
||||
from .qrreader import scan_qrcode_from_camera
|
||||
from .swap_dialog import SwapDialog, InvalidSwapParameters
|
||||
from .balance_dialog import (BalanceToolButton, COLOR_FROZEN, COLOR_UNMATURED, COLOR_UNCONFIRMED, COLOR_CONFIRMED,
|
||||
COLOR_LIGHTNING, COLOR_FROZEN_LIGHTNING)
|
||||
@@ -2313,7 +2313,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
return
|
||||
self.show_transaction(tx)
|
||||
|
||||
scan_qrcode(parent=self.top_level_window(), config=self.config, callback=cb)
|
||||
scan_qrcode_from_camera(parent=self.top_level_window(), config=self.config, callback=cb)
|
||||
|
||||
def read_tx_from_file(self) -> Optional[Transaction]:
|
||||
fileName = getOpenFileName(
|
||||
|
||||
@@ -26,7 +26,8 @@ from typing import Callable, Optional, TYPE_CHECKING, Mapping, Sequence
|
||||
|
||||
from PyQt6.QtWidgets import QMessageBox, QWidget
|
||||
from PyQt6.QtGui import QImage, QPainter, QColor
|
||||
from PyQt6.QtCore import QRect
|
||||
from PyQt6.QtCore import QRect, QCoreApplication
|
||||
from PyQt6 import QtCore
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import UserFacingException
|
||||
@@ -43,18 +44,24 @@ if TYPE_CHECKING:
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
|
||||
def scan_qrcode(
|
||||
def scan_qrcode_from_camera(
|
||||
*,
|
||||
parent: Optional[QWidget],
|
||||
config: 'SimpleConfig',
|
||||
callback: Callable[[bool, str, Optional[str]], None],
|
||||
) -> None:
|
||||
"""Scans QR code using camera."""
|
||||
"""Scans QR code using camera. It handles requesting camera access permission from the OS if needed."""
|
||||
assert parent is None or isinstance(parent, QWidget), f"parent should be a QWidget, not {parent!r}"
|
||||
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 do_scan():
|
||||
_scan_qrcode_from_camera(parent=parent, config=config, callback=callback)
|
||||
|
||||
if _has_camera_permission():
|
||||
do_scan()
|
||||
else:
|
||||
# Request permission now. This is only a thing on macOS atm.
|
||||
# Note: this assumes we are running on the main thread. Permissions can only be requested from the main thread.
|
||||
app = QCoreApplication.instance()
|
||||
app.requestPermission(QtCore.QCameraPermission(), lambda _x: do_scan())
|
||||
|
||||
|
||||
def scan_qr_from_image(image: QImage) -> Sequence[QrCodeResult]:
|
||||
@@ -181,3 +188,29 @@ def _scan_qrcode_using_qtmultimedia(
|
||||
_qr_dialog = None
|
||||
callback(False, repr(e), None)
|
||||
|
||||
|
||||
def _scan_qrcode_from_camera(
|
||||
*,
|
||||
parent: Optional[QWidget],
|
||||
config: 'SimpleConfig',
|
||||
callback: Callable[[bool, str, Optional[str]], None],
|
||||
) -> None:
|
||||
"""Scans QR code using camera."""
|
||||
assert parent is None or isinstance(parent, QWidget), f"parent should be a QWidget, not {parent!r}"
|
||||
if not _has_camera_permission():
|
||||
callback(False, _("Missing camera permission."), None)
|
||||
return
|
||||
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 _has_camera_permission() -> bool:
|
||||
if not hasattr(QtCore, "QCameraPermission"): # requires Qt 6.5+
|
||||
_logger.info(f"QtCore does not support QCameraPermission. This requires Qt 6.5+")
|
||||
return True # hope for the best
|
||||
app = QCoreApplication.instance()
|
||||
permission_status = app.checkPermission(QtCore.QCameraPermission())
|
||||
return permission_status == QtCore.Qt.PermissionStatus.Granted
|
||||
|
||||
|
||||
@@ -792,10 +792,10 @@ class GenericInputHandler:
|
||||
except Exception as e:
|
||||
show_error(_('Invalid payment identifier in QR') + ':\n' + repr(e))
|
||||
|
||||
from .qrreader import scan_qrcode
|
||||
from .qrreader import scan_qrcode_from_camera
|
||||
if parent is None:
|
||||
parent = self if isinstance(self, QWidget) else None
|
||||
scan_qrcode(parent=parent, config=config, callback=cb)
|
||||
scan_qrcode_from_camera(parent=parent, config=config, callback=cb)
|
||||
|
||||
def input_qr_from_screenshot(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user