Compare commits

...

223 Commits

Author SHA1 Message Date
luigi1111
e0faddf964 Merge pull request #2975
b54127e main: disable NetworkAccessBlockingFactory.h if Qt < 5.12 (selsta)
2020-06-30 14:43:32 -05:00
selsta
b54127e997 main: disable NetworkAccessBlockingFactory.h if Qt < 5.12
Due to a potential Qt bug qrc:///lang/languages.xml gets
blocked resulting in broken translations.
2020-06-30 04:21:14 +02:00
luigi1111
ccd8eb1c3a Merge pull request #2967
1c62ede Revert 'main: update balance only when wallet is synced' (selsta)
2020-06-24 13:36:53 -05:00
luigi1111
d5b165bde2 Merge pull request #2966
34439af build: set submodule to v0.16.0.1 (selsta)
2020-06-24 13:34:33 -05:00
luigi1111
c978027933 Merge pull request #2964
e1e862b cmake: implement Linux 'release-static' build target (xiphon)
2020-06-24 13:10:35 -05:00
luigi1111
9deec4dad0 Merge pull request #2957
749460f README: update donation fund address (selsta)
2020-06-24 13:08:55 -05:00
luigi1111
e306992ce8 Merge pull request #2955
f64dcde simple mode: enable settings log tab (selsta)
2020-06-24 13:07:53 -05:00
luigi1111
19daa074ca Merge pull request #2909
66e7696 QML: disable networking (selsta)
2020-06-24 13:06:57 -05:00
selsta
1c62edeff4 Revert "main: update balance only when wallet is synced"
This reverts commit 333c9ee311.
2020-06-24 04:02:27 +02:00
selsta
34439af67e build: set submodule to v0.16.0.1 2020-06-24 03:59:45 +02:00
xiphon
e1e862bce4 cmake: implement Linux 'release-static' build target 2020-06-21 02:03:26 +00:00
luigi1111
0b2e74cdb5 Merge pull request #2963
a3fc675 cmake: fix x11 linking (xiphon)
2020-06-19 16:58:28 -05:00
luigi1111
3fee17e564 Merge pull request #2933
1e7d829 openpgp: fix gcc 5.4.0 compilation (xiphon)
2020-06-19 16:56:05 -05:00
xiphon
1e7d8293cb openpgp: fix gcc 5.4.0 compilation 2020-06-19 21:53:47 +00:00
xiphon
a3fc6754e0 cmake: fix x11 linking 2020-06-19 21:50:42 +00:00
luigi1111
8354c251c5 Merge pull request #2962
27532dc cmake: fix USE_DEVICE_TREZOR option (xiphon)
2020-06-19 16:16:57 -05:00
luigi1111
8f5053bd61 Merge pull request #2961
52c090b cmake: downgrade minimum required boost version to 1.58 (xiphon)
2020-06-19 16:16:08 -05:00
luigi1111
92b0a115f4 Merge pull request #2959
c0e0626 SettingsLayout: ask password for password relevant setting (selsta)
2020-06-19 16:15:20 -05:00
luigi1111
6a3e1aaf40 Merge pull request #2950
d18af7d LeftPanel: simpifly color binding (selsta)
2020-06-19 16:14:31 -05:00
luigi1111
c32e11d3e8 Merge pull request #2947
333c9ee main: update balance only when wallet is synced (rating89us)
2020-06-19 16:13:48 -05:00
luigi1111
9580c19da3 Merge pull request #2937
c6d5c5d workflows: update msys2 setup action v0 -> v1 (selsta)
2020-06-19 16:12:52 -05:00
xiphon
27532dc1bf cmake: fix USE_DEVICE_TREZOR option 2020-06-17 14:04:35 +00:00
selsta
c0e0626b84 SettingsLayout: ask password for password relevant setting 2020-06-17 10:01:42 +02:00
selsta
c6d5c5dc3a workflows: update msys2 setup action v0 -> v1 2020-06-15 23:07:48 +02:00
selsta
749460fb46 README: update donation fund address 2020-06-15 22:23:19 +02:00
luigi1111
c9ee76c2ee Merge pull request #2943
ef5d855 openpgp: implement public subkeys support (xiphon)
2020-06-15 15:11:56 -05:00
luigi1111
94dbf179d5 Merge pull request #2942
ff4de8e updater: use monero tools::sha256sum (xiphon)
2020-06-15 15:10:47 -05:00
luigi1111
7deecbfdf6 Merge pull request #2941
e6f3057 windeploy_helper: add libgcrypt and update 65 -> 67 (selsta)
2020-06-15 15:09:58 -05:00
luigi1111
29a742ba26 Merge pull request #2928
7f0c199 workflows: Windows CMake 'release-win64' target CI (xiphon)
2020-06-15 15:09:08 -05:00
luigi1111
47559e51be Merge pull request #2927
1580c3a DaemonManager: remove max-concurrency upper bound (selsta)
2020-06-15 15:07:59 -05:00
selsta
f64dcde600 simple mode: enable settings log tab 2020-06-15 13:17:42 +02:00
xiphon
52c090b82f cmake: downgrade minimum required boost version to 1.58 2020-06-14 12:30:40 +00:00
selsta
d18af7da72 LeftPanel: simpifly color binding 2020-06-13 03:34:16 +02:00
rating89us
333c9ee311 main: update balance only when wallet is synced 2020-06-12 00:08:04 +02:00
xiphon
ff4de8e8f7 updater: use monero tools::sha256sum 2020-06-10 15:11:15 +00:00
xiphon
ef5d855950 openpgp: implement public subkeys support 2020-06-10 12:48:55 +00:00
selsta
e6f30578c0 windeploy_helper: add libgcrypt and update 65 -> 67 2020-06-10 00:38:07 +02:00
xiphon
7f0c19950b workflows: Windows CMake 'release-win64' target CI 2020-06-04 00:30:47 +00:00
selsta
1580c3a574 DaemonManager: remove max-concurrency upper bound 2020-05-29 13:58:53 +02:00
luigi1111
c8f4355e15 Merge pull request #2926
39561f8 cmake: workflows: implement 'release' Linux build target + CI (xiphon)
2020-05-28 12:55:08 -05:00
xiphon
39561f8ead cmake: workflows: implement 'release' Linux build target + CI 2020-05-28 14:51:52 +00:00
luigi1111
b15dbbb9b0 Merge pull request #2921
55cbc56 workflows: fix windows build (selsta)
2020-05-26 15:40:24 -05:00
luigi1111
a73a4363ec Update submodule to recent master (77a008f) 2020-05-26 13:10:17 -05:00
selsta
55cbc562b6 workflows: fix windows build 2020-05-24 23:34:31 +02:00
luigi1111
e6c4c32d01 Merge pull request #2919
c946905 OpenGL: Windows - fall back to software renderer if OpenGL < 2.1 (xiphon)
2020-05-21 12:42:00 -05:00
luigi1111
e36d4a918f Merge pull request #2917
43f0854 Translations from Weblate (Monero-Weblate)
2020-05-21 12:40:51 -05:00
luigi1111
0355ca2747 Merge pull request #2916
f38e460 build: set submodule to v0.16.0.0 (selsta)
2020-05-21 12:39:34 -05:00
xiphon
c946905907 OpenGL: Windows - fall back to software renderer if OpenGL < 2.1 2020-05-20 13:45:16 +00:00
luigi1111
a8bd2ab77e Merge pull request #2913
0de1ba9 Windows installer: Update for Nitrogen Nebula 0.16.0.0 (rbrunner7)
2020-05-18 12:30:09 -05:00
luigi1111
149e373367 Merge pull request #2912
1503885 cmake: always link libgcc, libstdc++ statically (xiphon)
2020-05-18 12:29:15 -05:00
luigi1111
fa3c8b5f89 Merge pull request #2911
754a968 build: release-win64 support (xiphon)
2020-05-18 12:28:20 -05:00
luigi1111
e0f6577afd Merge pull request #2910
efc9ad4 cmake: fix Qt 5.14+ build, link Qt5QmlModels if available (xiphon)
2020-05-18 12:26:49 -05:00
Anonymous
43f0854de9 Translations from Weblate
Credits:

* Ukrainian

    * reketen
    * TheFuzzStone

* Spanish

    * Michael Russo
    * kombometralla
    * CharlesCrisco
    * Anonymous
    * porokon7
    * Juanpaab

* Korean

    * enaSo97

* Croatian

    * TasmaniaKrama

* Finnish

    * vaa red

* Italian

    * 8uDD4T
    * Anonymous

* Chinese (Traditional)

    * Lafudoci

* Esperanto

    * Gilberto F da Silva

* Romanian

    * Marius Tanislav
    * Anonymous
    * Vlad G.

* Russian

    * Agent LvM
    * solevoy-psih
    * v1docq47
    * TheFuzzStone
    * Russian Bear
    * translator133

* Japanese

    * Scott Anecito

* Portuguese (Portugal)

    * Anonymous

* French

    * Anonymous
    * Viktor

* Czech

    * Anonymous

* Chinese (Simplified)

    * jindouyunz
    * anony_xmr
    * tan
    * Muge Niu
    * Anonymous
    * TE Scott
    * razorshaman909

* Bulgarian

    * Weblate Admin

* Arabic

    * Weblate Admin

* Slovenian

    * Matija Mazi

* Polish

    * To Ja
    * Anonymous

* Portuguese (Brazil)

    * ANMNQ
    * Nelson Renan
    * netrik182
    * Anonymous
    * Boçogrolho Tabúrcio Mendez
    * Asdrubal Petronidas Calhofos

* Norwegian Bokmål

    * bitbooz
    * Anonymous

* Dutch

    * Marcus

* Swedish

    * B
    * peter hermansson

* Danish

    * KforG
    * Poul
    * Anonymous

* German

    * Wobole
    * Anonymous
    * René Brunner
    * GreenPiece
2020-05-18 09:42:46 +00:00
selsta
f38e460842 build: set submodule to v0.16.0.0 2020-05-18 01:40:53 +02:00
René Brunner
0de1ba9f51 Windows installer: Update for Nitrogen Nebula 0.16.0.0 2020-05-15 15:09:40 +02:00
selsta
66e769603c QML: disable networking 2020-05-15 06:21:08 +02:00
xiphon
15038850c2 cmake: always link libgcc, libstdc++ statically 2020-05-15 02:28:47 +00:00
xiphon
efc9ad45e4 cmake: fix Qt 5.14+ build, link Qt5QmlModels if available 2020-05-15 02:27:09 +00:00
xiphon
754a968706 build: release-win64 support 2020-05-15 02:24:14 +00:00
luigi1111
c20a0ef928 Merge pull request #2908
210248e cmake: disable '-pie' on Win with gcc (xiphon)
2020-05-14 16:06:39 -05:00
luigi1111
eab98e3a48 Merge pull request #2906
da4e0db cmake: rename monero-gui binary to monero-wallet-gui (xiphon)
2020-05-14 16:05:53 -05:00
luigi1111
2a8960bc2c Merge pull request #2905
f82948f cmake: drop '-std=c++0x' compiler flag (xiphon)
2020-05-14 16:05:04 -05:00
luigi1111
fe68f59763 Merge pull request #2903
9dd3f4f add isTrezor() function (rating89us)
2020-05-14 16:04:17 -05:00
luigi1111
80e8dd6aef Merge pull request #2901
72a3b34 Merchant: allow to exit when height < minHeight (selsta)
2020-05-14 16:03:22 -05:00
luigi1111
c82bd94bc3 Merge pull request #2899
abbe042 Filter out multiple blanks in seed input when counting words (rbrunner7)
2020-05-14 16:02:27 -05:00
luigi1111
28ca9503df Merge pull request #2897
d662e46 Merchant: display size warning when height < minHeight (rating89us)
2020-05-14 16:01:28 -05:00
luigi1111
73cc400ae8 Merge pull request #2896
bbc9ca8 Transfer: fix html paragraph tag (rating89us)
2020-05-14 16:00:43 -05:00
luigi1111
8b7438ace2 Merge pull request #2894
f7271d1 SettingsNode: add translation to Address and Port strings (rating89us)
2020-05-14 15:59:40 -05:00
xiphon
210248e6ef cmake: disable '-pie' on Win with gcc 2020-05-14 18:48:08 +00:00
xiphon
f82948f4b0 cmake: drop '-std=c++0x' compiler flag 2020-05-13 14:29:01 +00:00
xiphon
da4e0dbf0f cmake: rename monero-gui binary to monero-wallet-gui 2020-05-13 14:26:14 +00:00
rating89us
9dd3f4fecb add isTrezor() function 2020-05-13 13:22:49 +02:00
rbrunner7
abbe042c8a Filter out multiple blanks in seed input when counting words 2020-05-13 07:51:31 +02:00
selsta
72a3b346bb Merchant: allow to exit when height < minHeight 2020-05-12 20:43:46 +02:00
rating89us
d662e46146 Merchant: display size warning when height < minHeight 2020-05-11 00:42:45 +02:00
rating89us
bbc9ca8f08 Transfer: fix html paragraph tag 2020-05-10 23:48:48 +02:00
rating89us
f7271d1c7b SettingsNode: add translation to Address and Port strings 2020-05-09 19:31:36 +02:00
luigi1111
afe1ae9b9c Merge pull request #2892
b616a14 LanguageSidebar: fix white theme (selsta)
2020-05-08 12:27:37 -05:00
luigi1111
7f5b8ea0ad Merge pull request #2890
2f8c0ca Main: clear spendable funds message when closing wallet (rating89us)
2020-05-08 12:26:24 -05:00
luigi1111
aa5000f556 Merge pull request #2876
4354a76 StandardDropdown: drop Qt 5.8 workaround (xiphon)
2020-05-08 12:25:15 -05:00
luigi1111
8b6978b2a5 Merge pull request #2829
86d21a3 trezor: support new passphrase entry mechanism (ph4r05)
2020-05-08 12:21:59 -05:00
Dusan Klinec
86d21a34ba trezor: support new passphrase entry mechanism
- passphrase can be prompted also when wallet is running (thus added to the wallet listener)
- device/host decision is now made on the host
2020-05-08 18:40:17 +02:00
xiphon
4354a76df9 StandardDropdown: drop Qt 5.8 workaround 2020-05-07 21:55:44 +00:00
selsta
b616a14f88 LanguageSidebar: fix white theme 2020-05-07 21:04:03 +02:00
luigi1111
7536e922e9 Merge pull request #2885
8f70fb4 Transfer: improve warning message while connecting (selsta)
2020-05-07 10:49:44 -05:00
luigi1111
5bf0dd9684 Merge pull request #2884
c439d68 main: fix local node status check on closing uninitialized wallet (xiphon)
2020-05-07 10:31:35 -05:00
luigi1111
cdf4ce2d6f Merge pull request #2891
37a8bcb SimpleMode: use connection timer 'running' prop, fix daemon restart (xiphon)
2020-05-07 10:30:13 -05:00
xiphon
37a8bcb525 SimpleMode: use connection timer 'running' prop, fix daemon restart 2020-05-07 13:40:43 +00:00
rating89us
2f8c0ca499 Main: clear spendable funds message when closing wallet 2020-05-06 19:18:55 +02:00
luigi1111
38612c1285 Merge pull request #2881
487e706 Improved daemon sync progress bar (rbrunner7)
2020-05-06 00:41:41 -05:00
luigi1111
1d3a201077 Merge pull request #2879
ca79525 Slider component (xiphon)
503c1af SettingsLayout: implement autosave, enabled by default (10 minutes) (xiphon)
2020-05-06 00:40:36 -05:00
luigi1111
a91a4f51ab Merge pull request #2871
7db5de0 WizardAskPassword: drop custom password fields, use LineEdit (xiphon)
2020-05-06 00:39:26 -05:00
luigi1111
52fbbae484 Merge pull request #2883
f26f146 PasswordDialog: fix focus on open (selsta)
2020-05-06 00:38:20 -05:00
selsta
8f70fb4f79 Transfer: improve warning message while connecting 2020-05-05 13:12:10 +02:00
xiphon
c439d6814b main: fix local node status check on closing uninitialized wallet 2020-05-04 14:48:56 +00:00
selsta
f26f1469ca PasswordDialog: fix focus on open 2020-05-03 15:51:46 +02:00
rbrunner7
487e706f06 Improved daemon sync progress bar 2020-05-02 16:34:54 +02:00
xiphon
503c1af5f8 SettingsLayout: implement autosave, enabled by default (10 minutes) 2020-05-02 01:54:11 +00:00
xiphon
ca79525fdb Slider component 2020-05-02 01:54:05 +00:00
xiphon
7db5de082d WizardAskPassword: drop custom password fields, use LineEdit 2020-05-02 01:32:35 +00:00
luigi1111
ce6cc47afe Merge pull request #2845
7f9b28c WizardNav: use PageIndicator in wizardProgress (rating89us)
2020-05-01 15:51:48 -05:00
luigi1111
a10a94f51c Merge pull request #2866
5265e52 ProcessingSplash: new design (selsta)
2020-05-01 15:11:43 -05:00
luigi1111
1833c16e39 Merge pull request #2785
be954cc StandardDropdown: fix margins, text overlapping (xiphon)
2020-05-01 15:10:49 -05:00
luigi1111
d7207bfde3 Merge pull request #2874
387e643 Transfer: fix 'call method estimateTransactionFeeAsync of undefined' (xiphon)
2020-05-01 15:09:49 -05:00
selsta
5265e52b8b ProcessingSplash: new design 2020-05-01 22:09:27 +02:00
luigi1111
b6fdb709ba Merge pull request #2873
4a7ccd8 SettingsLayout: add check for updates checkbox (selsta)
2020-05-01 15:07:50 -05:00
luigi1111
9aef3bab33 Merge pull request #2870
2195c67 LineEdit: password mode, linking. PasswordDialog: use LineEdit (xiphon)
2020-05-01 15:06:46 -05:00
luigi1111
4b55197e18 Merge pull request #2848
c7f9ac9 LanguageSidebar: reenable (selsta)
3835387 wizard: add flags to language sidebar (mmbyday, selsta)
2020-05-01 15:04:55 -05:00
luigi1111
11617d2f76 Merge pull request #2846
2102e4b Merchant: fix confirmations (selsta)
b245d0a MerchantTrackingList: point out unconfirmed amount (selsta)
07ecca5 Merchant: fix payment URL (selsta)
3af99e9 Merchant: remove unused variables (selsta)
2020-05-01 15:03:04 -05:00
luigi1111
765e93cfd0 Merge pull request #2840
d31e661 Transfer: redesign advanced options (rating89us)
2020-05-01 14:58:56 -05:00
rating89us
d31e661cd1 Transfer: redesign advanced options 2020-05-01 11:48:53 +02:00
rating89us
7f9b28c05f WizardNav: use PageIndicator in wizardProgress 2020-05-01 11:40:47 +02:00
selsta
4a7ccd8d82 SettingsLayout: add check for updates checkbox 2020-05-01 03:46:58 +02:00
xiphon
be954cc2c4 StandardDropdown: fix margins, text overlapping 2020-05-01 01:30:40 +00:00
xiphon
387e643ae9 Transfer: fix 'call method estimateTransactionFeeAsync of undefined' 2020-04-29 22:33:08 +00:00
luigi1111
6f71d47806 Merge pull request #2868
5c13624 DaemonManager: drop unused 'initialized', 'm_has_daemon' members (xiphon)
2020-04-28 21:41:19 -05:00
luigi1111
135c970ad9 Merge pull request #2865
cfdba59 Wallet: implement async wallet storing (xiphon)
2020-04-28 21:40:17 -05:00
luigi1111
fd5d1f584e Merge pull request #2861
4b0dcb9 StandardDialog: reset button text on close (selsta)
2020-04-28 21:38:59 -05:00
xiphon
2195c67f58 LineEdit: password mode, linking. PasswordDialog: use LineEdit 2020-04-28 20:44:53 +00:00
xiphon
5c13624596 DaemonManager: drop unused 'initialized', 'm_has_daemon' members 2020-04-28 20:42:23 +00:00
xiphon
cfdba59584 Wallet: implement async wallet storing 2020-04-28 20:36:22 +00:00
luigi1111
4141832a4d Merge pull request #2869
3d24300 SettingsNode: forbid 'data-dir', 'bootstrap-daemon-address' flags (xiphon)
2020-04-28 15:24:11 -05:00
luigi1111
5f183da6e3 Merge pull request #2867
9748974 DaemonManager: fix memory leak (xiphon)
2020-04-28 15:23:03 -05:00
luigi1111
30c54b1c6e Merge pull request #2864
ce4cb65 WizardAskPassword: hide strength if getPasswordStrength is missing (xiphon)
2020-04-28 15:22:02 -05:00
luigi1111
c1da3d1c97 Merge pull request #2863
120b528 WizardDaemonSettings: bootstrap node address 'auto' special case (xiphon)
2020-04-28 15:21:19 -05:00
luigi1111
cbd03229dd Merge pull request #2862
33c1a6f repo: cleanup unused images (selsta)
2020-04-28 15:20:22 -05:00
luigi1111
2fc1b287dd Merge pull request #2860
fdb7f80 SettingsInfo: fix number string formatting (selsta)
2020-04-28 15:19:36 -05:00
luigi1111
7c8c6a116f Merge pull request #2856
a810bf3 CheckBox: add enabled property (selsta)
2020-04-28 15:18:51 -05:00
luigi1111
ef93139a80 Merge pull request #2855
b022735 main: add --disable-check-updates flag (selsta)
2020-04-28 15:17:57 -05:00
luigi1111
1b7844ec34 Merge pull request #2854
a99eef6 always use native directory separators in paths (xiphon)
2020-04-28 15:16:57 -05:00
luigi1111
8f63e8870f Merge pull request #2851
7ebdb88 README: list libgcrypt linux (selsta)
2020-04-28 15:16:10 -05:00
luigi1111
286c75aa5b Merge pull request #2850
9b98e0a checkUpates: installer support (Win), use GUI buildTag and version (xiphon)
2020-04-28 15:14:40 -05:00
luigi1111
0a4d65dd99 Merge pull request #2849
02eec35 src: fix qt 5.15 warnings (selsta)
2020-04-28 15:13:45 -05:00
luigi1111
83ccadb6a8 Merge pull request #2847
238b1b7 pages: remove legacy code (selsta)
2020-04-28 15:12:57 -05:00
luigi1111
eae7eff9db Merge pull request #2844
81d5dd1 Transfer: add offline transaction signing info in send button warning (rating89us)
2020-04-28 15:11:58 -05:00
selsta
2102e4be0d Merchant: fix confirmations 2020-04-28 19:30:22 +02:00
selsta
02eec351b9 src: fix qt 5.15 warnings 2020-04-28 19:00:53 +02:00
xiphon
ce4cb6512d WizardAskPassword: hide strength if getPasswordStrength is missing 2020-04-28 15:05:43 +00:00
xiphon
3d24300963 SettingsNode: forbid 'data-dir', 'bootstrap-daemon-address' flags 2020-04-27 13:18:23 +00:00
xiphon
9748974ce0 DaemonManager: fix memory leak 2020-04-27 12:46:15 +00:00
xiphon
120b5285fb WizardDaemonSettings: bootstrap node address "auto" special case 2020-04-26 01:14:29 +00:00
selsta
b022735506 main: add --disable-check-updates flag 2020-04-26 02:57:35 +02:00
selsta
33c1a6f4fc repo: cleanup unused images 2020-04-26 02:18:35 +02:00
selsta
4b0dcb95bf StandardDialog: reset button text on close 2020-04-25 21:10:25 +02:00
selsta
fdb7f806fa SettingsInfo: fix number string formatting 2020-04-25 20:55:32 +02:00
selsta
a810bf3eb7 CheckBox: add enabled property 2020-04-24 04:35:21 +02:00
xiphon
a99eef68f5 always use native directory separators in paths 2020-04-24 00:43:40 +00:00
selsta
7ebdb884a1 README: list libgcrypt linux 2020-04-23 23:31:43 +02:00
selsta
c7f9ac926b LanguageSidebar: reenable 2020-04-23 20:28:09 +02:00
xiphon
9b98e0a2f5 checkUpates: installer support (Win), use GUI buildTag and version 2020-04-23 16:08:10 +00:00
mmbyday
3835387eea wizard: add flags to language sidebar 2020-04-23 00:04:07 +02:00
selsta
238b1b777f pages: remove legacy code 2020-04-22 23:18:08 +02:00
selsta
3af99e91e4 Merchant: remove unused variables 2020-04-22 22:10:40 +02:00
selsta
07ecca5af4 Merchant: fix payment URL
This would previously get loaded on the Receive page.
2020-04-22 22:10:40 +02:00
selsta
b245d0af7a MerchantTrackingList: point out unconfirmed amount 2020-04-22 22:10:40 +02:00
luigi1111
585fb2810d Merge pull request #2843
5076757 SettingsLog: don't assign color to font family (selsta)
2020-04-22 13:37:34 -05:00
luigi1111
0784532001 Merge pull request #2842
1d5b940 Merchant: fix close button (selsta)
2020-04-22 13:36:37 -05:00
luigi1111
dcbdae0954 Merge pull request #2841
3563d44 Transfer: move address field to the top (rating89us)
2020-04-22 13:35:49 -05:00
luigi1111
2bef74fe8a Merge pull request #2838
ffceda9 SettingsLayout: add checkbox 'ask for password before sending a transaction' (rating89us)
2020-04-22 13:34:47 -05:00
rating89us
81d5dd1cae Transfer: add offline transaction signing info in send button warning 2020-04-21 20:57:55 +02:00
selsta
50767570f0 SettingsLog: don't assign color to font family 2020-04-21 20:02:23 +02:00
selsta
1d5b940349 Merchant: fix close button 2020-04-21 19:42:05 +02:00
rating89us
3563d44d99 Transfer: move address field to the top 2020-04-18 19:25:45 +02:00
rating89us
ffceda9159 SettingsLayout: add checkbox 'ask for password before sending a transaction' 2020-04-18 17:42:27 +02:00
luigi1111
a75a0fb8c5 Merge pull request #2839
fee81ba workflows: add trezor support ubuntu (selsta)
2020-04-17 22:10:21 -05:00
luigi1111
5fa64c34ec Merge pull request #2831
de6a9b6 Transfer: address book inline button, 2nd inline button, scan QR code button fixes (rating89us)
2020-04-17 22:09:18 -05:00
luigi1111
376421667a Merge pull request #2836
3f13a5c QRCodeScanner: don't load webcam on startup (mmbyday/selsta)
2020-04-17 20:53:46 -05:00
luigi1111
8a73fd241e Merge pull request #2835
a078705 QrScanThread: use sizeInBytes to fix warning (selsta)
2020-04-17 20:52:33 -05:00
luigi1111
9760886eff Merge pull request #2834
fb7470a build: find zbar header on macOS (selsta)
2020-04-17 20:49:18 -05:00
luigi1111
34089599cd Merge pull request #2833
e2c6ae6 AddressBook: remove leftover code (selsta)
2020-04-17 20:46:49 -05:00
luigi1111
4a7a98034e Merge pull request #2832
ea25b71 updater: fetch signed hashes from getmonero.org, verify downloads (xiphon)
2020-04-17 20:45:24 -05:00
luigi1111
79f78f48e2 Merge pull request #2830
5ebe3f5 Settings: move Merchant menu entry (tobtoht/selsta)
2020-04-17 20:43:40 -05:00
selsta
fee81ba210 workflows: add trezor support ubuntu 2020-04-16 22:53:33 +02:00
selsta
a078705ec6 QrScanThread: use sizeInBytes to fix warning 2020-04-16 19:11:29 +02:00
rating89us
de6a9b6779 Transfer: address book inline button, 2nd inline button, scan QR code button fixes
spacing fix
2020-04-16 13:25:28 +02:00
mmbyday
3f13a5c9a4 QRCodeScanner: don't load webcam on startup 2020-04-16 03:29:39 +02:00
selsta
fb7470a2a6 build: find zbar header on macOS 2020-04-16 03:13:33 +02:00
selsta
e2c6ae6472 AddressBook: remove leftover code 2020-04-16 02:08:32 +02:00
xiphon
ea25b71ca6 updater: fetch signed hashes from getmonero.org, verify downloads 2020-04-14 21:37:04 +00:00
thotbot
5ebe3f5092 Settings: move Merchant menu entry 2020-04-13 23:15:14 +02:00
luigi1111
8e4124f06a Merge pull request #2828
e1258e0 TitleBar: fix close icon fallback (selsta)
2020-04-13 15:49:17 -05:00
luigi1111
e47fd5f760 Merge pull request #2827
925cced README: add weblate's widget for translations status (erciccione)
2020-04-13 15:48:14 -05:00
luigi1111
8767c71107 Merge pull request #2825
94083e7 cmake: use monero core compilation and linking flags (xiphon)
2020-04-13 15:46:38 -05:00
luigi1111
2156a6533b Merge pull request #2824
5f27a45 '--verify-update', shasum support, OpenPGP signatures verification (xiphon)
2020-04-13 15:43:24 -05:00
luigi1111
7eac690d44 Merge pull request #2822
a49d579 build: fix MSYS2 folder detection (xiphon)
2020-04-13 15:40:42 -05:00
luigi1111
31adc6cfd6 Merge pull request #2821
6d7db13 Settings: rework wallet page (thotbot/xiphon)
2020-04-13 15:39:54 -05:00
luigi1111
d582fd338d Merge pull request #2819
6ed7fce UpdateDialog: implement update download functionality (xiphon)
2020-04-13 15:38:06 -05:00
luigi1111
eed51e3ffa Merge pull request #2772
48aab5c WalletManager: wallet recovery - seed offset passphrase support (xiphon)
2020-04-13 15:36:44 -05:00
luigi1111
6a889bdaa1 Merge pull request #2739
b5fafb5 Transfer: display estimated transaction fee, requires #6302 (xiphon)
2020-04-13 15:35:44 -05:00
xiphon
5f27a45910 '--verify-update', shasum support, OpenPGP signatures verification 2020-04-12 21:34:22 +00:00
selsta
e1258e0ada TitleBar: fix close icon fallback 2020-04-10 13:55:25 +02:00
erciccione
925cced7d7 README: add weblate's widget for translations status 2020-04-09 12:25:54 +02:00
xiphon
94083e746f cmake: use monero core compilation and linking flags 2020-04-08 02:31:16 +00:00
xiphon
a49d579bd3 build: fix MSYS2 folder detection 2020-04-06 17:22:34 +00:00
xiphon
6ed7fcec67 UpdateDialog: implement update download functionality 2020-04-06 16:59:33 +00:00
thotbot
6d7db135e7 Settings: rework wallet page 2020-04-05 13:07:11 +00:00
luigi1111
042400b83f Set submodule back to master 2020-04-04 14:30:32 -05:00
luigi1111
df54439972 Merge pull request #2818
a9fea24 workflows: retry on apt failure / increase timeout (selsta)
2020-04-04 13:22:37 -05:00
luigi1111
86252506f0 Merge pull request #2817
58987b2 network: provide common HTTP GET functionality with js callbacks (xiphon)
2020-04-04 13:21:23 -05:00
selsta
a9fea2462b workflows: retry on apt failure / increase timeout
Co-authored-by: xiphon <xiphon@protonmail.com>
2020-04-01 00:50:24 +02:00
xiphon
58987b2ec6 network: provide common HTTP GET functionality with js callbacks 2020-03-31 21:37:19 +00:00
luigi1111
aecd218d15 Merge pull request #2807
d07da76 MenuButton: add hover effect (xiphon)
2020-03-27 10:55:07 -05:00
luigi1111
856843b52a Merge pull request #2803
5872bc8 build: disable _FORTIFY_SOURCE when building without optimization (xiphon)
2020-03-27 10:53:46 -05:00
luigi1111
fc740a89ab Merge pull request #2801
dede10e Add QT Creator cache file to gitignore (tobtoht)
2020-03-27 10:19:31 -05:00
luigi1111
0643607ec3 Merge pull request #2792
d21c22b SettingsNode: blockchain location 'Reset' button (xiphon)
2020-03-27 10:16:58 -05:00
xiphon
5872bc8a2b build: disable _FORTIFY_SOURCE when building without optimization 2020-03-23 06:34:18 +00:00
xiphon
d07da76383 MenuButton: add hover effect 2020-03-14 01:19:08 +00:00
luigi1111
c092de97f2 Merge pull request #2805
c9e461d build: set submodule to v0.15.0.5 (selsta)
2020-03-10 18:58:00 -04:00
luigi1111
c84a8fb07d Merge pull request #2799
55b548f qt: drop Qt Network and Qt OpenSSL deps, use epee library instead (xiphon)
2020-03-10 18:56:04 -04:00
luigi1111
63b4566475 Merge pull request #2796
c7df74d README: add protobuf to linux instructions (selsta)
2020-03-10 18:50:54 -04:00
luigi1111
3699dc9f2e Merge pull request #2795
94a27b9 README: cleanup macOS instructions (selsta)
2020-03-10 18:49:22 -04:00
luigi1111
eee48ce74c Merge pull request #2797
b739cdd cmake: -static, -static-libgcc, -static-libstdc++ linker flags (xiphon)
2020-03-10 18:48:01 -04:00
luigi1111
9072f89a4e Merge pull request #2794
bf9b04b installers: fix warning (selsta)
2020-03-10 18:46:45 -04:00
selsta
c9e461dcb6 build: set submodule to v0.15.0.5 2020-03-09 20:50:16 +01:00
xiphon
55b548f31c qt: drop Qt Network and Qt OpenSSL deps, use epee library instead 2020-03-09 11:05:49 +00:00
thotbot
dede10ea1a Add QT Creator cache file to gitignore 2020-03-08 15:58:29 +00:00
xiphon
b739cdd52a cmake: -static, -static-libgcc, -static-libstdc++ linker flags 2020-03-06 11:54:16 +00:00
selsta
94a27b964a README: cleanup macOS instructions 2020-03-06 02:06:08 +01:00
selsta
c7df74dab7 README: add protobuf to linux instructions 2020-03-06 01:52:00 +01:00
selsta
bf9b04b126 installers: fix warning 2020-03-04 20:02:08 +01:00
xiphon
d21c22b444 SettingsNode: blockchain location 'Reset' button 2020-03-02 10:35:50 +00:00
xiphon
b5fafb55c9 Transfer: display estimated transaction fee, requires #6302 2020-02-16 11:55:36 +00:00
xiphon
48aab5c6e5 WalletManager: wallet recovery - seed offset passphrase support 2020-02-12 13:49:26 +00:00
163 changed files with 33531 additions and 23215 deletions

View File

@@ -6,47 +6,78 @@ jobs:
build-macos:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: update brew and install dependencies
run: brew update && brew install boost hidapi zmq libpgm miniupnpc ldns expat libunwind-headers protobuf qt5
run: brew update && brew install boost hidapi zmq libpgm miniupnpc ldns expat libunwind-headers protobuf qt5 libgcrypt
- name: build
run: export PATH=$PATH:/usr/local/opt/qt/bin && ./build.sh
- name: test qml
run: build/release/bin/monero-wallet-gui.app/Contents/MacOS/monero-wallet-gui --test-qml
build-ubuntu:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain:
- name: "qmake"
cmd: "./build.sh"
- name: "cmake"
cmd: "USE_SINGLE_BUILDDIR=ON DEV_MODE=ON make release -j3"
name: build-ubuntu-${{ matrix.toolchain.name }}
steps:
- uses: actions/checkout@v1
with:
submodules: recursive
- name: remove bundled boost
run: sudo rm -rf /usr/local/share/boost
- name: set apt conf
run: |
echo "Acquire::Retries \"3\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom
echo "Acquire::http::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom
echo "Acquire::ftp::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom
- name: update apt
run: sudo apt update
- name: install monero dependencies
run: sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev
run: sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev libprotobuf-dev protobuf-compiler
- name: install monero gui dependencies
run: sudo apt -y install qtbase5-dev qt5-default qtdeclarative5-dev qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-dialogs qml-module-qtquick-xmllistmodel qml-module-qt-labs-settings qml-module-qt-labs-folderlistmodel qttools5-dev-tools qml-module-qtquick-templates2 libqt5svg5-dev xvfb
run: sudo apt -y install qtbase5-dev qt5-default qtdeclarative5-dev qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-dialogs qml-module-qtquick-xmllistmodel qml-module-qt-labs-settings qml-module-qt-labs-folderlistmodel qttools5-dev-tools qml-module-qtquick-templates2 libqt5svg5-dev libgcrypt20-dev xvfb
- name: build
run: ./build.sh
run: ${{ matrix.toolchain.cmd }}
- name: test qml
run: xvfb-run -a build/release/bin/monero-wallet-gui --test-qml
build-windows:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
toolchain:
- name: "qmake"
cmd: "./build.sh release"
- name: "cmake"
cmd: "USE_SINGLE_BUILDDIR=ON DEV_MODE=ON make release-win64 -j2"
name: build-windows-${{ matrix.toolchain.name }}
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v1
- uses: numworks/setup-msys2@v1
- name: update pacman
run: msys2do pacman -Syu --noconfirm
- name: install monero dependencies
run: msys2do pacman -S --noconfirm mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb git mingw-w64-x86_64-qt5
with:
submodules: recursive
- uses: eine/setup-msys2@v1
with:
update: true
install: mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb git mingw-w64-x86_64-qt5 mingw-w64-x86_64-libgcrypt
- name: build
run: msys2do ./build.sh release
run: |
sed -i 's/CONFIG\ +=\ qtquickcompiler//' monero-wallet-gui.pro
${{ matrix.toolchain.cmd }}
- name: test qml
run: msys2do build/release/bin/monero-wallet-gui --test-qml
run: build/release/bin/monero-wallet-gui --test-qml

1
.gitignore vendored
View File

@@ -13,6 +13,7 @@ monero-wallet-gui_plugin_import.cpp
monero-wallet-gui_qml_plugin_import.cpp
*.qmlc
*.jsc
qml_qmlcache.qrc
### Vim ###
# Swap

View File

@@ -8,15 +8,19 @@ set(VERSION_MINOR "0")
set(VERSION_REVISION "3")
set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}")
# libwallet requires a static build, so we only allow static compilation
set(STATIC ON)
option(STATIC "Link libraries statically, requires static Qt")
option(USE_DEVICE_TREZOR ON)
option(USE_DEVICE_TREZOR "Trezor support compilation" ON)
option(ENABLE_PASS_STRENGTH_METER "Disable zxcvbn" OFF)
option(WITH_SCANNER "Enable webcam QR scanner" OFF)
option(DEV_MODE "Checkout latest monero master on build" OFF)
list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_SOURCE_DIR}/cmake")
include(CheckCCompilerFlag)
include(CheckCXXCompilerFlag)
include(CheckLinkerFlag)
include(FindCcache)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
@@ -27,7 +31,7 @@ endif()
set(BUILD_GUI_DEPS ON)
set(ARCH "x86-64")
set(BUILD_64 ON)
set(INSTALL_VENDORED_LIBUNBOUND=ON)
set(INSTALL_VENDORED_LIBUNBOUND ${STATIC})
function (add_c_flag_if_supported flag var)
string(REPLACE "-" "_" supported ${flag}_c)
@@ -166,7 +170,7 @@ message(STATUS "libhidapi: libraries at ${HIDAPI_LIBRARIES}")
if(DEBUG)
set(Boost_DEBUG ON)
endif()
find_package(Boost 1.62 REQUIRED COMPONENTS
find_package(Boost 1.58 REQUIRED COMPONENTS
system
filesystem
thread
@@ -194,21 +198,6 @@ if(MINGW)
string(REGEX MATCH "^[^/]:/[^/]*" msys2_install_path "${CMAKE_C_COMPILER}")
message(STATUS "MSYS location: ${msys2_install_path}")
set(CMAKE_INCLUDE_PATH "${msys2_install_path}/mingw${ARCH_WIDTH}/include")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/Qt/labs/folderlistmodel")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/Qt/labs/settings")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtGraphicalEffects")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtGraphicalEffects/private")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtMultimedia")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick.2")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick/Controls")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick/Controls.2")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick/Dialogs")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick/Dialogs/Private")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick/Layouts")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick/PrivateWidgets")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick/Templates.2")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick/Window.2")
link_directories("${msys2_install_path}/mingw${ARCH_WIDTH}/qml/QtQuick/XmlListModel")
# This is necessary because otherwise CMake will make Boost libraries -lfoo
# rather than a full path. Unfortunately, this makes the shared libraries get
# linked due to a bug in CMake which misses putting -static flags around the
@@ -221,35 +210,46 @@ endif()
set(QT5_LIBRARIES
Qt5Core
Qt5Quick
Qt5QuickControls2
Qt5Widgets
Qt5Gui
Qt5Network
Qt5Qml
Qt5Multimedia
Qt5Xml
Qt5XmlPatterns
Qt5Svg
Qt5Xml
)
if(WITH_SCANNER)
list(APPEND QT5_LIBRARIES Qt5Multimedia)
endif()
# TODO: drop this once we switch to Qt 5.14+
find_package(Qt5QmlModels QUIET)
if(Qt5QmlModels_FOUND)
list(APPEND QT5_LIBRARIES Qt5QmlModels)
endif()
# TODO: drop this once we switch to Qt 5.12+
find_package(Qt5XmlPatterns QUIET)
if(Qt5XmlPatterns_FOUND)
list(APPEND QT5_LIBRARIES Qt5XmlPatterns)
endif()
foreach(QT5_MODULE ${QT5_LIBRARIES})
find_package(${QT5_MODULE} REQUIRED)
endforeach()
find_package(PkgConfig)
if(PKGCONFIG_FOUND)
pkg_check_modules(QT5_PKG_CONFIG ${QT5_LIBRARIES})
find_package(PkgConfig REQUIRED)
pkg_check_modules(QT5_PKG_CONFIG REQUIRED ${QT5_LIBRARIES})
if(QT5_PKG_CONFIG_FOUND)
set(QT5_PKG_CONFIG "QT5_PKG_CONFIG")
if(STATIC)
set(QT5_PKG_CONFIG "${QT5_PKG_CONFIG}_STATIC")
endif()
set(QT5_LIBRARIES ${${QT5_PKG_CONFIG}_LIBRARIES} ${${QT5_PKG_CONFIG}_LDFLAGS_OTHER})
include_directories(${${QT5_PKG_CONFIG}_INCLUDE_DIRS})
link_directories(${${QT5_PKG_CONFIG}_LIBRARY_DIRS})
if(QT5_PKG_CONFIG_FOUND)
set(QT5_PKG_CONFIG "QT5_PKG_CONFIG")
if(STATIC)
set(QT5_PKG_CONFIG "${QT5_PKG_CONFIG}_STATIC")
endif()
set(QT5_LIBRARIES ${${QT5_PKG_CONFIG}_LIBRARIES} ${${QT5_PKG_CONFIG}_LDFLAGS_OTHER})
include_directories(${${QT5_PKG_CONFIG}_INCLUDE_DIRS})
link_directories(${${QT5_PKG_CONFIG}_LIBRARY_DIRS})
endif()
list(APPEND QT5_LIBRARIES
@@ -260,37 +260,91 @@ list(APPEND QT5_LIBRARIES
)
if(STATIC)
set(QT5_LIBRARIES
qtquickcontrols2plugin # has to be the first one, depends on Qt5QuickControls2
${QT5_LIBRARIES}
declarative_multimedia
set(QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/Qt/labs/folderlistmodel)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/Qt/labs/settings)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtGraphicalEffects)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtGraphicalEffects/private)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtMultimedia)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick.2)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick/Controls)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick/Controls.2)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick/Dialogs)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick/Dialogs/Private)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick/Layouts)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick/PrivateWidgets)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick/Templates.2)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick/Window.2)
list(APPEND QT5_EXTRA_PATHS ${QT5_PKG_CONFIG_Qt5Qml_PREFIX}/qml/QtQuick/XmlListModel)
set(QT5_EXTRA_LIBRARIES_LIST
qtquicktemplates2plugin
Qt5QuickTemplates2
qtquickcontrols2plugin
Qt5QuickControls2
dialogplugin
dialogsprivateplugin
qmlfolderlistmodelplugin
qmlsettingsplugin
qmlxmllistmodelplugin
qquicklayoutsplugin
)
if(UNIX AND NOT APPLE)
list(APPEND QT5_EXTRA_LIBRARIES_LIST
Qt5XcbQpa
xcb-static
Qt5ServiceSupport
)
endif()
if(WITH_SCANNER)
list(APPEND QT5_EXTRA_LIBRARIES_LIST
declarative_multimedia
Qt5MultimediaQuick_p
)
endif()
list(APPEND QT5_EXTRA_LIBRARIES_LIST
Qt5EventDispatcherSupport
Qt5FontDatabaseSupport
Qt5MultimediaQuick_p
Qt5PacketProtocol
Qt5ThemeSupport
qtgraphicaleffectsplugin
qtgraphicaleffectsprivate
qtquick2plugin
qtquickcontrolsplugin
qtquicktemplates2plugin
widgetsplugin
windowplugin
)
set(QT5_EXTRA_LIBRARIES)
foreach(LIBRARY ${QT5_EXTRA_LIBRARIES_LIST})
find_library(${LIBRARY}_LIBRARY ${LIBRARY} PATHS ${QT5_EXTRA_PATHS})
list(APPEND QT5_EXTRA_LIBRARIES ${${LIBRARY}_LIBRARY})
endforeach()
if(MINGW)
list(APPEND QT5_LIBRARIES qtfreetype)
list(APPEND QT5_EXTRA_LIBRARIES qtfreetype)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
list(APPEND QT5_LIBRARIES D3D11 Dwrite D2d1)
list(APPEND QT5_EXTRA_LIBRARIES D3D11 Dwrite D2d1)
endif()
endif()
set(QT5_LIBRARIES
${QT5_EXTRA_LIBRARIES}
${QT5_LIBRARIES}
)
if(UNIX AND NOT APPLE)
pkg_check_modules(X11_XCB REQUIRED x11-xcb)
pkg_check_modules(FONTCONFIG REQUIRED fontconfig)
list(APPEND QT5_LIBRARIES
${FONTCONFIG_STATIC_LIBRARIES}
${X11_XCB_STATIC_LIBRARIES}
)
endif()
endif()
message(STATUS "Using Boost include dir at ${Boost_INCLUDE_DIRS}")
@@ -384,13 +438,36 @@ if (noexecheap_SUPPORTED)
set(LD_SECURITY_FLAGS "${LD_SECURITY_FLAGS} -Wl,-z,noexecheap")
endif()
# some windows linker bits
if (WIN32)
add_linker_flag_if_supported(-Wl,--dynamicbase LD_SECURITY_FLAGS)
add_linker_flag_if_supported(-Wl,--nxcompat LD_SECURITY_FLAGS)
add_linker_flag_if_supported(-Wl,--high-entropy-va LD_SECURITY_FLAGS)
endif()
if(STATIC)
add_linker_flag_if_supported(-static-libgcc STATIC_FLAGS)
add_linker_flag_if_supported(-static-libstdc++ STATIC_FLAGS)
if(MINGW)
add_linker_flag_if_supported(-static STATIC_FLAGS)
endif()
endif()
# With GCC 6.1.1 the compiled binary malfunctions due to aliasing. Until that
# is fixed in the code (Issue #847), force compiler to be conservative.
add_c_flag_if_supported(-fno-strict-aliasing C_SECURITY_FLAGS)
add_cxx_flag_if_supported(-fno-strict-aliasing CXX_SECURITY_FLAGS)
add_c_flag_if_supported(-fPIC C_SECURITY_FLAGS)
add_cxx_flag_if_supported(-fPIC CXX_SECURITY_FLAGS)
message(STATUS "Using C security hardening flags: ${C_SECURITY_FLAGS}")
message(STATUS "Using C++ security hardening flags: ${CXX_SECURITY_FLAGS}")
message(STATUS "Using linker security hardening flags: ${LD_SECURITY_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_SECURITY_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_SECURITY_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LD_SECURITY_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 ${C_SECURITY_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${CXX_SECURITY_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LD_SECURITY_FLAGS} ${STATIC_FLAGS}")
if (HIDAPI_FOUND OR LibUSB_COMPILE_TEST_PASSED)
if (APPLE)
@@ -411,5 +488,5 @@ endif()
add_subdirectory(src)
# Required to make wallet_merged build before the gui
add_dependencies(monero-gui wallet_merged)
add_dependencies(monero-wallet-gui wallet_merged)

View File

@@ -64,7 +64,6 @@ Rectangle {
signal addressBookClicked()
signal miningClicked()
signal signClicked()
signal merchantClicked()
signal accountClicked()
function selectItem(pos) {
@@ -72,7 +71,6 @@ Rectangle {
if(pos === "History") menuColumn.previousButton = historyButton
else if(pos === "Transfer") menuColumn.previousButton = transferButton
else if(pos === "Receive") menuColumn.previousButton = receiveButton
else if(pos === "Merchant") menuColumn.previousButton = merchantButton
else if(pos === "AddressBook") menuColumn.previousButton = addressBookButton
else if(pos === "Mining") menuColumn.previousButton = miningButton
else if(pos === "TxKey") menuColumn.previousButton = txkeyButton
@@ -265,6 +263,10 @@ Rectangle {
anchors.leftMargin: 58
anchors.baseline: currencyLabel.baseline
color: MoneroComponents.Style.blackTheme ? "white" : "black"
Binding on color {
when: balancePart1MouseArea.containsMouse || balancePart2MouseArea.containsMouse
value: MoneroComponents.Style.orange
}
text: {
if (persistentSettings.fiatPriceEnabled && persistentSettings.fiatPriceToggle) {
return balanceFiatString.split('.')[0] + "."
@@ -286,14 +288,6 @@ Rectangle {
hoverEnabled: true
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onEntered: {
balancePart1.color = MoneroComponents.Style.orange
balancePart2.color = MoneroComponents.Style.orange
}
onExited: {
balancePart1.color = Qt.binding(function() { return MoneroComponents.Style.blackTheme ? "white" : "black" })
balancePart2.color = Qt.binding(function() { return MoneroComponents.Style.blackTheme ? "white" : "black" })
}
onClicked: {
console.log("Copied to clipboard");
clipboard.setText(balancePart1.text + balancePart2.text);
@@ -307,7 +301,7 @@ Rectangle {
anchors.left: balancePart1.right
anchors.leftMargin: 2
anchors.baseline: currencyLabel.baseline
color: MoneroComponents.Style.blackTheme ? "white" : "black"
color: balancePart1.color
text: {
if (persistentSettings.fiatPriceEnabled && persistentSettings.fiatPriceToggle) {
return balanceFiatString.split('.')[1]
@@ -317,11 +311,10 @@ Rectangle {
}
font.pixelSize: 16
MouseArea {
id: balancePart2MouseArea
hoverEnabled: true
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onEntered: balancePart1MouseArea.entered()
onExited: balancePart1MouseArea.exited()
onClicked: balancePart1MouseArea.clicked(mouse)
}
}
@@ -454,30 +447,6 @@ Rectangle {
anchors.leftMargin: 20
}
// ------------- Merchant tab ---------------
MoneroComponents.MenuButton {
id: merchantButton
visible: appWindow.walletMode >= 2
anchors.left: parent.left
anchors.right: parent.right
text: qsTr("Merchant") + translationManager.emptyString
symbol: qsTr("U") + translationManager.emptyString
under: receiveButton
onClicked: {
parent.previousButton.checked = false
parent.previousButton = merchantButton
panel.merchantClicked()
}
}
MoneroComponents.MenuButtonDivider {
visible: merchantButton.present && appWindow.walletMode >= 2
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 20
}
// ------------- History tab ---------------
MoneroComponents.MenuButton {

View File

@@ -28,13 +28,21 @@ clean:
scanner:
mkdir -p build && cd build && cmake -D ARCH="x86-64" -D DEV_MODE=$(or ${DEV_MODE},ON) -D WITH_SCANNER=ON -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release .. && $(MAKE)
release:
mkdir -p $(builddir)/release && cd $(builddir)/release && cmake -D DEV_MODE=$(or ${DEV_MODE},OFF) -D ARCH="x86-64" -D CMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE)
release-static:
mkdir -p $(builddir)/release && cd $(builddir)/release && cmake -D STATIC=ON -D DEV_MODE=$(or ${DEV_MODE},OFF) -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE)
debug-static-win64:
mkdir -p $(builddir)/debug && cd $(builddir)/debug && cmake -D STATIC=ON -G "MSYS Makefiles" -D DEV_MODE=$(or ${DEV_MODE},ON) -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Debug -D BUILD_TAG="win-x64" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=$(cd $MINGW_PREFIX/.. && pwd -W) -D MINGW=ON $(topdir) && $(MAKE)
mkdir -p $(builddir)/debug && cd $(builddir)/debug && cmake -D STATIC=ON -G "MSYS Makefiles" -D DEV_MODE=$(or ${DEV_MODE},ON) -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Debug -D BUILD_TAG="win-x64" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=$(shell cd ${MINGW_PREFIX}/.. && pwd -W) -D MINGW=ON $(topdir) && $(MAKE)
debug-static-mac64:
mkdir -p $(builddir)/debug
cd $(builddir)/debug && cmake -D STATIC=ON -D DEV_MODE=$(or ${DEV_MODE},ON) -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release -D BUILD_TAG="mac-x64" $(topdir) && $(MAKE)
release-static-win64:
mkdir -p $(builddir)/release && cd $(builddir)/release && cmake -D STATIC=ON -G "MSYS Makefiles" -D DEV_MODE=$(or ${DEV_MODE},OFF) -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="win-x64" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=$(cd $MINGW_PREFIX/.. && pwd -W) -D MINGW=ON $(topdir) && $(MAKE)
mkdir -p $(builddir)/release && cd $(builddir)/release && cmake -D STATIC=ON -G "MSYS Makefiles" -D DEV_MODE=$(or ${DEV_MODE},OFF) -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="win-x64" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=$(shell cd ${MINGW_PREFIX}/.. && pwd -W) -D MINGW=ON $(topdir) && $(MAKE)
release-win64:
mkdir -p $(builddir)/release && cd $(builddir)/release && cmake -D STATIC=OFF -G "MSYS Makefiles" -D DEV_MODE=$(or ${DEV_MODE},OFF) -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="win-x64" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=$(shell cd ${MINGW_PREFIX}/.. && pwd -W) -D MINGW=ON $(topdir) && $(MAKE)

View File

@@ -36,7 +36,7 @@ As with many development projects, the repository on Github is considered to be
Monero is a 100% community-sponsored endeavor. If you want to join our efforts, the easiest thing you can do is support the project financially. Both Monero and Bitcoin donations can be made to **donate.getmonero.org** if using a client that supports the [OpenAlias](https://openalias.org) standard.
The Monero donation address is: `44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A` (viewkey: `f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501`)
The Monero donation address is: `888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H` (viewkey: `f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501`)
The Bitcoin donation address is: `1KTexdemPdxSBcG55heUuTjDRYqbC5ZL8H`
@@ -62,6 +62,11 @@ Do you speak a second language and would like to help translate the Monero GUI?
If you need help/support or any info you can contact the localization workgroup on the IRC channel #monero-translations (relayed on matrix/riot and MatterMost) or by email at translate[at]getmonero[dot]org. For more info about the Localization workgroup: [github.com/monero-ecosystem/monero-translations](https://github.com/monero-ecosystem/monero-translations)
Status of the translations:
<a href="https://translate.getmonero.org/engage/monero/?utm_source=widget">
<img src="https://translate.getmonero.org/widgets/monero/-/gui-wallet/horizontal-auto.svg" alt="Translation status" />
</a>
## Installing the Monero GUI from a package
Packages are available for
@@ -84,15 +89,15 @@ Packaging for your favorite distribution would be a welcome contribution!
- For Debian distributions (Debian, Ubuntu, Mint, Tails...)
`sudo apt install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev`
`sudo apt install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev libprotobuf-dev protobuf-compiler libgcrypt20-dev`
- For Gentoo
`sudo emerge app-arch/xz-utils app-doc/doxygen dev-cpp/gtest dev-libs/boost dev-libs/expat dev-libs/openssl dev-util/cmake media-gfx/graphviz net-dns/unbound net-libs/ldns net-libs/miniupnpc net-libs/zeromq sys-libs/libunwind dev-libs/libsodium dev-libs/hidapi`
`sudo emerge app-arch/xz-utils app-doc/doxygen dev-cpp/gtest dev-libs/boost dev-libs/expat dev-libs/openssl dev-util/cmake media-gfx/graphviz net-dns/unbound net-libs/ldns net-libs/miniupnpc net-libs/zeromq sys-libs/libunwind dev-libs/libsodium dev-libs/hidapi dev-libs/libgcrypt`
- For Fedora
`sudo dnf install make automake cmake gcc-c++ boost-devel miniupnpc-devel graphviz doxygen unbound-devel libunwind-devel pkgconfig openssl-devel libcurl-devel hidapi-devel libusb-devel zeromq-devel`
`sudo dnf install make automake cmake gcc-c++ boost-devel miniupnpc-devel graphviz doxygen unbound-devel libunwind-devel pkgconfig openssl-devel libcurl-devel hidapi-devel libusb-devel zeromq-devel libgcrypt-devel`
2. Install Qt:
@@ -144,26 +149,12 @@ The executable can be found in the build/release/bin folder.
3. Install [monero](https://github.com/monero-project/monero) dependencies:
`brew install boost`
`brew install openssl` - to install openssl headers
`brew install pkgconfig`
`brew install cmake`
`brew install zeromq`
*Note*: If cmake can not find zmq.hpp file on OS X, installing `zmq.hpp` from https://github.com/zeromq/cppzmq to `/usr/local/include` should fix that error.
`brew install boost hidapi zmq libpgm miniupnpc ldns expat libunwind-headers protobuf libgcrypt`
4. Install Qt:
`brew install qt5` (or download QT 5.9.7+ from [qt.io](https://www.qt.io/download-open-source/))
If you have an older version of Qt installed via homebrew, you can force it to use 5.x like so:
`brew link --force --overwrite qt5`
5. Add the Qt bin directory to your path
- Example for Qt: `export PATH=$PATH:$HOME/Qt/5.9.7/clang_64/bin`
@@ -183,19 +174,6 @@ The executable can be found in the build/release/bin folder.
The executable can be found in the `build/release/bin` folder.
**Note:** Workaround for "ERROR: Xcode not set up properly"
Edit `$HOME/Qt/5.9.7/clang_64/mkspecs/features/mac/default_pre.prf`
replace
`isEmpty($$list($$system("/usr/bin/xcrun -find xcrun 2>/dev/null")))`
with
`isEmpty($$list($$system("/usr/bin/xcrun -find xcodebuild 2>/dev/null")))`
More info: http://stackoverflow.com/a/35098040/1683164
### On Windows:
The Monero GUI on Windows is 64 bits only; 32-bit Windows GUI builds are not officially supported anymore.
@@ -207,7 +185,7 @@ The Monero GUI on Windows is 64 bits only; 32-bit Windows GUI builds are not off
3. Install MSYS2 packages for Monero dependencies; the needed 64-bit packages have `x86_64` in their names
```
pacman -S mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb
pacman -S mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb mingw-w64-x86_64-libgcrypt
```
Optional : To build the flag `WITH_SCANNER`

56
cmake/FindCcache.cmake Normal file
View File

@@ -0,0 +1,56 @@
# Copyright (c) 2014-2020, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# - Try to find readline include dirs and libraries
#
# Automatically finds ccache build accelerator, if it's found in PATH.
#
# Usage of this module as follows:
#
# project(monero)
# include(FindCcache) # Include AFTER the project() macro to be able to reach the CMAKE_CXX_COMPILER variable
#
# Properties modified by this module:
#
# GLOBAL PROPERTY RULE_LAUNCH_COMPILE set to ccache, when ccache found
# GLOBAL PROPERTY RULE_LAUNCH_LINK set to ccache, when ccache found
find_program(CCACHE_FOUND ccache)
if (CCACHE_FOUND)
set(TEMP_CPP_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/test-program.cpp")
file(WRITE "${TEMP_CPP_FILE}" "int main() { return 0; }")
execute_process(COMMAND "${CCACHE_FOUND}" "${CMAKE_CXX_COMPILER}" "${TEMP_CPP_FILE}" RESULT_VARIABLE RET)
if (${RET} EQUAL 0)
message("found usable ccache: ${CCACHE_FOUND}")
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_FOUND}")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_FOUND}")
else()
message("found ccache ${CCACHE_FOUND}, but is UNUSABLE! Return code: ${RET}")
endif()
else()
message("ccache NOT found!")
endif()

View File

@@ -0,0 +1,106 @@
import QtQuick 2.9
import QtQuick.Layouts 1.1
import FontAwesome 1.0
import "../components" as MoneroComponents
RowLayout {
id: advancedOptionsItem
property alias title: title.text
property alias button1: button1
property alias button2: button2
property alias button3: button3
property alias helpTextLarge: helpTextLarge
property alias helpTextSmall: helpTextSmall
RowLayout {
id: titlecolumn
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.preferredWidth: 195
Layout.maximumWidth: 195
Layout.leftMargin: 10
MoneroComponents.Label {
id: title
fontSize: 14
}
MoneroComponents.Label {
id: iconLabel
fontSize: 12
text: FontAwesome.questionCircle
fontFamily: FontAwesome.fontFamily
opacity: 0.3
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: helpText.visible = !helpText.visible
onEntered: parent.opacity = 0.4
onExited: parent.opacity = 0.3
}
}
Rectangle {
id: separator
Layout.fillWidth: true
height: 10
color: "transparent"
}
}
ColumnLayout {
Layout.fillWidth: false
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
spacing: 4
RowLayout {
Layout.fillWidth: false
spacing: 12
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
StandardButton {
id: button1
small: true
visible: button1.text
}
StandardButton {
id: button2
small: true
visible: button2.text
}
StandardButton {
id: button3
small: true
visible: button3.text
}
}
ColumnLayout {
id: helpText
visible: false
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
MoneroComponents.TextPlain {
id: helpTextLarge
visible: helpTextLarge.text
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 13
color: MoneroComponents.Style.defaultFontColor
}
MoneroComponents.TextPlain {
id: helpTextSmall
visible: helpTextSmall.text
Layout.leftMargin: 5
textFormat: Text.RichText
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 12
color: MoneroComponents.Style.defaultFontColor
}
}
}
}

View File

@@ -74,7 +74,7 @@ Item {
visible: checkBox.border
anchors.fill: parent
radius: 3
color: "transparent"
color: checkBox.enabled ? "transparent" : MoneroComponents.Style.inputBoxBackgroundDisabled
border.color:
if(checkBox.checked){
return MoneroComponents.Style.inputBorderColorActive;

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2014-2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.9
import "." as MoneroComponents
Item {
id: root
property var onAcceptedCallback
property var onWalletEntryCallback
property var onRejectedCallback
function open(canEnterOnDevice_) {
var canEnterOnDevice = canEnterOnDevice_ !== null ? canEnterOnDevice_ : canEnterOnDevice
root.visible = true;
if (canEnterOnDevice) {
entryChooserDialog.okText = qsTr("Hardware wallet")
entryChooserDialog.cancelText = qsTr("Computer")
entryChooserDialog.open()
} else {
openPassphraseDialog()
}
}
function openPassphraseDialog() {
root.visible = true
passphraseDialog.openPassphraseDialog()
}
function close() {
root.visible = false;
if (entryChooserDialog.visible)
entryChooserDialog.close()
if (passphraseDialog.visible)
passphraseDialog.close()
}
StandardDialog {
id: entryChooserDialog
title: qsTr("Hardware wallet passphrase") + translationManager.emptyString
text: qsTr("Please select where you want to enter passphrase.\nIt is recommended to enter passphrase on the hardware wallet for better security.") + translationManager.emptyString
onAccepted: {
if (onWalletEntryCallback){
onWalletEntryCallback()
}
}
onRejected: {
openPassphraseDialog()
}
onCloseCallback: {
root.close()
}
}
PasswordDialog {
id: passphraseDialog
anchors.fill: parent
passphraseDialogMode: true
onAcceptedPassphrase: {
if (onAcceptedCallback)
onAcceptedCallback(passphraseDialog.password);
}
onRejectedPassphrase: {
if (onRejectedCallback)
onRejectedCallback();
}
onCloseCallback: {
root.close()
}
}
}

View File

@@ -53,6 +53,7 @@ Item {
property alias fontPixelSize: inlineText.font.pixelSize
property alias fontFamily: inlineText.font.family
property alias buttonColor: rect.color
property alias buttonHeight: rect.height
signal clicked()
function doClick() {

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2014-2019, The Monero Project
// Copyright (c) 2014-2020, The Monero Project
//
// All rights reserved.
//
@@ -53,7 +53,7 @@ Drawer {
y: titleBar.height
background: Rectangle {
color: "#0d0d0d"
color: MoneroComponents.Style.blackTheme ? "#0d0d0d" : "white"
width: parent.width
}
@@ -79,9 +79,24 @@ Drawer {
width: sideBar.width
height: 32
Rectangle {
id: flagRect
height: 24
width: 24
anchors.left: parent.left
anchors.leftMargin: 4
anchors.verticalCenter: parent.verticalCenter
color: "transparent"
Image {
anchors.fill: parent
source: flag
}
}
MoneroComponents.TextPlain {
anchors.left: parent.left
anchors.leftMargin: 16
anchors.leftMargin: 30
font.bold: true
font.pixelSize: 14
color: MoneroComponents.Style.defaultFontColor
@@ -118,8 +133,10 @@ Drawer {
// set wizard language settings
wizard.language_locale = locale;
wizard.language_wallet = wallet_language;
wizard.language_language = display_name + " (" + locale_spl[1] + ") ";
sideBar.close()
wizard.language_language = display_name;
appWindow.showStatusMessage(qsTr("Language changed."), 3);
appWindow.toggleLanguageView();
}
hoverEnabled: true
onEntered: {
@@ -134,15 +151,8 @@ Drawer {
}
}
ScrollIndicator.vertical: ScrollIndicator {
// @TODO: QT 5.9 introduces `policy: ScrollBar.AlwaysOn`
active: true
contentItem.opacity: 0.7
onActiveChanged: {
if (!active) {
active = true;
}
}
ScrollBar.vertical: ScrollBar {
onActiveChanged: if (!active && !isMac) active = true
}
}
}

View File

@@ -26,6 +26,7 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import FontAwesome 1.0
import QtQuick 2.9
import QtGraphicalEffects 1.0
@@ -36,6 +37,10 @@ Item {
property alias input: input
property alias text: input.text
property bool password: false
property bool passwordHidden: true
property var passwordLinked: null
property alias placeholderText: placeholderLabel.text
property bool placeholderCenter: false
property string placeholderFontFamily: MoneroComponents.Style.fontRegular.name
@@ -48,7 +53,6 @@ Item {
property alias validator: input.validator
property alias readOnly : input.readOnly
property alias cursorPosition: input.cursorPosition
property alias echoMode: input.echoMode
property alias inlineButton: inlineButtonId
property alias inlineButtonText: inlineButtonId.text
property alias inlineIcon: inlineIcon.visible
@@ -109,6 +113,31 @@ Item {
}
}
function isPasswordHidden() {
if (password) {
return passwordHidden;
}
if (passwordLinked) {
return passwordLinked.passwordHidden;
}
return false;
}
function reset() {
text = "";
if (!passwordLinked) {
passwordHidden = true;
}
}
function passwordToggle() {
if (passwordLinked) {
passwordLinked.passwordHidden = !passwordLinked.passwordHidden;
} else {
passwordHidden = !passwordHidden;
}
}
MoneroComponents.TextPlain {
id: inputLabel
anchors.top: parent.top
@@ -210,6 +239,27 @@ Item {
onTextChanged: item.textUpdated()
topPadding: 10
bottomPadding: 10
echoMode: isPasswordHidden() ? TextInput.Password : TextInput.Normal
MoneroComponents.Label {
visible: password || passwordLinked
fontSize: 20
text: isPasswordHidden() ? FontAwesome.eye : FontAwesome.eyeSlash
opacity: eyeMouseArea.containsMouse ? 0.9 : 0.7
fontFamily: FontAwesome.fontFamily
anchors.right: parent.right
anchors.rightMargin: 15
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1
MouseArea {
id: eyeMouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: passwordToggle()
}
}
}
MoneroComponents.InlineButton {

View File

@@ -87,6 +87,8 @@ ColumnLayout {
property alias inlineButton: inlineButtonId
property bool inlineButtonVisible: false
property alias inlineButton2: inlineButton2Id
property bool inlineButton2Visible: false
signal labelButtonClicked();
signal inputLabelLinkActivated();
@@ -202,5 +204,12 @@ ColumnLayout {
anchors.right: parent.right
anchors.rightMargin: 8
}
MoneroComponents.InlineButton {
id: inlineButton2Id
visible: (inlineButton2Id.text || inlineButton2Id.icon) && inlineButton2Visible ? true : false
anchors.right: parent.right
anchors.rightMargin: inlineButtonVisible ? 48 : 8
}
}
}

View File

@@ -62,7 +62,7 @@ Rectangle {
height: present ? ((appWindow.height >= 800) ? 44 : 38 ) : 0
LinearGradient {
visible: isOpenGL && button.checked
visible: isOpenGL && (button.checked || buttonArea.containsMouse)
height: parent.height
width: 260
anchors.verticalCenter: parent.verticalCenter
@@ -75,13 +75,15 @@ Rectangle {
GradientStop { position: 0.0; color: MoneroComponents.Style.menuButtonGradientStart }
GradientStop { position: 1.0; color: MoneroComponents.Style.menuButtonGradientStop }
}
opacity: button.checked ? 1 : 0.3
}
// fallback hover effect when opengl is not available
Rectangle {
visible: !isOpenGL && button.checked
visible: !isOpenGL && (button.checked || buttonArea.containsMouse)
anchors.fill: parent
color: MoneroComponents.Style.menuButtonFallbackBackgroundColor
opacity: button.checked ? 1 : 0.3
}
// button decorations that are subject to leftMargin offsets

View File

@@ -43,7 +43,6 @@ Item {
visible: false
z: parent.z + 2
property bool isHidden: true
property alias password: passwordInput1.text
property string walletName
property string errorText
@@ -61,13 +60,10 @@ Item {
signal closeCallback()
function _openInit(walletName, errorText) {
isHidden = true
capsLockTextLabel.visible = oshelper.isCapsLock();
passwordInput1.echoMode = TextInput.Password
passwordInput2.echoMode = TextInput.Password
passwordInput1.text = ""
passwordInput2.text = ""
passwordInput1.forceActiveFocus();
passwordInput1.reset();
passwordInput2.reset();
passwordInput1.input.forceActiveFocus();
root.walletName = walletName ? walletName : ""
errorTextLabel.text = errorText ? errorText : "";
leftPanel.enabled = false
@@ -116,10 +112,29 @@ Item {
closeCallback();
}
function toggleIsHidden() {
passwordInput1.echoMode = isHidden ? TextInput.Normal : TextInput.Password;
passwordInput2.echoMode = isHidden ? TextInput.Normal : TextInput.Password;
isHidden = !isHidden;
function onOk() {
if (!passwordDialogMode && passwordInput1.text !== passwordInput2.text) {
return;
}
root.close()
if (passwordDialogMode) {
root.accepted()
} else if (newPasswordDialogMode) {
root.acceptedNewPassword()
} else if (passphraseDialogMode) {
root.acceptedPassphrase()
}
}
function onCancel() {
root.close()
if (passwordDialogMode) {
root.rejected()
} else if (newPasswordDialogMode) {
root.rejectedNewPassword()
} else if (passphraseDialogMode) {
root.rejectedPassphrase()
}
}
ColumnLayout {
@@ -184,15 +199,11 @@ Item {
text: qsTr("CAPSLOCKS IS ON.") + translationManager.emptyString;
}
MoneroComponents.Input {
MoneroComponents.LineEdit {
id: passwordInput1
password: true
Layout.topMargin: 6
Layout.fillWidth: true
horizontalAlignment: TextInput.AlignLeft
verticalAlignment: TextInput.AlignVCenter
font.family: MoneroComponents.Style.fontLight.name
font.pixelSize: 24
echoMode: TextInput.Password
KeyNavigation.tab: {
if (passwordDialogMode) {
return okButton
@@ -200,81 +211,12 @@ Item {
return passwordInput2
}
}
implicitHeight: 50
bottomPadding: 10
leftPadding: 10
topPadding: 10
color: MoneroComponents.Style.defaultFontColor
selectionColor: MoneroComponents.Style.textSelectionColor
selectedTextColor: MoneroComponents.Style.textSelectedColor
onTextChanged: capsLockTextLabel.visible = oshelper.isCapsLock();
background: Rectangle {
radius: 2
color: MoneroComponents.Style.blackTheme ? "black" : "#A9FFFFFF"
border.color: MoneroComponents.Style.inputBorderColorInActive
border.width: 1
MoneroEffects.ColorTransition {
targetObj: parent
blackColor: "black"
whiteColor: "#A9FFFFFF"
}
MoneroComponents.Label {
fontSize: 20
text: isHidden ? FontAwesome.eye : FontAwesome.eyeSlash
opacity: 0.7
fontFamily: FontAwesome.fontFamily
anchors.right: parent.right
anchors.rightMargin: 15
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
toggleIsHidden();
}
onEntered: {
parent.opacity = 0.9
parent.fontSize = 24
}
onExited: {
parent.opacity = 0.7
parent.fontSize = 20
}
}
}
}
Keys.enabled: root.visible
Keys.onEnterPressed: Keys.onReturnPressed(event)
Keys.onReturnPressed: {
if (!passwordDialogMode && passwordInput1.text !== passwordInput2.text) {
return;
}
root.close()
if (passwordDialogMode) {
root.accepted()
} else if (newPasswordDialogMode) {
root.acceptedNewPassword()
} else if (passphraseDialogMode) {
root.acceptedPassphrase()
}
}
Keys.onEscapePressed: {
root.close()
if (passwordDialogMode) {
root.rejected()
} else if (newPasswordDialogMode) {
root.rejectedNewPassword()
} else if (passphraseDialogMode) {
root.rejectedPassphrase()
}
}
Keys.onEnterPressed: root.onOk()
Keys.onReturnPressed: root.onOk()
Keys.onEscapePressed: root.onCancel()
}
// padding
@@ -298,81 +240,19 @@ Item {
color: MoneroComponents.Style.defaultFontColor
}
MoneroComponents.Input {
MoneroComponents.LineEdit {
id: passwordInput2
passwordLinked: passwordInput1
visible: !passwordDialogMode
Layout.topMargin: 6
Layout.fillWidth: true
horizontalAlignment: TextInput.AlignLeft
verticalAlignment: TextInput.AlignVCenter
font.family: MoneroComponents.Style.fontLight.name
font.pixelSize: 24
echoMode: TextInput.Password
KeyNavigation.tab: okButton
implicitHeight: 50
bottomPadding: 10
leftPadding: 10
topPadding: 10
color: MoneroComponents.Style.defaultFontColor
selectionColor: MoneroComponents.Style.textSelectionColor
selectedTextColor: MoneroComponents.Style.textSelectedColor
onTextChanged: capsLockTextLabel.visible = oshelper.isCapsLock();
background: Rectangle {
radius: 2
border.color: MoneroComponents.Style.inputBorderColorInActive
border.width: 1
color: MoneroComponents.Style.blackTheme ? "black" : "#A9FFFFFF"
MoneroComponents.Label {
fontSize: 20
text: isHidden ? FontAwesome.eye : FontAwesome.eyeSlash
opacity: 0.7
fontFamily: FontAwesome.fontFamily
anchors.right: parent.right
anchors.rightMargin: 15
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
toggleIsHidden()
}
onEntered: {
parent.opacity = 0.9
parent.fontSize = 24
}
onExited: {
parent.opacity = 0.7
parent.fontSize = 20
}
}
}
}
Keys.enabled: root.visible
Keys.onEnterPressed: Keys.onReturnPressed(event)
Keys.onReturnPressed: {
if (passwordInput1.text === passwordInput2.text) {
root.close()
if (newPasswordDialogMode) {
root.acceptedNewPassword()
} else if (passphraseDialogMode) {
root.acceptedPassphrase()
}
}
}
Keys.onEscapePressed: {
root.close()
if (newPasswordDialogMode) {
root.rejectedNewPassword()
} else if (passphraseDialogMode) {
root.rejectedPassphrase()
}
}
Keys.onEnterPressed: root.onOk()
Keys.onReturnPressed: root.onOk()
Keys.onEscapePressed: root.onCancel()
}
// padding
@@ -397,16 +277,7 @@ Item {
small: true
text: qsTr("Cancel") + translationManager.emptyString
KeyNavigation.tab: passwordInput1
onClicked: {
root.close()
if (passwordDialogMode) {
root.rejected()
} else if (newPasswordDialogMode) {
root.rejectedNewPassword()
} else if (passphraseDialogMode) {
root.rejectedPassphrase()
}
}
onClicked: onCancel()
}
MoneroComponents.StandardButton {
@@ -415,16 +286,7 @@ Item {
text: qsTr("Ok") + translationManager.emptyString
KeyNavigation.tab: cancelButton
enabled: (passwordDialogMode == true) ? true : passwordInput1.text === passwordInput2.text
onClicked: {
root.close()
if (passwordDialogMode) {
root.accepted()
} else if (newPasswordDialogMode) {
root.acceptedNewPassword()
} else if (passphraseDialogMode) {
root.acceptedPassphrase()
}
}
onClicked: onOk()
}
}
}

View File

@@ -29,21 +29,23 @@
import QtQuick 2.9
import QtQuick.Window 2.1
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.1
import "../components" as MoneroComponents
Rectangle {
id: root
color: MoneroComponents.Style.blackTheme ? "white" : "transparent"
color: MoneroComponents.Style.blackTheme ? "black" : "white"
visible: false
radius: 10
border.color: MoneroComponents.Style.blackTheme ? Qt.rgba(255, 255, 255, 0.25) : Qt.rgba(0, 0, 0, 0.25)
border.width: 1
z: 11
property alias messageText: messageTitle.text
property alias heightProgressText : heightProgress.text
width: 200
height: 100
opacity: 0.7
width: 100
height: 50
function show() {
root.visible = true;
@@ -56,44 +58,55 @@ Rectangle {
ColumnLayout {
id: rootLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.centerIn: parent
anchors.leftMargin: 30
anchors.rightMargin: 30
spacing: 12
spacing: 21
BusyIndicator {
running: parent.visible
Item {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.preferredHeight: 80
Image {
id: imgLogo
width: 60
height: 60
anchors.centerIn: parent
source: "qrc:///images/monero-vector.svg"
mipmap: true
}
BusyIndicator {
running: parent.visible
anchors.centerIn: imgLogo
style: BusyIndicatorStyle {
indicator: Image {
visible: control.running
source: "qrc:///images/busy-indicator.png"
RotationAnimator on rotation {
running: control.running
loops: Animation.Infinite
duration: 1000
from: 0
to: 360
}
}
}
}
}
MoneroComponents.TextPlain {
id: messageTitle
text: "Please wait..."
font {
pixelSize: 22
}
text: qsTr("Please wait...") + translationManager.emptyString
font.pixelSize: 24
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.fillWidth: true
themeTransition: false
color: "black"
}
MoneroComponents.TextPlain {
id: heightProgress
font {
pixelSize: 18
}
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.fillWidth: true
themeTransition: false
color: "black"
color: MoneroComponents.Style.defaultFontColor
}
}
}

View File

@@ -42,7 +42,7 @@ Rectangle {
function updateProgress(currentBlock,targetBlock, blocksToSync, statusTxt){
if(targetBlock > 0) {
var remaining = (currentBlock < targetBlock) ? targetBlock - currentBlock : 0
var progressLevel = (blocksToSync > 0 && blocksToSync != remaining) ? (100*(blocksToSync - remaining)/blocksToSync).toFixed(0) : (100*(currentBlock / targetBlock)).toFixed(0)
var progressLevel = (blocksToSync > 0 ) ? (100*(blocksToSync - remaining)/blocksToSync).toFixed(0) : 100
fillLevel = progressLevel
if(typeof statusTxt != "undefined" && statusTxt != "") {
progressText.text = statusTxt;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2014-2018, The Monero Project
// Copyright (c) 2014-2020, The Monero Project
//
// All rights reserved.
//
@@ -53,6 +53,7 @@ Rectangle {
script: {
root.visible = true
camera.captureMode = Camera.CaptureStillImage
camera.cameraState = Camera.ActiveState
camera.start()
finder.enabled = true
}
@@ -65,6 +66,7 @@ Rectangle {
camera.stop()
root.visible = false
finder.enabled = false
camera.cameraState = Camera.UnloadedState
}
}
}
@@ -74,6 +76,7 @@ Rectangle {
id: camera
objectName: "qrCameraQML"
captureMode: Camera.CaptureStillImage
cameraState: Camera.UnloadedState
focus {
focusMode: Camera.FocusContinuous

View File

@@ -1,41 +1,71 @@
import QtQuick 2.9
import QtQuick.Layouts 1.1
import FontAwesome 1.0
import "../components" as MoneroComponents
ColumnLayout {
property alias buttonText: button.text
property alias description: description.text
property alias title: title.text
id: settingsListItem
property alias iconText: iconLabel.text
property alias description: area.text
property alias title: header.text
property bool isLast: false
signal clicked()
id: settingsListItem
Layout.fillWidth: true
spacing: 0
Rectangle {
// divider
Layout.preferredHeight: 1
id: root
Layout.fillWidth: true
Layout.bottomMargin: 8
color: MoneroComponents.Style.dividerColor
opacity: MoneroComponents.Style.dividerOpacity
}
Layout.minimumHeight: 75
Layout.preferredHeight: rect.height + 15
color: "transparent"
RowLayout {
Layout.fillWidth: true
spacing: 0
Rectangle {
id: divider
anchors.topMargin: 0
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: MoneroComponents.Style.dividerColor
opacity: MoneroComponents.Style.dividerOpacity
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 0
Rectangle {
id: rect
width: parent.width
height: header.height + area.contentHeight
color: "transparent";
anchors.left: parent.left
anchors.bottomMargin: 4
anchors.topMargin: 4
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: icon
color: "transparent"
height: 32
width: 32
anchors.left: parent.left
anchors.leftMargin: 16
anchors.verticalCenter: parent.verticalCenter
MoneroComponents.Label {
id: iconLabel
fontSize: 32
fontFamily: FontAwesome.fontFamilySolid
anchors.centerIn: parent
fontColor: MoneroComponents.Style.defaultFontColor
styleName: "Solid"
}
}
MoneroComponents.TextPlain {
id: title
Layout.fillWidth: true
Layout.preferredHeight: 20
Layout.topMargin: 8
id: header
anchors.left: icon.right
anchors.leftMargin: 16
anchors.top: parent.top
color: MoneroComponents.Style.defaultFontColor
opacity: MoneroComponents.Style.blackTheme ? 1.0 : 0.8
font.bold: true
@@ -43,23 +73,43 @@ ColumnLayout {
font.pixelSize: 16
}
MoneroComponents.TextPlainArea {
id: description
Text {
id: area
anchors.top: header.bottom
anchors.topMargin: 4
anchors.left: icon.right
anchors.leftMargin: 16
color: MoneroComponents.Style.dimmedFontColor
colorBlackTheme: MoneroComponents.Style._b_dimmedFontColor
colorWhiteTheme: MoneroComponents.Style._w_dimmedFontColor
Layout.fillWidth: true
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
horizontalAlignment: TextInput.AlignLeft
wrapMode: Text.WordWrap;
leftPadding: 0
topPadding: 0
width: parent.width - (icon.width + icon.anchors.leftMargin + anchors.leftMargin)
}
}
MoneroComponents.StandardButton {
id: button
small: true
Rectangle {
id: bottomDivider
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: MoneroComponents.Style.dividerColor
opacity: MoneroComponents.Style.dividerOpacity
visible: settingsListItem.isLast
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
hoverEnabled: true
onEntered: root.color = MoneroComponents.Style.titleBarButtonHoverColor
onExited: root.color = "transparent"
onClicked: {
settingsListItem.clicked()
}
width: 135
}
}
}

68
components/Slider.qml Normal file
View File

@@ -0,0 +1,68 @@
import QtQuick 2.9
import QtQuick.Controls 2.0 as QtQuickControls
import QtQuick.Layouts 1.1
import "../components" as MoneroComponents
ColumnLayout {
property alias from: slider.from
property alias stepSize: slider.stepSize
property alias to: slider.to
property alias value: slider.value
property alias text: label.text
signal moved()
spacing: 0
Text {
id: label
color: MoneroComponents.Style.defaultFontColor
font.pixelSize: 14
Layout.fillWidth: true
}
QtQuickControls.Slider {
id: slider
leftPadding: 0
snapMode: QtQuickControls.Slider.SnapAlways
background: Rectangle {
x: parent.leftPadding
y: parent.topPadding + parent.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 4
width: parent.availableWidth
height: implicitHeight
radius: 2
color: MoneroComponents.Style.progressBarBackgroundColor
Rectangle {
width: parent.visualPosition * parent.width
height: parent.height
color: MoneroComponents.Style.green
radius: 2
}
}
handle: Rectangle {
x: parent.leftPadding + parent.visualPosition * (parent.availableWidth - width)
y: parent.topPadding + parent.availableHeight / 2 - height / 2
implicitWidth: 18
implicitHeight: 18
radius: 8
color: parent.pressed ? "#f0f0f0" : "#f6f6f6"
border.color: MoneroComponents.Style.grey
}
onMoved: parent.moved()
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
}
}
}

View File

@@ -33,11 +33,17 @@ import "../components" as MoneroComponents
Item {
id: button
property bool primary: true
property string rightIcon: ""
property string rightIconInactive: ""
property string textColor: button.enabled? MoneroComponents.Style.buttonTextColor: MoneroComponents.Style.buttonTextColorDisabled
property color textColor: !button.enabled
? MoneroComponents.Style.buttonTextColorDisabled
: primary
? MoneroComponents.Style.buttonTextColor
: MoneroComponents.Style.buttonSecondaryTextColor;
property bool small: false
property alias text: label.text
property alias fontBold: label.font.bold
property int fontSize: {
if(small) return 14;
else return 16;
@@ -70,7 +76,9 @@ Item {
when: buttonArea.containsMouse || button.focus
PropertyChanges {
target: buttonRect
color: MoneroComponents.Style.buttonBackgroundColorHover
color: primary
? MoneroComponents.Style.buttonBackgroundColorHover
: MoneroComponents.Style.buttonSecondaryBackgroundColorHover
}
},
State {
@@ -78,7 +86,9 @@ Item {
when: button.enabled
PropertyChanges {
target: buttonRect
color: MoneroComponents.Style.buttonBackgroundColor
color: primary
? MoneroComponents.Style.buttonBackgroundColor
: MoneroComponents.Style.buttonSecondaryBackgroundColor
}
},
State {

View File

@@ -90,6 +90,10 @@ Rectangle {
function close() {
root.visible = false;
// reset button text
okButton.text = qsTr("OK")
cancelButton.text = qsTr("Cancel")
closeCallback();
}

View File

@@ -58,11 +58,6 @@ Item {
onExpandedChanged: if(expanded) appWindow.currentItem = dropdown
// Workaroud for suspected memory leak in 5.8 causing malloc crash on app exit
function update() {
firstColText.text = columnid.currentIndex < repeater.model.rowCount() ? qsTr(repeater.model.get(columnid.currentIndex).column1) + translationManager.emptyString : ""
}
Item {
id: head
anchors.left: parent.left
@@ -80,15 +75,17 @@ Item {
}
MoneroComponents.TextPlain {
id: firstColText
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: dropIndicator.left
anchors.rightMargin: 12
elide: Text.ElideRight
font.family: MoneroComponents.Style.fontRegular.name
font.bold: dropdown.headerFontBold
font.pixelSize: dropdown.fontHeaderSize
color: dropdown.textColor
text: columnid.currentIndex < repeater.model.count ? qsTr(repeater.model.get(columnid.currentIndex).column1) + translationManager.emptyString : ""
}
Item {
@@ -96,7 +93,8 @@ Item {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 32
anchors.rightMargin: 12
width: dropdownIcon.width
Image {
id: dropdownIcon
@@ -214,7 +212,6 @@ Item {
popup.close()
columnid.currentIndex = index
changed();
dropdown.update()
}
}
}

View File

@@ -29,6 +29,7 @@ QtObject {
property string textSelectedColor: blackTheme ? _b_textSelectedColor : _w_textSelectedColor
property string inputBoxBackground: blackTheme ? _b_inputBoxBackground : _w_inputBoxBackground
property string inputBoxBackgroundDisabled: blackTheme ? _b_inputBoxBackgroundDisabled : _w_inputBoxBackgroundDisabled
property string inputBoxBackgroundError: blackTheme ? _b_inputBoxBackgroundError : _w_inputBoxBackgroundError
property string inputBoxColor: blackTheme ? _b_inputBoxColor : _w_inputBoxColor
property string legacy_placeholderFontColor: blackTheme ? _b_legacy_placeholderFontColor : _w_legacy_placeholderFontColor
@@ -43,6 +44,9 @@ QtObject {
property string buttonInlineBackgroundColor: blackTheme ? _b_buttonInlineBackgroundColor : _w_buttonInlineBackgroundColor
property string buttonTextColor: blackTheme ? _b_buttonTextColor : _w_buttonTextColor
property string buttonTextColorDisabled: blackTheme ? _b_buttonTextColorDisabled : _w_buttonTextColorDisabled
property string buttonSecondaryBackgroundColor: "#d9d9d9"
property string buttonSecondaryBackgroundColorHover: "#a6a6a6"
property string buttonSecondaryTextColor: "#4d4d4d"
property string dividerColor: blackTheme ? _b_dividerColor : _w_dividerColor
property real dividerOpacity: blackTheme ? _b_dividerOpacity : _w_dividerOpacity
@@ -85,6 +89,7 @@ QtObject {
property string _b_textSelectedColor: "white"
property string _b_inputBoxBackground: "black"
property string _b_inputBoxBackgroundDisabled: Qt.rgba(255, 255, 255, 0.10)
property string _b_inputBoxBackgroundError: "#FFDDDD"
property string _b_inputBoxColor: "white"
property string _b_legacy_placeholderFontColor: "#BABABA"
@@ -141,6 +146,7 @@ QtObject {
property string _w_textSelectedColor: "black"
property string _w_inputBoxBackground: "white"
property string _w_inputBoxBackgroundDisabled: Qt.rgba(0, 0, 0, 0.20)
property string _w_inputBoxBackgroundError: "#FFDDDD"
property string _w_inputBoxColor: "black"
property string _w_legacy_placeholderFontColor: "#BABABA"

View File

@@ -309,8 +309,8 @@ Rectangle {
width: 16
image: MoneroComponents.Style.titleBarCloseSource
color: MoneroComponents.Style.defaultFontColor
fontAwesomeFallbackIcon: FontAwesome.timesRectangle
fontAwesomeFallbackSize: 18
fontAwesomeFallbackIcon: FontAwesome.times
fontAwesomeFallbackSize: 21
fontAwesomeFallbackOpacity: MoneroComponents.Style.blackTheme ? 0.8 : 0.6
opacity: 0.75
}

203
components/UpdateDialog.qml Normal file
View File

@@ -0,0 +1,203 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.1
import moneroComponents.Downloader 1.0
import "../components" as MoneroComponents
Popup {
id: updateDialog
property bool active: false
property bool allowed: true
property string error: ""
property string filename: ""
property string hash: ""
property double progress: url && downloader.total > 0 ? downloader.loaded * 100 / downloader.total : 0
property string url: ""
property bool valid: false
property string version: ""
background: Rectangle {
border.color: MoneroComponents.Style.appWindowBorderColor
border.width: 1
color: MoneroComponents.Style.middlePanelBackgroundColor
}
closePolicy: Popup.NoAutoClose
padding: 20
visible: active && allowed
function show(version, url, hash) {
updateDialog.error = "";
updateDialog.hash = hash;
updateDialog.url = url;
updateDialog.valid = false;
updateDialog.version = version;
updateDialog.active = true;
}
ColumnLayout {
id: mainLayout
spacing: updateDialog.padding
Text {
color: MoneroComponents.Style.defaultFontColor
font.bold: true
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 18
text: qsTr("New Monero version v%1 is available.").arg(updateDialog.version)
}
Text {
id: errorText
color: "red"
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 18
text: updateDialog.error
visible: text
}
Text {
id: statusText
color: updateDialog.valid ? MoneroComponents.Style.green : MoneroComponents.Style.defaultFontColor
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 18
visible: !errorText.visible
text: {
if (!updateDialog.url) {
return qsTr("Please visit getmonero.org for details") + translationManager.emptyString;
}
if (downloader.active) {
return "%1 (%2%)"
.arg(qsTr("Downloading"))
.arg(updateDialog.progress.toFixed(1))
+ translationManager.emptyString;
}
if (updateDialog.valid) {
return qsTr("Update downloaded, signature verified") + translationManager.emptyString;
}
return qsTr("Do you want to download and verify new version?") + translationManager.emptyString;
}
}
Rectangle {
id: progressBar
color: MoneroComponents.Style.lightGreyFontColor
height: 3
Layout.fillWidth: true
visible: updateDialog.valid || downloader.active
Rectangle {
color: MoneroComponents.Style.buttonBackgroundColor
height: parent.height
width: parent.width * updateDialog.progress / 100
}
}
RowLayout {
Layout.alignment: Qt.AlignRight
spacing: parent.spacing
MoneroComponents.StandardButton {
id: cancelButton
fontBold: false
primary: !updateDialog.url
text: {
if (!updateDialog.url) {
return qsTr("Ok") + translationManager.emptyString;
}
if (updateDialog.valid || downloader.active || errorText.visible) {
return qsTr("Cancel") + translationManager.emptyString;
}
return qsTr("Download later") + translationManager.emptyString;
}
onClicked: {
downloader.cancel();
updateDialog.active = false;
}
}
MoneroComponents.StandardButton {
id: downloadButton
KeyNavigation.tab: cancelButton
fontBold: false
text: (updateDialog.error ? qsTr("Retry") : qsTr("Download")) + translationManager.emptyString
visible: updateDialog.url && !updateDialog.valid && !downloader.active
onClicked: {
updateDialog.error = "";
updateDialog.filename = updateDialog.url.replace(/^.*\//, '');
const downloadingStarted = downloader.get(updateDialog.url, updateDialog.hash, function(error) {
if (error) {
console.error("Download failed", error);
updateDialog.error = qsTr("Download failed") + translationManager.emptyString;
} else {
updateDialog.valid = true;
}
});
if (!downloadingStarted) {
updateDialog.error = qsTr("Failed to start download") + translationManager.emptyString;
}
}
}
MoneroComponents.StandardButton {
id: saveButton
KeyNavigation.tab: cancelButton
fontBold: false
onClicked: {
const fullPath = oshelper.openSaveFileDialog(
qsTr("Save as") + translationManager.emptyString,
oshelper.downloadLocation(),
updateDialog.filename);
if (!fullPath) {
return;
}
if (downloader.saveToFile(fullPath)) {
cancelButton.clicked();
oshelper.openContainingFolder(fullPath);
} else {
updateDialog.error = qsTr("Save operation failed") + translationManager.emptyString;
}
}
text: qsTr("Save to file") + translationManager.emptyString
visible: updateDialog.valid
}
}
}
Downloader {
id: downloader
}
}

View File

@@ -149,6 +149,7 @@ Object {
property string caretUp : "\uf0d8"
property string cartArrowDown : "\uf218"
property string cartPlus : "\uf217"
property string cashRegister: "\uf788"
property string cc : "\uf20a"
property string ccAmex : "\uf1f3"
property string ccDinersClub : "\uf24c"

View File

@@ -17,7 +17,7 @@ if [ ! -d $MONERO_DIR/src ]; then
fi
git submodule update --remote
git -C $MONERO_DIR fetch
git -C $MONERO_DIR checkout v0.15.0.1
git -C $MONERO_DIR checkout v0.16.0.1
# get monero core tag
pushd $MONERO_DIR

BIN
images/busy-indicator.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 B

2
images/monero-vector.svg Normal file
View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 6000 6000" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><defs><clipPath id="a"><path d="m0 4500h4500v-4500h-4500z"/></clipPath></defs><g transform="matrix(1.3333 0 0 -1.3333 0 6e3)"><g clip-path="url(#a)"><g transform="translate(4128 2250.2)"><path d="m0 0c0-1037.2-840.79-1878.1-1878.1-1878.1-1037.2 0-1878 840.88-1878 1878.1 0 1037.3 840.8 1878.1 1878 1878.1 1037.3 0 1878.1-840.79 1878.1-1878.1" fill="#fff"/></g><g transform="translate(2250 4128.2)"><path d="m0 0c-1036.9 0-1879.1-842.06-1877.8-1878 0.262-207.26 33.308-406.63 95.342-593.12h561.88v1579.9l1220.6-1220.6 1220.6 1220.6v-1579.9h561.96c62.117 186.48 95.008 385.85 95.369 593.12 1.809 1037-840.89 1877.8-1877.9 1877.8z" fill="#f36e36"/></g><g transform="translate(1969.3 1735.8)"><path d="m0 0-532.67 532.7v-994.14h-407.26l-384.29-0.07c329.63-540.8 925.35-902.56 1604.9-902.56 679.54 0 1275.3 361.85 1605 902.65l-384.44-0.013h-407.27v994.14l-813.3-813.31-280.62 280.61z" fill="#575757"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 B

View File

@@ -1,8 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="18" viewBox="0 0 10 18">
<g fill="none" fill-rule="evenodd" opacity="1">
<path fill="none" d="M-13-9h36v36h-36z" opacity="1"/>
<g fill="#000" fill-rule="nonzero">
<path d="M5 0C3.75 0 2.571.468 1.643 1.296A5.057 5.057 0 0 0 0 5.04c0 .396.321.72.714.72h.715a.72.72 0 0 0 .714-.72c0-.828.357-1.584.964-2.16A2.823 2.823 0 0 1 5 2.16c.107 0 .214 0 .321.036 1.322.144 2.358 1.224 2.5 2.52.143 1.188-.464 2.304-1.5 2.88-1.5.792-2.428 2.304-2.428 3.96v2.124c0 .396.321.72.714.72h.714a.72.72 0 0 0 .715-.72v-2.124c0-.828.5-1.62 1.285-2.052A4.98 4.98 0 0 0 9.93 4.5C9.714 2.16 7.857.288 5.57.036 5.393 0 5.18 0 5 0zM5.714 18H4.286a.358.358 0 0 1-.357-.36V16.2c0-.2.16-.36.357-.36h1.428c.198 0 .357.16.357.36v1.44c0 .2-.16.36-.357.36z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 848 B

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014-2019, The Monero Project
Copyright (c) 2014-2020, The Monero Project
All rights reserved.

View File

@@ -1,5 +1,5 @@
; Monero Carbon Chamaeleon GUI Wallet Installer for Windows
; Copyright (c) 2017-2019, The Monero Project
; Monero Nitrogen Nebula GUI Wallet Installer for Windows
; Copyright (c) 2017-2020, The Monero Project
; See LICENSE
#define GuiVersion GetFileVersion("bin\monero-wallet-gui.exe")
@@ -11,7 +11,7 @@ AppName=Monero GUI Wallet
AppVersion={#GuiVersion}
VersionInfoVersion={#GuiVersion}
DefaultDirName={pf}\Monero GUI Wallet
DefaultDirName={commonpf}\Monero GUI Wallet
DefaultGroupName=Monero GUI Wallet
UninstallDisplayIcon={app}\monero-wallet-gui.exe
PrivilegesRequired=admin
@@ -62,7 +62,6 @@ Name: "en"; MessagesFile: "compiler:Default.isl"
; .exe/.dll file possibly with version info).
;
; This is far more robust than relying on version info or on file dates (flag "comparetimestamp").
; As of version 0.15.0.0, the Monero .exe files do not carry version info anyway in their .exe headers.
; The only small drawback seems to be somewhat longer update times because each and every file is
; copied again, even if already present with correct file date and identical content.
;
@@ -71,17 +70,18 @@ Name: "en"; MessagesFile: "compiler:Default.isl"
Source: {#file AddBackslash(SourcePath) + "ReadMe.htm"}; DestDir: "{app}"; DestName: "ReadMe.htm"; Flags: ignoreversion
Source: "FinishImage.bmp"; Flags: dontcopy
Source: "LICENSE"; DestDir: "{app}"; Flags: ignoreversion
; Monero GUI wallet exe and guide
Source: "bin\monero-wallet-gui.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-gui-wallet-guide.pdf"; DestDir: "{app}"; Flags: ignoreversion
; Monero CLI wallet
Source: "bin\monero-wallet-cli.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-gen-trusted-multisig.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-wallet-cli.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-gen-trusted-multisig.exe"; DestDir: "{app}"; Flags: ignoreversion
; Monero wallet RPC interface implementation
Source: "bin\monero-wallet-rpc.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-wallet-rpc.exe"; DestDir: "{app}"; Flags: ignoreversion
; Monero daemon
Source: "bin\monerod.exe"; DestDir: "{app}"; Flags: ignoreversion
@@ -90,16 +90,17 @@ Source: "bin\monerod.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "monero-daemon.bat"; DestDir: "{app}"; Flags: ignoreversion;
; Monero blockchain utilities
Source: "bin\monero-blockchain-export.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-blockchain-import.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-blockchain-mark-spent-outputs.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-blockchain-usage.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-blockchain-import.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-blockchain-ancestry.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-blockchain-depth.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-blockchain-prune-known-spent-data.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-blockchain-prune.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\monero-blockchain-stats.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-export.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-import.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-mark-spent-outputs.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-usage.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-import.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-ancestry.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-depth.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-prune-known-spent-data.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-prune.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-blockchain-stats.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\extras\monero-gen-ssl-cert.exe"; DestDir: "{app}"; Flags: ignoreversion
; Qt Quick 2D Renderer fallback for systems / environments with "low-level graphics" i.e. without 3D support
Source: "bin\start-low-graphics-mode.bat"; DestDir: "{app}"; Flags: ignoreversion
@@ -202,7 +203,7 @@ begin
// Additional wizard page for entering a special blockchain location
blockChainDefaultDir := ExpandConstant('{commonappdata}\bitmonero');
s := 'The default folder to store the Monero blockchain is ' + blockChainDefaultDir;
s := s + '. As this will need more than 74 GB of free space, you may want to use a folder on a different drive.';
s := s + '. As this will need more than 90 GB of free space, you may want to use a folder on a different drive.';
s := s + ' If yes, specify that folder here.';
BlockChainDirPage := CreateInputDirPage(wpSelectDir,

View File

@@ -1,12 +1,12 @@
# Monero GUI Wallet Windows Installer #
Copyright (c) 2017-2019, The Monero Project
Copyright (c) 2017-2020, The Monero Project
## Introduction ##
This is a *Inno Setup* script `Monero.iss` plus some related files
that allows you to build a standalone Windows installer (.exe) for
the GUI wallet that comes with the Carbon Chamaeleon release of Monero.
the GUI wallet that comes with the Nitrogen Nebula release of Monero.
This turns the GUI wallet into a more or less standard Windows program,
by default installed into a subdirectory of `C:\Program Files`, a
@@ -18,7 +18,7 @@ Monero.
As the setup script in file [Monero.iss](Monero.iss) has to list many
files and directories of the GUI wallet package to install by name,
this version of the script only works with exactly the GUI wallet
for Monero release *Carbon Chamaeleon* that you find on
for Monero release *Nitrogen Nebula* that you find on
[the official download page](https://getmonero.org/downloads/).
It should however be easy to modify the script for future
@@ -32,15 +32,15 @@ See [LICENSE](LICENSE).
You can only build on Windows, and the result is always a
Windows .exe file that can act as a standalone installer for the
Carbon Chamaeleon GUI wallet.
Nitrogen Nebula GUI wallet.
Note that the installer build process is now reproducible / deterministic. For details check the file [Deterministic.md](Deterministic.md).
The build steps in detail:
1. Install *Inno Setup*. You can get it from [here](http://www.jrsoftware.org/isdl.php)
2. Get the Inno Setup script plus related files by cloning the whole [monero-gui GitHub repository](https://github.com/monero-project/monero-gui); you will only need the files in the installer directory `installers\windows` however. Depending on development state, additionally instead of simply using `master` you may have to checkout a specific branch, like `release-v0.15`.
3. The setup script is written to take the GUI wallet files from a subdirectory named `bin`; so create `installers\windows\bin`, get the zip file of the GUI wallet from [here](https://getmonero.org/downloads/), unpack it somewhere, and copy all the files and subdirectories in the single subdirectory there (currently named `monero-gui-0.15.0.0`) to this `bin` subdirectory
2. Get the Inno Setup script plus related files by cloning the whole [monero-gui GitHub repository](https://github.com/monero-project/monero-gui); you will only need the files in the installer directory `installers\windows` however. Depending on development state, additionally instead of simply using `master` you may have to checkout a specific branch, like `release-v0.16`.
3. The setup script is written to take the GUI wallet files from a subdirectory named `bin`; so create `installers\windows\bin`, get the zip file of the GUI wallet from [here](https://getmonero.org/downloads/), unpack it somewhere, and copy all the files and subdirectories in the single subdirectory there (currently named `monero-gui-0.16.0.0`) to this `bin` subdirectory
4. Start Inno Setup, load `Monero.iss` and compile it
5. The result i.e. the finished installer will be the file `mysetup.exe` in the `installers\windows\Output` subdirectory

View File

@@ -1,12 +1,12 @@
<html>
<head>
<title>Monero Carbon Chamaeleon GUI Wallet</title>
<title>Monero Nitrogen Nebula GUI Wallet</title>
</head>
<body style="font-family: Arial, Helvetica, sans-serif">
<h1>Monero Carbon Chamaeleon GUI Wallet</h1>
<h1>Monero Nitrogen Nebula GUI Wallet</h1>
<p>Copyright (c) 2014-2019, The Monero Project</p>
<p>Copyright (c) 2014-2020, The Monero Project</p>
<h2>Preface</h2>
@@ -22,7 +22,7 @@
<h2>Content of the Package</h2>
<p>You just installed the <i>Monero GUI wallet</i> for Windows, release Carbon Chamaeleon, version {#GuiVersion}.
<p>You just installed the <i>Monero GUI wallet</i> for Windows, release Nitrogen Nebula, version {#GuiVersion}.
The wallet enables you to send and receive Moneroj in a secure and very private way.
</p>
@@ -60,7 +60,7 @@
provides the most security and privacy possible for you.</p>
<p>However if your Internet access makes it difficult to run a full node, or if you have simply no room to store
the blockchain locally (somewhat over 74 GB in November 2019, and of course growing), you can compromise and try to connect
the blockchain locally (about 90 GB in May 2020, and of course growing), you can compromise and try to connect
to a remote node. One way of finding such a node is checking
<a href="https://moneroworld.com/#nodes">this page</a>.
</p>
@@ -104,7 +104,7 @@
<p>The Monero software and especially the GUI wallet are "work in progress", and sometimes things go wrong.</p>
<p>Please note that despite any technical problems that you may encounter your moneroj are almost always safe: You may
<p>Please note that despite any technical problems that you may encounter your Moneroj are almost always safe: You may
not be able to move them or you even may not see how many you currently have, but you most probably won't loose any.
But do remember that the seed needed to re-create the wallet <b>is</b> critical, however: <b>Never loose your
seed!</b></p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 440 KiB

View File

@@ -133,3 +133,7 @@ function capitalize(s){
if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1)
}
function removeTrailingZeros(value) {
return (value + '').replace(/(\.\d*[1-9])0+$/, '$1');
}

View File

@@ -58,11 +58,6 @@ function switchPage(next) {
}
function createWalletPath(isIOS, folder_path,account_name){
// Remove trailing slash - (default on windows and mac)
if (folder_path.substring(folder_path.length -1) === "/"){
folder_path = folder_path.substring(0,folder_path.length -1)
}
// Store releative path on ios.
if(isIOS)
folder_path = "";
@@ -102,10 +97,6 @@ function tr(text) {
return qsTr(text) + translationManager.emptyString
}
function lineBreaksToSpaces(text) {
return text.trim().replace(/(\r\n|\n|\r)/gm, " ");
}
function usefulName(path) {
// arbitrary "short enough" limit
if (path.length < 32)
@@ -115,7 +106,7 @@ function usefulName(path) {
function checkSeed(seed) {
console.log("Checking seed")
var wordsArray = lineBreaksToSpaces(seed).split(" ");
var wordsArray = seed.split(/\s+/);
return wordsArray.length === 25 || wordsArray.length === 24
}

316
main.qml
View File

@@ -45,6 +45,7 @@ import "pages/merchant" as MoneroMerchant
import "wizard"
import "js/Utils.js" as Utils
import "js/Windows.js" as Windows
import "version.js" as Version
ApplicationWindow {
id: appWindow
@@ -66,12 +67,14 @@ ApplicationWindow {
property bool walletSynced: false
property int maxWindowHeight: (isAndroid || isIOS)? screenHeight : (screenHeight < 900)? 720 : 800;
property bool daemonRunning: !persistentSettings.useRemoteNode && !disconnected
property bool daemonStartStopInProgress: false
property alias toolTip: toolTip
property string walletName
property bool viewOnly: false
property bool foundNewBlock: false
property bool qrScannerEnabled: (typeof builtWithScanner != "undefined") && builtWithScanner
property int blocksToSync: 1
property int firstBlockSeen
property bool isMining: false
property int walletMode: persistentSettings.walletMode
property var cameraUi
@@ -214,7 +217,7 @@ ApplicationWindow {
appWindow.viewState = prevState;
}
};
passwordDialog.open(usefulName(walletPath()));
passwordDialog.open(usefulName(persistentSettings.wallet_path));
}
function initialize() {
@@ -250,10 +253,9 @@ ApplicationWindow {
// enable timers
userInActivityTimer.running = true;
simpleModeConnectionTimer.running = true;
// wallet already opened with wizard, we just need to initialize it
var wallet_path = walletPath();
var wallet_path = persistentSettings.wallet_path;
if(isIOS)
wallet_path = moneroAccountsDir + wallet_path;
// console.log("opening wallet at: ", wallet_path, "with password: ", appWindow.walletPassword);
@@ -291,6 +293,7 @@ ApplicationWindow {
currentWallet.connectionStatusChanged.disconnect(onWalletConnectionStatusChanged)
currentWallet.deviceButtonRequest.disconnect(onDeviceButtonRequest);
currentWallet.deviceButtonPressed.disconnect(onDeviceButtonPressed);
currentWallet.walletPassphraseNeeded.disconnect(onWalletPassphraseNeededWallet);
currentWallet.transactionCommitted.disconnect(onTransactionCommitted);
middlePanel.paymentClicked.disconnect(handlePayment);
middlePanel.sweepUnmixableClicked.disconnect(handleSweepUnmixable);
@@ -358,6 +361,7 @@ ApplicationWindow {
currentWallet.connectionStatusChanged.connect(onWalletConnectionStatusChanged)
currentWallet.deviceButtonRequest.connect(onDeviceButtonRequest);
currentWallet.deviceButtonPressed.connect(onDeviceButtonPressed);
currentWallet.walletPassphraseNeeded.connect(onWalletPassphraseNeededWallet);
currentWallet.transactionCommitted.connect(onTransactionCommitted);
middlePanel.paymentClicked.connect(handlePayment);
middlePanel.sweepUnmixableClicked.connect(handleSweepUnmixable);
@@ -392,11 +396,6 @@ ApplicationWindow {
return !persistentSettings.useRemoteNode || persistentSettings.is_trusted_daemon;
}
function walletPath() {
var wallet_path = persistentSettings.wallet_path
return wallet_path;
}
function usefulName(path) {
// arbitrary "short enough" limit
if (path.length < 32)
@@ -474,9 +473,9 @@ ApplicationWindow {
console.log("Wallet connection status changed " + status)
middlePanel.updateStatus();
leftPanel.networkStatus.connected = status
// Update fee multiplier dropdown on transfer page
middlePanel.transferView.updatePriorityDropdown();
if (status == Wallet.ConnectionStatus_Disconnected) {
firstBlockSeen = 0;
}
// If wallet isnt connected, advanced wallet mode and no daemon is running - Ask
if (appWindow.walletMode >= 2 && !persistentSettings.useRemoteNode && !walletInitialized && disconnected) {
@@ -558,19 +557,32 @@ ApplicationWindow {
}
}
function onWalletPassphraseNeeded(){
function onWalletPassphraseNeededManager(on_device){
onWalletPassphraseNeeded(walletManager, on_device)
}
function onWalletPassphraseNeededWallet(on_device){
onWalletPassphraseNeeded(currentWallet, on_device)
}
function onWalletPassphraseNeeded(handler, on_device){
hideProcessingSplash();
console.log(">>> wallet passphrase needed: ")
passwordDialog.onAcceptedPassphraseCallback = function() {
walletManager.onPassphraseEntered(passwordDialog.password);
devicePassphraseDialog.onAcceptedCallback = function(passphrase) {
handler.onPassphraseEntered(passphrase, false, false);
appWindow.onWalletOpening();
}
passwordDialog.onRejectedPassphraseCallback = function() {
walletManager.onPassphraseEntered("", true);
devicePassphraseDialog.onWalletEntryCallback = function() {
handler.onPassphraseEntered("", true, false);
appWindow.onWalletOpening();
}
passwordDialog.openPassphraseDialog()
devicePassphraseDialog.onRejectedCallback = function() {
handler.onPassphraseEntered("", false, true);
appWindow.onWalletOpening();
}
devicePassphraseDialog.open(on_device)
}
function onWalletUpdate() {
@@ -613,18 +625,22 @@ ApplicationWindow {
currentDaemonAddress = localDaemonAddress
currentWallet.initAsync(currentDaemonAddress, isTrustedDaemon());
walletManager.setDaemonAddressAsync(currentDaemonAddress);
firstBlockSeen = 0;
}
function onHeightRefreshed(bcHeight, dCurrentBlock, dTargetBlock) {
// Daemon fully synced
// TODO: implement onDaemonSynced or similar in wallet API and don't start refresh thread before daemon is synced
// targetBlock = currentBlock = 1 before network connection is established.
if (firstBlockSeen == 0 && dTargetBlock != 1) {
firstBlockSeen = dCurrentBlock;
}
daemonSynced = dCurrentBlock >= dTargetBlock && dTargetBlock != 1
walletSynced = bcHeight >= dTargetBlock
// Update progress bars
if(!daemonSynced) {
leftPanel.daemonProgressBar.updateProgress(dCurrentBlock,dTargetBlock, dTargetBlock-dCurrentBlock);
leftPanel.daemonProgressBar.updateProgress(dCurrentBlock,dTargetBlock, dTargetBlock-firstBlockSeen);
leftPanel.progressBar.updateProgress(0,dTargetBlock, dTargetBlock, qsTr("Waiting for daemon to sync"));
} else {
leftPanel.daemonProgressBar.updateProgress(dCurrentBlock,dTargetBlock, 0, qsTr("Daemon is synchronized (%1)").arg(dCurrentBlock.toFixed(0)));
@@ -665,12 +681,11 @@ ApplicationWindow {
}
function startDaemon(flags){
daemonStartStopInProgress = true;
// Pause refresh while starting daemon
currentWallet.pauseRefresh();
// Pause simplemode connection timer
simpleModeConnectionTimer.stop();
appWindow.showProcessingSplash(qsTr("Waiting for daemon to start..."))
const noSync = appWindow.walletMode === 0;
const bootstrapNodeAddress = persistentSettings.walletMode < 2 ? "auto" : persistentSettings.bootstrapNodeAddress
@@ -678,8 +693,10 @@ ApplicationWindow {
}
function stopDaemon(callback){
daemonStartStopInProgress = true;
appWindow.showProcessingSplash(qsTr("Waiting for daemon to stop..."))
daemonManager.stopAsync(persistentSettings.nettype, function(result) {
daemonStartStopInProgress = false;
hideProcessingSplash();
callback(result);
});
@@ -687,13 +704,13 @@ ApplicationWindow {
function onDaemonStarted(){
console.log("daemon started");
daemonStartStopInProgress = false;
hideProcessingSplash();
currentWallet.connected(true);
// resume refresh
currentWallet.startRefresh();
// resume simplemode connection timer
appWindow.disconnectedEpoch = Utils.epoch();
simpleModeConnectionTimer.start();
}
function onDaemonStopped(){
currentWallet.connected(true);
@@ -701,6 +718,7 @@ ApplicationWindow {
function onDaemonStartFailure(error) {
console.log("daemon start failed");
daemonStartStopInProgress = false;
hideProcessingSplash();
// resume refresh
currentWallet.startRefresh();
@@ -976,7 +994,11 @@ ApplicationWindow {
informationPopup.open()
currentWallet.refresh()
currentWallet.disposeTransaction(transaction)
currentWallet.store();
currentWallet.storeAsync(function(success) {
if (!success) {
appWindow.showStatusMessage(qsTr("Failed to store the wallet"), 3);
}
});
}
// called on "getProof"
@@ -1076,7 +1098,6 @@ ApplicationWindow {
console.log("Displaying processing splash")
if (typeof message != 'undefined') {
splash.messageText = message
splash.heightProgressText = ""
}
leftPanel.enabled = false;
@@ -1103,14 +1124,15 @@ ApplicationWindow {
wizard.restart();
wizard.wizardState = "wizardHome";
rootItem.state = "wizard"
// reset balance
// reset balance, clear spendable funds message
clearMoneroCardLabelText();
leftPanel.minutesToUnlock = "";
// reset fields
middlePanel.addressBookView.clearFields();
middlePanel.transferView.clearFields();
middlePanel.receiveView.clearFields();
// disable timers
userInActivityTimer.running = false;
simpleModeConnectionTimer.running = false;
});
}
@@ -1135,9 +1157,9 @@ ApplicationWindow {
triggeredOnStart: false
}
function fiatApiParseTicker(resp, currency){
function fiatApiParseTicker(url, resp, currency){
// parse & validate incoming JSON
if(resp._url.startsWith("https://api.kraken.com/0/")){
if(url.startsWith("https://api.kraken.com/0/")){
if(resp.hasOwnProperty("error") && resp.error.length > 0 || !resp.hasOwnProperty("result")){
appWindow.fiatApiError("Kraken API has error(s)");
return;
@@ -1146,14 +1168,14 @@ ApplicationWindow {
var key = currency === "xmreur" ? "XXMRZEUR" : "XXMRZUSD";
var ticker = resp.result[key]["o"];
return ticker;
} else if(resp._url.startsWith("https://api.coingecko.com/api/v3/")){
} else if(url.startsWith("https://api.coingecko.com/api/v3/")){
var key = currency === "xmreur" ? "eur" : "usd";
if(!resp.hasOwnProperty("monero") || !resp["monero"].hasOwnProperty(key)){
appWindow.fiatApiError("Coingecko API has error(s)");
return;
}
return resp["monero"][key];
} else if(resp._url.startsWith("https://min-api.cryptocompare.com/data/")){
} else if(url.startsWith("https://min-api.cryptocompare.com/data/")){
var key = currency === "xmreur" ? "EUR" : "USD";
if(!resp.hasOwnProperty(key)){
appWindow.fiatApiError("cryptocompare API has error(s)");
@@ -1163,13 +1185,7 @@ ApplicationWindow {
}
}
function fiatApiGetCurrency(resp){
// map response to `appWindow.fiatPriceAPIs` object
if (!resp.hasOwnProperty('_url')){
appWindow.fiatApiError("invalid JSON");
return;
}
function fiatApiGetCurrency(url) {
var apis = appWindow.fiatPriceAPIs;
for (var api in apis){
if (!apis.hasOwnProperty(api))
@@ -1179,23 +1195,34 @@ ApplicationWindow {
if(!apis[api].hasOwnProperty(cur))
continue;
var url = apis[api][cur];
if(url === resp._url){
if (apis[api][cur] === url) {
return cur;
}
}
}
}
function fiatApiJsonReceived(resp){
function fiatApiJsonReceived(url, resp, error) {
if (error) {
appWindow.fiatApiError(error);
return;
}
try {
resp = JSON.parse(resp);
} catch (e) {
appWindow.fiatApiError("bad JSON: " + e);
return;
}
// handle incoming JSON, set ticker
var currency = appWindow.fiatApiGetCurrency(resp);
var currency = appWindow.fiatApiGetCurrency(url);
if(typeof currency == "undefined"){
appWindow.fiatApiError("could not get currency");
return;
}
var ticker = appWindow.fiatApiParseTicker(resp, currency);
var ticker = appWindow.fiatApiParseTicker(url, resp, currency);
if(ticker <= 0){
appWindow.fiatApiError("could not get ticker");
return;
@@ -1227,7 +1254,7 @@ ApplicationWindow {
}
var url = provider[userCurrency];
Prices.getJSON(url);
Network.getJSON(url, fiatApiJsonReceived);
}
function fiatApiCurrencySymbol() {
@@ -1283,9 +1310,8 @@ ApplicationWindow {
walletManager.deviceButtonRequest.connect(onDeviceButtonRequest);
walletManager.deviceButtonPressed.connect(onDeviceButtonPressed);
walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete);
walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeeded);
walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeededManager);
IPC.uriHandler.connect(onUriHandler);
Prices.priceJsonReceived.connect(appWindow.fiatApiJsonReceived);
if(typeof daemonManager != "undefined") {
daemonManager.daemonStarted.connect(onDaemonStarted);
@@ -1317,8 +1343,6 @@ ApplicationWindow {
openWallet("wizard");
}
checkUpdates();
if(persistentSettings.fiatPriceEnabled){
appWindow.fiatApiRefresh();
appWindow.fiatTimerStart();
@@ -1363,10 +1387,14 @@ ApplicationWindow {
property int segregationHeight: 0
property int kdfRounds: 1
property bool hideBalance: false
property bool askPasswordBeforeSending: true
property bool lockOnUserInActivity: true
property int walletMode: 2
property int lockOnUserInActivityInterval: 10 // minutes
property bool blackTheme: true
property bool checkForUpdates: true
property bool autosave: true
property int autosaveMinutes: 10
property bool fiatPriceEnabled: false
property bool fiatPriceToggle: false
@@ -1398,21 +1426,28 @@ ApplicationWindow {
z: parent.z + 1
id: transactionConfirmationPopup
onAccepted: {
var handleAccepted = function() {
// Save transaction to file if view only wallet
if (viewOnly) {
saveTxDialog.open();
} else {
handleTransactionConfirmed()
}
}
close();
passwordDialog.onAcceptedCallback = function() {
if(walletPassword === passwordDialog.password){
// Save transaction to file if view only wallet
if(viewOnly) {
saveTxDialog.open();
} else {
handleTransactionConfirmed()
}
handleAccepted()
} else {
passwordDialog.showError(qsTr("Wrong password") + translationManager.emptyString);
}
}
passwordDialog.onRejectedCallback = null;
passwordDialog.open()
if(!persistentSettings.askPasswordBeforeSending) {
handleAccepted()
} else {
passwordDialog.open()
}
}
}
@@ -1432,6 +1467,14 @@ ApplicationWindow {
}
}
MoneroComponents.UpdateDialog {
id: updateDialog
allowed: !passwordDialog.visible && !inputDialog.visible && !splash.visible
x: (parent.width - width) / 2
y: (parent.height - height) / 2
}
// Choose blockchain folder
FileDialog {
id: blockchainFileDialog
@@ -1461,7 +1504,6 @@ ApplicationWindow {
confirmationDialog.text += qsTr("Note: lmdb folder not found. A new folder will be created.") + "\n\n"
confirmationDialog.icon = StandardIcon.Question
confirmationDialog.cancelText = qsTr("Cancel")
// Continue
confirmationDialog.onAcceptedCallback = function() {
@@ -1485,8 +1527,6 @@ ApplicationWindow {
anchors.fill: parent
property var onAcceptedCallback
property var onRejectedCallback
property var onAcceptedPassphraseCallback
property var onRejectedPassphraseCallback
onAccepted: {
if (onAcceptedCallback)
onAcceptedCallback();
@@ -1510,14 +1550,13 @@ ApplicationWindow {
informationPopup.open();
}
onRejectedNewPassword: {}
onAcceptedPassphrase: {
if (onAcceptedPassphraseCallback)
onAcceptedPassphraseCallback();
}
onRejectedPassphrase: {
if (onRejectedPassphraseCallback)
onRejectedPassphraseCallback();
}
}
DevicePassphraseDialog {
id: devicePassphraseDialog
visible: false
z: parent.z + 1
anchors.fill: parent
}
InputDialog {
@@ -1548,8 +1587,8 @@ ApplicationWindow {
ProcessingSplash {
id: splash
width: appWindow.width / 1.5
height: appWindow.height / 2
width: appWindow.width / 2
height: appWindow.height / 2.66
x: (appWindow.width - width) / 2
y: (appWindow.height - height) / 2
messageText: qsTr("Please wait...") + translationManager.emptyString
@@ -1607,12 +1646,6 @@ ApplicationWindow {
updateBalance();
}
onMerchantClicked: {
middlePanel.state = "Merchant";
middlePanel.flickable.contentY = 0;
updateBalance();
}
onTxkeyClicked: {
middlePanel.state = "TxKey";
middlePanel.flickable.contentY = 0;
@@ -1687,16 +1720,10 @@ ApplicationWindow {
anchors.fill: blurredArea
source: blurredArea
radius: 64
visible: passwordDialog.visible || inputDialog.visible || splash.visible
visible: passwordDialog.visible || inputDialog.visible || splash.visible || updateDialog.visible || devicePassphraseDialog.visible
}
WizardLang {
id: languageView
visible: false
anchors.fill: parent
}
property int minWidth: 326
property int minHeight: 400
MouseArea {
@@ -1794,16 +1821,10 @@ ApplicationWindow {
color: "#FFFFFF"
}
}
Notifier {
visible:false
id: notifier
}
}
function toggleLanguageView(){
middlePanel.visible = !middlePanel.visible;
languageView.visible = !languageView.visible
languageSidebar.isOpened ? languageSidebar.close() : languageSidebar.open();
resetLanguageFields()
// update after changing language from settings page
if (persistentSettings.language != wizard.language_language) {
@@ -1812,6 +1833,24 @@ ApplicationWindow {
}
}
Timer {
id: autosaveTimer
interval: persistentSettings.autosaveMinutes * 60 * 1000
repeat: true
running: persistentSettings.autosave
onTriggered: {
if (currentWallet) {
currentWallet.storeAsync(function(success) {
if (success) {
appWindow.showStatusMessage(qsTr("Autosaved the wallet"), 3);
} else {
appWindow.showStatusMessage(qsTr("Failed to autosave the wallet"), 3);
}
});
}
}
}
// TODO: Make the callback dynamic
Timer {
id: statusMessageTimer
@@ -1851,9 +1890,6 @@ ApplicationWindow {
}
function checkSimpleModeConnection(){
// auto-connection mechanism for simple mode
if(appWindow.walletMode >= 2) return;
const disconnectedTimeoutSec = 30;
const firstCheckDelaySec = 2;
@@ -1870,15 +1906,20 @@ ApplicationWindow {
}
if (appWindow.daemonRunning) {
appWindow.stopDaemon();
appWindow.stopDaemon(function() {
appWindow.startDaemon("")
});
} else {
appWindow.startDaemon("");
}
appWindow.startDaemon("");
}
Timer {
// Simple mode connection check timer
id: simpleModeConnectionTimer
interval: 2000; running: false; repeat: true
interval: 2000
running: appWindow.walletMode < 2 && currentWallet != undefined && !daemonStartStopInProgress
repeat: true
onTriggered: appWindow.checkSimpleModeConnection()
}
@@ -1952,14 +1993,26 @@ ApplicationWindow {
}
// If daemon is running - prompt user before exiting
if(typeof daemonManager != "undefined" && daemonRunning) {
if (appWindow.walletMode == 0) {
stopDaemon(closeAccepted);
} else {
showDaemonIsRunningDialog(closeAccepted);
}
} else {
if(daemonManager == undefined || persistentSettings.useRemoteNode) {
closeAccepted();
} else if (appWindow.walletMode == 0) {
stopDaemon(closeAccepted);
} else {
showProcessingSplash(qsTr("Checking local node status..."));
const handler = function(running) {
hideProcessingSplash();
if (running) {
showDaemonIsRunningDialog(closeAccepted);
} else {
closeAccepted();
}
};
if (currentWallet) {
handler(!currentWallet.disconnected);
} else {
daemonManager.runningAsync(persistentSettings.nettype, handler);
}
}
}
@@ -1971,34 +2024,42 @@ ApplicationWindow {
closeWallet(Qt.quit);
}
function onWalletCheckUpdatesComplete(update) {
if (update === "")
return
print("Update found: " + update)
var parts = update.split("|")
if (parts.length == 4) {
var version = parts[0]
var hash = parts[1]
var user_url = parts[2]
var msg = qsTr("New version of Monero v%1 is available.").arg(version)
if (isMac || isWindows || isLinux) {
msg += "<br><br>%1:<br>%2<br><br>%3:<br>%4".arg(qsTr("Download")).arg(user_url).arg(qsTr("SHA256 Hash")).arg(hash) + translationManager.emptyString
} else {
msg += " " + qsTr("Check out getmonero.org") + translationManager.emptyString
}
notifier.show(msg)
} else {
print("Failed to parse update spec")
function onWalletCheckUpdatesComplete(version, downloadUrl, hash, firstSigner, secondSigner) {
const alreadyAsked = updateDialog.url == downloadUrl && updateDialog.hash == hash;
if (!alreadyAsked)
{
updateDialog.show(version, isMac || isWindows || isLinux ? downloadUrl : "", hash);
}
}
function getBuildTag() {
if (isMac) {
return "mac-x64";
}
if (isWindows) {
return oshelper.installed ? "install-win-x64" : "win-x64";
}
if (isLinux) {
return "linux-x64";
}
return "source";
}
function checkUpdates() {
walletManager.checkUpdatesAsync("monero-gui", "gui")
const version = Version.GUI_VERSION.match(/\d+\.\d+\.\d+\.\d+/);
if (version) {
walletManager.checkUpdatesAsync("monero-gui", "gui", getBuildTag(), version[0]);
} else {
console.error("failed to parse version number", Version.GUI_VERSION);
}
}
Timer {
id: updatesTimer
interval: 3600*1000; running: true; repeat: true
interval: 3600 * 1000
repeat: true
running: !disableCheckUpdatesFlag && persistentSettings.checkForUpdates
triggeredOnStart: true
onTriggered: checkUpdates()
}
@@ -2078,7 +2139,7 @@ ApplicationWindow {
if (mode < 2) {
persistentSettings.useRemoteNode = false;
if (middlePanel.settingsView.settingsStateViewState === "Node" || middlePanel.settingsView.settingsStateViewState === "Log") {
if (middlePanel.settingsView.settingsStateViewState === "Node") {
middlePanel.settingsView.settingsStateViewState = "Wallet"
}
}
@@ -2098,6 +2159,11 @@ ApplicationWindow {
blackColor: "black"
whiteColor: "white"
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
}
}
// borders on white theme + linux
@@ -2165,8 +2231,8 @@ ApplicationWindow {
}
}
// @TODO: QML type 'Drawer' has issues with buildbot; debug after Qt 5.9 migration
// MoneroComponents.LanguageSidebar {
// id: languageSidebar
// }
MoneroComponents.LanguageSidebar {
id: languageSidebar
dragMargin: 0
}
}

2
monero

Submodule monero updated: 581994b61c...a498a1b4ce

View File

@@ -43,7 +43,9 @@ INCLUDEPATH += $$WALLET_ROOT/include \
$$PWD/src/libwalletqt \
$$PWD/src/QR-Code-generator \
$$PWD/src \
$$WALLET_ROOT/src
$$WALLET_ROOT/src \
$$WALLET_ROOT/external/easylogging++ \
$$WALLET_ROOT/contrib/epee/include
HEADERS += \
src/main/filter.h \
@@ -51,6 +53,7 @@ HEADERS += \
src/main/oscursor.h \
src/libwalletqt/WalletManager.h \
src/libwalletqt/Wallet.h \
src/libwalletqt/PassphraseHelper.h \
src/libwalletqt/PendingTransaction.h \
src/libwalletqt/TransactionHistory.h \
src/libwalletqt/TransactionInfo.h \
@@ -74,11 +77,12 @@ HEADERS += \
src/libwalletqt/UnsignedTransaction.h \
src/main/Logger.h \
src/main/MainApp.h \
src/qt/downloader.h \
src/qt/FutureScheduler.h \
src/qt/ipc.h \
src/qt/KeysFiles.h \
src/qt/network.h \
src/qt/utils.h \
src/qt/prices.h \
src/qt/macoshelper.h \
src/qt/MoneroSettings.h \
src/qt/TailsOS.h
@@ -88,12 +92,15 @@ SOURCES += src/main/main.cpp \
src/main/clipboardAdapter.cpp \
src/main/oscursor.cpp \
src/libwalletqt/WalletManager.cpp \
src/libwalletqt/WalletListenerImpl.cpp \
src/libwalletqt/Wallet.cpp \
src/libwalletqt/PassphraseHelper.cpp \
src/libwalletqt/PendingTransaction.cpp \
src/libwalletqt/TransactionHistory.cpp \
src/libwalletqt/TransactionInfo.cpp \
src/libwalletqt/QRCodeImageProvider.cpp \
src/main/oshelper.cpp \
src/openpgp/openpgp.cpp \
src/TranslationManager.cpp \
src/model/TransactionHistoryModel.cpp \
src/model/TransactionHistorySortFilterModel.cpp \
@@ -110,11 +117,13 @@ SOURCES += src/main/main.cpp \
src/libwalletqt/UnsignedTransaction.cpp \
src/main/Logger.cpp \
src/main/MainApp.cpp \
src/qt/downloader.cpp \
src/qt/FutureScheduler.cpp \
src/qt/ipc.cpp \
src/qt/KeysFiles.cpp \
src/qt/network.cpp \
src/qt/updater.cpp \
src/qt/utils.cpp \
src/qt/prices.cpp \
src/qt/MoneroSettings.cpp \
src/qt/TailsOS.cpp
@@ -155,6 +164,8 @@ ios:arm64 {
}
LIBS_COMMON = \
-lgcrypt \
-lgpg-error \
-lwallet_merged \
-llmdb \
-lepee \
@@ -176,8 +187,10 @@ android {
QMAKE_CXXFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 -Werror -Wformat -Wformat-security
QMAKE_CFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 -Werror -Wformat -Wformat-security
QMAKE_CXXFLAGS += -Werror -Wformat -Wformat-security
QMAKE_CFLAGS += -Werror -Wformat -Wformat-security
QMAKE_CXXFLAGS_RELEASE += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 -O2
QMAKE_CFLAGS_RELEASE += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 -O2
ios {
message("Host is IOS")
@@ -221,6 +234,14 @@ CONFIG(WITH_SCANNER) {
LIBS += -lzbarjni -liconv
} else {
LIBS += -lzbar
macx {
ZBAR_DIR = $$system(brew --prefix zbar, lines, EXIT_CODE)
equals(EXIT_CODE, 0) {
INCLUDEPATH += $$ZBAR_DIR/include
} else {
INCLUDEPATH += /usr/local/include
}
}
}
} else {
message("Skipping camera scanner because of Incompatible Qt Version !")
@@ -372,11 +393,39 @@ macx {
# LIBS+= -Wl,-Bstatic
# }
OPENSSL_LIBRARY_DIRS = $$system(brew --prefix openssl, lines, EXIT_CODE)
OPENSSL_DIR = $$system(brew --prefix openssl, lines, EXIT_CODE)
!equals(EXIT_CODE, 0) {
OPENSSL_DIR = /usr/local/ssl
}
OPENSSL_LIBRARY_DIR = $$OPENSSL_DIR/lib
INCLUDEPATH += $$OPENSSL_DIR/include
BOOST_DIR = $$system(brew --prefix boost, lines, EXIT_CODE)
equals(EXIT_CODE, 0) {
OPENSSL_LIBRARY_DIRS = $$OPENSSL_LIBRARY_DIRS/lib
INCLUDEPATH += $$BOOST_DIR/include
} else {
OPENSSL_LIBRARY_DIRS = /usr/local/ssl/lib
INCLUDEPATH += /usr/local/include
}
GCRYPT_DIR = $$system(brew --prefix libgcrypt, lines, EXIT_CODE)
equals(EXIT_CODE, 0) {
INCLUDEPATH += $$GCRYPT_DIR/include
} else {
INCLUDEPATH += /usr/local/include
}
GPGP_ERROR_DIR = $$system(brew --prefix libgpg-error, lines, EXIT_CODE)
equals(EXIT_CODE, 0) {
INCLUDEPATH += $$GPGP_ERROR_DIR/include
} else {
INCLUDEPATH += /usr/local/include
}
SODIUM_DIR = $$system(brew --prefix libsodium, lines, EXIT_CODE)
equals(EXIT_CODE, 0) {
INCLUDEPATH += $$SODIUM_DIR/include
} else {
INCLUDEPATH += /usr/local/include
}
QT += macextras
@@ -386,7 +435,7 @@ macx {
LIBS+= -Wl,-bind_at_load
LIBS+= \
-L/usr/local/lib \
-L$$OPENSSL_LIBRARY_DIRS \
-L$$OPENSSL_LIBRARY_DIR \
-L/usr/local/opt/boost/lib \
-lboost_serialization \
-lboost_thread-mt \

View File

@@ -79,13 +79,6 @@ Rectangle {
topPadding: 0
text: qsTr("Save your most used addresses here") + translationManager.emptyString
width: parent.width
// @TODO: Legacy. Remove after Qt 5.8.
// https://stackoverflow.com/questions/41990013
MouseArea {
anchors.fill: parent
enabled: false
}
}
Text {
@@ -99,13 +92,6 @@ Rectangle {
topPadding: 0
text: qsTr("This makes it easier to send or receive Monero and reduces errors when typing in addresses manually.") + translationManager.emptyString
width: parent.width
// @TODO: Legacy. Remove after Qt 5.8.
// https://stackoverflow.com/questions/41990013
MouseArea {
anchors.fill: parent
enabled: false
}
}
MoneroComponents.StandardButton {
@@ -325,8 +311,6 @@ Rectangle {
if (!parsed.error) {
addressLine.text = parsed.address;
descriptionLine.text = parsed.tx_description;
} else {
addressLine.text = clipboardText;
}
}

View File

@@ -27,6 +27,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.9
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import moneroComponents.Clipboard 1.0
@@ -38,6 +39,7 @@ import "../components"
import "../components" as MoneroComponents
import "." 1.0
import "../js/TxUtils.js" as TxUtils
import "../js/Utils.js" as Utils
Rectangle {
@@ -54,8 +56,8 @@ Rectangle {
property string sendButtonWarning: {
// Currently opened wallet is not view-only
if (appWindow.viewOnly) {
return qsTr("Wallet is view-only and sends are not possible. Unless key images are imported, " +
"the balance reflects only incoming but not outgoing transactions.") + translationManager.emptyString;
return qsTr("Wallet is view-only and sends are only possible by using offline transaction signing. " +
"Unless key images are imported, the balance reflects only incoming but not outgoing transactions.") + translationManager.emptyString;
}
// There are sufficient unlocked funds available
@@ -116,7 +118,6 @@ Rectangle {
amountLine.text = ""
setDescription("");
priorityDropdown.currentIndex = 0
updatePriorityDropdown()
}
// Information dialog
@@ -162,96 +163,6 @@ Rectangle {
}
}
GridLayout {
columns: appWindow.walletMode < 2 ? 1 : 2
Layout.fillWidth: true
columnSpacing: 32
ColumnLayout {
Layout.fillWidth: true
Layout.minimumWidth: 200
// Amount input
LineEdit {
id: amountLine
Layout.fillWidth: true
inlineIcon: true
labelText: "<style type='text/css'>a {text-decoration: none; color: #858585; font-size: 14px;}</style>\
%1 <a href='#'>(%2)</a>".arg(qsTr("Amount")).arg(qsTr("Change account"))
+ translationManager.emptyString
copyButton: !isNaN(amountLine.text) && persistentSettings.fiatPriceEnabled
copyButtonText: fiatApiCurrencySymbol() + " ~" + fiatApiConvertToFiat(amountLine.text)
copyButtonEnabled: false
onLabelLinkActivated: {
middlePanel.accountView.selectAndSend = true;
appWindow.showPageRequest("Account")
}
placeholderText: "0.00"
width: 100
fontBold: true
inlineButtonText: qsTr("All") + translationManager.emptyString
inlineButton.onClicked: amountLine.text = "(all)"
onTextChanged: {
const match = amountLine.text.match(/^0+(\d.*)/);
if (match) {
const cursorPosition = amountLine.cursorPosition;
amountLine.text = match[1];
amountLine.cursorPosition = Math.max(cursorPosition, 1) - 1;
} else if(amountLine.text.indexOf('.') === 0){
amountLine.text = '0' + amountLine.text;
if (amountLine.text.length > 2) {
amountLine.cursorPosition = 1;
}
}
amountLine.error = walletManager.amountFromString(amountLine.text) > appWindow.getUnlockedBalance()
}
validator: RegExpValidator {
regExp: /^(\d{1,8})?([\.]\d{1,12})?$/
}
}
}
ColumnLayout {
visible: appWindow.walletMode >= 2
Layout.fillWidth: true
Label {
id: transactionPriority
Layout.topMargin: 12
text: qsTr("Transaction priority") + translationManager.emptyString
fontBold: false
fontSize: 16
}
// Note: workaround for translations in listElements
// ListElement: cannot use script for property value, so
// code like this wont work:
// ListElement { column1: qsTr("LOW") + translationManager.emptyString ; column2: ""; priority: PendingTransaction.Priority_Low }
// For translations to work, the strings need to be listed in
// the file components/StandardDropdown.qml too.
// Priorites after v5
ListModel {
id: priorityModelV5
ListElement { column1: qsTr("Automatic") ; column2: ""; priority: 0}
ListElement { column1: qsTr("Slow (x0.2 fee)") ; column2: ""; priority: 1}
ListElement { column1: qsTr("Normal (x1 fee)") ; column2: ""; priority: 2 }
ListElement { column1: qsTr("Fast (x5 fee)") ; column2: ""; priority: 3 }
ListElement { column1: qsTr("Fastest (x200 fee)") ; column2: ""; priority: 4 }
}
StandardDropdown {
Layout.fillWidth: true
id: priorityDropdown
Layout.topMargin: 5
currentIndex: 0
}
}
// Make sure dropdown is on top
z: parent.z + 1
}
// recipient address input
RowLayout {
id: addressLineRow
@@ -260,10 +171,9 @@ Rectangle {
LineEditMulti {
id: addressLine
spacing: 0
inputPaddingRight: inlineButtonVisible && inlineButton2Visible ? 100 : 60
fontBold: true
labelText: qsTr("<style type='text/css'>a {text-decoration: none; color: #858585; font-size: 14px;}</style>\
%1 <a href='#'>(%2)</a>").arg(qsTr("Address")).arg(qsTr("Address book"))
+ translationManager.emptyString
labelText: qsTr("Address") + translationManager.emptyString
labelButtonText: qsTr("Resolve") + translationManager.emptyString
placeholderText: {
if(persistentSettings.nettype == NetworkType.MAINNET){
@@ -276,10 +186,6 @@ Rectangle {
}
wrapMode: Text.WrapAnywhere
addressValidation: true
onInputLabelLinkActivated: {
middlePanel.addressBookView.selectAndSend = true;
appWindow.showPageRequest("AddressBook");
}
onTextChanged: {
const parsed = walletManager.parse_uri_to_object(text);
if (!parsed.error) {
@@ -289,16 +195,27 @@ Rectangle {
setDescription(parsed.tx_description);
}
}
inlineButton.text: FontAwesome.qrcode
inlineButton.text: FontAwesome.addressBook
inlineButton.buttonHeight: 30
inlineButton.fontPixelSize: 22
inlineButton.fontFamily: FontAwesome.fontFamily
inlineButton.textColor: MoneroComponents.Style.defaultFontColor
inlineButton.buttonColor: MoneroComponents.Style.orange
inlineButton.onClicked: {
cameraUi.state = "Capture"
cameraUi.qrcode_decoded.connect(updateFromQrCode)
middlePanel.addressBookView.selectAndSend = true;
appWindow.showPageRequest("AddressBook");
}
inlineButtonVisible : appWindow.qrScannerEnabled && !addressLine.text
inlineButtonVisible: true
inlineButton2.text: FontAwesome.qrcode
inlineButton2.buttonHeight: 30
inlineButton2.fontPixelSize: 22
inlineButton2.fontFamily: FontAwesome.fontFamily
inlineButton2.textColor: MoneroComponents.Style.defaultFontColor
inlineButton2.onClicked: {
cameraUi.state = "Capture"
cameraUi.qrcode_decoded.connect(updateFromQrCode)
}
inlineButton2Visible: appWindow.qrScannerEnabled
}
}
@@ -348,6 +265,142 @@ Rectangle {
}
}
GridLayout {
columns: appWindow.walletMode < 2 ? 1 : 2
Layout.fillWidth: true
columnSpacing: 32
ColumnLayout {
Layout.fillWidth: true
Layout.minimumWidth: 200
// Amount input
LineEdit {
id: amountLine
Layout.fillWidth: true
inlineIcon: true
labelText: "<style type='text/css'>a {text-decoration: none; color: #858585; font-size: 14px;}</style>\
%1 <a href='#'>(%2)</a>".arg(qsTr("Amount")).arg(qsTr("Change account"))
+ translationManager.emptyString
copyButton: !isNaN(amountLine.text) && persistentSettings.fiatPriceEnabled
copyButtonText: "~%1 %2".arg(fiatApiConvertToFiat(amountLine.text)).arg(fiatApiCurrencySymbol())
copyButtonEnabled: false
onLabelLinkActivated: {
middlePanel.accountView.selectAndSend = true;
appWindow.showPageRequest("Account")
}
placeholderText: "0.00"
width: 100
fontBold: true
inlineButtonText: qsTr("All") + translationManager.emptyString
inlineButton.onClicked: amountLine.text = "(all)"
onTextChanged: {
const match = amountLine.text.match(/^0+(\d.*)/);
if (match) {
const cursorPosition = amountLine.cursorPosition;
amountLine.text = match[1];
amountLine.cursorPosition = Math.max(cursorPosition, 1) - 1;
} else if(amountLine.text.indexOf('.') === 0){
amountLine.text = '0' + amountLine.text;
if (amountLine.text.length > 2) {
amountLine.cursorPosition = 1;
}
}
amountLine.error = walletManager.amountFromString(amountLine.text) > appWindow.getUnlockedBalance()
}
validator: RegExpValidator {
regExp: /^(\d{1,8})?([\.]\d{1,12})?$/
}
}
MoneroComponents.TextPlain {
id: feeLabel
Layout.alignment: Qt.AlignRight
Layout.topMargin: 12
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 14
color: MoneroComponents.Style.defaultFontColor
property bool estimating: false
property var estimatedFee: null
property string estimatedFeeFiat: {
if (!persistentSettings.fiatPriceEnabled || estimatedFee == null) {
return "";
}
const fiatFee = fiatApiConvertToFiat(estimatedFee);
return " (%1 %3)".arg(fiatFee < 0.01 ? "<0.01" : "~" + fiatFee).arg(fiatApiCurrencySymbol());
}
property var fee: {
estimatedFee = null;
estimating = sendButton.enabled;
if (!sendButton.enabled || !currentWallet) {
return;
}
currentWallet.estimateTransactionFeeAsync(
addressLine.text,
walletManager.amountFromString(amountLine.text),
priorityModelV5.get(priorityDropdown.currentIndex).priority,
function (amount) {
estimatedFee = Utils.removeTrailingZeros(amount);
estimating = false;
});
}
text: {
if (!sendButton.enabled || estimatedFee == null) {
return ""
}
return "%1: ~%2 XMR".arg(qsTr("Fee")).arg(estimatedFee) +
estimatedFeeFiat +
translationManager.emptyString;
}
BusyIndicator {
anchors.right: parent.right
running: feeLabel.estimating
height: parent.height
}
}
}
ColumnLayout {
visible: appWindow.walletMode >= 2
Layout.alignment: Qt.AlignTop
Label {
id: transactionPriority
Layout.topMargin: 0
text: qsTr("Transaction priority") + translationManager.emptyString
fontBold: false
fontSize: 16
}
// Note: workaround for translations in listElements
// ListElement: cannot use script for property value, so
// code like this wont work:
// ListElement { column1: qsTr("LOW") + translationManager.emptyString ; column2: ""; priority: PendingTransaction.Priority_Low }
// For translations to work, the strings need to be listed in
// the file components/StandardDropdown.qml too.
// Priorites after v5
ListModel {
id: priorityModelV5
ListElement { column1: qsTr("Automatic") ; column2: ""; priority: 0}
ListElement { column1: qsTr("Slow (x0.2 fee)") ; column2: ""; priority: 1}
ListElement { column1: qsTr("Normal (x1 fee)") ; column2: ""; priority: 2 }
ListElement { column1: qsTr("Fast (x5 fee)") ; column2: ""; priority: 3 }
ListElement { column1: qsTr("Fastest (x200 fee)") ; column2: ""; priority: 4 }
}
StandardDropdown {
Layout.preferredWidth: 200
id: priorityDropdown
Layout.topMargin: 5
currentIndex: 0
dataModel: priorityModelV5
}
}
}
MoneroComponents.WarningBox {
text: qsTr("Description field contents match long payment ID format. \
Please don't paste long payment ID into description field, your funds might be lost.") + translationManager.emptyString;
@@ -461,10 +514,9 @@ Rectangle {
id: advancedLayout
anchors.top: pageRoot.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 20
anchors.topMargin: 32
spacing: 26
spacing: 10
enabled: !viewOnly || pageRoot.enabled
RowLayout {
@@ -479,84 +531,88 @@ Rectangle {
}
}
GridLayout {
AdvancedOptionsItem {
visible: persistentSettings.transferShowAdvanced && appWindow.walletMode >= 2
columns: 6
StandardButton {
id: sweepUnmixableButton
text: qsTr("Sweep Unmixable") + translationManager.emptyString
enabled : pageRoot.enabled
small: true
onClicked: {
console.log("Transfer: sweepUnmixableClicked")
root.sweepUnmixableClicked()
}
title: qsTr("Key images") + translationManager.emptyString
button1.text: qsTr("Export") + translationManager.emptyString
button1.enabled: !appWindow.viewOnly
button1.onClicked: {
console.log("Transfer: export key images clicked")
exportKeyImagesDialog.open();
}
StandardButton {
id: saveTxButton
text: qsTr("Create tx file") + translationManager.emptyString
visible: appWindow.viewOnly
enabled: pageRoot.checkInformation(amountLine.text, addressLine.text, appWindow.persistentSettings.nettype)
small: true
onClicked: {
console.log("Transfer: saveTx Clicked")
var priority = priorityModelV5.get(priorityDropdown.currentIndex).priority
console.log("priority: " + priority)
console.log("amount: " + amountLine.text)
addressLine.text = addressLine.text.trim()
setPaymentId(paymentIdLine.text.trim());
root.paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, root.mixin, priority, descriptionLine.text)
}
button2.text: qsTr("Import") + translationManager.emptyString
button2.enabled: appWindow.viewOnly && appWindow.isTrustedDaemon()
button2.onClicked: {
console.log("Transfer: import key images clicked")
importKeyImagesDialog.open();
}
helpTextLarge.text: qsTr("Required for view-only wallets to display the real balance") + translationManager.emptyString
helpTextSmall.text: {
var errorMessage = "";
if (appWindow.viewOnly && !appWindow.isTrustedDaemon()){
errorMessage = "<p class='orange'>" + qsTr("* To import, you must connect to a local node or a trusted remote node") + "</p>";
}
return "<style type='text/css'>p{line-height:20px; margin-top:0px; margin-bottom:0px; color:#ffffff;} p.orange{color:#ff9323;}</style>" +
"<p>" + qsTr("1. Using cold wallet, export the key images into a file") + "</p>" +
"<p>" + qsTr("2. Using view-only wallet, import the key images file") + "</p>" +
errorMessage + translationManager.emptyString
}
helpTextSmall.themeTransition: false
}
AdvancedOptionsItem {
visible: persistentSettings.transferShowAdvanced && appWindow.walletMode >= 2
title: qsTr("Offline transaction signing") + translationManager.emptyString
button1.text: qsTr("Create") + translationManager.emptyString
button1.enabled: appWindow.viewOnly && pageRoot.checkInformation(amountLine.text, addressLine.text, appWindow.persistentSettings.nettype)
button1.onClicked: {
console.log("Transfer: saveTx Clicked")
var priority = priorityModelV5.get(priorityDropdown.currentIndex).priority
console.log("priority: " + priority)
console.log("amount: " + amountLine.text)
addressLine.text = addressLine.text.trim()
setPaymentId(paymentIdLine.text.trim());
root.paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, root.mixin, priority, descriptionLine.text)
}
button2.text: qsTr("Sign (offline)") + translationManager.emptyString
button2.enabled: !appWindow.viewOnly
button2.onClicked: {
console.log("Transfer: sign tx clicked")
signTxDialog.open();
}
button3.text: qsTr("Submit") + translationManager.emptyString
button3.enabled: appWindow.viewOnly
button3.onClicked: {
console.log("Transfer: submit tx clicked")
submitTxDialog.open();
}
helpTextLarge.text: qsTr("Spend XMR from a cold (offline) wallet") + translationManager.emptyString
helpTextSmall.text: {
var errorMessage = "";
if (appWindow.viewOnly && !pageRoot.checkInformation(amountLine.text, addressLine.text, appWindow.persistentSettings.nettype)){
errorMessage = "<p class='orange'>" + qsTr("* To create a transaction file, please enter address and amount above") + "</p>";
}
return "<style type='text/css'>p{line-height:20px; margin-top:0px; margin-bottom:0px; color:#ffffff;} p.orange{color:#ff9323;}</style>" +
"<p>" + qsTr("1. Using view-only wallet, export the outputs into a file") + "</p>" +
"<p>" + qsTr("2. Using cold wallet, import the outputs file and export the key images") + "</p>" +
"<p>" + qsTr("3. Using view-only wallet, import the key images file and create a transaction file") + "</p>" +
errorMessage +
"<p>" + qsTr("4. Using cold wallet, sign your transaction file") + "</p>" +
"<p>" + qsTr("5. Using view-only wallet, submit your signed transaction") + "</p>" + translationManager.emptyString
}
helpTextSmall.themeTransition: false
}
StandardButton {
id: signTxButton
text: qsTr("Sign tx file") + translationManager.emptyString
small: true
visible: !appWindow.viewOnly
onClicked: {
console.log("Transfer: sign tx clicked")
signTxDialog.open();
}
}
StandardButton {
id: submitTxButton
text: qsTr("Submit tx file") + translationManager.emptyString
small: true
visible: appWindow.viewOnly
enabled: pageRoot.enabled
onClicked: {
console.log("Transfer: submit tx clicked")
submitTxDialog.open();
}
}
StandardButton {
id: exportKeyImagesButton
text: qsTr("Export key images") + translationManager.emptyString
small: true
visible: !appWindow.viewOnly
enabled: pageRoot.enabled
onClicked: {
console.log("Transfer: export key images clicked")
exportKeyImagesDialog.open();
}
}
StandardButton {
id: importKeyImagesButton
text: qsTr("Import key images") + translationManager.emptyString
small: true
enabled: appWindow.viewOnly && appWindow.isTrustedDaemon()
onClicked: {
console.log("Transfer: import key images clicked")
importKeyImagesDialog.open();
}
AdvancedOptionsItem {
visible: persistentSettings.transferShowAdvanced && appWindow.walletMode >= 2
title: qsTr("Unmixable outputs") + translationManager.emptyString
button1.text: qsTr("Sweep") + translationManager.emptyString
button1.enabled : pageRoot.enabled
button1.onClicked: {
console.log("Transfer: sweepUnmixableClicked")
root.sweepUnmixableClicked()
}
helpTextLarge.text: qsTr("Create a transaction that spends old unmovable outputs") + translationManager.emptyString
}
}
@@ -689,12 +745,6 @@ Rectangle {
function onPageCompleted() {
console.log("transfer page loaded")
updateStatus();
updatePriorityDropdown()
}
function updatePriorityDropdown() {
priorityDropdown.dataModel = priorityModelV5;
priorityDropdown.update()
}
//TODO: Add daemon sync status
@@ -717,6 +767,8 @@ Rectangle {
switch (currentWallet.connected()) {
case Wallet.ConnectionStatus_Connecting:
root.warningContent = qsTr("Wallet is connecting to daemon.")
break
case Wallet.ConnectionStatus_Disconnected:
root.warningContent = messageNotConnected;
break

View File

@@ -25,6 +25,7 @@ Item {
anchors.margins: 0
property int minWidth: 900
property int minHeight: 600
property int qrCodeSize: 220
property bool enableTracking: false
property string trackingError: "" // setting this will show a message @ tracking table
@@ -33,6 +34,9 @@ Item {
property var hiddenAmounts: []
function onPageCompleted() {
if (appWindow.currentWallet) {
appWindow.current_address = appWindow.currentWallet.address(appWindow.currentWallet.currentSubaddressAccount, 0)
}
// prepare tracking
trackingCheckbox.checked = root.enableTracking
root.update();
@@ -67,7 +71,7 @@ Item {
ColumnLayout {
id: mainLayout
visible: parent.width >= root.minWidth
visible: parent.width >= root.minWidth && appWindow.height >= root.minHeight
spacing: 0
// emulates max-width + center for container
@@ -544,7 +548,7 @@ Item {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: appWindow.showPageRequest("Receive")
onClicked: appWindow.showPageRequest("Settings")
}
}
}
@@ -553,7 +557,7 @@ Item {
Rectangle {
// Shows when the window is too small
visible: parent.width < root.minWidth
visible: parent.width < root.minWidth || appWindow.height < root.minHeight
anchors.top: parent.top
anchors.topMargin: 100;
anchors.horizontalCenter: parent.horizontalCenter
@@ -570,6 +574,13 @@ Item {
text: qsTr("The merchant page requires a larger window") + translationManager.emptyString
themeTransition: false
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: appWindow.showPageRequest("Settings")
}
}
function update() {
@@ -589,9 +600,7 @@ Item {
var model = appWindow.currentWallet.historyModel
var count = model.rowCount()
var totalAmount = 0
var nTransactions = 0
var blockchainHeight = null
var txs = []
// Currently selected subaddress as per Receive page
@@ -607,8 +616,6 @@ Item {
var subaddrIndex = model.data(idx, TransactionHistoryModel.TransactionSubaddrIndexRole);
if (!isout && subaddrAccount == appWindow.currentWallet.currentSubaddressAccount && subaddrIndex == current_subaddress_table_index) {
var amount = model.data(idx, TransactionHistoryModel.TransactionAtomicAmountRole);
totalAmount = walletManager.addi(totalAmount, amount)
nTransactions += 1
var txid = model.data(idx, TransactionHistoryModel.TransactionHashRole);
@@ -616,21 +623,17 @@ Item {
var in_txpool = false;
var confirmations = 0;
var displayAmount = 0;
var displayAmount = model.data(idx, TransactionHistoryModel.TransactionDisplayAmountRole);
if (blockHeight == 0) {
if (blockHeight === undefined) {
in_txpool = true;
} else {
if (blockchainHeight == null)
blockchainHeight = walletManager.blockchainHeight()
confirmations = blockchainHeight - blockHeight - 1
displayAmount = model.data(idx, TransactionHistoryModel.TransactionDisplayAmountRole);
confirmations = model.data(idx, TransactionHistoryModel.TransactionConfirmationsRole);
}
txs.push({
"amount": displayAmount,
"confirmations": confirmations,
"blockheight": blockHeight,
"in_txpool": in_txpool,
"txid": txid,
"time_epoch": timeEpoch,
@@ -650,9 +653,7 @@ Item {
txs.forEach(function(tx){
trackingModel.append({
"amount": tx.amount,
"blockheight": tx.blockheight,
"confirmations": tx.confirmations,
"blockheight": tx.blockHeight,
"in_txpool": tx.in_txpool,
"txid": tx.txid,
"time_epoch": tx.time_epoch,

View File

@@ -125,7 +125,7 @@ ListView {
font.pixelSize: 14
font.bold: true
color: hide_amount ? "#707070" : "#009F1E"
text: hide_amount ? '-' : '+' + amount
text: hide_amount ? '-' : '+' + amount + (in_txpool ? ' (%1)'.arg(qsTr('unconfirmed')) : '')
selectionColor: MoneroComponents.Style.textSelectionColor
selectByMouse: true
readOnly: true

View File

@@ -273,7 +273,6 @@ Rectangle {
// LOG
id: navLog
property bool isActive: settingsStateView.state === "Log"
visible: appWindow.walletMode >= 2
Layout.preferredWidth: navLogText.width + grid.textMargin
Layout.preferredHeight: 32
Layout.minimumWidth: 72

View File

@@ -131,10 +131,11 @@ Rectangle {
}
MoneroComponents.TextBlock {
id: walletLocation
Layout.fillWidth: true
color: MoneroComponents.Style.dimmedFontColor
font.pixelSize: 14
property string walletPath: (isIOS ? moneroAccountsDir : "") + appWindow.walletPath()
property string walletPath: (isIOS ? moneroAccountsDir : "") + persistentSettings.wallet_path
text: "\
<style type='text/css'>\
a {cursor:pointer;text-decoration: none; color: #FF6C3C}\
@@ -212,7 +213,6 @@ Rectangle {
+ "The old wallet cache file will be renamed and can be restored later.\n"
);
confirmationDialog.icon = StandardIcon.Question
confirmationDialog.cancelText = qsTr("Cancel")
confirmationDialog.onAcceptedCallback = function() {
appWindow.closeWallet(function() {
walletManager.clearWalletCache(persistentSettings.wallet_path);
@@ -230,7 +230,7 @@ Rectangle {
appWindow.showStatusMessage(qsTr("Invalid restore height specified. Must be a number or a date formatted YYYY-MM-DD"),3);
}
inputDialog.onRejectedCallback = null;
inputDialog.open(currentWallet ? currentWallet.walletCreationHeight : "0")
inputDialog.open(currentWallet ? currentWallet.walletCreationHeight.toFixed(0) : "0")
}
MouseArea {
@@ -389,12 +389,7 @@ Rectangle {
var data = "";
data += "GUI version: " + Version.GUI_VERSION + " (Qt " + qtRuntimeVersion + ")";
data += "\nEmbedded Monero version: " + Version.GUI_MONERO_VERSION;
data += "\nWallet path: ";
var wallet_path = walletPath();
if(isIOS)
wallet_path = moneroAccountsDir + wallet_path;
data += wallet_path;
data += "\nWallet path: " + walletLocation.walletPath;
data += "\nWallet creation height: ";
if(currentWallet)

View File

@@ -58,6 +58,14 @@ Rectangle {
text: qsTr("Custom decorations") + translationManager.emptyString
}
MoneroComponents.CheckBox {
id: checkForUpdatesCheckBox
enabled: !disableCheckUpdatesFlag
checked: persistentSettings.checkForUpdates && !disableCheckUpdatesFlag
onClicked: persistentSettings.checkForUpdates = !persistentSettings.checkForUpdates
text: qsTr("Check for updates periodically") + translationManager.emptyString
}
MoneroComponents.CheckBox {
id: hideBalanceCheckBox
checked: persistentSettings.hideBalance
@@ -78,6 +86,46 @@ Rectangle {
persistentSettings.blackTheme = MoneroComponents.Style.blackTheme;
}
}
MoneroComponents.CheckBox {
checked: persistentSettings.askPasswordBeforeSending
text: qsTr("Ask for password before sending a transaction") + translationManager.emptyString
toggleOnClick: false
onClicked: {
if (persistentSettings.askPasswordBeforeSending) {
passwordDialog.onAcceptedCallback = function() {
if (appWindow.walletPassword === passwordDialog.password){
persistentSettings.askPasswordBeforeSending = false;
} else {
passwordDialog.showError(qsTr("Wrong password"));
}
}
passwordDialog.onRejectedCallback = null;
passwordDialog.open()
} else {
persistentSettings.askPasswordBeforeSending = true;
}
}
}
MoneroComponents.CheckBox {
checked: persistentSettings.autosave
onClicked: persistentSettings.autosave = !persistentSettings.autosave
text: qsTr("Autosave") + translationManager.emptyString
}
MoneroComponents.Slider {
Layout.fillWidth: true
Layout.leftMargin: 35
Layout.topMargin: 6
visible: persistentSettings.autosave
from: 1
stepSize: 1
to: 60
value: persistentSettings.autosaveMinutes
text: "%1 %2 %3".arg(qsTr("Every")).arg(value).arg(qsTr("minute(s)")) + translationManager.emptyString
onMoved: persistentSettings.autosaveMinutes = value
}
MoneroComponents.CheckBox {
id: userInActivityCheckbox
@@ -86,70 +134,20 @@ Rectangle {
text: qsTr("Lock wallet on inactivity") + translationManager.emptyString
}
ColumnLayout {
MoneroComponents.Slider {
visible: userInActivityCheckbox.checked
Layout.fillWidth: true
Layout.topMargin: 6
Layout.leftMargin: 42
spacing: 0
Text {
color: MoneroComponents.Style.defaultFontColor
font.pixelSize: 14
Layout.fillWidth: true
text: {
var val = userInactivitySlider.value;
var minutes = val > 1 ? qsTr("minutes") : qsTr("minute");
qsTr("After ") + val + " " + minutes + translationManager.emptyString;
}
}
Slider {
id: userInactivitySlider
from: 1
value: persistentSettings.lockOnUserInActivityInterval
to: 60
leftPadding: 0
stepSize: 1
snapMode: Slider.SnapAlways
background: Rectangle {
x: parent.leftPadding
y: parent.topPadding + parent.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 4
width: parent.availableWidth
height: implicitHeight
radius: 2
color: MoneroComponents.Style.progressBarBackgroundColor
Rectangle {
width: parent.visualPosition * parent.width
height: parent.height
color: MoneroComponents.Style.green
radius: 2
}
}
handle: Rectangle {
x: parent.leftPadding + parent.visualPosition * (parent.availableWidth - width)
y: parent.topPadding + parent.availableHeight / 2 - height / 2
implicitWidth: 18
implicitHeight: 18
radius: 8
color: parent.pressed ? "#f0f0f0" : "#f6f6f6"
border.color: MoneroComponents.Style.grey
}
onMoved: persistentSettings.lockOnUserInActivityInterval = userInactivitySlider.value;
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
}
Layout.leftMargin: 35
from: 1
stepSize: 1
to: 60
value: persistentSettings.lockOnUserInActivityInterval
text: {
var minutes = value > 1 ? qsTr("minutes") : qsTr("minute");
return qsTr("After ") + value + " " + minutes + translationManager.emptyString;
}
onMoved: persistentSettings.lockOnUserInActivityInterval = value
}
//! Manage pricing
@@ -212,6 +210,7 @@ Rectangle {
MoneroComponents.StandardDropdown {
id: fiatPriceCurrencyDropdown
Layout.fillWidth: true
currentIndex: persistentSettings.fiatPriceCurrency === "xmrusd" ? 0 : 1
dataModel: fiatPriceCurrencyModel
onChanged: {
var obj = dataModel.get(currentIndex);
@@ -297,10 +296,6 @@ Rectangle {
i += 1;
}
fiatPriceProviderDropDown.update();
fiatPriceCurrencyDropdown.currentIndex = persistentSettings.fiatPriceCurrency === "xmrusd" ? 0 : 1;
fiatPriceCurrencyDropdown.update();
console.log('SettingsLayout loaded');
}
}

View File

@@ -159,7 +159,7 @@ Rectangle {
textFormat: TextEdit.RichText
selectByMouse: true
selectByKeyboard: true
font.family: MoneroComponents.Style.defaultFontColor
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 14
wrapMode: TextEdit.Wrap
readOnly: true
@@ -231,9 +231,6 @@ Rectangle {
}
Component.onCompleted: {
logLevelDropdown.currentIndex = appWindow.persistentSettings.logLevel;
logLevelDropdown.update();
if(typeof daemonManager != "undefined")
daemonManager.daemonConsoleUpdated.connect(onDaemonConsoleUpdated)
}

View File

@@ -130,13 +130,6 @@ Rectangle{
topPadding: 0
text: qsTr("The blockchain is downloaded to your computer. Provides higher security and requires more local storage.") + translationManager.emptyString
width: parent.width - (localNodeIcon.width + localNodeIcon.anchors.leftMargin + anchors.leftMargin)
// @TODO: Legacy. Remove after Qt 5.8.
// https://stackoverflow.com/questions/41990013
MouseArea {
anchors.fill: parent
enabled: false
}
}
}
@@ -229,13 +222,6 @@ Rectangle{
topPadding: 0
text: qsTr("Uses a third-party server to connect to the Monero network. Less secure, but easier on your computer.") + translationManager.emptyString
width: parent.width - (remoteNodeIcon.width + remoteNodeIcon.anchors.leftMargin + anchors.leftMargin)
// @TODO: Legacy. Remove after Qt 5.8.
// https://stackoverflow.com/questions/41990013
MouseArea {
anchors.fill: parent
enabled: false
}
}
MouseArea {
@@ -279,8 +265,8 @@ Rectangle{
Layout.minimumWidth: 100
placeholderFontSize: 15
daemonAddrLabelText: qsTr("Address")
daemonPortLabelText: qsTr("Port")
daemonAddrLabelText: qsTr("Address") + translationManager.emptyString
daemonPortLabelText: qsTr("Port") + translationManager.emptyString
property var rna: persistentSettings.remoteNodeAddress
daemonAddrText: rna.search(":") != -1 ? rna.split(":")[0].trim() : ""
@@ -318,7 +304,7 @@ Rectangle{
labelText: qsTr("Daemon password") + translationManager.emptyString
text: persistentSettings.daemonPassword
placeholderText: qsTr("Password") + translationManager.emptyString
echoMode: TextInput.Password
password: true
placeholderFontSize: 15
labelFontSize: 14
fontSize: 15
@@ -382,14 +368,12 @@ Rectangle{
labelFontSize: 14
property string style: "<style type='text/css'>a {cursor:pointer;text-decoration: none; color: #FF6C3C}</style>"
labelText: qsTr("Blockchain location") + style + " <a href='#'> (%1)</a>".arg(qsTr("Change")) + translationManager.emptyString
labelButtonText: qsTr("Reset") + translationManager.emptyString
labelButtonVisible: text
placeholderText: qsTr("(default)") + translationManager.emptyString
placeholderFontSize: 15
readOnly: true
text: {
if(persistentSettings.blockchainDataDir.length > 0){
return persistentSettings.blockchainDataDir;
} else { return "" }
}
text: persistentSettings.blockchainDataDir
addressValidation: false
onInputLabelLinkActivated: {
//mouse.accepted = false
@@ -399,6 +383,7 @@ Rectangle{
blockchainFileDialog.open();
blockchainFolder.focus = true;
}
onLabelButtonClicked: persistentSettings.blockchainDataDir = ""
}
}
@@ -413,7 +398,12 @@ Rectangle{
placeholderFontSize: 15
text: persistentSettings.daemonFlags
addressValidation: false
onEditingFinished: persistentSettings.daemonFlags = daemonFlags.text;
error: text.match(/(^|\s)--(data-dir|bootstrap-daemon-address)/)
onEditingFinished: {
if (!daemonFlags.error) {
persistentSettings.daemonFlags = daemonFlags.text;
}
}
}
RowLayout {
@@ -427,8 +417,8 @@ Rectangle{
Layout.minimumWidth: 100
Layout.bottomMargin: 20
daemonAddrLabelText: qsTr("Bootstrap Address")
daemonPortLabelText: qsTr("Bootstrap Port")
daemonAddrLabelText: qsTr("Bootstrap Address") + translationManager.emptyString
daemonPortLabelText: qsTr("Bootstrap Port") + translationManager.emptyString
daemonAddrText: persistentSettings.bootstrapNodeAddress.split(":")[0].trim()
daemonPortText: {
var node_split = persistentSettings.bootstrapNodeAddress.split(":");

View File

@@ -30,6 +30,7 @@ import QtQuick 2.9
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.0
import QtQuick.Dialogs 1.2
import FontAwesome 1.0
import "../../js/Utils.js" as Utils
import "../../components" as MoneroComponents
@@ -47,10 +48,10 @@ Rectangle {
anchors.right: parent.right
anchors.margins: 20
anchors.topMargin: 0
spacing: 8
spacing: 0
MoneroComponents.SettingsListItem {
buttonText: qsTr("Close wallet") + translationManager.emptyString
iconText: FontAwesome.signOutAlt
description: qsTr("Logs out of this wallet.") + translationManager.emptyString
title: qsTr("Close this wallet") + translationManager.emptyString
@@ -58,7 +59,7 @@ Rectangle {
}
MoneroComponents.SettingsListItem {
buttonText: qsTr("Create wallet") + translationManager.emptyString
iconText: FontAwesome.eye
description: qsTr("Creates a new wallet that can only view and initiate transactions, but requires a spendable wallet to sign transactions before sending.") + translationManager.emptyString
title: qsTr("Create a view-only wallet") + translationManager.emptyString
visible: !appWindow.viewOnly
@@ -80,7 +81,7 @@ Rectangle {
}
MoneroComponents.SettingsListItem {
buttonText: qsTr("Show seed") + translationManager.emptyString
iconText: FontAwesome.key
description: qsTr("Store this information safely to recover your wallet in the future.") + translationManager.emptyString
title: qsTr("Show seed & keys") + translationManager.emptyString
@@ -90,7 +91,7 @@ Rectangle {
}
MoneroComponents.SettingsListItem {
buttonText: qsTr("Rescan") + translationManager.emptyString
iconText: FontAwesome.repeat
description: qsTr("Use this feature if you think the shown balance is not accurate.") + translationManager.emptyString
title: qsTr("Rescan wallet balance") + translationManager.emptyString
visible: appWindow.walletMode >= 2
@@ -114,7 +115,7 @@ Rectangle {
}
MoneroComponents.SettingsListItem {
buttonText: qsTr("Change password") + translationManager.emptyString
iconText: FontAwesome.ellipsisH
description: qsTr("Change the password of your wallet.") + translationManager.emptyString
title: qsTr("Change wallet password") + translationManager.emptyString
@@ -135,6 +136,19 @@ Rectangle {
passwordDialog.open()
}
}
MoneroComponents.SettingsListItem {
iconText: FontAwesome.cashRegister
isLast: true
description: qsTr("Receive Monero for your business, easily.") + translationManager.emptyString
title: qsTr("Enter merchant mode") + translationManager.emptyString
onClicked: {
middlePanel.state = "Merchant";
middlePanel.flickable.contentY = 0;
updateBalance();
}
}
}
Component.onCompleted: {

20
qml.qrc
View File

@@ -5,10 +5,14 @@
<file>MiddlePanel.qml</file>
<file>components/Label.qml</file>
<file>components/SettingsListItem.qml</file>
<file>components/Slider.qml</file>
<file>components/UpdateDialog.qml</file>
<file>images/whatIsIcon.png</file>
<file>images/whatIsIcon@2x.png</file>
<file>images/lockIcon.png</file>
<file>components/MenuButton.qml</file>
<file>monero/utils/gpg_keys/binaryfate.asc</file>
<file>monero/utils/gpg_keys/fluffypony.asc</file>
<file>monero/utils/gpg_keys/luigi1111.asc</file>
<file>pages/Account.qml</file>
<file>pages/Transfer.qml</file>
<file>pages/History.qml</file>
@@ -23,7 +27,7 @@
<file>components/TipItem.qml</file>
<file>images/tip.png</file>
<file>components/MenuButtonDivider.qml</file>
<file>images/moneroIcon.png</file>
<file>images/monero-vector.svg</file>
<file>components/StandardDropdown.qml</file>
<file>images/whiteDropIndicator.png</file>
<file>images/whiteDropIndicator@2x.png</file>
@@ -34,7 +38,6 @@
<file>images/prevMonth.png</file>
<file>images/prevMonth@2x.png</file>
<file>components/TitleBar.qml</file>
<file>images/moneroLogo2.png</file>
<file>images/resize.png</file>
<file>images/resize@2x.png</file>
<file>images/resizeHovered.png</file>
@@ -96,11 +99,11 @@
<file>components/ProcessingSplash.qml</file>
<file>components/ProgressBar.qml</file>
<file>components/StandardDialog.qml</file>
<file>components/DevicePassphraseDialog.qml</file>
<file>pages/Sign.qml</file>
<file>components/DaemonManagerDialog.qml</file>
<file>version.js</file>
<file>components/QRCodeScanner.qml</file>
<file>components/Notifier.qml</file>
<file>components/TextBlock.qml</file>
<file>components/RemoteNodeEdit.qml</file>
<file>pages/Keys.qml</file>
@@ -110,8 +113,6 @@
<file>images/card-background-white.png</file>
<file>images/card-background-white@2x.png</file>
<file>images/moneroLogo_white.png</file>
<file>images/question.png</file>
<file>images/question@2x.png</file>
<file>images/titlebarLogo.png</file>
<file>images/titlebarLogo@2x.png</file>
<file>pages/merchant/MerchantTitlebar.qml</file>
@@ -188,7 +189,6 @@
<file>wizard/WizardHeader.qml</file>
<file>wizard/WizardHome.qml</file>
<file>wizard/WizardLanguage.qml</file>
<file>wizard/WizardLang.qml</file>
<file>wizard/WizardNav.qml</file>
<file>wizard/WizardWalletInput.qml</file>
<file>wizard/WizardRestoreWallet1.qml</file>
@@ -204,7 +204,6 @@
<file>js/Wizard.js</file>
<file>components/LanguageSidebar.qml</file>
<file>images/world-flags-globe.png</file>
<file>images/langFlagGrey.png</file>
<file>images/restore-wallet-from-hardware@2x.png</file>
<file>images/restore-wallet-from-hardware.png</file>
<file>images/open-wallet-from-file@2x.png</file>
@@ -219,7 +218,6 @@
<file>images/local-node@2x.png</file>
<file>images/local-node-full.png</file>
<file>images/local-node-full@2x.png</file>
<file>wizard/WizardNavProgressDot.qml</file>
<file>wizard/WizardOpenWallet1.qml</file>
<file>images/arrow-right-in-circle.png</file>
<file>images/arrow-right-in-circle@2x.png</file>
@@ -233,7 +231,6 @@
<file>images/themes/white/close.svg</file>
<file>images/themes/white/fullscreen.svg</file>
<file>images/themes/white/minimize.svg</file>
<file>images/themes/white/question.svg</file>
<file>components/effects/ColorTransition.qml</file>
<file>components/effects/GradientBackground.qml</file>
<file>images/check-white.svg</file>
@@ -241,5 +238,8 @@
<file>images/edit.svg</file>
<file>images/arrow-right-in-circle-outline-medium-white.svg</file>
<file>images/tails-grey.png</file>
<file>components/AdvancedOptionsItem.qml</file>
<file>images/busy-indicator.png</file>
<file>images/busy-indicator@2x.png</file>
</qresource>
</RCC>

View File

@@ -3,6 +3,7 @@ add_subdirectory(QR-Code-scanner)
add_subdirectory(daemon)
add_subdirectory(libwalletqt)
add_subdirectory(model)
add_subdirectory(openpgp)
add_subdirectory(zxcvbn-c)
qt5_add_resources(RESOURCES ../qml.qrc)
@@ -14,7 +15,9 @@ file(GLOB SOURCE_FILES
"main/*.h"
"main/*.cpp"
"libwalletqt/WalletManager.cpp"
"libwalletqt/WalletListenerImpl.cpp"
"libwalletqt/Wallet.cpp"
"libwalletqt/PassphraseHelper.cpp"
"libwalletqt/PendingTransaction.cpp"
"libwalletqt/TransactionHistory.cpp"
"libwalletqt/TransactionInfo.cpp"
@@ -28,6 +31,7 @@ file(GLOB SOURCE_FILES
"libwalletqt/UnsignedTransaction.cpp"
"libwalletqt/WalletManager.h"
"libwalletqt/Wallet.h"
"libwalletqt/PassphraseHelper.h"
"libwalletqt/PendingTransaction.h"
"libwalletqt/TransactionHistory.h"
"libwalletqt/TransactionInfo.h"
@@ -76,27 +80,29 @@ if(MINGW)
list(APPEND RESOURCES ${ICON_RES})
endif()
add_executable(monero-gui ${EXECUTABLE_FLAG} main/main.cpp
add_executable(monero-wallet-gui ${EXECUTABLE_FLAG} main/main.cpp
${SOURCE_FILES}
${PASS_STRENGTH_FILES}
${QR_CODE_FILES}
${RESOURCES}
)
set_property(TARGET monero-gui PROPERTY RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set_property(TARGET monero-wallet-gui PROPERTY RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
# OpenGL
target_include_directories(monero-gui PUBLIC ${OPENGL_INCLUDE_DIR})
target_include_directories(monero-wallet-gui PUBLIC ${OPENGL_INCLUDE_DIR})
message(STATUS "OpenGL: include dir at ${OPENGL_INCLUDE_DIR}")
message(STATUS "OpenGL: libraries at ${OPENGL_LIBRARIES}")
target_include_directories(monero-gui PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
target_include_directories(monero-wallet-gui PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
file(GLOB_RECURSE SRC_SOURCES *.cpp)
file(GLOB_RECURSE SRC_HEADERS *.h)
target_include_directories(monero-gui PUBLIC
target_include_directories(monero-wallet-gui PUBLIC
${CMAKE_SOURCE_DIR}/monero/include
${CMAKE_SOURCE_DIR}/monero/src
${CMAKE_SOURCE_DIR}/monero/external/easylogging++
${CMAKE_SOURCE_DIR}/monero/contrib/epee/include
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/daemon
${CMAKE_CURRENT_SOURCE_DIR}/libwalletqt
@@ -112,28 +118,25 @@ target_include_directories(monero-gui PUBLIC
${ZBAR_INCLUDE_DIR}
)
target_compile_definitions(monero-gui
target_compile_definitions(monero-wallet-gui
PUBLIC
${Qt5Widgets_DEFINITIONS}
${Qt5Qml_DEFINITIONS}
)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
if(X11_FOUND)
target_link_libraries(monero-gui ${X11_LIBRARIES} pthread dl Xt xcb X11)
if(INSTALL_VENDORED_LIBUNBOUND)
set(UNBOUND_LIBRARY ${CMAKE_BINARY_DIR}/monero/external/unbound/libunbound.a)
else()
set(UNBOUND_LIBRARY unbound)
endif()
if(DEVICE_TREZOR_READY)
target_link_libraries(monero-gui ${TREZOR_DEP_LIBS})
endif()
target_link_libraries(monero-gui
target_link_libraries(monero-wallet-gui
${CMAKE_BINARY_DIR}/lib/libwallet_merged.a
${LMDB_LIBRARY}
${CMAKE_BINARY_DIR}/monero/contrib/epee/src/libepee.a
${CMAKE_BINARY_DIR}/monero/external/unbound/libunbound.a
${UNBOUND_LIBRARY}
${SODIUM_LIBRARY}
${CMAKE_BINARY_DIR}/monero/external/easylogging++/libeasylogging.a
${CMAKE_BINARY_DIR}/monero/src/blockchain_db/libblockchain_db.a
@@ -147,10 +150,19 @@ target_link_libraries(monero-gui
${QT5_LIBRARIES}
${EXTRA_LIBRARIES}
${ICU_LIBRARIES}
openpgp
)
if(DEVICE_TREZOR_READY)
target_link_libraries(monero-wallet-gui ${TREZOR_DEP_LIBS})
endif()
if(X11_FOUND)
target_link_libraries(monero-wallet-gui ${X11_LIBRARIES})
endif()
if(WITH_SCANNER)
target_link_libraries(monero-gui
target_link_libraries(monero-wallet-gui
${ZBAR_LIBRARIES}
jpeg
v4l2
@@ -159,6 +171,6 @@ if(WITH_SCANNER)
)
endif()
install(TARGETS monero-gui
install(TARGETS monero-wallet-gui
DESTINATION ${CMAKE_INSTALL_PREFIX}
)

View File

@@ -80,7 +80,11 @@ bool QrScanThread::zimageFromQImage(const QImage &qimg, zbar::Image &dst)
unsigned int bpl( qimg.bytesPerLine() ), width( bpl / 4), height( qimg.height());
dst.set_size(width, height);
dst.set_format("BGR4");
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
unsigned long datalen = qimg.sizeInBytes();
#else
unsigned long datalen = qimg.byteCount();
#endif
dst.set_data(qimg.bits(), datalen);
if((width * 4 != bpl) || (width * height * 4 > datalen)){
emit notifyError(QString("QImage to Zbar::Image failed !"));

View File

@@ -29,6 +29,7 @@
#include "DaemonManager.h"
#include <QElapsedTimer>
#include <QFile>
#include <QMutexLocker>
#include <QThread>
#include <QFileInfo>
#include <QDir>
@@ -113,8 +114,8 @@ bool DaemonManager::start(const QString &flags, NetworkType::Type nettype, const
arguments << "--check-updates" << "disabled";
// --max-concurrency based on threads available. max: 6
int32_t concurrency = qBound(1, QThread::idealThreadCount() / 2, 6);
// --max-concurrency based on threads available.
int32_t concurrency = qMax(1, QThread::idealThreadCount() / 2);
if(!flags.contains("--max-concurrency", Qt::CaseSensitive)){
arguments << "--max-concurrency" << QString::number(concurrency);
@@ -123,18 +124,19 @@ bool DaemonManager::start(const QString &flags, NetworkType::Type nettype, const
qDebug() << "starting monerod " + m_monerod;
qDebug() << "With command line arguments " << arguments;
m_daemon = new QProcess();
initialized = true;
QMutexLocker locker(&m_daemonMutex);
m_daemon.reset(new QProcess());
// Connect output slots
connect (m_daemon, SIGNAL(readyReadStandardOutput()), this, SLOT(printOutput()));
connect (m_daemon, SIGNAL(readyReadStandardError()), this, SLOT(printError()));
connect(m_daemon.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(printOutput()));
connect(m_daemon.get(), SIGNAL(readyReadStandardError()), this, SLOT(printError()));
// Start monerod
bool started = m_daemon->startDetached(m_monerod, arguments);
// add state changed listener
connect(m_daemon,SIGNAL(stateChanged(QProcess::ProcessState)),this,SLOT(stateChanged(QProcess::ProcessState)));
connect(m_daemon.get(), SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(stateChanged(QProcess::ProcessState)));
if (!started) {
qDebug() << "Daemon start error: " + m_daemon->errorString();
@@ -200,9 +202,9 @@ bool DaemonManager::stopWatcher(NetworkType::Type nettype) const
if(counter >= 5) {
qDebug() << "Killing it! ";
#ifdef Q_OS_WIN
QProcess::execute("taskkill /F /IM monerod.exe");
QProcess::execute("taskkill", {"/F", "/IM", "monerod.exe"});
#else
QProcess::execute("pkill monerod");
QProcess::execute("pkill", {"monerod"});
#endif
}
@@ -223,7 +225,10 @@ void DaemonManager::stateChanged(QProcess::ProcessState state)
void DaemonManager::printOutput()
{
QByteArray byteArray = m_daemon->readAllStandardOutput();
QByteArray byteArray = [this]() {
QMutexLocker locker(&m_daemonMutex);
return m_daemon->readAllStandardOutput();
}();
QStringList strLines = QString(byteArray).split("\n");
foreach (QString line, strLines) {
@@ -234,7 +239,10 @@ void DaemonManager::printOutput()
void DaemonManager::printError()
{
QByteArray byteArray = m_daemon->readAllStandardError();
QByteArray byteArray = [this]() {
QMutexLocker locker(&m_daemonMutex);
return m_daemon->readAllStandardError();
}();
QStringList strLines = QString(byteArray).split("\n");
foreach (QString line, strLines) {
@@ -351,7 +359,6 @@ DaemonManager::DaemonManager(QObject *parent)
if (m_monerod.length() == 0) {
qCritical() << "no daemon binary defined for current platform";
m_has_daemon = false;
}
}

View File

@@ -29,6 +29,9 @@
#ifndef DAEMONMANAGER_H
#define DAEMONMANAGER_H
#include <memory>
#include <QMutex>
#include <QObject>
#include <QUrl>
#include <QProcess>
@@ -78,10 +81,9 @@ private:
static DaemonManager * m_instance;
static QStringList m_clArgs;
QProcess *m_daemon;
bool initialized = false;
std::unique_ptr<QProcess> m_daemon;
QMutex m_daemonMutex;
QString m_monerod;
bool m_has_daemon = true;
bool m_app_exit = false;
bool m_noSync = false;

View File

@@ -0,0 +1,70 @@
// Copyright (c) 2014-2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "PassphraseHelper.h"
#include <QMutexLocker>
#include <QDebug>
Monero::optional<std::string> PassphraseHelper::onDevicePassphraseRequest(bool & on_device)
{
qDebug() << __FUNCTION__;
QMutexLocker locker(&m_mutex_pass);
m_passphrase_on_device = true;
m_passphrase_abort = false;
if (m_prompter != nullptr){
m_prompter->onWalletPassphraseNeeded(on_device);
}
m_cond_pass.wait(&m_mutex_pass);
if (m_passphrase_abort)
{
throw std::runtime_error("Passphrase entry abort");
}
on_device = m_passphrase_on_device;
if (!on_device) {
auto tmpPass = m_passphrase.toStdString();
m_passphrase = QString();
return Monero::optional<std::string>(tmpPass);
} else {
return Monero::optional<std::string>();
}
}
void PassphraseHelper::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
{
qDebug() << __FUNCTION__;
QMutexLocker locker(&m_mutex_pass);
m_passphrase = passphrase;
m_passphrase_abort = entry_abort;
m_passphrase_on_device = enter_on_device;
m_cond_pass.wakeAll();
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2014-2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef MONERO_GUI_PASSPHRASEHELPER_H
#define MONERO_GUI_PASSPHRASEHELPER_H
#include <QtGlobal>
#include <wallet/api/wallet2_api.h>
#include <QMutex>
#include <QPointer>
#include <QWaitCondition>
#include <QMutex>
/**
* Implements component responsible for showing entry prompt to the user,
* typically Wallet / Wallet manager.
*/
class PassprasePrompter {
public:
virtual void onWalletPassphraseNeeded(bool onDevice) = 0;
};
/**
* Implements receiver of the passphrase responsible for passing it back to the wallet,
* typically wallet listener.
*/
class PassphraseReceiver {
public:
virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) = 0;
};
class PassphraseHelper {
public:
PassphraseHelper(PassprasePrompter * prompter=nullptr): m_prompter(prompter) {};
PassphraseHelper(const PassphraseHelper & h): PassphraseHelper(h.m_prompter) {};
Monero::optional<std::string> onDevicePassphraseRequest(bool & on_device);
void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort);
private:
PassprasePrompter * m_prompter;
QWaitCondition m_cond_pass;
QMutex m_mutex_pass;
QString m_passphrase;
bool m_passphrase_abort;
bool m_passphrase_on_device;
};
#endif //MONERO_GUI_PASSPHRASEHELPER_H

View File

@@ -34,6 +34,7 @@
#include <QDebug>
#include <QReadLocker>
#include <QWriteLocker>
#include <QtGlobal>
bool TransactionHistory::transaction(int index, std::function<void (TransactionInfo &)> callback)
@@ -58,7 +59,11 @@ bool TransactionHistory::transaction(int index, std::function<void (TransactionI
void TransactionHistory::refresh(quint32 accountIndex)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QDateTime firstDateTime = QDate(2014, 4, 18).startOfDay();
#else
QDateTime firstDateTime = QDateTime(QDate(2014, 4, 18)); // the genesis block
#endif
QDateTime lastDateTime = QDateTime::currentDateTime().addDays(1); // tomorrow (guard against jitter and timezones)
emit refreshStarted();
@@ -143,7 +148,11 @@ bool TransactionHistory::TransactionHistory::locked() const
TransactionHistory::TransactionHistory(Monero::TransactionHistory *pimpl, QObject *parent)
: QObject(parent), m_pimpl(pimpl), m_minutesToUnlock(0), m_locked(false)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
m_firstDateTime = QDate(2014, 4, 18).startOfDay();
#else
m_firstDateTime = QDateTime(QDate(2014, 4, 18)); // the genesis block
#endif
m_lastDateTime = QDateTime::currentDateTime().addDays(1); // tomorrow (guard against jitter and timezones)
}

View File

@@ -48,7 +48,6 @@
#include <QtConcurrent/QtConcurrent>
#include <QList>
#include <QVector>
#include <QMutex>
#include <QMutexLocker>
namespace {
@@ -59,66 +58,6 @@ namespace {
static constexpr char ATTRIBUTE_SUBADDRESS_ACCOUNT[] ="gui.subaddress_account";
}
class WalletListenerImpl : public Monero::WalletListener
{
public:
WalletListenerImpl(Wallet * w)
: m_wallet(w)
{
}
virtual void moneySpent(const std::string &txId, uint64_t amount) override
{
qDebug() << __FUNCTION__;
emit m_wallet->moneySpent(QString::fromStdString(txId), amount);
}
virtual void moneyReceived(const std::string &txId, uint64_t amount) override
{
qDebug() << __FUNCTION__;
emit m_wallet->moneyReceived(QString::fromStdString(txId), amount);
}
virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override
{
qDebug() << __FUNCTION__;
emit m_wallet->unconfirmedMoneyReceived(QString::fromStdString(txId), amount);
}
virtual void newBlock(uint64_t height) override
{
// qDebug() << __FUNCTION__;
emit m_wallet->newBlock(height, m_wallet->daemonBlockChainTargetHeight());
}
virtual void updated() override
{
emit m_wallet->updated();
}
// called when wallet refreshed by background thread or explicitly
virtual void refreshed() override
{
qDebug() << __FUNCTION__;
emit m_wallet->refreshed();
}
virtual void onDeviceButtonRequest(uint64_t code) override
{
emit m_wallet->deviceButtonRequest(code);
}
virtual void onDeviceButtonPressed() override
{
emit m_wallet->deviceButtonPressed();
}
private:
Wallet * m_wallet;
};
Wallet::Wallet(QObject * parent)
: Wallet(nullptr, parent)
{
@@ -227,12 +166,22 @@ QString Wallet::address(quint32 accountIndex, quint32 addressIndex) const
QString Wallet::path() const
{
return QString::fromStdString(m_walletImpl->path());
return QDir::toNativeSeparators(QString::fromStdString(m_walletImpl->path()));
}
bool Wallet::store(const QString &path)
void Wallet::storeAsync(const QJSValue &callback, const QString &path /* = "" */)
{
return m_walletImpl->store(path.toStdString());
const auto future = m_scheduler.run(
[this, path] {
QMutexLocker locker(&m_storeMutex);
return QJSValueList({m_walletImpl->store(path.toStdString())});
},
callback);
if (!future.first)
{
QJSValue(callback).call(QJSValueList({false}));
}
}
bool Wallet::init(const QString &daemonAddress, bool trustedDaemon, quint64 upperTransactionLimit, bool isRecovering, bool isRecoveringFromDevice, quint64 restoreHeight)
@@ -290,6 +239,11 @@ bool Wallet::isLedger() const
return m_walletImpl->getDeviceType() == Monero::Wallet::Device_Ledger;
}
bool Wallet::isTrezor() const
{
return m_walletImpl->getDeviceType() == Monero::Wallet::Device_Trezor;
}
//! create a view only wallet
bool Wallet::createViewOnly(const QString &path, const QString &password) const
{
@@ -600,6 +554,19 @@ void Wallet::disposeTransaction(UnsignedTransaction *t)
delete t;
}
void Wallet::estimateTransactionFeeAsync(const QString &destination,
quint64 amount,
PendingTransaction::Priority priority,
const QJSValue &callback)
{
m_scheduler.run([this, destination, amount, priority] {
const uint64_t fee = m_walletImpl->estimateTransactionFee(
{std::make_pair(destination.toStdString(), amount)},
static_cast<Monero::PendingTransaction::Priority>(priority));
return QJSValueList({QString::fromStdString(Monero::Wallet::displayAmount(fee))});
}, callback);
}
TransactionHistory *Wallet::history() const
{
return m_history;
@@ -999,6 +966,19 @@ void Wallet::keyReuseMitigation2(bool mitigation)
m_walletImpl->keyReuseMitigation2(mitigation);
}
void Wallet::onWalletPassphraseNeeded(bool on_device)
{
emit this->walletPassphraseNeeded(on_device);
}
void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
{
if (m_walletListener != nullptr)
{
m_walletListener->onPassphraseEntered(passphrase, enter_on_device, entry_abort);
}
}
Wallet::Wallet(Monero::Wallet *w, QObject *parent)
: QObject(parent)
, m_walletImpl(w)

View File

@@ -41,6 +41,8 @@
#include "PendingTransaction.h" // we need to have an access to the PendingTransaction::Priority enum here;
#include "UnsignedTransaction.h"
#include "NetworkType.h"
#include "PassphraseHelper.h"
#include "WalletListenerImpl.h"
namespace Monero {
struct Wallet; // forward declaration
@@ -57,7 +59,7 @@ class SubaddressModel;
class SubaddressAccount;
class SubaddressAccountModel;
class Wallet : public QObject
class Wallet : public QObject, public PassprasePrompter
{
Q_OBJECT
Q_PROPERTY(bool disconnected READ disconnected NOTIFY disconnectedChanged)
@@ -144,7 +146,7 @@ public:
//! saves wallet to the file by given path
//! empty path stores in current location
Q_INVOKABLE bool store(const QString &path = "");
Q_INVOKABLE void storeAsync(const QJSValue &callback, const QString &path = "");
//! initializes wallet asynchronously
Q_INVOKABLE void initAsync(const QString &daemonAddress, bool trustedDaemon = false, quint64 upperTransactionLimit = 0, bool isRecovering = false, bool isRecoveringFromDevice = false, quint64 restoreHeight = 0);
@@ -185,6 +187,7 @@ public:
//! hw-device backed wallets
Q_INVOKABLE bool isHwBacked() const;
Q_INVOKABLE bool isLedger() const;
Q_INVOKABLE bool isTrezor() const;
//! returns if view only wallet
Q_INVOKABLE bool viewOnly() const;
@@ -250,6 +253,11 @@ public:
//! deletes unsigned transaction and frees memory
Q_INVOKABLE void disposeTransaction(UnsignedTransaction * t);
Q_INVOKABLE void estimateTransactionFeeAsync(const QString &destination,
quint64 amount,
PendingTransaction::Priority priority,
const QJSValue &callback);
//! returns transaction history
TransactionHistory * history() const;
@@ -343,6 +351,10 @@ public:
Q_INVOKABLE void segregationHeight(quint64 height);
Q_INVOKABLE void keyReuseMitigation2(bool mitigation);
// Passphrase entry for hardware wallets
Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false);
virtual void onWalletPassphraseNeeded(bool on_device) override;
// TODO: setListenter() when it implemented in API
signals:
// emitted on every event happened with wallet
@@ -362,6 +374,7 @@ signals:
void walletCreationHeightChanged();
void deviceButtonRequest(quint64 buttonCode);
void deviceButtonPressed();
void walletPassphraseNeeded(bool onDevice);
void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid);
void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const;
void deviceShowAddressShowed();
@@ -427,8 +440,9 @@ private:
bool m_connectionStatusRunning;
QString m_daemonUsername;
QString m_daemonPassword;
Monero::WalletListener *m_walletListener;
WalletListenerImpl *m_walletListener;
FutureScheduler m_scheduler;
QMutex m_storeMutex;
};

View File

@@ -0,0 +1,97 @@
// Copyright (c) 2014-2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "WalletListenerImpl.h"
#include "Wallet.h"
WalletListenerImpl::WalletListenerImpl(Wallet * w)
: m_wallet(w)
, m_phelper(w)
{
}
void WalletListenerImpl::moneySpent(const std::string &txId, uint64_t amount)
{
qDebug() << __FUNCTION__;
emit m_wallet->moneySpent(QString::fromStdString(txId), amount);
}
void WalletListenerImpl::moneyReceived(const std::string &txId, uint64_t amount)
{
qDebug() << __FUNCTION__;
emit m_wallet->moneyReceived(QString::fromStdString(txId), amount);
}
void WalletListenerImpl::unconfirmedMoneyReceived(const std::string &txId, uint64_t amount)
{
qDebug() << __FUNCTION__;
emit m_wallet->unconfirmedMoneyReceived(QString::fromStdString(txId), amount);
}
void WalletListenerImpl::newBlock(uint64_t height)
{
// qDebug() << __FUNCTION__;
emit m_wallet->newBlock(height, m_wallet->daemonBlockChainTargetHeight());
}
void WalletListenerImpl::updated()
{
emit m_wallet->updated();
}
// called when wallet refreshed by background thread or explicitly
void WalletListenerImpl::refreshed()
{
qDebug() << __FUNCTION__;
emit m_wallet->refreshed();
}
void WalletListenerImpl::onDeviceButtonRequest(uint64_t code)
{
qDebug() << __FUNCTION__;
emit m_wallet->deviceButtonRequest(code);
}
void WalletListenerImpl::onDeviceButtonPressed()
{
qDebug() << __FUNCTION__;
emit m_wallet->deviceButtonPressed();
}
void WalletListenerImpl::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
{
qDebug() << __FUNCTION__;
m_phelper.onPassphraseEntered(passphrase, enter_on_device, entry_abort);
}
Monero::optional<std::string> WalletListenerImpl::onDevicePassphraseRequest(bool & on_device)
{
qDebug() << __FUNCTION__;
return m_phelper.onDevicePassphraseRequest(on_device);
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2014-2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef MONERO_GUI_WALLETLISTENERIMPL_H
#define MONERO_GUI_WALLETLISTENERIMPL_H
#include "wallet/api/wallet2_api.h"
#include "PassphraseHelper.h"
class Wallet;
class WalletListenerImpl : public Monero::WalletListener, public PassphraseReceiver
{
public:
WalletListenerImpl(Wallet * w);
virtual void moneySpent(const std::string &txId, uint64_t amount) override;
virtual void moneyReceived(const std::string &txId, uint64_t amount) override;
virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override;
virtual void newBlock(uint64_t height) override;
virtual void updated() override;
// called when wallet refreshed by background thread or explicitly
virtual void refreshed() override;
virtual void onDeviceButtonRequest(uint64_t code) override;
virtual void onDeviceButtonPressed() override;
virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override;
virtual Monero::optional<std::string> onDevicePassphraseRequest(bool & on_device) override;
private:
Wallet * m_wallet;
PassphraseHelper m_phelper;
};
#endif //MONERO_GUI_WALLETLISTENERIMPL_H

View File

@@ -41,10 +41,13 @@
#include <QMutexLocker>
#include <QString>
class WalletPassphraseListenerImpl : public Monero::WalletListener
#include "qt/updater.h"
#include "qt/ScopeGuard.h"
class WalletPassphraseListenerImpl : public Monero::WalletListener, public PassphraseReceiver
{
public:
WalletPassphraseListenerImpl(WalletManager * mgr): m_mgr(mgr), m_wallet(nullptr) {}
WalletPassphraseListenerImpl(WalletManager * mgr): m_mgr(mgr), m_phelper(mgr) {}
virtual void moneySpent(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; };
virtual void moneyReceived(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; };
@@ -53,43 +56,33 @@ public:
virtual void updated() override {};
virtual void refreshed() override {};
virtual Monero::optional<std::string> onDevicePassphraseRequest(bool on_device) override
virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override
{
qDebug() << __FUNCTION__;
if (on_device) return Monero::optional<std::string>();
m_phelper.onPassphraseEntered(passphrase, enter_on_device, entry_abort);
}
m_mgr->onWalletPassphraseNeeded(m_wallet);
if (m_mgr->m_passphrase_abort)
{
throw std::runtime_error("Passphrase entry abort");
}
auto tmpPass = m_mgr->m_passphrase.toStdString();
m_mgr->m_passphrase = QString();
return Monero::optional<std::string>(tmpPass);
virtual Monero::optional<std::string> onDevicePassphraseRequest(bool & on_device) override
{
qDebug() << __FUNCTION__;
return m_phelper.onDevicePassphraseRequest(on_device);
}
virtual void onDeviceButtonRequest(uint64_t code) override
{
emit m_mgr->deviceButtonRequest(code);
qDebug() << __FUNCTION__;
emit m_mgr->deviceButtonRequest(code);
}
virtual void onDeviceButtonPressed() override
{
emit m_mgr->deviceButtonPressed();
}
virtual void onSetWallet(Monero::Wallet * wallet) override
{
qDebug() << __FUNCTION__;
m_wallet = wallet;
emit m_mgr->deviceButtonPressed();
}
private:
WalletManager * m_mgr;
Monero::Wallet * m_wallet;
PassphraseHelper m_phelper;
};
WalletManager * WalletManager::m_instance = nullptr;
@@ -121,6 +114,13 @@ Wallet *WalletManager::openWallet(const QString &path, const QString &password,
{
QMutexLocker locker(&m_mutex);
WalletPassphraseListenerImpl tmpListener(this);
m_mutex_passphraseReceiver.lock();
m_passphraseReceiver = &tmpListener;
m_mutex_passphraseReceiver.unlock();
const auto cleanup = sg::make_scope_guard([this]() noexcept {
QMutexLocker passphrase_locker(&m_mutex_passphraseReceiver);
this->m_passphraseReceiver = nullptr;
});
if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet;
@@ -151,14 +151,14 @@ void WalletManager::openWalletAsync(const QString &path, const QString &password
}
Wallet *WalletManager::recoveryWallet(const QString &path, const QString &memo, NetworkType::Type nettype, quint64 restoreHeight, quint64 kdfRounds)
Wallet *WalletManager::recoveryWallet(const QString &path, const QString &seed, const QString &seed_offset, NetworkType::Type nettype, quint64 restoreHeight, quint64 kdfRounds)
{
QMutexLocker locker(&m_mutex);
if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet;
}
Monero::Wallet * w = m_pimpl->recoveryWallet(path.toStdString(), "", memo.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight, kdfRounds);
Monero::Wallet * w = m_pimpl->recoveryWallet(path.toStdString(), "", seed.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight, kdfRounds, seed_offset.toStdString());
m_currentWallet = new Wallet(w);
return m_currentWallet;
}
@@ -184,6 +184,13 @@ Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString
{
QMutexLocker locker(&m_mutex);
WalletPassphraseListenerImpl tmpListener(this);
m_mutex_passphraseReceiver.lock();
m_passphraseReceiver = &tmpListener;
m_mutex_passphraseReceiver.unlock();
const auto cleanup = sg::make_scope_guard([this]() noexcept {
QMutexLocker passphrase_locker(&m_mutex_passphraseReceiver);
this->m_passphraseReceiver = nullptr;
});
if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet;
@@ -461,15 +468,43 @@ bool WalletManager::saveQrCode(const QString &code, const QString &path) const
return QRCodeImageProvider::genQrImage(code, &size).scaled(size.expandedTo(QSize(240, 240)), Qt::KeepAspectRatio).save(path, "PNG", 100);
}
void WalletManager::checkUpdatesAsync(const QString &software, const QString &subdir)
void WalletManager::checkUpdatesAsync(
const QString &software,
const QString &subdir,
const QString &buildTag,
const QString &version)
{
m_scheduler.run([this, software, subdir] {
emit checkUpdatesComplete(checkUpdates(software, subdir));
m_scheduler.run([this, software, subdir, buildTag, version] {
const auto updateInfo = Monero::WalletManager::checkUpdates(
software.toStdString(),
subdir.toStdString(),
buildTag.toStdString().c_str(),
version.toStdString().c_str());
if (!std::get<0>(updateInfo))
{
return;
}
const QString version = QString::fromStdString(std::get<1>(updateInfo));
const QByteArray hashFromDns = QByteArray::fromHex(QString::fromStdString(std::get<2>(updateInfo)).toUtf8());
const QString downloadUrl = QString::fromStdString(std::get<4>(updateInfo));
try
{
const QString binaryFilename = QUrl(downloadUrl).fileName();
QPair<QString, QString> signers;
const QString signedHash = Updater().fetchSignedHash(binaryFilename, hashFromDns, signers).toHex();
qInfo() << "Update found" << version << downloadUrl << "hash" << signedHash << "signed by" << signers;
emit checkUpdatesComplete(version, downloadUrl, signedHash, signers.first, signers.second);
}
catch (const std::exception &e)
{
qCritical() << "Failed to fetch and verify signed hash:" << e.what();
}
});
}
QString WalletManager::checkUpdates(const QString &software, const QString &subdir) const
{
qDebug() << "Checking for updates";
@@ -499,6 +534,7 @@ bool WalletManager::clearWalletCache(const QString &wallet_path) const
WalletManager::WalletManager(QObject *parent)
: QObject(parent)
, m_passphraseReceiver(nullptr)
, m_scheduler(this)
{
m_pimpl = Monero::WalletManagerFactory::getWalletManager();
@@ -509,22 +545,16 @@ WalletManager::~WalletManager()
m_scheduler.shutdownWaitForFinished();
}
void WalletManager::onWalletPassphraseNeeded(Monero::Wallet *)
void WalletManager::onWalletPassphraseNeeded(bool on_device)
{
m_mutex_pass.lock();
m_passphrase_abort = false;
emit this->walletPassphraseNeeded();
m_cond_pass.wait(&m_mutex_pass);
m_mutex_pass.unlock();
emit this->walletPassphraseNeeded(on_device);
}
void WalletManager::onPassphraseEntered(const QString &passphrase, bool entry_abort)
void WalletManager::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
{
m_mutex_pass.lock();
m_passphrase = passphrase;
m_passphrase_abort = entry_abort;
m_cond_pass.wakeAll();
m_mutex_pass.unlock();
QMutexLocker locker(&m_mutex_passphraseReceiver);
if (m_passphraseReceiver != nullptr)
{
m_passphraseReceiver->onPassphraseEntered(passphrase, enter_on_device, entry_abort);
}
}

View File

@@ -36,16 +36,16 @@
#include <QMutex>
#include <QPointer>
#include <QWaitCondition>
#include <QMutex>
#include "qt/FutureScheduler.h"
#include "NetworkType.h"
#include "PassphraseHelper.h"
class Wallet;
namespace Monero {
struct WalletManager;
}
class WalletManager : public QObject
class WalletManager : public QObject, public PassprasePrompter
{
Q_OBJECT
Q_PROPERTY(bool connected READ connected)
@@ -83,7 +83,7 @@ public:
Q_INVOKABLE void openWalletAsync(const QString &path, const QString &password, NetworkType::Type nettype = NetworkType::MAINNET, quint64 kdfRounds = 1);
// wizard: recoveryWallet path; hint: internally it recorvers wallet and set password = ""
Q_INVOKABLE Wallet * recoveryWallet(const QString &path, const QString &memo,
Q_INVOKABLE Wallet * recoveryWallet(const QString &path, const QString &seed, const QString &seed_offset,
NetworkType::Type nettype = NetworkType::MAINNET, quint64 restoreHeight = 0, quint64 kdfRounds = 1);
Q_INVOKABLE Wallet * createWalletFromKeys(const QString &path,
@@ -176,23 +176,32 @@ public:
Q_INVOKABLE bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error) const;
Q_INVOKABLE QVariantMap parse_uri_to_object(const QString &uri) const;
Q_INVOKABLE bool saveQrCode(const QString &, const QString &) const;
Q_INVOKABLE void checkUpdatesAsync(const QString &software, const QString &subdir);
Q_INVOKABLE void checkUpdatesAsync(
const QString &software,
const QString &subdir,
const QString &buildTag,
const QString &version);
Q_INVOKABLE QString checkUpdates(const QString &software, const QString &subdir) const;
// clear/rename wallet cache
Q_INVOKABLE bool clearWalletCache(const QString &fileName) const;
Q_INVOKABLE void onWalletPassphraseNeeded(Monero::Wallet * wallet);
Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool entry_abort=false);
Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false);
virtual void onWalletPassphraseNeeded(bool on_device) override;
signals:
void walletOpened(Wallet * wallet);
void walletCreated(Wallet * wallet);
void walletPassphraseNeeded();
void walletPassphraseNeeded(bool onDevice);
void deviceButtonRequest(quint64 buttonCode);
void deviceButtonPressed();
void checkUpdatesComplete(const QString &result) const;
void checkUpdatesComplete(
const QString &version,
const QString &downloadUrl,
const QString &hash,
const QString &firstSigner,
const QString &secondSigner) const;
void miningStatus(bool isMining) const;
public slots:
@@ -208,12 +217,8 @@ private:
Monero::WalletManager * m_pimpl;
mutable QMutex m_mutex;
QPointer<Wallet> m_currentWallet;
QWaitCondition m_cond_pass;
QMutex m_mutex_pass;
QString m_passphrase;
bool m_passphrase_abort;
PassphraseReceiver * m_passphraseReceiver;
QMutex m_mutex_passphraseReceiver;
FutureScheduler m_scheduler;
};

View File

@@ -30,7 +30,6 @@
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QStandardPaths>
#include <QNetworkAccessManager>
#include <QIcon>
#include <QDebug>
#include <QDesktopServices>
@@ -62,18 +61,25 @@
#include "wallet/api/wallet2_api.h"
#include "Logger.h"
#include "MainApp.h"
#include "qt/downloader.h"
#include "qt/ipc.h"
#include "qt/network.h"
#include "qt/updater.h"
#include "qt/utils.h"
#include "qt/TailsOS.h"
#include "qt/KeysFiles.h"
#include "qt/MoneroSettings.h"
#include "qt/prices.h"
#include "qt/NetworkAccessBlockingFactory.h"
// IOS exclusions
#ifndef Q_OS_IOS
#include "daemon/DaemonManager.h"
#endif
#if defined(Q_OS_WIN)
#include <QOpenGLContext>
#endif
#ifdef WITH_SCANNER
#include "QR-Code-scanner/QrCodeScanner.h"
#endif
@@ -89,11 +95,8 @@
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin);
#endif
Q_IMPORT_PLUGIN(QSvgIconPlugin)
Q_IMPORT_PLUGIN(QGifPlugin)
Q_IMPORT_PLUGIN(QICNSPlugin)
Q_IMPORT_PLUGIN(QICOPlugin)
Q_IMPORT_PLUGIN(QJpegPlugin)
Q_IMPORT_PLUGIN(QMngPlugin)
Q_IMPORT_PLUGIN(QSvgPlugin)
Q_IMPORT_PLUGIN(QTgaPlugin)
Q_IMPORT_PLUGIN(QTiffPlugin)
@@ -125,7 +128,9 @@ Q_IMPORT_PLUGIN(QtQuick2PrivateWidgetsPlugin)
Q_IMPORT_PLUGIN(QtQuickControls2Plugin)
Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin)
Q_IMPORT_PLUGIN(QmlXmlListModelPlugin)
#ifdef WITH_SCANNER
Q_IMPORT_PLUGIN(QMultimediaDeclarativeModule)
#endif
#endif
@@ -178,6 +183,17 @@ int main(int argc, char *argv[])
MainApp app(argc, argv);
#if defined(Q_OS_WIN)
if (isOpenGL)
{
QOpenGLContext ctx;
isOpenGL = ctx.create() && ctx.format().version() >= qMakePair(2, 1);
if (!isOpenGL) {
qputenv("QMLSCENE_DEVICE", "softwarecontext");
}
}
#endif
app.setApplicationName("monero-core");
app.setOrganizationDomain("getmonero.org");
app.setOrganizationName("monero-project");
@@ -209,6 +225,7 @@ int main(int argc, char *argv[])
qCritical() << "Error: accounts root directory could not be set";
return 1;
}
moneroAccountsDir = QDir::toNativeSeparators(moneroAccountsDir);
#if defined(Q_OS_LINUX)
if (isDesktop) app.setWindowIcon(QIcon(":/images/appicon.ico"));
@@ -222,6 +239,16 @@ int main(int argc, char *argv[])
QCoreApplication::translate("main", "Log to specified file"),
QCoreApplication::translate("main", "file"));
QCommandLineOption verifyUpdateOption("verify-update", "\
Verify update binary using 'shasum'-compatible (SHA256 algo) output signed by two maintainers.\n\
* Requires 'hashes.txt' - signed 'shasum' output \
(i.e. 'gpg -o hashes.txt --clear-sign <shasum_output>') generated by a maintainer.\n\
* Requires 'hashes.txt.sig' - detached signature of 'hashes.txt' \
(i.e. 'gpg -b hashes.txt') generated by another maintainer.", "update-binary");
parser.addOption(verifyUpdateOption);
QCommandLineOption disableCheckUpdatesOption("disable-check-updates", "Disable automatic check for updates.");
parser.addOption(disableCheckUpdatesOption);
QCommandLineOption testQmlOption("test-qml");
testQmlOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(logPathOption);
@@ -232,7 +259,7 @@ int main(int argc, char *argv[])
Monero::Utils::onStartup();
// Log settings
const QString logPath = getLogPath(parser.value(logPathOption));
const QString logPath = QDir::toNativeSeparators(getLogPath(parser.value(logPathOption)));
Monero::Wallet::init(argv[0], "monero-wallet-gui", logPath.toStdString().c_str(), true);
qInstallMessageHandler(messageHandler);
@@ -245,6 +272,32 @@ int main(int argc, char *argv[])
}
qWarning().noquote() << "app startd" << "(log: " + logPath + ")";
if (parser.isSet(verifyUpdateOption))
{
const QString updateBinaryFullPath = parser.value(verifyUpdateOption);
const QFileInfo updateBinaryInfo(updateBinaryFullPath);
const QString updateBinaryDir = QDir::toNativeSeparators(updateBinaryInfo.absolutePath()) + QDir::separator();
const QString hashesTxt = updateBinaryDir + "hashes.txt";
const QString hashesTxtSig = hashesTxt + ".sig";
try
{
const QByteArray updateBinaryContents = fileGetContents(updateBinaryFullPath);
const QPair<QString, QString> signers = Updater().verifySignaturesAndHashSum(
fileGetContents(hashesTxt),
fileGetContents(hashesTxtSig),
updateBinaryInfo.fileName(),
updateBinaryContents.data(),
updateBinaryContents.size());
qCritical() << "successfully verified, signed by" << signers.first << "and" << signers.second;
return 0;
}
catch (const std::exception &e)
{
qCritical() << e.what();
}
return 1;
}
// Desktop entry
#ifdef Q_OS_LINUX
registerXdgMime(app);
@@ -296,6 +349,7 @@ int main(int argc, char *argv[])
// registering types for QML
qmlRegisterType<clipboardAdapter>("moneroComponents.Clipboard", 1, 0, "Clipboard");
qmlRegisterType<Downloader>("moneroComponents.Downloader", 1, 0, "Downloader");
// Temporary Qt.labs.settings replacement
qmlRegisterType<MoneroSettings>("moneroComponents.Settings", 1, 0, "MoneroSettings");
@@ -364,6 +418,9 @@ int main(int argc, char *argv[])
QQmlApplicationEngine engine;
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
engine.setNetworkAccessManagerFactory(new NetworkAccessBlockingFactory);
#endif
OSCursor cursor;
engine.rootContext()->setContextProperty("globalCursor", &cursor);
OSHelper osHelper;
@@ -430,6 +487,7 @@ int main(int argc, char *argv[])
engine.rootContext()->setContextProperty("homePath", QDir::homePath());
engine.rootContext()->setContextProperty("applicationDirectory", QApplication::applicationDirPath());
engine.rootContext()->setContextProperty("idealThreadCount", QThread::idealThreadCount());
engine.rootContext()->setContextProperty("disableCheckUpdatesFlag", parser.isSet(disableCheckUpdatesOption));
bool builtWithScanner = false;
#ifdef WITH_SCANNER
@@ -437,9 +495,8 @@ int main(int argc, char *argv[])
#endif
engine.rootContext()->setContextProperty("builtWithScanner", builtWithScanner);
QNetworkAccessManager *manager = new QNetworkAccessManager();
Prices prices(manager);
engine.rootContext()->setContextProperty("Prices", &prices);
Network network;
engine.rootContext()->setContextProperty("Network", &network);
// Load main window (context properties needs to be defined obove this line)
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));

View File

@@ -27,6 +27,9 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "oshelper.h"
#include <QCoreApplication>
#include <QFileDialog>
#include <QStandardPaths>
#include <QTemporaryFile>
#include <QDir>
#include <QDebug>
@@ -82,6 +85,11 @@ OSHelper::OSHelper(QObject *parent) : QObject(parent)
}
QString OSHelper::downloadLocation() const
{
return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
}
bool OSHelper::openContainingFolder(const QString &filePath) const
{
#if defined(Q_OS_WIN)
@@ -105,6 +113,12 @@ bool OSHelper::openContainingFolder(const QString &filePath) const
return QDesktopServices::openUrl(url);
}
QString OSHelper::openSaveFileDialog(const QString &title, const QString &folder, const QString &filename) const
{
const QString hint = (folder.isEmpty() ? "" : folder + QDir::separator()) + filename;
return QFileDialog::getSaveFileName(nullptr, title, hint);
}
QString OSHelper::temporaryFilename() const
{
QString tempFileName;
@@ -151,3 +165,47 @@ QString OSHelper::temporaryPath() const
{
return QDir::tempPath();
}
bool OSHelper::installed() const
{
#ifdef Q_OS_WIN
static constexpr const wchar_t installKey[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Monero GUI Wallet_is1";
static constexpr const wchar_t installValue[] = L"InstallLocation";
DWORD size;
LSTATUS status =
::RegGetValueW(HKEY_LOCAL_MACHINE, installKey, installValue, RRF_RT_REG_SZ, nullptr, nullptr, &size);
if (status == ERROR_FILE_NOT_FOUND)
{
return false;
}
if (status != ERROR_SUCCESS)
{
qCritical() << "RegGetValueW failed (get size)" << status;
return false;
}
std::wstring installLocation;
installLocation.resize(size / sizeof(std::wstring::value_type));
size = installLocation.size() * sizeof(std::wstring::value_type);
status = ::RegGetValueW(
HKEY_LOCAL_MACHINE,
installKey,
installValue,
RRF_RT_REG_SZ,
nullptr,
&installLocation[0],
&size);
if (status != ERROR_SUCCESS)
{
qCritical() << "RegGetValueW Failed (read)" << status;
return false;
}
const QDir installDir(QString(reinterpret_cast<const QChar *>(&installLocation[0])));
return installDir == QDir(QCoreApplication::applicationDirPath());
#else
return false;
#endif
}

View File

@@ -36,15 +36,22 @@
class OSHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(bool installed READ installed CONSTANT);
public:
explicit OSHelper(QObject *parent = 0);
Q_INVOKABLE QString downloadLocation() const;
Q_INVOKABLE bool openContainingFolder(const QString &filePath) const;
Q_INVOKABLE QString openSaveFileDialog(const QString &title, const QString &folder, const QString &filename) const;
Q_INVOKABLE QString temporaryFilename() const;
Q_INVOKABLE QString temporaryPath() const;
Q_INVOKABLE bool removeTemporaryWallet(const QString &walletName) const;
Q_INVOKABLE bool isCapsLock() const;
private:
bool installed() const;
signals:
public slots:

View File

@@ -30,6 +30,7 @@
#include "TransactionHistoryModel.h"
#include <QDebug>
#include <QtGlobal>
namespace {
/**
@@ -204,9 +205,14 @@ bool TransactionHistorySortFilterModel::filterAcceptsRow(int source_row, const Q
break;
case TransactionHistoryModel::TransactionTimeStampRole:
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QDateTime from = dateFromFilter().startOfDay();
QDateTime to = dateToFilter().endOfDay();
#else
QDateTime from = QDateTime(dateFromFilter());
QDateTime to = QDateTime(dateToFilter());
to = to.addDays(1); // including upperbound
#endif
QDateTime timestamp = data.toDateTime();
bool matchFrom = from.isNull() || timestamp.isNull() || timestamp >= from;
bool matchTo = to.isNull() || timestamp.isNull() || timestamp <= to;

View File

@@ -0,0 +1,18 @@
file(GLOB_RECURSE SOURCES *.cpp)
file(GLOB_RECURSE HEADERS *.h)
find_library(GCRYPT_LIBRARY gcrypt)
find_library(GPG_ERROR_LIBRARY gpg-error)
add_library(openpgp
${SOURCES}
${HEADERS})
target_include_directories(openpgp
PUBLIC
${CMAKE_SOURCE_DIR}/monero/contrib/epee/include)
target_link_libraries(openpgp
PUBLIC
${GCRYPT_LIBRARY}
${GPG_ERROR_LIBRARY})

107
src/openpgp/hash.h Normal file
View File

@@ -0,0 +1,107 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <vector>
#include <gcrypt.h>
#include <span.h>
namespace openpgp
{
class hash
{
public:
enum algorithm : uint8_t
{
sha256 = 8,
};
hash(const hash &) = delete;
hash &operator=(const hash &) = delete;
hash(uint8_t algorithm)
: algo(algorithm)
, consumed(0)
{
if (gcry_md_open(&md, algo, 0) != GPG_ERR_NO_ERROR)
{
throw std::runtime_error("failed to create message digest object");
}
}
~hash()
{
gcry_md_close(md);
}
hash &operator<<(uint8_t byte)
{
gcry_md_putc(md, byte);
++consumed;
return *this;
}
hash &operator<<(const epee::span<const uint8_t> &bytes)
{
gcry_md_write(md, &bytes[0], bytes.size());
consumed += bytes.size();
return *this;
}
hash &operator<<(const std::vector<uint8_t> &bytes)
{
return *this << epee::to_span(bytes);
}
std::vector<uint8_t> finish() const
{
std::vector<uint8_t> result(gcry_md_get_algo_dlen(algo));
const void *digest = gcry_md_read(md, algo);
if (digest == nullptr)
{
throw std::runtime_error("failed to read the digest");
}
memcpy(&result[0], digest, result.size());
return result;
}
size_t consumed_bytes() const
{
return consumed;
}
private:
const uint8_t algo;
gcry_md_hd_t md;
size_t consumed;
};
}

78
src/openpgp/mpi.h Normal file
View File

@@ -0,0 +1,78 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <gcrypt.h>
namespace openpgp
{
class mpi
{
public:
mpi(const mpi &) = delete;
mpi &operator=(const mpi &) = delete;
mpi(mpi &&other)
: data(other.data)
{
other.data = nullptr;
}
template <
typename byte_container,
typename = typename std::enable_if<(sizeof(typename byte_container::value_type) == 1)>::type>
mpi(const byte_container &buffer, gcry_mpi_format format = GCRYMPI_FMT_USG)
: mpi(&buffer[0], buffer.size(), format)
{
}
mpi(const void *buffer, size_t size, gcry_mpi_format format = GCRYMPI_FMT_USG)
{
if (gcry_mpi_scan(&data, format, buffer, size, nullptr) != GPG_ERR_NO_ERROR)
{
throw std::runtime_error("failed to read mpi from buffer");
}
}
~mpi()
{
gcry_mpi_release(data);
}
const gcry_mpi_t &get() const
{
return data;
}
private:
gcry_mpi_t data;
};
} // namespace openpgp

382
src/openpgp/openpgp.cpp Normal file
View File

@@ -0,0 +1,382 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "openpgp.h"
#include <algorithm>
#include <locale>
#include <vector>
#include <string_coding.h>
#include "hash.h"
#include "mpi.h"
#include "packet_stream.h"
#include "s_expression.h"
#include "serialization.h"
namespace openpgp
{
namespace
{
std::string::const_iterator find_next_line(std::string::const_iterator begin, const std::string::const_iterator &end)
{
begin = std::find(begin, end, '\n');
return begin != end ? ++begin : end;
}
std::string::const_iterator find_line_starting_with(
std::string::const_iterator it,
const std::string::const_iterator &end,
const std::string &starts_with)
{
for (std::string::const_iterator next_line; it != end; it = next_line)
{
next_line = find_next_line(it, end);
const size_t line_length = static_cast<size_t>(std::distance(it, next_line));
if (line_length >= starts_with.size() && std::equal(starts_with.begin(), starts_with.end(), it))
{
return it;
}
}
return end;
}
std::string::const_iterator find_empty_line(std::string::const_iterator it, const std::string::const_iterator &end)
{
for (; it != end && *it != '\r' && *it != '\n'; it = find_next_line(it, end))
{
}
return it;
}
std::string get_armored_block_contents(const std::string &text, const std::string &block_name)
{
static constexpr const char dashes[] = "-----";
const std::string armor_header = dashes + block_name + dashes;
auto block_start = find_line_starting_with(text.begin(), text.end(), armor_header);
auto block_headers = find_next_line(block_start, text.end());
auto block_end = find_line_starting_with(block_headers, text.end(), dashes);
auto contents_begin = find_next_line(find_empty_line(block_headers, block_end), block_end);
if (contents_begin == block_end)
{
throw std::runtime_error("armored block not found");
}
return std::string(contents_begin, block_end);
}
} // namespace
public_key_rsa::public_key_rsa(s_expression expression, size_t bits)
: m_expression(std::move(expression))
, m_bits(bits)
{
}
const gcry_sexp_t &public_key_rsa::get() const
{
return m_expression.get();
}
size_t public_key_rsa::bits() const
{
return m_bits;
}
public_key_block::public_key_block(const std::string &armored)
: public_key_block(epee::to_byte_span(epee::to_span(epee::string_encoding::base64_decode(
strip_line_breaks(get_armored_block_contents(armored, "BEGIN PGP PUBLIC KEY BLOCK"))))))
{
}
// TODO: Public-Key expiration, User ID and Public-Key certification, Subkey binding checks
public_key_block::public_key_block(const epee::span<const uint8_t> buffer)
{
packet_stream packets(buffer);
const std::vector<uint8_t> *data = packets.find_first(packet_tag::type::user_id);
if (data == nullptr)
{
throw std::runtime_error("user id is missing");
}
m_user_id.assign(data->begin(), data->end());
const auto append_public_key = [this](const std::vector<uint8_t> &data) {
deserializer<std::vector<uint8_t>> serialized(data);
const auto version = serialized.read_big_endian<uint8_t>();
if (version != 4)
{
throw std::runtime_error("unsupported public key version");
}
/* const auto timestamp = */ serialized.read_big_endian<uint32_t>();
const auto algorithm = serialized.read_big_endian<uint8_t>();
if (algorithm != openpgp::algorithm::rsa)
{
throw std::runtime_error("unsupported public key algorithm");
}
{
const mpi public_key_n = serialized.read_mpi();
const mpi public_key_e = serialized.read_mpi();
emplace_back(
s_expression("(public-key (rsa (n %m) (e %m)))", public_key_n.get(), public_key_e.get()),
gcry_mpi_get_nbits(public_key_n.get()));
}
};
data = packets.find_first(packet_tag::type::public_key);
if (data == nullptr)
{
throw std::runtime_error("public key is missing");
}
append_public_key(*data);
packets.for_each(packet_tag::type::public_subkey, append_public_key);
}
std::string public_key_block::user_id() const
{
return m_user_id;
}
// TODO: Signature expiration check
signature_rsa::signature_rsa(
uint8_t algorithm,
std::pair<uint8_t, uint8_t> hash_leftmost_bytes,
uint8_t hash_algorithm,
const std::vector<uint8_t> &hashed_data,
type type,
s_expression signature,
uint8_t version)
: m_hash_algorithm(hash_algorithm)
, m_hash_leftmost_bytes(hash_leftmost_bytes)
, m_hashed_appendix(format_hashed_appendix(algorithm, hash_algorithm, hashed_data, type, version))
, m_signature(std::move(signature))
, m_type(type)
{
}
signature_rsa signature_rsa::from_armored(const std::string &armored_signed_message)
{
return from_base64(get_armored_block_contents(armored_signed_message, "BEGIN PGP SIGNATURE"));
}
signature_rsa signature_rsa::from_base64(const std::string &base64)
{
std::string decoded = epee::string_encoding::base64_decode(strip_line_breaks(base64));
epee::span<const uint8_t> buffer(reinterpret_cast<const uint8_t *>(&decoded[0]), decoded.size());
return from_buffer(buffer);
}
signature_rsa signature_rsa::from_buffer(const epee::span<const uint8_t> input)
{
packet_stream packets(input);
const std::vector<uint8_t> *data = packets.find_first(packet_tag::type::signature);
if (data == nullptr)
{
throw std::runtime_error("signature is missing");
}
deserializer<std::vector<uint8_t>> buffer(*data);
const auto version = buffer.read_big_endian<uint8_t>();
if (version != 4)
{
throw std::runtime_error("unsupported signature version");
}
const auto signature_type = static_cast<type>(buffer.read_big_endian<uint8_t>());
const auto algorithm = buffer.read_big_endian<uint8_t>();
if (algorithm != openpgp::algorithm::rsa)
{
throw std::runtime_error("unsupported signature algorithm");
}
const auto hash_algorithm = buffer.read_big_endian<uint8_t>();
const auto hashed_data_length = buffer.read_big_endian<uint16_t>();
std::vector<uint8_t> hashed_data = buffer.read(hashed_data_length);
const auto unhashed_data_length = buffer.read_big_endian<uint16_t>();
buffer.read_span(unhashed_data_length);
std::pair<uint8_t, uint8_t> hash_leftmost_bytes{buffer.read_big_endian<uint8_t>(), buffer.read_big_endian<uint8_t>()};
const mpi signature = buffer.read_mpi();
return signature_rsa(
algorithm,
std::move(hash_leftmost_bytes),
hash_algorithm,
hashed_data,
signature_type,
s_expression("(sig-val (rsa (s %m)))", signature.get()),
version);
}
bool signature_rsa::verify(const epee::span<const uint8_t> message, const public_key_rsa &public_key) const
{
const s_expression signed_data = hash_message(message, public_key.bits());
return gcry_pk_verify(m_signature.get(), signed_data.get(), public_key.get()) == 0;
}
s_expression signature_rsa::hash_message(const epee::span<const uint8_t> message, size_t public_key_bits) const
{
switch (m_type)
{
case type::binary_document:
return hash_bytes(message, public_key_bits);
case type::canonical_text_document:
{
std::vector<uint8_t> crlf_formatted;
crlf_formatted.reserve(message.size());
const size_t message_size = message.size();
for (size_t offset = 0; offset < message_size; ++offset)
{
const auto &character = message[offset];
if (character == '\r')
{
continue;
}
if (character == '\n')
{
const bool skip_last_crlf = offset + 1 == message_size;
if (skip_last_crlf)
{
break;
}
crlf_formatted.push_back('\r');
}
crlf_formatted.push_back(character);
}
return hash_bytes(epee::to_span(crlf_formatted), public_key_bits);
}
default:
throw std::runtime_error("unsupported signature type");
}
}
std::vector<uint8_t> signature_rsa::hash_asn_object_id() const
{
size_t size;
if (gcry_md_algo_info(m_hash_algorithm, GCRYCTL_GET_ASNOID, nullptr, &size) != GPG_ERR_NO_ERROR)
{
throw std::runtime_error("failed to get ASN.1 Object Identifier (OID) size");
}
std::vector<uint8_t> asn_object_id(size);
if (gcry_md_algo_info(m_hash_algorithm, GCRYCTL_GET_ASNOID, &asn_object_id[0], &size) != GPG_ERR_NO_ERROR)
{
throw std::runtime_error("failed to get ASN.1 Object Identifier (OID)");
}
return asn_object_id;
}
s_expression signature_rsa::hash_bytes(const epee::span<const uint8_t> message, size_t public_key_bits) const
{
const std::vector<uint8_t> plain_hash = (hash(m_hash_algorithm) << message << m_hashed_appendix).finish();
if (plain_hash.size() < 2)
{
throw std::runtime_error("insufficient message hash size");
}
if (plain_hash[0] != m_hash_leftmost_bytes.first || plain_hash[1] != m_hash_leftmost_bytes.second)
{
throw std::runtime_error("signature checksum doesn't match the expected value");
}
std::vector<uint8_t> asn_object_id = hash_asn_object_id();
const size_t public_key_bytes = bits_to_bytes(public_key_bits);
if (public_key_bytes < plain_hash.size() + asn_object_id.size() + 11)
{
throw std::runtime_error("insufficient public key bit length");
}
std::vector<uint8_t> emsa_pkcs1_v1_5_encoded;
emsa_pkcs1_v1_5_encoded.reserve(public_key_bytes);
emsa_pkcs1_v1_5_encoded.push_back(0);
emsa_pkcs1_v1_5_encoded.push_back(1);
const size_t ps_size = public_key_bytes - plain_hash.size() - asn_object_id.size() - 3;
emsa_pkcs1_v1_5_encoded.insert(emsa_pkcs1_v1_5_encoded.end(), ps_size, 0xff);
emsa_pkcs1_v1_5_encoded.push_back(0);
emsa_pkcs1_v1_5_encoded.insert(emsa_pkcs1_v1_5_encoded.end(), asn_object_id.begin(), asn_object_id.end());
emsa_pkcs1_v1_5_encoded.insert(emsa_pkcs1_v1_5_encoded.end(), plain_hash.begin(), plain_hash.end());
mpi value(emsa_pkcs1_v1_5_encoded);
return s_expression("(data (flags raw) (value %m))", value.get());
}
std::vector<uint8_t> signature_rsa::format_hashed_appendix(
uint8_t algorithm,
uint8_t hash_algorithm,
const std::vector<uint8_t> &hashed_data,
uint8_t type,
uint8_t version)
{
const uint16_t hashed_data_size = static_cast<uint16_t>(hashed_data.size());
const uint32_t hashed_pefix_size = sizeof(version) + sizeof(type) + sizeof(algorithm) + sizeof(hash_algorithm) +
sizeof(hashed_data_size) + hashed_data.size();
std::vector<uint8_t> appendix;
appendix.reserve(hashed_pefix_size + sizeof(version) + sizeof(uint8_t) + sizeof(hashed_pefix_size));
appendix.push_back(version);
appendix.push_back(type);
appendix.push_back(algorithm);
appendix.push_back(hash_algorithm);
appendix.push_back(static_cast<uint8_t>(hashed_data_size >> 8));
appendix.push_back(static_cast<uint8_t>(hashed_data_size));
appendix.insert(appendix.end(), hashed_data.begin(), hashed_data.end());
appendix.push_back(version);
appendix.push_back(0xff);
appendix.push_back(static_cast<uint8_t>(hashed_pefix_size >> 24));
appendix.push_back(static_cast<uint8_t>(hashed_pefix_size >> 16));
appendix.push_back(static_cast<uint8_t>(hashed_pefix_size >> 8));
appendix.push_back(static_cast<uint8_t>(hashed_pefix_size));
return appendix;
}
message_armored::message_armored(const std::string &message_armored)
: m_message(get_armored_block_contents(message_armored, "BEGIN PGP SIGNED MESSAGE"))
{
}
message_armored::operator epee::span<const uint8_t>() const
{
return epee::to_byte_span(epee::to_span(m_message));
}
} // namespace openpgp

127
src/openpgp/openpgp.h Normal file
View File

@@ -0,0 +1,127 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <vector>
#include <gcrypt.h>
#include <span.h>
#include "s_expression.h"
namespace openpgp
{
enum algorithm : uint8_t
{
rsa = 1,
};
class public_key_rsa
{
public:
public_key_rsa(s_expression expression, size_t bits);
size_t bits() const;
const gcry_sexp_t &get() const;
private:
s_expression m_expression;
size_t m_bits;
};
class public_key_block : public std::vector<public_key_rsa>
{
public:
public_key_block(const std::string &armored);
public_key_block(const epee::span<const uint8_t> buffer);
std::string user_id() const;
private:
std::string m_user_id;
};
class signature_rsa
{
public:
enum type : uint8_t
{
binary_document = 0,
canonical_text_document = 1,
};
signature_rsa(
uint8_t algorithm,
std::pair<uint8_t, uint8_t> hash_leftmost_bytes,
uint8_t hash_algorithm,
const std::vector<uint8_t> &hashed_data,
type type,
s_expression signature,
uint8_t version);
static signature_rsa from_armored(const std::string &armored_signed_message);
static signature_rsa from_base64(const std::string &base64);
static signature_rsa from_buffer(const epee::span<const uint8_t> input);
bool verify(const epee::span<const uint8_t> message, const public_key_rsa &public_key) const;
private:
s_expression hash_message(const epee::span<const uint8_t> message, size_t public_key_bits) const;
std::vector<uint8_t> hash_asn_object_id() const;
s_expression hash_bytes(const epee::span<const uint8_t> message, size_t public_key_bits) const;
static std::vector<uint8_t> format_hashed_appendix(
uint8_t algorithm,
uint8_t hash_algorithm,
const std::vector<uint8_t> &hashed_data,
uint8_t type,
uint8_t version);
private:
uint8_t m_hash_algorithm;
std::pair<uint8_t, uint8_t> m_hash_leftmost_bytes;
std::vector<uint8_t> m_hashed_appendix;
s_expression m_signature;
type m_type;
};
class message_armored
{
public:
message_armored(const std::string &message_armored);
operator epee::span<const uint8_t>() const;
private:
std::string m_message;
};
} // namespace openpgp

View File

@@ -1,21 +1,21 @@
// Copyright (c) 2017-2018, The Monero Project
//
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
@@ -26,60 +26,63 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.9
import QtQuick.Controls 1.4
import moneroComponents.Wallet 1.0
import "." as MoneroComponents
#pragma once
Item {
id: item
property string message: ""
property bool active: false
height: 180
width: 320
property int margin: 15
x: parent.width - width - margin
y: parent.height - height * scale.yScale - margin * scale.yScale
#include <vector>
Rectangle {
color: "#FF6C3C"
border.color: "black"
anchors.fill: parent
#include <span.h>
TextArea {
id:versionText
readOnly: true
backgroundVisible: false
textFormat: TextEdit.AutoText
anchors.fill: parent
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 12
textMargin: 20
textColor: "white"
text: item.message
wrapMode: Text.WrapAnywhere
}
#include "serialization.h"
namespace openpgp
{
class packet_stream
{
public:
packet_stream(const epee::span<const uint8_t> buffer)
: packet_stream(deserializer<epee::span<const uint8_t>>(buffer))
{
}
template <
typename byte_container,
typename = typename std::enable_if<(sizeof(typename byte_container::value_type) == 1)>::type>
packet_stream(deserializer<byte_container> buffer)
{
while (!buffer.empty())
{
packet_tag tag = buffer.read_packet_tag();
packets.push_back({std::move(tag), buffer.read(tag.length)});
}
}
transform: Scale {
id: scale
yScale: item.active ? 1 : 0
Behavior on yScale {
NumberAnimation { duration: 500; easing.type: Easing.InOutCubic }
}
const std::vector<uint8_t> *find_first(packet_tag::type type) const
{
for (const auto &packet : packets)
{
if (packet.first.packet_type == type)
{
return &packet.second;
}
}
return nullptr;
}
Timer {
id: hider
interval: 30000; running: false; repeat: false
onTriggered: { item.active = false }
template <typename Callback>
void for_each(packet_tag::type type, Callback &callback) const
{
for (const auto &packet : packets)
{
if (packet.first.packet_type == type)
{
callback(packet.second);
}
}
}
function show(message) {
item.visible = true
item.message = message
item.active = true
hider.running = true
}
}
private:
std::vector<std::pair<packet_tag, std::vector<uint8_t>>> packets;
};
} // namespace openpgp

View File

@@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero Project
//
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
@@ -26,26 +26,53 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.9
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0
#pragma once
import "../components" as MoneroComponents
#include <algorithm>
#include <stdexcept>
Rectangle {
property bool active: false
Layout.preferredWidth: 30
Layout.fillHeight: true
property string activeColor: MoneroComponents.Style.defaultFontColor
property string inactiveColor: MoneroComponents.Style.progressBarBackgroundColor
color: "transparent"
#include <gcrypt.h>
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
width: 10
height: 10
radius: 10
color: parent.active ? parent.activeColor : parent.inactiveColor
namespace openpgp
{
class s_expression
{
public:
s_expression(const s_expression &) = delete;
s_expression &operator=(const s_expression &) = delete;
template <typename... Args>
s_expression(Args... args)
{
if (gcry_sexp_build(&data, nullptr, args...) != GPG_ERR_NO_ERROR)
{
throw std::runtime_error("failed to build S-expression");
}
}
}
s_expression(s_expression &&other)
{
std::swap(data, other.data);
}
s_expression(gcry_sexp_t data)
: data(data)
{
}
~s_expression()
{
gcry_sexp_release(data);
}
const gcry_sexp_t &get() const
{
return data;
}
private:
gcry_sexp_t data = nullptr;
};
} // namespace openpgp

172
src/openpgp/serialization.h Normal file
View File

@@ -0,0 +1,172 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include "mpi.h"
namespace openpgp
{
size_t bits_to_bytes(size_t bits)
{
constexpr const uint16_t bits_in_byte = 8;
return (bits + bits_in_byte - 1) / bits_in_byte;
}
std::string strip_line_breaks(const std::string &string)
{
std::string result;
result.reserve(string.size());
for (const auto &character : string)
{
if (character != '\r' && character != '\n')
{
result.push_back(character);
}
}
return result;
}
struct packet_tag
{
enum type : uint8_t
{
signature = 2,
public_key = 6,
user_id = 13,
public_subkey = 14,
};
const type packet_type;
const size_t length;
};
template <
typename byte_container,
typename = typename std::enable_if<(sizeof(typename byte_container::value_type) == 1)>::type>
class deserializer
{
public:
deserializer(byte_container buffer)
: buffer(std::move(buffer))
, cursor(0)
{
}
bool empty() const
{
return buffer.size() - cursor == 0;
}
packet_tag read_packet_tag()
{
const auto tag = read_big_endian<uint8_t>();
constexpr const uint8_t format_mask = 0b11000000;
constexpr const uint8_t format_old_tag = 0b10000000;
if ((tag & format_mask) != format_old_tag)
{
throw std::runtime_error("invalid packet tag");
}
const packet_tag::type packet_type = static_cast<packet_tag::type>((tag & 0b00111100) >> 2);
const uint8_t length_type = tag & 0b00000011;
size_t length;
switch (length_type)
{
case 0:
length = read_big_endian<uint8_t>();
break;
case 1:
length = read_big_endian<uint16_t>();
break;
case 2:
length = read_big_endian<uint32_t>();
break;
default:
throw std::runtime_error("unsupported packet length type");
}
return {packet_type, length};
}
mpi read_mpi()
{
const size_t bit_length = read_big_endian<uint16_t>();
return mpi(read_span(bits_to_bytes(bit_length)));
}
std::vector<uint8_t> read(size_t size)
{
if (buffer.size() - cursor < size)
{
throw std::runtime_error("insufficient buffer size");
}
const size_t offset = cursor;
cursor += size;
return {&buffer[offset], &buffer[cursor]};
}
template <typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
T read_big_endian()
{
if (buffer.size() - cursor < sizeof(T))
{
throw std::runtime_error("insufficient buffer size");
}
T result = 0;
for (size_t read = 0; read < sizeof(T); ++read)
{
result = (result << 8) | static_cast<uint8_t>(buffer[cursor++]);
}
return result;
}
epee::span<const uint8_t> read_span(size_t size)
{
if (buffer.size() - cursor < size)
{
throw std::runtime_error("insufficient buffer size");
}
const size_t offset = cursor;
cursor += size;
return {reinterpret_cast<const uint8_t *>(&buffer[offset]), size};
}
private:
byte_container buffer;
size_t cursor;
};
} // namespace openpgp

View File

@@ -43,11 +43,20 @@
#include "KeysFiles.h"
WalletKeysFiles::WalletKeysFiles(const qint64 &modified, const QString &path, const quint8 &networkType, const QString &address)
: m_modified(modified), m_path(path), m_networkType(networkType), m_address(address)
WalletKeysFiles::WalletKeysFiles(const QFileInfo &info, quint8 networkType, QString address)
: m_fileName(info.fileName())
, m_modified(info.lastModified().toSecsSinceEpoch())
, m_path(QDir::toNativeSeparators(info.absoluteFilePath()))
, m_networkType(networkType)
, m_address(std::move(address))
{
}
QString WalletKeysFiles::fileName() const
{
return m_fileName;
}
qint64 WalletKeysFiles::modified() const
{
return m_modified;
@@ -127,11 +136,7 @@ void WalletKeysFilesModel::findWallets(const QString &moneroAccountsDir)
file.close();
}
const QFileInfo info(wallet);
const QDateTime modifiedAt = info.lastModified();
this->addWalletKeysFile(WalletKeysFiles(modifiedAt.toSecsSinceEpoch(),
info.absoluteFilePath(), networkType, address));
this->addWalletKeysFile(WalletKeysFiles(wallet, networkType, std::move(address)));
}
}
@@ -152,6 +157,8 @@ QVariant WalletKeysFilesModel::data(const QModelIndex & index, int role) const {
return QVariant();
const WalletKeysFiles &walletKeyFile = m_walletKeyFiles[index.row()];
if (role == FileNameRole)
return walletKeyFile.fileName();
if (role == ModifiedRole)
return walletKeyFile.modified();
else if (role == PathRole)
@@ -165,6 +172,7 @@ QVariant WalletKeysFilesModel::data(const QModelIndex & index, int role) const {
QHash<int, QByteArray> WalletKeysFilesModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[FileNameRole] = "fileName";
roles[ModifiedRole] = "modified";
roles[PathRole] = "path";
roles[NetworkTypeRole] = "networktype";

View File

@@ -37,14 +37,16 @@
class WalletKeysFiles
{
public:
WalletKeysFiles(const qint64 &modified, const QString &path, const quint8 &networkType, const QString &address);
WalletKeysFiles(const QFileInfo &info, quint8 networkType, QString address);
QString fileName() const;
qint64 modified() const;
QString path() const;
quint8 networkType() const;
QString address() const;
private:
QString m_fileName;
qint64 m_modified;
QString m_path;
quint8 m_networkType;
@@ -56,7 +58,8 @@ class WalletKeysFilesModel : public QAbstractListModel
Q_OBJECT
public:
enum KeysFilesRoles {
ModifiedRole = Qt::UserRole + 1,
FileNameRole = Qt::UserRole + 1,
ModifiedRole,
PathRole,
NetworkTypeRole,
AddressRole

View File

@@ -0,0 +1,67 @@
/* Ricochet - https://ricochet.im/
* Copyright (C) 2014, John Brooks <john.brooks@dereferenced.net>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the names of the copyright owners nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Through the QQmlNetworkAccessManagerFactory below, all network requests
* created via QML will be passed to this object; including, for example,
* <img> tags parsed in rich Text items.
*
* Ricochet's UI does not directly cause network requests for any reason. These
* are always a potentially deanonymizing bug. This object will block them,
* and assert if appropriate.
*/
#include <QDebug>
class BlockedNetworkAccessManager : public QNetworkAccessManager
{
public:
BlockedNetworkAccessManager(QObject *parent)
: QNetworkAccessManager(parent)
{
setProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, QLatin1String("0.0.0.0"), 0));
}
protected:
virtual QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData = 0)
{
qCritical() << "QML attempted to load a network resource from" << req.url() << " - this is potentially an input sanitization flaw.";
return QNetworkAccessManager::createRequest(op, QNetworkRequest(), outgoingData);
}
};
class NetworkAccessBlockingFactory : public QQmlNetworkAccessManagerFactory
{
public:
virtual QNetworkAccessManager *create(QObject *parent)
{
return new BlockedNetworkAccessManager(parent);
}
};

205
src/qt/ScopeGuard.h Normal file
View File

@@ -0,0 +1,205 @@
// Author: ricab
// Source: https://github.com/ricab/scope_guard
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN 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.
//
// For more information, please refer to <http://unlicense.org>
#ifndef SCOPE_GUARD_HPP_
#define SCOPE_GUARD_HPP_
#include <type_traits>
#include <utility>
#if __cplusplus >= 201703L && defined(SG_REQUIRE_NOEXCEPT_IN_CPP17)
#define SG_REQUIRE_NOEXCEPT
#endif
namespace sg
{
namespace detail
{
/* --- Some custom type traits --- */
// Type trait determining whether a type is callable with no arguments
template<typename T, typename = void>
struct is_noarg_callable_t
: public std::false_type
{}; // in general, false
template<typename T>
struct is_noarg_callable_t<T, decltype(std::declval<T&&>()())>
: public std::true_type
{}; // only true when call expression valid
// Type trait determining whether a no-argument callable returns void
template<typename T>
struct returns_void_t
: public std::is_same<void, decltype(std::declval<T&&>()())>
{};
/* Type trait determining whether a no-arg callable is nothrow invocable if
required. This is where SG_REQUIRE_NOEXCEPT logic is encapsulated. */
template<typename T>
struct is_nothrow_invocable_if_required_t
: public
#ifdef SG_REQUIRE_NOEXCEPT
std::is_nothrow_invocable<T> /* Note: _r variants not enough to
confirm void return: any return can be
discarded so all returns are
compatible with void */
#else
std::true_type
#endif
{};
// logic AND of two or more type traits
template<typename A, typename B, typename... C>
struct and_t : public and_t<A, and_t<B, C...>>
{}; // for more than two arguments
template<typename A, typename B>
struct and_t<A, B> : public std::conditional<A::value, B, A>::type
{}; // for two arguments
// Type trait determining whether a type is a proper scope_guard callback.
template<typename T>
struct is_proper_sg_callback_t
: public and_t<is_noarg_callable_t<T>,
returns_void_t<T>,
is_nothrow_invocable_if_required_t<T>,
std::is_nothrow_destructible<T>>
{};
/* --- The actual scope_guard template --- */
template<typename Callback,
typename = typename std::enable_if<
is_proper_sg_callback_t<Callback>::value>::type>
class scope_guard;
/* --- Now the friend maker --- */
template<typename Callback>
detail::scope_guard<Callback> make_scope_guard(Callback&& callback)
noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value); /*
we need this in the inner namespace due to MSVC bugs preventing
sg::detail::scope_guard from befriending a sg::make_scope_guard
template instance in the parent namespace (see https://is.gd/xFfFhE). */
/* --- The template specialization that actually defines the class --- */
template<typename Callback>
class scope_guard<Callback> final
{
public:
typedef Callback callback_type;
scope_guard(scope_guard&& other)
noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value);
~scope_guard() noexcept; // highlight noexcept dtor
void dismiss() noexcept;
public:
scope_guard() = delete;
scope_guard(const scope_guard&) = delete;
scope_guard& operator=(const scope_guard&) = delete;
scope_guard& operator=(scope_guard&&) = delete;
private:
explicit scope_guard(Callback&& callback)
noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value); /*
meant for friends only */
friend scope_guard<Callback> make_scope_guard<Callback>(Callback&&)
noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value); /*
only make_scope_guard can create scope_guards from scratch (i.e. non-move)
*/
private:
Callback m_callback;
bool m_active;
};
} // namespace detail
/* --- Now the single public maker function --- */
using detail::make_scope_guard; // see comment on declaration above
} // namespace sg
////////////////////////////////////////////////////////////////////////////////
template<typename Callback>
sg::detail::scope_guard<Callback>::scope_guard(Callback&& callback)
noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value)
: m_callback(std::forward<Callback>(callback)) /* use () instead of {} because
of DR 1467 (https://is.gd/WHmWuo), which still impacts older compilers
(e.g. GCC 4.x and clang <=3.6, see https://godbolt.org/g/TE9tPJ and
https://is.gd/Tsmh8G) */
, m_active{true}
{}
////////////////////////////////////////////////////////////////////////////////
template<typename Callback>
sg::detail::scope_guard<Callback>::~scope_guard() noexcept
{
if(m_active)
m_callback();
}
////////////////////////////////////////////////////////////////////////////////
template<typename Callback>
sg::detail::scope_guard<Callback>::scope_guard(scope_guard&& other)
noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value)
: m_callback(std::forward<Callback>(other.m_callback)) // idem
, m_active{std::move(other.m_active)}
{
other.m_active = false;
}
////////////////////////////////////////////////////////////////////////////////
template<typename Callback>
inline void sg::detail::scope_guard<Callback>::dismiss() noexcept
{
m_active = false;
}
////////////////////////////////////////////////////////////////////////////////
template<typename Callback>
inline auto sg::detail::make_scope_guard(Callback&& callback)
noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value)
-> detail::scope_guard<Callback>
{
return detail::scope_guard<Callback>{std::forward<Callback>(callback)};
}
#endif /* SCOPE_GUARD_HPP_ */

222
src/qt/downloader.cpp Normal file
View File

@@ -0,0 +1,222 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "downloader.h"
#include <QReadLocker>
#include <QWriteLocker>
#include "updater.h"
namespace
{
class DownloaderStateGuard
{
public:
DownloaderStateGuard(bool &active, QReadWriteLock &mutex, std::function<void()> onActiveChanged)
: m_active(active)
, m_acquired(false)
, m_mutex(mutex)
, m_onActiveChanged(std::move(onActiveChanged))
{
{
QWriteLocker locker(&m_mutex);
if (m_active)
{
return;
}
m_active = true;
}
m_onActiveChanged();
m_acquired = true;
}
~DownloaderStateGuard()
{
if (!m_acquired)
{
return;
}
{
QWriteLocker locker(&m_mutex);
m_active = false;
}
m_onActiveChanged();
}
bool acquired() const
{
return m_acquired;
}
private:
bool &m_active;
bool m_acquired;
QReadWriteLock &m_mutex;
std::function<void()> m_onActiveChanged;
};
} // namespace
Downloader::Downloader(QObject *parent)
: QObject(parent)
, m_active(false)
, m_httpClient(new HttpClient())
, m_network(this)
, m_scheduler(this)
{
QObject::connect(m_httpClient.get(), SIGNAL(contentLengthChanged()), this, SIGNAL(totalChanged()));
QObject::connect(m_httpClient.get(), SIGNAL(receivedChanged()), this, SIGNAL(loadedChanged()));
}
Downloader::~Downloader()
{
cancel();
}
void Downloader::cancel()
{
m_httpClient->cancel();
QWriteLocker locker(&m_mutex);
m_contents.clear();
}
bool Downloader::get(const QString &url, const QString &hash, const QJSValue &callback)
{
auto future = m_scheduler.run(
[this, url, hash]() {
DownloaderStateGuard stateGuard(m_active, m_mutex, [this]() {
emit activeChanged();
});
if (!stateGuard.acquired())
{
return QJSValueList({"downloading is already running"});
}
{
QWriteLocker locker(&m_mutex);
m_contents.clear();
}
std::string response;
{
QString error;
auto task = m_scheduler.run([this, &error, &response, &url] {
error = m_network.get(m_httpClient, url, response);
});
if (!task.first)
{
return QJSValueList({"failed to start downloading task"});
}
task.second.waitForFinished();
if (!error.isEmpty())
{
return QJSValueList({error});
}
}
if (response.empty())
{
return QJSValueList({"empty response"});
}
try
{
const QByteArray calculatedHash = Updater().getHash(&response[0], response.size());
if (QByteArray::fromHex(hash.toUtf8()) != calculatedHash)
{
return QJSValueList({"hash sum mismatch"});
}
}
catch (const std::exception &e)
{
return QJSValueList({e.what()});
}
{
QWriteLocker locker(&m_mutex);
m_contents = std::move(response);
}
return QJSValueList({});
},
callback);
return future.first;
}
bool Downloader::saveToFile(const QString &path) const
{
QWriteLocker locker(&m_mutex);
if (m_active || m_contents.empty())
{
return false;
}
QFile file(path);
if (!file.open(QIODevice::WriteOnly))
{
return false;
}
if (static_cast<size_t>(file.write(m_contents.data(), m_contents.size())) != m_contents.size())
{
return false;
}
return true;
}
bool Downloader::active() const
{
QReadLocker locker(&m_mutex);
return m_active;
}
quint64 Downloader::loaded() const
{
return m_httpClient->received();
}
quint64 Downloader::total() const
{
return m_httpClient->contentLength();
}

67
src/qt/downloader.h Normal file
View File

@@ -0,0 +1,67 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <QReadWriteLock>
#include "network.h"
class Downloader : public QObject
{
Q_OBJECT
Q_PROPERTY(bool active READ active NOTIFY activeChanged);
Q_PROPERTY(quint64 loaded READ loaded NOTIFY loadedChanged);
Q_PROPERTY(quint64 total READ total NOTIFY totalChanged);
public:
Downloader(QObject *parent = nullptr);
~Downloader();
Q_INVOKABLE void cancel();
Q_INVOKABLE bool get(const QString &url, const QString &hash, const QJSValue &callback);
Q_INVOKABLE bool saveToFile(const QString &path) const;
signals:
void activeChanged() const;
void loadedChanged() const;
void totalChanged() const;
private:
bool active() const;
quint64 loaded() const;
quint64 total() const;
private:
bool m_active;
std::string m_contents;
std::shared_ptr<HttpClient> m_httpClient;
mutable QReadWriteLock m_mutex;
Network m_network;
mutable FutureScheduler m_scheduler;
};

165
src/qt/network.cpp Normal file
View File

@@ -0,0 +1,165 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "network.h"
#include <QDebug>
#include <QtCore>
#include "utils.h"
using epee::net_utils::http::fields_list;
using epee::net_utils::http::http_response_info;
using epee::net_utils::http::http_simple_client;
HttpClient::HttpClient(QObject *parent /* = nullptr */)
: QObject(parent)
, m_cancel(false)
, m_contentLength(0)
, m_received(0)
{
}
void HttpClient::cancel()
{
m_cancel = true;
}
quint64 HttpClient::contentLength() const
{
return m_contentLength;
}
quint64 HttpClient::received() const
{
return m_received;
}
bool HttpClient::on_header(const http_response_info &headers)
{
if (m_cancel.exchange(false))
{
return false;
}
size_t contentLength = 0;
if (!epee::string_tools::get_xtype_from_string(contentLength, headers.m_header_info.m_content_length))
{
qWarning() << "Failed to get Content-Length";
}
m_contentLength = contentLength;
emit contentLengthChanged();
m_received = 0;
emit receivedChanged();
return http_simple_client::on_header(headers);
}
bool HttpClient::handle_target_data(std::string &piece_of_transfer)
{
if (m_cancel.exchange(false))
{
return false;
}
m_received += piece_of_transfer.size();
emit receivedChanged();
return http_simple_client::handle_target_data(piece_of_transfer);
}
Network::Network(QObject *parent)
: QObject(parent)
, m_scheduler(this)
{
}
void Network::get(const QString &url, const QJSValue &callback, const QString &contentType /* = {} */) const
{
m_scheduler.run(
[this, url, contentType] {
std::string response;
std::shared_ptr<http_simple_client> httpClient(new http_simple_client());
QString error = get(httpClient, url, response, contentType);
return QJSValueList({url, QString::fromStdString(response), error});
},
callback);
}
void Network::getJSON(const QString &url, const QJSValue &callback) const
{
get(url, callback, "application/json; charset=utf-8");
}
std::string Network::get(const QString &url, const QString &contentType /* = {} */) const
{
std::string response;
QString error = get(std::shared_ptr<http_simple_client>(new http_simple_client()), url, response, contentType);
if (!error.isEmpty())
{
throw std::runtime_error(QString("failed to fetch %1: %2").arg(url).arg(error).toStdString());
}
return response;
}
QString Network::get(
std::shared_ptr<http_simple_client> httpClient,
const QString &url,
std::string &response,
const QString &contentType /* = {} */) const
{
const QUrl urlParsed(url);
httpClient->set_server(urlParsed.host().toStdString(), urlParsed.scheme() == "https" ? "443" : "80", {});
const QString uri = (urlParsed.hasQuery() ? urlParsed.path() + "?" + urlParsed.query() : urlParsed.path());
const http_response_info *pri = NULL;
constexpr std::chrono::milliseconds timeout = std::chrono::seconds(15);
fields_list headers({{"User-Agent", randomUserAgent().toStdString()}});
if (!contentType.isEmpty())
{
headers.push_back({"Content-Type", contentType.toStdString()});
}
const bool result = httpClient->invoke(uri.toStdString(), "GET", {}, timeout, std::addressof(pri), headers);
if (!result)
{
return "unknown error";
}
if (!pri)
{
return "internal error";
}
if (pri->m_response_code != 200)
{
return QString("response code %1").arg(pri->m_response_code);
}
response = std::move(pri->m_body);
return {};
}

89
src/qt/network.h Normal file
View File

@@ -0,0 +1,89 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <QCoreApplication>
#include <QtNetwork>
// TODO: wallet_merged - epee library triggers the warnings
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wreorder"
#include <net/http_client.h>
#pragma GCC diagnostic pop
#include "FutureScheduler.h"
class HttpClient : public QObject, public epee::net_utils::http::http_simple_client
{
Q_OBJECT
Q_PROPERTY(quint64 contentLength READ contentLength NOTIFY contentLengthChanged);
Q_PROPERTY(quint64 received READ received NOTIFY receivedChanged);
public:
HttpClient(QObject *parent = nullptr);
void cancel();
quint64 contentLength() const;
quint64 received() const;
signals:
void contentLengthChanged() const;
void receivedChanged() const;
protected:
bool on_header(const epee::net_utils::http::http_response_info &headers) final;
bool handle_target_data(std::string &piece_of_transfer) final;
private:
std::atomic<bool> m_cancel;
std::atomic<size_t> m_contentLength;
std::atomic<size_t> m_received;
};
class Network : public QObject
{
Q_OBJECT
public:
Network(QObject *parent = nullptr);
public:
Q_INVOKABLE void get(const QString &url, const QJSValue &callback, const QString &contentType = {}) const;
Q_INVOKABLE void getJSON(const QString &url, const QJSValue &callback) const;
std::string get(const QString &url, const QString &contentType = {}) const;
QString get(
std::shared_ptr<epee::net_utils::http::http_simple_client> httpClient,
const QString &url,
std::string &response,
const QString &contentType = {}) const;
private:
mutable FutureScheduler m_scheduler;
};

View File

@@ -1,110 +0,0 @@
// Copyright (c) 2014-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QtCore>
#include <QNetworkAccessManager>
#include "utils.h"
#include "prices.h"
Prices::Prices(QNetworkAccessManager *networkAccessManager, QObject *parent)
: QObject(parent) {
this->m_networkAccessManager = networkAccessManager;
}
void Prices::getJSON(const QString url) {
qDebug() << QString("Fetching: %1").arg(url);
QNetworkRequest request;
request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", randomUserAgent().toUtf8());
request.setRawHeader("Content-Type", "application/json");
m_reply = this->m_networkAccessManager->get(request);
connect(m_reply, SIGNAL(finished()), this, SLOT(gotJSON()));
}
void Prices::gotJSON() {
// Check connectivity
if (!m_reply || m_reply->error() != QNetworkReply::NoError){
this->gotError("Problem with reply from server. Check connectivity.");
m_reply->deleteLater();
return;
}
// Check json header
QList<QByteArray> headerList = m_reply->rawHeaderList();
QByteArray headerJson = m_reply->rawHeader("Content-Type");
if(headerJson.length() <= 15){
this->gotError("Bad Content-Type");
m_reply->deleteLater();
return;
}
QString headerJsonStr = QTextCodec::codecForMib(106)->toUnicode(headerJson);
int _contentType = headerList.indexOf("Content-Type");
if (_contentType < 0 || !headerJsonStr.startsWith("application/json")){
this->gotError("Bad Content-Type");
m_reply->deleteLater();
return;
}
// Check valid json document
QByteArray data = m_reply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(data);
QString jsonString = doc.toJson(QJsonDocument::Indented);
if (jsonString.isEmpty()){
this->gotError("Bad JSON");
m_reply->deleteLater();
return;
}
// Insert source url for later reference
QUrl url = m_reply->url();
QJsonObject docobj = doc.object();
docobj["_url"] = url.toString();
doc.setObject(docobj);
qDebug() << QString("Fetched: %1").arg(url.toString());
// Emit signal
QVariantMap vMap = doc.object().toVariantMap();
emit priceJsonReceived(vMap);
m_reply->deleteLater();
}
void Prices::gotError() {
this->gotError("Unknown error");
}
void Prices::gotError(const QString &message) {
qCritical() << "[Fiat API] Error:" << message;
emit priceJsonError(message);
}

View File

@@ -1,30 +0,0 @@
#ifndef PRICES_H
#define PRICES_H
#include <QCoreApplication>
#include <QtNetwork>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDebug>
class Prices : public QObject
{
Q_OBJECT
public:
Prices(QNetworkAccessManager *networkAccessManager, QObject *parent = nullptr);
public slots:
Q_INVOKABLE void getJSON(const QString url);
void gotJSON();
void gotError();
void gotError(const QString &message);
signals:
void priceJsonReceived(QVariantMap document);
void priceJsonError(QString message);
private:
mutable QPointer<QNetworkReply> m_reply;
QNetworkAccessManager *m_networkAccessManager;
};
#endif // PRICES_H

171
src/qt/updater.cpp Normal file
View File

@@ -0,0 +1,171 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "updater.h"
#include <common/util.h>
#include <openpgp/hash.h>
#include "network.h"
#include "utils.h"
Updater::Updater()
{
m_maintainers.emplace_back(fileGetContents(":/monero/utils/gpg_keys/binaryfate.asc").toStdString());
m_maintainers.emplace_back(fileGetContents(":/monero/utils/gpg_keys/fluffypony.asc").toStdString());
m_maintainers.emplace_back(fileGetContents(":/monero/utils/gpg_keys/luigi1111.asc").toStdString());
}
QByteArray Updater::fetchSignedHash(
const QString &binaryFilename,
const QByteArray &hashFromDns,
QPair<QString, QString> &signers) const
{
static constexpr const char hashesTxtUrl[] = "https://web.getmonero.org/downloads/hashes.txt";
static constexpr const char hashesTxtSigUrl[] = "https://web.getmonero.org/downloads/hashes.txt.sig";
const Network network;
std::string hashesTxt = network.get(hashesTxtUrl);
std::string hashesTxtSig = network.get(hashesTxtSigUrl);
const QByteArray signedHash = verifyParseSignedHahes(
QByteArray(&hashesTxt[0], hashesTxt.size()),
QByteArray(&hashesTxtSig[0], hashesTxtSig.size()),
binaryFilename,
signers);
if (signedHash != hashFromDns)
{
throw std::runtime_error("DNS hash mismatch");
}
return signedHash;
}
QByteArray Updater::verifyParseSignedHahes(
const QByteArray &armoredSignedHashes,
const QByteArray &secondDetachedSignature,
const QString &binaryFilename,
QPair<QString, QString> &signers) const
{
const QString signedMessage = verifySignature(armoredSignedHashes, signers.first);
signers.second = verifySignature(
epee::span<const uint8_t>(
reinterpret_cast<const uint8_t *>(armoredSignedHashes.data()),
armoredSignedHashes.size()),
openpgp::signature_rsa::from_buffer(epee::span<const uint8_t>(
reinterpret_cast<const uint8_t *>(secondDetachedSignature.data()),
secondDetachedSignature.size())));
if (signers.first == signers.second)
{
throw std::runtime_error("both signatures were generated by the same person");
}
return parseShasumOutput(signedMessage, binaryFilename);
}
QPair<QString, QString> Updater::verifySignaturesAndHashSum(
const QByteArray &armoredSignedHashes,
const QByteArray &secondDetachedSignature,
const QString &binaryFilename,
const void *binaryData,
size_t binarySize) const
{
QPair<QString, QString> signers;
const QByteArray signedHash =
verifyParseSignedHahes(armoredSignedHashes, secondDetachedSignature, binaryFilename, signers);
const QByteArray calculatedHash = getHash(binaryData, binarySize);
if (signedHash != calculatedHash)
{
throw std::runtime_error("hash sum mismatch");
}
return signers;
}
QByteArray Updater::getHash(const void *data, size_t size) const
{
QByteArray hash(sizeof(crypto::hash), 0);
tools::sha256sum(static_cast<const uint8_t *>(data), size, *reinterpret_cast<crypto::hash *>(hash.data()));
return hash;
}
QByteArray Updater::parseShasumOutput(const QString &message, const QString &filename) const
{
for (const auto &line : message.splitRef("\n"))
{
const auto trimmed = line.trimmed();
if (trimmed.endsWith(filename))
{
const int pos = trimmed.indexOf(' ');
if (pos != -1)
{
return QByteArray::fromHex(trimmed.left(pos).toUtf8());
}
}
else if (trimmed.startsWith(filename))
{
const int pos = trimmed.lastIndexOf(' ');
if (pos != -1)
{
return QByteArray::fromHex(trimmed.right(trimmed.size() - pos).toUtf8());
}
}
}
throw std::runtime_error("hash not found");
}
QString Updater::verifySignature(const QByteArray &armoredSignedMessage, QString &signer) const
{
const std::string messageString = armoredSignedMessage.toStdString();
const openpgp::message_armored signedMessage(messageString);
signer = verifySignature(signedMessage, openpgp::signature_rsa::from_armored(messageString));
const epee::span<const uint8_t> message = signedMessage;
return QString(QByteArray(reinterpret_cast<const char *>(&message[0]), message.size()));
}
QString Updater::verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const
{
for (const auto &maintainer : m_maintainers)
{
for (const auto &public_key : maintainer)
{
if (signature.verify(data, public_key))
{
return QString::fromStdString(maintainer.user_id());
}
}
}
throw std::runtime_error("not signed by a maintainer");
}

64
src/qt/updater.h Normal file
View File

@@ -0,0 +1,64 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <QPair>
#include <openpgp/openpgp.h>
class Updater
{
public:
Updater();
QByteArray fetchSignedHash(
const QString &binaryFilename,
const QByteArray &hashFromDns,
QPair<QString, QString> &signers) const;
QByteArray getHash(const void *data, size_t size) const;
QPair<QString, QString> verifySignaturesAndHashSum(
const QByteArray &armoredSignedHashes,
const QByteArray &secondDetachedSignature,
const QString &binaryFilename,
const void *binaryData,
size_t binarySize) const;
private:
QByteArray verifyParseSignedHahes(
const QByteArray &armoredSignedHashes,
const QByteArray &secondDetachedSignature,
const QString &binaryFilename,
QPair<QString, QString> &signers) const;
QString verifySignature(const QByteArray &armoredSignedMessage, QString &signer) const;
QString verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const;
QByteArray parseShasumOutput(const QString &message, const QString &filename) const;
private:
std::vector<openpgp::public_key_block> m_maintainers;
};

View File

@@ -28,6 +28,7 @@
#include <QtCore>
#include <QApplication>
#include <QtGlobal>
#include "TailsOS.h"
#include "utils.h"
@@ -37,6 +38,24 @@ bool fileExists(QString path) {
return check_file.exists() && check_file.isFile();
}
QByteArray fileGetContents(QString path)
{
QFile file(path);
if (!file.open(QFile::ReadOnly))
{
throw std::runtime_error(QString("failed to open %1").arg(path).toStdString());
}
QByteArray data;
data.resize(file.size());
if (file.read(data.data(), data.size()) != data.size())
{
throw std::runtime_error(QString("failed to read %1").arg(path).toStdString());
}
return data;
}
QByteArray fileOpen(QString path) {
QFile file(path);
if(!file.open(QFile::ReadOnly | QFile::Text))
@@ -50,7 +69,8 @@ QByteArray fileOpen(QString path) {
bool fileWrite(QString path, QString data) {
QFile file(path);
if(file.open(QIODevice::WriteOnly)){
QTextStream out(&file); out << data << endl;
QTextStream out(&file);
out << data << '\n';
file.close();
return true;
}

View File

@@ -34,6 +34,7 @@
#include <QApplication>
bool fileExists(QString path);
QByteArray fileGetContents(QString path);
QByteArray fileOpen(QString path);
bool fileWrite(QString path, QString data);
QString getAccountName();

File diff suppressed because it is too large Load Diff

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