Compare commits
727 Commits
f5378a55ed
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| c61c211dfe | |||
| 16244d84a0 | |||
| 32be868dbc | |||
| 8bb3198562 | |||
| 4b3f0fa92b | |||
| c7c3440a7b | |||
| e779ded2e8 | |||
| 940dd21464 | |||
| 207f9074db | |||
| 05b00b158b | |||
| f9ddab059e | |||
| de1ae736de | |||
| 03c1b63e03 | |||
| 8c12b27c0a | |||
| c7d163f44a | |||
| 7e4822c021 | |||
| f9b622cb25 | |||
| 9204fa148a | |||
| da0e9f5915 | |||
| d188a08db7 | |||
| ff5f5ddf23 | |||
| 56f9802fb9 | |||
| efb271cb9a | |||
| eb69e491b9 | |||
| 2675b8ab93 | |||
| b3444e0a89 | |||
| 45b652f514 | |||
| fffee9f0b5 | |||
| 00ffc959e5 | |||
| b561406c23 | |||
| 5a0743a17b | |||
| a7b0770ad0 | |||
| 7d8323a622 | |||
| 320944fd18 | |||
| 6d5e0ac614 | |||
| 02554d523d | |||
| 21da9e75fc | |||
| c53b7f771e | |||
| bcee4bfe72 | |||
| e1870c3b23 | |||
| 7cf0bb8bc7 | |||
| 541a9e1fd5 | |||
| 07b8e0b2cb | |||
| de48414c65 | |||
| f0c681b0b5 | |||
| fdf2502ca8 | |||
| e393b0d847 | |||
| 3b041f20c1 | |||
| 77a0ad64b0 | |||
| d0ca2fdf52 | |||
| 9c07b6d33d | |||
| fedfe3d60c | |||
| 95ff9ce9ae | |||
| 203967411a | |||
| 3d0305a9ca | |||
| e7f38c2a45 | |||
| 3a2be661ea | |||
| 17525003c5 | |||
| 267d839d5b | |||
| 987d9b93c7 | |||
| 4c43f2e082 | |||
| 57fa6470b7 | |||
| c68889d276 | |||
| b0a4333cdf | |||
| 4471f54842 | |||
| e08121af2b | |||
| 31ebfc782e | |||
| f3776bcbe5 | |||
| 38f2aa2f8f | |||
| 40f8425d94 | |||
| 2157a30192 | |||
| bef3c0c47d | |||
| 5c570613c8 | |||
| 567732206e | |||
| 0dcc09fc5f | |||
| 82d3178119 | |||
| 7d47c6e14e | |||
| 973bc0d338 | |||
| e4c9b98ca2 | |||
| 1c38f68781 | |||
| 818c29fca7 | |||
| 0a43742897 | |||
| defe14d913 | |||
| 8b55a90102 | |||
| 0fd3d214a4 | |||
| 996e10ed02 | |||
| 2c909d35ea | |||
| 3b5db02e09 | |||
| 8d8cd337cf | |||
| acde8c0833 | |||
| a78b44246e | |||
| 63ed31d2ec | |||
| 64b27db2ff | |||
| 03fe6e077c | |||
| 125ffa8863 | |||
| 3465acc57b | |||
| 30bd0d99ff | |||
| 29fbe46cce | |||
| fc414eeed4 | |||
| fce873936e | |||
| 7351d8a06d | |||
| 76093fe82d | |||
| ef247c95ff | |||
| c291e8a587 | |||
| 06e55d6394 | |||
| 2072f70a60 | |||
| ee9ea15233 | |||
| 11de117331 | |||
| fac0245297 | |||
| 9cb91415d9 | |||
| 24c661f743 | |||
| fecc9015fb | |||
| bce69362eb | |||
| 7f46d9e2d5 | |||
| 100ed01469 | |||
| 055685a4c4 | |||
| 46f93e380b | |||
| 6f6ad89c9f | |||
| d9911a9bc9 | |||
| faf77de9ce | |||
| 7f414b3dcf | |||
| 994ddea6cd | |||
| 41e4e73e63 | |||
| 88091bd77c | |||
| 02839dc415 | |||
| 17b70a1388 | |||
| 42d59709f9 | |||
| 30bc80b2f2 | |||
| 3808b3ee82 | |||
| bb5124c1ae | |||
| e6c78da062 | |||
| 5874142186 | |||
| 928335dd6b | |||
| 63dcda0ad1 | |||
| 122db9d903 | |||
| fc509a9340 | |||
| f546d3e2b1 | |||
| 6f0f95f4fb | |||
| a7c50ff19b | |||
| e7f2d2e3c7 | |||
| 137147921c | |||
| fd0cab41f0 | |||
| 4b8d80b2fc | |||
| 923d086092 | |||
| 6ac8f66644 | |||
| 62fc202557 | |||
| f8034b843a | |||
| 731d4e04ff | |||
| 9649654c3c | |||
| e1df5ea798 | |||
| a53b13b6b6 | |||
| bc3c6037fc | |||
| a338fac208 | |||
| 9bc2a0b62a | |||
| 1b3446e43c | |||
| 19dea53ef8 | |||
| 3d2b734541 | |||
| 2d4ba89c00 | |||
| 2249bc31d5 | |||
| 1ce37aa0fa | |||
| 33317c4e78 | |||
| 03dde25339 | |||
| 3fbb64d14c | |||
| 1b0006a4af | |||
| ba13875eaf | |||
| a5d44c8aca | |||
| 1a41dec8d8 | |||
| 88e10f3e06 | |||
| d29283a9b7 | |||
| ca65aa8bf7 | |||
| dae677acd8 | |||
| 4ef09d5546 | |||
| 21be39c725 | |||
| abbdf3f4f7 | |||
| 28288d7197 | |||
| 304f65c3b4 | |||
| 04a5078133 | |||
| b6b4fdacb5 | |||
| f6024557d5 | |||
| d82821ba06 | |||
| 8290e96ec4 | |||
| 46f7da4fac | |||
| 5bd07b0505 | |||
| 90df19b8b6 | |||
| 5985f05fb2 | |||
| 60ee73bf1d | |||
| 625df8abe6 | |||
| 5dd68dc9bd | |||
| 8a30578872 | |||
| 63d5c817aa | |||
| 3662df550c | |||
| 067c96c425 | |||
| a2c4a8df73 | |||
| 822891d4ef | |||
| 22638b094c | |||
| bf81ec1702 | |||
| 56cd9b6273 | |||
| 8b9cd5bf80 | |||
| 2b191cea34 | |||
| 6ecbf835ec | |||
| a13190a6df | |||
| ffc24ee99b | |||
| 3274c72a58 | |||
| b7b32bfde8 | |||
| 0b47134112 | |||
| 828018de2b | |||
| 2a96c41e07 | |||
| c25da0d26d | |||
| 0c55ed0fe4 | |||
| 1fc3e03e6f | |||
| f428292ea4 | |||
| 46f3001360 | |||
| 8293f02e36 | |||
| b34069f57c | |||
| 4416e01f9c | |||
| 44a17f3ad0 | |||
| c9e9c9f979 | |||
| ab4dd370c0 | |||
| 675d434958 | |||
| e8055888a5 | |||
| 48eceb6593 | |||
| 4f71c18884 | |||
| 232b81f8ee | |||
| c40c252c9a | |||
| d156dddcd0 | |||
| 0e1957c5f9 | |||
| 3f95765dcc | |||
| 62c92cc862 | |||
| 3aeb847657 | |||
| 2a9d32e78b | |||
| 97a54f0e0c | |||
| 82da4af857 | |||
| bfe8b4d77d | |||
| 78c00daf92 | |||
| e82514f46a | |||
| 76708e9191 | |||
| a767702ff6 | |||
| 40e8128a30 | |||
| 326192e75e | |||
| a107fda9b8 | |||
| d1ff58c374 | |||
| 156735eb06 | |||
| 2f86bec98e | |||
| 6310f51f65 | |||
| 09e0b962c3 | |||
| 758ea06ab7 | |||
| d189ee5ddb | |||
| 32d6e34c45 | |||
| 088e3371d9 | |||
| 49b5ef6b20 | |||
| 6aaab9e01f | |||
| b7da6335d1 | |||
| b765d62e00 | |||
| 3dc4d2d860 | |||
| fe40d38f0d | |||
| 1d3a919ac6 | |||
| d549127e41 | |||
| 54a14485a5 | |||
| 8eefab99ec | |||
| 4fdf281230 | |||
| c4f69b5a85 | |||
| 171b705429 | |||
| 175950a2ec | |||
| 36efe99a0f | |||
| fadbd6a879 | |||
| 112c581247 | |||
| 57147fd06b | |||
| aa60188665 | |||
| cff958c21e | |||
| c50125523c | |||
| 063f1fa4be | |||
| ad7a150ad6 | |||
| 433c7ae46f | |||
| a5efbaf83d | |||
| 46fb0029b8 | |||
| aa587d1d84 | |||
| 3a9edf2200 | |||
| c25a4e4150 | |||
| 54945c94bf | |||
| 96a1c54556 | |||
| bedb482855 | |||
| c352364bdc | |||
| 6b7438b529 | |||
| c6624913de | |||
| e6bea8eb04 | |||
| 5e5f167fb0 | |||
| 1fd794ccc1 | |||
| ed4d61c364 | |||
| 3df516be70 | |||
| f8fbc32463 | |||
| 06c8b99bce | |||
| 5eff3adc3c | |||
| e010822a74 | |||
| b2882095bb | |||
| a80feb708e | |||
| 010465f835 | |||
| eb08b81706 | |||
| 8cbd0cab9d | |||
| a9f0d544cc | |||
| 13ff20d791 | |||
| d8cc1e9ee6 | |||
| e7a3f90102 | |||
| b70f1ae643 | |||
| 76a2ef51f3 | |||
| 8846a96e3d | |||
| 5db7941fbc | |||
| a09adb14fb | |||
| d23ee9b75f | |||
| e8a8ce68b2 | |||
| 72bf59149d | |||
| 82e61da62f | |||
| 48c79567d7 | |||
| 6c19fac6f5 | |||
| 0d9908019d | |||
| a86cb8f0f3 | |||
| 5228da707f | |||
| e66f2b6c8c | |||
| 7ee20bb497 | |||
| 718cb82c13 | |||
| 403e145b30 | |||
| 036199a011 | |||
| 715cfd622e | |||
| b75801f5c4 | |||
| 6ddca6aed0 | |||
| ca1ac22b17 | |||
| 6bfb56aeb9 | |||
| 6c08a08127 | |||
| f5bde67f64 | |||
| 516e5ec688 | |||
| 008cc82ee5 | |||
| 2e6188a36d | |||
| e191680782 | |||
| 4db609fb52 | |||
| 980a100edd | |||
| 46eec37013 | |||
| 1738468f8c | |||
| ba03de938e | |||
| d043538e2f | |||
| 2502487e44 | |||
| cec330ee47 | |||
| 0ff56bf5a4 | |||
| ddd53dc006 | |||
| ee59a9e2cd | |||
| 62998179ec | |||
| 061db16004 | |||
| ec48538881 | |||
| 658c0f355b | |||
| 41f5548593 | |||
| 1dbbd44759 | |||
| b0cc6bcef4 | |||
| dd9bc0bd2b | |||
| b2a8c4487e | |||
| 65bb98cd09 | |||
| 129a8e6449 | |||
| df14533ad3 | |||
| 24bb32eb61 | |||
| d3dcb5a015 | |||
| 7249d11899 | |||
| ee6903ae56 | |||
| 1c5bc0d4d6 | |||
| dfca9bbd92 | |||
| 840ead3645 | |||
| 1c8e97c0a4 | |||
| f1e66c08d1 | |||
| c83348ab63 | |||
| da56bb73a0 | |||
| ad59d62b82 | |||
| 07f6d2efc9 | |||
| e84cea27d9 | |||
| a457f0262e | |||
| 4c466d78d1 | |||
| 0d4eaa02e7 | |||
| dddf1b6ec7 | |||
| a523611779 | |||
| 7c90d72ded | |||
| bb2337b552 | |||
| 19e31388d9 | |||
| 30139a5698 | |||
| 94b57e91e3 | |||
| 10289b33b5 | |||
| 42df658275 | |||
| 9d2c4a8ad3 | |||
| 65c06d0e6b | |||
| c7f89e5cd3 | |||
| 34ded6b441 | |||
| 0ea423dcea | |||
| 61738ef24a | |||
| ccbb0783b5 | |||
| c6b32e86c0 | |||
| 5caffc84b0 | |||
| ebf01bd858 | |||
| d91be3b34e | |||
| 4b46d4a20c | |||
| 69cd8ba048 | |||
| 4543cdc495 | |||
| 71fb81670f | |||
| a092097141 | |||
| 50873d1112 | |||
| 7e8628f94c | |||
| 0ae52ee095 | |||
| 529a74f6a7 | |||
| 88f1415bcc | |||
| b8cfc1c79c | |||
| a0fd9aff82 | |||
| 14b08d1358 | |||
| d689b02de9 | |||
| 84498b1312 | |||
| 8c2b0521a6 | |||
| 4411cb45e2 | |||
| 7db6564a0e | |||
| faf8e4e430 | |||
| 1affea0273 | |||
| 32d8d5663a | |||
| 3568b12dbf | |||
| e74989ec2c | |||
| fe18628216 | |||
| 4dc6b2ef8f | |||
| 5afb339ba7 | |||
| 0e8816e220 | |||
| 2035f71c65 | |||
| d007f3de39 | |||
| 1fdb6656aa | |||
| 3305be6318 | |||
| 19482214f1 | |||
| 4aed72c73b | |||
| 3e6987d72f | |||
| 42c511b13a | |||
| ef9141c248 | |||
| 8c88afe16e | |||
| d48c6db21c | |||
| ced1773e70 | |||
| d76c109063 | |||
| 5aa3cc4d51 | |||
| 4e15a2b1ba | |||
| 021ba88522 | |||
| c1d5f79502 | |||
| db212a81c6 | |||
| 67f6156030 | |||
| 76ed09fad0 | |||
| 77219c5ce7 | |||
| b26aff392b | |||
| b0ded528b1 | |||
| 301b3df77e | |||
| ae4e6486c6 | |||
| d838243caa | |||
| 75f33e41eb | |||
| 1263058ba7 | |||
| 9bcdf51895 | |||
| 67feaea767 | |||
| 3e1cdd15f3 | |||
| af2db4366c | |||
| cb74fbbff0 | |||
| 121a79e768 | |||
| 262891229a | |||
| dcd09dc542 | |||
| 4635a56e89 | |||
| 9c533fa485 | |||
| de11850c74 | |||
| 280a71e973 | |||
| 7891c689fb | |||
| f8150434d4 | |||
| 530bf24abf | |||
| e3b53258b7 | |||
| f9e4db4abf | |||
| bdccddfeee | |||
| d68bc27aac | |||
| 51667b5a27 | |||
| 96e05b4ba6 | |||
| 36386b2b84 | |||
| 2fc577fb18 | |||
| 5180f74a68 | |||
| fc5ce019e2 | |||
| 7826be4b13 | |||
| 5d6e78c719 | |||
| a1e4acfe86 | |||
| 419f8bb9cb | |||
| c5ef9e42ed | |||
| f0243307f1 | |||
| 95ddfa05ee | |||
| ecc0043559 | |||
| 3a9ff5f021 | |||
| 1faaa54a07 | |||
| 61f8c54742 | |||
| 48fc9ba8bb | |||
| 2552234610 | |||
| ff20a8a44e | |||
| 245d9bf976 | |||
| 1c248d727a | |||
| 57291ef28c | |||
| 18c1de87db | |||
| 0cdbbd024e | |||
| 779c9682d4 | |||
| 33735b1d52 | |||
| 19e83eb12a | |||
| f12153cdd0 | |||
| 8abc0ec113 | |||
| 5a677f6202 | |||
| efec3a50a1 | |||
| 4ed69d4e9f | |||
| e0d6d6bc4d | |||
| e9121fe49e | |||
| b5001ad4db | |||
| 1266b6e68e | |||
| 57f52b7a6c | |||
| a6964621a8 | |||
| 14f5fdc559 | |||
| 1a00ec3359 | |||
| 5c490c8d53 | |||
| d0586e5e75 | |||
| 24e8fb4942 | |||
| 1393d9ae1a | |||
| 3ce62326f9 | |||
| b20e7efb16 | |||
| 4473e7e00a | |||
| 2e8e214689 | |||
| 69a6fb3e64 | |||
| ed6120eef9 | |||
| a5bd2dadd7 | |||
| 25ee1496b4 | |||
| e2bc3623b6 | |||
| b32fe07cb1 | |||
| a0532275dd | |||
| 7df00b0909 | |||
| 090e288c44 | |||
| 6544c10ac1 | |||
| b2e104358d | |||
| de70e68472 | |||
| 0fe12d65df | |||
| 37c8287a12 | |||
| 6ff80354df | |||
| f58d009703 | |||
| 0e2c786ebf | |||
| d54c7f9e11 | |||
| b3251e9244 | |||
| c40f4d5815 | |||
| 5547ab1cac | |||
| f0867084f3 | |||
| 5167b52cbd | |||
| e7d11f620a | |||
| 168cae9306 | |||
| 317d9028a3 | |||
| 09ab8d52c5 | |||
| 101c835c46 | |||
| c71c3c3378 | |||
| 2e115aef39 | |||
| 574307f6ac | |||
| 88851f5eea | |||
| 5796664b51 | |||
| b71f8ae0a8 | |||
| 555f541c84 | |||
| 2b70ee5cd8 | |||
| d8c055c125 | |||
| 2470675746 | |||
| 3a597482da | |||
| b9b2d469d4 | |||
| 25fef8ff4d | |||
| bc788d008e | |||
| 9ee8f9a43b | |||
| bf91c4eb6c | |||
| 4d78ca0d7d | |||
| 00ee61fe64 | |||
| 3a4998f57c | |||
| d27017daeb | |||
| 2182c060e6 | |||
| 4ee830c5dd | |||
| b6567ee196 | |||
| 1a8d6fd30f | |||
| 9389859ee9 | |||
| e63aba6959 | |||
| fa240e7b99 | |||
| e21f7bf8aa | |||
| 47f228fa47 | |||
| 9f82bba260 | |||
| 323cb341f1 | |||
| ee6cac41c4 | |||
| 094771af81 | |||
| e40962cdf2 | |||
| 63b3a04716 | |||
| cf77c6cbe0 | |||
| 560f2bcf91 | |||
| 255d9399fa | |||
| 1fb6dc44d9 | |||
| 2ba8a799ff | |||
| 41b380449e | |||
| 4a65dce947 | |||
| c8183241c3 | |||
| 0bf80d2757 | |||
| 6f9123f651 | |||
| 320c659689 | |||
| 6ff1fda870 | |||
| 8c51b092f8 | |||
| 788d71a549 | |||
| 3d4b013b0c | |||
| dc07491abb | |||
| 7e568e4bf1 | |||
| 85a1080b52 | |||
| dbeae3ac98 | |||
| 9ff5508989 | |||
| 79e5adcbd3 | |||
| 6531d0c4d2 | |||
| 142a6826af | |||
| 070a516f4e | |||
| 3cec333d84 | |||
| fc438ab962 | |||
| 8c2c1c2aaf | |||
| a605e35409 | |||
| 6ed80d2d79 | |||
| e978db85ca | |||
| e00772db6e | |||
| 8f22db5eea | |||
| 5c883d4b91 | |||
| 274f7ea1af | |||
| 3799330bb0 | |||
| 53a10e149d | |||
| 1bc7f5c8cd | |||
| 20b22410e9 | |||
| 7195c25376 | |||
| a6921bca60 | |||
| 8ba4233b9b | |||
| 732d892d4d | |||
| afd612be7e | |||
| 4d769c8719 | |||
| 0e1b19d0f2 | |||
| b24212fb8f | |||
| 64fe8fc6c9 | |||
| b9881278af | |||
| 85b53baeaf | |||
| eece57c025 | |||
| 8765fdf362 | |||
| 98e0cce8ec | |||
| 5c87bc6e87 | |||
| 946958b591 | |||
| f5561c0dac | |||
| b3c2282b53 | |||
| ca14aaddc7 | |||
| a5da5562cf | |||
| c676ec8287 | |||
| f474b0d633 | |||
| af252575cf | |||
| 74bd22958a | |||
| cd60bded9f | |||
| 59b8c4da81 | |||
| 950d7ace50 | |||
| d52d3d1b7f | |||
| 9569b0ba43 | |||
| 50b0419dfe | |||
| 4f7a4fb38e | |||
| f511c0d509 | |||
| 235504657d | |||
| 76c2ac5db8 | |||
| 89bd21018a | |||
| 9d7054b245 | |||
| 70608fcb7a | |||
| 0c819afdd4 | |||
| db4778e6a7 | |||
| eb6114ee19 | |||
| 5d317f6be3 | |||
| a677c09984 | |||
| 6611d57147 | |||
| 6a4e98b7ed | |||
| 4b9d6f7db5 | |||
| 043cdc7128 | |||
| 2daea67a1e | |||
| e6b91ca661 | |||
| c6e28fc4da | |||
| 268eba6321 | |||
| 3119440cd9 | |||
| 59c55e33f8 | |||
| a8b5d2f6a3 | |||
| 012341b1a4 | |||
| 5586f334a4 | |||
| 863d015628 | |||
| a78a13edf3 | |||
| e95ad50e41 | |||
| 975743f754 | |||
| 948ef419ac | |||
| 9edab31728 | |||
| ee8a08e569 | |||
| ddb810e2f3 | |||
| aa26ab5fbd | |||
| 40cec14ebf | |||
| 88d30c1612 | |||
| dbe6546f9f | |||
| 6e2db50675 | |||
| d2dccbac05 | |||
| 1860e9b277 | |||
| 648a6c29e0 | |||
| c013038ef7 | |||
| 20cbad687d | |||
| ddca8b2e43 | |||
| 50e9e7d75e | |||
| d755f6816b | |||
| 84d2b9c39d | |||
| 27e9a8df26 | |||
| 8d51f374cd | |||
| 7ab8f5d82c | |||
| e4b1b644b3 | |||
| 8ef8abeb37 | |||
| 09f876eb60 | |||
| aa3bd4e304 | |||
| 801fa2b96b | |||
| e0bfeb2f29 | |||
| 9ed4fbc476 | |||
| 2b3277529e | |||
| d79d013060 | |||
| 474250bb50 | |||
| 40dd6d45b2 | |||
| 1b97476a54 | |||
| d7bc5c638a | |||
| 39f193a264 | |||
| 4023af9466 | |||
| 853e3e0f17 | |||
| 1bcfcd1c73 | |||
| 6bb35bd3e1 | |||
| 2c5a658ea5 | |||
| f416ff3d09 | |||
| b3a0ce29ed | |||
| e2265b0bdf | |||
| 9368b945e0 | |||
| eda19ef6d5 | |||
| 7d48936f30 | |||
| bc2ef4ffaf | |||
| 0da1657b12 | |||
| 0d30ebc8e8 | |||
| c778e03f37 | |||
| 0a3f68f1eb | |||
| 1a5c4e8744 |
173
.github/copilot-instructions.md
vendored
@@ -1,173 +0,0 @@
|
|||||||
# Copilot Instructions — DragonX ImGui Wallet
|
|
||||||
|
|
||||||
## UI Layout: All values in `ui.toml`
|
|
||||||
|
|
||||||
**Every UI layout constant must be defined in `res/themes/ui.toml` and read at runtime via the schema API.** Never hardcode pixel sizes, ratios, rounding values, thicknesses, or spacing constants directly in C++ source files. This is critical for maintainability, theming support, and hot-reload.
|
|
||||||
|
|
||||||
### Schema API reference
|
|
||||||
|
|
||||||
The singleton is accessed via `schema::UI()` (header: `#include "../schema/ui_schema.h"`).
|
|
||||||
|
|
||||||
| Method | Returns | Use for |
|
|
||||||
|---|---|---|
|
|
||||||
| `drawElement(section, name)` | `DrawElementStyle` | Custom DrawList layout values (`.size`, `.height`, `.thickness`, `.radius`, `.opacity`) |
|
|
||||||
| `button(section, name)` | `ButtonStyle` | Button width/height/font |
|
|
||||||
| `input(section, name)` | `InputStyle` | Input field dimensions |
|
|
||||||
| `label(section, name)` | `LabelStyle` | Label styling |
|
|
||||||
| `table(section, name)` | `TableStyle` | Table layout |
|
|
||||||
| `window(section, name)` | `WindowStyle` | Window/dialog dimensions |
|
|
||||||
| `combo(section, name)` | `ComboStyle` | Combo box styling |
|
|
||||||
| `slider(section, name)` | `SliderStyle` | Slider styling |
|
|
||||||
| `checkbox(section, name)` | `CheckboxStyle` | Checkbox styling |
|
|
||||||
| `separator(section, name)` | `SeparatorStyle` | Separator/divider styling |
|
|
||||||
|
|
||||||
### Section naming convention
|
|
||||||
|
|
||||||
Sections use dot-separated paths matching the file/feature:
|
|
||||||
|
|
||||||
- `tabs.send`, `tabs.receive`, `tabs.transactions`, `tabs.mining`, `tabs.peers`, `tabs.market` — tab-specific values
|
|
||||||
- `tabs.balance` — balance/home tab
|
|
||||||
- `components.main-layout`, `components.settings-page` — shared components
|
|
||||||
- `dialogs.about`, `dialogs.backup`, etc. — dialog-specific values
|
|
||||||
- `sidebar` — navigation sidebar
|
|
||||||
|
|
||||||
### How to add a new layout value
|
|
||||||
|
|
||||||
1. **Add the entry to `res/themes/ui.toml`** under the appropriate section:
|
|
||||||
```toml
|
|
||||||
[tabs.example]
|
|
||||||
my-element = {size = 42.0}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Read it in C++** instead of using a literal:
|
|
||||||
```cpp
|
|
||||||
// WRONG — hardcoded
|
|
||||||
float myValue = 42.0f;
|
|
||||||
|
|
||||||
// CORRECT — schema-driven
|
|
||||||
float myValue = schema::UI().drawElement("tabs.example", "my-element").size;
|
|
||||||
```
|
|
||||||
|
|
||||||
3. For values used as min/max pairs with scaling:
|
|
||||||
```cpp
|
|
||||||
// WRONG
|
|
||||||
float h = std::max(18.0f, 24.0f * vScale);
|
|
||||||
|
|
||||||
// CORRECT
|
|
||||||
float h = std::max(
|
|
||||||
schema::UI().drawElement("tabs.example", "row-min-height").size,
|
|
||||||
schema::UI().drawElement("tabs.example", "row-height").size * vScale
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### What belongs in `ui.toml`
|
|
||||||
|
|
||||||
- Pixel sizes (card heights, icon sizes, bar widths/heights)
|
|
||||||
- Ratios (column width ratios, max-width ratios)
|
|
||||||
- Rounding values (`FrameRounding`, corner radius)
|
|
||||||
- Thickness values (accent bars, chart lines, borders)
|
|
||||||
- Dot/circle radii
|
|
||||||
- Fade zones, padding constants
|
|
||||||
- Min/max dimension bounds
|
|
||||||
- Font selection (via schema font name strings, resolved with `S.resolveFont()`)
|
|
||||||
- Colors (via `schema::UI().resolveColor()` or color variable references like `"var(--primary)"`)
|
|
||||||
- Animation durations (transition times, fade durations, pulse speeds)
|
|
||||||
- Business logic values (fee amounts, ticker strings, buffer sizes, reward amounts)
|
|
||||||
|
|
||||||
### What does NOT belong in `ui.toml`
|
|
||||||
|
|
||||||
- Spacing that already goes through `Layout::spacing*()` or `spacing::dp()`
|
|
||||||
|
|
||||||
### Legacy system: `UILayout`
|
|
||||||
|
|
||||||
`UILayout::instance()` is the older layout system still used for fonts, typography, panels, and global spacing. New layout values should use `schema::UI().drawElement()` instead. Do not add new keys to `UILayout`.
|
|
||||||
|
|
||||||
### Validation
|
|
||||||
|
|
||||||
After editing `ui.toml`, always validate:
|
|
||||||
```bash
|
|
||||||
python3 -c "import toml; toml.load('res/themes/ui.toml'); print('Valid TOML')"
|
|
||||||
```
|
|
||||||
|
|
||||||
Or with the C++ toml++ parser (which is what the app uses):
|
|
||||||
```bash
|
|
||||||
cd build && make -j$(nproc)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Linux
|
|
||||||
cd build && make -j$(nproc)
|
|
||||||
|
|
||||||
# Windows cross-compile
|
|
||||||
./build.sh --win-release
|
|
||||||
```
|
|
||||||
|
|
||||||
## Plans
|
|
||||||
|
|
||||||
When asked to "create a plan", always create a new markdown document in the `docs/` directory with the plan contents.
|
|
||||||
|
|
||||||
## Icons: Use Material Design icon font, never Unicode symbols
|
|
||||||
|
|
||||||
**Never use raw Unicode symbols or emoji characters** (e.g. `↓`, `↗`, `⛏`, `🔍`, `📬`, `⚠️`, `ℹ`) for icons in C++ code. Always use the **Material Design Icons font** via the `ICON_MD_*` defines from `#include "../../embedded/IconsMaterialDesign.h"`.
|
|
||||||
|
|
||||||
### Icon font API
|
|
||||||
|
|
||||||
| Method | Size | Fallback |
|
|
||||||
|---|---|---|
|
|
||||||
| `Type().iconSmall()` | 14px | Body2 |
|
|
||||||
| `Type().iconMed()` | 18px | Body1 |
|
|
||||||
| `Type().iconLarge()` | 24px | H5 |
|
|
||||||
| `Type().iconXL()` | 40px | H3 |
|
|
||||||
|
|
||||||
### Correct usage
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include "../../embedded/IconsMaterialDesign.h"
|
|
||||||
|
|
||||||
// WRONG — raw Unicode symbol
|
|
||||||
itemSpec.leadingIcon = "↙";
|
|
||||||
|
|
||||||
// CORRECT — Material Design icon codepoint
|
|
||||||
itemSpec.leadingIcon = ICON_MD_CALL_RECEIVED;
|
|
||||||
|
|
||||||
// WRONG — emoji for search
|
|
||||||
searchSpec.leadingIcon = "🔍";
|
|
||||||
|
|
||||||
// CORRECT — Material Design icon
|
|
||||||
searchSpec.leadingIcon = ICON_MD_SEARCH;
|
|
||||||
|
|
||||||
// For rendering with icon font directly:
|
|
||||||
ImGui::PushFont(Type().iconSmall());
|
|
||||||
ImGui::TextUnformatted(ICON_MD_ARROW_DOWNWARD);
|
|
||||||
ImGui::PopFont();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Why
|
|
||||||
|
|
||||||
Raw Unicode symbols and emoji render inconsistently across platforms and may not be present in the loaded text fonts. The Material Design icon font is always loaded and provides consistent, scalable glyphs on both Linux and Windows.
|
|
||||||
|
|
||||||
### Audit for Unicode symbols
|
|
||||||
|
|
||||||
Before completing any task that touches UI code, search for and replace any raw Unicode symbols that may have been introduced. Common symbols to look for:
|
|
||||||
|
|
||||||
| Unicode | Replacement |
|
|
||||||
|---------|-------------|
|
|
||||||
| `▶` `▷` | `ICON_MD_PLAY_ARROW` |
|
|
||||||
| `■` `□` `◼` `◻` | `ICON_MD_STOP` or `ICON_MD_SQUARE` |
|
|
||||||
| `●` `○` `◉` `◎` | `ICON_MD_FIBER_MANUAL_RECORD` or `ICON_MD_CIRCLE` |
|
|
||||||
| `↑` `↓` `←` `→` | `ICON_MD_ARROW_UPWARD`, `_DOWNWARD`, `_BACK`, `_FORWARD` |
|
|
||||||
| `↙` `↗` `↖` `↘` | `ICON_MD_CALL_RECEIVED`, `_MADE`, etc. |
|
|
||||||
| `✓` `✔` | `ICON_MD_CHECK` |
|
|
||||||
| `✗` `✕` `✖` | `ICON_MD_CLOSE` |
|
|
||||||
| `⚠` `⚠️` | `ICON_MD_WARNING` |
|
|
||||||
| `ℹ` `ℹ️` | `ICON_MD_INFO` |
|
|
||||||
| `🔍` | `ICON_MD_SEARCH` |
|
|
||||||
| `📋` | `ICON_MD_CONTENT_COPY` or `ICON_MD_DESCRIPTION` |
|
|
||||||
| `🛡` `🛡️` | `ICON_MD_SHIELD` |
|
|
||||||
| `⏳` | `ICON_MD_HOURGLASS_EMPTY` |
|
|
||||||
| `🔄` `↻` `⟳` | `ICON_MD_SYNC` or `ICON_MD_REFRESH` |
|
|
||||||
| `⚙` `⚙️` | `ICON_MD_SETTINGS` |
|
|
||||||
| `🔒` | `ICON_MD_LOCK` |
|
|
||||||
| `★` `☆` | `ICON_MD_STAR` or `ICON_MD_STAR_BORDER` |
|
|
||||||
31
.gitignore
vendored
@@ -11,8 +11,8 @@ prebuilt-binaries/dragonxd-win/*
|
|||||||
!prebuilt-binaries/dragonxd-win/.gitkeep
|
!prebuilt-binaries/dragonxd-win/.gitkeep
|
||||||
prebuilt-binaries/dragonxd-mac/*
|
prebuilt-binaries/dragonxd-mac/*
|
||||||
!prebuilt-binaries/dragonxd-mac/.gitkeep
|
!prebuilt-binaries/dragonxd-mac/.gitkeep
|
||||||
prebuilt-binaries/xmrig-hac/*
|
prebuilt-binaries/drg-xmrig/*
|
||||||
!prebuilt-binaries/xmrig-hac/.gitkeep
|
!prebuilt-binaries/drg-xmrig/.gitkeep
|
||||||
|
|
||||||
|
|
||||||
# External sources / toolchains (created by scripts/setup.sh)
|
# External sources / toolchains (created by scripts/setup.sh)
|
||||||
@@ -32,4 +32,29 @@ imgui.ini
|
|||||||
*.bak
|
*.bak
|
||||||
*.bak*
|
*.bak*
|
||||||
*.params
|
*.params
|
||||||
asmap.dat
|
asmap.dat
|
||||||
|
/external/drg-xmrig
|
||||||
|
/memory
|
||||||
|
/todo.md
|
||||||
|
/.github/
|
||||||
|
/ObsidianDragon-agent/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
# Local-only archive of superseded lite-wallet design/planning docs (untracked)
|
||||||
|
docs/_archive/
|
||||||
|
|
||||||
|
# ed25519 release-signing keys — the secret key must NEVER be committed
|
||||||
|
*.ed25519.key
|
||||||
|
*.ed25519.pub.b64
|
||||||
|
|
||||||
|
|
||||||
|
# Lite-backend deps are fetched (or `cargo vendor`-ed locally for offline); not committed.
|
||||||
|
third_party/silentdragonxlite/lib/vendor/
|
||||||
|
|
||||||
|
# Generated by configure_file from res/ObsidianDragon.manifest.in (do not track)
|
||||||
|
res/ObsidianDragon.manifest
|
||||||
|
|
||||||
|
# Cross-built mingw FreeType (color emoji) — regenerated by scripts/build-freetype-mingw.sh
|
||||||
|
third_party/freetype-mingw/
|
||||||
|
third_party/.freetype-mingw-build/
|
||||||
|
|||||||
58
CHANGELOG.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable user-facing changes to ObsidianDragon are documented here. The format loosely
|
||||||
|
follows [Keep a Changelog](https://keepachangelog.com/); the project uses Conventional Commits.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### ⚠️ Breaking changes
|
||||||
|
|
||||||
|
- **Remote RPC over plain HTTP is now refused by default.** If your wallet is configured to
|
||||||
|
reach a **remote** `rpchost`/`rpcconnect` **without TLS**, it will no longer connect — it
|
||||||
|
previously sent your `rpcuser`/`rpcpassword` in cleartext (capturable by anyone on the
|
||||||
|
network path) after only a dismissible warning. To reconnect, either:
|
||||||
|
- add **`rpctls=1`** to `DRAGONX.conf` (preferred, if your daemon supports TLS), or
|
||||||
|
- add **`rpcallowplaintext=1`** to `DRAGONX.conf` to explicitly accept the plaintext link.
|
||||||
|
|
||||||
|
Local and embedded daemons (`127.0.0.0/8`, `localhost`, `::1`) are unaffected.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Refuse remote plaintext RPC credential transmission by default (see Breaking changes above).
|
||||||
|
- Tightened localhost detection: a hostname that merely *starts* with `127.` (e.g.
|
||||||
|
`127.evil.com`) is no longer mistaken for a loopback address, so it can no longer bypass the
|
||||||
|
plaintext-RPC protection.
|
||||||
|
- Sapling parameters are now integrity-checked (SHA-256) against pinned canonical digests
|
||||||
|
before use, instead of only checking that the files exist. A truncated or corrupt parameter
|
||||||
|
file is caught up front rather than surfacing later as a confusing shielded-operation failure.
|
||||||
|
(Cached via a `size:mtime` marker so it doesn't re-hash ~48 MB on every launch.)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Daemon crashes are no longer occasionally missed: a race between the UI thread and the
|
||||||
|
process monitor could consume the daemon's exit status, hiding a crash and defeating the
|
||||||
|
automatic-restart cap. The monitor is now the sole reaper.
|
||||||
|
- A daemon that fails to launch (missing execute permission, wrong architecture, corrupt
|
||||||
|
binary) now reports a precise error immediately instead of briefly showing "running" and
|
||||||
|
then a generic "exited unexpectedly (exit code 127)".
|
||||||
|
- A quick stop→start no longer triggers a restart storm: the wallet now waits briefly for a
|
||||||
|
previous daemon to release the data-directory lock and shows a clear, non-crash message
|
||||||
|
instead of exhausting the crash-restart budget.
|
||||||
|
- Failures while writing the daemon binaries or Sapling parameters (disk full, permission
|
||||||
|
denied) are now surfaced clearly up front instead of failing opaquely when the daemon later
|
||||||
|
can't start.
|
||||||
|
- Directory-creation failures on startup (read-only home, permission denied) now produce a
|
||||||
|
clear "Cannot create <dir>" message instead of a confusing downstream "config missing" /
|
||||||
|
"binary not found" error (or, in one path, an uncaught exception).
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- A "Taking longer than expected" notice now appears if the daemon is reachable but hasn't
|
||||||
|
finished initializing after ~45 s (configurable via `ui.toml`), with guidance to restart the
|
||||||
|
daemon or open the Console — instead of an indefinite silent spinner. It clears itself
|
||||||
|
automatically once the daemon connects.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Engineering detail and the finding-by-finding rationale for this batch live in
|
||||||
|
`docs/daemon-startup-hardening.md`.
|
||||||
111
CLAUDE.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
ObsidianDragon is a portable, full-node GUI wallet for DragonX (DRGX), written in C++17 using SDL3 + Dear ImGui (immediate-mode). It drives a `dragonxd` full node over JSON-RPC and can embed/extract the daemon itself. A separate **Lite** variant (`ObsidianDragonLite`) drops the full node and instead talks to an external lite-wallet backend library.
|
||||||
|
|
||||||
|
## Build & run
|
||||||
|
|
||||||
|
`build.sh` is the single entry point for all builds. `setup.sh` (repo root) installs/validates dependencies.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build.sh # Dev build (native, no packaging) -> build/linux/bin/ObsidianDragon
|
||||||
|
./build.sh --lite # Dev build of the Lite variant -> build/linux/bin/ObsidianDragonLite
|
||||||
|
./build.sh --clean # Wipe the build dir first
|
||||||
|
./build.sh --linux-release # Release zip + AppImage -> release/linux/
|
||||||
|
./build.sh --win-release # Windows cross-compile (mingw-w64) -> release/windows/
|
||||||
|
./build.sh --mac-release # macOS .app bundle + DMG
|
||||||
|
./setup.sh --check # Report missing build deps without installing
|
||||||
|
```
|
||||||
|
|
||||||
|
Dev builds use `build/linux/` (or `build/mac/`). To re-build incrementally without re-running CMake config: `cmake --build build/linux -j$(nproc)`.
|
||||||
|
|
||||||
|
The wallet connects to the daemon using credentials in `~/.hush/DRAGONX/DRAGONX.conf` (`rpcuser`/`rpcpassword`/`rpcport`). It searches for `dragonxd`/`dragonx-cli` binaries in the **executable's own directory first**, so dropping custom node builds next to the wallet binary overrides the bundled ones.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Tests live in `tests/test_phase4.cpp` — a single large translation unit using a custom assertion harness (`EXPECT_TRUE`/`EXPECT_EQ`/`EXPECT_NEAR` macros, one `main()`, exit code = failure count). `include(CTest)` enables `BUILD_TESTING=ON` by default, so the `ObsidianDragonTests` executable is built alongside the app.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd build/linux && ctest --output-on-failure # run the suite
|
||||||
|
./build/linux/bin/ObsidianDragonTests # run the binary directly (same thing)
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no per-test filtering — it is one binary that runs every assertion. The suite exercises the services layer, lite-wallet bridge, and pure helpers (parsers, formatters, model classes) without launching the GUI. Fixtures are under `tests/fixtures/` (path injected as `DRAGONX_TEST_FIXTURE_DIR`).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Entry & main loop.** `src/main.cpp` owns SDL3 window creation, ImGui/OpenGL(or DX11 on Windows) setup, and the frame loop. The `App` class is the central controller; because it is large it is split across four files that all implement the same class:
|
||||||
|
- `src/app.cpp` — core lifecycle, the per-frame `render()`, tab dispatch
|
||||||
|
- `src/app_network.cpp` — RPC orchestration, sync, peers, daemon lifecycle
|
||||||
|
- `src/app_security.cpp` — encryption, PIN/lock screen, key import/export, backup
|
||||||
|
- `src/app_wizard.cpp` — first-run wizard
|
||||||
|
|
||||||
|
**RPC.** All daemon calls go through `src/rpc/` (`rpc_client`, `connection`, `rpc_worker`). **Never block the main/UI thread with synchronous network I/O — dispatch through `RPCWorker`** (async). `rpc/types.h` holds the shared DTOs.
|
||||||
|
|
||||||
|
**Services** (`src/services/`) hold the non-UI state machines that the `App` owns: `NetworkRefreshService` + `RefreshScheduler` (polling/refresh of balance, peers, txs on intervals) and the `WalletSecurity*` controller/workflow stack (encryption & unlock flows).
|
||||||
|
|
||||||
|
**Data model** (`src/data/`): `WalletState`, `address_book`, `transaction_history_cache`, `exchange_info`. UI reads from these.
|
||||||
|
|
||||||
|
**UI** (`src/ui/`): `windows/` are the tabs and dialogs (one pair per screen, e.g. `send_tab`, `mining_tab`, `console_tab`), `pages/` are multi-section screens (Settings), `material/` is the design-system layer (the live helpers `color_theme`, `colors`, `type`/`typography`, `draw_helpers`, `layout`, `project_icons`, `components/buttons`), `schema/` loads the TOML UI schema/skins, `effects/` is GL post-processing (blur/acrylic).
|
||||||
|
|
||||||
|
**Lite wallet** (`src/wallet/`): the bridge to an external `litelib_*` C-ABI backend. `lite_client_bridge` loads the backend (via direct `litelib_*` externs in `linkedSdxl()`) and owns each Rust string through `lite_owned_string` (copy-before-free / free-once). On top sit `lite_connection_service`, `lite_sync_service`, `lite_result_parsers`, `lite_wallet_gateway`, `lite_wallet_state_mapper`, and `lite_wallet_lifecycle_service`, all driven by `lite_wallet_controller`. The real frontend entry points are `lite_wallet_lifecycle_ui_adapter` and `lite_wallet_server_selection_adapter` (used by `src/ui/pages/settings_page.cpp`); everything else is reachable through them. (The prebuilt-backend symbol check for `DRAGONX_ENABLE_LITE_BACKEND` is done in CMake against the symbols inventory — see below — not in C++.)
|
||||||
|
|
||||||
|
> ⚠️ **Do not regrow the `_plan`/`_batch` churn.** This directory previously held ~160 dead `lite_wallet_*_plan` / `*_batch*_receipt_custody_acceptance_confirmation_archive_handoff_*` files (filenames up to 250 chars) — auto-generated scaffolding that never reached the shipping binary. They were deleted. When extending lite-wallet behavior, **edit the named service/bridge/runtime files in place**; never add another "promotion/receipt/custody/handoff/stewardship" wrapper layer. `scripts/check-source-hygiene.sh` (wired as a `.git/hooks/pre-commit` hook) blocks >80-char filenames and chained churn-token names — run it in CI too.
|
||||||
|
|
||||||
|
**Chat** (`src/chat/*`): the HushChat protocol port (Contacts/Chat tabs, seed-derived identity, secretstream crypto, seed-encrypted sqlite store, two-variant send/receive transport). Runtime behavior is gated by `DRAGONX_ENABLE_CHAT`, now **default ON** (the sources always compile; the flag folds the feature away at runtime via `hushChatFeatureEnabledAtBuild()`). Full-node chat derives its identity from the wallet's mnemonic (`z_exportmnemonic`, portable/SDXLite-compatible) or, for legacy/non-mnemonic wallets, a stable z-address spending key (`z_exportkey`).
|
||||||
|
|
||||||
|
## Build variants & feature gating
|
||||||
|
|
||||||
|
Variants are selected with CMake options (set by `build.sh` flags), surfaced to C++ as compile definitions:
|
||||||
|
- `DRAGONX_BUILD_LITE` (`--lite`) → `DRAGONX_LITE_BUILD` define; renames the app to `ObsidianDragonLite` and excludes embedded-daemon / full-node assets (Sapling params, asmap, dragonxd).
|
||||||
|
- `DRAGONX_ENABLE_LITE_BACKEND` → links a real external lite backend. Requires `--lite`, link mode `imported`, ABI `sdxl-c-v1`, and a symbols inventory file (built by `scripts/build-lite-backend-artifact.sh`); CMake hard-fails if any required `litelib_*` symbol is missing. The backend **source is vendored in-tree** at `third_party/silentdragonxlite/` — the `qtlib` C-ABI wrapper (`lib/`, produces `libsilentdragonxlite.a`) and the `silentdragonxlitelib` core (`silentdragonxlite-cli/lib/`, with `proto/` + `res/`). `build-lite-backend-artifact.sh` defaults `--backend-dir` there, so the lite wallet builds **without** the upstream SilentDragonXLite repo. External build inputs are limited to the **Rust toolchain (rustc/cargo 1.63)** plus two project-controlled sources on `git.dragonx.is`: the librustzcash crates come from the mirror `git.dragonx.is/DragonX/librustzcash` (the 6 `git =` deps in the core `Cargo.toml`, pinned to rev `acff1444…`), and the **Sapling params are not committed** (gitignored) — the build fetches them from the `git.dragonx.is/DragonX/zcash-params` release `sapling-v1` and verifies their SHA-256 before rust-embed bakes them in (`ensure_sapling_params`; override the URL with `SAPLING_PARAMS_BASE_URL`). Other crate deps come from crates.io. For a fully offline build, `cargo vendor` into `third_party/silentdragonxlite/lib/vendor/` and add a `vendored-sources` redirect to `lib/.cargo/config.toml` (the build script symlinks `vendor/` into its prepared dir if present); `vendor/` is gitignored.
|
||||||
|
- `DRAGONX_ENABLE_CHAT` → `DRAGONX_ENABLE_CHAT` define gating the chat module.
|
||||||
|
|
||||||
|
Guard full-node-only code paths with `#if DRAGONX_LITE_BUILD` / chat code with `DRAGONX_ENABLE_CHAT`.
|
||||||
|
|
||||||
|
## Lite wallet status
|
||||||
|
|
||||||
|
The Lite variant is **functionally complete and runtime-verified on Linux + Windows** (work lives on branch `cleanup/lite-plan-churn`, **local-only — not pushed yet**):
|
||||||
|
- **Implemented:** lifecycle (create/open/restore + auto-open on startup), sync, refresh, send / shield / import / export / seed, persistence (the backend does *not* auto-save after sync/send/shield — the controller triggers `save` at those points), and passphrase **encryption** (encrypt/unlock/lock/decrypt + Settings UI + send-time & startup unlock; the backend locks immediately on `encrypt`). All controller-tested against the fake backend (`tests/fake_lite_backend.h`) and smoke-verified against the real SDXL backend via `tools/lite_smoke` (incl. a full sync). GUI is wired end-to-end with lite-appropriate wording; the full-node RPC connect loop / wizard / daemon strings are gated out of lite (lite "online" is derived from `lite_wallet_->walletOpen()`, not RPC).
|
||||||
|
- **Packaging:** `./build.sh --lite-backend --linux-release` (zip + AppImage, **verified**) and `--win-release` (cross-compiled `.exe`, **verified**; first build the Windows backend artifact with `scripts/build-lite-backend-artifact.sh --platform windows`). macOS `--lite-backend --mac-release` is **wired but not yet verified on this Linux box** (needs macOS/osxcross): the `.app`/launcher/rpath/`CFBundleExecutable` follow `ObsidianDragonLite`, full-node assets are skipped, and the lite variant gets its own `CFBundleName` ("DragonX Wallet Lite"), bundle id (`is.hush.dragonx.lite`), and DMG name so it can coexist with the full-node app. All variants correctly exclude full-node assets.
|
||||||
|
- **Rollout / kill-switch (implemented):** `wallet/lite_rollout_policy.{h,cpp}` is a pure, fail-open gate (local-only, no network) feeding `LiteWalletLifecycleService::availability()` (new `RolloutDisabled` reason). Inputs: the emergency env var `DRAGONX_LITE_KILL_SWITCH` (absolute — not even `force_on` bypasses it); a `lite_rollout` setting (`auto`/`force_on`/`force_off`); and an optional **locally-cached** manifest at `<config-dir>/lite_rollout.json` (`global_enabled`, `min_version`/`max_version`, `blocked_versions`, `rollout_permille`, `message`) keyed for staged rollout on a hashed, never-transmitted per-install id. A signed remote fetcher can populate that cache later without touching the policy. Resolved in `App::rebuildLiteWallet()`; the disable message surfaces via the lifecycle status. Unit-tested + runtime-verified (env / manifest / control).
|
||||||
|
- **Remaining (M5b):** verify the wired macOS `--lite` packaging on a Mac/osxcross, CI backend-artifact build + signing.
|
||||||
|
- **To publish:** rename branch → `feat/lite-wallet`, base the PR on `dev` (the full-node UX is already there), and handle the dormant gated-OFF HushChat content bundled in commit `af06b8b`.
|
||||||
|
|
||||||
|
The detailed milestone plan and design history (the v2 plan, backend artifact/ABI/signing design docs, the v1 plan, chat specs, etc.) are kept **untracked** under `docs/_archive/`.
|
||||||
|
|
||||||
|
## Miner updater (xmrig)
|
||||||
|
|
||||||
|
The mining tab's pool section has an **"Update miner…"** button that downloads/verifies/installs the latest DRG-XMRig from the project Gitea (`util/XmrigUpdater` + `ui/windows/xmrig_download_dialog.h`). Flow: query `git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases/latest` → pick the asset for this platform (`linux-x64` / `win-x64` / `macos-x86_64`; no match → "Unavailable") → libcurl download (TLS verified) → verify the archive **SHA-256** (from the release body) **and** a detached **ed25519 signature** → miniz-extract the binary (flattening the versioned subdir) into `resources::getDaemonDirectory()`. The whole archive is verified, so extracted members are trusted by transitivity (no per-member hash check). The pure, no-I/O core is split into `xmrig_updater_core.cpp` for unit tests; an env-gated (`DRAGONX_TEST_NETWORK=1`) test exercises the worker live. The dialog is a two-pane version picker (every `/releases` entry on the left, newest first, pre-releases included) so users can pin an older or pre-release build — same verify/install path via `startInstallRelease()`. It shares only the `ReleaseRow` row model with the daemon updater (`ui/windows/release_list_view.h`); each dialog renders its own tactile Material list.
|
||||||
|
|
||||||
|
**Signature verification is enforced** (`kXmrigRequireSignature = true` in `src/util/xmrig_updater.h`), checked against the public key pinned in `kXmrigSignaturePublicKeyBase64`. **Consequence for releases:** every `drg-xmrig` release MUST ship a detached signature per archive or the in-app updater refuses it. To cut a release: build the archives, then `scripts/sign-xmrig-release.sh sign <secret.key> <archive.zip>...` (OpenSSL-based, no extra deps) and upload each `<archive>.sig` as a release asset alongside its `.zip`. The signing **secret key must stay offline** (it is gitignored: `*.ed25519.key`); only its base64 public key is pinned in the source. To rotate the key, regenerate (`scripts/sign-xmrig-release.sh keygen`) and update `kXmrigSignaturePublicKeyBase64`. An emergency env override is not provided — disabling verification means setting `kXmrigSignaturePublicKeyBase64` empty (and rebuilding).
|
||||||
|
|
||||||
|
## Daemon updater (dragonxd)
|
||||||
|
|
||||||
|
Settings → **NODE & SECURITY → DAEMON BINARY** has a **"Check for updates…"** button that downloads/verifies/installs the latest **dragonxd full node** from the project Gitea — the full-node sibling of the xmrig updater (`util/DaemonUpdater` + `ui/windows/daemon_download_dialog.h`, pure no-I/O core in `daemon_updater_core.cpp`; gated full-node-only via `supportsFullNodeLifecycleActions()`). Flow: query `git.dragonx.is/api/v1/repos/DragonX/dragonx/releases/latest` → pick the archive for this platform (`linux-amd64` / `macos` / `win64`; no match → "Unavailable") → libcurl download (TLS verified) → verify the archive **SHA-256** (parsed from the release body's markdown **checksum table**, not xmrig's `<hash> <name>` lines) **and** a detached **ed25519 signature** → miniz-extract the three executables (`dragonxd`/`dragonx-cli`/`dragonx-tx`, flattening the versioned subdir) into `resources::getDaemonDirectory()`. The archive also bundles Sapling params/asmap, which the updater deliberately leaves to the wallet's own resource extraction. Install is **atomic and safe while the node runs** (POSIX `rename()` replaces the in-use binary; Windows moves the locked `.exe` aside to `.old`); the new binary takes effect on the **next daemon start**, so the Done screen offers **"Restart daemon now"** (`App::restartDaemon()`). The dialog is a two-pane version picker (every `/releases` entry on the left) so users can pin a specific/older/pre-release node build via `startInstallRelease()` — with a downgrade caution, since an older binary may not match current chain data. It shares only the `ReleaseRow` row model (`ui/windows/release_list_view.h`) with the miner updater; each renders its own tactile Material list.
|
||||||
|
|
||||||
|
**Signature verification is enforced** (`kDaemonRequireSignature = true` in `src/util/daemon_updater.h`), checked against `kDaemonSignaturePublicKeyBase64`. **Consequence for releases:** every `dragonx` release MUST ship a detached `<archive>.sig` per platform archive or the in-app updater refuses it (as of v1.0.2 the releases publish SHA-256 but **no** signatures yet — sign + upload them to enable in-app updates). To cut a release: `scripts/sign-daemon-release.sh sign <secret.key> dragonx-<ver>-{linux-amd64,macos,win64}.zip` (OpenSSL-based) and upload each `.sig` next to its `.zip`. The signing **secret key stays offline** (gitignored `*.ed25519.key`; this repo's is `dragonx-daemon.ed25519.key`); only the base64 public key is pinned. To rotate: `scripts/sign-daemon-release.sh keygen` and update `kDaemonSignaturePublicKeyBase64`. The generic SHA-256 / ed25519 primitives are shared with the miner updater (`util::sha256Hex` / `util::verifyXmrigSignature`).
|
||||||
|
|
||||||
|
## Seed phrase & migrate-to-seed (full node)
|
||||||
|
|
||||||
|
Full-node wallets are **BIP39-mnemonic-backed** and can **migrate a legacy (non-mnemonic) wallet into a seed wallet**. All of this **requires the `hd-transparent-keys`/`dev` daemon** (`z_exportmnemonic` + `-usemnemonic`); the older bundled binary lacks those RPCs, so these features degrade gracefully (chat falls back to a `z_exportkey` identity; the backup screen shows a "legacy wallet" note). The daemon source is vendored at `external/dragonx/` (build with its own `./build.sh`); deploy the built `dragonxd`/`dragonx-cli`/`dragonx-tx` into the wallet's daemon dir (`build/*/bin`) or via the daemon updater.
|
||||||
|
|
||||||
|
- **New wallets get a phrase.** `EmbeddedDaemon::getChainParams()` always passes `-usemnemonic=1`. The daemon reads it **only inside `GenerateNewSeed()` when a wallet has no seed yet**, so it is inert on existing wallets (safe to pass unconditionally) and makes every fresh wallet mnemonic-backed.
|
||||||
|
- **Back up seed phrase.** Settings → Backup & Data → "Seed phrase" opens `renderSeedBackupDialog` (`App::exportSeedPhrase` → `z_exportmnemonic`, wiped via `sodium_memzero`). A one-time nudge (`maybeRemindSeedBackup`, settings flag `seed_backup_reminded`) reminds mnemonic-wallet users to back up.
|
||||||
|
- **Migrate-to-seed** (`showSeedMigrationDialog` / `renderSeedMigrationDialog`, state machine `SeedMigrationStep`). **Phase 1 — create:** `daemon::SeedWalletCreator` runs a **second, isolated `dragonxd`** on its own port + throwaway datadir (`<config>/seed-migrate/DRAGONX`, basename MUST be the acname; `-usemnemonic=1 -connect=0`; RPC is plaintext http, not TLS), mints a mnemonic wallet, exports its seed + a sweep-target z-address, then stops. Uses the one-shot `EmbeddedDaemon::setNextStartOverride` + `setSkipPortCheck`. **Phase 2 — sweep + adopt:** `z_mergetoaddress ["ANY_TADDR","ANY_ZADDR"]` sweeps all funds to the new address; a **Confirming gate** (`pollSweepStatus`) only allows adopt once the sweep tx is **mined (≥1 conf) AND the legacy balance is ~0** (offering a remainder re-sweep); adopt (`beginAdoptSeedWallet`, background) stops the daemon, moves `wallet.dat` aside to a **timestamped `.bak` (never deleted, restored on failure)**, installs the new wallet, and restarts with `-rescan`. Fund-moving code — **two rounds of adversarial review + a live mainnet run** gate it; the pending stage persists (`seed_migration_*` settings) so a restart resumes at sweep/confirm.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
The version has a **single source of truth**: `project(... VERSION 1.2.0 ...)` plus `DRAGONX_VERSION_SUFFIX` in `CMakeLists.txt`. CMake generates `build/.../generated/dragonx_generated_version.h` from `src/config/version.h.in`. Do not hand-edit generated version output or hardcode version strings — bump the `project()` version in `CMakeLists.txt`.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- **C++17.** Match the surrounding code's style per file.
|
||||||
|
- **Icons:** use the Material Design icon font defines (`ICON_MD_*`); never raw Unicode glyphs.
|
||||||
|
- **UI layout values** belong in `res/themes/ui.toml`, read via `schema::UI()` — do not hardcode pixel sizes/offsets in code.
|
||||||
|
- **DPI / display scaling:** `schema::UI()` returns **logical (raw) px**; `Layout::dpiScale()` (= OS DPI × the in-app font-scale) is the single scale factor. The `Layout::k*()` helpers and `BeginOverlayDialog` already fold it in, and ImGui auto-layout scales via the DPI-rebuilt font atlas + `ScaleAllSizes` — so tabs/standard widgets scale for free. But **hand-drawn absolute geometry** (`dl->AddText` at manual `cy += 24.0f` offsets, `SetNextWindowSize`, explicit `ImVec2(width,0)` button sizes, `SameLine(x)` column strides) is immune to all of that and must be multiplied by `ui::Layout::dpiScale()` yourself, or it renders native-size (tiny) on a HiDPI display. Font metrics (`font->LegacySize`, `CalcTextSize`) are already scaled — don't double-scale those. Verify at scale with a full sweep run at `font_scale: 1.5` (same `dpiScale()==1.5` code path as OS 150%).
|
||||||
|
- **i18n:** user-facing strings are translated via `src/util/i18n`; the English source of truth is the `strings_[...]` map in `src/util/i18n.cpp`, and the per-language translations live as the **source of truth** in `res/lang/` (`de`, `es`, `fr`, `ja`, `ko`, `pt`, `ru`, `zh`). **Edit those JSONs directly and additively** — write with `json.dump(..., indent=4, sort_keys=True, ensure_ascii=False)`; never bulk-regenerate/overwrite a whole file (that path silently dropped ~285 keys/language before). `scripts/add_missing_translations.py` back-fills only the keys missing from a JSON (non-destructive), and `scripts/build_cjk_subset.py` rebuilds the CJK subset font (`res/fonts/NotoSansCJK-Subset.ttf`) after new CJK glyphs are added.
|
||||||
|
- **Commits:** the history uses Conventional Commits (`feat(scope): …`, `fix(scope): …`). PRs target `master`.
|
||||||
679
CMakeLists.txt
@@ -3,12 +3,32 @@
|
|||||||
# Released under the GPLv3
|
# Released under the GPLv3
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(ObsidianDragon
|
|
||||||
VERSION 1.0.0
|
# macOS: set deployment target and universal architectures BEFORE project()
|
||||||
|
# so they propagate to all targets, including FetchContent dependencies (SDL3, etc.)
|
||||||
|
if(APPLE)
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum macOS version" FORCE)
|
||||||
|
# Build universal binary (Apple Silicon + Intel) unless the user explicitly set architectures
|
||||||
|
if(NOT DEFINED CMAKE_OSX_ARCHITECTURES OR CMAKE_OSX_ARCHITECTURES STREQUAL "")
|
||||||
|
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "macOS architectures" FORCE)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(ObsidianDragon
|
||||||
|
VERSION 2.0.1
|
||||||
LANGUAGES C CXX
|
LANGUAGES C CXX
|
||||||
DESCRIPTION "DragonX Cryptocurrency Wallet"
|
DESCRIPTION "DragonX Cryptocurrency Wallet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Pre-release suffix (e.g. "-rc1", "-beta2"). Leave empty for stable releases.
|
||||||
|
set(DRAGONX_VERSION_SUFFIX "")
|
||||||
|
|
||||||
|
# ObsidianDragonLite is versioned INDEPENDENTLY of the full-node app above. The active variant's
|
||||||
|
# version flows to the generated header, the Windows .rc/manifest, and build.sh's release names via
|
||||||
|
# DRAGONX_APP_VERSION* (resolved in the lite/full block below).
|
||||||
|
set(DRAGONX_LITE_VERSION "1.0.0")
|
||||||
|
set(DRAGONX_LITE_VERSION_SUFFIX "")
|
||||||
|
|
||||||
# C++17 standard
|
# C++17 standard
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
@@ -22,6 +42,119 @@ endif()
|
|||||||
# Options
|
# Options
|
||||||
option(DRAGONX_USE_SYSTEM_SDL3 "Use system SDL3 instead of fetching" ON)
|
option(DRAGONX_USE_SYSTEM_SDL3 "Use system SDL3 instead of fetching" ON)
|
||||||
option(DRAGONX_ENABLE_EMBEDDED_DAEMON "Enable embedded dragonxd support" ON)
|
option(DRAGONX_ENABLE_EMBEDDED_DAEMON "Enable embedded dragonxd support" ON)
|
||||||
|
option(DRAGONX_BUILD_LITE "Build ObsidianDragonLite variant without full-node features" OFF)
|
||||||
|
option(DRAGONX_ENABLE_LITE_BACKEND "Enable real lite wallet backend integration" OFF)
|
||||||
|
option(DRAGONX_ENABLE_CHAT "Enable the HushChat protocol/UI integration" ON)
|
||||||
|
set(DRAGONX_LITE_BACKEND_LIBRARY "" CACHE FILEPATH "Path to a prebuilt SDXL-compatible lite backend library")
|
||||||
|
set(DRAGONX_LITE_BACKEND_INCLUDE_DIR "" CACHE PATH "Optional include directory for SDXL-compatible lite backend headers")
|
||||||
|
set(DRAGONX_LITE_BACKEND_EXTRA_LIBS "" CACHE STRING "Additional libraries needed by the SDXL-compatible lite backend")
|
||||||
|
set(DRAGONX_LITE_BACKEND_LINK_MODE "imported" CACHE STRING "Lite backend link mode; Phase 1 supports imported only")
|
||||||
|
set_property(CACHE DRAGONX_LITE_BACKEND_LINK_MODE PROPERTY STRINGS imported)
|
||||||
|
set(DRAGONX_LITE_BACKEND_ABI "sdxl-c-v1" CACHE STRING "Expected lite backend C ABI version")
|
||||||
|
set(DRAGONX_LITE_BACKEND_SYMBOLS_FILE "" CACHE FILEPATH "Path to generated lite backend exported-symbol inventory")
|
||||||
|
set(DRAGONX_LITE_BACKEND_MANIFEST "" CACHE FILEPATH "Optional path to generated lite backend artifact manifest")
|
||||||
|
set(DRAGONX_LITE_BACKEND_REQUIRED_SYMBOLS
|
||||||
|
litelib_wallet_exists
|
||||||
|
litelib_initialize_new
|
||||||
|
litelib_initialize_new_from_phrase
|
||||||
|
litelib_initialize_existing
|
||||||
|
litelib_execute
|
||||||
|
litelib_rust_free_string
|
||||||
|
litelib_check_server_online
|
||||||
|
litelib_shutdown
|
||||||
|
)
|
||||||
|
|
||||||
|
if(DRAGONX_BUILD_LITE)
|
||||||
|
set(DRAGONX_APP_NAME "ObsidianDragonLite")
|
||||||
|
set(DRAGONX_BINARY_NAME "ObsidianDragonLite")
|
||||||
|
# NOTE: do NOT FORCE-write DRAGONX_ENABLE_EMBEDDED_DAEMON=OFF into the cache here. A forced
|
||||||
|
# cache write persists into a later full-node reconfigure of the same build dir and silently
|
||||||
|
# disables the embedded daemon — the binary still embeds/extracts, but isUsingEmbeddedDaemon()
|
||||||
|
# returns false, so it "unpacks dragonxd but never starts" (the 1.3.0 regression). It is also
|
||||||
|
# redundant: makeWalletCapabilities() already forces the embedded-daemon capability off for any
|
||||||
|
# lite build via `fullNodeBuild && embeddedDaemonCompiled`, so lite never launches a daemon
|
||||||
|
# regardless of this flag. build.sh sets the flag explicitly per variant to defeat stale caches.
|
||||||
|
set(DRAGONX_APP_VERSION "${DRAGONX_LITE_VERSION}")
|
||||||
|
set(DRAGONX_APP_VERSION_SUFFIX "${DRAGONX_LITE_VERSION_SUFFIX}")
|
||||||
|
else()
|
||||||
|
set(DRAGONX_APP_NAME "ObsidianDragon")
|
||||||
|
set(DRAGONX_BINARY_NAME "ObsidianDragon")
|
||||||
|
set(DRAGONX_APP_VERSION "${PROJECT_VERSION}")
|
||||||
|
set(DRAGONX_APP_VERSION_SUFFIX "${DRAGONX_VERSION_SUFFIX}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Split the active version into numeric components for the generated header + Windows VERSIONINFO.
|
||||||
|
string(REPLACE "." ";" _dragonx_ver_parts "${DRAGONX_APP_VERSION}")
|
||||||
|
list(GET _dragonx_ver_parts 0 DRAGONX_APP_VERSION_MAJOR)
|
||||||
|
list(GET _dragonx_ver_parts 1 DRAGONX_APP_VERSION_MINOR)
|
||||||
|
list(GET _dragonx_ver_parts 2 DRAGONX_APP_VERSION_PATCH)
|
||||||
|
|
||||||
|
set(DRAGONX_LITE_BACKEND_READY OFF)
|
||||||
|
if(DRAGONX_ENABLE_LITE_BACKEND)
|
||||||
|
if(NOT DRAGONX_BUILD_LITE)
|
||||||
|
message(FATAL_ERROR "DRAGONX_ENABLE_LITE_BACKEND is only supported with DRAGONX_BUILD_LITE=ON")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_LINK_MODE STREQUAL "imported")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_LINK_MODE currently supports only 'imported'; runtime dynamic loading is a later bridge-runtime phase")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_ABI STREQUAL "sdxl-c-v1")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_ABI must be sdxl-c-v1")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_LIBRARY)
|
||||||
|
message(FATAL_ERROR "DRAGONX_ENABLE_LITE_BACKEND requires DRAGONX_LITE_BACKEND_LIBRARY to point at an SDXL-compatible artifact")
|
||||||
|
endif()
|
||||||
|
if(NOT EXISTS "${DRAGONX_LITE_BACKEND_LIBRARY}")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_LIBRARY does not exist: ${DRAGONX_LITE_BACKEND_LIBRARY}")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_SYMBOLS_FILE)
|
||||||
|
message(FATAL_ERROR "DRAGONX_ENABLE_LITE_BACKEND requires DRAGONX_LITE_BACKEND_SYMBOLS_FILE generated by scripts/build-lite-backend-artifact.sh")
|
||||||
|
endif()
|
||||||
|
if(NOT EXISTS "${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_SYMBOLS_FILE does not exist: ${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||||
|
endif()
|
||||||
|
file(STRINGS "${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}" DRAGONX_LITE_BACKEND_SYMBOL_LINES)
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_SYMBOL_LINES)
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_SYMBOLS_FILE is empty: ${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||||
|
endif()
|
||||||
|
foreach(DRAGONX_LITE_REQUIRED_SYMBOL IN LISTS DRAGONX_LITE_BACKEND_REQUIRED_SYMBOLS)
|
||||||
|
list(FIND DRAGONX_LITE_BACKEND_SYMBOL_LINES "${DRAGONX_LITE_REQUIRED_SYMBOL}" DRAGONX_LITE_SYMBOL_INDEX)
|
||||||
|
if(DRAGONX_LITE_SYMBOL_INDEX EQUAL -1)
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_SYMBOLS_FILE is missing required symbol: ${DRAGONX_LITE_REQUIRED_SYMBOL}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
if(DRAGONX_LITE_BACKEND_MANIFEST AND NOT EXISTS "${DRAGONX_LITE_BACKEND_MANIFEST}")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_MANIFEST does not exist: ${DRAGONX_LITE_BACKEND_MANIFEST}")
|
||||||
|
endif()
|
||||||
|
# Note (F15-1): the former signature-metadata gate was removed. It trusted a
|
||||||
|
# "verification_status: verified" field that scripts/build-lite-backend-artifact.sh
|
||||||
|
# self-attested with no cryptographic check (the "verified" SHA was just the artifact's
|
||||||
|
# own SHA). The trust root is now build-from-source: that script builds the backend from
|
||||||
|
# the vendored in-tree source and refuses prebuilt artifacts, so the library linked here
|
||||||
|
# is the one built from reviewed source. The required-symbol inventory check above stays.
|
||||||
|
|
||||||
|
add_library(dragonx_lite_backend UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(dragonx_lite_backend PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${DRAGONX_LITE_BACKEND_LIBRARY}"
|
||||||
|
)
|
||||||
|
if(APPLE)
|
||||||
|
# The Rust backend's TLS stack (security-framework / core-foundation crates)
|
||||||
|
# references Secure Transport (SSL*) + CoreFoundation symbols. Link the frameworks
|
||||||
|
# that provide them, or the static lib leaves ~130 symbols undefined at link time.
|
||||||
|
set_property(TARGET dragonx_lite_backend APPEND PROPERTY
|
||||||
|
INTERFACE_LINK_LIBRARIES "-framework Security" "-framework CoreFoundation")
|
||||||
|
endif()
|
||||||
|
if(DRAGONX_LITE_BACKEND_INCLUDE_DIR)
|
||||||
|
if(NOT IS_DIRECTORY "${DRAGONX_LITE_BACKEND_INCLUDE_DIR}")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_INCLUDE_DIR does not exist: ${DRAGONX_LITE_BACKEND_INCLUDE_DIR}")
|
||||||
|
endif()
|
||||||
|
set_target_properties(dragonx_lite_backend PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${DRAGONX_LITE_BACKEND_INCLUDE_DIR}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
set(DRAGONX_LITE_BACKEND_READY ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
|
||||||
# Output directories
|
# Output directories
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||||
@@ -94,6 +227,32 @@ FetchContent_Declare(
|
|||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(tomlplusplus)
|
FetchContent_MakeAvailable(tomlplusplus)
|
||||||
|
|
||||||
|
# SQLite amalgamation - local Explorer block-summary cache
|
||||||
|
FetchContent_Declare(
|
||||||
|
sqlite3
|
||||||
|
URL https://www.sqlite.org/2024/sqlite-amalgamation-3450300.zip
|
||||||
|
URL_HASH SHA256=ea170e73e447703e8359308ca2e4366a3ae0c4304a8665896f068c736781c651
|
||||||
|
)
|
||||||
|
FetchContent_GetProperties(sqlite3)
|
||||||
|
if(NOT sqlite3_POPULATED)
|
||||||
|
FetchContent_Populate(sqlite3)
|
||||||
|
endif()
|
||||||
|
file(GLOB SQLITE3_AMALGAMATION_C CONFIGURE_DEPENDS
|
||||||
|
${sqlite3_SOURCE_DIR}/sqlite3.c
|
||||||
|
${sqlite3_SOURCE_DIR}/*/sqlite3.c
|
||||||
|
)
|
||||||
|
if(NOT SQLITE3_AMALGAMATION_C)
|
||||||
|
message(FATAL_ERROR "SQLite amalgamation source not found")
|
||||||
|
endif()
|
||||||
|
list(GET SQLITE3_AMALGAMATION_C 0 SQLITE3_SOURCE_FILE)
|
||||||
|
get_filename_component(SQLITE3_INCLUDE_DIR ${SQLITE3_SOURCE_FILE} DIRECTORY)
|
||||||
|
add_library(sqlite3_amalgamation STATIC ${SQLITE3_SOURCE_FILE})
|
||||||
|
target_include_directories(sqlite3_amalgamation PUBLIC ${SQLITE3_INCLUDE_DIR})
|
||||||
|
target_compile_definitions(sqlite3_amalgamation PRIVATE
|
||||||
|
SQLITE_THREADSAFE=1
|
||||||
|
SQLITE_OMIT_LOAD_EXTENSION
|
||||||
|
)
|
||||||
|
|
||||||
# libcurl for HTTPS RPC connections (more reliable than cpp-httplib with OpenSSL 3.x)
|
# libcurl for HTTPS RPC connections (more reliable than cpp-httplib with OpenSSL 3.x)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
# For Windows cross-compilation, fetch and build libcurl statically
|
# For Windows cross-compilation, fetch and build libcurl statically
|
||||||
@@ -123,6 +282,38 @@ else()
|
|||||||
set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
|
set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# libwebp - WebP decode (still + animated via WebPAnimDecoder). Built from source, static, decode-only
|
||||||
|
# so Linux / mingw-Windows / macOS-osxcross all build it identically (the mingw/osx sysroots have no
|
||||||
|
# webp). Encode tools are disabled to avoid pulling in libpng/zlib that the cross sysroots lack.
|
||||||
|
message(STATUS "Fetching libwebp (decode-only, static)...")
|
||||||
|
FetchContent_Declare(
|
||||||
|
libwebp
|
||||||
|
GIT_REPOSITORY https://github.com/webmproject/libwebp.git
|
||||||
|
GIT_TAG v1.4.0
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
# libwebp's cpu.cmake applies -mno-sse2/-mno-sse4.1 to its scalar reference DSP
|
||||||
|
# files when it can't probe SSE support. Under a macOS universal build
|
||||||
|
# (-arch arm64;x86_64) that probe fails, so the flags land on the x86_64 slice,
|
||||||
|
# where -mno-sse2 disables _Float16 and breaks the SDK's <math.h>. Neutralize
|
||||||
|
# those disable flags (SSE2 is x86_64 baseline). Portable + idempotent; a no-op
|
||||||
|
# for single-arch Linux/Windows/x86_64 builds. See cmake/patch-libwebp-simd.cmake.
|
||||||
|
PATCH_COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DCPU_CMAKE=<SOURCE_DIR>/cmake/cpu.cmake
|
||||||
|
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch-libwebp-simd.cmake
|
||||||
|
)
|
||||||
|
set(WEBP_LINK_STATIC ON CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_ANIM_UTILS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_CWEBP OFF CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_DWEBP OFF CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_GIF2WEBP OFF CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_IMG2WEBP OFF CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_VWEBP OFF CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_WEBPINFO OFF CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_LIBWEBPMUX OFF CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_WEBPMUX OFF CACHE BOOL "" FORCE)
|
||||||
|
set(WEBP_BUILD_EXTRAS OFF CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_MakeAvailable(libwebp)
|
||||||
|
|
||||||
# libsodium - platform-specific
|
# libsodium - platform-specific
|
||||||
# Search order per platform:
|
# Search order per platform:
|
||||||
# 1. Local pre-built in libs/libsodium{-mac,-win}/ (downloaded by scripts/fetch-libsodium.sh)
|
# 1. Local pre-built in libs/libsodium{-mac,-win}/ (downloaded by scripts/fetch-libsodium.sh)
|
||||||
@@ -147,6 +338,9 @@ elseif(APPLE)
|
|||||||
if(EXISTS ${CMAKE_SOURCE_DIR}/libs/libsodium-mac/lib/libsodium.a)
|
if(EXISTS ${CMAKE_SOURCE_DIR}/libs/libsodium-mac/lib/libsodium.a)
|
||||||
set(SODIUM_LIBRARY ${CMAKE_SOURCE_DIR}/libs/libsodium-mac/lib/libsodium.a)
|
set(SODIUM_LIBRARY ${CMAKE_SOURCE_DIR}/libs/libsodium-mac/lib/libsodium.a)
|
||||||
set(SODIUM_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/libs/libsodium-mac/include)
|
set(SODIUM_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/libs/libsodium-mac/include)
|
||||||
|
elseif(EXISTS ${CMAKE_SOURCE_DIR}/libs/libsodium/lib/libsodium.a)
|
||||||
|
set(SODIUM_LIBRARY ${CMAKE_SOURCE_DIR}/libs/libsodium/lib/libsodium.a)
|
||||||
|
set(SODIUM_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/libs/libsodium/include)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
# Linux: prefer system libsodium, fall back to local build
|
# Linux: prefer system libsodium, fall back to local build
|
||||||
@@ -208,6 +402,35 @@ else()
|
|||||||
list(APPEND IMGUI_HEADERS ${IMGUI_DIR}/backends/imgui_impl_opengl3.h)
|
list(APPEND IMGUI_HEADERS ${IMGUI_DIR}/backends/imgui_impl_opengl3.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Optional FreeType font loader — enables color-emoji rendering (COLR/CPAL Twemoji) when the chat
|
||||||
|
# "color emoji" setting is on; otherwise the wallet falls back to the monochrome emoji subset.
|
||||||
|
# - Native Linux/macOS: use the system FreeType via find_package.
|
||||||
|
# - Windows (mingw cross): the toolchain ships no FreeType, so build.sh --win-release cross-builds a
|
||||||
|
# static one (scripts/build-freetype-mingw.sh) and passes -DDRAGONX_MINGW_FREETYPE_PREFIX here.
|
||||||
|
# - Other cross builds (osxcross) without FreeType: silently fall back to monochrome.
|
||||||
|
set(DRAGONX_FREETYPE OFF)
|
||||||
|
set(DRAGONX_FREETYPE_LIB "")
|
||||||
|
set(DRAGONX_FREETYPE_INC "")
|
||||||
|
if(DEFINED DRAGONX_MINGW_FREETYPE_PREFIX AND EXISTS "${DRAGONX_MINGW_FREETYPE_PREFIX}/lib/libfreetype.a")
|
||||||
|
set(DRAGONX_FREETYPE ON)
|
||||||
|
set(DRAGONX_FREETYPE_LIB "${DRAGONX_MINGW_FREETYPE_PREFIX}/lib/libfreetype.a")
|
||||||
|
set(DRAGONX_FREETYPE_INC "${DRAGONX_MINGW_FREETYPE_PREFIX}/include/freetype2")
|
||||||
|
message(STATUS "FreeType (mingw cross-built) found — chat color emoji enabled")
|
||||||
|
elseif(NOT CMAKE_CROSSCOMPILING)
|
||||||
|
find_package(Freetype QUIET)
|
||||||
|
if(FREETYPE_FOUND)
|
||||||
|
set(DRAGONX_FREETYPE ON)
|
||||||
|
set(DRAGONX_FREETYPE_LIB Freetype::Freetype) # imported target carries include dirs
|
||||||
|
message(STATUS "FreeType ${FREETYPE_VERSION_STRING} found — chat color emoji enabled")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
if(DRAGONX_FREETYPE)
|
||||||
|
list(APPEND IMGUI_SOURCES ${IMGUI_DIR}/misc/freetype/imgui_freetype.cpp)
|
||||||
|
list(APPEND IMGUI_HEADERS ${IMGUI_DIR}/misc/freetype/imgui_freetype.h)
|
||||||
|
else()
|
||||||
|
message(STATUS "FreeType not found — chat color emoji falls back to monochrome")
|
||||||
|
endif()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# QR Code library (bundled)
|
# QR Code library (bundled)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -229,22 +452,72 @@ set(APP_SOURCES
|
|||||||
src/app.cpp
|
src/app.cpp
|
||||||
src/app_network.cpp
|
src/app_network.cpp
|
||||||
src/app_security.cpp
|
src/app_security.cpp
|
||||||
|
src/app_sweep.cpp
|
||||||
src/app_wizard.cpp
|
src/app_wizard.cpp
|
||||||
|
src/services/network_refresh_service.cpp
|
||||||
|
src/services/refresh_scheduler.cpp
|
||||||
|
src/services/wallet_security_controller.cpp
|
||||||
|
src/services/wallet_security_workflow.cpp
|
||||||
|
src/services/wallet_security_workflow_executor.cpp
|
||||||
|
src/chat/chat_protocol.cpp
|
||||||
|
src/chat/chat_crypto.cpp
|
||||||
|
src/chat/chat_identity.cpp
|
||||||
|
src/chat/chat_store.cpp
|
||||||
|
src/chat/chat_service.cpp
|
||||||
|
src/chat/chat_database.cpp
|
||||||
|
src/chat/chat_outgoing.cpp
|
||||||
|
src/wallet/lite_owned_string.cpp
|
||||||
|
src/wallet/lite_rollout_policy.cpp
|
||||||
|
src/wallet/lite_client_bridge.cpp
|
||||||
|
src/wallet/lite_connection_service.cpp
|
||||||
|
src/wallet/lite_diagnostics.cpp
|
||||||
|
src/wallet/lite_wallet_controller.cpp
|
||||||
|
src/wallet/lite_result_parsers.cpp
|
||||||
|
src/wallet/lite_sync_service.cpp
|
||||||
|
src/wallet/lite_wallet_gateway.cpp
|
||||||
|
src/wallet/lite_wallet_state_mapper.cpp
|
||||||
|
src/wallet/lite_wallet_lifecycle_ui_adapter.cpp
|
||||||
|
src/wallet/lite_wallet_server_selection_adapter.cpp
|
||||||
|
src/wallet/lite_wallet_lifecycle_service.cpp
|
||||||
src/data/wallet_state.cpp
|
src/data/wallet_state.cpp
|
||||||
|
src/data/transaction_history_cache.cpp
|
||||||
src/ui/theme.cpp
|
src/ui/theme.cpp
|
||||||
src/ui/theme_loader.cpp
|
src/ui/theme_loader.cpp
|
||||||
|
src/ui/explorer/explorer_block_cache.cpp
|
||||||
src/ui/material/color_theme.cpp
|
src/ui/material/color_theme.cpp
|
||||||
src/ui/material/typography.cpp
|
src/ui/material/typography.cpp
|
||||||
src/ui/notifications.cpp
|
src/ui/notifications.cpp
|
||||||
src/ui/windows/main_window.cpp
|
src/ui/windows/main_window.cpp
|
||||||
src/ui/windows/balance_tab.cpp
|
src/ui/windows/balance_tab.cpp
|
||||||
|
src/ui/windows/balance_components.cpp
|
||||||
|
src/ui/windows/balance_address_list.cpp
|
||||||
|
src/ui/windows/balance_recent_tx.cpp
|
||||||
|
src/ui/windows/balance_tab_helpers.cpp
|
||||||
src/ui/windows/send_tab.cpp
|
src/ui/windows/send_tab.cpp
|
||||||
src/ui/windows/receive_tab.cpp
|
src/ui/windows/receive_tab.cpp
|
||||||
src/ui/windows/transactions_tab.cpp
|
src/ui/windows/transactions_tab.cpp
|
||||||
src/ui/windows/mining_tab.cpp
|
src/ui/windows/mining_tab.cpp
|
||||||
|
src/ui/windows/mining_earnings.cpp
|
||||||
|
src/ui/windows/mining_stats.cpp
|
||||||
|
src/ui/windows/mining_controls.cpp
|
||||||
|
src/ui/windows/mining_mode_toggle.cpp
|
||||||
|
src/ui/windows/mining_benchmark.cpp
|
||||||
|
src/ui/windows/mining_pool_panel.cpp
|
||||||
|
src/ui/windows/mining_tab_helpers.cpp
|
||||||
src/ui/windows/peers_tab.cpp
|
src/ui/windows/peers_tab.cpp
|
||||||
|
src/ui/windows/network_tab.cpp
|
||||||
|
src/ui/windows/explorer_tab.cpp
|
||||||
src/ui/windows/market_tab.cpp
|
src/ui/windows/market_tab.cpp
|
||||||
src/ui/windows/console_tab.cpp
|
src/ui/windows/console_tab.cpp
|
||||||
|
src/ui/windows/console_command_executor.cpp
|
||||||
|
src/ui/windows/console_command_reference.cpp
|
||||||
|
src/ui/windows/console_input_model.cpp
|
||||||
|
src/ui/windows/console_model.cpp
|
||||||
|
src/ui/windows/console_output_model.cpp
|
||||||
|
src/ui/windows/console_scroll_controller.cpp
|
||||||
|
src/ui/windows/console_selection_controller.cpp
|
||||||
|
src/ui/windows/console_tab_helpers.cpp
|
||||||
|
src/ui/windows/console_text_layout.cpp
|
||||||
src/ui/windows/settings_window.cpp
|
src/ui/windows/settings_window.cpp
|
||||||
src/ui/pages/settings_page.cpp
|
src/ui/pages/settings_page.cpp
|
||||||
src/ui/windows/about_dialog.cpp
|
src/ui/windows/about_dialog.cpp
|
||||||
@@ -252,32 +525,48 @@ set(APP_SOURCES
|
|||||||
src/ui/windows/transaction_details_dialog.cpp
|
src/ui/windows/transaction_details_dialog.cpp
|
||||||
src/ui/windows/qr_popup_dialog.cpp
|
src/ui/windows/qr_popup_dialog.cpp
|
||||||
src/ui/windows/validate_address_dialog.cpp
|
src/ui/windows/validate_address_dialog.cpp
|
||||||
src/ui/windows/address_book_dialog.cpp
|
src/ui/windows/contacts_tab.cpp
|
||||||
|
src/ui/windows/chat_tab.cpp
|
||||||
src/ui/windows/shield_dialog.cpp
|
src/ui/windows/shield_dialog.cpp
|
||||||
src/ui/windows/request_payment_dialog.cpp
|
src/ui/windows/request_payment_dialog.cpp
|
||||||
src/ui/windows/block_info_dialog.cpp
|
src/ui/windows/block_info_dialog.cpp
|
||||||
src/ui/windows/import_key_dialog.cpp
|
|
||||||
src/ui/windows/export_all_keys_dialog.cpp
|
src/ui/windows/export_all_keys_dialog.cpp
|
||||||
src/ui/windows/export_transactions_dialog.cpp
|
src/ui/windows/export_transactions_dialog.cpp
|
||||||
src/ui/windows/backup_wallet_dialog.cpp
|
|
||||||
src/ui/widgets/qr_code.cpp
|
src/ui/widgets/qr_code.cpp
|
||||||
src/rpc/rpc_client.cpp
|
src/rpc/rpc_client.cpp
|
||||||
src/rpc/rpc_worker.cpp
|
src/rpc/rpc_worker.cpp
|
||||||
src/rpc/connection.cpp
|
src/rpc/connection.cpp
|
||||||
src/config/settings.cpp
|
src/config/settings.cpp
|
||||||
src/data/address_book.cpp
|
src/data/address_book.cpp
|
||||||
|
src/data/wallet_index.cpp
|
||||||
src/data/exchange_info.cpp
|
src/data/exchange_info.cpp
|
||||||
src/util/logger.cpp
|
src/util/logger.cpp
|
||||||
|
src/util/async_task_manager.cpp
|
||||||
|
src/util/amount_format.cpp
|
||||||
|
src/util/address_validation.cpp
|
||||||
src/util/base64.cpp
|
src/util/base64.cpp
|
||||||
src/util/single_instance.cpp
|
src/util/single_instance.cpp
|
||||||
src/util/i18n.cpp
|
src/util/i18n.cpp
|
||||||
|
src/util/text_format.cpp
|
||||||
src/util/platform.cpp
|
src/util/platform.cpp
|
||||||
src/util/payment_uri.cpp
|
src/util/payment_uri.cpp
|
||||||
src/util/texture_loader.cpp
|
src/util/texture_loader.cpp
|
||||||
|
src/util/svg_texture.cpp
|
||||||
src/util/noise_texture.cpp
|
src/util/noise_texture.cpp
|
||||||
src/daemon/embedded_daemon.cpp
|
src/daemon/embedded_daemon.cpp
|
||||||
|
src/daemon/seed_wallet_creator.cpp
|
||||||
|
src/daemon/daemon_controller.cpp
|
||||||
|
src/daemon/lifecycle_adapters.cpp
|
||||||
src/daemon/xmrig_manager.cpp
|
src/daemon/xmrig_manager.cpp
|
||||||
src/util/bootstrap.cpp
|
src/util/bootstrap.cpp
|
||||||
|
src/util/lite_server_probe.cpp
|
||||||
|
src/util/pool_registry_core.cpp
|
||||||
|
src/util/pool_stats_service.cpp
|
||||||
|
src/util/http_download.cpp
|
||||||
|
src/util/xmrig_updater.cpp
|
||||||
|
src/util/xmrig_updater_core.cpp
|
||||||
|
src/util/daemon_updater.cpp
|
||||||
|
src/util/daemon_updater_core.cpp
|
||||||
src/util/secure_vault.cpp
|
src/util/secure_vault.cpp
|
||||||
src/ui/effects/framebuffer.cpp
|
src/ui/effects/framebuffer.cpp
|
||||||
src/ui/effects/blur_shader.cpp
|
src/ui/effects/blur_shader.cpp
|
||||||
@@ -308,33 +597,77 @@ endif()
|
|||||||
|
|
||||||
set(APP_HEADERS
|
set(APP_HEADERS
|
||||||
src/app.h
|
src/app.h
|
||||||
|
src/services/network_refresh_service.h
|
||||||
|
src/services/refresh_scheduler.h
|
||||||
|
src/services/wallet_security_controller.h
|
||||||
|
src/services/wallet_security_workflow.h
|
||||||
|
src/services/wallet_security_workflow_executor.h
|
||||||
|
src/wallet/wallet_capabilities.h
|
||||||
|
src/wallet/wallet_backend.h
|
||||||
|
src/wallet/lite_owned_string.h
|
||||||
|
src/wallet/lite_rollout_policy.h
|
||||||
|
src/wallet/lite_client_bridge.h
|
||||||
|
src/wallet/lite_connection_service.h
|
||||||
|
src/wallet/lite_result_parsers.h
|
||||||
|
src/wallet/lite_sync_service.h
|
||||||
|
src/wallet/lite_wallet_gateway.h
|
||||||
|
src/wallet/lite_wallet_state_mapper.h
|
||||||
|
src/wallet/lite_wallet_lifecycle_ui_adapter.h
|
||||||
|
src/wallet/lite_wallet_server_selection_adapter.h
|
||||||
|
src/wallet/lite_wallet_lifecycle_service.h
|
||||||
|
src/chat/chat_protocol.h
|
||||||
|
src/chat/chat_crypto.h
|
||||||
|
src/chat/chat_identity.h
|
||||||
|
src/chat/chat_message.h
|
||||||
|
src/chat/chat_store.h
|
||||||
|
src/chat/chat_service.h
|
||||||
|
src/chat/chat_database.h
|
||||||
|
src/chat/chat_outgoing.h
|
||||||
src/config/version.h
|
src/config/version.h
|
||||||
src/data/wallet_state.h
|
src/data/wallet_state.h
|
||||||
|
src/data/transaction_history_cache.h
|
||||||
src/ui/theme.h
|
src/ui/theme.h
|
||||||
src/ui/theme_loader.h
|
src/ui/theme_loader.h
|
||||||
|
src/ui/explorer/explorer_block_cache.h
|
||||||
src/ui/notifications.h
|
src/ui/notifications.h
|
||||||
src/ui/windows/main_window.h
|
src/ui/windows/main_window.h
|
||||||
src/ui/windows/balance_tab.h
|
src/ui/windows/balance_tab.h
|
||||||
|
src/ui/windows/balance_address_list.h
|
||||||
|
src/ui/windows/balance_recent_tx.h
|
||||||
|
src/ui/windows/balance_tab_helpers.h
|
||||||
src/ui/windows/send_tab.h
|
src/ui/windows/send_tab.h
|
||||||
src/ui/windows/receive_tab.h
|
src/ui/windows/receive_tab.h
|
||||||
src/ui/windows/transactions_tab.h
|
src/ui/windows/transactions_tab.h
|
||||||
src/ui/windows/mining_tab.h
|
src/ui/windows/mining_tab.h
|
||||||
|
src/ui/windows/mining_benchmark.h
|
||||||
|
src/ui/windows/mining_pool_panel.h
|
||||||
|
src/ui/windows/mining_tab_helpers.h
|
||||||
src/ui/windows/peers_tab.h
|
src/ui/windows/peers_tab.h
|
||||||
|
src/ui/windows/explorer_tab.h
|
||||||
src/ui/windows/market_tab.h
|
src/ui/windows/market_tab.h
|
||||||
|
src/ui/windows/console_channel.h
|
||||||
|
src/ui/windows/console_command_reference.h
|
||||||
|
src/ui/windows/console_input_model.h
|
||||||
|
src/ui/windows/console_model.h
|
||||||
|
src/ui/windows/console_output_model.h
|
||||||
|
src/ui/windows/console_scroll_controller.h
|
||||||
|
src/ui/windows/console_selection_controller.h
|
||||||
|
src/ui/windows/console_tab.h
|
||||||
|
src/ui/windows/console_tab_helpers.h
|
||||||
src/ui/windows/settings_window.h
|
src/ui/windows/settings_window.h
|
||||||
src/ui/windows/about_dialog.h
|
src/ui/windows/about_dialog.h
|
||||||
src/ui/windows/key_export_dialog.h
|
src/ui/windows/key_export_dialog.h
|
||||||
src/ui/windows/transaction_details_dialog.h
|
src/ui/windows/transaction_details_dialog.h
|
||||||
src/ui/windows/qr_popup_dialog.h
|
src/ui/windows/qr_popup_dialog.h
|
||||||
src/ui/windows/validate_address_dialog.h
|
src/ui/windows/validate_address_dialog.h
|
||||||
src/ui/windows/address_book_dialog.h
|
src/ui/windows/contacts_tab.h
|
||||||
|
src/ui/windows/chat_tab.h
|
||||||
|
src/ui/windows/contact_picker.h
|
||||||
src/ui/windows/shield_dialog.h
|
src/ui/windows/shield_dialog.h
|
||||||
src/ui/windows/request_payment_dialog.h
|
src/ui/windows/request_payment_dialog.h
|
||||||
src/ui/windows/block_info_dialog.h
|
src/ui/windows/block_info_dialog.h
|
||||||
src/ui/windows/import_key_dialog.h
|
|
||||||
src/ui/windows/export_all_keys_dialog.h
|
src/ui/windows/export_all_keys_dialog.h
|
||||||
src/ui/windows/export_transactions_dialog.h
|
src/ui/windows/export_transactions_dialog.h
|
||||||
src/ui/windows/backup_wallet_dialog.h
|
|
||||||
src/ui/widgets/qr_code.h
|
src/ui/widgets/qr_code.h
|
||||||
src/rpc/rpc_client.h
|
src/rpc/rpc_client.h
|
||||||
src/rpc/rpc_worker.h
|
src/rpc/rpc_worker.h
|
||||||
@@ -344,6 +677,8 @@ set(APP_HEADERS
|
|||||||
src/data/address_book.h
|
src/data/address_book.h
|
||||||
src/data/exchange_info.h
|
src/data/exchange_info.h
|
||||||
src/util/logger.h
|
src/util/logger.h
|
||||||
|
src/util/async_task_manager.h
|
||||||
|
src/util/amount_format.h
|
||||||
src/util/base64.h
|
src/util/base64.h
|
||||||
src/util/single_instance.h
|
src/util/single_instance.h
|
||||||
src/util/i18n.h
|
src/util/i18n.h
|
||||||
@@ -351,6 +686,9 @@ set(APP_HEADERS
|
|||||||
src/util/payment_uri.h
|
src/util/payment_uri.h
|
||||||
src/util/secure_vault.h
|
src/util/secure_vault.h
|
||||||
src/daemon/embedded_daemon.h
|
src/daemon/embedded_daemon.h
|
||||||
|
src/daemon/seed_wallet_creator.h
|
||||||
|
src/daemon/daemon_controller.h
|
||||||
|
src/daemon/lifecycle_adapters.h
|
||||||
src/daemon/xmrig_manager.h
|
src/daemon/xmrig_manager.h
|
||||||
src/ui/effects/framebuffer.h
|
src/ui/effects/framebuffer.h
|
||||||
src/ui/effects/blur_shader.h
|
src/ui/effects/blur_shader.h
|
||||||
@@ -373,11 +711,14 @@ endif()
|
|||||||
# Windows application icon + VERSIONINFO (.rc -> .res -> linked into .exe)
|
# Windows application icon + VERSIONINFO (.rc -> .res -> linked into .exe)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(OBSIDIAN_ICO_PATH "${CMAKE_SOURCE_DIR}/res/img/ObsidianDragon.ico")
|
set(OBSIDIAN_ICO_PATH "${CMAKE_SOURCE_DIR}/res/img/ObsidianDragon.ico")
|
||||||
# Version numbers for the VERSIONINFO resource block
|
# Generate manifest with version from project()
|
||||||
set(DRAGONX_VER_MAJOR 1)
|
configure_file(
|
||||||
set(DRAGONX_VER_MINOR 0)
|
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest.in
|
||||||
set(DRAGONX_VER_PATCH 0)
|
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest
|
||||||
set(DRAGONX_VERSION "1.0.0")
|
@ONLY
|
||||||
|
)
|
||||||
|
set(OBSIDIAN_MANIFEST_PATH "${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest")
|
||||||
|
# Generate .rc with version from project()
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.rc
|
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.rc
|
||||||
${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc
|
${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc
|
||||||
@@ -386,6 +727,15 @@ if(WIN32)
|
|||||||
set(WIN_RC_FILE ${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc)
|
set(WIN_RC_FILE ${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Generate version values from the single project(VERSION ...) declaration.
|
||||||
|
# Keep the build-specific app name in the build tree so full/lite configures do
|
||||||
|
# not rewrite a tracked source header.
|
||||||
|
configure_file(
|
||||||
|
${CMAKE_SOURCE_DIR}/src/config/version.h.in
|
||||||
|
${CMAKE_BINARY_DIR}/generated/dragonx_generated_version.h
|
||||||
|
@ONLY
|
||||||
|
)
|
||||||
|
|
||||||
# Generate INCBIN font embedding source with absolute paths to .ttf files
|
# Generate INCBIN font embedding source with absolute paths to .ttf files
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_SOURCE_DIR}/src/embedded/embedded_fonts.cpp.in
|
${CMAKE_SOURCE_DIR}/src/embedded/embedded_fonts.cpp.in
|
||||||
@@ -393,6 +743,24 @@ configure_file(
|
|||||||
@ONLY
|
@ONLY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# INCBIN uses .incbin assembler directives that reference font files at
|
||||||
|
# assembly time — CMake doesn't track these implicit dependencies.
|
||||||
|
# Tell CMake that the generated source depends on the actual font binaries
|
||||||
|
# so a font file change triggers recompilation.
|
||||||
|
set_source_files_properties(
|
||||||
|
${CMAKE_BINARY_DIR}/generated/embedded_fonts.cpp
|
||||||
|
PROPERTIES OBJECT_DEPENDS
|
||||||
|
"${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-R.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-Light.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-Medium.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/UbuntuMono-R.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/MaterialIcons-Regular.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/NotoSansCJK-Subset.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/NotoEmoji-Subset.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/TwemojiMozilla-Color.ttf"
|
||||||
|
)
|
||||||
|
|
||||||
add_executable(ObsidianDragon
|
add_executable(ObsidianDragon
|
||||||
${APP_SOURCES}
|
${APP_SOURCES}
|
||||||
${CMAKE_BINARY_DIR}/generated/embedded_fonts.cpp
|
${CMAKE_BINARY_DIR}/generated/embedded_fonts.cpp
|
||||||
@@ -403,6 +771,8 @@ add_executable(ObsidianDragon
|
|||||||
${WIN_RC_FILE}
|
${WIN_RC_FILE}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(ObsidianDragon PROPERTIES OUTPUT_NAME "${DRAGONX_BINARY_NAME}")
|
||||||
|
|
||||||
target_include_directories(ObsidianDragon PRIVATE
|
target_include_directories(ObsidianDragon PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/src
|
${CMAKE_SOURCE_DIR}/src
|
||||||
${CMAKE_SOURCE_DIR}/src/embedded
|
${CMAKE_SOURCE_DIR}/src/embedded
|
||||||
@@ -416,19 +786,75 @@ target_include_directories(ObsidianDragon PRIVATE
|
|||||||
${GLAD_INCLUDE}
|
${GLAD_INCLUDE}
|
||||||
${CURL_INCLUDE_DIRS}
|
${CURL_INCLUDE_DIRS}
|
||||||
${MINIZ_DIR}
|
${MINIZ_DIR}
|
||||||
|
${libwebp_SOURCE_DIR}/src # <webp/decode.h>, <webp/demux.h> (FetchContent build tree)
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(ObsidianDragon PRIVATE
|
target_link_libraries(ObsidianDragon PRIVATE
|
||||||
SDL3::SDL3
|
SDL3::SDL3
|
||||||
nlohmann_json::nlohmann_json
|
nlohmann_json::nlohmann_json
|
||||||
tomlplusplus::tomlplusplus
|
tomlplusplus::tomlplusplus
|
||||||
|
sqlite3_amalgamation
|
||||||
${CURL_LIBRARIES}
|
${CURL_LIBRARIES}
|
||||||
${SODIUM_LIBRARY}
|
${SODIUM_LIBRARY}
|
||||||
|
webp
|
||||||
|
webpdemux # WebPAnimDecoder (animated WebP); transitively pulls in webp + sharpyuv
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(DRAGONX_LITE_BACKEND_READY)
|
||||||
|
target_link_libraries(ObsidianDragon PRIVATE dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS})
|
||||||
|
|
||||||
|
# Real-backend smoke tool (only built when a real lite backend is linked).
|
||||||
|
add_executable(lite_smoke
|
||||||
|
tools/lite_smoke.cpp
|
||||||
|
src/wallet/lite_client_bridge.cpp
|
||||||
|
src/wallet/lite_owned_string.cpp
|
||||||
|
src/wallet/lite_rollout_policy.cpp
|
||||||
|
src/wallet/lite_connection_service.cpp
|
||||||
|
src/wallet/lite_result_parsers.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(lite_smoke PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${CMAKE_BINARY_DIR}/generated
|
||||||
|
${SODIUM_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
target_compile_definitions(lite_smoke PRIVATE DRAGONX_ENABLE_LITE_BACKEND=1)
|
||||||
|
target_link_libraries(lite_smoke PRIVATE
|
||||||
|
dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS}
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
${SODIUM_LIBRARY}
|
||||||
|
)
|
||||||
|
if(UNIX)
|
||||||
|
target_link_libraries(lite_smoke PRIVATE ${CMAKE_DL_LIBS} pthread)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Real-backend SEND smoke tool — drives the exact GUI send path (bridge.execute("send", ...)).
|
||||||
|
add_executable(lite_send_smoke
|
||||||
|
tools/lite_send_smoke.cpp
|
||||||
|
src/wallet/lite_client_bridge.cpp
|
||||||
|
src/wallet/lite_owned_string.cpp
|
||||||
|
src/wallet/lite_rollout_policy.cpp
|
||||||
|
src/wallet/lite_connection_service.cpp
|
||||||
|
src/wallet/lite_result_parsers.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(lite_send_smoke PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${CMAKE_BINARY_DIR}/generated
|
||||||
|
${SODIUM_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
target_compile_definitions(lite_send_smoke PRIVATE DRAGONX_ENABLE_LITE_BACKEND=1)
|
||||||
|
target_link_libraries(lite_send_smoke PRIVATE
|
||||||
|
dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS}
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
${SODIUM_LIBRARY}
|
||||||
|
)
|
||||||
|
if(UNIX)
|
||||||
|
target_link_libraries(lite_send_smoke PRIVATE ${CMAKE_DL_LIBS} pthread)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Platform-specific settings
|
# Platform-specific settings
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(ObsidianDragon PRIVATE ws2_32 winmm imm32 version setupapi dwmapi crypt32 wldap32 psapi d3d11 dxgi d3dcompiler dcomp)
|
target_link_libraries(ObsidianDragon PRIVATE ws2_32 winmm imm32 version setupapi dwmapi crypt32 wldap32 psapi iphlpapi d3d11 dxgi d3dcompiler dcomp)
|
||||||
# Hide console window in release builds
|
# Hide console window in release builds
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
set_target_properties(ObsidianDragon PROPERTIES WIN32_EXECUTABLE TRUE)
|
set_target_properties(ObsidianDragon PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||||
@@ -453,7 +879,11 @@ endif()
|
|||||||
|
|
||||||
# Compile definitions
|
# Compile definitions
|
||||||
target_compile_definitions(ObsidianDragon PRIVATE
|
target_compile_definitions(ObsidianDragon PRIVATE
|
||||||
$<$<CONFIG:Debug>:DRAGONX_DEBUG>
|
DRAGONX_DEBUG
|
||||||
|
DRAGONX_LITE_BUILD=$<BOOL:${DRAGONX_BUILD_LITE}>
|
||||||
|
DRAGONX_ENABLE_EMBEDDED_DAEMON=$<BOOL:${DRAGONX_ENABLE_EMBEDDED_DAEMON}>
|
||||||
|
DRAGONX_ENABLE_LITE_BACKEND=$<BOOL:${DRAGONX_LITE_BACKEND_READY}>
|
||||||
|
DRAGONX_ENABLE_CHAT=$<BOOL:${DRAGONX_ENABLE_CHAT}>
|
||||||
)
|
)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_USE_DX11)
|
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_USE_DX11)
|
||||||
@@ -461,6 +891,35 @@ else()
|
|||||||
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_HAS_GLAD)
|
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_HAS_GLAD)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Color-emoji font loader (FreeType) — linked + flagged only when found (see DRAGONX_FREETYPE above).
|
||||||
|
if(DRAGONX_FREETYPE)
|
||||||
|
target_link_libraries(ObsidianDragon PRIVATE ${DRAGONX_FREETYPE_LIB})
|
||||||
|
if(DRAGONX_FREETYPE_INC)
|
||||||
|
target_include_directories(ObsidianDragon PRIVATE ${DRAGONX_FREETYPE_INC})
|
||||||
|
endif()
|
||||||
|
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_HAVE_FREETYPE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(HushChatFixtureCheck
|
||||||
|
tools/hushchat_fixture_check.cpp
|
||||||
|
src/chat/chat_protocol.cpp
|
||||||
|
src/chat/chat_fixture_tooling.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(HushChatFixtureCheck PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${SODIUM_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(HushChatFixtureCheck PRIVATE
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
${SODIUM_LIBRARY}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(HushChatFixtureCheck PRIVATE
|
||||||
|
DRAGONX_ENABLE_CHAT=0
|
||||||
|
)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Copy resources
|
# Copy resources
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -480,17 +939,44 @@ elseif(EXISTS ${CMAKE_SOURCE_DIR}/../SilentDragonX/res/Ubuntu-R.ttf)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Copy language files
|
# Copy language files at BUILD time (not just cmake configure time)
|
||||||
|
# so edits to res/lang/*.json are picked up by 'make' without re-running cmake.
|
||||||
file(GLOB LANG_FILES ${CMAKE_SOURCE_DIR}/res/lang/*.json)
|
file(GLOB LANG_FILES ${CMAKE_SOURCE_DIR}/res/lang/*.json)
|
||||||
if(LANG_FILES)
|
if(LANG_FILES)
|
||||||
|
find_program(XXD_EXECUTABLE NAMES xxd)
|
||||||
|
if(NOT XXD_EXECUTABLE)
|
||||||
|
message(WARNING "xxd not found; runtime language JSON files will be copied, but embedded build/generated/embedded/lang_*.h files will not be regenerated")
|
||||||
|
endif()
|
||||||
|
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated/embedded)
|
||||||
|
|
||||||
foreach(LANG_FILE ${LANG_FILES})
|
foreach(LANG_FILE ${LANG_FILES})
|
||||||
get_filename_component(LANG_FILENAME ${LANG_FILE} NAME)
|
get_filename_component(LANG_FILENAME ${LANG_FILE} NAME)
|
||||||
configure_file(
|
add_custom_command(
|
||||||
${LANG_FILE}
|
OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/lang/${LANG_FILENAME}
|
||||||
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/lang/${LANG_FILENAME}
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
COPYONLY
|
${LANG_FILE}
|
||||||
|
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/lang/${LANG_FILENAME}
|
||||||
|
DEPENDS ${LANG_FILE}
|
||||||
|
COMMENT "Copying ${LANG_FILENAME}"
|
||||||
)
|
)
|
||||||
|
list(APPEND LANG_OUTPUTS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/lang/${LANG_FILENAME})
|
||||||
|
|
||||||
|
# Also regenerate the embedded header so the binary always has fresh translations
|
||||||
|
if(XXD_EXECUTABLE)
|
||||||
|
get_filename_component(LANG_CODE ${LANG_FILENAME} NAME_WE)
|
||||||
|
set(LANG_HEADER ${CMAKE_BINARY_DIR}/generated/embedded/lang_${LANG_CODE}.h)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${LANG_HEADER}
|
||||||
|
COMMAND ${XXD_EXECUTABLE} -i "res/lang/${LANG_FILENAME}" > "${LANG_HEADER}"
|
||||||
|
DEPENDS ${LANG_FILE}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
COMMENT "Embedding lang_${LANG_CODE}.h"
|
||||||
|
)
|
||||||
|
list(APPEND LANG_OUTPUTS ${LANG_HEADER})
|
||||||
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
add_custom_target(copy_langs ALL DEPENDS ${LANG_OUTPUTS})
|
||||||
|
add_dependencies(ObsidianDragon copy_langs)
|
||||||
message(STATUS " Language files: ${LANG_FILES}")
|
message(STATUS " Language files: ${LANG_FILES}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -502,14 +988,39 @@ embed_resource(
|
|||||||
${CMAKE_BINARY_DIR}/generated/ui_toml_embedded.h
|
${CMAKE_BINARY_DIR}/generated/ui_toml_embedded.h
|
||||||
ui_toml
|
ui_toml
|
||||||
)
|
)
|
||||||
|
embed_resource(
|
||||||
|
${CMAKE_SOURCE_DIR}/res/default_banlist.txt
|
||||||
|
${CMAKE_BINARY_DIR}/generated/default_banlist_embedded.h
|
||||||
|
default_banlist
|
||||||
|
)
|
||||||
|
|
||||||
# Note: xmrig is embedded via build.sh (embedded_data.h) for Windows builds,
|
# Note: xmrig is embedded via build.sh (embedded_data.h) for Windows builds,
|
||||||
# following the same pattern as daemon embedding.
|
# following the same pattern as daemon embedding.
|
||||||
|
|
||||||
# Copy theme files at BUILD time (not just cmake configure time)
|
# Expand and copy theme files at BUILD time — skin files get layout sections
|
||||||
# so edits to res/themes/*.toml are picked up by 'make' without re-running cmake.
|
# from ui.toml appended automatically so users can see/edit all properties.
|
||||||
|
# Source skin files stay minimal; the merged output goes to build/bin/res/themes/.
|
||||||
|
find_package(Python3 QUIET COMPONENTS Interpreter)
|
||||||
|
if(NOT Python3_FOUND)
|
||||||
|
find_program(Python3_EXECUTABLE NAMES python3 python)
|
||||||
|
endif()
|
||||||
file(GLOB THEME_FILES ${CMAKE_SOURCE_DIR}/res/themes/*.toml)
|
file(GLOB THEME_FILES ${CMAKE_SOURCE_DIR}/res/themes/*.toml)
|
||||||
if(THEME_FILES)
|
if(THEME_FILES AND Python3_EXECUTABLE)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/themes/.expanded
|
||||||
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/expand_themes.py
|
||||||
|
${CMAKE_SOURCE_DIR}/res/themes
|
||||||
|
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/themes
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/themes/.expanded
|
||||||
|
DEPENDS ${THEME_FILES} ${CMAKE_SOURCE_DIR}/scripts/expand_themes.py
|
||||||
|
COMMENT "Expanding theme files (merging layout from ui.toml)"
|
||||||
|
)
|
||||||
|
add_custom_target(copy_themes ALL DEPENDS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/themes/.expanded)
|
||||||
|
add_dependencies(ObsidianDragon copy_themes)
|
||||||
|
message(STATUS " Theme files: ${THEME_FILES} (build-time expansion via Python)")
|
||||||
|
elseif(THEME_FILES)
|
||||||
|
# Fallback: plain copy if Python is not available
|
||||||
|
message(WARNING "Python3 not found; copying theme files without expand_themes.py layout merge")
|
||||||
foreach(THEME_FILE ${THEME_FILES})
|
foreach(THEME_FILE ${THEME_FILES})
|
||||||
get_filename_component(THEME_FILENAME ${THEME_FILE} NAME)
|
get_filename_component(THEME_FILENAME ${THEME_FILE} NAME)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
@@ -524,7 +1035,7 @@ if(THEME_FILES)
|
|||||||
endforeach()
|
endforeach()
|
||||||
add_custom_target(copy_themes ALL DEPENDS ${THEME_OUTPUTS})
|
add_custom_target(copy_themes ALL DEPENDS ${THEME_OUTPUTS})
|
||||||
add_dependencies(ObsidianDragon copy_themes)
|
add_dependencies(ObsidianDragon copy_themes)
|
||||||
message(STATUS " Theme files: ${THEME_FILES}")
|
message(STATUS " Theme files: ${THEME_FILES} (plain copy, Python not found)")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Copy image files (including backgrounds/ subdirectories and logos/)
|
# Copy image files (including backgrounds/ subdirectories and logos/)
|
||||||
@@ -559,20 +1070,136 @@ install(TARGETS ObsidianDragon
|
|||||||
)
|
)
|
||||||
|
|
||||||
install(DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res
|
install(DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res
|
||||||
DESTINATION share/ObsidianDragon
|
DESTINATION share/${DRAGONX_BINARY_NAME}
|
||||||
OPTIONAL
|
OPTIONAL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
add_executable(ObsidianDragonTests
|
||||||
|
tests/test_phase4.cpp
|
||||||
|
src/services/network_refresh_service.cpp
|
||||||
|
src/services/refresh_scheduler.cpp
|
||||||
|
src/services/wallet_security_controller.cpp
|
||||||
|
src/services/wallet_security_workflow.cpp
|
||||||
|
src/services/wallet_security_workflow_executor.cpp
|
||||||
|
src/chat/chat_protocol.cpp
|
||||||
|
src/chat/chat_crypto.cpp
|
||||||
|
src/chat/chat_identity.cpp
|
||||||
|
src/chat/chat_store.cpp
|
||||||
|
src/chat/chat_service.cpp
|
||||||
|
src/chat/chat_database.cpp
|
||||||
|
src/chat/chat_outgoing.cpp
|
||||||
|
src/wallet/lite_owned_string.cpp
|
||||||
|
src/wallet/lite_rollout_policy.cpp
|
||||||
|
src/wallet/lite_client_bridge.cpp
|
||||||
|
src/wallet/lite_connection_service.cpp
|
||||||
|
src/wallet/lite_diagnostics.cpp
|
||||||
|
src/wallet/lite_wallet_controller.cpp
|
||||||
|
src/wallet/lite_result_parsers.cpp
|
||||||
|
src/wallet/lite_sync_service.cpp
|
||||||
|
src/wallet/lite_wallet_gateway.cpp
|
||||||
|
src/wallet/lite_wallet_state_mapper.cpp
|
||||||
|
src/wallet/lite_wallet_lifecycle_ui_adapter.cpp
|
||||||
|
src/wallet/lite_wallet_server_selection_adapter.cpp
|
||||||
|
src/wallet/lite_wallet_lifecycle_service.cpp
|
||||||
|
src/ui/explorer/explorer_block_cache.cpp
|
||||||
|
src/ui/windows/balance_address_list.cpp
|
||||||
|
src/ui/windows/balance_recent_tx.cpp
|
||||||
|
src/ui/windows/console_input_model.cpp
|
||||||
|
src/ui/windows/console_model.cpp
|
||||||
|
src/ui/windows/console_output_model.cpp
|
||||||
|
src/ui/windows/console_scroll_controller.cpp
|
||||||
|
src/ui/windows/console_selection_controller.cpp
|
||||||
|
src/ui/windows/console_tab_helpers.cpp
|
||||||
|
src/ui/windows/console_text_layout.cpp
|
||||||
|
src/ui/windows/mining_benchmark.cpp
|
||||||
|
src/ui/windows/mining_pool_panel.cpp
|
||||||
|
src/ui/windows/mining_tab_helpers.cpp
|
||||||
|
src/util/payment_uri.cpp
|
||||||
|
src/util/amount_format.cpp
|
||||||
|
src/util/address_validation.cpp
|
||||||
|
src/util/i18n.cpp
|
||||||
|
src/util/text_format.cpp
|
||||||
|
src/data/wallet_state.cpp
|
||||||
|
src/data/transaction_history_cache.cpp
|
||||||
|
src/data/address_book.cpp
|
||||||
|
src/data/wallet_index.cpp
|
||||||
|
src/daemon/lifecycle_adapters.cpp
|
||||||
|
src/daemon/embedded_daemon.cpp
|
||||||
|
src/rpc/connection.cpp
|
||||||
|
src/config/settings.cpp
|
||||||
|
src/resources/embedded_resources.cpp
|
||||||
|
src/util/secure_vault.cpp
|
||||||
|
src/util/platform.cpp
|
||||||
|
src/util/logger.cpp
|
||||||
|
src/util/lite_server_probe.cpp
|
||||||
|
src/util/pool_registry_core.cpp
|
||||||
|
src/util/http_download.cpp
|
||||||
|
src/util/xmrig_updater.cpp
|
||||||
|
src/util/xmrig_updater_core.cpp
|
||||||
|
src/util/daemon_updater.cpp
|
||||||
|
src/util/daemon_updater_core.cpp
|
||||||
|
${MINIZ_SOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(ObsidianDragonTests PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${CMAKE_SOURCE_DIR}/src/resources
|
||||||
|
${CMAKE_SOURCE_DIR}/libs
|
||||||
|
${CMAKE_BINARY_DIR}/generated
|
||||||
|
${IMGUI_DIR}
|
||||||
|
${SODIUM_INCLUDE_DIR}
|
||||||
|
${CURL_INCLUDE_DIRS}
|
||||||
|
${MINIZ_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(ObsidianDragonTests PRIVATE
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
sqlite3_amalgamation
|
||||||
|
${SODIUM_LIBRARY}
|
||||||
|
${CURL_LIBRARIES}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(ObsidianDragonTests PRIVATE
|
||||||
|
DRAGONX_ENABLE_CHAT=$<BOOL:${DRAGONX_ENABLE_CHAT}>
|
||||||
|
DRAGONX_LITE_BUILD=$<BOOL:${DRAGONX_BUILD_LITE}>
|
||||||
|
DRAGONX_ENABLE_EMBEDDED_DAEMON=$<BOOL:${DRAGONX_ENABLE_EMBEDDED_DAEMON}>
|
||||||
|
DRAGONX_ENABLE_LITE_BACKEND=$<BOOL:${DRAGONX_LITE_BACKEND_READY}>
|
||||||
|
DRAGONX_TEST_FIXTURE_DIR="${CMAKE_SOURCE_DIR}/tests/fixtures"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(DRAGONX_LITE_BACKEND_READY)
|
||||||
|
target_link_libraries(ObsidianDragonTests PRIVATE dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(UNIX)
|
||||||
|
target_link_libraries(ObsidianDragonTests PRIVATE ${CMAKE_DL_LIBS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME ObsidianDragonPhase4Tests COMMAND ObsidianDragonTests)
|
||||||
|
endif()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Summary
|
# Summary
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
message(STATUS "")
|
message(STATUS "")
|
||||||
message(STATUS "DragonX ImGui Wallet Configuration:")
|
message(STATUS "DragonX ImGui Wallet Configuration:")
|
||||||
message(STATUS " Version: ${PROJECT_VERSION}")
|
message(STATUS " Version: ${DRAGONX_APP_VERSION}${DRAGONX_APP_VERSION_SUFFIX} (${DRAGONX_APP_NAME})")
|
||||||
message(STATUS " Build type: ${CMAKE_BUILD_TYPE}")
|
message(STATUS " Build type: ${CMAKE_BUILD_TYPE}")
|
||||||
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
|
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
|
||||||
message(STATUS " ImGui dir: ${IMGUI_DIR}")
|
message(STATUS " ImGui dir: ${IMGUI_DIR}")
|
||||||
message(STATUS " SDL3 found: ${SDL3_FOUND}")
|
message(STATUS " SDL3 found: ${SDL3_FOUND}")
|
||||||
message(STATUS " Sodium lib: ${SODIUM_LIBRARY}")
|
message(STATUS " Sodium lib: ${SODIUM_LIBRARY}")
|
||||||
|
message(STATUS " Lite build: ${DRAGONX_BUILD_LITE}")
|
||||||
|
message(STATUS " Lite requested: ${DRAGONX_ENABLE_LITE_BACKEND}")
|
||||||
|
message(STATUS " Lite backend: ${DRAGONX_LITE_BACKEND_READY}")
|
||||||
|
message(STATUS " Lite lib: ${DRAGONX_LITE_BACKEND_LIBRARY}")
|
||||||
|
message(STATUS " Lite symbols: ${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||||
|
message(STATUS " Lite manifest: ${DRAGONX_LITE_BACKEND_MANIFEST}")
|
||||||
|
message(STATUS " Lite trust: built-from-source (vendored third_party/silentdragonxlite)")
|
||||||
message(STATUS "")
|
message(STATUS "")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Thank you for your interest in contributing! This guide will help you get starte
|
|||||||
1. Fork the repository
|
1. Fork the repository
|
||||||
2. Clone your fork and create a branch:
|
2. Clone your fork and create a branch:
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.hush.is/<your-username>/ObsidianDragon.git
|
git clone https://git.dragonx.is/<your-username>/ObsidianDragon.git
|
||||||
cd ObsidianDragon
|
cd ObsidianDragon
|
||||||
git checkout -b my-feature
|
git checkout -b my-feature
|
||||||
```
|
```
|
||||||
@@ -67,7 +67,7 @@ Run the wallet and verify:
|
|||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
- Use the [issue tracker](https://git.hush.is/dragonx/ObsidianDragon/issues)
|
- Use the [issue tracker](https://git.dragonx.is/dragonx/ObsidianDragon/issues)
|
||||||
- Include: OS, wallet version, steps to reproduce, expected vs actual behaviour
|
- Include: OS, wallet version, steps to reproduce, expected vs actual behaviour
|
||||||
- For crashes: include any terminal output
|
- For crashes: include any terminal output
|
||||||
|
|
||||||
|
|||||||
BIN
ObsidianDragon.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
ObsidianDragon.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
ObsidianDragon.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ObsidianDragon.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
ObsidianDragon.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
ObsidianDragon.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
ObsidianDragon.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
ObsidianDragon.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
ObsidianDragon.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
ObsidianDragon.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
69
README.md
@@ -1,6 +1,8 @@
|
|||||||
# DragonX Wallet - ImGui Edition
|
# ObsidianDragon - DragonX Wallet
|
||||||
|
|
||||||
A lightweight, portable cryptocurrency wallet for DragonX (DRGX), built with Dear ImGui.
|
A lightweight, portable full-node cryptocurrency wallet for DragonX (DRGX), built with Dear ImGui.
|
||||||
|
|
||||||
|
Current pre-release: **1.2.0-rc1**.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -9,10 +11,13 @@ A lightweight, portable cryptocurrency wallet for DragonX (DRGX), built with Dea
|
|||||||
|
|
||||||
- **Full Node Support**: Connects to dragonxd for complete blockchain verification
|
- **Full Node Support**: Connects to dragonxd for complete blockchain verification
|
||||||
- **Shielded Transactions**: Full z-address support with encrypted memos
|
- **Shielded Transactions**: Full z-address support with encrypted memos
|
||||||
- **Integrated Mining**: CPU mining controls with hashrate monitoring
|
- **Address Management**: Labels, icons, favorites, hidden addresses, and address-to-address transfers
|
||||||
|
- **Integrated Mining**: Solo CPU mining plus pool mining through xmrig, with idle-mining controls
|
||||||
|
- **Explorer Tools**: Block/transaction lookup and bootstrap snapshot download
|
||||||
- **Market Data**: Real-time price charts from CoinGecko
|
- **Market Data**: Real-time price charts from CoinGecko
|
||||||
- **QR Codes**: Generate and display QR codes for receiving addresses
|
- **QR Codes**: Generate and display QR codes for receiving addresses
|
||||||
- **Multi-language**: i18n support (English, Spanish, more coming)
|
- **Multi-language**: i18n support for English, German, Spanish, French, Japanese, Korean, Portuguese, Russian, and Chinese
|
||||||
|
- **CJK Fonts**: Bundled CJK subset font for translated interfaces
|
||||||
- **Lightweight**: ~5-10MB binary vs ~50MB+ for Qt version
|
- **Lightweight**: ~5-10MB binary vs ~50MB+ for Qt version
|
||||||
- **Fast Builds**: Compiles in seconds, not minutes
|
- **Fast Builds**: Compiles in seconds, not minutes
|
||||||
|
|
||||||
@@ -33,10 +38,10 @@ A lightweight, portable cryptocurrency wallet for DragonX (DRGX), built with Dea
|
|||||||
The setup script detects your OS, installs all build dependencies, and validates your environment:
|
The setup script detects your OS, installs all build dependencies, and validates your environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/setup.sh # Install core build deps (interactive)
|
./setup.sh # Install core build deps (interactive)
|
||||||
./scripts/setup.sh --check # Just report what's missing
|
./setup.sh --check # Just report what's missing
|
||||||
./scripts/setup.sh --all # Core + Windows/macOS cross-compile + Sapling params
|
./setup.sh --all # Core + Windows/macOS cross-compile + Sapling params
|
||||||
./scripts/setup.sh --win # Also install mingw-w64 + libsodium-win
|
./setup.sh --win # Also install mingw-w64 + libsodium-win
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual Prerequisites
|
### Manual Prerequisites
|
||||||
@@ -71,20 +76,20 @@ brew install cmake
|
|||||||
### Binaries
|
### Binaries
|
||||||
Download linux and windows binaries of latest releases and place in binary directories:
|
Download linux and windows binaries of latest releases and place in binary directories:
|
||||||
|
|
||||||
**DragonX daemon** (https://git.hush.is/dragonx/hush3):
|
**DragonX daemon** (https://git.dragonx.is/DragonX/dragonx):
|
||||||
- prebuilt-binaries/dragonxd-linux/
|
- prebuilt-binaries/dragonxd-linux/
|
||||||
- prebuilt-binaries/dragonxd-win/
|
- prebuilt-binaries/dragonxd-win/
|
||||||
- prebuilt-binaries/dragonxd-mac/
|
- prebuilt-binaries/dragonxd-mac/
|
||||||
|
|
||||||
**xmrig HAC fork** (https://git.hush.is/dragonx/xmrig-hac):
|
**DRG-XMRig fork** (https://git.dragonx.is/DragonX/drg-xmrig):
|
||||||
- prebuilt-binaries/xmrig-hac/
|
- prebuilt-binaries/drg-xmrig/
|
||||||
|
|
||||||
|
|
||||||
## Build Steps
|
## Build Steps
|
||||||
|
|
||||||
```
|
```
|
||||||
### Clone repository (if not already)
|
### Clone repository (if not already)
|
||||||
git clone https://git.hush.is/dragonx/ObsidianDragon.git
|
git clone https://git.dragonx.is/dragonx/ObsidianDragon.git
|
||||||
cd ObsidianDragon/
|
cd ObsidianDragon/
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -116,7 +121,8 @@ cd ObsidianDragon/
|
|||||||
./ObsidianDragon
|
./ObsidianDragon
|
||||||
```
|
```
|
||||||
|
|
||||||
The wallet will automatically connect to the daemon using credentials from \`~/.hush/DRAGONX/DRAGONX.conf\`.
|
The wallet will automatically connect to the daemon using credentials from `~/.hush/DRAGONX/DRAGONX.conf`.
|
||||||
|
|
||||||
### Using Custom Node Binaries
|
### Using Custom Node Binaries
|
||||||
|
|
||||||
The wallet checks its **own directory first** when looking for DragonX node binaries. This means you can test new or different branch builds of `hush-arrakis-chain`/`hushd` without waiting for a new wallet release:
|
The wallet checks its **own directory first** when looking for DragonX node binaries. This means you can test new or different branch builds of `hush-arrakis-chain`/`hushd` without waiting for a new wallet release:
|
||||||
@@ -128,12 +134,13 @@ The wallet checks its **own directory first** when looking for DragonX node bina
|
|||||||
**Search order:**
|
**Search order:**
|
||||||
1. Wallet executable directory (highest priority)
|
1. Wallet executable directory (highest priority)
|
||||||
2. Embedded/extracted daemon (app data directory)
|
2. Embedded/extracted daemon (app data directory)
|
||||||
3. System-wide locations (`/usr/local/bin`, `~/hush3/src`, etc.)
|
3. System-wide locations (`/usr/local/bin`, `~/dragonx/src`, etc.)
|
||||||
|
|
||||||
This is useful for testing new branches or hotfixes to the node software before they are bundled into a wallet release.
|
This is useful for testing new branches or hotfixes to the node software before they are bundled into a wallet release.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Configuration is stored in \`~/.hush/DRAGONX/DRAGONX.conf\`:
|
Configuration is stored in `~/.hush/DRAGONX/DRAGONX.conf`:
|
||||||
|
|
||||||
```
|
```
|
||||||
rpcuser=your_rpc_user
|
rpcuser=your_rpc_user
|
||||||
@@ -148,44 +155,46 @@ ObsidianDragon/
|
|||||||
├── src/
|
├── src/
|
||||||
│ ├── main.cpp # Entry point, SDL/ImGui setup
|
│ ├── main.cpp # Entry point, SDL/ImGui setup
|
||||||
│ ├── app.cpp/h # Main application class
|
│ ├── app.cpp/h # Main application class
|
||||||
│ ├── wallet_state.h # Wallet data structures
|
│ ├── data/ # WalletState, address book, exchange info
|
||||||
│ ├── version.h # Version definitions
|
│ ├── config/ # Settings persistence and committed/generated version.h
|
||||||
│ ├── ui/
|
│ ├── ui/
|
||||||
│ │ ├── theme.cpp/h # DragonX theme
|
│ │ ├── schema/ # TOML UI schema and skin manager
|
||||||
│ │ └── windows/ # UI tabs and dialogs
|
│ │ ├── material/ # Material components, typography, layout
|
||||||
|
│ │ ├── windows/ # Tabs and dialogs
|
||||||
|
│ │ └── pages/ # Multi-page screens such as Settings
|
||||||
│ ├── rpc/
|
│ ├── rpc/
|
||||||
│ │ ├── rpc_client.cpp # JSON-RPC client
|
│ │ ├── rpc_client.cpp # JSON-RPC client
|
||||||
│ │ └── connection.cpp # Daemon connection
|
│ │ └── connection.cpp # Daemon connection
|
||||||
│ ├── config/
|
│ ├── resources/ # Embedded resource extraction
|
||||||
│ │ └── settings.cpp # Settings persistence
|
│ ├── platform/ # Windows DX11/backdrop helpers
|
||||||
│ ├── util/
|
│ ├── util/
|
||||||
│ │ ├── i18n.cpp # Internationalization
|
│ │ ├── i18n.cpp # Internationalization
|
||||||
│ │ └── ...
|
│ │ └── ...
|
||||||
│ └── daemon/
|
│ └── daemon/
|
||||||
│ └── embedded_daemon.cpp
|
│ └── embedded_daemon.cpp
|
||||||
├── res/
|
├── res/
|
||||||
│ ├── fonts/ # Ubuntu font
|
│ ├── fonts/ # Ubuntu, icon, and CJK fonts
|
||||||
│ └── lang/ # Translation files
|
│ └── lang/ # Translation files
|
||||||
├── libs/
|
├── libs/
|
||||||
│ └── qrcode/ # QR code generation
|
│ └── qrcode/ # QR code generation
|
||||||
├── CMakeLists.txt
|
├── CMakeLists.txt
|
||||||
├── build-release.sh # Build script
|
├── build.sh # Release/cross-platform build script
|
||||||
└── create-appimage.sh # AppImage packaging
|
└── scripts/create-appimage.sh # AppImage packaging
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
Fetched automatically by CMake (no manual install needed):
|
Fetched or discovered by CMake:
|
||||||
|
|
||||||
- **[SDL3](https://github.com/libsdl-org/SDL)** — Cross-platform windowing/input
|
- **[SDL3](https://github.com/libsdl-org/SDL)** — Cross-platform windowing/input
|
||||||
- **[nlohmann/json](https://github.com/nlohmann/json)** — JSON parsing
|
- **[nlohmann/json](https://github.com/nlohmann/json)** — JSON parsing
|
||||||
- **[toml++](https://github.com/marzer/tomlplusplus)** — TOML parsing (UI schema/themes)
|
- **[toml++](https://github.com/marzer/tomlplusplus)** — TOML parsing (UI schema/themes)
|
||||||
- **[libcurl](https://curl.se/libcurl/)** — HTTPS RPC transport (system on Linux, fetched on Windows)
|
- **[libcurl](https://curl.se/libcurl/)** — HTTP/HTTPS transport for daemon RPC and network calls (system on Linux/macOS, fetched on Windows)
|
||||||
|
|
||||||
Bundled in `libs/`:
|
Bundled in `libs/`:
|
||||||
|
|
||||||
- **[Dear ImGui](https://github.com/ocornut/imgui)** — Immediate mode GUI
|
- **[Dear ImGui](https://github.com/ocornut/imgui)** — Immediate mode GUI
|
||||||
- **[libsodium](https://libsodium.org)** — Cryptographic operations (fetched by `scripts/fetch-libsodium.sh`)
|
- **[libsodium](https://libsodium.org)** — Cryptographic operations (system on Linux or fetched by `scripts/fetch-libsodium.sh`)
|
||||||
- **[QR-Code-generator](https://github.com/nayuki/QR-Code-generator)** — QR code rendering
|
- **[QR-Code-generator](https://github.com/nayuki/QR-Code-generator)** — QR code rendering
|
||||||
- **[miniz](https://github.com/richgel999/miniz)** — ZIP compression
|
- **[miniz](https://github.com/richgel999/miniz)** — ZIP compression
|
||||||
- **[GLAD](https://glad.dav1d.de/)** — OpenGL loader (Linux/macOS)
|
- **[GLAD](https://glad.dav1d.de/)** — OpenGL loader (Linux/macOS)
|
||||||
@@ -202,9 +211,11 @@ Bundled in `libs/`:
|
|||||||
|
|
||||||
## Translation
|
## Translation
|
||||||
|
|
||||||
|
Current language files live in `res/lang/` as `de`, `es`, `fr`, `ja`, `ko`, `pt`, `ru`, and `zh` JSON files, with built-in English fallbacks.
|
||||||
|
|
||||||
To add a new language:
|
To add a new language:
|
||||||
|
|
||||||
1. Copy \`res/lang/es.json\` to \`res/lang/<code>.json\`
|
1. Copy `res/lang/es.json` to `res/lang/<code>.json`
|
||||||
2. Translate all strings
|
2. Translate all strings
|
||||||
3. The language will appear in Settings automatically
|
3. The language will appear in Settings automatically
|
||||||
|
|
||||||
@@ -223,4 +234,4 @@ This project is licensed under the GNU General Public License v3 (GPLv3).
|
|||||||
|
|
||||||
- Website: https://dragonx.is
|
- Website: https://dragonx.is
|
||||||
- Explorer: https://explorer.dragonx.is
|
- Explorer: https://explorer.dragonx.is
|
||||||
- Source: https://git.hush.is/dragonx/ObsidianDragon
|
- Source: https://git.dragonx.is/dragonx/ObsidianDragon
|
||||||
|
|||||||
@@ -361,7 +361,22 @@ https://www.apache.org/licenses/LICENSE-2.0
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 13. IconFontCppHeaders
|
## 13. Material Design Icons Pickaxe Subset Font
|
||||||
|
|
||||||
|
- **Location:** `res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf`
|
||||||
|
- **Source:** https://github.com/Templarian/MaterialDesign-Webfont
|
||||||
|
- **Derived from:** Pictogrammers Material Design Icons webfont (`materialdesignicons-webfont.ttf`)
|
||||||
|
- **Copyright:** Pictogrammers contributors
|
||||||
|
- **License:** Apache License 2.0
|
||||||
|
|
||||||
|
This bundled font is a local one-glyph subset containing only the MDI pickaxe
|
||||||
|
icon, remapped onto a BMP private-use codepoint for Dear ImGui compatibility.
|
||||||
|
The full text of the Apache License 2.0 is available at:
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. IconFontCppHeaders
|
||||||
|
|
||||||
- **Location:** `src/embedded/IconsMaterialDesign.h`
|
- **Location:** `src/embedded/IconsMaterialDesign.h`
|
||||||
- **Source:** https://github.com/juliettef/IconFontCppHeaders
|
- **Source:** https://github.com/juliettef/IconFontCppHeaders
|
||||||
@@ -390,7 +405,7 @@ freely, subject to the following restrictions:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 14. Ubuntu Font Family
|
## 15. Ubuntu Font Family
|
||||||
|
|
||||||
- **Location:** `res/fonts/Ubuntu-Light.ttf`, `Ubuntu-Medium.ttf`, `Ubuntu-R.ttf`
|
- **Location:** `res/fonts/Ubuntu-Light.ttf`, `Ubuntu-Medium.ttf`, `Ubuntu-R.ttf`
|
||||||
- **Source:** https://design.ubuntu.com/font
|
- **Source:** https://design.ubuntu.com/font
|
||||||
|
|||||||
672
build.sh
@@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./build.sh # Dev build (Linux, debug-friendly)
|
# ./build.sh # Dev build (Linux, debug-friendly)
|
||||||
# ./build.sh --linux-release # Linux release + AppImage
|
# ./build.sh --linux-release # Linux release (zip + AppImage)
|
||||||
# ./build.sh --win-release # Windows cross-compile (mingw-w64)
|
# ./build.sh --win-release # Windows cross-compile (mingw-w64)
|
||||||
# ./build.sh --mac-release # macOS .app bundle + DMG
|
# ./build.sh --mac-release # macOS .app bundle + DMG
|
||||||
# ./build.sh --linux-release --win-release # Multiple targets
|
# ./build.sh --linux-release --win-release # Multiple targets
|
||||||
@@ -20,7 +20,9 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
VERSION="1.0.0"
|
# VERSION is resolved per-variant from CMakeLists.txt (the single source of truth) after arg
|
||||||
|
# parsing — see the APP_BASENAME block below. Placeholder until then.
|
||||||
|
VERSION=""
|
||||||
|
|
||||||
# ── Colours ──────────────────────────────────────────────────────────────────
|
# ── Colours ──────────────────────────────────────────────────────────────────
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -41,6 +43,8 @@ DO_DEV=false
|
|||||||
DO_LINUX=false
|
DO_LINUX=false
|
||||||
DO_WIN=false
|
DO_WIN=false
|
||||||
DO_MAC=false
|
DO_MAC=false
|
||||||
|
DO_LITE=false
|
||||||
|
DO_LITE_BACKEND=false
|
||||||
CLEAN=false
|
CLEAN=false
|
||||||
BUILD_TYPE="Release"
|
BUILD_TYPE="Release"
|
||||||
|
|
||||||
@@ -51,9 +55,13 @@ DragonX Wallet — Unified Build Script
|
|||||||
Usage: $0 [options]
|
Usage: $0 [options]
|
||||||
|
|
||||||
Targets (at least one required, or none for dev build):
|
Targets (at least one required, or none for dev build):
|
||||||
--linux-release Linux release build + AppImage -> release/linux/
|
--linux-release Linux release (zip + AppImage) -> release/linux/
|
||||||
--win-release Windows cross-compile (mingw-w64) -> release/windows/
|
--win-release Windows cross-compile (mingw-w64) -> release/windows/
|
||||||
--mac-release macOS .app bundle + DMG -> release/mac/
|
--mac-release macOS .app bundle + DMG -> release/mac/
|
||||||
|
--lite Build ObsidianDragonLite variant (no embedded daemon/full-node features)
|
||||||
|
--lite-backend Like --lite, and link the real SDXL litelib backend artifact
|
||||||
|
(auto-discovers build/lite-backend/<platform>/; build it with
|
||||||
|
scripts/build-lite-backend-artifact.sh, or set DRAGONX_LITE_BACKEND_DIR)
|
||||||
|
|
||||||
Build trees are stored under build/{linux,windows,mac}/
|
Build trees are stored under build/{linux,windows,mac}/
|
||||||
|
|
||||||
@@ -71,9 +79,10 @@ Cross-compiling from Linux:
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$0 # Quick dev build (Linux)
|
$0 # Quick dev build (Linux)
|
||||||
$0 --linux-release # Linux release + AppImage
|
$0 --linux-release # Linux release (zip + AppImage)
|
||||||
$0 --win-release # Windows cross-compile
|
$0 --win-release # Windows cross-compile
|
||||||
$0 --mac-release # macOS bundle + DMG (native or osxcross)
|
$0 --mac-release # macOS bundle + DMG (native or osxcross)
|
||||||
|
$0 --lite-backend --mac-release # macOS ObsidianDragonLite.app + DMG (lite backend)
|
||||||
$0 --clean --linux-release --win-release # Clean + both
|
$0 --clean --linux-release --win-release # Clean + both
|
||||||
EOF
|
EOF
|
||||||
exit 0
|
exit 0
|
||||||
@@ -85,6 +94,8 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--linux-release) DO_LINUX=true; shift ;;
|
--linux-release) DO_LINUX=true; shift ;;
|
||||||
--win-release) DO_WIN=true; shift ;;
|
--win-release) DO_WIN=true; shift ;;
|
||||||
--mac-release) DO_MAC=true; shift ;;
|
--mac-release) DO_MAC=true; shift ;;
|
||||||
|
--lite) DO_LITE=true; shift ;;
|
||||||
|
--lite-backend) DO_LITE=true; DO_LITE_BACKEND=true; shift ;;
|
||||||
-c|--clean) CLEAN=true; shift ;;
|
-c|--clean) CLEAN=true; shift ;;
|
||||||
-d|--debug) BUILD_TYPE="Debug"; shift ;;
|
-d|--debug) BUILD_TYPE="Debug"; shift ;;
|
||||||
-j) JOBS="$2"; shift 2 ;;
|
-j) JOBS="$2"; shift 2 ;;
|
||||||
@@ -98,6 +109,92 @@ if ! $DO_LINUX && ! $DO_WIN && ! $DO_MAC; then
|
|||||||
DO_DEV=true
|
DO_DEV=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
APP_BASENAME="ObsidianDragon"
|
||||||
|
CMAKE_LITE_ARGS=()
|
||||||
|
# Always set the variant flag EXPLICITLY (ON and OFF) so switching variants in a shared build dir
|
||||||
|
# can't reuse a stale cached value (e.g. a prior --lite build leaving DRAGONX_BUILD_LITE=ON).
|
||||||
|
if $DO_LITE; then
|
||||||
|
APP_BASENAME="ObsidianDragonLite"
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_BUILD_LITE=ON")
|
||||||
|
# Lite never embeds/launches a daemon; set it explicitly too for cache hygiene.
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_ENABLE_EMBEDDED_DAEMON=OFF")
|
||||||
|
info "Lite mode enabled: building ${APP_BASENAME}"
|
||||||
|
else
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_BUILD_LITE=OFF")
|
||||||
|
# Re-assert the embedded daemon ON for full-node builds, EXPLICITLY, so a build dir whose cache
|
||||||
|
# was poisoned OFF by a prior --lite configure (or any stale value) is healed — otherwise the
|
||||||
|
# full-node app extracts dragonxd but never launches it (isUsingEmbeddedDaemon() == false).
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_ENABLE_EMBEDDED_DAEMON=ON")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve the release version string for the active variant from CMakeLists.txt (single source of
|
||||||
|
# truth): the full-node app uses project() VERSION + DRAGONX_VERSION_SUFFIX; ObsidianDragonLite uses
|
||||||
|
# DRAGONX_LITE_VERSION + DRAGONX_LITE_VERSION_SUFFIX.
|
||||||
|
_cml="$SCRIPT_DIR/CMakeLists.txt"
|
||||||
|
_full_ver=$(sed -n 's/^[[:space:]]*VERSION[[:space:]][[:space:]]*\([0-9][0-9.]*\).*/\1/p' "$_cml" | head -1)
|
||||||
|
_full_suffix=$(sed -n 's/^set(DRAGONX_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
|
||||||
|
_lite_ver=$(sed -n 's/^set(DRAGONX_LITE_VERSION[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
|
||||||
|
_lite_suffix=$(sed -n 's/^set(DRAGONX_LITE_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
|
||||||
|
if $DO_LITE; then
|
||||||
|
VERSION="${_lite_ver}${_lite_suffix}"
|
||||||
|
else
|
||||||
|
VERSION="${_full_ver}${_full_suffix}"
|
||||||
|
fi
|
||||||
|
[ -n "$_full_ver" ] && [ -n "$VERSION" ] || { err "Could not parse version from CMakeLists.txt"; exit 1; }
|
||||||
|
info "Release version: ${VERSION} (${APP_BASENAME})"
|
||||||
|
|
||||||
|
# ── Lite backend (real SDXL litelib) linking ─────────────────────────────────
|
||||||
|
# Enables DRAGONX_ENABLE_LITE_BACKEND with an imported artifact produced by
|
||||||
|
# scripts/build-lite-backend-artifact.sh. Auto-discovers build/lite-backend/<platform>/;
|
||||||
|
# override the directory with DRAGONX_LITE_BACKEND_DIR.
|
||||||
|
if $DO_LITE_BACKEND; then
|
||||||
|
# Artifact platform follows the cross target when exactly one non-host release is requested,
|
||||||
|
# so `--lite-backend --win-release` links the Windows backend (not the host's) automatically.
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux) lb_platform="linux" ;;
|
||||||
|
Darwin) lb_platform="macos" ;;
|
||||||
|
*) lb_platform="linux" ;;
|
||||||
|
esac
|
||||||
|
if $DO_WIN && ! $DO_LINUX && ! $DO_MAC; then lb_platform="windows"; fi
|
||||||
|
if $DO_MAC && ! $DO_LINUX && ! $DO_WIN; then lb_platform="macos"; fi
|
||||||
|
lb_dir="${DRAGONX_LITE_BACKEND_DIR:-$SCRIPT_DIR/build/lite-backend/$lb_platform}"
|
||||||
|
lb_lib=""
|
||||||
|
for cand in "$lb_dir"/libsilentdragonxlite.a "$lb_dir"/libsilentdragonxlite.so "$lb_dir"/silentdragonxlite.lib; do
|
||||||
|
[[ -f "$cand" ]] && { lb_lib="$cand"; break; }
|
||||||
|
done
|
||||||
|
lb_symbols="$lb_dir/lite-backend-symbols.txt"
|
||||||
|
lb_manifest="$lb_dir/lite-backend-artifact-manifest.json"
|
||||||
|
if [[ -z "$lb_lib" || ! -f "$lb_symbols" ]]; then
|
||||||
|
err "Lite backend artifact not found under: $lb_dir"
|
||||||
|
err "Build it first: ./scripts/build-lite-backend-artifact.sh --platform $lb_platform"
|
||||||
|
err "Or set DRAGONX_LITE_BACKEND_DIR to an existing artifact directory."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
CMAKE_LITE_ARGS+=(
|
||||||
|
"-DDRAGONX_ENABLE_LITE_BACKEND=ON"
|
||||||
|
"-DDRAGONX_LITE_BACKEND_LIBRARY=$lb_lib"
|
||||||
|
"-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE=$lb_symbols"
|
||||||
|
"-DDRAGONX_LITE_BACKEND_LINK_MODE=imported"
|
||||||
|
"-DDRAGONX_LITE_BACKEND_ABI=sdxl-c-v1"
|
||||||
|
)
|
||||||
|
[[ -f "$lb_manifest" ]] && CMAKE_LITE_ARGS+=("-DDRAGONX_LITE_BACKEND_MANIFEST=$lb_manifest")
|
||||||
|
# A Rust x86_64-pc-windows-gnu staticlib pulls in Win32 system libs (rustls/schannel, ring,
|
||||||
|
# dirs, std) that the app doesn't already link. The set is rustc's `--print native-static-libs`
|
||||||
|
# for the backend (winapi_* shims mapped to the real mingw import libs); all exist in mingw-w64.
|
||||||
|
if [[ "$lb_platform" == "windows" ]]; then
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_LITE_BACKEND_EXTRA_LIBS=advapi32;ws2_32;kernel32;bcrypt;cfgmgr32;credui;crypt32;cryptnet;fwpuclnt;gdi32;msimg32;ncrypt;ntdll;ole32;opengl32;secur32;shell32;synchronization;user32;winspool;userenv")
|
||||||
|
fi
|
||||||
|
info "Lite backend enabled ($lb_platform): $lb_lib"
|
||||||
|
else
|
||||||
|
# Explicit OFF so a prior --lite-backend configure in a shared build dir can't leave it ON
|
||||||
|
# (which would then fail the BUILD_LITE=OFF guard in CMake).
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_ENABLE_LITE_BACKEND=OFF")
|
||||||
|
fi
|
||||||
|
|
||||||
|
should_bundle_full_node_assets() {
|
||||||
|
! $DO_LITE
|
||||||
|
}
|
||||||
|
|
||||||
# ── Helper: find resource files ──────────────────────────────────────────────
|
# ── Helper: find resource files ──────────────────────────────────────────────
|
||||||
find_sapling_params() {
|
find_sapling_params() {
|
||||||
local dirs=(
|
local dirs=(
|
||||||
@@ -122,10 +219,10 @@ find_sapling_params() {
|
|||||||
|
|
||||||
find_asmap() {
|
find_asmap() {
|
||||||
local paths=(
|
local paths=(
|
||||||
"$SCRIPT_DIR/external/hush3/asmap.dat"
|
"$SCRIPT_DIR/external/dragonx/asmap.dat"
|
||||||
"$SCRIPT_DIR/external/hush3/contrib/asmap/asmap.dat"
|
"$SCRIPT_DIR/external/dragonx/contrib/asmap/asmap.dat"
|
||||||
"$HOME/hush3/asmap.dat"
|
"$HOME/dragonx/asmap.dat"
|
||||||
"$HOME/hush3/contrib/asmap/asmap.dat"
|
"$HOME/dragonx/contrib/asmap/asmap.dat"
|
||||||
"$SCRIPT_DIR/../asmap.dat"
|
"$SCRIPT_DIR/../asmap.dat"
|
||||||
"$SCRIPT_DIR/asmap.dat"
|
"$SCRIPT_DIR/asmap.dat"
|
||||||
"$SCRIPT_DIR/../SilentDragonX/asmap.dat"
|
"$SCRIPT_DIR/../SilentDragonX/asmap.dat"
|
||||||
@@ -147,37 +244,37 @@ bundle_linux_daemon() {
|
|||||||
local dest="$1"
|
local dest="$1"
|
||||||
local found=0
|
local found=0
|
||||||
|
|
||||||
local launcher_paths=(
|
local daemon_paths=(
|
||||||
"$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/hush-arrakis-chain"
|
"$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/dragonxd"
|
||||||
"$SCRIPT_DIR/../hush-arrakis-chain"
|
"$SCRIPT_DIR/../dragonxd"
|
||||||
"$SCRIPT_DIR/external/hush3/src/hush-arrakis-chain"
|
"$SCRIPT_DIR/external/dragonx/src/dragonxd"
|
||||||
"$HOME/hush3/src/hush-arrakis-chain"
|
"$HOME/dragonx/src/dragonxd"
|
||||||
)
|
)
|
||||||
for p in "${launcher_paths[@]}"; do
|
for p in "${daemon_paths[@]}"; do
|
||||||
if [[ -f "$p" ]]; then
|
if [[ -f "$p" ]]; then
|
||||||
cp "$p" "$dest/hush-arrakis-chain"; chmod +x "$dest/hush-arrakis-chain"
|
cp "$p" "$dest/dragonxd"; chmod +x "$dest/dragonxd"
|
||||||
info " Bundled hush-arrakis-chain"; found=1; break
|
info " Bundled dragonxd"; found=1; break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
local hushd_paths=(
|
local cli_paths=(
|
||||||
"$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/hushd"
|
"$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/dragonx-cli"
|
||||||
"$SCRIPT_DIR/../hushd"
|
"$SCRIPT_DIR/../dragonx-cli"
|
||||||
"$SCRIPT_DIR/external/hush3/src/hushd"
|
"$SCRIPT_DIR/external/dragonx/src/dragonx-cli"
|
||||||
"$HOME/hush3/src/hushd"
|
"$HOME/dragonx/src/dragonx-cli"
|
||||||
)
|
)
|
||||||
for p in "${hushd_paths[@]}"; do
|
for p in "${cli_paths[@]}"; do
|
||||||
if [[ -f "$p" ]]; then
|
if [[ -f "$p" ]]; then
|
||||||
cp "$p" "$dest/hushd"; chmod +x "$dest/hushd"
|
cp "$p" "$dest/dragonx-cli"; chmod +x "$dest/dragonx-cli"
|
||||||
info " Bundled hushd"; break
|
info " Bundled dragonx-cli"; break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
local dragonxd_paths=(
|
local dragonxd_paths=(
|
||||||
"$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/dragonxd"
|
"$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/dragonxd"
|
||||||
"$SCRIPT_DIR/../dragonxd"
|
"$SCRIPT_DIR/../dragonxd"
|
||||||
"$SCRIPT_DIR/external/hush3/src/dragonxd"
|
"$SCRIPT_DIR/external/dragonx/src/dragonxd"
|
||||||
"$HOME/hush3/src/dragonxd"
|
"$HOME/dragonx/src/dragonxd"
|
||||||
)
|
)
|
||||||
for p in "${dragonxd_paths[@]}"; do
|
for p in "${dragonxd_paths[@]}"; do
|
||||||
if [[ -f "$p" ]]; then
|
if [[ -f "$p" ]]; then
|
||||||
@@ -197,7 +294,14 @@ bundle_linux_daemon() {
|
|||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
build_dev() {
|
build_dev() {
|
||||||
header "Dev Build ($(uname -s) / $BUILD_TYPE)"
|
header "Dev Build ($(uname -s) / $BUILD_TYPE)"
|
||||||
local bd="$SCRIPT_DIR/build/linux"
|
|
||||||
|
# Use platform-appropriate build directory
|
||||||
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||||
|
local bd="$SCRIPT_DIR/build/mac"
|
||||||
|
export MACOSX_DEPLOYMENT_TARGET="11.0"
|
||||||
|
else
|
||||||
|
local bd="$SCRIPT_DIR/build/linux"
|
||||||
|
fi
|
||||||
|
|
||||||
if $CLEAN; then
|
if $CLEAN; then
|
||||||
info "Cleaning $bd ..."; rm -rf "$bd"
|
info "Cleaning $bd ..."; rm -rf "$bd"
|
||||||
@@ -208,17 +312,18 @@ build_dev() {
|
|||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=ON
|
-DDRAGONX_USE_SYSTEM_SDL3=ON \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon" ]] || { err "Build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}" ]] || { err "Build failed"; exit 1; }
|
||||||
info "Dev binary: $bd/bin/ObsidianDragon ($(du -h bin/ObsidianDragon | cut -f1))"
|
info "Dev binary: $bd/bin/${APP_BASENAME} ($(du -h "bin/${APP_BASENAME}" | cut -f1))"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
# RELEASE: LINUX — build + strip + bundle daemon + AppImage
|
# RELEASE: LINUX — build + strip + bundle daemon + zip + AppImage
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
build_release_linux() {
|
build_release_linux() {
|
||||||
header "Release: Linux x86_64"
|
header "Release: Linux x86_64"
|
||||||
@@ -235,32 +340,64 @@ build_release_linux() {
|
|||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=ON
|
-DDRAGONX_USE_SYSTEM_SDL3=ON \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon" ]] || { err "Linux build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}" ]] || { err "Linux build failed"; exit 1; }
|
||||||
|
|
||||||
info "Stripping ..."
|
info "Stripping ..."
|
||||||
strip bin/ObsidianDragon
|
strip "bin/${APP_BASENAME}"
|
||||||
info "Binary: $(du -h bin/ObsidianDragon | cut -f1)"
|
info "Binary: $(du -h "bin/${APP_BASENAME}" | cut -f1)"
|
||||||
|
|
||||||
# ── Bundle daemon ────────────────────────────────────────────────────────
|
if should_bundle_full_node_assets; then
|
||||||
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
# ── Bundle daemon ────────────────────────────────────────────────────
|
||||||
|
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
||||||
|
|
||||||
|
# ── Bundle Sapling params ────────────────────────────────────────────
|
||||||
|
SAPLING_SPEND="" SAPLING_OUTPUT=""
|
||||||
|
find_sapling_params && {
|
||||||
|
cp -f "$SAPLING_SPEND" "bin/sapling-spend.params"
|
||||||
|
cp -f "$SAPLING_OUTPUT" "bin/sapling-output.params"
|
||||||
|
info "Bundled Sapling params"
|
||||||
|
} || warn "Sapling params not found — not bundled"
|
||||||
|
else
|
||||||
|
info "Lite mode: skipping daemon and Sapling/asmap bundling"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Package: release/linux/ ──────────────────────────────────────────────
|
# ── Package: release/linux/ ──────────────────────────────────────────────
|
||||||
rm -rf "$out"
|
# Remove only THIS variant's prior artifacts so full-node and lite releases can coexist in the
|
||||||
|
# same output dir (both ObsidianDragon* and ObsidianDragonLite* end up under release/linux/).
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
rm -rf "$out/${APP_BASENAME}-"* "$out/${APP_BASENAME}.AppImage"
|
||||||
|
|
||||||
cp bin/ObsidianDragon "$out/"
|
local DIST="${APP_BASENAME}-${VERSION}-Linux-x64"
|
||||||
[[ -f bin/hush-arrakis-chain ]] && cp bin/hush-arrakis-chain "$out/"
|
local dist_dir="$out/$DIST"
|
||||||
[[ -f bin/hushd ]] && cp bin/hushd "$out/"
|
mkdir -p "$dist_dir"
|
||||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$out/"
|
|
||||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$out/"
|
|
||||||
cp -r bin/res "$out/" 2>/dev/null || true
|
|
||||||
|
|
||||||
# ── AppImage ─────────────────────────────────────────────────────────────
|
cp "bin/${APP_BASENAME}" "$dist_dir/"
|
||||||
|
if should_bundle_full_node_assets; then
|
||||||
|
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$dist_dir/"
|
||||||
|
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$dist_dir/"
|
||||||
|
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$dist_dir/"
|
||||||
|
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$dist_dir/"
|
||||||
|
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$dist_dir/"
|
||||||
|
fi
|
||||||
|
# Bundle xmrig for mining support
|
||||||
|
local XMRIG_LINUX="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig/xmrig"
|
||||||
|
[[ -f "$XMRIG_LINUX" ]] && { cp "$XMRIG_LINUX" "$dist_dir/"; chmod +x "$dist_dir/xmrig"; info "Bundled xmrig"; } || warn "xmrig not found — mining unavailable in zip"
|
||||||
|
cp -r bin/res "$dist_dir/" 2>/dev/null || true
|
||||||
|
|
||||||
|
# ── Zip ──────────────────────────────────────────────────────────────────
|
||||||
|
if command -v zip &>/dev/null; then
|
||||||
|
(cd "$out" && zip -r "$DIST.zip" "$DIST")
|
||||||
|
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
|
||||||
|
fi
|
||||||
|
rm -rf "$dist_dir"
|
||||||
|
|
||||||
|
# ── AppImage (single-file) ───────────────────────────────────────────────
|
||||||
info "Creating AppImage ..."
|
info "Creating AppImage ..."
|
||||||
local APPDIR="$bd/AppDir"
|
local APPDIR="$bd/AppDir"
|
||||||
rm -rf "$APPDIR"
|
rm -rf "$APPDIR"
|
||||||
@@ -269,22 +406,29 @@ build_release_linux() {
|
|||||||
"$APPDIR/usr/share/icons/hicolor/256x256/apps" \
|
"$APPDIR/usr/share/icons/hicolor/256x256/apps" \
|
||||||
"$APPDIR/usr/share/ObsidianDragon/res"
|
"$APPDIR/usr/share/ObsidianDragon/res"
|
||||||
|
|
||||||
cp bin/ObsidianDragon "$APPDIR/usr/bin/"
|
cp "bin/${APP_BASENAME}" "$APPDIR/usr/bin/"
|
||||||
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
|
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
|
||||||
|
|
||||||
# Daemon inside AppImage
|
if should_bundle_full_node_assets; then
|
||||||
[[ -f bin/hush-arrakis-chain ]] && cp bin/hush-arrakis-chain "$APPDIR/usr/bin/"
|
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
||||||
[[ -f bin/hushd ]] && cp bin/hushd "$APPDIR/usr/bin/"
|
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
|
||||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
# Daemon data files must be alongside the daemon binary (usr/bin/)
|
||||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/share/ObsidianDragon/"
|
# because dragonxd searches relative to its own directory.
|
||||||
|
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/bin/"
|
||||||
|
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$APPDIR/usr/bin/"
|
||||||
|
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$APPDIR/usr/bin/"
|
||||||
|
fi
|
||||||
|
# Bundle xmrig for mining support
|
||||||
|
local XMRIG_LINUX_AI="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig/xmrig"
|
||||||
|
[[ -f "$XMRIG_LINUX_AI" ]] && { cp "$XMRIG_LINUX_AI" "$APPDIR/usr/bin/"; chmod +x "$APPDIR/usr/bin/xmrig"; }
|
||||||
|
|
||||||
# Desktop entry
|
# Desktop entry
|
||||||
cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<'DESK'
|
cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<DESK
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=DragonX Wallet
|
Name=DragonX Wallet
|
||||||
Comment=DragonX Cryptocurrency Wallet
|
Comment=DragonX Cryptocurrency Wallet
|
||||||
Exec=ObsidianDragon
|
Exec=${APP_BASENAME}
|
||||||
Icon=ObsidianDragon
|
Icon=ObsidianDragon
|
||||||
Categories=Finance;Network;
|
Categories=Finance;Network;
|
||||||
Terminal=false
|
Terminal=false
|
||||||
@@ -315,14 +459,14 @@ SVG
|
|||||||
cp "$APPDIR/ObsidianDragon.svg" "$APPDIR/ObsidianDragon.png" 2>/dev/null || true
|
cp "$APPDIR/ObsidianDragon.svg" "$APPDIR/ObsidianDragon.png" 2>/dev/null || true
|
||||||
|
|
||||||
# AppRun
|
# AppRun
|
||||||
cat > "$APPDIR/AppRun" <<'APPRUN'
|
cat > "$APPDIR/AppRun" <<APPRUN
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
SELF=$(readlink -f "$0")
|
SELF=\$(readlink -f "\$0")
|
||||||
HERE=${SELF%/*}
|
HERE=\${SELF%/*}
|
||||||
export DRAGONX_RES_PATH="${HERE}/usr/share/ObsidianDragon/res"
|
export DRAGONX_RES_PATH="\${HERE}/usr/share/ObsidianDragon/res"
|
||||||
export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}"
|
export LD_LIBRARY_PATH="\${HERE}/usr/lib:\${LD_LIBRARY_PATH}"
|
||||||
cd "${HERE}/usr/share/ObsidianDragon"
|
cd "\${HERE}/usr/share/ObsidianDragon"
|
||||||
exec "${HERE}/usr/bin/ObsidianDragon" "$@"
|
exec "\${HERE}/usr/bin/${APP_BASENAME}" "\$@"
|
||||||
APPRUN
|
APPRUN
|
||||||
chmod +x "$APPDIR/AppRun"
|
chmod +x "$APPDIR/AppRun"
|
||||||
|
|
||||||
@@ -334,35 +478,37 @@ APPRUN
|
|||||||
done
|
done
|
||||||
[[ -f "$bd/_deps/sdl3-build/libSDL3.so" ]] && cp "$bd/_deps/sdl3-build/libSDL3.so"* "$APPDIR/usr/lib/" 2>/dev/null || true
|
[[ -f "$bd/_deps/sdl3-build/libSDL3.so" ]] && cp "$bd/_deps/sdl3-build/libSDL3.so"* "$APPDIR/usr/lib/" 2>/dev/null || true
|
||||||
|
|
||||||
# appimagetool
|
# appimagetool — pinned to a tagged release and SHA-256 verified before we exec it.
|
||||||
|
# The old "continuous" tag is a MOVING build fetched over the network and run on the release
|
||||||
|
# builder; a compromised/MITM'd artifact would execute here. Verify, or refuse to package.
|
||||||
|
local APPIMAGETOOL_URL="https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage"
|
||||||
|
local APPIMAGETOOL_SHA256="46fdd785094c7f6e545b61afcfb0f3d98d8eab243f644b4b17698c01d06083d1"
|
||||||
local APPIMAGETOOL=""
|
local APPIMAGETOOL=""
|
||||||
if command -v appimagetool &>/dev/null; then
|
if command -v appimagetool &>/dev/null; then
|
||||||
APPIMAGETOOL="appimagetool"
|
APPIMAGETOOL="appimagetool" # maintainer's own trusted system install
|
||||||
elif [[ -f "$bd/appimagetool-x86_64.AppImage" ]]; then
|
|
||||||
APPIMAGETOOL="$bd/appimagetool-x86_64.AppImage"
|
|
||||||
else
|
else
|
||||||
info "Downloading appimagetool ..."
|
local at="$bd/appimagetool-x86_64.AppImage"
|
||||||
wget -q -O "$bd/appimagetool-x86_64.AppImage" \
|
# Re-verify any cached copy too; a stale unverified download must not be trusted.
|
||||||
"https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
if [[ ! -f "$at" ]] || ! echo "${APPIMAGETOOL_SHA256} ${at}" | sha256sum -c --status; then
|
||||||
chmod +x "$bd/appimagetool-x86_64.AppImage"
|
info "Downloading appimagetool 1.9.0 (pinned) ..."
|
||||||
APPIMAGETOOL="$bd/appimagetool-x86_64.AppImage"
|
wget -q -O "$at" "$APPIMAGETOOL_URL"
|
||||||
|
if ! echo "${APPIMAGETOOL_SHA256} ${at}" | sha256sum -c --status; then
|
||||||
|
err "appimagetool SHA-256 verification failed — refusing to use it"
|
||||||
|
rm -f "$at"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
chmod +x "$at"
|
||||||
|
fi
|
||||||
|
APPIMAGETOOL="$at"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local ARCH
|
local ARCH
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
local IMG_NAME="DragonX_Wallet-${VERSION}-${ARCH}.AppImage"
|
|
||||||
cd "$bd"
|
cd "$bd"
|
||||||
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "$IMG_NAME" 2>/dev/null && {
|
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "${APP_BASENAME}-${VERSION}-${ARCH}.AppImage" 2>/dev/null && {
|
||||||
cp "$IMG_NAME" "$out/"
|
cp "${APP_BASENAME}-${VERSION}-${ARCH}.AppImage" "$out/${APP_BASENAME}-${VERSION}.AppImage"
|
||||||
info "AppImage: $out/$IMG_NAME ($(du -h "$IMG_NAME" | cut -f1))"
|
info "AppImage: $out/${APP_BASENAME}-${VERSION}.AppImage ($(du -h "$out/${APP_BASENAME}-${VERSION}.AppImage" | cut -f1))"
|
||||||
} || warn "AppImage creation failed (appimagetool issue) — raw binary still in release/linux/"
|
} || warn "AppImage creation failed — binaries zip still in release/linux/"
|
||||||
|
|
||||||
# Clean up: keep only AppImage + raw binary in release/linux/
|
|
||||||
if ls "$out"/*.AppImage 1>/dev/null 2>&1; then
|
|
||||||
# AppImage succeeded — remove everything except AppImage and the binary
|
|
||||||
find "$out" -maxdepth 1 -type f ! -name '*.AppImage' ! -name 'ObsidianDragon' -delete
|
|
||||||
rm -rf "$out/res" 2>/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Linux release artifacts: $out/"
|
info "Linux release artifacts: $out/"
|
||||||
ls -lh "$out/"
|
ls -lh "$out/"
|
||||||
@@ -401,6 +547,22 @@ build_release_win() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Patch libwinpthread + libpthread to remove VERSIONINFO resources ────
|
||||||
|
# mingw-w64's libwinpthread.a and libpthread.a each ship a version.o
|
||||||
|
# with their own VERSIONINFO ("POSIX WinThreads for Windows") that
|
||||||
|
# collides with ours during .rsrc merge, causing Task Manager to show
|
||||||
|
# the wrong process description.
|
||||||
|
local PATCHED_LIB_DIR="$bd/patched-lib"
|
||||||
|
mkdir -p "$PATCHED_LIB_DIR"
|
||||||
|
for plib in libwinpthread.a libpthread.a; do
|
||||||
|
local SYS_LIB="/usr/x86_64-w64-mingw32/lib/$plib"
|
||||||
|
if [[ -f "$SYS_LIB" ]]; then
|
||||||
|
cp -f "$SYS_LIB" "$PATCHED_LIB_DIR/$plib"
|
||||||
|
x86_64-w64-mingw32-ar d "$PATCHED_LIB_DIR/$plib" version.o 2>/dev/null || true
|
||||||
|
info "Patched $plib (removed version.o VERSIONINFO resource)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# ── Toolchain file ───────────────────────────────────────────────────────
|
# ── Toolchain file ───────────────────────────────────────────────────────
|
||||||
cat > "$bd/mingw-toolchain.cmake" <<TOOLCHAIN
|
cat > "$bd/mingw-toolchain.cmake" <<TOOLCHAIN
|
||||||
set(CMAKE_SYSTEM_NAME Windows)
|
set(CMAKE_SYSTEM_NAME Windows)
|
||||||
@@ -412,7 +574,7 @@ set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
|
|||||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive")
|
set(CMAKE_EXE_LINKER_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic,--whole-archive -L$PATCHED_LIB_DIR -lwinpthread -Wl,--no-whole-archive")
|
||||||
set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} -static")
|
set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} -static")
|
||||||
set(CMAKE_C_FLAGS "\${CMAKE_C_FLAGS} -static")
|
set(CMAKE_C_FLAGS "\${CMAKE_C_FLAGS} -static")
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
||||||
@@ -454,26 +616,48 @@ HDR
|
|||||||
|
|
||||||
# ── Daemon binaries ──────────────────────────────────────────────
|
# ── Daemon binaries ──────────────────────────────────────────────
|
||||||
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
||||||
if [[ -d "$DD" && -f "$DD/hushd.exe" ]]; then
|
if should_bundle_full_node_assets; then
|
||||||
info "Embedding daemon binaries ..."
|
if [[ -d "$DD" && -f "$DD/dragonxd.exe" ]]; then
|
||||||
echo -e "\n#define HAS_EMBEDDED_DAEMON 1\n" >> "$GEN/embedded_data.h"
|
info "Embedding daemon binaries ..."
|
||||||
for f in hushd.exe hush-cli.exe hush-tx.exe dragonxd.bat dragonx-cli.bat; do
|
echo -e "\n#define HAS_EMBEDDED_DAEMON 1\n" >> "$GEN/embedded_data.h"
|
||||||
local sym=$(echo "$f" | sed 's/[^a-zA-Z0-9]/_/g')
|
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||||
if [[ -f "$DD/$f" ]]; then
|
local sym=$(echo "$f" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||||
cp -f "$DD/$f" "$RES/$f"
|
if [[ -f "$DD/$f" ]]; then
|
||||||
info " Staged $f ($(du -h "$DD/$f" | cut -f1))"
|
cp -f "$DD/$f" "$RES/$f"
|
||||||
echo "INCBIN(${sym}, \"$RES/$f\");" >> "$GEN/embedded_data.h"
|
info " Staged $f ($(du -h "$DD/$f" | cut -f1))"
|
||||||
else
|
echo "INCBIN(${sym}, \"$RES/$f\");" >> "$GEN/embedded_data.h"
|
||||||
echo "extern \"C\" { static const uint8_t* g_${sym}_data = nullptr; }" >> "$GEN/embedded_data.h"
|
else
|
||||||
echo "static const unsigned int g_${sym}_size = 0;" >> "$GEN/embedded_data.h"
|
echo "extern \"C\" { static const uint8_t* g_${sym}_data = nullptr; }" >> "$GEN/embedded_data.h"
|
||||||
fi
|
echo "static const unsigned int g_${sym}_size = 0;" >> "$GEN/embedded_data.h"
|
||||||
done
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
warn "prebuilt-binaries/dragonxd-win/ not found — wallet-only build"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
warn "prebuilt-binaries/dragonxd-win/ not found — wallet-only build"
|
info "Lite mode: skipping embedded daemon binaries"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── xmrig binary (from prebuilt-binaries/xmrig-hac/) ────────────────
|
# ── xmrig binary (from prebuilt-binaries/drg-xmrig/) ────────────────
|
||||||
local XMRIG_DIR="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac"
|
local XMRIG_DIR="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig"
|
||||||
|
# The published DRG-XMRig archives ship the binary inside a versioned subdir, not as a flat
|
||||||
|
# xmrig.exe. Extract it from the matching win-x64 zip if it isn't already staged — otherwise
|
||||||
|
# the embed below never fires (HAS_EMBEDDED_XMRIG stays undefined) and the wallet ships with
|
||||||
|
# no miner ("xmrig binary not found" at runtime).
|
||||||
|
if [[ ! -f "$XMRIG_DIR/xmrig.exe" ]]; then
|
||||||
|
local _xz; _xz=$(ls "$XMRIG_DIR"/drg-xmrig-*-win-x64.zip 2>/dev/null | head -1)
|
||||||
|
if [[ -n "$_xz" ]] && command -v unzip >/dev/null 2>&1; then
|
||||||
|
local _xtmp; _xtmp=$(mktemp -d)
|
||||||
|
# -j flattens the versioned subdir; check the file (not unzip's exit code, which is
|
||||||
|
# non-zero if a pattern matches nothing).
|
||||||
|
unzip -j -o "$_xz" '*xmrig.exe' -d "$_xtmp" >/dev/null 2>&1 || true
|
||||||
|
if [[ -f "$_xtmp/xmrig.exe" ]]; then
|
||||||
|
cp -f "$_xtmp/xmrig.exe" "$XMRIG_DIR/xmrig.exe"
|
||||||
|
info " Extracted xmrig.exe from $(basename "$_xz")"
|
||||||
|
fi
|
||||||
|
rm -rf "$_xtmp"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
if [[ -f "$XMRIG_DIR/xmrig.exe" ]]; then
|
if [[ -f "$XMRIG_DIR/xmrig.exe" ]]; then
|
||||||
cp -f "$XMRIG_DIR/xmrig.exe" "$RES/xmrig.exe"
|
cp -f "$XMRIG_DIR/xmrig.exe" "$RES/xmrig.exe"
|
||||||
info " Staged xmrig.exe ($(du -h "$XMRIG_DIR/xmrig.exe" | cut -f1))"
|
info " Staged xmrig.exe ($(du -h "$XMRIG_DIR/xmrig.exe" | cut -f1))"
|
||||||
@@ -517,9 +701,13 @@ HDR
|
|||||||
echo "};" >> "$GEN/embedded_data.h"
|
echo "};" >> "$GEN/embedded_data.h"
|
||||||
|
|
||||||
# ── Overlay themes ───────────────────────────────────────────────
|
# ── Overlay themes ───────────────────────────────────────────────
|
||||||
|
# Expand skin files with layout sections from ui.toml before embedding
|
||||||
echo -e "\n// ---- Bundled overlay themes ----" >> "$GEN/embedded_data.h"
|
echo -e "\n// ---- Bundled overlay themes ----" >> "$GEN/embedded_data.h"
|
||||||
|
local THEME_STAGE_DIR="$bd/_expanded_themes"
|
||||||
|
mkdir -p "$THEME_STAGE_DIR"
|
||||||
|
python3 "$SCRIPT_DIR/scripts/expand_themes.py" "$SCRIPT_DIR/res/themes" "$THEME_STAGE_DIR"
|
||||||
local THEME_TABLE="" THEME_COUNT=0
|
local THEME_TABLE="" THEME_COUNT=0
|
||||||
for tf in "$SCRIPT_DIR/res/themes"/*.toml; do
|
for tf in "$THEME_STAGE_DIR"/*.toml; do
|
||||||
local tbn=$(basename "$tf")
|
local tbn=$(basename "$tf")
|
||||||
[[ "$tbn" == "ui.toml" ]] && continue
|
[[ "$tbn" == "ui.toml" ]] && continue
|
||||||
local tsym=$(echo "$tbn" | sed 's/[^a-zA-Z0-9]/_/g')
|
local tsym=$(echo "$tbn" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||||
@@ -547,58 +735,77 @@ HDR
|
|||||||
"$SCRIPT_DIR/scripts/fetch-libsodium.sh" --win
|
"$SCRIPT_DIR/scripts/fetch-libsodium.sh" --win
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── FreeType for Windows (color-emoji rendering) ───────────────────────
|
||||||
|
# The mingw toolchain ships no FreeType; cross-build a minimal static one (COLR/CPAL, no external
|
||||||
|
# deps). Failure is non-fatal — the wallet just falls back to monochrome emoji.
|
||||||
|
local FT_MINGW_PREFIX="$SCRIPT_DIR/third_party/freetype-mingw"
|
||||||
|
if [[ ! -f "$FT_MINGW_PREFIX/lib/libfreetype.a" ]]; then
|
||||||
|
info "Cross-building FreeType for Windows (color emoji) ..."
|
||||||
|
"$SCRIPT_DIR/scripts/build-freetype-mingw.sh" "$FT_MINGW_PREFIX" \
|
||||||
|
|| warn "FreeType cross-build failed — Windows build will use monochrome emoji"
|
||||||
|
fi
|
||||||
|
local FT_CMAKE_ARG=()
|
||||||
|
if [[ -f "$FT_MINGW_PREFIX/lib/libfreetype.a" ]]; then
|
||||||
|
FT_CMAKE_ARG=(-DDRAGONX_MINGW_FREETYPE_PREFIX="$FT_MINGW_PREFIX")
|
||||||
|
fi
|
||||||
|
|
||||||
# ── CMake + build ────────────────────────────────────────────────────────
|
# ── CMake + build ────────────────────────────────────────────────────────
|
||||||
info "Configuring (cross-compile) ..."
|
info "Configuring (cross-compile) ..."
|
||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \
|
-DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=OFF
|
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
||||||
|
"${FT_CMAKE_ARG[@]}" \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon.exe" ]] || { err "Windows build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}.exe" ]] || { err "Windows build failed"; exit 1; }
|
||||||
info "Binary: $(du -h bin/ObsidianDragon.exe | cut -f1)"
|
info "Binary: $(du -h "bin/${APP_BASENAME}.exe" | cut -f1)"
|
||||||
|
|
||||||
# ── Package: release/windows/ ────────────────────────────────────────────
|
# ── Package: release/windows/ ────────────────────────────────────────────
|
||||||
rm -rf "$out"
|
# Remove only THIS variant's prior artifacts so full-node and lite releases coexist here.
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
rm -rf "$out/${APP_BASENAME}-"* "$out/${APP_BASENAME}.exe"
|
||||||
|
|
||||||
local DIST="DragonX-Wallet-Windows-x64"
|
local DIST="${APP_BASENAME}-${VERSION}-Windows-x64"
|
||||||
local dist_dir="$out/$DIST"
|
local dist_dir="$out/$DIST"
|
||||||
mkdir -p "$dist_dir"
|
mkdir -p "$dist_dir"
|
||||||
cp bin/ObsidianDragon.exe "$dist_dir/"
|
cp "bin/${APP_BASENAME}.exe" "$dist_dir/"
|
||||||
|
|
||||||
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
||||||
for f in dragonxd.bat dragonx-cli.bat hushd.exe hush-cli.exe hush-tx.exe; do
|
if should_bundle_full_node_assets; then
|
||||||
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||||
done
|
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
||||||
|
done
|
||||||
|
|
||||||
cat > "$dist_dir/README.txt" <<'README'
|
# Bundle Sapling params + asmap for the zip distribution
|
||||||
DragonX Wallet - Windows Edition
|
# (The single-file exe has these embedded via INCBIN, but the zip
|
||||||
================================
|
# needs them on disk so the daemon can find them in its work dir.)
|
||||||
|
for f in sapling-spend.params sapling-output.params asmap.dat; do
|
||||||
|
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
info "Lite mode: skipping daemon and Sapling/asmap assets in Windows zip"
|
||||||
|
fi
|
||||||
|
|
||||||
SINGLE-FILE DISTRIBUTION
|
# Bundle xmrig for mining support
|
||||||
========================
|
local XMRIG_WIN="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig/xmrig.exe"
|
||||||
This wallet is a true single-file executable with all resources embedded.
|
[[ -f "$XMRIG_WIN" ]] && { cp "$XMRIG_WIN" "$dist_dir/"; info "Bundled xmrig.exe"; } || warn "xmrig.exe not found — mining unavailable in zip"
|
||||||
Just run ObsidianDragon.exe — no additional files needed!
|
|
||||||
|
|
||||||
On first run, the wallet will automatically extract:
|
cp -r bin/res "$dist_dir/" 2>/dev/null || true
|
||||||
- Sapling parameters to %APPDATA%\ZcashParams\
|
|
||||||
- asmap.dat to %APPDATA%\Hush\DRAGONX\
|
|
||||||
|
|
||||||
For support: https://git.hush.is/hush/ObsidianDragon
|
# ── Single-file exe (all resources embedded) ────────────────────────────
|
||||||
README
|
cp "bin/${APP_BASENAME}.exe" "$out/${APP_BASENAME}-${VERSION}.exe"
|
||||||
|
info "Single-file exe: $out/${APP_BASENAME}-${VERSION}.exe ($(du -h "$out/${APP_BASENAME}-${VERSION}.exe" | cut -f1))"
|
||||||
# Copy single-file exe to release dir
|
|
||||||
cp bin/ObsidianDragon.exe "$out/"
|
|
||||||
|
|
||||||
|
# ── Zip ──────────────────────────────────────────────────────────────────
|
||||||
if command -v zip &>/dev/null; then
|
if command -v zip &>/dev/null; then
|
||||||
(cd "$out" && zip -r "$DIST.zip" "$DIST")
|
(cd "$out" && zip -r "$DIST.zip" "$DIST")
|
||||||
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
|
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
|
||||||
# Clean up: keep .zip + single-file exe, remove loose directory
|
|
||||||
rm -rf "$dist_dir"
|
|
||||||
fi
|
fi
|
||||||
|
rm -rf "$dist_dir"
|
||||||
|
|
||||||
info "Windows release artifacts: $out/"
|
info "Windows release artifacts: $out/"
|
||||||
ls -lh "$out/"
|
ls -lh "$out/"
|
||||||
@@ -694,7 +901,27 @@ build_release_mac() {
|
|||||||
fi
|
fi
|
||||||
info "macOS cross-compiler: $OSXCROSS_CXX (arch: $MAC_ARCH)"
|
info "macOS cross-compiler: $OSXCROSS_CXX (arch: $MAC_ARCH)"
|
||||||
else
|
else
|
||||||
MAC_ARCH=$(uname -m)
|
# Native macOS: build universal (arm64 + x86_64) by default. Override with
|
||||||
|
# DRAGONX_MAC_ARCHS (e.g. "x86_64").
|
||||||
|
MAC_ARCHS="${DRAGONX_MAC_ARCHS:-arm64;x86_64}"
|
||||||
|
# When linking the real lite backend, the app can only include architectures
|
||||||
|
# the backend static library actually provides. Its pinned ring 0.16.11 has no
|
||||||
|
# Apple-Silicon assembly, so that artifact is x86_64-only — constrain the app
|
||||||
|
# arch to the backend's (unless the user explicitly forced DRAGONX_MAC_ARCHS),
|
||||||
|
# otherwise the arm64 slice fails to link.
|
||||||
|
if $DO_LITE_BACKEND && [[ -z "${DRAGONX_MAC_ARCHS:-}" && -n "${lb_lib:-}" ]] && command -v lipo &>/dev/null; then
|
||||||
|
local _backend_archs; _backend_archs=$(lipo -archs "$lb_lib" 2>/dev/null | tr ' ' ';')
|
||||||
|
if [[ -n "$_backend_archs" && "$_backend_archs" != "$MAC_ARCHS" ]]; then
|
||||||
|
warn "Lite backend provides only [$_backend_archs] — building the app for that instead of universal."
|
||||||
|
MAC_ARCHS="$_backend_archs"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ "$MAC_ARCHS" == *";"* || "$MAC_ARCHS" == *","* ]]; then
|
||||||
|
MAC_ARCH="universal"
|
||||||
|
else
|
||||||
|
MAC_ARCH="$MAC_ARCHS"
|
||||||
|
fi
|
||||||
|
export MACOSX_DEPLOYMENT_TARGET="11.0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
header "Release: macOS ($MAC_ARCH$(${IS_CROSS} && echo ' — cross-compile'))"
|
header "Release: macOS ($MAC_ARCH$(${IS_CROSS} && echo ' — cross-compile'))"
|
||||||
@@ -771,41 +998,71 @@ TOOLCHAIN
|
|||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
||||||
${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"}
|
${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"} \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
else
|
else
|
||||||
info "Configuring (native) ..."
|
# Build libsodium as universal if needed
|
||||||
|
local need_sodium=false
|
||||||
|
if [[ ! -f "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" ]] && \
|
||||||
|
[[ ! -f "$SCRIPT_DIR/libs/libsodium-mac/lib/libsodium.a" ]]; then
|
||||||
|
need_sodium=true
|
||||||
|
elif [[ -f "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" ]]; then
|
||||||
|
# Rebuild if existing lib is not universal (single-arch won't link)
|
||||||
|
if ! lipo -info "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" 2>/dev/null | grep -Eq "arm64.*x86_64|x86_64.*arm64"; then
|
||||||
|
info "Existing libsodium is not universal — rebuilding ..."
|
||||||
|
rm -rf "$SCRIPT_DIR/libs/libsodium"
|
||||||
|
need_sodium=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if $need_sodium; then
|
||||||
|
info "Building libsodium (universal) ..."
|
||||||
|
"$SCRIPT_DIR/scripts/fetch-libsodium.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Configuring (native macOS, arch: $MAC_ARCHS) ..."
|
||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES="$MAC_ARCHS" \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon" ]] || { err "macOS build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}" ]] || { err "macOS build failed"; exit 1; }
|
||||||
|
|
||||||
# Strip — use osxcross strip for cross-builds
|
# Strip — use osxcross strip for cross-builds
|
||||||
if $IS_CROSS; then
|
if $IS_CROSS; then
|
||||||
local STRIP_CMD="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-strip"
|
local STRIP_CMD="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-strip"
|
||||||
if [[ -x "$STRIP_CMD" ]]; then
|
if [[ -x "$STRIP_CMD" ]]; then
|
||||||
info "Stripping (osxcross) ..."
|
info "Stripping (osxcross) ..."
|
||||||
"$STRIP_CMD" bin/ObsidianDragon
|
"$STRIP_CMD" "bin/${APP_BASENAME}"
|
||||||
else
|
else
|
||||||
warn "osxcross strip not found at $STRIP_CMD — skipping"
|
warn "osxcross strip not found at $STRIP_CMD — skipping"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
info "Stripping ..."
|
info "Stripping ..."
|
||||||
strip bin/ObsidianDragon
|
strip "bin/${APP_BASENAME}"
|
||||||
|
# Verify universal binary
|
||||||
|
if command -v lipo &>/dev/null; then
|
||||||
|
info "Architecture info:"
|
||||||
|
lipo -info "bin/${APP_BASENAME}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
info "Binary: $(du -h bin/ObsidianDragon | cut -f1)"
|
info "Binary: $(du -h "bin/${APP_BASENAME}" | cut -f1)"
|
||||||
|
|
||||||
# ── Create .app bundle ───────────────────────────────────────────────────
|
# ── Create .app bundle ───────────────────────────────────────────────────
|
||||||
rm -rf "$out"
|
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
# Clean only THIS variant's prior artifacts so full-node and lite releases can
|
||||||
|
# coexist in release/mac/ (Linux/Windows scope their cleanup the same way). The
|
||||||
|
# "ObsidianDragon-" glob never matches "ObsidianDragonLite-" (and vice versa),
|
||||||
|
# and the ".app" names are exact.
|
||||||
|
rm -rf "$out/${APP_BASENAME}.app" "$out/${APP_BASENAME}-"*.app.zip "$out/${APP_BASENAME}-"*.dmg
|
||||||
|
|
||||||
local APP="$out/ObsidianDragon.app"
|
local APP="$out/${APP_BASENAME}.app"
|
||||||
local CONTENTS="$APP/Contents"
|
local CONTENTS="$APP/Contents"
|
||||||
local MACOS="$CONTENTS/MacOS"
|
local MACOS="$CONTENTS/MacOS"
|
||||||
local RESOURCES="$CONTENTS/Resources"
|
local RESOURCES="$CONTENTS/Resources"
|
||||||
@@ -814,40 +1071,62 @@ TOOLCHAIN
|
|||||||
mkdir -p "$MACOS" "$RESOURCES/res" "$FRAMEWORKS"
|
mkdir -p "$MACOS" "$RESOURCES/res" "$FRAMEWORKS"
|
||||||
|
|
||||||
# Main binary
|
# Main binary
|
||||||
cp bin/ObsidianDragon "$MACOS/"
|
cp "bin/${APP_BASENAME}" "$MACOS/"
|
||||||
chmod +x "$MACOS/ObsidianDragon"
|
chmod +x "$MACOS/${APP_BASENAME}"
|
||||||
|
|
||||||
# Resources
|
# Resources
|
||||||
cp -r bin/res/* "$RESOURCES/res/" 2>/dev/null || true
|
cp -r bin/res/* "$RESOURCES/res/" 2>/dev/null || true
|
||||||
|
|
||||||
# Daemon binaries (macOS native, from dragonxd-mac/)
|
if should_bundle_full_node_assets; then
|
||||||
local daemon_dir="$SCRIPT_DIR/prebuilt-binaries/dragonxd-mac"
|
# Daemon binaries (macOS native, from dragonxd-mac/)
|
||||||
if [[ -d "$daemon_dir" ]]; then
|
local daemon_dir="$SCRIPT_DIR/prebuilt-binaries/dragonxd-mac"
|
||||||
for f in hush-arrakis-chain hushd hush-cli hush-tx dragonxd; do
|
if [[ -d "$daemon_dir" ]]; then
|
||||||
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; chmod +x "$MACOS/$f"; info " Bundled $f"; }
|
for f in dragonxd dragonx-cli dragonx-tx; do
|
||||||
done
|
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; chmod +x "$MACOS/$f"; info " Bundled $f"; }
|
||||||
elif ! $IS_CROSS; then
|
done
|
||||||
# Native macOS: try standard paths
|
for f in sapling-spend.params sapling-output.params; do
|
||||||
local launcher_paths=(
|
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; info " Bundled $f"; }
|
||||||
"$SCRIPT_DIR/../hush-arrakis-chain"
|
done
|
||||||
"$HOME/hush3/src/hush-arrakis-chain"
|
elif ! $IS_CROSS; then
|
||||||
)
|
# Native macOS: try standard paths
|
||||||
for p in "${launcher_paths[@]}"; do
|
local daemon_paths=(
|
||||||
[[ -f "$p" ]] && { cp "$p" "$MACOS/hush-arrakis-chain"; chmod +x "$MACOS/hush-arrakis-chain"; info " Bundled hush-arrakis-chain"; break; }
|
"$SCRIPT_DIR/../dragonxd"
|
||||||
done
|
"$HOME/dragonx/src/dragonxd"
|
||||||
local hushd_paths=(
|
)
|
||||||
"$SCRIPT_DIR/../hushd"
|
for p in "${daemon_paths[@]}"; do
|
||||||
"$HOME/hush3/src/hushd"
|
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonxd"; chmod +x "$MACOS/dragonxd"; info " Bundled dragonxd"; break; }
|
||||||
)
|
done
|
||||||
for p in "${hushd_paths[@]}"; do
|
local cli_paths=(
|
||||||
[[ -f "$p" ]] && { cp "$p" "$MACOS/hushd"; chmod +x "$MACOS/hushd"; info " Bundled hushd"; break; }
|
"$SCRIPT_DIR/../dragonx-cli"
|
||||||
done
|
"$HOME/dragonx/src/dragonx-cli"
|
||||||
|
)
|
||||||
|
for p in "${cli_paths[@]}"; do
|
||||||
|
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonx-cli"; chmod +x "$MACOS/dragonx-cli"; info " Bundled dragonx-cli"; break; }
|
||||||
|
done
|
||||||
|
else
|
||||||
|
warn "prebuilt-binaries/dragonxd-mac/ not found — place macOS daemon binaries there for bundling"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
warn "prebuilt-binaries/dragonxd-mac/ not found — place macOS daemon binaries there for bundling"
|
info "Lite mode: skipping macOS daemon and Sapling/asmap bundling"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# asmap.dat
|
# xmrig binary (from prebuilt-binaries/drg-xmrig/)
|
||||||
find_asmap 2>/dev/null && cp "$ASMAP_DAT" "$RESOURCES/asmap.dat" && info " Bundled asmap.dat"
|
local XMRIG_MAC="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig/xmrig"
|
||||||
|
if [[ -f "$XMRIG_MAC" ]]; then
|
||||||
|
cp "$XMRIG_MAC" "$MACOS/xmrig"
|
||||||
|
chmod +x "$MACOS/xmrig"
|
||||||
|
info " Bundled xmrig"
|
||||||
|
else
|
||||||
|
warn "xmrig not found — mining unavailable in .app"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if should_bundle_full_node_assets; then
|
||||||
|
# asmap.dat — placed in MacOS/ so the daemon finds it next to its binary
|
||||||
|
find_asmap 2>/dev/null && {
|
||||||
|
cp "$ASMAP_DAT" "$MACOS/asmap.dat"
|
||||||
|
info " Bundled asmap.dat"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# Bundle SDL3 dylib
|
# Bundle SDL3 dylib
|
||||||
local sdl_dylib=""
|
local sdl_dylib=""
|
||||||
@@ -867,26 +1146,34 @@ TOOLCHAIN
|
|||||||
# Fix the rpath so the binary finds SDL3 in Frameworks/
|
# Fix the rpath so the binary finds SDL3 in Frameworks/
|
||||||
if $IS_CROSS; then
|
if $IS_CROSS; then
|
||||||
local INSTALL_NAME_TOOL="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-install_name_tool"
|
local INSTALL_NAME_TOOL="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-install_name_tool"
|
||||||
[[ -x "$INSTALL_NAME_TOOL" ]] && "$INSTALL_NAME_TOOL" -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/ObsidianDragon" 2>/dev/null || true
|
[[ -x "$INSTALL_NAME_TOOL" ]] && "$INSTALL_NAME_TOOL" -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/${APP_BASENAME}" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
install_name_tool -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/ObsidianDragon" 2>/dev/null || true
|
install_name_tool -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/${APP_BASENAME}" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
info " Bundled $sdl_name"
|
info " Bundled $sdl_name"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Launcher script (ensures working dir + dylib path)
|
# Launcher script (ensures working dir + dylib path). Uses ${APP_BASENAME} so the lite
|
||||||
mv "$MACOS/ObsidianDragon" "$MACOS/ObsidianDragon.bin"
|
# variant (ObsidianDragonLite) gets a correctly-named launcher + .bin pair.
|
||||||
cat > "$MACOS/ObsidianDragon" <<'LAUNCH'
|
mv "$MACOS/${APP_BASENAME}" "$MACOS/${APP_BASENAME}.bin"
|
||||||
|
cat > "$MACOS/${APP_BASENAME}" <<LAUNCH
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
DIR="\$(cd "\$(dirname "\$0")" && pwd)"
|
||||||
export DYLD_LIBRARY_PATH="$DIR/../Frameworks:$DYLD_LIBRARY_PATH"
|
export DYLD_LIBRARY_PATH="\$DIR/../Frameworks:\$DYLD_LIBRARY_PATH"
|
||||||
export DRAGONX_RES_PATH="$DIR/../Resources/res"
|
export DRAGONX_RES_PATH="\$DIR/../Resources/res"
|
||||||
cd "$DIR/../Resources"
|
cd "\$DIR/../Resources"
|
||||||
exec "$DIR/ObsidianDragon.bin" "$@"
|
exec "\$DIR/${APP_BASENAME}.bin" "\$@"
|
||||||
LAUNCH
|
LAUNCH
|
||||||
chmod +x "$MACOS/ObsidianDragon"
|
chmod +x "$MACOS/${APP_BASENAME}"
|
||||||
|
|
||||||
# Info.plist
|
# Info.plist — display name + bundle id differ per variant so lite and full-node .apps
|
||||||
|
# can coexist; the executable matches the launcher (${APP_BASENAME}); the icon is shared.
|
||||||
|
local APP_DISPLAY_NAME="DragonX Wallet"
|
||||||
|
local APP_BUNDLE_ID="is.hush.dragonx"
|
||||||
|
if $DO_LITE; then
|
||||||
|
APP_DISPLAY_NAME="DragonX Wallet Lite"
|
||||||
|
APP_BUNDLE_ID="is.hush.dragonx.lite"
|
||||||
|
fi
|
||||||
cat > "$CONTENTS/Info.plist" <<PLIST
|
cat > "$CONTENTS/Info.plist" <<PLIST
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||||
@@ -894,17 +1181,17 @@ LAUNCH
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>DragonX Wallet</string>
|
<string>${APP_DISPLAY_NAME}</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>DragonX Wallet</string>
|
<string>${APP_DISPLAY_NAME}</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>is.hush.dragonx</string>
|
<string>${APP_BUNDLE_ID}</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${VERSION}</string>
|
<string>${VERSION}</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>${VERSION}</string>
|
<string>${VERSION}</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>ObsidianDragon</string>
|
<string>${APP_BASENAME}</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>ObsidianDragon</string>
|
<string>ObsidianDragon</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
@@ -965,19 +1252,30 @@ PLIST
|
|||||||
|
|
||||||
info ".app bundle created: $APP"
|
info ".app bundle created: $APP"
|
||||||
|
|
||||||
|
# ── Zip the .app bundle ──────────────────────────────────────────────────
|
||||||
|
local APP_ZIP="${APP_BASENAME}-${VERSION}-macOS-${MAC_ARCH}.app.zip"
|
||||||
|
if command -v zip &>/dev/null; then
|
||||||
|
(cd "$out" && zip -r "$APP_ZIP" "${APP_BASENAME}.app")
|
||||||
|
info "App zip: $out/$APP_ZIP ($(du -h "$out/$APP_ZIP" | cut -f1))"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Create DMG ───────────────────────────────────────────────────────────
|
# ── Create DMG ───────────────────────────────────────────────────────────
|
||||||
local DMG_NAME="DragonX_Wallet-${VERSION}-macOS-${MAC_ARCH}.dmg"
|
# DMG filename matches the app bundle name (ObsidianDragon / ObsidianDragonLite).
|
||||||
|
# The mounted volume + CFBundleName keep the "DragonX Wallet" display branding
|
||||||
|
# (APP_DISPLAY_NAME above).
|
||||||
|
local DMG_BASENAME="${APP_BASENAME}"
|
||||||
|
local DMG_NAME="${DMG_BASENAME}-${VERSION}-macOS-${MAC_ARCH}.dmg"
|
||||||
|
|
||||||
if command -v create-dmg &>/dev/null; then
|
if command -v create-dmg &>/dev/null; then
|
||||||
# create-dmg (works on macOS; also available on Linux via npm)
|
# create-dmg (works on macOS; also available on Linux via npm)
|
||||||
info "Creating DMG with create-dmg ..."
|
info "Creating DMG with create-dmg ..."
|
||||||
create-dmg \
|
create-dmg \
|
||||||
--volname "DragonX Wallet" \
|
--volname "${APP_DISPLAY_NAME}" \
|
||||||
--volicon "$RESOURCES/ObsidianDragon.icns" \
|
--volicon "$RESOURCES/ObsidianDragon.icns" \
|
||||||
--window-pos 200 120 \
|
--window-pos 200 120 \
|
||||||
--window-size 600 400 \
|
--window-size 600 400 \
|
||||||
--icon-size 100 \
|
--icon-size 100 \
|
||||||
--icon "ObsidianDragon.app" 150 190 \
|
--icon "${APP_BASENAME}.app" 150 190 \
|
||||||
--app-drop-link 450 190 \
|
--app-drop-link 450 190 \
|
||||||
--no-internet-enable \
|
--no-internet-enable \
|
||||||
"$out/$DMG_NAME" \
|
"$out/$DMG_NAME" \
|
||||||
@@ -992,7 +1290,7 @@ PLIST
|
|||||||
mkdir -p "$staging"
|
mkdir -p "$staging"
|
||||||
cp -a "$APP" "$staging/"
|
cp -a "$APP" "$staging/"
|
||||||
ln -s /Applications "$staging/Applications"
|
ln -s /Applications "$staging/Applications"
|
||||||
hdiutil create -volname "DragonX Wallet" \
|
hdiutil create -volname "${APP_DISPLAY_NAME}" \
|
||||||
-srcfolder "$staging" \
|
-srcfolder "$staging" \
|
||||||
-ov -format UDZO \
|
-ov -format UDZO \
|
||||||
"$out/$DMG_NAME" 2>/dev/null && {
|
"$out/$DMG_NAME" 2>/dev/null && {
|
||||||
@@ -1008,7 +1306,7 @@ PLIST
|
|||||||
cp -a "$APP" "$staging/"
|
cp -a "$APP" "$staging/"
|
||||||
# Can't create a real symlink to /Applications in an ISO, but the .app
|
# Can't create a real symlink to /Applications in an ISO, but the .app
|
||||||
# is the important part — users drag it to Applications manually.
|
# is the important part — users drag it to Applications manually.
|
||||||
genisoimage -V "DragonX Wallet" \
|
genisoimage -V "${APP_DISPLAY_NAME}" \
|
||||||
-D -R -apple -no-pad \
|
-D -R -apple -no-pad \
|
||||||
-o "$out/$DMG_NAME" \
|
-o "$out/$DMG_NAME" \
|
||||||
"$staging" 2>/dev/null && {
|
"$staging" 2>/dev/null && {
|
||||||
@@ -1047,3 +1345,9 @@ if $DO_LINUX || $DO_WIN || $DO_MAC; then
|
|||||||
[[ -d "$SCRIPT_DIR/release/windows" ]] && echo -e " ${CYAN}windows/${NC} — .exe + .zip"
|
[[ -d "$SCRIPT_DIR/release/windows" ]] && echo -e " ${CYAN}windows/${NC} — .exe + .zip"
|
||||||
[[ -d "$SCRIPT_DIR/release/mac" ]] && echo -e " ${CYAN}mac/${NC} — .app + .dmg"
|
[[ -d "$SCRIPT_DIR/release/mac" ]] && echo -e " ${CYAN}mac/${NC} — .app + .dmg"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Reaching here means the build completed (real failures exit 1 at their point of failure).
|
||||||
|
# Exit 0 explicitly: the final `[[ -d release/mac ]] && echo` above returns non-zero on a
|
||||||
|
# non-mac build — and since set -e exempts the left side of an &&, that status would otherwise
|
||||||
|
# become the script's exit code and make a successful build report failure (e.g. to CI).
|
||||||
|
exit 0
|
||||||
|
|||||||
31
cmake/patch-libwebp-simd.cmake
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# patch-libwebp-simd.cmake — portable, idempotent FetchContent patch for libwebp.
|
||||||
|
#
|
||||||
|
# libwebp's cmake/cpu.cmake compiles its scalar *reference* DSP files with the
|
||||||
|
# SSE-disable flags "-mno-sse4.1;-mno-sse2" whenever it can't positively detect
|
||||||
|
# SSE support. Under a macOS *universal* build (-arch arm64;x86_64) the per-arch
|
||||||
|
# SSE flag probe fails (a flag valid for x86_64 is invalid for arm64), so those
|
||||||
|
# disable flags get applied to the x86_64 slice. clang gates the _Float16 type on
|
||||||
|
# SSE2 for x86_64, and the macOS 15+/26 SDK's <math.h> declares _Float16 math
|
||||||
|
# functions unconditionally — so any TU including <math.h> fails to compile with
|
||||||
|
# "_Float16 is not supported on this target".
|
||||||
|
#
|
||||||
|
# SSE2 is part of the x86_64 baseline ABI, so disabling it on the reference files
|
||||||
|
# is unnecessary on every platform we target. Blanking the SSE entries (indices
|
||||||
|
# must stay aligned with WEBP_SIMD_FLAGS) fixes the universal build and is a no-op
|
||||||
|
# for single-arch Linux/Windows/x86_64 builds. Idempotent: re-running is a no-op.
|
||||||
|
if(NOT DEFINED CPU_CMAKE OR NOT EXISTS "${CPU_CMAKE}")
|
||||||
|
message(FATAL_ERROR "patch-libwebp-simd: cpu.cmake not found at '${CPU_CMAKE}'")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(READ "${CPU_CMAKE}" _contents)
|
||||||
|
string(REPLACE
|
||||||
|
"set(SIMD_DISABLE_FLAGS \"-mno-sse4.1;-mno-sse2;;-mno-dspr2;;-mno-msa\")"
|
||||||
|
"set(SIMD_DISABLE_FLAGS \";;;-mno-dspr2;;-mno-msa\")"
|
||||||
|
_patched "${_contents}")
|
||||||
|
|
||||||
|
if(_patched STREQUAL _contents)
|
||||||
|
message(STATUS "patch-libwebp-simd: no change (already patched or pattern absent)")
|
||||||
|
else()
|
||||||
|
file(WRITE "${CPU_CMAKE}" "${_patched}")
|
||||||
|
message(STATUS "patch-libwebp-simd: neutralized x86 SSE-disable flags in cpu.cmake")
|
||||||
|
endif()
|
||||||
549
docs/daemon-startup-hardening.md
Normal file
@@ -0,0 +1,549 @@
|
|||||||
|
# Daemon Startup Hardening — Implementation Plan
|
||||||
|
|
||||||
|
Eight verified edge-case defects in how ObsidianDragon brings up (and watches) the
|
||||||
|
`dragonxd` daemon at launch. Each entry is a buildable fix: the defect (with exact
|
||||||
|
line references), the chosen approach, the call sites, a representative change, and how
|
||||||
|
to verify it.
|
||||||
|
|
||||||
|
- **Scope:** full-node startup path (`--lite` excludes the embedded daemon entirely).
|
||||||
|
- **Source:** line references are exact against branch `dev` @ `45b652f`.
|
||||||
|
- **Provenance:** findings verified by direct source read; each fix designed by an
|
||||||
|
independent agent grounded in the cited files, with a sequencing pass for ordering,
|
||||||
|
shared helpers, and merge conflicts.
|
||||||
|
|
||||||
|
**Severity:** 2 High, 6 Medium · **Effort:** ≈ 25–35 engineering-hours · **7 landing steps.**
|
||||||
|
|
||||||
|
Status legend: ☐ not started · ◐ in progress · ☑ landed & verified
|
||||||
|
|
||||||
|
**Status: all 8 landed & verified** (build-clean, `ctest` green after each) across four commits on
|
||||||
|
`dev` — lifecycle cluster (F1/F2/F4), filesystem+params cluster (F7/F6/F5), F3, and F8. Six new
|
||||||
|
pure-helper unit tests added.
|
||||||
|
|
||||||
|
**Wrap-up done:** release notes added (`CHANGELOG.md`, F8 breaking change front and center); i18n
|
||||||
|
back-fill applied additively to `res/lang/*.json` (42 keys — all 6 for es/de/fr/pt/ru; 6 zh/ja/ko
|
||||||
|
entries whose glyphs aren't in the current `NotoSansCJK-Subset.ttf` were left on English fallback
|
||||||
|
rather than render as tofu).
|
||||||
|
|
||||||
|
**Still owed before release:** a **CJK subset-font rebuild** (`scripts/build_cjk_subset.py`, needs
|
||||||
|
the Noto CJK source font) to cover the 6 deferred zh/ja/ko strings. *(F1 and F2 now have headless
|
||||||
|
integration-test coverage — see the progress log — so their GUI repros are optional, not blocking.)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended rollout sequence
|
||||||
|
|
||||||
|
A real dependency order, not a checklist. The daemon-lifecycle cluster lands first
|
||||||
|
because it makes the `State::Error` / `crash_count_` contract trustworthy — which the
|
||||||
|
connect-stall panel and the lock gate both build on. The filesystem cluster lands
|
||||||
|
around a single shared helper. The connectivity-breaking security flip lands last.
|
||||||
|
|
||||||
|
| Step | Finding(s) | Site | Why here | Status |
|
||||||
|
|------|-----------|------|----------|--------|
|
||||||
|
| 1 | **F1** | `embedded_daemon.cpp` · `isRunning()` | Smallest/highest-severity; establishes the reliable Error/crash-count transition steps 3 & 6 depend on. | ☑ |
|
||||||
|
| 2 | **F2** | `embedded_daemon.cpp` · `startProcess()` | Same file family, different function; test the F1+F2 pair together with `kill -SEGV` / bad-binary repros. | ☑ |
|
||||||
|
| 3 | **F4** | `embedded_daemon.cpp` · `start()` | After F1/F2 so crash-count semantics are settled; its bail deliberately stays out of the crash path. | ☑ |
|
||||||
|
| 4 | **F7** | `util/platform` · `connection.cpp` | Structural owner of the fs-error idiom + `ConnectionConfig` that F5/F6/F8 reuse. | ☑ |
|
||||||
|
| 5 | **F6 + F5** | `app.cpp` · `verifySaplingParams()` | Same `startEmbeddedDaemon` / `verifySaplingParams` block; land together. | ☑ |
|
||||||
|
| 6 | **F3** | `app.cpp` · `renderLoadingOverlay()` | After F1 — panel is guarded off during `State::Error` (owned by the crash-count hint). | ☑ |
|
||||||
|
| 7 | **F8** | `connection.cpp` · `tryConnect()` | Largest; only connectivity-breaking default flip — land last, with release notes. | ☑ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F1 — Double-`waitpid` race can swallow a daemon crash
|
||||||
|
|
||||||
|
**Severity:** High · **Effort:** S (~1–2h) · **Status:** ☑ landed & verified
|
||||||
|
|
||||||
|
### The defect
|
||||||
|
`EmbeddedDaemon::isRunning()` (`embedded_daemon.cpp:1136`, POSIX branch) calls
|
||||||
|
`waitpid(WNOHANG)` — from the **UI thread, nearly every frame** — racing
|
||||||
|
`monitorProcess()`'s own reap at `:1244`. `waitpid` is one-shot: if the UI thread wins,
|
||||||
|
the monitor never decodes the exit, so `crash_count_` never increments, `State::Error`
|
||||||
|
never fires, and the 3-strike auto-restart cap (`app_network.cpp:479`) is defeated. The
|
||||||
|
sibling `XmrigManager::isRunning()` (`xmrig_manager.cpp:512`) already fixed exactly this
|
||||||
|
with an atomic read.
|
||||||
|
|
||||||
|
### The fix
|
||||||
|
Make `isRunning()` read the existing `std::atomic<State> state_` (member at
|
||||||
|
`embedded_daemon.h:253`) instead of calling `waitpid`, leaving `monitorProcess()` as the
|
||||||
|
sole reaper. Predicate is `Running || Stopping` — `Stopping` must stay "alive" because
|
||||||
|
`stop()`'s graceful/SIGTERM wait loops poll `isRunning()` before the process has exited.
|
||||||
|
|
||||||
|
### Files touched
|
||||||
|
- `src/daemon/embedded_daemon.cpp` — `isRunning()`, POSIX branch (~1136)
|
||||||
|
|
||||||
|
### Core change
|
||||||
|
```cpp
|
||||||
|
bool EmbeddedDaemon::isRunning() const // POSIX branch
|
||||||
|
{
|
||||||
|
// Read the atomic state_ instead of waitpid() — monitorProcess() is the
|
||||||
|
// sole reaper. Previously both threads reaped; if the UI thread won, the
|
||||||
|
// monitor never saw the exit (crash_count_ / exit code / Error all lost).
|
||||||
|
if (process_pid_ <= 0) return false;
|
||||||
|
|
||||||
|
State s = state_.load(std::memory_order_relaxed);
|
||||||
|
// Stopping stays "alive": stop()'s wait loops poll isRunning() while
|
||||||
|
// state_ == Stopping, before the process has actually terminated.
|
||||||
|
return (s == State::Running || s == State::Stopping);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- Manual: `kill -SEGV` the daemon 10–20×; the monitor must report the exit and increment `crash_count_` every time (previously intermittent).
|
||||||
|
- Regression: a normal Settings-driven stop still escalates SIGTERM→SIGKILL (the `Stopping` predicate).
|
||||||
|
- Not unit-testable (real fork/exec/waitpid) — consistent with the no-process-spawn harness.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
Mirrors `XmrigManager::isRunning()`. Flags a separate latent hazard (out of scope):
|
||||||
|
`stop()`'s final blocking `waitpid` (`:1220`) can still race a mid-sleep monitor
|
||||||
|
iteration — file as its own ticket.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F2 — exec-after-fork silent failure: "Running" for a daemon that never started
|
||||||
|
|
||||||
|
**Severity:** High · **Effort:** S (~2–3h) · **Status:** ☑ landed & verified
|
||||||
|
|
||||||
|
### The defect
|
||||||
|
In `startProcess()` (`embedded_daemon.cpp:957–1061`, POSIX) the parent runs
|
||||||
|
`process_pid_ = pid; return true;` **unconditionally** after `fork()` — with no
|
||||||
|
exec-status handshake. On a non-executable / wrong-arch / corrupt binary the child's
|
||||||
|
`execv` fails and it `_exit(127)`s, but `start()` has already set `State::Running`
|
||||||
|
(`:565`). The real cause never reaches `last_error_`; it surfaces later, generically,
|
||||||
|
as "exited unexpectedly (exit code 127)".
|
||||||
|
|
||||||
|
### The fix
|
||||||
|
Add a **close-on-exec self-pipe** handshake — `pipe() + fcntl(FD_CLOEXEC)`, deliberately
|
||||||
|
**not** `pipe2()` (macOS lacks it; the POSIX branch is shared). The child writes `errno`
|
||||||
|
only on `execv` failure; a successful exec closes the write end for free. Parent reads:
|
||||||
|
EOF ⇒ success; 4 bytes ⇒ reap the zombie, set a precise `last_error_` ("not executable
|
||||||
|
or wrong architecture"), and return `false` so `start()` never reports Running. EINTR-safe
|
||||||
|
on both ends. Also comments the unchecked parent-side `setpgid` at `:1053`.
|
||||||
|
|
||||||
|
### Files touched
|
||||||
|
- `src/daemon/embedded_daemon.cpp` — `startProcess()` parent read path
|
||||||
|
- `src/daemon/embedded_daemon.cpp` — child `execv`-failure write (~1043)
|
||||||
|
- `src/daemon/embedded_daemon.cpp` — `setpgid` best-effort comment (~1053)
|
||||||
|
|
||||||
|
### Core change
|
||||||
|
```cpp
|
||||||
|
// Self-pipe exec handshake (pipe()+FD_CLOEXEC; NOT pipe2 — macOS lacks it).
|
||||||
|
int execpipe[2]; pipe(execpipe);
|
||||||
|
fcntl(execpipe[0], F_SETFD, FD_CLOEXEC);
|
||||||
|
fcntl(execpipe[1], F_SETFD, FD_CLOEXEC);
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid == 0) { // child
|
||||||
|
close(execpipe[0]);
|
||||||
|
/* setpgid / chdir / dup2 / argv … */
|
||||||
|
execv(binary_path.c_str(), argv.data());
|
||||||
|
int e = errno; // execv failed
|
||||||
|
while (write(execpipe[1], &e, sizeof e) < 0 && errno == EINTR) {}
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(execpipe[1]); // parent: must close or read() never EOFs
|
||||||
|
int child_errno = 0, total = 0;
|
||||||
|
for (;;) { // EOF ⇒ exec ok; 4 bytes ⇒ exec failed
|
||||||
|
ssize_t n = read(execpipe[0], (char*)&child_errno + total, sizeof(int) - total);
|
||||||
|
if (n == 0) break;
|
||||||
|
if (n < 0) { if (errno == EINTR) continue; break; }
|
||||||
|
if ((total += n) >= (int)sizeof(int)) break;
|
||||||
|
}
|
||||||
|
close(execpipe[0]);
|
||||||
|
if (total >= (int)sizeof(int)) { // exec never happened
|
||||||
|
waitpid(pid, nullptr, 0); // reap the zombie
|
||||||
|
last_error_ = "dragonxd could not be executed: " +
|
||||||
|
std::string(strerror(child_errno)) +
|
||||||
|
" — not executable or wrong architecture";
|
||||||
|
return false; // start() no longer reports Running
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- Point at a `chmod -x` / wrong-arch file → `start()` returns false immediately, precise message, no leftover zombie.
|
||||||
|
- Success path: real binary still starts with no perceptible added latency.
|
||||||
|
- Optional pure `formatExecFailureError(errno)` helper for a `test_phase4.cpp` unit test.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
F1 (same function family; sequence F1→F2). **Highest-risk mistake:** forgetting
|
||||||
|
`FD_CLOEXEC` makes every successful start hang the parent read forever.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F4 — Stale datadir-lock start → restart storm that wedges the UI
|
||||||
|
|
||||||
|
**Severity:** Medium · **Effort:** S (~3–5h) · **Status:** ☑ landed & verified
|
||||||
|
|
||||||
|
### The defect
|
||||||
|
`start()` (`embedded_daemon.cpp:466`) gates only on the RPC port (`:482`), never on
|
||||||
|
`isDaemonProcessRunning()` (`:1292`). A graceful shutdown frees the port but keeps the
|
||||||
|
datadir `.lock` for up to ~90s. A rapid stop→start spawns a daemon that dies "Cannot
|
||||||
|
obtain a lock on data directory" — routed to the generic crash path. With a ~4s retry
|
||||||
|
cadence, **three lock races in ~12s exhaust the 3-strike budget** and wedge the UI long
|
||||||
|
before the lock actually clears.
|
||||||
|
|
||||||
|
### The fix
|
||||||
|
Fail-fast with a **short bounded local wait (~300ms), not a 90s block**. After the port
|
||||||
|
bail, consult `isDaemonProcessRunning()` — gated by `!skip_port_check_` and exempt when
|
||||||
|
`override_datadir_` is set, so the isolated migrate-to-seed daemon still works. A pure
|
||||||
|
`evaluateDatadirLockGate()` returns a **distinct non-crash Error** that never increments
|
||||||
|
`crash_count_`. The connect loop's own retry then absorbs the transient.
|
||||||
|
|
||||||
|
### Files touched
|
||||||
|
- `src/daemon/embedded_daemon.h` — decision struct, helper decl, poll constants
|
||||||
|
- `src/daemon/embedded_daemon.cpp` — `start()` gate + `evaluateDatadirLockGate()`
|
||||||
|
|
||||||
|
### Core change
|
||||||
|
```cpp
|
||||||
|
static StartLockGateDecision evaluateDatadirLockGate(
|
||||||
|
bool skipPortCheck, bool isolatedOverride, bool stillRunningAfterWait) {
|
||||||
|
if (skipPortCheck || isolatedOverride) return {true, ""}; // migrate-to-seed exempt
|
||||||
|
if (!stillRunningAfterWait) return {true, ""};
|
||||||
|
return {false, "A previous dragonxd is still shutting down and holding the "
|
||||||
|
"data directory lock. Retrying shortly…"};
|
||||||
|
}
|
||||||
|
|
||||||
|
// start() — after the isPortInUse() bail, before setState(Starting):
|
||||||
|
if (!skip_port_check_ && override_datadir_.empty()) {
|
||||||
|
bool stillLocked = false; // ~300ms bounded wait, NOT ~90s
|
||||||
|
for (int i = 0; i < kDatadirLockWaitMaxPolls; ++i) {
|
||||||
|
if (!isDaemonProcessRunning()) { stillLocked = false; break; }
|
||||||
|
stillLocked = true;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(kDatadirLockWaitPollMs));
|
||||||
|
}
|
||||||
|
auto gate = evaluateDatadirLockGate(false, false, stillLocked);
|
||||||
|
if (!gate.proceed) { setState(State::Error, gate.errorMessage); return false; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- Unit: `evaluateDatadirLockGate()` across the skip / isolated / still-running matrix.
|
||||||
|
- Manual: rapid restart into a lingering lock → distinct message, no crash-cap wedge.
|
||||||
|
- Migrate-to-seed second daemon still starts (isolated exemption).
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
F1/F2 (must not touch `crash_count_`; wording must not collide with the monitor's
|
||||||
|
"exited unexpectedly"). Same TU, different function.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F5 — Extraction / copy write-failures never surfaced up front
|
||||||
|
|
||||||
|
**Severity:** Medium · **Effort:** S (~2–3h) · **Status:** ☑ landed & verified
|
||||||
|
|
||||||
|
### The defect
|
||||||
|
`startEmbeddedDaemon()` discards `extractEmbeddedResources()`'s `bool` return
|
||||||
|
(`app.cpp:4152`) and the second copy-fallback loop drops `copy_file`'s `error_code`
|
||||||
|
entirely (`:4236`). Only Sapling params **existence** is re-checked — never the daemon
|
||||||
|
binary/CLI/tx/asmap. A disk-full or truncated `dragonxd` write falls straight through to
|
||||||
|
spawn and fails opaquely. The innermost write already returns `false`
|
||||||
|
(`embedded_resources.cpp:307`) — the signal is simply thrown away.
|
||||||
|
|
||||||
|
### The fix
|
||||||
|
Minimal, surgical wiring — no new abstraction. Capture the extraction return and, on
|
||||||
|
failure, set `daemon_status_ = TR("sb_daemon_extract_failed")` and `return false` before
|
||||||
|
spawning. In the second copy loop, check `ec` after each `copy_file`, track `copyFailed`,
|
||||||
|
and abort with a dir-parameterized `sb_daemon_files_failed`. An **absent source** stays
|
||||||
|
fine (optional files); only an actual `error_code` counts. Written so F6/F7 slot in later
|
||||||
|
without re-touching this control flow.
|
||||||
|
|
||||||
|
### Files touched
|
||||||
|
- `src/app.cpp` — `startEmbeddedDaemon()` extraction check (~4152)
|
||||||
|
- `src/app.cpp` — second copy-fallback loop (~4210–4242)
|
||||||
|
- `src/util/i18n.cpp` + `res/lang/*.json` — 2 additive keys
|
||||||
|
|
||||||
|
### Core change
|
||||||
|
```cpp
|
||||||
|
// stop discarding the extraction result (~4152)
|
||||||
|
if (!resources::extractEmbeddedResources()) {
|
||||||
|
daemon_status_ = TR("sb_daemon_extract_failed"); // disk full / permission denied
|
||||||
|
return false; // abort before spawning
|
||||||
|
}
|
||||||
|
|
||||||
|
// second copy-fallback loop — was dropping ec entirely (~4236)
|
||||||
|
bool copyFailed = false;
|
||||||
|
for (const char* name : { "asmap.dat", "dragonxd", "dragonx-cli", "dragonx-tx" }) {
|
||||||
|
fs::path dst = fs::path(daemon_dir) / name;
|
||||||
|
if (fs::exists(dst)) continue; // already present — skip
|
||||||
|
for (const auto& dir : searchDirs) {
|
||||||
|
fs::path src = fs::path(dir) / name;
|
||||||
|
if (!fs::exists(src)) continue; // absent source is OK, not a failure
|
||||||
|
fs::copy_file(src, dst, ec);
|
||||||
|
if (ec) { copyFailed = true; ec.clear(); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (copyFailed) {
|
||||||
|
char buf[512];
|
||||||
|
snprintf(buf, sizeof buf, TR("sb_daemon_files_failed"), daemon_dir.c_str());
|
||||||
|
daemon_status_ = buf;
|
||||||
|
return false; // don't fall through to spawn
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- Unit: `extractEmbeddedResources()` returns false without embedded resources.
|
||||||
|
- Extract the copy loop into a testable helper; force one dst write to fail (dst is an existing directory).
|
||||||
|
- Manual: near-full tmpfs / read-only dir → clear status, daemon controller never constructed.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
Shares the `daemon_status_` surfacing convention with F6; its early-return pattern is the
|
||||||
|
template F7 matches. Open item: remove truncated dst files so a retry re-copies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F6 — Sapling params validated by existence/size only, never hashed
|
||||||
|
|
||||||
|
**Severity:** Medium · **Effort:** S (~3–5h) · **Status:** ☑ landed & verified
|
||||||
|
|
||||||
|
> **As-built note.** `verifySaplingParams()` now delegates to a public, injectable
|
||||||
|
> `verifySaplingParamsIn(dir, digests)` so the integrity + marker-cache logic is unit-testable
|
||||||
|
> with synthetic small files (the real 48 MB params aren't in the repo). i18n keys for F5 were
|
||||||
|
> added to `i18n.cpp` (English source of truth); the `res/lang/*.json` back-fill via
|
||||||
|
> `scripts/add_missing_translations.py` is deferred to a single run at the end of the batch,
|
||||||
|
> per the cross-cutting note. Non-English locales fall back to English until then.
|
||||||
|
|
||||||
|
### The defect
|
||||||
|
`verifySaplingParams()` (`connection.cpp:123`) only calls `fs::exists()`;
|
||||||
|
`resourceNeedsUpdate()` (`embedded_resources.cpp:250`) is size-only. On Linux (no
|
||||||
|
embedded resources) a **truncated-but-present** param passes and is handed to the daemon,
|
||||||
|
which then fails to build shielded proofs mid-operation — far from the real cause.
|
||||||
|
|
||||||
|
### The fix
|
||||||
|
Add a pinned `{ filename → size, sha256 }` table (one source of truth, cross-referenced
|
||||||
|
to `scripts/build-lite-backend-artifact.sh`) and hash-check each param after the
|
||||||
|
existence check, reusing the existing `util::sha256Hex` (no second implementation). Since
|
||||||
|
these are ~48 MB, **cache the result** via a `.sapling_verified` marker keyed on
|
||||||
|
`size:mtime` — re-hash only when the stat line changes, so startup isn't slowed.
|
||||||
|
|
||||||
|
### Files touched
|
||||||
|
- `src/rpc/connection.h` — `verifySaplingParams` decl
|
||||||
|
- `src/rpc/connection.cpp` — digest table, marker helpers, rewrite
|
||||||
|
|
||||||
|
### Core change
|
||||||
|
```cpp
|
||||||
|
// connection.cpp — pinned known-good digests
|
||||||
|
// (source of truth: scripts/build-lite-backend-artifact.sh ensure_sapling_params)
|
||||||
|
constexpr SaplingParamDigest kSaplingParamDigests[] = {
|
||||||
|
{ "sapling-spend.params", 47958396, "8e48ffd2…efc13" },
|
||||||
|
{ "sapling-output.params", 3592860, "2f0ebbcb…fb0e4" },
|
||||||
|
};
|
||||||
|
|
||||||
|
bool Connection::verifySaplingParams() {
|
||||||
|
// existence check (unchanged) …
|
||||||
|
// cache: skip re-hashing a ~48 MB file unless size:mtime changed
|
||||||
|
if (readMarkerMatches(marker, statLines)) return true;
|
||||||
|
for (auto& d : kSaplingParamDigests)
|
||||||
|
if (util::sha256Hex(bytes) != d.sha256) return false; // reuse existing helper
|
||||||
|
writeMarker(marker, statLines);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- Unit: good params pass; truncated / wrong-bytes rejected; marker cache short-circuits re-hash unless size/mtime changed. Real temp-file fixtures (matches existing `sha256Hex` tests).
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
F7 (reuse fs-error idiom; shares the `startEmbeddedDaemon`/`verifySaplingParams` block).
|
||||||
|
Third caller of the existing `util::sha256Hex`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F7 — Directory-create errors universally ignored on the daemon-env path
|
||||||
|
|
||||||
|
**Severity:** Medium · **Effort:** S (~3–4h) · **Status:** ☑ landed & verified
|
||||||
|
|
||||||
|
> **As-built notes.** Two deviations from the original design, both confirmed against the code:
|
||||||
|
> (1) `embedded_resources.cpp:270` already checks its `error_code` and returns `false` on failure — it was **not** a bug, so it is left untouched.
|
||||||
|
> (2) Of the four `autoDetectConfig` callers, only the primary connect path (`app_network.cpp:243`) was wired to check `dir_error`; the other three degrade gracefully on their own — `app.cpp:4306` and `app_wizard.cpp:912` are stop paths that already gate on empty creds, and `settings_page.cpp:434` is read-only display. `dir_error` is set by `autoDetectConfig`, so they can be wired later if desired.
|
||||||
|
|
||||||
|
### The defect
|
||||||
|
Five startup directory-create sites either drop the `error_code` or use the throwing
|
||||||
|
overload with no `catch`: `main.cpp:730`, `connection.cpp:216` (can throw **uncaught**
|
||||||
|
through its callers), `embedded_resources.cpp:270`, `app.cpp:4172`/`4218`. A read-only
|
||||||
|
home or permission-denied yields a confusing "conf missing" / "binary not found"
|
||||||
|
downstream — or an uncaught `filesystem_error` — instead of a clear cause.
|
||||||
|
|
||||||
|
### The fix
|
||||||
|
One shared, non-throwing `Platform::ensureDirectory(dir, outError)` in
|
||||||
|
`util/platform.{h,cpp}` that produces a single consistent message. Replace all five
|
||||||
|
sites; `autoDetectConfig()` moves off the throwing overload and sets a new
|
||||||
|
`ConnectionConfig::dir_error` that its four callers check and bail on. This is the
|
||||||
|
**structural owner** of the fs-error idiom that F5 and F6 reuse.
|
||||||
|
|
||||||
|
### Files touched
|
||||||
|
- `src/util/platform.h` / `.cpp` — `ensureDirectory()`
|
||||||
|
- `src/rpc/connection.h` / `.cpp` — `dir_error` + `autoDetectConfig`
|
||||||
|
- `main.cpp`, `app.cpp`, `app_network.cpp`, `app_wizard.cpp`, `settings_page.cpp`, `embedded_resources.cpp` — 5 sites + 4 callers
|
||||||
|
- `tests/test_phase4.cpp` — `TestPlatformEnsureDirectory`
|
||||||
|
|
||||||
|
### Core change
|
||||||
|
```cpp
|
||||||
|
// util/platform.cpp — one shared, non-throwing helper
|
||||||
|
bool Platform::ensureDirectory(const std::string& dir, std::string* outError) {
|
||||||
|
std::error_code ec;
|
||||||
|
if (std::filesystem::is_directory(dir, ec)) return true;
|
||||||
|
ec.clear();
|
||||||
|
std::filesystem::create_directories(dir, ec);
|
||||||
|
if (ec) {
|
||||||
|
if (outError)
|
||||||
|
*outError = "Cannot create " + dir + ": " + ec.message() +
|
||||||
|
". Check permissions / free space.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Replaces 5 ad-hoc sites; autoDetectConfig() now sets ConnectionConfig::dir_error,
|
||||||
|
// and its 4 callers bail on it.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- Unit `TestPlatformEnsureDirectory`: existing dir → true; fresh nested → created; POSIX unwritable → false + message.
|
||||||
|
- All four `autoDetectConfig` callers tolerate `dir_error`. Pre-App-init site (main.cpp) reports via stderr / MessageBox.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
**Owns** `Platform::ensureDirectory` (used by F5, F6) and the `ConnectionConfig`
|
||||||
|
extension (coordinated with F8). Land before F5/F6/F8.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F8 — Plaintext-remote RPC credential transmission is warn-only
|
||||||
|
|
||||||
|
**Severity:** Medium · **Effort:** M (~6–9h) · **Status:** ☑ landed & verified
|
||||||
|
|
||||||
|
> **⚠️ RELEASE NOTES REQUIRED — breaking default flip.** A wallet configured to talk to a
|
||||||
|
> **remote** `rpchost` over **plain HTTP** (no `rpctls=1`) will now be **refused** at connect
|
||||||
|
> time instead of warned. Affected users must add **`rpcallowplaintext=1`** to `DRAGONX.conf`
|
||||||
|
> (or switch to `rpctls=1`) to reconnect. Local/embedded daemons (`127.0.0.0/8`, `localhost`,
|
||||||
|
> `::1`) are unaffected. Call this out prominently in the release notes.
|
||||||
|
>
|
||||||
|
> **As-built note.** Shipped the security-complete core: `isLocalHost` tightened to exact
|
||||||
|
> loopback (`isExactIPv4Loopback` — `127.evil.com` no longer passes), refuse-by-default in
|
||||||
|
> `tryConnect`, and the `rpcallowplaintext` conf-key opt-in. The **Settings toggle UI was
|
||||||
|
> deferred** — the RPC section of `settings_page.cpp` is read-only display and a security
|
||||||
|
> toggle there is riskier surface; the conf-key opt-in fully covers recovery, and the refusal
|
||||||
|
> status/notification tells the user exactly what to add. The toggle can be added later
|
||||||
|
> (persist a `Settings` flag and OR it into `allowsPlaintextRemote`).
|
||||||
|
|
||||||
|
### The defect
|
||||||
|
A remote `rpchost` without `rpctls=1` sends Basic-auth `rpcuser:rpcpassword` over
|
||||||
|
cleartext HTTP. `tryConnect()` (`app_network.cpp:314`) only shows a **dismissible
|
||||||
|
warning** then proceeds — a local-network MITM sees the credentials. Compounding it,
|
||||||
|
`isLocalHost()`'s naive `rfind("127.",0)==0` misclassifies `127.evil.com` as local,
|
||||||
|
suppressing even the warning.
|
||||||
|
|
||||||
|
### The fix
|
||||||
|
Change the policy to **refuse-by-default with an explicit, persisted opt-in** — a
|
||||||
|
`rpcallowplaintext=1` conf key (for hand-editors) and a Settings toggle. Block the
|
||||||
|
connect and show a **blocking modal** explaining the risk and how to enable TLS or opt
|
||||||
|
in; localhost is unaffected. Tighten `isLocalHost()` to exact `127.x.y.z` / `::1` /
|
||||||
|
`localhost` via `isExactIPv4Loopback()`. **Back-compat:** default off ⇒ existing remote
|
||||||
|
users hit a hard stop until they opt in — **ship with prominent release notes.**
|
||||||
|
|
||||||
|
### Files touched
|
||||||
|
- `src/rpc/connection.h` / `.cpp` — `isLocalHost`, `allow_plaintext_remote`, `parseConfFile`
|
||||||
|
- `src/config/settings.h` / `.cpp` — persisted opt-in
|
||||||
|
- `src/app_network.cpp`, `src/app.h` — refuse + modal dispatch
|
||||||
|
- `src/ui/windows/plaintext_remote_rpc_dialog.h` — new blocking modal
|
||||||
|
- `src/ui/pages/settings_page.cpp` — toggle UI
|
||||||
|
|
||||||
|
### Core change
|
||||||
|
```cpp
|
||||||
|
// Tightened loopback test — "127.evil.com" is NOT local
|
||||||
|
bool Connection::isLocalHost(const std::string& host) {
|
||||||
|
std::string h = stripBrackets(lowercase(host));
|
||||||
|
return h == "localhost" || h == "::1" || isExactIPv4Loopback(h); // exact 127.x.y.z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refuse-by-default with an explicit, persisted opt-in
|
||||||
|
const bool plaintextRemote = rpc::Connection::usesPlaintextRemote(config);
|
||||||
|
const bool plaintextAllowed = config.allow_plaintext_remote // rpcallowplaintext=1
|
||||||
|
|| settings_.getAllowPlaintextRemoteRpc(); // Settings toggle
|
||||||
|
if (plaintextRemote && !plaintextAllowed) {
|
||||||
|
connection_status_ = TR("sb_plaintext_remote_blocked");
|
||||||
|
showPlaintextRemoteRpcDialog(config.host + ":" + config.port); // blocking modal
|
||||||
|
return; // no creds sent
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- Unit: `isLocalHost` — `127.evil.com` false, `127.0.0.1`/`::1`/`localhost` true; `allowsPlaintextRemote` honors conf key + settings flag.
|
||||||
|
- Manual: remote plaintext blocked; modal fires; opt-in persists across restart.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
F7 (second extender of `ConnectionConfig`/`parseConfFile`; land after so the struct grows
|
||||||
|
once). Wire `renderPlaintextRemoteRpcDialog` into the app modal-dispatch list.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Shared helpers & coordination points
|
||||||
|
|
||||||
|
| Helper | Purpose | Used by |
|
||||||
|
|--------|---------|---------|
|
||||||
|
| `Platform::ensureDirectory()` | Single non-throwing directory-create with one consistent message; replaces five ad-hoc sites. Owned by F7. | F7, F5, F6 |
|
||||||
|
| `ConnectionConfig` extension | Coordination point, not a function: F7 adds `dir_error`, F8 adds `allow_plaintext_remote`. Land F7→F8 so it grows once per step. | F7, F8 |
|
||||||
|
| `util::sha256Hex` *(existing)* | Already-compiled, curl-free SHA-256. F6 becomes its third caller — no second hash routine. | F6 |
|
||||||
|
| `connectHasStalled()` *(new, pure)* | Stall predicate split out of the ImGui/App code for unit testing, per the `*_updater_core.cpp` precedent. | F3 |
|
||||||
|
| `evaluateDatadirLockGate()` *(new, pure)* | Lock-gate decision as `{proceed, message}` from three booleans — unit-testable without real process/fs I/O. | F4 |
|
||||||
|
|
||||||
|
## F3 — Unbounded connect spinner (deferred to step 6)
|
||||||
|
|
||||||
|
**Severity:** Medium · **Effort:** S (~3–5h) · **Status:** ☑ landed & verified
|
||||||
|
|
||||||
|
> **As-built note.** `renderLoadingOverlay()` is a pure draw-list overlay with **no interactive
|
||||||
|
> widgets** (the existing crash case at ~5289 already communicates via guidance *text*, relying on
|
||||||
|
> the sidebar staying reachable). So rather than inject `ActionButton`s — which would fight the
|
||||||
|
> non-interactive overlay — the stall notice follows that same idiom: a "Taking longer than
|
||||||
|
> expected" title + a reassuring body (with elapsed seconds) + a full-node-gated hint ("Open
|
||||||
|
> Settings → Restart Daemon, or check the Console"). This let me drop the planned
|
||||||
|
> `WalletState::connect_stalled` flag too: the stalled state is computed locally in the overlay
|
||||||
|
> from `connect_stall_since_`, so the only new member is `App::connect_stall_since_`.
|
||||||
|
|
||||||
|
The connect loop retries forever while `!state_.connected` (`app.cpp:1239`);
|
||||||
|
`loading_timer_` only animates the spinner. Stamp `connect_stall_since_` when
|
||||||
|
"reachable but not ready" is first seen; a pure `connectHasStalled()` helper (new
|
||||||
|
`util/connect_stall.h`, default 45s from `ui.toml`) flips `state_.connect_stalled` at
|
||||||
|
threshold, and `renderLoadingOverlay()` shows a "Taking longer than expected" panel with
|
||||||
|
Retry / Restart daemon / Open console (full-node gated). The background retry keeps
|
||||||
|
firing — recovery clears the panel automatically. Guarded off while the daemon is in
|
||||||
|
`State::Error` (owned by F1's crash-count hint). Full detail lives in the sequencing/
|
||||||
|
design record; see the shared-helper table above.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-cutting notes
|
||||||
|
|
||||||
|
- **One TU, three functions.** `embedded_daemon.cpp` is edited by F1 (`isRunning`),
|
||||||
|
F2 (`startProcess`) and F4 (`start`) — no literal hunk overlap, but land in order to
|
||||||
|
keep "monitorProcess is the sole reaper" coherent.
|
||||||
|
- **Connection struct grows twice.** `connection.h/.cpp` is touched by F6, F7 and F8;
|
||||||
|
F7 and F8 both extend `ConnectionConfig` and `parseConfFile` — highest collision risk.
|
||||||
|
Sequence F7→F6→F8.
|
||||||
|
- **Testability split.** The three new pure predicates all get `tests/test_phase4.cpp`
|
||||||
|
coverage. F1/F2's fork/exec/waitpid changes are **not** unit-testable — they rely on
|
||||||
|
manual `kill` / non-executable-binary repros, consistent with the no-process-spawn harness.
|
||||||
|
- **i18n is additive-only.** Add each finding's English keys to `strings_`, then run
|
||||||
|
`scripts/add_missing_translations.py` **once at the very end**
|
||||||
|
(`json.dump indent=4, sort_keys=True, ensure_ascii=False`) — never bulk-regenerate a
|
||||||
|
`res/lang/*.json`.
|
||||||
|
- **F8 is a breaking default flip.** Refuse-plaintext-by-default stops existing
|
||||||
|
remote-RPC users cold until they opt in. Lands last, gated behind a persisted opt-in,
|
||||||
|
with release notes calling out the new `rpcallowplaintext` key and the Settings toggle.
|
||||||
|
- **Latent hazard, out of scope.** F1 surfaces (but doesn't fix) a second
|
||||||
|
double-`waitpid` window between `stop()`'s final blocking reap (`:1220`) and a
|
||||||
|
mid-sleep monitor iteration — file it as its own ticket.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress log
|
||||||
|
|
||||||
|
- **F1/F2 integration tests** — ☑ added `testExecFailureReported` (F2) and `testDaemonCrashDetected` (F1) to `test_phase4.cpp`, driving the **real** `EmbeddedDaemon` fork/exec/waitpid code headlessly (POSIX; required linking `embedded_daemon.cpp` into the test target — its deps were already there). The F1 test hammers `isRunning()` from the test thread while the child exits, so it's a genuine regression test for the reap race. **The F2 test caught a real bug:** `start()`'s failure branch overwrote `startProcess()`'s precise `last_error_` ("…not executable or wrong architecture") with a generic "Failed to start dragonxd process" (because `setState(Error, …)` stores its message into `last_error_`), so the precise reason never reached `getLastError()`/the UI — **fixed** to preserve the detail (now also surfaced via the state callback / crash panel). Build-clean; `ctest` 1/1.
|
||||||
|
|
||||||
|
- **F1** — ☑ landed: `isRunning()` (POSIX) now reads the atomic `state_` (predicate `Running || Stopping`) instead of calling `waitpid`, leaving `monitorProcess()` the sole reaper. Clean build (all targets link); `ctest` 1/1 passing. Not unit-testable — needs the manual `kill -SEGV` repro before release.
|
||||||
|
- **F2** — ☑ landed: `startProcess()` (POSIX) now creates a `FD_CLOEXEC` self-pipe before `fork()`; the child writes `errno` to it on `execv` failure, the parent reads EOF-vs-errno and, on failure, reaps the zombie + sets a precise `last_error_` ("not executable or wrong architecture") + returns `false` (so `start()` no longer reports `Running` for a daemon that never started). Parent-side `setpgid` is now best-effort with a `DEBUG_LOGF` on failure. Clean build; `ctest` 1/1 passing. Not unit-testable — needs the manual non-executable / wrong-arch-binary repro before release.
|
||||||
|
- **F8** — ☑ landed: `isLocalHost()` tightened to exact loopback via `isExactIPv4Loopback` (a `127.`-prefixed *hostname* like `127.evil.com` is no longer misclassified as local). `tryConnect()` now **refuses** a plaintext connection to a remote host instead of warn-and-proceeding — a local-network MITM can no longer capture `rpcuser:rpcpassword` — unless the user opts in with `rpcallowplaintext=1` in `DRAGONX.conf` (new `ConnectionConfig::allow_plaintext_remote` + `allowsPlaintextRemote()` policy). The refusal surfaces via status line + a one-time notification. New `testIsLocalHost` (12 assertions) + `testAllowsPlaintextRemote` (5). Clean build; `ctest` 1/1 passing. **Breaking — needs release notes; Settings-toggle UI deferred (see as-built note).**
|
||||||
|
- **F3** — ☑ landed: the connect loop now stamps `connect_stall_since_ = ImGui::GetTime()` the moment the daemon first goes "reachable but not ready" (warmup branch + `applyDaemonInitStatus`), and clears it in `onConnected` / `onDisconnected` / warmup-complete — all in `app_network.cpp`. The pure `util::connectHasStalled(stallSince, now, threshold)` helper (new `util/connect_stall.h`, default 45 s from `ui.toml`) drives a draw-list "Taking longer than expected" notice in `renderLoadingOverlay()` (title + elapsed-seconds body + full-node hint), guarded off while the daemon is in `State::Error`. Background retry continues, so the notice self-clears on connect. New `testConnectHasStalled` unit test (7 assertions). Clean build; `ctest` 1/1 passing. (Draw-list text, not buttons — see as-built note above.)
|
||||||
|
- **F6** — ☑ landed: `verifySaplingParams()` now hash-verifies each Sapling param against its pinned canonical SHA-256 (from `build-lite-backend-artifact.sh`), replacing the existence-only check, so a truncated/corrupt-but-present param is rejected instead of failing later on a shielded op. A `<params_dir>/.sapling_verified` marker keyed on `size:mtime` skips re-hashing ~48 MB on every startup. Logic extracted to the injectable `verifySaplingParamsIn(dir, digests)`; new `testVerifySaplingParams` unit test (valid / marker fast-path / wrong-hash / truncated / missing). Clean build; `ctest` 1/1 passing.
|
||||||
|
- **F5** — ☑ landed: `startEmbeddedDaemon()` now checks `extractEmbeddedResources()`'s return (abort with `sb_daemon_extract_failed` on failure) and the previously-dropped `copy_file` `error_code` in the daemon-binary fallback loop (abort with `sb_daemon_files_failed` incl. the dir), so a disk-full / truncated `dragonxd` write is surfaced up front instead of failing opaquely at spawn. An absent source file stays non-fatal. Two i18n keys added to `i18n.cpp`. Clean build; `ctest` 1/1 passing.
|
||||||
|
- **F7** — ☑ landed: new non-throwing `Platform::ensureDirectory(dir, outError)` in `util/platform.{h,cpp}` with one consistent message. Replaces the unchecked/throwing directory-create sites at `main.cpp:730` (pre-init: now logs + `MessageBoxA` on Windows + `return 1`), `connection.cpp:216` (autoDetectConfig now uses the ec overload — **no more uncaught `filesystem_error`** — and sets the new `ConnectionConfig::dir_error`), and both `app.cpp` daemon-dir sites (surface via `daemon_status_` + `return false`). Primary connect path (`app_network.cpp:243`) checks `dir_error` and bails to the status line instead of mislabelling it "waiting for config". `embedded_resources.cpp:270` left as-is (already correct). New `testPlatformEnsureDirectory` unit test (existing-dir / fresh-nested / empty / parent-is-file). Clean build; `ctest` 1/1 passing.
|
||||||
|
- **F4** — ☑ landed: `start()` now gates on a lingering datadir lock after the port bail. When `!skip_port_check_ && override_datadir_.empty()`, it polls `isDaemonProcessRunning()` with a bounded ~300 ms wait (3 × 100 ms, breaks early), then a pure header-inline `evaluateDatadirLockGate()` decides: if a sibling `dragonxd` is still alive it bails with a distinct **non-crash** `State::Error` ("…holding the data directory lock. Retrying shortly…") that never touches `crash_count_`, so the 3-strike cap can't trip; the connect loop's retry resumes once the lock clears. Isolated migrate-to-seed starts are exempt. New `testDatadirLockGate` unit test (5 assertions, proceed/bail/2× exempt) added to `test_phase4.cpp`. Clean build; `ctest` 1/1 passing.
|
||||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
167
docs/wallet-hardening.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Wallet Loading & Management — Hardening Plan
|
||||||
|
|
||||||
|
Prioritized, grouped remediation for the wallet loading/management audit (33 verified findings +
|
||||||
|
diagnosability QoL). Companion to the findings artifact. Line references are against `dev`.
|
||||||
|
|
||||||
|
- **Provenance:** 7 parallel subsystem finders, each finding adversarially verified against the
|
||||||
|
code; the 3 highest-impact confirmed findings re-checked by hand. 32 confirmed, 1 refuted
|
||||||
|
(W1-5), 1 raised (W5-3 Low→Med).
|
||||||
|
- **Severity:** 8 High · 12 Medium · 13 Low.
|
||||||
|
|
||||||
|
Status legend: ☐ not started · ◐ in progress · ☑ landed & verified
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Roadmap (ordered by risk; shared fixes grouped)
|
||||||
|
|
||||||
|
| Phase | Findings | Theme | Status |
|
||||||
|
|-------|----------|-------|--------|
|
||||||
|
| **P0-A** | W7-1, W2-1, W4-1, W4-3, W2-3, W4-5, W5-3 ✓ | Secret hardening (console redaction + delete-export + memzero + lite encrypt-at-create) | ☑ 7/7 |
|
||||||
|
| **P0-B** | W2-2/W4-2, W2-4 | Encryption integrity (never silently unencrypted) | ☑ |
|
||||||
|
| **P1-A** | W3-1, W3-2, W3-4, W3-3 ✓ | Migrate-to-seed correctness (fund-adjacent) | ☑ 4/4 (W3-3 pending a live-mainnet run) |
|
||||||
|
| **P1-B** | W1-1, W1-2, W1-3, W1-4 ✓ + startup guard | Missing/wrong wallet-file safety | ☑ |
|
||||||
|
| **P2** | W5-1, W5-2, W6-1, W6-3, W6-2 ✓ | Stale state & lite save-failure surfacing | ☑ 5/5 |
|
||||||
|
| **F** | W7-2, W7-3, W7-4 ✓ · QoL: copy-diag + open-log + node-error-banner + staleness-badge + alert-history ✓ | Diagnostics foundation + QoL bundle | ☑ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## P0-A — Secret hardening
|
||||||
|
|
||||||
|
Shared fix: a `SecureString` RAII buffer (zeroes on destruction) retrofitted onto the un-scrubbed
|
||||||
|
key/passphrase paths, plus console redaction and deleting the plaintext export.
|
||||||
|
|
||||||
|
- **W7-1 (High)** `console_tab.cpp:1419` — RPC console echoes/stores/clipboards raw secrets. Fix: an
|
||||||
|
allowlist of secret-bearing first-tokens (`walletpassphrase`, `walletpassphrasechange`,
|
||||||
|
`encryptwallet`, `importprivkey`, `importwallet`, `z_importkey`, `z_importviewingkey`,
|
||||||
|
`signrawtransaction`, `magicrecoverkey`, lite equivalents); echo `> walletpassphrase ****` and
|
||||||
|
keep the raw text out of `command_history_`. Extract a pure `redactConsoleCommand(cmd)` helper for
|
||||||
|
unit testing. **← implementing first (self-contained + testable).**
|
||||||
|
- **W2-1 (High)** `wallet_security_workflow.cpp:66` — delete the `obsidiandecryptexport<ts>` plaintext
|
||||||
|
key dump after `z_importwallet` succeeds (overwrite-then-unlink).
|
||||||
|
- **W4-3 (High)** `app_network.cpp:4481` — `sodium_memzero` the concatenated all-keys string in
|
||||||
|
`exportAllKeys`; write the backup 0600. (Also unify with `ExportAllKeysDialog` — QoL.)
|
||||||
|
- **W4-1 (High)** `app_network.cpp:3801` — zero the key copies in `importPrivateKey`/`sweepPrivateKey`
|
||||||
|
(local + worker-lambda copies).
|
||||||
|
- **W2-3 (Med)** `app_security.cpp:1481` — zero the passphrase threaded through the decrypt lambda chain.
|
||||||
|
- **W4-5 (Med)** `app.cpp:3577` — the seed-backup `.txt` is a permanent predictable cleartext seed;
|
||||||
|
at minimum warn + offer to delete, ideally discourage file save in favor of the on-screen phrase.
|
||||||
|
- **W5-3 (Med)** `lite_wallet_lifecycle_service.cpp:322` — remove the dead `passphrase` field from the
|
||||||
|
lite create/open/restore requests (unused; a secret copied for nothing).
|
||||||
|
|
||||||
|
## P0-B — Encryption integrity
|
||||||
|
|
||||||
|
- **W2-2 / W4-2 (High)** `wallet_security_controller.h:89` — the wizard's deferred encryption is
|
||||||
|
in-memory only and silently lost if the daemon doesn't connect or the app quits/crashes first, so a
|
||||||
|
wallet the user believes is encrypted stays plaintext. Fix: persist a lightweight
|
||||||
|
`encryption_requested_but_incomplete` settings flag (NEVER the passphrase) when
|
||||||
|
`beginDeferredEncryption` is called; surface a persistent warning banner while it's set; clear it
|
||||||
|
only on confirmed `encryptwallet` success; on next connect, if set, re-prompt for the passphrase to
|
||||||
|
complete it.
|
||||||
|
- **W2-4 (Med)** `app_security.cpp:480` — `lockWallet` only sets `locked` on RPC success; log the
|
||||||
|
failure and notify (currently a silent no-op that can leave the wallet unlocked).
|
||||||
|
|
||||||
|
## P1-A — Migrate-to-seed correctness (fund-adjacent; verify carefully)
|
||||||
|
|
||||||
|
- **W3-1 (High)** `app_network.cpp:4327` — adopt hardcodes `datadir + "/wallet.dat"`; use
|
||||||
|
`settings_->getActiveWalletFile()` so migrating a non-default active wallet swaps the right file.
|
||||||
|
- **W3-2 (High)** `seed_wallet_creator.cpp:57` — `remove_all(<config>/seed-migrate)` unconditionally
|
||||||
|
at Phase-1 start; refuse to wipe if a temp `DRAGONX/wallet.dat` already exists (a prior un-adopted
|
||||||
|
swept wallet) and surface it, so swept funds in the temp wallet can't be destroyed by re-entry.
|
||||||
|
- **W3-4 (Med)** `app_network.cpp:1124` — block wallet switching while a migration is *pending*
|
||||||
|
(`getSeedMigrationPending()`), not only while the dialog is open.
|
||||||
|
- **W3-3 (Med)** `app_network.cpp:4231` — persist the sweep opid so an app-close mid-Sweeping can
|
||||||
|
resume/re-poll it instead of silently dropping the txid.
|
||||||
|
|
||||||
|
## P1-B — Missing/wrong wallet-file safety
|
||||||
|
|
||||||
|
- **W1-1 (High)** `app_network.cpp:1109` — `fs::exists()`-check the target wallet file in
|
||||||
|
`switchToWallet()` and before the first daemon launch at startup; if missing, block with an explicit
|
||||||
|
"Wallet file not found — moved or deleted?" dialog (browse / create-new) instead of letting the
|
||||||
|
daemon fabricate an empty wallet.
|
||||||
|
- **W1-3 (Med)** `app_network.cpp:1095` — defer the `syncedHere=true` stamp to the first successful
|
||||||
|
address/balance readback (idHash non-empty), not bare `onConnected()`.
|
||||||
|
- **W1-2 (Med)** `app_network.cpp:198` — split `DB_CORRUPT`-specific strings from the generic "Error
|
||||||
|
loading wallet" fallback; give `DB_TOO_NEW` its own message/action (not a salvage offer).
|
||||||
|
- **W1-4 (Low)** `wallets_dialog.h:393` — re-`fs::exists()` the in-datadir row before switching (match
|
||||||
|
the out-of-datadir path).
|
||||||
|
|
||||||
|
## P2 — State & lite persistence
|
||||||
|
|
||||||
|
- **W6-2 (Med)** `network_refresh_service.cpp:1183` — record a per-field last-success timestamp / a
|
||||||
|
"refresh failed" flag so the UI can show a staleness badge instead of last-good-as-current.
|
||||||
|
- **W5-1 / W5-2 (Med)** `lite_wallet_controller.cpp:78,603` — `liteLog()` the failed save and bubble a
|
||||||
|
one-shot UI warning (both call sites currently discard the bool).
|
||||||
|
- **W6-1 (Med)** `wallet_state.h:313` — reset `mining`/`pool_mining` in `clear()` (or comment why not).
|
||||||
|
- **W6-3 (Low)** `address_book.cpp:46` — per-entry try/catch: skip + count malformed entries instead
|
||||||
|
of discarding the whole list.
|
||||||
|
|
||||||
|
## F — Diagnostics foundation + QoL
|
||||||
|
|
||||||
|
Land W7-2 first — it unblocks the rest.
|
||||||
|
|
||||||
|
- **W7-2 (Med)** `logger.cpp:31` — call `Logger::instance().init(<config>/dragonx-debug.log)` early in
|
||||||
|
`main()` on all platforms; add an "Open log folder" action.
|
||||||
|
- **W7-3 (Med)** `main.cpp:144` — add a `sigaction`-based crash handler writing `dragonx-crash.log` on
|
||||||
|
POSIX (mirror the Windows SEH path).
|
||||||
|
- **W7-4 (Low)** `logger.cpp:39` — size-cap/rotate the log on `init()`.
|
||||||
|
- **QoL** — "Copy diagnostics for support" bundle; persistent alert history; daemon/RPC error banner;
|
||||||
|
refresh-staleness badge; multi-wallet diagnostic panel; refresh-diagnostics panel; structured
|
||||||
|
switch/migration audit logging; restore-from-seed entry point (W4-4, effort L).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress log
|
||||||
|
|
||||||
|
- **Adversarial review of the 3 diagnostics UI features** — ran a 5-dimension finder → per-finding verify workflow over the node-banner + staleness-badge + alert-history commits (the hand-laid ImGui I couldn't visually verify). 4 confirmed, 1 refuted (banner title never overlaps its button — button is absolutely positioned + title is short), and the dedicated ImGui-stack-balance finder found **no** Push/Pop imbalance. Fixes landed:
|
||||||
|
- **(Med) Alert popup grew off the right edge** — pivot `(0,1)` pinned the panel's *left* edge at the bell (which sits near the window's right edge), so a 320px panel overflowed rightward (an explicit `SetNextWindowPos` pivot skips ImGui's on-screen clamp). Fixed to anchor the bottom-*right* corner at the bell (pivot `(1,1)`, at `bellMax.x`) so it grows left over the canvas.
|
||||||
|
- **(Low) Staleness badge could flash red on reconnect** — `WalletState::clear()` reset everything *except* the four `last_*_update` stamps, so after a reconnect the pre-outage timestamp survived and the badge briefly showed "Updated Nm ago" (red) on the same frame the node banner cleared — the exact contradiction the design forbids. Fixed by zeroing the four stamps in `clear()` (all readers treat 0 as "never"; verified `app_network.cpp:1473` guards on `!= 0`).
|
||||||
|
- **(Low) Banner min-height floor wasn't DPI-scaled** — `std::max(minH, baseH*vScale())` compared a raw-px floor against a scaled value; now `minH * dpiScale()`.
|
||||||
|
- **(Low) New i18n keys weren't in `res/lang/`** — back-filled all 16 diagnostics/QoL keys (this session's node_banner_*/data_stale_*/alerts_*/settings_*/tt_*) into all 8 language files, additively (128 insertions, 0 deletions). zh/ja/ko reworded around 2 glyphs missing from the CJK subset (提醒→通知; ko tooltip avoids 닐) and hard-asserted tofu-free against the subset font.
|
||||||
|
- **Foundation QoL / Persistent alert history** — ☑ landed. Toasts fade in 1–4s; there was no way to review what scrolled past. `Notifications` now retains every pushed alert in a capped (100) ring buffer with a wall-clock epoch (`AlertRecord`) — separate from the 5-item live-toast deque — plus a monotonic `total_pushed_` counter. A bell in the status-bar right cluster (`ICON_MD_NOTIFICATIONS`) opens an upward popup listing recent alerts newest-first with a severity icon/colour (reusing the toast palette), the message, and a relative age (`formatTimeAgoShort`), with a Clear-all action. An **unread dot** on the bell (coloured by the most-severe unseen alert) marks alerts that arrived since the panel was last opened — driven by `totalPushed()` deltas so it survives capping/clearing. Thread-safety: every push is on the UI thread (RPC results run as main-thread `MainCb`s), matching the class's existing lock-free model — documented as a no-raw-worker-thread invariant. Build-clean; `ctest` 1/1 (adds `testNotificationHistory`: retention, order, cap, monotonic counter, clear). **This closes the QoL bundle and the Foundation tier.**
|
||||||
|
- **W6-2 / Refresh-staleness badge** — ☑ landed. The Total Balance card now shows a small pill on its status line ("Updated 2m ago", amber → red past 3 min) **only when connected but the balance stopped refreshing** — a busy daemon can fail `z_gettotalbalance` without dropping the whole connection (only *both* core RPCs failing 3× triggers a disconnect), leaving stale numbers on screen while the node-status banner stays hidden. No refresh-path changes were needed: `WalletState::last_balance_update` is already stamped only on a successful fetch (`network_refresh_service.cpp:1187`), so the badge just reads it and computes age against the same `std::time` clock (`util::formatTimeAgoShort`). Decision is a pure, unit-tested helper (`ui/staleness_badge.h::evaluateStalenessBadge`, thresholds 45s/180s) gated on `connected` so it never contradicts the banner; hover shows a "may be out of date — check your node connection" tooltip. Build-clean; `ctest` 1/1 (adds `testStalenessBadge`). **This closes P2 (5/5).**
|
||||||
|
- **Foundation QoL / Persistent node-status banner** — ☑ landed. A persistent horizontal strip now sits at the top of the content column whenever the wallet can't reach its node — distinct from the transient toasts, so an offline wallet is never silently mistaken for a working one. The show/severity/action decision is a pure function (`ui/node_status_banner.h` → `evaluateNodeStatusBanner`, unit-tested) fed a state snapshot by `App::renderNodeStatusBanner()`. Three cases: **full-node offline** (amber, "Reconnect" → `tryConnect`), **embedded daemon crashed & auto-restart gave up** (red, "Restart node" → `restartDaemon`), **lite wallet failed to open** (red, message-only). Suppressed during the wizard / wallet-switch / daemon-restart / screenshot-sweep / shutdown, and while an expected startup phase (warmup/init/connect-in-progress) already owns the screen. Height in `res/themes/ui.toml` (`banners.node-status`); colours from the material semantic palette; detail text ellipsis-clipped so it can't shove the action button off-screen. Build-clean; `ctest` 1/1 (added `testNodeStatusBanner`). **Remaining QoL:** persistent alert history, and the W6-2 refresh-staleness badge.
|
||||||
|
- **Foundation QoL / "Copy diagnostics" + "Open log folder"** — ☑ landed: Settings (logging section) now has two actions. **Open log folder** opens the config dir (`Platform::openFolder`) so users can actually find `dragonx-debug.log`/`dragonx-crash.log`. **Copy diagnostics** copies a plaintext support snapshot to the clipboard via the new `App::buildDiagnosticsReport()` — version, build variant, platform, connection status, active wallet path + existence + size, encryption/lock state, sync heights, daemon status/running/crash-count/lastError (full-node), and the log paths. No secrets. Build-clean; `ctest` 1/1. **Remaining QoL:** persistent alert history, a daemon/RPC error banner, and the W6-2 refresh-staleness badge.
|
||||||
|
- **Foundation / W7-2 · W7-3 · W7-4 (diagnostics infrastructure)** — ☑ landed (answers the original "easier to diagnose" ask — the logging/crash foundation now actually works):
|
||||||
|
- **W7-2 (Med, keystone):** the app-level `Logger` file sink was never initialized, so `LOG`/`LOGF`/`VERBOSE_LOGF` went nowhere and `dragonx-debug.log` didn't exist on Linux/macOS at all. `main()` now calls `Logger::init(<config>/dragonx-debug.log)` on all platforms. Also fixed a **latent deadlock** this exposed: `init()` wrote its banner via `write()`, which re-locks the non-recursive `mutex_` it already holds — now written directly. On Windows the raw stdout/stderr `freopen` was moved to a separate `dragonx-stdout.log` so the two writers don't contend. New `testLoggerFileSink` (also a deadlock guard — it would hang if that regressed).
|
||||||
|
- **W7-3 (Med):** no crash handler existed on Linux/macOS. Added an **async-signal-safe** `sigaction` handler (SIGSEGV/ABRT/BUS/FPE/ILL) that writes a signal id + `backtrace_symbols_fd` backtrace to `dragonx-crash.log`, then re-raises the default disposition for a core dump — the POSIX counterpart of the Windows SEH filter.
|
||||||
|
- **W7-4 (Low):** `Logger::init` now rotates the log to a single `.1` backup when it exceeds 10 MB, so a long/verbose session can't grow it unbounded.
|
||||||
|
Build-clean; `ctest` 1/1. **Remaining Foundation:** the QoL bundle (mostly UI) — "copy diagnostics for support", an "open log folder" action, persistent alert history, a daemon/RPC error banner, and the W6-2 refresh-staleness badge.
|
||||||
|
- **P2 / W5-1 · W5-2 · W6-1 · W6-3 (localized batch)** — ☑ landed:
|
||||||
|
- **W5-1 (Med):** `persistAfterBroadcast` (lite send/shield save) returned false on a persistent save failure but both callers discarded it and it never logged — completely silent. It now `liteLog`s the failure (the note re-derives on next sync, so it's a robustness gap, not fund loss).
|
||||||
|
- **W5-2 (Med):** the post-**sync** and post-**rescan** `save` results (in the detached scan threads) were ignored; both now `liteLog` on failure (`LiteDiagnostics::log` is mutex-guarded, safe from those threads).
|
||||||
|
- **W6-1 (Med):** `WalletState::clear()` didn't reset `mining`/`pool_mining`, so a wallet switch could briefly show the previous wallet's hashrate/blocks. Now reset in `clear()` (the daemon restarts on switch, so mining genuinely stops).
|
||||||
|
- **W6-3 (Low):** `AddressBook::load()` did `entries_.clear()` then threw on the first non-object element — discarding **every** contact. Now it guards `is_object()` + per-entry try/catch, skipping and counting malformed entries.
|
||||||
|
Build-clean; `ctest` 1/1. **Remaining P2:** W6-2 (surface refresh staleness — the timestamps exist in `WalletState`; this needs the UI "updated Xs ago" badge, which overlaps the diagnostics/QoL Foundation bundle).
|
||||||
|
- **P1-B / W1-3 + startup wallet-existence guard** — ☑ landed:
|
||||||
|
- **W1-3 (Med):** `syncedHere` was stamped in the `markOpened` block at bare connect (idHash still empty), letting a freshly-restored wallet skip its needed rescan. It's now stamped only once the identity is verified (idHash non-empty), so it takes effect at the post-address-refresh index update (`updateWalletIndexForActiveWallet` after addresses load), while `lastOpenedEpoch` still records at open.
|
||||||
|
- **Startup guard (the W1-1 launch counterpart):** `App::init` now `exists()`-checks the recorded active wallet before the daemon is configured; a **non-default** active wallet that was moved/deleted between sessions falls back to the default `wallet.dat` with a warning, instead of the daemon silently auto-creating an empty wallet under the missing name. Runs before the PIN-vault init so the vault is scoped to the wallet actually opened.
|
||||||
|
Build-clean; `ctest` 1/1.
|
||||||
|
- **P1-A / W3-3 (sweep opid persistence)** — ☑ **implemented + two rounds of adversarial review** (the "live mainnet run" the migration code mandates is the remaining gate — see below). The deferral's core fear (re-tracking a stale opid hangs forever) was **refuted by the code**: the opid poller (`app.cpp:1122`) + `parseOperationStatusPoll` classify a tracked opid absent from a *successful* `z_getoperationstatus` as stale, remove it, and fire the callback `ok=false` — a thrown RPC aborts the poll so there's never a *false* stale. So re-tracking yields at worst one clean failure, never a hang.
|
||||||
|
- **What landed:** a persisted `seed_migration_sweep_opid` setting; the opid is adopted **atomically** with clearing any prior txid in the *same* `settings.save()` **only once the submit succeeds** (torn-write safe; txid always outranks opid on resume). Resume routing is a pure, unit-tested helper (`data/seed_migration_resume.h::decideSeedMigrationResume`): txid → Confirming; opid **and connected** → re-track (`Sweeping`); otherwise → the dismissable Sweep gate. The shared `makeSweepCompletionCallback(resumed)`: success → Confirming; resumed-stale → Sweep gate (re-fetch balance, honest "may have already completed" copy); fresh-fail → Error.
|
||||||
|
- **Round 1 (design review, 4 skeptics)** confirmed both safety facts (no fund loss — adopt gate + never-deleted `.bak` untouched; no hang) and caught 3 real resume-UX traps, all fixed: a missing **connectivity gate** (would trap the user in the buttonless `Sweeping` spinner while offline), a **missing balance re-fetch** on the stale fallback (permanent "Checking balance…"), and honest messaging since a daemon restart makes even a *successful* sweep read "stale".
|
||||||
|
- **Round 2 (implementation review, 3 reviewers)** caught one regression — clearing the old txid at sweep *entry* would forget an already-mined first sweep if a remainder re-sweep's submit failed; fixed by the atomic-on-success swap above. All other fixes verified present + correct.
|
||||||
|
- **⚑ Remaining gate — live mainnet run (user):** per CLAUDE.md this fund-moving path must be exercised once on mainnet before it ships. The self-verifiable parts (build, unit test, both review rounds) are green; a real interrupted-sweep resume on mainnet is the human gate I cannot perform.
|
||||||
|
- **P1-B / W1-1 (+ W1-4) · W1-2 (wallet-file safety)** — ☑ landed:
|
||||||
|
- **W1-1 (High):** `switchToWallet` never checked the target wallet file exists, so a moved/deleted file "opened" as a fresh empty wallet (dragonxd auto-creates for a missing `-wallet=`), looking exactly like fund loss. It now `std::filesystem::exists`-checks `datadir + "/" + walletFile` before switching and blocks with a "not found (moved or deleted?)" warning. Placed before the daemon-stop prompt, and — since the check runs no matter how `switchToWallet` is invoked — it also **closes W1-4** (the stale-switcher-row TOCTOU).
|
||||||
|
- **W1-2 (Med):** `walletOutputLooksCorrupt` matched the generic "Error loading wallet" string, so a `DB_TOO_NEW` (newer-version) wallet was offered a `-salvagewallet` repair that can't fix it. Now the generic match is excluded when the output also contains "newer version".
|
||||||
|
Build-clean; `ctest` 1/1. **Remaining P1-B:** W1-3 (defer the `syncedHere` stamp to a verified readback) + the startup-path existence check (`app.cpp` hands `getActiveWalletFile()` to the daemon with no `exists()` check — same silent-empty-wallet risk as W1-1 but at launch).
|
||||||
|
- **P1-A / W3-1 · W3-2 · W3-4 (migrate-to-seed correctness)** — ☑ landed (fund-adjacent — reviewed carefully):
|
||||||
|
- **W3-1 (High):** `beginAdoptSeedWallet` hardcoded `datadir + "/wallet.dat"` as the file to swap. With a non-default active wallet (e.g. `wallet-2.dat`), that installed the swept seed wallet into an unloaded `wallet.dat` and left the daemon reloading the emptied legacy — funds only recoverable via the seed phrase. Now swaps `datadir + "/" + getActiveWalletFile()` (captured on the main thread; switching is blocked during migration so it can't race).
|
||||||
|
- **W3-2 (High):** `SeedWalletCreator::create` did `remove_all(<config>/seed-migrate)` unconditionally at the start. A prior migration that swept funds into the temp wallet but was abandoned/crashed before adopting would have that fund-bearing wallet destroyed. It now refuses (with a clear message) when `DRAGONX/wallet.dat` already exists — a completed migration removes the dir on adopt, so a leftover means an unfinished one.
|
||||||
|
- **W3-4 (Med):** `switchToWallet` only blocked switching while the migration *dialog* was open; closing it via "Later" mid-migration dropped the guard. Now also blocks while `getSeedMigrationPending()`.
|
||||||
|
Build-clean; `ctest` 1/1. **Remaining P1-A:** W3-3 (persist the sweep opid so an app-close mid-sweep can resume/re-poll instead of silently dropping the txid).
|
||||||
|
|
||||||
|
- **P0-B / W2-2 (deferred encryption silently lost) + W2-4 (auto-lock silent-fail)** — ☑ landed:
|
||||||
|
- **W2-2:** the wizard's deferred encryption was stored only in memory, so a quit/crash or a failed daemon connect before it applied left the wallet unencrypted with **no record it was ever requested** — the user believing it was encrypted. Now a persisted `encryption_pending` settings flag is set the moment encryption is requested (**never the passphrase** — only the fact). `refreshWalletEncryptionState()` reconciles it on every connect: wallet observed **encrypted** → clear the flag; wallet **not** encrypted while the flag is set and no deferred encryption is pending/in-flight → a once-per-session **"your wallet is NOT encrypted — open Settings to finish"** warning (the flag stays set, so it recurs each launch until resolved). We deliberately don't persist the passphrase to auto-complete — surfacing it is the secure choice.
|
||||||
|
- **W2-4:** `lockWallet()`'s continuation only handled success — a failed `walletlock` silently left the wallet **unlocked** (an unfulfilled auto-lock). It now logs and warns once (reset on the next successful lock), so a failing auto-lock is visible instead of leaving the wallet exposed.
|
||||||
|
Touches `settings.{h,cpp}`, `app_wizard.cpp`, `app_security.cpp`, `app.h`. Not unit-testable at this layer (RPC/connect-driven state machine); build-clean, `ctest` 1/1.
|
||||||
|
|
||||||
|
- **P0-A / W5-3 (lite create-time passphrase)** — ☑ landed (chose option **(b) wire it up**). The lite create/open/restore passphrase was collected but never consumed by the backend — a "passphrase" field that did nothing. It now has a real meaning for all three operations, in `LiteWalletController`: **create/restore** → `encryptWallet(passphrase)` (the backend encrypts + locks + saves the brand-new wallet); **open** → `unlockWallet(passphrase)`, but only when `encryptionStatus()` reports the existing wallet is actually encrypted+locked (skips a spurious unlock otherwise). Encrypt/unlock take their own copy and wipe it; a post-create encrypt failure is `liteLog`'d (the wallet still exists — the create isn't failed). Six existing lite-controller tests carried an incidental `hunter2` create passphrase from the dead-field era; removed (they test non-encryption flows and want an unencrypted wallet), and added `testLiteWalletControllerCreateEncryptsWithPassphrase` to prove the new behavior. Build-clean; `ctest` 1/1. *(Follow-up UX polish: `settings_page` could show the passphrase field's meaning per operation — "encrypt" for create/restore vs "unlock" for open.)*
|
||||||
|
- **P0-A / W4-5 (seed-backup file)** — ☑ landed (proportionate): the seed "Save" already wrote 0600 + zeroed the in-memory buffer, but the success message was a bare "Saved to <path>". It now reads "**Saved an UNENCRYPTED seed file — move it to secure offline storage and delete this copy**: <path>", so the plaintext-on-disk risk is called out. `i18n.cpp` (English source; `res/lang` back-fill of this changed key is deferred to the batch i18n pass). A stronger fix (pre-save confirmation, or dropping the file-save in favor of on-screen + Copy) is a follow-up UX decision.
|
||||||
|
- **P0-A / W4-1 · W4-3 · W2-3 (memzero cluster)** — ☑ landed, using the file's established `sodium_memzero` pattern (matching the existing lambda-capture scrub at app_network.cpp:2885 and JSON scrub at :4025) rather than a new type, since this is fund-moving code:
|
||||||
|
- **W4-1** `importPrivateKey`/`sweepPrivateKey`: the spending/viewing key is now scrubbed on all paths — the calling-frame copy (after the worker post), the worker-lambda's captured copy (lambda made `mutable`, zeroed after the request is sent), and the JSON request `params` copy.
|
||||||
|
- **W4-3** `exportAllKeys`/`backupWallet`: the concatenated all-keys buffer is zeroed after the consumer uses it, and the backup file is now written via `Platform::writeFileAtomically(..., restrictPermissions=true)` (atomic + 0600) instead of a umask-default `ofstream`.
|
||||||
|
- **W2-3** decrypt-wallet passphrase: `std::move`-captured into the worker lambda (so no plaintext copy is left in the calling frame) and `sodium_memzero`'d right after `unlockWallet` (its only use).
|
||||||
|
Not unit-testable (the scrubbing has no observable RPC effect — the key value sent to the daemon is unchanged; only post-use memory zeroing is added). Build-clean; `ctest` 1/1 (no regression). **Remaining in P0-A:** W5-3 (remove the dead lite `passphrase` field), W4-5 (predictable plaintext seed-backup file).
|
||||||
|
- **P0-A / W2-1** — ☑ landed: the decrypt-wallet flow now scrubs (best-effort in-place zero-overwrite) and removes the plaintext key export (`obsidiandecryptexport…`) as soon as the `z_importwallet` attempt resolves — success or failure — so a full cleartext dump of every private key is no longer left on disk forever. Recovery remains the encrypted backup (`wallet.dat.encrypted.bak`). `app_security.cpp` (after the import call). Not unit-testable (fs I/O in a deep lambda); build-clean, `ctest` 1/1 (no regression).
|
||||||
|
- **P0-A / W7-1** — ☑ landed: `RedactConsoleCommand`/`ConsoleCommandCarriesSecret` in `console_tab_helpers` redact secret-bearing commands (an allowlist of 13 first-tokens: `walletpassphrase`, `encryptwallet`, `z_importkey`, …) to `> walletpassphrase ****` before they hit the console echo AND the recall history; the real command still executes unredacted. Wired into `submitConsoleCommand` (`console_tab.cpp`). New `testConsoleSecretRedaction` (11 assertions). Clean build; `ctest` 1/1. (Output-secret commands like `z_exportkey` — result redaction — remain a follow-up.)
|
||||||
@@ -67,7 +67,8 @@
|
|||||||
//#define IMGUI_USE_LEGACY_CRC32_ADLER
|
//#define IMGUI_USE_LEGACY_CRC32_ADLER
|
||||||
|
|
||||||
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
|
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
|
||||||
//#define IMGUI_USE_WCHAR32
|
//---- Enabled so chat can render emoji (U+1F300+, above the BMP) — see Typography::loadFont emoji merge (Q12).
|
||||||
|
#define IMGUI_USE_WCHAR32
|
||||||
|
|
||||||
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
|
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
|
||||||
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
|
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
|
||||||
|
|||||||
744
libs/imgui/misc/freetype/imgui_freetype.cpp
Normal file
@@ -0,0 +1,744 @@
|
|||||||
|
// dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder)
|
||||||
|
// (code)
|
||||||
|
|
||||||
|
// Get the latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype
|
||||||
|
// Original code by @vuhdo (Aleksei Skriabin) in 2017, with improvements by @mikesart.
|
||||||
|
// Maintained since 2019 by @ocornut.
|
||||||
|
|
||||||
|
// CHANGELOG
|
||||||
|
// (minor and older changes stripped away, please see git history for details)
|
||||||
|
// 2025/06/11: refactored for the new ImFontLoader architecture, and ImGuiBackendFlags_RendererHasTextures support.
|
||||||
|
// 2024/10/17: added plutosvg support for SVG Fonts (seems faster/better than lunasvg). Enable by using '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG'. (#7927)
|
||||||
|
// 2023/11/13: added support for ImFontConfig::RasterizationDensity field for scaling render density without scaling metrics.
|
||||||
|
// 2023/08/01: added support for SVG fonts, enable by using '#define IMGUI_ENABLE_FREETYPE_LUNASVG'. (#6591)
|
||||||
|
// 2023/01/04: fixed a packing issue which in some occurrences would prevent large amount of glyphs from being packed correctly.
|
||||||
|
// 2021/08/23: fixed crash when FT_Render_Glyph() fails to render a glyph and returns nullptr.
|
||||||
|
// 2021/03/05: added ImGuiFreeTypeBuilderFlags_Bitmap to load bitmap glyphs.
|
||||||
|
// 2021/03/02: set 'atlas->TexPixelsUseColors = true' to help some backends with deciding of a preferred texture format.
|
||||||
|
// 2021/01/28: added support for color-layered glyphs via ImGuiFreeTypeBuilderFlags_LoadColor (require Freetype 2.10+).
|
||||||
|
// 2021/01/26: simplified integration by using '#define IMGUI_ENABLE_FREETYPE'. renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. removed ImGuiFreeType::BuildFontAtlas().
|
||||||
|
// 2020/06/04: fix for rare case where FT_Get_Char_Index() succeed but FT_Load_Glyph() fails.
|
||||||
|
// 2019/02/09: added RasterizerFlags::Monochrome flag to disable font anti-aliasing (combine with ::MonoHinting for best results!)
|
||||||
|
// 2019/01/15: added support for imgui allocators + added FreeType only override function SetAllocatorFunctions().
|
||||||
|
// 2019/01/10: re-factored to match big update in STB builder. fixed texture height waste. fixed redundant glyphs when merging. support for glyph padding.
|
||||||
|
// 2018/06/08: added support for ImFontConfig::GlyphMinAdvanceX, GlyphMaxAdvanceX.
|
||||||
|
// 2018/02/04: moved to main imgui repository (away from http://www.github.com/ocornut/imgui_club)
|
||||||
|
// 2018/01/22: fix for addition of ImFontAtlas::TexUvscale member.
|
||||||
|
// 2017/10/22: minor inconsequential change to match change in master (removed an unnecessary statement).
|
||||||
|
// 2017/09/26: fixes for imgui internal changes.
|
||||||
|
// 2017/08/26: cleanup, optimizations, support for ImFontConfig::RasterizerFlags, ImFontConfig::RasterizerMultiply.
|
||||||
|
// 2017/08/16: imported from https://github.com/Vuhdo/imgui_freetype into http://www.github.com/ocornut/imgui_club, updated for latest changes in ImFontAtlas, minor tweaks.
|
||||||
|
|
||||||
|
// About Gamma Correct Blending:
|
||||||
|
// - FreeType assumes blending in linear space rather than gamma space.
|
||||||
|
// - See https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Render_Glyph
|
||||||
|
// - For correct results you need to be using sRGB and convert to linear space in the pixel shader output.
|
||||||
|
// - The default dear imgui styles will be impacted by this change (alpha values will need tweaking).
|
||||||
|
|
||||||
|
// FIXME: cfg.OversampleH, OversampleV are not supported, but generally not necessary with this rasterizer because Hinting makes everything look better.
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
#ifndef IMGUI_DISABLE
|
||||||
|
#include "imgui_freetype.h"
|
||||||
|
#include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H // <freetype/freetype.h>
|
||||||
|
#include FT_MODULE_H // <freetype/ftmodapi.h>
|
||||||
|
#include FT_GLYPH_H // <freetype/ftglyph.h>
|
||||||
|
#include FT_SIZES_H // <freetype/ftsizes.h>
|
||||||
|
#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
|
||||||
|
|
||||||
|
// Handle LunaSVG and PlutoSVG
|
||||||
|
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) && defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
|
||||||
|
#error "Cannot enable both IMGUI_ENABLE_FREETYPE_LUNASVG and IMGUI_ENABLE_FREETYPE_PLUTOSVG"
|
||||||
|
#endif
|
||||||
|
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||||
|
#include FT_OTSVG_H // <freetype/otsvg.h>
|
||||||
|
#include FT_BBOX_H // <freetype/ftbbox.h>
|
||||||
|
#include <lunasvg.h>
|
||||||
|
#endif
|
||||||
|
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
|
||||||
|
#include <plutosvg.h>
|
||||||
|
#endif
|
||||||
|
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined (IMGUI_ENABLE_FREETYPE_PLUTOSVG)
|
||||||
|
#if !((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
|
||||||
|
#error IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG requires FreeType version >= 2.12
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning (push)
|
||||||
|
#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
|
||||||
|
#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
|
||||||
|
#ifndef __clang__
|
||||||
|
#pragma GCC diagnostic ignored "-Wsubobject-linkage" // warning: 'xxxx' has a field 'xxxx' whose type uses the anonymous namespace
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// Data
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Default memory allocators
|
||||||
|
static void* ImGuiFreeTypeDefaultAllocFunc(size_t size, void* user_data) { IM_UNUSED(user_data); return IM_ALLOC(size); }
|
||||||
|
static void ImGuiFreeTypeDefaultFreeFunc(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_FREE(ptr); }
|
||||||
|
|
||||||
|
// Current memory allocators
|
||||||
|
static void* (*GImGuiFreeTypeAllocFunc)(size_t size, void* user_data) = ImGuiFreeTypeDefaultAllocFunc;
|
||||||
|
static void (*GImGuiFreeTypeFreeFunc)(void* ptr, void* user_data) = ImGuiFreeTypeDefaultFreeFunc;
|
||||||
|
static void* GImGuiFreeTypeAllocatorUserData = nullptr;
|
||||||
|
|
||||||
|
// Lunasvg support
|
||||||
|
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||||
|
static FT_Error ImGuiLunasvgPortInit(FT_Pointer* state);
|
||||||
|
static void ImGuiLunasvgPortFree(FT_Pointer* state);
|
||||||
|
static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state);
|
||||||
|
static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// Code
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define FT_CEIL(X) (((X + 63) & -64) / 64) // From SDL_ttf: Handy routines for converting from fixed point
|
||||||
|
#define FT_SCALEFACTOR 64.0f
|
||||||
|
|
||||||
|
// Glyph metrics:
|
||||||
|
// --------------
|
||||||
|
//
|
||||||
|
// xmin xmax
|
||||||
|
// | |
|
||||||
|
// |<-------- width -------->|
|
||||||
|
// | |
|
||||||
|
// | +-------------------------+----------------- ymax
|
||||||
|
// | | ggggggggg ggggg | ^ ^
|
||||||
|
// | | g:::::::::ggg::::g | | |
|
||||||
|
// | | g:::::::::::::::::g | | |
|
||||||
|
// | | g::::::ggggg::::::gg | | |
|
||||||
|
// | | g:::::g g:::::g | | |
|
||||||
|
// offsetX -|-------->| g:::::g g:::::g | offsetY |
|
||||||
|
// | | g:::::g g:::::g | | |
|
||||||
|
// | | g::::::g g:::::g | | |
|
||||||
|
// | | g:::::::ggggg:::::g | | |
|
||||||
|
// | | g::::::::::::::::g | | height
|
||||||
|
// | | gg::::::::::::::g | | |
|
||||||
|
// baseline ---*---------|---- gggggggg::::::g-----*-------- |
|
||||||
|
// / | | g:::::g | |
|
||||||
|
// origin | | gggggg g:::::g | |
|
||||||
|
// | | g:::::gg gg:::::g | |
|
||||||
|
// | | g::::::ggg:::::::g | |
|
||||||
|
// | | gg:::::::::::::g | |
|
||||||
|
// | | ggg::::::ggg | |
|
||||||
|
// | | gggggg | v
|
||||||
|
// | +-------------------------+----------------- ymin
|
||||||
|
// | |
|
||||||
|
// |------------- advanceX ----------->|
|
||||||
|
|
||||||
|
// Stored in ImFontAtlas::FontLoaderData. ALLOCATED BY US.
|
||||||
|
struct ImGui_ImplFreeType_Data
|
||||||
|
{
|
||||||
|
FT_Library Library;
|
||||||
|
FT_MemoryRec_ MemoryManager;
|
||||||
|
ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stored in ImFontConfig::FontLoaderData. ALLOCATED BY US.
|
||||||
|
struct ImGui_ImplFreeType_FontSrcData
|
||||||
|
{
|
||||||
|
// Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime.
|
||||||
|
bool InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_user_flags);
|
||||||
|
void CloseFont();
|
||||||
|
ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); }
|
||||||
|
~ImGui_ImplFreeType_FontSrcData() { CloseFont(); }
|
||||||
|
|
||||||
|
// Members
|
||||||
|
FT_Face FtFace;
|
||||||
|
ImGuiFreeTypeLoaderFlags UserFlags; // = ImFontConfig::FontLoaderFlags
|
||||||
|
FT_Int32 LoadFlags;
|
||||||
|
ImFontBaked* BakedLastActivated;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE.
|
||||||
|
struct ImGui_ImplFreeType_FontSrcBakedData
|
||||||
|
{
|
||||||
|
FT_Size FtSize; // This represent a FT_Face with a given size.
|
||||||
|
ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); }
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_font_loader_flags)
|
||||||
|
{
|
||||||
|
FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)src->FontData, (FT_Long)src->FontDataSize, (FT_Long)src->FontNo, &FtFace);
|
||||||
|
if (error != 0)
|
||||||
|
return false;
|
||||||
|
error = FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE);
|
||||||
|
if (error != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Convert to FreeType flags (NB: Bold and Oblique are processed separately)
|
||||||
|
UserFlags = (ImGuiFreeTypeLoaderFlags)(src->FontLoaderFlags | extra_font_loader_flags);
|
||||||
|
|
||||||
|
LoadFlags = 0;
|
||||||
|
if ((UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) == 0)
|
||||||
|
LoadFlags |= FT_LOAD_NO_BITMAP;
|
||||||
|
if (UserFlags & ImGuiFreeTypeLoaderFlags_NoHinting)
|
||||||
|
LoadFlags |= FT_LOAD_NO_HINTING;
|
||||||
|
if (UserFlags & ImGuiFreeTypeLoaderFlags_NoAutoHint)
|
||||||
|
LoadFlags |= FT_LOAD_NO_AUTOHINT;
|
||||||
|
if (UserFlags & ImGuiFreeTypeLoaderFlags_ForceAutoHint)
|
||||||
|
LoadFlags |= FT_LOAD_FORCE_AUTOHINT;
|
||||||
|
if (UserFlags & ImGuiFreeTypeLoaderFlags_LightHinting)
|
||||||
|
LoadFlags |= FT_LOAD_TARGET_LIGHT;
|
||||||
|
else if (UserFlags & ImGuiFreeTypeLoaderFlags_MonoHinting)
|
||||||
|
LoadFlags |= FT_LOAD_TARGET_MONO;
|
||||||
|
else
|
||||||
|
LoadFlags |= FT_LOAD_TARGET_NORMAL;
|
||||||
|
|
||||||
|
if (UserFlags & ImGuiFreeTypeLoaderFlags_LoadColor)
|
||||||
|
LoadFlags |= FT_LOAD_COLOR;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGui_ImplFreeType_FontSrcData::CloseFont()
|
||||||
|
{
|
||||||
|
if (FtFace)
|
||||||
|
{
|
||||||
|
FT_Done_Face(FtFace);
|
||||||
|
FtFace = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_FontSrcData* src_data, uint32_t codepoint)
|
||||||
|
{
|
||||||
|
uint32_t glyph_index = FT_Get_Char_Index(src_data->FtFace, codepoint);
|
||||||
|
if (glyph_index == 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts.
|
||||||
|
// - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076
|
||||||
|
// - https://github.com/ocornut/imgui/issues/4567
|
||||||
|
// - https://github.com/ocornut/imgui/issues/4566
|
||||||
|
// You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version.
|
||||||
|
FT_Error error = FT_Load_Glyph(src_data->FtFace, glyph_index, src_data->LoadFlags);
|
||||||
|
if (error)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Need an outline for this to work
|
||||||
|
FT_GlyphSlot slot = src_data->FtFace->glyph;
|
||||||
|
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
|
||||||
|
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP || slot->format == FT_GLYPH_FORMAT_SVG);
|
||||||
|
#else
|
||||||
|
#if ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
|
||||||
|
IM_ASSERT(slot->format != FT_GLYPH_FORMAT_SVG && "The font contains SVG glyphs, you'll need to enable IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG in imconfig.h and install required libraries in order to use this font");
|
||||||
|
#endif
|
||||||
|
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP);
|
||||||
|
#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||||
|
|
||||||
|
// Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting)
|
||||||
|
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bold)
|
||||||
|
FT_GlyphSlot_Embolden(slot);
|
||||||
|
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Oblique)
|
||||||
|
{
|
||||||
|
FT_GlyphSlot_Oblique(slot);
|
||||||
|
//FT_BBox bbox;
|
||||||
|
//FT_Outline_Get_BBox(&slot->outline, &bbox);
|
||||||
|
//slot->metrics.width = bbox.xMax - bbox.xMin;
|
||||||
|
//slot->metrics.height = bbox.yMax - bbox.yMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &slot->metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplFreeType_BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch)
|
||||||
|
{
|
||||||
|
IM_ASSERT(ft_bitmap != nullptr);
|
||||||
|
const uint32_t w = ft_bitmap->width;
|
||||||
|
const uint32_t h = ft_bitmap->rows;
|
||||||
|
const uint8_t* src = ft_bitmap->buffer;
|
||||||
|
const uint32_t src_pitch = ft_bitmap->pitch;
|
||||||
|
|
||||||
|
switch (ft_bitmap->pixel_mode)
|
||||||
|
{
|
||||||
|
case FT_PIXEL_MODE_GRAY: // Grayscale image, 1 byte per pixel.
|
||||||
|
{
|
||||||
|
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
|
||||||
|
for (uint32_t x = 0; x < w; x++)
|
||||||
|
dst[x] = IM_COL32(255, 255, 255, src[x]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FT_PIXEL_MODE_MONO: // Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB.
|
||||||
|
{
|
||||||
|
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
|
||||||
|
{
|
||||||
|
uint8_t bits = 0;
|
||||||
|
const uint8_t* bits_ptr = src;
|
||||||
|
for (uint32_t x = 0; x < w; x++, bits <<= 1)
|
||||||
|
{
|
||||||
|
if ((x & 7) == 0)
|
||||||
|
bits = *bits_ptr++;
|
||||||
|
dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? 255 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FT_PIXEL_MODE_BGRA:
|
||||||
|
{
|
||||||
|
// FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good.
|
||||||
|
#define DE_MULTIPLY(color, alpha) ImMin((ImU32)(255.0f * (float)color / (float)(alpha + FLT_MIN) + 0.5f), 255u)
|
||||||
|
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
|
||||||
|
for (uint32_t x = 0; x < w; x++)
|
||||||
|
{
|
||||||
|
uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
|
||||||
|
dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a);
|
||||||
|
}
|
||||||
|
#undef DE_MULTIPLY
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
IM_ASSERT(0 && "FreeTypeFont::BlitGlyph(): Unknown bitmap pixel mode!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeType memory allocation callbacks
|
||||||
|
static void* FreeType_Alloc(FT_Memory /*memory*/, long size)
|
||||||
|
{
|
||||||
|
return GImGuiFreeTypeAllocFunc((size_t)size, GImGuiFreeTypeAllocatorUserData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeType_Free(FT_Memory /*memory*/, void* block)
|
||||||
|
{
|
||||||
|
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* FreeType_Realloc(FT_Memory /*memory*/, long cur_size, long new_size, void* block)
|
||||||
|
{
|
||||||
|
// Implement realloc() as we don't ask user to provide it.
|
||||||
|
if (block == nullptr)
|
||||||
|
return GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
|
||||||
|
|
||||||
|
if (new_size == 0)
|
||||||
|
{
|
||||||
|
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_size > cur_size)
|
||||||
|
{
|
||||||
|
void* new_block = GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
|
||||||
|
memcpy(new_block, block, (size_t)cur_size);
|
||||||
|
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
|
||||||
|
return new_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ImGui_ImplFreeType_LoaderInit(ImFontAtlas* atlas)
|
||||||
|
{
|
||||||
|
IM_ASSERT(atlas->FontLoaderData == nullptr);
|
||||||
|
ImGui_ImplFreeType_Data* bd = IM_NEW(ImGui_ImplFreeType_Data)();
|
||||||
|
|
||||||
|
// FreeType memory management: https://www.freetype.org/freetype2/docs/design/design-4.html
|
||||||
|
bd->MemoryManager.user = nullptr;
|
||||||
|
bd->MemoryManager.alloc = &FreeType_Alloc;
|
||||||
|
bd->MemoryManager.free = &FreeType_Free;
|
||||||
|
bd->MemoryManager.realloc = &FreeType_Realloc;
|
||||||
|
|
||||||
|
// https://www.freetype.org/freetype2/docs/reference/ft2-module_management.html#FT_New_Library
|
||||||
|
FT_Error error = FT_New_Library(&bd->MemoryManager, &bd->Library);
|
||||||
|
if (error != 0)
|
||||||
|
{
|
||||||
|
IM_DELETE(bd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you don't call FT_Add_Default_Modules() the rest of code may work, but FreeType won't use our custom allocator.
|
||||||
|
FT_Add_Default_Modules(bd->Library);
|
||||||
|
|
||||||
|
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||||
|
// Install svg hooks for FreeType
|
||||||
|
// https://freetype.org/freetype2/docs/reference/ft2-properties.html#svg-hooks
|
||||||
|
// https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html#svg_fonts
|
||||||
|
SVG_RendererHooks hooks = { ImGuiLunasvgPortInit, ImGuiLunasvgPortFree, ImGuiLunasvgPortRender, ImGuiLunasvgPortPresetSlot };
|
||||||
|
FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", &hooks);
|
||||||
|
#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||||
|
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
|
||||||
|
// With plutosvg, use provided hooks
|
||||||
|
FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks());
|
||||||
|
#endif // IMGUI_ENABLE_FREETYPE_PLUTOSVG
|
||||||
|
|
||||||
|
// Store our data
|
||||||
|
atlas->FontLoaderData = (void*)bd;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplFreeType_LoaderShutdown(ImFontAtlas* atlas)
|
||||||
|
{
|
||||||
|
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
|
||||||
|
IM_ASSERT(bd != nullptr);
|
||||||
|
FT_Done_Library(bd->Library);
|
||||||
|
IM_DELETE(bd);
|
||||||
|
atlas->FontLoaderData = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src)
|
||||||
|
{
|
||||||
|
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
|
||||||
|
ImGui_ImplFreeType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplFreeType_FontSrcData);
|
||||||
|
IM_ASSERT(src->FontLoaderData == nullptr);
|
||||||
|
src->FontLoaderData = bd_font_data;
|
||||||
|
|
||||||
|
if (!bd_font_data->InitFont(bd->Library, src, (ImGuiFreeTypeLoaderFlags)atlas->FontLoaderFlags))
|
||||||
|
{
|
||||||
|
IM_DELETE(bd_font_data);
|
||||||
|
src->FontLoaderData = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src)
|
||||||
|
{
|
||||||
|
IM_UNUSED(atlas);
|
||||||
|
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
|
||||||
|
IM_DELETE(bd_font_data);
|
||||||
|
src->FontLoaderData = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
|
||||||
|
{
|
||||||
|
IM_UNUSED(atlas);
|
||||||
|
float size = baked->Size;
|
||||||
|
if (src->MergeMode && src->SizePixels != 0.0f)
|
||||||
|
size *= (src->SizePixels / baked->OwnerFont->Sources[0]->SizePixels);
|
||||||
|
size *= src->ExtraSizeScale;
|
||||||
|
|
||||||
|
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
|
||||||
|
bd_font_data->BakedLastActivated = baked;
|
||||||
|
|
||||||
|
// We use one FT_Size per (source + baked) combination.
|
||||||
|
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
|
||||||
|
IM_ASSERT(bd_baked_data != nullptr);
|
||||||
|
IM_PLACEMENT_NEW(bd_baked_data) ImGui_ImplFreeType_FontSrcBakedData();
|
||||||
|
|
||||||
|
FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize);
|
||||||
|
FT_Activate_Size(bd_baked_data->FtSize);
|
||||||
|
|
||||||
|
// Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
|
||||||
|
// is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me.
|
||||||
|
// FT_Set_Pixel_Sizes() doesn't seem to get us the same result."
|
||||||
|
// (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL)
|
||||||
|
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
|
||||||
|
FT_Size_RequestRec req;
|
||||||
|
req.type = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM;
|
||||||
|
req.width = 0;
|
||||||
|
req.height = (uint32_t)(size * 64 * rasterizer_density);
|
||||||
|
req.horiResolution = 0;
|
||||||
|
req.vertResolution = 0;
|
||||||
|
FT_Request_Size(bd_font_data->FtFace, &req);
|
||||||
|
|
||||||
|
// Output
|
||||||
|
if (src->MergeMode == false)
|
||||||
|
{
|
||||||
|
// Read metrics
|
||||||
|
FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics;
|
||||||
|
const float scale = 1.0f / (rasterizer_density * src->ExtraSizeScale);
|
||||||
|
baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive).
|
||||||
|
baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative).
|
||||||
|
//LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate.
|
||||||
|
//LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * scale; // The spacing in pixels between one row's descent and the next row's ascent.
|
||||||
|
//MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * scale; // This field gives the maximum horizontal cursor advance for all glyphs in the font.
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
|
||||||
|
{
|
||||||
|
IM_UNUSED(atlas);
|
||||||
|
IM_UNUSED(baked);
|
||||||
|
IM_UNUSED(src);
|
||||||
|
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
|
||||||
|
IM_ASSERT(bd_baked_data != nullptr);
|
||||||
|
FT_Done_Size(bd_baked_data->FtSize);
|
||||||
|
bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE()
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x)
|
||||||
|
{
|
||||||
|
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
|
||||||
|
uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
|
||||||
|
if (glyph_index == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (bd_font_data->BakedLastActivated != baked) // <-- could use id
|
||||||
|
{
|
||||||
|
// Activate current size
|
||||||
|
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
|
||||||
|
FT_Activate_Size(bd_baked_data->FtSize);
|
||||||
|
bd_font_data->BakedLastActivated = baked;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FT_Glyph_Metrics* metrics = ImGui_ImplFreeType_LoadGlyph(bd_font_data, codepoint);
|
||||||
|
if (metrics == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
FT_Face face = bd_font_data->FtFace;
|
||||||
|
FT_GlyphSlot slot = face->glyph;
|
||||||
|
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
|
||||||
|
|
||||||
|
// Load metrics only mode
|
||||||
|
const float advance_x = (slot->advance.x / FT_SCALEFACTOR) / rasterizer_density;
|
||||||
|
if (out_advance_x != NULL)
|
||||||
|
{
|
||||||
|
IM_ASSERT(out_glyph == NULL);
|
||||||
|
*out_advance_x = advance_x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render glyph into a bitmap (currently held by FreeType)
|
||||||
|
FT_Render_Mode render_mode = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Monochrome) ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL;
|
||||||
|
FT_Error error = FT_Render_Glyph(slot, render_mode);
|
||||||
|
const FT_Bitmap* ft_bitmap = &slot->bitmap;
|
||||||
|
if (error != 0 || ft_bitmap == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const int w = (int)ft_bitmap->width;
|
||||||
|
const int h = (int)ft_bitmap->rows;
|
||||||
|
const bool is_visible = (w != 0 && h != 0);
|
||||||
|
|
||||||
|
// Prepare glyph
|
||||||
|
out_glyph->Codepoint = codepoint;
|
||||||
|
out_glyph->AdvanceX = advance_x;
|
||||||
|
|
||||||
|
// Pack and retrieve position inside texture atlas
|
||||||
|
if (is_visible)
|
||||||
|
{
|
||||||
|
ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h);
|
||||||
|
if (pack_id == ImFontAtlasRectId_Invalid)
|
||||||
|
{
|
||||||
|
// Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?)
|
||||||
|
IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id);
|
||||||
|
|
||||||
|
// Render pixels to our temporary buffer
|
||||||
|
atlas->Builder->TempBuffer.resize(w * h * 4);
|
||||||
|
uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data;
|
||||||
|
ImGui_ImplFreeType_BlitGlyph(ft_bitmap, temp_buffer, w);
|
||||||
|
|
||||||
|
const float ref_size = baked->OwnerFont->Sources[0]->SizePixels;
|
||||||
|
const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f;
|
||||||
|
float font_off_x = ImFloor(src->GlyphOffset.x * offsets_scale + 0.5f); // Snap scaled offset.
|
||||||
|
float font_off_y = ImFloor(src->GlyphOffset.y * offsets_scale + 0.5f) + baked->Ascent;
|
||||||
|
float recip_h = 1.0f / rasterizer_density;
|
||||||
|
float recip_v = 1.0f / rasterizer_density;
|
||||||
|
|
||||||
|
// Register glyph
|
||||||
|
float glyph_off_x = (float)face->glyph->bitmap_left;
|
||||||
|
float glyph_off_y = (float)-face->glyph->bitmap_top;
|
||||||
|
out_glyph->X0 = glyph_off_x * recip_h + font_off_x;
|
||||||
|
out_glyph->Y0 = glyph_off_y * recip_v + font_off_y;
|
||||||
|
out_glyph->X1 = (glyph_off_x + w) * recip_h + font_off_x;
|
||||||
|
out_glyph->Y1 = (glyph_off_y + h) * recip_v + font_off_y;
|
||||||
|
out_glyph->Visible = true;
|
||||||
|
out_glyph->Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA);
|
||||||
|
out_glyph->PackId = pack_id;
|
||||||
|
ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, (const unsigned char*)temp_buffer, ImTextureFormat_RGBA32, w * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint)
|
||||||
|
{
|
||||||
|
IM_UNUSED(atlas);
|
||||||
|
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
|
||||||
|
int glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
|
||||||
|
return glyph_index != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImFontLoader* ImGuiFreeType::GetFontLoader()
|
||||||
|
{
|
||||||
|
static ImFontLoader loader;
|
||||||
|
loader.Name = "FreeType";
|
||||||
|
loader.LoaderInit = ImGui_ImplFreeType_LoaderInit;
|
||||||
|
loader.LoaderShutdown = ImGui_ImplFreeType_LoaderShutdown;
|
||||||
|
loader.FontSrcInit = ImGui_ImplFreeType_FontSrcInit;
|
||||||
|
loader.FontSrcDestroy = ImGui_ImplFreeType_FontSrcDestroy;
|
||||||
|
loader.FontSrcContainsGlyph = ImGui_ImplFreetype_FontSrcContainsGlyph;
|
||||||
|
loader.FontBakedInit = ImGui_ImplFreeType_FontBakedInit;
|
||||||
|
loader.FontBakedDestroy = ImGui_ImplFreeType_FontBakedDestroy;
|
||||||
|
loader.FontBakedLoadGlyph = ImGui_ImplFreeType_FontBakedLoadGlyph;
|
||||||
|
loader.FontBakedSrcLoaderDataSize = sizeof(ImGui_ImplFreeType_FontSrcBakedData);
|
||||||
|
return &loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data)
|
||||||
|
{
|
||||||
|
GImGuiFreeTypeAllocFunc = alloc_func;
|
||||||
|
GImGuiFreeTypeFreeFunc = free_func;
|
||||||
|
GImGuiFreeTypeAllocatorUserData = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGuiFreeType::DebugEditFontLoaderFlags(unsigned int* p_font_loader_flags)
|
||||||
|
{
|
||||||
|
bool edited = false;
|
||||||
|
edited |= ImGui::CheckboxFlags("NoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoHinting);
|
||||||
|
edited |= ImGui::CheckboxFlags("NoAutoHint", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoAutoHint);
|
||||||
|
edited |= ImGui::CheckboxFlags("ForceAutoHint",p_font_loader_flags, ImGuiFreeTypeLoaderFlags_ForceAutoHint);
|
||||||
|
edited |= ImGui::CheckboxFlags("LightHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LightHinting);
|
||||||
|
edited |= ImGui::CheckboxFlags("MonoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_MonoHinting);
|
||||||
|
edited |= ImGui::CheckboxFlags("Bold", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bold);
|
||||||
|
edited |= ImGui::CheckboxFlags("Oblique", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Oblique);
|
||||||
|
edited |= ImGui::CheckboxFlags("Monochrome", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Monochrome);
|
||||||
|
edited |= ImGui::CheckboxFlags("LoadColor", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LoadColor);
|
||||||
|
edited |= ImGui::CheckboxFlags("Bitmap", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bitmap);
|
||||||
|
return edited;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||||
|
// For more details, see https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c
|
||||||
|
// The original code from the demo is licensed under CeCILL-C Free Software License Agreement (https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/LICENSE.TXT)
|
||||||
|
struct LunasvgPortState
|
||||||
|
{
|
||||||
|
FT_Error err = FT_Err_Ok;
|
||||||
|
lunasvg::Matrix matrix;
|
||||||
|
std::unique_ptr<lunasvg::Document> svg = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
static FT_Error ImGuiLunasvgPortInit(FT_Pointer* _state)
|
||||||
|
{
|
||||||
|
*_state = IM_NEW(LunasvgPortState)();
|
||||||
|
return FT_Err_Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGuiLunasvgPortFree(FT_Pointer* _state)
|
||||||
|
{
|
||||||
|
IM_DELETE(*(LunasvgPortState**)_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state)
|
||||||
|
{
|
||||||
|
LunasvgPortState* state = *(LunasvgPortState**)_state;
|
||||||
|
|
||||||
|
// If there was an error while loading the svg in ImGuiLunasvgPortPresetSlot(), the renderer hook still get called, so just returns the error.
|
||||||
|
if (state->err != FT_Err_Ok)
|
||||||
|
return state->err;
|
||||||
|
|
||||||
|
// rows is height, pitch (or stride) equals to width * sizeof(int32)
|
||||||
|
lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch);
|
||||||
|
#if LUNASVG_VERSION_MAJOR >= 3
|
||||||
|
state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
|
||||||
|
#else
|
||||||
|
state->svg->setMatrix(state->svg->matrix().identity()); // Reset the svg matrix to the default value
|
||||||
|
state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
|
||||||
|
#endif
|
||||||
|
state->err = FT_Err_Ok;
|
||||||
|
return state->err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state)
|
||||||
|
{
|
||||||
|
FT_SVG_Document document = (FT_SVG_Document)slot->other;
|
||||||
|
LunasvgPortState* state = *(LunasvgPortState**)_state;
|
||||||
|
FT_Size_Metrics& metrics = document->metrics;
|
||||||
|
|
||||||
|
// This function is called twice, once in the FT_Load_Glyph() and another right before ImGuiLunasvgPortRender().
|
||||||
|
// If it's the latter, don't do anything because it's // already done in the former.
|
||||||
|
if (cache)
|
||||||
|
return state->err;
|
||||||
|
|
||||||
|
state->svg = lunasvg::Document::loadFromData((const char*)document->svg_document, document->svg_document_length);
|
||||||
|
if (state->svg == nullptr)
|
||||||
|
{
|
||||||
|
state->err = FT_Err_Invalid_SVG_Document;
|
||||||
|
return state->err;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LUNASVG_VERSION_MAJOR >= 3
|
||||||
|
lunasvg::Box box = state->svg->boundingBox();
|
||||||
|
#else
|
||||||
|
lunasvg::Box box = state->svg->box();
|
||||||
|
#endif
|
||||||
|
double scale = std::min(metrics.x_ppem / box.w, metrics.y_ppem / box.h);
|
||||||
|
double xx = (double)document->transform.xx / (1 << 16);
|
||||||
|
double xy = -(double)document->transform.xy / (1 << 16);
|
||||||
|
double yx = -(double)document->transform.yx / (1 << 16);
|
||||||
|
double yy = (double)document->transform.yy / (1 << 16);
|
||||||
|
double x0 = (double)document->delta.x / 64 * box.w / metrics.x_ppem;
|
||||||
|
double y0 = -(double)document->delta.y / 64 * box.h / metrics.y_ppem;
|
||||||
|
|
||||||
|
#if LUNASVG_VERSION_MAJOR >= 3
|
||||||
|
// Scale, transform and pre-translate the matrix for the rendering step
|
||||||
|
state->matrix = lunasvg::Matrix::translated(-box.x, -box.y);
|
||||||
|
state->matrix.multiply(lunasvg::Matrix(xx, xy, yx, yy, x0, y0));
|
||||||
|
state->matrix.scale(scale, scale);
|
||||||
|
|
||||||
|
// Apply updated transformation to the bounding box
|
||||||
|
box.transform(state->matrix);
|
||||||
|
#else
|
||||||
|
// Scale and transform, we don't translate the svg yet
|
||||||
|
state->matrix.identity();
|
||||||
|
state->matrix.scale(scale, scale);
|
||||||
|
state->matrix.transform(xx, xy, yx, yy, x0, y0);
|
||||||
|
state->svg->setMatrix(state->matrix);
|
||||||
|
|
||||||
|
// Pre-translate the matrix for the rendering step
|
||||||
|
state->matrix.translate(-box.x, -box.y);
|
||||||
|
|
||||||
|
// Get the box again after the transformation
|
||||||
|
box = state->svg->box();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Calculate the bitmap size
|
||||||
|
slot->bitmap_left = FT_Int(box.x);
|
||||||
|
slot->bitmap_top = FT_Int(-box.y);
|
||||||
|
slot->bitmap.rows = (unsigned int)(ImCeil((float)box.h));
|
||||||
|
slot->bitmap.width = (unsigned int)(ImCeil((float)box.w));
|
||||||
|
slot->bitmap.pitch = slot->bitmap.width * 4;
|
||||||
|
slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
|
||||||
|
|
||||||
|
// Compute all the bearings and set them correctly. The outline is scaled already, we just need to use the bounding box.
|
||||||
|
double metrics_width = box.w;
|
||||||
|
double metrics_height = box.h;
|
||||||
|
double horiBearingX = box.x;
|
||||||
|
double horiBearingY = -box.y;
|
||||||
|
double vertBearingX = slot->metrics.horiBearingX / 64.0 - slot->metrics.horiAdvance / 64.0 / 2.0;
|
||||||
|
double vertBearingY = (slot->metrics.vertAdvance / 64.0 - slot->metrics.height / 64.0) / 2.0;
|
||||||
|
slot->metrics.width = FT_Pos(IM_ROUND(metrics_width * 64.0)); // Using IM_ROUND() assume width and height are positive
|
||||||
|
slot->metrics.height = FT_Pos(IM_ROUND(metrics_height * 64.0));
|
||||||
|
slot->metrics.horiBearingX = FT_Pos(horiBearingX * 64);
|
||||||
|
slot->metrics.horiBearingY = FT_Pos(horiBearingY * 64);
|
||||||
|
slot->metrics.vertBearingX = FT_Pos(vertBearingX * 64);
|
||||||
|
slot->metrics.vertBearingY = FT_Pos(vertBearingY * 64);
|
||||||
|
|
||||||
|
if (slot->metrics.vertAdvance == 0)
|
||||||
|
slot->metrics.vertAdvance = FT_Pos(metrics_height * 1.2 * 64.0);
|
||||||
|
|
||||||
|
state->err = FT_Err_Ok;
|
||||||
|
return state->err;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning (pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // #ifndef IMGUI_DISABLE
|
||||||
83
libs/imgui/misc/freetype/imgui_freetype.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder)
|
||||||
|
// (headers)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "imgui.h" // IMGUI_API
|
||||||
|
#ifndef IMGUI_DISABLE
|
||||||
|
|
||||||
|
// Usage:
|
||||||
|
// - Add '#define IMGUI_ENABLE_FREETYPE' in your imconfig to automatically enable support
|
||||||
|
// for imgui_freetype in imgui. It is equivalent to selecting the default loader with:
|
||||||
|
// io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader())
|
||||||
|
|
||||||
|
// Optional support for OpenType SVG fonts:
|
||||||
|
// - Add '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG' to use plutosvg (not provided). See #7927.
|
||||||
|
// - Add '#define IMGUI_ENABLE_FREETYPE_LUNASVG' to use lunasvg (not provided). See #6591.
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
struct ImFontAtlas;
|
||||||
|
struct ImFontLoader;
|
||||||
|
|
||||||
|
// Hinting greatly impacts visuals (and glyph sizes).
|
||||||
|
// - By default, hinting is enabled and the font's native hinter is preferred over the auto-hinter.
|
||||||
|
// - When disabled, FreeType generates blurrier glyphs, more or less matches the stb_truetype.h
|
||||||
|
// - The Default hinting mode usually looks good, but may distort glyphs in an unusual way.
|
||||||
|
// - The Light hinting mode generates fuzzier glyphs but better matches Microsoft's rasterizer.
|
||||||
|
// You can set those flags globally in ImFontAtlas::FontLoaderFlags
|
||||||
|
// You can set those flags on a per font basis in ImFontConfig::FontLoaderFlags
|
||||||
|
typedef unsigned int ImGuiFreeTypeLoaderFlags;
|
||||||
|
enum ImGuiFreeTypeLoaderFlags_
|
||||||
|
{
|
||||||
|
ImGuiFreeTypeLoaderFlags_NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes.
|
||||||
|
ImGuiFreeTypeLoaderFlags_NoAutoHint = 1 << 1, // Disable auto-hinter.
|
||||||
|
ImGuiFreeTypeLoaderFlags_ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter.
|
||||||
|
ImGuiFreeTypeLoaderFlags_LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text.
|
||||||
|
ImGuiFreeTypeLoaderFlags_MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output.
|
||||||
|
ImGuiFreeTypeLoaderFlags_Bold = 1 << 5, // Styling: Should we artificially embolden the font?
|
||||||
|
ImGuiFreeTypeLoaderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style?
|
||||||
|
ImGuiFreeTypeLoaderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results!
|
||||||
|
ImGuiFreeTypeLoaderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs
|
||||||
|
ImGuiFreeTypeLoaderFlags_Bitmap = 1 << 9, // Enable FreeType bitmap glyphs
|
||||||
|
|
||||||
|
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||||
|
ImGuiFreeTypeBuilderFlags_NoHinting = ImGuiFreeTypeLoaderFlags_NoHinting,
|
||||||
|
ImGuiFreeTypeBuilderFlags_NoAutoHint = ImGuiFreeTypeLoaderFlags_NoAutoHint,
|
||||||
|
ImGuiFreeTypeBuilderFlags_ForceAutoHint = ImGuiFreeTypeLoaderFlags_ForceAutoHint,
|
||||||
|
ImGuiFreeTypeBuilderFlags_LightHinting = ImGuiFreeTypeLoaderFlags_LightHinting,
|
||||||
|
ImGuiFreeTypeBuilderFlags_MonoHinting = ImGuiFreeTypeLoaderFlags_MonoHinting,
|
||||||
|
ImGuiFreeTypeBuilderFlags_Bold = ImGuiFreeTypeLoaderFlags_Bold,
|
||||||
|
ImGuiFreeTypeBuilderFlags_Oblique = ImGuiFreeTypeLoaderFlags_Oblique,
|
||||||
|
ImGuiFreeTypeBuilderFlags_Monochrome = ImGuiFreeTypeLoaderFlags_Monochrome,
|
||||||
|
ImGuiFreeTypeBuilderFlags_LoadColor = ImGuiFreeTypeLoaderFlags_LoadColor,
|
||||||
|
ImGuiFreeTypeBuilderFlags_Bitmap = ImGuiFreeTypeLoaderFlags_Bitmap,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Obsolete names (will be removed)
|
||||||
|
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||||
|
typedef ImGuiFreeTypeLoaderFlags_ ImGuiFreeTypeBuilderFlags_;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace ImGuiFreeType
|
||||||
|
{
|
||||||
|
// This is automatically assigned when using '#define IMGUI_ENABLE_FREETYPE'.
|
||||||
|
// If you need to dynamically select between multiple builders:
|
||||||
|
// - you can manually assign this builder with 'atlas->SetFontLoader(ImGuiFreeType::GetFontLoader())'
|
||||||
|
// - prefer deep-copying this into your own ImFontLoader instance if you use hot-reloading that messes up static data.
|
||||||
|
IMGUI_API const ImFontLoader* GetFontLoader();
|
||||||
|
|
||||||
|
// Override allocators. By default ImGuiFreeType will use IM_ALLOC()/IM_FREE()
|
||||||
|
// However, as FreeType does lots of allocations we provide a way for the user to redirect it to a separate memory heap if desired.
|
||||||
|
IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data = nullptr);
|
||||||
|
|
||||||
|
// Display UI to edit ImFontAtlas::FontLoaderFlags (shared) or ImFontConfig::FontLoaderFlags (single source)
|
||||||
|
IMGUI_API bool DebugEditFontLoaderFlags(ImGuiFreeTypeLoaderFlags* p_font_loader_flags);
|
||||||
|
|
||||||
|
// Obsolete names (will be removed)
|
||||||
|
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||||
|
//IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); // Renamed/changed in 1.92. Change 'io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' to 'io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader())' if you need runtime selection.
|
||||||
|
//static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontLoaderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE'
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #ifndef IMGUI_DISABLE
|
||||||
@@ -40,7 +40,7 @@ extern "C" {
|
|||||||
* read-only segment just like a normal const array would.
|
* read-only segment just like a normal const array would.
|
||||||
*/
|
*/
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
# define INCBIN_SECTION ".const_data"
|
# define INCBIN_SECTION "__DATA,__const"
|
||||||
# define INCBIN_MANGLE "_"
|
# define INCBIN_MANGLE "_"
|
||||||
#else
|
#else
|
||||||
# define INCBIN_SECTION ".rodata"
|
# define INCBIN_SECTION ".rodata"
|
||||||
|
|||||||
3278
libs/nanosvg/nanosvg.h
Normal file
1472
libs/nanosvg/nanosvgrast.h
Normal file
270
overview_mockup.html
Normal file
51
res/ObsidianDragon.manifest.in
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
|
||||||
|
<!-- Application identity —————————————————————————————— -->
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="DragonX.ObsidianDragon.Wallet"
|
||||||
|
version="@DRAGONX_APP_VERSION_MAJOR@.@DRAGONX_APP_VERSION_MINOR@.@DRAGONX_APP_VERSION_PATCH@.0"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<description>ObsidianDragon Wallet</description>
|
||||||
|
|
||||||
|
<!-- Common Controls v6 (themed buttons, etc.) ————————— -->
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- DPI awareness (Per-Monitor V2) ————————————————————— -->
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
|
||||||
|
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<!-- Supported OS declarations (Windows 7 → 11) ———————— -->
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- Windows 7 -->
|
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||||
|
<!-- Windows 8 -->
|
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||||
|
<!-- Windows 8.1 -->
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||||
|
<!-- Windows 10 / 11 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
|
||||||
|
</assembly>
|
||||||
@@ -4,6 +4,13 @@
|
|||||||
// Use numeric ordinal 1 so LoadIcon(hInst, MAKEINTRESOURCE(1)) finds it.
|
// Use numeric ordinal 1 so LoadIcon(hInst, MAKEINTRESOURCE(1)) finds it.
|
||||||
1 ICON "@OBSIDIAN_ICO_PATH@"
|
1 ICON "@OBSIDIAN_ICO_PATH@"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Application Manifest — declares DPI awareness, common controls v6,
|
||||||
|
// UTF-8 code page, and application identity. Without this, Windows may
|
||||||
|
// fall back to legacy process grouping in Task Manager.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
1 24 "@OBSIDIAN_MANIFEST_PATH@"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// VERSIONINFO — sets the description shown in Task Manager, Explorer
|
// VERSIONINFO — sets the description shown in Task Manager, Explorer
|
||||||
// "Details" tab, and other Windows tools. Without this, MinGW-w64
|
// "Details" tab, and other Windows tools. Without this, MinGW-w64
|
||||||
@@ -12,8 +19,8 @@
|
|||||||
#include <winver.h>
|
#include <winver.h>
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION @DRAGONX_VER_MAJOR@,@DRAGONX_VER_MINOR@,@DRAGONX_VER_PATCH@,0
|
FILEVERSION @DRAGONX_APP_VERSION_MAJOR@,@DRAGONX_APP_VERSION_MINOR@,@DRAGONX_APP_VERSION_PATCH@,0
|
||||||
PRODUCTVERSION @DRAGONX_VER_MAJOR@,@DRAGONX_VER_MINOR@,@DRAGONX_VER_PATCH@,0
|
PRODUCTVERSION @DRAGONX_APP_VERSION_MAJOR@,@DRAGONX_APP_VERSION_MINOR@,@DRAGONX_APP_VERSION_PATCH@,0
|
||||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||||
FILEFLAGS 0x0L
|
FILEFLAGS 0x0L
|
||||||
FILEOS VOS_NT_WINDOWS32
|
FILEOS VOS_NT_WINDOWS32
|
||||||
@@ -24,14 +31,14 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "040904B0" // US-English, Unicode
|
BLOCK "040904B0" // US-English, Unicode
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "The Hush Developers\0"
|
VALUE "CompanyName", "DragonX Developers\0"
|
||||||
VALUE "FileDescription", "ObsidianDragon Wallet\0"
|
VALUE "FileDescription", "@DRAGONX_APP_NAME@ Wallet\0"
|
||||||
VALUE "FileVersion", "@DRAGONX_VERSION@\0"
|
VALUE "FileVersion", "@DRAGONX_APP_VERSION@@DRAGONX_APP_VERSION_SUFFIX@\0"
|
||||||
VALUE "InternalName", "ObsidianDragon\0"
|
VALUE "InternalName", "@DRAGONX_APP_NAME@\0"
|
||||||
VALUE "LegalCopyright", "Copyright 2024-2026 The Hush Developers. GPLv3.\0"
|
VALUE "LegalCopyright", "Copyright 2024-2026 DragonX Developers. GPLv3.\0"
|
||||||
VALUE "OriginalFilename", "ObsidianDragon.exe\0"
|
VALUE "OriginalFilename", "@DRAGONX_APP_NAME@.exe\0"
|
||||||
VALUE "ProductName", "ObsidianDragon\0"
|
VALUE "ProductName", "@DRAGONX_APP_NAME@\0"
|
||||||
VALUE "ProductVersion", "@DRAGONX_VERSION@\0"
|
VALUE "ProductVersion", "@DRAGONX_APP_VERSION@@DRAGONX_APP_VERSION_SUFFIX@\0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|||||||
10
res/default_banlist.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Default Ban List — DragonX Wallet
|
||||||
|
# IPs listed here are banned automatically when the wallet connects.
|
||||||
|
# One IP or subnet per line. Comments start with #. Blank lines are ignored.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# 192.168.1.100
|
||||||
|
# 10.0.0.0/8
|
||||||
|
# 203.0.113.42
|
||||||
|
#
|
||||||
|
# Rebuild the wallet after editing this file.
|
||||||
BIN
res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf
Normal file
BIN
res/fonts/NotoEmoji-Subset.ttf
Normal file
BIN
res/fonts/NotoSansCJK-Subset.otf
Normal file
BIN
res/fonts/NotoSansCJK-Subset.ttf
Normal file
BIN
res/fonts/TwemojiMozilla-Color.ttf
Normal file
BIN
res/fonts/UbuntuMono-R.ttf
Normal file
BIN
res/img/ObsidianDragon.icns
Normal file
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 105 KiB |
BIN
res/img/backgrounds/texture/jade_bg.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 556 KiB After Width: | Height: | Size: 370 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 15 KiB |
23
res/img/logos/logo_dragonx.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #d82652;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="cls-2" d="M103.98,128s-6.29-24.7-18.73-34.43c-8.53-8.03-15.63-16.49-21.25-24.16-5.62,7.68-12.72,16.17-21.25,24.16-12.4,9.74-18.73,34.43-18.73,34.43-2.38-24.34,7.85-35.82,12.87-41.29,7.82-8.5,15.31-16.56,21.75-25.02-7.64-11.44-11.41-19.72-11.41-19.72-.89-3.27-3.84-6.64-3.84-6.64,6.08-8.1-1.6-16.98-1.6-16.98,5.79-5.62,6.71-10.02,8.32-18.34-1.96,22.35,4.02,39.09,13.93,54.12,9.84-15.03,15.81-31.77,13.86-54.12,1.6,8.35,2.52,12.72,8.32,18.34,0,0-7.68,8.88-1.6,16.98,0,0-2.95,3.38-3.84,6.64,0,0-3.77,8.28-11.37,19.72,6.43,8.45,13.97,16.56,21.75,25.02,4.97,5.47,15.21,16.95,12.83,41.29h0Z"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-1" d="M55.33,61.62c-3.55,4.55-7.39,8.99-11.44,13.47-5.29-4.48-11.23-5.33-11.23-5.33,22.92-7.82,2.81-15.17.28-16.31C9.28,42.78,9.1,14.78,9.1,14.78c11.51,34.15,36.42,32.3,36.42,32.3.35-.21.67-.46.92-.71,1.64,3.27,4.58,8.67,8.88,15.24h0Z"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-1" d="M68.62,40.41c-1.35,2.98-2.91,5.83-4.62,8.63-1.71-2.81-3.23-5.69-4.62-8.63,1.74-3.45,4.62-20.58,4.62-20.58,0,0,2.88,17.13,4.62,20.58Z"/>
|
||||||
|
<path class="cls-1" d="M76.01,97.93l-3.48,2.34s-.1-4.44-3.52-1.84c-.42.32-2.38,2.21-.03,4.27,0,0-4.05,4.08-4.97,8.21-.92-4.12-4.97-8.21-4.97-8.21,2.34-2.06.39-3.95-.03-4.27-3.41-2.59-3.52,1.84-3.52,1.84l-3.48-2.34c.28-3.55.1-6.68-.46-9.42,4.69-4.94,8.85-9.88,12.47-14.61,3.66,4.72,7.78,9.67,12.47,14.61-.57,2.74-.75,5.86-.46,9.42Z"/>
|
||||||
|
<path class="cls-1" d="M95.34,69.76s-5.94.85-11.23,5.33c-4.02-4.48-7.89-8.92-11.44-13.47,4.3-6.57,7.25-11.98,8.88-15.24.25.25.57.5.92.71,0,0,24.91,1.84,36.42-32.3,0,0-.18,28-23.84,38.66-2.52,1.14-22.64,8.5.28,16.31h0Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
1895
res/lang/de.json
1919
res/lang/es.json
1919
res/lang/fr.json
1918
res/lang/ja.json
1918
res/lang/ko.json
1919
res/lang/pt.json
1919
res/lang/ru.json
1893
res/lang/zh.json
@@ -2,7 +2,7 @@
|
|||||||
name = "Color Pop Dark"
|
name = "Color Pop Dark"
|
||||||
author = "The Hush Developers"
|
author = "The Hush Developers"
|
||||||
dark = true
|
dark = true
|
||||||
elevation = { --elevation-0 = "#121218", --elevation-1 = "#1C1C24", --elevation-2 = "#26262E", --elevation-3 = "#303038", --elevation-4 = "#3A3A44" }
|
elevation = { --elevation-0 = "#121218", --elevation-1 = "#1C1C24", --elevation-2 = "#282836", --elevation-3 = "#303038", --elevation-4 = "#3A3A44" }
|
||||||
images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
||||||
|
|
||||||
[theme.palette]
|
[theme.palette]
|
||||||
@@ -14,13 +14,13 @@ images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "log
|
|||||||
--secondary-light = "#FF9CDC"
|
--secondary-light = "#FF9CDC"
|
||||||
--background = "#0E0E14"
|
--background = "#0E0E14"
|
||||||
--surface = "#161620"
|
--surface = "#161620"
|
||||||
--surface-variant = "#20202C"
|
--surface-variant = "#282836"
|
||||||
--on-primary = "#FFFFFF"
|
--on-primary = "#FFFFFF"
|
||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#E8E6F0"
|
--on-background = "#E8E6F0"
|
||||||
--on-surface = "#E8E6F0"
|
--on-surface = "#E8E6F0"
|
||||||
--on-surface-medium = "rgba(232,230,240,0.72)"
|
--on-surface-medium = "rgba(232,230,240,0.85)"
|
||||||
--on-surface-disabled = "rgba(232,230,240,0.40)"
|
--on-surface-disabled = "rgba(232,230,240,0.58)"
|
||||||
--error = "#FF5C72"
|
--error = "#FF5C72"
|
||||||
--on-error = "#000000"
|
--on-error = "#000000"
|
||||||
--success = "#3DE8A0"
|
--success = "#3DE8A0"
|
||||||
@@ -35,7 +35,7 @@ images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "log
|
|||||||
--surface-active = "rgba(200,190,240,0.10)"
|
--surface-active = "rgba(200,190,240,0.10)"
|
||||||
--glass-button = "rgba(124,108,255,0.08)"
|
--glass-button = "rgba(124,108,255,0.08)"
|
||||||
--glass-button-hover = "rgba(124,108,255,0.16)"
|
--glass-button-hover = "rgba(124,108,255,0.16)"
|
||||||
--card-border = "rgba(200,190,240,0.10)"
|
--card-border = "rgba(200,190,240,0.24)"
|
||||||
--text-shadow = "rgba(0,0,0,0.45)"
|
--text-shadow = "rgba(0,0,0,0.45)"
|
||||||
--input-overlay-text = "rgba(232,230,240,0.25)"
|
--input-overlay-text = "rgba(232,230,240,0.25)"
|
||||||
--slider-text = "rgba(232,230,240,0.82)"
|
--slider-text = "rgba(232,230,240,0.82)"
|
||||||
@@ -48,20 +48,20 @@ images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "log
|
|||||||
--tooltip-bg = "rgba(14,14,22,0.94)"
|
--tooltip-bg = "rgba(14,14,22,0.94)"
|
||||||
--tooltip-border = "rgba(124,108,255,0.18)"
|
--tooltip-border = "rgba(124,108,255,0.18)"
|
||||||
--glass-fill = "rgba(200,190,240,0.06)"
|
--glass-fill = "rgba(200,190,240,0.06)"
|
||||||
--glass-border = "rgba(200,190,240,0.10)"
|
--glass-border = "rgba(124,108,255,0.28)"
|
||||||
--glass-noise-tint = "rgba(124,108,255,0.03)"
|
--glass-noise-tint = "rgba(124,108,255,0.03)"
|
||||||
--tactile-top = "rgba(200,190,240,0.07)"
|
--tactile-top = "rgba(200,190,240,0.07)"
|
||||||
--tactile-bottom = "rgba(200,190,240,0.0)"
|
--tactile-bottom = "rgba(200,190,240,0.0)"
|
||||||
--hover-overlay = "rgba(124,108,255,0.05)"
|
--hover-overlay = "rgba(124,108,255,0.05)"
|
||||||
--active-overlay = "rgba(124,108,255,0.10)"
|
--active-overlay = "rgba(124,108,255,0.10)"
|
||||||
--rim-light = "rgba(124,108,255,0.10)"
|
--rim-light = "rgba(124,108,255,0.16)"
|
||||||
--status-divider = "rgba(200,190,240,0.06)"
|
--status-divider = "rgba(200,190,240,0.06)"
|
||||||
--sidebar-hover = "rgba(124,108,255,0.10)"
|
--sidebar-hover = "rgba(124,108,255,0.10)"
|
||||||
--sidebar-icon = "rgba(232,230,240,0.45)"
|
--sidebar-icon = "rgba(232,230,240,0.45)"
|
||||||
--sidebar-badge = "rgba(232,230,240,1.0)"
|
--sidebar-badge = "rgba(232,230,240,1.0)"
|
||||||
--sidebar-divider = "rgba(200,190,240,0.05)"
|
--sidebar-divider = "rgba(200,190,240,0.05)"
|
||||||
--chart-line = "rgba(124,108,255,0.12)"
|
--chart-line = "rgba(124,108,255,0.12)"
|
||||||
--window-control = "rgba(232,230,240,0.72)"
|
--window-control = "rgba(232,230,240,0.85)"
|
||||||
--window-control-hover = "rgba(124,108,255,0.12)"
|
--window-control-hover = "rgba(124,108,255,0.12)"
|
||||||
--window-close-hover = "rgba(255,92,114,0.75)"
|
--window-close-hover = "rgba(255,92,114,0.75)"
|
||||||
--spinner-track = "rgba(200,190,240,0.08)"
|
--spinner-track = "rgba(200,190,240,0.08)"
|
||||||
|
|||||||
@@ -19,16 +19,16 @@ images = { background_image = "backgrounds/texture/pop-light_bg.png", logo = "lo
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#1E1E2A"
|
--on-background = "#1E1E2A"
|
||||||
--on-surface = "#1E1E2A"
|
--on-surface = "#1E1E2A"
|
||||||
--on-surface-medium = "rgba(30,30,42,0.72)"
|
--on-surface-medium = "rgba(30,30,42,0.86)"
|
||||||
--on-surface-disabled = "rgba(30,30,42,0.38)"
|
--on-surface-disabled = "rgba(30,30,42,0.62)"
|
||||||
--error = "#E0304A"
|
--error = "#E0304A"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#18A860"
|
--success = "#18A860"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#E09020"
|
--warning = "#E09020"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(30,30,60,0.12)"
|
--divider = "rgba(30,30,60,0.20)"
|
||||||
--outline = "rgba(30,30,60,0.15)"
|
--outline = "rgba(30,30,60,0.24)"
|
||||||
--scrim = "rgba(0,0,0,0.45)"
|
--scrim = "rgba(0,0,0,0.45)"
|
||||||
--surface-hover = "rgba(96,64,224,0.05)"
|
--surface-hover = "rgba(96,64,224,0.05)"
|
||||||
--surface-alt = "rgba(96,64,224,0.02)"
|
--surface-alt = "rgba(96,64,224,0.02)"
|
||||||
@@ -47,14 +47,14 @@ images = { background_image = "backgrounds/texture/pop-light_bg.png", logo = "lo
|
|||||||
--chart-hover-ring = "rgba(96,64,224,0.30)"
|
--chart-hover-ring = "rgba(96,64,224,0.30)"
|
||||||
--tooltip-bg = "rgba(36,34,52,0.94)"
|
--tooltip-bg = "rgba(36,34,52,0.94)"
|
||||||
--tooltip-border = "rgba(96,64,224,0.16)"
|
--tooltip-border = "rgba(96,64,224,0.16)"
|
||||||
--glass-fill = "rgba(255,255,255,0.55)"
|
--glass-fill = "rgba(255,255,255,0.20)"
|
||||||
--glass-border = "rgba(30,30,60,0.10)"
|
--glass-border = "rgba(30,30,60,0.10)"
|
||||||
--glass-noise-tint = "rgba(96,64,224,0.02)"
|
--glass-noise-tint = "rgba(96,64,224,0.02)"
|
||||||
--tactile-top = "rgba(255,255,255,0.40)"
|
--tactile-top = "rgba(255,255,255,0.40)"
|
||||||
--tactile-bottom = "rgba(255,255,255,0.05)"
|
--tactile-bottom = "rgba(255,255,255,0.05)"
|
||||||
--hover-overlay = "rgba(96,64,224,0.04)"
|
--hover-overlay = "rgba(96,64,224,0.10)"
|
||||||
--active-overlay = "rgba(96,64,224,0.08)"
|
--active-overlay = "rgba(96,64,224,0.08)"
|
||||||
--rim-light = "rgba(96,64,224,0.06)"
|
--rim-light = "rgba(30,30,42,0.22)"
|
||||||
--status-divider = "rgba(30,30,60,0.08)"
|
--status-divider = "rgba(30,30,60,0.08)"
|
||||||
--sidebar-hover = "rgba(96,64,224,0.07)"
|
--sidebar-hover = "rgba(96,64,224,0.07)"
|
||||||
--sidebar-icon = "rgba(30,30,42,0.50)"
|
--sidebar-icon = "rgba(30,30,42,0.50)"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
name = "Dark"
|
name = "Dark"
|
||||||
author = "The Hush Developers"
|
author = "The Hush Developers"
|
||||||
dark = true
|
dark = true
|
||||||
elevation = { --elevation-0 = "#161618", --elevation-1 = "#222224", --elevation-2 = "#2C2C2E", --elevation-3 = "#363638", --elevation-4 = "#404044" }
|
elevation = { --elevation-0 = "#161618", --elevation-1 = "#222224", --elevation-2 = "#2E2E32", --elevation-3 = "#363638", --elevation-4 = "#404044" }
|
||||||
images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
||||||
|
|
||||||
[theme.palette]
|
[theme.palette]
|
||||||
@@ -14,13 +14,13 @@ images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/l
|
|||||||
--secondary-light = "#9DC5BE"
|
--secondary-light = "#9DC5BE"
|
||||||
--background = "#141416"
|
--background = "#141416"
|
||||||
--surface = "#1A1A1C"
|
--surface = "#1A1A1C"
|
||||||
--surface-variant = "#262628"
|
--surface-variant = "#2E2E32"
|
||||||
--on-primary = "#000000"
|
--on-primary = "#000000"
|
||||||
--on-secondary = "#000000"
|
--on-secondary = "#000000"
|
||||||
--on-background = "#D0D0D4"
|
--on-background = "#D0D0D4"
|
||||||
--on-surface = "#D0D0D4"
|
--on-surface = "#D0D0D4"
|
||||||
--on-surface-medium = "rgba(208,208,212,0.75)"
|
--on-surface-medium = "rgba(208,208,212,0.85)"
|
||||||
--on-surface-disabled = "rgba(208,208,212,0.45)"
|
--on-surface-disabled = "rgba(208,208,212,0.58)"
|
||||||
--error = "#B07080"
|
--error = "#B07080"
|
||||||
--on-error = "#000000"
|
--on-error = "#000000"
|
||||||
--success = "#7AAE7C"
|
--success = "#7AAE7C"
|
||||||
@@ -35,7 +35,7 @@ images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/l
|
|||||||
--surface-active = "rgba(220,220,225,0.08)"
|
--surface-active = "rgba(220,220,225,0.08)"
|
||||||
--glass-button = "rgba(220,220,225,0.05)"
|
--glass-button = "rgba(220,220,225,0.05)"
|
||||||
--glass-button-hover = "rgba(220,220,225,0.10)"
|
--glass-button-hover = "rgba(220,220,225,0.10)"
|
||||||
--card-border = "rgba(220,220,225,0.12)"
|
--card-border = "rgba(220,220,225,0.24)"
|
||||||
--text-shadow = "rgba(0,0,0,0.40)"
|
--text-shadow = "rgba(0,0,0,0.40)"
|
||||||
--input-overlay-text = "rgba(208,208,212,0.28)"
|
--input-overlay-text = "rgba(208,208,212,0.28)"
|
||||||
--slider-text = "rgba(208,208,212,0.85)"
|
--slider-text = "rgba(208,208,212,0.85)"
|
||||||
@@ -48,20 +48,20 @@ images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/l
|
|||||||
--tooltip-bg = "rgba(18,18,22,0.92)"
|
--tooltip-bg = "rgba(18,18,22,0.92)"
|
||||||
--tooltip-border = "rgba(220,220,225,0.10)"
|
--tooltip-border = "rgba(220,220,225,0.10)"
|
||||||
--glass-fill = "rgba(220,220,225,0.07)"
|
--glass-fill = "rgba(220,220,225,0.07)"
|
||||||
--glass-border = "rgba(220,220,225,0.12)"
|
--glass-border = "rgba(154,175,200,0.28)"
|
||||||
--glass-noise-tint = "rgba(220,220,225,0.03)"
|
--glass-noise-tint = "rgba(220,220,225,0.03)"
|
||||||
--tactile-top = "rgba(220,220,225,0.06)"
|
--tactile-top = "rgba(220,220,225,0.06)"
|
||||||
--tactile-bottom = "rgba(220,220,225,0.0)"
|
--tactile-bottom = "rgba(220,220,225,0.0)"
|
||||||
--hover-overlay = "rgba(220,220,225,0.04)"
|
--hover-overlay = "rgba(220,220,225,0.04)"
|
||||||
--active-overlay = "rgba(220,220,225,0.08)"
|
--active-overlay = "rgba(220,220,225,0.08)"
|
||||||
--rim-light = "rgba(220,220,225,0.08)"
|
--rim-light = "rgba(220,220,225,0.14)"
|
||||||
--status-divider = "rgba(220,220,225,0.06)"
|
--status-divider = "rgba(220,220,225,0.06)"
|
||||||
--sidebar-hover = "rgba(220,220,225,0.08)"
|
--sidebar-hover = "rgba(220,220,225,0.08)"
|
||||||
--sidebar-icon = "rgba(220,220,225,0.40)"
|
--sidebar-icon = "rgba(220,220,225,0.40)"
|
||||||
--sidebar-badge = "rgba(208,208,212,1.0)"
|
--sidebar-badge = "rgba(208,208,212,1.0)"
|
||||||
--sidebar-divider = "rgba(220,220,225,0.05)"
|
--sidebar-divider = "rgba(220,220,225,0.05)"
|
||||||
--chart-line = "rgba(220,220,225,0.08)"
|
--chart-line = "rgba(220,220,225,0.08)"
|
||||||
--window-control = "rgba(208,208,212,0.75)"
|
--window-control = "rgba(208,208,212,0.85)"
|
||||||
--window-control-hover = "rgba(220,220,225,0.10)"
|
--window-control-hover = "rgba(220,220,225,0.10)"
|
||||||
--window-close-hover = "rgba(200,50,60,0.70)"
|
--window-close-hover = "rgba(200,50,60,0.70)"
|
||||||
--spinner-track = "rgba(220,220,225,0.08)"
|
--spinner-track = "rgba(220,220,225,0.08)"
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ elevation = { --elevation-0 = "#FDF8F0", --elevation-1 = "#F5EDE0", --elevation-
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#3A2E22"
|
--on-background = "#3A2E22"
|
||||||
--on-surface = "#3A2E22"
|
--on-surface = "#3A2E22"
|
||||||
--on-surface-medium = "rgba(58,46,34,0.68)"
|
--on-surface-medium = "rgba(58,46,34,0.86)"
|
||||||
--on-surface-disabled = "rgba(58,46,34,0.38)"
|
--on-surface-disabled = "rgba(58,46,34,0.62)"
|
||||||
--error = "#A0524A"
|
--error = "#A0524A"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#6A8A5C"
|
--success = "#4E8A42"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#C08840"
|
--warning = "#C08840"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(140,110,70,0.14)"
|
--divider = "rgba(140,110,70,0.20)"
|
||||||
--outline = "rgba(140,110,70,0.16)"
|
--outline = "rgba(140,110,70,0.24)"
|
||||||
--scrim = "rgba(30,20,10,0.45)"
|
--scrim = "rgba(30,20,10,0.45)"
|
||||||
--surface-hover = "rgba(176,120,64,0.06)"
|
--surface-hover = "rgba(176,120,64,0.06)"
|
||||||
--surface-alt = "rgba(176,120,64,0.03)"
|
--surface-alt = "rgba(176,120,64,0.03)"
|
||||||
@@ -48,14 +48,14 @@ elevation = { --elevation-0 = "#FDF8F0", --elevation-1 = "#F5EDE0", --elevation-
|
|||||||
--chart-hover-ring = "rgba(176,120,64,0.28)"
|
--chart-hover-ring = "rgba(176,120,64,0.28)"
|
||||||
--tooltip-bg = "rgba(50,38,24,0.94)"
|
--tooltip-bg = "rgba(50,38,24,0.94)"
|
||||||
--tooltip-border = "rgba(176,120,64,0.12)"
|
--tooltip-border = "rgba(176,120,64,0.12)"
|
||||||
--glass-fill = "rgba(255,252,245,0.58)"
|
--glass-fill = "rgba(255,252,245,0.20)"
|
||||||
--glass-border = "rgba(176,120,64,0.14)"
|
--glass-border = "rgba(176,120,64,0.14)"
|
||||||
--glass-noise-tint = "rgba(180,140,80,0.03)"
|
--glass-noise-tint = "rgba(180,140,80,0.03)"
|
||||||
--tactile-top = "rgba(255,255,248,0.50)"
|
--tactile-top = "rgba(255,255,248,0.50)"
|
||||||
--tactile-bottom = "rgba(255,255,248,0.08)"
|
--tactile-bottom = "rgba(255,255,248,0.08)"
|
||||||
--hover-overlay = "rgba(176,120,64,0.05)"
|
--hover-overlay = "rgba(176,120,64,0.10)"
|
||||||
--active-overlay = "rgba(176,120,64,0.10)"
|
--active-overlay = "rgba(176,120,64,0.10)"
|
||||||
--rim-light = "rgba(212,160,108,0.10)"
|
--rim-light = "rgba(58,46,34,0.22)"
|
||||||
--status-divider = "rgba(176,120,64,0.10)"
|
--status-divider = "rgba(176,120,64,0.10)"
|
||||||
--sidebar-hover = "rgba(176,120,64,0.08)"
|
--sidebar-hover = "rgba(176,120,64,0.08)"
|
||||||
--sidebar-icon = "rgba(58,46,34,0.50)"
|
--sidebar-icon = "rgba(58,46,34,0.50)"
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ elevation = { --elevation-0 = "#FDFBFF", --elevation-1 = "#F5F0FA", --elevation-
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#1C1525"
|
--on-background = "#1C1525"
|
||||||
--on-surface = "#1C1525"
|
--on-surface = "#1C1525"
|
||||||
--on-surface-medium = "rgba(28,21,37,0.72)"
|
--on-surface-medium = "rgba(28,21,37,0.86)"
|
||||||
--on-surface-disabled = "rgba(28,21,37,0.40)"
|
--on-surface-disabled = "rgba(28,21,37,0.62)"
|
||||||
--error = "#C62828"
|
--error = "#C62828"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#2E7D32"
|
--success = "#2E7D32"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#E65100"
|
--warning = "#E65100"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(120,80,160,0.12)"
|
--divider = "rgba(120,80,160,0.20)"
|
||||||
--outline = "rgba(120,80,160,0.14)"
|
--outline = "rgba(120,80,160,0.24)"
|
||||||
--scrim = "rgba(20,10,30,0.45)"
|
--scrim = "rgba(20,10,30,0.45)"
|
||||||
--surface-hover = "rgba(140,107,175,0.06)"
|
--surface-hover = "rgba(140,107,175,0.06)"
|
||||||
--surface-alt = "rgba(140,107,175,0.03)"
|
--surface-alt = "rgba(140,107,175,0.03)"
|
||||||
@@ -48,14 +48,14 @@ elevation = { --elevation-0 = "#FDFBFF", --elevation-1 = "#F5F0FA", --elevation-
|
|||||||
--chart-hover-ring = "rgba(140,107,175,0.28)"
|
--chart-hover-ring = "rgba(140,107,175,0.28)"
|
||||||
--tooltip-bg = "rgba(32,24,48,0.94)"
|
--tooltip-bg = "rgba(32,24,48,0.94)"
|
||||||
--tooltip-border = "rgba(140,107,175,0.12)"
|
--tooltip-border = "rgba(140,107,175,0.12)"
|
||||||
--glass-fill = "rgba(255,255,255,0.55)"
|
--glass-fill = "rgba(255,255,255,0.20)"
|
||||||
--glass-border = "rgba(140,107,175,0.14)"
|
--glass-border = "rgba(140,107,175,0.14)"
|
||||||
--glass-noise-tint = "rgba(180,140,220,0.03)"
|
--glass-noise-tint = "rgba(180,140,220,0.03)"
|
||||||
--tactile-top = "rgba(255,255,255,0.50)"
|
--tactile-top = "rgba(255,255,255,0.50)"
|
||||||
--tactile-bottom = "rgba(255,255,255,0.08)"
|
--tactile-bottom = "rgba(255,255,255,0.08)"
|
||||||
--hover-overlay = "rgba(140,107,175,0.05)"
|
--hover-overlay = "rgba(140,107,175,0.10)"
|
||||||
--active-overlay = "rgba(140,107,175,0.10)"
|
--active-overlay = "rgba(140,107,175,0.10)"
|
||||||
--rim-light = "rgba(180,140,255,0.10)"
|
--rim-light = "rgba(28,21,37,0.22)"
|
||||||
--status-divider = "rgba(140,107,175,0.10)"
|
--status-divider = "rgba(140,107,175,0.10)"
|
||||||
--sidebar-hover = "rgba(140,107,175,0.08)"
|
--sidebar-hover = "rgba(140,107,175,0.08)"
|
||||||
--sidebar-icon = "rgba(28,21,37,0.50)"
|
--sidebar-icon = "rgba(28,21,37,0.50)"
|
||||||
|
|||||||
190
res/themes/jade.toml
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
[theme]
|
||||||
|
name = "Jade"
|
||||||
|
author = "The Hush Developers"
|
||||||
|
dark = true
|
||||||
|
elevation = { --elevation-0 = "#071210", --elevation-1 = "#0C1A16", --elevation-2 = "#16261F", --elevation-3 = "#1D3128", --elevation-4 = "#243B30" }
|
||||||
|
images = { background_image = "backgrounds/texture/jade_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
||||||
|
|
||||||
|
[theme.palette]
|
||||||
|
--primary = "#2FA07A"
|
||||||
|
--primary-variant = "#1E7357"
|
||||||
|
--primary-light = "#7FD1B5"
|
||||||
|
--secondary = "#C9A24E"
|
||||||
|
--secondary-variant = "#A8842F"
|
||||||
|
--secondary-light = "#E0C583"
|
||||||
|
--background = "#071210"
|
||||||
|
--surface = "#0C1A16"
|
||||||
|
--surface-variant = "#16261F"
|
||||||
|
--on-primary = "#FFFFFF"
|
||||||
|
--on-secondary = "#000000"
|
||||||
|
--on-background = "#DCEDE4"
|
||||||
|
--on-surface = "#DCEDE4"
|
||||||
|
--on-surface-medium = "rgba(220,237,228,0.85)"
|
||||||
|
--on-surface-disabled = "rgba(220,237,228,0.58)"
|
||||||
|
--error = "#CF6679"
|
||||||
|
--on-error = "#000000"
|
||||||
|
--success = "#81C784"
|
||||||
|
--on-success = "#000000"
|
||||||
|
--warning = "#FFB74D"
|
||||||
|
--on-warning = "#000000"
|
||||||
|
--divider = "rgba(130,205,170,0.14)"
|
||||||
|
--outline = "rgba(130,205,170,0.16)"
|
||||||
|
--scrim = "rgba(0,0,0,0.6)"
|
||||||
|
--surface-hover = "rgba(130,205,170,0.07)"
|
||||||
|
--surface-alt = "rgba(130,205,170,0.05)"
|
||||||
|
--surface-active = "rgba(130,205,170,0.10)"
|
||||||
|
--glass-button = "rgba(130,205,170,0.06)"
|
||||||
|
--glass-button-hover = "rgba(130,205,170,0.12)"
|
||||||
|
--card-border = "rgba(130,205,170,0.26)"
|
||||||
|
--text-shadow = "rgba(0,0,0,0.50)"
|
||||||
|
--input-overlay-text = "rgba(220,237,228,0.30)"
|
||||||
|
--slider-text = "rgba(220,237,228,0.85)"
|
||||||
|
--thumb-fill = "rgba(130,205,170,0.15)"
|
||||||
|
--thumb-border = "rgba(130,205,170,0.50)"
|
||||||
|
--disabled-label = "rgba(130,205,170,0.18)"
|
||||||
|
--chart-grid = "rgba(130,205,170,0.05)"
|
||||||
|
--chart-crosshair = "rgba(130,205,170,0.15)"
|
||||||
|
--chart-hover-ring = "rgba(130,205,170,0.30)"
|
||||||
|
--tooltip-bg = "rgba(9,20,16,0.92)"
|
||||||
|
--tooltip-border = "rgba(130,205,170,0.12)"
|
||||||
|
--glass-fill = "rgba(130,205,170,0.08)"
|
||||||
|
--glass-border = "rgba(47,160,122,0.30)"
|
||||||
|
--glass-noise-tint = "rgba(130,205,170,0.03)"
|
||||||
|
--tactile-top = "rgba(130,205,170,0.06)"
|
||||||
|
--tactile-bottom = "rgba(130,205,170,0.0)"
|
||||||
|
--hover-overlay = "rgba(130,205,170,0.05)"
|
||||||
|
--active-overlay = "rgba(130,205,170,0.10)"
|
||||||
|
--rim-light = "rgba(130,205,170,0.14)"
|
||||||
|
--status-divider = "rgba(130,205,170,0.08)"
|
||||||
|
--sidebar-hover = "rgba(130,205,170,0.10)"
|
||||||
|
--sidebar-icon = "rgba(130,205,170,0.42)"
|
||||||
|
--sidebar-badge = "rgba(220,237,228,1.0)"
|
||||||
|
--sidebar-divider = "rgba(130,205,170,0.06)"
|
||||||
|
--chart-line = "rgba(130,205,170,0.10)"
|
||||||
|
--window-control = "rgba(220,237,228,0.78)"
|
||||||
|
--window-control-hover = "rgba(130,205,170,0.12)"
|
||||||
|
--window-close-hover = "rgba(232,17,35,0.78)"
|
||||||
|
--spinner-track = "rgba(130,205,170,0.10)"
|
||||||
|
--spinner-active = "rgba(79,184,154,0.85)"
|
||||||
|
--shutdown-panel-bg = "rgba(7,18,14,0.90)"
|
||||||
|
--shutdown-panel-border = "rgba(130,205,170,0.07)"
|
||||||
|
--ram-bar-app = "#2FA07A"
|
||||||
|
--ram-bar-system = "rgba(255,255,255,0.18)"
|
||||||
|
--accent-total = "#7FD1B5"
|
||||||
|
--accent-shielded = "#4FB89A"
|
||||||
|
--accent-transparent = "#C9A24E"
|
||||||
|
--accent-action = "#2FA07A"
|
||||||
|
--accent-market = "#4FB89A"
|
||||||
|
--accent-portfolio = "#7FD1B5"
|
||||||
|
--toast-info-accent = "#2FA07A"
|
||||||
|
--toast-info-text = "#7FD1B5"
|
||||||
|
--toast-success-accent = "rgba(50,180,80,1.0)"
|
||||||
|
--toast-success-text = "rgba(180,255,180,1.0)"
|
||||||
|
--toast-warning-accent = "rgba(204,166,50,1.0)"
|
||||||
|
--toast-warning-text = "rgba(255,230,130,1.0)"
|
||||||
|
--toast-error-accent = "rgba(204,64,64,1.0)"
|
||||||
|
--toast-error-text = "rgba(255,153,153,1.0)"
|
||||||
|
--snackbar-bg = "rgba(24,40,34,0.95)"
|
||||||
|
--snackbar-text = "rgba(220,237,228,0.87)"
|
||||||
|
--snackbar-action = "rgba(79,184,154,1.0)"
|
||||||
|
--snackbar-action-hover = "rgba(127,209,181,1.0)"
|
||||||
|
--switch-track-off = "rgba(130,205,170,0.12)"
|
||||||
|
--switch-track-on = "rgba(47,160,122,0.50)"
|
||||||
|
--switch-thumb-off = "#A0C0B4"
|
||||||
|
--switch-thumb-on = "#DCEDE4"
|
||||||
|
--control-shadow = "rgba(0,0,0,0.24)"
|
||||||
|
--checkbox-check = "#000000"
|
||||||
|
--app-bar-shadow = "rgba(0,0,0,0.25)"
|
||||||
|
|
||||||
|
[backdrop]
|
||||||
|
base-color-top = "rgba(14,32,26,210)"
|
||||||
|
base-color-bottom = "rgba(6,18,14,210)"
|
||||||
|
texture-tint-alpha = 120
|
||||||
|
gradient-top-r = 10
|
||||||
|
gradient-top-g = 30
|
||||||
|
gradient-top-b = 22
|
||||||
|
gradient-top-a = 90
|
||||||
|
gradient-bottom-r = 5
|
||||||
|
gradient-bottom-g = 16
|
||||||
|
gradient-bottom-b = 12
|
||||||
|
gradient-bottom-a = 70
|
||||||
|
background-alpha = 0.42
|
||||||
|
surface-alpha = 0.56
|
||||||
|
frame-alpha = 0.78
|
||||||
|
surface-inline-alpha = 0.58
|
||||||
|
background-inline-alpha = 0.40
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Theme Visual Effects — Jade (veins of gold shifting through the stone)
|
||||||
|
# Jade's signature is a slow jade→gold color-shifting border on every glass
|
||||||
|
# panel + the active nav button — a vein of gold surfacing through nephrite.
|
||||||
|
# It's drawn via AddRect so it hugs the real rounded corners (no polygonal
|
||||||
|
# edge-trace). Sparse jade motes drift up the viewport. No other theme turns
|
||||||
|
# gradient-border-panels on, so the panel-wide vein is Jade's own —
|
||||||
|
# deliberately NOT Obsidian's specular glare.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
[effects]
|
||||||
|
hue-cycle-enabled = { size = 0.0 }
|
||||||
|
rainbow-border-enabled = { size = 0.0 }
|
||||||
|
|
||||||
|
# No shimmer sweep — replaced by specular glare
|
||||||
|
shimmer-enabled = { size = 0.0 }
|
||||||
|
|
||||||
|
positional-hue-enabled = { size = 0.0 }
|
||||||
|
|
||||||
|
glow-pulse-enabled = { size = 0.0 }
|
||||||
|
|
||||||
|
# Edge-trace OFF — its hand-walked perimeter chamfers rounded corners.
|
||||||
|
# Jade's vein is the gradient-border below (corner-clean via AddRect).
|
||||||
|
edge-trace-enabled = { size = 0.0 }
|
||||||
|
edge-trace-speed = { size = 0.16 }
|
||||||
|
edge-trace-length = { size = 0.34 }
|
||||||
|
edge-trace-thickness = { size = 1.6 }
|
||||||
|
edge-trace-alpha = { size = 0.55 }
|
||||||
|
edge-trace-color = { color = "#C9A24E" }
|
||||||
|
|
||||||
|
# Specular glare OFF — that's Obsidian's signature; Jade shouldn't echo it.
|
||||||
|
specular-glare-enabled = { size = 0.0 }
|
||||||
|
specular-glare-speed = { size = 0.018 }
|
||||||
|
specular-glare-intensity = { size = 0.008 }
|
||||||
|
specular-glare-radius = { size = 0.65 }
|
||||||
|
specular-glare-count = { size = 1.0 }
|
||||||
|
specular-glare-color = { color = "rgba(150,220,180,1.0)" }
|
||||||
|
|
||||||
|
# HERO — vein of gold: a slow jade→gold color-shifting border on the active
|
||||||
|
# nav button AND (via gradient-border-panels) every glass panel. Drawn with
|
||||||
|
# AddRect so it follows the rounded corners exactly. Panels drift at a softer
|
||||||
|
# alpha and position-phased offset, so a screenful reads like veins at
|
||||||
|
# different depths rather than one synchronized pulse.
|
||||||
|
gradient-border-enabled = { size = 1.0 }
|
||||||
|
gradient-border-panels = { size = 1.0 }
|
||||||
|
gradient-border-speed = { size = 0.10 }
|
||||||
|
gradient-border-thickness = { size = 1.5 }
|
||||||
|
gradient-border-alpha = { size = 0.55 }
|
||||||
|
gradient-border-color-a = { color = "#7FD1B5" }
|
||||||
|
gradient-border-color-b = { color = "#C9A24E" }
|
||||||
|
|
||||||
|
# Ambient jade motes — sparse, slow, cool green particles drifting up the
|
||||||
|
# viewport (recolored ember-rise; a different mood from dragonx's fire embers).
|
||||||
|
ember-rise-enabled = { size = 1.0 }
|
||||||
|
ember-rise-count = { size = 5.0 }
|
||||||
|
ember-rise-speed = { size = 0.18 }
|
||||||
|
ember-rise-particle-size = { size = 1.4 }
|
||||||
|
ember-rise-alpha = { size = 0.26 }
|
||||||
|
ember-rise-color = { color = "#7FD1B5" }
|
||||||
|
|
||||||
|
# Shader-like viewport overlay — deep green stone atmosphere
|
||||||
|
viewport-wash-enabled = { size = 1.0 }
|
||||||
|
viewport-wash-alpha = { size = 0.05 }
|
||||||
|
viewport-wash-tl = { color = "#12402E" }
|
||||||
|
viewport-wash-tr = { color = "#0E3828" }
|
||||||
|
viewport-wash-bl = { color = "#16442E" }
|
||||||
|
viewport-wash-br = { color = "#1A4A34" }
|
||||||
|
viewport-wash-rotate = { size = 0.015 }
|
||||||
|
viewport-wash-pulse = { size = 0.0 }
|
||||||
|
viewport-wash-pulse-depth = { size = 0.0 }
|
||||||
|
|
||||||
|
viewport-vignette-enabled = { size = 1.0 }
|
||||||
|
viewport-vignette-color = { color = "#04140D" }
|
||||||
|
viewport-vignette-radius = { size = 0.22 }
|
||||||
|
viewport-vignette-alpha = { size = 0.15 }
|
||||||
@@ -19,16 +19,16 @@ elevation = { --elevation-0 = "#FAFAFA", --elevation-1 = "#F2F3F5", --elevation-
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#2A2C30"
|
--on-background = "#2A2C30"
|
||||||
--on-surface = "#2A2C30"
|
--on-surface = "#2A2C30"
|
||||||
--on-surface-medium = "rgba(42,44,48,0.68)"
|
--on-surface-medium = "rgba(42,44,48,0.86)"
|
||||||
--on-surface-disabled = "rgba(42,44,48,0.38)"
|
--on-surface-disabled = "rgba(42,44,48,0.62)"
|
||||||
--error = "#8C5A62"
|
--error = "#8C5A62"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#5A7E5C"
|
--success = "#3D7A42"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#8A7A52"
|
--warning = "#9A7A2E"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(42,44,48,0.12)"
|
--divider = "rgba(42,44,48,0.20)"
|
||||||
--outline = "rgba(42,44,48,0.14)"
|
--outline = "rgba(42,44,48,0.24)"
|
||||||
--scrim = "rgba(0,0,0,0.42)"
|
--scrim = "rgba(0,0,0,0.42)"
|
||||||
--surface-hover = "rgba(42,44,48,0.04)"
|
--surface-hover = "rgba(42,44,48,0.04)"
|
||||||
--surface-alt = "rgba(42,44,48,0.02)"
|
--surface-alt = "rgba(42,44,48,0.02)"
|
||||||
@@ -47,14 +47,14 @@ elevation = { --elevation-0 = "#FAFAFA", --elevation-1 = "#F2F3F5", --elevation-
|
|||||||
--chart-hover-ring = "rgba(42,44,48,0.24)"
|
--chart-hover-ring = "rgba(42,44,48,0.24)"
|
||||||
--tooltip-bg = "rgba(50,52,58,0.92)"
|
--tooltip-bg = "rgba(50,52,58,0.92)"
|
||||||
--tooltip-border = "rgba(42,44,48,0.10)"
|
--tooltip-border = "rgba(42,44,48,0.10)"
|
||||||
--glass-fill = "rgba(255,255,255,0.55)"
|
--glass-fill = "rgba(255,255,255,0.20)"
|
||||||
--glass-border = "rgba(42,44,48,0.10)"
|
--glass-border = "rgba(42,44,48,0.10)"
|
||||||
--glass-noise-tint = "rgba(42,44,48,0.015)"
|
--glass-noise-tint = "rgba(42,44,48,0.015)"
|
||||||
--tactile-top = "rgba(255,255,255,0.35)"
|
--tactile-top = "rgba(255,255,255,0.35)"
|
||||||
--tactile-bottom = "rgba(255,255,255,0.04)"
|
--tactile-bottom = "rgba(255,255,255,0.04)"
|
||||||
--hover-overlay = "rgba(42,44,48,0.04)"
|
--hover-overlay = "rgba(42,44,48,0.10)"
|
||||||
--active-overlay = "rgba(42,44,48,0.08)"
|
--active-overlay = "rgba(42,44,48,0.08)"
|
||||||
--rim-light = "rgba(42,44,48,0.06)"
|
--rim-light = "rgba(42,44,48,0.22)"
|
||||||
--status-divider = "rgba(42,44,48,0.08)"
|
--status-divider = "rgba(42,44,48,0.08)"
|
||||||
--sidebar-hover = "rgba(42,44,48,0.05)"
|
--sidebar-hover = "rgba(42,44,48,0.05)"
|
||||||
--sidebar-icon = "rgba(42,44,48,0.45)"
|
--sidebar-icon = "rgba(42,44,48,0.45)"
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ elevation = { --elevation-0 = "#FAFAF8", --elevation-1 = "#F0EEEC", --elevation-
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#2C2A28"
|
--on-background = "#2C2A28"
|
||||||
--on-surface = "#2C2A28"
|
--on-surface = "#2C2A28"
|
||||||
--on-surface-medium = "rgba(44,42,40,0.68)"
|
--on-surface-medium = "rgba(44,42,40,0.86)"
|
||||||
--on-surface-disabled = "rgba(44,42,40,0.38)"
|
--on-surface-disabled = "rgba(44,42,40,0.62)"
|
||||||
--error = "#8C5250"
|
--error = "#8C5250"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#5C7A62"
|
--success = "#3F7A48"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#8A7A4C"
|
--warning = "#9A7A2E"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(80,75,68,0.12)"
|
--divider = "rgba(80,75,68,0.20)"
|
||||||
--outline = "rgba(80,75,68,0.14)"
|
--outline = "rgba(80,75,68,0.24)"
|
||||||
--scrim = "rgba(20,18,16,0.42)"
|
--scrim = "rgba(20,18,16,0.42)"
|
||||||
--surface-hover = "rgba(110,117,128,0.05)"
|
--surface-hover = "rgba(110,117,128,0.05)"
|
||||||
--surface-alt = "rgba(110,117,128,0.025)"
|
--surface-alt = "rgba(110,117,128,0.025)"
|
||||||
@@ -48,14 +48,14 @@ elevation = { --elevation-0 = "#FAFAF8", --elevation-1 = "#F0EEEC", --elevation-
|
|||||||
--chart-hover-ring = "rgba(110,117,128,0.24)"
|
--chart-hover-ring = "rgba(110,117,128,0.24)"
|
||||||
--tooltip-bg = "rgba(44,42,40,0.94)"
|
--tooltip-bg = "rgba(44,42,40,0.94)"
|
||||||
--tooltip-border = "rgba(110,117,128,0.10)"
|
--tooltip-border = "rgba(110,117,128,0.10)"
|
||||||
--glass-fill = "rgba(255,255,254,0.62)"
|
--glass-fill = "rgba(255,255,254,0.20)"
|
||||||
--glass-border = "rgba(110,117,128,0.10)"
|
--glass-border = "rgba(110,117,128,0.10)"
|
||||||
--glass-noise-tint = "rgba(80,75,68,0.02)"
|
--glass-noise-tint = "rgba(80,75,68,0.02)"
|
||||||
--tactile-top = "rgba(255,255,255,0.45)"
|
--tactile-top = "rgba(255,255,255,0.45)"
|
||||||
--tactile-bottom = "rgba(255,255,255,0.06)"
|
--tactile-bottom = "rgba(255,255,255,0.06)"
|
||||||
--hover-overlay = "rgba(110,117,128,0.04)"
|
--hover-overlay = "rgba(110,117,128,0.10)"
|
||||||
--active-overlay = "rgba(110,117,128,0.08)"
|
--active-overlay = "rgba(110,117,128,0.08)"
|
||||||
--rim-light = "rgba(180,175,168,0.10)"
|
--rim-light = "rgba(44,42,40,0.22)"
|
||||||
--status-divider = "rgba(110,117,128,0.08)"
|
--status-divider = "rgba(110,117,128,0.08)"
|
||||||
--sidebar-hover = "rgba(110,117,128,0.06)"
|
--sidebar-hover = "rgba(110,117,128,0.06)"
|
||||||
--sidebar-icon = "rgba(44,42,40,0.48)"
|
--sidebar-icon = "rgba(44,42,40,0.48)"
|
||||||
|
|||||||
@@ -2,25 +2,25 @@
|
|||||||
name = "Obsidian"
|
name = "Obsidian"
|
||||||
author = "The Hush Developers"
|
author = "The Hush Developers"
|
||||||
dark = true
|
dark = true
|
||||||
elevation = { --elevation-0 = "#0E0B14", --elevation-1 = "#17121E", --elevation-2 = "#1C1625", --elevation-3 = "#211A2C", --elevation-4 = "#261E33" }
|
elevation = { --elevation-0 = "#0E0B14", --elevation-1 = "#17121E", --elevation-2 = "#252030", --elevation-3 = "#2D2838", --elevation-4 = "#353040" }
|
||||||
images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
||||||
|
|
||||||
[theme.palette]
|
[theme.palette]
|
||||||
--primary = "#AB47BC"
|
--primary = "#9A6BA8"
|
||||||
--primary-variant = "#8E24AA"
|
--primary-variant = "#7A4888"
|
||||||
--primary-light = "#CE93D8"
|
--primary-light = "#C5A8CC"
|
||||||
--secondary = "#B388FF"
|
--secondary = "#A898D8"
|
||||||
--secondary-variant = "#7C4DFF"
|
--secondary-variant = "#8074C8"
|
||||||
--secondary-light = "#D1C4E9"
|
--secondary-light = "#CCC4D9"
|
||||||
--background = "#0A0810"
|
--background = "#0A0810"
|
||||||
--surface = "#110E18"
|
--surface = "#110E18"
|
||||||
--surface-variant = "#1C1625"
|
--surface-variant = "#252030"
|
||||||
--on-primary = "#FFFFFF"
|
--on-primary = "#FFFFFF"
|
||||||
--on-secondary = "#000000"
|
--on-secondary = "#000000"
|
||||||
--on-background = "#E8E0F0"
|
--on-background = "#E8E0F0"
|
||||||
--on-surface = "#E8E0F0"
|
--on-surface = "#E8E0F0"
|
||||||
--on-surface-medium = "rgba(232,224,240,0.75)"
|
--on-surface-medium = "rgba(232,224,240,0.85)"
|
||||||
--on-surface-disabled = "rgba(232,224,240,0.45)"
|
--on-surface-disabled = "rgba(232,224,240,0.58)"
|
||||||
--error = "#CF6679"
|
--error = "#CF6679"
|
||||||
--on-error = "#000000"
|
--on-error = "#000000"
|
||||||
--success = "#81C784"
|
--success = "#81C784"
|
||||||
@@ -35,7 +35,7 @@ images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "log
|
|||||||
--surface-active = "rgba(200,180,255,0.10)"
|
--surface-active = "rgba(200,180,255,0.10)"
|
||||||
--glass-button = "rgba(200,180,255,0.06)"
|
--glass-button = "rgba(200,180,255,0.06)"
|
||||||
--glass-button-hover = "rgba(200,180,255,0.12)"
|
--glass-button-hover = "rgba(200,180,255,0.12)"
|
||||||
--card-border = "rgba(200,180,255,0.14)"
|
--card-border = "rgba(200,180,255,0.26)"
|
||||||
--text-shadow = "rgba(0,0,0,0.50)"
|
--text-shadow = "rgba(0,0,0,0.50)"
|
||||||
--input-overlay-text = "rgba(232,224,240,0.30)"
|
--input-overlay-text = "rgba(232,224,240,0.30)"
|
||||||
--slider-text = "rgba(232,224,240,0.85)"
|
--slider-text = "rgba(232,224,240,0.85)"
|
||||||
@@ -48,13 +48,13 @@ images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "log
|
|||||||
--tooltip-bg = "rgba(14,11,20,0.92)"
|
--tooltip-bg = "rgba(14,11,20,0.92)"
|
||||||
--tooltip-border = "rgba(200,180,255,0.12)"
|
--tooltip-border = "rgba(200,180,255,0.12)"
|
||||||
--glass-fill = "rgba(200,180,255,0.08)"
|
--glass-fill = "rgba(200,180,255,0.08)"
|
||||||
--glass-border = "rgba(200,180,255,0.14)"
|
--glass-border = "rgba(154,107,168,0.30)"
|
||||||
--glass-noise-tint = "rgba(200,180,255,0.03)"
|
--glass-noise-tint = "rgba(200,180,255,0.03)"
|
||||||
--tactile-top = "rgba(200,180,255,0.06)"
|
--tactile-top = "rgba(200,180,255,0.06)"
|
||||||
--tactile-bottom = "rgba(200,180,255,0.0)"
|
--tactile-bottom = "rgba(200,180,255,0.0)"
|
||||||
--hover-overlay = "rgba(200,180,255,0.05)"
|
--hover-overlay = "rgba(200,180,255,0.05)"
|
||||||
--active-overlay = "rgba(200,180,255,0.10)"
|
--active-overlay = "rgba(200,180,255,0.10)"
|
||||||
--rim-light = "rgba(200,180,255,0.08)"
|
--rim-light = "rgba(200,180,255,0.14)"
|
||||||
--status-divider = "rgba(200,180,255,0.08)"
|
--status-divider = "rgba(200,180,255,0.08)"
|
||||||
--sidebar-hover = "rgba(200,180,255,0.10)"
|
--sidebar-hover = "rgba(200,180,255,0.10)"
|
||||||
--sidebar-icon = "rgba(200,180,255,0.42)"
|
--sidebar-icon = "rgba(200,180,255,0.42)"
|
||||||
@@ -65,19 +65,19 @@ images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "log
|
|||||||
--window-control-hover = "rgba(200,180,255,0.12)"
|
--window-control-hover = "rgba(200,180,255,0.12)"
|
||||||
--window-close-hover = "rgba(232,17,35,0.78)"
|
--window-close-hover = "rgba(232,17,35,0.78)"
|
||||||
--spinner-track = "rgba(200,180,255,0.10)"
|
--spinner-track = "rgba(200,180,255,0.10)"
|
||||||
--spinner-active = "rgba(179,136,255,0.85)"
|
--spinner-active = "rgba(168,152,216,0.85)"
|
||||||
--shutdown-panel-bg = "rgba(10,8,16,0.90)"
|
--shutdown-panel-bg = "rgba(10,8,16,0.90)"
|
||||||
--shutdown-panel-border = "rgba(200,180,255,0.07)"
|
--shutdown-panel-border = "rgba(200,180,255,0.07)"
|
||||||
--ram-bar-app = "#AB47BC"
|
--ram-bar-app = "#9A6BA8"
|
||||||
--ram-bar-system = "rgba(255,255,255,0.18)"
|
--ram-bar-system = "rgba(255,255,255,0.18)"
|
||||||
--accent-total = "#CE93D8"
|
--accent-total = "#C5A8CC"
|
||||||
--accent-shielded = "#80CBC4"
|
--accent-shielded = "#8AB8B4"
|
||||||
--accent-transparent = "#FFAB91"
|
--accent-transparent = "#D8B0A0"
|
||||||
--accent-action = "#AB47BC"
|
--accent-action = "#9A6BA8"
|
||||||
--accent-market = "#80CBC4"
|
--accent-market = "#8AB8B4"
|
||||||
--accent-portfolio = "#B388FF"
|
--accent-portfolio = "#A898D8"
|
||||||
--toast-info-accent = "#AB47BC"
|
--toast-info-accent = "#9A6BA8"
|
||||||
--toast-info-text = "#CE93D8"
|
--toast-info-text = "#C5A8CC"
|
||||||
--toast-success-accent = "rgba(50,180,80,1.0)"
|
--toast-success-accent = "rgba(50,180,80,1.0)"
|
||||||
--toast-success-text = "rgba(180,255,180,1.0)"
|
--toast-success-text = "rgba(180,255,180,1.0)"
|
||||||
--toast-warning-accent = "rgba(204,166,50,1.0)"
|
--toast-warning-accent = "rgba(204,166,50,1.0)"
|
||||||
@@ -86,10 +86,10 @@ images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "log
|
|||||||
--toast-error-text = "rgba(255,153,153,1.0)"
|
--toast-error-text = "rgba(255,153,153,1.0)"
|
||||||
--snackbar-bg = "rgba(40,35,55,0.95)"
|
--snackbar-bg = "rgba(40,35,55,0.95)"
|
||||||
--snackbar-text = "rgba(232,224,240,0.87)"
|
--snackbar-text = "rgba(232,224,240,0.87)"
|
||||||
--snackbar-action = "rgba(179,136,255,1.0)"
|
--snackbar-action = "rgba(168,152,216,1.0)"
|
||||||
--snackbar-action-hover = "rgba(206,147,216,1.0)"
|
--snackbar-action-hover = "rgba(197,168,204,1.0)"
|
||||||
--switch-track-off = "rgba(200,180,255,0.12)"
|
--switch-track-off = "rgba(200,180,255,0.12)"
|
||||||
--switch-track-on = "rgba(171,71,188,0.50)"
|
--switch-track-on = "rgba(154,107,168,0.50)"
|
||||||
--switch-thumb-off = "#B0A0C0"
|
--switch-thumb-off = "#B0A0C0"
|
||||||
--switch-thumb-on = "#E8E0F0"
|
--switch-thumb-on = "#E8E0F0"
|
||||||
--control-shadow = "rgba(0,0,0,0.24)"
|
--control-shadow = "rgba(0,0,0,0.24)"
|
||||||
@@ -144,18 +144,18 @@ gradient-border-enabled = { size = 1.0 }
|
|||||||
gradient-border-speed = { size = 0.12 }
|
gradient-border-speed = { size = 0.12 }
|
||||||
gradient-border-thickness = { size = 1.5 }
|
gradient-border-thickness = { size = 1.5 }
|
||||||
gradient-border-alpha = { size = 0.55 }
|
gradient-border-alpha = { size = 0.55 }
|
||||||
gradient-border-color-a = { color = "#CE93D8" }
|
gradient-border-color-a = { color = "#C5A8CC" }
|
||||||
gradient-border-color-b = { color = "#3F51B5" }
|
gradient-border-color-b = { color = "#6878A8" }
|
||||||
|
|
||||||
ember-rise-enabled = { size = 0.0 }
|
ember-rise-enabled = { size = 0.0 }
|
||||||
|
|
||||||
# Shader-like viewport overlay — deep indigo crystal atmosphere
|
# Shader-like viewport overlay — deep indigo crystal atmosphere
|
||||||
viewport-wash-enabled = { size = 1.0 }
|
viewport-wash-enabled = { size = 1.0 }
|
||||||
viewport-wash-alpha = { size = 0.05 }
|
viewport-wash-alpha = { size = 0.05 }
|
||||||
viewport-wash-tl = { color = "#4A148C" }
|
viewport-wash-tl = { color = "#3A2860" }
|
||||||
viewport-wash-tr = { color = "#1A237E" }
|
viewport-wash-tr = { color = "#2A3058" }
|
||||||
viewport-wash-bl = { color = "#311B92" }
|
viewport-wash-bl = { color = "#302860" }
|
||||||
viewport-wash-br = { color = "#6A1B9A" }
|
viewport-wash-br = { color = "#4A3068" }
|
||||||
viewport-wash-rotate = { size = 0.015 }
|
viewport-wash-rotate = { size = 0.015 }
|
||||||
viewport-wash-pulse = { size = 0.0 }
|
viewport-wash-pulse = { size = 0.0 }
|
||||||
viewport-wash-pulse-depth = { size = 0.0 }
|
viewport-wash-pulse-depth = { size = 0.0 }
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ spacing-tokens = { xs = 2.0, sm = 4.0, md = 8.0, lg = 12.0, xl = 16.0, xxl = 24.
|
|||||||
name = "DragonX"
|
name = "DragonX"
|
||||||
author = "DanS"
|
author = "DanS"
|
||||||
dark = true
|
dark = true
|
||||||
elevation = { --elevation-0 = "#120A08", --elevation-1 = "#1A0F0C", --elevation-2 = "#201410", --elevation-3 = "#261914", --elevation-4 = "#2C1E18" }
|
elevation = { --elevation-0 = "#120A08", --elevation-1 = "#1A0F0C", --elevation-2 = "#281C16", --elevation-3 = "#30241C", --elevation-4 = "#382C22" }
|
||||||
images = { background_image = "backgrounds/texture/drgx_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
images = { background_image = "backgrounds/texture/drgx_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
||||||
|
|
||||||
[theme.palette]
|
[theme.palette]
|
||||||
@@ -32,13 +32,13 @@ images = { background_image = "backgrounds/texture/drgx_bg.png", logo = "logos/l
|
|||||||
--secondary-light = "#FF9E40"
|
--secondary-light = "#FF9E40"
|
||||||
--background = "#0C0606"
|
--background = "#0C0606"
|
||||||
--surface = "#120A08"
|
--surface = "#120A08"
|
||||||
--surface-variant = "#201410"
|
--surface-variant = "#281C16"
|
||||||
--on-primary = "#FFFFFF"
|
--on-primary = "#FFFFFF"
|
||||||
--on-secondary = "#000000"
|
--on-secondary = "#000000"
|
||||||
--on-background = "#F0E0D8"
|
--on-background = "#F0E0D8"
|
||||||
--on-surface = "#F0E0D8"
|
--on-surface = "#F0E0D8"
|
||||||
--on-surface-medium = "rgba(240,224,216,0.7)"
|
--on-surface-medium = "rgba(240,224,216,0.85)"
|
||||||
--on-surface-disabled = "rgba(240,224,216,0.44)"
|
--on-surface-disabled = "rgba(240,224,216,0.58)"
|
||||||
--error = "#FF5252"
|
--error = "#FF5252"
|
||||||
--on-error = "#000000"
|
--on-error = "#000000"
|
||||||
--success = "#81C784"
|
--success = "#81C784"
|
||||||
@@ -66,13 +66,13 @@ images = { background_image = "backgrounds/texture/drgx_bg.png", logo = "logos/l
|
|||||||
--tooltip-bg = "rgba(12,8,6,0.92)"
|
--tooltip-bg = "rgba(12,8,6,0.92)"
|
||||||
--tooltip-border = "rgba(255,180,140,0.12)"
|
--tooltip-border = "rgba(255,180,140,0.12)"
|
||||||
--glass-fill = "rgba(255,180,140,0.08)"
|
--glass-fill = "rgba(255,180,140,0.08)"
|
||||||
--glass-border = "rgba(255,180,140,0.13)"
|
--glass-border = "rgba(211,47,47,0.26)"
|
||||||
--glass-noise-tint = "rgba(255,180,140,0.03)"
|
--glass-noise-tint = "rgba(255,180,140,0.03)"
|
||||||
--tactile-top = "rgba(255,180,140,0.06)"
|
--tactile-top = "rgba(255,180,140,0.06)"
|
||||||
--tactile-bottom = "rgba(255,180,140,0.0)"
|
--tactile-bottom = "rgba(255,180,140,0.0)"
|
||||||
--hover-overlay = "rgba(255,180,140,0.05)"
|
--hover-overlay = "rgba(255,180,140,0.05)"
|
||||||
--active-overlay = "rgba(255,180,140,0.10)"
|
--active-overlay = "rgba(255,180,140,0.10)"
|
||||||
--rim-light = "rgba(255,180,140,0.08)"
|
--rim-light = "rgba(255,180,140,0.14)"
|
||||||
--status-divider = "rgba(255,180,140,0.08)"
|
--status-divider = "rgba(255,180,140,0.08)"
|
||||||
--sidebar-hover = "rgba(255,180,140,0.10)"
|
--sidebar-hover = "rgba(255,180,140,0.10)"
|
||||||
--sidebar-icon = "rgba(255,180,140,0.36)"
|
--sidebar-icon = "rgba(255,180,140,0.36)"
|
||||||
@@ -485,12 +485,12 @@ suggestion-trunc-len = { size = 50 }
|
|||||||
fee-rounding = { size = 10.0 }
|
fee-rounding = { size = 10.0 }
|
||||||
amount-bar-max-btn-width = { size = 80.0 }
|
amount-bar-max-btn-width = { size = 80.0 }
|
||||||
amount-bar-height = { size = 22.0 }
|
amount-bar-height = { size = 22.0 }
|
||||||
confirm-popup-max-width = { size = 420.0 }
|
confirm-popup-max-width = { size = 560.0 }
|
||||||
confirm-addr-card-height = { size = 28.0 }
|
confirm-addr-card-height = { size = 28.0 }
|
||||||
confirm-amount-card-height = { size = 70.0 }
|
confirm-amount-card-height = { size = 96.0 }
|
||||||
confirm-row-step = { size = 16.0 }
|
confirm-row-step = { size = 24.0 }
|
||||||
confirm-val-col-x = { size = 90.0 }
|
confirm-val-col-x = { size = 150.0 }
|
||||||
confirm-usd-col-x = { size = 80.0 }
|
confirm-usd-col-x = { size = 116.0 }
|
||||||
progress-card-height = { size = 36.0 }
|
progress-card-height = { size = 36.0 }
|
||||||
progress-card-height-txid = { size = 52.0 }
|
progress-card-height-txid = { size = 52.0 }
|
||||||
progress-card-pad-x = { size = 12.0 }
|
progress-card-pad-x = { size = 12.0 }
|
||||||
@@ -516,10 +516,10 @@ error-icon-inset = { size = 20.0 }
|
|||||||
error-btn-rounding = { size = 4.0 }
|
error-btn-rounding = { size = 4.0 }
|
||||||
progress-glass-rounding-ratio = { size = 0.75 }
|
progress-glass-rounding-ratio = { size = 0.75 }
|
||||||
confirm-addr-card-min-height = { size = 24.0 }
|
confirm-addr-card-min-height = { size = 24.0 }
|
||||||
confirm-val-col-min-x = { size = 70.0 }
|
confirm-val-col-min-x = { size = 112.0 }
|
||||||
confirm-usd-col-min-x = { size = 60.0 }
|
confirm-usd-col-min-x = { size = 92.0 }
|
||||||
confirm-amount-card-min-height = { size = 54.0 }
|
confirm-amount-card-min-height = { size = 82.0 }
|
||||||
confirm-row-step-min = { size = 12.0 }
|
confirm-row-step-min = { size = 20.0 }
|
||||||
action-btn-min-height = { size = 26.0 }
|
action-btn-min-height = { size = 26.0 }
|
||||||
recent-icon-min-size = { size = 3.5 }
|
recent-icon-min-size = { size = 3.5 }
|
||||||
recent-icon-size = { size = 5.0 }
|
recent-icon-size = { size = 5.0 }
|
||||||
@@ -566,7 +566,7 @@ txid-trunc-len = { size = 14 }
|
|||||||
txid-label-x-offset = { size = 20.0 }
|
txid-label-x-offset = { size = 20.0 }
|
||||||
txid-copy-btn-right-offset = { size = 50.0 }
|
txid-copy-btn-right-offset = { size = 50.0 }
|
||||||
txid-copy-btn-y-offset = { size = 2.0 }
|
txid-copy-btn-y-offset = { size = 2.0 }
|
||||||
confirm-popup-width-ratio = { size = 0.85 }
|
confirm-popup-width-ratio = { size = 0.92 }
|
||||||
confirm-glass-rounding-ratio = { size = 0.75 }
|
confirm-glass-rounding-ratio = { size = 0.75 }
|
||||||
confirm-addr-trunc-len = { size = 48 }
|
confirm-addr-trunc-len = { size = 48 }
|
||||||
confirm-divider-thickness = { size = 1.0 }
|
confirm-divider-thickness = { size = 1.0 }
|
||||||
@@ -700,6 +700,12 @@ status-pill-bg-alpha = { size = 30 }
|
|||||||
status-pill-y-offset = { size = 1 }
|
status-pill-y-offset = { size = 1 }
|
||||||
confirmed-threshold = { size = 10 }
|
confirmed-threshold = { size = 10 }
|
||||||
|
|
||||||
|
# Persistent node/RPC error strip at the top of the content column (see App::renderNodeStatusBanner).
|
||||||
|
# Slightly taller than the per-tab sync banner so it comfortably holds the Reconnect/Restart action.
|
||||||
|
[banners.node-status]
|
||||||
|
min-height = { size = 26.0 }
|
||||||
|
height = { size = 30.0 }
|
||||||
|
|
||||||
[tabs.transactions]
|
[tabs.transactions]
|
||||||
search-max-width = 300.0
|
search-max-width = 300.0
|
||||||
search-width-ratio = 0.3
|
search-width-ratio = 0.3
|
||||||
@@ -766,7 +772,7 @@ control-card-min-height = { size = 60.0 }
|
|||||||
active-cell-border-thickness = { size = 1.5 }
|
active-cell-border-thickness = { size = 1.5 }
|
||||||
cell-border-thickness = { size = 1.0 }
|
cell-border-thickness = { size = 1.0 }
|
||||||
button-icon-y-ratio = { size = 0.42 }
|
button-icon-y-ratio = { size = 0.42 }
|
||||||
button-label-y-ratio = { size = 0.78 }
|
button-label-y-ratio = { size = 0.72 }
|
||||||
chart-line-thickness = { size = 1.5 }
|
chart-line-thickness = { size = 1.5 }
|
||||||
details-card-min-height = { size = 50.0 }
|
details-card-min-height = { size = 50.0 }
|
||||||
ram-bar = { height = 6.0, rounding = 3.0, opacity = 0.65 }
|
ram-bar = { height = 6.0, rounding = 3.0, opacity = 0.65 }
|
||||||
@@ -779,8 +785,11 @@ pool-url-input = { width = 300.0, height = 28.0 }
|
|||||||
pool-worker-input = { width = 200.0, height = 28.0 }
|
pool-worker-input = { width = 200.0, height = 28.0 }
|
||||||
log-panel-height = { size = 120.0 }
|
log-panel-height = { size = 120.0 }
|
||||||
log-panel-min = { size = 60.0 }
|
log-panel-min = { size = 60.0 }
|
||||||
|
idle-row-height = { size = 28.0 }
|
||||||
|
idle-combo-width = { size = 64.0 }
|
||||||
|
|
||||||
[tabs.peers]
|
[tabs.peers]
|
||||||
|
refresh-button = { size = 110.0 }
|
||||||
table-min-height = 150.0
|
table-min-height = 150.0
|
||||||
table-height-ratio = 0.45
|
table-height-ratio = 0.45
|
||||||
version-column-width = 150.0
|
version-column-width = 150.0
|
||||||
@@ -807,6 +816,7 @@ dir-pill-padding = { size = 4.0 }
|
|||||||
dir-pill-y-offset = { size = 1.0 }
|
dir-pill-y-offset = { size = 1.0 }
|
||||||
dir-pill-y-bottom = { size = 3.0 }
|
dir-pill-y-bottom = { size = 3.0 }
|
||||||
dir-pill-rounding = { size = 3.0 }
|
dir-pill-rounding = { size = 3.0 }
|
||||||
|
seed-badge-padding = { size = 3.0 }
|
||||||
tls-badge-min-width = { size = 20.0 }
|
tls-badge-min-width = { size = 20.0 }
|
||||||
tls-badge-width = { size = 28.0 }
|
tls-badge-width = { size = 28.0 }
|
||||||
tls-badge-rounding = { size = 3.0 }
|
tls-badge-rounding = { size = 3.0 }
|
||||||
@@ -870,7 +880,7 @@ accent-stripe-inset-ratio = { size = 0.0 }
|
|||||||
accent-stripe-left-offset = { size = 0.0 }
|
accent-stripe-left-offset = { size = 0.0 }
|
||||||
accent-stripe-width = { size = 4.0 }
|
accent-stripe-width = { size = 4.0 }
|
||||||
accent-stripe-rounding = { size = 1.5 }
|
accent-stripe-rounding = { size = 1.5 }
|
||||||
chart-y-axis-min-padding = { size = 40.0 }
|
chart-y-axis-min-padding = { size = 54.0 }
|
||||||
chart-y-axis-padding = { size = 70.0 }
|
chart-y-axis-padding = { size = 70.0 }
|
||||||
chart-dot-min-radius = { size = 1.5 }
|
chart-dot-min-radius = { size = 1.5 }
|
||||||
chart-dot-radius = { size = 2.0 }
|
chart-dot-radius = { size = 2.0 }
|
||||||
@@ -890,8 +900,21 @@ pair-bar-arrow-size = { size = 28.0 }
|
|||||||
exchange-combo-width = { size = 180.0 }
|
exchange-combo-width = { size = 180.0 }
|
||||||
exchange-top-gap = { size = 0.0 }
|
exchange-top-gap = { size = 0.0 }
|
||||||
|
|
||||||
|
[tabs.explorer]
|
||||||
|
search-input-width = { size = 400.0 }
|
||||||
|
search-button-width = { size = 100.0 }
|
||||||
|
search-bar-height = { size = 32.0 }
|
||||||
|
row-height = { size = 32.0 }
|
||||||
|
row-rounding = { size = 4.0 }
|
||||||
|
tx-row-height = { size = 28.0 }
|
||||||
|
label-column = { size = 160.0 }
|
||||||
|
detail-modal-width = { size = 700.0 }
|
||||||
|
detail-max-height = { size = 600.0 }
|
||||||
|
scroll-fade-zone = { size = 24.0 }
|
||||||
|
|
||||||
[tabs.console]
|
[tabs.console]
|
||||||
input-area-padding = 8.0
|
input-area-padding = 8.0
|
||||||
|
bg-darken-alpha = { size = 110.0 } # black overlay alpha (0-255) for the terminal-dark output + input
|
||||||
output-line-spacing = 2.0
|
output-line-spacing = 2.0
|
||||||
output = { line-spacing = 2 }
|
output = { line-spacing = 2 }
|
||||||
scroll-multiplier = { size = 3.0 }
|
scroll-multiplier = { size = 3.0 }
|
||||||
@@ -914,7 +937,7 @@ scanline-speed = { size = 40.0 }
|
|||||||
scanline-height = { size = 36.0 }
|
scanline-height = { size = 36.0 }
|
||||||
scanline-alpha = { size = 8.0 }
|
scanline-alpha = { size = 8.0 }
|
||||||
scanline-gap = { size = 2.0 }
|
scanline-gap = { size = 2.0 }
|
||||||
scanline-line-alpha = { size = 4.0 }
|
scanline-line-alpha = { size = 2.0 }
|
||||||
scanline-glow-spread = { size = 4.0 }
|
scanline-glow-spread = { size = 4.0 }
|
||||||
scanline-glow-intensity = { size = 0.6 }
|
scanline-glow-intensity = { size = 0.6 }
|
||||||
scanline-glow-color = { size = 255.0 }
|
scanline-glow-color = { size = 255.0 }
|
||||||
@@ -1152,6 +1175,8 @@ key-input = { height = 150 }
|
|||||||
rescan-height-input = { width = 100 }
|
rescan-height-input = { width = 100 }
|
||||||
import-button = { width = 120, font = "button" }
|
import-button = { width = 120, font = "button" }
|
||||||
close-button = { width = 100, font = "button" }
|
close-button = { width = 100, font = "button" }
|
||||||
|
paste-preview-alpha = { size = 0.3 }
|
||||||
|
paste-preview-max-chars = { size = 200 }
|
||||||
|
|
||||||
[components]
|
[components]
|
||||||
|
|
||||||
@@ -1200,6 +1225,9 @@ notification-progress = { color = "var(--primary)", height = 4, position = 18 }
|
|||||||
fill-alpha = { size = 12.0 }
|
fill-alpha = { size = 12.0 }
|
||||||
noise-alpha = { size = 14.0 }
|
noise-alpha = { size = 14.0 }
|
||||||
|
|
||||||
|
[components.overlay-dialog]
|
||||||
|
confirm-btn-height = { size = 40.0 }
|
||||||
|
|
||||||
[components.qr-code]
|
[components.qr-code]
|
||||||
module-scale = { size = 4 }
|
module-scale = { size = 4 }
|
||||||
border-modules = { size = 2 }
|
border-modules = { size = 2 }
|
||||||
@@ -1212,9 +1240,10 @@ width = { size = 140.0 }
|
|||||||
collapsed-width = { size = 64.0 }
|
collapsed-width = { size = 64.0 }
|
||||||
collapse-anim-speed = { size = 10.0 }
|
collapse-anim-speed = { size = 10.0 }
|
||||||
auto-collapse-threshold = { size = 800.0 }
|
auto-collapse-threshold = { size = 800.0 }
|
||||||
section-gap = { size = 4.0 }
|
section-gap = { size = 8.0 }
|
||||||
section-label-pad-left = { size = 16.0 }
|
section-label-pad-left = { size = 16.0 }
|
||||||
item-height = { size = 42.0 }
|
section-label-pad-bottom = { size = 4.0 }
|
||||||
|
item-height = { size = 36.0 }
|
||||||
item-pad-x = { size = 8.0 }
|
item-pad-x = { size = 8.0 }
|
||||||
min-height = { size = 360.0 }
|
min-height = { size = 360.0 }
|
||||||
margin-top = { size = -12 }
|
margin-top = { size = -12 }
|
||||||
@@ -1230,8 +1259,8 @@ icon-half-size = { size = 7.0 }
|
|||||||
icon-label-gap = { size = 8.0 }
|
icon-label-gap = { size = 8.0 }
|
||||||
badge-radius-dot = { size = 4.0 }
|
badge-radius-dot = { size = 4.0 }
|
||||||
badge-radius-number = { size = 8.0 }
|
badge-radius-number = { size = 8.0 }
|
||||||
button-spacing = { size = 4.0 }
|
button-spacing = { size = 6.0 }
|
||||||
bottom-padding = { size = 0.0 }
|
bottom-padding = { size = 4.0 }
|
||||||
exit-icon-gap = { size = 4.0 }
|
exit-icon-gap = { size = 4.0 }
|
||||||
cutout-shadow-alpha = { size = 55 }
|
cutout-shadow-alpha = { size = 55 }
|
||||||
cutout-highlight-alpha = { size = 8 }
|
cutout-highlight-alpha = { size = 8 }
|
||||||
@@ -1247,8 +1276,8 @@ inset-shadow-fade-ratio = { size = 5.0 }
|
|||||||
[components.content-area]
|
[components.content-area]
|
||||||
padding-x = 0.0
|
padding-x = 0.0
|
||||||
padding-y = 0.0
|
padding-y = 0.0
|
||||||
margin-top = { size = 4.0 }
|
margin-top = { size = 6.0 }
|
||||||
margin-bottom = { size = -40.0 }
|
margin-bottom = { size = -39.0 }
|
||||||
edge-fade-zone = { size = 0.0 }
|
edge-fade-zone = { size = 0.0 }
|
||||||
|
|
||||||
[components.content-area.window]
|
[components.content-area.window]
|
||||||
@@ -1260,10 +1289,10 @@ page-fade-speed = { size = 8.0 }
|
|||||||
collapse-hysteresis = { size = 60.0 }
|
collapse-hysteresis = { size = 60.0 }
|
||||||
header-icon = { icon-dark = "logos/logo_ObsidianDragon_dark.png", icon-light = "logos/logo_ObsidianDragon_light.png" }
|
header-icon = { icon-dark = "logos/logo_ObsidianDragon_dark.png", icon-light = "logos/logo_ObsidianDragon_light.png" }
|
||||||
coin-icon = { icon = "logos/logo_dragonx_128.png" }
|
coin-icon = { icon = "logos/logo_dragonx_128.png" }
|
||||||
header-title = { font = "subtitle1", size = 14.0, pad-x = 16.0, pad-y = 12.0, logo-gap = 8.0, opacity = 0.7 }
|
header-title = { font = "subtitle1", size = 12.0, pad-x = 8.0, pad-y = 10.0, logo-gap = 4.0, opacity = 0.7, offset-y = -2.0 }
|
||||||
|
|
||||||
[components.main-window.window]
|
[components.main-window.window]
|
||||||
padding = [12, 38]
|
padding = [12, 36]
|
||||||
|
|
||||||
[components.shutdown]
|
[components.shutdown]
|
||||||
content-height = { height = 120.0 }
|
content-height = { height = 120.0 }
|
||||||
@@ -1305,8 +1334,13 @@ wallet-btn-padding = { size = 24.0 }
|
|||||||
rpc-label-min-width = { size = 70.0 }
|
rpc-label-min-width = { size = 70.0 }
|
||||||
rpc-label-width = { size = 85.0 }
|
rpc-label-width = { size = 85.0 }
|
||||||
security-combo-width = { size = 120.0 }
|
security-combo-width = { size = 120.0 }
|
||||||
|
node-grid-breakpoint = { size = 900.0 }
|
||||||
port-input-min-width = { size = 60.0 }
|
port-input-min-width = { size = 60.0 }
|
||||||
port-input-width-ratio = { size = 0.4 }
|
port-input-width-ratio = { size = 0.4 }
|
||||||
|
idle-combo-width = { size = 64.0 }
|
||||||
|
# Reserved height basis for the About-card logo; the logo is drawn scaled to the
|
||||||
|
# card's actual height (aspect-preserved) and capped to this * aspect in width.
|
||||||
|
about-logo-size = { size = 150.0 }
|
||||||
|
|
||||||
[components.main-layout]
|
[components.main-layout]
|
||||||
app-bar-height = { size = 64.0 }
|
app-bar-height = { size = 64.0 }
|
||||||
@@ -1357,6 +1391,17 @@ summary = { min-width = 280.0, max-width = 400.0, width-ratio = 0.32, min-height
|
|||||||
side-panel = { min-width = 280.0, max-width = 450.0, width-ratio = 0.4, min-height = 200.0, max-height = 350.0, height-ratio = 0.8 }
|
side-panel = { min-width = 280.0, max-width = 450.0, width-ratio = 0.4, min-height = 200.0, max-height = 350.0, height-ratio = 0.8 }
|
||||||
table = { min-height = 150.0, height-ratio = 0.45, min-remaining = 100.0, default-reserve = 30.0 }
|
table = { min-height = 150.0, height-ratio = 0.45, min-remaining = 100.0, default-reserve = 30.0 }
|
||||||
|
|
||||||
|
[dialog]
|
||||||
|
width-default = 480.0
|
||||||
|
width-lg = 600.0
|
||||||
|
width-xl = 660.0
|
||||||
|
min-width = 280.0
|
||||||
|
form-width = 400.0
|
||||||
|
action-width = 100.0
|
||||||
|
action-gap = 8.0
|
||||||
|
max-height-ratio = 0.94
|
||||||
|
compact-bottom-ratio = 0.64
|
||||||
|
|
||||||
[button]
|
[button]
|
||||||
min-width = 180.0
|
min-width = 180.0
|
||||||
width = 140.0
|
width = 140.0
|
||||||
@@ -1464,6 +1509,7 @@ progress-bar = { height = 6.0, radius = 3.0 }
|
|||||||
progress-width = { size = 260.0 }
|
progress-width = { size = 260.0 }
|
||||||
backdrop-alpha = { opacity = 0.80 }
|
backdrop-alpha = { opacity = 0.80 }
|
||||||
vertical-gap = { size = 8.0 }
|
vertical-gap = { size = 8.0 }
|
||||||
|
stall-timeout-sec = { size = 45.0 }
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# First-Run Wizard Screens
|
# First-Run Wizard Screens
|
||||||
@@ -1489,7 +1535,6 @@ title = { font = "h5" }
|
|||||||
input = { width = 320.0, height = 40.0 }
|
input = { width = 320.0, height = 40.0 }
|
||||||
unlock-button = { width = 320.0, height = 44.0, font = "subtitle1" }
|
unlock-button = { width = 320.0, height = 44.0, font = "subtitle1" }
|
||||||
error-text = { font = "caption" }
|
error-text = { font = "caption" }
|
||||||
backdrop-alpha = { opacity = 0.0 }
|
|
||||||
mode-toggle = { font = "caption" }
|
mode-toggle = { font = "caption" }
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
2307
scripts/add_missing_translations.py
Normal file
94
scripts/build-freetype-mingw.sh
Executable file
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Cross-build a MINIMAL static FreeType for the mingw-w64 (Windows) target.
|
||||||
|
#
|
||||||
|
# Why: the wallet's optional color-emoji rendering needs FreeType (to rasterize the COLR/CPAL Twemoji
|
||||||
|
# font). Native Linux/macOS pick up the system FreeType via find_package; the Debian/Ubuntu mingw-w64
|
||||||
|
# cross toolchain ships no FreeType, so we build one here. The Twemoji font is COLRv0 (layered vector),
|
||||||
|
# which FreeType renders WITHOUT libpng / harfbuzz / brotli / zlib — so this is a dependency-free static
|
||||||
|
# build (no external libs to also cross-compile), producing a self-contained libfreetype.a.
|
||||||
|
#
|
||||||
|
# Output: <prefix>/include/freetype2/... + <prefix>/lib/libfreetype.a (default prefix: third_party/freetype-mingw)
|
||||||
|
# build.sh --win-release runs this automatically and passes -DDRAGONX_MINGW_FREETYPE_PREFIX to CMake.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
FT_VERSION="2.13.3"
|
||||||
|
FT_SHA256="5c3a8e78f7b24c20b25b54ee575d6daa40007a5f4eea2845861c3409b3021747" # freetype-2.13.3.tar.gz
|
||||||
|
FT_URL="https://download.savannah.gnu.org/releases/freetype/freetype-${FT_VERSION}.tar.gz"
|
||||||
|
FT_URL_MIRROR="https://downloads.sourceforge.net/project/freetype/freetype2/${FT_VERSION}/freetype-${FT_VERSION}.tar.gz"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PREFIX="${1:-$SCRIPT_DIR/third_party/freetype-mingw}"
|
||||||
|
WORK="$SCRIPT_DIR/third_party/.freetype-mingw-build"
|
||||||
|
|
||||||
|
# Already built? (libfreetype.a present) → nothing to do.
|
||||||
|
if [[ -f "$PREFIX/lib/libfreetype.a" && -d "$PREFIX/include/freetype2" ]]; then
|
||||||
|
echo "FreeType (mingw) already built at: $PREFIX"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pick the mingw compilers (posix threads variant preferred, matching build.sh).
|
||||||
|
if command -v x86_64-w64-mingw32-gcc-posix &>/dev/null; then
|
||||||
|
MINGW_GCC=x86_64-w64-mingw32-gcc-posix; MINGW_GXX=x86_64-w64-mingw32-g++-posix
|
||||||
|
elif command -v x86_64-w64-mingw32-gcc &>/dev/null; then
|
||||||
|
MINGW_GCC=x86_64-w64-mingw32-gcc; MINGW_GXX=x86_64-w64-mingw32-g++
|
||||||
|
else
|
||||||
|
echo "ERROR: x86_64-w64-mingw32-gcc not found (install mingw-w64)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$WORK"
|
||||||
|
cd "$WORK"
|
||||||
|
|
||||||
|
TARBALL="freetype-${FT_VERSION}.tar.gz"
|
||||||
|
if [[ ! -f "$TARBALL" ]]; then
|
||||||
|
echo "Downloading FreeType ${FT_VERSION} ..."
|
||||||
|
curl -fsSL -o "$TARBALL" "$FT_URL" || curl -fsSL -o "$TARBALL" "$FT_URL_MIRROR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Verifying SHA-256 ..."
|
||||||
|
echo "${FT_SHA256} ${TARBALL}" | sha256sum -c - || {
|
||||||
|
echo "ERROR: FreeType tarball checksum mismatch (expected ${FT_SHA256})." >&2
|
||||||
|
echo " got: $(sha256sum "$TARBALL" | cut -d' ' -f1)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -rf "freetype-${FT_VERSION}"
|
||||||
|
tar xf "$TARBALL"
|
||||||
|
SRC="$WORK/freetype-${FT_VERSION}"
|
||||||
|
|
||||||
|
# Minimal mingw toolchain for FreeType's own CMake.
|
||||||
|
cat > "$WORK/ft-mingw-toolchain.cmake" <<TOOLCHAIN
|
||||||
|
set(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||||
|
set(CMAKE_C_COMPILER ${MINGW_GCC})
|
||||||
|
set(CMAKE_CXX_COMPILER ${MINGW_GXX})
|
||||||
|
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
TOOLCHAIN
|
||||||
|
|
||||||
|
echo "Configuring FreeType (static, no external deps) ..."
|
||||||
|
rm -rf "$WORK/build"
|
||||||
|
cmake -S "$SRC" -B "$WORK/build" \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="$WORK/ft-mingw-toolchain.cmake" \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DFT_DISABLE_ZLIB=ON \
|
||||||
|
-DFT_DISABLE_BZIP2=ON \
|
||||||
|
-DFT_DISABLE_PNG=ON \
|
||||||
|
-DFT_DISABLE_HARFBUZZ=ON \
|
||||||
|
-DFT_DISABLE_BROTLI=ON
|
||||||
|
|
||||||
|
echo "Building + installing FreeType ..."
|
||||||
|
cmake --build "$WORK/build" -j "$(nproc)"
|
||||||
|
cmake --install "$WORK/build"
|
||||||
|
|
||||||
|
if [[ -f "$PREFIX/lib/libfreetype.a" ]]; then
|
||||||
|
echo "OK: mingw FreeType -> $PREFIX/lib/libfreetype.a"
|
||||||
|
else
|
||||||
|
echo "ERROR: build did not produce libfreetype.a" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
755
scripts/build-lite-backend-artifact.sh
Executable file
@@ -0,0 +1,755 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This script uses bash 4+ features (mapfile, safe empty-array expansion under
|
||||||
|
# `set -u`). macOS ships bash 3.2, so re-exec under a newer bash when one is
|
||||||
|
# present (Homebrew), and fail with a clear message otherwise.
|
||||||
|
if [ "${BASH_VERSINFO:-0}" -lt 4 ]; then
|
||||||
|
for _newer_bash in /opt/homebrew/bin/bash /usr/local/bin/bash; do
|
||||||
|
[ -x "$_newer_bash" ] && exec "$_newer_bash" "$0" "$@"
|
||||||
|
done
|
||||||
|
echo "ERROR: build-lite-backend-artifact.sh requires bash 4+ (found ${BASH_VERSION:-unknown})." >&2
|
||||||
|
echo " On macOS: brew install bash" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
ABI_VERSION="sdxl-c-v1"
|
||||||
|
LINK_MODE="imported"
|
||||||
|
BACKEND_DIR="$PROJECT_ROOT/third_party/silentdragonxlite/lib"
|
||||||
|
BACKEND_SOURCE_DIR=""
|
||||||
|
BUILD_BACKEND_DIR=""
|
||||||
|
BACKEND_DEPENDENCY_DIR=""
|
||||||
|
BACKEND_DEPENDENCY_OVERRIDE_REQUESTED=false
|
||||||
|
OUT_DIR="$PROJECT_ROOT/build/lite-backend"
|
||||||
|
PLATFORM=""
|
||||||
|
RUST_TARGET=""
|
||||||
|
CARGO_TARGET_DIR_VALUE="${CARGO_TARGET_DIR:-}"
|
||||||
|
ARTIFACT_PATH=""
|
||||||
|
BUILD_ARTIFACT=true
|
||||||
|
BUILDER="${DRAGONX_LITE_BACKEND_BUILDER:-local}"
|
||||||
|
JOBS="${JOBS:-}"
|
||||||
|
SOURCE_DATE_EPOCH_VALUE="${SOURCE_DATE_EPOCH:-}"
|
||||||
|
REPRODUCIBLE=false
|
||||||
|
SIGNATURE_REQUIRED=false
|
||||||
|
SIGNATURE_FILE=""
|
||||||
|
SIGNATURE_FORMAT=""
|
||||||
|
SIGNATURE_VERIFICATION_TOOL=""
|
||||||
|
SIGNATURE_VERIFICATION_COMMAND=""
|
||||||
|
SIGNATURE_KEY_FINGERPRINT=""
|
||||||
|
SIGNATURE_CERTIFICATE_IDENTITY=""
|
||||||
|
SIGNATURE_CERTIFICATE_ISSUER=""
|
||||||
|
SIGNATURE_TRANSPARENCY_LOG_URL=""
|
||||||
|
SIGNATURE_VERIFIED_SHA256=""
|
||||||
|
SIGNATURE_POLICY_NAME="dragonx-lite-backend-signature-policy-v1"
|
||||||
|
SIGNATURE_POLICY_DEFINED_MANIFEST_VALUE=true
|
||||||
|
SIGNATURE_REQUIRED_MANIFEST_VALUE=false
|
||||||
|
SIGNATURE_METADATA_PROVIDED=false
|
||||||
|
SIGNATURE_VERIFICATION_PERFORMED=false
|
||||||
|
SIGNATURE_VERIFICATION_STATUS="not-provided"
|
||||||
|
SIGNATURE_FILE_SHA256=""
|
||||||
|
|
||||||
|
REQUIRED_SYMBOLS=(
|
||||||
|
litelib_wallet_exists
|
||||||
|
litelib_initialize_new
|
||||||
|
litelib_initialize_new_from_phrase
|
||||||
|
litelib_initialize_existing
|
||||||
|
litelib_execute
|
||||||
|
litelib_rust_free_string
|
||||||
|
litelib_check_server_online
|
||||||
|
litelib_shutdown
|
||||||
|
)
|
||||||
|
|
||||||
|
EXTRA_CARGO_ARGS=()
|
||||||
|
EXTRA_REMAP_PATH_PREFIXES=()
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Build or inventory the SDXL-compatible lite backend artifact.
|
||||||
|
|
||||||
|
Usage: $0 [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--platform linux|windows|macos Artifact platform. Defaults to host platform.
|
||||||
|
--rust-target TRIPLE Cargo target triple for cross builds.
|
||||||
|
--cargo-target-dir PATH Isolated Cargo target directory for clean builds.
|
||||||
|
--backend-dir PATH SilentDragonXLite/lib source directory.
|
||||||
|
--silentdragonxlitelib-dir PATH Override the wrapper's silentdragonxlitelib dependency path.
|
||||||
|
--out-dir PATH Output directory for copied artifact and metadata.
|
||||||
|
--reproducible Add deterministic Rust path remaps for clean builds.
|
||||||
|
--remap-path-prefix FROM=TO Extra rustc path remap used with --reproducible.
|
||||||
|
--builder NAME Redacted builder/provenance label. Default: local.
|
||||||
|
-j, --jobs N Cargo parallel jobs.
|
||||||
|
--cargo-arg ARG Extra argument forwarded to cargo build.
|
||||||
|
-h, --help Show this help.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
<out>/<platform>/<artifact>
|
||||||
|
<out>/<platform>/lite-backend-symbols.txt
|
||||||
|
<out>/<platform>/lite-backend-artifact-manifest.json
|
||||||
|
|
||||||
|
The lite backend is always built from the vendored in-tree source
|
||||||
|
(third_party/silentdragonxlite), which is the trust root. Prebuilt artifacts
|
||||||
|
and self-attested signature metadata are NOT accepted (F15-1) — the previous
|
||||||
|
scheme only recorded an unverified "verified" claim. The script captures the
|
||||||
|
freshly-built artifact's symbols and checksum, and records build provenance.
|
||||||
|
It does not load the library, resolve function pointers, call SDXL, sign,
|
||||||
|
upload, or publish artifacts.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
info() { printf '[lite-backend] %s\n' "$*"; }
|
||||||
|
warn() { printf '[lite-backend] warning: %s\n' "$*" >&2; }
|
||||||
|
die() { printf '[lite-backend] ERROR: %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
absolute_path() {
|
||||||
|
local path="$1"
|
||||||
|
if [[ "$path" = /* ]]; then
|
||||||
|
printf '%s\n' "$path"
|
||||||
|
else
|
||||||
|
printf '%s/%s\n' "$PWD" "$path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
host_platform() {
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux) printf 'linux\n' ;;
|
||||||
|
Darwin) printf 'macos\n' ;;
|
||||||
|
MINGW*|MSYS*|CYGWIN*) printf 'windows\n' ;;
|
||||||
|
*) die "unsupported host platform: $(uname -s)" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_platform() {
|
||||||
|
case "$1" in
|
||||||
|
linux|Linux) printf 'linux\n' ;;
|
||||||
|
windows|win|Win|Windows) printf 'windows\n' ;;
|
||||||
|
macos|mac|darwin|Darwin) printf 'macos\n' ;;
|
||||||
|
"") host_platform ;;
|
||||||
|
*) die "unsupported platform: $1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--platform)
|
||||||
|
[[ $# -ge 2 ]] || die "--platform requires a value"
|
||||||
|
PLATFORM="$(normalize_platform "$2")"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--rust-target)
|
||||||
|
[[ $# -ge 2 ]] || die "--rust-target requires a value"
|
||||||
|
RUST_TARGET="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--cargo-target-dir)
|
||||||
|
[[ $# -ge 2 ]] || die "--cargo-target-dir requires a value"
|
||||||
|
CARGO_TARGET_DIR_VALUE="$(absolute_path "$2")"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--backend-dir)
|
||||||
|
[[ $# -ge 2 ]] || die "--backend-dir requires a value"
|
||||||
|
BACKEND_DIR="$(absolute_path "$2")"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--silentdragonxlitelib-dir)
|
||||||
|
[[ $# -ge 2 ]] || die "--silentdragonxlitelib-dir requires a value"
|
||||||
|
BACKEND_DEPENDENCY_DIR="$(absolute_path "$2")"
|
||||||
|
BACKEND_DEPENDENCY_OVERRIDE_REQUESTED=true
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--out-dir)
|
||||||
|
[[ $# -ge 2 ]] || die "--out-dir requires a value"
|
||||||
|
OUT_DIR="$(absolute_path "$2")"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--artifact|--no-build)
|
||||||
|
die "$1 was removed (F15-1): the lite backend must be built from the vendored in-tree source (third_party/silentdragonxlite); prebuilt artifacts are no longer accepted."
|
||||||
|
;;
|
||||||
|
--reproducible)
|
||||||
|
REPRODUCIBLE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--remap-path-prefix)
|
||||||
|
[[ $# -ge 2 ]] || die "--remap-path-prefix requires FROM=TO"
|
||||||
|
[[ "$2" == *=* ]] || die "--remap-path-prefix requires FROM=TO"
|
||||||
|
EXTRA_REMAP_PATH_PREFIXES+=("$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--builder)
|
||||||
|
[[ $# -ge 2 ]] || die "--builder requires a value"
|
||||||
|
BUILDER="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-required|--signature-file|--signature-path|--signature-format|\
|
||||||
|
--signature-verification-tool|--signature-tool|--signature-verification-command|\
|
||||||
|
--signature-key-fingerprint|--signature-certificate-identity|--signature-certificate-issuer|\
|
||||||
|
--signature-transparency-log-url|--signature-verified-sha256)
|
||||||
|
die "signature-attestation flags were removed (F15-1): they recorded a self-attested \"verified\" claim without running any cryptographic verifier. The lite backend is built from the vendored in-tree source, which is the trust root."
|
||||||
|
;;
|
||||||
|
-j|--jobs)
|
||||||
|
[[ $# -ge 2 ]] || die "--jobs requires a value"
|
||||||
|
JOBS="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--cargo-arg)
|
||||||
|
[[ $# -ge 2 ]] || die "--cargo-arg requires a value"
|
||||||
|
EXTRA_CARGO_ARGS+=("$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*) die "unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
PLATFORM="$(normalize_platform "$PLATFORM")"
|
||||||
|
BACKEND_SOURCE_DIR="$BACKEND_DIR"
|
||||||
|
BUILD_BACKEND_DIR="$BACKEND_SOURCE_DIR"
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "windows" && -z "$RUST_TARGET" ]]; then
|
||||||
|
RUST_TARGET="x86_64-pc-windows-gnu"
|
||||||
|
fi
|
||||||
|
if [[ "$PLATFORM" == "macos" && -z "$RUST_TARGET" && "$(host_platform)" != "macos" ]]; then
|
||||||
|
die "macOS artifacts require --rust-target when not running on macOS"
|
||||||
|
fi
|
||||||
|
if [[ "$BUILD_ARTIFACT" == false && -z "$ARTIFACT_PATH" ]]; then
|
||||||
|
die "--no-build requires --artifact"
|
||||||
|
fi
|
||||||
|
|
||||||
|
backend_dependency_path_from_cargo() {
|
||||||
|
local cargo_toml="$1"
|
||||||
|
awk '
|
||||||
|
/^[[:space:]]*silentdragonxlitelib[[:space:]]*=/ {
|
||||||
|
original = $0
|
||||||
|
path = $0
|
||||||
|
sub(/.*path[[:space:]]*=[[:space:]]*"/, "", path)
|
||||||
|
sub(/".*/, "", path)
|
||||||
|
if (path != original) print path
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "$cargo_toml"
|
||||||
|
}
|
||||||
|
|
||||||
|
canonical_dependency_path() {
|
||||||
|
local path="$1"
|
||||||
|
if [[ -d "$path" ]]; then
|
||||||
|
(cd "$path" && pwd -P)
|
||||||
|
else
|
||||||
|
absolute_path "$path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_backend_dependency_source() {
|
||||||
|
[[ -n "$BACKEND_DEPENDENCY_DIR" ]] || return
|
||||||
|
|
||||||
|
if [[ ! -f "$BACKEND_DEPENDENCY_DIR/Cargo.toml" ]]; then
|
||||||
|
if [[ "$BUILD_ARTIFACT" == true || "$BACKEND_DEPENDENCY_OVERRIDE_REQUESTED" == true ]]; then
|
||||||
|
die "Cargo.toml not found in $BACKEND_DEPENDENCY_DIR"
|
||||||
|
fi
|
||||||
|
warn "Cargo.toml not found in silentdragonxlitelib source: $BACKEND_DEPENDENCY_DIR"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -Eq '^[[:space:]]*name[[:space:]]*=[[:space:]]*"silentdragonxlitelib"' "$BACKEND_DEPENDENCY_DIR/Cargo.toml"; then
|
||||||
|
if [[ "$BUILD_ARTIFACT" == true || "$BACKEND_DEPENDENCY_OVERRIDE_REQUESTED" == true ]]; then
|
||||||
|
die "dependency path does not look like silentdragonxlitelib: $BACKEND_DEPENDENCY_DIR"
|
||||||
|
fi
|
||||||
|
warn "dependency path does not look like silentdragonxlitelib: $BACKEND_DEPENDENCY_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure the Sapling proving params are present in the core crate (rust-embed bakes them in at build
|
||||||
|
# time). They are the fixed Zcash trusted-setup output — not buildable — so fetch + verify them from
|
||||||
|
# git.dragonx.is when absent. Override the source with SAPLING_PARAMS_BASE_URL.
|
||||||
|
SAPLING_PARAMS_BASE_URL="${SAPLING_PARAMS_BASE_URL:-https://git.dragonx.is/DragonX/zcash-params/releases/download/sapling-v1}"
|
||||||
|
ensure_sapling_params() {
|
||||||
|
local dir="$1"
|
||||||
|
[[ -n "$dir" ]] || return 0
|
||||||
|
mkdir -p "$dir"
|
||||||
|
local specs=(
|
||||||
|
"sapling-spend.params:8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13"
|
||||||
|
"sapling-output.params:2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4"
|
||||||
|
)
|
||||||
|
local spec name want path got
|
||||||
|
for spec in "${specs[@]}"; do
|
||||||
|
name="${spec%%:*}"; want="${spec##*:}"; path="$dir/$name"
|
||||||
|
if [[ -f "$path" ]] && [[ "$(compute_sha256 "$path")" == "$want" ]]; then
|
||||||
|
info "sapling param present and verified: $name"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
info "fetching $name from $SAPLING_PARAMS_BASE_URL"
|
||||||
|
curl -fsSL "$SAPLING_PARAMS_BASE_URL/$name" -o "$path" || die "failed to download sapling param: $name"
|
||||||
|
got="$(compute_sha256 "$path")"
|
||||||
|
[[ "$got" == "$want" ]] || { rm -f "$path"; die "sapling param $name sha256 mismatch (got $got, want $want)"; }
|
||||||
|
info "downloaded and verified $name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_backend_source() {
|
||||||
|
BUILD_BACKEND_DIR="$BACKEND_SOURCE_DIR"
|
||||||
|
|
||||||
|
if [[ "$BACKEND_DEPENDENCY_OVERRIDE_REQUESTED" == false ]]; then
|
||||||
|
if [[ -f "$BACKEND_SOURCE_DIR/Cargo.toml" ]]; then
|
||||||
|
local configured_dependency_path
|
||||||
|
configured_dependency_path="$(backend_dependency_path_from_cargo "$BACKEND_SOURCE_DIR/Cargo.toml")"
|
||||||
|
if [[ -n "$configured_dependency_path" ]]; then
|
||||||
|
if [[ "$configured_dependency_path" = /* ]]; then
|
||||||
|
BACKEND_DEPENDENCY_DIR="$(canonical_dependency_path "$configured_dependency_path")"
|
||||||
|
warn "backend Cargo.toml uses an absolute silentdragonxlitelib path; use --silentdragonxlitelib-dir for portable builders"
|
||||||
|
else
|
||||||
|
BACKEND_DEPENDENCY_DIR="$(canonical_dependency_path "$BACKEND_SOURCE_DIR/$configured_dependency_path")"
|
||||||
|
info "using relative silentdragonxlitelib dependency at $BACKEND_DEPENDENCY_DIR"
|
||||||
|
fi
|
||||||
|
validate_backend_dependency_source
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$BACKEND_SOURCE_DIR/Cargo.toml" ]] || die "Cargo.toml not found in $BACKEND_SOURCE_DIR"
|
||||||
|
validate_backend_dependency_source
|
||||||
|
[[ "$BACKEND_DEPENDENCY_DIR" != *\"* ]] || die "--silentdragonxlitelib-dir path cannot contain a double quote"
|
||||||
|
|
||||||
|
local prepared_root="$OUT_DIR/.prepared-backend/$PLATFORM"
|
||||||
|
[[ "$prepared_root" == */.prepared-backend/* ]] || die "refusing unsafe prepared backend path: $prepared_root"
|
||||||
|
rm -rf "$prepared_root"
|
||||||
|
mkdir -p "$prepared_root"
|
||||||
|
|
||||||
|
ln -s "$BACKEND_SOURCE_DIR/src" "$prepared_root/src"
|
||||||
|
[[ -f "$BACKEND_SOURCE_DIR/Cargo.lock" ]] && ln -s "$BACKEND_SOURCE_DIR/Cargo.lock" "$prepared_root/Cargo.lock"
|
||||||
|
[[ -d "$BACKEND_SOURCE_DIR/.cargo" ]] && ln -s "$BACKEND_SOURCE_DIR/.cargo" "$prepared_root/.cargo"
|
||||||
|
# Honor the pinned Rust toolchain (rust-toolchain.toml) inside the prepared root too,
|
||||||
|
# so builds using --silentdragonxlitelib-dir still select rustc 1.63.
|
||||||
|
[[ -f "$BACKEND_SOURCE_DIR/rust-toolchain.toml" ]] && ln -s "$BACKEND_SOURCE_DIR/rust-toolchain.toml" "$prepared_root/rust-toolchain.toml"
|
||||||
|
[[ -d "$BACKEND_SOURCE_DIR/libsodium-mingw" ]] && ln -s "$BACKEND_SOURCE_DIR/libsodium-mingw" "$prepared_root/libsodium-mingw"
|
||||||
|
# Vendored crate deps (offline builds): the .cargo/config.toml's vendored-sources directory is
|
||||||
|
# "vendor" relative to the build root, so expose it inside the prepared root too.
|
||||||
|
[[ -d "$BACKEND_SOURCE_DIR/vendor" ]] && ln -s "$BACKEND_SOURCE_DIR/vendor" "$prepared_root/vendor"
|
||||||
|
[[ -f "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" ]] && ln -s "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" "$prepared_root/silentdragonxlitelib.h"
|
||||||
|
|
||||||
|
local replacement="silentdragonxlitelib = { path = \"$BACKEND_DEPENDENCY_DIR\" }"
|
||||||
|
awk -v replacement="$replacement" '
|
||||||
|
BEGIN { replaced = 0 }
|
||||||
|
/^[[:space:]]*silentdragonxlitelib[[:space:]]*=/ {
|
||||||
|
print replacement
|
||||||
|
replaced = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END { if (replaced != 1) exit 42 }
|
||||||
|
' "$BACKEND_SOURCE_DIR/Cargo.toml" > "$prepared_root/Cargo.toml" \
|
||||||
|
|| die "failed to prepare backend Cargo.toml with portable silentdragonxlitelib path"
|
||||||
|
|
||||||
|
BUILD_BACKEND_DIR="$prepared_root"
|
||||||
|
info "prepared backend source at $BUILD_BACKEND_DIR with silentdragonxlitelib from $BACKEND_DEPENDENCY_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_backend_source
|
||||||
|
|
||||||
|
artifact_kind() {
|
||||||
|
local name="${1##*/}"
|
||||||
|
case "$name" in
|
||||||
|
*.a|*.lib) printf 'static-library\n' ;;
|
||||||
|
*.so|*.dylib|*.dll) printf 'shared-library\n' ;;
|
||||||
|
*) printf 'unknown\n' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
cargo_output_candidates() {
|
||||||
|
local cargo_target_root="$BUILD_BACKEND_DIR/target"
|
||||||
|
if [[ -n "$CARGO_TARGET_DIR_VALUE" ]]; then
|
||||||
|
cargo_target_root="$CARGO_TARGET_DIR_VALUE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local base="$cargo_target_root/release"
|
||||||
|
if [[ -n "$RUST_TARGET" ]]; then
|
||||||
|
base="$cargo_target_root/$RUST_TARGET/release"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$PLATFORM" in
|
||||||
|
linux)
|
||||||
|
printf '%s\n' "$base/libsilentdragonxlite.a" "$base/silentdragonxlite.a" "$base/libsilentdragonxlite.so"
|
||||||
|
;;
|
||||||
|
windows)
|
||||||
|
printf '%s\n' "$base/silentdragonxlite.lib" "$base/libsilentdragonxlite.a" "$base/silentdragonxlite.dll"
|
||||||
|
;;
|
||||||
|
macos)
|
||||||
|
printf '%s\n' "$base/libsilentdragonxlite.a" "$base/silentdragonxlite.a" "$base/libsilentdragonxlite.dylib" "$base/silentdragonxlite.dylib"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
source_revision_for() {
|
||||||
|
local dir="$1"
|
||||||
|
local revision_file
|
||||||
|
for revision_file in "$dir/DRAGONX_SOURCE_REVISION" "$dir/../DRAGONX_SOURCE_REVISION"; do
|
||||||
|
if [[ -f "$revision_file" ]]; then
|
||||||
|
sed -n '1p' "$revision_file"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
git -C "$dir" rev-parse HEAD 2>/dev/null || printf 'unknown'
|
||||||
|
else
|
||||||
|
printf 'unknown'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
default_source_date_epoch() {
|
||||||
|
if git -C "$PROJECT_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
git -C "$PROJECT_ROOT" log -1 --format=%ct 2>/dev/null || printf '0'
|
||||||
|
else
|
||||||
|
printf '0'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
append_rustflag() {
|
||||||
|
local rustflag="$1"
|
||||||
|
if [[ -n "${RUSTFLAGS:-}" ]]; then
|
||||||
|
export RUSTFLAGS="${RUSTFLAGS} ${rustflag}"
|
||||||
|
else
|
||||||
|
export RUSTFLAGS="$rustflag"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
append_rust_path_remap() {
|
||||||
|
local from_path="$1"
|
||||||
|
local to_path="$2"
|
||||||
|
[[ -n "$from_path" && -n "$to_path" ]] || return
|
||||||
|
append_rustflag "--remap-path-prefix=${from_path}=${to_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_reproducible_rustflags() {
|
||||||
|
local cargo_target_root="$BUILD_BACKEND_DIR/target"
|
||||||
|
if [[ -n "$CARGO_TARGET_DIR_VALUE" ]]; then
|
||||||
|
cargo_target_root="$CARGO_TARGET_DIR_VALUE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
append_rust_path_remap "$PROJECT_ROOT" "/dragonx-project"
|
||||||
|
append_rust_path_remap "$BACKEND_SOURCE_DIR" "/dragonx-lite-backend"
|
||||||
|
if [[ "$BUILD_BACKEND_DIR" != "$BACKEND_SOURCE_DIR" ]]; then
|
||||||
|
append_rust_path_remap "$BUILD_BACKEND_DIR" "/dragonx-lite-backend"
|
||||||
|
fi
|
||||||
|
append_rust_path_remap "$BACKEND_DEPENDENCY_DIR" "/dragonx-lite-backend-dependency"
|
||||||
|
for path_remap in "${EXTRA_REMAP_PATH_PREFIXES[@]}"; do
|
||||||
|
append_rustflag "--remap-path-prefix=${path_remap}"
|
||||||
|
done
|
||||||
|
|
||||||
|
local cargo_home="${CARGO_HOME:-}"
|
||||||
|
if [[ -z "$cargo_home" && -n "${HOME:-}" ]]; then
|
||||||
|
cargo_home="$HOME/.cargo"
|
||||||
|
fi
|
||||||
|
if [[ -n "$cargo_home" && -d "$cargo_home" ]]; then
|
||||||
|
append_rust_path_remap "$cargo_home" "/cargo-home"
|
||||||
|
fi
|
||||||
|
append_rust_path_remap "$cargo_target_root" "/dragonx-lite-cargo-target"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_with_cargo() {
|
||||||
|
command -v cargo >/dev/null 2>&1 || die "cargo was not found"
|
||||||
|
[[ -f "$BUILD_BACKEND_DIR/Cargo.toml" ]] || die "Cargo.toml not found in $BUILD_BACKEND_DIR"
|
||||||
|
|
||||||
|
if [[ -z "$SOURCE_DATE_EPOCH_VALUE" ]]; then
|
||||||
|
SOURCE_DATE_EPOCH_VALUE="$(default_source_date_epoch)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export CARGO_INCREMENTAL=0
|
||||||
|
export SOURCE_DATE_EPOCH="$SOURCE_DATE_EPOCH_VALUE"
|
||||||
|
if [[ -n "$CARGO_TARGET_DIR_VALUE" ]]; then
|
||||||
|
export CARGO_TARGET_DIR="$CARGO_TARGET_DIR_VALUE"
|
||||||
|
fi
|
||||||
|
if [[ "$REPRODUCIBLE" == true ]]; then
|
||||||
|
apply_reproducible_rustflags
|
||||||
|
fi
|
||||||
|
if [[ "$PLATFORM" == "windows" && -d "$BUILD_BACKEND_DIR/libsodium-mingw" ]]; then
|
||||||
|
export SODIUM_LIB_DIR="$BUILD_BACKEND_DIR/libsodium-mingw"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "$BACKEND_DEPENDENCY_DIR" ]] && ensure_sapling_params "$BACKEND_DEPENDENCY_DIR/zcash-params"
|
||||||
|
|
||||||
|
local cargo_cmd=(cargo build --locked --lib --release)
|
||||||
|
if [[ -n "$RUST_TARGET" ]]; then
|
||||||
|
cargo_cmd+=(--target "$RUST_TARGET")
|
||||||
|
fi
|
||||||
|
if [[ -n "$JOBS" ]]; then
|
||||||
|
cargo_cmd+=(-j "$JOBS")
|
||||||
|
fi
|
||||||
|
cargo_cmd+=("${EXTRA_CARGO_ARGS[@]}")
|
||||||
|
|
||||||
|
info "building backend in $BUILD_BACKEND_DIR"
|
||||||
|
(cd "$BUILD_BACKEND_DIR" && "${cargo_cmd[@]}")
|
||||||
|
|
||||||
|
while IFS= read -r candidate; do
|
||||||
|
if [[ -f "$candidate" ]]; then
|
||||||
|
ARTIFACT_PATH="$candidate"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done < <(cargo_output_candidates)
|
||||||
|
|
||||||
|
die "cargo finished, but no expected backend artifact was found under $BUILD_BACKEND_DIR/target"
|
||||||
|
}
|
||||||
|
|
||||||
|
select_nm_tool() {
|
||||||
|
if [[ "$PLATFORM" == "windows" ]] && command -v x86_64-w64-mingw32-nm >/dev/null 2>&1; then
|
||||||
|
printf 'x86_64-w64-mingw32-nm\n'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if command -v llvm-nm >/dev/null 2>&1; then
|
||||||
|
printf 'llvm-nm\n'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if command -v nm >/dev/null 2>&1; then
|
||||||
|
printf 'nm\n'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
die "no symbol inventory tool found; install nm, llvm-nm, or x86_64-w64-mingw32-nm"
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_sha256() {
|
||||||
|
local file="$1"
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
sha256sum "$file" | awk '{print $1}'
|
||||||
|
elif command -v shasum >/dev/null 2>&1; then
|
||||||
|
shasum -a 256 "$file" | awk '{print $1}'
|
||||||
|
else
|
||||||
|
die "sha256sum or shasum is required"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
json_escape() {
|
||||||
|
local value="$1"
|
||||||
|
value="${value//\\/\\\\}"
|
||||||
|
value="${value//\"/\\\"}"
|
||||||
|
value="${value//$'\n'/\\n}"
|
||||||
|
value="${value//$'\r'/}"
|
||||||
|
value="${value//$'\t'/\\t}"
|
||||||
|
printf '"%s"' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_array() {
|
||||||
|
local first=true
|
||||||
|
printf '['
|
||||||
|
for value in "$@"; do
|
||||||
|
if [[ "$first" == true ]]; then
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
printf ','
|
||||||
|
fi
|
||||||
|
json_escape "$value"
|
||||||
|
done
|
||||||
|
printf ']'
|
||||||
|
}
|
||||||
|
|
||||||
|
json_array_from_file() {
|
||||||
|
local file="$1"
|
||||||
|
local values=()
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
mapfile -t values < "$file"
|
||||||
|
fi
|
||||||
|
json_array "${values[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
signature_metadata_requested() {
|
||||||
|
[[ "$SIGNATURE_REQUIRED" == true || \
|
||||||
|
-n "$SIGNATURE_FILE" || \
|
||||||
|
-n "$SIGNATURE_FORMAT" || \
|
||||||
|
-n "$SIGNATURE_VERIFICATION_TOOL" || \
|
||||||
|
-n "$SIGNATURE_VERIFICATION_COMMAND" || \
|
||||||
|
-n "$SIGNATURE_KEY_FINGERPRINT" || \
|
||||||
|
-n "$SIGNATURE_CERTIFICATE_IDENTITY" || \
|
||||||
|
-n "$SIGNATURE_CERTIFICATE_ISSUER" || \
|
||||||
|
-n "$SIGNATURE_TRANSPARENCY_LOG_URL" || \
|
||||||
|
-n "$SIGNATURE_VERIFIED_SHA256" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_signature_metadata() {
|
||||||
|
SIGNATURE_REQUIRED_MANIFEST_VALUE=false
|
||||||
|
if [[ "$SIGNATURE_REQUIRED" == true ]]; then
|
||||||
|
SIGNATURE_REQUIRED_MANIFEST_VALUE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! signature_metadata_requested; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "$SIGNATURE_FILE" ]] || die "signature metadata requires --signature-file"
|
||||||
|
[[ -f "$SIGNATURE_FILE" ]] || die "signature file does not exist: $SIGNATURE_FILE"
|
||||||
|
[[ -n "$SIGNATURE_FORMAT" ]] || die "signature metadata requires --signature-format"
|
||||||
|
case "$SIGNATURE_FORMAT" in
|
||||||
|
minisign|gpg|sigstore|external|other) ;;
|
||||||
|
*) die "unsupported --signature-format: $SIGNATURE_FORMAT" ;;
|
||||||
|
esac
|
||||||
|
[[ -n "$SIGNATURE_VERIFICATION_TOOL" ]] || die "signature metadata requires --signature-verification-tool"
|
||||||
|
[[ -n "$SIGNATURE_VERIFIED_SHA256" ]] || die "signature metadata requires --signature-verified-sha256"
|
||||||
|
[[ "$SIGNATURE_VERIFIED_SHA256" == "$SHA256_DIGEST" ]] || die "signature verified SHA-256 does not match artifact SHA-256"
|
||||||
|
if [[ -z "$SIGNATURE_KEY_FINGERPRINT" && -z "$SIGNATURE_CERTIFICATE_IDENTITY" ]]; then
|
||||||
|
die "signature metadata requires --signature-key-fingerprint or --signature-certificate-identity"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SIGNATURE_METADATA_PROVIDED=true
|
||||||
|
SIGNATURE_VERIFICATION_PERFORMED=true
|
||||||
|
SIGNATURE_VERIFICATION_STATUS="verified"
|
||||||
|
SIGNATURE_FILE_SHA256="$(compute_sha256 "$SIGNATURE_FILE")"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$BUILD_ARTIFACT" == true ]]; then
|
||||||
|
build_with_cargo
|
||||||
|
fi
|
||||||
|
if [[ -z "$SOURCE_DATE_EPOCH_VALUE" ]]; then
|
||||||
|
SOURCE_DATE_EPOCH_VALUE="$(default_source_date_epoch)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$ARTIFACT_PATH" ]] || die "artifact not found: $ARTIFACT_PATH"
|
||||||
|
|
||||||
|
KIND="$(artifact_kind "$ARTIFACT_PATH")"
|
||||||
|
[[ "$KIND" != "unknown" ]] || die "artifact kind is unsupported: $ARTIFACT_PATH"
|
||||||
|
|
||||||
|
PLATFORM_OUT_DIR="$OUT_DIR/$PLATFORM"
|
||||||
|
mkdir -p "$PLATFORM_OUT_DIR"
|
||||||
|
|
||||||
|
ARTIFACT_NAME="$(basename "$ARTIFACT_PATH")"
|
||||||
|
ARTIFACT_OUTPUT="$PLATFORM_OUT_DIR/$ARTIFACT_NAME"
|
||||||
|
if [[ "$(absolute_path "$ARTIFACT_PATH")" != "$(absolute_path "$ARTIFACT_OUTPUT")" ]]; then
|
||||||
|
cp -p "$ARTIFACT_PATH" "$ARTIFACT_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SYMBOLS_FILE="$PLATFORM_OUT_DIR/lite-backend-symbols.txt"
|
||||||
|
RAW_SYMBOLS_FILE="$PLATFORM_OUT_DIR/lite-backend-symbols.raw.txt"
|
||||||
|
NM_TOOL="$(select_nm_tool)"
|
||||||
|
|
||||||
|
info "capturing exported symbols with $NM_TOOL"
|
||||||
|
if ! "$NM_TOOL" -g --defined-only "$ARTIFACT_OUTPUT" > "$RAW_SYMBOLS_FILE" 2> "$PLATFORM_OUT_DIR/lite-backend-symbols.err.txt"; then
|
||||||
|
die "symbol inventory failed; see $PLATFORM_OUT_DIR/lite-backend-symbols.err.txt"
|
||||||
|
fi
|
||||||
|
awk '{print $NF}' "$RAW_SYMBOLS_FILE" \
|
||||||
|
| sed 's/^_//' \
|
||||||
|
| grep -E '^(litelib_[A-Za-z0-9_]*|blake3_PW)$' \
|
||||||
|
| sort -u > "$SYMBOLS_FILE" || true
|
||||||
|
|
||||||
|
[[ -s "$SYMBOLS_FILE" ]] || die "no SDXL C ABI symbols were found in $ARTIFACT_OUTPUT"
|
||||||
|
|
||||||
|
MISSING_SYMBOLS=()
|
||||||
|
for required in "${REQUIRED_SYMBOLS[@]}"; do
|
||||||
|
if ! grep -Fxq "$required" "$SYMBOLS_FILE"; then
|
||||||
|
MISSING_SYMBOLS+=("$required")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ ${#MISSING_SYMBOLS[@]} -ne 0 ]]; then
|
||||||
|
printf '%s\n' "${MISSING_SYMBOLS[@]}" > "$PLATFORM_OUT_DIR/lite-backend-missing-symbols.txt"
|
||||||
|
die "artifact is missing required symbols; see $PLATFORM_OUT_DIR/lite-backend-missing-symbols.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SHA256_DIGEST="$(compute_sha256 "$ARTIFACT_OUTPUT")"
|
||||||
|
validate_signature_metadata
|
||||||
|
ARTIFACT_SIZE_BYTES="$(wc -c < "$ARTIFACT_OUTPUT" | tr -d ' ')"
|
||||||
|
PROJECT_REVISION="$(source_revision_for "$PROJECT_ROOT")"
|
||||||
|
BACKEND_REVISION="$(source_revision_for "$BACKEND_SOURCE_DIR")"
|
||||||
|
BACKEND_DEPENDENCY_REVISION=""
|
||||||
|
if [[ -n "$BACKEND_DEPENDENCY_DIR" ]]; then
|
||||||
|
BACKEND_DEPENDENCY_REVISION="$(source_revision_for "$BACKEND_DEPENDENCY_DIR")"
|
||||||
|
fi
|
||||||
|
ARTIFACT_SET_ID="$PLATFORM-${SHA256_DIGEST:0:16}"
|
||||||
|
REPRODUCIBLE_MANIFEST_VALUE=false
|
||||||
|
if [[ "$BUILD_ARTIFACT" == true && "$REPRODUCIBLE" == true ]]; then
|
||||||
|
REPRODUCIBLE_MANIFEST_VALUE=true
|
||||||
|
fi
|
||||||
|
PORTABLE_DEPENDENCY_OVERRIDE_MANIFEST_VALUE=false
|
||||||
|
if [[ "$BACKEND_DEPENDENCY_OVERRIDE_REQUESTED" == true ]]; then
|
||||||
|
PORTABLE_DEPENDENCY_OVERRIDE_MANIFEST_VALUE=true
|
||||||
|
fi
|
||||||
|
FILE_DESCRIPTION="unknown"
|
||||||
|
if command -v file >/dev/null 2>&1; then
|
||||||
|
FILE_DESCRIPTION="$(file -b "$ARTIFACT_OUTPUT")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
MANIFEST_FILE="$PLATFORM_OUT_DIR/lite-backend-artifact-manifest.json"
|
||||||
|
{
|
||||||
|
printf '{\n'
|
||||||
|
printf ' "schema": "dragonx.lite.backend-artifact.v1",\n'
|
||||||
|
printf ' "generated_by": "scripts/build-lite-backend-artifact.sh",\n'
|
||||||
|
printf ' "read_only_inventory": true,\n'
|
||||||
|
printf ' "artifact_mutation_requested": false,\n'
|
||||||
|
printf ' "upload_requested": false,\n'
|
||||||
|
printf ' "signing_requested": false,\n'
|
||||||
|
printf ' "publication_requested": false,\n'
|
||||||
|
printf ' "signature_verification": {\n'
|
||||||
|
printf ' "policy_name": '; json_escape "$SIGNATURE_POLICY_NAME"; printf ',\n'
|
||||||
|
printf ' "policy_defined": %s,\n' "$SIGNATURE_POLICY_DEFINED_MANIFEST_VALUE"
|
||||||
|
printf ' "required_for_release": %s,\n' "$SIGNATURE_REQUIRED_MANIFEST_VALUE"
|
||||||
|
printf ' "metadata_read_only": true,\n'
|
||||||
|
printf ' "metadata_provided": %s,\n' "$SIGNATURE_METADATA_PROVIDED"
|
||||||
|
printf ' "verification_performed": %s,\n' "$SIGNATURE_VERIFICATION_PERFORMED"
|
||||||
|
printf ' "verification_status": '; json_escape "$SIGNATURE_VERIFICATION_STATUS"; printf ',\n'
|
||||||
|
printf ' "signature_format": '; json_escape "$SIGNATURE_FORMAT"; printf ',\n'
|
||||||
|
printf ' "signature_path": '; json_escape "$SIGNATURE_FILE"; printf ',\n'
|
||||||
|
printf ' "signature_file_sha256": '; json_escape "$SIGNATURE_FILE_SHA256"; printf ',\n'
|
||||||
|
printf ' "verification_tool": '; json_escape "$SIGNATURE_VERIFICATION_TOOL"; printf ',\n'
|
||||||
|
printf ' "verification_command": '; json_escape "$SIGNATURE_VERIFICATION_COMMAND"; printf ',\n'
|
||||||
|
printf ' "key_fingerprint": '; json_escape "$SIGNATURE_KEY_FINGERPRINT"; printf ',\n'
|
||||||
|
printf ' "certificate_identity": '; json_escape "$SIGNATURE_CERTIFICATE_IDENTITY"; printf ',\n'
|
||||||
|
printf ' "certificate_issuer": '; json_escape "$SIGNATURE_CERTIFICATE_ISSUER"; printf ',\n'
|
||||||
|
printf ' "transparency_log_url": '; json_escape "$SIGNATURE_TRANSPARENCY_LOG_URL"; printf ',\n'
|
||||||
|
printf ' "verified_artifact_sha256": '; json_escape "$SIGNATURE_VERIFIED_SHA256"; printf '\n'
|
||||||
|
printf ' },\n'
|
||||||
|
printf ' "abi_version": '; json_escape "$ABI_VERSION"; printf ',\n'
|
||||||
|
printf ' "link_mode": '; json_escape "$LINK_MODE"; printf ',\n'
|
||||||
|
printf ' "platform": '; json_escape "$PLATFORM"; printf ',\n'
|
||||||
|
printf ' "rust_target": '; json_escape "$RUST_TARGET"; printf ',\n'
|
||||||
|
printf ' "artifact": {\n'
|
||||||
|
printf ' "path": '; json_escape "$ARTIFACT_OUTPUT"; printf ',\n'
|
||||||
|
printf ' "kind": '; json_escape "$KIND"; printf ',\n'
|
||||||
|
printf ' "size_bytes": %s,\n' "$ARTIFACT_SIZE_BYTES"
|
||||||
|
printf ' "sha256": '; json_escape "$SHA256_DIGEST"; printf ',\n'
|
||||||
|
printf ' "file_description": '; json_escape "$FILE_DESCRIPTION"; printf '\n'
|
||||||
|
printf ' },\n'
|
||||||
|
printf ' "symbol_inventory": {\n'
|
||||||
|
printf ' "tool": '; json_escape "$NM_TOOL"; printf ',\n'
|
||||||
|
printf ' "symbols_path": '; json_escape "$SYMBOLS_FILE"; printf ',\n'
|
||||||
|
printf ' "raw_symbols_path": '; json_escape "$RAW_SYMBOLS_FILE"; printf ',\n'
|
||||||
|
printf ' "required_symbols": '; json_array "${REQUIRED_SYMBOLS[@]}"; printf ',\n'
|
||||||
|
printf ' "exported_symbols": '; json_array_from_file "$SYMBOLS_FILE"; printf ',\n'
|
||||||
|
printf ' "missing_required_symbols": []\n'
|
||||||
|
printf ' },\n'
|
||||||
|
printf ' "provenance": {\n'
|
||||||
|
printf ' "owner_ready": true,\n'
|
||||||
|
printf ' "built_from_source": true,\n'
|
||||||
|
printf ' "metadata_provided": true,\n'
|
||||||
|
printf ' "source": '; json_escape "$BACKEND_SOURCE_DIR"; printf ',\n'
|
||||||
|
printf ' "cargo_build_source": '; json_escape "$BUILD_BACKEND_DIR"; printf ',\n'
|
||||||
|
printf ' "portable_dependency_override": %s,\n' "$PORTABLE_DEPENDENCY_OVERRIDE_MANIFEST_VALUE"
|
||||||
|
printf ' "silentdragonxlitelib_source": '; json_escape "$BACKEND_DEPENDENCY_DIR"; printf ',\n'
|
||||||
|
printf ' "builder": '; json_escape "$BUILDER"; printf ',\n'
|
||||||
|
printf ' "source_revision": '; json_escape "$BACKEND_REVISION"; printf ',\n'
|
||||||
|
printf ' "silentdragonxlitelib_revision": '; json_escape "$BACKEND_DEPENDENCY_REVISION"; printf ',\n'
|
||||||
|
printf ' "project_revision": '; json_escape "$PROJECT_REVISION"; printf ',\n'
|
||||||
|
printf ' "artifact_set_id": '; json_escape "$ARTIFACT_SET_ID"; printf ',\n'
|
||||||
|
printf ' "source_date_epoch": '; json_escape "$SOURCE_DATE_EPOCH_VALUE"; printf ',\n'
|
||||||
|
printf ' "reproducible": %s,\n' "$REPRODUCIBLE_MANIFEST_VALUE"
|
||||||
|
printf ' "redacted": true\n'
|
||||||
|
printf ' }\n'
|
||||||
|
printf '}\n'
|
||||||
|
} > "$MANIFEST_FILE"
|
||||||
|
|
||||||
|
info "artifact: $ARTIFACT_OUTPUT"
|
||||||
|
info "symbols: $SYMBOLS_FILE"
|
||||||
|
info "manifest: $MANIFEST_FILE"
|
||||||
|
info "sha256: $SHA256_DIGEST"
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
CMake configure example:
|
||||||
|
cmake -S "$PROJECT_ROOT" -B "$PROJECT_ROOT/build/lite" \\
|
||||||
|
-DDRAGONX_BUILD_LITE=ON \\
|
||||||
|
-DDRAGONX_ENABLE_LITE_BACKEND=ON \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_LIBRARY="$ARTIFACT_OUTPUT" \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE="$SYMBOLS_FILE" \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_MANIFEST="$MANIFEST_FILE" \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_LINK_MODE=$LINK_MODE \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_ABI=$ABI_VERSION
|
||||||
|
EOF
|
||||||
131
scripts/build_cjk_subset.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Build a NotoSansCJK subset font containing all characters used by
|
||||||
|
the zh, ja, and ko translation files, plus common CJK punctuation
|
||||||
|
and symbols.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 scripts/build_cjk_subset.py
|
||||||
|
|
||||||
|
Requires: pip install fonttools brotli
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools import subset as ftsubset
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
LANG_DIR = os.path.join(ROOT, 'res', 'lang')
|
||||||
|
SOURCE_FONT = '/tmp/NotoSansCJKsc-Regular.otf'
|
||||||
|
OUTPUT_FONT = os.path.join(ROOT, 'res', 'fonts', 'NotoSansCJK-Subset.ttf')
|
||||||
|
|
||||||
|
# Collect all characters used in CJK translation files
|
||||||
|
needed = set()
|
||||||
|
for lang in ['zh', 'ja', 'ko']:
|
||||||
|
path = os.path.join(LANG_DIR, f'{lang}.json')
|
||||||
|
if not os.path.exists(path):
|
||||||
|
continue
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for v in data.values():
|
||||||
|
if isinstance(v, str):
|
||||||
|
for c in v:
|
||||||
|
cp = ord(c)
|
||||||
|
if cp > 0x7F: # non-ASCII only (ASCII handled by Ubuntu font)
|
||||||
|
needed.add(cp)
|
||||||
|
|
||||||
|
# Also add common CJK ranges that future translations might use:
|
||||||
|
# - CJK punctuation and symbols (3000-303F)
|
||||||
|
# - Hiragana (3040-309F)
|
||||||
|
# - Katakana (30A0-30FF)
|
||||||
|
# - Bopomofo (3100-312F)
|
||||||
|
# - CJK quotation marks, brackets
|
||||||
|
for cp in range(0x3000, 0x3100):
|
||||||
|
needed.add(cp)
|
||||||
|
for cp in range(0x3100, 0x3130):
|
||||||
|
needed.add(cp)
|
||||||
|
# Fullwidth ASCII variants (commonly mixed in CJK text)
|
||||||
|
for cp in range(0xFF01, 0xFF5F):
|
||||||
|
needed.add(cp)
|
||||||
|
|
||||||
|
print(f"Total non-ASCII characters to include: {len(needed)}")
|
||||||
|
|
||||||
|
# Check which of these the source font supports
|
||||||
|
font = TTFont(SOURCE_FONT)
|
||||||
|
cmap = font.getBestCmap()
|
||||||
|
supportable = needed & set(cmap.keys())
|
||||||
|
unsupported = needed - set(cmap.keys())
|
||||||
|
|
||||||
|
print(f"Supported by source font: {len(supportable)}")
|
||||||
|
if unsupported:
|
||||||
|
print(f"Not in source font (will use fallback): {len(unsupported)}")
|
||||||
|
for cp in sorted(unsupported)[:10]:
|
||||||
|
print(f" U+{cp:04X} {chr(cp)}")
|
||||||
|
|
||||||
|
# Build the subset using pyftsubset CLI-style API
|
||||||
|
args = [
|
||||||
|
SOURCE_FONT,
|
||||||
|
f'--output-file={OUTPUT_FONT}',
|
||||||
|
f'--unicodes={",".join(f"U+{cp:04X}" for cp in sorted(supportable))}',
|
||||||
|
'--no-hinting',
|
||||||
|
'--desubroutinize',
|
||||||
|
]
|
||||||
|
|
||||||
|
ftsubset.main(args)
|
||||||
|
|
||||||
|
# Convert CFF outlines to TrueType (glyf) outlines.
|
||||||
|
# stb_truetype (used by ImGui) doesn't handle CID-keyed CFF fonts properly.
|
||||||
|
from fontTools.pens.cu2quPen import Cu2QuPen
|
||||||
|
from fontTools.pens.ttGlyphPen import TTGlyphPen
|
||||||
|
from fontTools.ttLib import newTable
|
||||||
|
|
||||||
|
tmp_otf = OUTPUT_FONT + '.tmp.otf'
|
||||||
|
os.rename(OUTPUT_FONT, tmp_otf)
|
||||||
|
|
||||||
|
conv = TTFont(tmp_otf)
|
||||||
|
if 'CFF ' in conv:
|
||||||
|
print("Converting CFF -> TrueType outlines...")
|
||||||
|
glyphOrder = conv.getGlyphOrder()
|
||||||
|
glyphSet = conv.getGlyphSet()
|
||||||
|
glyf_table = newTable("glyf")
|
||||||
|
glyf_table.glyphs = {}
|
||||||
|
glyf_table.glyphOrder = glyphOrder
|
||||||
|
loca_table = newTable("loca")
|
||||||
|
from fontTools.ttLib.tables._g_l_y_f import Glyph as TTGlyph
|
||||||
|
for gname in glyphOrder:
|
||||||
|
try:
|
||||||
|
ttPen = TTGlyphPen(glyphSet)
|
||||||
|
cu2quPen = Cu2QuPen(ttPen, max_err=1.0, reverse_direction=True)
|
||||||
|
glyphSet[gname].draw(cu2quPen)
|
||||||
|
glyf_table.glyphs[gname] = ttPen.glyph()
|
||||||
|
except Exception:
|
||||||
|
glyf_table.glyphs[gname] = TTGlyph()
|
||||||
|
del conv['CFF ']
|
||||||
|
if 'VORG' in conv:
|
||||||
|
del conv['VORG']
|
||||||
|
conv['glyf'] = glyf_table
|
||||||
|
conv['loca'] = loca_table
|
||||||
|
conv['head'].indexToLocFormat = 1
|
||||||
|
if 'maxp' in conv:
|
||||||
|
conv['maxp'].version = 0x00010000
|
||||||
|
conv.sfntVersion = "\x00\x01\x00\x00"
|
||||||
|
conv.save(OUTPUT_FONT)
|
||||||
|
conv.close()
|
||||||
|
os.remove(tmp_otf)
|
||||||
|
|
||||||
|
size = os.path.getsize(OUTPUT_FONT)
|
||||||
|
print(f"\nOutput: {OUTPUT_FONT}")
|
||||||
|
print(f"Size: {size / 1024:.0f} KB")
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
verify = TTFont(OUTPUT_FONT)
|
||||||
|
verify_cmap = set(verify.getBestCmap().keys())
|
||||||
|
still_missing = needed - verify_cmap
|
||||||
|
print(f"Verified glyphs in subset: {len(verify_cmap)}")
|
||||||
|
if still_missing:
|
||||||
|
# These are chars not in the source font - expected for some Hangul/Hiragana
|
||||||
|
print(f"Not coverable by this font: {len(still_missing)} (need additional font)")
|
||||||
|
for cp in sorted(still_missing)[:10]:
|
||||||
|
print(f" U+{cp:04X} {chr(cp)}")
|
||||||
|
else:
|
||||||
|
print("All needed characters are covered!")
|
||||||
87
scripts/build_emoji_subset.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Build a monochrome Noto Emoji subset for the chat/message UI.
|
||||||
|
|
||||||
|
Dear ImGui rasterizes fonts with stb_truetype, which handles only monochrome
|
||||||
|
(outline `glyf`) fonts — NOT color emoji (CBDT/sbix/COLR). So we use Google's
|
||||||
|
*monochrome* Noto Emoji (github.com/google/fonts, ofl/notoemoji, OFL-licensed)
|
||||||
|
and merge it into the text fonts (see Typography::loadFont, the block after the
|
||||||
|
CJK merge). ImGui renders one glyph per codepoint with no shaping, so ZWJ
|
||||||
|
sequences / regional-indicator flags won't compose — single-codepoint emoji
|
||||||
|
(😀 🎉 ❤ 🔥 👍 …) render fine, which covers the overwhelming majority of use.
|
||||||
|
|
||||||
|
Source is the variable font pinned to wght=400 → static, then subset to the
|
||||||
|
emoji planes plus the higher symbol/star ranges the base UI font doesn't cover.
|
||||||
|
The base Ubuntu font already owns U+2600–26FF etc.; ImGui's MergeMode gives the
|
||||||
|
first-loaded glyph precedence, so those stay text-styled and only the codepoints
|
||||||
|
the base lacks fall through to this font.
|
||||||
|
|
||||||
|
Get the source once (OFL, redistributable):
|
||||||
|
curl -fsSL -o /tmp/NotoEmoji-VF.ttf \
|
||||||
|
'https://github.com/google/fonts/raw/main/ofl/notoemoji/NotoEmoji%5Bwght%5D.ttf'
|
||||||
|
|
||||||
|
Then: python3 scripts/build_emoji_subset.py
|
||||||
|
Output: res/fonts/NotoEmoji-Subset.ttf (committed; embedded via INCBIN)
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from fontTools import ttLib, subset
|
||||||
|
from fontTools.varLib.instancer import instantiateVariableFont
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
SOURCE_VF = '/tmp/NotoEmoji-VF.ttf'
|
||||||
|
STATIC = '/tmp/NotoEmoji-Static.ttf'
|
||||||
|
OUTPUT = os.path.join(ROOT, 'res', 'fonts', 'NotoEmoji-Subset.ttf')
|
||||||
|
|
||||||
|
# Emoji codepoints to keep. Ranges are inclusive.
|
||||||
|
RANGES = [
|
||||||
|
(0x1F000, 0x1FAFF), # all the main emoji planes (emoticons, pictographs, transport, supplement, extended)
|
||||||
|
(0x2600, 0x27BF), # Miscellaneous Symbols + Dingbats
|
||||||
|
(0x2B00, 0x2BFF), # stars (⭐ 2B50) and misc arrows
|
||||||
|
(0xFE00, 0xFE0F), # variation selectors (VS16 emoji-style)
|
||||||
|
(0x2194, 0x21AA), # arrows used as emoji
|
||||||
|
(0x231A, 0x231B), # ⌚ ⌛
|
||||||
|
(0x23E9, 0x23FA), # media-control emoji
|
||||||
|
(0x25AA, 0x25FE), # small squares
|
||||||
|
]
|
||||||
|
SINGLES = [0x200D, 0x2934, 0x2935, 0x3030, 0x303D, 0x3297, 0x3299,
|
||||||
|
0x00A9, 0x00AE, 0x2122, 0x2139, 0x24C2]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if not os.path.exists(SOURCE_VF):
|
||||||
|
raise SystemExit(f"missing source font {SOURCE_VF} — see the header for the curl command")
|
||||||
|
|
||||||
|
# 1. Pin the weight axis so stb_truetype rasterizes a clean static instance.
|
||||||
|
f = ttLib.TTFont(SOURCE_VF)
|
||||||
|
if 'fvar' in f:
|
||||||
|
instantiateVariableFont(f, {'wght': 400}, inplace=True)
|
||||||
|
f.save(STATIC)
|
||||||
|
|
||||||
|
unicodes = list(SINGLES)
|
||||||
|
for lo, hi in RANGES:
|
||||||
|
unicodes.extend(range(lo, hi + 1))
|
||||||
|
|
||||||
|
opts = subset.Options()
|
||||||
|
opts.layout_features = [] # ImGui does no shaping — drop GSUB/GPOS
|
||||||
|
opts.name_IDs = []
|
||||||
|
opts.notdef_outline = True
|
||||||
|
opts.glyph_names = False
|
||||||
|
opts.drop_tables = ['GSUB', 'GPOS', 'GDEF', 'morx', 'kern']
|
||||||
|
|
||||||
|
font = subset.load_font(STATIC, opts)
|
||||||
|
ss = subset.Subsetter(options=opts)
|
||||||
|
ss.populate(unicodes=unicodes)
|
||||||
|
ss.subset(font)
|
||||||
|
subset.save_font(font, OUTPUT, opts)
|
||||||
|
|
||||||
|
out = ttLib.TTFont(OUTPUT)
|
||||||
|
cmap = out.getBestCmap()
|
||||||
|
color = any(t in out.reader.keys() for t in ('CBDT', 'sbix', 'COLR'))
|
||||||
|
print(f"Output: {OUTPUT}")
|
||||||
|
print(f"Size: {os.path.getsize(OUTPUT)//1024} KB | glyphs: {len(cmap)} | color tables: {color}")
|
||||||
|
if color:
|
||||||
|
raise SystemExit("ERROR: subset has color tables — stb_truetype cannot render it")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
56
scripts/check-source-hygiene.sh
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Source-tree hygiene guard.
|
||||||
|
#
|
||||||
|
# Blocks two failure modes that an AI coding session previously introduced in
|
||||||
|
# src/wallet/ (the lite-wallet "_plan"/"_batch" churn): pathologically long
|
||||||
|
# filenames (which also break the Windows MAX_PATH 260-char limit during the
|
||||||
|
# cross-build) and the runaway "receipt/custody/handoff/stewardship" naming
|
||||||
|
# explosion where each session wrapped the previous artifact in one more layer.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/check-source-hygiene.sh # check working-tree src/
|
||||||
|
# scripts/check-source-hygiene.sh --staged # check staged files (pre-commit)
|
||||||
|
#
|
||||||
|
# Install as a git pre-commit hook:
|
||||||
|
# ln -sf ../../scripts/check-source-hygiene.sh .git/hooks/pre-commit
|
||||||
|
# # (the hook invokes it with --staged automatically when named pre-commit)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MAX_LEN=80
|
||||||
|
# Naming-explosion tokens. Two or more chained in one basename is the smell.
|
||||||
|
CHURN_RE='receipt|custody|handoff|stewardship|promotion_activation|acceptance_confirmation|archive_handoff|post_closure'
|
||||||
|
|
||||||
|
mode="${1:-}"
|
||||||
|
if [[ "$mode" == "--staged" || "$(basename "$0")" == "pre-commit" ]]; then
|
||||||
|
mapfile -t files < <(git diff --cached --name-only --diff-filter=AR | grep -E '\.(cpp|h|hpp|cc)$' || true)
|
||||||
|
else
|
||||||
|
mapfile -t files < <(git ls-files 'src/**/*.cpp' 'src/**/*.h' 2>/dev/null; \
|
||||||
|
find src -type f \( -name '*.cpp' -o -name '*.h' \) 2>/dev/null)
|
||||||
|
# de-dup
|
||||||
|
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u)
|
||||||
|
fi
|
||||||
|
|
||||||
|
fail=0
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
[[ -z "$f" ]] && continue
|
||||||
|
base="$(basename "$f")"
|
||||||
|
len=${#base}
|
||||||
|
if (( len > MAX_LEN )); then
|
||||||
|
echo "✗ filename too long ($len > $MAX_LEN chars): $f" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
# count distinct churn tokens in the basename ( || true: grep exits 1 on no match)
|
||||||
|
n=$(printf '%s' "$base" | grep -oE "$CHURN_RE" | sort -u | wc -l || true)
|
||||||
|
if (( n >= 2 )); then
|
||||||
|
echo "✗ runaway naming pattern ($n churn tokens) — refactor in place, don't add a layer: $f" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( fail )); then
|
||||||
|
echo "" >&2
|
||||||
|
echo "Source hygiene check failed. See docs in scripts/check-source-hygiene.sh." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "source hygiene OK (${#files[@]} files checked)"
|
||||||
64
scripts/check_cjk_coverage.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check which characters in translation files fall outside the font glyph ranges."""
|
||||||
|
import json
|
||||||
|
import unicodedata
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
|
||||||
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
LANG_DIR = os.path.join(SCRIPT_DIR, '..', 'res', 'lang')
|
||||||
|
|
||||||
|
# Glyph ranges from typography.cpp (regular font + CJK merge)
|
||||||
|
RANGES = [
|
||||||
|
# Regular font ranges
|
||||||
|
(0x0020, 0x00FF), # Basic Latin + Latin-1 Supplement
|
||||||
|
(0x0100, 0x024F), # Latin Extended-A + B
|
||||||
|
(0x0370, 0x03FF), # Greek and Coptic
|
||||||
|
(0x0400, 0x04FF), # Cyrillic
|
||||||
|
(0x0500, 0x052F), # Cyrillic Supplement
|
||||||
|
(0x2000, 0x206F), # General Punctuation
|
||||||
|
(0x2190, 0x21FF), # Arrows
|
||||||
|
(0x2200, 0x22FF), # Mathematical Operators
|
||||||
|
(0x2600, 0x26FF), # Miscellaneous Symbols
|
||||||
|
# CJK ranges
|
||||||
|
(0x2E80, 0x2FDF), # CJK Radicals
|
||||||
|
(0x3000, 0x30FF), # CJK Symbols, Hiragana, Katakana
|
||||||
|
(0x3100, 0x312F), # Bopomofo
|
||||||
|
(0x31F0, 0x31FF), # Katakana Extensions
|
||||||
|
(0x3400, 0x4DBF), # CJK Extension A
|
||||||
|
(0x4E00, 0x9FFF), # CJK Unified Ideographs
|
||||||
|
(0xAC00, 0xD7AF), # Hangul Syllables
|
||||||
|
(0xFF00, 0xFFEF), # Fullwidth Forms
|
||||||
|
]
|
||||||
|
|
||||||
|
def in_ranges(cp):
|
||||||
|
return any(lo <= cp <= hi for lo, hi in RANGES)
|
||||||
|
|
||||||
|
for path in sorted(glob.glob(os.path.join(LANG_DIR, '*.json'))):
|
||||||
|
lang = os.path.basename(path).replace('.json', '')
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
missing = {}
|
||||||
|
for key, val in data.items():
|
||||||
|
if not isinstance(val, str):
|
||||||
|
continue
|
||||||
|
for c in val:
|
||||||
|
cp = ord(c)
|
||||||
|
if cp > 0x7F and not in_ranges(cp):
|
||||||
|
if c not in missing:
|
||||||
|
missing[c] = []
|
||||||
|
missing[c].append(key)
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
print(f"\n=== {lang}.json: {len(missing)} missing characters ===")
|
||||||
|
for c in sorted(missing, key=lambda x: ord(x)):
|
||||||
|
cp = ord(c)
|
||||||
|
name = unicodedata.name(c, 'UNKNOWN')
|
||||||
|
keys = missing[c][:3]
|
||||||
|
key_str = ', '.join(keys)
|
||||||
|
if len(missing[c]) > 3:
|
||||||
|
key_str += f' (+{len(missing[c])-3} more)'
|
||||||
|
print(f" U+{cp:04X} {c} ({name}) — used in: {key_str}")
|
||||||
|
else:
|
||||||
|
print(f"=== {lang}.json: OK (all characters covered) ===")
|
||||||
47
scripts/check_font_coverage.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check which characters needed by translations are missing from bundled fonts."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
FONTS_DIR = os.path.join(ROOT, 'res', 'fonts')
|
||||||
|
LANG_DIR = os.path.join(ROOT, 'res', 'lang')
|
||||||
|
|
||||||
|
# Load font cmaps
|
||||||
|
cjk = TTFont(os.path.join(FONTS_DIR, 'NotoSansCJK-Subset.ttf'))
|
||||||
|
cjk_cmap = set(cjk.getBestCmap().keys())
|
||||||
|
|
||||||
|
ubuntu = TTFont(os.path.join(FONTS_DIR, 'Ubuntu-R.ttf'))
|
||||||
|
ubuntu_cmap = set(ubuntu.getBestCmap().keys())
|
||||||
|
|
||||||
|
combined = cjk_cmap | ubuntu_cmap
|
||||||
|
|
||||||
|
print(f"CJK subset font glyphs: {len(cjk_cmap)}")
|
||||||
|
print(f"Ubuntu font glyphs: {len(ubuntu_cmap)}")
|
||||||
|
print(f"Combined: {len(combined)}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for lang in ['zh', 'ja', 'ko', 'ru', 'de', 'es', 'fr', 'pt']:
|
||||||
|
path = os.path.join(LANG_DIR, f'{lang}.json')
|
||||||
|
if not os.path.exists(path):
|
||||||
|
continue
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
needed = set()
|
||||||
|
for v in data.values():
|
||||||
|
if isinstance(v, str):
|
||||||
|
for c in v:
|
||||||
|
needed.add(ord(c))
|
||||||
|
|
||||||
|
missing = sorted(needed - combined)
|
||||||
|
if missing:
|
||||||
|
print(f"{lang}.json: {len(needed)} chars needed, {len(missing)} MISSING")
|
||||||
|
for cp in missing[:20]:
|
||||||
|
c = chr(cp)
|
||||||
|
print(f" U+{cp:04X} {c}")
|
||||||
|
if len(missing) > 20:
|
||||||
|
print(f" ... and {len(missing) - 20} more")
|
||||||
|
else:
|
||||||
|
print(f"{lang}.json: OK ({len(needed)} chars, all covered)")
|
||||||
214
scripts/convert_cjk_to_ttf.py
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Convert CJK subset from CID-keyed CFF/OTF to TrueType/TTF.
|
||||||
|
|
||||||
|
stb_truetype (used by ImGui) doesn't handle CID-keyed CFF fonts properly,
|
||||||
|
so we need glyf-based TrueType outlines instead.
|
||||||
|
|
||||||
|
Two approaches:
|
||||||
|
1. Direct CFF->TTF conversion via cu2qu (fontTools)
|
||||||
|
2. Download NotoSansSC-Regular.ttf (already TTF) and re-subset
|
||||||
|
|
||||||
|
This script tries approach 1 first, falls back to approach 2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import glob
|
||||||
|
|
||||||
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
PROJECT_ROOT = os.path.dirname(SCRIPT_DIR)
|
||||||
|
FONT_DIR = os.path.join(PROJECT_ROOT, "res", "fonts")
|
||||||
|
LANG_DIR = os.path.join(PROJECT_ROOT, "res", "lang")
|
||||||
|
|
||||||
|
SRC_OTF = os.path.join(FONT_DIR, "NotoSansCJK-Subset.otf")
|
||||||
|
DST_TTF = os.path.join(FONT_DIR, "NotoSansCJK-Subset.ttf")
|
||||||
|
|
||||||
|
|
||||||
|
def get_needed_codepoints():
|
||||||
|
"""Collect all unique codepoints from CJK translation files."""
|
||||||
|
codepoints = set()
|
||||||
|
for lang_file in glob.glob(os.path.join(LANG_DIR, "*.json")):
|
||||||
|
with open(lang_file, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for value in data.values():
|
||||||
|
if isinstance(value, str):
|
||||||
|
for ch in value:
|
||||||
|
cp = ord(ch)
|
||||||
|
# Include CJK + Hangul + fullwidth + CJK symbols/kana
|
||||||
|
if cp >= 0x2E80:
|
||||||
|
codepoints.add(cp)
|
||||||
|
return codepoints
|
||||||
|
|
||||||
|
|
||||||
|
def convert_cff_to_ttf():
|
||||||
|
"""Convert existing OTF/CFF font to TTF using fontTools cu2qu."""
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools.pens.cu2quPen import Cu2QuPen
|
||||||
|
from fontTools.pens.ttGlyphPen import TTGlyphPen
|
||||||
|
|
||||||
|
print(f"Loading {SRC_OTF}...")
|
||||||
|
font = TTFont(SRC_OTF)
|
||||||
|
|
||||||
|
# Verify it's CFF
|
||||||
|
if "CFF " not in font:
|
||||||
|
print("Font is not CFF, skipping conversion")
|
||||||
|
return False
|
||||||
|
|
||||||
|
cff = font["CFF "]
|
||||||
|
top = cff.cff.topDictIndex[0]
|
||||||
|
print(f"ROS: {getattr(top, 'ROS', None)}")
|
||||||
|
print(f"CID-keyed: {getattr(top, 'FDSelect', None) is not None}")
|
||||||
|
|
||||||
|
glyphOrder = font.getGlyphOrder()
|
||||||
|
print(f"Glyphs: {len(glyphOrder)}")
|
||||||
|
|
||||||
|
# Use fontTools' built-in otf2ttf if available
|
||||||
|
try:
|
||||||
|
from fontTools.otf2ttf import otf_to_ttf
|
||||||
|
otf_to_ttf(font)
|
||||||
|
font.save(DST_TTF)
|
||||||
|
print(f"Saved TTF: {DST_TTF} ({os.path.getsize(DST_TTF)} bytes)")
|
||||||
|
font.close()
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Manual conversion using cu2qu
|
||||||
|
print("Using manual CFF->TTF conversion with cu2qu...")
|
||||||
|
|
||||||
|
from fontTools.pens.recordingPen import RecordingPen
|
||||||
|
from fontTools.pens.pointPen import SegmentToPointPen
|
||||||
|
from fontTools import ttLib
|
||||||
|
from fontTools.ttLib.tables._g_l_y_f import Glyph as TTGlyph
|
||||||
|
import struct
|
||||||
|
|
||||||
|
# Get glyph set
|
||||||
|
glyphSet = font.getGlyphSet()
|
||||||
|
|
||||||
|
# Create new glyf table
|
||||||
|
from fontTools.ttLib import newTable
|
||||||
|
|
||||||
|
glyf_table = newTable("glyf")
|
||||||
|
glyf_table.glyphs = {}
|
||||||
|
glyf_table.glyphOrder = glyphOrder
|
||||||
|
|
||||||
|
loca_table = newTable("loca")
|
||||||
|
|
||||||
|
max_error = 1.0 # em-units tolerance for cubic->quadratic
|
||||||
|
|
||||||
|
for gname in glyphOrder:
|
||||||
|
try:
|
||||||
|
ttPen = TTGlyphPen(glyphSet)
|
||||||
|
cu2quPen = Cu2QuPen(ttPen, max_err=max_error, reverse_direction=True)
|
||||||
|
glyphSet[gname].draw(cu2quPen)
|
||||||
|
glyf_table.glyphs[gname] = ttPen.glyph()
|
||||||
|
except Exception as e:
|
||||||
|
# Fallback: empty glyph
|
||||||
|
glyf_table.glyphs[gname] = TTGlyph()
|
||||||
|
|
||||||
|
# Replace CFF with glyf
|
||||||
|
del font["CFF "]
|
||||||
|
if "VORG" in font:
|
||||||
|
del font["VORG"]
|
||||||
|
|
||||||
|
font["glyf"] = glyf_table
|
||||||
|
font["loca"] = loca_table
|
||||||
|
|
||||||
|
# Add required tables for TTF
|
||||||
|
# head table needs indexToLocFormat
|
||||||
|
font["head"].indexToLocFormat = 1 # long format
|
||||||
|
|
||||||
|
# Create maxp for TrueType
|
||||||
|
if "maxp" in font:
|
||||||
|
font["maxp"].version = 0x00010000
|
||||||
|
|
||||||
|
# Update sfntVersion
|
||||||
|
font.sfntVersion = "\x00\x01\x00\x00" # TrueType
|
||||||
|
|
||||||
|
font.save(DST_TTF)
|
||||||
|
print(f"Saved TTF: {DST_TTF} ({os.path.getsize(DST_TTF)} bytes)")
|
||||||
|
font.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def download_and_subset():
|
||||||
|
"""Download NotoSansSC-Regular.ttf and subset it."""
|
||||||
|
import urllib.request
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools import subset
|
||||||
|
|
||||||
|
# Google Fonts provides static TTF files
|
||||||
|
url = "https://github.com/notofonts/noto-cjk/raw/main/Sans/SubsetOTF/SC/NotoSansSC-Regular.otf"
|
||||||
|
# Actually, we want TTF. Let's try the variable font approach.
|
||||||
|
# Or better: use google-fonts API for static TTF
|
||||||
|
|
||||||
|
# NotoSansSC static TTF from Google Fonts CDN
|
||||||
|
tmp_font = "/tmp/NotoSansSC-Regular.ttf"
|
||||||
|
|
||||||
|
if not os.path.exists(tmp_font):
|
||||||
|
print(f"Downloading NotoSansSC-Regular.ttf...")
|
||||||
|
url = "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTC/NotoSansCJK-Regular.ttc"
|
||||||
|
# This is a TTC (font collection), too large.
|
||||||
|
# Use the OTF we already have and convert it.
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"Using {tmp_font}")
|
||||||
|
font = TTFont(tmp_font)
|
||||||
|
cmap = font.getBestCmap()
|
||||||
|
print(f"Source has {len(cmap)} cmap entries")
|
||||||
|
|
||||||
|
needed = get_needed_codepoints()
|
||||||
|
print(f"Need {len(needed)} CJK codepoints")
|
||||||
|
|
||||||
|
# Subset
|
||||||
|
subsetter = subset.Subsetter()
|
||||||
|
subsetter.populate(unicodes=needed)
|
||||||
|
subsetter.subset(font)
|
||||||
|
|
||||||
|
font.save(DST_TTF)
|
||||||
|
print(f"Saved: {DST_TTF} ({os.path.getsize(DST_TTF)} bytes)")
|
||||||
|
font.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def verify_result():
|
||||||
|
"""Verify the output TTF has glyf outlines and correct characters."""
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
|
||||||
|
font = TTFont(DST_TTF)
|
||||||
|
cmap = font.getBestCmap()
|
||||||
|
|
||||||
|
print(f"\n--- Verification ---")
|
||||||
|
print(f"Format: {font.sfntVersion!r}")
|
||||||
|
print(f"Has glyf: {'glyf' in font}")
|
||||||
|
print(f"Has CFF: {'CFF ' in font}")
|
||||||
|
print(f"Cmap entries: {len(cmap)}")
|
||||||
|
|
||||||
|
# Check key characters
|
||||||
|
test_chars = {
|
||||||
|
"历": 0x5386, "史": 0x53F2, # Chinese: history
|
||||||
|
"概": 0x6982, "述": 0x8FF0, # Chinese: overview
|
||||||
|
"设": 0x8BBE, "置": 0x7F6E, # Chinese: settings
|
||||||
|
}
|
||||||
|
for name, cp in test_chars.items():
|
||||||
|
status = "YES" if cp in cmap else "NO"
|
||||||
|
print(f" {name} (U+{cp:04X}): {status}")
|
||||||
|
|
||||||
|
size = os.path.getsize(DST_TTF)
|
||||||
|
print(f"File size: {size} bytes ({size/1024:.1f} KB)")
|
||||||
|
font.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=== CJK Font CFF -> TTF Converter ===\n")
|
||||||
|
|
||||||
|
if convert_cff_to_ttf():
|
||||||
|
verify_result()
|
||||||
|
else:
|
||||||
|
print("Direct conversion failed, trying download approach...")
|
||||||
|
if download_and_subset():
|
||||||
|
verify_result()
|
||||||
|
else:
|
||||||
|
print("ERROR: Could not convert font")
|
||||||
|
sys.exit(1)
|
||||||
@@ -8,7 +8,7 @@ set -e
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
||||||
APPDIR="${BUILD_DIR}/AppDir"
|
APPDIR="${BUILD_DIR}/AppDir"
|
||||||
VERSION="1.0.0"
|
VERSION="1.2.0"
|
||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
@@ -24,18 +24,27 @@ if [ ! -f "${BUILD_DIR}/bin/ObsidianDragon" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for appimagetool
|
# Check for appimagetool — pinned to a tagged release and SHA-256 verified before we exec it.
|
||||||
|
# The old "continuous" tag is a moving, unverified network download that runs on the release
|
||||||
|
# builder; verify it or refuse to package.
|
||||||
|
APPIMAGETOOL_URL="https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage"
|
||||||
|
APPIMAGETOOL_SHA256="46fdd785094c7f6e545b61afcfb0f3d98d8eab243f644b4b17698c01d06083d1"
|
||||||
APPIMAGETOOL=""
|
APPIMAGETOOL=""
|
||||||
if command -v appimagetool &> /dev/null; then
|
if command -v appimagetool &> /dev/null; then
|
||||||
APPIMAGETOOL="appimagetool"
|
APPIMAGETOOL="appimagetool" # maintainer's own trusted system install
|
||||||
elif [ -f "${BUILD_DIR}/appimagetool-x86_64.AppImage" ]; then
|
|
||||||
APPIMAGETOOL="${BUILD_DIR}/appimagetool-x86_64.AppImage"
|
|
||||||
else
|
else
|
||||||
print_status "Downloading appimagetool..."
|
AT="${BUILD_DIR}/appimagetool-x86_64.AppImage"
|
||||||
wget -q -O "${BUILD_DIR}/appimagetool-x86_64.AppImage" \
|
if [ ! -f "$AT" ] || ! echo "${APPIMAGETOOL_SHA256} ${AT}" | sha256sum -c --status; then
|
||||||
"https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
print_status "Downloading appimagetool 1.9.0 (pinned)..."
|
||||||
chmod +x "${BUILD_DIR}/appimagetool-x86_64.AppImage"
|
wget -q -O "$AT" "$APPIMAGETOOL_URL"
|
||||||
APPIMAGETOOL="${BUILD_DIR}/appimagetool-x86_64.AppImage"
|
if ! echo "${APPIMAGETOOL_SHA256} ${AT}" | sha256sum -c --status; then
|
||||||
|
print_error "appimagetool SHA-256 verification failed — refusing to use it"
|
||||||
|
rm -f "$AT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
chmod +x "$AT"
|
||||||
|
fi
|
||||||
|
APPIMAGETOOL="$AT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_status "Creating AppDir structure..."
|
print_status "Creating AppDir structure..."
|
||||||
|
|||||||
122
scripts/expand_themes.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Build-time theme expander — merges layout sections from ui.toml into skin files.
|
||||||
|
|
||||||
|
Called by CMake during build. Reads source skin files + ui.toml, writes merged
|
||||||
|
output files to the build directory. Source files are never modified.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 expand_themes.py <source_themes_dir> <output_themes_dir>
|
||||||
|
|
||||||
|
For each .toml file in source_themes_dir (except ui.toml), the script:
|
||||||
|
1. Copies the skin file contents (theme/palette/backdrop/effects)
|
||||||
|
2. Appends all layout sections from ui.toml (fonts, tabs, components, etc.)
|
||||||
|
3. Writes the merged result to output_themes_dir/<filename>
|
||||||
|
|
||||||
|
ui.toml itself is copied unchanged.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Sections to SKIP when extracting from ui.toml (theme-specific, already in skins)
|
||||||
|
SKIP_SECTIONS = {"theme", "theme.palette", "backdrop", "effects"}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_layout_sections(ui_toml_path):
|
||||||
|
"""Extract non-theme sections from ui.toml as a string."""
|
||||||
|
sections = []
|
||||||
|
current_section = None
|
||||||
|
current_lines = []
|
||||||
|
section_re = re.compile(r'^\[{1,2}([^\]]+)\]{1,2}\s*$')
|
||||||
|
|
||||||
|
with open(ui_toml_path, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
m = section_re.match(line.strip())
|
||||||
|
if m:
|
||||||
|
if current_section is not None or current_lines:
|
||||||
|
sections.append((current_section, current_lines))
|
||||||
|
current_section = m.group(1).strip()
|
||||||
|
current_lines = [line]
|
||||||
|
else:
|
||||||
|
current_lines.append(line)
|
||||||
|
if current_section is not None or current_lines:
|
||||||
|
sections.append((current_section, current_lines))
|
||||||
|
|
||||||
|
layout_parts = []
|
||||||
|
for section_name, lines in sections:
|
||||||
|
if section_name is None:
|
||||||
|
# Preamble: only include top-level key=value lines
|
||||||
|
kv_lines = [l for l in lines
|
||||||
|
if l.strip() and not l.strip().startswith('#') and '=' in l]
|
||||||
|
if kv_lines:
|
||||||
|
layout_parts.append(''.join(kv_lines))
|
||||||
|
continue
|
||||||
|
if section_name in SKIP_SECTIONS:
|
||||||
|
continue
|
||||||
|
layout_parts.append(''.join(lines))
|
||||||
|
|
||||||
|
return '\n'.join(layout_parts)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print(f"Usage: {sys.argv[0]} <source_themes_dir> <output_themes_dir>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
src_dir = sys.argv[1]
|
||||||
|
out_dir = sys.argv[2]
|
||||||
|
ui_toml = os.path.join(src_dir, "ui.toml")
|
||||||
|
|
||||||
|
if not os.path.exists(ui_toml):
|
||||||
|
print(f"ERROR: ui.toml not found at {ui_toml}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
|
||||||
|
layout_content = extract_layout_sections(ui_toml)
|
||||||
|
|
||||||
|
separator = (
|
||||||
|
"\n# ===========================================================================\n"
|
||||||
|
"# Layout & Component Properties\n"
|
||||||
|
"# All values below can be customized per-theme. Edit and save to see\n"
|
||||||
|
"# changes reflected in the app in real time via hot-reload.\n"
|
||||||
|
"# ===========================================================================\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
for fname in sorted(os.listdir(src_dir)):
|
||||||
|
if not fname.endswith('.toml'):
|
||||||
|
continue
|
||||||
|
src_path = os.path.join(src_dir, fname)
|
||||||
|
dst_path = os.path.join(out_dir, fname)
|
||||||
|
|
||||||
|
if fname == "ui.toml":
|
||||||
|
# Copy ui.toml unchanged
|
||||||
|
with open(src_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
with open(dst_path, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f" COPY {fname}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skin file — append layout sections
|
||||||
|
with open(src_path, 'r') as f:
|
||||||
|
skin = f.read()
|
||||||
|
if not skin.endswith('\n'):
|
||||||
|
skin += '\n'
|
||||||
|
|
||||||
|
merged = skin + separator + layout_content
|
||||||
|
if not merged.endswith('\n'):
|
||||||
|
merged += '\n'
|
||||||
|
|
||||||
|
with open(dst_path, 'w') as f:
|
||||||
|
f.write(merged)
|
||||||
|
lines = merged.count('\n')
|
||||||
|
print(f" MERGE {fname} → {lines} lines")
|
||||||
|
|
||||||
|
print("Done.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -50,12 +50,20 @@ if [[ ! -f "$TARBALL" ]]; then
|
|||||||
curl -fSL -o "$TARBALL" "$SODIUM_URL"
|
curl -fSL -o "$TARBALL" "$SODIUM_URL"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify checksum
|
# Verify checksum (sha256sum on Linux, shasum on macOS)
|
||||||
echo "$SODIUM_SHA256 $TARBALL" | sha256sum -c - || {
|
if command -v sha256sum &>/dev/null; then
|
||||||
echo "[fetch-libsodium] ERROR: SHA256 mismatch! Removing corrupted download."
|
echo "$SODIUM_SHA256 $TARBALL" | sha256sum -c - || {
|
||||||
rm -f "$TARBALL"
|
echo "[fetch-libsodium] ERROR: SHA256 mismatch! Removing corrupted download."
|
||||||
exit 1
|
rm -f "$TARBALL"
|
||||||
}
|
exit 1
|
||||||
|
}
|
||||||
|
elif command -v shasum &>/dev/null; then
|
||||||
|
echo "$SODIUM_SHA256 $TARBALL" | shasum -a 256 -c - || {
|
||||||
|
echo "[fetch-libsodium] ERROR: SHA256 mismatch! Removing corrupted download."
|
||||||
|
rm -f "$TARBALL"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Extract ─────────────────────────────────────────────────────────────────
|
# ── Extract ─────────────────────────────────────────────────────────────────
|
||||||
if [[ ! -d "$SRC_DIR" ]]; then
|
if [[ ! -d "$SRC_DIR" ]]; then
|
||||||
@@ -77,7 +85,7 @@ case "$TARGET" in
|
|||||||
mac)
|
mac)
|
||||||
# Cross-compile for macOS via osxcross
|
# Cross-compile for macOS via osxcross
|
||||||
if [[ -z "${OSXCROSS:-}" ]]; then
|
if [[ -z "${OSXCROSS:-}" ]]; then
|
||||||
for try in "$HOME/osxcross" "/opt/osxcross" "$PROJECT_DIR/osxcross"; do
|
for try in "$PROJECT_DIR/external/osxcross" "$HOME/osxcross" "/opt/osxcross" "$PROJECT_DIR/osxcross"; do
|
||||||
[[ -d "$try/target" ]] && OSXCROSS="$try" && break
|
[[ -d "$try/target" ]] && OSXCROSS="$try" && break
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -115,6 +123,69 @@ case "$TARGET" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
# ── Native macOS: build universal binary (arm64 + x86_64) ───────────────────
|
||||||
|
IS_MACOS_NATIVE=false
|
||||||
|
if [[ "$TARGET" == "native" && "$(uname -s)" == "Darwin" ]]; then
|
||||||
|
IS_MACOS_NATIVE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $IS_MACOS_NATIVE; then
|
||||||
|
echo "[fetch-libsodium] Building universal (arm64 + x86_64) for macOS..."
|
||||||
|
export MACOSX_DEPLOYMENT_TARGET="11.0"
|
||||||
|
|
||||||
|
INSTALL_ARM64="$PROJECT_DIR/libs/libsodium-arm64"
|
||||||
|
INSTALL_X86_64="$PROJECT_DIR/libs/libsodium-x86_64"
|
||||||
|
|
||||||
|
for ARCH in arm64 x86_64; do
|
||||||
|
echo "[fetch-libsodium] Building for $ARCH..."
|
||||||
|
cd "$SRC_DIR"
|
||||||
|
make clean 2>/dev/null || true
|
||||||
|
make distclean 2>/dev/null || true
|
||||||
|
|
||||||
|
if [[ "$ARCH" == "arm64" ]]; then
|
||||||
|
ARCH_INSTALL="$INSTALL_ARM64"
|
||||||
|
HOST_TRIPLE="aarch64-apple-darwin"
|
||||||
|
else
|
||||||
|
ARCH_INSTALL="$INSTALL_X86_64"
|
||||||
|
HOST_TRIPLE="x86_64-apple-darwin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARCH_CFLAGS="-arch $ARCH -mmacosx-version-min=11.0"
|
||||||
|
|
||||||
|
./configure \
|
||||||
|
--prefix="$ARCH_INSTALL" \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--with-pic \
|
||||||
|
--host="$HOST_TRIPLE" \
|
||||||
|
CFLAGS="$ARCH_CFLAGS" \
|
||||||
|
LDFLAGS="-arch $ARCH" \
|
||||||
|
> /dev/null
|
||||||
|
|
||||||
|
make -j"$(sysctl -n hw.ncpu 2>/dev/null || echo 4)" > /dev/null 2>&1
|
||||||
|
make install > /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
# Merge with lipo
|
||||||
|
echo "[fetch-libsodium] Creating universal binary with lipo..."
|
||||||
|
mkdir -p "$INSTALL_DIR/lib" "$INSTALL_DIR/include"
|
||||||
|
lipo -create \
|
||||||
|
"$INSTALL_ARM64/lib/libsodium.a" \
|
||||||
|
"$INSTALL_X86_64/lib/libsodium.a" \
|
||||||
|
-output "$INSTALL_DIR/lib/libsodium.a"
|
||||||
|
cp -R "$INSTALL_ARM64/include/"* "$INSTALL_DIR/include/"
|
||||||
|
|
||||||
|
# Clean up per-arch builds
|
||||||
|
rm -rf "$INSTALL_ARM64" "$INSTALL_X86_64"
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
rm -rf "$SRC_DIR"
|
||||||
|
rm -f "$TARBALL"
|
||||||
|
|
||||||
|
echo "[fetch-libsodium] Done (universal): $INSTALL_DIR/lib/libsodium.a"
|
||||||
|
lipo -info "$INSTALL_DIR/lib/libsodium.a"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[fetch-libsodium] Configuring for target: $TARGET ..."
|
echo "[fetch-libsodium] Configuring for target: $TARGET ..."
|
||||||
./configure "${CONFIGURE_ARGS[@]}" > /dev/null
|
./configure "${CONFIGURE_ARGS[@]}" > /dev/null
|
||||||
|
|
||||||
|
|||||||
36
scripts/fix_mojibake.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Fix mojibake en-dash (and other common patterns) in translation JSON files."""
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
LANG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'res', 'lang')
|
||||||
|
|
||||||
|
# Common mojibake patterns: UTF-8 bytes interpreted as Latin-1
|
||||||
|
MOJIBAKE_FIXES = {
|
||||||
|
'\u00e2\u0080\u0093': '\u2013', # en dash
|
||||||
|
'\u00e2\u0080\u0094': '\u2014', # em dash
|
||||||
|
'\u00e2\u0080\u0099': '\u2019', # right single quote
|
||||||
|
'\u00e2\u0080\u009c': '\u201c', # left double quote
|
||||||
|
'\u00e2\u0080\u009d': '\u201d', # right double quote
|
||||||
|
'\u00e2\u0080\u00a6': '\u2026', # ellipsis
|
||||||
|
}
|
||||||
|
|
||||||
|
total_fixed = 0
|
||||||
|
for path in sorted(glob.glob(os.path.join(LANG_DIR, '*.json'))):
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
raw = f.read()
|
||||||
|
|
||||||
|
original = raw
|
||||||
|
for bad, good in MOJIBAKE_FIXES.items():
|
||||||
|
if bad in raw:
|
||||||
|
count = raw.count(bad)
|
||||||
|
raw = raw.replace(bad, good)
|
||||||
|
lang = os.path.basename(path)
|
||||||
|
print(f" {lang}: fixed {count} x {repr(good)}")
|
||||||
|
total_fixed += count
|
||||||
|
|
||||||
|
if raw != original:
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(raw)
|
||||||
|
|
||||||
|
print(f"\nTotal fixes: {total_fixed}")
|
||||||
35
scripts/gen-lite-checkpoints.sh
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Generate SDXL lite-wallet mainnet checkpoint entries from a fully-synced dragonxd.
|
||||||
|
# Each entry is (height,"blockhash","serialized_sapling_tree") in checkpoints.rs format.
|
||||||
|
# Fills the 1,770,000 -> tip gap so wallets reseed close to their birthday on rescan,
|
||||||
|
# bounding the (divergence-prone) compact-block replay span. Usage:
|
||||||
|
# scripts/gen-lite-checkpoints.sh [start] [step] > /tmp/new_checkpoints.txt
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CLI=${DRAGONX_CLI:-/home/d/dragonx/src/dragonx-cli}
|
||||||
|
START=${1:-1770000}
|
||||||
|
STEP=${2:-10000}
|
||||||
|
|
||||||
|
tip=$("$CLI" getblockcount)
|
||||||
|
end=$(( (tip / STEP) * STEP ))
|
||||||
|
|
||||||
|
# Sanity: confirm the method reproduces a KNOWN checkpoint tree before trusting it.
|
||||||
|
ref_hash=$("$CLI" getblockhash 1760000 | tr -d '"[:space:]')
|
||||||
|
ref_tree=$("$CLI" getblockmerkletree 1760000 | tr -d '"[:space:]')
|
||||||
|
expect_hash="0000545a45b8d4ee4e4b423cb1ea74d67e3a04c320c6ea2f59ee06c08f91a117"
|
||||||
|
if [ "$ref_hash" != "$expect_hash" ]; then
|
||||||
|
echo "ABORT: getblockhash 1760000 = $ref_hash != known $expect_hash" >&2; exit 1
|
||||||
|
fi
|
||||||
|
echo "# self-check: 1760000 hash matches; tree len=${#ref_tree}" >&2
|
||||||
|
|
||||||
|
n=0
|
||||||
|
h=$START
|
||||||
|
while [ "$h" -le "$end" ]; do
|
||||||
|
hash=$("$CLI" getblockhash "$h" | tr -d '"[:space:]')
|
||||||
|
tree=$("$CLI" getblockmerkletree "$h" | tr -d '"[:space:]')
|
||||||
|
if [ -z "$hash" ] || [ -z "$tree" ]; then echo "ABORT: empty hash/tree at $h" >&2; exit 1; fi
|
||||||
|
printf '\t(%s,"%s",\n\t\t"%s"\n\t),\n' "$h" "$hash" "$tree"
|
||||||
|
n=$((n+1))
|
||||||
|
h=$((h+STEP))
|
||||||
|
done
|
||||||
|
echo "# generated $n checkpoints from $START to $end (tip=$tip)" >&2
|
||||||
@@ -7,7 +7,7 @@ set -e
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
||||||
VERSION="1.0.0"
|
VERSION="1.2.0"
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -137,7 +137,7 @@ if [ -f "bin/ObsidianDragon" ]; then
|
|||||||
"${SCRIPT_DIR}/prebuilt-binaries/dragonxd-linux/hush-arrakis-chain"
|
"${SCRIPT_DIR}/prebuilt-binaries/dragonxd-linux/hush-arrakis-chain"
|
||||||
"${SCRIPT_DIR}/../hush-arrakis-chain"
|
"${SCRIPT_DIR}/../hush-arrakis-chain"
|
||||||
"${SCRIPT_DIR}/hush-arrakis-chain"
|
"${SCRIPT_DIR}/hush-arrakis-chain"
|
||||||
"$HOME/hush3/src/hush-arrakis-chain"
|
"$HOME/dragonx/src/hush-arrakis-chain"
|
||||||
)
|
)
|
||||||
|
|
||||||
for lpath in "${LAUNCHER_PATHS[@]}"; do
|
for lpath in "${LAUNCHER_PATHS[@]}"; do
|
||||||
@@ -155,7 +155,7 @@ if [ -f "bin/ObsidianDragon" ]; then
|
|||||||
"${SCRIPT_DIR}/prebuilt-binaries/dragonxd-linux/hushd"
|
"${SCRIPT_DIR}/prebuilt-binaries/dragonxd-linux/hushd"
|
||||||
"${SCRIPT_DIR}/../hushd"
|
"${SCRIPT_DIR}/../hushd"
|
||||||
"${SCRIPT_DIR}/hushd"
|
"${SCRIPT_DIR}/hushd"
|
||||||
"$HOME/hush3/src/hushd"
|
"$HOME/dragonx/src/hushd"
|
||||||
)
|
)
|
||||||
|
|
||||||
for hpath in "${HUSHD_PATHS[@]}"; do
|
for hpath in "${HUSHD_PATHS[@]}"; do
|
||||||
@@ -172,7 +172,7 @@ if [ -f "bin/ObsidianDragon" ]; then
|
|||||||
"${SCRIPT_DIR}/prebuilt-binaries/dragonxd-linux/dragonxd"
|
"${SCRIPT_DIR}/prebuilt-binaries/dragonxd-linux/dragonxd"
|
||||||
"${SCRIPT_DIR}/../dragonxd"
|
"${SCRIPT_DIR}/../dragonxd"
|
||||||
"${SCRIPT_DIR}/dragonxd"
|
"${SCRIPT_DIR}/dragonxd"
|
||||||
"$HOME/hush3/src/dragonxd"
|
"$HOME/dragonx/src/dragonxd"
|
||||||
)
|
)
|
||||||
|
|
||||||
for dpath in "${DRAGONXD_PATHS[@]}"; do
|
for dpath in "${DRAGONXD_PATHS[@]}"; do
|
||||||
@@ -189,8 +189,8 @@ if [ -f "bin/ObsidianDragon" ]; then
|
|||||||
"${SCRIPT_DIR}/prebuilt-binaries/dragonxd-linux/asmap.dat"
|
"${SCRIPT_DIR}/prebuilt-binaries/dragonxd-linux/asmap.dat"
|
||||||
"${SCRIPT_DIR}/../asmap.dat"
|
"${SCRIPT_DIR}/../asmap.dat"
|
||||||
"${SCRIPT_DIR}/asmap.dat"
|
"${SCRIPT_DIR}/asmap.dat"
|
||||||
"$HOME/hush3/asmap.dat"
|
"$HOME/dragonx/asmap.dat"
|
||||||
"$HOME/hush3/contrib/asmap/asmap.dat"
|
"$HOME/dragonx/contrib/asmap/asmap.dat"
|
||||||
)
|
)
|
||||||
|
|
||||||
for apath in "${ASMAP_PATHS[@]}"; do
|
for apath in "${ASMAP_PATHS[@]}"; do
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ done
|
|||||||
|
|
||||||
# Look for asmap.dat
|
# Look for asmap.dat
|
||||||
ASMAP_PATHS=(
|
ASMAP_PATHS=(
|
||||||
"$HOME/hush3/asmap.dat"
|
"$HOME/dragonx/asmap.dat"
|
||||||
"$HOME/hush3/contrib/asmap/asmap.dat"
|
"$HOME/dragonx/contrib/asmap/asmap.dat"
|
||||||
"$SCRIPT_DIR/../asmap.dat"
|
"$SCRIPT_DIR/../asmap.dat"
|
||||||
"$SCRIPT_DIR/asmap.dat"
|
"$SCRIPT_DIR/asmap.dat"
|
||||||
"$SCRIPT_DIR/../SilentDragonX/asmap.dat"
|
"$SCRIPT_DIR/../SilentDragonX/asmap.dat"
|
||||||
@@ -256,8 +256,8 @@ HEADER_START
|
|||||||
echo -e "${YELLOW}Note: Daemon binaries not found in prebuilt-binaries/dragonxd-win/ — wallet only${NC}"
|
echo -e "${YELLOW}Note: Daemon binaries not found in prebuilt-binaries/dragonxd-win/ — wallet only${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── xmrig binary (from prebuilt-binaries/xmrig-hac/) ────────────────
|
# ── xmrig binary (from prebuilt-binaries/drg-xmrig/) ────────────────
|
||||||
XMRIG_DIR="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac"
|
XMRIG_DIR="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig"
|
||||||
if [ -f "$XMRIG_DIR/xmrig.exe" ]; then
|
if [ -f "$XMRIG_DIR/xmrig.exe" ]; then
|
||||||
cp -f "$XMRIG_DIR/xmrig.exe" "$EMBED_RES_DIR/xmrig.exe"
|
cp -f "$XMRIG_DIR/xmrig.exe" "$EMBED_RES_DIR/xmrig.exe"
|
||||||
echo " Staged xmrig.exe ($(du -h "$XMRIG_DIR/xmrig.exe" | cut -f1))"
|
echo " Staged xmrig.exe ($(du -h "$XMRIG_DIR/xmrig.exe" | cut -f1))"
|
||||||
@@ -411,7 +411,7 @@ if [ -f "bin/ObsidianDragon.exe" ]; then
|
|||||||
echo ""
|
echo ""
|
||||||
echo -e "${GREEN}Creating distribution package...${NC}"
|
echo -e "${GREEN}Creating distribution package...${NC}"
|
||||||
cd bin
|
cd bin
|
||||||
DIST_NAME="DragonX-Wallet-Windows-x64"
|
DIST_NAME="ObsidianDragon-Windows-x64"
|
||||||
rm -rf "$DIST_NAME" "$DIST_NAME.zip"
|
rm -rf "$DIST_NAME" "$DIST_NAME.zip"
|
||||||
mkdir -p "$DIST_NAME"
|
mkdir -p "$DIST_NAME"
|
||||||
cp ObsidianDragon.exe "$DIST_NAME/"
|
cp ObsidianDragon.exe "$DIST_NAME/"
|
||||||
@@ -441,7 +441,7 @@ The wallet will look for the daemon config at:
|
|||||||
|
|
||||||
This will be auto-created on first run if the daemon is present.
|
This will be auto-created on first run if the daemon is present.
|
||||||
|
|
||||||
For support: https://git.hush.is/hush/ObsidianDragon
|
For support: https://git.dragonx.is/dragonx/ObsidianDragon
|
||||||
READMEEOF
|
READMEEOF
|
||||||
|
|
||||||
if command -v zip &> /dev/null; then
|
if command -v zip &> /dev/null; then
|
||||||
|
|||||||
414
scripts/setup.sh
@@ -1,414 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# ── scripts/setup.sh ────────────────────────────────────────────────────────
|
|
||||||
# DragonX Wallet — Development Environment Setup
|
|
||||||
# Copyright 2024-2026 The Hush Developers
|
|
||||||
# Released under the GPLv3
|
|
||||||
#
|
|
||||||
# Detects your OS/distro, installs build prerequisites, fetches libraries,
|
|
||||||
# and validates the environment so you can build immediately with:
|
|
||||||
#
|
|
||||||
# ./build.sh # dev build
|
|
||||||
# ./build.sh --win-release # Windows cross-compile
|
|
||||||
# ./build.sh --linux-release # Linux release + AppImage
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# ./scripts/setup.sh # Interactive — install everything needed
|
|
||||||
# ./scripts/setup.sh --check # Just report what's missing, don't install
|
|
||||||
# ./scripts/setup.sh --all # Install dev + all cross-compile targets
|
|
||||||
# ./scripts/setup.sh --win # Also install Windows cross-compile deps
|
|
||||||
# ./scripts/setup.sh --mac # Also install macOS cross-compile deps
|
|
||||||
# ./scripts/setup.sh --sapling # Also download Sapling params (~51 MB)
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
||||||
|
|
||||||
# ── Colours ──────────────────────────────────────────────────────────────────
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
CYAN='\033[0;36m'
|
|
||||||
BOLD='\033[1m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
|
||||||
miss() { echo -e " ${RED}✗${NC} $1"; }
|
|
||||||
skip() { echo -e " ${YELLOW}—${NC} $1"; }
|
|
||||||
info() { echo -e "${GREEN}[*]${NC} $1"; }
|
|
||||||
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
|
||||||
err() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
||||||
header(){ echo -e "\n${CYAN}── $1 ──${NC}"; }
|
|
||||||
|
|
||||||
# ── Parse args ───────────────────────────────────────────────────────────────
|
|
||||||
CHECK_ONLY=false
|
|
||||||
SETUP_WIN=false
|
|
||||||
SETUP_MAC=false
|
|
||||||
SETUP_SAPLING=false
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case $1 in
|
|
||||||
--check) CHECK_ONLY=true; shift ;;
|
|
||||||
--win) SETUP_WIN=true; shift ;;
|
|
||||||
--mac) SETUP_MAC=true; shift ;;
|
|
||||||
--sapling) SETUP_SAPLING=true; shift ;;
|
|
||||||
--all) SETUP_WIN=true; SETUP_MAC=true; SETUP_SAPLING=true; shift ;;
|
|
||||||
-h|--help)
|
|
||||||
sed -n '2,/^# ─\{10\}/{ /^# ─\{10\}/d; s/^# \?//p; }' "$0"
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*) err "Unknown option: $1"; exit 1 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# ── Detect OS / distro ──────────────────────────────────────────────────────
|
|
||||||
detect_os() {
|
|
||||||
OS="$(uname -s)"
|
|
||||||
DISTRO="unknown"
|
|
||||||
PKG=""
|
|
||||||
|
|
||||||
case "$OS" in
|
|
||||||
Linux)
|
|
||||||
if [[ -f /etc/os-release ]]; then
|
|
||||||
. /etc/os-release
|
|
||||||
case "${ID:-}" in
|
|
||||||
ubuntu|debian|linuxmint|pop|elementary|zorin|neon)
|
|
||||||
DISTRO="debian"; PKG="apt" ;;
|
|
||||||
fedora|rhel|centos|rocky|alma)
|
|
||||||
DISTRO="fedora"; PKG="dnf" ;;
|
|
||||||
arch|manjaro|endeavouros|garuda)
|
|
||||||
DISTRO="arch"; PKG="pacman" ;;
|
|
||||||
opensuse*|suse*)
|
|
||||||
DISTRO="suse"; PKG="zypper" ;;
|
|
||||||
void)
|
|
||||||
DISTRO="void"; PKG="xbps" ;;
|
|
||||||
gentoo)
|
|
||||||
DISTRO="gentoo"; PKG="emerge" ;;
|
|
||||||
*)
|
|
||||||
# Fallback: check for package managers
|
|
||||||
command -v apt &>/dev/null && { DISTRO="debian"; PKG="apt"; } ||
|
|
||||||
command -v dnf &>/dev/null && { DISTRO="fedora"; PKG="dnf"; } ||
|
|
||||||
command -v pacman &>/dev/null && { DISTRO="arch"; PKG="pacman"; }
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
Darwin)
|
|
||||||
DISTRO="macos"
|
|
||||||
command -v brew &>/dev/null && PKG="brew"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
err "Unsupported OS: $OS"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Package lists per distro ────────────────────────────────────────────────
|
|
||||||
# Core: minimum to do a dev build (Linux native)
|
|
||||||
pkgs_core_debian="build-essential cmake git pkg-config
|
|
||||||
libgl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev
|
|
||||||
libxinerama-dev libxi-dev libxkbcommon-dev libwayland-dev
|
|
||||||
libsodium-dev libcurl4-openssl-dev"
|
|
||||||
|
|
||||||
pkgs_core_fedora="gcc gcc-c++ cmake git pkg-config
|
|
||||||
mesa-libGL-devel libX11-devel libXcursor-devel libXrandr-devel
|
|
||||||
libXinerama-devel libXi-devel libxkbcommon-devel wayland-devel
|
|
||||||
libsodium-devel libcurl-devel"
|
|
||||||
|
|
||||||
pkgs_core_arch="base-devel cmake git pkg-config
|
|
||||||
mesa libx11 libxcursor libxrandr libxinerama libxi
|
|
||||||
libxkbcommon wayland libsodium curl"
|
|
||||||
|
|
||||||
pkgs_core_macos="cmake"
|
|
||||||
|
|
||||||
# Windows cross-compile (from Linux)
|
|
||||||
pkgs_win_debian="mingw-w64 zip"
|
|
||||||
pkgs_win_fedora="mingw64-gcc mingw64-gcc-c++ zip"
|
|
||||||
pkgs_win_arch="mingw-w64-gcc zip"
|
|
||||||
|
|
||||||
# macOS cross-compile helpers (osxcross is separate)
|
|
||||||
pkgs_mac_debian="genisoimage icnsutils"
|
|
||||||
pkgs_mac_fedora="genisoimage"
|
|
||||||
pkgs_mac_arch="cdrtools"
|
|
||||||
|
|
||||||
# ── Helpers ──────────────────────────────────────────────────────────────────
|
|
||||||
has_cmd() { command -v "$1" &>/dev/null; }
|
|
||||||
|
|
||||||
# Install packages for the detected distro
|
|
||||||
install_pkgs() {
|
|
||||||
local pkgs="$1"
|
|
||||||
local desc="$2"
|
|
||||||
|
|
||||||
if $CHECK_ONLY; then
|
|
||||||
warn "Would install ($desc): $pkgs"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Installing $desc packages..."
|
|
||||||
case "$PKG" in
|
|
||||||
apt) sudo apt-get update -qq && sudo apt-get install -y $pkgs ;;
|
|
||||||
dnf) sudo dnf install -y $pkgs ;;
|
|
||||||
pacman) sudo pacman -S --needed --noconfirm $pkgs ;;
|
|
||||||
zypper) sudo zypper install -y $pkgs ;;
|
|
||||||
brew) brew install $pkgs ;;
|
|
||||||
*) err "No supported package manager found for $DISTRO"
|
|
||||||
echo "Please install manually: $pkgs"
|
|
||||||
return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Select the right package list variable
|
|
||||||
get_pkgs() {
|
|
||||||
local category="$1" # core, win, mac
|
|
||||||
local var="pkgs_${category}_${DISTRO}"
|
|
||||||
echo "${!var:-}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Check individual tools ──────────────────────────────────────────────────
|
|
||||||
MISSING=0
|
|
||||||
|
|
||||||
check_tool() {
|
|
||||||
local cmd="$1"
|
|
||||||
local label="${2:-$1}"
|
|
||||||
if has_cmd "$cmd"; then
|
|
||||||
ok "$label"
|
|
||||||
else
|
|
||||||
miss "$label (not found: $cmd)"
|
|
||||||
MISSING=$((MISSING + 1))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_file() {
|
|
||||||
local path="$1"
|
|
||||||
local label="$2"
|
|
||||||
if [[ -f "$path" ]]; then
|
|
||||||
ok "$label"
|
|
||||||
else
|
|
||||||
miss "$label ($path)"
|
|
||||||
MISSING=$((MISSING + 1))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_dir() {
|
|
||||||
local path="$1"
|
|
||||||
local label="$2"
|
|
||||||
if [[ -d "$path" ]]; then
|
|
||||||
ok "$label"
|
|
||||||
else
|
|
||||||
miss "$label ($path)"
|
|
||||||
MISSING=$((MISSING + 1))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════════════════════════
|
|
||||||
# MAIN
|
|
||||||
# ═════════════════════════════════════════════════════════════════════════════
|
|
||||||
echo -e "${BOLD}DragonX Wallet — Development Setup${NC}"
|
|
||||||
echo "═══════════════════════════════════"
|
|
||||||
|
|
||||||
detect_os
|
|
||||||
info "Detected: $OS / $DISTRO (package manager: ${PKG:-none})"
|
|
||||||
|
|
||||||
# ── 1. Core build dependencies ──────────────────────────────────────────────
|
|
||||||
header "Core Build Dependencies"
|
|
||||||
|
|
||||||
core_pkgs="$(get_pkgs core)"
|
|
||||||
if [[ -z "$core_pkgs" ]]; then
|
|
||||||
warn "No package list for $DISTRO — check README for manual instructions"
|
|
||||||
else
|
|
||||||
# Check if key tools are already present
|
|
||||||
NEED_CORE=false
|
|
||||||
has_cmd cmake && has_cmd g++ && has_cmd pkg-config || NEED_CORE=true
|
|
||||||
|
|
||||||
if $NEED_CORE; then
|
|
||||||
install_pkgs "$core_pkgs" "core build"
|
|
||||||
else
|
|
||||||
ok "Core tools already installed (cmake, g++, pkg-config)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
check_tool cmake "cmake"
|
|
||||||
check_tool g++ "g++ (C++ compiler)"
|
|
||||||
check_tool git "git"
|
|
||||||
check_tool make "make"
|
|
||||||
|
|
||||||
# ── 2. libsodium ────────────────────────────────────────────────────────────
|
|
||||||
header "libsodium"
|
|
||||||
|
|
||||||
SODIUM_OK=false
|
|
||||||
# Check system libsodium
|
|
||||||
if pkg-config --exists libsodium 2>/dev/null; then
|
|
||||||
ok "libsodium (system, $(pkg-config --modversion libsodium))"
|
|
||||||
SODIUM_OK=true
|
|
||||||
elif [[ -f "$PROJECT_DIR/libs/libsodium/lib/libsodium.a" ]]; then
|
|
||||||
ok "libsodium (local build)"
|
|
||||||
SODIUM_OK=true
|
|
||||||
else
|
|
||||||
miss "libsodium not found"
|
|
||||||
if ! $CHECK_ONLY; then
|
|
||||||
info "Building libsodium from source..."
|
|
||||||
"$SCRIPT_DIR/fetch-libsodium.sh" && SODIUM_OK=true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 3. Windows cross-compile (optional) ─────────────────────────────────────
|
|
||||||
header "Windows Cross-Compile"
|
|
||||||
|
|
||||||
if $SETUP_WIN; then
|
|
||||||
win_pkgs="$(get_pkgs win)"
|
|
||||||
if [[ -n "$win_pkgs" ]]; then
|
|
||||||
install_pkgs "$win_pkgs" "Windows cross-compile"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set posix thread model if available
|
|
||||||
if has_cmd update-alternatives && [[ "$PKG" == "apt" ]]; then
|
|
||||||
if ! $CHECK_ONLY; then
|
|
||||||
sudo update-alternatives --set x86_64-w64-mingw32-gcc \
|
|
||||||
/usr/bin/x86_64-w64-mingw32-gcc-posix 2>/dev/null || true
|
|
||||||
sudo update-alternatives --set x86_64-w64-mingw32-g++ \
|
|
||||||
/usr/bin/x86_64-w64-mingw32-g++-posix 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fetch libsodium for Windows
|
|
||||||
if [[ ! -f "$PROJECT_DIR/libs/libsodium-win/lib/libsodium.a" ]]; then
|
|
||||||
if ! $CHECK_ONLY; then
|
|
||||||
info "Building libsodium for Windows target..."
|
|
||||||
"$SCRIPT_DIR/fetch-libsodium.sh" --win
|
|
||||||
else
|
|
||||||
miss "libsodium-win (not built yet)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
ok "libsodium-win"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if has_cmd x86_64-w64-mingw32-g++-posix || has_cmd x86_64-w64-mingw32-g++; then
|
|
||||||
ok "mingw-w64 ($(x86_64-w64-mingw32-g++-posix --version 2>/dev/null | head -1 || x86_64-w64-mingw32-g++ --version 2>/dev/null | head -1))"
|
|
||||||
else
|
|
||||||
if $SETUP_WIN; then
|
|
||||||
miss "mingw-w64"
|
|
||||||
else
|
|
||||||
skip "mingw-w64 (use --win to install)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 4. macOS cross-compile (optional) ───────────────────────────────────────
|
|
||||||
header "macOS Cross-Compile"
|
|
||||||
|
|
||||||
if $SETUP_MAC; then
|
|
||||||
mac_pkgs="$(get_pkgs mac)"
|
|
||||||
if [[ -n "$mac_pkgs" ]]; then
|
|
||||||
install_pkgs "$mac_pkgs" "macOS cross-compile helpers"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fetch libsodium for macOS
|
|
||||||
if [[ ! -f "$PROJECT_DIR/libs/libsodium-mac/lib/libsodium.a" ]]; then
|
|
||||||
if ! $CHECK_ONLY; then
|
|
||||||
info "Building libsodium for macOS target..."
|
|
||||||
"$SCRIPT_DIR/fetch-libsodium.sh" --mac
|
|
||||||
else
|
|
||||||
miss "libsodium-mac (not built yet)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
ok "libsodium-mac"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -d "$PROJECT_DIR/external/osxcross/target" ]] || [[ -d "${OSXCROSS:-}/target" ]]; then
|
|
||||||
ok "osxcross"
|
|
||||||
else
|
|
||||||
if $SETUP_MAC; then
|
|
||||||
miss "osxcross (must be set up manually — see README)"
|
|
||||||
else
|
|
||||||
skip "osxcross (use --mac to set up macOS deps)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 5. Sapling parameters ───────────────────────────────────────────────────
|
|
||||||
header "Sapling Parameters"
|
|
||||||
|
|
||||||
SAPLING_DIR=""
|
|
||||||
for d in "$HOME/.zcash-params" "$HOME/.hush-params"; do
|
|
||||||
if [[ -f "$d/sapling-spend.params" && -f "$d/sapling-output.params" ]]; then
|
|
||||||
SAPLING_DIR="$d"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Also check project-local locations
|
|
||||||
if [[ -z "$SAPLING_DIR" ]]; then
|
|
||||||
for d in "$PROJECT_DIR/prebuilt-binaries/dragonxd-linux" "$PROJECT_DIR/prebuilt-binaries/dragonxd-win"; do
|
|
||||||
if [[ -f "$d/sapling-spend.params" && -f "$d/sapling-output.params" ]]; then
|
|
||||||
SAPLING_DIR="$d"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$SAPLING_DIR" ]]; then
|
|
||||||
ok "sapling-spend.params ($(du -h "$SAPLING_DIR/sapling-spend.params" | cut -f1))"
|
|
||||||
ok "sapling-output.params ($(du -h "$SAPLING_DIR/sapling-output.params" | cut -f1))"
|
|
||||||
elif $SETUP_SAPLING; then
|
|
||||||
if ! $CHECK_ONLY; then
|
|
||||||
info "Downloading Sapling parameters (~51 MB)..."
|
|
||||||
PARAMS_DIR="$HOME/.zcash-params"
|
|
||||||
mkdir -p "$PARAMS_DIR"
|
|
||||||
|
|
||||||
SPEND_URL="https://z.cash/downloads/sapling-spend.params"
|
|
||||||
OUTPUT_URL="https://z.cash/downloads/sapling-output.params"
|
|
||||||
|
|
||||||
curl -fSL -o "$PARAMS_DIR/sapling-spend.params" "$SPEND_URL" && \
|
|
||||||
ok "Downloaded sapling-spend.params"
|
|
||||||
curl -fSL -o "$PARAMS_DIR/sapling-output.params" "$OUTPUT_URL" && \
|
|
||||||
ok "Downloaded sapling-output.params"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
skip "Sapling params not found (use --sapling to download, or they'll be extracted at runtime from embedded builds)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 6. Binary directories ───────────────────────────────────────────────────
|
|
||||||
header "Binary Directories"
|
|
||||||
|
|
||||||
for platform in dragonxd-linux dragonxd-win dragonxd-mac xmrig; do
|
|
||||||
dir="$PROJECT_DIR/prebuilt-binaries/$platform"
|
|
||||||
if [[ -d "$dir" ]]; then
|
|
||||||
# Count actual files (not .gitkeep)
|
|
||||||
count=$(find "$dir" -maxdepth 1 -type f ! -name '.gitkeep' | wc -l)
|
|
||||||
if [[ $count -gt 0 ]]; then
|
|
||||||
ok "prebuilt-binaries/$platform/ ($count files)"
|
|
||||||
else
|
|
||||||
skip "prebuilt-binaries/$platform/ (empty — place binaries here)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if ! $CHECK_ONLY; then
|
|
||||||
mkdir -p "$dir"
|
|
||||||
touch "$dir/.gitkeep"
|
|
||||||
ok "Created prebuilt-binaries/$platform/"
|
|
||||||
else
|
|
||||||
miss "prebuilt-binaries/$platform/"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# ── Summary ──────────────────────────────────────────────────────────────────
|
|
||||||
echo ""
|
|
||||||
echo "═══════════════════════════════════════════"
|
|
||||||
if [[ $MISSING -eq 0 ]]; then
|
|
||||||
echo -e "${GREEN}${BOLD} Setup complete — ready to build!${NC}"
|
|
||||||
echo ""
|
|
||||||
echo " Quick start:"
|
|
||||||
echo " ./build.sh # Dev build"
|
|
||||||
echo " ./build.sh --linux-release # Linux release + AppImage"
|
|
||||||
echo " ./build.sh --win-release # Windows cross-compile"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}${BOLD} $MISSING item(s) still need attention${NC}"
|
|
||||||
if $CHECK_ONLY; then
|
|
||||||
echo ""
|
|
||||||
echo " Run without --check to install automatically:"
|
|
||||||
echo " ./scripts/setup.sh"
|
|
||||||
echo " ./scripts/setup.sh --all # Include cross-compile + Sapling"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo "═══════════════════════════════════════════"
|
|
||||||
157
scripts/sign-daemon-release.sh
Executable file
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Package the prebuilt dragonx full-node binaries into per-platform release archives and sign them
|
||||||
|
# for the wallet's in-app daemon updater (ed25519 over the EXACT archive bytes).
|
||||||
|
#
|
||||||
|
# The wallet verifies a detached ed25519 signature over the archive bytes against a public key
|
||||||
|
# pinned in src/util/daemon_updater.h (kDaemonSignaturePublicKeyBase64). Verification is MANDATORY
|
||||||
|
# (kDaemonRequireSignature = true): an in-app update is refused unless a valid "<archive>.sig" is
|
||||||
|
# published next to the archive. The wallet also checks each archive's SHA-256 against a markdown
|
||||||
|
# checksum table in the release body, so `release` prints that table for you to paste in.
|
||||||
|
#
|
||||||
|
# Uses OpenSSL (>= 1.1.1) only — no Python/PyNaCl. OpenSSL's ed25519 is PureEdDSA (RFC 8032), the
|
||||||
|
# same primitive libsodium's crypto_sign_verify_detached checks, so the signatures are compatible.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/sign-daemon-release.sh keygen [out-prefix] # -> <prefix>.ed25519.{key,pub.b64}
|
||||||
|
# scripts/sign-daemon-release.sh pubkey <secret.key> # print the base64 public key to pin
|
||||||
|
# scripts/sign-daemon-release.sh sign <secret.key> <file>... # sign existing files -> <file>.sig
|
||||||
|
# scripts/sign-daemon-release.sh release <secret.key> <version> [--src DIR] [--out DIR]
|
||||||
|
# # zip prebuilt-binaries/dragonxd-{linux,mac,win}/ into dragonx-<version>-{linux-amd64,macos,
|
||||||
|
# # win64}.zip, sign each, and print the SHA-256 checksum table. Platforms with no dragonxd
|
||||||
|
# # binary staged are skipped.
|
||||||
|
#
|
||||||
|
# Keep the secret key (.ed25519.key) OFFLINE (mode 600). Paste the base64 public key into
|
||||||
|
# kDaemonSignaturePublicKeyBase64 in src/util/daemon_updater.h.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
command -v openssl >/dev/null || die "openssl not found (need >= 1.1.1 with ed25519)"
|
||||||
|
|
||||||
|
# Raw 32-byte ed25519 public key (base64) from a private key file. The DER SubjectPublicKeyInfo for
|
||||||
|
# ed25519 is a fixed 12-byte prefix + the 32-byte key, so the trailing 32 bytes are the raw key.
|
||||||
|
pubkey_b64() { openssl pkey -in "$1" -pubout -outform DER | tail -c 32 | openssl base64 -A; }
|
||||||
|
|
||||||
|
sha256_of() {
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}';
|
||||||
|
else shasum -a 256 "$1" | awk '{print $1}'; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detached ed25519 signature over the raw file bytes -> <file>.sig (base64 of the 64-byte sig).
|
||||||
|
sign_file() {
|
||||||
|
local key="$1" f="$2" raw
|
||||||
|
raw="$(mktemp)"
|
||||||
|
openssl pkeyutl -sign -inkey "$key" -rawin -in "$f" -out "$raw"
|
||||||
|
openssl base64 -A -in "$raw" > "$f.sig"
|
||||||
|
printf '\n' >> "$f.sig"
|
||||||
|
rm -f "$raw"
|
||||||
|
}
|
||||||
|
|
||||||
|
# platform -> (staging dir under prebuilt-binaries, release token, expected daemon binary name)
|
||||||
|
plat_dir() { case "$1" in linux) echo dragonxd-linux;; mac) echo dragonxd-mac;; win) echo dragonxd-win;; esac; }
|
||||||
|
plat_token() { case "$1" in linux) echo linux-amd64;; mac) echo macos;; win) echo win64;; esac; }
|
||||||
|
plat_daemon() { case "$1" in win) echo dragonxd.exe;; *) echo dragonxd;; esac; }
|
||||||
|
|
||||||
|
cmd="${1:-}"; shift || true
|
||||||
|
case "$cmd" in
|
||||||
|
keygen)
|
||||||
|
prefix="${1:-dragonx-daemon}"
|
||||||
|
[ -e "$prefix.ed25519.key" ] && die "$prefix.ed25519.key already exists — refusing to overwrite"
|
||||||
|
openssl genpkey -algorithm ed25519 -out "$prefix.ed25519.key"
|
||||||
|
chmod 600 "$prefix.ed25519.key"
|
||||||
|
pub="$(pubkey_b64 "$prefix.ed25519.key")"
|
||||||
|
printf '%s\n' "$pub" > "$prefix.ed25519.pub.b64"
|
||||||
|
echo "secret key : $prefix.ed25519.key (KEEP OFFLINE, mode 600)"
|
||||||
|
echo "public key : $prefix.ed25519.pub.b64"
|
||||||
|
echo
|
||||||
|
echo "Pin this in src/util/daemon_updater.h (kDaemonSignaturePublicKeyBase64):"
|
||||||
|
echo " $pub"
|
||||||
|
;;
|
||||||
|
|
||||||
|
pubkey)
|
||||||
|
[ $# -ge 1 ] || die "usage: pubkey <secret.key>"
|
||||||
|
pubkey_b64 "$1"
|
||||||
|
;;
|
||||||
|
|
||||||
|
sign)
|
||||||
|
[ $# -ge 2 ] || die "usage: sign <secret.key> <file>..."
|
||||||
|
key="$1"; shift
|
||||||
|
[ -f "$key" ] || die "no such key: $key"
|
||||||
|
for f in "$@"; do
|
||||||
|
[ -f "$f" ] || die "no such file: $f"
|
||||||
|
sign_file "$key" "$f"
|
||||||
|
echo "signed: $f -> $f.sig"
|
||||||
|
done
|
||||||
|
echo "Upload each .sig as a release asset next to its archive."
|
||||||
|
;;
|
||||||
|
|
||||||
|
release)
|
||||||
|
[ $# -ge 2 ] || die "usage: release <secret.key> <version> [--src DIR] [--out DIR]"
|
||||||
|
key="$1"; version="$2"; shift 2
|
||||||
|
src="$PROJECT_ROOT/prebuilt-binaries"
|
||||||
|
out="$PROJECT_ROOT/release/daemon"
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--src) [ $# -ge 2 ] || die "--src needs a value"; src="$2"; shift 2 ;;
|
||||||
|
--out) [ $# -ge 2 ] || die "--out needs a value"; out="$2"; shift 2 ;;
|
||||||
|
*) die "unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ -f "$key" ] || die "no such key: $key"
|
||||||
|
[ -d "$src" ] || die "no such source dir: $src"
|
||||||
|
command -v zip >/dev/null 2>&1 || die "zip not found (install 'zip')"
|
||||||
|
mkdir -p "$out"
|
||||||
|
|
||||||
|
# Sanity: warn if this key does not match the public key pinned in the wallet (the wallet would
|
||||||
|
# then reject every signature made with it — only expected when deliberately rotating the key).
|
||||||
|
pinned="$(grep -oE '"[A-Za-z0-9+/]{43}="' "$PROJECT_ROOT/src/util/daemon_updater.h" 2>/dev/null | head -1 | tr -d '"')"
|
||||||
|
mine="$(pubkey_b64 "$key")"
|
||||||
|
if [ -n "$pinned" ] && [ "$pinned" != "$mine" ]; then
|
||||||
|
echo "WARNING: this key's public key does not match the one pinned in daemon_updater.h:" >&2
|
||||||
|
echo " signing key -> $mine" >&2
|
||||||
|
echo " pinned key -> $pinned" >&2
|
||||||
|
echo " The wallet will REJECT these signatures unless you are rotating the pinned key." >&2
|
||||||
|
echo >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
made=0
|
||||||
|
table=""
|
||||||
|
for plat in linux mac win; do
|
||||||
|
d="$src/$(plat_dir "$plat")"
|
||||||
|
daemon="$d/$(plat_daemon "$plat")"
|
||||||
|
if [ ! -f "$daemon" ]; then
|
||||||
|
echo "skip $plat: no $(plat_daemon "$plat") staged in $d" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
archive="dragonx-$version-$(plat_token "$plat").zip"
|
||||||
|
apath="$out/$archive"
|
||||||
|
rm -f "$apath"
|
||||||
|
# Zip the staged files at the archive root (binaries + sapling params + asmap), excluding
|
||||||
|
# the .gitkeep placeholder. The updater flattens paths via baseName(), so a flat zip is fine.
|
||||||
|
files=()
|
||||||
|
while IFS= read -r fn; do files+=("$fn"); done < <(cd "$d" && ls -A | grep -vx '.gitkeep')
|
||||||
|
[ "${#files[@]}" -gt 0 ] || { echo "skip $plat: nothing to package in $d" >&2; continue; }
|
||||||
|
( cd "$d" && zip -q -X "$apath" "${files[@]}" )
|
||||||
|
sign_file "$key" "$apath"
|
||||||
|
sum="$(sha256_of "$apath")"
|
||||||
|
table+="| $archive | \`$sum\` |"$'\n'
|
||||||
|
echo "packaged + signed: $apath (+ .sig) sha256=$sum"
|
||||||
|
made=$((made + 1))
|
||||||
|
done
|
||||||
|
[ "$made" -gt 0 ] || die "no platform had a staged daemon binary under $src/dragonxd-{linux,mac,win}/"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Checksum table (paste into the release body so the wallet can verify SHA-256):"
|
||||||
|
echo "| Archive | SHA-256 |"
|
||||||
|
echo "|---|---|"
|
||||||
|
printf '%s' "$table"
|
||||||
|
echo
|
||||||
|
echo "Upload each .zip AND its .zip.sig as release assets. Wallet enforces the ed25519 signature"
|
||||||
|
echo "(kDaemonRequireSignature=true) and the SHA-256 from the table above."
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
die "usage: $0 {keygen [prefix] | pubkey <secret.key> | sign <secret.key> <file>... | release <secret.key> <version> [--src DIR] [--out DIR]}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
66
scripts/sign-xmrig-release.sh
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sign DRG-XMRig release archives for the wallet's in-app updater (opt-in ed25519 signatures).
|
||||||
|
#
|
||||||
|
# The wallet verifies a detached ed25519 signature over the EXACT archive bytes against a public
|
||||||
|
# key pinned in src/util/xmrig_updater.h (kXmrigSignaturePublicKeyBase64). For each archive
|
||||||
|
# <name>.zip this produces <name>.zip.sig holding the base64 of the raw 64-byte ed25519 signature —
|
||||||
|
# upload that .sig next to the .zip as a release asset.
|
||||||
|
#
|
||||||
|
# Uses OpenSSL (>= 1.1.1) only — no Python/PyNaCl needed. OpenSSL's ed25519 is PureEdDSA (RFC 8032),
|
||||||
|
# the same primitive libsodium's crypto_sign_verify_detached checks, so signatures are compatible
|
||||||
|
# (verified by the wallet's unit tests + an interop check).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/sign-xmrig-release.sh keygen [out-prefix] # -> <prefix>.ed25519.{key,pub.b64}
|
||||||
|
# scripts/sign-xmrig-release.sh pubkey <secret.key> # print the base64 public key to pin
|
||||||
|
# scripts/sign-xmrig-release.sh sign <secret.key> <file>...# -> <file>.sig per file
|
||||||
|
#
|
||||||
|
# Keep the secret key (.ed25519.key) OFFLINE. Paste the base64 public key into
|
||||||
|
# kXmrigSignaturePublicKeyBase64 in src/util/xmrig_updater.h.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
command -v openssl >/dev/null || die "openssl not found (need >= 1.1.1 with ed25519)"
|
||||||
|
|
||||||
|
# Raw 32-byte ed25519 public key (base64) from a private key file. The DER SubjectPublicKeyInfo for
|
||||||
|
# ed25519 is a fixed 12-byte prefix + the 32-byte key, so the trailing 32 bytes are the raw key.
|
||||||
|
pubkey_b64() { openssl pkey -in "$1" -pubout -outform DER | tail -c 32 | openssl base64 -A; }
|
||||||
|
|
||||||
|
cmd="${1:-}"; shift || true
|
||||||
|
case "$cmd" in
|
||||||
|
keygen)
|
||||||
|
prefix="${1:-drg-xmrig}"
|
||||||
|
[ -e "$prefix.ed25519.key" ] && die "$prefix.ed25519.key already exists — refusing to overwrite"
|
||||||
|
openssl genpkey -algorithm ed25519 -out "$prefix.ed25519.key"
|
||||||
|
chmod 600 "$prefix.ed25519.key"
|
||||||
|
pub="$(pubkey_b64 "$prefix.ed25519.key")"
|
||||||
|
printf '%s\n' "$pub" > "$prefix.ed25519.pub.b64"
|
||||||
|
echo "secret key : $prefix.ed25519.key (KEEP OFFLINE, mode 600)"
|
||||||
|
echo "public key : $prefix.ed25519.pub.b64"
|
||||||
|
echo
|
||||||
|
echo "Pin this in src/util/xmrig_updater.h (kXmrigSignaturePublicKeyBase64):"
|
||||||
|
echo " $pub"
|
||||||
|
;;
|
||||||
|
pubkey)
|
||||||
|
[ $# -ge 1 ] || die "usage: pubkey <secret.key>"
|
||||||
|
pubkey_b64 "$1"
|
||||||
|
;;
|
||||||
|
sign)
|
||||||
|
[ $# -ge 2 ] || die "usage: sign <secret.key> <file>..."
|
||||||
|
key="$1"; shift
|
||||||
|
[ -f "$key" ] || die "no such key: $key"
|
||||||
|
for f in "$@"; do
|
||||||
|
[ -f "$f" ] || die "no such file: $f"
|
||||||
|
raw="$(mktemp)"
|
||||||
|
openssl pkeyutl -sign -inkey "$key" -rawin -in "$f" -out "$raw"
|
||||||
|
openssl base64 -A -in "$raw" > "$f.sig"
|
||||||
|
printf '\n' >> "$f.sig"
|
||||||
|
rm -f "$raw"
|
||||||
|
echo "signed: $f -> $f.sig"
|
||||||
|
done
|
||||||
|
echo "Upload each .sig as a release asset next to its archive."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "usage: $0 {keygen [prefix] | pubkey <secret.key> | sign <secret.key> <file>...}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
866
setup.sh
Executable file
@@ -0,0 +1,866 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ── setup.sh ────────────────────────────────────────────────────────────────
|
||||||
|
# DragonX Wallet — Development Environment Setup
|
||||||
|
# Copyright 2024-2026 The Hush Developers
|
||||||
|
# Released under the GPLv3
|
||||||
|
#
|
||||||
|
# Detects your OS/distro, installs build prerequisites, fetches libraries,
|
||||||
|
# and validates the environment so you can build immediately with:
|
||||||
|
#
|
||||||
|
# ./build.sh # dev build
|
||||||
|
# ./build.sh --win-release # Windows cross-compile
|
||||||
|
# ./build.sh --linux-release # Linux release + AppImage
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./setup.sh # Interactive — install everything needed
|
||||||
|
# ./setup.sh --check # Just report what's missing, don't install
|
||||||
|
# ./setup.sh --all # Install dev + all cross-compile targets
|
||||||
|
# ./setup.sh --win # Also install Windows cross-compile deps
|
||||||
|
# ./setup.sh --mac # Also install macOS cross-compile deps
|
||||||
|
# ./setup.sh --sapling # Also download Sapling params (~51 MB)
|
||||||
|
# ./setup.sh -j6 # Use 6 threads for building
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# ── Parallel builds ─────────────────────────────────────────────────────────
|
||||||
|
# Default to 1 core; use -jN to increase (e.g. -j6)
|
||||||
|
NPROC=1
|
||||||
|
|
||||||
|
# ── Colours ──────────────────────────────────────────────────────────────────
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
||||||
|
miss() { echo -e " ${RED}✗${NC} $1"; }
|
||||||
|
skip() { echo -e " ${YELLOW}—${NC} $1"; }
|
||||||
|
info() { echo -e "${GREEN}[*]${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||||
|
err() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
header(){ echo -e "\n${CYAN}── $1 ──${NC}"; }
|
||||||
|
|
||||||
|
# ── Parse args ───────────────────────────────────────────────────────────────
|
||||||
|
CHECK_ONLY=false
|
||||||
|
SETUP_WIN=false
|
||||||
|
SETUP_MAC=false
|
||||||
|
SETUP_SAPLING=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--check) CHECK_ONLY=true; shift ;;
|
||||||
|
--win) SETUP_WIN=true; shift ;;
|
||||||
|
--mac) SETUP_MAC=true; shift ;;
|
||||||
|
--sapling) SETUP_SAPLING=true; shift ;;
|
||||||
|
--all) SETUP_WIN=true; SETUP_MAC=true; SETUP_SAPLING=true; shift ;;
|
||||||
|
-j*) NPROC="${1#-j}"; shift ;;
|
||||||
|
-h|--help)
|
||||||
|
sed -n '2,/^# ─\{10\}/{ /^# ─\{10\}/d; s/^# \?//p; }' "$0"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*) err "Unknown option: $1"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Apply parallel build flag (after arg parsing so -jN override takes effect)
|
||||||
|
# NOTE: We do NOT export MAKEFLAGS globally — that interferes with autotools
|
||||||
|
# builds (dragonx daemon). Instead, -j is passed explicitly where needed.
|
||||||
|
|
||||||
|
# ── Detect OS / distro ──────────────────────────────────────────────────────
|
||||||
|
detect_os() {
|
||||||
|
OS="$(uname -s)"
|
||||||
|
DISTRO="unknown"
|
||||||
|
PKG=""
|
||||||
|
|
||||||
|
case "$OS" in
|
||||||
|
Linux)
|
||||||
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
. /etc/os-release
|
||||||
|
case "${ID:-}" in
|
||||||
|
ubuntu|debian|linuxmint|pop|elementary|zorin|neon)
|
||||||
|
DISTRO="debian"; PKG="apt" ;;
|
||||||
|
fedora|rhel|centos|rocky|alma)
|
||||||
|
DISTRO="fedora"; PKG="dnf" ;;
|
||||||
|
arch|manjaro|endeavouros|garuda)
|
||||||
|
DISTRO="arch"; PKG="pacman" ;;
|
||||||
|
opensuse*|suse*)
|
||||||
|
DISTRO="suse"; PKG="zypper" ;;
|
||||||
|
void)
|
||||||
|
DISTRO="void"; PKG="xbps" ;;
|
||||||
|
gentoo)
|
||||||
|
DISTRO="gentoo"; PKG="emerge" ;;
|
||||||
|
*)
|
||||||
|
# Fallback: check for package managers
|
||||||
|
command -v apt &>/dev/null && { DISTRO="debian"; PKG="apt"; } ||
|
||||||
|
command -v dnf &>/dev/null && { DISTRO="fedora"; PKG="dnf"; } ||
|
||||||
|
command -v pacman &>/dev/null && { DISTRO="arch"; PKG="pacman"; } ||
|
||||||
|
true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
Darwin)
|
||||||
|
DISTRO="macos"
|
||||||
|
if command -v brew &>/dev/null; then PKG="brew"; fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
err "Unsupported OS: $OS"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Package lists per distro ────────────────────────────────────────────────
|
||||||
|
# Core: minimum to do a dev build (Linux native)
|
||||||
|
pkgs_core_debian="build-essential cmake git pkg-config
|
||||||
|
libgl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev
|
||||||
|
libxinerama-dev libxi-dev libxkbcommon-dev libwayland-dev
|
||||||
|
libsodium-dev libcurl4-openssl-dev
|
||||||
|
autoconf automake libtool wget python3 xxd"
|
||||||
|
|
||||||
|
pkgs_core_fedora="gcc gcc-c++ cmake git pkg-config
|
||||||
|
mesa-libGL-devel libX11-devel libXcursor-devel libXrandr-devel
|
||||||
|
libXinerama-devel libXi-devel libxkbcommon-devel wayland-devel
|
||||||
|
libsodium-devel libcurl-devel
|
||||||
|
autoconf automake libtool wget python3 vim-common"
|
||||||
|
|
||||||
|
pkgs_core_arch="base-devel cmake git pkg-config
|
||||||
|
mesa libx11 libxcursor libxrandr libxinerama libxi
|
||||||
|
libxkbcommon wayland libsodium curl
|
||||||
|
autoconf automake libtool wget python xxd"
|
||||||
|
|
||||||
|
pkgs_core_macos="bash cmake python xxd"
|
||||||
|
|
||||||
|
# Windows cross-compile (from Linux)
|
||||||
|
pkgs_win_debian="mingw-w64 zip"
|
||||||
|
pkgs_win_fedora="mingw64-gcc mingw64-gcc-c++ zip"
|
||||||
|
pkgs_win_arch="mingw-w64-gcc zip"
|
||||||
|
|
||||||
|
# macOS cross-compile helpers (osxcross is separate)
|
||||||
|
pkgs_mac_debian="genisoimage icnsutils"
|
||||||
|
pkgs_mac_fedora="genisoimage"
|
||||||
|
pkgs_mac_arch="cdrtools"
|
||||||
|
|
||||||
|
# ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
has_cmd() { command -v "$1" &>/dev/null; }
|
||||||
|
|
||||||
|
# Install packages for the detected distro
|
||||||
|
install_pkgs() {
|
||||||
|
local pkgs="$1"
|
||||||
|
local desc="$2"
|
||||||
|
|
||||||
|
if $CHECK_ONLY; then
|
||||||
|
warn "Would install ($desc): $pkgs"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Installing $desc packages..."
|
||||||
|
case "$PKG" in
|
||||||
|
apt) sudo apt-get update -qq && sudo apt-get install -y $pkgs ;;
|
||||||
|
dnf) sudo dnf install -y $pkgs ;;
|
||||||
|
pacman)
|
||||||
|
# Try pacman first; fall back to AUR helper for packages not in official repos
|
||||||
|
if ! sudo pacman -S --needed --noconfirm $pkgs 2>/dev/null; then
|
||||||
|
if command -v yay &>/dev/null; then
|
||||||
|
yay -S --needed --noconfirm $pkgs
|
||||||
|
elif command -v paru &>/dev/null; then
|
||||||
|
paru -S --needed --noconfirm $pkgs
|
||||||
|
else
|
||||||
|
err "pacman failed and no AUR helper (yay/paru) found"
|
||||||
|
echo "Some packages may be in the AUR. Install yay or paru, then retry."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
zypper) sudo zypper install -y $pkgs ;;
|
||||||
|
brew) brew install $pkgs ;;
|
||||||
|
*) err "No supported package manager found for $DISTRO"
|
||||||
|
echo "Please install manually: $pkgs"
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Select the right package list variable
|
||||||
|
get_pkgs() {
|
||||||
|
local category="$1" # core, win, mac
|
||||||
|
local var="pkgs_${category}_${DISTRO}"
|
||||||
|
echo "${!var:-}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Check individual tools ──────────────────────────────────────────────────
|
||||||
|
MISSING=0
|
||||||
|
|
||||||
|
check_tool() {
|
||||||
|
local cmd="$1"
|
||||||
|
local label="${2:-$1}"
|
||||||
|
if has_cmd "$cmd"; then
|
||||||
|
ok "$label"
|
||||||
|
else
|
||||||
|
miss "$label (not found: $cmd)"
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_file() {
|
||||||
|
local path="$1"
|
||||||
|
local label="$2"
|
||||||
|
if [[ -f "$path" ]]; then
|
||||||
|
ok "$label"
|
||||||
|
else
|
||||||
|
miss "$label ($path)"
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_dir() {
|
||||||
|
local path="$1"
|
||||||
|
local label="$2"
|
||||||
|
if [[ -d "$path" ]]; then
|
||||||
|
ok "$label"
|
||||||
|
else
|
||||||
|
miss "$label ($path)"
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════════
|
||||||
|
# MAIN
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════════
|
||||||
|
echo -e "${BOLD}DragonX Wallet — Development Setup${NC}"
|
||||||
|
echo "═══════════════════════════════════"
|
||||||
|
|
||||||
|
detect_os
|
||||||
|
info "Detected: $OS / $DISTRO (package manager: ${PKG:-none})"
|
||||||
|
|
||||||
|
# ── 1. Core build dependencies ──────────────────────────────────────────────
|
||||||
|
header "Core Build Dependencies"
|
||||||
|
|
||||||
|
core_pkgs="$(get_pkgs core)"
|
||||||
|
if [[ -z "$core_pkgs" ]]; then
|
||||||
|
warn "No package list for $DISTRO — check README for manual instructions"
|
||||||
|
else
|
||||||
|
# Check if key tools are already present
|
||||||
|
NEED_CORE=false
|
||||||
|
has_cmd cmake && has_cmd g++ && has_cmd pkg-config && has_cmd python3 && has_cmd xxd || NEED_CORE=true
|
||||||
|
|
||||||
|
if $NEED_CORE; then
|
||||||
|
install_pkgs "$core_pkgs" "core build"
|
||||||
|
else
|
||||||
|
ok "Core tools already installed (cmake, g++, pkg-config)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_tool cmake "cmake"
|
||||||
|
check_tool g++ "g++ (C++ compiler)"
|
||||||
|
check_tool git "git"
|
||||||
|
check_tool make "make"
|
||||||
|
check_tool python3 "python3 (theme expansion)"
|
||||||
|
check_tool xxd "xxd (embedded language headers)"
|
||||||
|
|
||||||
|
# ── 2. libsodium ────────────────────────────────────────────────────────────
|
||||||
|
header "libsodium"
|
||||||
|
|
||||||
|
SODIUM_OK=false
|
||||||
|
# Check system libsodium
|
||||||
|
if pkg-config --exists libsodium 2>/dev/null; then
|
||||||
|
ok "libsodium (system, $(pkg-config --modversion libsodium))"
|
||||||
|
SODIUM_OK=true
|
||||||
|
elif [[ -f "$PROJECT_DIR/libs/libsodium/lib/libsodium.a" ]]; then
|
||||||
|
ok "libsodium (local build)"
|
||||||
|
SODIUM_OK=true
|
||||||
|
else
|
||||||
|
miss "libsodium not found"
|
||||||
|
if ! $CHECK_ONLY; then
|
||||||
|
info "Building libsodium from source..."
|
||||||
|
"$PROJECT_DIR/scripts/fetch-libsodium.sh" && SODIUM_OK=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 3. Windows cross-compile (optional) ─────────────────────────────────────
|
||||||
|
header "Windows Cross-Compile"
|
||||||
|
|
||||||
|
if $SETUP_WIN; then
|
||||||
|
# Only touch apt / update-alternatives (which need sudo) when the toolchain is missing. If it is
|
||||||
|
# already installed, skip them so `./setup.sh --win` can run WITHOUT sudo — important because the
|
||||||
|
# daemon cross-compile that follows should run as the invoking user. Running the whole setup under
|
||||||
|
# sudo leaves root-owned build artifacts under external/dragonx, which then break `make clean` on
|
||||||
|
# a later non-sudo build (stale objects get relinked -> the mingw link failure recurs).
|
||||||
|
if has_cmd x86_64-w64-mingw32-g++-posix || has_cmd x86_64-w64-mingw32-g++; then
|
||||||
|
ok "Windows cross-compile toolchain already present — skipping apt install"
|
||||||
|
else
|
||||||
|
win_pkgs="$(get_pkgs win)"
|
||||||
|
if [[ -n "$win_pkgs" ]]; then
|
||||||
|
install_pkgs "$win_pkgs" "Windows cross-compile"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set posix thread model if available
|
||||||
|
if has_cmd update-alternatives && [[ "$PKG" == "apt" ]]; then
|
||||||
|
if ! $CHECK_ONLY; then
|
||||||
|
sudo update-alternatives --set x86_64-w64-mingw32-gcc \
|
||||||
|
/usr/bin/x86_64-w64-mingw32-gcc-posix 2>/dev/null || true
|
||||||
|
sudo update-alternatives --set x86_64-w64-mingw32-g++ \
|
||||||
|
/usr/bin/x86_64-w64-mingw32-g++-posix 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fetch libsodium for Windows
|
||||||
|
if [[ ! -f "$PROJECT_DIR/libs/libsodium-win/lib/libsodium.a" ]]; then
|
||||||
|
if ! $CHECK_ONLY; then
|
||||||
|
info "Building libsodium for Windows target..."
|
||||||
|
"$PROJECT_DIR/scripts/fetch-libsodium.sh" --win
|
||||||
|
else
|
||||||
|
miss "libsodium-win (not built yet)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ok "libsodium-win"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if has_cmd x86_64-w64-mingw32-g++-posix || has_cmd x86_64-w64-mingw32-g++; then
|
||||||
|
ok "mingw-w64 ($(x86_64-w64-mingw32-g++-posix --version 2>/dev/null | head -1 || x86_64-w64-mingw32-g++ --version 2>/dev/null | head -1))"
|
||||||
|
else
|
||||||
|
if $SETUP_WIN; then
|
||||||
|
miss "mingw-w64"
|
||||||
|
else
|
||||||
|
skip "mingw-w64 (use --win to install)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 4. macOS cross-compile (optional) ───────────────────────────────────────
|
||||||
|
header "macOS Cross-Compile"
|
||||||
|
|
||||||
|
if $SETUP_MAC; then
|
||||||
|
mac_pkgs="$(get_pkgs mac)"
|
||||||
|
if [[ -n "$mac_pkgs" ]]; then
|
||||||
|
install_pkgs "$mac_pkgs" "macOS cross-compile helpers"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fetch libsodium for macOS
|
||||||
|
if [[ ! -f "$PROJECT_DIR/libs/libsodium-mac/lib/libsodium.a" ]]; then
|
||||||
|
if ! $CHECK_ONLY; then
|
||||||
|
# Requires osxcross — skip gracefully if not available
|
||||||
|
if [[ -d "$PROJECT_DIR/external/osxcross/target" ]] || [[ -d "${OSXCROSS:-}/target" ]]; then
|
||||||
|
info "Building libsodium for macOS target..."
|
||||||
|
"$PROJECT_DIR/scripts/fetch-libsodium.sh" --mac || warn "libsodium-mac build failed"
|
||||||
|
else
|
||||||
|
skip "libsodium-mac (requires osxcross — see README)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
miss "libsodium-mac (not built yet)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ok "libsodium-mac"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$PROJECT_DIR/external/osxcross/target" ]] || [[ -d "${OSXCROSS:-}/target" ]]; then
|
||||||
|
ok "osxcross"
|
||||||
|
else
|
||||||
|
if $SETUP_MAC; then
|
||||||
|
miss "osxcross (must be set up manually — see README)"
|
||||||
|
else
|
||||||
|
skip "osxcross (use --mac to set up macOS deps)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 5. Sapling parameters ───────────────────────────────────────────────────
|
||||||
|
header "Sapling Parameters"
|
||||||
|
|
||||||
|
SAPLING_DIR=""
|
||||||
|
for d in "$HOME/.zcash-params" "$HOME/.hush-params"; do
|
||||||
|
if [[ -f "$d/sapling-spend.params" && -f "$d/sapling-output.params" ]]; then
|
||||||
|
SAPLING_DIR="$d"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Also check project-local locations
|
||||||
|
if [[ -z "$SAPLING_DIR" ]]; then
|
||||||
|
for d in "$PROJECT_DIR/prebuilt-binaries/dragonxd-linux" "$PROJECT_DIR/prebuilt-binaries/dragonxd-win"; do
|
||||||
|
if [[ -f "$d/sapling-spend.params" && -f "$d/sapling-output.params" ]]; then
|
||||||
|
SAPLING_DIR="$d"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$SAPLING_DIR" ]]; then
|
||||||
|
ok "sapling-spend.params ($(du -h "$SAPLING_DIR/sapling-spend.params" | cut -f1))"
|
||||||
|
ok "sapling-output.params ($(du -h "$SAPLING_DIR/sapling-output.params" | cut -f1))"
|
||||||
|
elif $SETUP_SAPLING; then
|
||||||
|
if ! $CHECK_ONLY; then
|
||||||
|
info "Downloading Sapling parameters (~51 MB)..."
|
||||||
|
PARAMS_DIR="$HOME/.zcash-params"
|
||||||
|
mkdir -p "$PARAMS_DIR"
|
||||||
|
|
||||||
|
SPEND_URL="https://z.cash/downloads/sapling-spend.params"
|
||||||
|
OUTPUT_URL="https://z.cash/downloads/sapling-output.params"
|
||||||
|
# Consensus-critical MPC parameters with fixed, well-known SHA-256 (identical across every
|
||||||
|
# Zcash-family node; also pinned in scripts/build-lite-backend-artifact.sh). z.cash is
|
||||||
|
# plain HTTPS with no signature, so verify the digest and refuse a tampered/corrupt file.
|
||||||
|
SPEND_SHA256="8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13"
|
||||||
|
OUTPUT_SHA256="2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4"
|
||||||
|
|
||||||
|
if curl -fSL -o "$PARAMS_DIR/sapling-spend.params" "$SPEND_URL" \
|
||||||
|
&& echo "${SPEND_SHA256} $PARAMS_DIR/sapling-spend.params" | sha256sum -c --status; then
|
||||||
|
ok "Downloaded + verified sapling-spend.params"
|
||||||
|
else
|
||||||
|
rm -f "$PARAMS_DIR/sapling-spend.params"
|
||||||
|
err "sapling-spend.params download or SHA-256 verification failed — not installed"
|
||||||
|
fi
|
||||||
|
if curl -fSL -o "$PARAMS_DIR/sapling-output.params" "$OUTPUT_URL" \
|
||||||
|
&& echo "${OUTPUT_SHA256} $PARAMS_DIR/sapling-output.params" | sha256sum -c --status; then
|
||||||
|
ok "Downloaded + verified sapling-output.params"
|
||||||
|
else
|
||||||
|
rm -f "$PARAMS_DIR/sapling-output.params"
|
||||||
|
err "sapling-output.params download or SHA-256 verification failed — not installed"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
skip "Sapling params not found (use --sapling to download, or they'll be extracted at runtime from embedded builds)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 6. DragonX daemon ────────────────────────────────────────────────────────
|
||||||
|
header "DragonX Daemon"
|
||||||
|
|
||||||
|
DRAGONX_SRC="$PROJECT_DIR/external/dragonx"
|
||||||
|
DRAGONXD_LINUX="$PROJECT_DIR/prebuilt-binaries/dragonxd-linux"
|
||||||
|
DRAGONXD_WIN="$PROJECT_DIR/prebuilt-binaries/dragonxd-win"
|
||||||
|
DRAGONXD_MAC="$PROJECT_DIR/prebuilt-binaries/dragonxd-mac"
|
||||||
|
|
||||||
|
DAEMON_BINS="dragonxd dragonx-cli dragonx-tx hushd hush-arrakis-chain hush-cli hush-tx"
|
||||||
|
DAEMON_DATA="asmap.dat sapling-spend.params sapling-output.params"
|
||||||
|
|
||||||
|
# Helper: clone / update dragonx source
|
||||||
|
clone_dragonx_if_needed() {
|
||||||
|
if [[ ! -d "$DRAGONX_SRC" ]]; then
|
||||||
|
info "Cloning dragonx..."
|
||||||
|
git clone https://git.dragonx.is/DragonX/dragonx.git "$DRAGONX_SRC"
|
||||||
|
else
|
||||||
|
ok "dragonx source already present"
|
||||||
|
info "Switching to dragonx branch and pulling latest..."
|
||||||
|
(cd "$DRAGONX_SRC" && git checkout dragonx 2>/dev/null && git pull --ff-only 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper: copy data files (asmap, sapling params) into a destination dir
|
||||||
|
copy_daemon_data() {
|
||||||
|
local dest="$1"
|
||||||
|
for p in "$DRAGONX_SRC/asmap.dat" "$DRAGONX_SRC/contrib/asmap/asmap.dat"; do
|
||||||
|
if [[ -f "$p" ]]; then
|
||||||
|
cp "$p" "$dest/asmap.dat"
|
||||||
|
ok " Installed asmap.dat"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for p in sapling-spend.params sapling-output.params; do
|
||||||
|
if [[ -f "$DRAGONX_SRC/$p" ]]; then
|
||||||
|
cp "$DRAGONX_SRC/$p" "$dest/"
|
||||||
|
ok " Installed $p"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Stale-daemon guard ───────────────────────────────────────────────────────
|
||||||
|
# A prebuilt daemon binary is only rebuilt on its platform's flag (--win/--mac),
|
||||||
|
# and build.sh merely BUNDLES whatever binary already exists — so a daemon left
|
||||||
|
# over from an old source revision silently ships in the wallet (e.g. the Network
|
||||||
|
# tab once reported v1.0.1 while the source was v1.0.2). These helpers compare the
|
||||||
|
# version baked into a prebuilt binary against the dragonx source and flag drift.
|
||||||
|
STALE_DAEMON=0
|
||||||
|
|
||||||
|
# MAJOR.MINOR.REVISION from the checked-out dragonx source (empty if unavailable).
|
||||||
|
dragonx_source_version() {
|
||||||
|
local hdr="$DRAGONX_SRC/src/clientversion.h"
|
||||||
|
[[ -f "$hdr" ]] || return 1
|
||||||
|
local maj min rev
|
||||||
|
maj=$(awk '/#define[ \t]+CLIENT_VERSION_MAJOR/{print $3; exit}' "$hdr")
|
||||||
|
min=$(awk '/#define[ \t]+CLIENT_VERSION_MINOR/{print $3; exit}' "$hdr")
|
||||||
|
rev=$(awk '/#define[ \t]+CLIENT_VERSION_REVISION/{print $3; exit}' "$hdr")
|
||||||
|
[[ -n "$maj" && -n "$min" && -n "$rev" ]] || return 1
|
||||||
|
printf '%s.%s.%s' "$maj" "$min" "$rev"
|
||||||
|
}
|
||||||
|
|
||||||
|
# vX.Y.Z baked into a built daemon binary (the daemon embeds "vX.Y.Z-<githash>").
|
||||||
|
# Uses grep -a so no `strings`/binutils dependency is required.
|
||||||
|
dragonx_binary_version() {
|
||||||
|
local bin="$1"
|
||||||
|
[[ -f "$bin" ]] || return 1
|
||||||
|
LC_ALL=C grep -aoE 'v[0-9]+\.[0-9]+\.[0-9]+-[0-9a-f]{6,}' "$bin" 2>/dev/null \
|
||||||
|
| head -1 | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compare a prebuilt daemon against the source; warn (and set STALE_DAEMON) on drift.
|
||||||
|
# $1 = label, $2 = binary path, $3 = rebuild flag(s) (e.g. "--win", "" for Linux)
|
||||||
|
daemon_version_guard() {
|
||||||
|
local label="$1" bin="$2" rebuild_hint="$3"
|
||||||
|
[[ -f "$bin" ]] || return 0
|
||||||
|
local src bv
|
||||||
|
src=$(dragonx_source_version) || return 0 # no source checked out → can't compare
|
||||||
|
bv=$(dragonx_binary_version "$bin")
|
||||||
|
[[ -n "$bv" ]] || return 0 # couldn't read the binary's version
|
||||||
|
if [[ "$bv" == "$src" ]]; then
|
||||||
|
ok " $label daemon is v$bv (matches dragonx source)"
|
||||||
|
else
|
||||||
|
warn " $label daemon is v$bv but dragonx source is v$src — STALE"
|
||||||
|
warn " rebuild so the wallet ships the current daemon: ./setup.sh${rebuild_hint:+ $rebuild_hint}"
|
||||||
|
STALE_DAEMON=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Linux daemon ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Skip Linux daemon build if only cross-compile targets were requested
|
||||||
|
# and we already have Linux binaries (avoids contaminating the build tree)
|
||||||
|
SKIP_LINUX_DAEMON=false
|
||||||
|
if ($SETUP_WIN || $SETUP_MAC) && [[ -f "$DRAGONXD_LINUX/dragonxd" || -f "$DRAGONXD_LINUX/hushd" ]]; then
|
||||||
|
SKIP_LINUX_DAEMON=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean previous prebuilt daemon binaries so we always rebuild
|
||||||
|
if ! $CHECK_ONLY && ! $SKIP_LINUX_DAEMON; then
|
||||||
|
for f in $DAEMON_BINS $DAEMON_DATA; do
|
||||||
|
rm -f "$DRAGONXD_LINUX/$f" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $CHECK_ONLY; then
|
||||||
|
if [[ -f "$DRAGONXD_LINUX/dragonxd" ]] || [[ -f "$DRAGONXD_LINUX/hushd" ]]; then
|
||||||
|
ok "dragonxd daemon (Linux) present"
|
||||||
|
daemon_version_guard "Linux" "$DRAGONXD_LINUX/dragonxd" ""
|
||||||
|
else
|
||||||
|
miss "dragonxd daemon (Linux) not built"
|
||||||
|
fi
|
||||||
|
elif $SKIP_LINUX_DAEMON; then
|
||||||
|
skip "dragonxd (Linux) — skipped, binaries already present (cross-compile only)"
|
||||||
|
daemon_version_guard "Linux" "$DRAGONXD_LINUX/dragonxd" ""
|
||||||
|
else
|
||||||
|
clone_dragonx_if_needed
|
||||||
|
|
||||||
|
info "Building dragonx daemon for Linux (this may take a while)..."
|
||||||
|
(
|
||||||
|
cd "$DRAGONX_SRC"
|
||||||
|
bash build.sh -j"$NPROC"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy binaries to prebuilt-binaries
|
||||||
|
mkdir -p "$DRAGONXD_LINUX"
|
||||||
|
local_found=0
|
||||||
|
for f in dragonxd dragonx-cli dragonx-tx hushd hush-arrakis-chain hush-cli hush-tx; do
|
||||||
|
if [[ -f "$DRAGONX_SRC/src/$f" ]]; then
|
||||||
|
cp "$DRAGONX_SRC/src/$f" "$DRAGONXD_LINUX/"
|
||||||
|
chmod +x "$DRAGONXD_LINUX/$f"
|
||||||
|
ok " Installed $f"
|
||||||
|
local_found=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
copy_daemon_data "$DRAGONXD_LINUX"
|
||||||
|
|
||||||
|
if [[ $local_found -eq 0 ]]; then
|
||||||
|
err "DragonX daemon (Linux) build failed — no binaries found"
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
else
|
||||||
|
ok "DragonX daemon built and installed to prebuilt-binaries/dragonxd-linux/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Windows daemon (cross-compile, only with --win or --all) ─────────────────
|
||||||
|
|
||||||
|
if ! $SETUP_WIN; then
|
||||||
|
skip "dragonxd (Windows) — use --win to cross-compile"
|
||||||
|
daemon_version_guard "Windows" "$DRAGONXD_WIN/dragonxd.exe" "--win"
|
||||||
|
elif $CHECK_ONLY; then
|
||||||
|
if [[ -f "$DRAGONXD_WIN/dragonxd.exe" ]] || [[ -f "$DRAGONXD_WIN/hushd.exe" ]]; then
|
||||||
|
ok "dragonxd daemon (Windows) present"
|
||||||
|
daemon_version_guard "Windows" "$DRAGONXD_WIN/dragonxd.exe" "--win"
|
||||||
|
else
|
||||||
|
miss "dragonxd daemon (Windows) not built"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
clone_dragonx_if_needed
|
||||||
|
|
||||||
|
# Clean previous Windows prebuilt binaries
|
||||||
|
if ! $CHECK_ONLY; then
|
||||||
|
rm -f "$DRAGONXD_WIN"/*.exe "$DRAGONXD_WIN"/*.bat 2>/dev/null || true
|
||||||
|
for f in $DAEMON_DATA; do rm -f "$DRAGONXD_WIN/$f" 2>/dev/null || true; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Building dragonx daemon for Windows (cross-compile, this may take a long time)..."
|
||||||
|
(
|
||||||
|
cd "$DRAGONX_SRC"
|
||||||
|
bash build.sh --win-release -j"$NPROC"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy binaries from release directory
|
||||||
|
mkdir -p "$DRAGONXD_WIN"
|
||||||
|
local_found=0
|
||||||
|
# Find the release subdirectory (e.g. release/dragonx-1.0.0-win64/)
|
||||||
|
WIN_RELEASE_DIR=$(find "$DRAGONX_SRC/release" -maxdepth 1 -type d -name '*win64*' 2>/dev/null | head -1)
|
||||||
|
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||||
|
if [[ -n "$WIN_RELEASE_DIR" ]] && [[ -f "$WIN_RELEASE_DIR/$f" ]]; then
|
||||||
|
cp "$WIN_RELEASE_DIR/$f" "$DRAGONXD_WIN/"
|
||||||
|
ok " Installed $f"
|
||||||
|
local_found=1
|
||||||
|
elif [[ -f "$DRAGONX_SRC/src/$f" ]]; then
|
||||||
|
cp "$DRAGONX_SRC/src/$f" "$DRAGONXD_WIN/"
|
||||||
|
ok " Installed $f (from src/)"
|
||||||
|
local_found=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# .bat launchers
|
||||||
|
for f in dragonxd.bat dragonx-cli.bat bootstrap-dragonx.bat; do
|
||||||
|
if [[ -n "$WIN_RELEASE_DIR" ]] && [[ -f "$WIN_RELEASE_DIR/$f" ]]; then
|
||||||
|
cp "$WIN_RELEASE_DIR/$f" "$DRAGONXD_WIN/"
|
||||||
|
ok " Installed $f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
copy_daemon_data "$DRAGONXD_WIN"
|
||||||
|
|
||||||
|
if [[ $local_found -eq 0 ]]; then
|
||||||
|
err "DragonX daemon (Windows) build failed — no binaries found"
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
else
|
||||||
|
ok "DragonX daemon (Windows) built and installed to prebuilt-binaries/dragonxd-win/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── macOS daemon (only with --mac or --all) ──────────────────────────────────
|
||||||
|
|
||||||
|
if ! $SETUP_MAC; then
|
||||||
|
skip "dragonxd (macOS) — use --mac to cross-compile"
|
||||||
|
daemon_version_guard "macOS" "$DRAGONXD_MAC/dragonxd" "--mac"
|
||||||
|
elif $CHECK_ONLY; then
|
||||||
|
if [[ -f "$DRAGONXD_MAC/dragonxd" ]] || [[ -f "$DRAGONXD_MAC/hushd" ]]; then
|
||||||
|
ok "dragonxd daemon (macOS) present"
|
||||||
|
daemon_version_guard "macOS" "$DRAGONXD_MAC/dragonxd" "--mac"
|
||||||
|
else
|
||||||
|
miss "dragonxd daemon (macOS) not built"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
clone_dragonx_if_needed
|
||||||
|
|
||||||
|
# Clean previous macOS prebuilt binaries
|
||||||
|
if ! $CHECK_ONLY; then
|
||||||
|
for f in $DAEMON_BINS $DAEMON_DATA; do
|
||||||
|
rm -f "$DRAGONXD_MAC/$f" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# macOS build requires either native macOS or osxcross
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
info "Building dragonx daemon for macOS (native)..."
|
||||||
|
(
|
||||||
|
cd "$DRAGONX_SRC"
|
||||||
|
bash build.sh -j"$NPROC"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
info "Building dragonx daemon for macOS (requires osxcross)..."
|
||||||
|
OSXCROSS=""
|
||||||
|
for d in "$PROJECT_DIR/external/osxcross" "$HOME/osxcross" "/opt/osxcross"; do
|
||||||
|
if [[ -d "$d/target/bin" ]]; then
|
||||||
|
OSXCROSS="$d"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -z "$OSXCROSS" ]]; then
|
||||||
|
warn "osxcross not found — skipping macOS daemon build"
|
||||||
|
warn "Install osxcross to external/osxcross to enable macOS cross-compilation"
|
||||||
|
else
|
||||||
|
info "Using osxcross at $OSXCROSS"
|
||||||
|
(
|
||||||
|
cd "$DRAGONX_SRC"
|
||||||
|
export PATH="$OSXCROSS/target/bin:$PATH"
|
||||||
|
bash util/build-mac-cross.sh 2>/dev/null || bash util/build-mac.sh
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy binaries
|
||||||
|
mkdir -p "$DRAGONXD_MAC"
|
||||||
|
local_found=0
|
||||||
|
for f in dragonxd dragonx-cli dragonx-tx hushd hush-arrakis-chain hush-cli hush-tx; do
|
||||||
|
if [[ -f "$DRAGONX_SRC/src/$f" ]]; then
|
||||||
|
cp "$DRAGONX_SRC/src/$f" "$DRAGONXD_MAC/"
|
||||||
|
chmod +x "$DRAGONXD_MAC/$f"
|
||||||
|
ok " Installed $f"
|
||||||
|
local_found=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
copy_daemon_data "$DRAGONXD_MAC"
|
||||||
|
|
||||||
|
if [[ $local_found -eq 0 ]]; then
|
||||||
|
warn "DragonX daemon (macOS) — no binaries found (osxcross may be missing)"
|
||||||
|
else
|
||||||
|
ok "DragonX daemon (macOS) built and installed to prebuilt-binaries/dragonxd-mac/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prominent reminder if any prebuilt daemon drifted from the source — these are bundled verbatim
|
||||||
|
# by build.sh, so a stale binary ships in the wallet (and shows an old version in the Network tab).
|
||||||
|
if [[ "$STALE_DAEMON" -eq 1 ]]; then
|
||||||
|
warn "One or more prebuilt daemons are OLDER than the dragonx source (see above)."
|
||||||
|
warn "build.sh bundles them as-is, so rebuild the stale platform(s) before releasing:"
|
||||||
|
warn " Linux: ./setup.sh · Windows: ./setup.sh --win · macOS: ./setup.sh --mac"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 7. drg-xmrig (mining binary) ────────────────────────────────────────────
|
||||||
|
header "drg-xmrig Mining Binary"
|
||||||
|
|
||||||
|
XMRIG_SRC="$PROJECT_DIR/external/drg-xmrig"
|
||||||
|
# Output dir bundled by build.sh (Linux zip, AppImage, Windows embed, mac .app)
|
||||||
|
# and scripts/legacy/build-windows.sh — keep this path in sync with those.
|
||||||
|
XMRIG_PREBUILT="$PROJECT_DIR/prebuilt-binaries/drg-xmrig"
|
||||||
|
|
||||||
|
# Clean previous prebuilt xmrig binaries so we always rebuild
|
||||||
|
# Only clean the binary for the platform(s) we are actually building,
|
||||||
|
# otherwise a plain ./setup.sh deletes xmrig.exe without rebuilding it.
|
||||||
|
if ! $CHECK_ONLY; then
|
||||||
|
rm -f "$XMRIG_PREBUILT/xmrig" 2>/dev/null || true
|
||||||
|
if $SETUP_WIN; then
|
||||||
|
rm -f "$XMRIG_PREBUILT/xmrig.exe" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Helper: clone drg-xmrig if not present
|
||||||
|
clone_xmrig_if_needed() {
|
||||||
|
if [[ ! -d "$XMRIG_SRC" ]]; then
|
||||||
|
info "Cloning drg-xmrig..."
|
||||||
|
git clone https://git.dragonx.is/DragonX/drg-xmrig.git "$XMRIG_SRC"
|
||||||
|
else
|
||||||
|
ok "drg-xmrig source already present"
|
||||||
|
info "Pulling latest drg-xmrig..."
|
||||||
|
(cd "$XMRIG_SRC" && git pull --ff-only 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Linux xmrig ─────────────────────────────────────────────────────────────
|
||||||
|
XMRIG_LINUX="$XMRIG_PREBUILT/xmrig"
|
||||||
|
|
||||||
|
if $CHECK_ONLY; then
|
||||||
|
if [[ -f "$XMRIG_LINUX" ]]; then
|
||||||
|
ok "xmrig (Linux) ($(du -h "$XMRIG_LINUX" | cut -f1))"
|
||||||
|
else
|
||||||
|
miss "xmrig (Linux) not built (run setup without --check to build)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
clone_xmrig_if_needed
|
||||||
|
|
||||||
|
# Clean previous build
|
||||||
|
rm -rf "$XMRIG_SRC/build"
|
||||||
|
|
||||||
|
# Build dependencies (libuv, hwloc, openssl)
|
||||||
|
info "Building drg-xmrig dependencies (libuv, hwloc, openssl)..."
|
||||||
|
(
|
||||||
|
cd "$XMRIG_SRC/scripts"
|
||||||
|
sh build_deps.sh
|
||||||
|
)
|
||||||
|
ok "drg-xmrig dependencies built"
|
||||||
|
|
||||||
|
# Build xmrig
|
||||||
|
info "Building drg-xmrig (Linux)..."
|
||||||
|
mkdir -p "$XMRIG_SRC/build"
|
||||||
|
(
|
||||||
|
cd "$XMRIG_SRC/build"
|
||||||
|
cmake .. \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DWITH_OPENCL=OFF \
|
||||||
|
-DWITH_CUDA=OFF \
|
||||||
|
-DWITH_HWLOC=ON \
|
||||||
|
-DCMAKE_PREFIX_PATH="$XMRIG_SRC/scripts/deps"
|
||||||
|
make -j"$NPROC"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy binary to prebuilt-binaries
|
||||||
|
mkdir -p "$XMRIG_PREBUILT"
|
||||||
|
if [[ -f "$XMRIG_SRC/build/xmrig" ]]; then
|
||||||
|
cp "$XMRIG_SRC/build/xmrig" "$XMRIG_LINUX"
|
||||||
|
ok "xmrig (Linux) built and installed to prebuilt-binaries/drg-xmrig/"
|
||||||
|
else
|
||||||
|
err "xmrig (Linux) build failed — binary not found"
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Windows xmrig (cross-compile, only with --win or --all) ─────────────────
|
||||||
|
XMRIG_WIN="$XMRIG_PREBUILT/xmrig.exe"
|
||||||
|
|
||||||
|
if ! $SETUP_WIN; then
|
||||||
|
skip "xmrig.exe (Windows) — use --win to cross-compile"
|
||||||
|
elif $CHECK_ONLY; then
|
||||||
|
if [[ -f "$XMRIG_WIN" ]]; then
|
||||||
|
ok "xmrig.exe (Windows) ($(du -h "$XMRIG_WIN" | cut -f1))"
|
||||||
|
else
|
||||||
|
miss "xmrig.exe (Windows) not built (run setup --win without --check to build)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
clone_xmrig_if_needed
|
||||||
|
|
||||||
|
# Clean previous Windows build
|
||||||
|
rm -rf "$XMRIG_SRC/build-windows"
|
||||||
|
|
||||||
|
info "Building drg-xmrig (Windows cross-compile)..."
|
||||||
|
(
|
||||||
|
cd "$XMRIG_SRC/scripts"
|
||||||
|
bash build_windows.sh
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy binary to prebuilt-binaries
|
||||||
|
mkdir -p "$XMRIG_PREBUILT"
|
||||||
|
if [[ -f "$XMRIG_SRC/build-windows/xmrig.exe" ]]; then
|
||||||
|
cp "$XMRIG_SRC/build-windows/xmrig.exe" "$XMRIG_WIN"
|
||||||
|
ok "xmrig.exe (Windows) built and installed to prebuilt-binaries/drg-xmrig/"
|
||||||
|
else
|
||||||
|
err "xmrig.exe (Windows) build failed — binary not found"
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 8. Binary directories ───────────────────────────────────────────────────
|
||||||
|
header "Binary Directories"
|
||||||
|
|
||||||
|
for platform in dragonxd-linux dragonxd-win dragonxd-mac drg-xmrig; do
|
||||||
|
dir="$PROJECT_DIR/prebuilt-binaries/$platform"
|
||||||
|
if [[ -d "$dir" ]]; then
|
||||||
|
# Count actual files (not .gitkeep)
|
||||||
|
count=$(find "$dir" -maxdepth 1 -type f ! -name '.gitkeep' | wc -l)
|
||||||
|
if [[ $count -gt 0 ]]; then
|
||||||
|
ok "prebuilt-binaries/$platform/ ($count files)"
|
||||||
|
else
|
||||||
|
skip "prebuilt-binaries/$platform/ (empty — place binaries here)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! $CHECK_ONLY; then
|
||||||
|
mkdir -p "$dir"
|
||||||
|
touch "$dir/.gitkeep"
|
||||||
|
ok "Created prebuilt-binaries/$platform/"
|
||||||
|
else
|
||||||
|
miss "prebuilt-binaries/$platform/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Summary ──────────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "═══════════════════════════════════════════"
|
||||||
|
if [[ $MISSING -eq 0 ]]; then
|
||||||
|
echo -e "${GREEN}${BOLD} Setup complete — ready to build!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " Quick start:"
|
||||||
|
echo " ./build.sh # Dev build"
|
||||||
|
echo " ./build.sh --linux-release # Linux release + AppImage"
|
||||||
|
echo " ./build.sh --win-release # Windows cross-compile"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}${BOLD} $MISSING item(s) still need attention${NC}"
|
||||||
|
if $CHECK_ONLY; then
|
||||||
|
echo ""
|
||||||
|
echo " Run without --check to install automatically:"
|
||||||
|
echo " ./scripts/setup.sh"
|
||||||
|
echo " ./scripts/setup.sh --all # Include cross-compile + Sapling"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "═══════════════════════════════════════════"
|
||||||