mirror of
https://github.com/feather-wallet/feather.git
synced 2024-12-22 11:39:25 +00:00
Qr Scanner
This commit is contained in:
parent
1ee6f596a7
commit
3db3e330d5
24 changed files with 693 additions and 66 deletions
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
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
|
1
Makefile
1
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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
file(GLOB_RECURSE SRC_SOURCES *.cpp)
|
||||
file(GLOB_RECURSE SRC_HEADERS *.h)
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <windows.h>
|
||||
#include <vfw.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX) && defined(STATIC)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -204,6 +204,7 @@ private:
|
|||
void updateTitle();
|
||||
void donationNag();
|
||||
void updateRecentlyOpened(const QString &filename);
|
||||
void updateWidgetIcons();
|
||||
|
||||
Ui::MainWindow *ui;
|
||||
WindowManager *m_windowManager;
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
file(GLOB_RECURSE SRC_SOURCES *.cpp)
|
||||
file(GLOB_RECURSE SRC_HEADERS *.h)
|
||||
|
||||
|
3
src/qrcode_scanner/CMakeLists.txt
Normal file
3
src/qrcode_scanner/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
file(GLOB_RECURSE SRC_SOURCES *.cpp)
|
||||
file(GLOB_RECURSE SRC_HEADERS *.h)
|
||||
|
76
src/qrcode_scanner/QrCodeScanDialog.cpp
Normal file
76
src/qrcode_scanner/QrCodeScanDialog.cpp
Normal file
|
@ -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 <QMessageBox>
|
||||
#include <QtMultimedia/QCamera>
|
||||
#include <QtMultimedia/QCameraInfo>
|
||||
|
||||
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<int>::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;
|
||||
}
|
39
src/qrcode_scanner/QrCodeScanDialog.h
Normal file
39
src/qrcode_scanner/QrCodeScanDialog.h
Normal file
|
@ -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 <QDialog>
|
||||
#include <QCamera>
|
||||
|
||||
#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<QCameraInfo> m_cameras;
|
||||
QCamera *m_camera = nullptr;
|
||||
QrCodeScanner *m_scanner;
|
||||
};
|
||||
|
||||
#endif //FEATHER_QRCODESCANDIALOG_H
|
110
src/qrcode_scanner/QrCodeScanDialog.ui
Normal file
110
src/qrcode_scanner/QrCodeScanDialog.ui
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QrCodeScanDialog</class>
|
||||
<widget class="QDialog" name="QrCodeScanDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCameraViewfinder" 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="QFrame" name="frame_unavailable">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<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>10</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<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>
|
||||
<class>QCameraViewfinder</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qcameraviewfinder.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
72
src/qrcode_scanner/QrCodeScanner.cpp
Normal file
72
src/qrcode_scanner/QrCodeScanner.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Copyright (c) 2014-2021, The Monero Project.
|
||||
|
||||
#include "QrCodeScanner.h"
|
||||
#include <WalletManager.h>
|
||||
#include <QVideoProbe>
|
||||
#include <QCamera>
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
48
src/qrcode_scanner/QrCodeScanner.h
Normal file
48
src/qrcode_scanner/QrCodeScanner.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Copyright (c) 2014-2020, The Monero Project.
|
||||
|
||||
|
||||
#ifndef QRCODESCANNER_H_
|
||||
#define QRCODESCANNER_H_
|
||||
|
||||
#include <QImage>
|
||||
#include <QVideoFrame>
|
||||
#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
|
102
src/qrcode_scanner/QrScanThread.cpp
Normal file
102
src/qrcode_scanner/QrScanThread.cpp
Normal file
|
@ -0,0 +1,102 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Copyright (c) 2014-2020, 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(*(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<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::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());
|
||||
}
|
||||
}
|
||||
}
|
44
src/qrcode_scanner/QrScanThread.h
Normal file
44
src/qrcode_scanner/QrScanThread.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Copyright (c) 2020, The Monero Project.
|
||||
|
||||
#ifndef _QRSCANTHREAD_H_
|
||||
#define _QRSCANTHREAD_H_
|
||||
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QEvent>
|
||||
#include <QVideoFrame>
|
||||
#include <QCamera>
|
||||
#include <zbar.h>
|
||||
|
||||
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<zbar::Image> m_image;
|
||||
bool m_running;
|
||||
QMutex m_mutex;
|
||||
QWaitCondition m_waitCondition;
|
||||
QList<QVideoFrame> m_queue;
|
||||
};
|
||||
#endif
|
|
@ -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<AppContext> ctx, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
|
@ -26,6 +32,7 @@ SendWidget::SendWidget(QSharedPointer<AppContext> 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;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ public:
|
|||
~SendWidget() override;
|
||||
|
||||
public slots:
|
||||
void skinChanged();
|
||||
void scanClicked();
|
||||
void sendClicked();
|
||||
void clearClicked();
|
||||
void aliasClicked();
|
||||
|
|
|
@ -52,17 +52,51 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="PayToEdit" name="lineAddress">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="tabChangesFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="lineWrapMode">
|
||||
<enum>QPlainTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<item>
|
||||
<widget class="PayToEdit" name="lineAddress">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="tabChangesFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="lineWrapMode">
|
||||
<enum>QPlainTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Preferred</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnScan">
|
||||
<property name="text">
|
||||
<string>Scan</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="HelpLabel" name="label_Description">
|
||||
|
@ -208,6 +242,17 @@
|
|||
<header>widgets/PayToEdit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>lineAddress</tabstop>
|
||||
<tabstop>lineDescription</tabstop>
|
||||
<tabstop>lineAmount</tabstop>
|
||||
<tabstop>comboCurrencySelection</tabstop>
|
||||
<tabstop>btnMax</tabstop>
|
||||
<tabstop>btn_openAlias</tabstop>
|
||||
<tabstop>btnClear</tabstop>
|
||||
<tabstop>btnSend</tabstop>
|
||||
<tabstop>btnScan</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<slots>
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
file(GLOB_RECURSE SRC_SOURCES *.cpp)
|
||||
file(GLOB_RECURSE SRC_HEADERS *.h)
|
|
@ -15,7 +15,7 @@ class ChildProcess : public QProcess {
|
|||
Q_OBJECT
|
||||
public:
|
||||
explicit ChildProcess(QObject* parent = nullptr);
|
||||
~ChildProcess();
|
||||
~ChildProcess() override;
|
||||
protected:
|
||||
void setupChildProcess() override;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue