diff --git a/CMakeLists.txt b/CMakeLists.txt index b3accf6..1742644 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/Dockerfile.windows b/Dockerfile.windows index c2ac24d..ba97ac8 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -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) + diff --git a/Makefile b/Makefile index e97dc97..110955b 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/contrib/openvr/CMakeLists.txt b/contrib/openvr/CMakeLists.txt index 8956cda..4991ee5 100755 --- a/contrib/openvr/CMakeLists.txt +++ b/contrib/openvr/CMakeLists.txt @@ -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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 67e3129..17fe93c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/QR-Code-scanner/CMakeLists.txt b/src/QR-Code-scanner/CMakeLists.txt new file mode 100644 index 0000000..110e543 --- /dev/null +++ b/src/QR-Code-scanner/CMakeLists.txt @@ -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() diff --git a/src/QR-Code-scanner/Decoder.cpp b/src/QR-Code-scanner/Decoder.cpp new file mode 100644 index 0000000..44f6d9a --- /dev/null +++ b/src/QR-Code-scanner/Decoder.cpp @@ -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 +#include +#include +#include +#include +#include +#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 QrDecoder::decode(const QImage &image) +{ + if (image.format() == QImage::Format_Grayscale8) + { + return decodeGrayscale8(image); + } + return decodeGrayscale8(image.convertToFormat(QImage::Format_Grayscale8)); +} + +std::vector QrDecoder::decodePNG(QString pngPath) { + struct quirc *q; + std::vector 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(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 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 result; + result.reserve(static_cast(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 + * + * 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); +} diff --git a/src/QR-Code-scanner/Decoder.h b/src/QR-Code-scanner/Decoder.h new file mode 100644 index 0000000..665fdfd --- /dev/null +++ b/src/QR-Code-scanner/Decoder.h @@ -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 + +#include + +struct quirc; + +class QrDecoder +{ +public: + QrDecoder(const QrDecoder &) = delete; + QrDecoder &operator=(const QrDecoder &) = delete; + + QrDecoder(); + ~QrDecoder(); + + std::vector decode(const QImage &image); + std::vector 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 decodeGrayscale8(const QImage &image); + +private: + quirc *m_qr; +}; diff --git a/src/QR-Code-scanner/QrCodeScanner.cpp b/src/QR-Code-scanner/QrCodeScanner.cpp new file mode 100644 index 0000000..2e8bde9 --- /dev/null +++ b/src/QR-Code-scanner/QrCodeScanner.cpp @@ -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 +#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(); + 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(); + } + +} + diff --git a/src/QR-Code-scanner/QrCodeScanner.h b/src/QR-Code-scanner/QrCodeScanner.h new file mode 100644 index 0000000..10cf776 --- /dev/null +++ b/src/QR-Code-scanner/QrCodeScanner.h @@ -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 +#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: + 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 + diff --git a/src/QR-Code-scanner/QrScanThread.cpp b/src/QR-Code-scanner/QrScanThread.cpp new file mode 100644 index 0000000..6421321 --- /dev/null +++ b/src/QR-Code-scanner/QrScanThread.cpp @@ -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 +#include + +#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()); + } +} + diff --git a/src/QR-Code-scanner/QrScanThread.h b/src/QR-Code-scanner/QrScanThread.h new file mode 100644 index 0000000..7dfffc5 --- /dev/null +++ b/src/QR-Code-scanner/QrScanThread.h @@ -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 +#include +#include +#include +#include +#include + +#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 m_queue; +}; +#endif diff --git a/src/appcontext.cpp b/src/appcontext.cpp index ff63609..1a01197 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -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) { diff --git a/src/appcontext.h b/src/appcontext.h index dcca7e9..1372d10 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -54,6 +54,7 @@ public: QString accountName; QString configRoot; QString configDirectory; + QString configDirectoryVR; QString defaultWalletDir; QString defaultWalletDirRoot; QString tmpTxDescription; diff --git a/src/utils/config.cpp b/src/utils/config.cpp index 4868860..432ce69 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -26,6 +26,7 @@ static const QHash 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"), ""}}, diff --git a/src/utils/config.h b/src/utils/config.h index b49155a..389f047 100644 --- a/src/utils/config.h +++ b/src/utils/config.h @@ -29,6 +29,7 @@ public: autoOpenWalletPath, skin, openVRSkin, + openVRStreamerMode, preferredFiatCurrency, blockExplorer, walletDirectory, diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index eef71b9..c62cd84 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -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)) { diff --git a/src/utils/utils.h b/src/utils/utils.h index 5436a4d..9cee19f 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -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); diff --git a/src/vr/main.cpp b/src/vr/main.cpp index cd1bf40..da2bce8 100644 --- a/src/vr/main.cpp +++ b/src/vr/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -42,9 +43,15 @@ 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)) { + if(Utils::fileExists(icon)) { QFile f(icon); QFileInfo fileInfo(f); auto icon_path = QDir(ctx->configDirectory).filePath(fileInfo.fileName()); @@ -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 results) { + auto results_count = results.size(); + if(results_count == 1) + return QString::fromStdString(results[0]); + return ""; + } WowletVR::~WowletVR() { // bla + int wegeg = 1; } -} \ No newline at end of file +} diff --git a/src/vr/main.h b/src/vr/main.h index aa13882..6d7a28e 100644 --- a/src/vr/main.h +++ b/src/vr/main.h @@ -10,11 +10,13 @@ #include #include #include +#include #include #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 results); }; } diff --git a/src/vr/main.qml b/src/vr/main.qml index 61472db..64a0470 100644 --- a/src/vr/main.qml +++ b/src/vr/main.qml @@ -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) { diff --git a/src/vr/main2.qml b/src/vr/main2.qml new file mode 100644 index 0000000..6d605cc --- /dev/null +++ b/src/vr/main2.qml @@ -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 + } +} diff --git a/src/vr/overlaycontroller.cpp b/src/vr/overlaycontroller.cpp index 166e137..675aadf 100755 --- a/src/vr/overlaycontroller.cpp +++ b/src/vr/overlaycontroller.cpp @@ -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 diff --git a/src/vr/overlaycontroller.h b/src/vr/overlaycontroller.h index 6ca0dc5..2d721c8 100755 --- a/src/vr/overlaycontroller.h +++ b/src/vr/overlaycontroller.h @@ -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(); diff --git a/src/vr/qml.rcc b/src/vr/qml.rcc new file mode 100644 index 0000000..4156b55 Binary files /dev/null and b/src/vr/qml.rcc differ diff --git a/src/vr/qml/AboutPage.qml b/src/vr/qml/AboutPage.qml index 9c50a6d..a87330b 100644 --- a/src/vr/qml/AboutPage.qml +++ b/src/vr/qml/AboutPage.qml @@ -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 } diff --git a/src/vr/qml/SettingsPage.qml b/src/vr/qml/SettingsPage.qml index 890e387..287f7be 100644 --- a/src/vr/qml/SettingsPage.qml +++ b/src/vr/qml/SettingsPage.qml @@ -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: { diff --git a/src/vr/qml/common/MyStackViewPage.qml b/src/vr/qml/common/MyStackViewPage.qml index 0ad0566..792805e 100755 --- a/src/vr/qml/common/MyStackViewPage.qml +++ b/src/vr/qml/common/MyStackViewPage.qml @@ -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 { - rtn += WowletVR.wowToFiat(appWindow.spendable); + if(!appWindow.streamerMode) + rtn += WowletVR.wowToFiat(appWindow.spendable); + else + rtn += "HIDDEN"; } catch(err) { rtn += "ERROR"; } diff --git a/src/vr/qml/wallet/send/SendPageQR.qml b/src/vr/qml/wallet/send/SendPageQR.qml index a8ab76c..c2310ff 100644 --- a/src/vr/qml/wallet/send/SendPageQR.qml +++ b/src/vr/qml/wallet/send/SendPageQR.qml @@ -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,11 +45,36 @@ ColumnLayout { Layout.fillWidth: true } + 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){ reset(); } -} \ No newline at end of file +}