diff --git a/CMakeLists.txt b/CMakeLists.txt index d2da3361..5e52e002 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ option(TOR_BIN "Path to Tor binary to embed inside Feather" OFF) option(CHECK_UPDATES "Enable checking for application updates" OFF) option(USE_DEVICE_TREZOR "Trezor support compilation" OFF) option(DONATE_BEG "Prompt donation window every once in a while" ON) +option(WITH_SCANNER "Enable webcam QR scanner" OFF) list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_SOURCE_DIR}/cmake") include(CheckCCompilerFlag) @@ -56,34 +57,8 @@ if(STATIC) set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_RUNTIME ON) - add_definitions(-DMONERO_GUI_STATIC) endif() -function (add_c_flag_if_supported flag var) - string(REPLACE "-" "_" supported ${flag}_c) - check_c_compiler_flag(${flag} ${supported}) - if(${${supported}}) - set(${var} "${${var}} ${flag}" PARENT_SCOPE) - endif() -endfunction() - -function (add_cxx_flag_if_supported flag var) - string(REPLACE "-" "_" supported ${flag}_cxx) - check_cxx_compiler_flag(${flag} ${supported}) - if(${${supported}}) - set(${var} "${${var}} ${flag}" PARENT_SCOPE) - endif() -endfunction() - -function (add_linker_flag_if_supported flag var) - string(REPLACE "-" "_" supported ${flag}_ld) - string(REPLACE "," "_" supported ${flag}_ld) - check_linker_flag(${flag} ${supported}) - if(${${supported}}) - set(${var} "${${var}} ${flag}" PARENT_SCOPE) - endif() -endfunction() - find_package(Git) if(GIT_FOUND) execute_process(COMMAND git rev-parse "HEAD" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/monero OUTPUT_VARIABLE _MONERO_HEAD OUTPUT_STRIP_TRAILING_WHITESPACE) @@ -125,6 +100,13 @@ set(HIDAPI_FOUND OFF) # QrEncode find_package(QREncode REQUIRED) +# Qr scanner +if (WITH_SCANNER) + find_package(ZBAR REQUIRED) + message(STATUS "libzbar: include dir at ${ZBAR_INCLUDE_DIR}") + message(STATUS "libzbar: libraries at ${ZBAR_LIBRARIES}") +endif() + # Tevador 14 word Monero seed find_package(monero-seed CONFIG) if(NOT monero-seed_FOUND) @@ -199,9 +181,10 @@ if(TOR_BIN) execute_process(COMMAND bash -c "${TOR_BIN} --version --quiet" OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE out RESULT_VARIABLE ret) if (ret EQUAL "0") - set(TOR_VERSION "${out}") + string(REGEX MATCH "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]" version ${out}) + set(TOR_VERSION "${version}") endif() - message(STATUS "${TOR_VERSION}") + message(STATUS "Embedded Tor version: ${TOR_VERSION}") configure_file("cmake/config-feather.h.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/src/config-feather.h") # on the buildbot Tor is baked into the image @@ -245,7 +228,7 @@ if(MINGW) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wa,-mbig-obj") set(EXTRA_LIBRARIES mswsock;ws2_32;iphlpapi;crypt32;bcrypt) if(DEPENDS) - set(ICU_LIBRARIES iconv) + set(ICU_LIBRARIES iconv) else() set(ICU_LIBRARIES icuio icuin icuuc icudt icutu iconv) endif() diff --git a/Dockerfile b/Dockerfile index c85013ec..2a620ff6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # bionic-20210615.1 (18.04) FROM ubuntu@sha256:ce1e17c0e0aa9db95cf19fb6ba297eb2a52b9ba71768f32a74ab39213c416600 -ARG THREADS=1 +ARG THREADS=4 ENV CFLAGS="-fPIC" ENV CPPFLAGS="-fPIC" @@ -11,6 +11,7 @@ ENV DEBIAN_FRONTEND=noninteractive # Feather build flags ENV CHECK_UPDATES=ON +ENV WITH_SCANNER=ON COPY --from=featherwallet/feather-deps:linux-beta-8 /deps /deps COPY --from=featherwallet/feather-deps:linux-beta-8 /var/cache/apt/archives /archives diff --git a/Dockerfile.windows b/Dockerfile.windows index da29f887..cf1a2b93 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -3,13 +3,21 @@ FROM ubuntu:20.04 ARG THREADS=1 ARG QT_VERSION=5.15.2 ENV SOURCE_DATE_EPOCH=1397818193 +ENV DEBIAN_FRONTEND=noninteractive + +# Feather build flags +ENV CHECK_UPDATES=ON +ENV WITH_SCANNER=ON ENV OPENSSL_ROOT_DIR=/usr/local/openssl/ ENV TOR_BIN=/usr/local/tor/bin/tor.exe RUN apt update && \ - DEBIAN_FRONTEND=noninteractive apt install -y curl wget zip automake build-essential cmake gcc-mingw-w64 g++-mingw-w64 gettext git libtool pkg-config \ - python && \ + apt install -y \ + curl wget zip automake build-essential cmake gcc-mingw-w64 g++-mingw-w64 gettext git libtool pkg-config \ + python \ +# zbar + autopoint && \ rm -rf /var/lib/apt/lists/* RUN update-alternatives --set x86_64-w64-mingw32-g++ $(which x86_64-w64-mingw32-g++-posix) && \ @@ -189,4 +197,12 @@ RUN git clone https://github.com/nih-at/libzip.git && \ -DBUILD_SHARED_LIBS=OFF \ -DCMAKE_PREFIX_PATH=/usr/x86_64-w64-mingw32 && \ make -j$THREADS && \ - make -j$THREADS install \ No newline at end of file + make -j$THREADS install + +RUN git clone -b 0.23.92 --depth 1 --recursive https://github.com/mchehab/zbar.git && \ + cd zbar && \ + git reset --hard aac86d5f08d64ab4c3da78188eb622fa3cb07182 && \ + autoreconf -vfi && \ + ./configure --enable-static --disable-shared --without-imagemagick --disable-video --without-xv --with-gtk=no --with-python=no --enable-doc=no --host=x86_64-w64-mingw32 && \ + make -j$THREADS && \ + make install \ No newline at end of file diff --git a/Makefile b/Makefile index bde8936c..7d013304 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ CMAKEFLAGS = \ release-static: CMAKEFLAGS += -DBUILD_TAG="linux-x64" release-static: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF) release-static: CMAKEFLAGS += -DCHECK_UPDATES=$(or ${CHECK_UPDATES}, Off) +release-static: CMAKEFLAGS += -DWITH_SCANNER=$(or ${WITH_SCANNER}, Off) release-static: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release release-static: CMAKEFLAGS += -DREPRODUCIBLE=$(or ${SOURCE_DATE_EPOCH},OFF) release-static: diff --git a/contrib/build-appimage.sh b/contrib/build-appimage.sh index 3ba81573..6c7ff9cc 100755 --- a/contrib/build-appimage.sh +++ b/contrib/build-appimage.sh @@ -8,20 +8,35 @@ rm -rf $APPDIR mkdir -p "$APPDIR" mkdir -p "$APPDIR/usr/share/applications/" mkdir -p "$APPDIR/usr/bin" +mkdir -p "$APPDIR/usr/lib" +mkdir -p "$APPDIR/usr/plugins" cp "$PWD/../src/assets/feather.desktop" "$APPDIR/usr/share/applications/feather.desktop" cp "$PWD/../src/assets/images/appicons/64x64.png" "$APPDIR/feather.png" cp "$PWD/bin/feather" "$APPDIR/usr/bin/feather" +chmod +x "$APPDIR/usr/bin/feather" -LD_LIBRARY_PATH=/usr/local/lib /linuxdeployqt/squashfs-root/AppRun feather.AppDir/usr/share/applications/feather.desktop -bundle-non-qt-libs +export LD_LIBRARY_PATH=/usr/local/lib/x86_64-linux-gnu/:/usr/local/lib/$LD_LIBRARY_PATH +linuxdeployqt feather.AppDir/usr/share/applications/feather.desktop -bundle-non-qt-libs + +pushd feather.AppDir/usr/plugins +ln -s ../lib/ gstreamer +popd + +cp -r /usr/lib/x86_64-linux-gnu/gstreamer-1.0/* feather.AppDir/usr/plugins/gstreamer/ +cp /usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner feather.AppDir/usr/plugins/gstreamer/ + +rm "$APPDIR/AppRun" +cp "$PWD/../contrib/AppImage/AppRun" "$APPDIR/" +chmod +x "$APPDIR/AppRun" find feather.AppDir/ -exec touch -h -a -m -t 202101010100.00 {} \; # Manually create AppImage (reproducibly) # download runtime -wget -nc https://github.com/AppImage/AppImageKit/releases/download/12/runtime-x86_64 -echo "24da8e0e149b7211cbfb00a545189a1101cb18d1f27d4cfc1895837d2c30bc30 runtime-x86_64" | sha256sum -c +wget -nc https://github.com/AppImage/AppImageKit/releases/download/13/runtime-x86_64 +echo "328e0d745c5c6817048c27bc3e8314871703f8f47ffa81a37cb06cd95a94b323 runtime-x86_64" | sha256sum -c mksquashfs feather.AppDir feather.squashfs -info -root-owned -no-xattrs -noappend -fstime 0 # mksquashfs writes a timestamp to the header diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bdac1b53..56d93145 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,11 +5,24 @@ set(CMAKE_AUTOUIC ON) # pthread find_package(Threads REQUIRED) -find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Network Svg Xml WebSockets) +set(QT5_COMPONENTS + Core + Widgets + Gui + Network + Svg + Xml + WebSockets +) + +if (WITH_SCANNER) + list(APPEND QT5_COMPONENTS + Multimedia + MultimediaWidgets) +endif() + +find_package(Qt5 REQUIRED COMPONENTS ${QT5_COMPONENTS}) -add_subdirectory(libwalletqt) -add_subdirectory(model) -add_subdirectory(utils) add_subdirectory(openpgp) qt5_add_resources(RESOURCES assets.qrc) @@ -46,6 +59,15 @@ file(GLOB SOURCE_FILES "dialog/*.cpp" ) +if (WITH_SCANNER) + file(GLOB SCANNER_FILES + "qrcode_scanner/*.h" + "qrcode_scanner/*.cpp") + + list(APPEND SOURCE_FILES + ${SCANNER_FILES}) +endif() + if(TOR_BIN) if(APPLE) set(ASSETS_TOR "assets_tor_macos.qrc") @@ -90,12 +112,9 @@ set_target_properties(feather PROPERTIES set_property(TARGET feather PROPERTY RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") -target_include_directories(feather PUBLIC ${OPENGL_INCLUDE_DIR}) +#target_include_directories(feather PUBLIC ${OPENGL_INCLUDE_DIR}) target_include_directories(feather PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) -file(GLOB_RECURSE SRC_SOURCES *.cpp) -file(GLOB_RECURSE SRC_HEADERS *.h) - target_include_directories(feather PUBLIC ${CMAKE_BINARY_DIR}/src/feather_autogen/include ${CMAKE_SOURCE_DIR}/monero/include @@ -124,6 +143,14 @@ target_include_directories(feather PUBLIC ${LIBZIP_INCLUDE_DIRS} ) +if(WITH_SCANNER) + target_include_directories(feather PUBLIC + ${ZBAR_INCLUDE_DIR} + ${Qt5Multimedia_INCLUDE_DIRS} + ${Qt5MultimediaWidgets_INCLUDE_DIRS} + ) +endif() + if(DONATE_BEG) target_compile_definitions(feather PRIVATE DONATE_BEG=1) endif() @@ -144,6 +171,10 @@ if(XMRIG) target_compile_definitions(feather PRIVATE HAS_XMRIG=1) endif() +if(WITH_SCANNER) + target_compile_definitions(feather PRIVATE WITH_SCANNER=1) +endif() + # TODO: PLACEHOLDER target_compile_definitions(feather PRIVATE HAS_WEBSOCKET=1) @@ -172,7 +203,14 @@ target_compile_definitions(feather ${Qt5Svg_DEFINITIONS} ${Qt5Xml_DEFINITIONS} ${Qt5WebSockets_DEFINITIONS} - ) +) + +if (WITH_SCANNER) + target_compile_definitions(feather PUBLIC + ${Qt5Multimedia_DEFINITIONS} + ${Qt5MultimediaWidgets_DEFINITIONS} + ) +endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") @@ -212,6 +250,14 @@ target_link_libraries(feather ${LIBZIP_LIBRARIES} ) +if (WITH_SCANNER) + target_link_libraries(feather + Qt5::Multimedia + Qt5::MultimediaWidgets + ${ZBAR_LIBRARIES} + ) +endif() + if(APPLE) target_link_libraries(feather KDMacTouchBar @@ -247,4 +293,4 @@ endif() install(TARGETS feather DESTINATION ${CMAKE_INSTALL_PREFIX} - ) +) diff --git a/src/libwalletqt/CMakeLists.txt b/src/libwalletqt/CMakeLists.txt index 7d16b0fe..e69de29b 100644 --- a/src/libwalletqt/CMakeLists.txt +++ b/src/libwalletqt/CMakeLists.txt @@ -1,2 +0,0 @@ -file(GLOB_RECURSE SRC_SOURCES *.cpp) -file(GLOB_RECURSE SRC_HEADERS *.h) diff --git a/src/main.cpp b/src/main.cpp index a62ae807..62fdb9fd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,7 @@ #if defined(Q_OS_WIN) #include +#include #endif #if defined(Q_OS_LINUX) && defined(STATIC) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2ec89f1c..fd415fab 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -74,6 +74,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged)); connect(m_windowSettings, &Settings::amountPrecisionChanged, m_ctx.get(), &AppContext::onAmountPrecisionChanged); connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged); + QTimer::singleShot(1, [this]{this->updateWidgetIcons();}); connect(m_windowManager, &WindowManager::torSettingsChanged, m_ctx.get(), &AppContext::onTorSettingsChanged); connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged); @@ -791,11 +792,14 @@ void MainWindow::menuVerifyTxProof() { void MainWindow::skinChanged(const QString &skinName) { m_windowManager->changeSkin(skinName); ColorScheme::updateFromWidget(this); + this->updateWidgetIcons(); +} +void MainWindow::updateWidgetIcons() { + m_sendWidget->skinChanged(); #ifdef HAS_LOCALMONERO m_localMoneroWidget->skinChanged(); #endif - ui->conversionWidget->skinChanged(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index eaecad9c..efe14ff5 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -204,6 +204,7 @@ private: void updateTitle(); void donationNag(); void updateRecentlyOpened(const QString &filename); + void updateWidgetIcons(); Ui::MainWindow *ui; WindowManager *m_windowManager; diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt index 9ca31c61..e69de29b 100644 --- a/src/model/CMakeLists.txt +++ b/src/model/CMakeLists.txt @@ -1,4 +0,0 @@ -file(GLOB_RECURSE SRC_SOURCES *.cpp) -file(GLOB_RECURSE SRC_HEADERS *.h) - - diff --git a/src/qrcode_scanner/CMakeLists.txt b/src/qrcode_scanner/CMakeLists.txt new file mode 100644 index 00000000..e927cef6 --- /dev/null +++ b/src/qrcode_scanner/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB_RECURSE SRC_SOURCES *.cpp) +file(GLOB_RECURSE SRC_HEADERS *.h) + diff --git a/src/qrcode_scanner/QrCodeScanDialog.cpp b/src/qrcode_scanner/QrCodeScanDialog.cpp new file mode 100644 index 00000000..9d70f17f --- /dev/null +++ b/src/qrcode_scanner/QrCodeScanDialog.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "QrCodeScanDialog.h" +#include "ui_QrCodeScanDialog.h" + +#include +#include +#include + +QrCodeScanDialog::QrCodeScanDialog(QWidget *parent) + : QDialog(parent) + , ui(new Ui::QrCodeScanDialog) + , m_scanner(new QrCodeScanner(this)) +{ + ui->setupUi(this); + this->setWindowTitle("Scan QR Code"); + + QPixmap pixmap = QPixmap(":/assets/images/warning.png"); + ui->icon_warning->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation)); + + m_cameras = QCameraInfo::availableCameras(); + if (m_cameras.count() < 1) { + QMessageBox::warning(parent, "QR code scanner", "No available cameras found."); + this->close(); + } + + for (const auto &camera : m_cameras) { + ui->combo_camera->addItem(camera.deviceName()); + } + + connect(ui->combo_camera, QOverload::of(&QComboBox::currentIndexChanged), this, &QrCodeScanDialog::onCameraSwitched); + + connect(m_scanner, &QrCodeScanner::decoded, this, &QrCodeScanDialog::onDecoded); + connect(m_scanner, &QrCodeScanner::notifyError, this, &QrCodeScanDialog::notifyError); + + this->onCameraSwitched(0); +} + +void QrCodeScanDialog::onCameraSwitched(int index) { + if (index >= m_cameras.size()) { + return; + } + + m_scanner->setSource(nullptr); + delete m_camera; + + m_camera = new QCamera(m_cameras.at(index), this); + connect(m_camera, &QCamera::statusChanged, [this](QCamera::Status status){ + bool unloaded = (status == QCamera::Status::UnloadedStatus); + ui->frame_unavailable->setVisible(unloaded); + }); + + m_camera->setCaptureMode(QCamera::CaptureViewfinder); + m_camera->setViewfinder(ui->viewfinder); + + m_scanner->setSource(m_camera); + m_scanner->setEnabled(true); + + m_camera->start(); +} + +void QrCodeScanDialog::onDecoded(int type, const QString &data) { + decodedString = data; + this->accept(); +} + +void QrCodeScanDialog::notifyError(const QString &msg) { + qDebug() << "QrScanner error: " << msg; +} + +QrCodeScanDialog::~QrCodeScanDialog() +{ + delete m_camera; + delete ui; +} \ No newline at end of file diff --git a/src/qrcode_scanner/QrCodeScanDialog.h b/src/qrcode_scanner/QrCodeScanDialog.h new file mode 100644 index 00000000..5fe1ff4d --- /dev/null +++ b/src/qrcode_scanner/QrCodeScanDialog.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#ifndef FEATHER_QRCODESCANDIALOG_H +#define FEATHER_QRCODESCANDIALOG_H + +#include +#include + +#include "QrCodeScanner.h" + +namespace Ui { + class QrCodeScanDialog; +} + +class QrCodeScanDialog : public QDialog +{ + Q_OBJECT + +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: + Ui::QrCodeScanDialog *ui; + + QList m_cameras; + QCamera *m_camera = nullptr; + QrCodeScanner *m_scanner; +}; + +#endif //FEATHER_QRCODESCANDIALOG_H \ No newline at end of file diff --git a/src/qrcode_scanner/QrCodeScanDialog.ui b/src/qrcode_scanner/QrCodeScanDialog.ui new file mode 100644 index 00000000..fb555ebb --- /dev/null +++ b/src/qrcode_scanner/QrCodeScanDialog.ui @@ -0,0 +1,110 @@ + + + QrCodeScanDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + 0 + 0 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + icon + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 10 + 0 + + + + + + + + Lost connection to camera. Please restart scan dialog. + + + true + + + + + + + + + + + + + 0 + 0 + + + + Camera: + + + + + + + + + + + + + QCameraViewfinder + QWidget +
qcameraviewfinder.h
+ 1 +
+
+ + +
diff --git a/src/qrcode_scanner/QrCodeScanner.cpp b/src/qrcode_scanner/QrCodeScanner.cpp new file mode 100644 index 00000000..44c211c8 --- /dev/null +++ b/src/qrcode_scanner/QrCodeScanner.cpp @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2014-2021, The Monero Project. + +#include "QrCodeScanner.h" +#include +#include +#include + +QrCodeScanner::QrCodeScanner(QObject *parent) + : QObject(parent) + , m_processTimerId(-1) + , m_processInterval(750) + , m_enabled(true) +{ + m_probe = new QVideoProbe(this); + m_thread = new QrScanThread(this); + m_thread->start(); + + connect(m_thread, &QrScanThread::decoded, this, &QrCodeScanner::decoded); + connect(m_thread, &QrScanThread::notifyError, this, &QrCodeScanner::notifyError); + connect(m_probe, &QVideoProbe::videoFrameProbed, this, &QrCodeScanner::processFrame); +} + +void QrCodeScanner::setSource(QCamera *camera) +{ + m_probe->setSource((QMediaObject *)camera); +} + +void QrCodeScanner::processFrame(const QVideoFrame &frame) +{ + if (frame.isValid()){ + m_curFrame = frame; + } +} + +bool QrCodeScanner::enabled() const +{ + return m_enabled; +} + +void QrCodeScanner::setEnabled(bool enabled) +{ + m_enabled = enabled; + if(!enabled && (m_processTimerId != -1) ) + { + this->killTimer(m_processTimerId); + m_processTimerId = -1; + } + else if (enabled && (m_processTimerId == -1) ) + { + m_processTimerId = this->startTimer(m_processInterval); + } + emit enabledChanged(); +} + +void QrCodeScanner::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_processTimerId) { + m_thread->addFrame(m_curFrame); + } +} + +QrCodeScanner::~QrCodeScanner() +{ + m_thread->stop(); + m_thread->quit(); + if (!m_thread->wait(5000)) + { + m_thread->terminate(); + m_thread->wait(); + } +} diff --git a/src/qrcode_scanner/QrCodeScanner.h b/src/qrcode_scanner/QrCodeScanner.h new file mode 100644 index 00000000..5339faf4 --- /dev/null +++ b/src/qrcode_scanner/QrCodeScanner.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2014-2020, The Monero Project. + + +#ifndef QRCODESCANNER_H_ +#define QRCODESCANNER_H_ + +#include +#include +#include "QrScanThread.h" + +class QVideoProbe; +class QCamera; + +class QrCodeScanner : public QObject +{ +Q_OBJECT + +Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + +public: + explicit QrCodeScanner(QObject *parent = nullptr); + ~QrCodeScanner() override; + void setSource(QCamera*); + + bool enabled() const; + void setEnabled(bool enabled); + +public slots: + void processFrame(const QVideoFrame &frame); + +signals: + void enabledChanged(); + void decoded(int type, const QString &data); + void decode(int type, const QString &data); + void notifyError(const QString &error, bool warning = false); + +protected: + void timerEvent(QTimerEvent *); + QrScanThread *m_thread; + int m_processTimerId; + int m_processInterval; + int m_enabled; + QVideoFrame m_curFrame; + QVideoProbe *m_probe; +}; + +#endif diff --git a/src/qrcode_scanner/QrScanThread.cpp b/src/qrcode_scanner/QrScanThread.cpp new file mode 100644 index 00000000..2cfb0b79 --- /dev/null +++ b/src/qrcode_scanner/QrScanThread.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2014-2020, The Monero Project. + +#include "QrScanThread.h" +#include +#include + +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(*(long*)"Y800"); + 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(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::processVideoFrame(const QVideoFrame &frame) +{ + processQImage(frame.image()); +} + +void QrScanThread::stop() +{ + m_running = false; + m_waitCondition.wakeOne(); +} + +void QrScanThread::addFrame(const QVideoFrame &frame) +{ + QMutexLocker locker(&m_mutex); + m_queue.append(frame); + m_waitCondition.wakeOne(); +} + +void QrScanThread::run() +{ + QVideoFrame frame; + while (m_running) { + QMutexLocker locker(&m_mutex); + while (m_queue.isEmpty() && m_running) { + m_waitCondition.wait(&m_mutex); + } + if (!m_queue.isEmpty()) { + processVideoFrame(m_queue.takeFirst()); + } + } +} \ No newline at end of file diff --git a/src/qrcode_scanner/QrScanThread.h b/src/qrcode_scanner/QrScanThread.h new file mode 100644 index 00000000..95753402 --- /dev/null +++ b/src/qrcode_scanner/QrScanThread.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020, The Monero Project. + +#ifndef _QRSCANTHREAD_H_ +#define _QRSCANTHREAD_H_ + +#include +#include +#include +#include +#include +#include +#include + +class QrScanThread : public QThread, public zbar::Image::Handler +{ + Q_OBJECT + +public: + QrScanThread(QObject *parent = nullptr); + void addFrame(const QVideoFrame &frame); + virtual void stop(); + +signals: + void decoded(int type, const QString &data); + void notifyError(const QString &error, bool warning = false); + +protected: + virtual void run(); + void processVideoFrame(const QVideoFrame &); + 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 m_image; + bool m_running; + QMutex m_mutex; + QWaitCondition m_waitCondition; + QList m_queue; +}; +#endif \ No newline at end of file diff --git a/src/sendwidget.cpp b/src/sendwidget.cpp index 2e1cd29b..60f3fdcd 100644 --- a/src/sendwidget.cpp +++ b/src/sendwidget.cpp @@ -7,6 +7,12 @@ #include "ui_sendwidget.h" #include "constants.h" #include "utils/AppData.h" +#include "Icons.h" +#include "ColorScheme.h" + +#ifdef WITH_SCANNER +#include "qrcode_scanner/QrCodeScanDialog.h" +#endif SendWidget::SendWidget(QSharedPointer ctx, QWidget *parent) : QWidget(parent) @@ -26,6 +32,7 @@ SendWidget::SendWidget(QSharedPointer ctx, QWidget *parent) connect(m_ctx.get(), &AppContext::openAliasResolved, this, &SendWidget::onOpenAliasResolved); connect(m_ctx.get(), &AppContext::openAliasResolveError, this, &SendWidget::onOpenAliasResolveError); + connect(ui->btnScan, &QPushButton::clicked, this, &SendWidget::scanClicked); connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked); connect(ui->btnClear, &QPushButton::clicked, this, &SendWidget::clearClicked); connect(ui->btnMax, &QPushButton::clicked, this, &SendWidget::btnMaxClicked); @@ -103,6 +110,17 @@ void SendWidget::fillAddress(const QString &address) { ui->lineAddress->moveCursor(QTextCursor::Start); } +void SendWidget::scanClicked() { +#ifdef WITH_SCANNER + 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 +} + void SendWidget::sendClicked() { if (!m_ctx->wallet->isConnected()) { QMessageBox::warning(this, "Error", "Unable to create transaction:\n\n" @@ -276,6 +294,14 @@ void SendWidget::onPreferredFiatCurrencyChanged() { this->setupComboBox(); } +void SendWidget::skinChanged() { + if (ColorScheme::hasDarkBackground(this)) { + ui->btnScan->setIcon(icons()->icon("camera_white.png")); + } else { + ui->btnScan->setIcon(icons()->icon("camera_dark.png")); + } +} + SendWidget::~SendWidget() { delete ui; } diff --git a/src/sendwidget.h b/src/sendwidget.h index f4666db7..f72b9853 100644 --- a/src/sendwidget.h +++ b/src/sendwidget.h @@ -26,6 +26,8 @@ public: ~SendWidget() override; public slots: + void skinChanged(); + void scanClicked(); void sendClicked(); void clearClicked(); void aliasClicked(); diff --git a/src/sendwidget.ui b/src/sendwidget.ui index 5de09348..8ad083d5 100644 --- a/src/sendwidget.ui +++ b/src/sendwidget.ui @@ -52,17 +52,51 @@ - - - Qt::ScrollBarAlwaysOff + + + 10 - - true - - - QPlainTextEdit::NoWrap - - + + + + Qt::ScrollBarAlwaysOff + + + true + + + QPlainTextEdit::NoWrap + + + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 0 + + + + + + + + Scan + + + + + + @@ -208,6 +242,17 @@
widgets/PayToEdit.h
+ + lineAddress + lineDescription + lineAmount + comboCurrencySelection + btnMax + btn_openAlias + btnClear + btnSend + btnScan + diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 7d16b0fe..e69de29b 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -1,2 +0,0 @@ -file(GLOB_RECURSE SRC_SOURCES *.cpp) -file(GLOB_RECURSE SRC_HEADERS *.h) diff --git a/src/utils/childproc.h b/src/utils/childproc.h index 00afda79..b7548edb 100644 --- a/src/utils/childproc.h +++ b/src/utils/childproc.h @@ -15,7 +15,7 @@ class ChildProcess : public QProcess { Q_OBJECT public: explicit ChildProcess(QObject* parent = nullptr); - ~ChildProcess(); + ~ChildProcess() override; protected: void setupChildProcess() override; };