Compare commits
No commits in common. "master" and "upstreamMerge" have entirely different histories.
master
...
upstreamMe
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,6 +17,7 @@ bin/
|
||||
.idea
|
||||
|
||||
# icons
|
||||
electrum/gui/qt/icons_rc.py
|
||||
electrum/gui/kivy/theming/light-0.png
|
||||
electrum/gui/kivy/theming/light.atlas
|
||||
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +1,6 @@
|
||||
[submodule "contrib/deterministic-build/electrum-icons"]
|
||||
path = contrib/deterministic-build/electrum-icons
|
||||
url = https://github.com/spesmilo/electrum-icons
|
||||
[submodule "contrib/deterministic-build/electrum-locale"]
|
||||
path = contrib/deterministic-build/electrum-locale
|
||||
url = https://github.com/spesmilo/electrum-locale
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@ -44,13 +44,9 @@ jobs:
|
||||
- name: "Android build"
|
||||
language: python
|
||||
python: 3.7
|
||||
env:
|
||||
# reset API key to not have make_locale upload stuff here
|
||||
- crowdin_api_key=
|
||||
services:
|
||||
- docker
|
||||
install:
|
||||
- pip install requests && ./contrib/make_locale
|
||||
- ./contrib/make_packages
|
||||
- sudo docker build --no-cache -t electrum-android-builder-img electrum/gui/kivy/tools
|
||||
script:
|
||||
@ -74,17 +70,6 @@ jobs:
|
||||
script: ./contrib/osx/make_osx
|
||||
after_script: ls -lah dist && md5 dist/*
|
||||
after_success: true
|
||||
- name: "AppImage build"
|
||||
sudo: true
|
||||
language: c
|
||||
python: false
|
||||
services:
|
||||
- docker
|
||||
install:
|
||||
- sudo docker build --no-cache -t electrum-appimage-builder-img ./contrib/build-linux/appimage/
|
||||
script:
|
||||
- sudo docker run --name electrum-appimage-builder-cont -v $PWD:/opt/electrum --rm --workdir /opt/electrum/contrib/build-linux/appimage electrum-appimage-builder-img ./build.sh
|
||||
after_success: true
|
||||
- stage: release check
|
||||
install:
|
||||
- git fetch --all --tags
|
||||
|
||||
13
AUTHORS
13
AUTHORS
@ -1,5 +1,3 @@
|
||||
Electrum-BTC
|
||||
------------
|
||||
ThomasV - Creator and maintainer.
|
||||
Animazing / Tachikoma - Styled the new GUI. Mac version.
|
||||
Azelphur - GUI stuff.
|
||||
@ -11,13 +9,4 @@ Genjix - Porting pro-mode functionality to lite-gui and worked on server
|
||||
Slush - Work on the server. Designed the original Stratum spec.
|
||||
Julian Toash (Tuxavant) - Various fixes to the client.
|
||||
rdymac - Website and translations.
|
||||
kyuupichan - Miscellaneous.
|
||||
|
||||
|
||||
FLO-Electrum
|
||||
------------
|
||||
vivekteega - Maintainer and remaining stuff
|
||||
Bitspill - Bootstraped the project with core FLO changes
|
||||
Rohit Tripathy - Ideation and problem solving
|
||||
akhil2015 - Flodata and scrypt hashing
|
||||
|
||||
kyuupichan - Miscellaneous.
|
||||
@ -7,6 +7,8 @@ include contrib/requirements/requirements.txt
|
||||
include contrib/requirements/requirements-hw.txt
|
||||
recursive-include packages *.py
|
||||
recursive-include packages cacert.pem
|
||||
include icons.qrc
|
||||
graft icons
|
||||
|
||||
graft electrum
|
||||
prune electrum/tests
|
||||
|
||||
@ -66,6 +66,14 @@ Run install (this should install dependencies)::
|
||||
|
||||
python3 -m pip install .[fast]
|
||||
|
||||
Render the SVG icons to PNGs (optional)::
|
||||
|
||||
for i in lock unlock confirmed status_lagging status_disconnected status_connected_proxy status_connected status_waiting preferences; do convert -background none icons/$i.svg icons/$i.png; done
|
||||
|
||||
Compile the icons file for Qt::
|
||||
|
||||
sudo apt-get install pyqt5-dev-tools
|
||||
pyrcc5 icons.qrc -o electrum/gui/qt/icons_rc.py
|
||||
|
||||
Compile the protobuf description file::
|
||||
|
||||
|
||||
@ -1,19 +1,3 @@
|
||||
# Release 3.3.4 - (February 13, 2019)
|
||||
|
||||
* AppImage: we now also distribute self-contained binaries for x86_64
|
||||
Linux in the form of an AppImage (#5042). The Python interpreter,
|
||||
PyQt5, libsecp256k1, PyCryptodomex, zbar, hidapi/libusb (including
|
||||
hardware wallet libraries) are all bundled. Note that users of
|
||||
hw wallets still need to set udev rules themselves.
|
||||
* hw wallets: fix a regression during transaction signing that prompts
|
||||
the user too many times for confirmations (commit 2729909)
|
||||
* transactions now set nVersion to 2, to mimic Bitcoin Core
|
||||
* fix Qt bug that made all hw wallets unusable on Windows 8.1 (#4960)
|
||||
* fix bugs in wallet creation wizard that resulted in corrupted
|
||||
wallets being created in rare cases (#5082, #5057)
|
||||
* fix compatibility with Qt 5.12 (#5109)
|
||||
|
||||
|
||||
# Release 3.3.3 - (January 25, 2019)
|
||||
|
||||
* Do not expose users to server error messages (#4968)
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
FROM ubuntu:14.04@sha256:cac55e5d97fad634d954d00a5c2a56d80576a08dcc01036011f26b88263f1578
|
||||
|
||||
ENV LC_ALL=C.UTF-8 LANG=C.UTF-8
|
||||
|
||||
RUN apt-get update -q && \
|
||||
apt-get install -qy \
|
||||
git \
|
||||
wget \
|
||||
make \
|
||||
autotools-dev \
|
||||
autoconf \
|
||||
libtool \
|
||||
xz-utils \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
libffi6 \
|
||||
libffi-dev \
|
||||
libusb-1.0-0-dev \
|
||||
libudev-dev \
|
||||
gettext \
|
||||
libzbar0 \
|
||||
&& \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
@ -1,41 +0,0 @@
|
||||
AppImage binary for Electrum
|
||||
============================
|
||||
|
||||
This assumes an Ubuntu host, but it should not be too hard to adapt to another
|
||||
similar system. The docker commands should be executed in the project's root
|
||||
folder.
|
||||
|
||||
1. Install Docker
|
||||
|
||||
```
|
||||
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
|
||||
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install -y docker-ce
|
||||
```
|
||||
|
||||
2. Build image
|
||||
|
||||
```
|
||||
$ sudo docker build --no-cache -t electrum-appimage-builder-img contrib/build-linux/appimage
|
||||
```
|
||||
|
||||
3. Build binary
|
||||
|
||||
```
|
||||
$ sudo docker run -it \
|
||||
--name electrum-appimage-builder-cont \
|
||||
-v $PWD:/opt/electrum \
|
||||
--rm \
|
||||
--workdir /opt/electrum/contrib/build-linux/appimage \
|
||||
electrum-appimage-builder-img \
|
||||
./build.sh
|
||||
```
|
||||
|
||||
4. The generated binary is in `./dist`.
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
### How can I see what is included in the AppImage?
|
||||
Execute the binary as follows: `./electrum*.AppImage --appimage-extract`
|
||||
@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
APPDIR="$(dirname "$(readlink -e "$0")")"
|
||||
|
||||
export LD_LIBRARY_PATH="${APPDIR}/usr/lib/:${APPDIR}/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}"
|
||||
export PATH="${APPDIR}/usr/bin:${PATH}"
|
||||
export LDFLAGS="-L${APPDIR}/usr/lib/x86_64-linux-gnu -L${APPDIR}/usr/lib"
|
||||
|
||||
exec "${APPDIR}/usr/bin/python3.6" -s "${APPDIR}/usr/bin/electrum" "$@"
|
||||
@ -1,197 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_ROOT="$(dirname "$(readlink -e "$0")")/../../.."
|
||||
CONTRIB="$PROJECT_ROOT/contrib"
|
||||
DISTDIR="$PROJECT_ROOT/dist"
|
||||
BUILDDIR="$CONTRIB/build-linux/appimage/build/appimage"
|
||||
APPDIR="$BUILDDIR/electrum.AppDir"
|
||||
CACHEDIR="$CONTRIB/build-linux/appimage/.cache/appimage"
|
||||
|
||||
# pinned versions
|
||||
PYTHON_VERSION=3.6.8
|
||||
PKG2APPIMAGE_COMMIT="83483c2971fcaa1cb0c1253acd6c731ef8404381"
|
||||
LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7"
|
||||
|
||||
|
||||
VERSION=`git describe --tags --dirty --always`
|
||||
APPIMAGE="$DISTDIR/electrum-$VERSION-x86_64.AppImage"
|
||||
|
||||
rm -rf "$BUILDDIR"
|
||||
mkdir -p "$APPDIR" "$CACHEDIR" "$DISTDIR"
|
||||
|
||||
|
||||
. "$CONTRIB"/build_tools_util.sh
|
||||
|
||||
|
||||
info "downloading some dependencies."
|
||||
download_if_not_exist "$CACHEDIR/functions.sh" "https://raw.githubusercontent.com/AppImage/pkg2appimage/$PKG2APPIMAGE_COMMIT/functions.sh"
|
||||
verify_hash "$CACHEDIR/functions.sh" "a73a21a6c1d1e15c0a9f47f017ae833873d1dc6aa74a4c840c0b901bf1dcf09c"
|
||||
|
||||
download_if_not_exist "$CACHEDIR/appimagetool" "https://github.com/probonopd/AppImageKit/releases/download/11/appimagetool-x86_64.AppImage"
|
||||
verify_hash "$CACHEDIR/appimagetool" "c13026b9ebaa20a17e7e0a4c818a901f0faba759801d8ceab3bb6007dde00372"
|
||||
|
||||
download_if_not_exist "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz"
|
||||
verify_hash "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "35446241e995773b1bed7d196f4b624dadcadc8429f26282e756b2fb8a351193"
|
||||
|
||||
|
||||
|
||||
info "building python."
|
||||
tar xf "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" -C "$BUILDDIR"
|
||||
(
|
||||
cd "$BUILDDIR/Python-$PYTHON_VERSION"
|
||||
export SOURCE_DATE_EPOCH=1530212462
|
||||
./configure \
|
||||
--cache-file="$CACHEDIR/python.config.cache" \
|
||||
--prefix="$APPDIR/usr" \
|
||||
--enable-ipv6 \
|
||||
--enable-shared \
|
||||
--with-threads \
|
||||
-q
|
||||
make -s
|
||||
make -s install > /dev/null
|
||||
)
|
||||
|
||||
|
||||
info "building libsecp256k1."
|
||||
(
|
||||
git clone https://github.com/bitcoin-core/secp256k1 "$CACHEDIR"/secp256k1 || (cd "$CACHEDIR"/secp256k1 && git pull)
|
||||
cd "$CACHEDIR"/secp256k1
|
||||
git reset --hard "$LIBSECP_VERSION"
|
||||
git clean -f -x -q
|
||||
export SOURCE_DATE_EPOCH=1530212462
|
||||
./autogen.sh
|
||||
echo "LDFLAGS = -no-undefined" >> Makefile.am
|
||||
./configure \
|
||||
--prefix="$APPDIR/usr" \
|
||||
--enable-module-recovery \
|
||||
--enable-experimental \
|
||||
--enable-module-ecdh \
|
||||
--disable-jni \
|
||||
-q
|
||||
make -s
|
||||
make -s install > /dev/null
|
||||
)
|
||||
|
||||
|
||||
appdir_python() {
|
||||
env \
|
||||
PYTHONNOUSERSITE=1 \
|
||||
LD_LIBRARY_PATH="$APPDIR/usr/lib:$APPDIR/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}" \
|
||||
"$APPDIR/usr/bin/python3.6" "$@"
|
||||
}
|
||||
|
||||
python='appdir_python'
|
||||
|
||||
|
||||
info "installing pip."
|
||||
"$python" -m ensurepip
|
||||
|
||||
|
||||
info "preparing electrum-locale."
|
||||
(
|
||||
cd "$PROJECT_ROOT"
|
||||
git submodule update --init
|
||||
|
||||
pushd "$CONTRIB"/deterministic-build/electrum-locale
|
||||
if ! which msgfmt > /dev/null 2>&1; then
|
||||
echo "Please install gettext"
|
||||
exit 1
|
||||
fi
|
||||
for i in ./locale/*; do
|
||||
dir="$PROJECT_ROOT/electrum/$i/LC_MESSAGES"
|
||||
mkdir -p $dir
|
||||
msgfmt --output-file="$dir/electrum.mo" "$i/electrum.po" || true
|
||||
done
|
||||
popd
|
||||
)
|
||||
|
||||
|
||||
info "installing electrum and its dependencies."
|
||||
mkdir -p "$CACHEDIR/pip_cache"
|
||||
"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements.txt"
|
||||
"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements-binaries.txt"
|
||||
"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements-hw.txt"
|
||||
"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" "$PROJECT_ROOT"
|
||||
|
||||
|
||||
info "copying zbar"
|
||||
cp "/usr/lib/libzbar.so.0" "$APPDIR/usr/lib/libzbar.so.0"
|
||||
|
||||
|
||||
info "desktop integration."
|
||||
cp "$PROJECT_ROOT/electrum.desktop" "$APPDIR/electrum.desktop"
|
||||
cp "$PROJECT_ROOT/electrum/gui/icons/electrum.png" "$APPDIR/electrum.png"
|
||||
|
||||
|
||||
# add launcher
|
||||
cp "$CONTRIB/build-linux/appimage/apprun.sh" "$APPDIR/AppRun"
|
||||
|
||||
info "finalizing AppDir."
|
||||
(
|
||||
export PKG2AICOMMIT="$PKG2APPIMAGE_COMMIT"
|
||||
. "$CACHEDIR/functions.sh"
|
||||
|
||||
cd "$APPDIR"
|
||||
# copy system dependencies
|
||||
# note: temporarily move PyQt5 out of the way so
|
||||
# we don't try to bundle its system dependencies.
|
||||
mv "$APPDIR/usr/lib/python3.6/site-packages/PyQt5" "$BUILDDIR"
|
||||
copy_deps; copy_deps; copy_deps
|
||||
move_lib
|
||||
mv "$BUILDDIR/PyQt5" "$APPDIR/usr/lib/python3.6/site-packages"
|
||||
|
||||
# apply global appimage blacklist to exclude stuff
|
||||
# move usr/include out of the way to preserve usr/include/python3.6m.
|
||||
mv usr/include usr/include.tmp
|
||||
delete_blacklisted
|
||||
mv usr/include.tmp usr/include
|
||||
)
|
||||
|
||||
|
||||
info "stripping binaries from debug symbols."
|
||||
strip_binaries()
|
||||
{
|
||||
chmod u+w -R "$APPDIR"
|
||||
{
|
||||
printf '%s\0' "$APPDIR/usr/bin/python3.6"
|
||||
find "$APPDIR" -type f -regex '.*\.so\(\.[0-9.]+\)?$' -print0
|
||||
} | xargs -0 --no-run-if-empty --verbose -n1 strip
|
||||
}
|
||||
strip_binaries
|
||||
|
||||
remove_emptydirs()
|
||||
{
|
||||
find "$APPDIR" -type d -empty -print0 | xargs -0 --no-run-if-empty rmdir -vp --ignore-fail-on-non-empty
|
||||
}
|
||||
remove_emptydirs
|
||||
|
||||
|
||||
info "removing some unneeded stuff to decrease binary size."
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/test
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/translations/qtwebengine_locales
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/resources/qtwebengine_*
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/qml
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Web*
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Designer*
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Qml*
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Quick*
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Location*
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Test*
|
||||
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Xml*
|
||||
|
||||
|
||||
info "creating the AppImage."
|
||||
(
|
||||
cd "$BUILDDIR"
|
||||
chmod +x "$CACHEDIR/appimagetool"
|
||||
"$CACHEDIR/appimagetool" --appimage-extract
|
||||
env VERSION="$VERSION" ./squashfs-root/AppRun --no-appstream --verbose "$APPDIR" "$APPIMAGE"
|
||||
)
|
||||
|
||||
|
||||
info "done."
|
||||
ls -la "$DISTDIR"
|
||||
sha256sum "$DISTDIR"/*
|
||||
@ -20,7 +20,7 @@ cd tmp
|
||||
|
||||
pushd $WINEPREFIX/drive_c/electrum
|
||||
|
||||
# Load electrum-locale for this release
|
||||
# Load electrum-icons and electrum-locale for this release
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
@ -43,6 +43,7 @@ find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
|
||||
popd
|
||||
|
||||
cp $WINEPREFIX/drive_c/electrum/LICENCE .
|
||||
cp $WINEPREFIX/drive_c/electrum/contrib/deterministic-build/electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/electrum/gui/qt/
|
||||
|
||||
# Install frozen dependencies
|
||||
$PYTHON -m pip install -r ../../deterministic-build/requirements.txt
|
||||
@ -50,7 +51,7 @@ $PYTHON -m pip install -r ../../deterministic-build/requirements.txt
|
||||
$PYTHON -m pip install -r ../../deterministic-build/requirements-hw.txt
|
||||
|
||||
pushd $WINEPREFIX/drive_c/electrum
|
||||
$PYTHON -m pip install .
|
||||
$PYTHON setup.py install
|
||||
popd
|
||||
|
||||
cd ..
|
||||
@ -74,4 +75,4 @@ mv electrum-setup.exe $NAME_ROOT-$VERSION-setup.exe
|
||||
cd ..
|
||||
|
||||
echo "Done."
|
||||
sha256sum dist/electrum*exe
|
||||
md5sum dist/electrum*exe
|
||||
|
||||
@ -29,8 +29,7 @@ else
|
||||
git pull
|
||||
fi
|
||||
|
||||
LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7"
|
||||
git reset --hard "$LIBSECP_VERSION"
|
||||
git reset --hard 452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4
|
||||
git clean -f -x -q
|
||||
|
||||
build_dll i686-w64-mingw32 # 64-bit would be: x86_64-w64-mingw32
|
||||
|
||||
@ -23,11 +23,6 @@ hiddenimports += collect_submodules('keepkeylib')
|
||||
hiddenimports += collect_submodules('websocket')
|
||||
hiddenimports += collect_submodules('ckcc')
|
||||
|
||||
# safetlib imports PyQt5.Qt. We use a local updated copy of pinmatrix.py until they
|
||||
# release a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e
|
||||
hiddenimports.remove('safetlib.qt.pinmatrix')
|
||||
|
||||
|
||||
# Add libusb binary
|
||||
binaries = [(PYHOME+"/libusb-1.0.dll", ".")]
|
||||
|
||||
@ -42,7 +37,6 @@ datas = [
|
||||
(home+'electrum/locale', 'electrum/locale'),
|
||||
(home+'electrum/plugins', 'electrum/plugins'),
|
||||
('C:\\Program Files (x86)\\ZBar\\bin\\', '.'),
|
||||
(home+'electrum/gui/icons', 'electrum/gui/icons'),
|
||||
]
|
||||
datas += collect_data_files('trezorlib')
|
||||
datas += collect_data_files('safetlib')
|
||||
@ -119,7 +113,7 @@ exe_standalone = EXE(
|
||||
debug=False,
|
||||
strip=None,
|
||||
upx=False,
|
||||
icon=home+'electrum/gui/icons/electrum.ico',
|
||||
icon=home+'icons/electrum.ico',
|
||||
console=False)
|
||||
# console=True makes an annoying black box pop up, but it does make Electrum output command line commands, with this turned off no output will be given but commands can still be used
|
||||
|
||||
@ -132,7 +126,7 @@ exe_portable = EXE(
|
||||
debug=False,
|
||||
strip=None,
|
||||
upx=False,
|
||||
icon=home+'electrum/gui/icons/electrum.ico',
|
||||
icon=home+'icons/electrum.ico',
|
||||
console=False)
|
||||
|
||||
#####
|
||||
@ -146,7 +140,7 @@ exe_dependent = EXE(
|
||||
debug=False,
|
||||
strip=None,
|
||||
upx=False,
|
||||
icon=home+'electrum/gui/icons/electrum.ico',
|
||||
icon=home+'icons/electrum.ico',
|
||||
console=False)
|
||||
|
||||
coll = COLLECT(
|
||||
@ -157,6 +151,6 @@ coll = COLLECT(
|
||||
strip=None,
|
||||
upx=True,
|
||||
debug=False,
|
||||
icon=home+'electrum/gui/icons/electrum.ico',
|
||||
icon=home+'icons/electrum.ico',
|
||||
console=False,
|
||||
name=os.path.join('dist', 'electrum'))
|
||||
|
||||
@ -9,10 +9,19 @@ RUN dpkg --add-architecture i386 && \
|
||||
gnupg2=2.2.4-1ubuntu1.2 \
|
||||
dirmngr=2.2.4-1ubuntu1.2 \
|
||||
python3-software-properties=0.96.24.32.1 \
|
||||
software-properties-common=0.96.24.32.1
|
||||
|
||||
RUN apt-get update -q && \
|
||||
apt-get install -qy \
|
||||
software-properties-common=0.96.24.32.1 \
|
||||
&& \
|
||||
wget -nc https://dl.winehq.org/wine-builds/Release.key && \
|
||||
wget -nc https://dl.winehq.org/wine-builds/winehq.key && \
|
||||
apt-key add Release.key && \
|
||||
apt-key add winehq.key && \
|
||||
apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \
|
||||
apt-get update -q && \
|
||||
apt-get install -qy \
|
||||
wine-stable-amd64:amd64=3.0.1~bionic \
|
||||
wine-stable-i386:i386=3.0.1~bionic \
|
||||
wine-stable:amd64=3.0.1~bionic \
|
||||
winehq-stable:amd64=3.0.1~bionic \
|
||||
git=1:2.17.1-1ubuntu0.4 \
|
||||
p7zip-full=16.02+dfsg-6 \
|
||||
make=4.1-9.1ubuntu1 \
|
||||
@ -20,22 +29,8 @@ RUN apt-get update -q && \
|
||||
autotools-dev=20180224.1 \
|
||||
autoconf=2.69-11 \
|
||||
libtool=2.4.6-2 \
|
||||
gettext=0.19.8.1-6
|
||||
|
||||
RUN wget -nc https://dl.winehq.org/wine-builds/Release.key && \
|
||||
echo "c51bcb8cc4a12abfbd7c7660eaf90f49674d15e222c262f27e6c96429111b822 Release.key" | sha256sum -c - && \
|
||||
apt-key add Release.key && \
|
||||
wget -nc https://dl.winehq.org/wine-builds/winehq.key && \
|
||||
echo "78b185fabdb323971d13bd329fefc8038e08559aa51c4996de18db0639a51df6 winehq.key" | sha256sum -c - && \
|
||||
apt-key add winehq.key && \
|
||||
apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \
|
||||
apt-get update -q && \
|
||||
apt-get install -qy \
|
||||
wine-stable-amd64:amd64=4.0~bionic \
|
||||
wine-stable-i386:i386=4.0~bionic \
|
||||
wine-stable:amd64=4.0~bionic \
|
||||
winehq-stable:amd64=4.0~bionic
|
||||
|
||||
RUN rm -rf /var/lib/apt/lists/* && \
|
||||
gettext=0.19.8.1-6 \
|
||||
&& \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
|
||||
@ -20,7 +20,7 @@ folder.
|
||||
2. Build image
|
||||
|
||||
```
|
||||
$ sudo docker build -t electrum-wine-builder-img contrib/build-wine/docker
|
||||
$ sudo docker build --no-cache -t electrum-wine-builder-img contrib/build-wine/docker
|
||||
```
|
||||
|
||||
Note: see [this](https://stackoverflow.com/a/40516974/7499128) if having dns problems
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
!define MUI_ABORTWARNING
|
||||
!define MUI_ABORTWARNING_TEXT "Are you sure you wish to abort the installation of ${PRODUCT_NAME}?"
|
||||
|
||||
!define MUI_ICON "c:\electrum\electrum\gui\icons\electrum.ico"
|
||||
!define MUI_ICON "c:\electrum\icons\electrum.ico"
|
||||
|
||||
;--------------------------------
|
||||
;Pages
|
||||
@ -111,7 +111,7 @@ Section
|
||||
|
||||
;Files to pack into the installer
|
||||
File /r "dist\electrum\*.*"
|
||||
File "c:\electrum\electrum\gui\icons\electrum.ico"
|
||||
File "c:\electrum\icons\electrum.ico"
|
||||
|
||||
;Store installation folder
|
||||
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "" $INSTDIR
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Please update these carefully, some versions won't work under Wine
|
||||
NSIS_FILENAME=nsis-3.04-setup.exe
|
||||
NSIS_FILENAME=nsis-3.03-setup.exe
|
||||
NSIS_URL=https://prdownloads.sourceforge.net/nsis/$NSIS_FILENAME?download
|
||||
NSIS_SHA256=4e1db5a7400e348b1b46a4a11b6d9557fd84368e4ad3d4bc4c1be636c89638aa
|
||||
NSIS_SHA256=bd3b15ab62ec6b0c7a00f46022d441af03277be893326f6fea8e212dc2d77743
|
||||
|
||||
ZBAR_FILENAME=zbarw-20121031-setup.exe
|
||||
ZBAR_URL=https://sourceforge.net/projects/zbarw/files/$ZBAR_FILENAME/download
|
||||
@ -13,7 +13,7 @@ LIBUSB_FILENAME=libusb-1.0.22.7z
|
||||
LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.22/$LIBUSB_FILENAME?download
|
||||
LIBUSB_SHA256=671f1a420757b4480e7fadc8313d6fb3cbb75ca00934c417c1efa6e77fb8779b
|
||||
|
||||
PYTHON_VERSION=3.6.8
|
||||
PYTHON_VERSION=3.6.7
|
||||
|
||||
## These settings probably don't need change
|
||||
export WINEPREFIX=/opt/wine64
|
||||
@ -24,14 +24,68 @@ PYHOME="c:/$PYTHON_FOLDER"
|
||||
PYTHON="wine $PYHOME/python.exe -OO -B"
|
||||
|
||||
|
||||
# Let's begin!
|
||||
here="$(dirname "$(readlink -e "$0")")"
|
||||
set -e
|
||||
# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg
|
||||
verify_signature() {
|
||||
local file=$1 keyring=$2 out=
|
||||
if out=$(gpg --no-default-keyring --keyring "$keyring" --status-fd 1 --verify "$file" 2>/dev/null) &&
|
||||
echo "$out" | grep -qs "^\[GNUPG:\] VALIDSIG "; then
|
||||
return 0
|
||||
else
|
||||
echo "$out" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
. $here/../build_tools_util.sh
|
||||
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
|
||||
}
|
||||
|
||||
download_if_not_exist() {
|
||||
local file_name=$1 url=$2
|
||||
if [ ! -e $file_name ] ; then
|
||||
wget -O $PWD/$file_name "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh
|
||||
retry() {
|
||||
local result=0
|
||||
local count=1
|
||||
while [ $count -le 3 ]; do
|
||||
[ $result -ne 0 ] && {
|
||||
echo -e "\nThe command \"$@\" failed. Retrying, $count of 3.\n" >&2
|
||||
}
|
||||
! { "$@"; result=$?; }
|
||||
[ $result -eq 0 ] && break
|
||||
count=$(($count + 1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
[ $count -gt 3 ] && {
|
||||
echo -e "\nThe command \"$@\" failed 3 times.\n" >&2
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Let's begin!
|
||||
here=$(dirname $(readlink -e $0))
|
||||
set -e
|
||||
|
||||
wine 'wineboot'
|
||||
|
||||
# HACK to work around https://bugs.winehq.org/show_bug.cgi?id=42474#c22
|
||||
# needed for python 3.6+
|
||||
rm -f /opt/wine-stable/lib/wine/fakedlls/api-ms-win-core-path-l1-1-0.dll
|
||||
rm -f /opt/wine-stable/lib/wine/api-ms-win-core-path-l1-1-0.dll.so
|
||||
|
||||
cd /tmp/electrum-build
|
||||
|
||||
@ -55,9 +109,11 @@ for msifile in core dev exe lib pip tools; do
|
||||
wine msiexec /i "${msifile}.msi" /qb TARGETDIR=$PYHOME
|
||||
done
|
||||
|
||||
# Install dependencies specific to binaries
|
||||
# note that this also installs pinned versions of both pip and setuptools
|
||||
$PYTHON -m pip install -r "$here"/../deterministic-build/requirements-binaries.txt
|
||||
# upgrade pip
|
||||
$PYTHON -m pip install pip --upgrade
|
||||
|
||||
|
||||
$PYTHON -m pip install -r $here/../deterministic-build/requirements-binaries.txt
|
||||
|
||||
# Install PyInstaller
|
||||
$PYTHON -m pip install pyinstaller==3.4 --no-use-pep517
|
||||
@ -67,6 +123,9 @@ download_if_not_exist $ZBAR_FILENAME "$ZBAR_URL"
|
||||
verify_hash $ZBAR_FILENAME "$ZBAR_SHA256"
|
||||
wine "$PWD/$ZBAR_FILENAME" /S
|
||||
|
||||
# Upgrade setuptools (so Electrum can be installed later)
|
||||
$PYTHON -m pip install setuptools --upgrade
|
||||
|
||||
# Install NSIS installer
|
||||
download_if_not_exist $NSIS_FILENAME "$NSIS_URL"
|
||||
verify_hash $NSIS_FILENAME "$NSIS_SHA256"
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[0;33m'
|
||||
NC='\033[0m' # No Color
|
||||
function info {
|
||||
printf "\r💬 ${BLUE}INFO:${NC} ${1}\n"
|
||||
}
|
||||
function fail {
|
||||
printf "\r🗯 ${RED}ERROR:${NC} ${1}\n"
|
||||
exit 1
|
||||
}
|
||||
function warn {
|
||||
printf "\r⚠️ ${YELLOW}WARNING:${NC} ${1}\n"
|
||||
}
|
||||
|
||||
|
||||
# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg
|
||||
function verify_signature() {
|
||||
local file=$1 keyring=$2 out=
|
||||
if out=$(gpg --no-default-keyring --keyring "$keyring" --status-fd 1 --verify "$file" 2>/dev/null) &&
|
||||
echo "$out" | grep -qs "^\[GNUPG:\] VALIDSIG "; then
|
||||
return 0
|
||||
else
|
||||
echo "$out" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
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 -O $file_name "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh
|
||||
function retry() {
|
||||
local result=0
|
||||
local count=1
|
||||
while [ $count -le 3 ]; do
|
||||
[ $result -ne 0 ] && {
|
||||
echo -e "\nThe command \"$@\" failed. Retrying, $count of 3.\n" >&2
|
||||
}
|
||||
! { "$@"; result=$?; }
|
||||
[ $result -eq 0 ] && break
|
||||
count=$(($count + 1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
[ $count -gt 3 ] && {
|
||||
echo -e "\nThe command \"$@\" failed 3 times.\n" >&2
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
@ -18,6 +18,13 @@ function get_git_mtime {
|
||||
|
||||
fail=0
|
||||
|
||||
for f in icons/* "icons.qrc"; do
|
||||
if (( $(get_git_mtime "$f") > $(get_git_mtime "contrib/deterministic-build/electrum-icons/") )); then
|
||||
echo "Modification time of $f (" $(get_git_mtime --readable "$f") ") is newer than"\
|
||||
"last update of electrum-icons"
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $(date +%s -d "2 weeks ago") -gt $(get_git_mtime "contrib/deterministic-build/electrum-locale/") ]; then
|
||||
echo "Last update from electrum-locale is older than 2 weeks."\
|
||||
|
||||
1
contrib/deterministic-build/electrum-icons
Submodule
1
contrib/deterministic-build/electrum-icons
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 201d45cd5d855c4f9de5680ab5c53621574dc6b6
|
||||
@ -1 +1 @@
|
||||
Subproject commit ff5ad3a4436dddcc82799f8a91793013240c3b7b
|
||||
Subproject commit e5b28e4717d8c8a736d6d4e5398fa4fa67db80d0
|
||||
@ -48,9 +48,9 @@ PyQt5-sip==4.19.13 \
|
||||
--hash=sha256:a91a308a5e0cc99de1e97afd8f09f46dd7ca20cfaa5890ef254113eebaa1adff \
|
||||
--hash=sha256:b0342540da479d2713edc68fb21f307473f68da896ad5c04215dae97630e0069 \
|
||||
--hash=sha256:f997e21b4e26a3397cb7b255b8d1db5b9772c8e0c94b6d870a5a0ab5c27eacaa
|
||||
setuptools==40.8.0 \
|
||||
--hash=sha256:6e4eec90337e849ade7103723b9a99631c1f0d19990d6e8412dc42f5ae8b304d \
|
||||
--hash=sha256:e8496c0079f3ac30052ffe69b679bd876c5265686127a3159cfa415669b7f9ab
|
||||
setuptools==40.6.3 \
|
||||
--hash=sha256:3b474dad69c49f0d2d86696b68105f3a6f195f7ab655af12ef9a9c326d2b08f8 \
|
||||
--hash=sha256:e2c1ce9a832f34cf7a31ed010aabcab5008eb65ce8f2aadc04622232c14bdd0b
|
||||
wheel==0.32.3 \
|
||||
--hash=sha256:029703bf514e16c8271c3821806a1c171220cc5bdd325cbf4e7da1e056a01db6 \
|
||||
--hash=sha256:1e53cdb3f808d5ccd0df57f964263752aa74ea7359526d3da6c02114ec1e1d44
|
||||
|
||||
@ -14,35 +14,35 @@ click==7.0 \
|
||||
--hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7
|
||||
construct==2.9.45 \
|
||||
--hash=sha256:2271a0efd0798679dea825ff47e22a4c550456a5db0ba8baa82f7eae0af0118c
|
||||
Cython==0.29.4 \
|
||||
--hash=sha256:004eeb2fc64e9db4a3bc0d65583d69769c7242d29d9335121cbab776688dc122 \
|
||||
--hash=sha256:028ee8571884a129e0d5c4d48296f6b3ea679668c096bb65fe8b2ff7ac29d707 \
|
||||
--hash=sha256:162b8b794ca9210c7039d54b6d96cd342e0404e41e7e467baae69f0252d7e52a \
|
||||
--hash=sha256:1aba4cf581d203e8fa3b6a7b432b09416e4f93c0d1f7744834acacfe3e9db424 \
|
||||
--hash=sha256:1be8f08c87b92a880f2fd19f93293e738ca8647834ad05625635320cec9ecad4 \
|
||||
--hash=sha256:21c707a811912aeb65abe8a66e5adebc759889661c8f4cf677523cd33c609084 \
|
||||
--hash=sha256:234de250ef09ba667fc6a8f6ba07712d3fe5bb8d92d70d2b958d4c56e3172c4a \
|
||||
--hash=sha256:33dad82003df518e1242ac3b0592fc63c49d65d0d37b696cb43b7d35085e6bd5 \
|
||||
--hash=sha256:54ee6cbc1397b27670e598ae15cab36e826a01605f63bf267a5fd2642bd8a147 \
|
||||
--hash=sha256:6058c57657d2704c9fad8a56458173d2f525dce4083ca46e9b99b1b35da2b27f \
|
||||
--hash=sha256:6d3065f39ea1354eba4807e2752e97d57f26d6f68bc4a4c561264ca4300c46cb \
|
||||
--hash=sha256:7059e5acac1d7a82e75e553924d9ea59b0e79203adf903cb999287fbcc8f50f1 \
|
||||
--hash=sha256:71c31e01f20a3a7273f6f38760d29170ee89e895be540481130cb173ef6b7246 \
|
||||
--hash=sha256:89225447801e8bd0f6d8e2c0807ded83af8ad7bf4086b5ecf1f22c5a68d1b3e3 \
|
||||
--hash=sha256:9783f11fe4a4af66b0aa0da68fda833c10b95edd9099a6dbe710d03bcb96adf2 \
|
||||
--hash=sha256:9a0be0aac30d71fe490a2b0377fca6e13a5242ecc01d09c7a358f1f2fcb07a80 \
|
||||
--hash=sha256:9a2cccc26dcf2df1e0048cdf63bd714f1d5dfad457f03b9938c5cc3eef74c9ab \
|
||||
--hash=sha256:b0889310f8558eb406a4a853d63553b90c621476f1b5b80b46b1ff57eef198cf \
|
||||
--hash=sha256:c46ef7b771c88512435399e5ffbc3a70079d4945123d6fbfc6211b4cfdc4e546 \
|
||||
--hash=sha256:c71a77c1047d65e5b4e614053cbb7b567c36359b2bc1d27fba23b984ab6dddd0 \
|
||||
--hash=sha256:c9361811a1a49db11efce54fedd01a5544af8db074fce471c720bdb85ec9c7a8 \
|
||||
--hash=sha256:d021a8326a1d2cdb182b0dd7f49bb42d8a4e6ddfb3c8d388ee5be26d57d49f3b \
|
||||
--hash=sha256:d1ee3d39c73a094ae5b6e2f9263ae0dc61af1b549a0869ade8c3c30325ed9f26 \
|
||||
--hash=sha256:d49d7cf82192edc6e386262a07ceb3515028afbd9009dd8ec669d2c0a9f20128 \
|
||||
--hash=sha256:dc5fc1fa072a98f152e46465aaf3e02b3ea36a9d3b8c79bfabd47b0e3ad9226c \
|
||||
--hash=sha256:e290fed7fe73860657af564e596fff87e75cfda861c067e89212970a47826cc6 \
|
||||
--hash=sha256:fcf9a9a566ab98495db641eefee471eb03df71e394ee51fdfa9b4c0b9f6928eb \
|
||||
--hash=sha256:fe8c1d2538867bf2753988a4a2d548bcb211fcbba125aa3e9092391b16f47b56
|
||||
Cython==0.29.3 \
|
||||
--hash=sha256:1327655db47beb665961d3dc0365e20c9e8e80c234513ab2c7d06ec0dd9d63eb \
|
||||
--hash=sha256:142400f13102403f43576bb92d808a668e29deda5625388cfa39fe0bcf37b3d1 \
|
||||
--hash=sha256:1b4204715141281a631337378f0c15fe660b35e1b6888ca05f1f3f49df3b97d5 \
|
||||
--hash=sha256:23aabaaf8887e6db99df2145de6742f8c92830134735778bf2ae26338f2b406f \
|
||||
--hash=sha256:2a724c6f21fdf4e3c1e8c5c862ff87f5420fdaecf53a5a0417915e483d90217f \
|
||||
--hash=sha256:2c9c8c1c6e8bd3587e5f5db6f865a42195ff2dedcaf5cdb63fdea10c98bd6246 \
|
||||
--hash=sha256:3a1be38b774423605189d60652b3d8a324fc81d213f96569720c8093784245ab \
|
||||
--hash=sha256:46be5297a76513e4d5d6e746737d4866a762cfe457e57d7c54baa7ef8fea7e9a \
|
||||
--hash=sha256:48dc2ea4c4d3f34ddcad5bc71b1f1cf49830f868832d3e5df803c811e7395b6e \
|
||||
--hash=sha256:53f33e04d2ed078ac02841741bcd536b546e1f416608084468ab30a87638a466 \
|
||||
--hash=sha256:57b10588618ca19a4cc870f381aa8805bc5fe0c62d19d7f940232ff8a373887c \
|
||||
--hash=sha256:6001038341b52301450bb9c62e5d5da825788944572679277e137ffb3596e718 \
|
||||
--hash=sha256:70bef52e735607060f327d729be35c820d9018d260a875e4f98b20ba8c4fff96 \
|
||||
--hash=sha256:7d0f76b251699be8f1f1064dcb12d4b3b2b676ce15ff30c104e0c2091a015142 \
|
||||
--hash=sha256:9440b64c1569c26a184b7c778bb221cf9987c5c8486d32cda02302c66ea78980 \
|
||||
--hash=sha256:956cc97eac6f9d3b16e3b2d2a94c5586af3403ba97945e9d88a4a0f029899646 \
|
||||
--hash=sha256:ae430ad8cce937e07ea566d1d7899eef1fedc8ec512b4d5fa37ebf2c1f879936 \
|
||||
--hash=sha256:bdb575149881978d62167dd8427402a5872a79bd83e9d51219680670e9f80b40 \
|
||||
--hash=sha256:c0ffcddd3dbdf22aae3980931112cc8b2732315a6273988f3205cf5dacf36f45 \
|
||||
--hash=sha256:c133e2efc57426974366ac74f2ef0f1171b860301ac27f72316deacff4ccdc17 \
|
||||
--hash=sha256:c6e9521d0b77eb1da89e8264eb98c8f5cda7c49a49b8128acfd35f0ca50e56d0 \
|
||||
--hash=sha256:c7cac0220ecb733024e8acfcfb6b593a007185690f2ea470d2392b72510b7187 \
|
||||
--hash=sha256:d53483820ac28f2be2ff13eedd56c0f36a4c583727b551d3d468023556e2336a \
|
||||
--hash=sha256:d60210784186d61e0ec808d5dbee5d661c7457a57f93cb5fdc456394607ce98c \
|
||||
--hash=sha256:d687fb1cd9df28c1515666174c62e54bd894a6a6d0862f89705063cd47739f83 \
|
||||
--hash=sha256:d926764d9c768a48b0a16a91696aaa25498057e060934f968fa4c5629b942d85 \
|
||||
--hash=sha256:d94a2f4ad74732f58d1c771fc5d90a62c4fe4c98d0adfecbc76cd0d8d14bf044 \
|
||||
--hash=sha256:def76a546eeec059666f5f4117dfdf9c78e50fa1f95bdd23b04618c7adf845cd
|
||||
ecdsa==0.13 \
|
||||
--hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \
|
||||
--hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa
|
||||
@ -110,9 +110,9 @@ requests==2.21.0 \
|
||||
safet==0.1.4 \
|
||||
--hash=sha256:522c257910f9472e9c77c487425ed286f6721c314653e232bc41c6cedece1bb1 \
|
||||
--hash=sha256:b152874acdc89ff0c8b2d680bfbf020b3e53527c2ad3404489dd61a548aa56a1
|
||||
setuptools==40.8.0 \
|
||||
--hash=sha256:6e4eec90337e849ade7103723b9a99631c1f0d19990d6e8412dc42f5ae8b304d \
|
||||
--hash=sha256:e8496c0079f3ac30052ffe69b679bd876c5265686127a3159cfa415669b7f9ab
|
||||
setuptools==40.6.3 \
|
||||
--hash=sha256:3b474dad69c49f0d2d86696b68105f3a6f195f7ab655af12ef9a9c326d2b08f8 \
|
||||
--hash=sha256:e2c1ce9a832f34cf7a31ed010aabcab5008eb65ce8f2aadc04622232c14bdd0b
|
||||
six==1.12.0 \
|
||||
--hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \
|
||||
--hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73
|
||||
|
||||
@ -24,9 +24,9 @@ aiohttp==3.5.4 \
|
||||
aiohttp-socks==0.2.2 \
|
||||
--hash=sha256:e473ee222b001fe33798957b9ce3352b32c187cf41684f8e2259427925914993 \
|
||||
--hash=sha256:eebd8939a7c3c1e3e7e1b2552c60039b4c65ef6b8b2351efcbdd98290538e310
|
||||
aiorpcX==0.10.4 \
|
||||
--hash=sha256:7130105d31230f069b0eea4e1893c7199cfe2d89a52a31aec718d37f4449935d \
|
||||
--hash=sha256:e6dfd584f597ee3aa6a8d4cb5755c8ffbbe42754f32728561d9e5940379d5096
|
||||
aiorpcX==0.10.2 \
|
||||
--hash=sha256:23a59e625ca50cdf2866a2d8bd54256e648582a8d44d4495b34252f3dbc30e23 \
|
||||
--hash=sha256:d2bf57fc46ae37d769ab3f5e58ebee4b44acab626e597b5150a201284b9808dd
|
||||
async_timeout==3.0.1 \
|
||||
--hash=sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f \
|
||||
--hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3
|
||||
@ -112,9 +112,9 @@ QDarkStyle==2.5.4 \
|
||||
qrcode==6.1 \
|
||||
--hash=sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5 \
|
||||
--hash=sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369
|
||||
setuptools==40.8.0 \
|
||||
--hash=sha256:6e4eec90337e849ade7103723b9a99631c1f0d19990d6e8412dc42f5ae8b304d \
|
||||
--hash=sha256:e8496c0079f3ac30052ffe69b679bd876c5265686127a3159cfa415669b7f9ab
|
||||
setuptools==40.6.3 \
|
||||
--hash=sha256:3b474dad69c49f0d2d86696b68105f3a6f195f7ab655af12ef9a9c326d2b08f8 \
|
||||
--hash=sha256:e2c1ce9a832f34cf7a31ed010aabcab5008eb65ce8f2aadc04622232c14bdd0b
|
||||
six==1.12.0 \
|
||||
--hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \
|
||||
--hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
#!/bin/bash
|
||||
# Run this after a new release to update dependencies
|
||||
|
||||
set -e
|
||||
|
||||
venv_dir=~/.electrum-venv
|
||||
contrib=$(dirname "$0")
|
||||
|
||||
|
||||
@ -1,22 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
CONTRIB="$(dirname "$(readlink -e "$0")")"
|
||||
ROOT_FOLDER="$CONTRIB"/..
|
||||
PACKAGES="$ROOT_FOLDER"/packages/
|
||||
LOCALE="$ROOT_FOLDER"/electrum/locale/
|
||||
|
||||
if [ ! -d "$LOCALE" ]; then
|
||||
echo "Run make_locale first!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$PACKAGES" ]; then
|
||||
echo "Run make_packages first!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pushd ./electrum/gui/kivy/
|
||||
|
||||
make theming
|
||||
|
||||
@ -24,7 +24,6 @@ string = string.replace("##VERSION_APK##", APK_VERSION)
|
||||
|
||||
files = {
|
||||
'tgz': "Electrum-%s.tar.gz" % version,
|
||||
'appimage': "electrum-%s-x86_64.AppImage" % version,
|
||||
'zip': "Electrum-%s.zip" % version,
|
||||
'mac': "electrum-%s.dmg" % version_mac,
|
||||
'win': "electrum-%s.exe" % version_win,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
CONTRIB="$(dirname "$0")"
|
||||
test -n "$CONTRIB" -a -d "$CONTRIB" || exit
|
||||
contrib=$(dirname "$0")
|
||||
test -n "$contrib" -a -d "$contrib" || exit
|
||||
|
||||
rm "$CONTRIB"/../packages/ -r
|
||||
rm "$contrib"/../packages/ -r
|
||||
|
||||
#Install pure python modules in electrum directory
|
||||
python3 -m pip install -r "$CONTRIB"/deterministic-build/requirements.txt -t "$CONTRIB"/../packages
|
||||
python3 -m pip install -r $contrib/deterministic-build/requirements.txt -t $contrib/../packages
|
||||
|
||||
|
||||
@ -1,31 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
contrib=$(dirname "$0")
|
||||
packages="$contrib"/../packages/
|
||||
|
||||
CONTRIB="$(dirname "$(readlink -e "$0")")"
|
||||
ROOT_FOLDER="$CONTRIB"/..
|
||||
PACKAGES="$ROOT_FOLDER"/packages/
|
||||
LOCALE="$ROOT_FOLDER"/electrum/locale/
|
||||
|
||||
if [ ! -d "$LOCALE" ]; then
|
||||
echo "Run make_locale first!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$PACKAGES" ]; then
|
||||
if [ ! -d "$packages" ]; then
|
||||
echo "Run make_packages first!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(
|
||||
cd "$ROOT_FOLDER"
|
||||
|
||||
echo "'git clean -fx' would delete the following files: >>>"
|
||||
git clean -fx --dry-run
|
||||
echo "<<<"
|
||||
|
||||
# we could build the kivy atlas potentially?
|
||||
#(cd electrum/gui/kivy/; make theming) || echo "building kivy atlas failed! skipping."
|
||||
|
||||
python3 setup.py --quiet sdist --format=zip,gztar
|
||||
)
|
||||
python3 setup.py sdist --format=zip,gztar
|
||||
|
||||
@ -1,7 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
. $(dirname "$0")/../build_tools_util.sh
|
||||
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0,34m'
|
||||
YELLOW='\033[0;33m'
|
||||
NC='\033[0m' # No Color
|
||||
function info {
|
||||
printf "\r💬 ${BLUE}INFO:${NC} ${1}\n"
|
||||
}
|
||||
function fail {
|
||||
printf "\r🗯 ${RED}ERROR:${NC} ${1}\n"
|
||||
exit 1
|
||||
}
|
||||
function warn {
|
||||
printf "\r⚠️ ${YELLOW}WARNING:${NC} ${1}\n"
|
||||
}
|
||||
|
||||
function DoCodeSignMaybe { # ARGS: infoName fileOrDirName codesignIdentity
|
||||
infoName="$1"
|
||||
|
||||
@ -5,7 +5,7 @@ PYTHON_VERSION=3.6.4
|
||||
BUILDDIR=/tmp/electrum-build
|
||||
PACKAGE=Electrum
|
||||
GIT_REPO=https://github.com/spesmilo/electrum
|
||||
LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7"
|
||||
LIBSECP_VERSION=452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4
|
||||
|
||||
. $(dirname "$0")/base.sh
|
||||
|
||||
@ -48,14 +48,8 @@ pyenv global $PYTHON_VERSION || \
|
||||
fail "Unable to use Python $PYTHON_VERSION"
|
||||
|
||||
|
||||
info "install dependencies specific to binaries"
|
||||
# note that this also installs pinned versions of both pip and setuptools
|
||||
python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user \
|
||||
|| fail "Could not install pyinstaller"
|
||||
|
||||
|
||||
info "Installing pyinstaller"
|
||||
python3 -m pip install -I --user pyinstaller==3.4 --no-use-pep517 || fail "Could not install pyinstaller"
|
||||
python3 -m pip install -I --user pyinstaller==3.4 || fail "Could not install pyinstaller"
|
||||
|
||||
info "Using these versions for building $PACKAGE:"
|
||||
sw_vers
|
||||
@ -72,6 +66,7 @@ rm -rf $BUILDDIR > /dev/null 2>&1
|
||||
mkdir $BUILDDIR
|
||||
|
||||
cp -R ./contrib/deterministic-build/electrum-locale/locale/ ./electrum/locale/
|
||||
cp ./contrib/deterministic-build/electrum-icons/icons_rc.py ./electrum/gui/qt/
|
||||
|
||||
|
||||
info "Downloading libusb..."
|
||||
@ -104,7 +99,8 @@ DoCodeSignMaybe "CalinsQRReader.app" "${d}/build/Release/CalinsQRReader.app" "$A
|
||||
|
||||
|
||||
info "Installing requirements..."
|
||||
python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user || \
|
||||
python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \
|
||||
python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user || \
|
||||
fail "Could not install requirements"
|
||||
|
||||
info "Installing hardware wallet requirements..."
|
||||
@ -112,7 +108,7 @@ python3 -m pip install -Ir ./contrib/deterministic-build/requirements-hw.txt --u
|
||||
fail "Could not install hardware wallet requirements"
|
||||
|
||||
info "Building $PACKAGE..."
|
||||
python3 -m pip install --user . > /dev/null || fail "Could not build $PACKAGE"
|
||||
python3 setup.py install --user > /dev/null || fail "Could not build $PACKAGE"
|
||||
|
||||
info "Faking timestamps..."
|
||||
for d in ~/Library/Python/ ~/.pyenv .; do
|
||||
|
||||
@ -7,7 +7,7 @@ import sys, os
|
||||
PACKAGE='Electrum'
|
||||
PYPKG='electrum'
|
||||
MAIN_SCRIPT='run_electrum'
|
||||
ICONS_FILE=PYPKG + '/gui/icons/electrum.icns'
|
||||
ICONS_FILE='electrum.icns'
|
||||
APP_SIGN = os.environ.get('APP_SIGN', '')
|
||||
|
||||
def fail(*msg):
|
||||
@ -66,17 +66,11 @@ hiddenimports += collect_submodules('keepkeylib')
|
||||
hiddenimports += collect_submodules('websocket')
|
||||
hiddenimports += collect_submodules('ckcc')
|
||||
|
||||
# safetlib imports PyQt5.Qt. We use a local updated copy of pinmatrix.py until they
|
||||
# release a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e
|
||||
hiddenimports.remove('safetlib.qt.pinmatrix')
|
||||
|
||||
|
||||
datas = [
|
||||
(electrum + PYPKG + '/*.json', PYPKG),
|
||||
(electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'),
|
||||
(electrum + PYPKG + '/locale', PYPKG + '/locale'),
|
||||
(electrum + PYPKG + '/plugins', PYPKG + '/plugins'),
|
||||
(electrum + PYPKG + '/gui/icons', PYPKG + '/gui/icons'),
|
||||
]
|
||||
datas += collect_data_files('trezorlib')
|
||||
datas += collect_data_files('safetlib')
|
||||
|
||||
@ -85,4 +85,4 @@ dmg dmg Electrum_uncompressed.dmg electrum-$VERSION.dmg || fail "Unable to creat
|
||||
rm Electrum_uncompressed.dmg
|
||||
|
||||
echo "Done."
|
||||
sha256sum electrum-$VERSION.dmg
|
||||
md5sum electrum-$VERSION.dmg
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
Cython>=0.27
|
||||
trezor[hidapi]>=0.11.1
|
||||
trezor[hidapi]>=0.11.0
|
||||
safet[hidapi]>=0.1.0
|
||||
keepkey
|
||||
btchip-python>=0.1.26
|
||||
|
||||
@ -6,7 +6,7 @@ dnspython
|
||||
jsonrpclib-pelix
|
||||
qdarkstyle<2.6
|
||||
aiorpcx>=0.9,<0.11
|
||||
aiohttp>=3.3.0
|
||||
aiohttp
|
||||
aiohttp_socks
|
||||
certifi
|
||||
pylibscrypt==1.7.1
|
||||
pylibscrypt=1.7.1
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
version=`python3 -c "import electrum; print(electrum.version.ELECTRUM_VERSION)"`
|
||||
sig=`./run_electrum -w $SIGNING_WALLET signmessage $SIGNING_ADDRESS $version`
|
||||
echo "{ \"version\":\"$version\", \"signatures\":{ \"$SIGNING_ADDRESS\":\"$sig\"}}"
|
||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
@ -404,7 +404,6 @@ class AddressSynchronizer(PrintError):
|
||||
@profiler
|
||||
def load_local_history(self):
|
||||
self._history_local = {} # address -> set(txid)
|
||||
self._address_history_changed_events = defaultdict(asyncio.Event) # address -> Event
|
||||
for txid in itertools.chain(self.txi, self.txo):
|
||||
self._add_tx_to_local_history(txid)
|
||||
|
||||
@ -544,7 +543,6 @@ class AddressSynchronizer(PrintError):
|
||||
cur_hist = self._history_local.get(addr, set())
|
||||
cur_hist.add(txid)
|
||||
self._history_local[addr] = cur_hist
|
||||
self._mark_address_history_changed(addr)
|
||||
|
||||
def _remove_tx_from_local_history(self, txid):
|
||||
with self.transaction_lock:
|
||||
@ -557,21 +555,6 @@ class AddressSynchronizer(PrintError):
|
||||
else:
|
||||
self._history_local[addr] = cur_hist
|
||||
|
||||
def _mark_address_history_changed(self, addr: str) -> None:
|
||||
# history for this address changed, wake up coroutines:
|
||||
self._address_history_changed_events[addr].set()
|
||||
# clear event immediately so that coroutines can wait() for the next change:
|
||||
self._address_history_changed_events[addr].clear()
|
||||
|
||||
async def wait_for_address_history_to_change(self, addr: str) -> None:
|
||||
"""Wait until the server tells us about a new transaction related to addr.
|
||||
|
||||
Unconfirmed and confirmed transactions are not distinguished, and so e.g. SPV
|
||||
is not taken into account.
|
||||
"""
|
||||
assert self.is_mine(addr), "address needs to be is_mine to be watched"
|
||||
await self._address_history_changed_events[addr].wait()
|
||||
|
||||
def add_unverified_tx(self, tx_hash, tx_height):
|
||||
if tx_hash in self.verified_tx:
|
||||
if tx_height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT):
|
||||
|
||||
@ -27,7 +27,7 @@ import os
|
||||
import sys
|
||||
import traceback
|
||||
from functools import partial
|
||||
from typing import List, TYPE_CHECKING, Tuple, NamedTuple, Any
|
||||
from typing import List, TYPE_CHECKING, Tuple
|
||||
|
||||
from . import bitcoin
|
||||
from . import keystore
|
||||
@ -56,12 +56,6 @@ class ScriptTypeNotSupported(Exception): pass
|
||||
class GoBack(Exception): pass
|
||||
|
||||
|
||||
class WizardStackItem(NamedTuple):
|
||||
action: Any
|
||||
args: Any
|
||||
storage_data: dict
|
||||
|
||||
|
||||
class BaseWizard(object):
|
||||
|
||||
def __init__(self, config: SimpleConfig, plugins: Plugins, storage: WalletStorage):
|
||||
@ -70,7 +64,7 @@ class BaseWizard(object):
|
||||
self.plugins = plugins
|
||||
self.storage = storage
|
||||
self.wallet = None # type: Abstract_Wallet
|
||||
self._stack = [] # type: List[WizardStackItem]
|
||||
self.stack = []
|
||||
self.plugin = None
|
||||
self.keystores = []
|
||||
self.is_kivy = config.get('gui') == 'kivy'
|
||||
@ -82,8 +76,7 @@ class BaseWizard(object):
|
||||
def run(self, *args):
|
||||
action = args[0]
|
||||
args = args[1:]
|
||||
storage_data = self.storage.get_all_data()
|
||||
self._stack.append(WizardStackItem(action, args, storage_data))
|
||||
self.stack.append((action, args))
|
||||
if not action:
|
||||
return
|
||||
if type(action) is tuple:
|
||||
@ -98,23 +91,14 @@ class BaseWizard(object):
|
||||
raise Exception("unknown action", action)
|
||||
|
||||
def can_go_back(self):
|
||||
return len(self._stack) > 1
|
||||
return len(self.stack)>1
|
||||
|
||||
def go_back(self):
|
||||
if not self.can_go_back():
|
||||
return
|
||||
# pop 'current' frame
|
||||
self._stack.pop()
|
||||
# pop 'previous' frame
|
||||
stack_item = self._stack.pop()
|
||||
# try to undo side effects since we last entered 'previous' frame
|
||||
# FIXME only self.storage is properly restored
|
||||
self.storage.overwrite_all_data(stack_item.storage_data)
|
||||
# rerun 'previous' frame
|
||||
self.run(stack_item.action, *stack_item.args)
|
||||
|
||||
def reset_stack(self):
|
||||
self._stack = []
|
||||
self.stack.pop()
|
||||
action, args = self.stack.pop()
|
||||
self.run(action, *args)
|
||||
|
||||
def new(self):
|
||||
name = os.path.basename(self.storage.path)
|
||||
@ -166,8 +150,8 @@ class BaseWizard(object):
|
||||
|
||||
def choose_multisig(self):
|
||||
def on_multisig(m, n):
|
||||
multisig_type = "%dof%d" % (m, n)
|
||||
self.storage.put('wallet_type', multisig_type)
|
||||
self.multisig_type = "%dof%d"%(m, n)
|
||||
self.storage.put('wallet_type', self.multisig_type)
|
||||
self.n = n
|
||||
self.run('choose_keystore')
|
||||
self.multisig_dialog(run_next=on_multisig)
|
||||
@ -491,7 +475,7 @@ class BaseWizard(object):
|
||||
self.keystores.append(k)
|
||||
if len(self.keystores) == 1:
|
||||
xpub = k.get_master_public_key()
|
||||
self.reset_stack()
|
||||
self.stack = []
|
||||
self.run('show_xpub_and_add_cosigners', xpub)
|
||||
elif len(self.keystores) < self.n:
|
||||
self.run('choose_keystore')
|
||||
|
||||
@ -267,21 +267,21 @@ class Blockchain(util.PrintError):
|
||||
@classmethod
|
||||
def verify_header(cls, header: dict, prev_hash: str, target: int, expected_header_hash: str=None) -> None:
|
||||
_hash = hash_header(header)
|
||||
_powhash = pow_hash_header(header)
|
||||
#_powhash = pow_hash_header(header)
|
||||
if expected_header_hash and expected_header_hash != _hash:
|
||||
raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash))
|
||||
if prev_hash != header.get('prev_block_hash'):
|
||||
raise Exception("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
|
||||
if constants.net.TESTNET:
|
||||
return
|
||||
bits = cls.target_to_bits(target)
|
||||
#bits = cls.target_to_bits(target)
|
||||
bits = target
|
||||
if bits != header.get('bits'):
|
||||
raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
|
||||
block_hash_as_num = int.from_bytes(bfh(_hash), byteorder='big')
|
||||
target_val = cls.bits_to_target(bits)
|
||||
if int('0x' + _powhash, 16) > target_val:
|
||||
raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target_val))
|
||||
#target_val = cls.bits_to_target(bits)
|
||||
#if int('0x' + _powhash, 16) > target_val:
|
||||
# raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target_val))
|
||||
|
||||
def verify_chunk(self, index: int, data: bytes) -> None:
|
||||
num = len(data) // HEADER_SIZE
|
||||
|
||||
@ -537,14 +537,11 @@ class Commands:
|
||||
return tx.as_dict()
|
||||
|
||||
@command('w')
|
||||
def history(self, year=None, show_addresses=False, show_fiat=False, show_fees=False,
|
||||
from_height=None, to_height=None):
|
||||
def history(self, year=None, show_addresses=False, show_fiat=False, show_fees=False):
|
||||
"""Wallet history. Returns the transaction history of your wallet."""
|
||||
kwargs = {
|
||||
'show_addresses': show_addresses,
|
||||
'show_fees': show_fees,
|
||||
'from_height': from_height,
|
||||
'to_height': to_height,
|
||||
}
|
||||
if year:
|
||||
import time
|
||||
@ -834,9 +831,7 @@ command_options = {
|
||||
'show_fees': (None, "Show miner fees paid by transactions"),
|
||||
'year': (None, "Show history for a given year"),
|
||||
'fee_method': (None, "Fee estimation method to use"),
|
||||
'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position"),
|
||||
'from_height': (None, "Only show transactions that confirmed after given block height"),
|
||||
'to_height': (None, "Only show transactions that confirmed before given block height"),
|
||||
'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position")
|
||||
}
|
||||
|
||||
|
||||
@ -848,8 +843,6 @@ arg_types = {
|
||||
'nbits': int,
|
||||
'imax': int,
|
||||
'year': int,
|
||||
'from_height': int,
|
||||
'to_height': int,
|
||||
'tx': tx_from_str,
|
||||
'pubkeys': json_loads,
|
||||
'jsontx': json_loads,
|
||||
|
||||
@ -49,8 +49,8 @@ class BitcoinMainnet(AbstractNet):
|
||||
TESTNET = False
|
||||
WIF_PREFIX = 0xa3
|
||||
ADDRTYPE_P2PKH = 35
|
||||
ADDRTYPE_P2SH = 94
|
||||
SEGWIT_HRP = "flo"
|
||||
ADDRTYPE_P2SH = 8
|
||||
SEGWIT_HRP = "ltc"
|
||||
GENESIS = "09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea"
|
||||
DEFAULT_PORTS = {'t': '50001', 's': '50002'}
|
||||
DEFAULT_SERVERS = read_json('servers.json', {})
|
||||
@ -70,7 +70,7 @@ class BitcoinMainnet(AbstractNet):
|
||||
'p2wpkh': 0x04b24746, # zpub
|
||||
'p2wsh': 0x02aa7ed3, # Zpub
|
||||
}
|
||||
BIP44_COIN_TYPE = 216
|
||||
BIP44_COIN_TYPE = 2
|
||||
# FLO Network constants
|
||||
fPowAllowMinDifficultyBlocks = False
|
||||
fPowNoRetargeting = False
|
||||
@ -103,8 +103,8 @@ class BitcoinTestnet(AbstractNet):
|
||||
TESTNET = True
|
||||
WIF_PREFIX = 0xef
|
||||
ADDRTYPE_P2PKH = 115
|
||||
ADDRTYPE_P2SH = 58
|
||||
SEGWIT_HRP = "tflo"
|
||||
ADDRTYPE_P2SH = 198
|
||||
SEGWIT_HRP = "tltc"
|
||||
GENESIS = "9b7bc86236c34b5e3a39367c036b7fe8807a966c22a7a1f0da2a198a27e03731"
|
||||
DEFAULT_PORTS = {'t': '51001', 's': '51002'}
|
||||
DEFAULT_SERVERS = read_json('servers_testnet.json', {})
|
||||
|
||||
@ -37,7 +37,7 @@ from .jsonrpc import VerifyingJSONRPCServer
|
||||
from .version import ELECTRUM_VERSION
|
||||
from .network import Network
|
||||
from .util import (json_decode, DaemonThread, print_error, to_string,
|
||||
create_and_start_event_loop, profiler, standardize_path)
|
||||
create_and_start_event_loop, profiler)
|
||||
from .wallet import Wallet, Abstract_Wallet
|
||||
from .storage import WalletStorage
|
||||
from .commands import known_commands, Commands
|
||||
@ -235,7 +235,6 @@ class Daemon(DaemonThread):
|
||||
return response
|
||||
|
||||
def load_wallet(self, path, password) -> Optional[Abstract_Wallet]:
|
||||
path = standardize_path(path)
|
||||
# wizard will be launched if we return
|
||||
if path in self.wallets:
|
||||
wallet = self.wallets[path]
|
||||
@ -263,13 +262,6 @@ class Daemon(DaemonThread):
|
||||
def get_wallet(self, path):
|
||||
return self.wallets.get(path)
|
||||
|
||||
def delete_wallet(self, path):
|
||||
self.stop_wallet(path)
|
||||
if os.path.exists(path):
|
||||
os.unlink(path)
|
||||
return True
|
||||
return False
|
||||
|
||||
def stop_wallet(self, path):
|
||||
wallet = self.wallets.pop(path, None)
|
||||
if not wallet: return
|
||||
|
||||
@ -14,8 +14,8 @@ from typing import Sequence
|
||||
|
||||
from .bitcoin import COIN
|
||||
from .i18n import _
|
||||
from .util import (PrintError, ThreadJob, make_dir, log_exceptions,
|
||||
make_aiohttp_session, resource_path)
|
||||
from .util import PrintError, ThreadJob, make_dir, log_exceptions
|
||||
from .util import make_aiohttp_session
|
||||
from .network import Network
|
||||
from .simple_config import SimpleConfig
|
||||
|
||||
@ -402,7 +402,7 @@ def dictinvert(d):
|
||||
return inv
|
||||
|
||||
def get_exchanges_and_currencies():
|
||||
path = resource_path('currencies.json')
|
||||
path = os.path.join(os.path.dirname(__file__), 'currencies.json')
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
@ -24,19 +24,13 @@ folder.
|
||||
$ sudo docker build -t electrum-android-builder-img electrum/gui/kivy/tools
|
||||
```
|
||||
|
||||
3. Build locale files
|
||||
3. Prepare pure python dependencies
|
||||
|
||||
```
|
||||
$ ./contrib/make_locale
|
||||
$ sudo ./contrib/make_packages
|
||||
```
|
||||
|
||||
4. Prepare pure python dependencies
|
||||
|
||||
```
|
||||
$ ./contrib/make_packages
|
||||
```
|
||||
|
||||
5. Build binaries
|
||||
4. Build binaries
|
||||
|
||||
```
|
||||
$ sudo docker run -it --rm \
|
||||
|
||||
@ -671,7 +671,7 @@ class ElectrumWindow(App):
|
||||
self.receive_screen = None
|
||||
self.requests_screen = None
|
||||
self.address_screen = None
|
||||
self.icon = "electrum/gui/icons/electrum.png"
|
||||
self.icon = "icons/electrum.png"
|
||||
self.tabs = self.root.ids['tabs']
|
||||
|
||||
def update_interfaces(self, dt):
|
||||
@ -829,6 +829,9 @@ class ElectrumWindow(App):
|
||||
self._clipboard.copy(label.data)
|
||||
Clock.schedule_once(lambda dt: self.show_info(_('Text copied to clipboard.\nTap again to display it as QR code.')))
|
||||
|
||||
def set_send(self, address, amount, label, message):
|
||||
self.send_payment(address, amount=amount, label=label, message=message)
|
||||
|
||||
def show_error(self, error, width='200dp', pos=None, arrow_pos=None,
|
||||
exit=False, icon='atlas://electrum/gui/kivy/theming/light/error', duration=0,
|
||||
modal=False):
|
||||
|
||||
@ -4,17 +4,10 @@ FROM ubuntu:18.04
|
||||
|
||||
ENV ANDROID_HOME="/opt/android"
|
||||
|
||||
# configure locale
|
||||
RUN apt update -qq > /dev/null && apt install -qq --yes --no-install-recommends \
|
||||
locales && \
|
||||
locale-gen en_US.UTF-8
|
||||
ENV LANG="en_US.UTF-8" \
|
||||
LANGUAGE="en_US.UTF-8" \
|
||||
LC_ALL="en_US.UTF-8"
|
||||
|
||||
RUN apt -y update -qq \
|
||||
&& apt -y install -qq --no-install-recommends curl unzip ca-certificates \
|
||||
&& apt -y autoremove
|
||||
&& apt -y install -qq --no-install-recommends curl unzip git python3-pip python3-setuptools \
|
||||
&& apt -y autoremove \
|
||||
&& apt -y clean
|
||||
|
||||
|
||||
ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk"
|
||||
@ -39,7 +32,6 @@ ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk"
|
||||
|
||||
# get the latest version from https://developer.android.com/studio/index.html
|
||||
ENV ANDROID_SDK_TOOLS_VERSION="4333796"
|
||||
ENV ANDROID_SDK_BUILD_TOOLS_VERSION="28.0.3"
|
||||
ENV ANDROID_SDK_TOOLS_ARCHIVE="sdk-tools-linux-${ANDROID_SDK_TOOLS_VERSION}.zip"
|
||||
ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}"
|
||||
|
||||
@ -59,13 +51,14 @@ RUN mkdir --parents "${ANDROID_SDK_HOME}/.android/" \
|
||||
# accept Android licenses (JDK necessary!)
|
||||
RUN apt -y update -qq \
|
||||
&& apt -y install -qq --no-install-recommends openjdk-8-jdk \
|
||||
&& apt -y autoremove
|
||||
RUN yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null
|
||||
&& apt -y autoremove \
|
||||
&& apt -y clean
|
||||
RUN yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" --licenses > /dev/null
|
||||
|
||||
# download platforms, API, build tools
|
||||
RUN "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-24" > /dev/null && \
|
||||
"${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-28" > /dev/null && \
|
||||
"${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null && \
|
||||
"${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;28.0.3" > /dev/null && \
|
||||
"${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "extras;android;m2repository" > /dev/null && \
|
||||
chmod +x "${ANDROID_SDK_HOME}/tools/bin/avdmanager"
|
||||
|
||||
@ -78,9 +71,8 @@ ENV WORK_DIR="${HOME_DIR}/wspace" \
|
||||
# install system dependencies
|
||||
RUN apt -y update -qq \
|
||||
&& apt -y install -qq --no-install-recommends \
|
||||
python3 virtualenv python3-pip python3-setuptools git wget lbzip2 patch sudo \
|
||||
software-properties-common \
|
||||
&& apt -y autoremove
|
||||
python virtualenv python-pip wget lbzip2 patch sudo \
|
||||
software-properties-common
|
||||
|
||||
# install kivy
|
||||
RUN add-apt-repository ppa:kivy-team/kivy \
|
||||
@ -95,7 +87,7 @@ RUN python3 -m pip install image
|
||||
RUN dpkg --add-architecture i386 \
|
||||
&& apt -y update -qq \
|
||||
&& apt -y install -qq --no-install-recommends \
|
||||
build-essential ccache git python3 python3-dev \
|
||||
build-essential ccache git python2.7 python2.7-dev \
|
||||
libncurses5:i386 libstdc++6:i386 libgtk2.0-0:i386 \
|
||||
libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 libidn11:i386 \
|
||||
zip zlib1g-dev zlib1g:i386 \
|
||||
@ -105,7 +97,8 @@ RUN dpkg --add-architecture i386 \
|
||||
# specific recipes dependencies (e.g. libffi requires autoreconf binary)
|
||||
RUN apt -y update -qq \
|
||||
&& apt -y install -qq --no-install-recommends \
|
||||
libffi-dev autoconf automake cmake gettext libltdl-dev libtool pkg-config \
|
||||
autoconf automake cmake gettext libltdl-dev libtool pkg-config \
|
||||
python3.7 \
|
||||
&& apt -y autoremove \
|
||||
&& apt -y clean
|
||||
|
||||
@ -126,7 +119,8 @@ RUN chown ${USER} /opt
|
||||
USER ${USER}
|
||||
|
||||
|
||||
RUN python3 -m pip install --upgrade cython==0.28.6
|
||||
RUN pip install --upgrade cython==0.29
|
||||
RUN python3 -m pip install --upgrade cython==0.29
|
||||
|
||||
# prepare git
|
||||
RUN git config --global user.name "John Doe" \
|
||||
@ -136,7 +130,6 @@ RUN git config --global user.name "John Doe" \
|
||||
RUN cd /opt \
|
||||
&& git clone https://github.com/kivy/buildozer \
|
||||
&& cd buildozer \
|
||||
&& git checkout 88e4a4b0c7733eec1d14c00579ec412fb59ad7f2 \
|
||||
&& python3 -m pip install -e .
|
||||
|
||||
# install python-for-android
|
||||
@ -145,7 +138,7 @@ RUN cd /opt \
|
||||
&& cd python-for-android \
|
||||
&& git remote add sombernight https://github.com/SomberNight/python-for-android \
|
||||
&& git fetch --all \
|
||||
&& git checkout dec1badc3bd134a9a1c69275339423a95d63413e \
|
||||
&& git checkout fad5dd2fdc9b116b7621470deac501e4a7c4cc11 \
|
||||
# allowBackup="false":
|
||||
&& git cherry-pick 86eeec7c19679a5886d5e095ce0a43f1da138f87 \
|
||||
&& python3 -m pip install -e .
|
||||
|
||||
@ -31,14 +31,14 @@ version.filename = %(source.dir)s/electrum/version.py
|
||||
#version = 1.9.8
|
||||
|
||||
# (list) Application requirements
|
||||
requirements = python3, android, openssl, plyer, kivy==b47f669f44dbda4f463bcb7d2cada639f7fed3bc, libffi, libsecp256k1
|
||||
requirements = python3, android, openssl, plyer, kivy==master, libffi, libsecp256k1
|
||||
|
||||
# (str) Presplash of the application
|
||||
#presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png
|
||||
presplash.filename = %(source.dir)s/electrum/gui/icons/electrum_presplash.png
|
||||
presplash.filename = %(source.dir)s/icons/electrum_presplash.png
|
||||
|
||||
# (str) Icon of the application
|
||||
icon.filename = %(source.dir)s/electrum/gui/icons/electrum_launcher.png
|
||||
icon.filename = %(source.dir)s/icons/electrum_launcher.png
|
||||
|
||||
# (str) Supported orientation (one of landscape, portrait or all)
|
||||
orientation = portrait
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import traceback
|
||||
@ -35,14 +34,14 @@ try:
|
||||
except Exception:
|
||||
sys.exit("Error: Could not import PyQt5 on Linux systems, you may try 'sudo apt-get install python3-pyqt5'")
|
||||
|
||||
from PyQt5.QtGui import QGuiApplication
|
||||
from PyQt5.QtWidgets import (QApplication, QSystemTrayIcon, QWidget, QMenu,
|
||||
QMessageBox)
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, QTimer
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5.QtCore import *
|
||||
import PyQt5.QtCore as QtCore
|
||||
|
||||
from electrum.i18n import _, set_language
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.storage import WalletStorage
|
||||
from electrum.base_wizard import GoBack
|
||||
from electrum.util import (UserCancelled, PrintError, profiler,
|
||||
WalletFileException, BitcoinException, get_new_wallet_name)
|
||||
@ -50,7 +49,15 @@ from electrum.util import (UserCancelled, PrintError, profiler,
|
||||
from .installwizard import InstallWizard
|
||||
|
||||
|
||||
from .util import get_default_language, read_QIcon, ColorScheme
|
||||
try:
|
||||
from . import icons_rc
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("Error: Could not find icons file.")
|
||||
print("Please run 'pyrcc5 icons.qrc -o electrum/gui/qt/icons_rc.py'")
|
||||
sys.exit(1)
|
||||
|
||||
from .util import * # * needed for plugins
|
||||
from .main_window import ElectrumWindow
|
||||
from .network_dialog import NetworkDialog
|
||||
|
||||
@ -98,7 +105,6 @@ class ElectrumGui(PrintError):
|
||||
self.efilter = OpenFileEventFilter(self.windows)
|
||||
self.app = QElectrumApplication(sys.argv)
|
||||
self.app.installEventFilter(self.efilter)
|
||||
self.app.setWindowIcon(read_QIcon("electrum.png"))
|
||||
# timer
|
||||
self.timer = QTimer(self.app)
|
||||
self.timer.setSingleShot(False)
|
||||
@ -151,9 +157,9 @@ class ElectrumGui(PrintError):
|
||||
|
||||
def tray_icon(self):
|
||||
if self.dark_icon:
|
||||
return read_QIcon('electrum_dark_icon.png')
|
||||
return QIcon(':icons/electrum_dark_icon.png')
|
||||
else:
|
||||
return read_QIcon('electrum_light_icon.png')
|
||||
return QIcon(':icons/electrum_light_icon.png')
|
||||
|
||||
def toggle_tray_icon(self):
|
||||
self.dark_icon = not self.dark_icon
|
||||
@ -227,10 +233,10 @@ class ElectrumGui(PrintError):
|
||||
else:
|
||||
return
|
||||
if not wallet:
|
||||
wizard = InstallWizard(self.config, self.app, self.plugins, None)
|
||||
storage = WalletStorage(path, manual_upgrades=True)
|
||||
wizard = InstallWizard(self.config, self.app, self.plugins, storage)
|
||||
try:
|
||||
if wizard.select_storage(path, self.daemon.get_wallet):
|
||||
wallet = wizard.run_and_get_wallet()
|
||||
wallet = wizard.run_and_get_wallet(self.daemon.get_wallet)
|
||||
except UserCancelled:
|
||||
pass
|
||||
except GoBack as e:
|
||||
|
||||
@ -25,9 +25,11 @@
|
||||
|
||||
from electrum.i18n import _
|
||||
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QLabel
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
from .util import WindowModalDialog, ButtonsLineEdit, ColorScheme, Buttons, CloseButton
|
||||
from .util import *
|
||||
from .history_list import HistoryList, HistoryModel
|
||||
from .qrtextedit import ShowQRTextEdit
|
||||
|
||||
@ -57,7 +59,7 @@ class AddressDialog(WindowModalDialog):
|
||||
vbox.addWidget(QLabel(_("Address:")))
|
||||
self.addr_e = ButtonsLineEdit(self.address)
|
||||
self.addr_e.addCopyButton(self.app)
|
||||
icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png"
|
||||
icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png"
|
||||
self.addr_e.addButton(icon, self.show_qr, _("Show QR Code"))
|
||||
self.addr_e.setReadOnly(True)
|
||||
vbox.addWidget(self.addr_e)
|
||||
|
||||
@ -22,13 +22,7 @@
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import webbrowser
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex
|
||||
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont
|
||||
from PyQt5.QtWidgets import QAbstractItemView, QComboBox, QLabel, QMenu
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import block_explorer_URL
|
||||
@ -36,23 +30,13 @@ from electrum.plugin import run_hook
|
||||
from electrum.bitcoin import is_address
|
||||
from electrum.wallet import InternalAddressCorruption
|
||||
|
||||
from .util import MyTreeView, MONOSPACE_FONT, ColorScheme
|
||||
|
||||
from .util import *
|
||||
|
||||
class AddressList(MyTreeView):
|
||||
|
||||
class Columns(IntEnum):
|
||||
TYPE = 0
|
||||
ADDRESS = 1
|
||||
LABEL = 2
|
||||
COIN_BALANCE = 3
|
||||
FIAT_BALANCE = 4
|
||||
NUM_TXS = 5
|
||||
|
||||
filter_columns = [Columns.TYPE, Columns.ADDRESS, Columns.LABEL, Columns.COIN_BALANCE]
|
||||
filter_columns = [0, 1, 2, 3] # Type, Address, Label, Balance
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent, self.create_menu, stretch_column=self.Columns.LABEL)
|
||||
super().__init__(parent, self.create_menu, 2)
|
||||
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||
self.setSortingEnabled(True)
|
||||
self.show_change = 0
|
||||
@ -80,19 +64,11 @@ class AddressList(MyTreeView):
|
||||
config.set_key('show_toolbar_addresses', state)
|
||||
|
||||
def refresh_headers(self):
|
||||
headers = [_('Type'), _('Address'), _('Label'), _('Balance')]
|
||||
fx = self.parent.fx
|
||||
if fx and fx.get_fiat_address_config():
|
||||
ccy = fx.get_currency()
|
||||
else:
|
||||
ccy = _('Fiat')
|
||||
headers = {
|
||||
self.Columns.TYPE: _('Type'),
|
||||
self.Columns.ADDRESS: _('Address'),
|
||||
self.Columns.LABEL: _('Label'),
|
||||
self.Columns.COIN_BALANCE: _('Balance'),
|
||||
self.Columns.FIAT_BALANCE: ccy + ' ' + _('Balance'),
|
||||
self.Columns.NUM_TXS: _('Tx'),
|
||||
}
|
||||
headers.extend([_(fx.get_currency()+' Balance')])
|
||||
headers.extend([_('Tx')])
|
||||
self.update_headers(headers)
|
||||
|
||||
def toggle_change(self, state):
|
||||
@ -109,7 +85,7 @@ class AddressList(MyTreeView):
|
||||
|
||||
def update(self):
|
||||
self.wallet = self.parent.wallet
|
||||
current_address = self.current_item_user_role(col=self.Columns.LABEL)
|
||||
current_address = self.current_item_user_role(col=2)
|
||||
if self.show_change == 1:
|
||||
addr_list = self.wallet.get_receiving_addresses()
|
||||
elif self.show_change == 2:
|
||||
@ -137,48 +113,45 @@ class AddressList(MyTreeView):
|
||||
if fx and fx.get_fiat_address_config():
|
||||
rate = fx.exchange_rate()
|
||||
fiat_balance = fx.value_str(balance, rate)
|
||||
labels = ['', address, label, balance_text, fiat_balance, "%d"%num]
|
||||
address_item = [QStandardItem(e) for e in labels]
|
||||
else:
|
||||
fiat_balance = ''
|
||||
labels = ['', address, label, balance_text, fiat_balance, "%d"%num]
|
||||
address_item = [QStandardItem(e) for e in labels]
|
||||
labels = ['', address, label, balance_text, "%d"%num]
|
||||
address_item = [QStandardItem(e) for e in labels]
|
||||
# align text and set fonts
|
||||
for i, item in enumerate(address_item):
|
||||
item.setTextAlignment(Qt.AlignVCenter)
|
||||
if i not in (self.Columns.TYPE, self.Columns.LABEL):
|
||||
if i not in (0, 2):
|
||||
item.setFont(QFont(MONOSPACE_FONT))
|
||||
item.setEditable(i in self.editable_columns)
|
||||
address_item[self.Columns.FIAT_BALANCE].setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
if fx and fx.get_fiat_address_config():
|
||||
address_item[4].setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
# setup column 0
|
||||
if self.wallet.is_change(address):
|
||||
address_item[self.Columns.TYPE].setText(_('change'))
|
||||
address_item[self.Columns.TYPE].setBackground(ColorScheme.YELLOW.as_color(True))
|
||||
address_item[0].setText(_('change'))
|
||||
address_item[0].setBackground(ColorScheme.YELLOW.as_color(True))
|
||||
else:
|
||||
address_item[self.Columns.TYPE].setText(_('receiving'))
|
||||
address_item[self.Columns.TYPE].setBackground(ColorScheme.GREEN.as_color(True))
|
||||
address_item[self.Columns.LABEL].setData(address, Qt.UserRole)
|
||||
address_item[0].setText(_('receiving'))
|
||||
address_item[0].setBackground(ColorScheme.GREEN.as_color(True))
|
||||
address_item[2].setData(address, Qt.UserRole)
|
||||
# setup column 1
|
||||
if self.wallet.is_frozen(address):
|
||||
address_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True))
|
||||
address_item[1].setBackground(ColorScheme.BLUE.as_color(True))
|
||||
if self.wallet.is_beyond_limit(address):
|
||||
address_item[self.Columns.ADDRESS].setBackground(ColorScheme.RED.as_color(True))
|
||||
address_item[1].setBackground(ColorScheme.RED.as_color(True))
|
||||
# add item
|
||||
count = self.model().rowCount()
|
||||
self.model().insertRow(count, address_item)
|
||||
address_idx = self.model().index(count, self.Columns.LABEL)
|
||||
address_idx = self.model().index(count, 2)
|
||||
if address == current_address:
|
||||
set_address = QPersistentModelIndex(address_idx)
|
||||
self.set_current_idx(set_address)
|
||||
# show/hide columns
|
||||
if fx and fx.get_fiat_address_config():
|
||||
self.showColumn(self.Columns.FIAT_BALANCE)
|
||||
else:
|
||||
self.hideColumn(self.Columns.FIAT_BALANCE)
|
||||
|
||||
def create_menu(self, position):
|
||||
from electrum.wallet import Multisig_Wallet
|
||||
is_multisig = isinstance(self.wallet, Multisig_Wallet)
|
||||
can_delete = self.wallet.can_delete_address()
|
||||
selected = self.selected_in_column(self.Columns.ADDRESS)
|
||||
selected = self.selected_in_column(1)
|
||||
if not selected:
|
||||
return
|
||||
multi_select = len(selected) > 1
|
||||
@ -192,8 +165,8 @@ class AddressList(MyTreeView):
|
||||
return
|
||||
addr = addrs[0]
|
||||
|
||||
addr_column_title = self.model().horizontalHeaderItem(self.Columns.LABEL).text()
|
||||
addr_idx = idx.sibling(idx.row(), self.Columns.LABEL)
|
||||
addr_column_title = self.model().horizontalHeaderItem(2).text()
|
||||
addr_idx = idx.sibling(idx.row(), 2)
|
||||
|
||||
column_title = self.model().horizontalHeaderItem(col).text()
|
||||
copy_text = self.model().itemFromIndex(idx).text()
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QPalette, QPainter
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame)
|
||||
|
||||
from electrum.util import (format_satoshis_plain, decimal_point_to_base_unit_name,
|
||||
|
||||
@ -23,9 +23,9 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from PyQt5.QtGui import QTextCursor
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QCompleter, QPlainTextEdit, QApplication
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
from .util import ButtonsTextEdit
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import sys
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
import platform
|
||||
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5 import QtGui
|
||||
@ -13,7 +14,13 @@ from PyQt5 import QtWidgets
|
||||
from electrum import util
|
||||
from electrum.i18n import _
|
||||
|
||||
from .util import MONOSPACE_FONT
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
MONOSPACE_FONT = 'Lucida Console'
|
||||
elif platform.system() == 'Darwin':
|
||||
MONOSPACE_FONT = 'Monaco'
|
||||
else:
|
||||
MONOSPACE_FONT = 'monospace'
|
||||
|
||||
|
||||
class OverlayLabel(QtWidgets.QLabel):
|
||||
|
||||
@ -22,13 +22,12 @@
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import webbrowser
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5.QtGui import QStandardItemModel, QStandardItem
|
||||
from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex
|
||||
from PyQt5.QtWidgets import (QAbstractItemView, QMenu)
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QFileDialog, QMenu, QTreeWidgetItem)
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.bitcoin import is_address
|
||||
@ -39,21 +38,10 @@ from .util import MyTreeView, import_meta_gui, export_meta_gui
|
||||
|
||||
|
||||
class ContactList(MyTreeView):
|
||||
|
||||
class Columns(IntEnum):
|
||||
NAME = 0
|
||||
ADDRESS = 1
|
||||
|
||||
headers = {
|
||||
Columns.NAME: _('Name'),
|
||||
Columns.ADDRESS: _('Address'),
|
||||
}
|
||||
filter_columns = [Columns.NAME, Columns.ADDRESS]
|
||||
filter_columns = [0, 1] # Key, Value
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent, self.create_menu,
|
||||
stretch_column=self.Columns.NAME,
|
||||
editable_columns=[self.Columns.NAME])
|
||||
super().__init__(parent, self.create_menu, stretch_column=0, editable_columns=[0])
|
||||
self.setModel(QStandardItemModel(self))
|
||||
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||
self.setSortingEnabled(True)
|
||||
@ -73,19 +61,19 @@ class ContactList(MyTreeView):
|
||||
def create_menu(self, position):
|
||||
menu = QMenu()
|
||||
idx = self.indexAt(position)
|
||||
column = idx.column() or self.Columns.NAME
|
||||
column = idx.column() or 0
|
||||
selected = self.selected_in_column(column)
|
||||
selected_keys = []
|
||||
for s_idx in self.selected_in_column(self.Columns.NAME):
|
||||
for s_idx in selected:
|
||||
sel_key = self.model().itemFromIndex(s_idx).data(Qt.UserRole)
|
||||
selected_keys.append(sel_key)
|
||||
if not selected_keys or not idx.isValid():
|
||||
if not selected or not idx.isValid():
|
||||
menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog())
|
||||
menu.addAction(_("Import file"), lambda: self.import_contacts())
|
||||
menu.addAction(_("Export file"), lambda: self.export_contacts())
|
||||
else:
|
||||
column_title = self.model().horizontalHeaderItem(column).text()
|
||||
column_data = '\n'.join(self.model().itemFromIndex(s_idx).text()
|
||||
for s_idx in self.selected_in_column(column))
|
||||
column_data = '\n'.join(self.model().itemFromIndex(s_idx).text() for s_idx in selected)
|
||||
menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
|
||||
if column in self.editable_columns:
|
||||
item = self.model().itemFromIndex(idx)
|
||||
@ -97,28 +85,28 @@ class ContactList(MyTreeView):
|
||||
menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(selected_keys))
|
||||
URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, selected_keys)]
|
||||
if URLs:
|
||||
menu.addAction(_("View on block explorer"), lambda: [webbrowser.open(u) for u in URLs])
|
||||
menu.addAction(_("View on block explorer"), lambda: map(webbrowser.open, URLs))
|
||||
|
||||
run_hook('create_contact_menu', menu, selected_keys)
|
||||
menu.exec_(self.viewport().mapToGlobal(position))
|
||||
|
||||
def update(self):
|
||||
current_key = self.current_item_user_role(col=self.Columns.NAME)
|
||||
current_key = self.current_item_user_role(col=0)
|
||||
self.model().clear()
|
||||
self.update_headers(self.__class__.headers)
|
||||
self.update_headers([_('Name'), _('Address')])
|
||||
set_current = None
|
||||
for key in sorted(self.parent.contacts.keys()):
|
||||
contact_type, name = self.parent.contacts[key]
|
||||
items = [QStandardItem(x) for x in (name, key)]
|
||||
items[self.Columns.NAME].setEditable(contact_type != 'openalias')
|
||||
items[self.Columns.ADDRESS].setEditable(False)
|
||||
items[self.Columns.NAME].setData(key, Qt.UserRole)
|
||||
items[0].setEditable(contact_type != 'openalias')
|
||||
items[1].setEditable(False)
|
||||
items[0].setData(key, Qt.UserRole)
|
||||
row_count = self.model().rowCount()
|
||||
self.model().insertRow(row_count, items)
|
||||
if key == current_key:
|
||||
idx = self.model().index(row_count, self.Columns.NAME)
|
||||
idx = self.model().index(row_count, 0)
|
||||
set_current = QPersistentModelIndex(idx)
|
||||
self.set_current_idx(set_current)
|
||||
# FIXME refresh loses sort order; so set "default" here:
|
||||
self.sortByColumn(self.Columns.NAME, Qt.AscendingOrder)
|
||||
self.sortByColumn(0, Qt.AscendingOrder)
|
||||
run_hook('update_contacts_tab', self)
|
||||
|
||||
@ -27,12 +27,12 @@ import traceback
|
||||
|
||||
from PyQt5.QtCore import QObject
|
||||
import PyQt5.QtCore as QtCore
|
||||
from PyQt5.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit,
|
||||
QMessageBox, QHBoxLayout, QVBoxLayout)
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.base_crash_reporter import BaseCrashReporter
|
||||
from .util import MessageBoxMixin, read_QIcon
|
||||
from .util import MessageBoxMixin
|
||||
|
||||
|
||||
class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin):
|
||||
@ -74,7 +74,7 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin):
|
||||
|
||||
report_button = QPushButton(_('Send Bug Report'))
|
||||
report_button.clicked.connect(self.send_report)
|
||||
report_button.setIcon(read_QIcon("tab_send.png"))
|
||||
report_button.setIcon(QIcon(":icons/tab_send.png"))
|
||||
buttons.addWidget(report_button)
|
||||
|
||||
never_button = QPushButton(_('Never'))
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import threading
|
||||
|
||||
from PyQt5.QtGui import QCursor
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import QSlider, QToolTip
|
||||
|
||||
from electrum.i18n import _
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import os
|
||||
import webbrowser
|
||||
import datetime
|
||||
from datetime import date
|
||||
@ -32,21 +31,12 @@ import threading
|
||||
from enum import IntEnum
|
||||
from decimal import Decimal
|
||||
|
||||
from PyQt5.QtGui import QMouseEvent, QFont, QBrush, QColor
|
||||
from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, QAbstractItemModel,
|
||||
QSortFilterProxyModel, QVariant, QItemSelectionModel, QDate, QPoint)
|
||||
from PyQt5.QtWidgets import (QMenu, QHeaderView, QLabel, QMessageBox,
|
||||
QPushButton, QComboBox, QVBoxLayout, QCalendarWidget,
|
||||
QGridLayout)
|
||||
|
||||
from electrum.address_synchronizer import TX_HEIGHT_LOCAL
|
||||
from electrum.i18n import _
|
||||
from electrum.util import (block_explorer_URL, profiler, print_error, TxMinedInfo,
|
||||
OrderedDictWithIndex, PrintError, timestamp_to_datetime)
|
||||
OrderedDictWithIndex, PrintError)
|
||||
|
||||
from .util import (read_QIcon, MONOSPACE_FONT, Buttons, CancelButton, OkButton,
|
||||
filename_field, MyTreeView, AcceptFileDragDrop, WindowModalDialog,
|
||||
CloseButton)
|
||||
from .util import *
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum.wallet import Abstract_Wallet
|
||||
@ -164,7 +154,7 @@ class HistoryModel(QAbstractItemModel, PrintError):
|
||||
return QVariant(d[col])
|
||||
if role not in (Qt.DisplayRole, Qt.EditRole):
|
||||
if col == HistoryColumns.STATUS_ICON and role == Qt.DecorationRole:
|
||||
return QVariant(read_QIcon(TX_ICONS[status]))
|
||||
return QVariant(self.view.icon_cache.get(":icons/" + TX_ICONS[status]))
|
||||
elif col == HistoryColumns.STATUS_ICON and role == Qt.ToolTipRole:
|
||||
return QVariant(str(conf) + _(" confirmation" + ("s" if conf != 1 else "")))
|
||||
elif col > HistoryColumns.DESCRIPTION and role == Qt.TextAlignmentRole:
|
||||
@ -174,7 +164,7 @@ class HistoryModel(QAbstractItemModel, PrintError):
|
||||
return QVariant(monospace_font)
|
||||
elif col == HistoryColumns.DESCRIPTION and role == Qt.DecorationRole \
|
||||
and self.parent.wallet.invoices.paid.get(tx_hash):
|
||||
return QVariant(read_QIcon("seal"))
|
||||
return QVariant(self.view.icon_cache.get(":icons/seal"))
|
||||
elif col in (HistoryColumns.DESCRIPTION, HistoryColumns.COIN_VALUE) \
|
||||
and role == Qt.ForegroundRole and tx_item['value'].value < 0:
|
||||
red_brush = QBrush(QColor("#BC1E1E"))
|
||||
@ -307,7 +297,6 @@ class HistoryModel(QAbstractItemModel, PrintError):
|
||||
'confirmations': tx_mined_info.conf,
|
||||
'timestamp': tx_mined_info.timestamp,
|
||||
'txpos_in_block': tx_mined_info.txpos,
|
||||
'date': timestamp_to_datetime(tx_mined_info.timestamp),
|
||||
})
|
||||
topLeft = self.createIndex(row, 0)
|
||||
bottomRight = self.createIndex(row, len(HistoryColumns) - 1)
|
||||
@ -603,7 +592,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||
if child_tx:
|
||||
menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp(tx, child_tx))
|
||||
if pr_key:
|
||||
menu.addAction(read_QIcon("seal"), _("View invoice"), lambda: self.parent.show_invoice(pr_key))
|
||||
menu.addAction(self.icon_cache.get(":icons/seal"), _("View invoice"), lambda: self.parent.show_invoice(pr_key))
|
||||
if tx_URL:
|
||||
menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL))
|
||||
menu.exec_(self.viewport().mapToGlobal(position))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,11 +8,9 @@ import threading
|
||||
import traceback
|
||||
from typing import Tuple, List, Callable
|
||||
|
||||
from PyQt5.QtCore import QRect, QEventLoop, Qt, pyqtSignal
|
||||
from PyQt5.QtGui import QPalette, QPen, QPainter, QPixmap
|
||||
from PyQt5.QtWidgets import (QWidget, QDialog, QLabel, QHBoxLayout, QMessageBox,
|
||||
QVBoxLayout, QLineEdit, QFileDialog, QPushButton,
|
||||
QGridLayout, QSlider, QScrollArea)
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
from electrum.wallet import Wallet
|
||||
from electrum.storage import WalletStorage
|
||||
@ -22,8 +20,7 @@ from electrum.i18n import _
|
||||
|
||||
from .seed_dialog import SeedLayout, KeysLayout
|
||||
from .network_dialog import NetworkChoiceLayout
|
||||
from .util import (MessageBoxMixin, Buttons, icon_path, ChoicesLayout, WWLabel,
|
||||
InfoButton)
|
||||
from .util import *
|
||||
from .password_dialog import PasswordLayout, PasswordLayoutForHW, PW_NEW
|
||||
|
||||
|
||||
@ -157,12 +154,12 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||
hbox.setStretchFactor(scroll, 1)
|
||||
outer_vbox.addLayout(hbox)
|
||||
outer_vbox.addLayout(Buttons(self.back_button, self.next_button))
|
||||
self.set_icon('electrum.png')
|
||||
self.set_icon(':icons/electrum.png')
|
||||
self.show()
|
||||
self.raise_()
|
||||
self.refresh_gui() # Need for QT on MacOSX. Lame.
|
||||
|
||||
def select_storage(self, path, get_wallet_from_daemon):
|
||||
def run_and_get_wallet(self, get_wallet_from_daemon):
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
hbox = QHBoxLayout()
|
||||
@ -186,7 +183,6 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||
vbox.addLayout(hbox2)
|
||||
self.set_layout(vbox, title=_('Electrum wallet'))
|
||||
|
||||
self.storage = WalletStorage(path, manual_upgrades=True)
|
||||
wallet_folder = os.path.dirname(self.storage.path)
|
||||
|
||||
def on_choose():
|
||||
@ -276,7 +272,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||
None, _('Error'),
|
||||
_('Failed to decrypt using this hardware device.') + '\n' +
|
||||
_('If you use a passphrase, make sure it is correct.'))
|
||||
self.reset_stack()
|
||||
self.stack = []
|
||||
return self.run_and_get_wallet(get_wallet_from_daemon)
|
||||
except BaseException as e:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
@ -288,9 +284,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||
return
|
||||
else:
|
||||
raise Exception('Unexpected encryption version')
|
||||
return True
|
||||
|
||||
def run_and_get_wallet(self):
|
||||
path = self.storage.path
|
||||
if self.storage.requires_split():
|
||||
self.hide()
|
||||
@ -335,8 +329,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||
|
||||
def set_icon(self, filename):
|
||||
prior_filename, self.icon_filename = self.icon_filename, filename
|
||||
self.logo.setPixmap(QPixmap(icon_path(filename))
|
||||
.scaledToWidth(60, mode=Qt.SmoothTransformation))
|
||||
self.logo.setPixmap(QPixmap(filename).scaledToWidth(60, mode=Qt.SmoothTransformation))
|
||||
return prior_filename
|
||||
|
||||
def set_layout(self, layout, title=None, next_enabled=True):
|
||||
|
||||
@ -23,51 +23,27 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5.QtCore import Qt, QItemSelectionModel
|
||||
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont
|
||||
from PyQt5.QtWidgets import QHeaderView, QMenu
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import format_time
|
||||
|
||||
from .util import (MyTreeView, read_QIcon, MONOSPACE_FONT, PR_UNPAID,
|
||||
pr_tooltips, import_meta_gui, export_meta_gui, pr_icons)
|
||||
from .util import *
|
||||
|
||||
|
||||
class InvoiceList(MyTreeView):
|
||||
|
||||
class Columns(IntEnum):
|
||||
DATE = 0
|
||||
REQUESTOR = 1
|
||||
DESCRIPTION = 2
|
||||
AMOUNT = 3
|
||||
STATUS = 4
|
||||
|
||||
headers = {
|
||||
Columns.DATE: _('Expires'),
|
||||
Columns.REQUESTOR: _('Requestor'),
|
||||
Columns.DESCRIPTION: _('Description'),
|
||||
Columns.AMOUNT: _('Amount'),
|
||||
Columns.STATUS: _('Status'),
|
||||
}
|
||||
filter_columns = [Columns.DATE, Columns.REQUESTOR, Columns.DESCRIPTION, Columns.AMOUNT]
|
||||
filter_columns = [0, 1, 2, 3] # Date, Requestor, Description, Amount
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent, self.create_menu,
|
||||
stretch_column=self.Columns.DESCRIPTION,
|
||||
editable_columns=[])
|
||||
super().__init__(parent, self.create_menu, stretch_column=2, editable_columns=[])
|
||||
self.setSortingEnabled(True)
|
||||
self.setColumnWidth(self.Columns.REQUESTOR, 200)
|
||||
self.setColumnWidth(1, 200)
|
||||
self.setModel(QStandardItemModel(self))
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
inv_list = self.parent.invoices.unpaid_invoices()
|
||||
self.model().clear()
|
||||
self.update_headers(self.__class__.headers)
|
||||
self.header().setSectionResizeMode(self.Columns.REQUESTOR, QHeaderView.Interactive)
|
||||
self.update_headers([_('Expires'), _('Requestor'), _('Description'), _('Amount'), _('Status')])
|
||||
self.header().setSectionResizeMode(1, QHeaderView.Interactive)
|
||||
for idx, pr in enumerate(inv_list):
|
||||
key = pr.get_id()
|
||||
status = self.parent.invoices.get_status(key)
|
||||
@ -77,10 +53,10 @@ class InvoiceList(MyTreeView):
|
||||
labels = [date_str, requestor, pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')]
|
||||
items = [QStandardItem(e) for e in labels]
|
||||
self.set_editability(items)
|
||||
items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
|
||||
items[self.Columns.DATE].setData(key, role=Qt.UserRole)
|
||||
items[self.Columns.REQUESTOR].setFont(QFont(MONOSPACE_FONT))
|
||||
items[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT))
|
||||
items[4].setIcon(self.icon_cache.get(pr_icons.get(status)))
|
||||
items[0].setData(key, role=Qt.UserRole)
|
||||
items[1].setFont(QFont(MONOSPACE_FONT))
|
||||
items[3].setFont(QFont(MONOSPACE_FONT))
|
||||
self.model().insertRow(idx, items)
|
||||
self.selectionModel().select(self.model().index(0,0), QItemSelectionModel.SelectCurrent)
|
||||
if self.parent.isVisible():
|
||||
@ -97,7 +73,7 @@ class InvoiceList(MyTreeView):
|
||||
def create_menu(self, position):
|
||||
idx = self.indexAt(position)
|
||||
item = self.model().itemFromIndex(idx)
|
||||
item_col0 = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.DATE))
|
||||
item_col0 = self.model().itemFromIndex(idx.sibling(idx.row(), 0))
|
||||
if not item or not item_col0:
|
||||
return
|
||||
key = item_col0.data(Qt.UserRole)
|
||||
|
||||
@ -38,14 +38,10 @@ from functools import partial
|
||||
import queue
|
||||
import asyncio
|
||||
|
||||
from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor
|
||||
from PyQt5.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal
|
||||
from PyQt5.QtWidgets import (QMessageBox, QComboBox, QSystemTrayIcon, QTabWidget,
|
||||
QSpinBox, QMenuBar, QFileDialog, QCheckBox, QLabel,
|
||||
QVBoxLayout, QGridLayout, QLineEdit, QTreeWidgetItem,
|
||||
QHBoxLayout, QPushButton, QScrollArea, QTextEdit,
|
||||
QShortcut, QMainWindow, QCompleter, QInputDialog,
|
||||
QWidget, QMenu, QSizePolicy, QStatusBar)
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
import PyQt5.QtCore as QtCore
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
import electrum
|
||||
from electrum import (keystore, simple_config, ecc, constants, util, bitcoin, commands,
|
||||
@ -76,15 +72,9 @@ from .qrcodewidget import QRCodeWidget, QRDialog
|
||||
from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
|
||||
from .transaction_dialog import show_transaction
|
||||
from .fee_slider import FeeSlider
|
||||
from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialog,
|
||||
WindowModalDialog, ChoicesLayout, HelpLabel, FromList, Buttons,
|
||||
OkButton, InfoButton, WWLabel, TaskThread, CancelButton,
|
||||
CloseButton, HelpButton, MessageBoxMixin, EnterButton, expiration_values,
|
||||
ButtonsLineEdit, CopyCloseButton, import_meta_gui, export_meta_gui,
|
||||
filename_field, address_field)
|
||||
from .util import *
|
||||
from .installwizard import WIF_HELP_TEXT
|
||||
from .history_list import HistoryList, HistoryModel
|
||||
from .update_checker import UpdateCheck, UpdateCheckThread
|
||||
|
||||
|
||||
class StatusBarButton(QPushButton):
|
||||
@ -171,9 +161,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
self.utxo_tab = self.create_utxo_tab()
|
||||
self.console_tab = self.create_console_tab()
|
||||
self.contacts_tab = self.create_contacts_tab()
|
||||
tabs.addTab(self.create_history_tab(), read_QIcon("tab_history.png"), _('History'))
|
||||
tabs.addTab(self.send_tab, read_QIcon("tab_send.png"), _('Send'))
|
||||
tabs.addTab(self.receive_tab, read_QIcon("tab_receive.png"), _('Receive'))
|
||||
tabs.addTab(self.create_history_tab(), QIcon(":icons/tab_history.png"), _('History'))
|
||||
tabs.addTab(self.send_tab, QIcon(":icons/tab_send.png"), _('Send'))
|
||||
tabs.addTab(self.receive_tab, QIcon(":icons/tab_receive.png"), _('Receive'))
|
||||
|
||||
def add_optional_tab(tabs, tab, icon, description, name):
|
||||
tab.tab_icon = icon
|
||||
@ -183,10 +173,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
if self.config.get('show_{}_tab'.format(name), False):
|
||||
tabs.addTab(tab, icon, description.replace("&", ""))
|
||||
|
||||
add_optional_tab(tabs, self.addresses_tab, read_QIcon("tab_addresses.png"), _("&Addresses"), "addresses")
|
||||
add_optional_tab(tabs, self.utxo_tab, read_QIcon("tab_coins.png"), _("Co&ins"), "utxo")
|
||||
add_optional_tab(tabs, self.contacts_tab, read_QIcon("tab_contacts.png"), _("Con&tacts"), "contacts")
|
||||
add_optional_tab(tabs, self.console_tab, read_QIcon("tab_console.png"), _("Con&sole"), "console")
|
||||
add_optional_tab(tabs, self.addresses_tab, QIcon(":icons/tab_addresses.png"), _("&Addresses"), "addresses")
|
||||
add_optional_tab(tabs, self.utxo_tab, QIcon(":icons/tab_coins.png"), _("Co&ins"), "utxo")
|
||||
add_optional_tab(tabs, self.contacts_tab, QIcon(":icons/tab_contacts.png"), _("Con&tacts"), "contacts")
|
||||
add_optional_tab(tabs, self.console_tab, QIcon(":icons/tab_console.png"), _("Con&sole"), "console")
|
||||
|
||||
tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self.setCentralWidget(tabs)
|
||||
@ -194,7 +184,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
if self.config.get("is_maximized"):
|
||||
self.showMaximized()
|
||||
|
||||
self.setWindowIcon(read_QIcon("electrum.png"))
|
||||
self.setWindowIcon(QIcon(":icons/electrum.png"))
|
||||
self.init_menubar()
|
||||
|
||||
wrtabs = weakref.proxy(tabs)
|
||||
@ -685,7 +675,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
if self.tray:
|
||||
try:
|
||||
# this requires Qt 5.9
|
||||
self.tray.showMessage("Electrum", message, read_QIcon("electrum_dark_icon"), 20000)
|
||||
self.tray.showMessage("Electrum", message, QIcon(":icons/electrum_dark_icon"), 20000)
|
||||
except TypeError:
|
||||
self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
|
||||
|
||||
@ -783,7 +773,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
|
||||
if self.network is None:
|
||||
text = _("Offline")
|
||||
icon = read_QIcon("status_disconnected.png")
|
||||
icon = QIcon(":icons/status_disconnected.png")
|
||||
|
||||
elif self.network.is_connected():
|
||||
server_height = self.network.get_server_height()
|
||||
@ -794,10 +784,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
# Display the synchronizing message in that case.
|
||||
if not self.wallet.up_to_date or server_height == 0:
|
||||
text = _("Synchronizing...")
|
||||
icon = read_QIcon("status_waiting.png")
|
||||
icon = QIcon(":icons/status_waiting.png")
|
||||
elif server_lag > 1:
|
||||
text = _("Server is lagging ({} blocks)").format(server_lag)
|
||||
icon = read_QIcon("status_lagging%s.png"%fork_str)
|
||||
icon = QIcon(":icons/status_lagging%s.png"%fork_str)
|
||||
else:
|
||||
c, u, x = self.wallet.get_balance()
|
||||
text = _("Balance" ) + ": %s "%(self.format_amount_and_units(c))
|
||||
@ -811,15 +801,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
text += self.fx.get_fiat_status_text(c + u + x,
|
||||
self.base_unit(), self.get_decimal_point()) or ''
|
||||
if not self.network.proxy:
|
||||
icon = read_QIcon("status_connected%s.png"%fork_str)
|
||||
icon = QIcon(":icons/status_connected%s.png"%fork_str)
|
||||
else:
|
||||
icon = read_QIcon("status_connected_proxy%s.png"%fork_str)
|
||||
icon = QIcon(":icons/status_connected_proxy%s.png"%fork_str)
|
||||
else:
|
||||
if self.network.proxy:
|
||||
text = "{} ({})".format(_("Not connected"), _("proxy enabled"))
|
||||
else:
|
||||
text = _("Not connected")
|
||||
icon = read_QIcon("status_disconnected.png")
|
||||
icon = QIcon(":icons/status_disconnected.png")
|
||||
|
||||
self.tray.setToolTip("%s (%s)" % (text, self.wallet.basename()))
|
||||
self.balance_label.setText(text)
|
||||
@ -1253,7 +1243,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
_('Also, when batching RBF transactions, BIP 125 imposes a lower bound on the fee.'))
|
||||
QMessageBox.information(self, 'Fee rounding', text)
|
||||
|
||||
self.feerounding_icon = QPushButton(read_QIcon('info.png'), '')
|
||||
self.feerounding_icon = QPushButton(QIcon(':icons/info.png'), '')
|
||||
self.feerounding_icon.setFixedWidth(20)
|
||||
self.feerounding_icon.setFlat(True)
|
||||
self.feerounding_icon.clicked.connect(feerounding_onclick)
|
||||
@ -2055,23 +2045,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
self.update_check_button = QPushButton("")
|
||||
self.update_check_button.setFlat(True)
|
||||
self.update_check_button.setCursor(QCursor(Qt.PointingHandCursor))
|
||||
self.update_check_button.setIcon(read_QIcon("update.png"))
|
||||
self.update_check_button.setIcon(QIcon(":icons/update.png"))
|
||||
self.update_check_button.hide()
|
||||
sb.addPermanentWidget(self.update_check_button)
|
||||
|
||||
self.password_button = StatusBarButton(QIcon(), _("Password"), self.change_password_dialog )
|
||||
self.lock_icon = QIcon()
|
||||
self.password_button = StatusBarButton(self.lock_icon, _("Password"), self.change_password_dialog )
|
||||
sb.addPermanentWidget(self.password_button)
|
||||
|
||||
sb.addPermanentWidget(StatusBarButton(read_QIcon("preferences.png"), _("Preferences"), self.settings_dialog ) )
|
||||
self.seed_button = StatusBarButton(read_QIcon("seed.png"), _("Seed"), self.show_seed_dialog )
|
||||
sb.addPermanentWidget(StatusBarButton(QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
|
||||
self.seed_button = StatusBarButton(QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
|
||||
sb.addPermanentWidget(self.seed_button)
|
||||
self.status_button = StatusBarButton(read_QIcon("status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self))
|
||||
self.status_button = StatusBarButton(QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self))
|
||||
sb.addPermanentWidget(self.status_button)
|
||||
run_hook('create_status_bar', sb)
|
||||
self.setStatusBar(sb)
|
||||
|
||||
def update_lock_icon(self):
|
||||
icon = read_QIcon("lock.png") if self.wallet.has_password() else read_QIcon("unlock.png")
|
||||
icon = QIcon(":icons/lock.png") if self.wallet.has_password() else QIcon(":icons/unlock.png")
|
||||
self.password_button.setIcon(icon)
|
||||
|
||||
def update_buttons_on_seed(self):
|
||||
@ -2213,12 +2204,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
def _delete_wallet(self, password):
|
||||
wallet_path = self.wallet.storage.path
|
||||
basename = os.path.basename(wallet_path)
|
||||
r = self.gui_object.daemon.delete_wallet(wallet_path)
|
||||
self.gui_object.daemon.stop_wallet(wallet_path)
|
||||
self.close()
|
||||
if r:
|
||||
self.show_error(_("Wallet removed: {}").format(basename))
|
||||
else:
|
||||
self.show_error(_("Wallet file not found: {}").format(basename))
|
||||
os.unlink(wallet_path)
|
||||
self.show_error(_("Wallet removed: {}").format(basename))
|
||||
|
||||
@protected
|
||||
def show_seed_dialog(self, password):
|
||||
@ -3374,5 +3363,5 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
msg = (_("Transaction added to wallet history.") + '\n\n' +
|
||||
_("Note: this is an offline transaction, if you want the network "
|
||||
"to see it, you need to broadcast it."))
|
||||
win.msg_box(QPixmap(icon_path("offline_tx.png")), None, _('Success'), msg)
|
||||
win.msg_box(QPixmap(":icons/offline_tx.png"), None, _('Success'), msg)
|
||||
return True
|
||||
|
||||
@ -24,13 +24,11 @@
|
||||
# SOFTWARE.
|
||||
|
||||
import socket
|
||||
import time
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, QThread
|
||||
from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox,
|
||||
QLineEdit, QDialog, QVBoxLayout, QHeaderView, QCheckBox,
|
||||
QTabWidget, QWidget, QLabel)
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import *
|
||||
import PyQt5.QtCore as QtCore
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum import constants, blockchain
|
||||
@ -38,7 +36,7 @@ from electrum.util import print_error
|
||||
from electrum.interface import serialize_server, deserialize_server
|
||||
from electrum.network import Network
|
||||
|
||||
from .util import Buttons, CloseButton, HelpButton, read_QIcon
|
||||
from .util import *
|
||||
|
||||
protocol_names = ['TCP', 'SSL']
|
||||
protocol_letters = 'ts'
|
||||
@ -134,11 +132,6 @@ class NodesListWidget(QTreeWidget):
|
||||
|
||||
|
||||
class ServerListWidget(QTreeWidget):
|
||||
class Columns(IntEnum):
|
||||
HOST = 0
|
||||
PORT = 1
|
||||
|
||||
SERVER_STR_ROLE = Qt.UserRole + 100
|
||||
|
||||
def __init__(self, parent):
|
||||
QTreeWidget.__init__(self)
|
||||
@ -152,7 +145,7 @@ class ServerListWidget(QTreeWidget):
|
||||
if not item:
|
||||
return
|
||||
menu = QMenu()
|
||||
server = item.data(self.Columns.HOST, self.SERVER_STR_ROLE)
|
||||
server = item.data(1, Qt.UserRole)
|
||||
menu.addAction(_("Use as server"), lambda: self.set_server(server))
|
||||
menu.exec_(self.viewport().mapToGlobal(position))
|
||||
|
||||
@ -183,13 +176,13 @@ class ServerListWidget(QTreeWidget):
|
||||
if port:
|
||||
x = QTreeWidgetItem([_host, port])
|
||||
server = serialize_server(_host, port, protocol)
|
||||
x.setData(self.Columns.HOST, self.SERVER_STR_ROLE, server)
|
||||
x.setData(1, Qt.UserRole, server)
|
||||
self.addTopLevelItem(x)
|
||||
|
||||
h = self.header()
|
||||
h.setStretchLastSection(False)
|
||||
h.setSectionResizeMode(self.Columns.HOST, QHeaderView.Stretch)
|
||||
h.setSectionResizeMode(self.Columns.PORT, QHeaderView.ResizeToContents)
|
||||
h.setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
h.setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
||||
|
||||
super().update()
|
||||
|
||||
@ -277,7 +270,7 @@ class NetworkChoiceLayout(object):
|
||||
self.proxy_password.textEdited.connect(self.proxy_settings_changed)
|
||||
|
||||
self.tor_cb = QCheckBox(_("Use Tor Proxy"))
|
||||
self.tor_cb.setIcon(read_QIcon("tor_logo.png"))
|
||||
self.tor_cb.setIcon(QIcon(":icons/tor_logo.png"))
|
||||
self.tor_cb.hide()
|
||||
self.tor_cb.clicked.connect(self.use_tor_proxy)
|
||||
|
||||
|
||||
@ -27,13 +27,13 @@ import re
|
||||
import math
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import QLineEdit, QLabel, QGridLayout, QVBoxLayout, QCheckBox
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import run_hook
|
||||
|
||||
from .util import icon_path, WindowModalDialog, OkButton, CancelButton, Buttons
|
||||
from .util import *
|
||||
|
||||
|
||||
def check_password_strength(password):
|
||||
@ -103,11 +103,10 @@ class PasswordLayout(object):
|
||||
if wallet and wallet.has_password():
|
||||
grid.addWidget(QLabel(_('Current Password:')), 0, 0)
|
||||
grid.addWidget(self.pw, 0, 1)
|
||||
lockfile = "lock.png"
|
||||
lockfile = ":icons/lock.png"
|
||||
else:
|
||||
lockfile = "unlock.png"
|
||||
logo.setPixmap(QPixmap(icon_path(lockfile))
|
||||
.scaledToWidth(36, mode=Qt.SmoothTransformation))
|
||||
lockfile = ":icons/unlock.png"
|
||||
logo.setPixmap(QPixmap(lockfile).scaledToWidth(36, mode=Qt.SmoothTransformation))
|
||||
|
||||
grid.addWidget(QLabel(msgs[0]), 1, 0)
|
||||
grid.addWidget(self.new_pw, 1, 1)
|
||||
@ -196,11 +195,10 @@ class PasswordLayoutForHW(object):
|
||||
vbox.addLayout(logo_grid)
|
||||
|
||||
if wallet and wallet.has_storage_encryption():
|
||||
lockfile = "lock.png"
|
||||
lockfile = ":icons/lock.png"
|
||||
else:
|
||||
lockfile = "unlock.png"
|
||||
logo.setPixmap(QPixmap(icon_path(lockfile))
|
||||
.scaledToWidth(36, mode=Qt.SmoothTransformation))
|
||||
lockfile = ":icons/unlock.png"
|
||||
logo.setPixmap(QPixmap(lockfile).scaledToWidth(36, mode=Qt.SmoothTransformation))
|
||||
|
||||
vbox.addLayout(grid)
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
import re
|
||||
from decimal import Decimal
|
||||
|
||||
from PyQt5.QtGui import QFontMetrics
|
||||
from PyQt5.QtGui import *
|
||||
|
||||
from electrum import bitcoin
|
||||
from electrum.util import bfh, PrintError
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import os
|
||||
import qrcode
|
||||
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
import PyQt5.QtGui as QtGui
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QVBoxLayout, QTextEdit, QHBoxLayout, QPushButton, QWidget)
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import QFileDialog
|
||||
|
||||
from electrum.i18n import _
|
||||
@ -11,7 +13,7 @@ class ShowQRTextEdit(ButtonsTextEdit):
|
||||
def __init__(self, text=None):
|
||||
ButtonsTextEdit.__init__(self, text)
|
||||
self.setReadOnly(1)
|
||||
icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png"
|
||||
icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png"
|
||||
self.addButton(icon, self.qr_show, _("Show as QR code"))
|
||||
|
||||
run_hook('show_text_edit', self)
|
||||
@ -36,8 +38,8 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
||||
ButtonsTextEdit.__init__(self, text)
|
||||
self.allow_multi = allow_multi
|
||||
self.setReadOnly(0)
|
||||
self.addButton("file.png", self.file_input, _("Read file"))
|
||||
icon = "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png"
|
||||
self.addButton(":icons/file.png", self.file_input, _("Read file"))
|
||||
icon = ":icons/camera_white.png" if ColorScheme.dark_scheme else ":icons/camera_dark.png"
|
||||
self.addButton(icon, self.qr_input, _("Read QR code"))
|
||||
run_hook('scan_text_edit', self)
|
||||
|
||||
|
||||
@ -23,13 +23,24 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import platform
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QWidget
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QWidget
|
||||
|
||||
from .qrcodewidget import QRCodeWidget
|
||||
|
||||
from electrum.i18n import _
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
MONOSPACE_FONT = 'Lucida Console'
|
||||
elif platform.system() == 'Darwin':
|
||||
MONOSPACE_FONT = 'Monaco'
|
||||
else:
|
||||
MONOSPACE_FONT = 'monospace'
|
||||
|
||||
column_index = 4
|
||||
|
||||
|
||||
class QR_Window(QWidget):
|
||||
|
||||
|
||||
@ -23,8 +23,6 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5.QtGui import QStandardItemModel, QStandardItem
|
||||
from PyQt5.QtWidgets import QMenu
|
||||
from PyQt5.QtCore import Qt
|
||||
@ -35,42 +33,23 @@ from electrum.plugin import run_hook
|
||||
from electrum.paymentrequest import PR_UNKNOWN
|
||||
from electrum.wallet import InternalAddressCorruption
|
||||
|
||||
from .util import MyTreeView, pr_tooltips, pr_icons, read_QIcon
|
||||
|
||||
from .util import MyTreeView, pr_tooltips, pr_icons
|
||||
|
||||
class RequestList(MyTreeView):
|
||||
filter_columns = [0, 1, 2, 3, 4] # Date, Account, Address, Description, Amount
|
||||
|
||||
class Columns(IntEnum):
|
||||
DATE = 0
|
||||
ADDRESS = 1
|
||||
SIGNATURE = 2
|
||||
DESCRIPTION = 3
|
||||
AMOUNT = 4
|
||||
STATUS = 5
|
||||
|
||||
headers = {
|
||||
Columns.DATE: _('Date'),
|
||||
Columns.ADDRESS: _('Address'),
|
||||
Columns.SIGNATURE: '',
|
||||
Columns.DESCRIPTION: _('Description'),
|
||||
Columns.AMOUNT: _('Amount'),
|
||||
Columns.STATUS: _('Status'),
|
||||
}
|
||||
filter_columns = [Columns.DATE, Columns.ADDRESS, Columns.SIGNATURE, Columns.DESCRIPTION, Columns.AMOUNT]
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent, self.create_menu,
|
||||
stretch_column=self.Columns.DESCRIPTION,
|
||||
editable_columns=[])
|
||||
super().__init__(parent, self.create_menu, 3, editable_columns=[])
|
||||
self.setModel(QStandardItemModel(self))
|
||||
self.setSortingEnabled(True)
|
||||
self.setColumnWidth(self.Columns.DATE, 180)
|
||||
self.setColumnWidth(0, 180)
|
||||
self.update()
|
||||
self.selectionModel().currentRowChanged.connect(self.item_changed)
|
||||
|
||||
def item_changed(self, idx):
|
||||
# TODO use siblingAtColumn when min Qt version is >=5.11
|
||||
addr = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.ADDRESS)).text()
|
||||
addr = self.model().itemFromIndex(idx.sibling(idx.row(), 1)).text()
|
||||
req = self.wallet.receive_requests.get(addr)
|
||||
if req is None:
|
||||
self.update()
|
||||
@ -110,8 +89,8 @@ class RequestList(MyTreeView):
|
||||
self.parent.new_request_button.setEnabled(addr != current_address)
|
||||
|
||||
self.model().clear()
|
||||
self.update_headers(self.__class__.headers)
|
||||
self.hideColumn(self.Columns.ADDRESS)
|
||||
self.update_headers([_('Date'), _('Address'), '', _('Description'), _('Amount'), _('Status')])
|
||||
self.hideColumn(1) # hide address column
|
||||
for req in self.wallet.get_sorted_requests(self.config):
|
||||
address = req['address']
|
||||
if address not in domain:
|
||||
@ -129,17 +108,17 @@ class RequestList(MyTreeView):
|
||||
items = [QStandardItem(e) for e in labels]
|
||||
self.set_editability(items)
|
||||
if signature is not None:
|
||||
items[self.Columns.SIGNATURE].setIcon(read_QIcon("seal.png"))
|
||||
items[self.Columns.SIGNATURE].setToolTip(f'signed by {requestor}')
|
||||
items[2].setIcon(self.icon_cache.get(":icons/seal.png"))
|
||||
items[2].setToolTip('signed by '+ requestor)
|
||||
if status is not PR_UNKNOWN:
|
||||
items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
|
||||
items[self.Columns.DESCRIPTION].setData(address, Qt.UserRole)
|
||||
items[5].setIcon(self.icon_cache.get(pr_icons.get(status)))
|
||||
items[3].setData(address, Qt.UserRole)
|
||||
self.model().insertRow(self.model().rowCount(), items)
|
||||
|
||||
def create_menu(self, position):
|
||||
idx = self.indexAt(position)
|
||||
# TODO use siblingAtColumn when min Qt version is >=5.11
|
||||
item = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.ADDRESS))
|
||||
item = self.model().itemFromIndex(idx.sibling(idx.row(), 1))
|
||||
if not item:
|
||||
return
|
||||
addr = item.text()
|
||||
@ -151,7 +130,7 @@ class RequestList(MyTreeView):
|
||||
column_title = self.model().horizontalHeaderItem(column).text()
|
||||
column_data = item.text()
|
||||
menu = QMenu(self)
|
||||
if column != self.Columns.SIGNATURE:
|
||||
if column != 2:
|
||||
menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
|
||||
menu.addAction(_("Copy URI"), lambda: self.parent.view_and_paste('URI', '', self.parent.get_request_URI(addr)))
|
||||
menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
|
||||
|
||||
@ -23,17 +23,11 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit,
|
||||
QLabel, QCompleter, QDialog)
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.mnemonic import Mnemonic
|
||||
import electrum.old_mnemonic
|
||||
|
||||
from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path,
|
||||
EnterButton, CloseButton, WindowModalDialog)
|
||||
from .util import *
|
||||
from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
|
||||
from .completion_text_edit import CompletionTextEdit
|
||||
|
||||
@ -116,8 +110,7 @@ class SeedLayout(QVBoxLayout):
|
||||
hbox = QHBoxLayout()
|
||||
if icon:
|
||||
logo = QLabel()
|
||||
logo.setPixmap(QPixmap(icon_path("seed.png"))
|
||||
.scaledToWidth(64, mode=Qt.SmoothTransformation))
|
||||
logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(64, mode=Qt.SmoothTransformation))
|
||||
logo.setMaximumWidth(60)
|
||||
hbox.addWidget(logo)
|
||||
hbox.addWidget(self.seed_e)
|
||||
|
||||
@ -22,17 +22,15 @@
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import sys
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from PyQt5.QtCore import QSize
|
||||
from PyQt5.QtGui import QTextCharFormat, QBrush, QFont
|
||||
from PyQt5.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout,
|
||||
QTextEdit)
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
import qrcode
|
||||
from qrcode import exceptions
|
||||
|
||||
@ -43,8 +41,7 @@ from electrum import simple_config
|
||||
from electrum.util import bfh
|
||||
from electrum.transaction import SerializationError, Transaction
|
||||
|
||||
from .util import (MessageBoxMixin, read_QIcon, Buttons, CopyButton,
|
||||
MONOSPACE_FONT, ColorScheme, ButtonsLineEdit)
|
||||
from .util import *
|
||||
|
||||
|
||||
SAVE_BUTTON_ENABLED_TOOLTIP = _("Save transaction offline")
|
||||
@ -101,7 +98,7 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
vbox.addWidget(QLabel(_("Transaction ID:")))
|
||||
self.tx_hash_e = ButtonsLineEdit()
|
||||
qr_show = lambda: parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self)
|
||||
qr_icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png"
|
||||
qr_icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png"
|
||||
self.tx_hash_e.addButton(qr_icon, qr_show, _("Show as QR code"))
|
||||
self.tx_hash_e.setReadOnly(True)
|
||||
vbox.addWidget(self.tx_hash_e)
|
||||
@ -145,7 +142,7 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
b.setDefault(True)
|
||||
|
||||
self.qr_button = b = QPushButton()
|
||||
b.setIcon(read_QIcon(qr_icon))
|
||||
b.setIcon(QIcon(qr_icon))
|
||||
b.clicked.connect(self.show_qr)
|
||||
|
||||
self.copy_button = CopyButton(lambda: str(self.tx), parent.app)
|
||||
|
||||
@ -1,141 +0,0 @@
|
||||
# Copyright (C) 2019 The Electrum developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal
|
||||
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLabel, QProgressBar,
|
||||
QHBoxLayout, QPushButton)
|
||||
|
||||
from electrum import version
|
||||
from electrum import constants
|
||||
from electrum import ecc
|
||||
from electrum.i18n import _
|
||||
from electrum.util import PrintError, make_aiohttp_session
|
||||
|
||||
|
||||
class UpdateCheck(QWidget, PrintError):
|
||||
url = "https://electrum.org/version"
|
||||
download_url = "https://electrum.org/#download"
|
||||
|
||||
VERSION_ANNOUNCEMENT_SIGNING_KEYS = (
|
||||
"13xjmVAB1EATPP8RshTE8S8sNwwSUM9p1P",
|
||||
)
|
||||
|
||||
def __init__(self, main_window, latest_version=None):
|
||||
self.main_window = main_window
|
||||
QWidget.__init__(self)
|
||||
self.setWindowTitle('Electrum - ' + _('Update Check'))
|
||||
self.content = QVBoxLayout()
|
||||
self.content.setContentsMargins(*[10]*4)
|
||||
|
||||
self.heading_label = QLabel()
|
||||
self.content.addWidget(self.heading_label)
|
||||
|
||||
self.detail_label = QLabel()
|
||||
self.detail_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
|
||||
self.detail_label.setOpenExternalLinks(True)
|
||||
self.content.addWidget(self.detail_label)
|
||||
|
||||
self.pb = QProgressBar()
|
||||
self.pb.setMaximum(0)
|
||||
self.pb.setMinimum(0)
|
||||
self.content.addWidget(self.pb)
|
||||
|
||||
versions = QHBoxLayout()
|
||||
versions.addWidget(QLabel(_("Current version: {}".format(version.ELECTRUM_VERSION))))
|
||||
self.latest_version_label = QLabel(_("Latest version: {}".format(" ")))
|
||||
versions.addWidget(self.latest_version_label)
|
||||
self.content.addLayout(versions)
|
||||
|
||||
self.update_view(latest_version)
|
||||
|
||||
self.update_check_thread = UpdateCheckThread(self.main_window)
|
||||
self.update_check_thread.checked.connect(self.on_version_retrieved)
|
||||
self.update_check_thread.failed.connect(self.on_retrieval_failed)
|
||||
self.update_check_thread.start()
|
||||
|
||||
close_button = QPushButton(_("Close"))
|
||||
close_button.clicked.connect(self.close)
|
||||
self.content.addWidget(close_button)
|
||||
self.setLayout(self.content)
|
||||
self.show()
|
||||
|
||||
def on_version_retrieved(self, version):
|
||||
self.update_view(version)
|
||||
|
||||
def on_retrieval_failed(self):
|
||||
self.heading_label.setText('<h2>' + _("Update check failed") + '</h2>')
|
||||
self.detail_label.setText(_("Sorry, but we were unable to check for updates. Please try again later."))
|
||||
self.pb.hide()
|
||||
|
||||
@staticmethod
|
||||
def is_newer(latest_version):
|
||||
return latest_version > StrictVersion(version.ELECTRUM_VERSION)
|
||||
|
||||
def update_view(self, latest_version=None):
|
||||
if latest_version:
|
||||
self.pb.hide()
|
||||
self.latest_version_label.setText(_("Latest version: {}".format(latest_version)))
|
||||
if self.is_newer(latest_version):
|
||||
self.heading_label.setText('<h2>' + _("There is a new update available") + '</h2>')
|
||||
url = "<a href='{u}'>{u}</a>".format(u=UpdateCheck.download_url)
|
||||
self.detail_label.setText(_("You can download the new version from {}.").format(url))
|
||||
else:
|
||||
self.heading_label.setText('<h2>' + _("Already up to date") + '</h2>')
|
||||
self.detail_label.setText(_("You are already on the latest version of Electrum."))
|
||||
else:
|
||||
self.heading_label.setText('<h2>' + _("Checking for updates...") + '</h2>')
|
||||
self.detail_label.setText(_("Please wait while Electrum checks for available updates."))
|
||||
|
||||
|
||||
class UpdateCheckThread(QThread, PrintError):
|
||||
checked = pyqtSignal(object)
|
||||
failed = pyqtSignal()
|
||||
|
||||
def __init__(self, main_window):
|
||||
super().__init__()
|
||||
self.main_window = main_window
|
||||
|
||||
async def get_update_info(self):
|
||||
async with make_aiohttp_session(proxy=self.main_window.network.proxy) as session:
|
||||
async with session.get(UpdateCheck.url) as result:
|
||||
signed_version_dict = await result.json(content_type=None)
|
||||
# example signed_version_dict:
|
||||
# {
|
||||
# "version": "3.9.9",
|
||||
# "signatures": {
|
||||
# "1Lqm1HphuhxKZQEawzPse8gJtgjm9kUKT4": "IA+2QG3xPRn4HAIFdpu9eeaCYC7S5wS/sDxn54LJx6BdUTBpse3ibtfq8C43M7M1VfpGkD5tsdwl5C6IfpZD/gQ="
|
||||
# }
|
||||
# }
|
||||
version_num = signed_version_dict['version']
|
||||
sigs = signed_version_dict['signatures']
|
||||
for address, sig in sigs.items():
|
||||
if address not in UpdateCheck.VERSION_ANNOUNCEMENT_SIGNING_KEYS:
|
||||
continue
|
||||
sig = base64.b64decode(sig)
|
||||
msg = version_num.encode('utf-8')
|
||||
if ecc.verify_message_with_address(address=address, sig65=sig, message=msg,
|
||||
net=constants.BitcoinMainnet):
|
||||
self.print_error(f"valid sig for version announcement '{version_num}' from address '{address}'")
|
||||
break
|
||||
else:
|
||||
raise Exception('no valid signature for version announcement')
|
||||
return StrictVersion(version_num.strip())
|
||||
|
||||
def run(self):
|
||||
network = self.main_window.network
|
||||
if not network:
|
||||
self.failed.emit()
|
||||
return
|
||||
try:
|
||||
update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), network.asyncio_loop).result()
|
||||
except Exception as e:
|
||||
#self.print_error(traceback.format_exc())
|
||||
self.print_error(f"got exception: '{repr(e)}'")
|
||||
self.failed.emit()
|
||||
else:
|
||||
self.checked.emit(update_info)
|
||||
@ -5,24 +5,20 @@ import sys
|
||||
import platform
|
||||
import queue
|
||||
import traceback
|
||||
from distutils.version import StrictVersion
|
||||
from functools import partial
|
||||
from typing import NamedTuple, Callable, Optional, TYPE_CHECKING
|
||||
import base64
|
||||
|
||||
from functools import partial, lru_cache
|
||||
from typing import NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict
|
||||
|
||||
from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem,
|
||||
QPalette, QIcon)
|
||||
from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, pyqtSignal,
|
||||
QCoreApplication, QItemSelectionModel, QThread,
|
||||
QSortFilterProxyModel, QSize, QLocale)
|
||||
from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout,
|
||||
QAbstractItemView, QVBoxLayout, QLineEdit,
|
||||
QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton,
|
||||
QFileDialog, QWidget, QToolButton, QTreeView, QPlainTextEdit,
|
||||
QHeaderView, QApplication, QToolTip, QTreeWidget, QStyledItemDelegate)
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
from electrum import version
|
||||
from electrum import ecc
|
||||
from electrum import constants
|
||||
from electrum.i18n import _, languages
|
||||
from electrum.util import (FileImportFailed, FileExportFailed,
|
||||
resource_path)
|
||||
from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, PrintError
|
||||
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -40,9 +36,9 @@ else:
|
||||
dialogs = []
|
||||
|
||||
pr_icons = {
|
||||
PR_UNPAID:"unpaid.png",
|
||||
PR_PAID:"confirmed.png",
|
||||
PR_EXPIRED:"expired.png"
|
||||
PR_UNPAID:":icons/unpaid.png",
|
||||
PR_PAID:":icons/confirmed.png",
|
||||
PR_EXPIRED:":icons/expired.png"
|
||||
}
|
||||
|
||||
pr_tooltips = {
|
||||
@ -433,6 +429,8 @@ class MyTreeView(QTreeView):
|
||||
self.customContextMenuRequested.connect(create_menu)
|
||||
self.setUniformRowHeights(True)
|
||||
|
||||
self.icon_cache = IconCache()
|
||||
|
||||
# Control which columns are editable
|
||||
if editable_columns is None:
|
||||
editable_columns = {stretch_column}
|
||||
@ -473,17 +471,13 @@ class MyTreeView(QTreeView):
|
||||
assert set_current.isValid()
|
||||
self.selectionModel().select(QModelIndex(set_current), QItemSelectionModel.SelectCurrent)
|
||||
|
||||
def update_headers(self, headers: Union[List[str], Dict[int, str]]):
|
||||
# headers is either a list of column names, or a dict: (col_idx->col_name)
|
||||
if not isinstance(headers, dict): # convert to dict
|
||||
headers = dict(enumerate(headers))
|
||||
col_names = [headers[col_idx] for col_idx in sorted(headers.keys())]
|
||||
def update_headers(self, headers):
|
||||
model = self.model()
|
||||
model.setHorizontalHeaderLabels(col_names)
|
||||
model.setHorizontalHeaderLabels(headers)
|
||||
self.header().setStretchLastSection(False)
|
||||
for col_idx in headers:
|
||||
sm = QHeaderView.Stretch if col_idx == self.stretch_column else QHeaderView.ResizeToContents
|
||||
self.header().setSectionResizeMode(col_idx, sm)
|
||||
for col in range(len(headers)):
|
||||
sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents
|
||||
self.header().setSectionResizeMode(col, sm)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if self.itemDelegate().opened:
|
||||
@ -605,9 +599,8 @@ class ButtonsWidget(QWidget):
|
||||
|
||||
def addButton(self, icon_name, on_click, tooltip):
|
||||
button = QToolButton(self)
|
||||
button.setIcon(read_QIcon(icon_name))
|
||||
button.setIcon(QIcon(icon_name))
|
||||
button.setIconSize(QSize(25,25))
|
||||
button.setCursor(QCursor(Qt.PointingHandCursor))
|
||||
button.setStyleSheet("QToolButton { border: none; hover {border: 1px} pressed {border: 1px} padding: 0px; }")
|
||||
button.setVisible(True)
|
||||
button.setToolTip(tooltip)
|
||||
@ -617,7 +610,7 @@ class ButtonsWidget(QWidget):
|
||||
|
||||
def addCopyButton(self, app):
|
||||
self.app = app
|
||||
self.addButton("copy.png", self.on_copy, _("Copy to clipboard"))
|
||||
self.addButton(":icons/copy.png", self.on_copy, _("Copy to clipboard"))
|
||||
|
||||
def on_copy(self):
|
||||
self.app.clipboard().setText(self.text())
|
||||
@ -802,14 +795,15 @@ def get_parent_main_window(widget):
|
||||
return widget
|
||||
return None
|
||||
|
||||
class IconCache:
|
||||
|
||||
def icon_path(icon_basename):
|
||||
return resource_path('gui', 'icons', icon_basename)
|
||||
def __init__(self):
|
||||
self.__cache = {}
|
||||
|
||||
|
||||
@lru_cache(maxsize=1000)
|
||||
def read_QIcon(icon_basename):
|
||||
return QIcon(icon_path(icon_basename))
|
||||
def get(self, file_name):
|
||||
if file_name not in self.__cache:
|
||||
self.__cache[file_name] = QIcon(file_name)
|
||||
return self.__cache[file_name]
|
||||
|
||||
|
||||
def get_default_language():
|
||||
@ -833,6 +827,123 @@ class FromList(QTreeWidget):
|
||||
self.header().setSectionResizeMode(1, sm)
|
||||
|
||||
|
||||
class UpdateCheck(QWidget, PrintError):
|
||||
url = "https://electrum.org/version"
|
||||
download_url = "https://electrum.org/#download"
|
||||
|
||||
VERSION_ANNOUNCEMENT_SIGNING_KEYS = (
|
||||
"13xjmVAB1EATPP8RshTE8S8sNwwSUM9p1P",
|
||||
)
|
||||
|
||||
def __init__(self, main_window, latest_version=None):
|
||||
self.main_window = main_window
|
||||
QWidget.__init__(self)
|
||||
self.setWindowTitle('Electrum - ' + _('Update Check'))
|
||||
self.content = QVBoxLayout()
|
||||
self.content.setContentsMargins(*[10]*4)
|
||||
|
||||
self.heading_label = QLabel()
|
||||
self.content.addWidget(self.heading_label)
|
||||
|
||||
self.detail_label = QLabel()
|
||||
self.detail_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
|
||||
self.detail_label.setOpenExternalLinks(True)
|
||||
self.content.addWidget(self.detail_label)
|
||||
|
||||
self.pb = QProgressBar()
|
||||
self.pb.setMaximum(0)
|
||||
self.pb.setMinimum(0)
|
||||
self.content.addWidget(self.pb)
|
||||
|
||||
versions = QHBoxLayout()
|
||||
versions.addWidget(QLabel(_("Current version: {}".format(version.ELECTRUM_VERSION))))
|
||||
self.latest_version_label = QLabel(_("Latest version: {}".format(" ")))
|
||||
versions.addWidget(self.latest_version_label)
|
||||
self.content.addLayout(versions)
|
||||
|
||||
self.update_view(latest_version)
|
||||
|
||||
self.update_check_thread = UpdateCheckThread(self.main_window)
|
||||
self.update_check_thread.checked.connect(self.on_version_retrieved)
|
||||
self.update_check_thread.failed.connect(self.on_retrieval_failed)
|
||||
self.update_check_thread.start()
|
||||
|
||||
close_button = QPushButton(_("Close"))
|
||||
close_button.clicked.connect(self.close)
|
||||
self.content.addWidget(close_button)
|
||||
self.setLayout(self.content)
|
||||
self.show()
|
||||
|
||||
def on_version_retrieved(self, version):
|
||||
self.update_view(version)
|
||||
|
||||
def on_retrieval_failed(self):
|
||||
self.heading_label.setText('<h2>' + _("Update check failed") + '</h2>')
|
||||
self.detail_label.setText(_("Sorry, but we were unable to check for updates. Please try again later."))
|
||||
self.pb.hide()
|
||||
|
||||
@staticmethod
|
||||
def is_newer(latest_version):
|
||||
return latest_version > StrictVersion(version.ELECTRUM_VERSION)
|
||||
|
||||
def update_view(self, latest_version=None):
|
||||
if latest_version:
|
||||
self.pb.hide()
|
||||
self.latest_version_label.setText(_("Latest version: {}".format(latest_version)))
|
||||
if self.is_newer(latest_version):
|
||||
self.heading_label.setText('<h2>' + _("There is a new update available") + '</h2>')
|
||||
url = "<a href='{u}'>{u}</a>".format(u=UpdateCheck.download_url)
|
||||
self.detail_label.setText(_("You can download the new version from {}.").format(url))
|
||||
else:
|
||||
self.heading_label.setText('<h2>' + _("Already up to date") + '</h2>')
|
||||
self.detail_label.setText(_("You are already on the latest version of Electrum."))
|
||||
else:
|
||||
self.heading_label.setText('<h2>' + _("Checking for updates...") + '</h2>')
|
||||
self.detail_label.setText(_("Please wait while Electrum checks for available updates."))
|
||||
|
||||
|
||||
class UpdateCheckThread(QThread, PrintError):
|
||||
checked = pyqtSignal(object)
|
||||
failed = pyqtSignal()
|
||||
|
||||
def __init__(self, main_window):
|
||||
super().__init__()
|
||||
self.main_window = main_window
|
||||
|
||||
async def get_update_info(self):
|
||||
async with make_aiohttp_session(proxy=self.main_window.network.proxy) as session:
|
||||
async with session.get(UpdateCheck.url) as result:
|
||||
signed_version_dict = await result.json(content_type=None)
|
||||
# example signed_version_dict:
|
||||
# {
|
||||
# "version": "3.9.9",
|
||||
# "signatures": {
|
||||
# "1Lqm1HphuhxKZQEawzPse8gJtgjm9kUKT4": "IA+2QG3xPRn4HAIFdpu9eeaCYC7S5wS/sDxn54LJx6BdUTBpse3ibtfq8C43M7M1VfpGkD5tsdwl5C6IfpZD/gQ="
|
||||
# }
|
||||
# }
|
||||
version_num = signed_version_dict['version']
|
||||
sigs = signed_version_dict['signatures']
|
||||
for address, sig in sigs.items():
|
||||
if address not in UpdateCheck.VERSION_ANNOUNCEMENT_SIGNING_KEYS:
|
||||
continue
|
||||
sig = base64.b64decode(sig)
|
||||
msg = version_num.encode('utf-8')
|
||||
if ecc.verify_message_with_address(address=address, sig65=sig, message=msg,
|
||||
net=constants.BitcoinMainnet):
|
||||
self.print_error(f"valid sig for version announcement '{version_num}' from address '{address}'")
|
||||
break
|
||||
else:
|
||||
raise Exception('no valid signature for version announcement')
|
||||
return StrictVersion(version_num.strip())
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.checked.emit(asyncio.run_coroutine_threadsafe(self.get_update_info(), self.main_window.network.asyncio_loop).result())
|
||||
except Exception:
|
||||
self.print_error(traceback.format_exc())
|
||||
self.failed.emit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication([])
|
||||
t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done"))
|
||||
|
||||
@ -24,38 +24,17 @@
|
||||
# SOFTWARE.
|
||||
|
||||
from typing import Optional, List
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont
|
||||
from PyQt5.QtWidgets import QAbstractItemView, QMenu
|
||||
|
||||
from electrum.i18n import _
|
||||
|
||||
from .util import MyTreeView, ColorScheme, MONOSPACE_FONT
|
||||
from .util import *
|
||||
|
||||
class UTXOList(MyTreeView):
|
||||
|
||||
class Columns(IntEnum):
|
||||
ADDRESS = 0
|
||||
LABEL = 1
|
||||
AMOUNT = 2
|
||||
HEIGHT = 3
|
||||
OUTPOINT = 4
|
||||
|
||||
headers = {
|
||||
Columns.ADDRESS: _('Address'),
|
||||
Columns.LABEL: _('Label'),
|
||||
Columns.AMOUNT: _('Amount'),
|
||||
Columns.HEIGHT: _('Height'),
|
||||
Columns.OUTPOINT: _('Output point'),
|
||||
}
|
||||
filter_columns = [Columns.ADDRESS, Columns.LABEL]
|
||||
headers = [ _('Address'), _('Label'), _('Amount'), _('Height'), _('Output point')]
|
||||
filter_columns = [0, 1] # Address, Label
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent, self.create_menu,
|
||||
stretch_column=self.Columns.LABEL,
|
||||
editable_columns=[])
|
||||
super().__init__(parent, self.create_menu, 1, editable_columns=[])
|
||||
self.setModel(QStandardItemModel(self))
|
||||
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||
self.setSortingEnabled(True)
|
||||
@ -68,32 +47,27 @@ class UTXOList(MyTreeView):
|
||||
self.model().clear()
|
||||
self.update_headers(self.__class__.headers)
|
||||
for idx, x in enumerate(utxos):
|
||||
self.insert_utxo(idx, x)
|
||||
|
||||
def insert_utxo(self, idx, x):
|
||||
address = x.get('address')
|
||||
height = x.get('height')
|
||||
name = x.get('prevout_hash') + ":%d"%x.get('prevout_n')
|
||||
name_short = x.get('prevout_hash')[:10] + '...' + ":%d"%x.get('prevout_n')
|
||||
self.utxo_dict[name] = x
|
||||
label = self.wallet.get_label(x.get('prevout_hash'))
|
||||
amount = self.parent.format_amount(x['value'], whitespaces=True)
|
||||
labels = [address, label, amount, '%d'%height, name_short]
|
||||
utxo_item = [QStandardItem(x) for x in labels]
|
||||
self.set_editability(utxo_item)
|
||||
utxo_item[self.Columns.ADDRESS].setFont(QFont(MONOSPACE_FONT))
|
||||
utxo_item[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT))
|
||||
utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT))
|
||||
utxo_item[self.Columns.ADDRESS].setData(name, Qt.UserRole)
|
||||
utxo_item[self.Columns.OUTPOINT].setToolTip(name)
|
||||
if self.wallet.is_frozen(address):
|
||||
utxo_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True))
|
||||
self.model().insertRow(idx, utxo_item)
|
||||
address = x.get('address')
|
||||
height = x.get('height')
|
||||
name = x.get('prevout_hash') + ":%d"%x.get('prevout_n')
|
||||
self.utxo_dict[name] = x
|
||||
label = self.wallet.get_label(x.get('prevout_hash'))
|
||||
amount = self.parent.format_amount(x['value'], whitespaces=True)
|
||||
labels = [address, label, amount, '%d'%height, name[0:10] + '...' + name[-2:]]
|
||||
utxo_item = [QStandardItem(x) for x in labels]
|
||||
self.set_editability(utxo_item)
|
||||
utxo_item[0].setFont(QFont(MONOSPACE_FONT))
|
||||
utxo_item[2].setFont(QFont(MONOSPACE_FONT))
|
||||
utxo_item[4].setFont(QFont(MONOSPACE_FONT))
|
||||
utxo_item[0].setData(name, Qt.UserRole)
|
||||
if self.wallet.is_frozen(address):
|
||||
utxo_item[0].setBackground(ColorScheme.BLUE.as_color(True))
|
||||
self.model().insertRow(idx, utxo_item)
|
||||
|
||||
def selected_column_0_user_roles(self) -> Optional[List[str]]:
|
||||
if not self.model():
|
||||
return None
|
||||
items = self.selected_in_column(self.Columns.ADDRESS)
|
||||
items = self.selected_in_column(0)
|
||||
if not items:
|
||||
return None
|
||||
return [x.data(Qt.UserRole) for x in items]
|
||||
|
||||
@ -39,7 +39,7 @@ from .util import PrintError, ignore_exceptions, log_exceptions, bfh, SilentTask
|
||||
from . import util
|
||||
from . import x509
|
||||
from . import pem
|
||||
from . import version
|
||||
from .version import ELECTRUM_VERSION, PROTOCOL_VERSION
|
||||
from . import blockchain
|
||||
from .blockchain import Blockchain
|
||||
from . import constants
|
||||
@ -71,16 +71,8 @@ class NotificationSession(RPCSession):
|
||||
self.cache = {}
|
||||
self.in_flight_requests_semaphore = asyncio.Semaphore(100)
|
||||
self.default_timeout = NetworkTimeout.Generic.NORMAL
|
||||
self._msg_counter = 0
|
||||
self.interface = None # type: Optional[Interface]
|
||||
|
||||
def _get_and_inc_msg_counter(self):
|
||||
# runs in event loop thread, no need for lock
|
||||
self._msg_counter += 1
|
||||
return self._msg_counter
|
||||
|
||||
async def handle_request(self, request):
|
||||
self.maybe_log(f"--> {request}")
|
||||
# note: if server sends malformed request and we raise, the superclass
|
||||
# will catch the exception, count errors, and at some point disconnect
|
||||
if isinstance(request, Notification):
|
||||
@ -99,17 +91,12 @@ class NotificationSession(RPCSession):
|
||||
timeout = self.default_timeout
|
||||
# note: the semaphore implementation guarantees no starvation
|
||||
async with self.in_flight_requests_semaphore:
|
||||
msg_id = self._get_and_inc_msg_counter()
|
||||
self.maybe_log(f"<-- {args} {kwargs} (id: {msg_id})")
|
||||
try:
|
||||
response = await asyncio.wait_for(
|
||||
return await asyncio.wait_for(
|
||||
super().send_request(*args, **kwargs),
|
||||
timeout)
|
||||
except asyncio.TimeoutError as e:
|
||||
raise RequestTimedOut(f'request timed out: {args} (id: {msg_id})') from e
|
||||
else:
|
||||
self.maybe_log(f"--> {response} (id: {msg_id})")
|
||||
return response
|
||||
raise RequestTimedOut('request timed out: {}'.format(args)) from e
|
||||
|
||||
async def subscribe(self, method: str, params: List, queue: asyncio.Queue):
|
||||
# note: until the cache is written for the first time,
|
||||
@ -136,11 +123,6 @@ class NotificationSession(RPCSession):
|
||||
"""Hashable index for subscriptions and cache"""
|
||||
return str(method) + repr(params)
|
||||
|
||||
def maybe_log(self, msg: str) -> None:
|
||||
if not self.interface: return
|
||||
if self.interface.debug or self.interface.network.debug:
|
||||
self.interface.print_error(msg)
|
||||
|
||||
|
||||
class GracefulDisconnect(Exception): pass
|
||||
|
||||
@ -191,9 +173,6 @@ class Interface(PrintError):
|
||||
self.tip_header = None
|
||||
self.tip = 0
|
||||
|
||||
# Dump network messages (only for this interface). Set at runtime from the console.
|
||||
self.debug = False
|
||||
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self.network.main_taskgroup.spawn(self.run()), self.network.asyncio_loop)
|
||||
self.group = SilentTaskGroup()
|
||||
@ -391,10 +370,9 @@ class Interface(PrintError):
|
||||
host=self.host, port=self.port,
|
||||
ssl=sslc, proxy=self.proxy) as session:
|
||||
self.session = session # type: NotificationSession
|
||||
self.session.interface = self
|
||||
self.session.default_timeout = self.network.get_network_timeout_seconds(NetworkTimeout.Generic)
|
||||
try:
|
||||
ver = await session.send_request('server.version', [version.ELECTRUM_VERSION, version.PROTOCOL_VERSION])
|
||||
ver = await session.send_request('server.version', [ELECTRUM_VERSION, PROTOCOL_VERSION])
|
||||
except aiorpcx.jsonrpc.RPCError as e:
|
||||
raise GracefulDisconnect(e) # probably 'unsupported protocol version'
|
||||
if exit_early:
|
||||
|
||||
@ -26,7 +26,6 @@
|
||||
|
||||
from unicodedata import normalize
|
||||
import hashlib
|
||||
from typing import Tuple
|
||||
|
||||
from . import bitcoin, ecc, constants, bip32
|
||||
from .bitcoin import (deserialize_privkey, serialize_privkey,
|
||||
@ -36,9 +35,9 @@ from .bip32 import (bip32_public_derivation, deserialize_xpub, CKD_pub,
|
||||
bip32_private_key, bip32_derivation, BIP32_PRIME,
|
||||
is_xpub, is_xprv)
|
||||
from .ecc import string_to_number, number_to_string
|
||||
from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST,
|
||||
from .crypto import (pw_decode, pw_encode, sha256d, PW_HASH_VERSION_LATEST,
|
||||
SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion)
|
||||
from .util import (PrintError, InvalidPassword, WalletFileException,
|
||||
from .util import (PrintError, InvalidPassword, hfu, WalletFileException,
|
||||
BitcoinException, bh2u, bfh, print_error, inv_dict)
|
||||
from .mnemonic import Mnemonic, load_wordlist
|
||||
from .plugin import run_hook
|
||||
@ -601,15 +600,14 @@ def bip39_to_seed(mnemonic, passphrase):
|
||||
return hashlib.pbkdf2_hmac('sha512', mnemonic.encode('utf-8'),
|
||||
b'mnemonic' + passphrase.encode('utf-8'), iterations = PBKDF2_ROUNDS)
|
||||
|
||||
|
||||
def bip39_is_checksum_valid(mnemonic: str) -> Tuple[bool, bool]:
|
||||
"""Test checksum of bip39 mnemonic assuming English wordlist.
|
||||
Returns tuple (is_checksum_valid, is_wordlist_valid)
|
||||
"""
|
||||
# returns tuple (is_checksum_valid, is_wordlist_valid)
|
||||
def bip39_is_checksum_valid(mnemonic):
|
||||
words = [ normalize('NFKD', word) for word in mnemonic.split() ]
|
||||
words_len = len(words)
|
||||
wordlist = load_wordlist("english.txt")
|
||||
n = len(wordlist)
|
||||
checksum_length = 11*words_len//33
|
||||
entropy_length = 32*checksum_length
|
||||
i = 0
|
||||
words.reverse()
|
||||
while words:
|
||||
@ -621,12 +619,13 @@ def bip39_is_checksum_valid(mnemonic: str) -> Tuple[bool, bool]:
|
||||
i = i*n + k
|
||||
if words_len not in [12, 15, 18, 21, 24]:
|
||||
return False, True
|
||||
checksum_length = 11 * words_len // 33 # num bits
|
||||
entropy_length = 32 * checksum_length # num bits
|
||||
entropy = i >> checksum_length
|
||||
checksum = i % 2**checksum_length
|
||||
entropy_bytes = int.to_bytes(entropy, length=entropy_length//8, byteorder="big")
|
||||
hashed = int.from_bytes(sha256(entropy_bytes), byteorder="big")
|
||||
h = '{:x}'.format(entropy)
|
||||
while len(h) < entropy_length/4:
|
||||
h = '0'+h
|
||||
b = bytearray.fromhex(h)
|
||||
hashed = int(hfu(hashlib.sha256(b).digest()), 16)
|
||||
calculated_checksum = hashed >> (256 - checksum_length)
|
||||
return checksum == calculated_checksum, True
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ import string
|
||||
|
||||
import ecdsa
|
||||
|
||||
from .util import print_error, resource_path
|
||||
from .util import print_error
|
||||
from .bitcoin import is_old_seed, is_new_seed
|
||||
from . import version
|
||||
|
||||
@ -88,7 +88,7 @@ def normalize_text(seed: str) -> str:
|
||||
return seed
|
||||
|
||||
def load_wordlist(filename):
|
||||
path = resource_path('wordlist', filename)
|
||||
path = os.path.join(os.path.dirname(__file__), 'wordlist', filename)
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
s = f.read().strip()
|
||||
s = unicodedata.normalize('NFKD', s)
|
||||
|
||||
@ -43,8 +43,7 @@ from aiohttp import ClientResponse
|
||||
|
||||
from . import util
|
||||
from .util import (PrintError, print_error, log_exceptions, ignore_exceptions,
|
||||
bfh, SilentTaskGroup, make_aiohttp_session, send_exception_to_crash_reporter,
|
||||
is_hash256_str, is_non_negative_integer)
|
||||
bfh, SilentTaskGroup, make_aiohttp_session)
|
||||
|
||||
from .bitcoin import COIN
|
||||
from . import constants
|
||||
@ -189,24 +188,6 @@ class TxBroadcastServerReturnedError(TxBroadcastError):
|
||||
str(self))
|
||||
|
||||
|
||||
class TxBroadcastUnknownError(TxBroadcastError):
|
||||
def get_message_for_gui(self):
|
||||
return "{}\n{}" \
|
||||
.format(_("Unknown error when broadcasting the transaction."),
|
||||
_("Consider trying to connect to a different server, or updating Electrum."))
|
||||
|
||||
|
||||
class UntrustedServerReturnedError(Exception):
|
||||
def __init__(self, *, original_exception):
|
||||
self.original_exception = original_exception
|
||||
|
||||
def __str__(self):
|
||||
return _("The server returned an error.")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<UntrustedServerReturnedError original_exception: {repr(self.original_exception)}>"
|
||||
|
||||
|
||||
INSTANCE = None
|
||||
|
||||
|
||||
@ -276,9 +257,6 @@ class Network(PrintError):
|
||||
self.server_queue = None
|
||||
self.proxy = None
|
||||
|
||||
# Dump network messages (all interfaces). Set at runtime from the console.
|
||||
self.debug = False
|
||||
|
||||
self._set_status('disconnected')
|
||||
|
||||
def run_from_another_thread(self, coro):
|
||||
@ -460,7 +438,7 @@ class Network(PrintError):
|
||||
@with_recent_servers_lock
|
||||
def get_servers(self):
|
||||
# start with hardcoded servers
|
||||
out = dict(constants.net.DEFAULT_SERVERS) # copy
|
||||
out = constants.net.DEFAULT_SERVERS
|
||||
# add recent servers
|
||||
for s in self.recent_servers:
|
||||
try:
|
||||
@ -775,21 +753,8 @@ class Network(PrintError):
|
||||
raise BestEffortRequestFailed('no interface to do request on... gave up.')
|
||||
return make_reliable_wrapper
|
||||
|
||||
def catch_server_exceptions(func):
|
||||
async def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return await func(self, *args, **kwargs)
|
||||
except aiorpcx.jsonrpc.CodeMessageError as e:
|
||||
raise UntrustedServerReturnedError(original_exception=e) from e
|
||||
return wrapper
|
||||
|
||||
@best_effort_reliable
|
||||
@catch_server_exceptions
|
||||
async def get_merkle_for_transaction(self, tx_hash: str, tx_height: int) -> dict:
|
||||
if not is_hash256_str(tx_hash):
|
||||
raise Exception(f"{repr(tx_hash)} is not a txid")
|
||||
if not is_non_negative_integer(tx_height):
|
||||
raise Exception(f"{repr(tx_height)} is not a block height")
|
||||
return await self.interface.session.send_request('blockchain.transaction.get_merkle', [tx_hash, tx_height])
|
||||
|
||||
@best_effort_reliable
|
||||
@ -799,15 +764,9 @@ class Network(PrintError):
|
||||
try:
|
||||
out = await self.interface.session.send_request('blockchain.transaction.broadcast', [str(tx)], timeout=timeout)
|
||||
# note: both 'out' and exception messages are untrusted input from the server
|
||||
except (RequestTimedOut, asyncio.CancelledError, asyncio.TimeoutError):
|
||||
raise # pass-through
|
||||
except aiorpcx.jsonrpc.CodeMessageError as e:
|
||||
except aiorpcx.jsonrpc.RPCError as e:
|
||||
self.print_error(f"broadcast_transaction error: {repr(e)}")
|
||||
raise TxBroadcastServerReturnedError(self.sanitize_tx_broadcast_response(e.message)) from e
|
||||
except BaseException as e: # intentional BaseException for sanity!
|
||||
self.print_error(f"broadcast_transaction error2: {repr(e)}")
|
||||
send_exception_to_crash_reporter(e)
|
||||
raise TxBroadcastUnknownError() from e
|
||||
if out != tx.txid():
|
||||
self.print_error(f"unexpected txid for broadcast_transaction: {out} != {tx.txid()}")
|
||||
raise TxBroadcastHashMismatch(_("Server returned unexpected transaction ID."))
|
||||
@ -823,7 +782,7 @@ class Network(PrintError):
|
||||
# grep "reason ="
|
||||
policy_error_messages = {
|
||||
r"version": _("Transaction uses non-standard version."),
|
||||
r"tx-size": _("The transaction was rejected because it is too large (in bytes)."),
|
||||
r"tx-size": _("The transaction was rejected because it is too large."),
|
||||
r"scriptsig-size": None,
|
||||
r"scriptsig-not-pushonly": None,
|
||||
r"scriptpubkey": None,
|
||||
@ -947,39 +906,24 @@ class Network(PrintError):
|
||||
return _("Unknown error")
|
||||
|
||||
@best_effort_reliable
|
||||
@catch_server_exceptions
|
||||
async def request_chunk(self, height: int, tip=None, *, can_return_early=False):
|
||||
if not is_non_negative_integer(height):
|
||||
raise Exception(f"{repr(height)} is not a block height")
|
||||
async def request_chunk(self, height, tip=None, *, can_return_early=False):
|
||||
return await self.interface.request_chunk(height, tip=tip, can_return_early=can_return_early)
|
||||
|
||||
@best_effort_reliable
|
||||
@catch_server_exceptions
|
||||
async def get_transaction(self, tx_hash: str, *, timeout=None) -> str:
|
||||
if not is_hash256_str(tx_hash):
|
||||
raise Exception(f"{repr(tx_hash)} is not a txid")
|
||||
return await self.interface.session.send_request('blockchain.transaction.get', [tx_hash],
|
||||
timeout=timeout)
|
||||
|
||||
@best_effort_reliable
|
||||
@catch_server_exceptions
|
||||
async def get_history_for_scripthash(self, sh: str) -> List[dict]:
|
||||
if not is_hash256_str(sh):
|
||||
raise Exception(f"{repr(sh)} is not a scripthash")
|
||||
return await self.interface.session.send_request('blockchain.scripthash.get_history', [sh])
|
||||
|
||||
@best_effort_reliable
|
||||
@catch_server_exceptions
|
||||
async def listunspent_for_scripthash(self, sh: str) -> List[dict]:
|
||||
if not is_hash256_str(sh):
|
||||
raise Exception(f"{repr(sh)} is not a scripthash")
|
||||
return await self.interface.session.send_request('blockchain.scripthash.listunspent', [sh])
|
||||
|
||||
@best_effort_reliable
|
||||
@catch_server_exceptions
|
||||
async def get_balance_for_scripthash(self, sh: str) -> dict:
|
||||
if not is_hash256_str(sh):
|
||||
raise Exception(f"{repr(sh)} is not a scripthash")
|
||||
return await self.interface.session.send_request('blockchain.scripthash.get_balance', [sh])
|
||||
|
||||
def blockchain(self) -> Blockchain:
|
||||
@ -1177,32 +1121,3 @@ class Network(PrintError):
|
||||
assert network._loop_thread is not threading.currentThread()
|
||||
coro = asyncio.run_coroutine_threadsafe(network._send_http_on_proxy(method, url, **kwargs), network.asyncio_loop)
|
||||
return coro.result(5)
|
||||
|
||||
|
||||
|
||||
# methods used in scripts
|
||||
async def get_peers(self):
|
||||
while not self.is_connected():
|
||||
await asyncio.sleep(1)
|
||||
session = self.interface.session
|
||||
return parse_servers(await session.send_request('server.peers.subscribe'))
|
||||
|
||||
async def send_multiple_requests(self, servers: List[str], method: str, params: Sequence):
|
||||
responses = dict()
|
||||
async def get_response(server):
|
||||
interface = Interface(self, server, self.proxy)
|
||||
timeout = self.get_network_timeout_seconds(NetworkTimeout.Urgent)
|
||||
try:
|
||||
await asyncio.wait_for(interface.ready, timeout)
|
||||
except BaseException as e:
|
||||
await interface.close()
|
||||
return
|
||||
try:
|
||||
res = await interface.session.send_request(method, params, timeout=10)
|
||||
except Exception as e:
|
||||
res = e
|
||||
responses[interface.server] = res
|
||||
async with TaskGroup() as group:
|
||||
for server in servers:
|
||||
await group.spawn(get_response(server))
|
||||
return responses
|
||||
|
||||
@ -5,13 +5,15 @@ from io import BytesIO
|
||||
import sys
|
||||
import platform
|
||||
|
||||
from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton)
|
||||
|
||||
from electrum.plugin import BasePlugin, hook
|
||||
from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog, read_QIcon
|
||||
from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog
|
||||
from electrum.util import print_msg, print_error
|
||||
from electrum.i18n import _
|
||||
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton)
|
||||
|
||||
try:
|
||||
import amodem.audio
|
||||
import amodem.main
|
||||
@ -69,7 +71,7 @@ class Plugin(BasePlugin):
|
||||
@hook
|
||||
def transaction_dialog(self, dialog):
|
||||
b = QPushButton()
|
||||
b.setIcon(read_QIcon("speaker.png"))
|
||||
b.setIcon(QIcon(":icons/speaker.png"))
|
||||
|
||||
def handler():
|
||||
blob = json.dumps(dialog.tx.as_dict())
|
||||
@ -79,7 +81,7 @@ class Plugin(BasePlugin):
|
||||
|
||||
@hook
|
||||
def scan_text_edit(self, parent):
|
||||
parent.addButton('microphone.png', partial(self._recv, parent),
|
||||
parent.addButton(':icons/microphone.png', partial(self._recv, parent),
|
||||
_("Read from microphone"))
|
||||
|
||||
@hook
|
||||
@ -87,7 +89,7 @@ class Plugin(BasePlugin):
|
||||
def handler():
|
||||
blob = str(parent.toPlainText())
|
||||
self._send(parent=parent, blob=blob)
|
||||
parent.addButton('speaker.png', handler, _("Send to speaker"))
|
||||
parent.addButton(':icons/speaker.png', handler, _("Send to speaker"))
|
||||
|
||||
def _audio_interface(self):
|
||||
interface = amodem.audio.Interface(config=self.modem_config)
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import time
|
||||
from functools import partial
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
from PyQt5.QtWidgets import QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayout
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import hook
|
||||
from electrum.wallet import Standard_Wallet
|
||||
from electrum.gui.qt.util import WindowModalDialog, CloseButton, get_parent_main_window
|
||||
from electrum.gui.qt.util import *
|
||||
|
||||
from .coldcard import ColdcardPlugin
|
||||
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
|
||||
@ -15,8 +11,8 @@ from ..hw_wallet.plugin import only_hook_if_libraries_available
|
||||
|
||||
|
||||
class Plugin(ColdcardPlugin, QtPluginBase):
|
||||
icon_unpaired = "coldcard_unpaired.png"
|
||||
icon_paired = "coldcard.png"
|
||||
icon_unpaired = ":icons/coldcard_unpaired.png"
|
||||
icon_paired = ":icons/coldcard.png"
|
||||
|
||||
def create_handler(self, window):
|
||||
return Coldcard_Handler(window)
|
||||
|
||||
@ -26,7 +26,8 @@
|
||||
import time
|
||||
from xmlrpc.client import ServerProxy
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import QPushButton
|
||||
|
||||
from electrum import util, keystore, ecc, bip32, crypto
|
||||
|
||||
@ -10,8 +10,8 @@ from .digitalbitbox import DigitalBitboxPlugin
|
||||
|
||||
|
||||
class Plugin(DigitalBitboxPlugin, QtPluginBase):
|
||||
icon_unpaired = "digitalbitbox_unpaired.png"
|
||||
icon_paired = "digitalbitbox.png"
|
||||
icon_unpaired = ":icons/digitalbitbox_unpaired.png"
|
||||
icon_paired = ":icons/digitalbitbox.png"
|
||||
|
||||
def create_handler(self, window):
|
||||
return DigitalBitbox_Handler(window)
|
||||
|
||||
@ -37,7 +37,8 @@ from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.base import MIMEBase
|
||||
from email.encoders import encode_base64
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, QThread
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit,
|
||||
QInputDialog)
|
||||
|
||||
|
||||
@ -25,14 +25,11 @@
|
||||
# SOFTWARE.
|
||||
|
||||
import threading
|
||||
from functools import partial
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QLabel
|
||||
|
||||
from electrum.gui.qt.password_dialog import PasswordLayout, PW_PASSPHRASE
|
||||
from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDialog,
|
||||
Buttons, CancelButton, TaskThread)
|
||||
from electrum.gui.qt.util import *
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import PrintError
|
||||
@ -76,8 +73,8 @@ class QtHandlerBase(QObject, PrintError):
|
||||
def _update_status(self, paired):
|
||||
if hasattr(self, 'button'):
|
||||
button = self.button
|
||||
icon_name = button.icon_paired if paired else button.icon_unpaired
|
||||
button.setIcon(read_QIcon(icon_name))
|
||||
icon = button.icon_paired if paired else button.icon_unpaired
|
||||
button.setIcon(QIcon(icon))
|
||||
|
||||
def query_choice(self, msg, labels):
|
||||
self.done.clear()
|
||||
@ -203,7 +200,7 @@ class QtPluginBase(object):
|
||||
return
|
||||
tooltip = self.device + '\n' + (keystore.label or 'unnamed')
|
||||
cb = partial(self.show_settings_dialog, window, keystore)
|
||||
button = StatusBarButton(read_QIcon(self.icon_unpaired), tooltip, cb)
|
||||
button = StatusBarButton(QIcon(self.icon_unpaired), tooltip, cb)
|
||||
button.icon_paired = self.icon_paired
|
||||
button.icon_unpaired = self.icon_unpaired
|
||||
window.statusBar().addPermanentWidget(button)
|
||||
@ -237,4 +234,4 @@ class QtPluginBase(object):
|
||||
def show_address():
|
||||
addr = receive_address_e.text()
|
||||
keystore.thread.add(partial(plugin.show_address, wallet, addr, keystore))
|
||||
receive_address_e.addButton("eye1.png", show_address, _("Show on {}").format(plugin.device))
|
||||
receive_address_e.addButton(":icons/eye1.png", show_address, _("Show on {}").format(plugin.device))
|
||||
|
||||
@ -154,7 +154,7 @@ class KeepKeyPlugin(HW_PluginBase):
|
||||
return client
|
||||
|
||||
def get_coin_name(self):
|
||||
return "Testnet" if constants.net.TESTNET else "Florincoin"
|
||||
return "Testnet" if constants.net.TESTNET else "Bitcoin"
|
||||
|
||||
def initialize_device(self, device_id, wizard, handler):
|
||||
# Initialization method
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
from functools import partial
|
||||
import threading
|
||||
|
||||
from PyQt5.QtCore import Qt, QEventLoop, pyqtSignal, QRegExp
|
||||
from PyQt5.QtGui import QRegExpValidator
|
||||
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
|
||||
QHBoxLayout, QButtonGroup, QGroupBox, QDialog,
|
||||
QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget,
|
||||
QMessageBox, QFileDialog, QSlider, QTabWidget)
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QGridLayout, QInputDialog, QPushButton
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QLabel
|
||||
|
||||
from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
|
||||
OkButton, CloseButton)
|
||||
from electrum.gui.qt.util import *
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import hook
|
||||
from electrum.util import bh2u
|
||||
@ -299,8 +295,8 @@ class QtPlugin(QtPluginBase):
|
||||
|
||||
|
||||
class Plugin(KeepKeyPlugin, QtPlugin):
|
||||
icon_paired = "keepkey.png"
|
||||
icon_unpaired = "keepkey_unpaired.png"
|
||||
icon_paired = ":icons/keepkey.png"
|
||||
icon_unpaired = ":icons/keepkey_unpaired.png"
|
||||
|
||||
@classmethod
|
||||
def pin_matrix_widget_class(self):
|
||||
|
||||
@ -2,12 +2,15 @@ from functools import partial
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import (QHBoxLayout, QLabel, QVBoxLayout)
|
||||
|
||||
from electrum.plugin import hook
|
||||
from electrum.i18n import _
|
||||
from electrum.gui.qt.util import ThreadedButton, Buttons, EnterButton, WindowModalDialog, OkButton
|
||||
from electrum.gui.qt import EnterButton
|
||||
from electrum.gui.qt.util import ThreadedButton, Buttons
|
||||
from electrum.gui.qt.util import WindowModalDialog, OkButton
|
||||
|
||||
from .labels import LabelsPlugin
|
||||
|
||||
|
||||
@ -7,16 +7,17 @@ from binascii import hexlify, unhexlify
|
||||
|
||||
import websocket
|
||||
|
||||
from PyQt5.QtWidgets import (QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel,
|
||||
QWidget, QHBoxLayout, QComboBox, QPushButton)
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
from PyQt5.QtWidgets import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel
|
||||
import PyQt5.QtCore as QtCore
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
from btchip.btchip import BTChipException
|
||||
from btchip.btchip import *
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import print_msg
|
||||
from electrum import constants, bitcoin
|
||||
from electrum.gui.qt.qrcodewidget import QRCodeWidget
|
||||
from electrum.gui.qt.util import *
|
||||
|
||||
|
||||
DEBUG = False
|
||||
|
||||
@ -30,7 +30,7 @@ except ImportError:
|
||||
|
||||
MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
|
||||
' https://www.ledgerwallet.com'
|
||||
MSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or "Florincoin" app) too old for Segwit support. Please update at') + \
|
||||
MSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or "Bitcoin" app) too old for Segwit support. Please update at') + \
|
||||
' https://www.ledgerwallet.com'
|
||||
MULTI_OUTPUT_SUPPORT = '1.1.4'
|
||||
SEGWIT_SUPPORT = '1.1.10'
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
from functools import partial
|
||||
|
||||
#from btchip.btchipPersoWizard import StartBTChipPersoDialog
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtWidgets import QInputDialog, QLabel, QVBoxLayout, QLineEdit
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import hook
|
||||
from electrum.wallet import Standard_Wallet
|
||||
from electrum.gui.qt.util import WindowModalDialog
|
||||
from electrum.gui.qt.util import *
|
||||
|
||||
from .ledger import LedgerPlugin
|
||||
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
|
||||
@ -16,8 +11,8 @@ from ..hw_wallet.plugin import only_hook_if_libraries_available
|
||||
|
||||
|
||||
class Plugin(LedgerPlugin, QtPluginBase):
|
||||
icon_unpaired = "ledger_unpaired.png"
|
||||
icon_paired = "ledger.png"
|
||||
icon_unpaired = ":icons/ledger_unpaired.png"
|
||||
icon_paired = ":icons/ledger.png"
|
||||
|
||||
def create_handler(self, window):
|
||||
return Ledger_Handler(window)
|
||||
|
||||
@ -11,28 +11,20 @@ Tiago Romagnani Silveira, 2017
|
||||
|
||||
import os
|
||||
import random
|
||||
import qrcode
|
||||
import traceback
|
||||
from decimal import Decimal
|
||||
from functools import partial
|
||||
import sys
|
||||
|
||||
import qrcode
|
||||
from PyQt5.QtPrintSupport import QPrinter
|
||||
from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize
|
||||
from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,
|
||||
QColor, QDesktopServices, qRgba, QPainterPath)
|
||||
from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QPushButton, QLineEdit)
|
||||
|
||||
from electrum.plugin import hook
|
||||
from electrum.i18n import _
|
||||
from electrum.util import make_dir, InvalidPassword, UserCancelled
|
||||
from electrum.gui.qt.util import (read_QIcon, EnterButton, WWLabel, icon_path,
|
||||
WindowModalDialog, Buttons, CloseButton, OkButton)
|
||||
from electrum.util import make_dir, InvalidPassword, UserCancelled, bh2u, bfh
|
||||
from electrum.gui.qt.util import *
|
||||
from electrum.gui.qt.qrtextedit import ScanQRTextEdit
|
||||
from electrum.gui.qt.main_window import StatusBarButton
|
||||
|
||||
from .revealer import RevealerPlugin
|
||||
from .revealer import RevealerPlugin, VersionedSeed
|
||||
|
||||
|
||||
class Plugin(RevealerPlugin):
|
||||
@ -62,7 +54,7 @@ class Plugin(RevealerPlugin):
|
||||
|
||||
@hook
|
||||
def create_status_bar(self, parent):
|
||||
b = StatusBarButton(read_QIcon('revealer.png'), "Revealer "+_("secret backup utility"),
|
||||
b = StatusBarButton(QIcon(':icons/revealer.png'), "Revealer "+_("secret backup utility"),
|
||||
partial(self.setup_dialog, parent))
|
||||
parent.addPermanentWidget(b)
|
||||
|
||||
@ -106,7 +98,7 @@ class Plugin(RevealerPlugin):
|
||||
vbox = QVBoxLayout()
|
||||
logo = QLabel()
|
||||
self.hbox.addWidget(logo)
|
||||
logo.setPixmap(QPixmap(icon_path('revealer.png')))
|
||||
logo.setPixmap(QPixmap(':icons/revealer.png'))
|
||||
logo.setAlignment(Qt.AlignLeft)
|
||||
self.hbox.addSpacing(16)
|
||||
vbox.addWidget(WWLabel("<b>"+_("Revealer Secret Backup Plugin")+"</b><br>"
|
||||
@ -236,7 +228,7 @@ class Plugin(RevealerPlugin):
|
||||
self.vbox = QVBoxLayout()
|
||||
logo = QLabel()
|
||||
hbox.addWidget(logo)
|
||||
logo.setPixmap(QPixmap(icon_path('revealer.png')))
|
||||
logo.setPixmap(QPixmap(':icons/revealer.png'))
|
||||
logo.setAlignment(Qt.AlignLeft)
|
||||
hbox.addSpacing(16)
|
||||
self.vbox.addWidget(WWLabel("<b>" + _("Revealer Secret Backup Plugin") + "</b><br>"
|
||||
@ -557,7 +549,7 @@ class Plugin(RevealerPlugin):
|
||||
painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height())
|
||||
|
||||
painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11,
|
||||
QImage(icon_path('electrumb.png')).scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation))
|
||||
QImage(':icons/electrumb.png').scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation))
|
||||
|
||||
painter.setPen(QPen(Qt.white, border_thick*8))
|
||||
painter.drawLine(base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2,
|
||||
@ -583,7 +575,7 @@ class Plugin(RevealerPlugin):
|
||||
painter.drawLine(dist_h, 0, dist_h, base_img.height())
|
||||
painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
|
||||
painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height())
|
||||
logo = QImage(icon_path('revealer_c.png')).scaledToWidth(1.3*(total_distance_h))
|
||||
logo = QImage(':icons/revealer_c.png').scaledToWidth(1.3*(total_distance_h))
|
||||
painter.drawImage((total_distance_h)+ (border_thick), ((total_distance_h))+ (border_thick), logo, Qt.SmoothTransformation)
|
||||
|
||||
#frame around logo
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
# copy of https://raw.githubusercontent.com/archos-safe-t/python-safet/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e/trezorlib/qt/pinmatrix.py
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import math
|
||||
|
||||
try:
|
||||
from PyQt4.QtGui import (QPushButton, QLineEdit, QSizePolicy, QRegExpValidator, QLabel,
|
||||
QApplication, QWidget, QGridLayout, QVBoxLayout, QHBoxLayout)
|
||||
from PyQt4.QtCore import QObject, SIGNAL, QRegExp, Qt, QT_VERSION_STR
|
||||
except:
|
||||
from PyQt5.QtWidgets import (QPushButton, QLineEdit, QSizePolicy, QLabel,
|
||||
QApplication, QWidget, QGridLayout, QVBoxLayout, QHBoxLayout)
|
||||
from PyQt5.QtGui import QRegExpValidator
|
||||
from PyQt5.QtCore import QRegExp, Qt, QT_VERSION_STR
|
||||
|
||||
|
||||
class PinButton(QPushButton):
|
||||
def __init__(self, password, encoded_value):
|
||||
super(PinButton, self).__init__('?')
|
||||
self.password = password
|
||||
self.encoded_value = encoded_value
|
||||
|
||||
if QT_VERSION_STR >= '5':
|
||||
self.clicked.connect(self._pressed)
|
||||
elif QT_VERSION_STR >= '4':
|
||||
QObject.connect(self, SIGNAL('clicked()'), self._pressed)
|
||||
else:
|
||||
raise RuntimeError('Unsupported Qt version')
|
||||
|
||||
def _pressed(self):
|
||||
self.password.setText(self.password.text() + str(self.encoded_value))
|
||||
self.password.setFocus()
|
||||
|
||||
|
||||
class PinMatrixWidget(QWidget):
|
||||
'''
|
||||
Displays widget with nine blank buttons and password box.
|
||||
Encodes button clicks into sequence of numbers for passing
|
||||
into PinAck messages of TREZOR.
|
||||
|
||||
show_strength=True may be useful for entering new PIN
|
||||
'''
|
||||
def __init__(self, show_strength=True, parent=None):
|
||||
super(PinMatrixWidget, self).__init__(parent)
|
||||
|
||||
self.password = QLineEdit()
|
||||
self.password.setValidator(QRegExpValidator(QRegExp('[1-9]+'), None))
|
||||
self.password.setEchoMode(QLineEdit.Password)
|
||||
|
||||
if QT_VERSION_STR >= '5':
|
||||
self.password.textChanged.connect(self._password_changed)
|
||||
elif QT_VERSION_STR >= '4':
|
||||
QObject.connect(self.password, SIGNAL('textChanged(QString)'), self._password_changed)
|
||||
else:
|
||||
raise RuntimeError('Unsupported Qt version')
|
||||
|
||||
self.strength = QLabel()
|
||||
self.strength.setMinimumWidth(75)
|
||||
self.strength.setAlignment(Qt.AlignCenter)
|
||||
self._set_strength(0)
|
||||
|
||||
grid = QGridLayout()
|
||||
grid.setSpacing(0)
|
||||
for y in range(3)[::-1]:
|
||||
for x in range(3):
|
||||
button = PinButton(self.password, x + y * 3 + 1)
|
||||
button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
button.setFocusPolicy(Qt.NoFocus)
|
||||
grid.addWidget(button, 3 - y, x)
|
||||
|
||||
hbox = QHBoxLayout()
|
||||
hbox.addWidget(self.password)
|
||||
if show_strength:
|
||||
hbox.addWidget(self.strength)
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addLayout(grid)
|
||||
vbox.addLayout(hbox)
|
||||
self.setLayout(vbox)
|
||||
|
||||
def _set_strength(self, strength):
|
||||
if strength < 3000:
|
||||
self.strength.setText('weak')
|
||||
self.strength.setStyleSheet("QLabel { color : #d00; }")
|
||||
elif strength < 60000:
|
||||
self.strength.setText('fine')
|
||||
self.strength.setStyleSheet("QLabel { color : #db0; }")
|
||||
elif strength < 360000:
|
||||
self.strength.setText('strong')
|
||||
self.strength.setStyleSheet("QLabel { color : #0a0; }")
|
||||
else:
|
||||
self.strength.setText('ULTIMATE')
|
||||
self.strength.setStyleSheet("QLabel { color : #000; font-weight: bold;}")
|
||||
|
||||
def _password_changed(self, password):
|
||||
self._set_strength(self.get_strength())
|
||||
|
||||
def get_strength(self):
|
||||
digits = len(set(str(self.password.text())))
|
||||
strength = math.factorial(9) / math.factorial(9 - digits)
|
||||
return strength
|
||||
|
||||
def get_value(self):
|
||||
return self.password.text()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
'''
|
||||
Demo application showing PinMatrix widget in action
|
||||
'''
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
matrix = PinMatrixWidget()
|
||||
|
||||
def clicked():
|
||||
print("PinMatrix value is", matrix.get_value())
|
||||
print("Possible button combinations:", matrix.get_strength())
|
||||
sys.exit()
|
||||
|
||||
ok = QPushButton('OK')
|
||||
if QT_VERSION_STR >= '5':
|
||||
ok.clicked.connect(clicked)
|
||||
elif QT_VERSION_STR >= '4':
|
||||
QObject.connect(ok, SIGNAL('clicked()'), clicked)
|
||||
else:
|
||||
raise RuntimeError('Unsupported Qt version')
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addWidget(matrix)
|
||||
vbox.addWidget(ok)
|
||||
|
||||
w = QWidget()
|
||||
w.setLayout(vbox)
|
||||
w.move(100, 100)
|
||||
w.show()
|
||||
|
||||
app.exec_()
|
||||
@ -1,15 +1,11 @@
|
||||
from functools import partial
|
||||
import threading
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, QRegExp
|
||||
from PyQt5.QtGui import QRegExpValidator
|
||||
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
|
||||
QHBoxLayout, QButtonGroup, QGroupBox,
|
||||
QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget,
|
||||
QMessageBox, QFileDialog, QSlider, QTabWidget)
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QGridLayout, QInputDialog, QPushButton
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QLabel
|
||||
|
||||
from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
|
||||
OkButton, CloseButton)
|
||||
from electrum.gui.qt.util import *
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import hook
|
||||
from electrum.util import bh2u
|
||||
@ -173,15 +169,12 @@ class QtPlugin(QtPluginBase):
|
||||
|
||||
|
||||
class Plugin(SafeTPlugin, QtPlugin):
|
||||
icon_unpaired = "safe-t_unpaired.png"
|
||||
icon_paired = "safe-t.png"
|
||||
icon_unpaired = ":icons/safe-t_unpaired.png"
|
||||
icon_paired = ":icons/safe-t.png"
|
||||
|
||||
@classmethod
|
||||
def pin_matrix_widget_class(self):
|
||||
# We use a local updated copy of pinmatrix.py until safetlib
|
||||
# releases a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e
|
||||
# from safetlib.qt.pinmatrix import PinMatrixWidget
|
||||
from .pinmatrix import PinMatrixWidget
|
||||
from safetlib.qt.pinmatrix import PinMatrixWidget
|
||||
return PinMatrixWidget
|
||||
|
||||
|
||||
|
||||
@ -159,7 +159,7 @@ class SafeTPlugin(HW_PluginBase):
|
||||
return client
|
||||
|
||||
def get_coin_name(self):
|
||||
return "Testnet" if constants.net.TESTNET else "Florincoin"
|
||||
return "Testnet" if constants.net.TESTNET else "Bitcoin"
|
||||
|
||||
def initialize_device(self, device_id, wizard, handler):
|
||||
# Initialization method
|
||||
|
||||
@ -65,7 +65,7 @@ class TrezorClientBase(PrintError):
|
||||
if issubclass(exc_type, Cancelled):
|
||||
raise UserCancelled from exc_value
|
||||
elif issubclass(exc_type, TrezorFailure):
|
||||
raise RuntimeError(str(exc_value)) from exc_value
|
||||
raise RuntimeError(exc_value.message) from exc_value
|
||||
elif issubclass(exc_type, OutdatedFirmwareError):
|
||||
raise UserFacingException(exc_value) from exc_value
|
||||
else:
|
||||
@ -147,7 +147,7 @@ class TrezorClientBase(PrintError):
|
||||
else:
|
||||
msg = _("Confirm on your {} device to set a PIN")
|
||||
with self.run_flow(msg):
|
||||
trezorlib.device.change_pin(self.client, remove)
|
||||
trezorlib.device.change_pin(remove)
|
||||
|
||||
def clear_session(self):
|
||||
'''Clear the session to force pin (and passphrase if enabled)
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
from functools import partial
|
||||
import threading
|
||||
|
||||
from PyQt5.QtCore import Qt, QEventLoop, pyqtSignal
|
||||
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
|
||||
QHBoxLayout, QButtonGroup, QGroupBox, QDialog,
|
||||
QLineEdit, QRadioButton, QCheckBox, QWidget,
|
||||
QMessageBox, QFileDialog, QSlider, QTabWidget)
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QGridLayout, QInputDialog, QPushButton
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QLabel
|
||||
|
||||
from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
|
||||
OkButton, CloseButton)
|
||||
from electrum.gui.qt.util import *
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import hook
|
||||
from electrum.util import bh2u
|
||||
@ -262,8 +259,8 @@ class QtPlugin(QtPluginBase):
|
||||
|
||||
|
||||
class Plugin(TrezorPlugin, QtPlugin):
|
||||
icon_unpaired = "trezor_unpaired.png"
|
||||
icon_paired = "trezor.png"
|
||||
icon_unpaired = ":icons/trezor_unpaired.png"
|
||||
icon_paired = ":icons/trezor.png"
|
||||
|
||||
@classmethod
|
||||
def pin_matrix_widget_class(self):
|
||||
|
||||
@ -160,7 +160,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||
return client
|
||||
|
||||
def get_coin_name(self):
|
||||
return "Testnet" if constants.net.TESTNET else "Florincoin"
|
||||
return "Testnet" if constants.net.TESTNET else "Bitcoin"
|
||||
|
||||
def initialize_device(self, device_id, wizard, handler):
|
||||
# Initialization method
|
||||
|
||||
@ -25,20 +25,17 @@
|
||||
|
||||
from functools import partial
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
from threading import Thread
|
||||
import re
|
||||
from decimal import Decimal
|
||||
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
from PyQt5.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout,
|
||||
QRadioButton, QCheckBox, QLineEdit)
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
|
||||
from electrum.gui.qt.util import (read_QIcon, WindowModalDialog, WaitingDialog, OkButton,
|
||||
CancelButton, Buttons, icon_path, WWLabel, CloseButton)
|
||||
from electrum.gui.qt.util import *
|
||||
from electrum.gui.qt.qrcodewidget import QRCodeWidget
|
||||
from electrum.gui.qt.amountedit import AmountEdit
|
||||
from electrum.gui.qt.main_window import StatusBarButton
|
||||
from electrum.gui.qt.installwizard import InstallWizard
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import hook
|
||||
from electrum.util import PrintError, is_valid_email
|
||||
@ -93,7 +90,7 @@ class Plugin(TrustedCoinPlugin):
|
||||
action = lambda: window.show_message(msg)
|
||||
else:
|
||||
action = partial(self.settings_dialog, window)
|
||||
button = StatusBarButton(read_QIcon("trustedcoin-status.png"),
|
||||
button = StatusBarButton(QIcon(":icons/trustedcoin-status.png"),
|
||||
_("TrustedCoin"), action)
|
||||
window.statusBar().addPermanentWidget(button)
|
||||
self.start_request_thread(window.wallet)
|
||||
@ -155,7 +152,7 @@ class Plugin(TrustedCoinPlugin):
|
||||
hbox = QHBoxLayout()
|
||||
|
||||
logo = QLabel()
|
||||
logo.setPixmap(QPixmap(icon_path("trustedcoin-status.png")))
|
||||
logo.setPixmap(QPixmap(":icons/trustedcoin-status.png"))
|
||||
msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '<br/>'\
|
||||
+ _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>"
|
||||
label = QLabel(msg)
|
||||
@ -198,7 +195,7 @@ class Plugin(TrustedCoinPlugin):
|
||||
vbox.addLayout(Buttons(CloseButton(d)))
|
||||
d.exec_()
|
||||
|
||||
def go_online_dialog(self, wizard: InstallWizard):
|
||||
def go_online_dialog(self, wizard):
|
||||
msg = [
|
||||
_("Your wallet file is: {}.").format(os.path.abspath(wizard.storage.path)),
|
||||
_("You need to be online in order to complete the creation of "
|
||||
@ -209,7 +206,7 @@ class Plugin(TrustedCoinPlugin):
|
||||
_('If you are online, click on "{}" to continue.').format(_('Next'))
|
||||
]
|
||||
msg = '\n\n'.join(msg)
|
||||
wizard.reset_stack()
|
||||
wizard.stack = []
|
||||
wizard.confirm_dialog(title='', message=msg, run_next = lambda x: wizard.run('accept_terms_of_use'))
|
||||
|
||||
def accept_terms_of_use(self, window):
|
||||
@ -256,7 +253,7 @@ class Plugin(TrustedCoinPlugin):
|
||||
|
||||
tos_e.tos_signal.connect(on_result)
|
||||
tos_e.error_signal.connect(on_error)
|
||||
t = threading.Thread(target=request_TOS)
|
||||
t = Thread(target=request_TOS)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
email_e.textChanged.connect(set_enabled)
|
||||
|
||||
@ -48,7 +48,6 @@ from electrum.plugin import BasePlugin, hook
|
||||
from electrum.util import NotEnoughFunds, UserFacingException
|
||||
from electrum.storage import STO_EV_USER_PW
|
||||
from electrum.network import Network
|
||||
from electrum.base_wizard import BaseWizard
|
||||
|
||||
def get_signing_xpub(xtype):
|
||||
if not constants.net.TESTNET:
|
||||
@ -492,9 +491,9 @@ class TrustedCoinPlugin(BasePlugin):
|
||||
def do_clear(self, window):
|
||||
window.wallet.is_billing = False
|
||||
|
||||
def show_disclaimer(self, wizard: BaseWizard):
|
||||
wizard.set_icon('trustedcoin-wizard.png')
|
||||
wizard.reset_stack()
|
||||
def show_disclaimer(self, wizard):
|
||||
wizard.set_icon(':icons/trustedcoin-wizard.png')
|
||||
wizard.stack = []
|
||||
wizard.confirm_dialog(title='Disclaimer', message='\n\n'.join(self.disclaimer_msg), run_next = lambda x: wizard.run('choose_seed'))
|
||||
|
||||
def choose_seed(self, wizard):
|
||||
@ -581,9 +580,9 @@ class TrustedCoinPlugin(BasePlugin):
|
||||
f = lambda x: self.restore_choice(wizard, seed, x)
|
||||
wizard.passphrase_dialog(run_next=f) if is_ext else f('')
|
||||
|
||||
def restore_choice(self, wizard: BaseWizard, seed, passphrase):
|
||||
wizard.set_icon('trustedcoin-wizard.png')
|
||||
wizard.reset_stack()
|
||||
def restore_choice(self, wizard, seed, passphrase):
|
||||
wizard.set_icon(':icons/trustedcoin-wizard.png')
|
||||
wizard.stack = []
|
||||
title = _('Restore 2FA wallet')
|
||||
msg = ' '.join([
|
||||
'You are going to restore a wallet protected with two-factor authentication.',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user