Compare commits

..

No commits in common. "master" and "upstreamMerge" have entirely different histories.

201 changed files with 955 additions and 74250 deletions

1
.gitignore vendored
View File

@ -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
View File

@ -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

View File

@ -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
View File

@ -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.

View File

@ -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

View File

@ -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::

View 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)

View File

@ -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

View File

@ -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`

View File

@ -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" "$@"

View File

@ -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"/*

View File

@ -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

View File

@ -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

View File

@ -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'))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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
}

View File

@ -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."\

@ -0,0 +1 @@
Subproject commit 201d45cd5d855c4f9de5680ab5c53621574dc6b6

@ -1 +1 @@
Subproject commit ff5ad3a4436dddcc82799f8a91793013240c3b7b
Subproject commit e5b28e4717d8c8a736d6d4e5398fa4fa67db80d0

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,8 +1,6 @@
#!/bin/bash
# Run this after a new release to update dependencies
set -e
venv_dir=~/.electrum-venv
contrib=$(dirname "$0")

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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\"}}"

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -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):

View File

@ -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')

View File

@ -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

View File

@ -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,

View File

@ -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', {})

View File

@ -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

View File

@ -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())

View File

@ -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 \

View File

@ -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):

View File

@ -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 .

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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()

View File

@ -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,

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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'))

View File

@ -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 _

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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"))

View File

@ -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]

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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)

View File

@ -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

View File

@ -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_()

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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)

View File

@ -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