Compare commits

..
Author SHA1 Message Date
tobtoht d41d526da0 Trigger CI 2020-12-05 19:35:57 +00:00
124 changed files with 9882 additions and 1716 deletions
+15 -16
View File
@@ -14,10 +14,9 @@ steps:
commands:
- git config --global url."http://gitea:3000/tor/".insteadOf https://git.torproject.org/
- git config --global url."http://gitea:3000/".insteadOf https://github.com/
- git config --global url."http://gitea:3000/".insteadOf https://git.wownero.com/
- git submodule update --init monero
- git submodule update --init --depth 1 --recursive monero
- TOR_BIN="/usr/local/tor/bin/tor" make -j8 release-static
- git submodule update --init --depth 120 monero
- git submodule update --init --depth 120 --recursive monero
- TOR="/usr/local/tor/bin/tor" XMRIG="/xmrig/xmrig" make -j6 release-static
environment:
OPENSSL_ROOT_DIR: /usr/local/openssl/
CMAKEFLAGS_EXTRA: -DFETCH_DEPS=Off
@@ -46,19 +45,17 @@ volumes:
path: /build/feather_files/files/linux-release/
---
kind: pipeline
type: docker
name: linux-release-appimage
steps:
- name: build
image: feather:appimage
volumes:
- name: files_linux_release
path: /files
commands:
- export FN="feather-`echo $DRONE_COMMIT_AFTER | cut -c 1-7`.zip"
- export BRANCH="$DRONE_SOURCE_BRANCH"
- cp /files/$BRANCH/$FN feather.zip
- bash ./contrib/build-appimage.sh
- name: deploy
image: feather:appimage
@@ -72,18 +69,18 @@ steps:
- echo "writing to $TARGET_DIR/$FN"
- mv "Feather-1.0-x86_64.AppImage" "$TARGET_DIR/$FN"
- echo "[*] written to https://build.featherwallet.org/files/linux-release-appimage/$DRONE_SOURCE_BRANCH/$FN"
volumes:
- name: files_linux_appimage
host:
path: /build/feather_files/files/linux-release-appimage/
- name: files_linux_release
host:
path: /build/feather_files/files/linux-release/
---
kind: pipeline
type: docker
name: windows-mxe-release
steps:
- name: build
image: feather:win
@@ -95,10 +92,9 @@ steps:
commands:
- git config --global url."http://gitea:3000/tor/".insteadOf https://git.torproject.org/
- git config --global url."http://gitea:3000/".insteadOf https://github.com/
- git config --global url."http://gitea:3000/".insteadOf https://git.wownero.com/
- git submodule update --init monero
- git submodule update --init --depth 1 --recursive monero
- PATH="/mxe/usr/bin/:$PATH" TOR_BIN="/mxe/usr/x86_64-w64-mingw32.static/bin/tor.exe" make -j8 windows-mxe-release
- git submodule update --init --depth 120 monero
- git submodule update --init --depth 120 --recursive monero
- PATH="/mxe/usr/bin/:$PATH" TOR="/mxe/usr/x86_64-w64-mingw32.static/bin/tor.exe" XMRIG="/xmrig/xmrig.exe" make -j6 windows-mxe-release
environment:
CMAKEFLAGS_EXTRA: -DFETCH_DEPS=Off
- name: deploy
@@ -115,6 +111,7 @@ steps:
- echo "writing to $TARGET_DIR/$FN"
- zip -j "$TARGET_DIR/$FN" build/feather.log build/bin/feather.exe
- echo "[*] written to https://build.featherwallet.org/files/windows-mxe-release/$DRONE_SOURCE_BRANCH/$FN"
volumes:
- name: ccache_win_release
host:
@@ -124,6 +121,7 @@ volumes:
path: /build/feather_files/files/windows-mxe-release/
---
kind: pipeline
type: docker
name: mac-release
@@ -136,6 +134,7 @@ steps:
path: /files
commands:
- mkdir -p build
- scp -P22 utils/build_macos.sh administrator@steve.jobs.xmr.pm:build_macos.sh
- ssh administrator@steve.jobs.xmr.pm "chmod +x build_macos.sh && PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin ~/build_macos.sh $DRONE_COMMIT_SHA"
- scp -P22 administrator@steve.jobs.xmr.pm:feather.zip build/feather.zip
- name: deploy
@@ -157,6 +156,6 @@ volumes:
path: /build/feather_files/files/mac-release/
---
kind: signature
hmac: f16a0379280e2e89987930d635ec6fb938d67732fdaf4ddc488f2a9db64bda2c
hmac: 527d334190a8a824b3b781a05ae4c7d87f4fa2bc37ebc53a96db91f925fa4a52
...
+3
View File
@@ -1,6 +1,9 @@
[submodule "monero"]
path = monero
url = https://git.wownero.com/feather/monero.git
[submodule "contrib/torsocks"]
path = contrib/torsocks
url = https://git.torproject.org/torsocks.git
[submodule "contrib/tor"]
path = contrib/tor
url = https://git.torproject.org/tor.git
+36 -41
View File
@@ -10,44 +10,6 @@ Static builds via Docker are done in 3 steps:
2. Creating a base Docker image
3. Using the base image to compile a build
### Linux (reproducible)
The docker image for reproducible Linux static builds uses Ubuntu 16.04 and compiles the required libraries statically
so that the resulting Feather binary is static. For more information, check the Dockerfile: `Dockerfile`.
#### 1. Clone
```bash
git clone --branch master --recursive https://git.wownero.com/feather/feather.git
cd feather
```
Replace `master` with the desired version tag (e.g. `beta-1`) to build the release binary.
#### 2. Base image
```bash
docker build --tag feather:linux --build-arg THREADS=4 .
```
Building the base image takes a while. You only need to build the base image once.
#### 3. Build
```bash
docker run --rm -it -v $PWD:/feather --env OPENSSL_ROOT_DIR=/usr/local/openssl/ -w /feather feather:linux sh -c 'TOR_BIN="/usr/local/tor/bin/tor" make release-static -j4'
```
If you're re-running a build make sure to `rm -rf build/` first.
The resulting binary can be found in `build/bin/feather`.
Hashes for tagged commits should match:
```
beta-1: d1a52e3bac1abbae4adda1fc88cb2a7a06fbd61085868421897c6a4f3f4eb091 feather
```
### Windows
The docker image for Windows static compiles uses Ubuntu 18.04 and installs [mxe](https://mxe.cc) from [our git](https://git.wownero.com/feather/mxe/src/branch/feather-patch),
@@ -73,12 +35,45 @@ Note: You only need to build the base image once.
#### 3. Build
```bash
docker run --rm -it -v /tmp/ccache:/root/.ccache -v PATH_TO_FEATHER:/feather -w /feather feather:win /bin/bash -c 'PATH="/mxe/usr/bin/:$PATH" TOR_BIN="/mxe/usr/x86_64-w64-mingw32.static/bin/tor.exe" make windows-mxe-release -j8'
docker run --rm -it -v /tmp/ccache:/root/.ccache -v /root/feather:/feather -w /feather feather:win /bin/bash -c 'PATH="/mxe/usr/bin/:$PATH" TOR="/mxe/usr/x86_64-w64-mingw32.static/bin/tor.exe" XMRIG="/xmrig/xmrig.exe" make windows-mxe-release -j8'
```
Replace `PATH_TO_FEATHER` with the absolute path to Feather locally.
The resulting binary can be found in `build/bin/feather.exe`.
### Linux
The docker image for Linux static compiles uses Ubuntu 18.04 and compiles the required libraries statically so that
the resulting Feather binary is static. It comes with OpenSSL 1.1.1g, Qt 5.15.0 (OpenGL disabled). For more information,
check the Dockerfile: `Dockerfile`.
#### 1. Clone
```bash
git clone --recursive https://git.wownero.com/feather/feather.git
cd feather
```
#### 2. Base image
Warning: Building the base image takes a while, go prepare some dinner.
```bash
docker build --tag feather:linux --build-arg THREADS=8 .
```
Note: You only need to build the base image once.
#### 3. Build
```bash
docker run --env OPENSSL_ROOT_DIR=/usr/local/openssl/ --rm -it -v /tmp/ccache:/root/.ccache -v PATH_TO_FEATHER:/feather -w /feather feather:linux sh -c 'TOR="/usr/local/tor/bin/tor" XMRIG="/xmrig/xmrig" make release-static -j8'
```
Replace `PATH_TO_FEATHER` with the absolute path to Feather locally.
The resulting binary can be found in `build/bin/feather.exe`.
The resulting binary can be found in `build/bin/feather`.
## macOS
@@ -103,4 +98,4 @@ Build Feather.
CMAKE_PREFIX_PATH=~/Qt5.15.1/5.15.1/clang_64 make mac-release
```
The resulting Mac OS application can be found `build/bin/feather.app` and will **not** have Tor embedded.
The resulting Mac OS application can be found `build/bin/feather.app` and will **not** have Tor embedded.
+29 -10
View File
@@ -7,13 +7,14 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
set(VERSION_MAJOR "0")
set(VERSION_MINOR "1")
set(VERSION_REVISION "0")
set(VERSION "beta-3")
set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}")
option(FETCH_DEPS "Download dependencies if they are not found" ON)
option(XMRTO "Include Xmr.To module" ON)
option(XMRIG "Include XMRig module" ON)
option(TOR_BIN "Path to Tor binary to embed inside Feather" OFF)
option(MORPHTOKEN "Include MorphToken module" ON)
option(XMRIG "Path to XMRig binary to embed inside Feather" OFF)
option(TOR "Path to Tor binary to embed inside Feather" OFF)
option(TOR_VERSION "Optional git hash or tag of embedded Tor version" "tor-0.4.3.5")
option(STATIC "Link libraries statically, requires static Qt")
option(USE_DEVICE_TREZOR "Trezor support compilation" OFF)
option(DONATE_BEG "Prompt donation window every once in a while" ON)
@@ -30,7 +31,7 @@ if(DEBUG)
set(CMAKE_VERBOSE_MAKEFILE ON)
endif()
set(MONERO_HEAD "85b0b4f73aa6114e3ff91207aa94ad2a15c939a2")
set(MONERO_HEAD "2c41634c13994a8a112a1e3a61e5e0b456791a97")
set(BUILD_GUI_DEPS ON)
set(ARCH "x86-64")
set(BUILD_64 ON)
@@ -180,8 +181,8 @@ if("$ENV{DRONE}" STREQUAL "true")
message(STATUS "We are inside a static compile with Drone CI")
endif()
# To build Feather with embedded (and static) Tor, pass CMake -DTOR_BIN=/path/to/tor
if(TOR_BIN)
# To build Feather with embedded (and static) Tor, pass CMake -DTOR=/path/to/tor
if(TOR)
if(APPLE)
execute_process(COMMAND bash -c "touch ${CMAKE_CURRENT_SOURCE_DIR}/src/tor/libevent-2.1.7.dylib")
endif()
@@ -190,16 +191,34 @@ if(TOR_BIN)
# - linux: See `Dockerfile`
# - windows: https://github.com/mxe/mxe/blob/1024dc7d2db5eb7d5d3c64a2c12b5f592572f1ce/plugins/apps/tor.mk
# - macos: taken from Tor Browser official release
set(TOR_COPY_CMD "cp -u ${TOR_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/exec/tor")
set(TOR_COPY_CMD "cp ${TOR} ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/exec/tor")
message(STATUS "${TOR_COPY_CMD}")
execute_process(COMMAND bash -c "${TOR_COPY_CMD}" RESULT_VARIABLE ret)
if(ret EQUAL "1")
message(FATAL_ERROR "Tor copy failure: ${TOR_COPY_CMD}")
endif()
message(STATUS "Embedding Tor binary at ${TOR_BIN}")
message(STATUS "Embedding Tor binary at ${TOR}")
else()
message(STATUS "Skipping Tor inclusion because -DTOR_BIN=Off")
message(STATUS "Skipping Tor inclusion because -DTOR=Off")
endif()
# To build Feather with embedded (and static) XMRig, pass CMake -DXMRIG=/path/to/xmrig
if(XMRIG)
# on the buildbot XMRig is baked into the image
# - linux: See `Dockerfile`
# - windows: See `Dockerfile_windows`
# - macos: manually downloaded an official release
set(XMRIG_COPY_CMD "cp ${XMRIG} ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/exec/xmrig")
message(STATUS "${XMRIG_COPY_CMD}")
execute_process(COMMAND bash -c "${XMRIG_COPY_CMD}" RESULT_VARIABLE ret)
if(ret EQUAL "1")
message(FATAL_ERROR "XMRig copy failure: ${XMRIG_COPY_CMD}")
endif()
message(STATUS "Embedding XMRig binary at ${XMRIG}")
else()
message(STATUS "Skipping XMRig inclusion because -DXMRIG=Off")
endif()
if(MINGW)
+138 -173
View File
@@ -1,214 +1,188 @@
FROM ubuntu:16.04
FROM ubuntu:18.04
ARG THREADS=1
ARG QT_VERSION=5.15.2
ENV CFLAGS="-fPIC"
ENV CPPFLAGS="-fPIC"
ENV CXXFLAGS="-fPIC"
ENV SOURCE_DATE_EPOCH=1397818193
RUN apt clean && apt update
RUN apt install -y gnupg
RUN apt-get update
RUN apt-get install -y nano vim ccache software-properties-common
COPY utils/pubkeys/kitware.asc /kitware.asc
RUN cat /kitware.asc | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null
RUN apt install -y automake git pkg-config python wget python3.6-distutils software-properties-common
RUN apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' && apt update
RUN add-apt-repository ppa:git-core/ppa
RUN apt-get update
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Europe/Amsterdam
RUN apt install -y build-essential nano vim aptitude ccache libusb-1.0-0-dev tzdata
RUN apt install -y automake git pkg-config python xutils-dev && \
RUN apt install -y xutils-dev && \
git clone -b xorgproto-2020.1 --depth 1 https://gitlab.freedesktop.org/xorg/proto/xorgproto && \
cd xorgproto && \
git reset --hard c62e8203402cafafa5ba0357b6d1c019156c9f36 && \
./autogen.sh && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b 1.12 --depth 1 https://gitlab.freedesktop.org/xorg/proto/xcbproto && \
RUN git clone -b xcb-proto-1.13 --depth 1 https://gitlab.freedesktop.org/xorg/proto/xcbproto && \
cd xcbproto && \
git reset --hard 6398e42131eedddde0d98759067dde933191f049 && \
./autogen.sh && \
git reset --hard 94228cde97d9aecfda04a8e699d462ba2b89e3a0 && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN apt install -y libtool-bin && \
git clone -b libXau-1.0.9 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxau && \
cd libxau && \
git reset --hard d9443b2c57b512cfb250b35707378654d86c7dea && \
./autogen.sh --enable-shared --disable-static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN apt install -y libpthread-stubs0-dev && \
git clone -b 1.12 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxcb && \
git clone -b 1.13.1 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxcb && \
cd libxcb && \
git reset --hard d34785a34f28fa6a00f8ce00d87e3132ff0f6467 && \
./autogen.sh --enable-shared --disable-static && \
git reset --hard 8287ebd7b752c33b0cabc4982606fe4831106f7e && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
make -j$THREADS clean && \
rm /usr/local/lib/libxcb-xinerama.so && \
./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
cp src/.libs/libxcb-xinerama.a /usr/local/lib/ && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b 0.4.0 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxcb-util && \
RUN git clone -b 0.4.0 --depth 1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-util.git && \
cd libxcb-util && \
git reset --hard acf790d7752f36e450d476ad79807d4012ec863b && \
git submodule init && \
git clone --depth 1 https://gitlab.freedesktop.org/xorg/util/xcb-util-m4 m4 && \
git -C m4 reset --hard f662e3a93ebdec3d1c9374382dcc070093a42fed && \
./autogen.sh --enable-shared --disable-static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b 0.4.0 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxcb-image && \
RUN git clone -b xtrans-1.3.5 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxtrans.git && \
cd libxtrans && \
git reset --hard 7cbad9fe2e61cd9d5caeaf361826a6f4bd320f03 && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install
RUN git clone -b 0.4.0 --depth 1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-image.git && \
cd libxcb-image && \
git reset --hard d882052fb2ce439c6483fce944ba8f16f7294639 && \
git submodule init && \
git clone --depth 1 https://gitlab.freedesktop.org/xorg/util/xcb-util-m4 m4 && \
git -C m4 reset --hard f662e3a93ebdec3d1c9374382dcc070093a42fed && \
./autogen.sh --enable-shared --disable-static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b 0.4.0 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxcb-keysyms && \
RUN git clone -b 0.4.0 --depth 1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-keysyms.git && \
cd libxcb-keysyms && \
git reset --hard 0e51ee5570a6a80bdf98770b975dfe8a57f4eeb1 && \
git submodule init && \
git clone --depth 1 https://gitlab.freedesktop.org/xorg/util/xcb-util-m4 m4 && \
git -C m4 reset --hard f662e3a93ebdec3d1c9374382dcc070093a42fed && \
./autogen.sh --enable-shared --disable-static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b 0.3.9 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxcb-render-util && \
RUN git clone -b 0.3.9 --depth 1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-render-util.git && \
cd libxcb-render-util && \
git reset --hard 0317caf63de532fd7a0493ed6afa871a67253747 && \
git submodule init && \
git clone --depth 1 https://gitlab.freedesktop.org/xorg/util/xcb-util-m4 m4 && \
git -C m4 reset --hard f662e3a93ebdec3d1c9374382dcc070093a42fed && \
./autogen.sh --enable-shared --disable-static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b 0.4.1 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxcb-wm && \
RUN git clone -b 0.4.1 --depth 1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-wm.git && \
cd libxcb-wm && \
git reset --hard 24eb17df2e1245885e72c9d4bbb0a0f69f0700f2 && \
git submodule init && \
git clone --depth 1 https://gitlab.freedesktop.org/xorg/util/xcb-util-m4 m4 && \
git -C m4 reset --hard f662e3a93ebdec3d1c9374382dcc070093a42fed && \
./autogen.sh --enable-shared --disable-static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN apt install -y bison && \
git clone -b xkbcommon-0.5.0 --depth 1 https://github.com/xkbcommon/libxkbcommon && \
cd libxkbcommon && \
git reset --hard c43c3c866eb9d52cd8f61e75cbef1c30d07f3a28 && \
./autogen.sh --prefix=/usr --enable-shared --disable-static --enable-x11 --disable-docs && \
RUN git clone -b libX11-1.6.9 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libx11 && \
cd libx11 && \
git reset --hard db7cca17ad7807e92a928da9d4c68a00f4836da2 && \
ACLOCAL='aclocal -I /usr/local/share/aclocal/' CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b libXext-1.3.4 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxext && \
cd libxext && \
git reset --hard ebb167f34a3514783966775fb12573c4ed209625 && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install
RUN apt install -y libpthread-stubs0-dev && \
git clone -b libXinerama-1.1.4 --depth 1 https://gitlab.freedesktop.org/xorg/lib/libxinerama.git && \
cd libxinerama && \
git reset --hard c3ab2361f13154921df2992f9eacc1ea1b3f946b && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install
RUN git clone -b v1.2.11 --depth 1 https://github.com/madler/zlib && \
cd zlib && \
git reset --hard cacf7f1d4e3d44d871b605da3b647f07d718623f && \
./configure --static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --static && \
make -j$THREADS && \
make -j$THREADS install && \
./configure --static --prefix=/usr/local/zlib && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --static --prefix=/usr/local/zlib && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b VER-2-10-2 --depth 1 https://git.savannah.gnu.org/git/freetype/freetype2.git && \
RUN git clone -b VER-2-10-2 https://git.sv.nongnu.org/r/freetype/freetype2.git && \
cd freetype2 && \
git reset --hard 132f19b779828b194b3fede187cee719785db4d8 && \
./autogen.sh && \
./configure --disable-shared --enable-static --with-zlib=no && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --disable-shared --enable-static --with-zlib=no && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b R_2_2_9 --depth 1 https://github.com/libexpat/libexpat && \
cd libexpat/expat && \
git reset --hard a7bc26b69768f7fb24f0c7976fae24b157b85b13 && \
./buildconf.sh && \
./configure --disable-shared --enable-static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN apt install -y autopoint gettext gperf libpng12-dev && \
RUN apt install -y autopoint gettext gperf libpng-dev && \
git clone -b 2.13.92 --depth 1 https://gitlab.freedesktop.org/fontconfig/fontconfig && \
cd fontconfig && \
git reset --hard b1df1101a643ae16cdfa1d83b939de2497b1bf27 && \
./autogen.sh --disable-shared --enable-static --sysconfdir=/etc --localstatedir=/var && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static --sysconfdir=/etc --localstatedir=/var && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b release-64-2 --depth 1 https://github.com/unicode-org/icu && \
cd icu/icu4c/source && \
git reset --hard e2d85306162d3a0691b070b4f0a73e4012433444 && \
./configure --disable-shared --enable-static --disable-tests --disable-samples && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --disable-shared --enable-static --disable-tests --disable-samples && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN apt install -y wget && \
wget https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.tar.gz && \
echo "9995e192e68528793755692917f9eb6422f3052a53c5e13ba278a228af6c7acf boost_1_73_0.tar.gz" | sha256sum -c && \
tar -xzf boost_1_73_0.tar.gz && \
rm boost_1_73_0.tar.gz && \
echo "9995e192e68528793755692917f9eb6422f3052a53c5e13ba278a228af6c7acf boost_1_73_0.tar.gz" > hashsum.txt && \
sha256sum -c hashsum.txt && \
tar -xvzf boost_1_73_0.tar.gz && \
cd boost_1_73_0 && \
./bootstrap.sh && \
./b2 --with-atomic --with-system --with-filesystem --with-thread --with-date_time --with-chrono --with-regex --with-serialization --with-program_options --with-locale variant=release link=static runtime-link=static cflags="${CFLAGS}" cxxflags="${CXXFLAGS}" install -a --prefix=/usr && \
rm -rf $(pwd)
./b2 --with-atomic --with-system --with-filesystem --with-thread --with-date_time --with-chrono --with-regex --with-serialization --with-program_options --with-locale variant=release link=static runtime-link=static cflags='-fPIC' cxxflags='-fPIC' install -a --prefix=/usr
RUN wget https://www.openssl.org/source/openssl-1.1.1i.tar.gz && \
echo "e8be6a35fe41d10603c3cc635e93289ed00bf34b79671a3a4de64fcee00d5242 openssl-1.1.1i.tar.gz" | sha256sum -c && \
tar -xzf openssl-1.1.1i.tar.gz && \
rm openssl-1.1.1i.tar.gz && \
cd openssl-1.1.1i && \
./config no-shared no-zlib-dynamic --prefix=/usr/local/openssl && \
RUN wget https://www.openssl.org/source/openssl-1.1.1g.tar.gz && \
echo "ddb04774f1e32f0c49751e21b67216ac87852ceb056b75209af2443400636d46 openssl-1.1.1g.tar.gz" > hashsum.txt && \
sha256sum -c hashsum.txt && \
tar -xzf openssl-1.1.1g.tar.gz && \
cd openssl-1.1.1g && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./config no-shared no-zlib-dynamic --prefix=/usr/local/openssl && \
make -j$THREADS && \
make -j$THREADS install && \
./config no-shared no-zlib-dynamic --openssldir=/usr && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN apt install -y libgl1-mesa-dev libglib2.0-dev mesa-common-dev && \
rm /usr/lib/x86_64-linux-gnu/libX11.a && \
rm /usr/lib/x86_64-linux-gnu/libXext.a && \
rm /usr/lib/x86_64-linux-gnu/libX11-xcb.a && \
git clone git://code.qt.io/qt/qt5.git -b ${QT_VERSION} --depth 1 && \
cd qt5 && \
git clone git://code.qt.io/qt/qtbase.git -b ${QT_VERSION} --depth 1 && \
git clone git://code.qt.io/qt/qtimageformats.git -b ${QT_VERSION} --depth 1 && \
git clone git://code.qt.io/qt/qtmultimedia.git -b ${QT_VERSION} --depth 1 && \
git clone git://code.qt.io/qt/qtsvg.git -b ${QT_VERSION} --depth 1 && \
git clone git://code.qt.io/qt/qttools.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/qtx11extras.git -b ${QT_VERSION} --depth 1 && \
git clone git://code.qt.io/qt/qtwebsockets.git -b ${QT_VERSION} --depth 1 && \
RUN wget https://download.qt.io/archive/qt/5.15/5.15.0/single/qt-everywhere-src-5.15.0.tar.xz && \
echo "22b63d7a7a45183865cc4141124f12b673e7a17b1fe2b91e433f6547c5d548c3 qt-everywhere-src-5.15.0.tar.xz" > hashsum.txt && \
sha256sum -c hashsum.txt && \
tar -xf qt-everywhere-src-5.15.0.tar.xz
COPY contrib/Qt5.15_LinuxPatch.json /qt-everywhere-src-5.15.0/qtbase/src/gui/configure.json
RUN apt install -y libgl1-mesa-dev libglib2.0-dev libxkbcommon-dev libxkbcommon-x11-dev
RUN cd /qt-everywhere-src-5.15.0 && \
sed -ri s/\(Libs:.*\)/\\1\ -lexpat/ /usr/local/lib/pkgconfig/fontconfig.pc && \
sed -ri s/\(Libs:.*\)/\\1\ -lz/ /usr/local/lib/pkgconfig/freetype2.pc && \
sed -ri s/\(Libs:.*\)/\\1\ -lXau/ /usr/local/lib/pkgconfig/xcb.pc && \
sed -i s/\\/usr\\/X11R6\\/lib64/\\/usr\\/local\\/lib/ qtbase/mkspecs/linux-g++-64/qmake.conf && \
OPENSSL_LIBS="-lssl -lcrypto -lpthread -ldl" \
./configure --prefix=/usr -platform linux-g++-64 -opensource -confirm-license -release -static -no-avx \
-no-opengl -qpa xcb --xcb -xcb-xlib -feature-xlib -openssl-linked -I /usr/local/openssl/include \
-L /usr/local/openssl/lib -system-freetype -fontconfig -glib \
-no-opengl -qpa xcb -openssl-linked -I /usr/local/openssl/include -L /usr/local/openssl/lib -system-freetype -fontconfig -glib \
-no-dbus -no-sql-sqlite -no-use-gold-linker -no-kms \
-qt-harfbuzz -qt-libjpeg -qt-libpng -qt-pcre -qt-zlib \
-skip qt3d -skip qtandroidextras -skip qtcanvas3d -skip qtcharts -skip qtconnectivity -skip qtdatavis3d \
@@ -219,101 +193,82 @@ RUN apt install -y libgl1-mesa-dev libglib2.0-dev mesa-common-dev && \
-skip qtwinextras -skip qtx11extras -skip gamepad -skip serialbus -skip location -skip webengine \
-skip qtdeclarative \
-no-feature-cups -no-feature-ftp -no-feature-pdf -no-feature-animation \
-nomake examples -nomake tests -nomake tools && \
-nomake examples -nomake tests -nomake tools
RUN cd /qt-everywhere-src-5.15.0 && \
make -j$THREADS && \
make -j$THREADS install && \
cd qttools/src/linguist/lrelease && \
../../../../qtbase/bin/qmake && \
make -j$THREADS install
RUN cd qt-everywhere-src-5.15.0/qttools/src/linguist/lrelease && \
qmake && \
make -j$THREADS && \
make -j$THREADS install && \
cd ../../../.. && \
rm -rf $(pwd)
make -j$THREADS install
RUN apt install -y libudev-dev && \
git clone -b v1.0.23 --depth 1 https://github.com/libusb/libusb && \
cd libusb && \
git reset --hard e782eeb2514266f6738e242cdcb18e3ae1ed06fa && \
./autogen.sh --disable-shared --enable-static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./autogen.sh --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b hidapi-0.9.0 --depth 1 https://github.com/libusb/hidapi && \
cd hidapi && \
git reset --hard 7da5cc91fc0d2dbe4df4f08cd31f6ca1a262418f && \
./bootstrap && \
./configure --disable-shared --enable-static && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --disable-shared --enable-static && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN apt install -y libsodium-dev && \
git clone -b v4.3.2 --depth 1 https://github.com/zeromq/libzmq && \
cd libzmq && \
git reset --hard a84ffa12b2eb3569ced199660bac5ad128bff1f0 && \
./autogen.sh && \
./configure --disable-shared --enable-static --disable-libunwind --with-libsodium && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --disable-shared --enable-static --disable-libunwind --with-libsodium && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b libgpg-error-1.38 --depth 1 git://git.gnupg.org/libgpg-error.git && \
cd libgpg-error && \
git reset --hard 71d278824c5fe61865f7927a2ed1aa3115f9e439 && \
./autogen.sh && \
./configure --disable-shared --enable-static --disable-doc --disable-tests && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --disable-shared --enable-static --disable-doc --disable-tests && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b libgcrypt-1.8.5 --depth 1 git://git.gnupg.org/libgcrypt.git && \
cd libgcrypt && \
git reset --hard 56606331bc2a80536db9fc11ad53695126007298 && \
./autogen.sh && \
./configure --disable-shared --enable-static --disable-doc && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --disable-shared --enable-static --disable-doc && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b v3.10.0 --depth 1 https://github.com/protocolbuffers/protobuf && \
cd protobuf && \
git reset --hard 6d4e7fd7966c989e38024a8ea693db83758944f1 && \
./autogen.sh && \
./configure --enable-static --disable-shared && \
CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --enable-static --disable-shared && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b v3.18.4 --depth 1 https://github.com/Kitware/CMake && \
cd CMake && \
git reset --hard 3cc3d42aba879fff5e85b363ae8f21386a3f9f9b && \
./bootstrap && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
RUN apt install -y libusb-1.0-0-dev
RUN apt install -y zip && git clone -b v4.0.2 --depth 1 https://github.com/fukuchi/libqrencode.git && \
RUN apt install -y zip cmake && git clone -b v4.0.2 --depth 1 https://github.com/fukuchi/libqrencode.git && \
cd libqrencode && \
git reset --hard 59ee597f913fcfda7a010a6e106fbee2595f68e4 && \
cmake -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=/usr . && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN apt install -y libmbedtls-dev && git clone -b release-2.1.12-stable --depth 1 https://github.com/libevent/libevent.git && \
cd libevent && \
git reset --hard 5df3037d10556bfcb675bc73e516978b75fc7bc7 && \
mkdir build && cd build && \
cmake -DEVENT_LIBRARY_STATIC=ON -DOPENSSL_ROOT_DIR=/usr/local/openssl -DCMAKE_INSTALL_PREFIX=/usr/local/libevent .. && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone -b tor-0.4.4.6 --depth 1 https://git.torproject.org/tor.git && \
RUN git clone -b tor-0.4.3.5 --depth 1 https://git.torproject.org/tor.git && \
cd tor && \
git reset --hard 2a8b789ea6f308d081f369d78fa7cfdc9d00bf90 && \
bash autogen.sh && \
LDFLAGS="-L/usr/local/openssl/lib/" LIBS="-lssl -lcrypto -lpthread -ldl" CPPFLAGS="-I/usr/local/openssl/include/" ./configure \
--enable-static-zlib \
@@ -336,13 +291,23 @@ RUN git clone -b tor-0.4.4.6 --depth 1 https://git.torproject.org/tor.git && \
--disable-system-torrc \
--prefix=/usr/local/tor && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)
make -j$THREADS install
RUN git clone https://git.torproject.org/torsocks.git && \
cd torsocks && \
bash autogen.sh && \
./configure --prefix=/usr/local/torsocks && \
make -j$THREADS && \
make -j$THREADS install
RUN git clone https://git.wownero.com/feather/monero-seed.git && \
cd monero-seed && \
git reset --hard 4674ef09b6faa6fe602ab5ae0b9ca8e1fd7d5e1b && \
cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && \
make -Cbuild -j$THREADS && \
make -Cbuild install && \
rm -rf $(pwd)
make -Cbuild install
RUN apt install -y curl && \
curl -LO "https://github.com/xmrig/xmrig/releases/download/v6.3.5/xmrig-6.3.5-linux-static-x64.tar.gz" && \
echo "24d4f07cf5850f00ab513b228f95769a5a5ed68d35808d98f9959b58d97985a0 xmrig-6.3.5-linux-static-x64.tar.gz" > hashsum.txt && \
sha256sum -c hashsum.txt && \
tar xvf xmrig-6.3.5-linux-static-x64.tar.gz --one-top-level=/xmrig --strip 1
+2 -2
View File
@@ -3,7 +3,7 @@ FROM ubuntu:20.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt clean && apt update
RUN apt install -y golang git build-essential wget curl ngrep unzip file squashfs-tools desktop-file-utils patchelf libxkbcommon-x11-dev libx11-xcb-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render0 libxcb-render-util0 libxcb-shape0 libxcb-sync1 libxcb-xfixes0 libxcb-shm0 libxcb-keysyms1 libxcb-xkb1
RUN apt install -y golang git build-essential wget curl ngrep unzip file squashfs-tools desktop-file-utils patchelf libxkbcommon-x11-dev
RUN go get github.com/probonopd/go-appimage/src/appimagetool
RUN go build -trimpath -ldflags="-s -w" github.com/probonopd/go-appimage/src/appimagetool
@@ -15,4 +15,4 @@ RUN cd /usr/bin && \
RUN cd / && \
wget -c https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-x86_64 && \
chmod +x runtime-x86_64
chmod +x runtime-x86_64
+7
View File
@@ -72,3 +72,10 @@ RUN git clone https://git.wownero.com/feather/monero-seed.git && \
cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && \
make -Cbuild -j$THREADS && \
make -Cbuild install
RUN apt install -y curl && \
curl -LO "https://github.com/xmrig/xmrig/releases/download/v6.3.5/xmrig-6.3.5-gcc-win64.zip" && \
echo "e45915ada7e6e30f6ab40abf33831056449d5914307d7706bb0ad439b6d64c12 xmrig-6.3.5-gcc-win64.zip" > hashsum.txt && \
sha256sum -c hashsum.txt && \
unzip -q xmrig-6.3.5-gcc-win64.zip -d /xmrig && \
mv /xmrig/xmrig-6.3.5/* /xmrig/
+3 -2
View File
@@ -42,8 +42,9 @@ via the `CMAKE_PREFIX_PATH` definition. For me this is:
There are some Monero/Feather related options/definitions that you may pass:
- `-DXMRTO=OFF` - disable Xmr.To feature
- `-DXMRIG=OFF` - disable XMRig feature
- `-DTOR_BIN=/path/to/tor` - Embed a Tor executable inside Feather
- `-DMORPHTOKEN=OFF` - diable MorphToken feature
- `-DTOR=/path/to/tor` - Embed a Tor executable inside Feather
- `-DXMRIG=/path/to/xmrig` - Embed a XMRig executable inside Feather
- `-DDONATE_BEG=OFF` - disable the dreaded donate requests
And:
+12 -7
View File
@@ -30,9 +30,10 @@ CMAKEFLAGS = \
-DARCH=x86_64 \
-DBUILD_64=On \
-DBUILD_TESTS=Off \
-DXMRTO=On \
-DXMRIG=On \
-DTOR_BIN=Off \
-DXMRTO=ON \
-DMORPHTOKEN=ON \
-DXMRIG=Off \
-DTOR=Off \
-DCMAKE_CXX_STANDARD=11 \
-DCMAKE_VERBOSE_MAKEFILE=On \
-DINSTALL_VENDORED_LIBUNBOUND=Off \
@@ -42,28 +43,32 @@ CMAKEFLAGS = \
$(CMAKEFLAGS_EXTRA)
release-static: CMAKEFLAGS += -DBUILD_TAG="linux-x64"
release-static: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF)
release-static: CMAKEFLAGS += -DTOR=$(or ${TOR},OFF)
release-static: CMAKEFLAGS += -DXMRIG=$(or ${XMRIG},OFF)
release-static: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
release-static:
cmake -Bbuild $(CMAKEFLAGS)
$(MAKE) -Cbuild
windows-mxe-release: CMAKEFLAGS += -DBUILD_TAG="win-x64"
windows-mxe-release: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF)
windows-mxe-release: CMAKEFLAGS += -DTOR=$(or ${TOR},OFF)
windows-mxe-release: CMAKEFLAGS += -DXMRIG=$(or ${XMRIG},OFF)
windows-mxe-release: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
windows-mxe-release:
cmake -Bbuild $(CMAKEFLAGS)
$(MAKE) -Cbuild
windows-mxe-debug: CMAKEFLAGS += -DBUILD_TAG="win-x64"
windows-mxe-debug: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF)
windows-mxe-debug: CMAKEFLAGS += -DTOR=$(or ${TOR},OFF)
windows-mxe-debug: CMAKEFLAGS += -DXMRIG=$(or ${XMRIG},OFF)
windows-mxe-debug: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Debug
windows-mxe-debug:
cmake -Bbuild $(CMAKEFLAGS)
$(MAKE) -Cbuild
mac-release: CMAKEFLAGS += -DSTATIC=Off
mac-release: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF)
mac-release: CMAKEFLAGS += -DTOR=$(or ${TOR},OFF)
mac-release: CMAKEFLAGS += -DXMRIG=$(or ${XMRIG},OFF)
mac-release: CMAKEFLAGS += -DBUILD_TAG="mac-x64"
mac-release: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
mac-release:
+1 -1
View File
@@ -2,7 +2,7 @@
[![Build Status](https://build.featherwallet.org/api/badges/feather/feather/status.svg)](https://build.featherwallet.org/feather/feather)
Feather is a free, open-source Monero client Linux with ports for Mac OS and Windows written in C++ with the Qt framework. It is created and maintained by [dsc](dsc@xmr.pm) and [tobtoht](thotbot@protonmail.com).
Feather is a free, open-source Monero client Linux with ports for Mac OS and Windows written in C++ with the Qt framework. It is created and maintained by [dsc](dsc@xmr.pm) and [tobtoht](thotbot@protonmail.com).
## Development resources
* Web: [featherwallet.org](https://featherwallet.org)
+22
View File
@@ -2,11 +2,33 @@
set -e
function verify_hash() {
local file=$1 expected_hash=$2
actual_hash=$(sha256sum $file | awk '{print $1}')
if [ "$actual_hash" == "$expected_hash" ]; then
return 0
else
echo "$file $actual_hash (unexpected hash)" >&2
rm "$file"
exit 1
fi
}
function download_if_not_exist() {
local file_name=$1 url=$2
if [ ! -e $file_name ] ; then
wget -q -O $file_name "$url"
fi
}
APPDIR="$PWD/feather.AppDir"
mkdir -p "$APPDIR"
mkdir -p "$APPDIR/usr/share/applications/"
mkdir -p "$APPDIR/usr/bin"
echo "Downloading dependencies"
download_if_not_exist "feather.zip" "https://build.featherwallet.org/files/linux-release/$BRANCH/$FN"
unzip -q feather.zip
cp "$PWD/src/assets/feather.desktop" "$APPDIR/usr/share/applications/feather.desktop"
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
# this file is used by feather's CMake
# arguments: ./build.tor $TAG $ROOT_FEATHER_DIR
set -ex
ERR_WIN="This script does not work on Windows"
if [[ "$OSTYPE" == "msys" ]]; then
echo "$ERR_WIN"
exit 1
elif [[ "$OSTYPE" == "win32" ]]; then
echo "$ERR_WIN"
exit 1
fi
TOR_TAG="$1"
ROOT_DIR="$2"
STATIC="$3"
TOR_DIR="$ROOT_DIR/contrib/tor"
TORSOCKS_DIR="$ROOT_DIR/contrib/torsocks"
TARGET_DIR="$ROOT_DIR/src/tor"
CPU_CORE_COUNT="$(nproc)"
#
### tor
#
pushd "$TOR_DIR"
rm -rf "$TOR_DIR/build"
mkdir -p "$TOR_DIR/build"
# configure
git -C "$TOR_DIR" fetch
git -C "$TOR_DIR" checkout tor-0.4.3.5
bash "$TOR_DIR/autogen.sh"
if [[ "$STATIC" = "ON" ]]; then
# static assumes that openssl has been compiled with:
# CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./config no-asm no-shared no-zlib-dynamic --prefix=/usr/local/openssl --openssldir=/usr/local/openssl
# and libevent with:
# cmake -DEVENT_LIBRARY_STATIC=ON -DOPENSSL_ROOT_DIR=/usr/local/openssl -DCMAKE_INSTALL_PREFIX=/usr/local/libevent
# and zlib with:
# CFLAGS='-fPIC' CXXFLAGS='-fPIC' ./configure --static --prefix=/usr/local/zlib
LDFLAGS="-L/usr/local/openssl/lib/" LIBS="-lssl -lcrypto -lpthread -ldl" CPPFLAGS="-I/usr/local/openssl/include/" ./configure \
--enable-static-zlib \
--enable-static-openssl \
--enable-static-libevent \
--disable-system-torrc \
--with-libevent-dir=/usr/local/libevent \
--with-openssl-dir=/usr/local/openssl/ \
--with-zlib-dir=/usr/local/zlib \
--disable-system-torrc \
--disable-tool-name-check \
--disable-systemd \
--disable-lzma \
--disable-unittests \
--disable-zstd \
--disable-seccomp \
--disable-asciidoc \
--disable-manpage \
--disable-html-manual \
--disable-system-torrc \
--prefix="$TOR_DIR/build"
else
bash "$TOR_DIR/configure" \
--disable-tool-name-check \
--disable-systemd \
--disable-lzma \
--disable-unittests \
--disable-zstd \
--disable-asciidoc \
--disable-manpage \
--disable-html-manual \
--prefix="$TOR_DIR/build"
fi
# build
make -j "$CPU_CORE_COUNT"
make install -j "$CPU_CORE_COUNT"
# copy to lib/tor
cp "$TOR_DIR/build/bin/tor" "$TARGET_DIR"
cp "$TOR_DIR/build/etc/tor/torrc.sample"* "$TARGET_DIR"
#
### torsocks
#
pushd "$TORSOCKS_DIR"
mkdir -p "$TORSOCKS_DIR/build"
# configure
bash "$TORSOCKS_DIR/autogen.sh"
bash "$TORSOCKS_DIR/configure" --prefix="$TORSOCKS_DIR/build"
# build
make -j "$CPU_CORE_COUNT"
make install -j "$CPU_CORE_COUNT"
# copy to lib/torsocks
cp "$TORSOCKS_DIR/build/lib/torsocks/"* "$TARGET_DIR"
cp "$TORSOCKS_DIR/build/bin/"* "$TARGET_DIR"
cp "$TORSOCKS_DIR/build/etc/tor/"* "$TARGET_DIR"
#
### verify installation
#
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
for fn in "$TARGET_DIR/libtorsocks.so" "$TARGET_DIR/tor"; do
if [[ ! -f "$fn" ]]; then
echo "[*] Failed to install tor or torsocks: no such file $fn"
exit 1
fi; done
elif [[ "$OSTYPE" == "darwin"* ]]; then
for fn in "$TARGET_DIR/libtorsocks.dylib" "$TARGET_DIR/tor"; do
if [[ ! -f "$fn" ]]; then
echo "[*] Failed to install tor or torsocks: no such file $fn"
exit 1
fi; done
fi
echo "[*] Compiled tor/torsocks into $TARGET_DIR"
+1
Submodule contrib/torsocks added at 4c00ec8773
+53
View File
@@ -0,0 +1,53 @@
From fc5eafeb2886605d4de1546846f06a12a18c87ef Mon Sep 17 00:00:00 2001
From: "J.W" <jakwings@gmail.com>
Date: Mon, 22 Apr 2019 05:19:32 +0100
Subject: [PATCH 1/2] Fix macros for accept4(2)
Both accept(2) and accept4(2) exist on linux but accept4(2) does not
exist on macos 10.11.6 (and maybe other distros).
---
src/lib/torsocks.c | 9 ++++++++-
src/lib/torsocks.h | 4 +++-
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/lib/torsocks.c b/src/lib/torsocks.c
index 16f2da0..9527513 100644
--- a/src/lib/torsocks.c
+++ b/src/lib/torsocks.c
@@ -234,9 +234,16 @@ static void init_libc_symbols(void)
tsocks_libc_socket = dlsym(libc_ptr, LIBC_SOCKET_NAME_STR);
tsocks_libc_syscall = dlsym(libc_ptr, LIBC_SYSCALL_NAME_STR);
tsocks_libc_execve = dlsym(libc_ptr, LIBC_EXECVE_NAME_STR);
+ tsocks_libc_accept = dlsym(libc_ptr, LIBC_ACCEPT_NAME_STR);
+#if (defined(__linux__))
tsocks_libc_accept4 = dlsym(libc_ptr, LIBC_ACCEPT4_NAME_STR);
+#endif
+
if (!tsocks_libc_connect || !tsocks_libc_close || !tsocks_libc_socket ||
- !tsocks_libc_syscall || !tsocks_libc_execve || ! tsocks_libc_accept4) {
+#if (defined(__linux__))
+ !tsocks_libc_accept4 ||
+#endif
+ !tsocks_libc_syscall || !tsocks_libc_execve || ! tsocks_libc_accept) {
ERR("Unable to lookup symbols in " LIBC_NAME "(%s)", dlerror());
goto error;
}
diff --git a/src/lib/torsocks.h b/src/lib/torsocks.h
index 33da526..bf9109d 100644
--- a/src/lib/torsocks.h
+++ b/src/lib/torsocks.h
@@ -30,8 +30,10 @@
* libc call outside of torsocks can be used. These are declared for each
* symbol torsocks hijacked.
*/
+#define TSOCKS_LIBC_FUNC(name) \
+ tsocks_libc_##name
#define TSOCKS_LIBC_DECL(name, type, sig) \
- type (*tsocks_libc_##name)(sig);
+ type (*TSOCKS_LIBC_FUNC(name))(sig);
#define TSOCKS_DECL(name, type, sig) \
extern type tsocks_##name(sig);
--
2.21.0
+1 -1
Submodule monero updated: 85b0b4f73a...2c41634c13
+14 -5
View File
@@ -42,7 +42,7 @@ file(GLOB SOURCE_FILES
"dialog/*.cpp"
)
if(TOR_BIN)
if(TOR)
if(APPLE)
set(ASSETS_TOR "assets_tor_macos.qrc")
else()
@@ -50,6 +50,10 @@ if(TOR_BIN)
endif()
endif()
if(XMRIG)
set(ASSETS_XMRIG "assets_mining.qrc")
endif()
set(EXECUTABLE_FLAG)
if(MINGW)
set(EXECUTABLE_FLAG WIN32)
@@ -73,7 +77,8 @@ add_executable(feather ${EXECUTABLE_FLAG} main.cpp
${SOURCE_FILES}
${RESOURCES}
${ASSETS_TOR}
)
${ASSETS_XMRIG}
)
# mac os bundle
set_target_properties(feather PROPERTIES
@@ -121,11 +126,15 @@ if(DONATE_BEG)
endif()
if(XMRTO)
target_compile_definitions(feather PRIVATE HAS_XMRTO=1)
target_compile_definitions(feather PRIVATE XMRTO=1)
endif()
if(TOR_BIN)
target_compile_definitions(feather PRIVATE HAS_TOR_BIN=1)
if(MORPHTOKEN)
target_compile_definitions(feather PRIVATE HAS_MORPHTOKEN=1)
endif()
if(TOR)
target_compile_definitions(feather PRIVATE HAS_TOR=1)
endif()
if(XMRIG)
+226
View File
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "MorphTokenWidget.h"
#include "ui_MorphTokenWidget.h"
#include "mainwindow.h"
#include "qrcode/QrCode.h"
#include "dialog/qrcodedialog.h"
#include <QMessageBox>
MorphTokenWidget::MorphTokenWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MorphTokenWidget)
{
ui->setupUi(this);
m_ctx = MainWindow::getContext();
m_network = new UtilsNetworking(this->m_ctx->network);
m_api = new MorphTokenApi(this, m_network);
connect(ui->btnCreateTrade, &QPushButton::clicked, this, &MorphTokenWidget::createTrade);
connect(ui->btn_lookupTrade, &QPushButton::clicked, this, &MorphTokenWidget::lookupTrade);
connect(ui->btn_getRates, &QPushButton::clicked, this, &MorphTokenWidget::getRates);
connect(m_api, &MorphTokenApi::ApiResponse, this, &MorphTokenWidget::onApiResponse);
connect(ui->combo_From, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
this->displayRate();
ui->label_refundAddress->setText(QString("Refund address (%1):").arg(ui->combo_From->currentText()));
});
connect(ui->combo_To, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
this->displayRate();
ui->label_destinationAddress->setText(QString("Destination address (%1):").arg(ui->combo_To->currentText()));
});
connect(ui->check_autorefresh, &QCheckBox::toggled, [this](bool toggled){
m_countdown = 30;
toggled ? m_countdownTimer.start(1000) : m_countdownTimer.stop();
ui->check_autorefresh->setText("Autorefresh");
});
connect(&m_countdownTimer, &QTimer::timeout, this, &MorphTokenWidget::onCountdown);
connect(ui->line_Id, &QLineEdit::textChanged, [this](const QString &text){
ui->btn_lookupTrade->setEnabled(!text.isEmpty());
ui->check_autorefresh->setEnabled(!text.isEmpty());
});
// Default to BTC -> XMR
ui->combo_From->setCurrentIndex(1);
ui->combo_To->setCurrentIndex(0);
ui->label_rate->setVisible(false);
m_ratesTimer.setSingleShot(true);
connect(&m_ratesTimer, &QTimer::timeout, [this]{
ui->label_rate->setVisible(false);
});
ui->qrCode->setVisible(false);
ui->label_depositAddress->setVisible(false);
connect(ui->qrCode, &ClickableLabel::clicked, this, &MorphTokenWidget::showQrCodeDialog);
ui->tabWidget->setTabVisible(2, false);
}
void MorphTokenWidget::createTrade() {
QString inputAsset = ui->combo_From->currentText();
QString outputAsset = ui->combo_To->currentText();
QString refundAddress = ui->line_refundAddress->text();
QString destinationAddress = ui->line_destinationAddress->text();
m_api->createTrade(inputAsset, outputAsset, refundAddress, destinationAddress);
}
void MorphTokenWidget::lookupTrade() {
QString morphId = ui->line_Id->text();
if (!morphId.isEmpty())
m_api->getTrade(morphId);
}
void MorphTokenWidget::getRates() {
m_api->getRates();
}
void MorphTokenWidget::onApiResponse(const MorphTokenApi::MorphTokenResponse &resp) {
if (!resp.ok) {
ui->check_autorefresh->setChecked(false);
QMessageBox::warning(this, "MorphToken error", QString("Request failed:\n\n%1").arg(resp.message));
return;
}
ui->debugInfo->setPlainText(QJsonDocument(resp.obj).toJson(QJsonDocument::Indented));
bool shouldShowQr = (resp.endpoint == MorphTokenApi::Endpoint::CREATE_TRADE || resp.endpoint == MorphTokenApi::Endpoint::GET_TRADE);
ui->qrCode->setVisible(shouldShowQr);
ui->label_depositAddress->setVisible(shouldShowQr);
if (resp.endpoint == MorphTokenApi::Endpoint::CREATE_TRADE || resp.endpoint == MorphTokenApi::Endpoint::GET_TRADE) {
ui->tabWidget->setCurrentIndex(1);
ui->line_Id->setText(resp.obj.value("id").toString());
auto obj = resp.obj;
auto input = obj["input"].toObject();
auto output = obj["output"].toArray()[0].toObject();
QString state = obj.value("state").toString();
QString statusText;
ui->trade->setTitle(QString("Trade (%1)").arg(state));
statusText += QString("Morph ID: %1\n\n").arg(obj["id"].toString());
if (state == "PENDING") {
statusText += QString("Waiting for a deposit, send %1 to %2\n").arg(input["asset"].toString(),
input["deposit_address"].toString());
statusText += QString("Rate: 1 %1 -> %2 %3\n\n").arg(input["asset"].toString(),
output["seen_rate"].toString(),
output["asset"].toString());
statusText += "Limits:\n";
statusText += QString(" Minimum amount accepted: %1 %2\n").arg(formatAmount(input["asset"].toString(), input["limits"].toObject()["min"].toDouble()),
input["asset"].toString());
statusText += QString(" Maximum amount accepted: %1 %2\n").arg(formatAmount(input["asset"].toString(), input["limits"].toObject()["max"].toDouble()),
input["asset"].toString());
statusText += QString("\nSend a single deposit. If the amount is outside the limits, a refund will happen.");
m_depositAddress = input["deposit_address"].toString();
const QrCode qrc(m_depositAddress, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::MEDIUM);
int width = ui->qrCode->width();
if (qrc.isValid()) {
ui->qrCode->setPixmap(qrc.toPixmap(1).scaled(width, width, Qt::KeepAspectRatio));
}
} else if (state == "PROCESSING" || state == "TRADING" || state == "CONFIRMING") {
if (state == "CONFIRMING") {
statusText += QString("Waiting for confirmations\n");
} else if (state == "TRADING") {
statusText += QString("Your transaction has been received and is confirmed. MorphToken is now executing your trade.\n"
"Usually this step takes no longer than a minute, "
"but in rare cases it can take a couple hours.\n"
"Wait a bit before contacting support.\n");
}
statusText += QString("Converting %1 to %2\n").arg(input["asset"].toString(), output["asset"].toString());
statusText += QString("Sending to %1\n").arg(output["address"].toString());
statusText += QString("Stuck? Contact support at contact@morphtoken.com");
} else if (state == "COMPLETE") {
if (output["txid"].toString().isEmpty()) {
statusText += QString("MorphToken is sending your transaction.\n");
statusText += QString("MorphToken will send %1 %2 to %2").arg(this->formatAmount(output["asset"].toString(), output["converted_amount"].toDouble() - output["network_fee"].toObject()["fee"].toDouble()),
output["asset"].toString(),
output["address"].toString());
} else {
statusText += QString("Sent %1 %2 to %3\ntxid: {}").arg(this->formatAmount(output["asset"].toString(), output["converted_amount"].toDouble() - output["network_fee"].toObject()["fee"].toDouble()),
output["asset"].toString(),
output["address"].toString(),
output["txid"].toString());
}
} else if (state == "PROCESSING_REFUND" || state == "COMPLETE_WITH_REFUND") {
statusText += QString("MorphToken will refund %1 %2\nReason: %3\n").arg(obj["final_amount"].toString(),
obj["asset"].toString(),
obj["reason"].toString());
if (obj.contains("txid")) {
statusText += QString("txid: %1").arg(obj["txid"].toString());
}
} else if (state == "COMPLETE_WITHOUT_REFUND") {
statusText += "Deposit amount below network fee, too small to refund.";
}
ui->label_status->setText(statusText);
} else if (resp.endpoint == MorphTokenApi::Endpoint::GET_RATES) {
m_rates = resp.obj.value("data").toObject();
this->displayRate();
ui->label_rate->setVisible(true);
m_ratesTimer.start(120 * 1000);
}
if (resp.endpoint == MorphTokenApi::Endpoint::CREATE_TRADE) {
QMessageBox::information(this, "MorphToken", "Trade created!\n\nMake sure to save your Morph ID. You may need it in case something goes wrong.");
}
}
void MorphTokenWidget::onCountdown() {
if (m_countdown > 0) {
m_countdown -= 1;
} else {
this->lookupTrade();
m_countdown = 30;
}
ui->check_autorefresh->setText(QString("Autorefresh (%1)").arg(m_countdown));
}
void MorphTokenWidget::displayRate() {
QString inputAsset = ui->combo_From->currentText();
QString outputAsset = ui->combo_To->currentText();
QString outputRate = m_rates.value(inputAsset).toObject().value(outputAsset).toString("1");
QString rateStr = QString("1 %1 -> %2 %3").arg(inputAsset, outputRate, outputAsset);
ui->label_rate->setText(rateStr);
}
void MorphTokenWidget::showQrCodeDialog() {
QrCode qr(m_depositAddress, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::HIGH);
auto *dialog = new QrCodeDialog(this, qr, "Deposit address");
dialog->exec();
dialog->deleteLater();
}
QString MorphTokenWidget::formatAmount(const QString &asset, double amount) {
double displayAmount;
double div;
if (asset == "ETH")
div = 1e18;
else if (asset == "XMR")
div = 1e12;
else
div = 1e8;
displayAmount = amount / div;
return QString::number(displayAmount, 'f', 8);
}
MorphTokenWidget::~MorphTokenWidget() {
delete ui;
}
+47
View File
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_MORPHTOKENWIDGET_H
#define FEATHER_MORPHTOKENWIDGET_H
#include <QWidget>
#include "appcontext.h"
#include "utils/MorphTokenApi.h"
namespace Ui {
class MorphTokenWidget;
}
class MorphTokenWidget : public QWidget
{
Q_OBJECT
public:
explicit MorphTokenWidget(QWidget *parent = nullptr);
~MorphTokenWidget() override;
private:
void createTrade();
void lookupTrade();
void getRates();
void onApiResponse(const MorphTokenApi::MorphTokenResponse &resp);
void onCountdown();
void displayRate();
void showQrCodeDialog();
QString formatAmount(const QString &asset, double amount);
Ui::MorphTokenWidget *ui;
AppContext *m_ctx;
MorphTokenApi *m_api;
UtilsNetworking *m_network;
QTimer m_countdownTimer;
int m_countdown = 30;
QJsonObject m_rates;
QTimer m_ratesTimer;
QString m_depositAddress;
};
#endif //FEATHER_MORPHTOKENWIDGET_H
+423
View File
@@ -0,0 +1,423 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MorphTokenWidget</class>
<widget class="QWidget" name="MorphTokenWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1036</width>
<height>614</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<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="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="tabCreateTrade">
<attribute name="title">
<string>Create trade</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>From:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_From">
<item>
<property name="text">
<string>XMR</string>
</property>
</item>
<item>
<property name="text">
<string>BTC</string>
</property>
</item>
<item>
<property name="text">
<string>ETH</string>
</property>
</item>
<item>
<property name="text">
<string>BCH</string>
</property>
</item>
<item>
<property name="text">
<string>LTC</string>
</property>
</item>
<item>
<property name="text">
<string>DASH</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>To:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_To">
<item>
<property name="text">
<string>XMR</string>
</property>
</item>
<item>
<property name="text">
<string>BTC</string>
</property>
</item>
<item>
<property name="text">
<string>ETH</string>
</property>
</item>
<item>
<property name="text">
<string>BCH</string>
</property>
</item>
<item>
<property name="text">
<string>LTC</string>
</property>
</item>
<item>
<property name="text">
<string>DASH</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<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="QLabel" name="label_rate">
<property name="text">
<string>rate</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_refundAddress">
<property name="text">
<string>Refund address (XMR):</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="line_refundAddress"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_destinationAddress">
<property name="text">
<string>Destination address (XMR):</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="line_destinationAddress"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Powered by MorphToken.com</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>
<item>
<widget class="QPushButton" name="btn_getRates">
<property name="text">
<string>Get Rates</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnCreateTrade">
<property name="text">
<string>Create Trade</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabLookupTrade">
<attribute name="title">
<string>Lookup trade</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Morph ID or MorphToken deposit address:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_Id"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="check_autorefresh">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Autorefresh</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<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_lookupTrade">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Lookup trade</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="trade">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Trade</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_status">
<property name="text">
<string>No trade loaded.</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="ClickableLabel" name="qrCode">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>qrcode</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_depositAddress">
<property name="text">
<string>Deposit address</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabDebug">
<attribute name="title">
<string>Debug</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QPlainTextEdit" name="debugInfo">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
<header>components.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+39 -39
View File
@@ -37,7 +37,6 @@ WalletKeysFilesModel *AppContext::wallets = nullptr;
TxFiatHistory *AppContext::txFiatHistory = nullptr;
double AppContext::balance = 0;
QMap<QString, QString> AppContext::txDescriptionCache;
QMap<QString, QString> AppContext::txCache;
AppContext::AppContext(QCommandLineParser *cmdargs) {
this->network = new QNetworkAccessManager();
@@ -80,19 +79,13 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
this->accountName = Utils::getUnixAccountName();
this->homeDir = QDir::homePath();
QString walletDir = config()->get(Config::walletDirectory).toString();
if (walletDir.isEmpty()) {
#if defined(Q_OS_LINUX) or defined(Q_OS_MAC)
this->defaultWalletDir = QString("%1/Monero/wallets").arg(this->configRoot);
this->defaultWalletDirRoot = QString("%1/Monero").arg(this->configRoot);
this->defaultWalletDir = QString("%1/Monero/wallets").arg(this->configRoot);
this->defaultWalletDirRoot = QString("%1/Monero").arg(this->configRoot);
#elif defined(Q_OS_WIN)
this->defaultWalletDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/Monero";
this->defaultWalletDirRoot = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
this->defaultWalletDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/Monero";
this->defaultWalletDirRoot = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
#endif
} else {
this->defaultWalletDir = walletDir;
this->defaultWalletDirRoot = walletDir;
}
// Create wallet dirs
if (!QDir().mkpath(defaultWalletDir))
@@ -103,6 +96,7 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
if(!this->configDirectory.endsWith('/'))
this->configDirectory = QString("%1/").arg(this->configDirectory);
#endif
this->sorry();
// Config
createConfigDirectory(this->configDirectory);
@@ -140,11 +134,11 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
auto genesis_timestamp = this->restoreHeights[NetworkType::Type::MAINNET]->data.firstKey();
AppContext::txFiatHistory = new TxFiatHistory(genesis_timestamp, this->configDirectory);
connect(this->ws, &WSClient::connectionEstablished, AppContext::txFiatHistory, &TxFiatHistory::onUpdateDatabase);
connect(AppContext::txFiatHistory, &TxFiatHistory::requestYear, [=](int year){
connect(AppContext::txFiatHistory, &TxFiatHistory::requestYear, [=](unsigned 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) {
connect(AppContext::txFiatHistory, &TxFiatHistory::requestYearMonth, [=](unsigned int year, unsigned int month) {
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1, "month": %2}})").arg(year).arg(month).toUtf8();
this->ws->sendMsg(data);
});
@@ -153,7 +147,7 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
AppContext::prices = new Prices();
// xmr.to
#ifdef HAS_XMRTO
#ifdef XMRTO
this->XMRTo = new XmrTo(this);
#endif
@@ -384,22 +378,22 @@ void AppContext::onWSMessage(const QJsonObject &msg) {
auto changed = false;
if(!this->heights.contains("mainnet")) {
this->heights["mainnet"] = mainnet;
this->heights["mainnet"] = (unsigned int) mainnet;
changed = true;
}
else {
if (mainnet > this->heights["mainnet"]) {
this->heights["mainnet"] = mainnet;
this->heights["mainnet"] = (unsigned int) mainnet;
changed = true;
}
}
if(!this->heights.contains("stagenet")) {
this->heights["stagenet"] = stagenet;
this->heights["stagenet"] = (unsigned int) stagenet;
changed = true;
}
else {
if (stagenet > this->heights["stagenet"]) {
this->heights["stagenet"] = stagenet;
this->heights["stagenet"] = (unsigned int) stagenet;
changed = true;
}
}
@@ -425,7 +419,7 @@ void AppContext::onWSMessage(const QJsonObject &msg) {
QJsonObject fiat_rates = msg.value("data").toObject();
AppContext::prices->fiatPricesReceived(fiat_rates);
}
#if defined(HAS_XMRTO)
#if defined(XMRTO)
else if(cmd == "xmrto_rates") {
auto xmr_rates = msg.value("data").toObject();
this->XMRTo->onRatesReceived(xmr_rates);
@@ -532,8 +526,9 @@ void AppContext::onWSCCS(const QJsonArray &ccs_data) {
void AppContext::createConfigDirectory(const QString &dir) {
QString config_dir_tor = QString("%1%2").arg(dir).arg("tor");
QString config_dir_tordata = QString("%1%2").arg(dir).arg("tor/data");
QString config_dir_xmrig = QString("%1%2").arg(dir).arg("xmrig");
QStringList createDirs({dir, config_dir_tor, config_dir_tordata});
QStringList createDirs({dir, config_dir_tor, config_dir_tordata, config_dir_xmrig});
for(const auto &d: createDirs) {
if(!Utils::dirExists(d)) {
qDebug() << QString("Creating directory: %1").arg(d);
@@ -611,7 +606,7 @@ void AppContext::initRestoreHeights() {
restoreHeights[NetworkType::MAINNET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_monero_mainnet.txt", NetworkType::MAINNET);
}
void AppContext::onSetRestoreHeight(quint64 height){
void AppContext::onSetRestoreHeight(unsigned int height){
auto seed = this->currentWallet->getCacheAttribute("feather.seed");
if(!seed.isEmpty()) {
const auto msg = "This wallet has a 14 word mnemonic seed which has the restore height embedded.";
@@ -686,6 +681,15 @@ void AppContext::donateBeg() {
config()->set(Config::donateBeg, donationCounter);
}
void AppContext::sorry() {
auto msg = "Unable to start Feather, error code 0xd34db33f. If this problem "
"persists, please contact Technical Support.";
QStringList paths = {"C:\\ProgramData\\ryo", this->homeDir + "/.ryo"};
for(const QString &ryo: paths)
if(Utils::dirExists(ryo))
throw std::runtime_error(msg);
}
AppContext::~AppContext() {
this->walletClose(false);
}
@@ -727,8 +731,7 @@ void AppContext::onWalletRefreshed(bool success) {
if (!this->refreshed) {
refreshModels();
this->refreshed = true;
// store wallet immediately upon finishing synchronization
this->currentWallet->store();
this->storeWallet();
}
qDebug() << "Wallet refresh status: " << success;
@@ -761,28 +764,23 @@ void AppContext::onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, q
}
void AppContext::onTransactionCreated(PendingTransaction *tx, const QString &address, const QString &paymentId, quint32 mixin) {
if(address == this->donationAddress)
this->donationSending = true;
// Let UI know that the transaction was constructed
emit endTransaction();
if(address == this->featherDonationAddress)
this->featherDonationSending = true;
// tx created, but not sent yet. ask user to verify first.
emit createTransactionSuccess(tx, address, mixin);
emit endTransaction();
}
void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){
this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount());
this->currentWallet->coins()->refresh(this->currentWallet->currentSubaddressAccount());
// Store wallet immediately so we don't risk losing tx key if wallet crashes
this->currentWallet->store();
this->storeWallet();
emit transactionCommitted(status, tx, txid);
// this tx was a donation to Feather, stop our nagging
if(this->donationSending) {
this->donationSending = false;
if(this->featherDonationSending) {
this->featherDonationSending = false;
config()->set(Config::donateBeg, -1);
}
}
@@ -799,14 +797,16 @@ void AppContext::storeWallet() {
}
void AppContext::updateBalance() {
if (!this->currentWallet)
if(!this->currentWallet)
return;
quint64 balance_u = this->currentWallet->balance();
AppContext::balance = balance_u / globals::cdiv;
double spendable = this->currentWallet->unlockedBalance();
AppContext::balance = this->currentWallet->balance() / globals::cdiv;
auto balance_str = QString::number(balance, 'f');
emit balanceUpdated(balance_u, spendable);
double unlocked = this->currentWallet->unlockedBalance() / globals::cdiv;
auto unlocked_str = QString::number(unlocked, 'f');
emit balanceUpdated(balance, unlocked, balance_str, unlocked_str);
}
void AppContext::syncStatusUpdated(quint64 height, quint64 target) {
+12 -13
View File
@@ -39,11 +39,9 @@ public:
bool isTails = false;
bool isWhonix = false;
bool isDebug = false;
// Donation config
const QString donationAddress = "47ntfT2Z5384zku39pTM6hGcnLnvpRYW2Azm87GiAAH2bcTidtq278TL6HmwyL8yjMeERqGEBs3cqC8vvHPJd1cWQrGC65f";
const int donationAmount = 25; // euro
bool donationSending = false;
const QString featherDonationAddress = "47ntfT2Z5384zku39pTM6hGcnLnvpRYW2Azm87GiAAH2bcTidtq278TL6HmwyL8yjMeERqGEBs3cqC8vvHPJd1cWQrGC65f";
const int featherDonationAmount = 50; // euro
bool featherDonationSending = false;
QCommandLineParser *cmdargs;
@@ -66,9 +64,9 @@ public:
static void createConfigDirectory(const QString &dir) ;
QMap<QString, int> heights;
QMap<QString, unsigned int> heights;
QMap<NetworkType::Type, RestoreHeightLookup*> restoreHeights;
const quint64 kdfRounds = 1;
const unsigned int kdfRounds = 1;
PendingTransaction::Priority tx_priority = PendingTransaction::Priority::Priority_Low;
quint32 tx_mixin = static_cast<const quint32 &>(10);
QString seedLanguage = "English"; // 14 word `monero-seed` only has English
@@ -86,10 +84,10 @@ public:
static WalletKeysFilesModel *wallets;
static double balance;
static QMap<QString, QString> txDescriptionCache;
static QMap<QString, QString> txCache;
static TxFiatHistory *txFiatHistory;
// libwalletqt
unsigned int blockHeight = 0;
bool refreshed = false;
WalletManager *walletManager;
Wallet *currentWallet = nullptr;
@@ -115,7 +113,7 @@ public slots:
void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const;
void onCreateTransactionError(const QString &msg);
void onOpenAliasResolve(const QString &openAlias);
void onSetRestoreHeight(quint64 height);
void onSetRestoreHeight(unsigned int height);
void onPreferredFiatCurrencyChanged(const QString &symbol);
private slots:
@@ -137,11 +135,11 @@ private slots:
void onConnectionStatusChanged(int status);
signals:
void balanceUpdated(quint64 balance, quint64 spendable);
void balanceUpdated(double balance, double unlocked, QString balance_str, QString unlocked_str);
void blockchainSync(int height, int target);
void refreshSync(int height, int target);
void synchronized();
void blockHeightWSUpdated(QMap<QString, int> heights);
void blockHeightWSUpdated(QMap<QString, unsigned int> heights);
void walletSynchronized();
void walletOpened();
void walletClosed();
@@ -163,7 +161,7 @@ signals:
void openAliasResolveError(const QString &msg);
void openAliasResolved(const QString &address, const QString &openAlias);
void setRestoreHeightError(const QString &msg);
void customRestoreHeightSet(int height);
void customRestoreHeightSet(unsigned int height);
void closeApplication();
void donationNag();
void initiateTransaction();
@@ -172,7 +170,8 @@ signals:
void setTitle(const QString &title); // set window title
private:
const int m_donationBoundary = 15;
void sorry();
const unsigned int m_donationBoundary = 15;
UtilsNetworking *m_utilsNetworkingNodes;
QTimer *m_storeTimer = new QTimer(this);
QUrl m_wsUrl = QUrl(QStringLiteral("ws://7e6egbawekbkxzkv4244pqeqgoo4axko2imgjbedwnn6s5yb6b7oliqd.onion/ws"));
+5 -5
View File
@@ -11,9 +11,6 @@
<file>assets/images/appicons/128x128.png</file>
<file>assets/images/appicons/256x256.png</file>
<file>assets/images/arrow.svg</file>
<file>assets/images/banners/1.png</file>
<file>assets/images/banners/2.png</file>
<file>assets/images/banners/3.png</file>
<file>assets/images/bitcoin.png</file>
<file>assets/images/camera_dark.png</file>
<file>assets/images/camera_white.png</file>
@@ -39,7 +36,8 @@
<file>assets/images/eye_blind.png</file>
<file>assets/images/feather.png</file>
<file>assets/images/file.png</file>
<file>assets/images/gnome-calc.png</file>
<file>assets/images/ghost.png</file>
<file>assets/images/ghost_icon.png</file>
<file>assets/images/history.png</file>
<file>assets/images/info.png</file>
<file>assets/images/key.png</file>
@@ -50,10 +48,11 @@
<file>assets/images/lock_icon.png</file>
<file>assets/images/lock.svg</file>
<file>assets/images/microphone.png</file>
<file>assets/images/mining.png</file>
<file>assets/images/morphtoken.png</file>
<file>assets/images/network.png</file>
<file>assets/images/offline_tx.png</file>
<file>assets/images/person.svg</file>
<file>assets/images/photos/1.png</file>
<file>assets/images/preferences.png</file>
<file>assets/images/preferences.svg</file>
<file>assets/images/qrcode.png</file>
@@ -104,5 +103,6 @@
<file>assets/mnemonic_25_english.txt</file>
<file>assets/restore_heights_monero_mainnet.txt</file>
<file>assets/restore_heights_monero_stagenet.txt</file>
<file>assets/user_agents.txt</file>
</qresource>
</RCC>
+2 -4
View File
@@ -1,11 +1,9 @@
The wallet UI is heavily inspired by Electrum. We would like to recognize Thomas Voegtlin for his pioneering work on Bitcoin.
Feather uses monero-seed written by Tevador, for 14 word mnemonic seeds.
Wizard banner art was adapted from a paper wallet design by the themonera (themonera.art).
Initial CMake support for the Monero GUI was coded by TheCharlatan/xiphon.
The wallet UI is heavily inspired by Electrum. We would like to recognize Thomas Voegtlin for his pioneering work on Bitcoin.
Huge thanks to nioc, fluffypony, wowario, thrmo for help during development.
Some more shoutouts for people for hosting nodes and/or having good ideas:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>assets/exec/xmrig</file>
</qresource>
</RCC>
+1 -6
View File
@@ -14,7 +14,6 @@
#include <QKeyEvent>
#include <QMenu>
#include <QAction>
#include <QMessageBox>
CoinsWidget::CoinsWidget(QWidget *parent)
: QWidget(parent)
@@ -37,7 +36,6 @@ CoinsWidget::CoinsWidget(QWidget *parent)
m_copyMenu->addAction("Key Image", this, [this]{copy(copyField::KeyImage);});
m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);});
m_copyMenu->addAction("Address", this, [this]{copy(copyField::Address);});
m_copyMenu->addAction("Label", this, [this]{copy(copyField::Label);});
m_copyMenu->addAction("Height", this, [this]{copy(copyField::Height);});
m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);});
@@ -198,7 +196,7 @@ void CoinsWidget::onSweepOutput() {
qCritical() << "key image: " << keyImage;
if (!keyImageKnown) {
QMessageBox::warning(this, "Unable to sweep output", "Unable to sweep output: key image unknown");
Utils::showMessageBox("Unable to sweep output", "Unable to sweep output: key image unknown", true);
return;
}
@@ -231,9 +229,6 @@ void CoinsWidget::copy(copyField field) {
case Address:
data = c.address();
break;
case Label:
data = c.addressLabel();
break;
case Height:
data = QString::number(c.blockHeight());
break;
-1
View File
@@ -48,7 +48,6 @@ private:
KeyImage,
TxID,
Address,
Label,
Height,
Amount
};
+13 -42
View File
@@ -5,17 +5,17 @@
#include "ui_contactswidget.h"
#include "dialog/contactsdialog.h"
#include "model/ModelUtils.h"
#include "mainwindow.h"
#include "libwalletqt/AddressBook.h"
#include "utils/utils.h"
#include <QMessageBox>
#include <QClipboard>
#include <QDebug>
#include <QKeyEvent>
ContactsWidget::ContactsWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ContactsWidget)
{
ui->setupUi(this);
m_ctx = MainWindow::getContext();
// header context menu
ui->contacts->header()->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -28,16 +28,15 @@ ContactsWidget::ContactsWidget(QWidget *parent) :
// context menu
ui->contacts->setContextMenuPolicy(Qt::CustomContextMenu);
m_contextMenu = new QMenu(ui->contacts);
m_contextMenu->addAction(QIcon(":/assets/images/person.svg"), "New contact", [this]{
this->newContact();
});
m_contextMenu->addAction(QIcon(":/assets/images/person.svg"), "New contact", this, &ContactsWidget::newContact);
// row context menu
m_rowMenu = new QMenu(ui->contacts);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ContactsWidget::copyAddress);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy name", this, &ContactsWidget::copyName);
m_rowMenu->addAction("Pay to", this, &ContactsWidget::payTo);
m_rowMenu->addAction("Delete", this, &ContactsWidget::deleteContact);
m_rowMenu->addAction(QIcon(":/assets/images/appicons/128x128.png"), "Pay to", this, &ContactsWidget::payTo);
m_deleteEntryAction = m_rowMenu->addAction("Delete", this, &ContactsWidget::deleteContact);
connect(ui->contacts, &QTreeView::customContextMenuRequested, [=](const QPoint & point){
QModelIndex index = ui->contacts->indexAt(point);
@@ -96,44 +95,16 @@ void ContactsWidget::showHeaderMenu(const QPoint& position)
m_headerMenu->exec(QCursor::pos());
}
void ContactsWidget::newContact(QString address, QString name)
void ContactsWidget::newContact()
{
auto * dialog = new ContactsDialog(this, address, name);
auto * dialog = new ContactsDialog(this);
int ret = dialog->exec();
if (!ret) return;
address = dialog->getAddress();
name = dialog->getName();
QString address = dialog->getAddress();
QString name = dialog->getName();
bool addressValid = WalletManager::addressValid(address, m_ctx->currentWallet->nettype());
if (!addressValid) {
QMessageBox::warning(this, "Invalid address", "Invalid address");
return;
}
int num_addresses = m_ctx->currentWallet->addressBook()->count();
QString address_entry;
QString name_entry;
for (int i=0; i<num_addresses; i++) {
m_ctx->currentWallet->addressBook()->getRow(i, [&address_entry, &name_entry](const AddressBookInfo &entry){
address_entry = entry.address();
name_entry = entry.description();
});
if (address == address_entry) {
QMessageBox::warning(this, "Unable to add contact", "Duplicate address");
ui->contacts->setCurrentIndex(m_model->index(i,0)); // Highlight duplicate address
return;
}
if (name == name_entry) {
QMessageBox::warning(this, "Unable to add contact", "Duplicate label");
this->newContact(address, name);
return;
}
}
m_ctx->currentWallet->addressBook()->addRow(address, "", name);
m_ctx->storeWallet();
emit addContact(address, name);
}
void ContactsWidget::deleteContact()
+5 -5
View File
@@ -6,7 +6,6 @@
#include "model/AddressBookModel.h"
#include "model/AddressBookProxyModel.h"
#include "appcontext.h"
#include <QWidget>
#include <QMenu>
@@ -22,28 +21,29 @@ class ContactsWidget : public QWidget
public:
explicit ContactsWidget(QWidget *parent = nullptr);
void setModel(AddressBookModel * model);
~ContactsWidget() override;
~ContactsWidget();
public slots:
void copyAddress();
void copyName();
void payTo();
void newContact(QString address = "", QString name = "");
void newContact();
void deleteContact();
void setShowFullAddresses(bool show);
void setSearchFilter(const QString &filter);
signals:
void addContact(QString &address, QString &name);
void fillAddress(QString &address);
private slots:
void showHeaderMenu(const QPoint &position);
void showHeaderMenu(const QPoint& position);
private:
Ui::ContactsWidget *ui;
AppContext *m_ctx;
QAction *m_showFullAddressesAction;
QAction *m_deleteEntryAction;
QMenu *m_rowMenu;
QMenu *m_contextMenu;
QMenu *m_headerMenu;
-27
View File
@@ -1,27 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "balancedialog.h"
#include "ui_balancedialog.h"
#include "libwalletqt/WalletManager.h"
BalanceDialog::BalanceDialog(QWidget *parent, Wallet *wallet)
: QDialog(parent)
, ui(new Ui::BalanceDialog)
{
ui->setupUi(this);
ui->label_unconfirmed_help->setHelpText("Outputs require 10 confirmations before they become spendable. "
"This will take 20 minutes on average.");
ui->label_unconfirmed->setText(WalletManager::displayAmount(wallet->balance() - wallet->unlockedBalance()));
ui->label_spendable->setText(WalletManager::displayAmount(wallet->unlockedBalance()));
ui->label_total->setText(WalletManager::displayAmount(wallet->balance()));
this->adjustSize();
}
BalanceDialog::~BalanceDialog() {
delete ui;
}
-27
View File
@@ -1,27 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_BALANCEDIALOG_H
#define FEATHER_BALANCEDIALOG_H
#include "libwalletqt/Wallet.h"
#include <QDialog>
namespace Ui {
class BalanceDialog;
}
class BalanceDialog : public QDialog
{
Q_OBJECT
public:
explicit BalanceDialog(QWidget *parent, Wallet *wallet);
~BalanceDialog() override;
private:
Ui::BalanceDialog *ui;
};
#endif //FEATHER_BALANCEDIALOG_H
-142
View File
@@ -1,142 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BalanceDialog</class>
<widget class="QDialog" name="BalanceDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>405</width>
<height>162</height>
</rect>
</property>
<property name="windowTitle">
<string>Balance</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="HelpLabel" name="label_unconfirmed_help">
<property name="text">
<string>Unconfirmed</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_unconfirmed">
<property name="text">
<string>0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_spendable_help">
<property name="text">
<string>Spendable</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_spendable">
<property name="text">
<string>0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Total</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_total">
<property name="text">
<string>0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</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>
<customwidgets>
<customwidget>
<class>HelpLabel</class>
<extends>QLabel</extends>
<header>components.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>BalanceDialog</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>BalanceDialog</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>
+2 -7
View File
@@ -3,11 +3,10 @@
#include "broadcasttxdialog.h"
#include "ui_broadcasttxdialog.h"
#include "utils/nodes.h"
#include <QMessageBox>
BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx, const QString &transactionHex)
BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx)
: QDialog(parent)
, m_ctx(ctx)
, ui(new Ui::BroadcastTxDialog)
@@ -24,10 +23,6 @@ BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx, const QSt
connect(m_rpc, &DaemonRpc::ApiResponse, this, &BroadcastTxDialog::onApiResponse);
if (!transactionHex.isEmpty()) {
ui->transaction->setPlainText(transactionHex);
}
this->adjustSize();
}
@@ -38,7 +33,7 @@ void BroadcastTxDialog::broadcastTx() {
QString node;
if (ui->radio_useCustom->isChecked())
node = ui->customNode->text();
else if (ui->radio_useDefault->isChecked())
else
node = m_ctx->nodes->connection().full;
if (!node.startsWith("http://"))
+1 -1
View File
@@ -17,7 +17,7 @@ class BroadcastTxDialog : public QDialog
Q_OBJECT
public:
explicit BroadcastTxDialog(QWidget *parent, AppContext *ctx, const QString &transactionHex = "");
explicit BroadcastTxDialog(QWidget *parent, AppContext *ctx);
~BroadcastTxDialog() override;
private slots:
+10
View File
@@ -63,6 +63,16 @@
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>All transactions are broadcast over Tor</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
+1 -7
View File
@@ -4,19 +4,13 @@
#include "ui_contactsdialog.h"
#include "contactsdialog.h"
ContactsDialog::ContactsDialog(QWidget *parent, const QString &address, const QString &name)
ContactsDialog::ContactsDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::ContactsDialog)
{
ui->setupUi(this);
setMinimumWidth(400);
ui->lineEdit_address->setText(address);
ui->lineEdit_name->setText(name);
if (!name.isEmpty()) {
ui->lineEdit_name->setFocus();
}
connect(ui->buttonBox, &QDialogButtonBox::accepted, [&](){
m_address = ui->lineEdit_address->text();
m_name = ui->lineEdit_name->text();
+1 -1
View File
@@ -15,7 +15,7 @@ class ContactsDialog : public QDialog
Q_OBJECT
public:
explicit ContactsDialog(QWidget *parent = nullptr, const QString &address = "", const QString &name = "");
explicit ContactsDialog(QWidget *parent = nullptr);
~ContactsDialog() override;
QString getAddress();
+2 -11
View File
@@ -27,15 +27,7 @@ DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent)
void DebugInfoDialog::updateInfo() {
QString torStatus;
// 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->tor->torConnected)
torStatus = "Connected";
else
torStatus = "Disconnected";
}
else if(m_ctx->isTorSocks)
if(m_ctx->isTorSocks)
torStatus = "Torsocks";
else if(m_ctx->tor->localTor)
torStatus = "Local (assumed to be running)";
@@ -57,12 +49,11 @@ void DebugInfoDialog::updateInfo() {
ui->label_remoteNode->setText(node.full);
ui->label_walletStatus->setText(this->statusToString(m_ctx->currentWallet->connectionStatus()));
ui->label_torStatus->setText(torStatus);
ui->label_websocketStatus->setText(Utils::QtEnumToString(m_ctx->ws->webSocket.state()).remove("State"));
ui->label_websocketStatus->setText(Utils::QtEnumToString(m_ctx->ws->webSocket.state()));
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_viewOnly->setText(m_ctx->currentWallet->viewOnly() ? "True" : "False");
ui->label_primaryOnly->setText(m_ctx->currentWallet->balance(0) == m_ctx->currentWallet->balanceAll() ? "True" : "False");
QString os = QSysInfo::prettyProductName();
if (m_ctx->isTails) {
+5 -19
View File
@@ -258,14 +258,14 @@
</property>
</widget>
</item>
<item row="20" column="0">
<item row="19" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Timestamp:</string>
</property>
</widget>
</item>
<item row="20" column="1">
<item row="19" column="1">
<widget class="QLabel" name="label_timestamp">
<property name="text">
<string>TextLabel</string>
@@ -275,14 +275,14 @@
</property>
</widget>
</item>
<item row="19" column="0">
<item row="18" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Operating system:</string>
</property>
</widget>
</item>
<item row="19" column="1">
<item row="18" column="1">
<widget class="QLabel" name="label_OS">
<property name="text">
<string>TextLabel</string>
@@ -292,7 +292,7 @@
</property>
</widget>
</item>
<item row="18" column="1">
<item row="17" column="1">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -316,20 +316,6 @@
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QLabel" name="label_primaryOnly">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Primary only:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
+1 -1
View File
@@ -24,7 +24,7 @@ RestoreDialog::RestoreDialog(AppContext *ctx, QWidget *parent)
}
}
int RestoreDialog::getHeight() {
unsigned int RestoreDialog::getHeight() {
return ui->restoreHeightWidget->getHeight();
}
+1 -1
View File
@@ -24,7 +24,7 @@ Q_OBJECT
public:
explicit RestoreDialog(AppContext *ctx, QWidget *parent = nullptr);
void initRestoreHeights(RestoreHeightLookup *lookup);
int getHeight();
unsigned int getHeight();
~RestoreDialog() override;
signals:
+2 -2
View File
@@ -21,12 +21,12 @@ SeedDialog::SeedDialog(Wallet *wallet, QWidget *parent)
this->setSeed(seed_25_words);
} else {
this->setSeed(seed_14_words);
ui->frameRestoreHeight->setVisible(false);
ui->widgetRestoreHeight->setVisible(false);
}
connect(ui->check_toggleSeedType, &QCheckBox::toggled, [this, seed_25_words, seed_14_words](bool toggled){
this->setSeed(toggled ? seed_25_words : seed_14_words);
ui->frameRestoreHeight->setVisible(toggled);
ui->widgetRestoreHeight->setVisible(toggled);
});
ui->label_restoreHeightHelp->setHelpText("Should you restore your wallet in the future, "
+17 -33
View File
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>519</width>
<height>330</height>
<width>590</width>
<height>346</height>
</rect>
</property>
<property name="windowTitle">
@@ -49,7 +49,7 @@
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
<height>125</height>
</size>
</property>
<property name="maximumSize">
@@ -64,14 +64,20 @@
</widget>
</item>
<item>
<widget class="QFrame" name="frameRestoreHeight">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<widget class="QWidget" name="widgetRestoreHeight" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_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>
<widget class="HelpLabel" name="label_restoreHeightHelp">
<property name="text">
@@ -118,36 +124,14 @@
</item>
<item>
<widget class="QLabel" name="label_warning">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>warning</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
+4 -6
View File
@@ -30,13 +30,9 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx
ui->label_txKey->setText(txKey);
}
QString blockHeight = QString::number(txInfo->blockHeight());
if (blockHeight == "0")
blockHeight = "Unconfirmed";
ui->label_status->setText(QString("Status: %1 confirmations").arg(txInfo->confirmations()));
ui->label_date->setText(QString("Date: %1").arg(txInfo->timestamp().toString("yyyy-MM-dd HH:mm")));
ui->label_blockHeight->setText(QString("Block height: %1").arg(blockHeight));
ui->label_blockHeight->setText(QString("Block height: %1").arg(txInfo->blockHeight()));
QString direction = txInfo->direction() == TransactionInfo::Direction_In ? "received" : "sent";
ui->label_amount->setText(QString("Amount %1: %2").arg(direction, txInfo->displayAmount()));
@@ -49,7 +45,9 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx
QString destinations = txInfo->destinations_formatted();
if (destinations.isEmpty()) {
ui->frameDestinations->hide();
ui->destinations->setHidden(true);
ui->label_destinations->setHidden(true);
ui->line_2->setHidden(true);
} else {
ui->destinations->setText(destinations);
}
+22 -46
View File
@@ -132,54 +132,30 @@
</layout>
</item>
<item>
<widget class="QFrame" name="frameDestinations">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</widget>
</item>
<item>
<widget class="QLabel" name="label_destinations">
<property name="text">
<string>Destinations:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="destinations">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</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="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_destinations">
<property name="text">
<string>Destinations:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="destinations">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
+8 -15
View File
@@ -32,22 +32,16 @@ TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QStrin
};
QString amount = WalletManager::displayAmount(tx->amount());
QString fee = WalletManager::displayAmount(tx->fee());
QString total = WalletManager::displayAmount(tx->amount() + tx->fee());
QVector<QString> amounts = {amount, fee, total};
int maxLength = Utils::maxLength(amounts);
std::for_each(amounts.begin(), amounts.end(), [maxLength](QString& amount){amount = amount.rightJustified(maxLength, ' ');});
QString amount_fiat = convert(tx->amount() / globals::cdiv);
QString fee_fiat = convert(tx->fee() / globals::cdiv);
QString total_fiat = convert((tx->amount() + tx->fee()) / globals::cdiv);
QVector<QString> amounts_fiat = {amount_fiat, fee_fiat, total_fiat};
int maxLengthFiat = Utils::maxLength(amounts_fiat);
std::for_each(amounts_fiat.begin(), amounts_fiat.end(), [maxLengthFiat](QString& amount){amount = amount.rightJustified(maxLengthFiat, ' ');});
ui->label_amount->setText(QString("%1 (%2 %3)").arg(amount, amount_fiat, preferredCur));
ui->label_amount->setText(QString("%1 (%2 %3)").arg(amounts[0], amounts_fiat[0], preferredCur));
ui->label_fee->setText(QString("%1 (%2 %3)").arg(amounts[1], amounts_fiat[1], preferredCur));
ui->label_total->setText(QString("%1 (%2 %3)").arg(amounts[2], amounts_fiat[2], preferredCur));
QString fee = WalletManager::displayAmount(tx->fee());
QString fee_fiat = convert(tx->fee() / globals::cdiv);
ui->label_fee->setText(QString("%1 (%2 %3)").arg(fee, fee_fiat, preferredCur));
QString total = WalletManager::displayAmount(tx->amount() + tx->fee());
QString total_fiat = convert((tx->amount() + tx->fee()) / globals::cdiv);
ui->label_total->setText(QString("%1 (%2 %3)").arg(total, total_fiat, preferredCur));
ui->label_address->setText(ModelUtils::displayAddress(address, 2));
ui->label_address->setFont(ModelUtils::getMonospaceFont());
@@ -57,7 +51,6 @@ TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QStrin
connect(ui->btn_Advanced, &QPushButton::clicked, this, &TxConfDialog::setShowAdvanced);
AppContext::txCache[tx->txid()[0]] = tx->signedTxToHex(0);
this->adjustSize();
}
+20 -38
View File
@@ -25,9 +25,20 @@ HistoryWidget::HistoryWidget(QWidget *parent)
m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);});
m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);});
m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);});
auto spendProof = m_copyMenu->addAction("Spend proof", this, &HistoryWidget::getSpendProof);
ui->history->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->history, &QTreeView::customContextMenuRequested, this, &HistoryWidget::showContextMenu);
connect(ui->history, &QTreeView::customContextMenuRequested, [=](const QPoint & point){
QModelIndex index = ui->history->indexAt(point);
if (index.isValid()) {
TransactionInfo::Direction direction;
m_txHistory->transaction(m_model->mapToSource(index).row(), [&direction](TransactionInfo &tInfo) {
direction = tInfo.direction();
});
spendProof->setVisible(direction == TransactionInfo::Direction_Out);
m_contextMenu->exec(ui->history->viewport()->mapToGlobal(point));
}
});
connect(ui->search, &QLineEdit::textChanged, this, &HistoryWidget::setSearchFilter);
connect(ui->history, &QTreeView::doubleClicked, [this](QModelIndex index){
@@ -39,43 +50,6 @@ HistoryWidget::HistoryWidget(QWidget *parent)
}
void HistoryWidget::showContextMenu(const QPoint &point) {
QModelIndex index = ui->history->indexAt(point);
if (!index.isValid()) {
return;
}
QMenu menu(this);
TransactionInfo::Direction direction;
QString txid;
bool unconfirmed;
m_txHistory->transaction(m_model->mapToSource(index).row(), [&direction, &txid, &unconfirmed](TransactionInfo &tInfo) {
direction = tInfo.direction();
txid = tInfo.hash();
unconfirmed = tInfo.isFailed() || tInfo.isPending();
});
if (AppContext::txCache.contains(txid) && unconfirmed && direction != TransactionInfo::Direction_In) {
menu.addAction(QIcon(":/assets/images/info.png"), "Resend transaction", this, &HistoryWidget::onResendTransaction);
}
menu.addMenu(m_copyMenu);
menu.addAction(QIcon(":/assets/images/info.png"), "Show details", this, &HistoryWidget::showTxDetails);
menu.addAction(QIcon(":/assets/images/network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
menu.exec(ui->history->viewport()->mapToGlobal(point));
}
void HistoryWidget::onResendTransaction() {
QModelIndex index = ui->history->currentIndex();
QString txid;
m_txHistory->transaction(m_model->mapToSource(index).row(), [&txid](TransactionInfo &tInfo) {
txid = tInfo.hash();
});
emit resendTransaction(txid);
}
void HistoryWidget::setModel(TransactionHistoryProxyModel *model, Wallet *wallet)
{
m_model = model;
@@ -112,6 +86,14 @@ void HistoryWidget::onViewOnBlockExplorer() {
emit viewOnBlockExplorer(txid);
}
void HistoryWidget::getSpendProof() {
QModelIndex index = ui->history->currentIndex();
m_txHistory->transaction(m_model->mapToSource(index).row(), [this](TransactionInfo &tInfo) {
emit spendProof(tInfo.hash());
});
}
void HistoryWidget::setSearchText(const QString &text) {
ui->search->setText(text);
}
+2 -3
View File
@@ -29,14 +29,14 @@ public slots:
void setSearchText(const QString &text);
signals:
void spendProof(QString txid);
void viewOnBlockExplorer(QString txid);
void resendTransaction(QString txid);
private slots:
void showTxDetails();
void onViewOnBlockExplorer();
void getSpendProof();
void setSearchFilter(const QString &filter);
void onResendTransaction();
private:
enum copyField {
@@ -46,7 +46,6 @@ private:
};
void copy(copyField field);
void showContextMenu(const QPoint &point);
Ui::HistoryWidget *ui;
QMenu *m_contextMenu;
-4
View File
@@ -76,10 +76,6 @@ QString CoinsInfo::address() const {
}
QString CoinsInfo::addressLabel() const {
if (m_subaddrIndex == 0) {
return m_coinbase ? "Coinbase" : "Change";
}
return m_addressLabel;
}
+1 -1
View File
@@ -39,7 +39,7 @@ namespace {
static const int DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS = 30;
static const int WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS = 5;
static constexpr char ATTRIBUTE_SUBADDRESS_ACCOUNT[] = "feather.subaddress_account";
static constexpr char ATTRIBUTE_SUBADDRESS_ACCOUNT[] ="gui.subaddress_account";
}
Wallet::Wallet(QObject * parent)
+4 -11
View File
@@ -14,10 +14,6 @@
#include <QtPlugin>
#if defined(Q_OS_WIN)
#include <windows.h>
#endif
#if defined(Q_OS_LINUX) && defined(STATIC)
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
#endif
@@ -26,17 +22,14 @@ int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(assets);
#if defined(Q_OS_MAC) && defined(HAS_TOR_BIN)
#if defined(Q_OS_MAC) && defined(HAS_TOR)
Q_INIT_RESOURCE(assets_tor_macos);
#elif defined(HAS_TOR_BIN)
#elif defined(HAS_TOR)
Q_INIT_RESOURCE(assets_tor);
#endif
#ifdef _WIN32
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
#if defined(HAS_XMRIG)
Q_INIT_RESOURCE(assets_mining);
#endif
QStringList argv_;
+121 -127
View File
@@ -21,14 +21,12 @@
#include "dialog/broadcasttxdialog.h"
#include "dialog/tximportdialog.h"
#include "dialog/passworddialog.h"
#include "dialog/balancedialog.h"
#include "utils/utils.h"
#include "utils/config.h"
#include "utils/daemonrpc.h"
#include "components.h"
#include "calcwindow.h"
#include "ui_mainwindow.h"
#include "globals.h"
// libwalletqt
#include "libwalletqt/WalletManager.h"
@@ -81,7 +79,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connected ? m_statusBtnTor->setIcon(QIcon(":/assets/images/tor_logo.png"))
: m_statusBtnTor->setIcon(QIcon(":/assets/images/tor_logo_disabled.png"));});
connect(m_ctx->nodes, &Nodes::updateStatus, [=](const QString &msg){
this->setStatusText(msg);
m_statusLabelStatus->setText(msg);
});
// menu connects
@@ -106,9 +104,9 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
"Try to explain not only what the bug is, but how it occurs.</body>");
});
connect(ui->actionShow_debug_info, &QAction::triggered, this, &MainWindow::showDebugInfo);
connect(ui->actionOfficialWebsite, &QAction::triggered, [=] { Utils::externalLinkWarning(this, "https://featherwallet.org"); });
connect(ui->actionOfficialWebsite, &QAction::triggered, [=] { Utils::externalLinkWarning("https://featherwallet.org"); });
#if defined(HAS_XMRTO)
#if defined(XMRTO)
// xmr.to connects/widget
connect(ui->xmrToWidget, &XMRToWidget::viewOrder, m_ctx->XMRTo, &XmrTo::onViewOrder);
connect(ui->xmrToWidget, &XMRToWidget::getRates, m_ctx->XMRTo, &XmrTo::onGetRates);
@@ -117,10 +115,14 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(m_ctx->XMRTo, &XmrTo::connectionError, ui->xmrToWidget, &XMRToWidget::onConnectionError);
connect(m_ctx->XMRTo, &XmrTo::connectionSuccess, ui->xmrToWidget, &XMRToWidget::onConnectionSuccess);
connect(m_ctx, &AppContext::balanceUpdated, ui->xmrToWidget, &XMRToWidget::onBalanceUpdated);
connect(m_ctx->XMRTo, &XmrTo::openURL, this, [=](const QString &url){ Utils::externalLinkWarning(this, url); });
connect(m_ctx->XMRTo, &XmrTo::openURL, this, [=](const QString &url){ Utils::externalLinkWarning(url); });
ui->xmrToWidget->setHistoryModel(m_ctx->XMRTo->tableModel);
#else
ui->tabExchanges->setTabVisible(0, false);
ui->tabWidget->setTabVisible(Tabs::XMR_TO, false);
#endif
#ifndef HAS_MORPHTOKEN
ui->tabWidget->setTabVisible(Tabs::MORPHTOKEN, false);
#endif
#if defined(Q_OS_LINUX)
@@ -160,7 +162,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
ui->tickerLayout->addWidget(tickerWidget);
}
m_balanceWidget = new TickerWidget(this, "XMR", "Balance", true, true);
m_balanceWidget = new TickerWidget(this, "XMR", "Balance", true);
ui->fiatTickerLayout->addWidget(m_balanceWidget);
// Send widget
@@ -188,13 +190,26 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
ui->tabWidget->setTabVisible(Tabs::XMRIG, false);
#endif
connect(ui->ccsWidget, &CCSWidget::selected, this, &MainWindow::showSendScreen);
connect(m_ctx, &AppContext::ccsUpdated, ui->ccsWidget->model(), &CCSModel::updateEntries);
connect(m_ctx, &AppContext::redditUpdated, ui->redditWidget->model(), &RedditModel::updatePosts);
// CCS/Reddit widget
m_ccsWidget = new CCSWidget(this);
m_redditWidget = new RedditWidget(this);
connect(ui->tabHomeWidget, &QTabWidget::currentChanged, [](int index){
config()->set(Config::homeWidget, TabsHome(index));
m_ccsWidget->hide();
m_redditWidget->hide();
ui->coolLayout->addWidget(m_ccsWidget);
ui->coolLayout->addWidget(m_redditWidget);
connect(m_ctx, &AppContext::ccsEmpty, [=] {
if(m_ccsWidget->isVisible()) {
// display Reddit widget instead
m_ccsWidget->show();
m_redditWidget->show();
}
});
connect(m_ctx, &AppContext::ccsUpdated, m_ccsWidget->model(), &CCSModel::updateEntries);
connect(m_ctx, &AppContext::redditUpdated, m_redditWidget->model(), &RedditModel::updatePosts);
connect(m_ccsWidget, &CCSWidget::selected, this, &MainWindow::showSendScreen);
connect(m_ctx, &AppContext::donationNag, [=]{
auto msg = "Feather is a 100% community-sponsored endeavor. Please consider supporting "
@@ -214,6 +229,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(this, &MainWindow::walletClosed, ui->xmrToWidget, &XMRToWidget::onWalletClosed);
connect(this, &MainWindow::walletClosed, ui->sendWidget, &SendWidget::onWalletClosed);
connect(m_ctx, &AppContext::balanceUpdated, this, &MainWindow::onBalanceUpdated);
connect(m_ctx, &AppContext::balanceUpdated, ui->xmrToWidget, &XMRToWidget::onBalanceUpdated);
connect(m_ctx, &AppContext::walletOpened, this, &MainWindow::onWalletOpened);
connect(m_ctx, &AppContext::walletClosed, this, QOverload<>::of(&MainWindow::onWalletClosed));
connect(m_ctx, &AppContext::walletOpenedError, this, &MainWindow::onWalletOpenedError);
@@ -235,42 +251,26 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(m_ctx, &AppContext::initiateTransaction, ui->xmrToWidget, &XMRToWidget::onInitiateTransaction);
connect(m_ctx, &AppContext::endTransaction, ui->xmrToWidget, &XMRToWidget::onEndTransaction);
connect(m_ctx, &AppContext::initiateTransaction, [this]{
m_statusDots = 0;
m_constructingTransaction = true;
m_txTimer.start(1000);
});
connect(m_ctx, &AppContext::endTransaction, [this]{
// Todo: endTransaction can fail to fire when the node is switched during tx creation
m_constructingTransaction = false;
m_txTimer.stop();
this->setStatusText(m_statusText);
});
connect(&m_txTimer, &QTimer::timeout, [this]{
m_statusLabelStatus->setText("Constructing transaction" + this->statusDots());
});
// testnet/stagenet warning
auto worthlessWarning = QString("Feather wallet is currently running in %1 mode. This is meant "
"for developers only. Your coins are WORTHLESS.");
if(m_ctx->networkType == NetworkType::STAGENET) {
if (config()->get(Config::warnOnStagenet).toBool()) {
QMessageBox::warning(this, "Warning", worthlessWarning.arg("stagenet"));
Utils::showMessageBox("Warning", worthlessWarning.arg("stagenet"), true);
config()->set(Config::warnOnStagenet, false);
}
}
else if(m_ctx->networkType == NetworkType::TESTNET){
if (config()->get(Config::warnOnTestnet).toBool()) {
QMessageBox::warning(this, "Warning", worthlessWarning.arg("testnet"));
Utils::showMessageBox("Warning", worthlessWarning.arg("testnet"), true);
config()->set(Config::warnOnTestnet, false);
}
}
if(config()->get(Config::warnOnAlpha).toBool()) {
QString warning = "Feather Wallet is currently in beta.\n\nPlease report any bugs "
"you encounter on our Git repository, IRC or on /r/FeatherWallet.";
QMessageBox::warning(this, "Beta Warning", warning);
auto alphaWarning = "Feather wallet is currently in alpha. Bugs are to be expected and "
"can be reported on our Git repository or on IRC.";
Utils::showMessageBox("Warning", alphaWarning, true);
config()->set(Config::warnOnAlpha, false);
}
@@ -282,6 +282,9 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx, &AppContext::onPreferredFiatCurrencyChanged);
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, ui->sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged));
// CCS/Reddit widget
connect(m_windowSettings, &Settings::homeWidgetChanged, this, &MainWindow::homeWidgetChanged);
// Skin
connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged);
@@ -302,10 +305,14 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
});
// History
connect(ui->historyWidget, &HistoryWidget::spendProof, [&](const QString &txid){
TxProof txproof = m_ctx->currentWallet->getSpendProof(txid, "");
Utils::copyToClipboard(txproof.proof);
});
connect(ui->historyWidget, &HistoryWidget::viewOnBlockExplorer, this, &MainWindow::onViewOnBlockExplorer);
connect(ui->historyWidget, &HistoryWidget::resendTransaction, this, &MainWindow::onResendTransaction);
// Contacts
connect(ui->contactWidget, &ContactsWidget::addContact, this, &MainWindow::onAddContact);
connect(ui->contactWidget, &ContactsWidget::fillAddress, ui->sendWidget, &SendWidget::fillAddress);
// Open alias
@@ -399,7 +406,6 @@ void MainWindow::initMenu() {
ui->actionClose->setShortcut(QKeySequence("Ctrl+W"));
ui->actionShow_debug_info->setShortcut(QKeySequence("Ctrl+D"));
ui->actionSettings->setShortcut(QKeySequence("Ctrl+Alt+S"));
ui->actionUpdate_balance->setShortcut(QKeySequence("Ctrl+U"));
// hide/show tabs
m_tabShowHideSignalMapper = new QSignalMapper(this);
@@ -412,18 +418,26 @@ void MainWindow::initMenu() {
m_tabShowHideMapper["Calc"] = new ToggleTab(ui->tabCalc, "Calc", "Calc", ui->actionShow_calc, Config::showTabCalc);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_calc, "Calc");
#if defined(HAS_XMRTO)
connect(ui->actionShow_Exchange, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Exchange"] = new ToggleTab(ui->tabExchange, "Exchange", "Exchange", ui->actionShow_Exchange, Config::showTabExchange);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_Exchange, "Exchange");
#if defined(HAS_MORPHTOKEN)
connect(ui->actionShow_MorphToken, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["MorphToken"] = new ToggleTab(ui->tabMorphToken, "MorphToken", "MorphToken", ui->actionShow_MorphToken, Config::showTabMorphToken);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_MorphToken, "MorphToken");
#else
ui->actionShow_Exchanges->setVisible(false);
ui->actionShow_MorphToken->setVisible(false);
#endif
#if defined(XMRTO)
connect(ui->actionShow_xmr_to, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["XMRto"] = new ToggleTab(ui->tabXmrTo, "XMRto", "XMR.to", ui->actionShow_xmr_to, Config::showTabXMRto);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_xmr_to, "XMRto");
#else
ui->actionShow_xmr_to->setVisible(false);
#endif
#if defined(HAS_XMRIG)
connect(ui->actionShow_XMRig, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Mining"] = new ToggleTab(ui->tabXmrRig, "Mining", "Mining", ui->actionShow_XMRig, Config::showTabXMRig);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_XMRig, "Mining");
m_tabShowHideMapper["XMRig"] = new ToggleTab(ui->tabXmrRig, "XMRig", "XMRig", ui->actionShow_XMRig, Config::showTabXMRig);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_XMRig, "XMRig");
#else
ui->actionShow_XMRig->setVisible(false);
#endif
@@ -442,16 +456,15 @@ void MainWindow::initMenu() {
connect(ui->actionPassword, &QAction::triggered, this, &MainWindow::showPasswordDialog);
connect(ui->actionKeys, &QAction::triggered, this, &MainWindow::showKeysDialog);
connect(ui->actionViewOnly, &QAction::triggered, this, &MainWindow::showViewOnlyDialog);
connect(ui->actionStore_wallet, &QAction::triggered, [this]{
connect(ui->actionStore_wallet, &QAction::triggered, [&]{
m_ctx->currentWallet->store();
});
connect(ui->actionRefresh_tabs, &QAction::triggered, [this]{
connect(ui->actionRefresh_tabs, &QAction::triggered, [&]{
m_ctx->refreshModels();
});
connect(ui->actionUpdate_balance, &QAction::triggered, [this]{
connect(ui->actionUpdate_balance, &QAction::triggered, [&]{
m_ctx->updateBalance();
});
connect(ui->actionRescan_spent, &QAction::triggered, this, &MainWindow::rescanSpent);
connect(ui->actionExportKeyImages, &QAction::triggered, this, &MainWindow::exportKeyImages);
connect(ui->actionImportKeyImages, &QAction::triggered, this, &MainWindow::importKeyImages);
connect(ui->actionExportOutputs, &QAction::triggered, this, &MainWindow::exportOutputs);
@@ -459,7 +472,7 @@ void MainWindow::initMenu() {
// set restore height
connect(ui->actionChange_restore_height, &QAction::triggered, this, &MainWindow::showRestoreHeightDialog);
connect(m_ctx, &AppContext::customRestoreHeightSet, [=](int height){
connect(m_ctx, &AppContext::customRestoreHeightSet, [=](unsigned int height){
auto msg = QString("The restore height for this wallet has been set to %1. "
"Please re-open the wallet. Feather will now quit.").arg(height);
QMessageBox::information(this, "Cannot set custom restore height", msg);
@@ -473,7 +486,7 @@ void MainWindow::initMenu() {
if(fn.isEmpty()) return;
if(!fn.endsWith(".csv")) fn += ".csv";
m_ctx->currentWallet->history()->writeCSV(fn);
QMessageBox::information(this, "CSV export", QString("Transaction history exported to %1").arg(fn));
Utils::showMessageBox("CSV export", QString("Transaction history exported to %1").arg(fn), false);
});
// Contact widget
@@ -481,7 +494,7 @@ void MainWindow::initMenu() {
if(m_ctx->currentWallet == nullptr) return;
auto *model = m_ctx->currentWallet->addressBookModel();
if (model->rowCount() <= 0){
QMessageBox::warning(this, "Error", "Addressbook empty");
Utils::showMessageBox("Error", "Addressbook empty", true);
return;
}
@@ -491,7 +504,7 @@ void MainWindow::initMenu() {
qint64 now = QDateTime::currentDateTime().currentMSecsSinceEpoch();
QString fn = QString("%1/monero-contacts_%2.csv").arg(targetDir, QString::number(now / 1000));
if(model->writeCSV(fn))
QMessageBox::information(this, "Address book exported", QString("Address book exported to %1").arg(fn));
Utils::showMessageBox("Address book exported", QString("Address book exported to %1").arg(fn), false);
});
connect(ui->actionImportContactsCSV, &QAction::triggered, this, &MainWindow::importContacts);
@@ -509,8 +522,9 @@ void MainWindow::initMenu() {
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked);
connect(ui->actionDonate_to_Feather, &QAction::triggered, this, &MainWindow::donateButtonClicked);
// Close wallet
// Close / Open
connect(ui->actionClose, &QAction::triggered, this, &MainWindow::menuWalletCloseClicked);
connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::menuWalletOpenClicked);
}
void MainWindow::menuToggleTabVisible(const QString &key){
@@ -523,14 +537,20 @@ void MainWindow::menuToggleTabVisible(const QString &key){
}
void MainWindow::initWidgets() {
int homeWidget = config()->get(Config::homeWidget).toInt();
ui->tabHomeWidget->setCurrentIndex(TabsHome(homeWidget));
auto homeWidget = config()->get(Config::homeWidget).toString();
if(homeWidget == QString("ccs")) {
m_ccsWidget->show();
} else if (homeWidget == "reddit") {
m_redditWidget->show();
} else {
config()->set(Config::homeWidget, "ccs");
m_ccsWidget->show();
}
}
WalletWizard *MainWindow::createWizard(WalletWizard::Page startPage){
auto *wizard = new WalletWizard(m_ctx, startPage, this);
connect(wizard, &WalletWizard::openWallet, m_ctx, &AppContext::onOpenWallet);
connect(wizard, &WalletWizard::defaultWalletDirChanged, m_windowSettings, &Settings::updatePaths);
return wizard;
}
@@ -571,7 +591,7 @@ void MainWindow::touchbarShowWallet() {
}
void MainWindow::onWalletCreatedError(const QString &err) {
QMessageBox::warning(this, "Wallet creation error", err);
Utils::showMessageBox("Wallet creation error", err, true);
this->showWizard(WalletWizard::Page_CreateWallet);
}
@@ -618,9 +638,9 @@ void MainWindow::onWalletOpened() {
this->activateWindow();
this->setEnabled(true);
if(!m_ctx->tor->torConnected)
this->setStatusText("Wallet opened - Starting Tor (may take a while)");
m_statusLabelStatus->setText("Wallet opened - Starting Tor (may take a while)");
else
this->setStatusText("Wallet opened - Searching for node");
m_statusLabelStatus->setText("Wallet opened - Searching for node");
connect(m_ctx->currentWallet, &Wallet::connectionStatusChanged, this, &MainWindow::onConnectionStatusChanged);
@@ -662,47 +682,40 @@ void MainWindow::onWalletOpened() {
this->touchbarShowWallet();
this->updatePasswordIcon();
m_updateBytes.start(100);
m_updateBytes.start(1000);
}
void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) {
void MainWindow::onBalanceUpdated(double balance, double unlocked, const QString &balance_str, const QString &unlocked_str) {
qDebug() << Q_FUNC_INFO;
bool hide = config()->get(Config::hideBalance).toBool();
QString label_str = QString("Balance: %1 XMR").arg(QString::number(spendable / globals::cdiv, 'f'));
if (balance > spendable)
label_str += QString(" (+%1 XMR unconfirmed)").arg(QString::number((balance - spendable) / globals::cdiv, 'f'));
auto label_str = QString("Balance: %1 XMR").arg(unlocked_str);
if(balance > unlocked)
label_str += QString(" (+%1 XMR unconfirmed)").arg(QString::number(balance - unlocked, 'f'));
if (hide)
if (hide) {
label_str = "Balance: HIDDEN";
}
m_statusLabelBalance->setToolTip("Click for details");
m_statusLabelBalance->setText(label_str);
m_balanceWidget->setHidden(hide);
}
void MainWindow::setStatusText(const QString &text) {
m_statusText = text;
if (!m_constructingTransaction)
m_statusLabelStatus->setText(text);
}
void MainWindow::onSynchronized() {
this->updateNetStats();
this->setStatusText("Synchronized");
m_statusLabelStatus->setText("Synchronized");
this->onConnectionStatusChanged(Wallet::ConnectionStatus_Connected);
}
void MainWindow::onBlockchainSync(int height, int target) {
QString blocks = (target >= height) ? QString::number(target - height) : "?";
QString heightText = QString("Blockchain sync: %1 blocks remaining").arg(blocks);
this->setStatusText(heightText);
QString heightText = QString("Blockchain sync: %1/%2").arg(height).arg(target);
m_statusLabelStatus->setText(heightText);
}
void MainWindow::onRefreshSync(int height, int target) {
QString blocks = (target >= height) ? QString::number(target - height) : "?";
QString heightText = QString("Wallet refresh: %1 blocks remaining").arg(blocks);
this->setStatusText(heightText);
QString heightText = QString("Wallet refresh: %1/%2").arg(height).arg(target);
m_statusLabelStatus->setText(heightText);
this->updateNetStats();
}
void MainWindow::onConnectionStatusChanged(int status)
@@ -716,7 +729,7 @@ void MainWindow::onConnectionStatusChanged(int status)
switch(status){
case Wallet::ConnectionStatus_Disconnected:
statusIcon = ":/assets/images/status_disconnected.svg";
this->setStatusText("Disconnected");
m_statusLabelStatus->setText("Disconnected");
break;
case Wallet::ConnectionStatus_Connected:
if (m_ctx->currentWallet->synchronized()) {
@@ -727,11 +740,11 @@ void MainWindow::onConnectionStatusChanged(int status)
break;
case Wallet::ConnectionStatus_Connecting:
statusIcon = ":/assets/images/status_lagging.svg";
this->setStatusText("Connecting to daemon");
m_statusLabelStatus->setText("Connecting to daemon");
break;
case Wallet::ConnectionStatus_WrongVersion:
statusIcon = ":/assets/images/status_disconnected.svg";
this->setStatusText("Incompatible daemon");
m_statusLabelStatus->setText("Incompatible daemon");
break;
default:
statusIcon = ":/assets/images/status_disconnected.svg";
@@ -755,12 +768,12 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QStrin
err = QString("%1 %2").arg(err).arg(tx_err);
qDebug() << Q_FUNC_INFO << err;
QMessageBox::warning(this, "Transactions error", err);
Utils::showMessageBox("Transaction error", err, true);
m_ctx->currentWallet->disposeTransaction(tx);
} else if (tx->txCount() == 0) {
err = QString("%1 %2").arg(err).arg("No unmixable outputs to sweep.");
qDebug() << Q_FUNC_INFO << err;
QMessageBox::warning(this, "Transaction error", err);
Utils::showMessageBox("Transaction error", err, true);
m_ctx->currentWallet->disposeTransaction(tx);
} else {
const auto &description = m_ctx->tmpTxDescription;
@@ -833,11 +846,9 @@ void MainWindow::create_status_bar() {
m_statusLabelNetStats->setTextInteractionFlags(Qt::TextSelectableByMouse);
this->statusBar()->addWidget(m_statusLabelNetStats);
m_statusLabelBalance = new ClickableLabel(this);
m_statusLabelBalance->setText("Balance: 0.00 XMR");
m_statusLabelBalance = new QLabel("Balance: 0.00 XMR", this);
m_statusLabelBalance->setTextInteractionFlags(Qt::TextSelectableByMouse);
this->statusBar()->addPermanentWidget(m_statusLabelBalance);
connect(m_statusLabelBalance, &ClickableLabel::clicked, this, &MainWindow::showBalanceDialog);
m_statusBtnConnectionStatusIndicator = new StatusBarButton(QIcon(":/assets/images/status_disconnected.svg"), "Connection status");
connect(m_statusBtnConnectionStatusIndicator, &StatusBarButton::clicked, this, &MainWindow::showConnectionStatusDialog);
@@ -988,12 +999,12 @@ void MainWindow::menuWalletOpenClicked() {
QFileInfo infoPath(path);
if(!infoPath.isReadable()) {
QMessageBox::warning(this, "Cannot read wallet file", "Permission error.");
Utils::showMessageBox("Cannot read wallet file", "Permission error.", true);
return;
}
if(path == m_ctx->walletPath) {
QMessageBox::warning(this, "Wallet already opened", "Please open another wallet.");
Utils::showMessageBox("Wallet already opened", "Please open another wallet.", true);
return;
}
@@ -1035,6 +1046,16 @@ void MainWindow::skinChanged(const QString &skinName) {
qDebug() << QString("Skin changed to %1").arg(skinName);
}
void MainWindow::homeWidgetChanged(const QString &widgetName) {
if(widgetName == "ccs"){
m_ccsWidget->show();
m_redditWidget->hide();
} else if(widgetName == "reddit") {
m_ccsWidget->hide();
m_redditWidget->show();
}
}
void MainWindow::closeEvent(QCloseEvent *event) {
cleanupBeforeClose();
@@ -1042,11 +1063,11 @@ void MainWindow::closeEvent(QCloseEvent *event) {
}
void MainWindow::donateButtonClicked() {
double donation = AppContext::prices->convert("EUR", "XMR", m_ctx->donationAmount);
if (donation <= 0)
donation = 0.1337;
double donation = AppContext::prices->convert("EUR", "XMR", m_ctx->featherDonationAmount);
if(donation <= 0)
donation = 1.337;
ui->sendWidget->fill(m_ctx->donationAddress, "Donation to the Feather development team", donation);
ui->sendWidget->fill(m_ctx->featherDonationAddress, "Donation to the Feather development team", donation);
ui->tabWidget->setCurrentIndex(Tabs::SEND);
}
@@ -1071,21 +1092,17 @@ void MainWindow::showSendScreen(const CCSEntry &entry) {
void MainWindow::onViewOnBlockExplorer(const QString &txid) {
QString blockExplorerLink = Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), m_ctx->networkType, txid);
Utils::externalLinkWarning(this, blockExplorerLink);
Utils::externalLinkWarning(blockExplorerLink);
}
void MainWindow::onResendTransaction(const QString &txid) {
if (!AppContext::txCache.contains(txid)) {
QMessageBox::warning(this, "Unable to resend transaction", "Transaction was not found in transaction cache. Unable to resend.");
return;
void MainWindow::onAddContact(const QString &address, const QString &name) {
bool addressValid = WalletManager::addressValid(address, m_ctx->currentWallet->nettype());
if (!addressValid)
QMessageBox::warning(this, "Invalid address", "Invalid address");
else {
m_ctx->currentWallet->addressBook()->addRow(address, "", name);
m_ctx->storeWallet();
}
// Connect to a different node so chances of successful relay are higher
m_ctx->nodes->autoConnect(true);
auto dialog = new BroadcastTxDialog(this, m_ctx, AppContext::txCache[txid]);
dialog->exec();
dialog->deleteLater();
}
void MainWindow::importContacts() {
@@ -1326,29 +1343,6 @@ void MainWindow::updateNetStats() {
m_statusLabelNetStats->setText(QString("(D: %1)").arg(Utils::formatBytes(m_ctx->currentWallet->getBytesReceived())));
}
void MainWindow::rescanSpent() {
if (!m_ctx->currentWallet->rescanSpent()) {
QMessageBox::warning(this, "Rescan spent", m_ctx->currentWallet->errorString());
} else {
QMessageBox::information(this, "Rescan spent", "Successfully rescanned spent outputs.");
}
}
void MainWindow::showBalanceDialog() {
if (!m_ctx->currentWallet) {
return;
}
auto *dialog = new BalanceDialog(this, m_ctx->currentWallet);
dialog->exec();
dialog->deleteLater();
}
QString MainWindow::statusDots() {
m_statusDots++;
m_statusDots = m_statusDots % 4;
return QString(".").repeated(m_statusDots);
}
MainWindow::~MainWindow() {
delete ui;
}
+12 -18
View File
@@ -76,15 +76,11 @@ public:
RECEIVE,
COINS,
CALC,
EXCHANGES,
MORPHTOKEN,
XMR_TO,
XMRIG
};
enum TabsHome {
CCS,
REDDIT
};
public slots:
void initWidgets();
void initMenu();
@@ -106,6 +102,7 @@ public slots:
void showSendTab();
void showHistoryTab();
void showSendScreen(const CCSEntry &entry);
void homeWidgetChanged(const QString &widgetName);
void skinChanged(const QString &skinName);
void menuTorClicked();
void onBlockchainSync(int height, int target);
@@ -117,7 +114,7 @@ public slots:
void menuWalletOpenClicked();
void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path);
void onViewOnBlockExplorer(const QString &txid);
void onResendTransaction(const QString &txid);
void onAddContact(const QString &address, const QString &name);
void importContacts();
void showRestoreHeightDialog();
void importTransaction();
@@ -133,7 +130,7 @@ public slots:
void loadSignedTxFromText();
// libwalletqt
void onBalanceUpdated(quint64 balance, quint64 spendable);
void onBalanceUpdated(double balance, double unlocked, const QString &balance_str, const QString &unlocked_str);
void onSynchronized();
void onWalletOpened();
void onWalletClosed();
@@ -167,20 +164,22 @@ private:
void touchbarShowWallet();
void updatePasswordIcon();
void updateNetStats();
void rescanSpent();
void setStatusText(const QString &text);
void showBalanceDialog();
QString statusDots();
WalletWizard *createWizard(WalletWizard::Page startPage);
Ui::MainWindow *ui;
Settings *m_windowSettings = nullptr;
CalcWindow *m_windowCalc = nullptr;
SignVerifyDialog *m_windowSignVerify = nullptr;
RestoreDialog *m_restoreDialog = nullptr;
AboutDialog *m_aboutDialog = nullptr;
XMRigWidget *m_xmrig = nullptr;
bool m_windowSpawned = false;
CCSWidget *m_ccsWidget = nullptr;
RedditWidget *m_redditWidget = nullptr;
QSystemTrayIcon *m_trayIcon;
QMenu m_trayMenu;
QAction *m_trayActionCalc;
@@ -192,7 +191,7 @@ private:
TickerWidget *m_balanceWidget;
// lower status bar
ClickableLabel *m_statusLabelBalance;
QLabel *m_statusLabelBalance;
QLabel *m_statusLabelStatus;
QLabel *m_statusLabelNetStats;
StatusBarButton *m_statusBtnConnectionStatusIndicator;
@@ -218,11 +217,6 @@ private:
QTimer m_updateBytes;
QString m_statusText;
int m_statusDots;
bool m_constructingTransaction = false;
QTimer m_txTimer;
private slots:
void menuToggleTabVisible(const QString &key);
};
+70 -106
View File
@@ -87,65 +87,31 @@
</layout>
</item>
<item>
<widget class="Line" name="line">
<widget class="Line" name="homeLine">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabHomeWidget">
<property name="currentIndex">
<number>0</number>
<layout class="QHBoxLayout" name="coolLayout">
<property name="spacing">
<number>9</number>
</property>
<property name="documentMode">
<bool>true</bool>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>CCS</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="leftMargin">
<number>0</number>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="topMargin">
<number>0</number>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>40</height>
</size>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="CCSWidget" name="ccsWidget" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>/r/Monero</string>
</attribute>
<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="RedditWidget" name="redditWidget" native="true"/>
</item>
</layout>
</widget>
</widget>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
@@ -261,7 +227,7 @@
<widget class="QWidget" name="tabCalc">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/gnome-calc.png</normaloff>:/assets/images/gnome-calc.png</iconset>
<normaloff>:/assets/images/coldcard.png</normaloff>:/assets/images/coldcard.png</iconset>
</attribute>
<attribute name="title">
<string>Calc</string>
@@ -272,15 +238,29 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tabExchange">
<widget class="QWidget" name="tabMorphToken">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/update.png</normaloff>:/assets/images/update.png</iconset>
<normaloff>:/assets/images/morphtoken.png</normaloff>:/assets/images/morphtoken.png</iconset>
</attribute>
<attribute name="title">
<string>Exchange</string>
<string>MorphToken</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_8">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="MorphTokenWidget" name="morphtokenWidget" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabXmrTo">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/xmrto.png</normaloff>:/assets/images/xmrto.png</iconset>
</attribute>
<attribute name="title">
<string>XMR.to</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
@@ -291,50 +271,36 @@
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
<number>1</number>
</property>
<item>
<widget class="QTabWidget" name="tabExchanges">
<property name="currentIndex">
<number>0</number>
<widget class="XMRToWidget" name="xmrToWidget" native="true"/>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="tabXMRto">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/xmrto.png</normaloff>:/assets/images/xmrto.png</iconset>
</attribute>
<attribute name="title">
<string>XMR.to</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_10">
<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="XMRToWidget" name="xmrToWidget" native="true"/>
</item>
</layout>
</widget>
</widget>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabXmrRig">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/mining.png</normaloff>:/assets/images/mining.png</iconset>
<normaloff>:/assets/images/xmrig.ico</normaloff>:/assets/images/xmrig.ico</iconset>
</attribute>
<attribute name="title">
<string>Mining</string>
<string>XMRig</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
@@ -360,6 +326,7 @@
<property name="title">
<string>File</string>
</property>
<addaction name="actionOpen"/>
<addaction name="actionClose"/>
<addaction name="actionQuit"/>
<addaction name="separator"/>
@@ -403,7 +370,6 @@
<addaction name="actionStore_wallet"/>
<addaction name="actionUpdate_balance"/>
<addaction name="actionRefresh_tabs"/>
<addaction name="actionRescan_spent"/>
<addaction name="separator"/>
<addaction name="actionChange_restore_height"/>
<addaction name="menuExport"/>
@@ -465,7 +431,8 @@
</property>
<addaction name="actionShow_Coins"/>
<addaction name="actionShow_calc"/>
<addaction name="actionShow_Exchange"/>
<addaction name="actionShow_MorphToken"/>
<addaction name="actionShow_xmr_to"/>
<addaction name="actionShow_XMRig"/>
</widget>
<addaction name="menuFile"/>
@@ -474,6 +441,14 @@
<addaction name="menuTools"/>
<addaction name="menuHelp"/>
</widget>
<action name="actionOpen">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Open wallet</string>
</property>
</action>
<action name="actionQuit">
<property name="text">
<string>Quit application</string>
@@ -666,7 +641,7 @@
</action>
<action name="actionShow_XMRig">
<property name="text">
<string>Show Mining</string>
<string>Show XMRig</string>
</property>
</action>
<action name="actionImportTransaction">
@@ -704,14 +679,9 @@
<string>Import transaction</string>
</property>
</action>
<action name="actionShow_Exchange">
<action name="actionShow_MorphToken">
<property name="text">
<string>Show Exchange</string>
</property>
</action>
<action name="actionRescan_spent">
<property name="text">
<string>Rescan spent</string>
<string>Show MorphToken</string>
</property>
</action>
</widget>
@@ -759,15 +729,9 @@
<container>1</container>
</customwidget>
<customwidget>
<class>CCSWidget</class>
<class>MorphTokenWidget</class>
<extends>QWidget</extends>
<header>widgets/ccswidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>RedditWidget</class>
<extends>QWidget</extends>
<header>widgets/redditwidget.h</header>
<header>MorphTokenWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
+2
View File
@@ -22,6 +22,8 @@ void CCSModel::updateEntries(const QList<QSharedPointer<CCSEntry>>& entries) {
m_entries.clear();
for (const auto& entry : entries) {
if (entry->raised_amount > entry->target_amount)
continue;
m_entries.push_back(entry);
}
+5
View File
@@ -180,7 +180,12 @@ QVariant CoinsModel::parseTransactionInfo(const CoinsInfo &cInfo, int column, in
case Address:
return ModelUtils::displayAddress(cInfo.address(), 1, "");
case AddressLabel:
{
if (cInfo.subaddrIndex() == 0) {
return cInfo.coinbase() ? "Coinbase" : "Change";
}
return cInfo.addressLabel();
}
case Spent:
return cInfo.spent();
case SpentHeight:
+1 -1
View File
@@ -5,7 +5,7 @@
#include <utils/nodes.h>
#include "appcontext.h"
NodeModel::NodeModel(int nodeSource, QObject *parent)
NodeModel::NodeModel(unsigned int nodeSource, QObject *parent)
: QAbstractTableModel(parent)
, m_nodeSource(nodeSource)
, m_offline(QIcon(":/assets/images/expired_icon.png"))
+2 -2
View File
@@ -19,7 +19,7 @@ public:
COUNT
};
explicit NodeModel(int nodeSource, QObject *parent = nullptr);
explicit NodeModel(unsigned int nodeSource, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
@@ -34,7 +34,7 @@ private:
QList<FeatherNode> m_nodes;
QIcon m_offline;
QIcon m_online;
int m_nodeSource;
unsigned int m_nodeSource;
};
#endif //FEATHER_NODEMODEL_H
+2 -2
View File
@@ -169,7 +169,7 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
usd_amount = AppContext::prices->convert("USD", this->preferredFiatSymbol, usd_amount);
double fiat_rounded = ceil(Utils::roundSignificant(usd_amount, 3) * 100.0) / 100.0;
return QString("%1").arg(Utils::amountToCurrencyString(fiat_rounded, this->preferredFiatSymbol));
return QString("%1%2").arg(this->preferredFiatSign).arg(QString::number(fiat_rounded));
}
default:
{
@@ -239,4 +239,4 @@ Qt::ItemFlags TransactionHistoryModel::flags(const QModelIndex &index) const {
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
return QAbstractTableModel::flags(index);
}
}
+9 -24
View File
@@ -12,7 +12,6 @@ SendWidget::SendWidget(QWidget *parent) :
ui(new Ui::SendWidget)
{
ui->setupUi(this);
m_ctx = MainWindow::getContext();
QString amount_rx = R"(^\d{0,8}[\.,]\d{0,12}|(all)$)";
QRegExp rx;
@@ -31,16 +30,6 @@ SendWidget::SendWidget(QWidget *parent) :
ui->label_conversionAmount->hide();
ui->btn_openAlias->hide();
ui->label_PayTo->setHelpText("Recipient of the funds.\n\n"
"You may enter a Monero address, or an alias (email-like address that forwards to a Monero address)");
ui->label_Description->setHelpText("Description of the transaction (optional).\n\n"
"The description is not sent to the recipient of the funds. It is stored in your wallet cache, "
"and displayed in the 'History' tab.");
ui->label_Amount->setHelpText("Amount to be sent.\n\nThis is the exact amount the recipient will receive. "
"In addition to this amount a transaction fee will be subtracted from your balance. "
"You will be able to review the transaction fee before the transaction is broadcast.\n\n"
"To send all your balance, click the Max button to the right.");
this->setupComboBox();
}
@@ -66,13 +55,17 @@ void SendWidget::fill(double amount) {
ui->lineAmount->setText(QString::number(amount));
}
void SendWidget::fill(const QString &address, const QString &description, double amount) {
void SendWidget::fill(const QString &address, const QString& description){
ui->lineDescription->setText(description);
ui->lineAddress->setText(address);
ui->lineAddress->setCursorPosition(0);
if (amount > 0)
ui->lineAmount->setText(QString::number(amount));
this->updateConversionLabel();
}
void SendWidget::fill(const QString &address, const QString &description, double amount) {
ui->lineDescription->setText(description);
ui->lineAmount->setText(QString::number(amount));
ui->lineAddress->setText(address);
ui->lineAddress->setCursorPosition(0);
}
void SendWidget::fillAddress(const QString &address) {
@@ -81,14 +74,7 @@ void SendWidget::fillAddress(const QString &address) {
}
void SendWidget::sendClicked() {
if (m_ctx->currentWallet->connectionStatus() != Wallet::ConnectionStatus_Connected) {
QMessageBox::warning(this, "Error", "Unable to create transaction:\n\n"
"Wallet is not connected to a node.\n"
"Go to File -> Settings -> Node to manually connect to a node.");
return;
}
double amount;
double amount = 0.0;
QString currency = ui->comboCurrencySelection->currentText();
QString recipient = ui->lineAddress->text().simplified().remove(' ');
QString description = ui->lineDescription->text();
@@ -191,7 +177,6 @@ void SendWidget::clearFields() {
void SendWidget::onWalletClosed() {
this->clearFields();
ui->btnSend->setEnabled(true);
}
void SendWidget::onInitiateTransaction() {
+2 -1
View File
@@ -19,7 +19,8 @@ Q_OBJECT
public:
explicit SendWidget(QWidget *parent = nullptr);
void fill(const CCSEntry &entry);
void fill(const QString &address, const QString& description, double amount = 0);
void fill(const QString &address, const QString& description);
void fill(const QString &address, const QString& description, double amount);
void fill(double amount);
void clearFields();
~SendWidget() override;
+4 -11
View File
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>647</width>
<width>603</width>
<height>175</height>
</rect>
</property>
@@ -39,7 +39,7 @@
<number>0</number>
</property>
<item row="0" column="0">
<widget class="HelpLabel" name="label_PayTo">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Pay to</string>
</property>
@@ -53,7 +53,7 @@
</widget>
</item>
<item row="1" column="0">
<widget class="HelpLabel" name="label_Description">
<widget class="QLabel" name="label">
<property name="text">
<string>Description</string>
</property>
@@ -63,7 +63,7 @@
<widget class="QLineEdit" name="lineDescription"/>
</item>
<item row="2" column="0">
<widget class="HelpLabel" name="label_Amount">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Amount</string>
</property>
@@ -184,13 +184,6 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>HelpLabel</class>
<extends>QLabel</extends>
<header>components.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
<slots>
+17 -18
View File
@@ -7,8 +7,6 @@
#include "utils/config.h"
#include "mainwindow.h"
#include <QFileDialog>
Settings::Settings(QWidget *parent) :
QDialog(parent),
ui(new Ui::Settings)
@@ -40,11 +38,16 @@ Settings::Settings(QWidget *parent) :
ui->checkBox_hideBalance->setChecked(config()->get(Config::hideBalance).toBool());
// setup comboboxes
auto settingsHomeWidget = config()->get(Config::homeWidget).toString();
if (m_homeWidgets.contains(settingsHomeWidget))
ui->comboBox_homeWidget->setCurrentIndex(m_homeWidgets.indexOf(settingsHomeWidget));
this->setupSkinCombobox();
auto settingsSkin = config()->get(Config::skin).toString();
if (m_skins.contains(settingsSkin))
ui->comboBox_skin->setCurrentIndex(m_skins.indexOf(settingsSkin));
connect(ui->comboBox_homeWidget, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_homeWidgetChanged);
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);
@@ -60,26 +63,16 @@ Settings::Settings(QWidget *parent) :
connect(ui->comboBox_fiatCurrency, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::fiatCurrencySelected);
// setup paths tab
this->updatePaths();
connect(ui->btn_browseDefaultWalletDir, &QPushButton::clicked, [this]{
QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", m_ctx->defaultWalletDir, QFileDialog::ShowDirsOnly);
if (walletDir.isEmpty()) return;
m_ctx->defaultWalletDir = walletDir;
m_ctx->defaultWalletDirRoot = walletDir;
config()->set(Config::walletDirectory, walletDir);
ui->lineEdit_defaultWalletDir->setText(m_ctx->defaultWalletDir);
});
// setup path tab
#if defined(Q_OS_LINUX)
ui->textEditPaths->appendPlainText(m_ctx->applicationPath);
#endif
ui->textEditPaths->appendPlainText(m_ctx->configDirectory);
ui->textEditPaths->appendPlainText(m_ctx->defaultWalletDir);
this->adjustSize();
}
void Settings::updatePaths() {
ui->lineEdit_defaultWalletDir->setText(m_ctx->defaultWalletDir);
ui->lineEdit_configDir->setText(m_ctx->configDirectory);
ui->lineEdit_applicationDir->setText(m_ctx->applicationPath);
}
void Settings::fiatCurrencySelected(int index) {
QString selection = ui->comboBox_fiatCurrency->itemText(index);
config()->set(Config::preferredFiatCurrency, selection);
@@ -90,6 +83,12 @@ void Settings::comboBox_skinChanged(int pos) {
emit skinChanged(m_skins.at(pos));
}
void Settings::comboBox_homeWidgetChanged(int pos) {
config()->set(Config::homeWidget, m_homeWidgets.at(pos));
emit homeWidgetChanged(m_homeWidgets.at(pos));
}
void Settings::comboBox_blockExplorerChanged(int pos) {
QString blockExplorer = ui->comboBox_blockExplorer->currentText();
config()->set(Config::blockExplorer, blockExplorer);
+3 -1
View File
@@ -26,19 +26,21 @@ public:
signals:
void closed();
void preferredFiatCurrencyChanged(QString currency);
void homeWidgetChanged(QString widgetName);
void skinChanged(QString skinName);
void showHomeCCS(bool);
void blockExplorerChanged(QString blockExplorer);
public slots:
void updatePaths();
void copyToClipboard();
void checkboxExternalLinkWarn();
void fiatCurrencySelected(int index);
void comboBox_homeWidgetChanged(int pos);
void comboBox_skinChanged(int pos);
void comboBox_blockExplorerChanged(int post);
private:
const QStringList m_homeWidgets{"ccs", "reddit"};
QStringList m_skins{"Native", "QDarkStyle", "Breeze/Dark", "Breeze/Light"};
private:
+32 -74
View File
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1123</width>
<height>555</height>
<width>1019</width>
<height>358</height>
</rect>
</property>
<property name="windowTitle">
@@ -121,23 +121,44 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Home screen widget:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox_homeWidget">
<item>
<property name="text">
<string>CCS proposals</string>
</property>
</item>
<item>
<property name="text">
<string>/r/monero</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Appearance:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_skin"/>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Block explorer:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_blockExplorer">
<item>
<property name="text">
@@ -161,14 +182,14 @@
</item>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QCheckBox" name="checkBox_externalLink">
<property name="text">
<string>Warn before opening external link</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QCheckBox" name="checkBox_hideBalance">
<property name="text">
<string>Hide balance</string>
@@ -282,74 +303,11 @@
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="lineEdit_defaultWalletDir">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_browseDefaultWalletDir">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Wallet directory:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Config directory:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_configDir">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Application directory:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_applicationDir">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
<widget class="QPlainTextEdit" name="textEditPaths">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</widget>
</item>
</layout>
</widget>
+93
View File
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "MorphTokenApi.h"
MorphTokenApi::MorphTokenApi(QObject *parent, UtilsNetworking *network, QString baseUrl)
: QObject(parent)
, m_network(network)
, m_baseUrl(std::move(baseUrl))
{
}
void MorphTokenApi::createTrade(const QString &inputAsset, const QString &outputAsset, const QString &refundAddress, const QString &outputAddress) {
QJsonObject trade;
QJsonObject input;
input["asset"] = inputAsset;
input["refund"] = refundAddress;
QJsonArray output;
QJsonObject outputObj;
outputObj["asset"] = outputAsset;
outputObj["weight"] = 10000;
outputObj["address"] = outputAddress;
output.append(outputObj);
trade["input"] = input;
trade["output"] = output;
QString url = QString("%1/morph").arg(m_baseUrl);
QNetworkReply *reply = m_network->postJson(url, trade);
connect(reply, &QNetworkReply::finished, std::bind(&MorphTokenApi::onResponse, this, reply, Endpoint::CREATE_TRADE));
}
void MorphTokenApi::getTrade(const QString &morphId) {
QString url = QString("%1/morph/%2").arg(m_baseUrl, morphId);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&MorphTokenApi::onResponse, this, reply, Endpoint::GET_TRADE));
}
void MorphTokenApi::getRates() {
QString url = QString("%1/rates").arg(m_baseUrl);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&MorphTokenApi::onResponse, this, reply, Endpoint::GET_RATES));
}
void MorphTokenApi::getLimits(const QString &inputAsset, const QString &outputAsset) {
QJsonObject limits;
QJsonObject input;
input["asset"] = inputAsset;
QJsonArray output;
QJsonObject outputObj;
outputObj["asset"] = outputAsset;
outputObj["weight"] = 10000;
output.append(outputObj);
limits["input"] = input;
limits["output"] = output;
QString url = QString("%1/limits").arg(m_baseUrl);
QNetworkReply *reply = m_network->postJson(url, limits);
connect(reply, &QNetworkReply::finished, std::bind(&MorphTokenApi::onResponse, this, reply, Endpoint::GET_LIMITS));
}
void MorphTokenApi::onResponse(QNetworkReply *reply, Endpoint endpoint) {
const auto ok = reply->error() == QNetworkReply::NoError;
const auto err = reply->errorString();
QByteArray data = reply->readAll();
QJsonObject obj;
if (!data.isEmpty() && Utils::validateJSON(data)) {
auto doc = QJsonDocument::fromJson(data);
obj = doc.object();
}
else if (!ok) {
emit ApiResponse(MorphTokenResponse(false, endpoint, err, {}));
return;
}
else {
emit ApiResponse(MorphTokenResponse(false, endpoint, "Invalid response from MorphToken", {}));
return;
}
if (obj.contains("success")) {
emit ApiResponse(MorphTokenResponse(false, endpoint, obj.value("description").toString(), obj));
return;
}
reply->deleteLater();
emit ApiResponse(MorphTokenResponse(true, endpoint, "", obj));
}
+51
View File
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_MORPHTOKENAPI_H
#define FEATHER_MORPHTOKENAPI_H
#include <QObject>
#include <utility>
#include "utils/networking.h"
class MorphTokenApi : public QObject {
Q_OBJECT
public:
enum Endpoint {
CREATE_TRADE = 0,
GET_TRADE,
GET_RATES,
GET_LIMITS
};
struct MorphTokenResponse {
explicit MorphTokenResponse(bool ok, Endpoint endpoint, QString message, QJsonObject obj)
: ok(ok), endpoint(endpoint), message(std::move(message)), obj(std::move(obj)) {};
bool ok;
Endpoint endpoint;
QString message;
QJsonObject obj;
};
explicit MorphTokenApi(QObject *parent, UtilsNetworking *network, QString baseUrl = "https://api.morphtoken.com");
void createTrade(const QString &inputAsset, const QString &outputAsset, const QString &refundAddress, const QString &outputAddress);
void getTrade(const QString &morphId);
void getRates();
void getLimits(const QString &inputAsset, const QString &outputAsset);
signals:
void ApiResponse(MorphTokenResponse resp);
private slots:
void onResponse(QNetworkReply *reply, Endpoint endpoint);
private:
QString m_baseUrl;
UtilsNetworking *m_network;
};
#endif //FEATHER_MORPHTOKENAPI_H
+2 -1
View File
@@ -41,7 +41,8 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::nodeSource,{QS("nodeSource"), 0}},
{Config::useOnionNodes,{QS("useOnionNodes"), false}},
{Config::showTabCoins,{QS("showTabCoins"), false}},
{Config::showTabExchange, {QS("showTabExchange"), true}},
{Config::showTabMorphToken, {QS("showTabMorphToken"), false}},
{Config::showTabXMRto,{QS("showTabXMRto"), true}},
{Config::showTabXMRig,{QS("showTabXMRig"), false}},
{Config::showTabCalc,{QS("showTabCalc"), true}},
{Config::geometry, {QS("geometry"), {}}},
+2 -1
View File
@@ -39,7 +39,8 @@ public:
nodeSource,
useOnionNodes,
showTabCoins,
showTabExchange,
showTabMorphToken,
showTabXMRto,
showTabCalc,
showTabXMRig,
geometry,
+20 -34
View File
@@ -19,7 +19,7 @@
using namespace std::chrono;
WalletKeysFiles::WalletKeysFiles(const QFileInfo &info, int networkType, QString address) :
WalletKeysFiles::WalletKeysFiles(const QFileInfo &info, quint8 networkType, QString address) :
m_fileName(info.fileName()),
m_modified(info.lastModified().toSecsSinceEpoch()),
m_path(QDir::toNativeSeparators(info.absoluteFilePath())),
@@ -42,15 +42,23 @@ QString WalletKeysFiles::path() const {
return m_path;
}
int WalletKeysFiles::networkType() const {
quint8 WalletKeysFiles::networkType() const {
return m_networkType;
}
WalletKeysFilesModel::WalletKeysFilesModel(AppContext *ctx, QObject *parent)
: QAbstractTableModel(parent)
, m_ctx(ctx)
{
this->updateDirectories();
WalletKeysFilesModel::WalletKeysFilesModel(AppContext *ctx, QObject *parent) :
QAbstractTableModel(parent),
m_ctx(ctx) {
this->walletDirectories << m_ctx->defaultWalletDirRoot;
auto walletPath = config()->get(Config::walletPath).toString();
if(!walletPath.isEmpty() && Utils::fileExists(walletPath)) {
QDir d = QFileInfo(walletPath).absoluteDir();
this->walletDirectories << d.absolutePath();
}
this->walletDirectories << m_ctx->homeDir;
this->walletDirectories.removeDuplicates();
this->m_walletKeysFilesItemModel = qobject_cast<QAbstractItemModel *>(this);
}
@@ -63,20 +71,6 @@ void WalletKeysFilesModel::clear() {
void WalletKeysFilesModel::refresh() {
this->clear();
this->findWallets();
endResetModel();
}
void WalletKeysFilesModel::updateDirectories() {
this->walletDirectories.clear();
this->walletDirectories << m_ctx->defaultWalletDirRoot;
auto walletPath = config()->get(Config::walletPath).toString();
if(!walletPath.isEmpty() && Utils::fileExists(walletPath)) {
QDir d = QFileInfo(walletPath).absoluteDir();
this->walletDirectories << d.absolutePath();
}
this->walletDirectories << m_ctx->homeDir;
this->walletDirectories.removeDuplicates();
}
void WalletKeysFilesModel::findWallets() {
@@ -139,7 +133,7 @@ int WalletKeysFilesModel::rowCount(const QModelIndex & parent) const {
}
int WalletKeysFilesModel::columnCount(const QModelIndex &) const {
return 4;
return 3;
}
QVariant WalletKeysFilesModel::data(const QModelIndex &index, int role) const {
@@ -168,15 +162,13 @@ QVariant WalletKeysFilesModel::data(const QModelIndex &index, int role) const {
#endif
return fp;
}
case ModelColumns::Modified:
return walletKeyFile.modified();
default:
break;
}
} else if(role == Qt::UserRole) {
switch(index.column()) {
case ModelColumns::NetworkType:
return static_cast<int>(walletKeyFile.networkType());
return static_cast<uint>(walletKeyFile.networkType());
case ModelColumns::FileName:
return walletKeyFile.fileName();
case ModelColumns::Modified:
@@ -209,15 +201,9 @@ QVariant WalletKeysFilesModel::headerData(int section, Qt::Orientation orientati
return QVariant();
}
WalletKeysFilesProxyModel::WalletKeysFilesProxyModel(QObject *parent, NetworkType::Type nettype)
: QSortFilterProxyModel(parent)
, m_nettype(nettype)
{
}
WalletKeysFilesProxyModel::WalletKeysFilesProxyModel(QObject *parent) :
QSortFilterProxyModel(parent) {}
bool WalletKeysFilesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
QModelIndex nettypeIndex = sourceModel()->index(sourceRow, WalletKeysFilesModel::NetworkType, sourceParent);
NetworkType::Type nettype = static_cast<NetworkType::Type>(sourceModel()->data(nettypeIndex, Qt::UserRole).toInt());
return (m_nettype == nettype);
return true;
}
+4 -9
View File
@@ -12,19 +12,19 @@
class WalletKeysFiles
{
public:
WalletKeysFiles(const QFileInfo &info, int networkType, QString address);
WalletKeysFiles(const QFileInfo &info, quint8 networkType, QString address);
QString fileName() const;
qint64 modified() const;
QString path() const;
int networkType() const;
quint8 networkType() const;
QString address() const;
private:
QString m_fileName;
qint64 m_modified;
QString m_path;
int m_networkType;
quint8 m_networkType;
QString m_address;
};
@@ -54,8 +54,6 @@ public:
QStringList walletDirectories;
private:
void updateDirectories();
AppContext *m_ctx;
QList<WalletKeysFiles> m_walletKeyFiles;
QAbstractItemModel *m_walletKeysFilesItemModel;
@@ -66,11 +64,8 @@ class WalletKeysFilesProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit WalletKeysFilesProxyModel(QObject *parent, NetworkType::Type nettype);
explicit WalletKeysFilesProxyModel(QObject *parent = nullptr);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private:
NetworkType::Type m_nettype;
};
#endif // KEYSFILES_H
+1 -1
View File
@@ -101,7 +101,7 @@ void Nodes::connectToNode() {
// auto connect
m_wsExhaustedWarningEmitted = false;
m_customExhaustedWarningEmitted = false;
this->autoConnect(true);
this->autoConnect();
}
void Nodes::connectToNode(const FeatherNode &node) {
+25 -25
View File
@@ -19,44 +19,44 @@
struct RestoreHeightLookup {
NetworkType::Type type;
QMap<int, int> data;
QMap<unsigned int, unsigned int> data;
explicit RestoreHeightLookup(NetworkType::Type type) : type(type) {}
int dateToRestoreHeight(int date) {
unsigned int dateToRestoreHeight(unsigned int date) {
// restore height based on a given timestamp using a lookup
// table. If it cannot find the date in the lookup table, it
// will calculate the blockheight based off the last known
// date: ((now - lastKnownDate) / blockTime) - clearance
if(this->type == NetworkType::TESTNET) return 1;
int blockTime = 120;
int blocksPerDay = 86400 / blockTime;
int blockCalcClearance = blocksPerDay * 5;
QList<int> values = this->data.keys();
unsigned int blockTime = 120;
unsigned int blocksPerDay = 86400 / blockTime;
unsigned int blockCalcClearance = blocksPerDay * 5;
QList<unsigned int> values = this->data.keys();
if(date <= values.at(0))
return this->data[values.at(0)];
for(int i = 0; i != values.count(); i++) {
for(unsigned int i = 0; i != values.count(); i++) {
if(values[i] > date) {
return i - 1 < 0 ? this->data[values[i]] : this->data[values[i-1]] - blockCalcClearance;
}
}
// lookup failed, calculate blockheight from last known checkpoint
int lastBlockHeightTime = values.at(values.count() - 1);
int lastBlockHeight = this->data[lastBlockHeightTime];
int deltaTime = date - lastBlockHeightTime;
int deltaBlocks = deltaTime / blockTime;
int blockHeight = (lastBlockHeight + deltaBlocks) - blockCalcClearance;
unsigned int lastBlockHeightTime = values.at(values.count() - 1);
unsigned int lastBlockHeight = this->data[lastBlockHeightTime];
unsigned int deltaTime = date - lastBlockHeightTime;
unsigned int deltaBlocks = deltaTime / blockTime;
unsigned int blockHeight = (lastBlockHeight + deltaBlocks) - blockCalcClearance;
qDebug() << "Calculated blockheight: " << blockHeight << " from epoch " << date;
return blockHeight;
}
int restoreHeightToDate(int height) {
unsigned int restoreHeightToDate(unsigned int height) {
// @TODO: most likely inefficient, refactor
QMap<int, int>::iterator i;
int timestamp = 0;
QMap<unsigned int, unsigned int>::iterator i;
unsigned int timestamp = 0;
for (i = this->data.begin(); i != this->data.end(); ++i) {
int ts = i.key();
unsigned int ts = i.key();
if (i.value() > height)
return timestamp;
timestamp = ts;
@@ -68,7 +68,7 @@ struct RestoreHeightLookup {
// initialize this class using a lookup table, e.g `:/assets/restore_heights_monero_mainnet.txt`/
auto rtn = new RestoreHeightLookup(type);
auto data = Utils::barrayToString(Utils::fileOpen(fn));
QMap<int, int> _data;
QMap<unsigned int, unsigned int> _data;
for(const auto &line: data.split('\n')) {
if(line.trimmed().isEmpty()) continue;
auto spl = line.trimmed().split(':');
@@ -82,7 +82,7 @@ struct FeatherSeed {
QString mnemonicSeed;
QString spendKey;
time_t time = 0;
int restoreHeight = 0;
unsigned int restoreHeight = 0;
RestoreHeightLookup *lookup = nullptr;
QString language;
std::string coinName;
@@ -124,7 +124,7 @@ struct FeatherSeed {
return rtn;
}
Wallet *writeWallet(WalletManager *manager, NetworkType::Type type, const QString &path, const QString &password, quint64 kdfRounds) {
Wallet *writeWallet(WalletManager *manager, NetworkType::Type type, const QString &path, const QString &password, unsigned int kdfRounds) {
// writes both 14/25 word mnemonic seeds.
Wallet *wallet = nullptr;
if(this->lookup == nullptr) return wallet;
@@ -136,25 +136,25 @@ struct FeatherSeed {
this->restoreHeight = _seed.restoreHeight;
this->spendKey = _seed.spendKey;
}
wallet = manager->createDeterministicWalletFromSpendKey(path, password, this->language, type, this->spendKey, this->restoreHeight, kdfRounds);
wallet = manager->createDeterministicWalletFromSpendKey(path, password, this->language, type, this->spendKey, this->restoreHeight, (quint64)kdfRounds);
wallet->setCacheAttribute("feather.seed", this->mnemonicSeed);
} else {
wallet = manager->recoveryWallet(path, password, this->mnemonicSeed, "", type, this->restoreHeight, kdfRounds);
wallet = manager->recoveryWallet(path, password, this->mnemonicSeed, "", type, this->restoreHeight, (quint64) kdfRounds);
}
wallet->setPassword(password);
return wallet;
}
int setRestoreHeight() {
unsigned int setRestoreHeight() {
if(this->lookup == nullptr) return 1;
if(this->time == 0) return 1;
this->restoreHeight = this->lookup->dateToRestoreHeight(this->time);
this->restoreHeight = this->lookup->dateToRestoreHeight((unsigned int)this->time);
return this->restoreHeight;
}
int setRestoreHeight(int height) {
auto now = std::time(nullptr);
unsigned int setRestoreHeight(unsigned int height) {
auto now = (unsigned int)std::time(nullptr);
auto nowClearance = 3600 * 24;
auto currentBlockHeight = this->lookup->dateToRestoreHeight(now - nowClearance);
if(height >= currentBlockHeight + nowClearance) {
+1 -1
View File
@@ -43,7 +43,7 @@ Tor::Tor(AppContext *ctx, QObject *parent)
return;
}
#ifndef HAS_TOR_BIN
#ifndef HAS_TOR
qCritical() << "Feather built without embedded Tor. Assuming --use-local-tor";
this->localTor = true;
return;
+4 -4
View File
@@ -7,7 +7,7 @@
#include "txfiathistory.h"
#include "utils/utils.h"
TxFiatHistory::TxFiatHistory(int genesis_timestamp, const QString &configDirectory, QObject *parent) :
TxFiatHistory::TxFiatHistory(unsigned int genesis_timestamp, const QString &configDirectory, QObject *parent) :
QObject(parent),
m_genesis_timestamp(genesis_timestamp),
m_configDirectory(configDirectory) {
@@ -15,7 +15,7 @@ TxFiatHistory::TxFiatHistory(int genesis_timestamp, const QString &configDirecto
this->loadDatabase();
}
double TxFiatHistory::get(int timestamp) {
double TxFiatHistory::get(unsigned int timestamp) {
QDateTime ts;
ts.setTime_t(timestamp);
auto key = ts.toString("yyyyMMdd");
@@ -59,7 +59,7 @@ void TxFiatHistory::onUpdateDatabase() {
auto now = QDate::currentDate();
auto nowKey = now.toString("yyyyMMdd");
int year = genesis.toString("yyyy").toInt();
unsigned int year = genesis.toString("yyyy").toUInt();
auto yearCurrent = now.year();
// if current year is genesis year we'll refresh regardless.
@@ -71,7 +71,7 @@ void TxFiatHistory::onUpdateDatabase() {
// keep local fiatTxHistory database up to date, loop for missing dates
for(year; year != yearCurrent + 1; year += 1){
for(int month = 1; month != 13; month++) {
for(unsigned int month = 1; month != 13; month++) {
if(year == yearCurrent && month == now.month() && now.day() == 1) break;
QDateTime _now;
_now.setDate(QDate(year, month, 1));
+5 -5
View File
@@ -6,17 +6,17 @@ class TxFiatHistory : public QObject {
Q_OBJECT
public:
explicit TxFiatHistory(int genesis_timestamp, const QString &configDirectory, QObject *parent = nullptr);
explicit TxFiatHistory(unsigned int genesis_timestamp, const QString &configDirectory, QObject *parent = nullptr);
double get(const QString &date);
double get(int timestamp);
double get(unsigned int timestamp);
public slots:
void onUpdateDatabase();
void onWSData(const QJsonObject &data);
signals:
void requestYear(int year);
void requestYearMonth(int year, int month);
void requestYear(unsigned int year);
void requestYearMonth(unsigned int year, unsigned int month);
private:
void loadDatabase();
@@ -25,7 +25,7 @@ private:
QString m_configDirectory;
bool m_initialized = false;
QMap<QString, double> m_database;
int m_genesis_timestamp;
unsigned int m_genesis_timestamp;
};
#endif //FEATHER_TXFIATHISTORY_H
+36 -44
View File
@@ -22,7 +22,6 @@
#include "utils.h"
#include "utils/config.h"
#include "utils/tails.h"
#include "utils/whonix.h"
// Application log for current session
QVector<logMessage> applicationLog = QVector<logMessage>(); // todo: replace with ring buffer
@@ -277,7 +276,7 @@ bool Utils::testSocks5(const QString &host, quint16 port){
return false;
}
void Utils::externalLinkWarning(QWidget *parent, const QString &url){
void Utils::externalLinkWarning(const QString &url){
if(!config()->get(Config::warnOnExternalLink).toBool()) {
QDesktopServices::openUrl(QUrl(url));
return;
@@ -285,11 +284,9 @@ void Utils::externalLinkWarning(QWidget *parent, const QString &url){
QString body = "You are about to open the following link:\n\n";
body += QString("%1\n\n").arg(url);
body += "You will NOT be using Tor.";
if (!(TailsOS::detect() || WhonixOS::detect()))
body += "You will NOT be using Tor.";
switch (QMessageBox::warning(parent, "External link warning", body, QMessageBox::Cancel|QMessageBox::Ok)) {
switch (Utils::showMessageBox("External link warning", body, true)) {
case QMessageBox::Cancel:
break;
default:
@@ -330,6 +327,26 @@ bool Utils::walletExists(QString name, const QString &path) {
return Utils::fileExists(walletPath);
}
int Utils::showMessageBox(const QString &windowTitle, const QString &body, bool warning){
QMessageBox msgBox(QApplication::activeWindow());
msgBox.setWindowTitle(windowTitle);
msgBox.setText(body);
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Ok);
if(warning) {
QPixmap iconWarning = QPixmap(":/assets/images/ghost_icon.png");
msgBox.setIconPixmap(iconWarning);
}
else {
QPixmap iconInfo = QPixmap(":/assets/images/info.png")
.scaled(QSize(48,48), Qt::KeepAspectRatio, Qt::SmoothTransformation);
msgBox.setIconPixmap(iconInfo);
}
return msgBox.exec();
}
void Utils::copyToClipboard(const QString &string){
QClipboard * clipboard = QApplication::clipboard();
if (!clipboard) {
@@ -553,55 +570,30 @@ QString Utils::formatBytes(quint64 bytes)
int i;
double _data;
for (i = 0; i < sizes.count() && bytes >= 10000; i++, bytes /= 1000)
for (i = 0; i < sizes.count() && bytes >= 1000; i++, bytes /= 1000)
_data = bytes / 1000.0;
if (_data < 0)
_data = 0;
// unrealistic
if (_data > 10000)
if (_data > 1000)
_data = 0;
return QString("%1 %2").arg(QString::number(_data, 'f', 1), sizes[i]);
}
QMap<QString, QLocale> Utils::localeCache = {};
QLocale Utils::getCurrencyLocale(const QString &currencyCode) {
QLocale locale;
if (localeCache.contains(currencyCode)) {
locale = localeCache[currencyCode];
} else {
QList<QLocale> allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
for (const auto& locale_: allLocales) {
if (locale_.currencySymbol(QLocale::CurrencyIsoCode) == currencyCode) {
locale = locale_;
}
}
localeCache[currencyCode] = locale;
}
return locale;
}
QString Utils::amountToCurrencyString(double amount, const QString &currencyCode) {
QLocale locale = getCurrencyLocale(currencyCode);
// \xC2\xA0 = UTF-8 non-breaking space, it looks off.
if (currencyCode == "USD")
return locale.toCurrencyString(amount, "$").remove("\xC2\xA0");
return locale.toCurrencyString(amount).remove("\xC2\xA0");
}
int Utils::maxLength(const QVector<QString> &array) {
int maxLength = 0;
for (const auto &str: array) {
auto length = str.length();
if (length > maxLength) {
maxLength = length;
QLocale locale;
QList<QLocale> allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
for (const auto& locale_: allLocales) {
if (locale_.currencySymbol(QLocale::CurrencyIsoCode) == currencyCode) {
locale = locale_;
}
}
return maxLength;
}
if (currencyCode == "USD")
return locale.toCurrencyString(amount, "$");
return locale.toCurrencyString(amount);
}
+4 -4
View File
@@ -75,7 +75,8 @@ public:
static QStringList readJsonStringToQStringList(const QString &input);
static void applicationLogHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
static void openWindow(QWidget *w);
static void externalLinkWarning(QWidget *parent, const QString &url);
static void externalLinkWarning(const QString &url);
static int showMessageBox(const QString &windowTitle, const QString &body, bool warning);
static QList<processStruct> procList();
static bool dirExists(const QString &path);
static QString barrayToString(const QByteArray &data);
@@ -95,10 +96,9 @@ public:
static QFont relativeFont(int delta);
static double roundSignificant(double N, double n);
static QString formatBytes(quint64 bytes);
static QLocale getCurrencyLocale(const QString &currencyCode);
static QString amountToCurrencyString(double amount, const QString &currencyCode);
static int maxLength(const QVector<QString> &array);
static QMap<QString, QLocale> localeCache;
static QStringList randomHTTPAgents;
template<typename QEnum>
static QString QtEnumToString (const QEnum value)
-8
View File
@@ -21,14 +21,6 @@ WSClient::WSClient(AppContext *ctx, const QUrl &url, QObject *parent) :
connect(&this->webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &WSClient::onError);
m_tor = url.host().endsWith(".onion");
// Keep websocket connection alive
connect(&m_pingTimer, &QTimer::timeout, [this]{
if (this->webSocket.state() == QAbstractSocket::ConnectedState)
this->webSocket.ping();
});
m_pingTimer.setInterval(30 * 1000);
m_pingTimer.start();
}
void WSClient::sendMsg(const QByteArray &data) {
-1
View File
@@ -33,7 +33,6 @@ private slots:
private:
QTimer m_connectionTimer;
QTimer m_pingTimer;
AppContext *m_ctx;
bool m_tor = true;
};
+36 -1
View File
@@ -9,13 +9,22 @@
#include "utils/utils.h"
#include "utils/xmrig.h"
#include "appcontext.h"
XmRig::XmRig(const QString &configDir, QObject *parent) : QObject(parent) {
this->rigDir = QDir(configDir).filePath("xmrig");
}
void XmRig::prepare() {
// unpack and set process signals
if(!this->unpackBins()) {
qCritical() << "failed to write XMRig to config directory";
return;
}
m_process.setProcessChannelMode(QProcess::MergedChannels);
connect(&m_process, &QProcess::readyReadStandardOutput, this, &XmRig::handleProcessOutput);
connect(&m_process, &QProcess::errorOccurred, this, &XmRig::handleProcessError);
@@ -33,7 +42,7 @@ void XmRig::stop() {
}
void XmRig::start(const QString &path,
int threads,
unsigned int threads,
const QString &address,
const QString &username,
const QString &password,
@@ -101,3 +110,29 @@ void XmRig::handleProcessError(QProcess::ProcessError err) {
emit error(QString("XMRig binary failed to start: %1").arg(path));
}
}
bool XmRig::unpackBins() {
QString rigFile;
rigFile = ":/assets/exec/xmrig";
if (!Utils::fileExists(rigFile))
return false;
// write to disk
QFile f(rigFile);
QFileInfo fileInfo(f);
this->rigPath = QDir(this->rigDir).filePath(fileInfo.fileName());
#if defined(Q_OS_WIN)
if(!this->rigPath.endsWith(".exe"))
this->rigPath += ".exe";
#endif
qDebug() << "Writing XMRig executable to " << this->rigPath;
f.copy(rigPath);
f.close();
#if defined(Q_OS_UNIX)
QFile rigBin(this->rigPath);
rigBin.setPermissions(QFile::ExeGroup | QFile::ExeOther | QFile::ExeOther | QFile::ExeUser);
#endif
return true;
}
+1 -1
View File
@@ -23,7 +23,7 @@ public:
explicit XmRig(const QString &configDir, QObject *parent = nullptr);
void prepare();
void start(const QString &path, int threads, const QString &address, const QString &username, const QString &password, bool tor = false, bool tls = true);
void start(const QString &path, unsigned int threads, const QString &address, const QString &username, const QString &password, bool tor = false, bool tls = true);
void stop();
bool unpackBins();
+1 -3
View File
@@ -31,8 +31,6 @@ CCSWidget::CCSWidget(QWidget *parent) :
connect(ui->tableView, &QHeaderView::customContextMenuRequested, this, &CCSWidget::showContextMenu);
connect(ui->tableView, &QTableView::doubleClicked, this, &CCSWidget::linkClicked);
ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
}
CCSModel* CCSWidget::model() {
@@ -44,7 +42,7 @@ void CCSWidget::linkClicked() {
auto entry = m_model->entry(index.row());
if (entry)
Utils::externalLinkWarning(this, entry->url);
Utils::externalLinkWarning(entry->url);
}
void CCSWidget::donateClicked() {
+33
View File
@@ -48,6 +48,39 @@
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="warnLabel">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Powered by ccs.getmonero.org</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</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>
</layout>
</widget>
<resources/>
+76 -55
View File
@@ -18,10 +18,10 @@
#include "utils/nodes.h"
#include "mainwindow.h"
NodeWidget::NodeWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::NodeWidget)
{
NodeWidget::NodeWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::NodeWidget),
m_contextMenu(new QMenu(this)) {
ui->setupUi(this);
connect(ui->btn_add_custom, &QPushButton::clicked, this, &NodeWidget::onCustomAddClicked);
@@ -35,91 +35,112 @@ NodeWidget::NodeWidget(QWidget *parent)
}
});
m_contextActionRemove = new QAction("Remove", this);
m_contextActionConnect = new QAction(QIcon(":/assets/images/connect.svg"), "Connect to node", this);
m_contextActionOpenStatusURL = new QAction(QIcon(":/assets/images/network.png"), "Visit status page", this);
m_contextActionCopy = new QAction(QIcon(":/assets/images/copy.png"), "Copy", this);
connect(m_contextActionConnect, &QAction::triggered, this, &NodeWidget::onContextConnect);
connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove);
connect(m_contextActionOpenStatusURL, &QAction::triggered, this, &NodeWidget::onContextStatusURL);
connect(m_contextActionCopy, &QAction::triggered, this, &NodeWidget::onContextNodeCopy);
connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove);
ui->wsView->setContextMenuPolicy(Qt::CustomContextMenu);
ui->customView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->wsView, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowWSContextMenu);
connect(ui->customView, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowCustomContextMenu);
connect(ui->customView, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
connect(ui->wsView, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
}
void NodeWidget::onShowWSContextMenu(const QPoint &pos) {
m_activeView = ui->wsView;
FeatherNode node = this->selectedNode();
if (node.full.isEmpty()) return;
QModelIndex index = ui->wsView->indexAt(pos);
if (!index.isValid()) return;
FeatherNode node = m_wsModel->node(index.row());
this->showContextMenu(pos, node);
}
void NodeWidget::onShowCustomContextMenu(const QPoint &pos) {
m_activeView = ui->customView;
FeatherNode node = this->selectedNode();
if (node.full.isEmpty()) return;
QModelIndex index = ui->customView->indexAt(pos);
if (!index.isValid()) return;
FeatherNode node = m_customModel->node(index.row());
this->showContextMenu(pos, node);
}
void NodeWidget::showContextMenu(const QPoint &pos, const FeatherNode &node) {
QMenu menu(this);
if (!node.isActive) {
menu.addAction(m_contextActionConnect);
void NodeWidget::showContextMenu(const QPoint &pos, const FeatherNode &node) {
bool custom = node.custom;
m_activeView = custom ? ui->customView : ui->wsView;
m_contextActionRemove = new QAction("Remove");
m_contextActionConnect = new QAction("Connect to node");
m_contextActionConnect->setIcon(QIcon(":/assets/images/connect.svg"));
m_contextActionOpenStatusURL = new QAction("Visit status page");
m_contextActionOpenStatusURL->setIcon(QIcon(":/assets/images/network.png"));
m_contextActionCopy = new QAction("Copy");
m_contextActionCopy->setIcon(QIcon(":/assets/images/copy.png"));
if(!node.isActive) {
connect(m_contextActionConnect, &QAction::triggered, this, &NodeWidget::onContextConnect);
m_contextMenu->addAction(m_contextActionConnect);
}
menu.addAction(m_contextActionOpenStatusURL);
menu.addAction(m_contextActionCopy);
m_contextMenu->addAction(m_contextActionOpenStatusURL);
m_contextMenu->addAction(m_contextActionCopy);
if (m_activeView == ui->customView)
menu.addAction(m_contextActionRemove);
if(custom) {
connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove);
m_contextMenu->addAction(m_contextActionRemove);
menu.exec(m_activeView->viewport()->mapToGlobal(pos));
connect(m_contextActionOpenStatusURL, &QAction::triggered, this, &NodeWidget::onContextCustomStatusURL);
connect(m_contextActionCopy, &QAction::triggered, this, &NodeWidget::onContextCustomNodeCopy);
} else {
connect(m_contextActionOpenStatusURL, &QAction::triggered, this, &NodeWidget::onContextWSStatusURL);
connect(m_contextActionCopy, &QAction::triggered, this, &NodeWidget::onContextWSNodeCopy);
}
m_contextMenu->exec(m_activeView->viewport()->mapToGlobal(pos));
m_contextActionRemove->deleteLater();
m_contextActionConnect->deleteLater();
m_contextActionOpenStatusURL->deleteLater();
m_contextActionCopy->deleteLater();
}
void NodeWidget::onContextConnect() {
QObject *obj = sender();
if (obj == ui->customView)
m_activeView = ui->customView;
else
m_activeView = ui->wsView;
QModelIndex index = m_activeView->currentIndex();
if (!index.isValid()) return;
FeatherNode node = this->selectedNode();
if (!node.full.isEmpty())
if(m_activeView->objectName() == "wsView"){
FeatherNode node = m_wsModel->node(index.row());
emit connectToNode(node);
} else {
FeatherNode node = m_customModel->node(index.row());
emit connectToNode(node);
}
}
void NodeWidget::onContextStatusURL() {
FeatherNode node = this->selectedNode();
if (!node.full.isEmpty())
Utils::externalLinkWarning(this, node.as_url());
void NodeWidget::onContextWSStatusURL() {
QModelIndex index = ui->wsView->currentIndex();
if (!index.isValid()) return;
FeatherNode node = m_wsModel->node(index.row());
Utils::externalLinkWarning(node.as_url());
}
void NodeWidget::onContextNodeCopy() {
FeatherNode node = this->selectedNode();
void NodeWidget::onContextCustomStatusURL() {
QModelIndex index = ui->customView->currentIndex();
if (!index.isValid()) return;
FeatherNode node = m_customModel->node(index.row());
Utils::externalLinkWarning(node.as_url());
}
void NodeWidget::onContextDisconnect() {
QModelIndex index = ui->customView->currentIndex();
if (!index.isValid()) return;
FeatherNode node = m_customModel->node(index.row());
Utils::copyToClipboard(node.full);
}
FeatherNode NodeWidget::selectedNode() {
QModelIndex index = m_activeView->currentIndex();
if (!index.isValid()) return FeatherNode();
void NodeWidget::onContextWSNodeCopy() {
QModelIndex index = ui->wsView->currentIndex();
if (!index.isValid()) return;
FeatherNode node = m_wsModel->node(index.row());
Utils::copyToClipboard(node.full);
}
void NodeWidget::onContextCustomNodeCopy() {
FeatherNode node;
if (m_activeView == ui->customView) {
node = m_customModel->node(index.row());
} else {
node = m_wsModel->node(index.row());
}
return node;
}
void NodeWidget::onContextCustomNodeRemove() {
+7 -3
View File
@@ -35,9 +35,12 @@ public slots:
private slots:
void onContextConnect();
void onContextDisconnect();
void onContextCustomNodeRemove();
void onContextStatusURL();
void onContextNodeCopy();
void onContextWSStatusURL();
void onContextCustomStatusURL();
void onContextWSNodeCopy();
void onContextCustomNodeCopy();
signals:
void connectToNode(FeatherNode node);
@@ -51,13 +54,14 @@ private:
QTreeView *m_activeView;
QMenu *m_contextMenu;
QAction *m_contextActionConnect;
QAction *m_contextActionDisconnect;
QAction *m_contextActionRemove;
QAction *m_contextActionOpenStatusURL;
QAction *m_contextActionCopy;
void showContextMenu(const QPoint &pos, const FeatherNode &node);
FeatherNode selectedNode();
};
#endif // NODEWIDGET_H
+1 -3
View File
@@ -26,8 +26,6 @@ RedditWidget::RedditWidget(QWidget *parent) :
connect(ui->tableView, &QHeaderView::customContextMenuRequested, this, &RedditWidget::showContextMenu);
connect(ui->tableView, &QTableView::doubleClicked, this, &RedditWidget::linkClicked);
ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
}
RedditModel* RedditWidget::model() {
@@ -39,7 +37,7 @@ void RedditWidget::linkClicked() {
auto post = m_model->post(index.row());
if (post) {
Utils::externalLinkWarning(this, post->url);
Utils::externalLinkWarning(post->url);
}
}
+30
View File
@@ -51,6 +51,36 @@
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Powered by Reddit</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</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>
</layout>
</widget>
<resources/>
+18 -32
View File
@@ -17,10 +17,15 @@ RestoreHeightWidget::RestoreHeightWidget(QWidget *parent) :
{
ui->setupUi(this);
ui->lineEdit_restoreHeight->setValidator(new QIntValidator(0, 2147483647, this));
connect(ui->lineEdit_restoreHeight, &QLineEdit::textEdited, [this](const QString &val){
if (val.isEmpty()) return;
this->setHeight(val.toInt());
connect(ui->lineEdit_restoreHeight, &QLineEdit::textEdited, [=](const QString &val){
// update slider on lineEdit change
if(val.isEmpty()) return;
auto height = val.toUInt();
if(height <= 1) return;
auto timestamp = m_restoreHeightLookup->restoreHeightToDate(height);
ui->restoreSlider->blockSignals(true);
ui->restoreSlider->setValue(timestamp);
ui->restoreSlider->blockSignals(false);
});
}
@@ -28,45 +33,26 @@ void RestoreHeightWidget::hideSlider(){
ui->restoreGrid->hide();
}
void RestoreHeightWidget::setHeight(int height) {
if (height < 0)
height = 0;
// Update lineEdit
ui->lineEdit_restoreHeight->setText(QString::number(height));
// Update slider
int date = m_restoreHeightLookup->restoreHeightToDate(height);
ui->restoreSlider->setValue(date);
this->updateTimestamp(date);
}
void RestoreHeightWidget::initRestoreHeights(RestoreHeightLookup *lookup) {
// init slider
m_restoreHeightLookup = lookup;
int now = std::time(nullptr);
QList<int> blockDates = m_restoreHeightLookup->data.keys();
auto now = (unsigned int)std::time(nullptr);
QList<unsigned int> blockDates = m_restoreHeightLookup->data.keys();
ui->restoreSlider->setMinimum(blockDates[0]);
ui->restoreSlider->setMaximum(now);
connect(ui->restoreSlider, &QSlider::sliderMoved, [this](int date){
// Update lineEdit
int blockHeight = m_restoreHeightLookup->dateToRestoreHeight(date);
ui->lineEdit_restoreHeight->setText(QString::number(blockHeight));
this->updateTimestamp(date);
});
connect(ui->restoreSlider, &QSlider::valueChanged, this, &RestoreHeightWidget::onValueChanged);
}
void RestoreHeightWidget::updateTimestamp(int date) {
void RestoreHeightWidget::onValueChanged(int date) {
QDateTime timestamp;
timestamp.setTime_t(date);
timestamp.setTime_t((unsigned int) date);
ui->label_restoreHeightDate->setText(timestamp.toString("yyyy-MM-dd"));
auto blockHeight = m_restoreHeightLookup->dateToRestoreHeight((unsigned int) date);
ui->lineEdit_restoreHeight->setText(QString::number(blockHeight));
}
int RestoreHeightWidget::getHeight() {
return ui->lineEdit_restoreHeight->text().toInt();
unsigned int RestoreHeightWidget::getHeight() {
return ui->lineEdit_restoreHeight->text().toUInt();
}
RestoreHeightWidget::~RestoreHeightWidget() {
+5 -5
View File
@@ -20,14 +20,14 @@ class RestoreHeightWidget : public QWidget
public:
explicit RestoreHeightWidget(QWidget *parent = nullptr);
void initRestoreHeights(RestoreHeightLookup *lookup);
void setHeight(int height);
int getHeight();
unsigned int getHeight();
void hideSlider();
~RestoreHeightWidget() override;
~RestoreHeightWidget();
private slots:
void onValueChanged(int date);
private:
void updateTimestamp(int date);
RestoreHeightLookup *m_restoreHeightLookup = nullptr;
Ui::RestoreHeightWidget *ui;
};
+14 -13
View File
@@ -8,12 +8,11 @@
#include "utils/config.h"
#include "mainwindow.h"
TickerWidget::TickerWidget(QWidget *parent, QString symbol, QString title, bool convertBalance, bool hidePercent) :
TickerWidget::TickerWidget(QWidget *parent, QString symbol, QString title, bool convertBalance) :
QWidget(parent),
ui(new Ui::TickerWidget),
m_symbol(std::move(symbol)),
m_convertBalance(convertBalance),
m_hidePercent(hidePercent)
m_convertBalance(convertBalance)
{
ui->setupUi(this);
m_ctx = MainWindow::getContext();
@@ -28,8 +27,6 @@ TickerWidget::TickerWidget(QWidget *parent, QString symbol, QString title, bool
this->setPctText(defaultPct, true);
this->setFiatText(defaultFiat, 0.0);
ui->tickerPct->setHidden(hidePercent);
connect(AppContext::prices, &Prices::fiatPricesUpdated, this, &TickerWidget::init);
connect(AppContext::prices, &Prices::cryptoPricesUpdated, this, &TickerWidget::init);
if (convertBalance)
@@ -50,19 +47,15 @@ void TickerWidget::init() {
double amount = m_convertBalance ? AppContext::balance : 1.0;
double conversion = AppContext::prices->convert(m_symbol, fiatCurrency, amount);
if (conversion < 0) return;
setPercentHidden(conversion == 0);
auto markets = AppContext::prices->markets;
if(!markets.contains(m_symbol)) return;
bool hidePercent = (conversion == 0 || m_hidePercent);
if (hidePercent) {
ui->tickerPct->hide();
} else {
auto pct24h = markets[m_symbol].price_usd_change_pct_24h;
auto pct24hText = QString::number(pct24h, 'f', 2);
this->setPctText(pct24hText, pct24h >= 0.0);
}
auto pct24h = markets[m_symbol].price_usd_change_pct_24h;
auto pct24hText = QString::number(pct24h, 'f', 2);
this->setPctText(pct24hText, pct24h >= 0.0);
this->setFiatText(fiatCurrency, conversion);
}
@@ -88,6 +81,14 @@ void TickerWidget::setFontSizes() {
ui->tickerFiat->setFont(Utils::relativeFont(0));
}
void TickerWidget::removePctContainer() {
ui->tickerPct->deleteLater();
}
void TickerWidget::setPercentHidden(bool hidden) {
ui->tickerPct->setVisible(!hidden);
}
TickerWidget::~TickerWidget() {
delete ui;
}
+4 -2
View File
@@ -17,7 +17,8 @@ class TickerWidget : public QWidget
Q_OBJECT
public:
explicit TickerWidget(QWidget *parent, QString symbol, QString title = "", bool convertBalance = false, bool hidePercent = false);
explicit TickerWidget(QWidget *parent, QString symbol, QString title = "", bool convertBalance = false);
void removePctContainer();
void setFiatText(QString &fiatCurrency, double amount);
void setPctText(QString &text, bool positive);
void setFontSizes();
@@ -28,10 +29,11 @@ public slots:
void init();
private:
void setPercentHidden(bool hidden);
Ui::TickerWidget *ui;
QString m_symbol;
bool m_convertBalance;
bool m_hidePercent;
AppContext *m_ctx;
};

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