QR code feature beta and streamer mode

pull/45/head
dsc 3 years ago
parent 5bb95053fb
commit 13331ee5e7

@ -382,7 +382,20 @@ if(OPENVR)
add_definitions(-DVR_API_PUBLIC)
add_definitions(-DOPENVR_BUILD_STATIC) # is this needed?
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/contrib/openvr")
message(STATUS "yeepp")
endif()
if(APPLE)
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/contrib/KDMacTouchBar")
endif()
if(WITH_SCANNER)
add_library(quirc STATIC
contrib/quirc/lib/decode.c
contrib/quirc/lib/identify.c
contrib/quirc/lib/quirc.c
contrib/quirc/lib/version_db.c
)
target_include_directories(quirc PUBLIC contrib/quirc/lib)
endif()
add_subdirectory(src)

@ -89,7 +89,7 @@ RUN git clone -b libgcrypt-1.8.5 --depth 1 git://git.gnupg.org/libgcrypt.git &&
RUN git clone -b v1.2.11 --depth 1 https://github.com/madler/zlib && \
cd zlib && \
git reset --hard cacf7f1d4e3d44d871b605da3b647f07d718623f && \
CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-ar ./configure --static --prefix=/usr/x86_64-w64-mingw32 && \
CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-ar ./configure --static --prefix=/depends/x86_64-w64-mingw32 && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
@ -164,7 +164,7 @@ RUN git clone -b tor-0.4.5.7 --depth 1 https://git.torproject.org/tor.git && \
--enable-static-tor \
--with-libevent-dir=/usr/local/libevent \
--with-openssl-dir=/usr/local/openssl \
--with-zlib-dir=/usr/x86_64-w64-mingw32 \
--with-zlib-dir=/depends/x86_64-w64-mingw32 \
--disable-tool-name-check \
--enable-fatal-warnings \
--prefix=/usr/local/tor \
@ -174,7 +174,7 @@ RUN git clone -b tor-0.4.5.7 --depth 1 https://git.torproject.org/tor.git && \
rm -rf $(pwd) && \
strip -s -D /usr/local/tor/bin/tor.exe
RUN git clone https://git.featherwallet.org/feather/monero-seed.git && \
RUN git clone https://git.wownero.com/wowlet/monero-seed.git && \
cd monero-seed && \
git reset --hard 4674ef09b6faa6fe602ab5ae0b9ca8e1fd7d5e1b && \
cmake -DCMAKE_INSTALL_PREFIX=/depends/x86_64-w64-mingw32 \
@ -183,3 +183,4 @@ RUN git clone https://git.featherwallet.org/feather/monero-seed.git && \
make -Cbuild -j$THREADS && \
make -Cbuild install && \
rm -rf $(pwd)

@ -52,7 +52,12 @@ release-static:
depends:
mkdir -p build/$(target)/release
cd build/$(target)/release && cmake -D STATIC=ON -DREPRODUCIBLE=$(or ${SOURCE_DATE_EPOCH},OFF) -DTOR_VERSION=$(or ${TOR_VERSION}, OFF) -DOPENVR=ON -DQML=ON -DTOR_BIN=$(or ${TOR_BIN},OFF) -D DEV_MODE=$(or ${DEV_MODE},OFF) -D BUILD_TAG=$(tag) -D CMAKE_BUILD_TYPE=Debug -D CMAKE_TOOLCHAIN_FILE=$(root)/$(target)/share/toolchain.cmake ../../.. && $(MAKE)
cd build/$(target)/release && cmake -D STATIC=ON -DREPRODUCIBLE=$(or ${SOURCE_DATE_EPOCH},OFF) -DTOR_VERSION=$(or ${TOR_VERSION}, OFF) -DTOR_BIN=$(or ${TOR_BIN},OFF) -D DEV_MODE=$(or ${DEV_MODE},OFF) -D BUILD_TAG=$(tag) -D CMAKE_BUILD_TYPE=Release -D CMAKE_TOOLCHAIN_FILE=$(root)/$(target)/share/toolchain.cmake ../../.. && $(MAKE)
windows:
mkdir -p build/$(target)/release
cd build/$(target)/release && cmake -D STATIC=ON -DREPRODUCIBLE=$(or ${SOURCE_DATE_EPOCH},OFF) -DTOR_VERSION=$(or ${TOR_VERSION}, OFF) -DOPENVR=ON -DQML=ON -DWITH_SCANNER=ON -DTOR_BIN=$(or ${TOR_BIN},OFF) -D DEV_MODE=$(or ${DEV_MODE},OFF) -D BUILD_TAG=$(tag) -D CMAKE_BUILD_TYPE=Debug -D CMAKE_TOOLCHAIN_FILE=$(root)/$(target)/share/toolchain.cmake ../../.. && $(MAKE)
windows-mxe-release: CMAKEFLAGS += -DBUILD_TAG="win-x64"
windows-mxe-release: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF)

@ -1,6 +1,3 @@
# Set the minimum required version of CMake for this project.
cmake_minimum_required(VERSION 2.8)
# Set project name.
project(OpenVRSDK)

@ -5,6 +5,10 @@ set(CMAKE_AUTOUIC ON)
# pthread
find_package(Threads REQUIRED)
# PNG
find_package(ZLIB REQUIRED)
find_package(PNG REQUIRED)
# Compile these source files (.h/.cpp)
file(GLOB SOURCE_FILES
"*.h"
@ -34,9 +38,9 @@ file(GLOB SOURCE_FILES
)
if(QML)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Network Svg Xml WebSockets Quick Qml QuickControls2 QmlImportScanner)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Network Svg Xml WebSockets Quick Qml QuickControls2 QmlImportScanner Multimedia)
else()
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Network Svg Xml WebSockets)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Network Svg Xml WebSockets Multimedia)
endif()
if(OPENVR)
@ -57,6 +61,7 @@ add_subdirectory(libwalletqt)
add_subdirectory(model)
add_subdirectory(utils)
add_subdirectory(openpgp)
add_subdirectory(QR-Code-scanner)
qt5_add_resources(RESOURCES assets.qrc)
@ -256,7 +261,22 @@ target_link_libraries(wowlet PUBLIC
${ICU_LIBRARIES}
openpgp
Threads::Threads
${QRENCODE_LIBRARY})
${QRENCODE_LIBRARY}
qrdecoder
)
# Link scanner
if(WITH_SCANNER)
target_link_libraries(wowlet PUBLIC qrscanner)
if(LINUX AND NOT ANDROID)
target_link_libraries(wowlet PUBLIC
jpeg
v4l2
v4lconvert
rt
)
endif()
endif()
# Link OpenVR
if(OPENVR)

@ -0,0 +1,22 @@
add_library(qrdecoder STATIC
Decoder.cpp
)
target_link_libraries(qrdecoder
PUBLIC
Qt5::Gui
PNG::PNG
PRIVATE
quirc
)
if(WITH_SCANNER)
add_library(qrscanner
QrCodeScanner.cpp
QrScanThread.cpp
)
target_link_libraries(qrscanner
PUBLIC
Qt5::Multimedia
qrdecoder
)
endif()

@ -0,0 +1,359 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits>
#include <QDebug>
#include <QString>
#include "Decoder.h"
#include "quirc.h"
QrDecoder::QrDecoder()
: m_qr(quirc_new())
{
if (m_qr == nullptr)
{
throw std::runtime_error("QUIRC: failed to allocate memory");
}
}
QrDecoder::~QrDecoder()
{
quirc_destroy(m_qr);
}
std::vector<std::string> QrDecoder::decode(const QImage &image)
{
if (image.format() == QImage::Format_Grayscale8)
{
return decodeGrayscale8(image);
}
return decodeGrayscale8(image.convertToFormat(QImage::Format_Grayscale8));
}
std::vector<std::string> QrDecoder::decodePNG(QString pngPath) {
struct quirc *q;
std::vector<std::string> result;
auto pngPathStd = pngPath.toStdString();
auto pngPathCstr = pngPathStd.c_str();
q = quirc_new();
if (!q) {
qWarning() << "can't create quirc object";
return result;
}
int status = -1;
if (check_if_png(pngPathCstr)) {
status = load_png(q, pngPathCstr);
} else {
qWarning() << QString("Image is not a PNG: %1").arg(pngPath);
return result;
}
if (status < 0) {
quirc_destroy(q);
return result;
}
quirc_end(q);
auto count = quirc_count(q);
result.reserve(static_cast<size_t>(count));
for (int index = 0; index < count; ++index)
{
quirc_code code;
quirc_extract(q, index, &code);
quirc_data data;
const quirc_decode_error_t err = quirc_decode(&code, &data);
if (err == QUIRC_SUCCESS)
{
result.emplace_back(&data.payload[0], &data.payload[data.payload_len]);
}
}
quirc_destroy(q);
return result;
}
// I can't seem to get this function to work, we'll use dgbutil.h instead
std::vector<std::string> QrDecoder::decodeGrayscale8(const QImage &image)
{
if (quirc_resize(m_qr, image.width(), image.height()) < 0)
{
throw std::runtime_error("QUIRC: failed to allocate video memory");
}
uint8_t *rawImage = quirc_begin(m_qr, nullptr, nullptr);
if (rawImage == nullptr)
{
throw std::runtime_error("QUIRC: failed to get image buffer");
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
std::copy(image.constBits(), image.constBits() + image.sizeInBytes(), rawImage);
#else
std::copy(image.constBits(), image.constBits() + image.byteCount(), rawImage);
#endif
quirc_end(m_qr);
const int count = quirc_count(m_qr);
if (count < 0)
{
throw std::runtime_error("QUIRC: failed to get the number of recognized QR-codes");
}
std::vector<std::string> result;
result.reserve(static_cast<size_t>(count));
for (int index = 0; index < count; ++index)
{
quirc_code code;
quirc_extract(m_qr, index, &code);
quirc_data data;
const quirc_decode_error_t err = quirc_decode(&code, &data);
if (err == QUIRC_SUCCESS)
{
result.emplace_back(&data.payload[0], &data.payload[data.payload_len]);
}
}
return result;
}
/* quirc -- QR-code recognition library
* Copyright (C) 2010-2012 Daniel Beer <dlbeer@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
static const char *data_type_str(int dt)
{
switch (dt) {
case QUIRC_DATA_TYPE_NUMERIC: return "NUMERIC";
case QUIRC_DATA_TYPE_ALPHA: return "ALPHA";
case QUIRC_DATA_TYPE_BYTE: return "BYTE";
case QUIRC_DATA_TYPE_KANJI: return "KANJI";
}
return "unknown";
}
void QrDecoder::dump_data(const struct quirc_data *data)
{
printf(" Version: %d\n", data->version);
printf(" ECC level: %c\n", "MLHQ"[data->ecc_level]);
printf(" Mask: %d\n", data->mask);
printf(" Data type: %d (%s)\n",
data->data_type, data_type_str(data->data_type));
printf(" Length: %d\n", data->payload_len);
printf(" Payload: %s\n", data->payload);
if (data->eci)
printf(" ECI: %d\n", data->eci);
}
void QrDecoder::dump_cells(const struct quirc_code *code)
{
int u, v;
printf(" %d cells, corners:", code->size);
for (u = 0; u < 4; u++)
printf(" (%d,%d)", code->corners[u].x,
code->corners[u].y);
printf("\n");
for (v = 0; v < code->size; v++) {
printf(" ");
for (u = 0; u < code->size; u++) {
int p = v * code->size + u;
if (code->cell_bitmap[p >> 3] & (1 << (p & 7)))
printf("[]");
else
printf(" ");
}
printf("\n");
}
}
/* hacked from https://dev.w3.org/Amaya/libpng/example.c
*
* Check if a file is a PNG image using png_sig_cmp(). Returns 1 if the given
* file is a PNG and 0 otherwise.
*/
#define PNG_BYTES_TO_CHECK 4
int QrDecoder::check_if_png(const char *filename)
{
int ret = 0;
FILE *infile = NULL;
unsigned char buf[PNG_BYTES_TO_CHECK];
/* Open the prospective PNG file. */
if ((infile = fopen(filename, "rb")) == NULL)
goto out;
/* Read in some of the signature bytes */
if (fread(buf, 1, PNG_BYTES_TO_CHECK, infile) != PNG_BYTES_TO_CHECK)
goto out;
/*
* Compare the first PNG_BYTES_TO_CHECK bytes of the signature.
* png_sig_cmp() returns zero if the image is a PNG and nonzero if it
* isn't a PNG.
*/
if (png_sig_cmp(buf, (png_size_t)0, PNG_BYTES_TO_CHECK) == 0)
ret = 1;
/* FALLTHROUGH */
out:
if (infile)
fclose(infile);
return (ret);
}
int QrDecoder::load_png(struct quirc *q, const char *filename)
{
int width, height, rowbytes, interlace_type, number_passes = 1;
png_uint_32 trns;
png_byte color_type, bit_depth;
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
FILE *infile = NULL;
uint8_t *image;
int ret = -1;
int pass;
if ((infile = fopen(filename, "rb")) == NULL)
goto out;
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
goto out;
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
goto out;
if (setjmp(png_jmpbuf(png_ptr)))
goto out;
png_init_io(png_ptr, infile);
png_read_info(png_ptr, info_ptr);
color_type = png_get_color_type(png_ptr, info_ptr);
bit_depth = png_get_bit_depth(png_ptr, info_ptr);
interlace_type = png_get_interlace_type(png_ptr, info_ptr);
// Read any color_type into 8bit depth, Grayscale format.
// See http://www.libpng.org/pub/png/libpng-manual.txt
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png_ptr);
if ((trns = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)))
png_set_tRNS_to_alpha(png_ptr);
if (bit_depth == 16)
#if PNG_LIBPNG_VER >= 10504
png_set_scale_16(png_ptr);
#else
png_set_strip_16(png_ptr);
#endif
if ((trns) || color_type & PNG_COLOR_MASK_ALPHA)
png_set_strip_alpha(png_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE ||
color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
png_set_rgb_to_gray_fixed(png_ptr, 1, -1, -1);
}
if (interlace_type != PNG_INTERLACE_NONE)
number_passes = png_set_interlace_handling(png_ptr);
png_read_update_info(png_ptr, info_ptr);
width = png_get_image_width(png_ptr, info_ptr);
height = png_get_image_height(png_ptr, info_ptr);
rowbytes = png_get_rowbytes(png_ptr, info_ptr);
if (rowbytes != width) {
fprintf(stderr,
"load_png: expected rowbytes to be %u but got %u\n",
width, rowbytes);
goto out;
}
if (quirc_resize(q, width, height) < 0)
goto out;
image = quirc_begin(q, NULL, NULL);
for (pass = 0; pass < number_passes; pass++) {
int y;
for (y = 0; y < height; y++) {
png_bytep row_pointer = image + y * width;
png_read_rows(png_ptr, &row_pointer, NULL, 1);
}
}
png_read_end(png_ptr, info_ptr);
ret = 0;
/* FALLTHROUGH */
out:
/* cleanup */
if (png_ptr) {
if (info_ptr)
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
else
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
}
if (infile)
fclose(infile);
return (ret);
}

@ -0,0 +1,72 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QImage>
#include <png.h>
struct quirc;
class QrDecoder
{
public:
QrDecoder(const QrDecoder &) = delete;
QrDecoder &operator=(const QrDecoder &) = delete;
QrDecoder();
~QrDecoder();
std::vector<std::string> decode(const QImage &image);
std::vector<std::string> decodePNG(QString pngPath);
private:
/* Dump decoded information on stdout. */
void dump_data(const struct quirc_data *data);
/* Dump a grid cell map on stdout. */
void dump_cells(const struct quirc_code *code);
/* Check if a file is a PNG image.
*
* returns 1 if the given file is a PNG and 0 otherwise.
*/
int check_if_png(const char *filename);
/* Read a PNG image into the decoder.
*
* Note that you must call quirc_end() if the function returns
* successfully (0).
*/
int load_png(struct quirc *q, const char *filename);
private:
std::vector<std::string> decodeGrayscale8(const QImage &image);
private:
quirc *m_qr;
};

@ -0,0 +1,92 @@
// Copyright (c) 2014-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "QrCodeScanner.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();
QObject::connect(m_thread, SIGNAL(decoded(QString)), this, SIGNAL(decoded(QString)));
QObject::connect(m_thread, SIGNAL(notifyError(const QString &, bool)), this, SIGNAL(notifyError(const QString &, bool)));
connect(m_probe, SIGNAL(videoFrameProbed(QVideoFrame)), this, SLOT(processFrame(QVideoFrame)));
}
void QrCodeScanner::setSource(QCamera *camera)
{
m_probe->setSource(camera);
}
void QrCodeScanner::processFrame(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();
}
}

@ -0,0 +1,73 @@
// Copyright (c) 2014-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#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:
QrCodeScanner(QObject *parent = Q_NULLPTR);
~QrCodeScanner();
void setSource(QCamera*);
bool enabled() const;
void setEnabled(bool enabled);
public Q_SLOTS:
void processFrame(QVideoFrame);
Q_SIGNALS:
void enabledChanged();
void decoded(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

@ -0,0 +1,90 @@
// Copyright (c) 2014-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "QrScanThread.h"
#include <QtGlobal>
#include <QDebug>
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
extern QImage qt_imageFromVideoFrame(const QVideoFrame &f);
#endif
QrScanThread::QrScanThread(QObject *parent)
: QThread(parent)
,m_running(true)
{
}
void QrScanThread::processQImage(const QImage &qimg)
{
try {
for (const std::string &code : m_decoder.decode(qimg))
{
emit decoded(QString::fromStdString(code));
}
}
catch(std::exception &e) {
qDebug() << "ERROR: " << e.what();
emit notifyError(e.what());
}
}
void QrScanThread::processVideoFrame(const QVideoFrame &frame)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
processQImage( qt_imageFromVideoFrame(frame) );
#else
processQImage(frame.image());
#endif
}
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());
}
}

@ -0,0 +1,66 @@
// Copyright (c) 2014-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef _QRSCANTHREAD_H_
#define _QRSCANTHREAD_H_
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QEvent>
#include <QVideoFrame>
#include <QCamera>
#include "Decoder.h"
class QrScanThread : public QThread
{
Q_OBJECT
public:
QrScanThread(QObject *parent = Q_NULLPTR);
void addFrame(const QVideoFrame &frame);
virtual void stop();
Q_SIGNALS:
void decoded(const QString &data);
void notifyError(const QString &error, bool warning = false);
protected:
virtual void run();
void processVideoFrame(const QVideoFrame &);
void processQImage(const QImage &);
private:
QrDecoder m_decoder;
bool m_running;
QMutex m_mutex;
QWaitCondition m_waitCondition;
QList<QVideoFrame> m_queue;
};
#endif

@ -89,8 +89,9 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
if(!this->configDirectory.endsWith('/'))
this->configDirectory = QString("%1/").arg(this->configDirectory);
#endif
this->configDirectoryVR = QString("%1%2").arg(this->configDirectory, "vr");
// Config
// Create some directories
createConfigDirectory(this->configDirectory);
// if(this->cmdargs->isSet("stagenet"))
@ -545,8 +546,8 @@ void AppContext::onWSCCS(const QJsonArray &ccs_data) {
}
void AppContext::createConfigDirectory(const QString &dir) {
QString config_dir_tor = QString("%1%2").arg(dir).arg("tor");
QString config_dir_tordata = QString("%1%2").arg(dir).arg("tor/data");
auto config_dir_tor = QString("%1%2").arg(dir).arg("tor");
auto config_dir_tordata = QString("%1%2").arg(dir).arg("tor/data");
QStringList createDirs({dir, config_dir_tor, config_dir_tordata});
for(const auto &d: createDirs) {
@ -556,6 +557,13 @@ void AppContext::createConfigDirectory(const QString &dir) {
throw std::runtime_error("Could not create directory " + d.toStdString());
}
}
auto config_dir_vr = QString("%1%2").arg(dir, "vr");
if(!Utils::dirExists(config_dir_vr)) {
qDebug() << QString("Creating directory: %1").arg(config_dir_vr);
if (!QDir().mkpath(config_dir_vr))
throw std::runtime_error("Could not create directory " + config_dir_vr.toStdString());
}
}
void AppContext::createWalletWithoutSpecifyingSeed(const QString &name, const QString &password) {

@ -54,6 +54,7 @@ public:
QString accountName;
QString configRoot;
QString configDirectory;
QString configDirectoryVR;
QString defaultWalletDir;
QString defaultWalletDirRoot;
QString tmpTxDescription;

@ -26,6 +26,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::donateBeg,{QS("donateBeg"), 1}},
{Config::skin,{QS("skin"), "light"}},
{Config::openVRSkin,{QS("openVRSkin"), "default"}},
{Config::openVRStreamerMode,{QS("openVRStreamerMode"), false}},
{Config::preferredFiatCurrency,{QS("preferredFiatCurrency"), "USD"}},
{Config::blockExplorer,{QS("blockExplorer"), "explore.wownero.com"}},
{Config::walletDirectory,{QS("walletDirectory"), ""}},

@ -29,6 +29,7 @@ public:
autoOpenWalletPath,
skin,
openVRSkin,
openVRStreamerMode,
preferredFiatCurrency,
blockExplorer,
walletDirectory,

@ -42,6 +42,12 @@ QByteArray Utils::fileOpen(const QString &path) {
return data;
}
qint64 Utils::fileModifiedAge(const QString &path) {
QFileInfo fileInfo;
fileInfo.setFile(path);
return (QDateTime::currentSecsSinceEpoch() - fileInfo.lastModified().toSecsSinceEpoch());
}
QByteArray Utils::fileOpenQRC(const QString &path) {
QFile file(path);
if(!file.open(QIODevice::ReadOnly)) {

@ -46,6 +46,7 @@ public:
static bool fileExists(const QString &path);
static QByteArray fileOpen(const QString &path);
static QByteArray fileOpenQRC(const QString &path);
static qint64 fileModifiedAge(const QString &path);
static void desktopNotify(const QString &title, const QString &message, int duration);
static bool fileWrite(const QString &path, const QString &data);
static QStringList fileFind(const QRegExp &pattern, const QString &baseDir, int level, int depth, int maxPerDir);

@ -6,6 +6,7 @@
#include <QApplication>
#include <QCoreApplication>
#include <QQmlComponent>
#include <QObject>
#include <QtCore>
#include <QtGui>
#include <QQmlApplicationEngine>
@ -42,7 +43,13 @@ namespace wowletvr {
// turn on auto tx commits
ctx->autoCommitTx = true;
// write icon to disk so openvr overlay can refer to it
// QR code scanning from screenshots
m_qrScreenshotPreviewPath = ctx->configDirectoryVR + "/screenshot_preview";
m_qrScreenshotImagePath = ctx->configDirectoryVR + "/screenshot";
m_qrScreenshotTimer.setSingleShot(true);
connect(&m_qrScreenshotTimer, &QTimer::timeout, this, &WowletVR::onCheckQRScreenshot);
// write icon to disk so openvr overlay can use it
auto icon = ":/assets/images/wowlet.png";
if(Utils::fileExists(icon)) {
QFile f(icon);
@ -140,10 +147,79 @@ namespace wowletvr {
themes[themeName] = map;
}
};
}
void WowletVR::takeQRScreenshot() {
if(m_qrScreenshotTimer.isActive())
return;
m_controller->takeQRScreenshot(m_qrScreenshotPreviewPath, m_qrScreenshotImagePath);
m_qrScreenshotTimer.start(1000);
}
void WowletVR::onCheckQRScreenshot() {
qDebug() << "onCheckQRScreenshot()";
QString msg;
auto path = m_qrScreenshotPreviewPath + ".png";
auto pathPreview = m_qrScreenshotPreviewPath + "_inverted.png";
qDebug() << "path: " + path << " inverted: " + pathPreview;
if(!Utils::fileExists(path)) {
msg = "Screenshot was not saved to disk.";
qWarning() << msg;
emit qrScreenshotFailed(msg);
return;
}
auto age = Utils::fileModifiedAge(path);
if (age >= 5) {
msg = "Screenshot on disk too old. Leftover from the last time?";
qWarning() << msg;
emit qrScreenshotFailed(msg);
QFile file (path);
file.remove();
return;
}
auto results = m_qrDecoder.decodePNG(path);
auto result = wowletvr::WowletVR::checkQRScreenshotResults(results);
qDebug() << "no initial results";
if(result.isEmpty()) {
qDebug() << "trying to invert the image";
// lets try to invert the image
QImage image(path);
image.invertPixels();
image.save(pathPreview);
results = m_qrDecoder.decodePNG(pathPreview);
result = wowletvr::WowletVR::checkQRScreenshotResults(results);
if(!result.isEmpty()) {
qDebug() << "Found QR code after inverting the image.";
emit qrScreenshotSuccess(result);
QFile file (path);
file.remove();
return;
}
} else {
qDebug() << "QR code found.";
emit qrScreenshotSuccess(result);
QFile file (path);
file.remove();
return;
}
emit qrScreenshotSuccess("No QR code could be detected.");
}
QString WowletVR::checkQRScreenshotResults(std::vector<std::string> results) {
auto results_count = results.size();
if(results_count == 1)
return QString::fromStdString(results[0]);
return "";
}
WowletVR::~WowletVR() {
// bla
int wegeg = 1;
}
}

@ -10,11 +10,13 @@
#include <QtQml>
#include <QGuiApplication>
#include <QClipboard>
#include <QTimer>
#include <globals.h>
#include "overlaycontroller.h"
#include "appcontext.h"
#include "utils/config.h"
#include "QR-Code-scanner/Decoder.h"
namespace wowletvr {
@ -57,6 +59,14 @@ namespace wowletvr {
m_pClipboard->setText(text, QClipboard::Selection);
}
Q_INVOKABLE void setStreamerMode(bool status) {
config()->set(Config::openVRStreamerMode, status);
}
Q_INVOKABLE bool getStreamerMode() {
return config()->get(Config::openVRStreamerMode).toBool();
}
Q_INVOKABLE QString preferredFiat() {
return config()->get(Config::preferredFiatCurrency).toString();
}
@ -78,14 +88,31 @@ namespace wowletvr {
return QString("~%1").arg(QString::number(conversionAmount, 'f', 2));
}
Q_INVOKABLE void takeQRScreenshot();
signals:
void qrScreenshotFailed(QString error);
void qrScreenshotSuccess(QString address);
private slots:
void onCheckQRScreenshot();
private:
AppContext *ctx;
QCommandLineParser *m_parser;
QQmlEngine m_engine;
QQmlComponent *m_component;
bool desktopMode = false;
wowletvr::OverlayController *m_controller;
bool desktopMode = false;
QString m_qrScreenshotPreviewPath;
QString m_qrScreenshotImagePath;
QCommandLineParser *m_parser;
QClipboard *m_pClipboard;
QTimer m_qrScreenshotTimer;
QrDecoder m_qrDecoder;
static QString checkQRScreenshotResults(std::vector<std::string> results);
};
}

@ -24,6 +24,7 @@ Rectangle {
property var themes: {}
property string theme: "default"
property string fiatSymbol: "USD"
property bool streamerMode: false
signal initTheme();
// Components that have been dynamically created need to redraw
@ -307,6 +308,7 @@ Rectangle {
// Start animating the background
gradientBackgroundTimer.start();
// init some theme stuff
try {
appWindow.themes = WowletVR.getThemes();
appWindow.theme = WowletVR.getCurrentTheme();
@ -329,9 +331,13 @@ Rectangle {
}
}
}
appWindow.changeTheme(appWindow.theme);
appWindow.initTheme();
// streamer mode enabled?
try {
appWindow.streamerMode = WowletVR.getStreamerMode();
} catch(err){}
}
function changeTheme(theme) {

@ -0,0 +1,85 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.2
import QtGraphicalEffects 1.0
import QtQuick.Window 2.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2
import "."
import "mock/Windows.js" as Windows
import "mock/Version.js" as Version
import "mock/NetworkType.js" as NetworkType
import "mock/Settings.js" as Settings
import "mock"
import "qml/common"
import "qml/."
Rectangle {
width: 1600
height: 800
color: "red"
property var currentWallet;
property bool disconnected: currentWallet ? currentWallet.disconnected : false
property WalletDashboard WalletDashboard: WalletDashboard {
stackView: walletView
}
property SendPage sendPage: SendPage {
stackView: walletView
visible: false
}
property ReceivePage receivePage: ReceivePage {
stackView: walletView
visible: false
}
StackView {
id: walletView
anchors.fill: parent
pushEnter: Transition {
PropertyAnimation {
property: "x"
from: walletView.width
to: 0
duration: 300
easing.type: Easing.OutCubic
}
}
pushExit: Transition {
PropertyAnimation {
property: "x"
from: 0
to: -walletView.width
duration: 300
easing.type: Easing.OutCubic
}
}
popEnter: Transition {
PropertyAnimation {
property: "x"
from: -walletView.width
to: 0
duration: 300
easing.type: Easing.OutCubic
}
}
popExit: Transition {
PropertyAnimation {
property: "x"
from: 0
to: walletView.width
duration: 300
easing.type: Easing.OutCubic
}
}
initialItem: WalletDashboard
}
}

@ -459,4 +459,16 @@ const vr::VROverlayHandle_t& OverlayController::overlayThumbnailHandle() {
return m_ulOverlayThumbnailHandle;
}
void OverlayController::takeQRScreenshot(const QString &previewPath, const QString &imagePath) {
vr::IVRScreenshots *screen_taker = vr::VRScreenshots();
vr::ScreenshotHandle_t taker_handle;
screen_taker->RequestScreenshot(&taker_handle,
vr::EVRScreenshotType::VRScreenshotType_Mono,
previewPath.toStdString().c_str(),
imagePath.toStdString().c_str()
);
}
} // namespace wowletvr

@ -28,7 +28,6 @@
#include "openvr_init.h"
#include "vr/utils/paths.h"
#include "appcontext.h"
namespace application_strings
{
@ -59,6 +58,7 @@ public:
void Shutdown();
Q_INVOKABLE void exitApp();
void takeQRScreenshot(const QString &previewPath, const QString &imagePath);
bool isDashboardVisible()
{
@ -117,8 +117,6 @@ private:
bool m_keyPressOneState = false;
bool m_keyPressTwoState = false;
AppContext *m_ctx;
public slots:
void renderOverlay();
void OnRenderRequest();

Binary file not shown.

@ -48,7 +48,7 @@ ColumnLayout {
Layout.rightMargin: 40
Layout.fillWidth: true
fontSize: 21
text: "Shoutouts: matzman666, qvqc, ez, Gatto, RAGEHAÜZ, cisme, wowario, lza_menace, jwinterm, nioc, asymptotically, azy, selsta, kico, laura, thrmo, rottensox, solar, bl4sty, scoobybejesus"
text: "Shoutouts: matzman666, qvqc, ez, Gatto, RAGEHAÜZ, cisme, wowario, lza_menace, jwinterm, nioc, asymptotically, azy, selsta, kico, laura, thrmo, rottensox, GNVR, solar, bl4sty, scoobybejesus, Valve Corporation for OpenVR"
wrap: true
}

@ -27,6 +27,13 @@ ColumnLayout {
wrap: true
}
Rectangle {
color: Style.dividerColor
height: 1
Layout.topMargin: 10
Layout.fillWidth: true
}
RowLayout {
spacing: 30
@ -67,6 +74,43 @@ ColumnLayout {
}
}
}
RowLayout {
spacing: 30
MyText {
text: "Streamer mode (" + (appWindow.streamerMode ? "ON" : "OFF") + ")"
}
RowLayout {
spacing: 20
Layout.fillWidth: true
Layout.preferredHeight: 56
MyPushButton {
Layout.preferredWidth: 100
enabled: !appWindow.streamerMode
opacity: !appWindow.streamerMode ? 1.0 : 0.3
text: "On"
onClicked: {
appWindow.streamerMode = !appWindow.streamerMode;
WowletVR.setStreamerMode(appWindow.streamerMode);
}
}
MyPushButton {
Layout.preferredWidth: 100
enabled: appWindow.streamerMode
opacity: appWindow.streamerMode ? 1.0 : 0.3
text: "Off"
onClicked: {
appWindow.streamerMode = !appWindow.streamerMode;
WowletVR.setStreamerMode(appWindow.streamerMode);
}
}
}
}
}
Component.onCompleted: {

@ -108,7 +108,12 @@ Rectangle {
anchors.bottom: parent.bottom
fontSize: 30
fontBold: true
text: appWindow.balanceFormatted
text: {
if(!appWindow.streamerMode)
return appWindow.balanceFormatted;
else
return "HIDDEN";
}
}
}
}
@ -236,7 +241,10 @@ Rectangle {
text: {
let rtn = "Balance: ";
try {
if(!appWindow.streamerMode)
rtn += WowletVR.wowToFiat(appWindow.spendable);
else
rtn += "HIDDEN";
} catch(err) {
rtn += "ERROR";
}

@ -9,22 +9,25 @@ ColumnLayout {
id: root
spacing: 20
property bool takingScreenshot: false
Layout.fillWidth: true
MyText {
Layout.fillWidth: true
wrap: true
fontColor: Style.fontColorBright
text: "Look at a QR code in VR and take a screenshot."
text: "Look at a QR code and press the button below to take a screenshot. Note: make sure to look at the center of the QR code. The parser works best with simple, straight-forward QR codes. When using more complex QR codes, make sure to properly fill your screen with the QR code itself (plus some margins)."
}
MyPushButton {
id: continueButton
text: "Take in-game screenshot"
Layout.preferredWidth: 490
opacity: takingScreenshot ? 0.0 : 1.0
onClicked: {
// QR thingy
root.takingScreenshot = true;
WowletVR.takeQRScreenshot();
}
}
@ -42,8 +45,33 @@ ColumnLayout {
Layout.fillWidth: true
}
function reset() {
Connections {
target: WowletVR
function onQrScreenshotSuccess(address) {
root.takingScreenshot = false;
console.log("onPinLookupReceived", address);
if(!address.startsWith("wownero:")) {
messagePopup.showMessage("Invalid QR code", "QR data did not start with \"wownero:\"");
return;
}
if(sendStateView.currentView === sendStateView.qrPage) {
sendStateController.destinationAddress = address.slice(8);
sendStateView.state = "transferPage";
}
}
function onQrScreenshotFailed(msg) {
root.takingScreenshot = false;
console.log("onQrScreenshotFailed", msg);
messagePopup.showMessage("QR scan failure", msg)
reset();
}
}
function reset() {
root.takingScreenshot = false;
}
function onPageCompleted(previousView){

Loading…
Cancel
Save