]> Nutra Git (v2) - gamesguru/feather.git/commitdiff
Enable webcam QR scanner
authortobtoht <tob@featherwallet.org>
Tue, 27 Dec 2022 15:52:06 +0000 (16:52 +0100)
committertobtoht <tob@featherwallet.org>
Tue, 27 Dec 2022 16:46:14 +0000 (17:46 +0100)
CMakeLists.txt
src/CMakeLists.txt
src/SendWidget.cpp
src/qrcode_scanner_qt6/QrCodeScanDialog.cpp
src/qrcode_scanner_qt6/QrCodeScanDialog.h
src/qrcode_scanner_qt6/QrCodeScanDialog.ui
src/qrcode_scanner_qt6/QrScanThread.cpp [new file with mode: 0644]
src/qrcode_scanner_qt6/QrScanThread.h [new file with mode: 0644]
src/qrcode_utils/QrCodeUtils.cpp [moved from src/qrcode_scanner/QrCodeUtils.cpp with 100% similarity]
src/qrcode_utils/QrCodeUtils.h [moved from src/qrcode_scanner/QrCodeUtils.h with 100% similarity]
src/widgets/PayToEdit.cpp

index 4df48ee9c51c82a257844cf335a4c447cf0c8a34..729d81b0fbf35edfd4b0f64f1e8d0450977fc207 100644 (file)
@@ -25,9 +25,7 @@ option(CHECK_UPDATES "Enable checking for application updates" OFF)
 option(PLATFORM_INSTALLER "Built-in updater fetches installer (windows-only)" OFF)
 option(USE_DEVICE_TREZOR "Trezor support compilation" ON)
 option(DONATE_BEG "Prompt donation window every once in a while" ON)
-
-
-option(WITH_SCANNER "Enable webcam QR scanner" OFF)
+option(WITH_SCANNER "Enable webcam QR scanner" ON)
 
 list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_SOURCE_DIR}/cmake")
 include(CheckCCompilerFlag)
index fb21e4019805e6088d08d24d11148bdbc1f03da8..dac430dc68b1cc4424f2f9dc682bbde57f6c4fee 100644 (file)
@@ -69,8 +69,8 @@ file(GLOB SOURCE_FILES
         "polyseed/*.h"
         "polyseed/*.cpp"
         "polyseed/*.c"
-        "qrcode_scanner/QrCodeUtils.cpp"
-        "qrcode_scanner/QrCodeUtils.h"
+        "qrcode_utils/QrCodeUtils.cpp"
+        "qrcode_utils/QrCodeUtils.h"
         "monero_seed/argon2/blake2/*.c"
         "monero_seed/argon2/*.c"
         "monero_seed/*.cpp"
@@ -175,7 +175,7 @@ if(XMRIG)
     target_compile_definitions(feather PRIVATE HAS_XMRIG=1)
 endif()
 
-if(WITH_SCANNER AND NOT Qt6_FOUND)
+if(WITH_SCANNER)
     target_compile_definitions(feather PRIVATE WITH_SCANNER=1)
 endif()
 
index a8d1082d8c6343749672ab2de92a55f2015a36c1..570ad8c4c106fb21cec519530ebab65128113d55 100644 (file)
@@ -14,6 +14,9 @@
 #if defined(WITH_SCANNER) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
 #include "qrcode_scanner/QrCodeScanDialog.h"
 #include <QtMultimedia/QCameraInfo>
+#elif defined(WITH_SCANNER)
+#include "qrcode_scanner_qt6/QrCodeScanDialog.h"
+#include <QMediaDevices>
 #endif
 
 SendWidget::SendWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
@@ -123,6 +126,17 @@ void SendWidget::scanClicked() {
     dialog->exec();
     ui->lineAddress->setText(dialog->decodedString);
     dialog->deleteLater();
+#elif defined(WITH_SCANNER)
+    auto cameras = QMediaDevices::videoInputs();
+    if (cameras.empty()) {
+        QMessageBox::warning(this, "QR code scanner", "No available cameras found.");
+        return;
+    }
+
+    auto dialog = new QrCodeScanDialog(this);
+    dialog->exec();
+    ui->lineAddress->setText(dialog->decodedString);
+    dialog->deleteLater();
 #else
     QMessageBox::warning(this, "QR scanner", "Feather was built without webcam QR scanner support.");
 #endif
index 989c9fce44a940445f3f2f8d8011649181a232a9..75b04f6dfe145a05300bcc54ca2e67323695000d 100644 (file)
@@ -7,23 +7,99 @@
 #include <QCamera>
 #include <QMediaDevices>
 #include <QCameraDevice>
+#include <QMessageBox>
+#include <QImageCapture>
+#include <QVideoFrame>
 
 QrCodeScanDialog::QrCodeScanDialog(QWidget *parent)
         : QDialog(parent)
         , ui(new Ui::QrCodeScanDialog)
 {
     ui->setupUi(this);
+    this->setWindowTitle("Scan QR code");
 
-    m_camera.reset(new QCamera(QMediaDevices::defaultVideoInput()));
-    m_captureSession.setCamera(m_camera.data());
+    QPixmap pixmap = QPixmap(":/assets/images/warning.png");
+    ui->icon_warning->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation));
+
+    const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
+    for (const auto &camera : cameras) {
+        ui->combo_camera->addItem(camera.description());
+    }
+
+    connect(ui->combo_camera, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &QrCodeScanDialog::onCameraSwitched);
+
+    this->onCameraSwitched(0);
+
+    m_thread = new QrScanThread(this);
+    m_thread->start();
+
+    connect(m_thread, &QrScanThread::decoded, this, &QrCodeScanDialog::onDecoded);
+    connect(m_thread, &QrScanThread::notifyError, this, &QrCodeScanDialog::notifyError);
 
+    connect(&m_imageTimer, &QTimer::timeout, this, &QrCodeScanDialog::takeImage);
+    m_imageTimer.start(500);
+}
+
+void QrCodeScanDialog::onCameraSwitched(int index) {
+    const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
+
+    if (index >= cameras.size()) {
+        return;
+    }
+
+    m_camera.reset(new QCamera(cameras.at(index)));
+    m_captureSession.setCamera(m_camera.data());
     m_captureSession.setVideoOutput(ui->viewfinder);
 
+    m_imageCapture = new QImageCapture;
+    m_captureSession.setImageCapture(m_imageCapture);
+
+    connect(m_imageCapture, &QImageCapture::imageCaptured, this, &QrCodeScanDialog::processCapturedImage);
+    connect(m_camera.data(), &QCamera::errorOccurred, this, &QrCodeScanDialog::displayCameraError);
+    connect(m_camera.data(), &QCamera::activeChanged, [this](bool active){
+        ui->frame_unavailable->setVisible(!active);
+    });
+
     m_camera->start();
+}
+
+void QrCodeScanDialog::processCapturedImage(int requestId, const QImage& img) {
+    Q_UNUSED(requestId);
+    QImage image{img};
+    image.convertTo(QImage::Format_RGB32);
+    m_thread->addImage(image);
+}
+
+void QrCodeScanDialog::takeImage()
+{
+    if (m_imageCapture->isReadyForCapture()) {
+        m_imageCapture->capture();
+    }
+}
+
+void QrCodeScanDialog::onDecoded(int type, const QString &data) {
+    decodedString = data;
+    this->accept();
+}
+
+void QrCodeScanDialog::displayCameraError()
+{
+    if (m_camera->error() != QCamera::NoError) {
+        QMessageBox::warning(this, tr("Camera Error"), m_camera->errorString());
+    }
+}
 
-    const QList<QCameraDevice> availableCameras = QMediaDevices::videoInputs();
+void QrCodeScanDialog::notifyError(const QString &msg) {
+    qDebug() << "QrScanner error: " << msg;
 }
 
 QrCodeScanDialog::~QrCodeScanDialog()
 {
+    m_thread->stop();
+    m_thread->quit();
+    if (!m_thread->wait(5000))
+    {
+        m_thread->terminate();
+        m_thread->wait();
+    }
 }
\ No newline at end of file
index 8ea94ff4e04da02e44ecb768f12ccc7121fcad08..fdcc0d73cce95ef25002f19194c17d479d554fb0 100644 (file)
@@ -8,6 +8,9 @@
 #include <QCamera>
 #include <QScopedPointer>
 #include <QMediaCaptureSession>
+#include <QTimer>
+
+#include "QrScanThread.h"
 
 namespace Ui {
     class QrCodeScanDialog;
@@ -21,9 +24,23 @@ public:
     explicit QrCodeScanDialog(QWidget *parent);
     ~QrCodeScanDialog() override;
 
+    QString decodedString = "";
+
+private slots:
+    void onCameraSwitched(int index);
+    void onDecoded(int type, const QString &data);
+    void notifyError(const QString &msg);
+
 private:
+    void processCapturedImage(int requestId, const QImage& img);
+    void displayCameraError();
+    void takeImage();
+
     QScopedPointer<Ui::QrCodeScanDialog> ui;
 
+    QrScanThread *m_thread;
+    QImageCapture *m_imageCapture;
+    QTimer m_imageTimer;
     QScopedPointer<QCamera> m_camera;
     QMediaCaptureSession m_captureSession;
 };
index 0f371e4777a260ea37c331f42c0f58e41c3e6785..9cda99941c41a8fa35b8f6f6c9269d680dcc3b32 100644 (file)
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>816</width>
-    <height>688</height>
+    <width>490</width>
+    <height>422</height>
    </rect>
   </property>
   <property name="windowTitle">
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
-    <widget class="QVideoWidget" name="viewfinder" native="true"/>
+    <widget class="QVideoWidget" name="viewfinder" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+    </widget>
    </item>
    <item>
-    <widget class="QDialogButtonBox" name="buttonBox">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
+    <widget class="QFrame" name="frame_unavailable">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
      </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
      </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_3">
+      <item>
+       <widget class="QLabel" name="icon_warning">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="text">
+         <string>icon</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeType">
+         <enum>QSizePolicy::Preferred</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>55</width>
+          <height>0</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Lost connection to camera. Please restart scan dialog.</string>
+        </property>
+        <property name="wordWrap">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
     </widget>
    </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QLabel" name="label_3">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Camera:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="combo_camera"/>
+     </item>
+    </layout>
+   </item>
   </layout>
  </widget>
  <customwidgets>
   </customwidget>
  </customwidgets>
  <resources/>
- <connections>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
-   <receiver>QrCodeScanDialog</receiver>
-   <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>rejected()</signal>
-   <receiver>QrCodeScanDialog</receiver>
-   <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
+ <connections/>
 </ui>
diff --git a/src/qrcode_scanner_qt6/QrScanThread.cpp b/src/qrcode_scanner_qt6/QrScanThread.cpp
new file mode 100644 (file)
index 0000000..3b3db0a
--- /dev/null
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2022 The Monero Project
+
+#include "QrScanThread.h"
+#include <QtGlobal>
+#include <QDebug>
+
+QrScanThread::QrScanThread(QObject *parent)
+    : QThread(parent)
+    , m_running(true)
+{
+    m_scanner.set_handler(*this);
+}
+
+void QrScanThread::image_callback(zbar::Image &image)
+{
+    qDebug() << "image_callback :  Found Code ! " ;
+    for (zbar::Image::SymbolIterator sym = image.symbol_begin(); sym != image.symbol_end(); ++sym) {
+        if (!sym->get_count()) {
+            QString data = QString::fromStdString(sym->get_data());
+            emit decoded(sym->get_type(), data);
+        }
+    }
+}
+
+void QrScanThread::processZImage(zbar::Image &image)
+{
+    m_scanner.recycle_image(image);
+    zbar::Image tmp = image.convert(zbar_fourcc('Y', '8', '0', '0'));
+    m_scanner.scan(tmp);
+    image.set_symbols(tmp.get_symbols());
+}
+
+bool QrScanThread::zimageFromQImage(const QImage &qimg, zbar::Image &dst)
+{
+    switch (qimg.format()) {
+        case QImage::Format_RGB32 :
+        case QImage::Format_ARGB32 :
+        case QImage::Format_ARGB32_Premultiplied :
+            break;
+        default :
+            qDebug() << "Format: " << qimg.format();
+            emit notifyError(QString("Invalid QImage Format !"));
+            return false;
+    }
+    unsigned int bpl( qimg.bytesPerLine() ), width( bpl / 4), height( qimg.height());
+    dst.set_size(width, height);
+    dst.set_format("BGR4");
+    unsigned long datalen = qimg.sizeInBytes();
+    dst.set_data(qimg.bits(), datalen);
+    if((width * 4 != bpl) || (width * height * 4 > datalen)){
+        emit notifyError(QString("QImage to Zbar::Image failed !"));
+        return false;
+    }
+    return true;
+}
+
+void QrScanThread::processQImage(const QImage &qimg)
+{
+    try {
+        m_image = QSharedPointer<zbar::Image>(new zbar::Image());
+        if (!zimageFromQImage(qimg, *m_image))
+            return;
+        processZImage(*m_image);
+    }
+    catch(std::exception &e) {
+        qDebug() << "ERROR: " << e.what();
+        emit notifyError(e.what());
+    }
+}
+
+void QrScanThread::stop()
+{
+    m_running = false;
+    m_waitCondition.wakeOne();
+}
+
+void QrScanThread::addImage(const QImage &img)
+{
+    QMutexLocker locker(&m_mutex);
+    m_queue.append(img);
+    m_waitCondition.wakeOne();
+}
+
+void QrScanThread::run()
+{
+    while (m_running) {
+        QMutexLocker locker(&m_mutex);
+        while (m_queue.isEmpty() && m_running) {
+            m_waitCondition.wait(&m_mutex);
+        }
+        if (!m_queue.isEmpty()) {
+            processQImage(m_queue.takeFirst());
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/qrcode_scanner_qt6/QrScanThread.h b/src/qrcode_scanner_qt6/QrScanThread.h
new file mode 100644 (file)
index 0000000..16bee3b
--- /dev/null
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2022 The Monero Project
+
+#ifndef _QRSCANTHREAD_H_
+#define _QRSCANTHREAD_H_
+
+#include <QThread>
+#include <QMutex>
+#include <QWaitCondition>
+#include <QEvent>
+#include <QCamera>
+#include <zbar.h>
+
+class QrScanThread : public QThread, public zbar::Image::Handler
+{
+    Q_OBJECT
+
+public:
+    QrScanThread(QObject *parent = nullptr);
+    void addImage(const QImage &img);
+    virtual void stop();
+
+signals:
+    void decoded(int type, const QString &data);
+    void notifyError(const QString &error, bool warning = false);
+
+protected:
+    virtual void run();
+    void processQImage(const QImage &);
+    void processZImage(zbar::Image &image);
+    virtual void image_callback(zbar::Image &image);
+    bool zimageFromQImage(const QImage&, zbar::Image &);
+
+private:
+    zbar::ImageScanner m_scanner;
+    QSharedPointer<zbar::Image> m_image;
+    bool m_running;
+    QMutex m_mutex;
+    QWaitCondition m_waitCondition;
+    QList<QImage> m_queue;
+};
+#endif
\ No newline at end of file
index 2c46674d5b037e55bd054a289d9ca5dac0ebdad6..6ceb8ea0c464eace38dab9436446b8a20e9e3d4e 100644 (file)
@@ -12,7 +12,7 @@
 
 #include "libwalletqt/WalletManager.h"
 #include "model/ModelUtils.h"
-#include "qrcode_scanner/QrCodeUtils.h"
+#include "qrcode_utils/QrCodeUtils.h"
 
 PayToEdit::PayToEdit(QWidget *parent) : QPlainTextEdit(parent)
 {