mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
Use Electron for desktop app (#473)
* Add codesigning for macos app
* Add dock icon and fix fullscreen on mac
* Revert "Add dock icon and fix fullscreen on mac"
This reverts commit 7598cab644.
* Use electron for mac
* Download from GH, splash screen
* fixes
* Fix reopening after closing window
* Screen based default size
* Fix quit issue
* Fix camera access
* Add file hash verification
* Add auto generate of hash for macos release
* Only download for mac
* Preferencs and HWIBriidge mode
* Bugfixes
* Fix certificate errors
* Windows support
* Add tor support
* set version js script
* Fix windows release script
* Update docs and delete old app
* Update main.js
* Add error handling
* update unix build
* update windows binary
* Update build-osx.sh
* Update .gitlab-ci.yml
* delete accidental import
* switch to electron-builder
* kick
* kick
* kick
* kick
* remove docker info thing
* pip -> pip3
* fix pip again
* disable snap
* fix
* remove specterd-binaries (and hotwallet . typo), fix build
* test windows build
* Bugfix: move update_pending_psbt into try/except block
* Add specterd specific settings
* Show tor URL in HWIBridge mode (if --tor specified)
* Finish CI
* Build specterd only windows ci
Co-authored-by: Kim Neunert <k9ert@gmx.de>
Co-authored-by: Stepan Snigirev <snigirev.stepan@gmail.com>
This commit is contained in:
parent
6a1a72a5fb
commit
7fca3333e9
34 changed files with 4355 additions and 833 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -13,5 +13,10 @@ key.pem
|
|||
*.dmg
|
||||
pyinstaller/specterd
|
||||
pyinstaller/release
|
||||
pyinstaller/release-linux
|
||||
pyinstaller/release-win
|
||||
pyinstaller/version.txt
|
||||
pyinstaller/electron/version-data.json
|
||||
pyinstaller/electron/node_modules
|
||||
pyinstaller/electron/dist
|
||||
.DS_Store
|
||||
|
|
|
|||
|
|
@ -22,19 +22,24 @@ before_script:
|
|||
- python -V # Print out python version for debugging
|
||||
- apt update
|
||||
- apt install -y libusb-1.0-0-dev libudev-dev # usb-support in hidapi
|
||||
- pip install virtualenv
|
||||
- pip3 install virtualenv
|
||||
- virtualenv --python=python3 .env
|
||||
- source .env/bin/activate
|
||||
|
||||
test:
|
||||
stage: testing
|
||||
script:
|
||||
- pip3 install -r requirements.txt
|
||||
- pip3 install -e .
|
||||
- pip3 install -r test_requirements.txt
|
||||
# pytest --docker not working? Uncomment this for better debugging:
|
||||
# - python3 tests/conftest.py
|
||||
- pytest --docker
|
||||
- echo hello
|
||||
|
||||
#test:
|
||||
# stage: testing
|
||||
# script:
|
||||
# - pip3 install -r requirements.txt
|
||||
# - pip3 install -e .
|
||||
# - pip3 install -r test_requirements.txt
|
||||
# # pytest --docker not working? Uncomment this for better debugging:
|
||||
# # - python3 tests/conftest.py
|
||||
# - pytest --docker
|
||||
|
||||
release_pip:
|
||||
stage: releasing
|
||||
|
|
@ -55,25 +60,6 @@ release_pip:
|
|||
- sha256sum dist/cryptoadvance.specter-*.tar.gz > ./dist/SHA256SUMS.txt
|
||||
- echo $GH_BIN_UPLOAD_PW | github-binary-upload -u gitlab_upload_release_binaries cryptoadvance/specter-desktop $CI_COMMIT_TAG ./dist/SHA256SUMS.txt ./dist/cryptoadvance.specter-*.tar.gz
|
||||
|
||||
|
||||
release_binary_linux:
|
||||
image: registry.gitlab.com/cryptoadvance/specter-desktop/bionic-build:latest
|
||||
stage: releasing
|
||||
only:
|
||||
- tags
|
||||
before_script:
|
||||
- python -V
|
||||
- pip install virtualenv
|
||||
- virtualenv --python=python3 .env
|
||||
- source .env/bin/activate
|
||||
script:
|
||||
# Make sure the version-number is compatibe to the scheme
|
||||
- if ! [[ $CI_COMMIT_TAG =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]]; then exit 1; fi
|
||||
- pip3 install github-binary-upload
|
||||
- cd pyinstaller && ./build-unix.sh $CI_COMMIT_TAG
|
||||
- echo $GH_BIN_UPLOAD_PW | github-binary-upload -u gitlab_upload_release_binaries cryptoadvance/specter-desktop $CI_COMMIT_TAG ./release/specterd-${CI_COMMIT_TAG}-x86_64-linux-gnu.tar.gz ./release/specter_desktop-${CI_COMMIT_TAG}-x86_64-linux-gnu.tar.gz
|
||||
|
||||
|
||||
release_binary_windows:
|
||||
stage: releasing
|
||||
only:
|
||||
|
|
@ -82,7 +68,7 @@ release_binary_windows:
|
|||
- windows
|
||||
before_script:
|
||||
- python -V
|
||||
- pip install virtualenv
|
||||
- pip3 install virtualenv
|
||||
- virtualenv --python=python3 .env
|
||||
- .\.env\Scripts\activate
|
||||
script:
|
||||
|
|
@ -91,11 +77,26 @@ release_binary_windows:
|
|||
- whoami
|
||||
- pip3 install github-binary-upload
|
||||
- cd pyinstaller
|
||||
- .\build-win.bat $CI_COMMIT_TAG
|
||||
- (Get-Content .\specter-desktop.iss).replace('#define MyAppVersion "x.y.z"',"#define MyAppVersion `"$Env:CI_COMMIT_TAG`"") | Set-Content .\specter-desktop.iss
|
||||
- docker run --rm -i -v ${pwd}:/work k9ert/innosetup iscc /work/specter-desktop.iss
|
||||
- Get-FileHash .\release\specter_desktop_setup.exe -Algorithm SHA256 > .\release\specter_desktop_setup.exe.SHA256
|
||||
- echo $Env:GH_BIN_UPLOAD_PW | docker run -i -v ${pwd}:/work k9ert/github-binary-upload:latest -u gitlab_upload_release_binaries cryptoadvance/specter-desktop $Env:CI_COMMIT_TAG ./release/specter_desktop_setup.exe ./release/specter_desktop_setup.exe.SHA256
|
||||
- .\build-win-ci.bat $CI_COMMIT_TAG
|
||||
- echo $Env:GH_BIN_UPLOAD_PW | docker run -i -v ${pwd}:/work k9ert/github-binary-upload:latest -u gitlab_upload_release_binaries cryptoadvance/specter-desktop $Env:CI_COMMIT_TAG ./release/specterd-$CI_COMMIT_TAG-win64.zip
|
||||
|
||||
release_electron_linux_windows:
|
||||
image: registry.gitlab.com/cryptoadvance/specter-desktop/electron-builder:latest
|
||||
stage: releasing
|
||||
only:
|
||||
- tags
|
||||
before_script:
|
||||
- python -V # Print out python version for debugging
|
||||
- apt update
|
||||
- apt install -y unzip libusb-1.0-0-dev libudev-dev # usb-support in hidapi
|
||||
- pip3 install virtualenv
|
||||
# Only difference to default befor_script: (ToDo fix this)
|
||||
- python3 -m virtualenv --python=python3 .env
|
||||
- source .env/bin/activate
|
||||
script:
|
||||
- pip3 install github-binary-upload
|
||||
- cd pyinstaller && ./build-ci.sh $CI_COMMIT_TAG
|
||||
- echo $GH_BIN_UPLOAD_PW | github-binary-upload -u gitlab_upload_release_binaries cryptoadvance/specter-desktop $CI_COMMIT_TAG ./release-linux/specterd-${CI_COMMIT_TAG}-x86_64-linux-gnu.tar.gz ./release-linux/specter_desktop-${CI_COMMIT_TAG}-x86_64-linux-gnu.tar.gz ./release-win/Specter-Setup-${CI_COMMIT_TAG}.exe
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
3
docker/electron-builder/Dockerfile
Normal file
3
docker/electron-builder/Dockerfile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
FROM electronuserland/builder:wine
|
||||
|
||||
RUN apt-get update && apt-get install -y python3-pip zip unzip apt libusb-1.0-0-dev libudev-dev
|
||||
20
docker/electron-builder/README.md
Normal file
20
docker/electron-builder/README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Used for building the electron-app. In short it's the /pyinstaller/build-unix.sh script which is running in this image.
|
||||
|
||||
manually do it something like this (copied from [here](https://www.electron.build/multi-platform-build#build-electron-app-using-docker-on-a-local-machine)):
|
||||
```
|
||||
docker run --rm -ti \
|
||||
--env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS_TAG|TRAVIS|TRAVIS_REPO_|TRAVIS_BUILD_|TRAVIS_BRANCH|TRAVIS_PULL_REQUEST_|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \
|
||||
--env ELECTRON_CACHE="/root/.cache/electron" \
|
||||
--env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \
|
||||
-v ${PWD}:/project \
|
||||
-v ${PWD##*/}-node-modules:/project/node_modules \
|
||||
-v ~/.cache/electron:/root/.cache/electron \
|
||||
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
|
||||
electronuserland/builder:wine
|
||||
```
|
||||
|
||||
build the image like:
|
||||
```
|
||||
docker build -t registry.gitlab.com/cryptoadvance/specter-desktop/electron-builder:latest .
|
||||
docker push registry.gitlab.com/cryptoadvance/specter-desktop/electron-builder:latest
|
||||
```
|
||||
|
|
@ -4,7 +4,8 @@ Run `build-<your-os> <version_number>` file to build everything.
|
|||
|
||||
For example, `build-osx.sh 1.2.3` will create `SpecterDesktop-1.2.3.dmg` and `specterd-1.2.3-osx.zip` in the `release` folder.
|
||||
|
||||
On Windows `release` folder is empty, but `dist` folder contains a `specter_desktop` folder for inno setup and `specterd.exe` binary.
|
||||
If you're making a real release, you should append `"make hash"` at the end of your command calling the build script.
|
||||
This will update the file hash and version name the Specter Desktop app expects to download from GitHub.
|
||||
|
||||
# Pyinstaller build
|
||||
|
||||
|
|
@ -26,53 +27,42 @@ And for HWIBridge, run:
|
|||
pyinstaller hwibridge.spec
|
||||
```
|
||||
|
||||
# Building Specter launcher (tray app)
|
||||
## Code signing the macOS app for Apple GateKeeper
|
||||
|
||||
## Creating a Windows setup file
|
||||
*Note: for this, you'll need to have an active Apple Developer account*
|
||||
|
||||
From Powershell:
|
||||
If this is the first time you go through this process, you'll need to first set up the following:
|
||||
|
||||
1. Build `specterd` in onedir mode:
|
||||
### Apple Developer Certificate for Code-Signing
|
||||
1. Go to the Apple Developer website: https://developer.apple.com
|
||||
2. Click `Account` -> `Certificates, Identifiers & Profiles`
|
||||
3. Click the `+` icon to create a new certificate. Select `Developer ID Application` and click `Continue`
|
||||
4. You'll need now to create a certificate signing request, which you can do by following these instructions: https://help.apple.com/developer-account/#/devbfa00fef7, After that you should be able to generate and download the certificate.
|
||||
5. Download the certificate, then double-click the downloaded certificate to install it in your keychain.
|
||||
|
||||
### App Specific Password for authenticating to iTunesConnect for notarization
|
||||
1. Sign into you Apple ID account: https://appleid.apple.com
|
||||
2. Go to `Security` -> `App Specific Passwords` and click `Generate Password…`, you'll be asked to enter a label and click `Create`, then you'll receive a new password.
|
||||
3. Copy the password generated, then open the Terminal and run:
|
||||
```bash
|
||||
pyinstaller specterd_onedir.spec
|
||||
xcrun altool --store-password-in-keychain-item "AC_PASSWORD" -u "<your-apple-id>" -p "<the-generated-password>"
|
||||
```
|
||||
|
||||
You should get a `specterd` directory in the `dist` folder.
|
||||
|
||||
2. Copy `specterd` folder from `dist` folder to this directory.
|
||||
3. Run `pyinstaller specter_desktop.spec` - this should create a `specter_desktop` folder in the `dist` directory. Check that it works by running `dist\specter_desktop\specter_desktop.exe`
|
||||
4. Create an installer using [InnoSetup](https://jrsoftware.org/isdl.php#stable), select `dist\specter_desktop\specter_desktop.exe` as main executable, add `dist\specter_desktop` folder to the setup wizard as well.
|
||||
|
||||
## Creating a DMG for macOS
|
||||
|
||||
*Note*: pyinstaller doesn't fully support python3.8 at the moment, use python3.7.
|
||||
|
||||
1. Build `specterd` in onedir mode:
|
||||
After having these set up, you can use the automated script to sign by passing it 2 extra parameters:
|
||||
- Your certificate name, which you can see on the Keychain app going to the sidebar -> `My Certificates` and copying the name of the certificate you've created in step 1.
|
||||
- Your Apple ID.
|
||||
|
||||
With these two, you can run the command like so:
|
||||
```bash
|
||||
pyinstaller specterd_onedir.spec
|
||||
./build-osx.sh <version_number> "<certificate_name>" "<apple_id>" "make-hash"
|
||||
```
|
||||
*Note: "make-hash" is optional and will automatically calculate hash of specterd generated for the macOS app. Should be used only for real release.*
|
||||
|
||||
You should get a `specterd` directory in the `dist` folder.
|
||||
|
||||
2. Copy `specterd` folder from `dist` folder to this directory: `cp -r dist/specterd/ ./specterd`
|
||||
3. Now in the terminal, run `pyinstaller specter_desktop.spec` (you might need to use sudo). This should create a new Specter and Specter.app files.
|
||||
4. The `Specter.app` file is the executable macOS app we will need to package now as a `.dmg` for distribution.
|
||||
5. Make sure you have [`NPM`](https://www.npmjs.com/get-npm) installed, and run `npm install --global create-dmg`.
|
||||
6. Now run `create-dmg 'dist/Specter.app'`. This should generate a new `Specter 0.0.0.dmg`.
|
||||
7. The `.dmg` should now be ready to use! Note: You can rename the `.dmg` file to have the proper version (or just say `Specter`).
|
||||
|
||||
## Creating a binary for Linux
|
||||
|
||||
1. Build `specterd` in onedir mode:
|
||||
|
||||
This should take 10 minutes, during which you should receive an email from Apple notifying whatever the notarization was successful.
|
||||
If for some reason the notarization failed, you'll be able to get the reason by copying the `Request Identifier` (you should be able to find this in the email and in the logs).
|
||||
Then run the following command:
|
||||
```bash
|
||||
pyinstaller specterd_onedir.spec
|
||||
xcrun altool --verbose --notarization-info <request_identifier> -u "<apple_id>" -p "@keychain:AC_PASSWORD"
|
||||
```
|
||||
|
||||
You should get a `specterd` directory in the `dist` folder.
|
||||
|
||||
2. Copy `specterd` folder from `dist` folder to this directory: `cp -r dist/specterd/ ./specterd`
|
||||
3. Run `pyinstaller specter_desktop.spec`. This should create a Specter executable.
|
||||
|
||||
This will output a long message, at the end of which you should have be able to find the `LogFileURL:`.
|
||||
This URL should contain a JSON with the issues found by Apple and which you'll need to fix to be able to pass Apple's notarization.
|
||||
|
|
|
|||
43
pyinstaller/build-ci.sh
Executable file
43
pyinstaller/build-ci.sh
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# pass version number as an argument
|
||||
|
||||
echo $1 > version.txt
|
||||
pip3 install -r requirements.txt --require-hashes
|
||||
pip3 install -e ..
|
||||
rm -rf build/ dist/ release/ electron/release/ electron/dist release-linux/ release-win/
|
||||
pyinstaller specterd.spec
|
||||
cd electron
|
||||
npm ci
|
||||
node ./set-version $1 ../dist/specterd
|
||||
|
||||
# build electron app
|
||||
npm i
|
||||
npm run dist -- --linux
|
||||
cd ..
|
||||
|
||||
# copy everything to release folder
|
||||
mkdir release-linux
|
||||
cd dist
|
||||
cp -r ../../udev ./udev
|
||||
echo "Don't forget to set up udev rules! Check out udev folder for instructions." > README.md
|
||||
zip -r ../release-linux/specterd-$1-`arch`-linux-gnu.zip specterd udev README.md
|
||||
|
||||
cp ../electron/dist/Specter-* ./
|
||||
tar -czvf ../release-linux/specter_desktop-$1-`arch`-linux-gnu.tar.gz Specter-* udev README.md
|
||||
|
||||
cd ..
|
||||
rm -rf dist
|
||||
mkdir dist
|
||||
cd dist
|
||||
wget https://github.com/cryptoadvance/specter-desktop/releases/download/$1/specterd-$1-win64.zip -O ./specterd.zip
|
||||
unzip specterd.zip
|
||||
cd ../electron
|
||||
rm -rf dist/
|
||||
npm ci
|
||||
node ./set-version $1 ../dist/specterd.exe
|
||||
npm run dist -- --win
|
||||
cd ..
|
||||
|
||||
mkdir release-win
|
||||
cp electron/dist/Specter\ Setup\ *.exe release-win/Specter\ Setup\ $1.exe
|
||||
|
|
@ -1,17 +1,44 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# pass version number as an argument
|
||||
|
||||
echo $1 > version.txt
|
||||
pip install -r requirements.txt --require-hashes
|
||||
pip install -e ..
|
||||
rm -rf build/ dist/ release/
|
||||
pip3 install -r requirements.txt --require-hashes
|
||||
pip3 install -e ..
|
||||
rm -rf build/ dist/ release/ electron/release/ electron/dist
|
||||
rm *.dmg
|
||||
pyinstaller specter_desktop.spec
|
||||
pyinstaller specterd.spec
|
||||
cd electron
|
||||
npm ci
|
||||
if [[ "$4" == 'make-hash' ]]
|
||||
then
|
||||
node ./set-version $1 ../dist/specterd
|
||||
else
|
||||
node ./set-version $1
|
||||
fi
|
||||
npm i
|
||||
if [[ "$2" == '' ]]
|
||||
then
|
||||
echo "`jq '.build.mac.identity=null' package.json`" > package.json
|
||||
else
|
||||
echo "`jq '.build.mac.identity="'"$2"'"' package.json`" > package.json
|
||||
fi
|
||||
npm run dist
|
||||
|
||||
if [[ "$2" != '' ]]
|
||||
then
|
||||
echo 'Attempting to code sign...'
|
||||
ditto -c -k --keepParent "dist/mac/Specter.app" dist/Specter.zip
|
||||
xcrun altool --notarize-app -t osx -f dist/Specter.zip --primary-bundle-id "solutions.specter.desktop" -u "$3" --password "@keychain:AC_PASSWORD"
|
||||
sleep 180
|
||||
xcrun stapler staple "dist/mac/Specter.app"
|
||||
fi
|
||||
|
||||
cd ..
|
||||
|
||||
mkdir release
|
||||
|
||||
create-dmg 'dist/Specter.app'
|
||||
mv "Specter 0.0.0.dmg" release/SpecterDesktop-$1.dmg
|
||||
zip release/specterd-$1-osx.zip dist/specterd
|
||||
create-dmg 'electron/dist/mac/Specter.app' --identity="Developer ID Application: $2"
|
||||
mv "Specter ${1:1}.dmg" release/SpecterDesktop-$1.dmg
|
||||
|
||||
cd dist
|
||||
zip ../release/specterd-$1-osx.zip specterd
|
||||
cd ..
|
||||
|
|
|
|||
|
|
@ -3,20 +3,34 @@
|
|||
# pass version number as an argument
|
||||
|
||||
echo $1 > version.txt
|
||||
pip install -r requirements.txt --require-hashes
|
||||
pip install -e ..
|
||||
rm -rf build/ dist/ release/
|
||||
pyinstaller specter_desktop.spec
|
||||
pip3 install -r requirements.txt --require-hashes
|
||||
pip3 install -e ..
|
||||
rm -rf build/ dist/ release/ electron/release/ electron/dist
|
||||
pyinstaller specterd.spec
|
||||
cd electron
|
||||
npm ci
|
||||
|
||||
# calculate the hash of the binary for download
|
||||
if [[ "$2" == 'make-hash' ]]
|
||||
then
|
||||
node ./set-version $1 ../dist/specterd
|
||||
else
|
||||
node ./set-version $1
|
||||
fi
|
||||
|
||||
# build electron app
|
||||
npm i
|
||||
npm run dist
|
||||
cd ..
|
||||
|
||||
# copy everything to release folder
|
||||
mkdir release
|
||||
cd dist
|
||||
cp -r ../../udev ./udev
|
||||
echo "Don't forget to set up udev rules! Check out udev folder for instructions." > README.md
|
||||
zip -r ../release/specterd-$1-`arch`-linux-gnu.zip specterd udev README.md
|
||||
|
||||
mkdir release/specter_desktop-$1-`arch`-linux-gnu
|
||||
cp dist/Specter release/specter_desktop-$1-`arch`-linux-gnu/
|
||||
cp -r ../udev release/specter_desktop-$1-`arch`-linux-gnu/udev
|
||||
tar -czvf release/specter_desktop-$1-`arch`-linux-gnu.tar.gz release/specter_desktop-$1-`arch`-linux-gnu
|
||||
cp ../electron/dist/Specter-* ./
|
||||
tar -czvf ../release/specter_desktop-$1-`arch`-linux-gnu.tar.gz Specter-* udev README.md
|
||||
|
||||
mkdir release/specterd-$1-`arch`-linux-gnu
|
||||
cp dist/specterd release/specterd-$1-`arch`-linux-gnu/
|
||||
cp -r ../udev release/specterd-$1-`arch`-linux-gnu/udev
|
||||
tar -czvf release/specterd-$1-`arch`-linux-gnu.tar.gz release/specterd-$1-`arch`-linux-gnu
|
||||
cd ..
|
||||
|
|
|
|||
13
pyinstaller/build-win-ci.bat
Normal file
13
pyinstaller/build-win-ci.bat
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
@ECHO OFF
|
||||
echo %1% > version.txt
|
||||
pip3 install -r requirements.txt --require-hashes
|
||||
pip3 install -e ..
|
||||
rmdir /s /q .\dist\
|
||||
rmdir /s /q .\build\
|
||||
rmdir /s /q .\release\
|
||||
rmdir /s /q .\electron\dist\
|
||||
pyinstaller.exe specterd.spec
|
||||
|
||||
mkdir release
|
||||
|
||||
powershell Compress-Archive -Path dist\specterd.exe release\specterd-%1%-win64.zip
|
||||
|
|
@ -1,13 +1,27 @@
|
|||
@ECHO OFF
|
||||
echo %1 > version.txt
|
||||
pip install -r requirements.txt --require-hashes
|
||||
pip install -e ..
|
||||
echo %1% > version.txt
|
||||
pip3 install -r requirements.txt --require-hashes
|
||||
pip3 install -e ..
|
||||
rmdir /s /q .\dist\
|
||||
rmdir /s /q .\build\
|
||||
rmdir /s /q .\release\
|
||||
pyinstaller.exe specter_desktop.spec
|
||||
rmdir /s /q .\electron\dist\
|
||||
pyinstaller.exe specterd.spec
|
||||
cd electron
|
||||
call npm ci
|
||||
if "%2%"=="make-hash" (
|
||||
call node ./set-version "%1%" "../dist/specterd.exe"
|
||||
) else (
|
||||
node ./set-version "%1%"
|
||||
)
|
||||
call npm i
|
||||
call npm run dist
|
||||
cd ..
|
||||
|
||||
|
||||
mkdir release
|
||||
SET EXE_PATH="electron\dist\Specter Setup *.exe"
|
||||
SET EXE_RELEASE_PATH="release\Specter Setup %1%.exe"
|
||||
echo f | xcopy /s/y %EXE_PATH% %EXE_RELEASE_PATH%
|
||||
|
||||
echo We've built everything we could, now zip specterd and run inno-setup for specter-desktop
|
||||
powershell Compress-Archive -Path dist\specterd.exe release\specterd-%1%-win64.zip
|
||||
|
|
|
|||
10
pyinstaller/electron/build/entitlements.mac.plist
Normal file
10
pyinstaller/electron/build/entitlements.mac.plist
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
46
pyinstaller/electron/helpers.js
Normal file
46
pyinstaller/electron/helpers.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
let versionData
|
||||
try {
|
||||
versionData = require('./version-data.json')
|
||||
} catch {
|
||||
console.log('Could not find default version data configurations...')
|
||||
versionData = {
|
||||
"version": "",
|
||||
"sha256": ""
|
||||
}
|
||||
}
|
||||
const appSettingsPath = path.resolve(require('os').homedir(), '.specter/app_settings.json')
|
||||
|
||||
function getAppSettings() {
|
||||
let defaultSettings = {
|
||||
mode: 'specterd',
|
||||
specterURL: 'http://localhost:25441',
|
||||
tor: false,
|
||||
proxyURL: "socks5://127.0.0.1:9050",
|
||||
specterdVersion: versionData.version,
|
||||
specterdHash: versionData.sha256,
|
||||
specterdCLIArgs: ""
|
||||
}
|
||||
|
||||
try {
|
||||
fs.writeFileSync(appSettingsPath, JSON.stringify(defaultSettings), { flag: 'wx' });
|
||||
} catch {
|
||||
// settings file already exists
|
||||
}
|
||||
|
||||
// Make sure to add missing settings in case the format changed or new settings were added
|
||||
let appSettings = require(appSettingsPath)
|
||||
for (let key of Object.keys(defaultSettings)) {
|
||||
if (!appSettings.hasOwnProperty(key)) {
|
||||
appSettings[key] = defaultSettings[key]
|
||||
}
|
||||
}
|
||||
|
||||
return appSettings
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
appSettingsPath: appSettingsPath,
|
||||
getAppSettings: getAppSettings
|
||||
}
|
||||
BIN
pyinstaller/electron/icon.icns
Normal file
BIN
pyinstaller/electron/icon.icns
Normal file
Binary file not shown.
BIN
pyinstaller/electron/loader.gif
Normal file
BIN
pyinstaller/electron/loader.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 729 KiB |
286
pyinstaller/electron/main.js
Normal file
286
pyinstaller/electron/main.js
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
// Modules to control application life and create native browser window
|
||||
const { app, BrowserWindow, Menu, screen, shell, dialog } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const request = require('request')
|
||||
const extract = require('extract-zip')
|
||||
const crypto = require('crypto')
|
||||
const defaultMenu = require('electron-default-menu');
|
||||
const { spawn, exec } = require('child_process');
|
||||
const console = require('console')
|
||||
const getAppSettings = require('./helpers').getAppSettings
|
||||
let appSettings = getAppSettings()
|
||||
|
||||
let dimensions = { widIth: 1500, height: 1000 };
|
||||
|
||||
const download = (uri, filename, callback) => {
|
||||
request.head(uri, (err, res, body) => {
|
||||
console.log('content-type:', res.headers['content-type'])
|
||||
console.log('content-length:', res.headers['content-length'])
|
||||
request(uri).pipe(fs.createWriteStream(filename)).on('close', callback)
|
||||
})
|
||||
}
|
||||
|
||||
let specterdProcess
|
||||
let mainWindow
|
||||
let webPreferences = {
|
||||
worldSafeExecuteJavaScript: true,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch('ignore-certificate-errors');
|
||||
|
||||
let platformName = ''
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
platformName = 'osx'
|
||||
break
|
||||
case 'win32':
|
||||
platformName = 'win64'
|
||||
break
|
||||
case 'linux':
|
||||
platformName = 'x86_64-linux-gnu'
|
||||
break
|
||||
}
|
||||
|
||||
function createWindow (specterURL) {
|
||||
if (!mainWindow) {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: parseInt(dimensions.width * 0.8),
|
||||
height: parseInt(dimensions.height * 0.8),
|
||||
webPreferences
|
||||
})
|
||||
}
|
||||
|
||||
mainWindow.webContents.on("did-fail-load", function() {
|
||||
mainWindow.loadURL(`file://${__dirname}/splash.html`);
|
||||
updatingLoaderMsg(`Failed to load: ${specterURL}<br>Please make sure the URL is entered correctly in the Preferences and try again...`)
|
||||
});
|
||||
|
||||
// Create the browser window.
|
||||
if (appSettings.tor) {
|
||||
mainWindow.webContents.session.setProxy({ proxyRules: appSettings.proxyURL });
|
||||
}
|
||||
|
||||
mainWindow.loadURL(specterURL)
|
||||
// Open the DevTools.
|
||||
// mainWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
dimensions = screen.getPrimaryDisplay().size;
|
||||
|
||||
// create a new `splash`-Window
|
||||
mainWindow = new BrowserWindow({
|
||||
width: parseInt(dimensions.width * 0.8),
|
||||
height: parseInt(dimensions.height * 0.8),
|
||||
webPreferences
|
||||
})
|
||||
setMainMenu();
|
||||
|
||||
mainWindow.loadURL(`file://${__dirname}/splash.html`);
|
||||
|
||||
const specterdDirPath = path.resolve(require('os').homedir(), '.specter/specterd-binaries')
|
||||
if (!fs.existsSync(specterdDirPath)){
|
||||
fs.mkdirSync(specterdDirPath, { recursive: true });
|
||||
}
|
||||
const specterdPath = specterdDirPath + '/specterd'
|
||||
if (fs.existsSync(specterdPath + (platformName == 'win64' ? '.exe' : ''))) {
|
||||
getFileHash(specterdPath + (platformName == 'win64' ? '.exe' : ''), function (specterdHash) {
|
||||
if (appSettings.specterdHash.toLowerCase() == specterdHash || appSettings.specterdHash == "") {
|
||||
startSpecterd(specterdPath)
|
||||
} else if (appSettings.specterdVersion != "") {
|
||||
updatingLoaderMsg('Specterd version could not be validated.<br>Retrying fetching specterd...')
|
||||
downloadSpecterd(specterdPath)
|
||||
} else {
|
||||
updatingLoaderMsg('Specterd file could not be validated and no version is configured in the settings<br>Please go to Preferences and set version to fetch or add an executable manually...')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (appSettings.specterdVersion) {
|
||||
downloadSpecterd(specterdPath)
|
||||
} else {
|
||||
updatingLoaderMsg('Specterd was not found and no version is configured in the settings<br>Please go to Preferences and set version to fetch or add an executable manually...')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function downloadSpecterd(specterdPath) {
|
||||
updatingLoaderMsg('Fetching the Specter binary...')
|
||||
console.log("Using version ", appSettings.specterdVersion);
|
||||
console.log(`https://github.com/cryptoadvance/specter-desktop/releases/download/${appSettings.specterdVersion}/specterd-${appSettings.specterdVersion}-${platformName}.zip`);
|
||||
download(`https://github.com/cryptoadvance/specter-desktop/releases/download/${appSettings.specterdVersion}/specterd-${appSettings.specterdVersion}-${platformName}.zip`, specterdPath + '.zip', function() {
|
||||
updatingLoaderMsg('Unpacking files...')
|
||||
|
||||
extract(specterdPath + '.zip', { dir: specterdPath + '-dir' }).then(function () {
|
||||
let extraPath = ''
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
extraPath = 'specterd'
|
||||
break
|
||||
case 'win32':
|
||||
extraPath = 'specterd.exe'
|
||||
break
|
||||
case 'linux':
|
||||
extraPath = 'specterd'
|
||||
}
|
||||
var oldPath = specterdPath + `-dir/${extraPath}`
|
||||
var newPath = specterdPath + (platformName == 'win64' ? '.exe' : '')
|
||||
|
||||
fs.renameSync(oldPath, newPath)
|
||||
updatingLoaderMsg('Cleaning up...')
|
||||
fs.unlinkSync(specterdPath + '.zip')
|
||||
fs.rmdirSync(specterdPath + '-dir', { recursive: true });
|
||||
getFileHash(specterdPath + (platformName == 'win64' ? '.exe' : ''), function(specterdHash) {
|
||||
if (appSettings.specterdVersion.toLowerCase() === specterdHash || appSettings.specterdVersion == "") {
|
||||
startSpecterd(specterdPath)
|
||||
} else {
|
||||
updatingLoaderMsg('Specterd version could not be validated.')
|
||||
// app.quit()
|
||||
// TODO: This should never happen unless the specterd file was swapped on GitHub.
|
||||
// Think of what would be the appropriate way to handle this...
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getFileHash(filename, callback) {
|
||||
let shasum = crypto.createHash('sha256')
|
||||
// Updating shasum with file content
|
||||
, s = fs.ReadStream(filename)
|
||||
s.on('data', function(data) {
|
||||
shasum.update(data)
|
||||
})
|
||||
// making digest
|
||||
s.on('end', function() {
|
||||
var hash = shasum.digest('hex')
|
||||
callback(hash)
|
||||
})
|
||||
}
|
||||
function updatingLoaderMsg(msg) {
|
||||
let code = `
|
||||
var launchText = document.getElementById('launch-text');
|
||||
launchText.innerHTML = '${msg}';
|
||||
`;
|
||||
mainWindow.webContents.executeJavaScript(code);
|
||||
}
|
||||
|
||||
function startSpecterd(specterdPath) {
|
||||
if (platformName == 'win64') {
|
||||
specterdPath += '.exe'
|
||||
}
|
||||
let appSettings = getAppSettings()
|
||||
let hwiBridgeMode = appSettings.mode == 'hwibridge'
|
||||
updatingLoaderMsg('Launching Specter Desktop...')
|
||||
let specterdArgs = hwiBridgeMode ? ['--hwibridge'] : null
|
||||
if (appSettings.specterdCLIArgs != '') {
|
||||
if (specterdArgs == null) {
|
||||
specterdArgs = []
|
||||
}
|
||||
let specterdExtraArgs = appSettings.specterdCLIArgs.split('--')
|
||||
specterdExtraArgs = specterdExtraArgs.filter(Boolean)
|
||||
specterdExtraArgs.forEach((arg, index) => specterdExtraArgs[index] = '--' + arg.trim())
|
||||
|
||||
specterdArgs = specterdArgs.concat(specterdExtraArgs)
|
||||
}
|
||||
specterdProcess = spawn(specterdPath, specterdArgs);
|
||||
specterdProcess.stdout.on('data', (_) => {
|
||||
if (mainWindow) {
|
||||
createWindow(appSettings.specterURL)
|
||||
}
|
||||
});
|
||||
specterdProcess.stderr.on('data', function(_) {
|
||||
// https://stackoverflow.com/questions/20792427/why-is-my-node-child-process-that-i-created-via-spawn-hanging
|
||||
// needed so specterd won't get stuck
|
||||
});
|
||||
|
||||
app.on('activate', function () {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow(appSettings.specterURL)
|
||||
})
|
||||
// since these are streams, you can pipe them elsewhere
|
||||
specterdProcess.on('close', (code) => {
|
||||
console.log(`child process exited with code ${code}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', function () {
|
||||
mainWindow = null
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
|
||||
app.on('before-quit', () => {
|
||||
mainWindow = null;
|
||||
if (platformName == 'win64') {
|
||||
exec('taskkill -F -T -PID ' + specterdProcess.pid);
|
||||
process.kill(-specterdProcess.pid)
|
||||
}
|
||||
|
||||
|
||||
if (specterdProcess) {
|
||||
specterdProcess.kill('SIGINT')
|
||||
}
|
||||
})
|
||||
|
||||
function setMainMenu() {
|
||||
const menu = defaultMenu(app, shell);
|
||||
|
||||
// Add custom menu
|
||||
if (platformName == 'osx') {
|
||||
menu[0].submenu.splice(1, 0,
|
||||
{
|
||||
label: 'Preferences',
|
||||
click: openPreferences,
|
||||
accelerator: "CmdOrCtrl+,"
|
||||
}
|
||||
);
|
||||
} else {
|
||||
menu.unshift({
|
||||
label: 'Specter',
|
||||
submenu: [{
|
||||
label: 'Preferences',
|
||||
click: openPreferences,
|
||||
accelerator: "CmdOrCtrl+,"
|
||||
}]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(menu));
|
||||
}
|
||||
|
||||
function openPreferences() {
|
||||
let prefWindow = new BrowserWindow({
|
||||
width: 700,
|
||||
height: 750,
|
||||
parent: mainWindow,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true
|
||||
}
|
||||
})
|
||||
prefWindow.loadURL(`file://${__dirname}/settings.html`)
|
||||
prefWindow.show()
|
||||
}
|
||||
|
||||
function showError(error) {
|
||||
dialog.showErrorBox('Specter Desktop encounter an error', error.toString())
|
||||
updatingLoaderMsg('Specter Desktop encounter an error:<br>' + error.toString())
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', error => {
|
||||
showError(error)
|
||||
})
|
||||
|
||||
process.on("uncaughtException", error => {
|
||||
showError(error)
|
||||
})
|
||||
2403
pyinstaller/electron/package-lock.json
generated
Normal file
2403
pyinstaller/electron/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
50
pyinstaller/electron/package.json
Normal file
50
pyinstaller/electron/package.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "specter-desktop",
|
||||
"version": "v0.8.2-pre1",
|
||||
"description": "Specter Desktop Electron application",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"pack": "electron-builder --dir",
|
||||
"dist": "electron-builder --publish=never",
|
||||
"postinstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"repository": "https://github.com/cryptoadvance/specter-desktop",
|
||||
"keywords": [
|
||||
"Electron",
|
||||
"quick",
|
||||
"start",
|
||||
"tutorial",
|
||||
"demo"
|
||||
],
|
||||
"author": "Specter",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^10.1.3",
|
||||
"electron-builder": "^22.9.1"
|
||||
},
|
||||
"build": {
|
||||
"productName": "Specter",
|
||||
"appId": "solutions.specter.desktop",
|
||||
"mac": {
|
||||
"category": "public.app-category.utilities",
|
||||
"identity": "Ben Kaufman (6RPRX6WRJ5)",
|
||||
"entitlements": "./build/entitlements.mac.plist",
|
||||
"entitlementsInherit": "./build/entitlements.mac.plist",
|
||||
"hardenedRuntime": true
|
||||
},
|
||||
"win": {
|
||||
"icon": "../icons/icon.ico"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage"
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-default-menu": "^1.0.2",
|
||||
"extract-zip": "^2.0.1",
|
||||
"request": "^2.88.2"
|
||||
}
|
||||
}
|
||||
12
pyinstaller/electron/preload.js
Normal file
12
pyinstaller/electron/preload.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// All of the Node.js APIs are available in the preload process.
|
||||
// It has the same sandbox as a Chrome extension.
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const replaceText = (selector, text) => {
|
||||
const element = document.getElementById(selector)
|
||||
if (element) element.innerText = text
|
||||
}
|
||||
|
||||
for (const type of ['chrome', 'node', 'electron']) {
|
||||
replaceText(`${type}-version`, process.versions[type])
|
||||
}
|
||||
})
|
||||
6
pyinstaller/electron/renderer.js
Normal file
6
pyinstaller/electron/renderer.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// This file is required by the index.html file and will
|
||||
// be executed in the renderer process for that window.
|
||||
// No Node.js APIs are available in this process because
|
||||
// `nodeIntegration` is turned off. Use `preload.js` to
|
||||
// selectively enable features needed in the rendering
|
||||
// process.
|
||||
25
pyinstaller/electron/set-version.js
Normal file
25
pyinstaller/electron/set-version.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const fs = require('fs')
|
||||
const crypto = require('crypto')
|
||||
const version = process.argv[2]
|
||||
|
||||
async function setVersion() {
|
||||
let package = require('./package.json')
|
||||
package.version = version
|
||||
fs.writeFileSync('./package.json', JSON.stringify(package, undefined, 2))
|
||||
|
||||
if (process.argv[3]) {
|
||||
let versionData = {
|
||||
version,
|
||||
sha256: (await createHashFromFile(process.argv[3]))
|
||||
}
|
||||
|
||||
fs.writeFileSync('./version-data.json', JSON.stringify(versionData, undefined, 2))
|
||||
}
|
||||
}
|
||||
|
||||
const createHashFromFile = filePath => new Promise(resolve => {
|
||||
const hash = crypto.createHash('sha256');
|
||||
fs.createReadStream(filePath).on('data', data => hash.update(data)).on('end', () => resolve(hash.digest('hex')));
|
||||
});
|
||||
|
||||
setVersion()
|
||||
101
pyinstaller/electron/settings.html
Normal file
101
pyinstaller/electron/settings.html
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<html>
|
||||
<link rel="stylesheet" type="text/css" href="./styles.css">
|
||||
<body style="overflow: auto; height: 100%;">
|
||||
<div style="margin: auto; text-align: left;">
|
||||
<h1 style="margin-top: 20px;">Preferences</h1>
|
||||
<form action="" class="card">
|
||||
<div>
|
||||
<h2>Specter Server</h2>
|
||||
<div style="margin: 10px;">
|
||||
Run Specter:<br>
|
||||
<label><input type="radio" class="inline" name="mode" value="specterd" onclick="toggleHWIBridgeView(false)" checked>Run local Specter server</label><br>
|
||||
<label><input type="radio" class="inline" id="hwibridge-mode-active" name="mode" value="hwibridge" onclick="toggleHWIBridgeView(true);">Use a remote Specter server</label><br>
|
||||
<div id="hwibridge-mode-settings"><br>
|
||||
<input id="specter-url" style="margin-top: 10px;" type="url" placeholder="Please enter the remote Specter URL" />
|
||||
<br>
|
||||
<p style="margin-top: 20px;">
|
||||
<img style="width: 25px; margin-right: 7px; vertical-align: bottom;" src="./tor.svg"/><span style="vertical-align: bottom; margin-right: 10px;">Connect over Tor: </span><label class="switch">
|
||||
<input type="checkbox" id="tor-checkbox">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</p>
|
||||
<input id="proxy-url" type="url" placeholder="Proxy URL" />
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<h2>Specter daemon configurations</h2>
|
||||
<div style="margin: 10px;">
|
||||
<p>Specterd Version: <input id="specterd-version" style="margin-top: 10px;" type="text" placeholder="The specterd version to install (leave blank to skip installation)" /></p>
|
||||
<p>Specterd File Hash: <input id="specterd-hash" style="margin-top: 10px;" type="text" placeholder="The specterd expected file hash (leave blank to skip check)" /></p>
|
||||
<p>Specterd CLI args: <input id="specterd-cli-args" style="margin-top: 10px;" type="text" placeholder="example: --tor --port=25441" /></p>
|
||||
</div>
|
||||
</div><br>
|
||||
<div class="row flex-center">
|
||||
<button type="button" save-btn" class="btn" onclick="savePreferences()" style="margin: 5px;">Save</button>
|
||||
<button type="button" id="cancel-btn" class="btn" onclick="window.close()" style="margin: 5px;">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { dialog } = require('electron').remote
|
||||
const helpers = require('./helpers')
|
||||
const getAppSettings = helpers.getAppSettings
|
||||
|
||||
let appSettingsPath = helpers.appSettingsPath
|
||||
|
||||
function toggleHWIBridgeView(isActive) {
|
||||
document.getElementById('hwibridge-mode-active').checked = isActive
|
||||
document.getElementById('hwibridge-mode-settings').style.display = isActive ? 'block' : 'none'
|
||||
}
|
||||
|
||||
function savePreferences() {
|
||||
let hwiBridgeMode = document.getElementById('hwibridge-mode-active').checked
|
||||
let remoteSpecterURL = document.getElementById('specter-url').value
|
||||
if (hwiBridgeMode) {
|
||||
let hwiBridgeSettingsPath = path.resolve(require('os').homedir(), '.specter/hwi_bridge_config.json')
|
||||
let defaultHWIBridgeSettings = {
|
||||
whitelisted_domains: "http://127.0.0.1:25441/"
|
||||
}
|
||||
try {
|
||||
fs.writeFileSync(hwiBridgeSettingsPath, JSON.stringify(defaultHWIBridgeSettings), { flag: 'wx' });
|
||||
} catch {
|
||||
// settings file already exists
|
||||
}
|
||||
let hwiBridgeSettings = require(hwiBridgeSettingsPath)
|
||||
if (hwiBridgeSettings) {
|
||||
hwiBridgeSettings.whitelisted_domains += `\n${remoteSpecterURL}`
|
||||
}
|
||||
fs.writeFileSync(hwiBridgeSettingsPath, JSON.stringify(hwiBridgeSettings));
|
||||
}
|
||||
let appSettings = getAppSettings()
|
||||
appSettings.mode = hwiBridgeMode ? 'hwibridge' : 'specterd'
|
||||
appSettings.specterURL = hwiBridgeMode ? remoteSpecterURL : 'http://localhost:25441'
|
||||
appSettings.tor = document.getElementById('tor-checkbox').checked
|
||||
appSettings.proxyURL = document.getElementById('proxy-url').value
|
||||
appSettings.specterdVersion = document.getElementById('specterd-version').value
|
||||
appSettings.specterdHash = document.getElementById('specterd-hash').value
|
||||
appSettings.specterdCLIArgs = document.getElementById('specterd-cli-args').value
|
||||
fs.writeFileSync(appSettingsPath, JSON.stringify(appSettings));
|
||||
dialog.showMessageBox({message: 'Specter settings were saved successfully!\nPlease restart the app to activate the changes.', buttons: ['Continue'] });
|
||||
window.close()
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
let appSettings = getAppSettings()
|
||||
let hwiBridgeMode = appSettings.mode == 'hwibridge'
|
||||
if (hwiBridgeMode) {
|
||||
document.getElementById('specter-url').value = appSettings.specterURL
|
||||
}
|
||||
document.getElementById('proxy-url').value = appSettings.proxyURL
|
||||
document.getElementById('tor-checkbox').checked = appSettings.tor
|
||||
document.getElementById('specterd-version').value = appSettings.specterdVersion
|
||||
document.getElementById('specterd-hash').value = appSettings.specterdHash
|
||||
document.getElementById('specterd-cli-args').value = appSettings.specterdCLIArgs
|
||||
|
||||
toggleHWIBridgeView(hwiBridgeMode)
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
8
pyinstaller/electron/splash.html
Normal file
8
pyinstaller/electron/splash.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="stylesheet" type="text/css" href="./styles.css">
|
||||
<body style="background-color: #000">
|
||||
<div width="100%" height="100%" class="container">
|
||||
<img style="margin: auto;" src="./loader.gif"/>
|
||||
<p id="launch-text" style="margin: auto; color: #fff; font-size: 1.5em;">Launching Specter Desktop...</p>
|
||||
</div>
|
||||
</body>
|
||||
1128
pyinstaller/electron/styles.css
Normal file
1128
pyinstaller/electron/styles.css
Normal file
File diff suppressed because it is too large
Load diff
31
pyinstaller/electron/tor.svg
Normal file
31
pyinstaller/electron/tor.svg
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
|
||||
<stop stop-color="#420C5D" offset="0%"></stop>
|
||||
<stop stop-color="#951AD1" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"></path>
|
||||
<filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3">
|
||||
<feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="tor-browser-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="icon_512x512">
|
||||
<g id="Group">
|
||||
<g id="tb_icon/Stable">
|
||||
<g id="Stable">
|
||||
<circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"></circle>
|
||||
<path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,155.463547 256.525143,155.17259 L256.525143,124.146588 C329.115485,124.430449 387.881799,183.338693 387.881799,255.992903 C387.881799,328.654211 329.115485,387.562455 256.525143,387.846316 L256.525143,356.820314 Z M256.525143,201.718689 C286.266674,202.00255 310.3026,226.180407 310.3026,255.992903 C310.3026,285.812497 286.266674,309.990353 256.525143,310.274214 L256.525143,201.718689 Z M0,255.992903 C0,397.384044 114.60886,512 256,512 C397.384044,512 512,397.384044 512,255.992903 C512,114.60886 397.384044,0 256,0 C114.60886,0 0,114.60886 0,255.992903 Z" id="center" fill="url(#linearGradient-1)"></path>
|
||||
<g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) ">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
|
||||
<use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -1,3 +1 @@
|
|||
pyinstaller
|
||||
PyQt5
|
||||
PyQtWebEngine
|
||||
|
|
@ -15,43 +15,6 @@ pyinstaller-hooks-contrib==2020.9 \
|
|||
pyinstaller==4.0 \
|
||||
--hash=sha256:970beb07115761d5e4ec317c1351b712fd90ae7f23994db914c633281f99bab0 \
|
||||
# via -r requirements.in
|
||||
pyqt5-sip==12.8.1 \
|
||||
--hash=sha256:0304ca9114b9817a270f67f421355075b78ff9fc25ac58ffd72c2601109d2194 \
|
||||
--hash=sha256:0cd969be528c27bbd4755bd323dff4a79a8fdda28215364e6ce3e069cb56c2a9 \
|
||||
--hash=sha256:2f35e82fd7ec1e1f6716e9154721c7594956a4f5bd4f826d8c6a6453833cc2f0 \
|
||||
--hash=sha256:30e944db9abee9cc757aea16906d4198129558533eb7fadbe48c5da2bd18e0bd \
|
||||
--hash=sha256:34dcd29be47553d5f016ff86e89e24cbc5eebae92eb2f96fb32d2d7ba028c43c \
|
||||
--hash=sha256:5a011aeff89660622a6d5c3388d55a9d76932f3b82c95e82fc31abd8b1d2990d \
|
||||
--hash=sha256:6c1ebee60f1d2b3c70aff866b7933d8d8d7646011f7c32f9321ee88c290aa4f9 \
|
||||
--hash=sha256:7b81382ce188d63890a0e35abe0f9bb946cabc873a31873b73583b0fc84ac115 \
|
||||
--hash=sha256:832fd60a264de4134c2824d393320838f3ab648180c9c357ec58a74524d24507 \
|
||||
--hash=sha256:84ba7746762bd223bed22428e8561aa267a229c28344c2d28c5d5d3f8970cffb \
|
||||
--hash=sha256:9312ec47cac4e33c11503bc1cbeeb0bdae619620472f38e2078c5a51020a930f \
|
||||
--hash=sha256:a1b8ef013086e224b8e86c93f880f776d01b59195bdfa2a8e0b23f0480678fec \
|
||||
--hash=sha256:a29e2ac399429d3b7738f73e9081e50783e61ac5d29344e0802d0dcd6056c5a2 \
|
||||
--hash=sha256:b6d42250baec52a5f77de64e2951d001c5501c3a2df2179f625b241cbaec3369 \
|
||||
--hash=sha256:bb5a87b66fc1445915104ee97f7a20a69decb42f52803e3b0795fa17ff88226c \
|
||||
--hash=sha256:c317ab1263e6417c498b81f5c970a9b1af7acefab1f80b4cc0f2f8e661f29fc5 \
|
||||
--hash=sha256:c9800729badcb247765e4ffe2241549d02da1fa435b9db224845bc37c3e99cb0 \
|
||||
--hash=sha256:c9d6d448c29dc6606bb7974696608f81f4316c8234f7c7216396ed110075e777 \
|
||||
--hash=sha256:da9c9f1e65b9d09e73bd75befc82961b6b61b5a3b9d0a7c832168e1415f163c6 \
|
||||
--hash=sha256:ed897c58acf4a3cdca61469daa31fe6e44c33c6c06a37c3f21fab31780b3b86a \
|
||||
--hash=sha256:f168f0a7f32b81bfeffdf003c36f25d81c97dee5eb67072a5183e761fe250f13 \
|
||||
# via pyqt5, pyqtwebengine
|
||||
pyqt5==5.15.1 \
|
||||
--hash=sha256:17a6d5258796bae16e447aa3efa00258425c09cf88ef68238762628a5dde7c6f \
|
||||
--hash=sha256:4e47021c2b8e89a3bc64247dfb224144e5c8d77e3ab44f3842d120aab6b3cbd4 \
|
||||
--hash=sha256:b1ea7e82004dc7b311d1e29df2f276461016e2d180e10c73805ace4376125ed9 \
|
||||
--hash=sha256:b9e7cc3ec69f80834f3f7507478c77e4d42411d5e9e557350e61b2660d12abc2 \
|
||||
--hash=sha256:d9a76b850246d08da9863189ecb98f6c2aa9b4d97a3e85e29330a264aed0f9a1 \
|
||||
# via -r requirements.in, pyqtwebengine
|
||||
pyqtwebengine==5.15.1 \
|
||||
--hash=sha256:211e5b10667181dec74f2ef0fd56b94b3cdabaa3e0bdc0a477220b3a19ec92c1 \
|
||||
--hash=sha256:79fcbf3321457e2acc3b0010e80f987efb611fe7c9216a529f1e71f8f741fb4d \
|
||||
--hash=sha256:9d03d85a7a4ca3c1e751ff87f9b830c36be94dd87cadede6defad9639a79333f \
|
||||
--hash=sha256:d2f0785b3b9d0779cb7db4c05e15d630bc4fd7f202c7a4cad37dcc45580b3b2f \
|
||||
--hash=sha256:f0ca7915ee206ba5d703168c6ca40b0aad62c67360328fae4af5359cdbcee439 \
|
||||
# via -r requirements.in
|
||||
|
||||
# WARNING: The following packages were not pinned, but pip requires them to be
|
||||
# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
; Script generated by the Inno Setup Script Wizard.
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "Specter"
|
||||
|
||||
; Maybe we should remove the next 3 lines altogether
|
||||
; See https://stackoverflow.com/questions/13423317/inno-setup-ide-and-iscc-ispp-passing-define
|
||||
; for reasoning
|
||||
; So this can now be overridden via iscc ... /DMyAppVersion="v0.7.2"
|
||||
; as it didn't work, we're replacing it directly in the file
|
||||
#ifndef myarg
|
||||
#define MyAppVersion "x.y.z"
|
||||
#endif
|
||||
#define MyAppPublisher "CryptoAdvance GmbH"
|
||||
#define MyAppURL "https://github.com/cryptoadvance/specter-desktop/"
|
||||
#define MyAppExeName "specter_desktop.exe"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{25BB5828-3DCE-42B5-A666-00A431A2026E}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
;AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={autopf}\SpecterDesktop
|
||||
DisableProgramGroupPage=yes
|
||||
; Remove the following line to run in administrative install mode (install for all users.)
|
||||
PrivilegesRequired=lowest
|
||||
PrivilegesRequiredOverridesAllowed=dialog
|
||||
OutputDir=Z:\work\release
|
||||
OutputBaseFilename=specter_desktop_setup
|
||||
SetupIconFile=Z:\work\icons\icon.ico
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "Z:\work\dist\specter_desktop\specter_desktop.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "Z:\work\dist\specter_desktop\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
|
|
@ -1,498 +0,0 @@
|
|||
from PyQt5.QtGui import QIcon, QCursor, QDesktopServices
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication,
|
||||
QSystemTrayIcon,
|
||||
QMenu,
|
||||
QAction,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QVBoxLayout,
|
||||
QRadioButton,
|
||||
QLineEdit,
|
||||
QFileDialog,
|
||||
QLabel,
|
||||
QWidget,
|
||||
)
|
||||
from PyQt5.QtCore import (
|
||||
QRunnable,
|
||||
QThreadPool,
|
||||
QSettings,
|
||||
QUrl,
|
||||
Qt,
|
||||
pyqtSignal,
|
||||
pyqtSlot,
|
||||
QObject,
|
||||
QSize,
|
||||
QPoint,
|
||||
QEvent,
|
||||
)
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import json
|
||||
import platform
|
||||
import time
|
||||
import signal
|
||||
import requests
|
||||
from cryptoadvance.specter.config import DATA_FOLDER
|
||||
from cryptoadvance.specter.helpers import deep_update
|
||||
from cryptoadvance.specter.cli import server
|
||||
import threading
|
||||
|
||||
running = True
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
is_specterd_running = False
|
||||
specterd_thread = None
|
||||
settings = QSettings("cryptoadvance", "specter")
|
||||
wait_for_specterd_process = None
|
||||
|
||||
|
||||
def resource_path(relative_path):
|
||||
try:
|
||||
base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
base_path = os.path.abspath(".")
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
|
||||
class SpecterPreferencesDialog(QDialog):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.setWindowTitle("Specter Preferences")
|
||||
self.layout = QVBoxLayout()
|
||||
|
||||
QBtn = QDialogButtonBox.Save | QDialogButtonBox.Cancel
|
||||
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
# Mode setting
|
||||
self.mode_local = QRadioButton("Run Local Specter Server")
|
||||
self.mode_local.toggled.connect(self.toggle_mode)
|
||||
|
||||
self.mode_remote = QRadioButton("Use a Remote Specter Server")
|
||||
self.mode_remote.toggled.connect(self.toggle_mode)
|
||||
|
||||
self.specter_url = QLineEdit(
|
||||
placeholderText="Please enter the remote Specter URL"
|
||||
)
|
||||
|
||||
is_remote_mode = settings.value("remote_mode", defaultValue=False, type=bool)
|
||||
if is_remote_mode:
|
||||
self.mode_remote.setChecked(True)
|
||||
else:
|
||||
self.mode_local.setChecked(True)
|
||||
self.specter_url.hide()
|
||||
|
||||
settings.setValue("remote_mode_temp", is_remote_mode)
|
||||
remote_specter_url = (
|
||||
settings.value("specter_url", defaultValue="", type=str)
|
||||
if is_remote_mode
|
||||
else ""
|
||||
)
|
||||
settings.setValue("specter_url_temp", remote_specter_url)
|
||||
self.specter_url.setText(remote_specter_url)
|
||||
self.specter_url.textChanged.connect(
|
||||
lambda: settings.setValue("specter_url_temp", self.specter_url.text())
|
||||
)
|
||||
|
||||
self.layout.addWidget(self.mode_local)
|
||||
self.layout.addWidget(self.mode_remote)
|
||||
self.layout.addWidget(self.specter_url)
|
||||
|
||||
self.layout.addWidget(self.buttonBox)
|
||||
self.resize(500, 180)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def toggle_mode(self):
|
||||
if self.mode_local.isChecked():
|
||||
settings.setValue("remote_mode_temp", False)
|
||||
self.specter_url.hide()
|
||||
else:
|
||||
settings.setValue("remote_mode_temp", True)
|
||||
self.specter_url.show()
|
||||
|
||||
|
||||
# Cross communication between threads via signals
|
||||
# https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/
|
||||
|
||||
|
||||
class ProcessSignals(QObject):
|
||||
error = pyqtSignal()
|
||||
result = pyqtSignal()
|
||||
|
||||
|
||||
class ProcessRunnable(QRunnable):
|
||||
def __init__(self, menu):
|
||||
super().__init__()
|
||||
self.menu = menu
|
||||
self.signals = ProcessSignals()
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
menu = self.menu
|
||||
start_specterd_menu = menu.actions()[0]
|
||||
start_specterd_menu.setEnabled(False)
|
||||
start_specterd_menu.setText(
|
||||
"Starting up Specter{} daemon...".format(
|
||||
" HWIBridge"
|
||||
if settings.value("remote_mode", defaultValue=False, type=bool)
|
||||
else ""
|
||||
)
|
||||
)
|
||||
while running:
|
||||
is_remote_mode = settings.value(
|
||||
"remote_mode", defaultValue=False, type=bool
|
||||
)
|
||||
try:
|
||||
if is_remote_mode:
|
||||
requests.get(
|
||||
"http://localhost:25441/hwi/settings", allow_redirects=False
|
||||
)
|
||||
else:
|
||||
requests.get("http://localhost:25441/login", allow_redirects=False)
|
||||
start_specterd_menu.setText(
|
||||
"Specter{} daemon is running".format(
|
||||
" HWIBridge"
|
||||
if settings.value("remote_mode", defaultValue=False, type=bool)
|
||||
else ""
|
||||
)
|
||||
)
|
||||
toggle_specterd_status(menu)
|
||||
self.signals.result.emit()
|
||||
return
|
||||
except:
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
|
||||
def start(self):
|
||||
QThreadPool.globalInstance().start(self)
|
||||
|
||||
|
||||
def watch_specterd(menu, view, first_time=False):
|
||||
global specterd_thread, wait_for_specterd_process
|
||||
try:
|
||||
wait_for_specterd_process = ProcessRunnable(menu)
|
||||
wait_for_specterd_process.signals.result.connect(
|
||||
lambda: open_webview(view, first_time)
|
||||
)
|
||||
wait_for_specterd_process.signals.error.connect(lambda: print("error"))
|
||||
wait_for_specterd_process.start()
|
||||
except Exception as e:
|
||||
print("* Failed to start Specter daemon {}".format(e))
|
||||
|
||||
|
||||
def open_specter_window():
|
||||
webbrowser.open(settings.value("specter_url", type=str), new=1)
|
||||
|
||||
|
||||
def toggle_specterd_status(menu):
|
||||
global is_specterd_running
|
||||
start_specterd_menu = menu.actions()[0]
|
||||
open_webview_menu = menu.actions()[1]
|
||||
open_browser_menu = menu.actions()[2]
|
||||
|
||||
if is_specterd_running:
|
||||
start_specterd_menu.setEnabled(False)
|
||||
open_webview_menu.setEnabled(True)
|
||||
open_browser_menu.setEnabled(True)
|
||||
else:
|
||||
start_specterd_menu.setText(
|
||||
"Start Specter{} daemon".format(
|
||||
" HWIBridge"
|
||||
if settings.value("remote_mode", defaultValue=False, type=bool)
|
||||
else ""
|
||||
)
|
||||
)
|
||||
start_specterd_menu.setEnabled(True)
|
||||
open_webview_menu.setEnabled(False)
|
||||
open_browser_menu.setEnabled(False)
|
||||
is_specterd_running = not is_specterd_running
|
||||
|
||||
|
||||
def quit_specter(app):
|
||||
global running
|
||||
running = False
|
||||
app.quit()
|
||||
|
||||
|
||||
def open_settings():
|
||||
dlg = SpecterPreferencesDialog()
|
||||
if dlg.exec_():
|
||||
is_remote_mode = settings.value(
|
||||
"remote_mode_temp", defaultValue=False, type=bool
|
||||
)
|
||||
settings.setValue("remote_mode", is_remote_mode)
|
||||
|
||||
specter_url_temp = settings.value(
|
||||
"specter_url_temp", defaultValue="http://localhost:25441/", type=str
|
||||
)
|
||||
if not specter_url_temp.endswith("/"):
|
||||
specter_url_temp += "/"
|
||||
# missing schema?
|
||||
if "://" not in specter_url_temp:
|
||||
specter_url_temp = "http://" + specter_url_temp
|
||||
|
||||
settings.setValue(
|
||||
"specter_url",
|
||||
specter_url_temp if is_remote_mode else "http://localhost:25441/",
|
||||
)
|
||||
|
||||
hwibridge_settings_path = os.path.join(
|
||||
os.path.expanduser(DATA_FOLDER), "hwi_bridge_config.json"
|
||||
)
|
||||
|
||||
if is_remote_mode:
|
||||
config = {"whitelisted_domains": "http://127.0.0.1:25441/"}
|
||||
if os.path.isfile(hwibridge_settings_path):
|
||||
with open(hwibridge_settings_path, "r") as f:
|
||||
file_config = json.loads(f.read())
|
||||
deep_update(config, file_config)
|
||||
with open(hwibridge_settings_path, "w") as f:
|
||||
if "whitelisted_domains" in config:
|
||||
whitelisted_domains = ""
|
||||
if specter_url_temp not in config["whitelisted_domains"].split():
|
||||
config["whitelisted_domains"] += " " + specter_url_temp
|
||||
for url in config["whitelisted_domains"].split():
|
||||
if not url.endswith("/") and url != "*":
|
||||
# make sure the url end with a "/"
|
||||
url += "/"
|
||||
whitelisted_domains += url.strip() + "\n"
|
||||
config["whitelisted_domains"] = whitelisted_domains
|
||||
f.write(json.dumps(config, indent=4))
|
||||
# TODO: Add PORT setting
|
||||
|
||||
|
||||
def open_webview(view, first_time=False):
|
||||
url = settings.value("specter_url", type=str).strip("/")
|
||||
if first_time and settings.value("remote_mode", defaultValue=False, type=bool):
|
||||
url += "/settings/hwi"
|
||||
# missing schema?
|
||||
if "://" not in url:
|
||||
url = "http://" + url
|
||||
# if https:// or .onion - use browser
|
||||
if "https://" in url or ".onion" in url:
|
||||
webbrowser.open(settings.value("specter_url", type=str), new=1)
|
||||
return
|
||||
if not view.isVisible():
|
||||
view.load(QUrl(url))
|
||||
view.show()
|
||||
# if the window is already open just bring it to top
|
||||
# hack to make it pop-up
|
||||
else:
|
||||
view.show()
|
||||
getattr(view, "raise")()
|
||||
view.activateWindow()
|
||||
|
||||
|
||||
class WebEnginePage(QWebEnginePage):
|
||||
"""Web page"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.featurePermissionRequested.connect(self.onFeaturePermissionRequested)
|
||||
self.profile().downloadRequested.connect(self.onDownloadRequest)
|
||||
|
||||
def onFeaturePermissionRequested(self, url, feature):
|
||||
"""Enable camera and other stuff"""
|
||||
# allow everything
|
||||
self.setFeaturePermission(url, feature, QWebEnginePage.PermissionGrantedByUser)
|
||||
|
||||
def onDownloadRequest(self, item):
|
||||
"""Catch dowload files requests"""
|
||||
options = QFileDialog.Options()
|
||||
path = QFileDialog.getSaveFileName(
|
||||
None, "Where to save?", item.path(), options=options
|
||||
)[0]
|
||||
if path:
|
||||
item.setPath(path)
|
||||
item.accept()
|
||||
|
||||
def createWindow(self, _type):
|
||||
"""
|
||||
Catch clicks on _blank urls
|
||||
and open it in default browser
|
||||
"""
|
||||
page = WebEnginePage(self)
|
||||
page.urlChanged.connect(self.open_browser)
|
||||
return page
|
||||
|
||||
def open_browser(self, url):
|
||||
page = self.sender()
|
||||
QDesktopServices.openUrl(url)
|
||||
page.deleteLater()
|
||||
|
||||
|
||||
class WebView(QWidget):
|
||||
"""Window with the web browser"""
|
||||
|
||||
def __init__(self, tray, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.setStyleSheet("background-color:#263044;")
|
||||
self.tray = tray
|
||||
self.browser = QWebEngineView()
|
||||
self.browser.page = WebEnginePage()
|
||||
self.browser.setPage(self.browser.page)
|
||||
# loading progress widget
|
||||
self.progress = QWidget()
|
||||
self.progress.setFixedHeight(1)
|
||||
self.progress.setStyleSheet("background-color:#263044;")
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addWidget(self.progress, stretch=0)
|
||||
vbox.addWidget(self.browser)
|
||||
vbox.setSpacing(0)
|
||||
vbox.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(vbox)
|
||||
self.resize(settings.value("size", QSize(1200, 900)))
|
||||
self.move(settings.value("pos", QPoint(50, 50)))
|
||||
self.browser.loadStarted.connect(self.loadStartedHandler)
|
||||
self.browser.loadProgress.connect(self.loadProgressHandler)
|
||||
self.browser.loadFinished.connect(self.loadFinishedHandler)
|
||||
self.browser.urlChanged.connect(self.loadFinishedHandler)
|
||||
self.setWindowTitle("Specter Desktop")
|
||||
|
||||
def load(self, *args, **kwargs):
|
||||
self.browser.load(*args, **kwargs)
|
||||
|
||||
def loadStartedHandler(self):
|
||||
"""Set waiting cursor when the page is loading"""
|
||||
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
|
||||
def loadProgressHandler(self, progress):
|
||||
# just changes opacity over time for now
|
||||
alpha = int(time.time() * 100) % 100
|
||||
self.progress.setStyleSheet(f"background-color:rgba(75,140,26,{alpha});")
|
||||
|
||||
def loadFinishedHandler(self, *args, **kwargs):
|
||||
"""Recover cursor when done"""
|
||||
self.progress.setStyleSheet("background-color:#263044;")
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
def closeEvent(self, *args, **kwargs):
|
||||
"""
|
||||
Notify about tray app when window is closed
|
||||
for the first time.
|
||||
Also save geometry of the window.
|
||||
"""
|
||||
settings.setValue("size", self.size())
|
||||
settings.setValue("pos", self.pos())
|
||||
|
||||
if settings.value("first_time_close", defaultValue=True, type=bool):
|
||||
settings.setValue("first_time_close", False)
|
||||
self.tray.showMessage(
|
||||
"Specter is still running!",
|
||||
"Use tray icon to quit or reopen",
|
||||
self.tray.icon(),
|
||||
)
|
||||
super().closeEvent(*args, **kwargs)
|
||||
|
||||
|
||||
class Application(QApplication):
|
||||
def event(self, event):
|
||||
# not sure what 20 means
|
||||
if event and event.type() in [QEvent.Close, 20]:
|
||||
quit_specter(self)
|
||||
return False
|
||||
|
||||
|
||||
def init_desktop_app():
|
||||
app = Application([])
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
def sigint_handler(*args):
|
||||
"""Handler for the SIGINT signal."""
|
||||
quit_specter(app)
|
||||
|
||||
# fix termination ctrl+c
|
||||
signal.signal(signal.SIGINT, sigint_handler)
|
||||
|
||||
# This is the place to uncomment if we ever have issues like
|
||||
# https://github.com/cryptoadvance/specter-desktop/issues/373 again
|
||||
# So maybe let's keep it in here.
|
||||
if os.environ.get("DEP_REPORTING"):
|
||||
import psutil
|
||||
|
||||
print(
|
||||
"---------------------------DEP_REPORTING--------------------------------------------"
|
||||
)
|
||||
for item in psutil.Process().memory_maps():
|
||||
print(item.path)
|
||||
print(
|
||||
"-----------------------------DEP_REPORTING(end)-------------------------------------"
|
||||
)
|
||||
|
||||
# Create the icon
|
||||
icon = QIcon(os.path.join(resource_path("icons"), "icon.png"))
|
||||
# Create the tray
|
||||
tray = QSystemTrayIcon()
|
||||
tray.setIcon(icon)
|
||||
tray.setVisible(True)
|
||||
|
||||
# Create webview
|
||||
view = WebView(tray)
|
||||
|
||||
# Create the menu
|
||||
menu = QMenu()
|
||||
start_specterd_menu = QAction(
|
||||
"Start Specter{} daemon".format(
|
||||
" HWIBridge"
|
||||
if settings.value("remote_mode", defaultValue=False, type=bool)
|
||||
else ""
|
||||
)
|
||||
)
|
||||
start_specterd_menu.triggered.connect(lambda: watch_specterd(menu, view))
|
||||
menu.addAction(start_specterd_menu)
|
||||
|
||||
open_webview_menu = QAction("Open Specter App")
|
||||
open_webview_menu.triggered.connect(lambda: open_webview(view))
|
||||
menu.addAction(open_webview_menu)
|
||||
|
||||
open_specter_menu = QAction("Open in the browser")
|
||||
open_specter_menu.triggered.connect(open_specter_window)
|
||||
menu.addAction(open_specter_menu)
|
||||
|
||||
toggle_specterd_status(menu)
|
||||
|
||||
open_settings_menu = QAction("Preferences")
|
||||
open_settings_menu.triggered.connect(open_settings)
|
||||
menu.addAction(open_settings_menu)
|
||||
|
||||
# Add a Quit option to the menu.
|
||||
quit = QAction("Quit")
|
||||
quit.triggered.connect(lambda: quit_specter(app))
|
||||
menu.addAction(quit)
|
||||
|
||||
# Add the menu to the tray
|
||||
tray.setContextMenu(menu)
|
||||
|
||||
app.setWindowIcon(icon)
|
||||
|
||||
# Setup settings
|
||||
first_time = settings.value("first_time", defaultValue=True, type=bool)
|
||||
if first_time:
|
||||
settings.setValue("first_time", False)
|
||||
settings.setValue("remote_mode", False)
|
||||
settings.setValue("specter_url", "http://localhost:25441/")
|
||||
open_settings()
|
||||
|
||||
# start server
|
||||
global specterd_thread
|
||||
# add hwibridge to args
|
||||
if settings.value("remote_mode", defaultValue=False, type=bool):
|
||||
sys.argv.append("--hwibridge")
|
||||
# start thread
|
||||
specterd_thread = threading.Thread(target=server)
|
||||
specterd_thread.daemon = True
|
||||
specterd_thread.start()
|
||||
watch_specterd(menu, view)
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_desktop_app()
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
import platform
|
||||
import subprocess
|
||||
import mnemonic, os, sys
|
||||
|
||||
mnemonic_path = os.path.join(mnemonic.__path__[0], "wordlist")
|
||||
|
||||
block_cipher = None
|
||||
|
||||
binaries = []
|
||||
if platform.system() == 'Windows':
|
||||
binaries = [("./windll/libusb-1.0.dll", ".")]
|
||||
elif platform.system() == 'Linux':
|
||||
arch = platform.processor()
|
||||
binaries = [(f"/lib/{arch}-linux-gnu/libusb-1.0.so.0", "."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/dri/iris_dri.so","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/gio/modules/libgvfsdbus.so","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/gvfs/libgvfscommon.so","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/gtk-3.0/modules/libcanberra-gtk3-module.so","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libcanberra-gtk3.so.0","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libcanberra.so.0","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libdrm_amdgpu.so.1","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libdrm_nouveau.so.2","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libdrm_radeon.so.1","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libedit.so.2","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libelf.so.1","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libLLVM-10.so.1","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libltdl.so.7","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libsensors.so.4","."),
|
||||
(f"/usr/lib/{arch}-linux-gnu/libtdb.so.1","."),
|
||||
]
|
||||
elif platform.system() == 'Darwin':
|
||||
find_brew_libusb_proc = subprocess.Popen(['brew', '--prefix', 'libusb'], stdout=subprocess.PIPE)
|
||||
libusb_path = find_brew_libusb_proc.communicate()[0]
|
||||
binaries = [(libusb_path.rstrip().decode() + "/lib/libusb-1.0.dylib", ".")]
|
||||
|
||||
a = Analysis(['specter_desktop.py'],
|
||||
binaries=binaries,
|
||||
datas=[('../src/cryptoadvance/specter/templates', 'templates'),
|
||||
('../src/cryptoadvance/specter/static', 'static'),
|
||||
("./icons", "icons"),
|
||||
(mnemonic_path, 'mnemonic/wordlist'),
|
||||
("version.txt", "."),
|
||||
],
|
||||
hiddenimports=[
|
||||
'pkg_resources.py2_warn',
|
||||
'cryptoadvance.specter.config'
|
||||
],
|
||||
hookspath=['hooks/'],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
|
||||
if platform.system() == 'Linux':
|
||||
import hwilib
|
||||
a.datas += Tree('../udev', prefix='hwilib/udev')
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
name='Specter',
|
||||
exclude_binaries=True,
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True )
|
||||
|
||||
app = BUNDLE(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
name='Specter.app',
|
||||
icon='icons/icon.icns',
|
||||
bundle_identifier=None,
|
||||
info_plist={
|
||||
'NSPrincipleClass': 'NSApplication',
|
||||
'NSAppleScriptEnabled': False,
|
||||
'NSHighResolutionCapable': 'True',
|
||||
'NSRequiresAquaSystemAppearance': 'True',
|
||||
'LSUIElement': 'False',
|
||||
'LSBackgroundOnly': 'False'
|
||||
}
|
||||
)
|
||||
if sys.platform == 'linux':
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
name='Specter',
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
icon='icons/icon.ico'
|
||||
)
|
||||
|
||||
if sys.platform == 'win32' or sys.platform == 'win64':
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='specter_desktop',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False,
|
||||
icon='icons/icon.ico' )
|
||||
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
console=False,
|
||||
name='specter_desktop')
|
||||
|
||||
|
|
@ -180,11 +180,11 @@ def combine(wallet_alias):
|
|||
raw = app.specter.finalize(combined)
|
||||
if "psbt" not in raw:
|
||||
raw["psbt"] = combined
|
||||
psbt = wallet.update_pending_psbt(combined, txid, raw)
|
||||
except RpcError as e:
|
||||
return e.error_msg, e.status_code
|
||||
except Exception as e:
|
||||
return "Unknown error: %r" % e, 500
|
||||
psbt = wallet.update_pending_psbt(combined, txid, raw)
|
||||
devices = []
|
||||
raw["devices"] = psbt["devices_signed"]
|
||||
return json.dumps(raw)
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ def init_app(app, hwibridge=False, specter=None):
|
|||
def index():
|
||||
return redirect("/hwi/settings")
|
||||
|
||||
@app.context_processor
|
||||
def inject_tor():
|
||||
if app.config["DEBUG"]:
|
||||
return dict(tor_service_id="", tor_enabled=False)
|
||||
return dict(tor_service_id=app.tor_service_id, tor_enabled=app.tor_enabled)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -129,10 +129,10 @@ upub5En4f7k8gaG2KDHvBeEYox...rFpJRHpiZ4DE
|
|||
<table>
|
||||
{% for i in range(wordslist|length // 4) %}
|
||||
<tr>
|
||||
<td>{{ 4 * i + 1 }}. {{ wordslist[4 * i] }}.</td>
|
||||
<td>{{ 4 * i + 2 }}. {{ wordslist[4 * i + 1] }}</td>
|
||||
<td>{{ 4 * i + 3 }}. {{ wordslist[4 * i + 2] }}</td>
|
||||
<td>{{ 4 * i + 4 }}. {{ wordslist[4 * i + 3] }}</td>
|
||||
<td>{{ 4 * i + 1 }} {{ wordslist[4 * i] }}</td>
|
||||
<td>{{ 4 * i + 2 }} {{ wordslist[4 * i + 1] }}</td>
|
||||
<td>{{ 4 * i + 3 }} {{ wordslist[4 * i + 2] }}</td>
|
||||
<td>{{ 4 * i + 4 }} {{ wordslist[4 * i + 3] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,18 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="card">
|
||||
<div class="card" style="width: 600px;">
|
||||
<form action="." method="POST" role="form">
|
||||
<h1>HWI Bridge Settings</h1>
|
||||
{% if tor_service_id %}
|
||||
<img style="width: 28px; padding:3px;float: left;margin-right: 5px;" src="{{ url_for('static', filename='img/tor.svg') }}"/>
|
||||
<span style="float: left;">
|
||||
HWIBridge is also accessible ove Tor:<br>
|
||||
<span title="Copy Tor address" class="explorer-link" onclick="copyText('{{ tor_service_id }}.onion', 'Copied Tor hidden service address: {{ tor_service_id }}.onion')">
|
||||
{{ tor_service_id }}.onion
|
||||
</span>
|
||||
</span><br><br><br>
|
||||
{% endif %}
|
||||
<label>Whitelisted domains</label>
|
||||
<textarea class="form-control" placeholder="e.i. http://127.0.0.1:25441" name="whitelisted_domains">{{ whitelisted_domains }}</textarea>
|
||||
<div class="note">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.jinja" %}
|
||||
{% block main %}
|
||||
<form action="?" method="POST">
|
||||
<form action="?" method="POST" onsubmit="showPacman()">
|
||||
<h1 id="title" class="settings-title">General settings - Specter Desktop {{ current_version }}</h1>
|
||||
{% from 'settings/components/settings_menu.jinja' import settings_menu %}
|
||||
{{ settings_menu('general_settings', current_user) }}
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
</div>
|
||||
<span id="filesloaded"></span>
|
||||
<div class="row" style="margin-top: 5px;">
|
||||
<button type="submit" class="btn hidden" name="action" value="restore" id="restore" style="margin-bottom: 5px;">Load Specter backup</button>
|
||||
<button onsubmit="showPacman()" type="submit" class="btn hidden" name="action" value="restore" id="restore" style="margin-bottom: 5px;">Load Specter backup</button>
|
||||
</div><br><br>
|
||||
<h1> Miscellaneous </h1>
|
||||
Bitcoin unit to use (BTC/sats):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue