Merge pull request 'Beta-6' (#357) from tobtoht/feather:hw_ledger into master

Reviewed-on: https://git.featherwallet.org/feather/feather/pulls/357
This commit is contained in:
tobtoht 2021-05-04 23:17:01 +00:00
commit 7594b3d3e5
156 changed files with 6255 additions and 1952 deletions

1
.gitignore vendored
View file

@ -14,3 +14,4 @@ src/tor/*
!src/tor/.gitkeep !src/tor/.gitkeep
src/config-feather.h src/config-feather.h
src/assets/exec/* src/assets/exec/*
feather.AppDir/*

View file

@ -22,7 +22,7 @@ git clone --branch master --recursive https://git.featherwallet.org/feather/feat
cd feather cd feather
``` ```
Replace `master` with the desired version tag (e.g. `beta-4`) to build the release binary. Replace `master` with the desired version tag (e.g. `beta-6`) to build the release binary.
#### 2. Base image #### 2. Base image
@ -37,7 +37,7 @@ Building the base image takes a while. You only need to build the base image onc
##### Standalone binary ##### Standalone binary
```bash ```bash
docker run --rm -it -v $PWD:/feather -w /feather feather:linux sh -c 'make release-static -j4' docker run --rm -it -v $PWD:/feather -w /feather feather:linux sh -c 'CHECK_UPDATES=On make release-static -j4'
``` ```
If you're re-running a build make sure to `rm -rf build/` first. If you're re-running a build make sure to `rm -rf build/` first.
@ -75,7 +75,7 @@ Building the base image takes a while. You only need to build the base image onc
#### 3. Build #### 3. Build
```bash ```bash
docker run --rm -it -v $PWD:/feather -w /feather feather:win sh -c 'make depends root=/depends target=x86_64-w64-mingw32 tag=win-x64 -j4' docker run --rm -it -v $PWD:/feather -w /feather feather:win sh -c 'CHECK_UPDATES=On make depends root=/depends target=x86_64-w64-mingw32 tag=win-x64 -j4'
``` ```
If you're re-running a build make sure to `rm -rf build/` first. If you're re-running a build make sure to `rm -rf build/` first.

View file

@ -7,11 +7,13 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
set(VERSION_MAJOR "0") set(VERSION_MAJOR "0")
set(VERSION_MINOR "1") set(VERSION_MINOR "1")
set(VERSION_REVISION "0") set(VERSION_REVISION "0")
set(VERSION "beta-5") set(VERSION "beta-6")
option(FETCH_DEPS "Download dependencies if they are not found" ON) option(FETCH_DEPS "Download dependencies if they are not found" ON)
option(LOCALMONERO "Include LocalMonero module" ON)
option(XMRIG "Include XMRig module" ON) option(XMRIG "Include XMRig module" ON)
option(TOR_BIN "Path to Tor binary to embed inside Feather" OFF) option(TOR_BIN "Path to Tor binary to embed inside Feather" OFF)
option(CHECK_UPDATES "Enable checking for application updates" OFF)
option(STATIC "Link libraries statically, requires static Qt") option(STATIC "Link libraries statically, requires static Qt")
option(USE_DEVICE_TREZOR "Trezor support compilation" OFF) option(USE_DEVICE_TREZOR "Trezor support compilation" OFF)
@ -29,7 +31,7 @@ if(DEBUG)
set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_VERBOSE_MAKEFILE ON)
endif() endif()
set(MONERO_HEAD "41327974116dedccc2f9709d8ad3a8a1f591faed") set(MONERO_HEAD "36fb05da3394505f8033ceb8806b28909617696f")
set(BUILD_GUI_DEPS ON) set(BUILD_GUI_DEPS ON)
set(ARCH "x86-64") set(ARCH "x86-64")
set(BUILD_64 ON) set(BUILD_64 ON)
@ -139,6 +141,11 @@ if(NOT monero-seed_FOUND)
endif() endif()
endif() endif()
# libzip
find_package(zlib CONFIG)
find_path(LIBZIP_INCLUDE_DIRS zip.h)
find_library(LIBZIP_LIBRARIES zip)
# Boost # Boost
if(DEBUG) if(DEBUG)
set(Boost_DEBUG ON) set(Boost_DEBUG ON)

View file

@ -323,17 +323,17 @@ RUN rm /usr/lib/x86_64-linux-gnu/libX11.a && \
cd ../../../.. && \ cd ../../../.. && \
rm -rf $(pwd) rm -rf $(pwd)
RUN git clone -b v1.0.23 --depth 1 https://github.com/libusb/libusb && \ RUN git clone -b v1.0.24 --depth 1 https://github.com/libusb/libusb && \
cd libusb && \ cd libusb && \
git reset --hard e782eeb2514266f6738e242cdcb18e3ae1ed06fa && \ git reset --hard c6a35c56016ea2ab2f19115d2ea1e85e0edae155 && \
./autogen.sh --disable-shared --enable-static && \ ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \ make -j$THREADS && \
make -j$THREADS install && \ make -j$THREADS install && \
rm -rf $(pwd) rm -rf $(pwd)
RUN git clone -b hidapi-0.9.0 --depth 1 https://github.com/libusb/hidapi && \ RUN git clone -b hidapi-0.10.1 --depth 1 https://github.com/libusb/hidapi && \
cd hidapi && \ cd hidapi && \
git reset --hard 7da5cc91fc0d2dbe4df4f08cd31f6ca1a262418f && \ git reset --hard f6d0073fcddbdda24549199445e844971d3c9cef && \
./bootstrap && \ ./bootstrap && \
./configure --disable-shared --enable-static && \ ./configure --disable-shared --enable-static && \
make -j$THREADS && \ make -j$THREADS && \
@ -384,9 +384,9 @@ RUN git clone -b v3.18.4 --depth 1 https://github.com/Kitware/CMake && \
make -j$THREADS install && \ make -j$THREADS install && \
rm -rf $(pwd) rm -rf $(pwd)
RUN git clone -b v4.0.2 --depth 1 https://github.com/fukuchi/libqrencode.git && \ RUN git clone -b v4.1.1 --depth 1 https://github.com/fukuchi/libqrencode.git && \
cd libqrencode && \ cd libqrencode && \
git reset --hard 59ee597f913fcfda7a010a6e106fbee2595f68e4 && \ git reset --hard 715e29fd4cd71b6e452ae0f4e36d917b43122ce8 && \
cmake -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=/usr . && \ cmake -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=/usr . && \
make -j$THREADS && \ make -j$THREADS && \
make -j$THREADS install && \ make -j$THREADS install && \
@ -414,3 +414,10 @@ RUN mkdir linuxdeployqt && \
chmod +x linuxdeployqt-7-x86_64.AppImage && \ chmod +x linuxdeployqt-7-x86_64.AppImage && \
./linuxdeployqt-7-x86_64.AppImage --appimage-extract && \ ./linuxdeployqt-7-x86_64.AppImage --appimage-extract && \
rm linuxdeployqt-7-x86_64.AppImage rm linuxdeployqt-7-x86_64.AppImage
RUN git clone -b v1.7.3 --depth 1 https://github.com/nih-at/libzip.git && \
cd libzip && \
git reset --hard 66e496489bdae81bfda8b0088172871d8fda0032 && \
cmake -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=/usr . && \
make -j$THREADS && \
make -j$THREADS install

View file

@ -15,9 +15,9 @@ RUN apt update && \
RUN update-alternatives --set x86_64-w64-mingw32-g++ $(which x86_64-w64-mingw32-g++-posix) && \ RUN update-alternatives --set x86_64-w64-mingw32-g++ $(which x86_64-w64-mingw32-g++-posix) && \
update-alternatives --set x86_64-w64-mingw32-gcc $(which x86_64-w64-mingw32-gcc-posix) update-alternatives --set x86_64-w64-mingw32-gcc $(which x86_64-w64-mingw32-gcc-posix)
RUN git clone -b v0.17.1.9 --depth 1 https://github.com/monero-project/monero && \ RUN git clone -b v0.17.2.0 --depth 1 https://github.com/monero-project/monero && \
cd monero && \ cd monero && \
git reset --hard 8fef32e45c80aec41f25be9d1d8fb75adc883c64 && \ git reset --hard f6e63ef260e795aacd408c28008398785b84103a && \
cp -a contrib/depends / && \ cp -a contrib/depends / && \
cd .. && \ cd .. && \
rm -rf monero rm -rf monero
@ -34,12 +34,12 @@ RUN git clone git://code.qt.io/qt/qt5.git -b ${QT_VERSION} --depth 1 && \
git clone git://code.qt.io/qt/qttranslations.git -b ${QT_VERSION} --depth 1 && \ git clone git://code.qt.io/qt/qttranslations.git -b ${QT_VERSION} --depth 1 && \
git clone git://code.qt.io/qt/qtxmlpatterns.git -b ${QT_VERSION} --depth 1 && \ git clone git://code.qt.io/qt/qtxmlpatterns.git -b ${QT_VERSION} --depth 1 && \
git clone git://code.qt.io/qt/qtwebsockets.git -b ${QT_VERSION} --depth 1 && \ git clone git://code.qt.io/qt/qtwebsockets.git -b ${QT_VERSION} --depth 1 && \
OPENSSL_LIBS="-lssl -lcrypto -lpthread -ldl" \ OPENSSL_LIBS="-lssl -lcrypto -lws2_32" \
./configure --prefix=/depends/x86_64-w64-mingw32 -xplatform win32-g++ \ ./configure --prefix=/depends/x86_64-w64-mingw32 -xplatform win32-g++ \
-device-option CROSS_COMPILE=/usr/bin/x86_64-w64-mingw32- \ -device-option CROSS_COMPILE=/usr/bin/x86_64-w64-mingw32- \
-I $(pwd)/qtbase/src/3rdparty/angle/include \ -I $(pwd)/qtbase/src/3rdparty/angle/include \
-opensource -confirm-license -release -static -static-runtime -no-opengl \ -opensource -confirm-license -release -static -static-runtime -no-opengl \
-no-avx -openssl -I /depends/x86_64-w64-mingw32/include -L /depends/x86_64-w64-mingw32/lib \ -no-avx -openssl-linked -I /depends/x86_64-w64-mingw32/include -L /depends/x86_64-w64-mingw32/lib \
-qt-freetype -qt-harfbuzz -qt-libjpeg -qt-libpng -qt-pcre -qt-zlib \ -qt-freetype -qt-harfbuzz -qt-libjpeg -qt-libpng -qt-pcre -qt-zlib \
-skip gamepad -skip location -skip qt3d -skip qtactiveqt -skip qtandroidextras \ -skip gamepad -skip location -skip qt3d -skip qtactiveqt -skip qtandroidextras \
-skip qtcanvas3d -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdoc \ -skip qtcanvas3d -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdoc \
@ -122,11 +122,11 @@ RUN wget https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.16.tar.gz && \
rm -rf $(pwd) rm -rf $(pwd)
# OpenSSL -> Tor # OpenSSL -> Tor
RUN wget https://www.openssl.org/source/openssl-1.1.1.k.tar.gz && \ RUN wget https://www.openssl.org/source/openssl-1.1.1k.tar.gz && \
echo "892a0875b9872acd04a9fde79b1f943075d5ea162415de3047c327df33fbaee5 openssl-1.1.1.k.tar.gz" | sha256sum -c && \ echo "892a0875b9872acd04a9fde79b1f943075d5ea162415de3047c327df33fbaee5 openssl-1.1.1k.tar.gz" | sha256sum -c && \
tar -xzf openssl-1.1.1.k.tar.gz && \ tar -xzf openssl-1.1.1k.tar.gz && \
rm openssl-1.1.1.k.tar.gz && \ rm openssl-1.1.1k.tar.gz && \
cd openssl-1.1.1.k && \ cd openssl-1.1.1k && \
./Configure mingw64 no-shared no-dso --cross-compile-prefix=x86_64-w64-mingw32- --prefix=/usr/local/openssl && \ ./Configure mingw64 no-shared no-dso --cross-compile-prefix=x86_64-w64-mingw32- --prefix=/usr/local/openssl && \
make -j$THREADS && \ make -j$THREADS && \
make -j$THREADS install_sw && \ make -j$THREADS install_sw && \
@ -180,3 +180,13 @@ RUN git clone https://git.featherwallet.org/feather/monero-seed.git && \
make -Cbuild -j$THREADS && \ make -Cbuild -j$THREADS && \
make -Cbuild install && \ make -Cbuild install && \
rm -rf $(pwd) rm -rf $(pwd)
RUN git clone https://github.com/nih-at/libzip.git && \
cd libzip && \
git reset --hard 66e496489bdae81bfda8b0088172871d8fda0032 && \
cmake -DCMAKE_INSTALL_PREFIX=/depends/x86_64-w64-mingw32 \
-DCMAKE_TOOLCHAIN_FILE=/depends/x86_64-w64-mingw32/share/toolchain.cmake \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_PREFIX_PATH=/usr/x86_64-w64-mingw32 && \
make -j$THREADS && \
make -j$THREADS install

View file

@ -41,9 +41,11 @@ via the `CMAKE_PREFIX_PATH` definition. For me this is:
There are some Monero/Feather related options/definitions that you may pass: There are some Monero/Feather related options/definitions that you may pass:
- `-DLOCALMONERO=OFF` - disable LocalMonero feature
- `-DXMRIG=OFF` - disable XMRig feature - `-DXMRIG=OFF` - disable XMRig feature
- `-DTOR_BIN=/path/to/tor` - Embed a Tor executable inside Feather - `-DTOR_BIN=/path/to/tor` - Embed a Tor executable inside Feather
- `-DDONATE_BEG=OFF` - disable the dreaded donate requests - `-DDONATE_BEG=OFF` - disable the dreaded donate requests
- `-DCHECK_UPDATES=ON` - enable checking for updates, only for standalone binaries
And: And:

View file

@ -30,7 +30,9 @@ CMAKEFLAGS = \
-DARCH=x86_64 \ -DARCH=x86_64 \
-DBUILD_64=On \ -DBUILD_64=On \
-DBUILD_TESTS=Off \ -DBUILD_TESTS=Off \
-DLOCALMONERO=On \
-DXMRIG=On \ -DXMRIG=On \
-DCHECK_UPDATES=Off \
-DTOR_BIN=Off \ -DTOR_BIN=Off \
-DCMAKE_CXX_STANDARD=11 \ -DCMAKE_CXX_STANDARD=11 \
-DCMAKE_VERBOSE_MAKEFILE=On \ -DCMAKE_VERBOSE_MAKEFILE=On \
@ -42,6 +44,7 @@ CMAKEFLAGS = \
release-static: CMAKEFLAGS += -DBUILD_TAG="linux-x64" release-static: CMAKEFLAGS += -DBUILD_TAG="linux-x64"
release-static: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF) release-static: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF)
release-static: CMAKEFLAGS += -DCHECK_UPDATES=$(or ${CHECK_UPDATES}, Off)
release-static: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release release-static: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
release-static: CMAKEFLAGS += -DREPRODUCIBLE=$(or ${SOURCE_DATE_EPOCH},OFF) release-static: CMAKEFLAGS += -DREPRODUCIBLE=$(or ${SOURCE_DATE_EPOCH},OFF)
release-static: release-static:
@ -50,10 +53,11 @@ release-static:
depends: depends:
mkdir -p build/$(target)/release 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) -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) 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) -DCHECK_UPDATES=$(or ${CHECK_UPDATES}, 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)
mac-release: CMAKEFLAGS += -DSTATIC=Off mac-release: CMAKEFLAGS += -DSTATIC=Off
mac-release: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF) mac-release: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF)
mac-release: CMAKEFLAGS += -DCHECK_UPDATES=$(or ${CHECK_UPDATES}, Off)
mac-release: CMAKEFLAGS += -DBUILD_TAG="mac-x64" mac-release: CMAKEFLAGS += -DBUILD_TAG="mac-x64"
mac-release: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release mac-release: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
mac-release: mac-release:

View file

@ -2,24 +2,19 @@
# Contributor: wowario <wowario[at]protonmail[dot]com> # Contributor: wowario <wowario[at]protonmail[dot]com>
pkgname='monero-feather-git' pkgname='monero-feather-git'
pkgver=0.1.0.925ef5683 pkgver=0.5.0.d1bb4c143f
pkgrel=1 pkgrel=1
pkgdesc='a free Monero desktop wallet' pkgdesc='a free Monero desktop wallet'
license=('BSD') license=('BSD')
arch=('x86_64') arch=('x86_64')
url="https://featherwallet.org" url="https://featherwallet.org"
depends=('boost-libs' 'libunwind' 'openssl' 'readline' 'pcsclite' 'hidapi' 'protobuf' 'miniupnpc' 'libgcrypt' 'qrencode' 'libsodium' 'libpgm' 'expat' 'qt5-base' 'qt5-websockets' 'tor') depends=('boost-libs' 'libunwind' 'openssl' 'readline' 'zeromq' 'pcsclite' 'hidapi' 'protobuf' 'libusb' 'libudev.so' 'miniupnpc' 'libgcrypt' 'qrencode' 'libsodium' 'libpgm' 'expat' 'qt5-base' 'qt5-websockets' 'qt5-svg' 'tor' 'libzip')
makedepends=('git' 'cmake' 'boost') makedepends=('git' 'cmake' 'boost')
source=("${pkgname}"::"git+https://git.featherwallet.org/feather/feather") source=("${pkgname}"::"git+https://git.featherwallet.org/feather/feather")
sha256sums=('SKIP') sha256sums=('SKIP')
pkgver() {
cd "${srcdir}/${pkgname}"
printf "%s.%s" "$(git describe --tags --abbrev=0)" "$(git rev-parse --short=9 HEAD)"
}
build() { build() {
cd "${srcdir}/${pkgname}" cd "${srcdir}/${pkgname}"
git submodule update --init --recursive git submodule update --init --recursive
@ -32,4 +27,6 @@ build() {
package_monero-feather-git() { package_monero-feather-git() {
install -Dm644 "${srcdir}/${pkgname}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" install -Dm644 "${srcdir}/${pkgname}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
install -Dm755 "${srcdir}/${pkgname}/build/bin/feather" "${pkgdir}/usr/bin/feather" install -Dm755 "${srcdir}/${pkgname}/build/bin/feather" "${pkgdir}/usr/bin/feather"
install -Dm644 "${srcdir}/${pkgname}/src/assets/feather.desktop" "${pkgdir}/usr/share/applications/feather.desktop"
install -Dm644 "${srcdir}/${pkgname}/src/assets/images/feather.png" "${pkgdir}/usr/share/pixmaps/feather.png"
} }

View file

@ -0,0 +1,60 @@
# - try to find HIDAPI library
# from http://www.signal11.us/oss/hidapi/
#
# Cache Variables: (probably not for direct use in your scripts)
# HIDAPI_INCLUDE_DIR
# HIDAPI_LIBRARY
#
# Non-cache variables you might use in your CMakeLists.txt:
# HIDAPI_FOUND
# HIDAPI_INCLUDE_DIRS
# HIDAPI_LIBRARIES
#
# Requires these CMake modules:
# FindPackageHandleStandardArgs (known included with CMake >=2.6.2)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
find_library(HIDAPI_LIBRARY
NAMES hidapi hidapi-libusb)
find_path(HIDAPI_INCLUDE_DIR
NAMES hidapi.h
PATH_SUFFIXES
hidapi)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(HIDAPI
DEFAULT_MSG
HIDAPI_LIBRARY
HIDAPI_INCLUDE_DIR)
if(HIDAPI_FOUND)
set(HIDAPI_LIBRARIES "${HIDAPI_LIBRARY}")
if((STATIC AND UNIX AND NOT APPLE) OR (DEPENDS AND CMAKE_SYSTEM_NAME STREQUAL "Linux"))
find_library(LIBUSB-1.0_LIBRARY usb-1.0)
find_library(LIBUDEV_LIBRARY udev)
if(LIBUSB-1.0_LIBRARY)
set(HIDAPI_LIBRARIES "${HIDAPI_LIBRARIES};${LIBUSB-1.0_LIBRARY}")
if(LIBUDEV_LIBRARY)
set(HIDAPI_LIBRARIES "${HIDAPI_LIBRARIES};${LIBUDEV_LIBRARY}")
else()
message(WARNING "libudev library not found, binaries may fail to link.")
endif()
else()
message(WARNING "libusb-1.0 library not found, binaries may fail to link.")
endif()
endif()
set(HIDAPI_INCLUDE_DIRS "${HIDAPI_INCLUDE_DIR}")
endif()
mark_as_advanced(HIDAPI_INCLUDE_DIR HIDAPI_LIBRARY)

2
monero

@ -1 +1 @@
Subproject commit 41327974116dedccc2f9709d8ad3a8a1f591faed Subproject commit 36fb05da3394505f8033ceb8806b28909617696f

View file

@ -18,6 +18,8 @@ qt5_add_resources(RESOURCES assets.qrc)
file(GLOB SOURCE_FILES file(GLOB SOURCE_FILES
"*.h" "*.h"
"*.cpp" "*.cpp"
"api/*.h"
"api/*.cpp"
"utils/*.h" "utils/*.h"
"utils/*.cpp" "utils/*.cpp"
"libwalletqt/*.h" "libwalletqt/*.h"
@ -116,12 +118,22 @@ target_include_directories(feather PUBLIC
${Qt5Svg_INCLUDE_DIRS} ${Qt5Svg_INCLUDE_DIRS}
${Qt5Xml_INCLUDE_DIRS} ${Qt5Xml_INCLUDE_DIRS}
${Qt5WebSockets_INCLUDE_DIRS} ${Qt5WebSockets_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIRS}
${LIBZIP_INCLUDE_DIRS}
) )
if(DONATE_BEG) if(DONATE_BEG)
target_compile_definitions(feather PRIVATE DONATE_BEG=1) target_compile_definitions(feather PRIVATE DONATE_BEG=1)
endif() endif()
if (CHECK_UPDATES)
target_compile_definitions(feather PRIVATE CHECK_UPDATES=1)
endif()
if(LOCALMONERO)
target_compile_definitions(feather PRIVATE HAS_LOCALMONERO=1)
endif()
if(TOR_BIN) if(TOR_BIN)
target_compile_definitions(feather PRIVATE HAS_TOR_BIN=1) target_compile_definitions(feather PRIVATE HAS_TOR_BIN=1)
endif() endif()
@ -130,6 +142,9 @@ if(XMRIG)
target_compile_definitions(feather PRIVATE HAS_XMRIG=1) target_compile_definitions(feather PRIVATE HAS_XMRIG=1)
endif() endif()
# TODO: PLACEHOLDER
target_compile_definitions(feather PRIVATE HAS_WEBSOCKET=1)
if(HAVE_SYS_PRCTL_H) if(HAVE_SYS_PRCTL_H)
target_compile_definitions(feather PRIVATE HAVE_SYS_PRCTL_H=1) target_compile_definitions(feather PRIVATE HAVE_SYS_PRCTL_H=1)
endif() endif()
@ -191,6 +206,8 @@ target_link_libraries(feather
openpgp openpgp
Threads::Threads Threads::Threads
${QRENCODE_LIBRARY} ${QRENCODE_LIBRARY}
${ZLIB_LIBRARIES}
${LIBZIP_LIBRARIES}
) )
if(APPLE) if(APPLE)

121
src/api/LocalMoneroApi.cpp Normal file
View file

@ -0,0 +1,121 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "LocalMoneroApi.h"
LocalMoneroApi::LocalMoneroApi(QObject *parent, UtilsNetworking *network, const QString &baseUrl)
: QObject(parent)
, m_network(network)
, m_baseUrl(baseUrl)
{
}
void LocalMoneroApi::countryCodes() {
QString url = QString("%1/countrycodes").arg(m_baseUrl);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::COUNTRY_CODES));
}
void LocalMoneroApi::currencies() {
QString url = QString("%1/currencies").arg(m_baseUrl);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::CURRENCIES));
}
void LocalMoneroApi::paymentMethods(const QString &countryCode) {
QString url;
if (countryCode.isEmpty()) {
url = QString("%1/payment_methods").arg(m_baseUrl);
} else {
url = QString("%1/payment_methods/%2").arg(m_baseUrl, countryCode);
}
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::PAYMENT_METHODS));
}
void LocalMoneroApi::buyMoneroOnline(const QString &currencyCode, const QString &countryCode,
const QString &paymentMethod, const QString &amount, int page)
{
QString url = this->getBuySellUrl(true, currencyCode, countryCode, paymentMethod, amount, page);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::BUY_MONERO_ONLINE));
}
void LocalMoneroApi::sellMoneroOnline(const QString &currencyCode, const QString &countryCode,
const QString &paymentMethod, const QString &amount, int page)
{
QString url = this->getBuySellUrl(false, currencyCode, countryCode, paymentMethod, amount, page);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::SELL_MONERO_ONLINE));
}
void LocalMoneroApi::accountInfo(const QString &username) {
QString url = QString("%1/account_info/%2").arg(m_baseUrl, username);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::ACCOUNT_INFO));
}
void LocalMoneroApi::onResponse(QNetworkReply *reply, LocalMoneroApi::Endpoint endpoint) {
const bool ok = reply->error() == QNetworkReply::NoError;
const QString err = reply->errorString();
QByteArray data = reply->readAll();
qDebug() << "Response";
qDebug() << data;
for (const auto header : reply->rawHeaderList()) {
qDebug() << header << ": " << reply->rawHeader(header);
}
qDebug() << reply->rawHeaderPairs();
qDebug() << "Request";
for (const auto header : reply->request().rawHeaderList()) {
qDebug() << "header: " << header << ": " << reply->request().rawHeader(header);
}
qDebug() << reply->request().url();
reply->deleteLater();
QJsonObject obj;
if (!data.isEmpty() && Utils::validateJSON(data)) {
auto doc = QJsonDocument::fromJson(data);
obj = doc.object();
}
else if (!ok) {
emit ApiResponse(LocalMoneroResponse{false, endpoint, err, {}});
return;
}
else {
emit ApiResponse(LocalMoneroResponse{false, endpoint, "Invalid response from LocalMonero", {}});
return;
}
if (obj.contains("error")) {
QString errorStr = QJsonDocument(obj["error"].toObject()).toJson(QJsonDocument::Compact);
emit ApiResponse(LocalMoneroResponse{false, endpoint, errorStr, obj});
return;
}
emit ApiResponse(LocalMoneroResponse{true, endpoint, "", obj});
}
QString LocalMoneroApi::getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode,
const QString &paymentMethod, const QString &amount, int page)
{
QString url = QString("%1/%2-monero-online/%3").arg(m_baseUrl, buy ? "buy" : "sell", currencyCode);
if (!countryCode.isEmpty() && paymentMethod.isEmpty())
url += QString("/%1").arg(countryCode);
else if (countryCode.isEmpty() && !paymentMethod.isEmpty())
url += QString("/%1").arg(paymentMethod);
else if (!countryCode.isEmpty() && !paymentMethod.isEmpty())
url += QString("/%1/%2").arg(countryCode, paymentMethod);
QUrlQuery query;
if (!amount.isEmpty() && amount != "0")
query.addQueryItem("amount", amount);
if (page > 0)
query.addQueryItem("page", QString::number(page));
url += "?" + query.toString();
return url;
}

53
src/api/LocalMoneroApi.h Normal file
View file

@ -0,0 +1,53 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_LOCALMONEROAPI_H
#define FEATHER_LOCALMONEROAPI_H
#include <QObject>
#include "utils/networking.h"
class LocalMoneroApi : public QObject {
Q_OBJECT
public:
enum Endpoint {
COUNTRY_CODES,
CURRENCIES,
PAYMENT_METHODS,
BUY_MONERO_ONLINE,
SELL_MONERO_ONLINE,
ACCOUNT_INFO
};
struct LocalMoneroResponse {
bool ok;
Endpoint endpoint;
QString message;
QJsonObject obj;
};
explicit LocalMoneroApi(QObject *parent, UtilsNetworking *network, const QString &baseUrl = "https://agoradesk.com/api/v1");
void countryCodes();
void currencies();
void paymentMethods(const QString &countryCode = "");
void buyMoneroOnline(const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
void sellMoneroOnline(const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
void accountInfo(const QString &username);
signals:
void ApiResponse(LocalMoneroResponse resp);
private slots:
void onResponse(QNetworkReply *reply, Endpoint endpoint);
private:
QString getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
QString m_baseUrl;
UtilsNetworking *m_network;
};
#endif //FEATHER_LOCALMONEROAPI_H

View file

@ -13,20 +13,16 @@
#include "libwalletqt/Coins.h" #include "libwalletqt/Coins.h"
#include "model/TransactionHistoryModel.h" #include "model/TransactionHistoryModel.h"
#include "model/SubaddressModel.h" #include "model/SubaddressModel.h"
#include "utils/NetworkManager.h"
#include "utils/WebsocketClient.h"
#include "utils/WebsocketNotifier.h"
Prices *AppContext::prices = nullptr;
WalletKeysFilesModel *AppContext::wallets = nullptr; WalletKeysFilesModel *AppContext::wallets = nullptr;
TxFiatHistory *AppContext::txFiatHistory = nullptr;
double AppContext::balance = 0;
QMap<QString, QString> AppContext::txCache; QMap<QString, QString> AppContext::txCache;
AppContext::AppContext(QCommandLineParser *cmdargs) { AppContext::AppContext(QCommandLineParser *cmdargs) {
this->network = new QNetworkAccessManager();
this->networkClearnet = new QNetworkAccessManager();
this->cmdargs = cmdargs; this->cmdargs = cmdargs;
this->isTorSocks = Utils::isTorsocks();
this->isTails = TailsOS::detect(); this->isTails = TailsOS::detect();
this->isWhonix = WhonixOS::detect(); this->isWhonix = WhonixOS::detect();
@ -45,21 +41,20 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
// ----------------- Network Type ----------------- // ----------------- Network Type -----------------
if (this->cmdargs->isSet("stagenet")) if (this->cmdargs->isSet("stagenet")) {
this->networkType = NetworkType::STAGENET; this->networkType = NetworkType::STAGENET;
else if (this->cmdargs->isSet("testnet")) config()->set(Config::networkType, NetworkType::STAGENET);
}
else if (this->cmdargs->isSet("testnet")) {
this->networkType = NetworkType::TESTNET; this->networkType = NetworkType::TESTNET;
else config()->set(Config::networkType, NetworkType::TESTNET);
}
else {
this->networkType = NetworkType::MAINNET; this->networkType = NetworkType::MAINNET;
config()->set(Config::networkType, NetworkType::MAINNET);
}
this->nodes = new Nodes(this, this);
this->nodes = new Nodes(this, this->networkClearnet);
connect(this, &AppContext::nodeSourceChanged, this->nodes, &Nodes::onNodeSourceChanged);
connect(this, &AppContext::setCustomNodes, this->nodes, &Nodes::setCustomNodes);
// Tor & socks proxy
this->ws = new WSClient(this, globals::websocketUrl);
connect(this->ws, &WSClient::WSMessage, this, &AppContext::onWSMessage);
// Store the wallet every 2 minutes // Store the wallet every 2 minutes
m_storeTimer.start(2 * 60 * 1000); m_storeTimer.start(2 * 60 * 1000);
@ -67,31 +62,6 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
this->storeWallet(); this->storeWallet();
}); });
// restore height lookup
this->initRestoreHeights();
// price history lookup
auto genesis_timestamp = this->restoreHeights[NetworkType::Type::MAINNET]->data.firstKey();
AppContext::txFiatHistory = new TxFiatHistory(genesis_timestamp, configDir);
connect(this->ws, &WSClient::connectionEstablished, AppContext::txFiatHistory, &TxFiatHistory::onUpdateDatabase);
connect(AppContext::txFiatHistory, &TxFiatHistory::requestYear, [=](int year){
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1}})").arg(year).toUtf8();
this->ws->sendMsg(data);
});
connect(AppContext::txFiatHistory, &TxFiatHistory::requestYearMonth, [=](int year, int month) {
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1, "month": %2}})").arg(year).arg(month).toUtf8();
this->ws->sendMsg(data);
});
// fiat/crypto lookup
AppContext::prices = new Prices();
// XMRig
#ifdef HAS_XMRIG
this->XMRig = new XmRig(configDir, this);
this->XMRig->prepare();
#endif
this->walletManager = WalletManager::instance(); this->walletManager = WalletManager::instance();
QString logPath = QString("%1/daemon.log").arg(configDir); QString logPath = QString("%1/daemon.log").arg(configDir);
Monero::Utils::onStartup(); Monero::Utils::onStartup();
@ -108,23 +78,34 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
// libwallet connects // libwallet connects
connect(this->walletManager, &WalletManager::walletOpened, this, &AppContext::onWalletOpened); connect(this->walletManager, &WalletManager::walletOpened, this, &AppContext::onWalletOpened);
connect(this->walletManager, &WalletManager::walletCreated, this, &AppContext::onWalletCreated);
connect(this->walletManager, &WalletManager::deviceButtonRequest, this, &AppContext::onDeviceButtonRequest);
connect(this->walletManager, &WalletManager::deviceError, this, &AppContext::onDeviceError);
// TODO: move me
connect(websocketNotifier(), &WebsocketNotifier::NodesReceived, this->nodes, &Nodes::onWSNodesReceived);
m_rpc = new DaemonRpc{this, getNetworkTor(), ""};
} }
void AppContext::initTor() { void AppContext::initTor() {
this->tor = new Tor(this, this); if (this->cmdargs->isSet("tor-host"))
this->tor->start(); config()->set(Config::socks5Host, this->cmdargs->value("tor-host"));
if (this->cmdargs->isSet("tor-port"))
config()->set(Config::socks5Port, this->cmdargs->value("tor-port"));
if (this->cmdargs->isSet("use-local-tor"))
config()->set(Config::useLocalTor, true);
if (!(isWhonix) && !(isTorSocks)) { torManager()->init();
this->networkProxy = new QNetworkProxy(QNetworkProxy::Socks5Proxy, Tor::torHost, Tor::torPort); torManager()->start();
this->network->setProxy(*networkProxy);
if (globals::websocketUrl.host().endsWith(".onion")) { connect(torManager(), &TorManager::connectionStateChanged, &websocketNotifier()->websocketClient, &WebsocketClient::onToggleConnect);
this->ws->webSocket.setProxy(*networkProxy);
} this->onTorSettingsChanged();
}
} }
void AppContext::initWS() { void AppContext::initWS() {
this->ws->start(); websocketNotifier()->websocketClient.start();
} }
void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address) { void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address) {
@ -134,7 +115,7 @@ void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector<QStri
this->currentWallet->disposeTransaction(tx); this->currentWallet->disposeTransaction(tx);
} }
void AppContext::onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const { void AppContext::onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) {
if(this->currentWallet == nullptr){ if(this->currentWallet == nullptr){
qCritical() << "Cannot create transaction; no wallet loaded"; qCritical() << "Cannot create transaction; no wallet loaded";
return; return;
@ -146,6 +127,8 @@ void AppContext::onSweepOutput(const QString &keyImage, QString address, bool ch
qCritical() << "Creating transaction"; qCritical() << "Creating transaction";
this->currentWallet->createTransactionSingleAsync(keyImage, address, outputs, this->tx_priority); this->currentWallet->createTransactionSingleAsync(keyImage, address, outputs, this->tx_priority);
emit initiateTransaction();
} }
void AppContext::onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all) { void AppContext::onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all) {
@ -249,6 +232,17 @@ void AppContext::onOpenWallet(const QString &path, const QString &password){
this->walletManager->openWalletAsync(path, password, this->networkType, 1); this->walletManager->openWalletAsync(path, password, this->networkType, 1);
} }
void AppContext::onWalletCreated(Wallet * wallet) {
// Currently only called when a wallet is created from device.
auto state = wallet->status();
if (state != Wallet::Status_Ok) {
emit walletCreatedError(wallet->errorString());
return;
}
this->onWalletOpened(wallet);
}
void AppContext::onPreferredFiatCurrencyChanged(const QString &symbol) { void AppContext::onPreferredFiatCurrencyChanged(const QString &symbol) {
if(this->currentWallet) { if(this->currentWallet) {
auto *model = this->currentWallet->transactionHistoryModel(); auto *model = this->currentWallet->transactionHistoryModel();
@ -276,9 +270,6 @@ void AppContext::commitTransaction(PendingTransaction *tx) {
} }
void AppContext::onMultiBroadcast(PendingTransaction *tx) { void AppContext::onMultiBroadcast(PendingTransaction *tx) {
UtilsNetworking *net = new UtilsNetworking(this->network, this);
DaemonRpc *rpc = new DaemonRpc(this, net, "");
int count = tx->txCount(); int count = tx->txCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
QString txData = tx->signedTxToHex(i); QString txData = tx->signedTxToHex(i);
@ -286,40 +277,81 @@ void AppContext::onMultiBroadcast(PendingTransaction *tx) {
for (const auto& node: this->nodes->websocketNodes()) { for (const auto& node: this->nodes->websocketNodes()) {
if (!node.online) continue; if (!node.online) continue;
QString address = node.as_url(); QString address = node.toURL();
qDebug() << QString("Relaying %1 to: %2").arg(tx->txid()[i], address); qDebug() << QString("Relaying %1 to: %2").arg(tx->txid()[i], address);
rpc->setDaemonAddress(address); m_rpc->setDaemonAddress(address);
rpc->sendRawTransaction(txData); m_rpc->sendRawTransaction(txData);
} }
} }
} }
void AppContext::onDeviceButtonRequest(quint64 code) {
emit deviceButtonRequest(code);
}
void AppContext::onDeviceError(const QString &message) {
qCritical() << "Device error: " << message;
emit deviceError(message);
}
void AppContext::onTorSettingsChanged() {
if (WhonixOS::detect() || Utils::isTorsocks()) {
return;
}
// use local tor -> bundled tor
QString host = config()->get(Config::socks5Host).toString();
quint16 port = config()->get(Config::socks5Port).toString().toUShort();
if (!torManager()->isLocalTor()) {
host = torManager()->featherTorHost;
port = torManager()->featherTorPort;
}
QNetworkProxy proxy{QNetworkProxy::Socks5Proxy, host, port};
getNetworkTor()->setProxy(proxy);
websocketNotifier()->websocketClient.webSocket.setProxy(proxy);
this->nodes->connectToNode();
auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
qDebug() << "Changed privacyLevel to " << privacyLevel;
}
void AppContext::onInitialNetworkConfigured() {
this->initTor();
this->initWS();
}
void AppContext::onWalletOpened(Wallet *wallet) { void AppContext::onWalletOpened(Wallet *wallet) {
auto state = wallet->status(); auto state = wallet->status();
if (state != Wallet::Status_Ok) { if (state != Wallet::Status_Ok) {
auto errMsg = wallet->errorString(); auto errMsg = wallet->errorString();
if(errMsg == QString("basic_string::_M_replace_aux") || errMsg == QString("std::bad_alloc")) { if (state == Wallet::Status_BadPassword) {
this->closeWallet(false);
// Don't show incorrect password when we try with empty password for the first time
bool showIncorrectPassword = m_openWalletTriedOnce;
m_openWalletTriedOnce = true;
emit walletOpenPasswordNeeded(showIncorrectPassword, wallet->path());
}
else if (errMsg == QString("basic_string::_M_replace_aux") || errMsg == QString("std::bad_alloc")) {
qCritical() << errMsg; qCritical() << errMsg;
this->walletManager->clearWalletCache(this->walletPath); this->walletManager->clearWalletCache(this->walletPath);
errMsg = QString("%1\n\nAttempted to clean wallet cache. Please restart Feather.").arg(errMsg); errMsg = QString("%1\n\nAttempted to clean wallet cache. Please restart Feather.").arg(errMsg);
this->closeWallet(false); this->closeWallet(false);
emit walletOpenedError(errMsg); emit walletOpenedError(errMsg);
} else if(errMsg.contains("wallet cannot be opened as")) {
this->closeWallet(false);
emit walletOpenedError(errMsg);
} else if(errMsg.contains("is opened by another wallet program")) {
this->closeWallet(false);
emit walletOpenedError(errMsg);
} else { } else {
this->closeWallet(false); this->closeWallet(false);
emit walletOpenPasswordNeeded(!this->walletPassword.isEmpty(), wallet->path()); emit walletOpenedError(errMsg);
} }
return; return;
} }
m_openWalletTriedOnce = false;
this->refreshed = false; this->refreshed = false;
this->currentWallet = wallet; this->currentWallet = wallet;
this->walletPath = this->currentWallet->path() + ".keys"; this->walletPath = this->currentWallet->path() + ".keys";
this->walletPassword = this->currentWallet->getPassword();
config()->set(Config::walletPath, this->walletPath); config()->set(Config::walletPath, this->walletPath);
connect(this->currentWallet, &Wallet::moneySpent, this, &AppContext::onMoneySpent); connect(this->currentWallet, &Wallet::moneySpent, this, &AppContext::onMoneySpent);
@ -331,6 +363,8 @@ void AppContext::onWalletOpened(Wallet *wallet) {
connect(this->currentWallet, &Wallet::transactionCommitted, this, &AppContext::onTransactionCommitted); connect(this->currentWallet, &Wallet::transactionCommitted, this, &AppContext::onTransactionCommitted);
connect(this->currentWallet, &Wallet::heightRefreshed, this, &AppContext::onHeightRefreshed); connect(this->currentWallet, &Wallet::heightRefreshed, this, &AppContext::onHeightRefreshed);
connect(this->currentWallet, &Wallet::transactionCreated, this, &AppContext::onTransactionCreated); connect(this->currentWallet, &Wallet::transactionCreated, this, &AppContext::onTransactionCreated);
connect(this->currentWallet, &Wallet::deviceError, this, &AppContext::onDeviceError);
connect(this->currentWallet, &Wallet::deviceButtonRequest, this, &AppContext::onDeviceButtonRequest);
emit walletOpened(); emit walletOpened();
@ -346,142 +380,6 @@ void AppContext::onWalletOpened(Wallet *wallet) {
// force trigger preferredFiat signal for history model // force trigger preferredFiat signal for history model
this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString()); this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString());
this->setWindowTitle();
}
void AppContext::setWindowTitle(bool mining) {
QFileInfo fileInfo(this->walletPath);
auto title = QString("Feather - [%1]").arg(fileInfo.fileName());
if(this->currentWallet && this->currentWallet->viewOnly())
title += " [view-only]";
if(mining)
title += " [mining]";
emit setTitle(title);
}
void AppContext::onWSMessage(const QJsonObject &msg) {
QString cmd = msg.value("cmd").toString();
if (cmd == "blockheights") {
QJsonObject data = msg.value("data").toObject();
int mainnet = data.value("mainnet").toInt();
int stagenet = data.value("stagenet").toInt();
this->heights[NetworkType::MAINNET] = mainnet;
this->heights[NetworkType::STAGENET] = stagenet;
}
else if(cmd == "nodes") {
this->onWSNodes(msg.value("data").toArray());
}
#if defined(HAS_XMRIG)
else if(cmd == "xmrig") {
this->XMRigDownloads(msg.value("data").toObject());
}
#endif
else if(cmd == "crypto_rates") {
QJsonArray crypto_rates = msg.value("data").toArray();
AppContext::prices->cryptoPricesReceived(crypto_rates);
}
else if(cmd == "fiat_rates") {
QJsonObject fiat_rates = msg.value("data").toObject();
AppContext::prices->fiatPricesReceived(fiat_rates);
}
else if(cmd == "reddit") {
QJsonArray reddit_data = msg.value("data").toArray();
this->onWSReddit(reddit_data);
}
else if(cmd == "ccs") {
auto ccs_data = msg.value("data").toArray();
this->onWSCCS(ccs_data);
}
else if(cmd == "txFiatHistory") {
auto txFiatHistory_data = msg.value("data").toObject();
AppContext::txFiatHistory->onWSData(txFiatHistory_data);
}
}
void AppContext::onWSNodes(const QJsonArray &nodes) {
QList<QSharedPointer<FeatherNode>> l;
for (auto &&entry: nodes) {
auto obj = entry.toObject();
auto nettype = obj.value("nettype");
auto type = obj.value("type");
// filter remote node network types
if(nettype == "mainnet" && this->networkType != NetworkType::MAINNET)
continue;
if(nettype == "stagenet" && this->networkType != NetworkType::STAGENET)
continue;
if(nettype == "testnet" && this->networkType != NetworkType::TESTNET)
continue;
if(type == "clearnet" && (this->isTails || this->isWhonix || this->isTorSocks))
continue;
if(type == "tor" && (!(this->isTails || this->isWhonix || this->isTorSocks)))
continue;
auto node = new FeatherNode(
obj.value("address").toString(),
obj.value("height").toInt(),
obj.value("target_height").toInt(),
obj.value("online").toBool());
QSharedPointer<FeatherNode> r = QSharedPointer<FeatherNode>(node);
l.append(r);
}
this->nodes->onWSNodesReceived(l);
}
void AppContext::onWSReddit(const QJsonArray& reddit_data) {
QList<QSharedPointer<RedditPost>> l;
for (auto &&entry: reddit_data) {
auto obj = entry.toObject();
auto redditPost = new RedditPost(
obj.value("title").toString(),
obj.value("author").toString(),
obj.value("permalink").toString(),
obj.value("comments").toInt());
QSharedPointer<RedditPost> r = QSharedPointer<RedditPost>(redditPost);
l.append(r);
}
emit redditUpdated(l);
}
void AppContext::onWSCCS(const QJsonArray &ccs_data) {
QList<QSharedPointer<CCSEntry>> l;
QStringList fonts = {"state", "address", "author", "date",
"title", "target_amount", "raised_amount",
"percentage_funded", "contributions"};
for (auto &&entry: ccs_data) {
auto obj = entry.toObject();
auto c = QSharedPointer<CCSEntry>(new CCSEntry());
if (obj.value("state").toString() != "FUNDING-REQUIRED")
continue;
c->state = obj.value("state").toString();
c->address = obj.value("address").toString();
c->author = obj.value("author").toString();
c->date = obj.value("date").toString();
c->title = obj.value("title").toString();
c->url = obj.value("url").toString();
c->target_amount = obj.value("target_amount").toDouble();
c->raised_amount = obj.value("raised_amount").toDouble();
c->percentage_funded = obj.value("percentage_funded").toDouble();
c->contributions = obj.value("contributions").toInt();
l.append(c);
}
emit ccsUpdated(l);
} }
void AppContext::createConfigDirectory(const QString &dir) { void AppContext::createConfigDirectory(const QString &dir) {
@ -528,7 +426,18 @@ void AppContext::createWallet(FeatherSeed seed, const QString &path, const QStri
return; return;
} }
this->createWalletFinish(password); this->onWalletOpened(wallet);
}
void AppContext::createWalletFromDevice(const QString &path, const QString &password, int restoreHeight) {
if(Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
qCritical() << err;
emit walletCreatedError(err);
return;
}
this->walletManager->createWalletFromDeviceAsync(path, password, this->networkType, "Ledger", restoreHeight);
} }
void AppContext::createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic) { void AppContext::createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic) {
@ -539,7 +448,7 @@ void AppContext::createWalletFromKeys(const QString &path, const QString &passwo
return; return;
} }
if(!this->walletManager->addressValid(address, this->networkType)) { if(!WalletManager::addressValid(address, this->networkType)) {
auto err = QString("Failed to create wallet. Invalid address provided.").arg(path); auto err = QString("Failed to create wallet. Invalid address provided.").arg(path);
qCritical() << err; qCritical() << err;
emit walletCreatedError(err); emit walletCreatedError(err);
@ -560,21 +469,8 @@ void AppContext::createWalletFromKeys(const QString &path, const QString &passwo
return; return;
} }
this->currentWallet = this->walletManager->createWalletFromKeys(path, this->seedLanguage, this->networkType, address, viewkey, spendkey, restoreHeight); Wallet *wallet = this->walletManager->createWalletFromKeys(path, password, this->seedLanguage, this->networkType, address, viewkey, spendkey, restoreHeight);
this->createWalletFinish(password); this->walletManager->walletOpened(wallet);
}
void AppContext::createWalletFinish(const QString &password) {
this->currentWallet->setPassword(password);
this->currentWallet->store();
this->walletPassword = password;
emit walletCreated(this->currentWallet);
}
void AppContext::initRestoreHeights() {
restoreHeights[NetworkType::TESTNET] = new RestoreHeightLookup(NetworkType::TESTNET);
restoreHeights[NetworkType::STAGENET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_monero_stagenet.txt", NetworkType::STAGENET);
restoreHeights[NetworkType::MAINNET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_monero_mainnet.txt", NetworkType::MAINNET);
} }
void AppContext::onSetRestoreHeight(quint64 height){ void AppContext::onSetRestoreHeight(quint64 height){
@ -643,7 +539,7 @@ void AppContext::donateBeg() {
if (this->currentWallet->viewOnly()) return; if (this->currentWallet->viewOnly()) return;
auto donationCounter = config()->get(Config::donateBeg).toInt(); auto donationCounter = config()->get(Config::donateBeg).toInt();
if(donationCounter == -1) if (donationCounter == -1)
return; // previously donated return; // previously donated
donationCounter += 1; donationCounter += 1;
@ -688,7 +584,13 @@ void AppContext::onWalletUpdate() {
this->updateBalance(); this->updateBalance();
} }
void AppContext::onWalletRefreshed(bool success) { void AppContext::onWalletRefreshed(bool success, const QString &message) {
if (!success) {
// Something went wrong during refresh, in some cases we need to notify the user
qCritical() << "Exception during refresh: " << message; // Can't use ->errorString() here, other SLOT might snipe it first
return;
}
if (!this->refreshed) { if (!this->refreshed) {
refreshModels(); refreshModels();
this->refreshed = true; this->refreshed = true;
@ -727,6 +629,8 @@ void AppContext::onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, q
} }
void AppContext::onTransactionCreated(PendingTransaction *tx, const QVector<QString> &address) { void AppContext::onTransactionCreated(PendingTransaction *tx, const QVector<QString> &address) {
qDebug() << Q_FUNC_INFO;
for (auto &addr : address) { for (auto &addr : address) {
if (addr == globals::donationAddress) { if (addr == globals::donationAddress) {
this->donationSending = true; this->donationSending = true;
@ -778,11 +682,10 @@ void AppContext::updateBalance() {
if (!this->currentWallet) if (!this->currentWallet)
return; return;
quint64 balance_u = this->currentWallet->balance(); quint64 balance = this->currentWallet->balance();
AppContext::balance = balance_u / globals::cdiv; quint64 spendable = this->currentWallet->unlockedBalance();
double spendable = this->currentWallet->unlockedBalance();
emit balanceUpdated(balance_u, spendable); emit balanceUpdated(balance, spendable);
} }
void AppContext::syncStatusUpdated(quint64 height, quint64 target) { void AppContext::syncStatusUpdated(quint64 height, quint64 target) {

View file

@ -5,16 +5,13 @@
#define FEATHER_APPCONTEXT_H #define FEATHER_APPCONTEXT_H
#include <QObject> #include <QObject>
#include <QProcess>
#include <QNetworkAccessManager>
#include <QTimer> #include <QTimer>
#include "utils/tails.h" #include "utils/tails.h"
#include "utils/whonix.h" #include "utils/whonix.h"
#include "utils/prices.h" #include "utils/prices.h"
#include "utils/networking.h" #include "utils/networking.h"
#include "utils/tor.h" #include "utils/TorManager.h"
#include "utils/xmrig.h"
#include "utils/wsclient.h" #include "utils/wsclient.h"
#include "utils/txfiathistory.h" #include "utils/txfiathistory.h"
#include "utils/FeatherSeed.h" #include "utils/FeatherSeed.h"
@ -28,9 +25,6 @@
#include "utils/keysfiles.h" #include "utils/keysfiles.h"
#include "PendingTransaction.h" #include "PendingTransaction.h"
#define SUBADDRESS_LOOKAHEAD_MINOR 200
class AppContext : public QObject class AppContext : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -43,7 +37,6 @@ public:
bool isTails = false; bool isTails = false;
bool isWhonix = false; bool isWhonix = false;
bool isTorSocks = false;
bool donationSending = false; bool donationSending = false;
@ -54,25 +47,13 @@ public:
QString walletPassword = ""; QString walletPassword = "";
NetworkType::Type networkType; NetworkType::Type networkType;
QMap<NetworkType::Type, int> heights;
QMap<NetworkType::Type, RestoreHeightLookup*> restoreHeights;
PendingTransaction::Priority tx_priority = PendingTransaction::Priority::Priority_Low; PendingTransaction::Priority tx_priority = PendingTransaction::Priority::Priority_Low;
QString seedLanguage = "English"; // 14 word `monero-seed` only has English QString seedLanguage = "English"; // 14 word `monero-seed` only has English
QNetworkAccessManager *network; Nodes *nodes; // TODO: move this to mainwindow (?)
QNetworkAccessManager *networkClearnet;
QNetworkProxy *networkProxy{};
Tor *tor{};
WSClient *ws;
XmRig *XMRig;
Nodes *nodes;
DaemonRpc *daemonRpc;
static Prices *prices;
static WalletKeysFilesModel *wallets; static WalletKeysFilesModel *wallets;
static double balance;
static QMap<QString, QString> txCache; static QMap<QString, QString> txCache;
static TxFiatHistory *txFiatHistory;
static void createConfigDirectory(const QString &dir); static void createConfigDirectory(const QString &dir);
@ -81,17 +62,15 @@ public:
WalletManager *walletManager; WalletManager *walletManager;
Wallet *currentWallet = nullptr; Wallet *currentWallet = nullptr;
void createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset = ""); void createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset = "");
void createWalletFromDevice(const QString &path, const QString &password, int restoreHeight);
void createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic = false); void createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic = false);
void createWalletFinish(const QString &password);
void commitTransaction(PendingTransaction *tx); void commitTransaction(PendingTransaction *tx);
void syncStatusUpdated(quint64 height, quint64 target); void syncStatusUpdated(quint64 height, quint64 target);
void updateBalance(); void updateBalance();
void initTor(); void initTor();
void initRestoreHeights();
void initWS(); void initWS();
void donateBeg(); void donateBeg();
void refreshModels(); void refreshModels();
void setWindowTitle(bool mining = false);
// Closes the currently opened wallet // Closes the currently opened wallet
void closeWallet(bool emitClosedSignal = true, bool storeWallet = false); void closeWallet(bool emitClosedSignal = true, bool storeWallet = false);
@ -99,28 +78,28 @@ public:
public slots: public slots:
void onOpenWallet(const QString& path, const QString &password); void onOpenWallet(const QString& path, const QString &password);
void onWalletCreated(Wallet * wallet);
void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all); void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all);
void onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description); void onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address); void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address);
void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const; void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs);
void onCreateTransactionError(const QString &msg); void onCreateTransactionError(const QString &msg);
void onOpenAliasResolve(const QString &openAlias); void onOpenAliasResolve(const QString &openAlias);
void onSetRestoreHeight(quint64 height); void onSetRestoreHeight(quint64 height);
void onPreferredFiatCurrencyChanged(const QString &symbol); void onPreferredFiatCurrencyChanged(const QString &symbol);
void onAmountPrecisionChanged(int precision); void onAmountPrecisionChanged(int precision);
void onMultiBroadcast(PendingTransaction *tx); void onMultiBroadcast(PendingTransaction *tx);
void onDeviceButtonRequest(quint64 code);
void onTorSettingsChanged();
void onInitialNetworkConfigured();
void onDeviceError(const QString &message);
private slots: private slots:
void onWSNodes(const QJsonArray &nodes);
void onWSMessage(const QJsonObject& msg);
void onWSCCS(const QJsonArray &ccs_data);
void onWSReddit(const QJsonArray& reddit_data);
void onMoneySpent(const QString &txId, quint64 amount); void onMoneySpent(const QString &txId, quint64 amount);
void onMoneyReceived(const QString &txId, quint64 amount); void onMoneyReceived(const QString &txId, quint64 amount);
void onUnconfirmedMoneyReceived(const QString &txId, quint64 amount); void onUnconfirmedMoneyReceived(const QString &txId, quint64 amount);
void onWalletUpdate(); void onWalletUpdate();
void onWalletRefreshed(bool success); void onWalletRefreshed(bool success, const QString &message);
void onWalletOpened(Wallet *wallet); void onWalletOpened(Wallet *wallet);
void onWalletNewBlock(quint64 blockheight, quint64 targetHeight); void onWalletNewBlock(quint64 blockheight, quint64 targetHeight);
void onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight); void onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight);
@ -148,12 +127,6 @@ signals:
void createTransactionError(QString message); void createTransactionError(QString message);
void createTransactionCancelled(const QVector<QString> &address, double amount); void createTransactionCancelled(const QVector<QString> &address, double amount);
void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address); void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
void redditUpdated(QList<QSharedPointer<RedditPost>> &posts);
void nodesUpdated(QList<QSharedPointer<FeatherNode>> &nodes);
void ccsUpdated(QList<QSharedPointer<CCSEntry>> &entries);
void nodeSourceChanged(NodeSource nodeSource);
void XMRigDownloads(const QJsonObject &data);
void setCustomNodes(QList<FeatherNode> nodes);
void openAliasResolveError(const QString &msg); void openAliasResolveError(const QString &msg);
void openAliasResolved(const QString &address, const QString &openAlias); void openAliasResolved(const QString &address, const QString &openAlias);
void setRestoreHeightError(const QString &msg); void setRestoreHeightError(const QString &msg);
@ -162,10 +135,14 @@ signals:
void donationNag(); void donationNag();
void initiateTransaction(); void initiateTransaction();
void endTransaction(); void endTransaction();
void setTitle(const QString &title); // set window title void deviceButtonRequest(quint64 code);
void updatesAvailable(const QJsonObject &updates);
void deviceError(const QString &message);
private: private:
DaemonRpc *m_rpc;
QTimer m_storeTimer; QTimer m_storeTimer;
bool m_openWalletTriedOnce = false;
}; };
#endif //FEATHER_APPCONTEXT_H #endif //FEATHER_APPCONTEXT_H

View file

@ -5,6 +5,7 @@
<file>assets/contributors.txt</file> <file>assets/contributors.txt</file>
<file>assets/feather.desktop</file> <file>assets/feather.desktop</file>
<file>assets/nodes.json</file> <file>assets/nodes.json</file>
<file>assets/gpg_keys/featherwallet.asc</file>
<file>assets/images/appicons/32x32.png</file> <file>assets/images/appicons/32x32.png</file>
<file>assets/images/appicons/48x48.png</file> <file>assets/images/appicons/48x48.png</file>
<file>assets/images/appicons/64x64.png</file> <file>assets/images/appicons/64x64.png</file>
@ -44,10 +45,18 @@
<file>assets/images/gnome-calc.png</file> <file>assets/images/gnome-calc.png</file>
<file>assets/images/history.png</file> <file>assets/images/history.png</file>
<file>assets/images/info.png</file> <file>assets/images/info.png</file>
<file>assets/images/info2.svg</file>
<file>assets/images/key.png</file> <file>assets/images/key.png</file>
<file>assets/images/ledger.png</file> <file>assets/images/ledger.png</file>
<file>assets/images/ledger_unpaired.png</file> <file>assets/images/ledger_unpaired.png</file>
<file>assets/images/lightning.png</file> <file>assets/images/lightning.png</file>
<file>assets/images/localMonero_search.svg</file>
<file>assets/images/localMonero_buy.svg</file>
<file>assets/images/localMonero_buy_white.svg</file>
<file>assets/images/localMonero_sell.svg</file>
<file>assets/images/localMonero_sell_white.svg</file>
<file>assets/images/localMonero_logo.png</file>
<file>assets/images/localMonero_register.svg</file>
<file>assets/images/lock.png</file> <file>assets/images/lock.png</file>
<file>assets/images/lock_icon.png</file> <file>assets/images/lock_icon.png</file>
<file>assets/images/lock.svg</file> <file>assets/images/lock.svg</file>
@ -63,6 +72,12 @@
<file>assets/images/revealer_c.png</file> <file>assets/images/revealer_c.png</file>
<file>assets/images/revealer.png</file> <file>assets/images/revealer.png</file>
<file>assets/images/seal.png</file> <file>assets/images/seal.png</file>
<file>assets/images/securityLevelSafer.png</file>
<file>assets/images/securityLevelSafest.png</file>
<file>assets/images/securityLevelStandard.png</file>
<file>assets/images/securityLevelSaferWhite.png</file>
<file>assets/images/securityLevelSafestWhite.png</file>
<file>assets/images/securityLevelStandardWhite.png</file>
<file>assets/images/seed.png</file> <file>assets/images/seed.png</file>
<file>assets/images/speaker.png</file> <file>assets/images/speaker.png</file>
<file>assets/images/status_connected_fork.png</file> <file>assets/images/status_connected_fork.png</file>

View file

@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBF/HogkBEAChsjCJUsZhDxOx5FrnRA3X5/mJd2xdKskLSPFtnYiQUtKvpRW6
i/RVNMkTwFovzbXB6ucKJtY+OoEMu7xDhIkDWp//UlfHuP9AWAvqbhq6V5xVrZ41
9oQ7JNN4gwAI8+ZjcNq3IVFQQ9mZ3py9t1IUdgWtWM3P/SD7vWiPIjG0D3Bt3Ptl
/mZjIFZZWUtFBItJLkiTpW0Ue4t98XMP6mvQiQ/LhP82OtSyCZ6agj4Wa3ve5KjA
pdEqamBGytx2kmN+AQFgMt66yOvr+97zzuEzI6mlWYORzOc1CFMsmPd6bu/dtQ4Z
96T8PNI6i1Lv5VqvqC7RBErvD7hO36JZb8j+PnbE1YADTKrw0HmgpI6d3RLyVop3
n6ZQri0+nZ+TH0JG74MiihyZIz826zJO5OIwltexRcW0ZiRSpRCxZekU894lEs5Q
SxacRLeqM8ZVawB+9brqbeU3IJxmOCZgXLkkns0dBiSWGxtt+Tji+KXjogNfghmA
dVw9NQoBS+W5+pBtKEORD0YIGiUou9a7ukyMe2uvsl7rT+7BCOdvYtMBRbsfV5NP
s644wfJNIGa7OOjkWhuGwy6BVKTohDhJdKeZUpiTPKLV7ZLHjT4pkjuJgGQB7c+w
v7QYeUpwARwQNi8ZHuij2loG3Fb4l+3ejkcvivw0DLnDDhvUY57ezq53JwARAQAB
tCVGZWF0aGVyV2FsbGV0IDxkZXZAZmVhdGhlcndhbGxldC5vcmc+iQJOBBMBCgA4
FiEEgYXhWKMzMMf9YbwNH3bhVc77pxwFAl/HogkCGwMFCwkIBwMFFQoJCAsFFgID
AQACHgECF4AACgkQH3bhVc77pxzAxw/9GYXGm71lUlZl2yfBPmo91euSc3w/irEC
88X1kFBsdKwL19B8HUaksCOQJRG8fJQmKvJmFnRZg3NK/GLIHam+1WVObFZc1MTv
y2ERzX5ILr9sb7FptB0Wr9gk0y0Nv032ZKci3wn1j2nA87o40uopDoQTaadDTKXa
s3M2+y6zM4dCmCaV6ylJromTzIaL2Q+tWSHDD8EDF2GbnfSeeEV6TV4xj3vqfT5P
34rK4vuVNxEy/YvRQJVRYntveNMJu9C4KJvIpo8onauUHEgBu4m+qfFpixDLwQzq
bJiJQaCUrwJ3liKMolBKiPqjGNl5JRRDy+YR1Dgsj6CRobWg1fDNnrGXUwDLaBwx
zVdCB0VSmcjXpt+FKTxw1mbY+6i6trUfJSjaaawXJbktOkO6sl0bVX83oQxEgod1
aHwuo+eFCAW5zF0r+8R9Lk97Y5jkLWRKjXMFnMIyHaRhPdc24fOfojIQrXzQBMEO
lDhbWVd5vdOALhqvSOGYvjGjxBd9TE0pGzayNfPaee6kFEbxO3wZgF/QLPABl8i9
b6hHJewpY5W9mM9/yP4lHL2TRcEMzk6I7XxPQUGEb3fzTAEHRM+My4SLwaUBIFvM
L8+hRhbfNnLZPd0xDAmvH6wToL3qgK/xSl9SYwuZkzaynblmyXE4+dCFp+T2XTam
FIbphOl8Yt+5Ag0EX8eiCQEQAKv0XnHtGhWTaq/sQ4lulYWNRjBsFQRMqwSFIosO
PfzWwATQeHxxIgRlWkc25w8W0O//t8x0UcNA5rU4R+C7kVrchVSYYYl9PY0vBhKP
3efVtPgntl/VgGH8LAdShHEt3H8ZDMFjqT6gx4xnpgt3C5OdGOA3bIWuvSZ1P7qp
SYiFZakrDfPeCdI/ifucipd+EnZhFv7ivnaoIGs+jgaImQH/5uEEVxpA89Bpxoju
gXlEKSVkVAanZsUwQkc/xzhsh8dzuEF5yKomVbwTYmXDTYmpff02ycdUP7gHw0Qg
WrWaQ2M0Xq1qcZL3ZpoaWUa/A92OfuncCSDNq1pRLqwJrExqQUP9cHGwGbqeGl8K
n2tFds8Pnnv+57ZKiO8E1VTDyBey1J3/Y1hOzctfEz6BzrL52Vj4vPWh2WNNh5fL
u1ZEIdykflH/Kho0zQkRfBfD93FbN/nH1xL3V7pO/wXVGqHSD3HbFLIcJ9Ax+Jgc
Z9fm9Bvc2RkXC8lJU5+htQ+YwHPLDExvUKrBL8b8xksODCvJSWLKcTPooFQyKgbK
EnPW5kmn3eT0SHHHOArn6EHoQttkR0pV2Lrgpfg+uhy3LSTmKbtRWo7VgDY0kfVL
hsatIUqYAVdDTBzsuMhehaoWwtLAsJ01OqxAoc6+0velLddLBuLxtzGtsF0u2mEF
QJmBABEBAAGJAjYEGAEKACAWIQSBheFYozMwx/1hvA0fduFVzvunHAUCX8eiCQIb
DAAKCRAfduFVzvunHDx1D/45GVAtIP1X640PR6N8qa4Iysc/crKepgDqm8zzvpQ8
58MdeJZ9oPFEHDMkIMM8FGK9GbK4UE5mJzWJ2y5acMDOwvX4C9M206YaWQW9jPZt
fTfElP1KdAfTWz2/1UeOZKtOUuq9Wq+QlZGYg532JlX09TMyvINRM/w0+f4IBDlE
XIeRzRI6UQfz3BxpFpfWtMq/ayJnmJPrDsKQBPalai01OsbC+h4BUysZf1n7eTRF
DVaAKkSeOu+4gOVguE9PgKr11lDlKOI38tR6xBXzidBe3cPdun6vQbd1Bdfdmx3J
yFtlQo16kwwG2ZiVicXXugASBsrOFJa2/0lrtAPOnUWJsp2+1Ea6IzpRN8d1mNqr
6ND+CLxBsWj16UXq34GW6vt/QM7N1Br4/6SuPtv8OmDGRkRH7h2pz5yMf5GOwQFq
kgvOHt/x/sFPwk0GMgGn8aFr3vPH2YDg90mPn306Kv12e0JGkYVl4KqdL7u51gxT
3z5C/4+hhPVGHSPkf+g0VY/eY136kuuAZjV3P36M6UaBeCyqeD7b3fJ5IJcLwD9N
R0ustnn8IJ9zEwn+LY8kjRG8J3V57t2qAVGkMCiXnwFu3Vb+AYozOYi2ibu/N9QX
V4dTHarw64HUtLu/HEtcYuzuM5nGOXYvWPz3pQBtlqsyrhIfeaywQ+O55h5/KBo8
Ig==
=2rq8
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,5 @@
<svg version="1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" enable-background="new 0 0 48 48">
<circle fill="#2196F3" cx="24" cy="24" r="21"/>
<rect x="22" y="22" fill="#fff" width="4" height="11"/>
<circle fill="#fff" cx="24" cy="16.5" r="2.5"/>
</svg>

After

Width:  |  Height:  |  Size: 278 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M10,0V4H8L12,8L16,4H14V0M1,2V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18Z"></path></svg>

After

Width:  |  Height:  |  Size: 574 B

View file

@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M10,0V4H8L12,8L16,4H14V0M1,2V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18Z"></path></svg>

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root-684" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"></path></svg>

After

Width:  |  Height:  |  Size: 318 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root-6593" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></svg>

After

Width:  |  Height:  |  Size: 412 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M12,0L8,4H10V8H14V4H16M1,2V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18Z"></path></svg>

After

Width:  |  Height:  |  Size: 572 B

View file

@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M12,0L8,4H10V8H14V4H16M1,2V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18Z"></path></svg>

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,13 @@
<svg width="14px" height="16px" viewBox="0 0 14 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
use:not(:target) {
display: none;
}
</style>
<defs>
<g id="safer_icon" stroke="none" stroke-width="1">
<path fill-rule="nonzero" d="M7.0 2.1658351C7.0 13.931584 7.0 2.1658351 7.0 13.931584C9.8656467 13.057677 12.0 10.241354 12.0 7.2727273C12.0 7.2727273 12.0 4.2437572 12.0 4.2437572C12.0 4.2437572 7.0 2.1658351 7.0 2.1658351C7.0 2.1658351 7.0 2.1658351 7.0 2.1658351M7.0 0.0C7.0 0.0 14.0 2.9090909 14.0 2.9090909C14.0 2.9090909 14.0 7.2727273 14.0 7.2727273C14.0 11.309091 11.013333 15.083636 7.0 16.0C2.9866667 15.083636 0.0 11.309091 0.0 7.2727273C0.0 7.2727273 0.0 2.9090909 0.0 2.9090909C0.0 2.9090909 7.0 0.0 7.0 0.0"/>
</g>
</defs>
<use id="safer" fill="context-fill" fill-opacity="context-fill-opacity" href="#safer_icon" />
</svg>

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,13 @@
<svg width="14px" height="16px" viewBox="0 0 14 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
use:not(:target) {
display: none;
}
</style>
<defs>
<g id="safest_icon" stroke="none" stroke-width="1">
<path d="M7.0 0.0C7.0 0.0 14.0 2.90909091 14.0 2.90909091C14.0 2.90909091 14.0 7.27272727 14.0 7.27272727C14.0 11.3090909 11.0133333 15.0836364 7.0 16.0C2.98666667 15.0836364 0.0 11.3090909 0.0 7.27272727C0.0 7.27272727 0.0 2.90909091 0.0 2.90909091C0.0 2.90909091 7.0 0.0 7.0 0.0C7.0 0.0 7.0 0.0 7.0 0.0" />
</g>
</defs>
<use id="safest" fill="context-fill" fill-opacity="context-fill-opacity" href="#safest_icon" />
</svg>

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,13 @@
<svg width="14px" height="16px" viewBox="0 0 14 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
use:not(:target) {
display: none;
}
</style>
<defs>
<g id="standard_icon" stroke="none" stroke-width="1">
<path d="M7.0 2.16583509C7.0 2.16583509 2.0 4.24375717 2.0 4.24375717C2.0 4.24375717 2.0 7.27272727 2.0 7.27272727C2.0 10.2413541 4.13435329 13.0576771 7.0 13.9315843C9.8656467 13.0576771 12.0 10.2413541 12.0 7.27272727C12.0 7.27272727 12.0 4.24375717 12.0 4.24375717C12.0 4.24375717 7.0 2.16583509 7.0 2.16583509C7.0 2.16583509 7.0 2.16583509 7.0 2.16583509M7.0 0.0C7.0 0.0 14.0 2.90909091 14.0 2.90909091C14.0 2.90909091 14.0 7.27272727 14.0 7.27272727C14.0 11.3090909 11.0133333 15.0836364 7.0 16.0C2.98666667 15.0836364 0.0 11.3090909 0.0 7.27272727C0.0 7.27272727 0.0 2.90909091 0.0 2.90909091C0.0 2.90909091 7.0 0.0 7.0 0.0C7.0 0.0 7.0 0.0 7.0 0.0" />
</g>
</defs>
<use id="standard" fill="context-fill" fill-opacity="context-fill-opacity" href="#standard_icon" />
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -5,16 +5,15 @@
#include "calcwidget.h" #include "calcwidget.h"
#include "ui_calcwidget.h" #include "ui_calcwidget.h"
#include "mainwindow.h"
#include "components.h"
#include "utils/ColorScheme.h" #include "utils/ColorScheme.h"
#include "utils/AppData.h"
#include "utils/config.h"
CalcWidget::CalcWidget(QWidget *parent) : CalcWidget::CalcWidget(QWidget *parent) :
QWidget(parent), QWidget(parent),
ui(new Ui::CalcWidget) ui(new Ui::CalcWidget)
{ {
ui->setupUi(this); ui->setupUi(this);
m_ctx = MainWindow::getContext();
ui->imageExchange->setBackgroundRole(QPalette::Base); ui->imageExchange->setBackgroundRole(QPalette::Base);
ui->imageExchange->setAssets(":/assets/images/exchange.png", ":/assets/images/exchange_white.png"); ui->imageExchange->setAssets(":/assets/images/exchange.png", ":/assets/images/exchange_white.png");
@ -30,8 +29,8 @@ CalcWidget::CalcWidget(QWidget *parent) :
ui->lineFrom->setValidator(dv); ui->lineFrom->setValidator(dv);
ui->lineTo->setValidator(dv); ui->lineTo->setValidator(dv);
connect(AppContext::prices, &Prices::fiatPricesUpdated, this, &CalcWidget::initFiat); connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWidget::initFiat);
connect(AppContext::prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::initCrypto); connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::initCrypto);;
} }
void CalcWidget::fromChanged(const QString &data) { void CalcWidget::fromChanged(const QString &data) {
@ -56,12 +55,12 @@ void CalcWidget::fromChanged(const QString &data) {
} }
double amount = amount_str.toDouble(); double amount = amount_str.toDouble();
double result = AppContext::prices->convert(symbolFrom, symbolTo, amount); double result = appData()->prices.convert(symbolFrom, symbolTo, amount);
this->m_changing = true; this->m_changing = true;
int precision = 10; int precision = 10;
if(AppContext::prices->rates.contains(symbolTo)) if (appData()->prices.rates.contains(symbolTo))
precision = 2; precision = 2;
ui->lineTo->setText(QString::number(result, 'f', precision)); ui->lineTo->setText(QString::number(result, 'f', precision));
@ -91,12 +90,12 @@ void CalcWidget::toChanged(const QString &data) {
} }
double amount = amount_str.toDouble(); double amount = amount_str.toDouble();
double result = AppContext::prices->convert(symbolTo, symbolFrom, amount); double result = appData()->prices.convert(symbolTo, symbolFrom, amount);
this->m_changing = true; this->m_changing = true;
int precision = 10; int precision = 10;
if(AppContext::prices->rates.contains(symbolFrom)) if(appData()->prices.rates.contains(symbolFrom))
precision = 2; precision = 2;
ui->lineFrom->setText(QString::number(result, 'f', precision)); ui->lineFrom->setText(QString::number(result, 'f', precision));
@ -116,8 +115,8 @@ void CalcWidget::initFiat() {
void CalcWidget::initComboBox() { void CalcWidget::initComboBox() {
if(m_comboBoxInit) return; if(m_comboBoxInit) return;
QList<QString> marketsKeys = AppContext::prices->markets.keys(); QList<QString> marketsKeys = appData()->prices.markets.keys();
QList<QString> ratesKeys = AppContext::prices->rates.keys(); QList<QString> ratesKeys = appData()->prices.rates.keys();
if(marketsKeys.count() <= 0 || ratesKeys.count() <= 0) return; if(marketsKeys.count() <= 0 || ratesKeys.count() <= 0) return;
ui->comboCalcFrom->addItems(marketsKeys); ui->comboCalcFrom->addItems(marketsKeys);

View file

@ -4,8 +4,7 @@
#ifndef CALC_H #ifndef CALC_H
#define CALC_H #define CALC_H
#include <QMainWindow> #include <QWidget>
#include "appcontext.h"
namespace Ui { namespace Ui {
class CalcWidget; class CalcWidget;
@ -32,7 +31,7 @@ public slots:
private: private:
Ui::CalcWidget *ui; Ui::CalcWidget *ui;
AppContext *m_ctx;
bool m_comboBoxInit = false; bool m_comboBoxInit = false;
void initComboBox(); void initComboBox();
bool m_changing = false; bool m_changing = false;

View file

@ -3,6 +3,8 @@
#include "calcwindow.h" #include "calcwindow.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "utils/Icons.h"
#include "utils/AppData.h"
#include "ui_calcwindow.h" #include "ui_calcwindow.h"
@ -14,10 +16,10 @@ CalcWindow::CalcWindow(QWidget *parent) :
this->setWindowFlags(flags|Qt::WindowStaysOnTopHint); // on top this->setWindowFlags(flags|Qt::WindowStaysOnTopHint); // on top
ui->setupUi(this); ui->setupUi(this);
this->setWindowIcon(QIcon("://assets/images/gnome-calc.png")); this->setWindowIcon(icons()->icon("gnome-calc.png"));
connect(AppContext::prices, &Prices::fiatPricesUpdated, this, &CalcWindow::initFiat); connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWindow::initFiat);
connect(AppContext::prices, &Prices::cryptoPricesUpdated, this, &CalcWindow::initCrypto); connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWindow::initCrypto);
} }
void CalcWindow::initFiat() { void CalcWindow::initFiat() {

View file

@ -6,6 +6,7 @@
#include "dialog/outputinfodialog.h" #include "dialog/outputinfodialog.h"
#include "dialog/outputsweepdialog.h" #include "dialog/outputsweepdialog.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "utils/Icons.h"
#include <QClipboard> #include <QClipboard>
#include <QMessageBox> #include <QMessageBox>
@ -26,7 +27,7 @@ CoinsWidget::CoinsWidget(QWidget *parent)
connect(ui->coins->header(), &QHeaderView::customContextMenuRequested, this, &CoinsWidget::showHeaderMenu); connect(ui->coins->header(), &QHeaderView::customContextMenuRequested, this, &CoinsWidget::showHeaderMenu);
// copy menu // copy menu
m_copyMenu->setIcon(QIcon(":/assets/images/copy.png")); m_copyMenu->setIcon(icons()->icon("copy.png"));
m_copyMenu->addAction("Public key", this, [this]{copy(copyField::PubKey);}); m_copyMenu->addAction("Public key", this, [this]{copy(copyField::PubKey);});
m_copyMenu->addAction("Key Image", this, [this]{copy(copyField::KeyImage);}); m_copyMenu->addAction("Key Image", this, [this]{copy(copyField::KeyImage);});
m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);});
@ -44,7 +45,7 @@ CoinsWidget::CoinsWidget(QWidget *parent)
m_freezeAllSelectedAction = new QAction("Freeze selected", this); m_freezeAllSelectedAction = new QAction("Freeze selected", this);
m_thawAllSelectedAction = new QAction("Thaw selected", this); m_thawAllSelectedAction = new QAction("Thaw selected", this);
m_viewOutputAction = new QAction(QIcon(":/assets/images/info.png"), "Details", this); m_viewOutputAction = new QAction(icons()->icon("info2.svg"), "Details", this);
m_sweepOutputAction = new QAction("Sweep output", this); m_sweepOutputAction = new QAction("Sweep output", this);
connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeOutput); connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeOutput);
connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawOutput); connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawOutput);
@ -129,7 +130,7 @@ void CoinsWidget::setShowSpent(bool show)
void CoinsWidget::freezeOutput() { void CoinsWidget::freezeOutput() {
QModelIndex index = ui->coins->currentIndex(); QModelIndex index = ui->coins->currentIndex();
QVector<int> indexes = {m_proxyModel->mapToSource(index).row()}; QVector<int> indexes = {m_proxyModel->mapToSource(index).row()};
emit freeze(indexes); this->freezeCoins(indexes);
} }
void CoinsWidget::freezeAllSelected() { void CoinsWidget::freezeAllSelected() {
@ -139,13 +140,13 @@ void CoinsWidget::freezeAllSelected() {
for (QModelIndex index: list) { for (QModelIndex index: list) {
indexes.push_back(m_proxyModel->mapToSource(index).row()); // todo: will segfault if index get invalidated indexes.push_back(m_proxyModel->mapToSource(index).row()); // todo: will segfault if index get invalidated
} }
emit freeze(indexes); this->freezeCoins(indexes);
} }
void CoinsWidget::thawOutput() { void CoinsWidget::thawOutput() {
QModelIndex index = ui->coins->currentIndex(); QModelIndex index = ui->coins->currentIndex();
QVector<int> indexes = {m_proxyModel->mapToSource(index).row()}; QVector<int> indexes = {m_proxyModel->mapToSource(index).row()};
emit thaw(indexes); this->thawCoins(indexes);
} }
void CoinsWidget::thawAllSelected() { void CoinsWidget::thawAllSelected() {
@ -155,7 +156,7 @@ void CoinsWidget::thawAllSelected() {
for (QModelIndex index: list) { for (QModelIndex index: list) {
indexes.push_back(m_proxyModel->mapToSource(index).row()); indexes.push_back(m_proxyModel->mapToSource(index).row());
} }
emit thaw(indexes); this->thawCoins(indexes);
} }
void CoinsWidget::viewOutput() { void CoinsWidget::viewOutput() {
@ -181,7 +182,7 @@ void CoinsWidget::onSweepOutput() {
int ret = dialog->exec(); int ret = dialog->exec();
if (!ret) return; if (!ret) return;
emit sweepOutput(keyImage, dialog->address(), dialog->churn(), dialog->outputs()); m_ctx->onSweepOutput(keyImage, dialog->address(), dialog->churn(), dialog->outputs());
dialog->deleteLater(); dialog->deleteLater();
} }
@ -230,6 +231,22 @@ CoinsInfo* CoinsWidget::currentEntry() {
} }
} }
void CoinsWidget::freezeCoins(const QVector<int>& indexes) {
for (int i : indexes) {
m_ctx->currentWallet->coins()->freeze(i);
}
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
m_ctx->updateBalance();
}
void CoinsWidget::thawCoins(const QVector<int> &indexes) {
for (int i : indexes) {
m_ctx->currentWallet->coins()->thaw(i);
}
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
m_ctx->updateBalance();
}
CoinsWidget::~CoinsWidget() { CoinsWidget::~CoinsWidget() {
delete ui; delete ui;
} }

View file

@ -9,6 +9,7 @@
#include "model/CoinsProxyModel.h" #include "model/CoinsProxyModel.h"
#include "libwalletqt/Coins.h" #include "libwalletqt/Coins.h"
#include <QMenu>
#include <QWidget> #include <QWidget>
#include <QtSvg/QSvgWidget> #include <QtSvg/QSvgWidget>
@ -38,12 +39,10 @@ private slots:
void viewOutput(); void viewOutput();
void onSweepOutput(); void onSweepOutput();
signals:
void freeze(QVector<int> indexes);
void thaw(QVector<int> indexes);
void sweepOutput(const QString &keyImage, const QString &address, bool isChurn, int outputs);
private: private:
void freezeCoins(const QVector<int>& indexes);
void thawCoins(const QVector<int>& indexes);
enum copyField { enum copyField {
PubKey = 0, PubKey = 0,
KeyImage, KeyImage,

View file

@ -7,6 +7,7 @@
#include "model/ModelUtils.h" #include "model/ModelUtils.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "libwalletqt/AddressBook.h" #include "libwalletqt/AddressBook.h"
#include "utils/Icons.h"
#include <QMessageBox> #include <QMessageBox>
@ -28,14 +29,14 @@ ContactsWidget::ContactsWidget(QWidget *parent) :
// context menu // context menu
ui->contacts->setContextMenuPolicy(Qt::CustomContextMenu); ui->contacts->setContextMenuPolicy(Qt::CustomContextMenu);
m_contextMenu = new QMenu(ui->contacts); m_contextMenu = new QMenu(ui->contacts);
m_contextMenu->addAction(QIcon(":/assets/images/person.svg"), "New contact", [this]{ m_contextMenu->addAction(icons()->icon("person.svg"), "New contact", [this]{
this->newContact(); this->newContact();
}); });
// row context menu // row context menu
m_rowMenu = new QMenu(ui->contacts); m_rowMenu = new QMenu(ui->contacts);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ContactsWidget::copyAddress); m_rowMenu->addAction(icons()->icon("copy.png"), "Copy address", this, &ContactsWidget::copyAddress);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy name", this, &ContactsWidget::copyName); m_rowMenu->addAction(icons()->icon("copy.png"), "Copy name", this, &ContactsWidget::copyName);
m_rowMenu->addAction("Pay to", this, &ContactsWidget::payTo); m_rowMenu->addAction("Pay to", this, &ContactsWidget::payTo);
m_rowMenu->addAction("Delete", this, &ContactsWidget::deleteContact); m_rowMenu->addAction("Delete", this, &ContactsWidget::deleteContact);

19
src/dialog/InfoDialog.cpp Normal file
View file

@ -0,0 +1,19 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "InfoDialog.h"
#include "ui_InfoDialog.h"
InfoDialog::InfoDialog(QWidget *parent, const QString &title, const QString &infoData)
: QDialog(parent)
, ui(new Ui::InfoDialog)
{
ui->setupUi(this);
this->setWindowTitle(title);
ui->info->setPlainText(infoData);
}
InfoDialog::~InfoDialog() {
delete ui;
}

26
src/dialog/InfoDialog.h Normal file
View file

@ -0,0 +1,26 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_INFODIALOG_H
#define FEATHER_INFODIALOG_H
#include <QDialog>
namespace Ui {
class InfoDialog;
}
class InfoDialog : public QDialog
{
Q_OBJECT
public:
explicit InfoDialog(QWidget *parent, const QString &title, const QString &infoText);
~InfoDialog() override;
private:
Ui::InfoDialog *ui;
};
#endif //FEATHER_INFODIALOG_H

71
src/dialog/InfoDialog.ui Normal file
View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InfoDialog</class>
<widget class="QDialog" name="InfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>689</width>
<height>439</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPlainTextEdit" name="info">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>InfoDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>InfoDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,51 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "LocalMoneroInfoDialog.h"
#include "ui_LocalMoneroInfoDialog.h"
#include "utils/config.h"
#include "utils/utils.h"
LocalMoneroInfoDialog::LocalMoneroInfoDialog(QWidget *parent, LocalMoneroModel *model, int row)
: QDialog(parent)
, ui(new Ui::LocalMoneroInfoDialog)
, m_model(model)
, m_row(row)
{
ui->setupUi(this);
setLabelText(ui->label_price, LocalMoneroModel::PriceXMR);
setLabelText(ui->label_seller, LocalMoneroModel::Seller);
setLabelText(ui->label_paymentMethod, LocalMoneroModel::PaymentMethod);
setLabelText(ui->label_paymentDetail, LocalMoneroModel::PaymentMethodDetail);
setLabelText(ui->label_tradeLimits, LocalMoneroModel::Limits);
QJsonObject offerData = model->getOffer(row);
QString details = offerData["data"].toObject()["msg"].toString();
details.remove("*");
if (details.isEmpty()) {
details = "No details.";
}
ui->info->setPlainText(details);
connect(ui->btn_goToOffer, &QPushButton::clicked, this, &LocalMoneroInfoDialog::onGoToOffer);
}
void LocalMoneroInfoDialog::setLabelText(QLabel *label, LocalMoneroModel::Column column) {
QString data = m_model->data(m_model->index(m_row, column)).toString();
label->setText(data);
}
void LocalMoneroInfoDialog::onGoToOffer() {
QJsonObject offerData = m_model->getOffer(m_row);
QString frontend = config()->get(Config::localMoneroFrontend).toString();
QString offerUrl = QString("%1/ad/%2").arg(frontend, offerData["data"].toObject()["ad_id"].toString());
Utils::externalLinkWarning(this, offerUrl);
}
LocalMoneroInfoDialog::~LocalMoneroInfoDialog() {
delete ui;
}

View file

@ -0,0 +1,35 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_LOCALMONEROINFODIALOG_H
#define FEATHER_LOCALMONEROINFODIALOG_H
#include <QDialog>
#include <QLabel>
#include "model/LocalMoneroModel.h"
namespace Ui {
class LocalMoneroInfoDialog;
}
class LocalMoneroInfoDialog : public QDialog
{
Q_OBJECT
public:
explicit LocalMoneroInfoDialog(QWidget *parent, LocalMoneroModel *model, int row);
~LocalMoneroInfoDialog() override;
private slots:
void onGoToOffer();
private:
void setLabelText(QLabel *label, LocalMoneroModel::Column column);
Ui::LocalMoneroInfoDialog *ui;
LocalMoneroModel *m_model;
int m_row;
};
#endif //FEATHER_INFODIALOG_H

View file

@ -0,0 +1,195 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LocalMoneroInfoDialog</class>
<widget class="QDialog" name="LocalMoneroInfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>758</width>
<height>557</height>
</rect>
</property>
<property name="windowTitle">
<string>Offer info</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Info</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Price:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_price">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Seller:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_seller">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Payment method:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_paymentMethod">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Trade limits:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_tradeLimits">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Payment detail:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_paymentDetail">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="info">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_goToOffer">
<property name="text">
<string>Go to offer</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LocalMoneroInfoDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>LocalMoneroInfoDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -211,7 +211,8 @@ TxProof TxProofDialog::getProof() {
return m_wallet->getSpendProof(m_txid, message); return m_wallet->getSpendProof(m_txid, message);
} }
case Mode::OutProof: case Mode::OutProof:
case Mode::InProof: { // Todo: split this into separate functions case Mode::InProof:
default: { // Todo: split this into separate functions
return m_wallet->getTxProof(m_txid, address, message); return m_wallet->getTxProof(m_txid, address, message);
} }
} }

228
src/dialog/UpdateDialog.cpp Normal file
View file

@ -0,0 +1,228 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "UpdateDialog.h"
#include "ui_UpdateDialog.h"
#include <QFileDialog>
#include <utility>
#include "utils/networking.h"
#include "utils/NetworkManager.h"
#include "utils/AsyncTask.h"
#include "utils/Updater.h"
#include "utils/utils.h"
#include "zip.h"
UpdateDialog::UpdateDialog(QWidget *parent, QString version, QString downloadUrl, QString hash, QString signer)
: QDialog(parent)
, ui(new Ui::UpdateDialog)
, m_version(std::move(version))
, m_downloadUrl(std::move(downloadUrl))
, m_hash(std::move(hash))
, m_signer(std::move(signer))
{
ui->setupUi(this);
ui->btn_installUpdate->hide();
ui->btn_restart->hide();
ui->progressBar->hide();
auto bigFont = Utils::relativeFont(4);
ui->label_header->setFont(bigFont);
ui->label_header->setText(QString("New Feather version %1 is available").arg(m_version));
connect(ui->btn_cancel, &QPushButton::clicked, [this]{
if (m_reply) {
m_reply->abort();
}
this->reject();
});
connect(ui->btn_download, &QPushButton::clicked, this, &UpdateDialog::onDownloadClicked);
connect(ui->btn_installUpdate, &QPushButton::clicked, this, &UpdateDialog::onInstallUpdate);
connect(ui->btn_restart, &QPushButton::clicked, this, &UpdateDialog::onRestartClicked);
this->adjustSize();
}
void UpdateDialog::onDownloadClicked() {
ui->label_body->setText("Downloading update..");
ui->btn_download->hide();
ui->progressBar->show();
UtilsNetworking network{getNetworkTor()};
m_reply = network.get(m_downloadUrl);
connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdateDialog::onDownloadProgress);
connect(m_reply, &QNetworkReply::finished, this, &UpdateDialog::onDownloadFinished);
}
void UpdateDialog::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
ui->progressBar->setValue(bytesReceived);
ui->progressBar->setMaximum(bytesTotal);
}
void UpdateDialog::onDownloadFinished() {
bool error = (m_reply->error() != QNetworkReply::NoError);
if (error) {
this->onDownloadError(QString("Network error: %1").arg(m_reply->errorString()));
return;
}
QByteArray response = m_reply->readAll();
if (response.isEmpty()) {
this->onDownloadError("Network error: Empty response");
return;
}
std::string responseStr = response.toStdString();
try {
const QByteArray calculatedHash = AsyncTask::runAndWaitForFuture([this, responseStr]{
return Updater().getHash(&responseStr[0], responseStr.size());
});
const QByteArray signedHash = QByteArray::fromHex(m_hash.toUtf8());
if (signedHash != calculatedHash) {
this->onDownloadError("Error: Hash sum mismatch.");
return;
}
}
catch (const std::exception &e)
{
this->onDownloadError(QString("Error: Unable to calculate sha256sum: %1").arg(e.what()));
return;
}
this->setStatus("Download finished and verified.", true);
ui->btn_installUpdate->show();
ui->progressBar->hide();
m_updateZipArchive = responseStr;
}
void UpdateDialog::onDownloadError(const QString &errMsg) {
// Clean up so download can be retried
this->setStatus(errMsg);
ui->progressBar->hide();
ui->progressBar->setMaximum(100);
ui->progressBar->setValue(0);
ui->btn_download->show();
ui->btn_download->setText("Retry download");
}
void UpdateDialog::onInstallUpdate() {
ui->btn_installUpdate->hide();
this->setStatus("Unzipping archive...");
zip_error_t err;
zip_error_init(&err);
zip_source_t *zip_source = zip_source_buffer_create(&m_updateZipArchive[0], m_updateZipArchive.size(), 0, &err);
if (!zip_source) {
this->onInstallError(QString("Error in libzip: Unable to create zip source from buffer: %1").arg(QString::fromStdString(err.str)));
return;
}
zip_t *zip_archive = zip_open_from_source(zip_source, 0, &err);
if (!zip_archive) {
this->onInstallError(QString("Error in libzip: Unable to open archive from source: %1").arg(QString::fromStdString(err.str)));
return;
}
auto num_entries = zip_get_num_entries(zip_archive, 0);
if (num_entries <= 0) {
this->onInstallError("Error in libzip: Archive has no entries");
return;
}
// We only expect the archive to contain 1 file
std::string fname = zip_get_name(zip_archive, 0, 0);
if (fname.empty()) {
this->onInstallError("Error in libzip: Invalid filename in archive");
return;
}
struct zip_stat sb;
if (zip_stat_index(zip_archive, 0, 0, &sb) != 0) {
this->onInstallError("Error in libzip: Entry index not found");
return;
}
QString name = QString::fromStdString(sb.name);
qDebug() << "File found in archive: " << name << ", with size: " << QString::number(sb.size);
struct zip_file *zf;
zf = zip_fopen_index(zip_archive, 0, 0);
if (!zf) {
this->onInstallError("Error in libzip: Unable to open entry");
return;
}
std::unique_ptr<char[]> contents{new char[sb.size]};
auto bytes_read = zip_fread(zf, contents.get(), sb.size);
if (bytes_read != sb.size){
this->onInstallError("Error in libzip: File size inconsistent");
return;
}
zip_fclose(zf);
zip_close(zip_archive);
QString applicationPath = qgetenv("APPIMAGE");
if (!applicationPath.isEmpty()) {
applicationPath = QFileInfo(applicationPath).absoluteDir().path();
} else {
applicationPath = QCoreApplication::applicationDirPath();
}
QDir applicationDir(applicationPath);
QString filePath = applicationDir.filePath(name);
m_updatePath = filePath;
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly))
{
this->onInstallError(QString("Error: Could not write to application path: %1").arg(filePath));
return;
}
if (static_cast<size_t>(file.write(&contents[0], sb.size)) != sb.size) {
this->onInstallError("Error: Unable to write file");
return;
}
if (!file.setPermissions(QFile::ExeUser | QFile::ExeOwner | QFile::ExeGroup | QFile::ExeOther
| QFile::ReadUser | QFile::ReadOwner
| QFile::WriteUser | QFile::WriteOwner)) {
this->onInstallError("Error: Unable to set executable flags");
return;
}
this->setStatus("Installation successful. Do you want to restart Feather now?");
ui->btn_restart->show();
}
void UpdateDialog::onInstallError(const QString &errMsg) {
this->setStatus(errMsg);
}
void UpdateDialog::onRestartClicked() {
emit restartWallet(m_updatePath);
}
void UpdateDialog::setStatus(const QString &msg, bool success) {
ui->label_body->setText(msg);
if (success)
ui->label_body->setStyleSheet("QLabel { color : #2EB358; }");
else
ui->label_body->setStyleSheet("");
}
UpdateDialog::~UpdateDialog() {
delete ui;
}

51
src/dialog/UpdateDialog.h Normal file
View file

@ -0,0 +1,51 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_UPDATEDIALOG_H
#define FEATHER_UPDATEDIALOG_H
#include <QDialog>
#include <QNetworkReply>
namespace Ui {
class UpdateDialog;
}
class UpdateDialog : public QDialog
{
Q_OBJECT
public:
explicit UpdateDialog(QWidget *parent, QString version, QString downloadUrl, QString hash, QString signer);
~UpdateDialog() override;
private slots:
void onDownloadClicked();
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onDownloadFinished();
void onDownloadError(const QString &errMsg);
void onInstallUpdate();
void onInstallError(const QString &errMsg);
void onRestartClicked();
signals:
void restartWallet(const QString &binaryFilename);
private:
void setStatus(const QString &msg, bool success = false);
QString m_version;
QString m_downloadUrl;
QString m_hash;
QString m_signer;
QString m_updatePath;
std::string m_updateZipArchive;
QNetworkReply *m_reply = nullptr;
Ui::UpdateDialog *ui;
};
#endif //FEATHER_UPDATEDIALOG_H

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UpdateDialog</class>
<widget class="QDialog" name="UpdateDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>569</width>
<height>148</height>
</rect>
</property>
<property name="windowTitle">
<string>Update Available</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_header">
<property name="text">
<string>New Feather version is available.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_body">
<property name="text">
<string>Do you want to download and verify the new version?</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_download">
<property name="text">
<string>Download</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_installUpdate">
<property name="text">
<string>Install Update</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_restart">
<property name="text">
<string>Restart Feather</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -3,6 +3,7 @@
#include "broadcasttxdialog.h" #include "broadcasttxdialog.h"
#include "ui_broadcasttxdialog.h" #include "ui_broadcasttxdialog.h"
#include "utils/NetworkManager.h"
#include <QMessageBox> #include <QMessageBox>
@ -13,10 +14,8 @@ BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx, const QSt
{ {
ui->setupUi(this); ui->setupUi(this);
m_network = new UtilsNetworking(m_ctx->network, this);
auto node = ctx->nodes->connection(); auto node = ctx->nodes->connection();
m_rpc = new DaemonRpc(this, m_network, node.full); m_rpc = new DaemonRpc(this, getNetworkTor(), node.toAddress());
connect(ui->btn_Broadcast, &QPushButton::clicked, this, &BroadcastTxDialog::broadcastTx); connect(ui->btn_Broadcast, &QPushButton::clicked, this, &BroadcastTxDialog::broadcastTx);
connect(ui->btn_Close, &QPushButton::clicked, this, &BroadcastTxDialog::reject); connect(ui->btn_Close, &QPushButton::clicked, this, &BroadcastTxDialog::reject);
@ -38,7 +37,7 @@ void BroadcastTxDialog::broadcastTx() {
if (ui->radio_useCustom->isChecked()) if (ui->radio_useCustom->isChecked())
node = ui->customNode->text(); node = ui->customNode->text();
else if (ui->radio_useDefault->isChecked()) else if (ui->radio_useDefault->isChecked())
node = m_ctx->nodes->connection().full; node = m_ctx->nodes->connection().toAddress();
if (!node.startsWith("http://")) if (!node.startsWith("http://"))
node = QString("http://%1").arg(node); node = QString("http://%1").arg(node);

View file

@ -4,6 +4,9 @@
#include "debuginfodialog.h" #include "debuginfodialog.h"
#include "ui_debuginfodialog.h" #include "ui_debuginfodialog.h"
#include "config-feather.h" #include "config-feather.h"
#include "utils/WebsocketClient.h"
#include "utils/TorManager.h"
#include "utils/WebsocketNotifier.h"
DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent) DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent)
: QDialog(parent) : QDialog(parent)
@ -26,16 +29,16 @@ void DebugInfoDialog::updateInfo() {
// Special case for Tails because we know the status of the daemon by polling tails-tor-has-bootstrapped.target // Special case for Tails because we know the status of the daemon by polling tails-tor-has-bootstrapped.target
if(m_ctx->isTails) { if(m_ctx->isTails) {
if(m_ctx->tor->torConnected) if(torManager()->torConnected)
torStatus = "Connected"; torStatus = "Connected";
else else
torStatus = "Disconnected"; torStatus = "Disconnected";
} }
else if(m_ctx->isTorSocks) else if(Utils::isTorsocks())
torStatus = "Torsocks"; torStatus = "Torsocks";
else if(m_ctx->tor->localTor) else if(torManager()->isLocalTor())
torStatus = "Local (assumed to be running)"; torStatus = "Local (assumed to be running)";
else if(m_ctx->tor->torConnected) else if(torManager()->torConnected)
torStatus = "Running"; torStatus = "Running";
else else
torStatus = "Unknown"; torStatus = "Unknown";
@ -50,13 +53,37 @@ void DebugInfoDialog::updateInfo() {
ui->label_synchronized->setText(m_ctx->currentWallet->isSynchronized() ? "True" : "False"); ui->label_synchronized->setText(m_ctx->currentWallet->isSynchronized() ? "True" : "False");
auto node = m_ctx->nodes->connection(); auto node = m_ctx->nodes->connection();
ui->label_remoteNode->setText(node.full); ui->label_remoteNode->setText(node.toAddress());
ui->label_walletStatus->setText(this->statusToString(m_ctx->currentWallet->connectionStatus())); ui->label_walletStatus->setText(this->statusToString(m_ctx->currentWallet->connectionStatus()));
ui->label_torStatus->setText(torStatus); ui->label_torStatus->setText(torStatus);
ui->label_websocketStatus->setText(Utils::QtEnumToString(m_ctx->ws->webSocket.state()).remove("State")); ui->label_websocketStatus->setText(Utils::QtEnumToString(websocketNotifier()->websocketClient.webSocket.state()).remove("State"));
QString seedType = [this](){
if (m_ctx->currentWallet->isHwBacked())
return "Hardware";
if (m_ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty())
return "25 word";
else
return "14 word";
}();
QString deviceType = [this](){
if (m_ctx->currentWallet->isHwBacked()) {
if (m_ctx->currentWallet->isLedger())
return "Ledger";
else if (m_ctx->currentWallet->isTrezor())
return "Trezor";
else
return "Unknown";
}
else {
return "Software";
}
}();
ui->label_netType->setText(Utils::QtEnumToString(m_ctx->currentWallet->nettype())); ui->label_netType->setText(Utils::QtEnumToString(m_ctx->currentWallet->nettype()));
ui->label_seedType->setText(m_ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty() ? "25 word" : "14 word"); ui->label_seedType->setText(seedType);
ui->label_deviceType->setText(deviceType);
ui->label_viewOnly->setText(m_ctx->currentWallet->viewOnly() ? "True" : "False"); ui->label_viewOnly->setText(m_ctx->currentWallet->viewOnly() ? "True" : "False");
ui->label_primaryOnly->setText(m_ctx->currentWallet->balance(0) == m_ctx->currentWallet->balanceAll() ? "True" : "False"); ui->label_primaryOnly->setText(m_ctx->currentWallet->balance(0) == m_ctx->currentWallet->balanceAll() ? "True" : "False");
@ -107,6 +134,7 @@ void DebugInfoDialog::copyToClipboad() {
text += QString("Network type: %1 \n").arg(ui->label_netType->text()); text += QString("Network type: %1 \n").arg(ui->label_netType->text());
text += QString("Seed type: %1 \n").arg(ui->label_seedType->text()); text += QString("Seed type: %1 \n").arg(ui->label_seedType->text());
text += QString("Device type: %1 \n").arg(ui->label_deviceType->text());
text += QString("View only: %1 \n").arg(ui->label_viewOnly->text()); text += QString("View only: %1 \n").arg(ui->label_viewOnly->text());
text += QString("Primary only: %1 \n").arg(ui->label_primaryOnly->text()); text += QString("Primary only: %1 \n").arg(ui->label_primaryOnly->text());

View file

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>693</width> <width>693</width>
<height>612</height> <height>613</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -91,6 +91,23 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Target height:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_targetHeight">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="6" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_10"> <widget class="QLabel" name="label_10">
<property name="text"> <property name="text">
@ -242,82 +259,14 @@
</widget> </widget>
</item> </item>
<item row="16" column="0"> <item row="16" column="0">
<widget class="QLabel" name="label_7"> <widget class="QLabel" name="label_8">
<property name="text"> <property name="text">
<string>View only:</string> <string>Device type:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="1"> <item row="16" column="1">
<widget class="QLabel" name="label_viewOnly"> <widget class="QLabel" name="label_deviceType">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="20" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Timestamp:</string>
</property>
</widget>
</item>
<item row="20" column="1">
<widget class="QLabel" name="label_timestamp">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="19" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Operating system:</string>
</property>
</widget>
</item>
<item row="19" column="1">
<widget class="QLabel" name="label_OS">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Target height:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_targetHeight">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QLabel" name="label_primaryOnly">
<property name="text"> <property name="text">
<string>TextLabel</string> <string>TextLabel</string>
</property> </property>
@ -327,12 +276,80 @@
</widget> </widget>
</item> </item>
<item row="17" column="0"> <item row="17" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>View only:</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QLabel" name="label_viewOnly">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QLabel" name="label_11"> <widget class="QLabel" name="label_11">
<property name="text"> <property name="text">
<string>Primary only:</string> <string>Primary only:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="18" column="1">
<widget class="QLabel" name="label_primaryOnly">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="19" column="1">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="20" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Operating system:</string>
</property>
</widget>
</item>
<item row="20" column="1">
<widget class="QLabel" name="label_OS">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="21" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Timestamp:</string>
</property>
</widget>
</item>
<item row="21" column="1">
<widget class="QLabel" name="label_timestamp">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>

View file

@ -10,12 +10,15 @@ KeysDialog::KeysDialog(AppContext *ctx, QWidget *parent)
{ {
ui->setupUi(this); ui->setupUi(this);
ui->label_restoreHeight->setText(QString::number(ctx->currentWallet->getWalletCreationHeight())); auto w = ctx->currentWallet;
ui->label_primaryAddress->setText(ctx->currentWallet->address(0, 0)); QString unavailable = "Unavailable: Key is stored on hardware device";
ui->label_secretSpendKey->setText(ctx->currentWallet->getSecretSpendKey());
ui->label_secretViewKey->setText(ctx->currentWallet->getSecretViewKey()); ui->label_restoreHeight->setText(QString::number(w->getWalletCreationHeight()));
ui->label_publicSpendKey->setText(ctx->currentWallet->getPublicSpendKey()); ui->label_primaryAddress->setText(w->address(0, 0));
ui->label_publicViewKey->setText(ctx->currentWallet->getPublicViewKey()); ui->label_secretSpendKey->setText(w->isHwBacked() ? unavailable : w->getSecretSpendKey());
ui->label_secretViewKey->setText(w->getSecretViewKey());
ui->label_publicSpendKey->setText(w->getPublicSpendKey());
ui->label_publicViewKey->setText(w->getPublicViewKey());
this->adjustSize(); this->adjustSize();
} }

View file

@ -18,7 +18,7 @@ RestoreDialog::RestoreDialog(AppContext *ctx, QWidget *parent)
ui->restoreHeightWidget->hideSlider(); ui->restoreHeightWidget->hideSlider();
} else { } else {
// load restoreHeight lookup db // load restoreHeight lookup db
ui->restoreHeightWidget->initRestoreHeights(m_ctx->restoreHeights[m_ctx->networkType]); ui->restoreHeightWidget->initRestoreHeights(appData()->restoreHeights[m_ctx->networkType]);
} }
} }

View file

@ -0,0 +1,31 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "splashdialog.h"
#include "ui_splashdialog.h"
SplashDialog::SplashDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::SplashDialog)
{
ui->setupUi(this);
QPixmap pixmap = QPixmap(":/assets/images/key.png");
ui->icon->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation));
this->setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint);
this->adjustSize();
}
void SplashDialog::setMessage(const QString &message) {
ui->label_message->setText(message);
}
void SplashDialog::setIcon(const QPixmap &icon) {
ui->icon->setPixmap(icon.scaledToWidth(32, Qt::SmoothTransformation));
}
SplashDialog::~SplashDialog() {
delete ui;
}

28
src/dialog/splashdialog.h Normal file
View file

@ -0,0 +1,28 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_SPLASHDIALOG_H
#define FEATHER_SPLASHDIALOG_H
#include <QDialog>
namespace Ui {
class SplashDialog;
}
class SplashDialog : public QDialog
{
Q_OBJECT
public:
explicit SplashDialog(QWidget *parent = nullptr);
~SplashDialog() override;
void setMessage(const QString &message);
void setIcon(const QPixmap &icon);
private:
Ui::SplashDialog *ui;
};
#endif //FEATHER_SPLASHDIALOG_H

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SplashDialog</class>
<widget class="QDialog" name="SplashDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>751</width>
<height>101</height>
</rect>
</property>
<property name="windowTitle">
<string>Device Action Required</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="icon">
<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::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_message">
<property name="text">
<string>Action required on device: Export the view key to open the wallet.</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -4,32 +4,145 @@
#include "torinfodialog.h" #include "torinfodialog.h"
#include "ui_torinfodialog.h" #include "ui_torinfodialog.h"
#include <QPushButton>
#include <QDesktopServices> #include <QDesktopServices>
#include <QMessageBox>
TorInfoDialog::TorInfoDialog(AppContext *ctx, QWidget *parent) #include "utils/TorManager.h"
TorInfoDialog::TorInfoDialog(QWidget *parent, AppContext *ctx)
: QDialog(parent) : QDialog(parent)
, ui(new Ui::TorInfoDialog) , ui(new Ui::TorInfoDialog)
, m_ctx(ctx) , m_ctx(ctx)
{ {
ui->setupUi(this); ui->setupUi(this);
if (!m_ctx->tor->torConnected && !m_ctx->tor->errorMsg.isEmpty()) { if (!torManager()->torConnected && !torManager()->errorMsg.isEmpty()) {
ui->message->setText(m_ctx->tor->errorMsg); ui->message->setText(torManager()->errorMsg);
} else { } else {
ui->message->setText(QString("Currently using Tor instance: %1:%2").arg(Tor::torHost).arg(Tor::torPort)); ui->message->hide();
} }
if (m_ctx->tor->localTor) { if (torManager()->isLocalTor()) {
ui->logs->setHidden(true); ui->frame_logs->setHidden(true);
} else { } else {
ui->logs->setPlainText(m_ctx->tor->torLogs); ui->logs->setPlainText(torManager()->torLogs);
} }
initConnectionSettings();
initPrivacyLevel();
onConnectionStatusChanged(torManager()->torConnected);
connect(torManager(), &TorManager::connectionStateChanged, this, &TorInfoDialog::onConnectionStatusChanged);
connect(torManager(), &TorManager::logsUpdated, this, &TorInfoDialog::onLogsUpdated);
connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &TorInfoDialog::onApplySettings);
connect(ui->line_host, &QLineEdit::textEdited, this, &TorInfoDialog::onSettingsChanged);
connect(ui->line_port, &QLineEdit::textEdited, this, &TorInfoDialog::onSettingsChanged);
connect(ui->check_useLocalTor, &QCheckBox::stateChanged, this, &TorInfoDialog::onSettingsChanged);
connect(ui->btnGroup_privacyLevel, &QButtonGroup::idToggled, this, &TorInfoDialog::onSettingsChanged);
ui->label_changes->hide();
#ifndef HAS_TOR_BIN
ui->check_useLocalTor->setChecked(true);
ui->check_useLocalTor->setEnabled(false);
ui->check_useLocalTor->setToolTip("Feather was bundled without Tor");
#endif
this->adjustSize(); this->adjustSize();
} }
void TorInfoDialog::onLogsUpdated() { void TorInfoDialog::onLogsUpdated() {
ui->logs->setPlainText(m_ctx->tor->torLogs); ui->logs->setPlainText(torManager()->torLogs);
}
void TorInfoDialog::onConnectionStatusChanged(bool connected) {
if (connected) {
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_connected.png").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Connected");
} else {
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_disconnected.png").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Disconnected");
}
}
void TorInfoDialog::onApplySettings() {
config()->set(Config::socks5Host, ui->line_host->text());
config()->set(Config::socks5Port, ui->line_port->text());
int id = ui->btnGroup_privacyLevel->checkedId();
config()->set(Config::torPrivacyLevel, id);
ui->label_changes->hide();
bool useLocalTor = ui->check_useLocalTor->isChecked();
if (config()->get(Config::useLocalTor).toBool() && useLocalTor && torManager()->isStarted()) {
QMessageBox::warning(this, "Warning", "Feather is running the bundled Tor daemon, "
"but the option to never start a bundled Tor daemon was selected. "
"A restart is required to apply the setting.");
}
config()->set(Config::useLocalTor, useLocalTor);
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_lagging.png").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Connecting");
emit torSettingsChanged();
}
void TorInfoDialog::onSettingsChanged() {
ui->label_changes->show();
}
void TorInfoDialog::initConnectionSettings() {
bool localTor = torManager()->isLocalTor();
ui->label_connectionSettingsMessage->setVisible(!localTor);
ui->frame_connectionSettings->setVisible(localTor);
ui->line_host->setText(config()->get(Config::socks5Host).toString());
ui->line_port->setText(config()->get(Config::socks5Port).toString());
ui->check_useLocalTor->setChecked(config()->get(Config::useLocalTor).toBool());
}
void TorInfoDialog::initPrivacyLevel() {
ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptNode, Config::allTorExceptNode);
ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptInitSync, Config::allTorExceptInitSync);
ui->btnGroup_privacyLevel->setId(ui->radio_allTor, Config::allTor);
int privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
auto button = ui->btnGroup_privacyLevel->button(privacyLevel);
if (button) {
button->setChecked(true);
}
if (m_ctx->nodes->connection().isLocal()) {
ui->label_notice->setText("You are connected to a local node. Traffic is not routed over Tor.");
}
else if (Utils::isTorsocks()) {
ui->label_notice->setText("Feather was started with torsocks, all traffic is routed over Tor");
}
else if (WhonixOS::detect()) {
ui->label_notice->setText("Feather is running on Whonix, all traffic is routed over Tor");
}
else if (TailsOS::detect()) {
ui->label_notice->setText("Feather is running on Tails, all traffic is routed over Tor");
}
else {
ui->frame_notice->hide();
}
QPixmap iconNoTor(":/assets/images/securityLevelStandardWhite.png");
QPixmap iconNoSync(":/assets/images/securityLevelSaferWhite.png");
QPixmap iconAllTor(":/assets/images/securityLevelSafestWhite.png");
ui->icon_noTor->setPixmap(iconNoTor.scaledToHeight(16, Qt::SmoothTransformation));
ui->icon_noSync->setPixmap(iconNoSync.scaledToHeight(16, Qt::SmoothTransformation));
ui->icon_allTor->setPixmap(iconAllTor.scaledToHeight(16, Qt::SmoothTransformation));
}
void TorInfoDialog::onStopTor() {
torManager()->stop();
} }
TorInfoDialog::~TorInfoDialog() { TorInfoDialog::~TorInfoDialog() {

View file

@ -5,6 +5,7 @@
#define FEATHER_TORINFODIALOG_H #define FEATHER_TORINFODIALOG_H
#include <QDialog> #include <QDialog>
#include <QAbstractButton>
#include "appcontext.h" #include "appcontext.h"
@ -17,13 +18,25 @@ class TorInfoDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit TorInfoDialog(AppContext *ctx, QWidget *parent = nullptr); explicit TorInfoDialog(QWidget *parent, AppContext *ctx);
~TorInfoDialog() override; ~TorInfoDialog() override;
public slots: public slots:
void onLogsUpdated(); void onLogsUpdated();
private slots:
void onConnectionStatusChanged(bool connected);
void onApplySettings();
void onSettingsChanged();
void onStopTor();
signals:
void torSettingsChanged();
private: private:
void initConnectionSettings();
void initPrivacyLevel();
Ui::TorInfoDialog *ui; Ui::TorInfoDialog *ui;
AppContext *m_ctx; AppContext *m_ctx;
}; };

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>618</width> <width>703</width>
<height>386</height> <height>804</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -15,24 +15,335 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QLabel" name="message"> <widget class="QGroupBox" name="group_connectionSettings">
<property name="text"> <property name="title">
<string>Message</string> <string>Connection settings</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QFrame" name="frame_connectionSettings">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_host">
<property name="text">
<string>127.0.0.1</string>
</property>
<property name="placeholderText">
<string>127.0.0.1</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_port">
<property name="text">
<string>9050</string>
</property>
<property name="placeholderText">
<string>9050</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_connectionSettingsMessage">
<property name="text">
<string>Tor daemon is managed by Feather.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_useLocalTor">
<property name="text">
<string>Never start bundled Tor (requires local Tor daemon)</string>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPlainTextEdit" name="logs"> <widget class="QGroupBox" name="group_privacyLevel">
<property name="minimumSize"> <property name="title">
<string>Privacy Level</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="icon_noTor">
<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>
<widget class="QRadioButton" name="radio_allTorExceptNode">
<property name="text">
<string>Route all traffic over Tor, except traffic to node</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="icon_noSync">
<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>
<widget class="QRadioButton" name="radio_allTorExceptInitSync">
<property name="text">
<string>Route all traffic over Tor, except initial wallet synchronization</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="icon_allTor">
<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>
<widget class="QRadioButton" name="radio_allTor">
<property name="text">
<string>Route all traffic over Tor</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame_notice">
<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="label_notice">
<property name="text">
<string>notice</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Status</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="icon_connectionStatus">
<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>
<widget class="QLabel" name="label_testConnectionStatus">
<property name="text">
<string>status</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_changes">
<property name="text">
<string>(changes not applied)</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="message">
<property name="text">
<string>Message</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_logs">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Logs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPlainTextEdit" name="logs">
<property name="minimumSize">
<size>
<width>600</width>
<height>0</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size> <size>
<width>600</width> <width>20</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="readOnly"> </spacer>
<bool>true</bool>
</property>
</widget>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
@ -40,7 +351,7 @@
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Apply|QDialogButtonBox::Close</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -81,4 +392,7 @@
</hints> </hints>
</connection> </connection>
</connections> </connections>
<buttongroups>
<buttongroup name="btnGroup_privacyLevel"/>
</buttongroups>
</ui> </ui>

View file

@ -8,7 +8,6 @@
#include "libwalletqt/Transfer.h" #include "libwalletqt/Transfer.h"
#include "libwalletqt/Input.h" #include "libwalletqt/Input.h"
#include "model/ModelUtils.h" #include "model/ModelUtils.h"
#include "utils/ColorScheme.h"
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>

View file

@ -6,6 +6,7 @@
#include "model/ModelUtils.h" #include "model/ModelUtils.h"
#include "txconfadvdialog.h" #include "txconfadvdialog.h"
#include "globals.h" #include "globals.h"
#include "utils/AppData.h"
#include "utils/ColorScheme.h" #include "utils/ColorScheme.h"
#include <QMessageBox> #include <QMessageBox>
@ -26,7 +27,7 @@ TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QStrin
QString preferredCur = config()->get(Config::preferredFiatCurrency).toString(); QString preferredCur = config()->get(Config::preferredFiatCurrency).toString();
auto convert = [preferredCur](double amount){ auto convert = [preferredCur](double amount){
return QString::number(AppContext::prices->convert("XMR", preferredCur, amount), 'f', 2); return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2);
}; };
QString amount = WalletManager::displayAmount(tx->amount()); QString amount = WalletManager::displayAmount(tx->amount());

View file

@ -3,6 +3,7 @@
#include "tximportdialog.h" #include "tximportdialog.h"
#include "ui_tximportdialog.h" #include "ui_tximportdialog.h"
#include "utils/NetworkManager.h"
#include <QMessageBox> #include <QMessageBox>
@ -16,10 +17,8 @@ TxImportDialog::TxImportDialog(QWidget *parent, AppContext *ctx)
ui->resp->hide(); ui->resp->hide();
ui->label_loading->hide(); ui->label_loading->hide();
m_network = new UtilsNetworking(m_ctx->network, this);
auto node = ctx->nodes->connection(); auto node = ctx->nodes->connection();
m_rpc = new DaemonRpc(this, m_network, node.full); m_rpc = new DaemonRpc(this, getNetworkTor(), node.toAddress());
connect(ui->btn_load, &QPushButton::clicked, this, &TxImportDialog::loadTx); connect(ui->btn_load, &QPushButton::clicked, this, &TxImportDialog::loadTx);
connect(ui->btn_import, &QPushButton::clicked, this, &TxImportDialog::onImport); connect(ui->btn_import, &QPushButton::clicked, this, &TxImportDialog::onImport);
@ -35,7 +34,7 @@ TxImportDialog::TxImportDialog(QWidget *parent, AppContext *ctx)
void TxImportDialog::loadTx() { void TxImportDialog::loadTx() {
QString txid = ui->line_txid->text(); QString txid = ui->line_txid->text();
QString node = m_ctx->nodes->connection().full; QString node = m_ctx->nodes->connection().toAddress();
if (!node.startsWith("http://")) if (!node.startsWith("http://"))
node = QString("http://%1").arg(node); node = QString("http://%1").arg(node);

View file

@ -22,6 +22,9 @@ namespace globals
// websocket constants // websocket constants
const QUrl websocketUrl = QUrl(QStringLiteral("ws://7e6egbawekbkxzkv4244pqeqgoo4axko2imgjbedwnn6s5yb6b7oliqd.onion/ws")); const QUrl websocketUrl = QUrl(QStringLiteral("ws://7e6egbawekbkxzkv4244pqeqgoo4axko2imgjbedwnn6s5yb6b7oliqd.onion/ws"));
// website constants
const QString websiteUrl = "https://featherwallet.org";
} }
#endif //FEATHER_GLOBALS_H #endif //FEATHER_GLOBALS_H

View file

@ -5,6 +5,10 @@
#include "ui_historywidget.h" #include "ui_historywidget.h"
#include "dialog/transactioninfodialog.h" #include "dialog/transactioninfodialog.h"
#include "dialog/TxProofDialog.h" #include "dialog/TxProofDialog.h"
#include "utils/Icons.h"
#include "utils/config.h"
#include "appcontext.h"
#include <QMessageBox> #include <QMessageBox>
HistoryWidget::HistoryWidget(QWidget *parent) HistoryWidget::HistoryWidget(QWidget *parent)
@ -15,11 +19,11 @@ HistoryWidget::HistoryWidget(QWidget *parent)
{ {
ui->setupUi(this); ui->setupUi(this);
m_contextMenu->addMenu(m_copyMenu); m_contextMenu->addMenu(m_copyMenu);
m_contextMenu->addAction(QIcon(":/assets/images/info.png"), "Show details", this, &HistoryWidget::showTxDetails); m_contextMenu->addAction(icons()->icon("info2.svg"), "Show details", this, &HistoryWidget::showTxDetails);
m_contextMenu->addAction(QIcon(":/assets/images/network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); m_contextMenu->addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
// copy menu // copy menu
m_copyMenu->setIcon(QIcon(":/assets/images/copy.png")); m_copyMenu->setIcon(icons()->icon("copy.png"));
m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);});
m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);}); m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);});
m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);}); m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);});
@ -56,12 +60,12 @@ void HistoryWidget::showContextMenu(const QPoint &point) {
bool unconfirmed = tx->isFailed() || tx->isPending(); bool unconfirmed = tx->isFailed() || tx->isPending();
if (AppContext::txCache.contains(tx->hash()) && unconfirmed && tx->direction() != TransactionInfo::Direction_In) { if (AppContext::txCache.contains(tx->hash()) && unconfirmed && tx->direction() != TransactionInfo::Direction_In) {
menu.addAction(QIcon(":/assets/images/info.png"), "Resend transaction", this, &HistoryWidget::onResendTransaction); menu.addAction(icons()->icon("info2.svg"), "Resend transaction", this, &HistoryWidget::onResendTransaction);
} }
menu.addMenu(m_copyMenu); menu.addMenu(m_copyMenu);
menu.addAction(QIcon(":/assets/images/info.png"), "Show details", this, &HistoryWidget::showTxDetails); menu.addAction(icons()->icon("info2.svg"), "Show details", this, &HistoryWidget::showTxDetails);
menu.addAction(QIcon(":/assets/images/network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); menu.addAction(icons()->icon("network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
menu.addAction("Create tx proof", this, &HistoryWidget::createTxProof); menu.addAction("Create tx proof", this, &HistoryWidget::createTxProof);
menu.exec(ui->history->viewport()->mapToGlobal(point)); menu.exec(ui->history->viewport()->mapToGlobal(point));

View file

@ -10,6 +10,11 @@ Subaddress::Subaddress(Monero::Subaddress *subaddressImpl, QObject *parent)
getAll(); getAll();
} }
QString Subaddress::errorString() const
{
return QString::fromStdString(m_subaddressImpl->errorString());
}
void Subaddress::getAll() const void Subaddress::getAll() const
{ {
emit refreshStarted(); emit refreshStarted();
@ -46,17 +51,24 @@ bool Subaddress::getRow(int index, std::function<void (Monero::SubaddressRow &ro
return true; return true;
} }
void Subaddress::addRow(quint32 accountIndex, const QString &label) const bool Subaddress::addRow(quint32 accountIndex, const QString &label) const
{ {
m_subaddressImpl->addRow(accountIndex, label.toStdString()); bool r = m_subaddressImpl->addRow(accountIndex, label.toStdString());
getAll();
if (r)
getAll();
return r;
} }
void Subaddress::setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const bool Subaddress::setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const
{ {
m_subaddressImpl->setLabel(accountIndex, addressIndex, label.toStdString()); bool r = m_subaddressImpl->setLabel(accountIndex, addressIndex, label.toStdString());
getAll(); if (r) {
emit labelChanged(); getAll();
emit labelChanged();
}
return r;
} }
void Subaddress::refresh(quint32 accountIndex) const void Subaddress::refresh(quint32 accountIndex) const

View file

@ -16,13 +16,14 @@ class Subaddress : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE void getAll() const; void getAll() const;
Q_INVOKABLE bool getRow(int index, std::function<void (Monero::SubaddressRow &row)> callback) const; bool getRow(int index, std::function<void (Monero::SubaddressRow &row)> callback) const;
Q_INVOKABLE void addRow(quint32 accountIndex, const QString &label) const; bool addRow(quint32 accountIndex, const QString &label) const;
Q_INVOKABLE void setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const; bool setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const;
Q_INVOKABLE void refresh(quint32 accountIndex) const; void refresh(quint32 accountIndex) const;
Q_INVOKABLE quint64 unusedLookahead() const; quint64 unusedLookahead() const;
quint64 count() const; quint64 count() const;
QString errorString() const;
Monero::SubaddressRow* row(int index) const; Monero::SubaddressRow* row(int index) const;
signals: signals:

View file

@ -4,8 +4,8 @@
#include "TransactionHistory.h" #include "TransactionHistory.h"
#include "TransactionInfo.h" #include "TransactionInfo.h"
#include "utils/utils.h" #include "utils/utils.h"
#include "appcontext.h" #include "utils/AppData.h"
#include "utils/config.h"
bool TransactionHistory::transaction(int index, std::function<void (TransactionInfo &)> callback) bool TransactionHistory::transaction(int index, std::function<void (TransactionInfo &)> callback)
{ {
@ -164,11 +164,11 @@ bool TransactionHistory::writeCSV(const QString &path) {
// calc historical fiat price // calc historical fiat price
QString fiatAmount; QString fiatAmount;
QString preferredFiatSymbol = config()->get(Config::preferredFiatCurrency).toString(); QString preferredFiatSymbol = config()->get(Config::preferredFiatCurrency).toString();
const double usd_price = AppContext::txFiatHistory->get(timeStamp.toString("yyyyMMdd")); const double usd_price = appData()->txFiatHistory->get(timeStamp.toString("yyyyMMdd"));
double fiat_price = usd_price * amount; double fiat_price = usd_price * amount;
if(preferredFiatSymbol != "USD") if(preferredFiatSymbol != "USD")
fiat_price = AppContext::prices->convert("USD", preferredFiatSymbol, fiat_price); fiat_price = appData()->prices.convert("USD", preferredFiatSymbol, fiat_price);
double fiat_rounded = ceil(Utils::roundSignificant(fiat_price, 3) * 100.0) / 100.0; double fiat_rounded = ceil(Utils::roundSignificant(fiat_price, 3) * 100.0) / 100.0;
if(fiat_price != 0) if(fiat_price != 0)
fiatAmount = QString("%1 %2").arg(QString::number(fiat_rounded)).arg(preferredFiatSymbol); fiatAmount = QString("%1 %2").arg(QString::number(fiat_rounded)).arg(preferredFiatSymbol);

View file

@ -283,6 +283,11 @@ bool Wallet::isTrezor() const
return m_walletImpl->getDeviceType() == Monero::Wallet::Device_Trezor; return m_walletImpl->getDeviceType() == Monero::Wallet::Device_Trezor;
} }
bool Wallet::reconnectDevice()
{
return m_walletImpl->reconnectDevice();
}
//! create a view only wallet //! create a view only wallet
bool Wallet::createViewOnly(const QString &path, const QString &password) const bool Wallet::createViewOnly(const QString &path, const QString &password) const
{ {
@ -600,15 +605,15 @@ PendingTransaction *Wallet::createTransaction(const QString &dst_addr, const QSt
quint64 amount, quint32 mixin_count, quint64 amount, quint32 mixin_count,
PendingTransaction::Priority priority) PendingTransaction::Priority priority)
{ {
pauseRefresh(); // pauseRefresh();
std::set<uint32_t> subaddr_indices; std::set<uint32_t> subaddr_indices;
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction(
dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count, dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count,
static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices); static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices);
PendingTransaction * result = new PendingTransaction(ptImpl,0); PendingTransaction * result = new PendingTransaction(ptImpl, nullptr);
startRefresh(); // startRefresh();
return result; return result;
} }
@ -626,7 +631,7 @@ void Wallet::createTransactionAsync(const QString &dst_addr, const QString &paym
PendingTransaction* Wallet::createTransactionMultiDest(const QVector<QString> &dst_addr, const QVector<quint64> &amount, PendingTransaction* Wallet::createTransactionMultiDest(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
PendingTransaction::Priority priority) PendingTransaction::Priority priority)
{ {
pauseRefresh(); // pauseRefresh();
std::vector<std::string> dests; std::vector<std::string> dests;
for (auto &addr : dst_addr) { for (auto &addr : dst_addr) {
@ -642,7 +647,7 @@ PendingTransaction* Wallet::createTransactionMultiDest(const QVector<QString> &d
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransactionMultDest(dests, "", amounts, 11, static_cast<Monero::PendingTransaction::Priority>(priority)); Monero::PendingTransaction * ptImpl = m_walletImpl->createTransactionMultDest(dests, "", amounts, 11, static_cast<Monero::PendingTransaction::Priority>(priority));
PendingTransaction * result = new PendingTransaction(ptImpl); PendingTransaction * result = new PendingTransaction(ptImpl);
startRefresh(); // startRefresh();
return result; return result;
} }
@ -662,7 +667,7 @@ void Wallet::createTransactionMultiDestAsync(const QVector<QString> &dst_addr, c
PendingTransaction *Wallet::createTransactionAll(const QString &dst_addr, const QString &payment_id, PendingTransaction *Wallet::createTransactionAll(const QString &dst_addr, const QString &payment_id,
quint32 mixin_count, PendingTransaction::Priority priority) quint32 mixin_count, PendingTransaction::Priority priority)
{ {
pauseRefresh(); // pauseRefresh();
std::set<uint32_t> subaddr_indices; std::set<uint32_t> subaddr_indices;
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction(
@ -670,7 +675,7 @@ PendingTransaction *Wallet::createTransactionAll(const QString &dst_addr, const
static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices); static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices);
PendingTransaction * result = new PendingTransaction(ptImpl, this); PendingTransaction * result = new PendingTransaction(ptImpl, this);
startRefresh(); // startRefresh();
return result; return result;
} }
@ -688,13 +693,13 @@ void Wallet::createTransactionAllAsync(const QString &dst_addr, const QString &p
PendingTransaction *Wallet::createTransactionSingle(const QString &key_image, const QString &dst_addr, const size_t outputs, PendingTransaction *Wallet::createTransactionSingle(const QString &key_image, const QString &dst_addr, const size_t outputs,
PendingTransaction::Priority priority) PendingTransaction::Priority priority)
{ {
pauseRefresh(); // pauseRefresh();
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransactionSingle(key_image.toStdString(), dst_addr.toStdString(), Monero::PendingTransaction * ptImpl = m_walletImpl->createTransactionSingle(key_image.toStdString(), dst_addr.toStdString(),
outputs, static_cast<Monero::PendingTransaction::Priority>(priority)); outputs, static_cast<Monero::PendingTransaction::Priority>(priority));
PendingTransaction * result = new PendingTransaction(ptImpl, this); PendingTransaction * result = new PendingTransaction(ptImpl, this);
startRefresh(); // startRefresh();
return result; return result;
} }
@ -710,12 +715,12 @@ void Wallet::createTransactionSingleAsync(const QString &key_image, const QStrin
PendingTransaction *Wallet::createSweepUnmixableTransaction() PendingTransaction *Wallet::createSweepUnmixableTransaction()
{ {
pauseRefresh(); // pauseRefresh();
Monero::PendingTransaction * ptImpl = m_walletImpl->createSweepUnmixableTransaction(); Monero::PendingTransaction * ptImpl = m_walletImpl->createSweepUnmixableTransaction();
PendingTransaction * result = new PendingTransaction(ptImpl, this); PendingTransaction * result = new PendingTransaction(ptImpl, this);
startRefresh(); // startRefresh();
return result; return result;
} }
@ -1224,13 +1229,23 @@ void Wallet::onWalletPassphraseNeeded(bool on_device)
} }
quint64 Wallet::getBytesReceived() const { quint64 Wallet::getBytesReceived() const {
return m_walletImpl->getBytesReceived(); // TODO: this can segfault. Unclear why.
try {
return m_walletImpl->getBytesReceived();
}
catch (...) {
return 0;
}
} }
quint64 Wallet::getBytesSent() const { quint64 Wallet::getBytesSent() const {
return m_walletImpl->getBytesSent(); return m_walletImpl->getBytesSent();
} }
bool Wallet::isDeviceConnected() const {
return m_walletImpl->isDeviceConnected();
}
void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
{ {
if (m_walletListener != nullptr) if (m_walletListener != nullptr)
@ -1303,14 +1318,14 @@ Wallet::~Wallet()
delete m_coins; delete m_coins;
m_coins = NULL; m_coins = NULL;
//Monero::WalletManagerFactory::getWalletManager()->closeWallet(m_walletImpl); //Monero::WalletManagerFactory::getWalletManager()->closeWallet(m_walletImpl);
if(status() == Status_Critical) if(status() == Status_Critical || status() == Status_BadPassword)
qDebug("Not storing wallet cache"); qDebug("Not storing wallet cache");
else if( m_walletImpl->store("")) else if( m_walletImpl->store(""))
qDebug("Wallet cache stored successfully"); qDebug("Wallet cache stored successfully");
else else
qDebug("Error storing wallet cache"); qDebug("Error storing wallet cache");
delete m_walletImpl; delete m_walletImpl;
m_walletImpl = NULL; m_walletImpl = nullptr;
delete m_walletListener; delete m_walletListener;
m_walletListener = NULL; m_walletListener = NULL;
qDebug("m_walletImpl deleted"); qDebug("m_walletImpl deleted");
@ -1325,7 +1340,7 @@ void Wallet::startRefreshThread()
auto last = std::chrono::steady_clock::now(); auto last = std::chrono::steady_clock::now();
while (!m_scheduler.stopping()) while (!m_scheduler.stopping())
{ {
if (m_refreshEnabled) if (m_refreshEnabled && (!isHwBacked() || isDeviceConnected()))
{ {
const auto now = std::chrono::steady_clock::now(); const auto now = std::chrono::steady_clock::now();
const auto elapsed = now - last; const auto elapsed = now - last;

View file

@ -78,9 +78,10 @@ Q_OBJECT
public: public:
enum Status { enum Status {
Status_Ok = Monero::Wallet::Status_Ok, Status_Ok = Monero::Wallet::Status_Ok,
Status_Error = Monero::Wallet::Status_Error, Status_Error = Monero::Wallet::Status_Error,
Status_Critical = Monero::Wallet::Status_Critical Status_Critical = Monero::Wallet::Status_Critical,
Status_BadPassword = Monero::Wallet::Status_BadPassword
}; };
Q_ENUM(Status) Q_ENUM(Status)
@ -198,6 +199,9 @@ public:
bool isLedger() const; bool isLedger() const;
bool isTrezor() const; bool isTrezor() const;
//! attempt to reconnect to hw-device
bool reconnectDevice();
//! returns if view only wallet //! returns if view only wallet
bool viewOnly() const; bool viewOnly() const;
@ -424,6 +428,8 @@ public:
quint64 getBytesReceived() const; quint64 getBytesReceived() const;
quint64 getBytesSent() const; quint64 getBytesSent() const;
bool isDeviceConnected() const;
// TODO: setListenter() when it implemented in API // TODO: setListenter() when it implemented in API
signals: signals:
// emitted on every event happened with wallet // emitted on every event happened with wallet
@ -432,7 +438,7 @@ signals:
// emitted when refresh process finished (could take a long time) // emitted when refresh process finished (could take a long time)
// signalling only after we // signalling only after we
void refreshed(bool success); void refreshed(bool success, const QString &message);
void moneySpent(const QString &txId, quint64 amount); void moneySpent(const QString &txId, quint64 amount);
void moneyReceived(const QString &txId, quint64 amount); void moneyReceived(const QString &txId, quint64 amount);
@ -443,6 +449,7 @@ signals:
void walletCreationHeightChanged(); void walletCreationHeightChanged();
void deviceButtonRequest(quint64 buttonCode); void deviceButtonRequest(quint64 buttonCode);
void deviceButtonPressed(); void deviceButtonPressed();
void deviceError(const QString &message);
void walletPassphraseNeeded(bool onDevice); void walletPassphraseNeeded(bool onDevice);
void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid); void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid);
void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const; void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const;

View file

@ -45,8 +45,9 @@ void WalletListenerImpl::updated()
void WalletListenerImpl::refreshed(bool success) void WalletListenerImpl::refreshed(bool success)
{ {
qDebug() << __FUNCTION__; qDebug() << __FUNCTION__;
QString message = m_wallet->errorString();
m_wallet->onRefreshed(success); m_wallet->onRefreshed(success);
emit m_wallet->refreshed(success); emit m_wallet->refreshed(success, message);
} }
void WalletListenerImpl::onDeviceButtonRequest(uint64_t code) void WalletListenerImpl::onDeviceButtonRequest(uint64_t code)
@ -61,6 +62,12 @@ void WalletListenerImpl::onDeviceButtonPressed()
emit m_wallet->deviceButtonPressed(); emit m_wallet->deviceButtonPressed();
} }
void WalletListenerImpl::onDeviceError(const std::string &message)
{
qDebug() << __FUNCTION__;
emit m_wallet->deviceError(QString::fromStdString(message));
}
void WalletListenerImpl::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) void WalletListenerImpl::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
{ {
qDebug() << __FUNCTION__; qDebug() << __FUNCTION__;

View file

@ -31,6 +31,8 @@ public:
virtual void onDeviceButtonPressed() override; virtual void onDeviceButtonPressed() override;
virtual void onDeviceError(const std::string &message) override;
virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override; virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override;
virtual Monero::optional<std::string> onDevicePassphraseRequest(bool & on_device) override; virtual Monero::optional<std::string> onDevicePassphraseRequest(bool & on_device) override;

View file

@ -4,7 +4,6 @@
#include "libwalletqt/WalletManager.h" #include "libwalletqt/WalletManager.h"
#include "Wallet.h" #include "Wallet.h"
//#include "qt/updater.h"
#include "utils/ScopeGuard.h" #include "utils/ScopeGuard.h"
class WalletPassphraseListenerImpl : public Monero::WalletListener, public PassphraseReceiver class WalletPassphraseListenerImpl : public Monero::WalletListener, public PassphraseReceiver
@ -31,11 +30,11 @@ public:
// return m_phelper.onDevicePassphraseRequest(on_device); // return m_phelper.onDevicePassphraseRequest(on_device);
// } // }
// //
// virtual void onDeviceButtonRequest(uint64_t code) override virtual void onDeviceButtonRequest(uint64_t code) override
// { {
// qDebug() << __FUNCTION__; qDebug() << __FUNCTION__;
// emit m_mgr->deviceButtonRequest(code); emit m_mgr->deviceButtonRequest(code);
// } }
// //
// virtual void onDeviceButtonPressed() override // virtual void onDeviceButtonPressed() override
// { // {
@ -43,6 +42,12 @@ public:
// emit m_mgr->deviceButtonPressed(); // emit m_mgr->deviceButtonPressed();
// } // }
virtual void onDeviceError(const std::string &message) override
{
qDebug() << __FUNCTION__;
emit m_mgr->deviceError(QString::fromStdString(message));
}
private: private:
WalletManager * m_mgr; WalletManager * m_mgr;
PassphraseHelper m_phelper; PassphraseHelper m_phelper;
@ -125,17 +130,17 @@ Wallet *WalletManager::recoveryWallet(const QString &path, const QString &passwo
return m_currentWallet; return m_currentWallet;
} }
Wallet *WalletManager::createWalletFromKeys(const QString &path, const QString &language, NetworkType::Type nettype, Wallet *WalletManager::createWalletFromKeys(const QString &path, const QString &password, const QString &language,
const QString &address, const QString &viewkey, const QString &spendkey, NetworkType::Type nettype, const QString &address, const QString &viewkey,
quint64 restoreHeight, quint64 kdfRounds) const QString &spendkey, quint64 restoreHeight, quint64 kdfRounds)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
if (m_currentWallet) { if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet; qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet; delete m_currentWallet;
m_currentWallet = NULL; m_currentWallet = nullptr;
} }
Monero::Wallet * w = m_pimpl->createWalletFromKeys(path.toStdString(), "", language.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight, Monero::Wallet * w = m_pimpl->createWalletFromKeys(path.toStdString(), password.toStdString(), language.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight,
address.toStdString(), viewkey.toStdString(), spendkey.toStdString(), kdfRounds); address.toStdString(), viewkey.toStdString(), spendkey.toStdString(), kdfRounds);
m_currentWallet = new Wallet(w); m_currentWallet = new Wallet(w);
return m_currentWallet; return m_currentWallet;
@ -148,9 +153,9 @@ Wallet *WalletManager::createDeterministicWalletFromSpendKey(const QString &path
if (m_currentWallet) { if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet; qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet; delete m_currentWallet;
m_currentWallet = NULL; m_currentWallet = nullptr;
} }
Monero::Wallet * w = m_pimpl->createDeterministicWalletFromSpendKey(path.toStdString(), "", language.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight, Monero::Wallet * w = m_pimpl->createDeterministicWalletFromSpendKey(path.toStdString(), password.toStdString(), language.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight,
spendkey.toStdString(), kdfRounds, offset_passphrase.toStdString()); spendkey.toStdString(), kdfRounds, offset_passphrase.toStdString());
m_currentWallet = new Wallet(w); m_currentWallet = new Wallet(w);
return m_currentWallet; return m_currentWallet;
@ -172,7 +177,7 @@ Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString
if (m_currentWallet) { if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet; qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet; delete m_currentWallet;
m_currentWallet = NULL; m_currentWallet = nullptr;
} }
Monero::Wallet * w = m_pimpl->createWalletFromDevice(path.toStdString(), password.toStdString(), static_cast<Monero::NetworkType>(nettype), Monero::Wallet * w = m_pimpl->createWalletFromDevice(path.toStdString(), password.toStdString(), static_cast<Monero::NetworkType>(nettype),
deviceName.toStdString(), restoreHeight, subaddressLookahead.toStdString(), 1, &tmpListener); deviceName.toStdString(), restoreHeight, subaddressLookahead.toStdString(), 1, &tmpListener);
@ -201,10 +206,12 @@ void WalletManager::createWalletFromDeviceAsync(const QString &path, const QStri
QString WalletManager::closeWallet() QString WalletManager::closeWallet()
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
qDebug() << Q_FUNC_INFO ;
QString result; QString result;
if (m_currentWallet) { if (m_currentWallet) {
result = m_currentWallet->address(0, 0); result = m_currentWallet->address(0, 0);
delete m_currentWallet; delete m_currentWallet;
m_currentWallet = nullptr;
} else { } else {
qCritical() << "Trying to close non existing wallet " << m_currentWallet; qCritical() << "Trying to close non existing wallet " << m_currentWallet;
result = "0"; result = "0";
@ -428,15 +435,6 @@ QUrl WalletManager::localPathToUrl(const QString &path) const
return QUrl::fromLocalFile(path); return QUrl::fromLocalFile(path);
} }
QString WalletManager::checkUpdates(const QString &software, const QString &subdir) const
{
qDebug() << "Checking for updates";
const std::tuple<bool, std::string, std::string, std::string, std::string> result = Monero::WalletManager::checkUpdates(software.toStdString(), subdir.toStdString());
if (!std::get<0>(result))
return QString("");
return QString::fromStdString(std::get<1>(result) + "|" + std::get<2>(result) + "|" + std::get<3>(result) + "|" + std::get<4>(result));
}
bool WalletManager::clearWalletCache(const QString &wallet_path) const bool WalletManager::clearWalletCache(const QString &wallet_path) const
{ {
@ -459,6 +457,7 @@ WalletManager::WalletManager(QObject *parent)
: QObject(parent) : QObject(parent)
, m_passphraseReceiver(nullptr) , m_passphraseReceiver(nullptr)
, m_scheduler(this) , m_scheduler(this)
, m_currentWallet(nullptr)
{ {
m_pimpl = Monero::WalletManagerFactory::getWalletManager(); m_pimpl = Monero::WalletManagerFactory::getWalletManager();
} }

View file

@ -62,6 +62,7 @@ public:
NetworkType::Type nettype = NetworkType::MAINNET, quint64 restoreHeight = 0, quint64 kdfRounds = 1); NetworkType::Type nettype = NetworkType::MAINNET, quint64 restoreHeight = 0, quint64 kdfRounds = 1);
Q_INVOKABLE Wallet * createWalletFromKeys(const QString &path, Q_INVOKABLE Wallet * createWalletFromKeys(const QString &path,
const QString &password,
const QString &language, const QString &language,
NetworkType::Type nettype, NetworkType::Type nettype,
const QString &address, const QString &address,
@ -159,12 +160,6 @@ public:
Q_INVOKABLE bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error) const; Q_INVOKABLE bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error) const;
Q_INVOKABLE QVariantMap parse_uri_to_object(const QString &uri) const; Q_INVOKABLE QVariantMap parse_uri_to_object(const QString &uri) const;
// Q_INVOKABLE bool saveQrCode(const QString &, const QString &) const; // Q_INVOKABLE bool saveQrCode(const QString &, const QString &) const;
// Q_INVOKABLE void checkUpdatesAsync(
// const QString &software,
// const QString &subdir,
// const QString &buildTag,
// const QString &version);
Q_INVOKABLE QString checkUpdates(const QString &software, const QString &subdir) const;
// clear/rename wallet cache // clear/rename wallet cache
Q_INVOKABLE bool clearWalletCache(const QString &fileName) const; Q_INVOKABLE bool clearWalletCache(const QString &fileName) const;
@ -182,12 +177,7 @@ signals:
void walletPassphraseNeeded(bool onDevice); void walletPassphraseNeeded(bool onDevice);
void deviceButtonRequest(quint64 buttonCode); void deviceButtonRequest(quint64 buttonCode);
void deviceButtonPressed(); void deviceButtonPressed();
void checkUpdatesComplete( void deviceError(const QString &message);
const QString &version,
const QString &downloadUrl,
const QString &hash,
const QString &firstSigner,
const QString &secondSigner) const;
void miningStatus(bool isMining) const; void miningStatus(bool isMining) const;
void proxyAddressChanged() const; void proxyAddressChanged() const;

File diff suppressed because it is too large Load diff

View file

@ -4,32 +4,16 @@
#ifndef MAINWINDOW_H #ifndef MAINWINDOW_H
#define MAINWINDOW_H #define MAINWINDOW_H
#ifdef Q_OS_MAC
#include "src/kdmactouchbar.h"
#endif
#include <QMainWindow> #include <QMainWindow>
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include <QScreen> #include <QMenu>
#include <QtWidgets/QMenu>
#include <utility> #include <utility>
#include <model/SubaddressModel.h>
#include <model/SubaddressProxyModel.h>
#include <model/TransactionHistoryModel.h>
#include <model/CoinsModel.h>
#include <model/CoinsProxyModel.h>
#include "appcontext.h"
#include "components.h" #include "components.h"
#include "calcwindow.h" #include "calcwindow.h"
#include "widgets/ccswidget.h"
#include "widgets/redditwidget.h"
#include "widgets/tickerwidget.h"
#include "widgets/xmrigwidget.h"
#include "utils/networking.h"
#include "appcontext.h"
#include "utils/config.h"
#include "wizard/WalletWizard.h"
#include "settings.h" #include "settings.h"
#include "dialog/aboutdialog.h" #include "dialog/aboutdialog.h"
#include "dialog/signverifydialog.h" #include "dialog/signverifydialog.h"
#include "dialog/verifyproofdialog.h" #include "dialog/verifyproofdialog.h"
@ -38,7 +22,31 @@
#include "dialog/keysdialog.h" #include "dialog/keysdialog.h"
#include "dialog/aboutdialog.h" #include "dialog/aboutdialog.h"
#include "dialog/restoredialog.h" #include "dialog/restoredialog.h"
#include "dialog/splashdialog.h"
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
#include "model/SubaddressModel.h"
#include "model/SubaddressProxyModel.h"
#include "model/TransactionHistoryModel.h"
#include "model/CoinsModel.h"
#include "model/CoinsProxyModel.h"
#include "utils/networking.h"
#include "utils/config.h"
#include "widgets/ccswidget.h"
#include "widgets/redditwidget.h"
#include "widgets/tickerwidget.h"
#include "wizard/WalletWizard.h"
#ifdef HAS_LOCALMONERO
#include "widgets/LocalMoneroWidget.h"
#endif
#ifdef HAS_XMRIG
#include "widgets/xmrigwidget.h"
#endif
#ifdef Q_OS_MAC
#include "src/kdmactouchbar.h"
#endif
namespace Ui { namespace Ui {
class MainWindow; class MainWindow;
@ -64,13 +72,6 @@ public:
static AppContext *getContext(); static AppContext *getContext();
~MainWindow() override; ~MainWindow() override;
qreal screenDpiRef;
QRect screenGeo;
QRect screenRect;
qreal screenDpi;
qreal screenDpiPhysical;
qreal screenRatio;
enum Tabs { enum Tabs {
HOME = 0, HOME = 0,
HISTORY, HISTORY,
@ -88,8 +89,6 @@ public:
}; };
public slots: public slots:
void initWidgets();
void initMenu();
void showWizard(WalletWizard::Page startPage); void showWizard(WalletWizard::Page startPage);
void menuNewRestoreClicked(); void menuNewRestoreClicked();
void menuQuitClicked(); void menuQuitClicked();
@ -116,14 +115,17 @@ public slots:
void onRefreshSync(int height, int target); void onRefreshSync(int height, int target);
void onWalletOpenedError(const QString &err); void onWalletOpenedError(const QString &err);
void onWalletCreatedError(const QString &err); void onWalletCreatedError(const QString &err);
void onWalletCreated(Wallet *wallet);
void menuWalletCloseClicked(); void menuWalletCloseClicked();
void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path); void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path);
void onDeviceButtonRequest(quint64 code);
void onViewOnBlockExplorer(const QString &txid); void onViewOnBlockExplorer(const QString &txid);
void onResendTransaction(const QString &txid); void onResendTransaction(const QString &txid);
void importContacts(); void importContacts();
void showRestoreHeightDialog(); void showRestoreHeightDialog();
void importTransaction(); void importTransaction();
void onDeviceError(const QString &error);
void menuHwDeviceClicked();
void onUpdatesAvailable(const QJsonObject &updates);
// offline tx signing // offline tx signing
void exportKeyImages(); void exportKeyImages();
@ -148,15 +150,42 @@ public slots:
signals: signals:
void closed(); void closed();
private slots:
void onInitialNetworkConfigured();
void onCheckUpdatesComplete(const QString &version, const QString &binaryFilename, const QString &hash, const QString &signer);
void onShowUpdateCheck(const QString &version, const QString &binaryFilename, const QString &hash, const QString &signer);
void onRestartApplication(const QString &binaryFilename);
void onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version);
void onShowDonationNag();
void onInitiateTransaction();
void onEndTransaction();
void onCustomRestoreHeightSet(int height);
void onWalletAboutToClose();
// Menu
void onExportHistoryCSV(bool checked);
void onExportContactsCSV(bool checked);
void onCreateDesktopEntry(bool checked);
void onReportBug(bool checked);
private: private:
void initSkins();
void initStatusBar();
void initWidgets();
void initMenu();
void initTray();
void initHome();
void initTouchBar();
void initWalletContext();
void initWizard();
void startupWarning();
bool autoOpenWallet();
AppContext *m_ctx; AppContext *m_ctx;
static MainWindow * pMainWindow; static MainWindow * pMainWindow;
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
void cleanupBeforeClose(); void cleanupBeforeClose();
void create_status_bar();
void initMain();
void loadSkins();
QString loadStylesheet(const QString &resource); QString loadStylesheet(const QString &resource);
void saveGeo(); void saveGeo();
void restoreGeo(); void restoreGeo();
@ -173,7 +202,10 @@ private:
void showBalanceDialog(); void showBalanceDialog();
QString statusDots(); QString statusDots();
void bringToFront(); void bringToFront();
void centerWidget(QWidget &w); QString getPlatformTag();
void displayWalletErrorMsg(const QString &err);
QString getHardwareDevice();
void setTitle(bool mining);
WalletWizard *createWizard(WalletWizard::Page startPage); WalletWizard *createWizard(WalletWizard::Page startPage);
@ -181,8 +213,12 @@ private:
Settings *m_windowSettings = nullptr; Settings *m_windowSettings = nullptr;
CalcWindow *m_windowCalc = nullptr; CalcWindow *m_windowCalc = nullptr;
RestoreDialog *m_restoreDialog = nullptr; RestoreDialog *m_restoreDialog = nullptr;
AboutDialog *m_aboutDialog = nullptr;
XMRigWidget *m_xmrig = nullptr; XMRigWidget *m_xmrig = nullptr;
SplashDialog *m_splashDialog = nullptr;
#ifdef HAS_LOCALMONERO
LocalMoneroWidget *m_localMoneroWidget = nullptr;
#endif
QSystemTrayIcon *m_trayIcon; QSystemTrayIcon *m_trayIcon;
QMenu m_trayMenu; QMenu m_trayMenu;
@ -195,6 +231,7 @@ private:
TickerWidget *m_balanceWidget; TickerWidget *m_balanceWidget;
// lower status bar // lower status bar
QPushButton *m_statusUpdateAvailable;
ClickableLabel *m_statusLabelBalance; ClickableLabel *m_statusLabelBalance;
QLabel *m_statusLabelStatus; QLabel *m_statusLabelStatus;
QLabel *m_statusLabelNetStats; QLabel *m_statusLabelNetStats;
@ -203,6 +240,7 @@ private:
StatusBarButton *m_statusBtnPreferences; StatusBarButton *m_statusBtnPreferences;
StatusBarButton *m_statusBtnSeed; StatusBarButton *m_statusBtnSeed;
StatusBarButton *m_statusBtnTor; StatusBarButton *m_statusBtnTor;
StatusBarButton *m_statusBtnHwDevice;
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
QAction *m_touchbarActionWelcome; QAction *m_touchbarActionWelcome;
@ -210,6 +248,7 @@ private:
QList<QAction *> m_touchbarWalletItems; QList<QAction *> m_touchbarWalletItems;
QList<QAction *> m_touchbarWizardItems; QList<QAction *> m_touchbarWizardItems;
#endif #endif
QSignalMapper *m_tabShowHideSignalMapper; QSignalMapper *m_tabShowHideSignalMapper;
QMap<QString, ToggleTab*> m_tabShowHideMapper; QMap<QString, ToggleTab*> m_tabShowHideMapper;
WalletWizard *m_wizard = nullptr; WalletWizard *m_wizard = nullptr;
@ -222,13 +261,9 @@ private:
int m_statusDots; int m_statusDots;
bool m_constructingTransaction = false; bool m_constructingTransaction = false;
bool m_statusOverrideActive = false; bool m_statusOverrideActive = false;
bool m_showDeviceError = false;
QTimer m_txTimer; QTimer m_txTimer;
QIcon m_statusDisconnected;
QIcon m_statusConnecting;
QIcon m_statusSynchronizing;
QIcon m_statusSynchronized;
private slots: private slots:
void menuToggleTabVisible(const QString &key); void menuToggleTabVisible(const QString &key);
}; };

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1156</width> <width>977</width>
<height>496</height> <height>499</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -293,6 +293,39 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="QTabWidget" name="tabWidgetExchanges">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabLocalMonero">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/localMonero_logo.png</normaloff>:/assets/images/localMonero_logo.png</iconset>
</attribute>
<attribute name="title">
<string>LocalMonero</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="localMoneroLayout"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tabXmrRig"> <widget class="QWidget" name="tabXmrRig">
@ -319,8 +352,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1156</width> <width>977</width>
<height>30</height> <height>28</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">

View file

@ -5,6 +5,7 @@
#include "AddressBook.h" #include "AddressBook.h"
#include "ModelUtils.h" #include "ModelUtils.h"
#include "utils/utils.h" #include "utils/utils.h"
#include "utils/Icons.h"
AddressBookModel::AddressBookModel(QObject *parent, AddressBook *addressBook) AddressBookModel::AddressBookModel(QObject *parent, AddressBook *addressBook)
: QAbstractTableModel(parent), : QAbstractTableModel(parent),
@ -13,7 +14,7 @@ AddressBookModel::AddressBookModel(QObject *parent, AddressBook *addressBook)
{ {
connect(m_addressBook, &AddressBook::refreshStarted, this, &AddressBookModel::startReset); connect(m_addressBook, &AddressBook::refreshStarted, this, &AddressBookModel::startReset);
connect(m_addressBook, &AddressBook::refreshFinished, this, &AddressBookModel::endReset); connect(m_addressBook, &AddressBook::refreshFinished, this, &AddressBookModel::endReset);
m_contactIcon = QIcon(":/assets/images/person.svg"); m_contactIcon = icons()->icon("person.svg");
} }
void AddressBookModel::startReset(){ void AddressBookModel::startReset(){

View file

@ -7,9 +7,9 @@
#include "ModelUtils.h" #include "ModelUtils.h"
#include "globals.h" #include "globals.h"
#include "utils/ColorScheme.h" #include "utils/ColorScheme.h"
#include "utils/Icons.h"
#include <QBrush> #include <QBrush>
#include <QFont>
CoinsModel::CoinsModel(QObject *parent, Coins *coins) CoinsModel::CoinsModel(QObject *parent, Coins *coins)
: QAbstractTableModel(parent), : QAbstractTableModel(parent),
@ -17,9 +17,6 @@ CoinsModel::CoinsModel(QObject *parent, Coins *coins)
{ {
connect(m_coins, &Coins::refreshStarted, this, &CoinsModel::startReset); connect(m_coins, &Coins::refreshStarted, this, &CoinsModel::startReset);
connect(m_coins, &Coins::refreshFinished, this, &CoinsModel::endReset); connect(m_coins, &Coins::refreshFinished, this, &CoinsModel::endReset);
m_eye = QIcon(":/assets/images/eye1.png");
m_eyeBlind = QIcon(":/assets/images/eye_blind.png");
} }
void CoinsModel::startReset(){ void CoinsModel::startReset(){
@ -88,10 +85,10 @@ QVariant CoinsModel::data(const QModelIndex &index, int role) const
case KeyImageKnown: case KeyImageKnown:
{ {
if (cInfo.keyImageKnown()) { if (cInfo.keyImageKnown()) {
result = QVariant(m_eye); result = QVariant(icons()->icon("eye1.png"));
} }
else { else {
result = QVariant(m_eyeBlind); result = QVariant(icons()->icon("eye_blind.png"));
} }
} }
} }

View file

@ -53,8 +53,6 @@ private:
QVariant parseTransactionInfo(const CoinsInfo &cInfo, int column, int role) const; QVariant parseTransactionInfo(const CoinsInfo &cInfo, int column, int role) const;
Coins *m_coins; Coins *m_coins;
QIcon m_eye;
QIcon m_eyeBlind;
}; };
#endif //FEATHER_COINSMODEL_H #endif //FEATHER_COINSMODEL_H

View file

@ -5,6 +5,7 @@
#include "TransactionHistoryProxyModel.h" #include "TransactionHistoryProxyModel.h"
#include "libwalletqt/TransactionInfo.h" #include "libwalletqt/TransactionInfo.h"
#include "utils/utils.h"
#include <QHeaderView> #include <QHeaderView>
#include <QMenu> #include <QMenu>

View file

@ -9,6 +9,7 @@
#include <QActionGroup> #include <QActionGroup>
#include "TransactionHistoryModel.h" #include "TransactionHistoryModel.h"
#include "TransactionHistoryProxyModel.h"
class HistoryView : public QTreeView class HistoryView : public QTreeView
{ {

View file

@ -0,0 +1,172 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "LocalMoneroModel.h"
#include <QColor>
#include "utils/utils.h"
LocalMoneroModel::LocalMoneroModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
int LocalMoneroModel::rowCount(const QModelIndex &parent) const {
return m_data.count();
}
int LocalMoneroModel::columnCount(const QModelIndex &parent) const {
return Column::COUNT;
}
QVariant LocalMoneroModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case Seller:
return QString("Seller");
case Country:
return QString("Country");
case PaymentMethod:
return QString("Payment Method");
case PaymentMethodDetail:
return QString("Detail");
case PriceXMR:
return QString("Price/XMR");
case Limits:
return QString("Limits");
default:
return QVariant();
}
}
return QVariant();
}
QVariant LocalMoneroModel::data(const QModelIndex &index, int role) const {
const int col = index.column();
const auto row = m_data.at(index.row()).toObject()["data"].toObject();
if (row.isEmpty()) {
return QVariant();
}
if (role == Qt::DisplayRole) {
switch (col) {
case Column::Seller: {
auto seller = row["profile"].toObject();
return seller["name"].toString();
// TODO: online indicator
}
case Column::Country: {
return row["countrycode"].toString();
}
case Column::PaymentMethod: {
auto paymentMethodCode = row["online_provider"].toString();
if (paymentMethodCode == "NATIONAL_BANK") {
return QString("National bank transfer");
}
return m_paymentMethodNames.value(paymentMethodCode, paymentMethodCode);
}
case Column::PaymentMethodDetail: {
auto paymentMethodDetailText = row["payment_method_detail"].toString();
QString filteredString; // We can't display emojis in QTreeView
for (const auto &Char : paymentMethodDetailText) {
if (Char.unicode() < 256)
filteredString.append(Char);
else
filteredString.append(" ");
}
return filteredString.trimmed();
}
case Column::PriceXMR: {
return QString("%1 %2").arg(row["temp_price"].toString(), row["currency"].toString());
}
case Column::Limits: {
auto minAmount = row["min_amount"].toString();
auto maxAmount = row["max_amount"].toString();
if (maxAmount.isEmpty()) {
maxAmount = row["max_amount_available"].toString();
}
auto currency = row["currency"].toString();
if (minAmount.isEmpty() && maxAmount.isEmpty()) {
return QString("Up to any amount %1").arg(currency);
}
if (!minAmount.isEmpty() && maxAmount.isEmpty()) {
return QString("%1 - any amount %2").arg(minAmount, currency);
}
if (!minAmount.isEmpty() && !maxAmount.isEmpty()) {
return QString("%1 - %2 %3").arg(minAmount, maxAmount, currency);
}
if (minAmount.isEmpty() && !maxAmount.isEmpty()) {
return QString("Up to %1 %2").arg(maxAmount, currency);
}
return QVariant();
}
}
}
else if (role == Qt::ForegroundRole) {
switch (col) {
case Column::PriceXMR: {
return QVariant(QColor("#388538"));
}
}
}
else if (role == Qt::FontRole) {
switch (col) {
case Column::PriceXMR: {
auto bigFont = Utils::relativeFont(2);
bigFont.setBold(true);
return bigFont;
}
}
}
return QVariant();
}
void LocalMoneroModel::setData(const QJsonArray &data) {
beginResetModel();
m_data = data;
endResetModel();
}
void LocalMoneroModel::addData(const QJsonArray &data) {
beginResetModel();
for (const auto &row : data) {
m_data.append(row);
}
endResetModel();
}
void LocalMoneroModel::clearData() {
beginResetModel();
m_data = {};
endResetModel();
}
void LocalMoneroModel::setPaymentMethods(const QJsonObject &data) {
beginResetModel();
m_paymentMethods = data;
m_paymentMethodNames.clear();
for (const auto &payment_method : data) {
auto code = payment_method["code"].toString();
auto name = payment_method["name"].toString();
if (!code.isEmpty() && !name.isEmpty()) {
m_paymentMethodNames[code] = name;
}
}
endResetModel();
}
QJsonObject LocalMoneroModel::getOffer(int index) const {
return m_data.at(index).toObject();
}

View file

@ -0,0 +1,47 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_LOCALMONEROMODEL_H
#define FEATHER_LOCALMONEROMODEL_H
#include <QAbstractTableModel>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
class LocalMoneroModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum Column
{
Seller = 0,
Country,
PaymentMethod,
PaymentMethodDetail,
PriceXMR,
Limits,
COUNT
};
LocalMoneroModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void setData(const QJsonArray &data);
void setPaymentMethods(const QJsonObject &data);
void addData(const QJsonArray &data);
void clearData();
QJsonObject getOffer(int index) const;
private:
QJsonArray m_data;
QJsonObject m_paymentMethods;
QHash<QString, QString> m_paymentMethodNames;
};
#endif //FEATHER_LOCALMONEROMODEL_H

View file

@ -4,12 +4,13 @@
#include "NodeModel.h" #include "NodeModel.h"
#include "utils/nodes.h" #include "utils/nodes.h"
#include "utils/ColorScheme.h" #include "utils/ColorScheme.h"
#include "utils/Icons.h"
NodeModel::NodeModel(int nodeSource, QObject *parent) NodeModel::NodeModel(int nodeSource, QObject *parent)
: QAbstractTableModel(parent) : QAbstractTableModel(parent)
, m_nodeSource(nodeSource) , m_nodeSource(nodeSource)
, m_offline(QIcon(":/assets/images/expired_icon.png")) , m_offline(icons()->icon("expired_icon.png"))
, m_online(QIcon(":/assets/images/confirmed_icon.png")) , m_online(icons()->icon("confirmed_icon.png"))
{ {
} }
@ -47,7 +48,7 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const {
if(role == Qt::DisplayRole) { if(role == Qt::DisplayRole) {
switch(index.column()) { switch(index.column()) {
case NodeModel::URL: case NodeModel::URL:
return node.full; return node.toFullAddress();
case NodeModel::Height: case NodeModel::Height:
if(node.online) if(node.online)
return node.height == 0 ? QString("-") : QString::number(node.height); return node.height == 0 ? QString("-") : QString::number(node.height);

View file

@ -9,7 +9,6 @@
#include <QPoint> #include <QPoint>
#include <QColor> #include <QColor>
#include <QBrush> #include <QBrush>
#include <QFont>
SubaddressModel::SubaddressModel(QObject *parent, Subaddress *subaddress) SubaddressModel::SubaddressModel(QObject *parent, Subaddress *subaddress)
: QAbstractTableModel(parent), : QAbstractTableModel(parent),

View file

@ -5,21 +5,16 @@
#include "TransactionHistory.h" #include "TransactionHistory.h"
#include "TransactionInfo.h" #include "TransactionInfo.h"
#include "globals.h" #include "globals.h"
#include "utils/config.h"
#include "utils/ColorScheme.h" #include "utils/ColorScheme.h"
#include "utils/Icons.h"
#include "utils/AppData.h"
#include "ModelUtils.h" #include "ModelUtils.h"
TransactionHistoryModel::TransactionHistoryModel(QObject *parent) TransactionHistoryModel::TransactionHistoryModel(QObject *parent)
: QAbstractTableModel(parent), : QAbstractTableModel(parent),
m_transactionHistory(nullptr) m_transactionHistory(nullptr)
{ {
m_unconfirmedTx = QIcon(":/assets/images/unconfirmed.png");
m_warning = QIcon(":/assets/images/warning.png");
m_clock1 = QIcon(":/assets/images/clock1.png");
m_clock2 = QIcon(":/assets/images/clock2.png");
m_clock3 = QIcon(":/assets/images/clock3.png");
m_clock4 = QIcon(":/assets/images/clock4.png");
m_clock5 = QIcon(":/assets/images/clock5.png");
m_confirmedTx = QIcon(":/assets/images/confirmed.png");
} }
void TransactionHistoryModel::setTransactionHistory(TransactionHistory *th) { void TransactionHistoryModel::setTransactionHistory(TransactionHistory *th) {
@ -86,21 +81,21 @@ QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const
case Column::Date: case Column::Date:
{ {
if (tInfo.isFailed()) if (tInfo.isFailed())
result = QVariant(m_warning); result = QVariant(icons()->icon("warning.png"));
else if (tInfo.isPending()) else if (tInfo.isPending())
result = QVariant(m_unconfirmedTx); result = QVariant(icons()->icon("unconfirmed.png"));
else if (tInfo.confirmations() <= (1.0/5.0 * tInfo.confirmationsRequired())) else if (tInfo.confirmations() <= (1.0/5.0 * tInfo.confirmationsRequired()))
result = QVariant(m_clock1); result = QVariant(icons()->icon("clock1.png"));
else if (tInfo.confirmations() <= (2.0/5.0 * tInfo.confirmationsRequired())) else if (tInfo.confirmations() <= (2.0/5.0 * tInfo.confirmationsRequired()))
result = QVariant(m_clock2); result = QVariant(icons()->icon("clock2.png"));
else if (tInfo.confirmations() <= (3.0/5.0 * tInfo.confirmationsRequired())) else if (tInfo.confirmations() <= (3.0/5.0 * tInfo.confirmationsRequired()))
result = QVariant(m_clock3); result = QVariant(icons()->icon("clock3.png"));
else if (tInfo.confirmations() <= (4.0/5.0 * tInfo.confirmationsRequired())) else if (tInfo.confirmations() <= (4.0/5.0 * tInfo.confirmationsRequired()))
result = QVariant(m_clock4); result = QVariant(icons()->icon("clock4.png"));
else if (tInfo.confirmations() < tInfo.confirmationsRequired()) else if (tInfo.confirmations() < tInfo.confirmationsRequired())
result = QVariant(m_clock5); result = QVariant(icons()->icon("clock5.png"));
else if (tInfo.confirmations()) else if (tInfo.confirmations())
result = QVariant(m_confirmedTx); result = QVariant(icons()->icon("confirmed.png"));
} }
} }
} }
@ -161,13 +156,13 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
} }
case Column::FiatAmount: case Column::FiatAmount:
{ {
double usd_price = AppContext::txFiatHistory->get(tInfo.timestamp().toString("yyyyMMdd")); double usd_price = appData()->txFiatHistory->get(tInfo.timestamp().toString("yyyyMMdd"));
if (usd_price == 0.0) if (usd_price == 0.0)
return QVariant("?"); return QVariant("?");
double usd_amount = usd_price * (tInfo.balanceDelta() / globals::cdiv); double usd_amount = usd_price * (tInfo.balanceDelta() / globals::cdiv);
if(this->preferredFiatSymbol != "USD") if(this->preferredFiatSymbol != "USD")
usd_amount = AppContext::prices->convert("USD", this->preferredFiatSymbol, usd_amount); usd_amount = appData()->prices.convert("USD", this->preferredFiatSymbol, usd_amount);
if (role == Qt::UserRole) { if (role == Qt::UserRole) {
return usd_amount; return usd_amount;
} }

View file

@ -6,7 +6,6 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QIcon> #include <QIcon>
#include "appcontext.h"
class TransactionHistory; class TransactionHistory;
class TransactionInfo; class TransactionInfo;
@ -54,14 +53,6 @@ private:
QVariant parseTransactionInfo(const TransactionInfo &tInfo, int column, int role) const; QVariant parseTransactionInfo(const TransactionInfo &tInfo, int column, int role) const;
TransactionHistory * m_transactionHistory; TransactionHistory * m_transactionHistory;
QIcon m_unconfirmedTx;
QIcon m_warning;
QIcon m_clock1;
QIcon m_clock2;
QIcon m_clock3;
QIcon m_clock4;
QIcon m_clock5;
QIcon m_confirmedTx;
}; };
#endif // TRANSACTIONHISTORYMODEL_H #endif // TRANSACTIONHISTORYMODEL_H

View file

@ -5,8 +5,10 @@
#include "receivewidget.h" #include "receivewidget.h"
#include "model/ModelUtils.h" #include "model/ModelUtils.h"
#include "dialog/qrcodedialog.h" #include "dialog/qrcodedialog.h"
#include "utils/Icons.h"
#include <QMenu> #include <QMenu>
#include <QMessageBox>
ReceiveWidget::ReceiveWidget(QWidget *parent) : ReceiveWidget::ReceiveWidget(QWidget *parent) :
QWidget(parent), QWidget(parent),
@ -29,9 +31,7 @@ ReceiveWidget::ReceiveWidget(QWidget *parent) :
connect(m_showTransactionsAction, &QAction::triggered, this, &ReceiveWidget::onShowTransactions); connect(m_showTransactionsAction, &QAction::triggered, this, &ReceiveWidget::onShowTransactions);
connect(ui->addresses, &QTreeView::customContextMenuRequested, this, &ReceiveWidget::showContextMenu); connect(ui->addresses, &QTreeView::customContextMenuRequested, this, &ReceiveWidget::showContextMenu);
connect(ui->btn_generateSubaddress, &QPushButton::clicked, [=]() { connect(ui->btn_generateSubaddress, &QPushButton::clicked, this, &ReceiveWidget::generateSubaddress);
emit generateSubaddress();
});
connect(ui->qrCode, &ClickableLabel::clicked, this, &ReceiveWidget::showQrCodeDialog); connect(ui->qrCode, &ClickableLabel::clicked, this, &ReceiveWidget::showQrCodeDialog);
connect(ui->label_addressSearch, &QLineEdit::textChanged, this, &ReceiveWidget::setSearchFilter); connect(ui->label_addressSearch, &QLineEdit::textChanged, this, &ReceiveWidget::setSearchFilter);
@ -88,9 +88,9 @@ void ReceiveWidget::showContextMenu(const QPoint &point) {
auto *menu = new QMenu(ui->addresses); auto *menu = new QMenu(ui->addresses);
menu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ReceiveWidget::copyAddress); menu->addAction(icons()->icon("copy.png"), "Copy address", this, &ReceiveWidget::copyAddress);
menu->addAction(QIcon(":/assets/images/copy.png"), "Copy label", this, &ReceiveWidget::copyLabel); menu->addAction(icons()->icon("copy.png"), "Copy label", this, &ReceiveWidget::copyLabel);
menu->addAction(QIcon(":/assets/images/edit.png"), "Edit label", this, &ReceiveWidget::editLabel); menu->addAction(icons()->icon("edit.png"), "Edit label", this, &ReceiveWidget::editLabel);
if (isUsed) { if (isUsed) {
menu->addAction(m_showTransactionsAction); menu->addAction(m_showTransactionsAction);
@ -103,6 +103,10 @@ void ReceiveWidget::showContextMenu(const QPoint &point) {
menu->addAction("Hide address", this, &ReceiveWidget::hideAddress); menu->addAction("Hide address", this, &ReceiveWidget::hideAddress);
} }
if (m_wallet->isHwBacked()) {
menu->addAction("Show on device", this, &ReceiveWidget::showOnDevice);
}
menu->popup(ui->addresses->viewport()->mapToGlobal(point)); menu->popup(ui->addresses->viewport()->mapToGlobal(point));
} }
@ -164,6 +168,21 @@ void ReceiveWidget::showAddress()
m_proxyModel->setHiddenAddresses(this->getHiddenAddresses()); m_proxyModel->setHiddenAddresses(this->getHiddenAddresses());
} }
void ReceiveWidget::showOnDevice() {
Monero::SubaddressRow* row = this->currentEntry();
if (!row) return;
m_wallet->deviceShowAddressAsync(m_wallet->currentSubaddressAccount(), row->getRowId(), "");
}
void ReceiveWidget::generateSubaddress() {
if (!m_wallet) return;
bool r = m_wallet->subaddress()->addRow(m_wallet->currentSubaddressAccount(), "");
if (!r) {
QMessageBox::warning(this, "Warning", QString("Failed to generate subaddress:\n\n%1").arg(m_wallet->subaddress()->errorString()));
}
}
void ReceiveWidget::updateQrCode(){ void ReceiveWidget::updateQrCode(){
QModelIndex index = ui->addresses->currentIndex(); QModelIndex index = ui->addresses->currentIndex();
if (!index.isValid()) { if (!index.isValid()) {

View file

@ -40,13 +40,14 @@ public slots:
void resetModel(); void resetModel();
signals: signals:
void generateSubaddress();
void showTransactions(const QString& address); void showTransactions(const QString& address);
private slots: private slots:
void showHeaderMenu(const QPoint& position); void showHeaderMenu(const QPoint& position);
void hideAddress(); void hideAddress();
void showAddress(); void showAddress();
void showOnDevice();
void generateSubaddress();
private: private:
Ui::ReceiveWidget *ui; Ui::ReceiveWidget *ui;

View file

@ -6,10 +6,11 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "ui_sendwidget.h" #include "ui_sendwidget.h"
#include "globals.h" #include "globals.h"
#include "utils/AppData.h"
SendWidget::SendWidget(QWidget *parent) : SendWidget::SendWidget(QWidget *parent)
QWidget(parent), : QWidget(parent)
ui(new Ui::SendWidget) , ui(new Ui::SendWidget)
{ {
ui->setupUi(this); ui->setupUi(this);
m_ctx = MainWindow::getContext(); m_ctx = MainWindow::getContext();
@ -20,6 +21,12 @@ SendWidget::SendWidget(QWidget *parent) :
QValidator *validator = new QRegExpValidator(rx, this); QValidator *validator = new QRegExpValidator(rx, this);
ui->lineAmount->setValidator(validator); ui->lineAmount->setValidator(validator);
connect(m_ctx, &AppContext::initiateTransaction, this, &SendWidget::onInitiateTransaction);
connect(m_ctx, &AppContext::endTransaction, this, &SendWidget::onEndTransaction);
connect(m_ctx, &AppContext::openAliasResolved, this, &SendWidget::onOpenAliasResolved);
connect(m_ctx, &AppContext::openAliasResolveError, this, &SendWidget::onOpenAliasResolveError);
connect(m_ctx, &AppContext::walletClosed, this, &SendWidget::onWalletClosed);
connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked); connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked);
connect(ui->btnClear, &QPushButton::clicked, this, &SendWidget::clearClicked); connect(ui->btnClear, &QPushButton::clicked, this, &SendWidget::clearClicked);
connect(ui->btnMax, &QPushButton::clicked, this, &SendWidget::btnMaxClicked); connect(ui->btnMax, &QPushButton::clicked, this, &SendWidget::btnMaxClicked);
@ -140,7 +147,7 @@ void SendWidget::sendClicked() {
amounts.push_back(output.amount); amounts.push_back(output.amount);
} }
emit createTransactionMultiDest(addresses, amounts, description); m_ctx->onCreateTransactionMultiDest(addresses, amounts, description);
return; return;
} }
@ -152,20 +159,20 @@ void SendWidget::sendClicked() {
QMessageBox::warning(this, "Amount error", "Invalid amount specified."); QMessageBox::warning(this, "Amount error", "Invalid amount specified.");
return; return;
} }
emit createTransaction(recipient, amount, description, sendAll); m_ctx->onCreateTransaction(recipient, amount, description, sendAll);
} else { } else {
amount = WalletManager::amountFromDouble(this->conversionAmount()); amount = WalletManager::amountFromDouble(this->conversionAmount());
if (amount == 0) { if (amount == 0) {
QMessageBox::warning(this, "Fiat conversion error", "Could not create transaction."); QMessageBox::warning(this, "Fiat conversion error", "Could not create transaction.");
return; return;
} }
emit createTransaction(recipient, amount, description, false); m_ctx->onCreateTransaction(recipient, amount, description, false);
} }
} }
void SendWidget::aliasClicked() { void SendWidget::aliasClicked() {
auto address = ui->lineAddress->text(); auto address = ui->lineAddress->text();
emit resolveOpenAlias(address); m_ctx->onOpenAliasResolve(address);
} }
void SendWidget::clearClicked() { void SendWidget::clearClicked() {
@ -195,7 +202,7 @@ void SendWidget::updateConversionLabel() {
} else { } else {
auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString();
double conversionAmount = AppContext::prices->convert("XMR", preferredFiatCurrency, this->amountDouble()); double conversionAmount = appData()->prices.convert("XMR", preferredFiatCurrency, this->amountDouble());
return QString("~%1 %2").arg(QString::number(conversionAmount, 'f', 2), preferredFiatCurrency); return QString("~%1 %2").arg(QString::number(conversionAmount, 'f', 2), preferredFiatCurrency);
} }
}(); }();
@ -206,7 +213,7 @@ void SendWidget::updateConversionLabel() {
double SendWidget::conversionAmount() { double SendWidget::conversionAmount() {
QString currency = ui->comboCurrencySelection->currentText(); QString currency = ui->comboCurrencySelection->currentText();
return AppContext::prices->convert(currency, "XMR", this->amountDouble()); return appData()->prices.convert(currency, "XMR", this->amountDouble());
} }
quint64 SendWidget::amount() { quint64 SendWidget::amount() {

View file

@ -43,11 +43,6 @@ public slots:
void onInitiateTransaction(); void onInitiateTransaction();
void onEndTransaction(); void onEndTransaction();
signals:
void resolveOpenAlias(const QString &address);
void createTransaction(const QString &address, quint64 amount, const QString &description, bool all);
void createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
private: private:
void setupComboBox(); void setupComboBox();
double amountDouble(); double amountDouble();

View file

@ -17,7 +17,7 @@ Settings::Settings(QWidget *parent) :
this->setWindowIcon(QIcon("://assets/images/appicons/64x64.png")); this->setWindowIcon(QIcon("://assets/images/appicons/64x64.png"));
ui->tabWidget->setTabVisible(2, false); ui->tabWidget->setTabVisible(2, false);
ui->tabWidget->setTabVisible(4, false); ui->tabWidget->setTabVisible(5, false);
connect(ui->btnCopyToClipboard, &QPushButton::clicked, this, &Settings::copyToClipboard); connect(ui->btnCopyToClipboard, &QPushButton::clicked, this, &Settings::copyToClipboard);
connect(ui->checkBox_multiBroadcast, &QCheckBox::toggled, [](bool toggled){ connect(ui->checkBox_multiBroadcast, &QCheckBox::toggled, [](bool toggled){
@ -70,8 +70,6 @@ Settings::Settings(QWidget *parent) :
ui->comboBox_timeFormat->setCurrentIndex(m_timeFormats.indexOf(timeFormatSetting)); ui->comboBox_timeFormat->setCurrentIndex(m_timeFormats.indexOf(timeFormatSetting));
connect(ui->comboBox_skin, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_skinChanged); connect(ui->comboBox_skin, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_skinChanged);
connect(ui->comboBox_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_blockExplorerChanged);
connect(ui->comboBox_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_redditFrontendChanged);
connect(ui->comboBox_amountPrecision, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_amountPrecisionChanged); connect(ui->comboBox_amountPrecision, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_amountPrecisionChanged);
connect(ui->comboBox_dateFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_dateFormatChanged); connect(ui->comboBox_dateFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_dateFormatChanged);
connect(ui->comboBox_timeFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_timeFormatChanged); connect(ui->comboBox_timeFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_timeFormatChanged);
@ -98,6 +96,16 @@ Settings::Settings(QWidget *parent) :
ui->lineEdit_defaultWalletDir->setText(m_ctx->defaultWalletDir); ui->lineEdit_defaultWalletDir->setText(m_ctx->defaultWalletDir);
}); });
// Links tab
connect(ui->combo_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_blockExplorerChanged);
connect(ui->combo_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_redditFrontendChanged);
connect(ui->combo_localMoneroFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_localMoneroFrontendChanged);
ui->combo_blockExplorer->setCurrentIndex(ui->combo_blockExplorer->findText(config()->get(Config::blockExplorer).toString()));
ui->combo_redditFrontend->setCurrentIndex(ui->combo_redditFrontend->findText(config()->get(Config::redditFrontend).toString()));
this->setupLocalMoneroFrontendCombobox();
this->adjustSize(); this->adjustSize();
} }
@ -118,16 +126,21 @@ void Settings::comboBox_skinChanged(int pos) {
} }
void Settings::comboBox_blockExplorerChanged(int pos) { void Settings::comboBox_blockExplorerChanged(int pos) {
QString blockExplorer = ui->comboBox_blockExplorer->currentText(); QString blockExplorer = ui->combo_blockExplorer->currentText();
config()->set(Config::blockExplorer, blockExplorer); config()->set(Config::blockExplorer, blockExplorer);
emit blockExplorerChanged(blockExplorer); emit blockExplorerChanged(blockExplorer);
} }
void Settings::comboBox_redditFrontendChanged(int pos) { void Settings::comboBox_redditFrontendChanged(int pos) {
QString redditFrontend = ui->comboBox_redditFrontend->currentText(); QString redditFrontend = ui->combo_redditFrontend->currentText();
config()->set(Config::redditFrontend, redditFrontend); config()->set(Config::redditFrontend, redditFrontend);
} }
void Settings::comboBox_localMoneroFrontendChanged(int pos) {
QString localMoneroFrontend = ui->combo_localMoneroFrontend->currentData().toString();
config()->set(Config::localMoneroFrontend, localMoneroFrontend);
}
void Settings::comboBox_amountPrecisionChanged(int pos) { void Settings::comboBox_amountPrecisionChanged(int pos) {
config()->set(Config::amountPrecision, pos); config()->set(Config::amountPrecision, pos);
emit amountPrecisionChanged(pos); emit amountPrecisionChanged(pos);
@ -161,6 +174,15 @@ void Settings::setupSkinCombobox() {
ui->comboBox_skin->insertItems(0, m_skins); ui->comboBox_skin->insertItems(0, m_skins);
} }
void Settings::setupLocalMoneroFrontendCombobox() {
ui->combo_localMoneroFrontend->addItem("localmonero.co", "https://localmonero.co");
ui->combo_localMoneroFrontend->addItem("localmonero.co/nojs", "https://localmonero.co/nojs");
ui->combo_localMoneroFrontend->addItem("nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion",
"http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion");
ui->combo_localMoneroFrontend->setCurrentIndex(ui->combo_localMoneroFrontend->findData(config()->get(Config::localMoneroFrontend).toString()));
}
Settings::~Settings() { Settings::~Settings() {
delete ui; delete ui;
} }

View file

@ -37,14 +37,17 @@ public slots:
void checkboxExternalLinkWarn(); void checkboxExternalLinkWarn();
void fiatCurrencySelected(int index); void fiatCurrencySelected(int index);
void comboBox_skinChanged(int pos); void comboBox_skinChanged(int pos);
void comboBox_blockExplorerChanged(int pos);
void comboBox_redditFrontendChanged(int pos);
void comboBox_amountPrecisionChanged(int pos); void comboBox_amountPrecisionChanged(int pos);
void comboBox_dateFormatChanged(int pos); void comboBox_dateFormatChanged(int pos);
void comboBox_timeFormatChanged(int pos); void comboBox_timeFormatChanged(int pos);
void comboBox_blockExplorerChanged(int pos);
void comboBox_redditFrontendChanged(int pos);
void comboBox_localMoneroFrontendChanged(int pos);
private: private:
void setupSkinCombobox(); void setupSkinCombobox();
void setupLocalMoneroFrontendCombobox();
AppContext *m_ctx; AppContext *m_ctx;
Ui::Settings *ui; Ui::Settings *ui;

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1123</width> <width>915</width>
<height>555</height> <height>519</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -133,90 +133,33 @@
<widget class="QComboBox" name="comboBox_skin"/> <widget class="QComboBox" name="comboBox_skin"/>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Block explorer:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_blockExplorer">
<item>
<property name="text">
<string>exploremonero.com</string>
</property>
</item>
<item>
<property name="text">
<string>xmrchain.net</string>
</property>
</item>
<item>
<property name="text">
<string>moneroblocks.info</string>
</property>
</item>
<item>
<property name="text">
<string>blockchair.com</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Reddit frontend:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_redditFrontend">
<item>
<property name="text">
<string>old.reddit.com</string>
</property>
</item>
<item>
<property name="text">
<string>reddit.com</string>
</property>
</item>
<item>
<property name="text">
<string>teddit.net</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_9"> <widget class="QLabel" name="label_9">
<property name="text"> <property name="text">
<string>Amount precision:</string> <string>Amount precision:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="2" column="1">
<widget class="QComboBox" name="comboBox_amountPrecision"/> <widget class="QComboBox" name="comboBox_amountPrecision"/>
</item> </item>
<item row="5" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label_10"> <widget class="QLabel" name="label_10">
<property name="text"> <property name="text">
<string>Date format:</string> <string>Date format:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="3" column="1">
<widget class="QComboBox" name="comboBox_dateFormat"/> <widget class="QComboBox" name="comboBox_dateFormat"/>
</item> </item>
<item row="6" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_11"> <widget class="QLabel" name="label_11">
<property name="text"> <property name="text">
<string>Time format:</string> <string>Time format:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="4" column="1">
<widget class="QComboBox" name="comboBox_timeFormat"/> <widget class="QComboBox" name="comboBox_timeFormat"/>
</item> </item>
</layout> </layout>
@ -420,6 +363,80 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_links">
<attribute name="title">
<string>Links</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Block explorer:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="combo_blockExplorer">
<item>
<property name="text">
<string>exploremonero.com</string>
</property>
</item>
<item>
<property name="text">
<string>xmrchain.net</string>
</property>
</item>
<item>
<property name="text">
<string>moneroblocks.info</string>
</property>
</item>
<item>
<property name="text">
<string>blockchair.com</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Reddit frontend:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="combo_redditFrontend">
<item>
<property name="text">
<string>old.reddit.com</string>
</property>
</item>
<item>
<property name="text">
<string>reddit.com</string>
</property>
</item>
<item>
<property name="text">
<string>teddit.net</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>LocalMonero frontend:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="combo_localMoneroFrontend"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_console"> <widget class="QWidget" name="tab_console">
<attribute name="icon"> <attribute name="icon">
<iconset theme=":/assets/images/terminal.png"> <iconset theme=":/assets/images/terminal.png">

52
src/utils/AppData.cpp Normal file
View file

@ -0,0 +1,52 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "AppData.h"
#include "config.h"
#include "WebsocketNotifier.h"
AppData::AppData(QObject *parent)
: QObject(parent)
{
this->initRestoreHeights();
auto genesis_timestamp = this->restoreHeights[NetworkType::Type::MAINNET]->data.firstKey();
this->txFiatHistory = new TxFiatHistory(genesis_timestamp, Config::defaultConfigDir().path());
connect(&websocketNotifier()->websocketClient, &WebsocketClient::connectionEstablished, this->txFiatHistory, &TxFiatHistory::onUpdateDatabase);
connect(this->txFiatHistory, &TxFiatHistory::requestYear, [](int year){
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1}})").arg(year).toUtf8();
websocketNotifier()->websocketClient.sendMsg(data);
});
connect(this->txFiatHistory, &TxFiatHistory::requestYearMonth, [](int year, int month){
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1, "month": %2}})").arg(year).arg(month).toUtf8();
websocketNotifier()->websocketClient.sendMsg(data);
});
connect(websocketNotifier(), &WebsocketNotifier::CryptoRatesReceived, &this->prices, &Prices::cryptoPricesReceived);
connect(websocketNotifier(), &WebsocketNotifier::FiatRatesReceived, &this->prices, &Prices::fiatPricesReceived);
connect(websocketNotifier(), &WebsocketNotifier::TxFiatHistoryReceived, this->txFiatHistory, &TxFiatHistory::onWSData);
connect(websocketNotifier(), &WebsocketNotifier::BlockHeightsReceived, this, &AppData::onBlockHeightsReceived);
}
QPointer<AppData> AppData::m_instance(nullptr);
void AppData::onBlockHeightsReceived(int mainnet, int stagenet) {
this->heights[NetworkType::MAINNET] = mainnet;
this->heights[NetworkType::STAGENET] = stagenet;
}
void AppData::initRestoreHeights() {
restoreHeights[NetworkType::TESTNET] = new RestoreHeightLookup(NetworkType::TESTNET);
restoreHeights[NetworkType::STAGENET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_monero_stagenet.txt", NetworkType::STAGENET);
restoreHeights[NetworkType::MAINNET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_monero_mainnet.txt", NetworkType::MAINNET);
}
AppData* AppData::instance()
{
if (!m_instance) {
m_instance = new AppData(QCoreApplication::instance());
}
return m_instance;
}

41
src/utils/AppData.h Normal file
View file

@ -0,0 +1,41 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_APPDATA_H
#define FEATHER_APPDATA_H
#include <QObject>
#include <QPointer>
#include <QCoreApplication>
#include "prices.h"
#include "txfiathistory.h"
#include "RestoreHeightLookup.h"
class AppData : public QObject {
Q_OBJECT
public:
explicit AppData(QObject *parent);
static AppData* instance();
Prices prices;
TxFiatHistory *txFiatHistory;
QMap<NetworkType::Type, int> heights;
QMap<NetworkType::Type, RestoreHeightLookup*> restoreHeights;
private slots:
void onBlockHeightsReceived(int mainnet, int stagenet);
private:
void initRestoreHeights();
static QPointer<AppData> m_instance;
};
inline AppData* appData()
{
return AppData::instance();
}
#endif //FEATHER_APPDATA_H

85
src/utils/AsyncTask.h Normal file
View file

@ -0,0 +1,85 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_ASYNCTASK_HPP
#define KEEPASSXC_ASYNCTASK_HPP
#include <QFuture>
#include <QFutureWatcher>
#include <QtConcurrent/QtConcurrent>
/**
* Asynchronously run computations outside the GUI thread.
*/
namespace AsyncTask
{
/**
* Wait for the given future without blocking the event loop.
*
* @param future future to wait for
* @return async task result
*/
template <typename FunctionObject>
typename std::result_of<FunctionObject()>::type
waitForFuture(QFuture<typename std::result_of<FunctionObject()>::type> future)
{
QEventLoop loop;
QFutureWatcher<typename std::result_of<FunctionObject()>::type> watcher;
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
watcher.setFuture(future);
loop.exec();
return future.result();
}
/**
* Run a given task and wait for it to finish without blocking the event loop.
*
* @param task std::function object to run
* @return async task result
*/
template <typename FunctionObject>
typename std::result_of<FunctionObject()>::type runAndWaitForFuture(FunctionObject task)
{
return waitForFuture<FunctionObject>(QtConcurrent::run(task));
}
/**
* Run a given task then call the defined callback. Prevents event loop blocking and
* ensures the validity of the follow-on task through the context. If the context is
* deleted, the callback will not be processed preventing use after free errors.
*
* @param task std::function object to run
* @param context QObject responsible for calling this function
* @param callback std::function object to run after the task completess
*/
template <typename FunctionObject, typename FunctionObject2>
void runThenCallback(FunctionObject task, QObject* context, FunctionObject2 callback)
{
typedef QFutureWatcher<typename std::result_of<FunctionObject()>::type> FutureWatcher;
auto future = QtConcurrent::run(task);
auto watcher = new FutureWatcher(context);
QObject::connect(watcher, &QFutureWatcherBase::finished, context, [=]() {
watcher->deleteLater();
callback(future.result());
});
watcher->setFuture(future);
}
}; // namespace AsyncTask
#endif // KEEPASSXC_ASYNCTASK_HPP

View file

@ -3,6 +3,7 @@
// Copyright (c) 2012 thomasv@gitorious // Copyright (c) 2012 thomasv@gitorious
#include "ColorScheme.h" #include "ColorScheme.h"
#include <QDebug>
bool ColorScheme::darkScheme = false; bool ColorScheme::darkScheme = false;
ColorSchemeItem ColorScheme::GREEN = ColorSchemeItem("#117c11", "#8af296"); ColorSchemeItem ColorScheme::GREEN = ColorSchemeItem("#117c11", "#8af296");

Some files were not shown because too many files have changed in this diff Show more