diff --git a/.gitignore b/.gitignore index e092aedb..aee93bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ regtest/ venv/ pytest.log TODO +**/target diff --git a/gui/Cargo.lock b/gui/Cargo.lock new file mode 100644 index 00000000..c5a56114 --- /dev/null +++ b/gui/Cargo.lock @@ -0,0 +1,2977 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24606928a235e73cdef55a0c909719cadd72fce573e5713d58cb2952d8f5794c" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "ash" +version = "0.34.0+1.2.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f780da53d0063880d45554306489f09dd8d1bda47688b4a57bc579119356df" +dependencies = [ + "libloading", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64-compat" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +dependencies = [ + "byteorder", +] + +[[package]] +name = "bech32" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitcoin" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a41df6ad9642c5c15ae312dd3d074de38fd3eb7cc87ad4ce10f90292a83fe4d" +dependencies = [ + "base64-compat", + "bech32", + "bitcoin_hashes", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "bytemuck" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd2f4180c5721da6335cc9e9061cce522b87a35e51cc57636d28d22a9863c80" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "calloop" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" +dependencies = [ + "log", + "nix 0.22.3", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "checked_int_cast" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "clipboard-win" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "clipboard_macos" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145a7f9e9b89453bc0a5e32d166456405d389cea5b578f57f1274b1397588a95" +dependencies = [ + "objc", + "objc-foundation", + "objc_id", +] + +[[package]] +name = "clipboard_wayland" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f6364a9f7a66f2ac1a1a098aa1c7f6b686f2496c6ac5e5c0d773445df912747" +dependencies = [ + "smithay-clipboard", +] + +[[package]] +name = "clipboard_x11" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983a7010836ecd04dde2c6d27a0cb56ec5d21572177e782bdcb24a600124e921" +dependencies = [ + "thiserror", + "x11rb", +] + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation 0.9.3", + "core-graphics 0.22.3", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.9.3", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys 0.8.3", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation 0.9.3", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation 0.9.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "crossbeam-utils", + "memoffset", + "once_cell", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "d3d12" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" +dependencies = [ + "bitflags", + "libloading", + "winapi", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-url" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +dependencies = [ + "matches", +] + +[[package]] +name = "deflate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +dependencies = [ + "adler32", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "euclid" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52c2ef4a78da0ba68fbe1fd920627411096d2ac478f7f4c9f3a54ba6705bade" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fern" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a" +dependencies = [ + "log", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float_next_after" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc612c5837986b7104a87a0df74a5460931f1c5274be12f8d0f40aa2f30d632" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fontdb" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d66551cc28351f0bc6a73da86459ee7765caaad03ce284f2dc36472dbf539cd" +dependencies = [ + "log", + "memmap2 0.3.1", + "ttf-parser 0.12.3", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "glam" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579160312273c954cc51bd440f059dde741029ac8daf8c84fece76cb77f62c15" +dependencies = [ + "version_check", +] + +[[package]] +name = "glow" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glyph_brush" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69c65dd1f1fbb6209aa00f78636e436ad0a55b7d8e5de886d00720dcad9c6e2" +dependencies = [ + "glyph_brush_draw_cache", + "glyph_brush_layout", + "log", + "ordered-float", + "rustc-hash", + "twox-hash", +] + +[[package]] +name = "glyph_brush_draw_cache" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6010675390f6889e09a21e2c8b575b3ee25667ea8237a8d59423f73cb8c28610" +dependencies = [ + "ab_glyph", + "crossbeam-channel", + "crossbeam-deque", + "linked-hash-map", + "rayon", + "rustc-hash", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc32c2334f00ca5ac3695c5009ae35da21da8c62d255b5b96d56e2597a637a38" +dependencies = [ + "ab_glyph", + "approx", + "xi-unicode", +] + +[[package]] +name = "gpu-alloc" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" +dependencies = [ + "bitflags", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown 0.11.2", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown 0.11.2", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "iced" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6025abe6b1056c9b5adad79c484c5fd8b7012e5230f3b0439a1294ade7ded7bf" +dependencies = [ + "iced_core", + "iced_futures", + "iced_graphics", + "iced_native", + "iced_pure", + "iced_wgpu", + "iced_winit", + "thiserror", +] + +[[package]] +name = "iced_core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf9133ceb345ec640047d5597fb8aa88e9cf74ce2d0277a9a62e2d6ed4a8148" +dependencies = [ + "bitflags", + "wasm-timer", +] + +[[package]] +name = "iced_futures" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d13241d5ed32846bbcffaf60e27e7ceebb60cf16d791ff00d582f0d4d1b07b" +dependencies = [ + "futures", + "log", + "tokio", + "wasm-bindgen-futures", + "wasm-timer", +] + +[[package]] +name = "iced_graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2adcf703fc326e0985ea99c75f1b73e718560a7b220d57ec6478417ccb2f463f" +dependencies = [ + "bytemuck", + "glam", + "iced_native", + "iced_pure", + "iced_style", + "lyon", + "qrcode", + "raw-window-handle 0.4.3", + "thiserror", +] + +[[package]] +name = "iced_native" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca174d4693a5daa2ffcae38d5c28cf0dbd54bd8fc19848f28392cd52624751a" +dependencies = [ + "iced_core", + "iced_futures", + "iced_style", + "num-traits", + "twox-hash", + "unicode-segmentation", +] + +[[package]] +name = "iced_pure" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80aeaecadfd6832c2c787cbdfd357adc256a51c55d68142d852037451e72f393" +dependencies = [ + "iced_native", + "iced_style", + "num-traits", +] + +[[package]] +name = "iced_style" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90028c94ab62c13cd3b6fb1499a593a51510d4729c5b4e8e60705b2b28c6bc2" +dependencies = [ + "iced_core", +] + +[[package]] +name = "iced_wgpu" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc44ca209f77bd855f035d2e86e50e66332f55fb60d9fb67eeb09eae9d9de2e" +dependencies = [ + "bitflags", + "bytemuck", + "futures", + "glyph_brush", + "guillotiere", + "iced_graphics", + "iced_native", + "kamadak-exif", + "log", + "raw-window-handle 0.4.3", + "resvg", + "tiny-skia", + "usvg", + "wgpu", + "wgpu_glyph", +] + +[[package]] +name = "iced_winit" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72011b895e439e2ebad8f545720e3e97c7368ecfc47a23cbfeaa9508a98af90c" +dependencies = [ + "iced_futures", + "iced_graphics", + "iced_native", + "log", + "thiserror", + "web-sys", + "winapi", + "window_clipboard", + "winit", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "inplace_it" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67f0347836f3f6362c1e7efdadde2b1c4b4556d211310b70631bae7eb692070b" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8423b78fc94d12ef1a4a9d13c348c9a78766dda0cc18817adf0faf77e670c8" +dependencies = [ + "base64-compat", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "kamadak-exif" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70494964492bf8e491eb3951c5d70c9627eb7100ede6cc56d748b9a3f302cfb6" +dependencies = [ + "mutate_once", +] + +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading", +] + +[[package]] +name = "kurbo" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb348d766edbac91ba1eb83020d96f4f8867924d194393083c15a51f185e6a82" +dependencies = [ + "arrayvec 0.5.2", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "lyon" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf0510ed5e3e2fb80f3db2061ef5ca92d87bfda1a624bb1eacf3bd50226e4cbb" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8037f716541ba0d84d3de05c0069f8068baf73990d55980558b84d944c8a244a" +dependencies = [ + "lyon_path", + "sid", +] + +[[package]] +name = "lyon_geom" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d89ccbdafd83d259403e22061be27bccc3254bba65cdc5303250c4227c8c8e" +dependencies = [ + "arrayvec 0.5.2", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0a59fdf767ca0d887aa61d1b48d4bbf6a124c1a45503593f7d38ab945bfbc0" +dependencies = [ + "lyon_geom", +] + +[[package]] +name = "lyon_tessellation" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7230e08dd0638048e46f387f255dbe7a7344a3e6705beab53242b5af25635760" +dependencies = [ + "float_next_after", + "lyon_path", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0514f491f4cc03632ab399ee01e2c1c1b12d3e1cf2d667c1ff5f87d6dcd2084" +dependencies = [ + "bitflags", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minisafe" +version = "0.0.1" +source = "git+https://github.com/revault/minisafe?branch=master#b548451292f4f497a5a837244b6432e473e6cd55" +dependencies = [ + "backtrace", + "dirs", + "fern", + "jsonrpc", + "libc", + "log", + "miniscript", + "rusqlite", + "serde", + "serde_json", + "toml", +] + +[[package]] +name = "minisafe-gui" +version = "0.0.1" +dependencies = [ + "backtrace", + "bitcoin", + "chrono", + "dirs", + "fern", + "iced", + "iced_native", + "log", + "minisafe", + "serde", + "serde_json", + "tokio", + "toml", + "uds_windows", +] + +[[package]] +name = "miniscript" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e292b58407dfbf1384e5aca8428d3b0f2eaa09d24cb17088f6db0b7ca31194a" +dependencies = [ + "bitcoin", + "serde", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "mutate_once" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" + +[[package]] +name = "naga" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3012f2dbcc79e8e0b5825a4836a7106a75dd9b2fe42c528163be0f572538c705" +dependencies = [ + "bit-set", + "bitflags", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-glue" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71bee8ea72d685477e28bd004cfe1bf99c754d688cd78cad139eae4089484d4" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "ordered-float" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bcbab4bfea7a59c2c0fe47211a1ac4e3e96bea6eb446d704f310bc5c732ae2" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1e509cfe7a12db2a90bfa057dfcdbc55a347f5da677c506b53dd099cfec9d" +dependencies = [ + "ttf-parser 0.15.2", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pico-args" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "png" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc2916cde080c1876ff40292a396541241fe0072ef928cd76582e9ea5d60d2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f61dcf0b917cd75d4521d7343d1ffff3d1583054133c9b5cbea3375c703c40d" + +[[package]] +name = "qrcode" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f" +dependencies = [ + "checked_int_cast", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" + +[[package]] +name = "raw-window-handle" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28f55143d0548dad60bb4fbdc835a3d7ac6acc3324506450c5fdd6e42903a76" +dependencies = [ + "libc", + "raw-window-handle 0.4.3", +] + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", +] + +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "rctree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae028b272a6e99d9f8260ceefa3caa09300a8d6c8d2b2001316474bc52122e9" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534cfe58d6a18cc17120fbf4635d53d14691c1fe4d951064df9bd326178d7d5a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + +[[package]] +name = "resvg" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608c6e8aa6fb2c13bc06e4184d7c7b2cc1b7c138f88a539da8be55c3c033d7f4" +dependencies = [ + "jpeg-decoder", + "log", + "pico-args", + "png", + "rgb", + "svgfilters", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rgb" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b221de559e4a29df3b957eec92bc0de6bc8eaf6ca9cfed43e5e1d67ff65a34" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "roxmltree" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "rusqlite" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustybuzz" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44561062e583c4873162861261f16fd1d85fe927c4904d71329a4fe43dc355ef" +dependencies = [ + "bitflags", + "bytemuck", + "smallvec", + "ttf-parser 0.12.3", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "safe_arch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "secp256k1" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" +dependencies = [ + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sid" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5ac56c121948b4879bba9e519852c211bcdd8f014efff766441deff0b91bdb" +dependencies = [ + "num-traits", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "smithay-client-toolkit" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" +dependencies = [ + "bitflags", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2 0.3.1", + "nix 0.22.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +dependencies = [ + "bitflags", + "dlib", + "lazy_static", + "log", + "memmap2 0.5.5", + "nix 0.24.2", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit 0.16.0", + "wayland-client", +] + +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags", + "num-traits", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "svg_fmt" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" + +[[package]] +name = "svgfilters" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639abcebc15fdc2df179f37d6f5463d660c1c79cd552c12343a4600827a04bce" +dependencies = [ + "float-cmp", + "rgb", +] + +[[package]] +name = "svgtypes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc802f68b144cdf4d8ff21301f9a7863e837c627fde46537e29c05e8a18c85c1" +dependencies = [ + "siphasher", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tiny-skia" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d049bfef0eaa2521e75d9ffb5ce86ad54480932ae19b85f78bec6f52c4d30d78" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "bytemuck", + "cfg-if 1.0.0", + "png", + "safe_arch", +] + +[[package]] +name = "tokio" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +dependencies = [ + "autocfg", + "libc", + "mio", + "num_cpus", + "once_cell", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "ttf-parser" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" + +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if 0.1.10", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "uds_windows" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b22bf5f590ee6a2892e134e18482e63361319cc62ed108eb236803284cefec" +dependencies = [ + "tempdir", + "winapi", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-general-category" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07547e3ee45e28326cc23faac56d44f58f16ab23e413db526debce3b0bfd2742" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "unicode-script" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dd944fd05f2f0b5c674917aea8a4df6af84f2d8de3fe8d988b95d28fb8fb09" + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "usvg" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4058e0bd091a56f905e6963e40776ce6880b271275f0b493bff951433e303071" +dependencies = [ + "base64", + "data-url", + "flate2", + "float-cmp", + "fontdb", + "kurbo", + "log", + "pico-args", + "rctree", + "roxmltree", + "rustybuzz", + "simplecss", + "siphasher", + "svgtypes", + "ttf-parser 0.12.3", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-client" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.22.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +dependencies = [ + "nix 0.22.3", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +dependencies = [ + "nix 0.22.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97cd781ff044d6d697b632a2e212032c2e957d1afaa21dbf58069cbb8f78567" +dependencies = [ + "arrayvec 0.7.2", + "js-sys", + "log", + "naga", + "parking_lot", + "raw-window-handle 0.4.3", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4688c000eb841ca55f7b35db659b78d6e1cd77d7caf8fb929f4e181f754047d" +dependencies = [ + "arrayvec 0.7.2", + "bitflags", + "cfg_aliases", + "codespan-reporting", + "copyless", + "fxhash", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle 0.4.3", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d684ea6a34974a2fc19f1dfd183d11a62e22d75c4f187a574bb1224df8e056c2" +dependencies = [ + "arrayvec 0.7.2", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "js-sys", + "khronos-egl", + "libloading", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle 0.4.3", + "renderdoc-sys", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549533d9e1cdd4b4cda7718d33ff500fc4c34b5467b71d76b547ae0324f3b2a2" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wgpu_glyph" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8134edb15ae465caf308125646c9e98bdef7398cdefc69227ac77a5eb795e7fe" +dependencies = [ + "bytemuck", + "glyph_brush", + "log", + "wgpu", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window_clipboard" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47d7fb4df5cd1fea61e5ee3841380f54359bac814e227d8f72709f4f193f8cf" +dependencies = [ + "clipboard-win", + "clipboard_macos", + "clipboard_wayland", + "clipboard_x11", + "raw-window-handle 0.3.4", + "thiserror", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winit" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" +dependencies = [ + "bitflags", + "cocoa", + "core-foundation 0.9.3", + "core-graphics 0.22.3", + "core-video-sys", + "dispatch", + "instant", + "lazy_static", + "libc", + "log", + "mio", + "ndk", + "ndk-glue", + "ndk-sys", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle 0.4.3", + "smithay-client-toolkit 0.15.4", + "wasm-bindgen", + "wayland-client", + "wayland-protocols", + "web-sys", + "winapi", + "x11-dl", +] + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e99be55648b3ae2a52342f9a870c0e138709a3493261ce9b469afe6e4df6d8a" +dependencies = [ + "gethostname", + "nix 0.22.3", + "winapi", + "winapi-wsapoll", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xi-unicode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "xmlparser" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" diff --git a/gui/Cargo.toml b/gui/Cargo.toml new file mode 100644 index 00000000..d5c0d4cc --- /dev/null +++ b/gui/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "minisafe-gui" +version = "0.0.1" +readme = "README.md" +description = "Minisafe GUI" +repository = "https://github.com/revault/minisafe" +license = "BSD-3-Clause" +authors = ["Edouard Paris ", "Daniela Brozzoni "] +edition = "2018" +resolver = "2" + +[[bin]] +name = "minisafe-gui" +path = "src/main.rs" + +[dependencies] +bitcoin = { version = "0.27", features = ["base64", "use-serde"] } +minisafe = { git = "https://github.com/revault/minisafe", branch = "master", default-features = false} +backtrace = "0.3" + +iced = { version = "0.4", default-features= false, features = ["tokio", "wgpu", "svg", "qr_code", "pure"] } +iced_native = "0.5" + +tokio = {version = "1.9.0", features = ["signal"]} +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +# Logging stuff +log = "0.4" +fern = "0.6" + +dirs = "3.0.1" +toml = "0.5" + +chrono = "0.4" + +[target.'cfg(windows)'.dependencies] +uds_windows = "0.1.5" + +[dev-dependencies] +tokio = {version = "1.9.0", features = ["rt", "macros"]} diff --git a/gui/README.md b/gui/README.md index e69de29b..de0cb3df 100644 --- a/gui/README.md +++ b/gui/README.md @@ -0,0 +1,38 @@ +# minisafe-gui + +Revault GUI is an user graphical interface written in rust for the +[Minisafe daemon](https://github.com/revault/minisafe). + +## Dependencies + +- `fontconfig` (On Debian/Ubuntu `apt install libfontconfig1-dev`) +- [`pkg-config`](https://www.freedesktop.org/wiki/Software/pkg-config/) (On Debian/Ubuntu `apt install pkg-config`) +- [`libxkbcommon`](https://xkbcommon.org/) for the dummy signer (On Debian/Ubuntu `apt install libxkbcommon-dev`) +- Vulkan drivers (On Debian/Ubuntu `apt install mesa-vulkan-drivers libvulkan-dev`) +- `libudev-dev` (On Debian/Ubuntu `apt install libudev-dev`) + +We are striving to remove dependencies, especially the 3D ones. + +## Usage + +`minisafe-gui --datadir --` + +The default `datadir` is the default `minisafed` `datadir` (`~/.minisafe` +for linux) and the default `network` is the bitcoin mainnet. + +If no argument is provided, the GUI checks in the default `datadir` +the configuration file for the bitcoin mainnet. + +If the provided `datadir` is empty or does not have the configuration +file for the targeted `network`, the GUI starts with the installer mode. + +Instead of using `--datadir` and `--`, a direct path to +the GUI configuration file can be provided with `--conf`. + +After start up, The GUI will connect to the running minisafed. +A command starting minisafed is launched if no connection is made. + +## Troubleshooting + +- If you encounter layout issue on `X11`, try to start the GUI with + `WINIT_X11_SCALE_FACTOR` manually set to 1 diff --git a/gui/src/app/config.rs b/gui/src/app/config.rs new file mode 100644 index 00000000..bbfc1970 --- /dev/null +++ b/gui/src/app/config.rs @@ -0,0 +1,92 @@ +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Config { + /// Path to minisafed configuration file. + pub minisafed_config_path: PathBuf, + /// log level, can be "info", "debug", "trace". + pub log_level: Option, + /// Use iced debug feature if true. + pub debug: Option, +} + +pub const DEFAULT_FILE_NAME: &str = "gui.toml"; + +impl Config { + pub fn new(minisafed_config_path: PathBuf) -> Self { + Self { + minisafed_config_path, + log_level: None, + debug: None, + } + } + + pub fn from_file(path: &Path) -> Result { + let config = std::fs::read(path) + .map_err(|e| match e.kind() { + std::io::ErrorKind::NotFound => ConfigError::NotFound, + _ => ConfigError::ReadingFile(format!("Reading configuration file: {}", e)), + }) + .and_then(|file_content| { + toml::from_slice::(&file_content).map_err(|e| { + ConfigError::ReadingFile(format!("Parsing configuration file: {}", e)) + }) + })?; + Ok(config) + } + + pub fn default_path() -> Result { + let mut datadir = default_datadir().map_err(|_| { + ConfigError::Unexpected("Could not locate the default datadir directory.".to_owned()) + })?; + datadir.push(DEFAULT_FILE_NAME); + Ok(datadir) + } +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum ConfigError { + NotFound, + ReadingFile(String), + Unexpected(String), +} + +impl std::fmt::Display for ConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::NotFound => write!(f, "Config file not found"), + Self::ReadingFile(e) => write!(f, "Error while reading file: {}", e), + Self::Unexpected(e) => write!(f, "Unexpected error: {}", e), + } + } +} + +impl std::error::Error for ConfigError {} + +// Get the absolute path to the minisafe configuration folder. +/// +/// This a "minisafe" directory in the XDG standard configuration directory for all OSes but +/// Linux-based ones, for which it's `~/.minisafe`. +/// Rationale: we want to have the database, RPC socket, etc.. in the same folder as the +/// configuration file but for Linux the XDG specify a data directory (`~/.local/share/`) different +/// from the configuration one (`~/.config/`). +pub fn default_datadir() -> Result> { + #[cfg(target_os = "linux")] + let configs_dir = dirs::home_dir(); + + #[cfg(not(target_os = "linux"))] + let configs_dir = dirs::config_dir(); + + if let Some(mut path) = configs_dir { + #[cfg(target_os = "linux")] + path.push(".minisafe"); + + #[cfg(not(target_os = "linux"))] + path.push("Minisafe"); + + return Ok(path); + } + + Err("Failed to get default data directory".into()) +} diff --git a/gui/src/app/context.rs b/gui/src/app/context.rs new file mode 100644 index 00000000..6e1a0572 --- /dev/null +++ b/gui/src/app/context.rs @@ -0,0 +1,75 @@ +use std::fs::OpenOptions; +use std::io::Write; +use std::sync::Arc; + +use minisafe::config::Config as DaemonConfig; + +use crate::{ + app::{config, error::Error, menu::Menu}, + conversion::Converter, + daemon::Daemon, +}; + +/// Context is an object passing general information +/// and service clients through the application components. +pub struct Context { + pub config: ConfigContext, + pub blockheight: i32, + pub daemon: Arc, + pub converter: Converter, + pub menu: Menu, + pub managers_threshold: usize, +} + +impl Context { + pub fn new( + config: ConfigContext, + daemon: Arc, + converter: Converter, + menu: Menu, + ) -> Self { + Self { + config, + blockheight: 0, + daemon, + converter, + menu, + managers_threshold: 0, + } + } + + pub fn network(&self) -> bitcoin::Network { + self.config.daemon.bitcoin_config.network + } + + pub fn load_daemon_config(&mut self, cfg: DaemonConfig) -> Result<(), Error> { + loop { + if let Some(daemon) = Arc::get_mut(&mut self.daemon) { + daemon.load_config(cfg)?; + break; + } + } + + let mut daemon_config_file = OpenOptions::new() + .write(true) + .open(&self.config.gui.minisafed_config_path) + .map_err(|e| Error::Config(e.to_string()))?; + + let content = + toml::to_string(&self.config.daemon).map_err(|e| Error::Config(e.to_string()))?; + + daemon_config_file + .write_all(content.as_bytes()) + .map_err(|e| { + log::warn!("failed to write to file: {:?}", e); + Error::Config(e.to_string()) + })?; + + Ok(()) + } +} + +pub struct ConfigContext { + pub daemon: DaemonConfig, + pub gui: config::Config, +} diff --git a/gui/src/app/error.rs b/gui/src/app/error.rs new file mode 100644 index 00000000..db6cab3a --- /dev/null +++ b/gui/src/app/error.rs @@ -0,0 +1,52 @@ +use crate::daemon::DaemonError; +use minisafe::config::ConfigError; +use std::convert::From; +use std::io::ErrorKind; + +#[derive(Debug, Clone)] +pub enum Error { + Config(String), + Daemon(DaemonError), + Unexpected(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Config(e) => write!(f, "{}", e), + Self::Daemon(e) => match e { + DaemonError::Unexpected(e) => write!(f, "{}", e), + DaemonError::NoAnswer => write!(f, "Daemon did not answer"), + DaemonError::Transport(Some(ErrorKind::ConnectionRefused), _) => { + write!(f, "Failed to connect to daemon") + } + DaemonError::Transport(kind, e) => { + if let Some(k) = kind { + write!(f, "{} [{:?}]", e, k) + } else { + write!(f, "{}", e) + } + } + DaemonError::Start(e) => { + write!(f, "Failed to start daemon: {}", e) + } + DaemonError::Rpc(code, e) => { + write!(f, "[{:?}] {}", code, e) + } + }, + Self::Unexpected(e) => write!(f, "Unexpected error: {}", e), + } + } +} + +impl From for Error { + fn from(error: ConfigError) -> Self { + Error::Config(error.to_string()) + } +} + +impl From for Error { + fn from(error: DaemonError) -> Self { + Error::Daemon(error) + } +} diff --git a/gui/src/app/menu.rs b/gui/src/app/menu.rs new file mode 100644 index 00000000..2ff7eb67 --- /dev/null +++ b/gui/src/app/menu.rs @@ -0,0 +1,4 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Menu { + Home, +} diff --git a/gui/src/app/message.rs b/gui/src/app/message.rs new file mode 100644 index 00000000..cffc5097 --- /dev/null +++ b/gui/src/app/message.rs @@ -0,0 +1,14 @@ +use minisafe::config::Config as DaemonConfig; + +use crate::app::{error::Error, menu::Menu}; + +#[derive(Debug, Clone)] +pub enum Message { + Reload, + Tick, + Event(iced_native::Event), + Clipboard(String), + Menu(Menu), + LoadDaemonConfig(Box), + DaemonConfigLoaded(Result<(), Error>), +} diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs new file mode 100644 index 00000000..1bd655e2 --- /dev/null +++ b/gui/src/app/mod.rs @@ -0,0 +1,96 @@ +pub mod config; +pub mod context; +pub mod menu; +pub mod message; +pub mod state; + +mod error; + +use std::sync::Arc; +use std::time::Duration; + +use iced::pure::Element; +use iced::{clipboard, time, Command, Subscription}; +use iced_native::{window, Event}; + +pub use config::Config; +pub use message::Message; + +use state::{Home, State}; + +use crate::app::context::Context; + +pub struct App { + should_exit: bool, + state: Box, + context: Context, +} + +pub fn new_state(_context: &Context) -> Box { + Home {}.into() +} + +impl App { + pub fn new(context: Context) -> (App, Command) { + let state = new_state(&context); + let cmd = state.load(&context); + ( + Self { + should_exit: false, + state, + context, + }, + cmd, + ) + } + + pub fn subscription(&self) -> Subscription { + Subscription::batch(vec![ + iced_native::subscription::events().map(Message::Event), + time::every(Duration::from_secs(30)).map(|_| Message::Tick), + self.state.subscription(), + ]) + } + + pub fn should_exit(&self) -> bool { + self.should_exit + } + + pub fn stop(&mut self) { + log::info!("Close requested"); + if !self.context.daemon.is_external() { + log::info!("Stopping internal daemon..."); + if let Some(d) = Arc::get_mut(&mut self.context.daemon) { + d.stop().expect("Daemon is internal"); + log::info!("Internal daemon stopped"); + self.should_exit = true; + } + } else { + self.should_exit = true; + } + } + + pub fn update(&mut self, message: Message) -> Command { + match message { + Message::LoadDaemonConfig(cfg) => { + let res = self.context.load_daemon_config(*cfg); + self.update(Message::DaemonConfigLoaded(res)) + } + Message::Menu(menu) => { + self.context.menu = menu; + self.state = new_state(&self.context); + self.state.load(&self.context) + } + Message::Clipboard(text) => clipboard::write(text), + Message::Event(Event::Window(window::Event::CloseRequested)) => { + self.stop(); + Command::none() + } + _ => self.state.update(&self.context, message), + } + } + + pub fn view(&self) -> Element { + self.state.view(&self.context) + } +} diff --git a/gui/src/app/state.rs b/gui/src/app/state.rs new file mode 100644 index 00000000..d2da92dd --- /dev/null +++ b/gui/src/app/state.rs @@ -0,0 +1,32 @@ +use iced::pure::{column, Element}; +use iced::{Command, Subscription}; + +use super::{context::Context, message::Message}; + +pub trait State { + fn view(&self, ctx: &Context) -> Element; + fn update(&mut self, ctx: &Context, message: Message) -> Command; + fn subscription(&self) -> Subscription { + Subscription::none() + } + fn load(&self, _ctx: &Context) -> Command { + Command::none() + } +} + +pub struct Home {} + +impl State for Home { + fn view(&self, _ctx: &Context) -> Element { + column().into() + } + fn update(&mut self, _ctx: &Context, _message: Message) -> Command { + Command::none() + } +} + +impl From for Box { + fn from(s: Home) -> Box { + Box::new(s) + } +} diff --git a/gui/src/conversion.rs b/gui/src/conversion.rs new file mode 100644 index 00000000..d4cd0faa --- /dev/null +++ b/gui/src/conversion.rs @@ -0,0 +1,43 @@ +use bitcoin::Network; + +/// Converter purpose is to give a Conversion from a given amount in satoshis according to its +/// parameters. +pub struct Converter { + pub unit: Unit, +} + +impl Converter { + pub fn new(bitcoin_network: Network) -> Self { + let unit = match bitcoin_network { + Network::Testnet => Unit::TestnetBitcoin, + Network::Bitcoin => Unit::Bitcoin, + Network::Regtest => Unit::RegtestBitcoin, + Network::Signet => Unit::SignetBitcoin, + }; + Self { unit } + } + + /// converts amount in satoshis to BTC float. + pub fn converts(&self, amount: bitcoin::Amount) -> String { + format!("{:.8}", amount.as_btc()) + } +} + +/// Unit is the bitcoin ticker according to the network used. +pub enum Unit { + TestnetBitcoin, + RegtestBitcoin, + SignetBitcoin, + Bitcoin, +} + +impl std::fmt::Display for Unit { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::TestnetBitcoin => write!(f, "tBTC"), + Self::RegtestBitcoin => write!(f, "rBTC"), + Self::SignetBitcoin => write!(f, "sBTC"), + Self::Bitcoin => write!(f, "BTC"), + } + } +} diff --git a/gui/src/daemon/client/error.rs b/gui/src/daemon/client/error.rs new file mode 100644 index 00000000..eb5865f2 --- /dev/null +++ b/gui/src/daemon/client/error.rs @@ -0,0 +1,104 @@ +// Rust JSON-RPC Library +// Written in 2015 by +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Error handling +//! +//! Some useful methods for creating Error objects +//! + +use std::io; +use std::{error, fmt}; + +use serde::{Deserialize, Serialize}; + +#[allow(dead_code)] +#[derive(Debug)] +#[allow(non_camel_case_types)] +pub enum RpcErrorCode { + // Standard errors defined by JSON-RPC 2.0 standard + /// Invalid request + JSONRPC2_INVALID_REQUEST = -32600, + /// Method not found + JSONRPC2_METHOD_NOT_FOUND = -32601, + /// Invalid parameters + JSONRPC2_INVALID_PARAMS = -32602, +} + +/// A library error +#[derive(Debug)] +pub enum Error { + /// Json error + Json(serde_json::Error), + /// IO Error + Io(io::Error), + /// Error response + Rpc(RpcError), + /// Response has neither error nor result + NoErrorOrResult, + /// Response to a request did not have the expected nonce + NonceMismatch, + /// Response to a request had a jsonrpc field other than "2.0" + VersionMismatch, +} + +impl From for Error { + fn from(e: serde_json::Error) -> Error { + Error::Json(e) + } +} + +impl From for Error { + fn from(e: io::Error) -> Error { + Error::Io(e) + } +} + +impl From for Error { + fn from(e: RpcError) -> Error { + Error::Rpc(e) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::Json(ref e) => write!(f, "JSON decode error: {}", e), + Error::Io(ref e) => write!(f, "IO error response: {}", e), + Error::Rpc(ref r) => write!(f, "RPC error response: {:?}", r), + Error::NoErrorOrResult => write!(f, "Malformed RPC response"), + Error::NonceMismatch => write!(f, "Nonce of response did not match nonce of request"), + Error::VersionMismatch => write!(f, "`jsonrpc` field set to non-\"2.0\""), + } + } +} + +impl error::Error for Error { + fn cause(&self) -> Option<&dyn error::Error> { + match *self { + Error::Json(ref e) => Some(e), + _ => None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +/// A JSONRPC error object +pub struct RpcError { + /// The integer identifier of the error + pub code: i32, + /// A string describing the error + pub message: String, + /// Additional data specific to the error + pub data: Option, +} diff --git a/gui/src/daemon/client/jsonrpc.rs b/gui/src/daemon/client/jsonrpc.rs new file mode 100644 index 00000000..ca8a4eb4 --- /dev/null +++ b/gui/src/daemon/client/jsonrpc.rs @@ -0,0 +1,255 @@ +// Rust JSON-RPC Library +// Written by +// Andrew Poelstra +// Wladimir J. van der Laan +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// +//! Client support +//! +//! Support for connecting to JSONRPC servers over UNIX socets, sending requests, +//! and parsing responses +//! + +#[cfg(windows)] +use uds_windows::UnixStream; + +#[cfg(not(windows))] +use std::os::unix::net::UnixStream; + +use std::fmt::Debug; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use std::{error, fmt, io}; + +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use serde_json::{to_writer, Deserializer}; + +use log::debug; + +/// A handle to a remote JSONRPC server +#[derive(Debug, Clone)] +pub struct JsonRPCClient { + sockpath: PathBuf, + timeout: Option, +} + +impl super::Client for JsonRPCClient { + type Error = Error; + fn request( + &self, + method: &str, + params: Option, + ) -> Result { + self.send_request(method, params) + .and_then(|res| res.into_result()) + } +} + +impl JsonRPCClient { + /// Creates a new client + pub fn new>(sockpath: P) -> JsonRPCClient { + JsonRPCClient { + sockpath: sockpath.as_ref().to_path_buf(), + timeout: None, + } + } + + /// Set an optional timeout for requests + #[allow(dead_code)] + pub fn set_timeout(&mut self, timeout: Option) { + self.timeout = timeout; + } + + /// Sends a request to a client + pub fn send_request( + &self, + method: &str, + params: Option, + ) -> Result, Error> { + // Setup connection + let mut stream = UnixStream::connect(&self.sockpath)?; + stream.set_read_timeout(self.timeout)?; + stream.set_write_timeout(self.timeout)?; + + let request = Request { + method, + params, + id: std::process::id(), + jsonrpc: "2.0", + }; + + debug!("Sending to minisafed: {:#?}", request); + + to_writer(&mut stream, &request)?; + + let response: Response = Deserializer::from_reader(&mut stream) + .into_iter() + .next() + .map_or(Err(Error::NoErrorOrResult), |res| Ok(res?))?; + if response + .jsonrpc + .as_ref() + .map_or(false, |version| version != "2.0") + { + return Err(Error::VersionMismatch); + } + + if response.id != request.id { + return Err(Error::NonceMismatch); + } + + debug!("Received from minisafed: {:#?}", response); + + Ok(response) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +/// A JSONRPC request object +pub struct Request<'f, T: Serialize> { + /// The name of the RPC call + pub method: &'f str, + /// Parameters to the RPC call + pub params: Option, + /// Identifier for this Request, which should appear in the response + pub id: u32, + /// jsonrpc field, MUST be "2.0" + pub jsonrpc: &'f str, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +/// A JSONRPC response object +pub struct Response { + /// A result if there is one, or null + pub result: Option, + /// An error if there is one, or null + pub error: Option, + /// Identifier for this Request, which should match that of the request + pub id: u32, + /// jsonrpc field, MUST be "2.0" + pub jsonrpc: Option, +} + +impl Response { + /// Extract the result from a response, consuming the response + pub fn into_result(self) -> Result { + if let Some(e) = self.error { + return Err(Error::Rpc(e)); + } + + self.result.ok_or(Error::NoErrorOrResult) + } + + /// Returns whether or not the `result` field is empty + #[allow(dead_code)] + pub fn is_none(&self) -> bool { + self.result.is_none() + } +} + +#[allow(dead_code)] +#[derive(Debug)] +#[allow(non_camel_case_types)] +pub enum RpcErrorCode { + // Standard errors defined by JSON-RPC 2.0 standard + /// Invalid request + JSONRPC2_INVALID_REQUEST = -32600, + /// Method not found + JSONRPC2_METHOD_NOT_FOUND = -32601, + /// Invalid parameters + JSONRPC2_INVALID_PARAMS = -32602, +} + +/// A library error +#[derive(Debug)] +pub enum Error { + /// Json error + Json(serde_json::Error), + /// IO Error + Io(io::Error), + /// Error response + Rpc(RpcError), + /// Response has neither error nor result + NoErrorOrResult, + /// Response to a request did not have the expected nonce + NonceMismatch, + /// Response to a request had a jsonrpc field other than "2.0" + VersionMismatch, +} + +impl From for Error { + fn from(e: serde_json::Error) -> Error { + Error::Json(e) + } +} + +impl From for Error { + fn from(e: io::Error) -> Error { + Error::Io(e) + } +} + +impl From for Error { + fn from(e: RpcError) -> Error { + Error::Rpc(e) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::Json(ref e) => write!(f, "JSON decode error: {}", e), + Error::Io(ref e) => write!(f, "IO error response: {}", e), + Error::Rpc(ref r) => write!(f, "RPC error response: {:?}", r), + Error::NoErrorOrResult => write!(f, "Malformed RPC response"), + Error::NonceMismatch => write!(f, "Nonce of response did not match nonce of request"), + Error::VersionMismatch => write!(f, "`jsonrpc` field set to non-\"2.0\""), + } + } +} + +impl error::Error for Error { + fn cause(&self) -> Option<&dyn error::Error> { + match *self { + Error::Json(ref e) => Some(e), + _ => None, + } + } +} + +impl From for super::DaemonError { + fn from(e: Error) -> super::DaemonError { + match e { + Error::Io(e) => super::DaemonError::Transport(Some(e.kind()), format!("io: {:?}", e)), + Error::Json(e) => super::DaemonError::Transport(None, format!("json decode: {}", e)), + Error::NonceMismatch => { + super::DaemonError::Transport(None, format!("transport: {}", e)) + } + Error::VersionMismatch => { + super::DaemonError::Transport(None, format!("transport: {}", e)) + } + Error::NoErrorOrResult => super::DaemonError::NoAnswer, + Error::Rpc(e) => super::DaemonError::Rpc(e.code, e.message), + } + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +/// A JSONRPC error object +pub struct RpcError { + /// The integer identifier of the error + pub code: i32, + /// A string describing the error + pub message: String, + /// Additional data specific to the error + pub data: Option, +} diff --git a/gui/src/daemon/client/mod.rs b/gui/src/daemon/client/mod.rs new file mode 100644 index 00000000..425be25e --- /dev/null +++ b/gui/src/daemon/client/mod.rs @@ -0,0 +1,61 @@ +use std::fmt::Debug; + +use log::{error, info}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +pub mod error; +pub mod jsonrpc; + +use super::{model::*, Daemon, DaemonError}; + +pub trait Client { + type Error: Into + Debug; + fn request( + &self, + method: &str, + params: Option, + ) -> Result; +} + +#[derive(Debug, Clone)] +pub struct Minisafed { + client: C, +} + +impl Minisafed { + pub fn new(client: C) -> Minisafed { + Minisafed { client } + } + + /// Generic call function for RPC calls. + fn call( + &self, + method: &str, + input: Option, + ) -> Result { + info!("{}", method); + self.client.request(method, input).map_err(|e| { + error!("method {} failed: {:?}", method, e); + e.into() + }) + } +} + +impl Daemon for Minisafed { + fn is_external(&self) -> bool { + true + } + + fn stop(&mut self) -> Result<(), DaemonError> { + let _res: serde_json::value::Value = self.call("stop", Option::::None)?; + Ok(()) + } + + fn get_info(&self) -> Result { + self.call("getinfo", Option::::None) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Request {} diff --git a/gui/src/daemon/embedded.rs b/gui/src/daemon/embedded.rs new file mode 100644 index 00000000..6850942b --- /dev/null +++ b/gui/src/daemon/embedded.rs @@ -0,0 +1,61 @@ +use std::sync::Mutex; + +use super::{model::*, Daemon, DaemonError}; +use minisafe::{config::Config, DaemonHandle}; + +#[derive(Default)] +pub struct EmbeddedDaemon { + handle: Option>, +} + +impl EmbeddedDaemon { + pub fn start(&mut self, config: Config) -> Result<(), DaemonError> { + let handle = + DaemonHandle::start_default(config).map_err(|e| DaemonError::Start(e.to_string()))?; + self.handle = Some(Mutex::new(handle)); + Ok(()) + } +} + +impl std::fmt::Debug for EmbeddedDaemon { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DaemonHandle").finish() + } +} + +impl Daemon for EmbeddedDaemon { + fn is_external(&self) -> bool { + false + } + + fn load_config(&mut self, cfg: Config) -> Result<(), DaemonError> { + if self.handle.is_none() { + return Ok(()); + } + + let next = + DaemonHandle::start_default(cfg).map_err(|e| DaemonError::Start(e.to_string()))?; + self.handle.take().unwrap().into_inner().unwrap().shutdown(); + self.handle = Some(Mutex::new(next)); + Ok(()) + } + + fn stop(&mut self) -> Result<(), DaemonError> { + if let Some(h) = self.handle.take() { + let handle = h.into_inner().unwrap(); + handle.shutdown(); + } + Ok(()) + } + + fn get_info(&self) -> Result { + Ok(self + .handle + .as_ref() + .ok_or(DaemonError::NoAnswer)? + .lock() + .unwrap() + .control + .get_info()) + } +} diff --git a/gui/src/daemon/mod.rs b/gui/src/daemon/mod.rs new file mode 100644 index 00000000..094067bf --- /dev/null +++ b/gui/src/daemon/mod.rs @@ -0,0 +1,46 @@ +pub mod client; +pub mod embedded; +pub mod model; + +use std::fmt::Debug; +use std::io::ErrorKind; + +use minisafe::config::Config; + +#[derive(Debug, Clone)] +pub enum DaemonError { + /// Something was wrong with the request. + Rpc(i32, String), + /// Something was wrong with the communication. + Transport(Option, String), + /// Something unexpected happened. + Unexpected(String), + /// No response. + NoAnswer, + // Error at start up. + Start(String), +} + +impl std::fmt::Display for DaemonError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Rpc(code, e) => write!(f, "Daemon error rpc call: [{:?}] {}", code, e), + Self::NoAnswer => write!(f, "Daemon returned no answer"), + Self::Transport(kind, e) => write!(f, "Daemon transport error: [{:?}] {}", kind, e), + Self::Unexpected(e) => write!(f, "Daemon unexpected error: {}", e), + Self::Start(e) => write!(f, "Daemon did not start: {}", e), + } + } +} + +pub trait Daemon: Debug { + fn is_external(&self) -> bool; + + fn load_config(&mut self, _cfg: Config) -> Result<(), DaemonError> { + Ok(()) + } + + fn stop(&mut self) -> Result<(), DaemonError>; + + fn get_info(&self) -> Result; +} diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs new file mode 100644 index 00000000..65dbc0f0 --- /dev/null +++ b/gui/src/daemon/model.rs @@ -0,0 +1 @@ +pub use minisafe::commands::GetInfoResult; diff --git a/gui/src/installer/config.rs b/gui/src/installer/config.rs new file mode 100644 index 00000000..b0aa9f05 --- /dev/null +++ b/gui/src/installer/config.rs @@ -0,0 +1,80 @@ +use std::convert::TryFrom; + +use bitcoin::Network; +use minisafe::{ + config::{BitcoinConfig, BitcoindConfig, Config as MinisafeConfig}, + miniscript::{Descriptor, DescriptorPublicKey}, +}; + +use serde::Serialize; +use std::{net::SocketAddr, path::PathBuf, time::Duration}; + +/// Static informations we require to operate +/// fields with default values are not present, see minisafe::config. +#[derive(Debug, Clone, Serialize)] +pub struct Config { + #[serde(serialize_with = "serialize_option_to_string")] + pub main_descriptor: Option>, + pub bitcoin_config: BitcoinConfig, + /// Everything we need to know to talk to bitcoind + pub bitcoind_config: BitcoindConfig, + /// An optional custom data directory + pub data_dir: Option, +} + +impl Config { + pub const DEFAULT_FILE_NAME: &'static str = "daemon.toml"; + /// returns a minisafed config with empty or dummy values + pub fn new() -> Config { + Self { + main_descriptor: None, + bitcoin_config: BitcoinConfig { + network: Network::Bitcoin, + poll_interval_secs: Duration::from_secs(30), + }, + bitcoind_config: BitcoindConfig { + cookie_path: PathBuf::new(), + addr: SocketAddr::new( + std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), + 8080, + ), + }, + data_dir: None, + } + } +} + +impl Default for Config { + fn default() -> Self { + Self::new() + } +} + +pub fn serialize_option_to_string( + field: &Option, + s: S, +) -> Result { + match field { + Some(field) => s.serialize_str(&field.to_string()), + None => s.serialize_none(), + } +} + +impl TryFrom for MinisafeConfig { + type Error = &'static str; + + fn try_from(cfg: Config) -> Result { + if cfg.main_descriptor.is_none() { + return Err("config does not have a main Descriptor"); + } + Ok(MinisafeConfig { + #[cfg(unix)] + daemon: false, + log_level: log::LevelFilter::Info, + main_descriptor: cfg.main_descriptor.unwrap(), + data_dir: cfg.data_dir, + bitcoin_config: cfg.bitcoin_config, + bitcoind_config: Some(cfg.bitcoind_config), + }) + } +} diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs new file mode 100644 index 00000000..79461c3a --- /dev/null +++ b/gui/src/installer/message.rs @@ -0,0 +1,30 @@ +use std::path::PathBuf; + +use super::Error; + +#[derive(Debug, Clone)] +pub enum Message { + Event(iced_native::Event), + Exit(PathBuf), + Next, + Previous, + Install, + Installed(Result), + Network(bitcoin::Network), + DefineBitcoind(DefineBitcoind), + DefineDescriptor(DefineDescriptor), +} + +#[derive(Debug, Clone)] +pub enum DefineBitcoind { + CookiePathEdited(String), + AddressEdited(String), +} + +#[derive(Debug, Clone)] +pub enum DefineDescriptor { + ImportDescriptor(String), + UserXpubEdited(String), + HeirXpubEdited(String), + SequenceEdited(String), +} diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs new file mode 100644 index 00000000..260c288c --- /dev/null +++ b/gui/src/installer/mod.rs @@ -0,0 +1,209 @@ +mod config; +mod message; +mod step; +mod view; + +use iced::pure::Element; +use iced::{Command, Subscription}; +use iced_native::{window, Event}; + +use std::convert::TryInto; +use std::io::Write; +use std::path::PathBuf; + +use crate::{app::config as gui_config, installer::config::Config as DaemonConfig}; + +pub use message::Message; +use step::{Context, DefineBitcoind, DefineDescriptor, Final, Step, Welcome}; + +pub struct Installer { + should_exit: bool, + current: usize, + steps: Vec>, + + /// Context is data passed through each step. + context: Context, + config: DaemonConfig, +} + +impl Installer { + fn next(&mut self) { + if self.current < self.steps.len() - 1 { + self.current += 1; + } + } + + fn previous(&mut self) { + if self.current > 0 { + self.current -= 1; + } + } + + pub fn new( + destination_path: PathBuf, + network: bitcoin::Network, + ) -> (Installer, Command) { + let mut config = DaemonConfig::new(); + config.data_dir = Some(destination_path); + ( + Installer { + should_exit: false, + config, + current: 0, + steps: vec![ + Welcome::new(network).into(), + DefineDescriptor::new().into(), + DefineBitcoind::new().into(), + Final::new().into(), + ], + context: Context::new(network), + }, + Command::none(), + ) + } + + pub fn subscription(&self) -> Subscription { + iced_native::subscription::events().map(Message::Event) + } + + pub fn should_exit(&self) -> bool { + self.should_exit + } + + pub fn stop(&mut self) { + self.should_exit = true; + } + + pub fn update(&mut self, message: Message) -> Command { + match message { + Message::Next => { + let current_step = self + .steps + .get_mut(self.current) + .expect("There is always a step"); + if current_step.apply(&mut self.context, &mut self.config) { + self.next(); + // skip the step according to the current context. + while self + .steps + .get(self.current) + .expect("There is always a step") + .skip(&self.context) + { + self.next(); + } + // calculate new current_step. + let current_step = self + .steps + .get_mut(self.current) + .expect("There is always a step"); + current_step.load_context(&self.context); + } + } + Message::Previous => { + self.previous(); + } + Message::Install => { + self.steps + .get_mut(self.current) + .expect("There is always a step") + .update(message); + return Command::perform( + install(self.context.clone(), self.config.clone()), + Message::Installed, + ); + } + Message::Event(Event::Window(window::Event::CloseRequested)) => { + self.stop(); + return Command::none(); + } + _ => { + self.steps + .get_mut(self.current) + .expect("There is always a step") + .update(message); + } + }; + Command::none() + } + + pub fn view(&self) -> Element { + self.steps + .get(self.current) + .expect("There is always a step") + .view() + } +} + +pub async fn install(_ctx: Context, mut cfg: DaemonConfig) -> Result { + // Start Daemon to check correctness of installation + let daemon = + minisafe::DaemonHandle::start_default(cfg.clone().try_into().unwrap()).map_err(|e| { + Error::Unexpected(format!("Failed to start daemon with entered config: {}", e)) + })?; + daemon.shutdown(); + + cfg.data_dir = + Some(cfg.data_dir.unwrap().canonicalize().map_err(|e| { + Error::Unexpected(format!("Failed to canonicalize datadir path: {}", e)) + })?); + + let mut datadir_path = cfg.data_dir.clone().unwrap(); + datadir_path.push(cfg.bitcoin_config.network.to_string()); + + // create minisafed configuration file + let mut minisafed_config_path = datadir_path.clone(); + minisafed_config_path.push(DaemonConfig::DEFAULT_FILE_NAME); + let mut minisafed_config_file = std::fs::File::create(&minisafed_config_path) + .map_err(|e| Error::CannotCreateFile(e.to_string()))?; + + // Step needed because of ValueAfterTable error in the toml serialize implementation. + let minisafed_config = + toml::Value::try_from(&cfg).expect("daemon::Config has a proper Serialize implementation"); + + minisafed_config_file + .write_all(minisafed_config.to_string().as_bytes()) + .map_err(|e| Error::CannotWriteToFile(e.to_string()))?; + + // create minisafe GUI configuration file + let mut gui_config_path = datadir_path; + gui_config_path.push(gui_config::DEFAULT_FILE_NAME); + let mut gui_config_file = std::fs::File::create(&gui_config_path) + .map_err(|e| Error::CannotCreateFile(e.to_string()))?; + + gui_config_file + .write_all( + toml::to_string(&gui_config::Config::new( + minisafed_config_path.canonicalize().map_err(|e| { + Error::Unexpected(format!( + "Failed to canonicalize minisafed config path: {}", + e + )) + })?, + )) + .unwrap() + .as_bytes(), + ) + .map_err(|e| Error::CannotWriteToFile(e.to_string()))?; + + Ok(gui_config_path) +} + +#[derive(Debug, Clone)] +pub enum Error { + CannotCreateDatadir(String), + CannotCreateFile(String), + CannotWriteToFile(String), + Unexpected(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::CannotCreateDatadir(e) => write!(f, "Failed to create datadir: {}", e), + Self::CannotWriteToFile(e) => write!(f, "Failed to write to file: {}", e), + Self::CannotCreateFile(e) => write!(f, "Failed to create file: {}", e), + Self::Unexpected(e) => write!(f, "Unexpected: {}", e), + } + } +} diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs new file mode 100644 index 00000000..e7aaca04 --- /dev/null +++ b/gui/src/installer/step/mod.rs @@ -0,0 +1,396 @@ +use std::path::PathBuf; +use std::str::FromStr; + +use iced::pure::Element; +use minisafe::{ + descriptors::inheritance_descriptor, + miniscript::descriptor::{Descriptor, DescriptorPublicKey}, +}; + +use crate::ui::component::form; + +use crate::installer::{ + config, + message::{self, Message}, + view, +}; + +pub trait Step { + fn update(&mut self, message: Message); + fn view(&self) -> Element; + fn load_context(&mut self, _ctx: &Context) {} + fn skip(&self, _ctx: &Context) -> bool { + false + } + fn apply(&mut self, _ctx: &mut Context, _config: &mut config::Config) -> bool { + true + } +} + +#[derive(Clone)] +pub struct Context { + pub network: bitcoin::Network, +} + +impl Context { + pub fn new(network: bitcoin::Network) -> Self { + Self { network } + } +} + +impl Default for Context { + fn default() -> Self { + Self::new(bitcoin::Network::Bitcoin) + } +} + +pub struct Welcome { + network: bitcoin::Network, +} + +impl Welcome { + pub fn new(network: bitcoin::Network) -> Self { + Self { network } + } +} + +impl Step for Welcome { + fn update(&mut self, message: Message) { + if let message::Message::Network(network) = message { + self.network = network; + } + } + fn apply(&mut self, ctx: &mut Context, config: &mut config::Config) -> bool { + ctx.network = self.network; + config.bitcoin_config.network = self.network; + true + } + fn view(&self) -> Element { + view::welcome(&self.network) + } +} + +impl Default for Welcome { + fn default() -> Self { + Self::new(bitcoin::Network::Bitcoin) + } +} + +impl From for Box { + fn from(s: Welcome) -> Box { + Box::new(s) + } +} + +pub struct DefineDescriptor { + imported_descriptor: form::Value, + user_xpub: form::Value, + heir_xpub: form::Value, + sequence: form::Value, + error: Option, +} + +impl DefineDescriptor { + pub fn new() -> Self { + Self { + imported_descriptor: form::Value::default(), + user_xpub: form::Value::default(), + heir_xpub: form::Value::default(), + sequence: form::Value::default(), + error: None, + } + } +} + +impl Step for DefineDescriptor { + // form value is set as valid each time it is edited. + // Verification of the values is happening when the user click on Next button. + fn update(&mut self, message: Message) { + if let Message::DefineDescriptor(msg) = message { + match msg { + message::DefineDescriptor::ImportDescriptor(desc) => { + self.imported_descriptor.value = desc; + self.imported_descriptor.valid = true; + } + message::DefineDescriptor::UserXpubEdited(xpub) => { + self.user_xpub.value = xpub; + self.user_xpub.valid = true; + } + message::DefineDescriptor::HeirXpubEdited(xpub) => { + self.heir_xpub.value = xpub; + self.heir_xpub.valid = true; + } + message::DefineDescriptor::SequenceEdited(seq) => { + self.sequence.valid = true; + if seq.is_empty() || seq.parse::().is_ok() { + self.sequence.value = seq; + } + } + }; + }; + } + + fn apply(&mut self, _ctx: &mut Context, config: &mut config::Config) -> bool { + // descriptor forms for import or creation cannot be both empty or filled. + if self.imported_descriptor.value.is_empty() + == (self.user_xpub.value.is_empty() + || self.heir_xpub.value.is_empty() + || self.sequence.value.is_empty()) + { + if !self.user_xpub.value.is_empty() { + self.user_xpub.valid = DescriptorPublicKey::from_str(&self.user_xpub.value).is_ok(); + } + if !self.heir_xpub.value.is_empty() { + self.heir_xpub.valid = DescriptorPublicKey::from_str(&self.heir_xpub.value).is_ok(); + } + if !self.sequence.value.is_empty() { + self.sequence.valid = self.sequence.value.parse::().is_ok(); + } + if !self.imported_descriptor.value.is_empty() { + self.imported_descriptor.valid = + Descriptor::::from_str(&self.imported_descriptor.value) + .is_ok(); + } + false + } else if !self.imported_descriptor.value.is_empty() { + if let Ok(desc) = + Descriptor::::from_str(&self.imported_descriptor.value) + { + config.main_descriptor = Some(desc); + true + } else { + self.imported_descriptor.valid = false; + false + } + } else { + let user_key = DescriptorPublicKey::from_str(&self.user_xpub.value); + self.user_xpub.valid = user_key.is_ok(); + + let heir_key = DescriptorPublicKey::from_str(&self.heir_xpub.value); + self.user_xpub.valid = user_key.is_ok(); + + let sequence = self.sequence.value.parse::(); + self.sequence.valid = sequence.is_ok(); + + if !self.user_xpub.valid || !self.heir_xpub.valid || !self.sequence.valid { + return false; + } + + match inheritance_descriptor(user_key.unwrap(), heir_key.unwrap(), sequence.unwrap()) { + Ok(desc) => { + config.main_descriptor = Some(desc); + true + } + Err(e) => { + self.error = Some(e.to_string()); + false + } + } + } + } + + fn view(&self) -> Element { + view::define_descriptor( + &self.imported_descriptor, + &self.user_xpub, + &self.heir_xpub, + &self.sequence, + self.error.as_ref(), + ) + } +} + +impl Default for DefineDescriptor { + fn default() -> Self { + Self::new() + } +} + +impl From for Box { + fn from(s: DefineDescriptor) -> Box { + Box::new(s) + } +} + +pub struct DefineBitcoind { + cookie_path: form::Value, + address: form::Value, +} + +fn bitcoind_default_cookie_path(network: &bitcoin::Network) -> Option { + #[cfg(target_os = "linux")] + let configs_dir = dirs::home_dir(); + + #[cfg(not(target_os = "linux"))] + let configs_dir = dirs::config_dir(); + + if let Some(mut path) = configs_dir { + #[cfg(target_os = "linux")] + path.push(".bitcoin"); + + #[cfg(not(target_os = "linux"))] + path.push("Bitcoin"); + + match network { + bitcoin::Network::Bitcoin => { + path.push(".cookie"); + } + bitcoin::Network::Testnet => { + path.push("testnet3/.cookie"); + } + bitcoin::Network::Regtest => { + path.push("regtest/.cookie"); + } + bitcoin::Network::Signet => { + path.push("signet/.cookie"); + } + } + + return path.to_str().map(|s| s.to_string()); + } + None +} + +fn bitcoind_default_address(network: &bitcoin::Network) -> String { + match network { + bitcoin::Network::Bitcoin => "127.0.0.1:8332".to_string(), + bitcoin::Network::Testnet => "127.0.0.1:18332".to_string(), + bitcoin::Network::Regtest => "127.0.0.1:18443".to_string(), + bitcoin::Network::Signet => "127.0.0.1:38332".to_string(), + } +} + +impl DefineBitcoind { + pub fn new() -> Self { + Self { + cookie_path: form::Value::default(), + address: form::Value::default(), + } + } +} + +impl Step for DefineBitcoind { + fn load_context(&mut self, ctx: &Context) { + if self.cookie_path.value.is_empty() { + self.cookie_path.value = bitcoind_default_cookie_path(&ctx.network).unwrap_or_default() + } + if self.address.value.is_empty() { + self.address.value = bitcoind_default_address(&ctx.network); + } + } + fn update(&mut self, message: Message) { + if let Message::DefineBitcoind(msg) = message { + match msg { + message::DefineBitcoind::AddressEdited(address) => { + self.address.value = address; + self.address.valid = true; + } + message::DefineBitcoind::CookiePathEdited(path) => { + self.cookie_path.value = path; + self.address.valid = true; + } + }; + }; + } + + fn apply(&mut self, _ctx: &mut Context, config: &mut config::Config) -> bool { + match ( + PathBuf::from_str(&self.cookie_path.value), + std::net::SocketAddr::from_str(&self.address.value), + ) { + (Err(_), Ok(_)) => { + self.cookie_path.valid = false; + false + } + (Ok(_), Err(_)) => { + self.address.valid = false; + false + } + (Err(_), Err(_)) => { + self.cookie_path.valid = false; + self.address.valid = false; + false + } + (Ok(path), Ok(addr)) => { + config.bitcoind_config.cookie_path = path; + config.bitcoind_config.addr = addr; + true + } + } + } + + fn view(&self) -> Element { + view::define_bitcoin(&self.address, &self.cookie_path) + } +} + +impl Default for DefineBitcoind { + fn default() -> Self { + Self::new() + } +} + +impl From for Box { + fn from(s: DefineBitcoind) -> Box { + Box::new(s) + } +} + +pub struct Final { + generating: bool, + warning: Option, + config_path: Option, +} + +impl Final { + pub fn new() -> Self { + Self { + generating: false, + warning: None, + config_path: None, + } + } +} + +impl Step for Final { + fn update(&mut self, message: Message) { + match message { + Message::Installed(res) => { + self.generating = false; + match res { + Err(e) => { + self.config_path = None; + self.warning = Some(e.to_string()); + } + Ok(path) => self.config_path = Some(path), + } + } + Message::Install => { + self.generating = true; + self.config_path = None; + self.warning = None; + } + _ => {} + }; + } + + fn view(&self) -> Element { + view::install( + self.generating, + self.config_path.as_ref(), + self.warning.as_ref(), + ) + } +} + +impl Default for Final { + fn default() -> Self { + Self::new() + } +} + +impl From for Box { + fn from(s: Final) -> Box { + Box::new(s) + } +} diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs new file mode 100644 index 00000000..7faa997d --- /dev/null +++ b/gui/src/installer/view.rs @@ -0,0 +1,247 @@ +use iced::pure::{column, container, pick_list, row, scrollable, Element}; +use iced::{Alignment, Length}; + +use crate::ui::{ + component::{ + button, form, + text::{text, Text}, + }, + util::Collection, +}; + +use crate::installer::message::{self, Message}; + +const NETWORKS: [bitcoin::Network; 4] = [ + bitcoin::Network::Bitcoin, + bitcoin::Network::Testnet, + bitcoin::Network::Signet, + bitcoin::Network::Regtest, +]; + +pub fn welcome(network: &bitcoin::Network) -> Element { + container(container( + column() + .push(container( + pick_list(&NETWORKS[..], Some(*network), message::Message::Network).padding(10), + )) + .push( + button::primary(None, "Install") + .on_press(Message::Next) + .width(Length::Units(200)), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding(100) + .spacing(50) + .align_items(Alignment::Center), + )) + .center_y() + .center_x() + .height(Length::Fill) + .width(Length::Fill) + .into() +} + +pub fn define_descriptor<'a>( + imported_descriptor: &form::Value, + user_xpub: &form::Value, + heir_xpub: &form::Value, + sequence: &form::Value, + error: Option<&String>, +) -> Element<'a, Message> { + let col_descriptor = column() + .push(text("Descriptor:").bold()) + .push( + form::Form::new("Descriptor", imported_descriptor, |msg| { + Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(msg)) + }) + .warning("Please enter correct descriptor") + .size(20) + .padding(10), + ) + .spacing(10); + + let col_user_xpub = column() + .push(text("Your xpub:").bold()) + .push( + form::Form::new("Xpub", user_xpub, |msg| { + Message::DefineDescriptor(message::DefineDescriptor::UserXpubEdited(msg)) + }) + .warning("Please enter correct xpub") + .size(20) + .padding(10), + ) + .spacing(10); + + let col_heir_xpub = column() + .push(text("Heir xpub:").bold()) + .push( + form::Form::new("Xpub", heir_xpub, |msg| { + Message::DefineDescriptor(message::DefineDescriptor::HeirXpubEdited(msg)) + }) + .warning("Please enter correct xpub") + .size(20) + .padding(10), + ) + .spacing(10); + + let col_sequence = column() + .push(text("Number of block").bold()) + .push( + form::Form::new("Number of block", sequence, |msg| { + Message::DefineDescriptor(message::DefineDescriptor::SequenceEdited(msg)) + }) + .warning("Please enter correct block number") + .size(20) + .padding(10), + ) + .spacing(10); + + layout( + column() + .push(text("Create the descriptor").bold().size(50)) + .push( + column() + .push(col_user_xpub) + .push( + row() + .push(col_sequence.width(Length::FillPortion(1))) + .push(col_heir_xpub.width(Length::FillPortion(4))) + .spacing(20), + ) + .spacing(20), + ) + .push(text("or import it").bold().size(25)) + .push(col_descriptor) + .push( + if !imported_descriptor.value.is_empty() + && (!user_xpub.value.is_empty() + || !heir_xpub.value.is_empty() + || !sequence.value.is_empty()) + { + button::primary(None, "Next").width(Length::Units(200)) + } else { + button::primary(None, "Next") + .width(Length::Units(200)) + .on_press(Message::Next) + }, + ) + .push_maybe(error.map(|e| text(e).size(15))) + .width(Length::Fill) + .height(Length::Fill) + .padding(100) + .spacing(50) + .align_items(Alignment::Center), + ) +} + +pub fn define_bitcoin<'a>( + address: &form::Value, + cookie_path: &form::Value, +) -> Element<'a, Message> { + let col_address = column() + .push(text("Address:").bold()) + .push( + form::Form::new("Address", address, |msg| { + Message::DefineBitcoind(message::DefineBitcoind::AddressEdited(msg)) + }) + .warning("Please enter correct address") + .size(20) + .padding(10), + ) + .spacing(10); + + let col_cookie = column() + .push(text("Cookie path:").bold()) + .push( + form::Form::new("Cookie path", cookie_path, |msg| { + Message::DefineBitcoind(message::DefineBitcoind::CookiePathEdited(msg)) + }) + .warning("Please enter correct path") + .size(20) + .padding(10), + ) + .spacing(10); + + layout( + column() + .push( + text("Set up connection to the Bitcoin full node") + .bold() + .size(50), + ) + .push(col_address) + .push(col_cookie) + .push( + button::primary(None, "Next") + .on_press(Message::Next) + .width(Length::Units(200)), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding(100) + .spacing(50) + .align_items(Alignment::Center), + ) +} + +pub fn install<'a>( + generating: bool, + config_path: Option<&std::path::PathBuf>, + warning: Option<&String>, +) -> Element<'a, Message> { + let mut col = column() + .width(Length::Fill) + .height(Length::Fill) + .padding(100) + .spacing(50) + .align_items(Alignment::Center); + + if let Some(error) = warning { + col = col.push(text(error)); + } + + if generating { + col = col.push(button::primary(None, "Installing ...").width(Length::Units(200))) + } else if let Some(path) = config_path { + col = col.push( + container( + column() + .push(container(text("Installed !"))) + .push(container( + button::primary(None, "Start") + .on_press(Message::Exit(path.clone())) + .width(Length::Units(200)), + )) + .align_items(Alignment::Center) + .spacing(20), + ) + .padding(50) + .width(Length::Fill) + .center_x(), + ); + } else { + col = col.push( + button::primary(None, "Finalize installation") + .on_press(Message::Install) + .width(Length::Units(200)), + ); + } + + layout(col) +} + +fn layout<'a>(content: impl Into>) -> Element<'a, Message> { + container(scrollable( + column() + .push( + container(button::transparent(None, "< Previous").on_press(Message::Previous)) + .padding(5), + ) + .push(container(content).width(Length::Fill).center_x()), + )) + .center_x() + .height(Length::Fill) + .width(Length::Fill) + .into() +} diff --git a/gui/src/lib.rs b/gui/src/lib.rs new file mode 100644 index 00000000..7681b165 --- /dev/null +++ b/gui/src/lib.rs @@ -0,0 +1,6 @@ +pub mod app; +pub mod conversion; +pub mod daemon; +pub mod installer; +pub mod loader; +pub mod ui; diff --git a/gui/src/loader.rs b/gui/src/loader.rs new file mode 100644 index 00000000..a3f03f5a --- /dev/null +++ b/gui/src/loader.rs @@ -0,0 +1,281 @@ +use std::convert::From; +use std::io::ErrorKind; +use std::path::PathBuf; +use std::sync::Arc; + +use iced::pure::{column, text, Element}; +use iced::{Alignment, Command, Subscription}; +use iced_native::{window, Event}; +use log::{debug, info}; + +use minisafe::config::{Config, ConfigError}; + +use crate::{ + app::config::{default_datadir, Config as GUIConfig}, + daemon::{client, embedded::EmbeddedDaemon, model::*, Daemon, DaemonError}, +}; + +type Minisafed = client::Minisafed; + +pub struct Loader { + pub gui_config: GUIConfig, + pub daemon_config: Config, + pub daemon_started: bool, + + should_exit: bool, + step: Step, +} + +enum Step { + Connecting, + StartingDaemon, + Syncing { + daemon: Arc, + progress: f64, + }, + Error(Box), +} + +#[derive(Debug)] +pub enum Message { + Event(iced_native::Event), + Syncing(Result), + Synced(GetInfoResult, Arc), + Started(Result, Error>), + Loaded(Result, Error>), + DaemonStarted(EmbeddedDaemon), + Failure(DaemonError), +} + +impl Loader { + pub fn new(gui_config: GUIConfig, daemon_config: Config) -> (Self, Command) { + let path = socket_path( + &daemon_config.data_dir, + daemon_config.bitcoin_config.network, + ) + .unwrap(); + ( + Loader { + daemon_config, + gui_config, + step: Step::Connecting, + should_exit: false, + daemon_started: false, + }, + Command::perform(connect(path), Message::Loaded), + ) + } + + fn on_load(&mut self, res: Result, Error>) -> Command { + match res { + Ok(daemon) => { + self.step = Step::Syncing { + daemon: daemon.clone(), + progress: 0.0, + }; + return Command::perform(sync(daemon, false), Message::Syncing); + } + Err(e) => match e { + Error::Config(_) => { + self.step = Step::Error(Box::new(e)); + } + Error::Daemon(DaemonError::Transport(Some(ErrorKind::ConnectionRefused), _)) + | Error::Daemon(DaemonError::Transport(Some(ErrorKind::NotFound), _)) => { + self.step = Step::StartingDaemon; + self.daemon_started = true; + return Command::perform( + start_daemon(self.gui_config.minisafed_config_path.clone()), + Message::Started, + ); + } + _ => { + self.step = Step::Error(Box::new(e)); + } + }, + } + Command::none() + } + + fn on_start(&mut self, res: Result, Error>) -> Command { + match res { + Ok(minisafed) => { + self.step = Step::Syncing { + daemon: minisafed.clone(), + progress: 0.0, + }; + Command::perform(sync(minisafed, false), Message::Syncing) + } + Err(e) => { + self.step = Step::Error(Box::new(e)); + Command::none() + } + } + } + + fn on_sync(&mut self, res: Result) -> Command { + match &mut self.step { + Step::Syncing { + daemon, progress, .. + } => { + match res { + Ok(info) => { + if (info.sync - 1.0_f64).abs() < f64::EPSILON { + let daemon = daemon.clone(); + return Command::perform(async move { (info, daemon) }, |res| { + Message::Synced(res.0, res.1) + }); + } else { + *progress = info.sync + } + } + Err(e) => { + self.step = Step::Error(Box::new(e.into())); + return Command::none(); + } + }; + Command::perform(sync(daemon.clone(), true), Message::Syncing) + } + _ => Command::none(), + } + } + + pub fn stop(&mut self) { + log::info!("Close requested"); + if let Step::Syncing { daemon, .. } = &mut self.step { + if !daemon.is_external() { + log::info!("Stopping internal daemon..."); + if let Some(d) = Arc::get_mut(daemon) { + d.stop().expect("Daemon is internal"); + log::info!("Internal daemon stopped"); + self.should_exit = true; + } + } else { + self.should_exit = true; + } + } else { + self.should_exit = true; + } + } + + pub fn update(&mut self, message: Message) -> Command { + match message { + Message::Started(res) => self.on_start(res), + Message::Loaded(res) => self.on_load(res), + Message::Syncing(res) => self.on_sync(res), + Message::Failure(_) => { + self.daemon_started = false; + Command::none() + } + Message::Event(Event::Window(window::Event::CloseRequested)) => { + self.stop(); + Command::none() + } + _ => Command::none(), + } + } + + pub fn subscription(&self) -> Subscription { + iced_native::subscription::events().map(Message::Event) + } + + pub fn should_exit(&self) -> bool { + self.should_exit + } + + pub fn view(&self) -> Element { + match &self.step { + Step::StartingDaemon => cover(text("Starting daemon...")), + Step::Connecting => cover(text("Connecting to daemon...")), + Step::Syncing { progress, .. } => cover(text(&format!("Syncing... {}%", progress))), + Step::Error(error) => cover(text(&format!("Error: {}", error))), + } + } +} + +pub fn cover<'a, T: 'a, C: Into>>(content: C) -> Element<'a, T> { + column() + .push(content) + .width(iced::Length::Fill) + .height(iced::Length::Fill) + .padding(50) + .spacing(50) + .align_items(Alignment::Center) + .into() +} + +async fn connect(socket_path: PathBuf) -> Result, Error> { + let client = client::jsonrpc::JsonRPCClient::new(socket_path); + let minisafed = Minisafed::new(client); + + debug!("Searching for external daemon"); + minisafed.get_info()?; + info!("Connected to external daemon"); + + Ok(Arc::new(minisafed)) +} + +// Daemon can start only if a config path is given. +pub async fn start_daemon(config_path: PathBuf) -> Result, Error> { + debug!("starting minisafe daemon"); + + let config = Config::from_file(Some(config_path)) + .map_err(|e| DaemonError::Start(format!("Error parsing config: {}", e)))?; + + let mut daemon = EmbeddedDaemon::default(); + daemon.start(config)?; + + Ok(Arc::new(daemon)) +} + +async fn sync( + minisafed: Arc, + sleep: bool, +) -> Result { + if sleep { + std::thread::sleep(std::time::Duration::from_secs(1)); + } + minisafed.get_info() +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug)] +pub enum Error { + Config(ConfigError), + Daemon(DaemonError), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Config(e) => write!(f, "Config error: {}", e), + Self::Daemon(e) => write!(f, "RevaultD error: {}", e), + } + } +} + +impl From for Error { + fn from(error: ConfigError) -> Self { + Error::Config(error) + } +} + +impl From for Error { + fn from(error: DaemonError) -> Self { + Error::Daemon(error) + } +} + +/// default minisafed socket path is .minisafe/bitcoin/minisafed_rpc +fn socket_path( + datadir: &Option, + network: bitcoin::Network, +) -> Result { + let mut path = if let Some(ref datadir) = datadir { + datadir.clone() + } else { + default_datadir().map_err(|_| ConfigError::DatadirNotFound)? + }; + path.push(network.to_string()); + path.push("minisafed_rpc"); + Ok(path) +} diff --git a/gui/src/main.rs b/gui/src/main.rs new file mode 100644 index 00000000..8a09188b --- /dev/null +++ b/gui/src/main.rs @@ -0,0 +1,362 @@ +use std::{error::Error, path::PathBuf, str::FromStr}; + +use iced::pure::{Application, Element}; +use iced::{executor, Command, Settings, Subscription}; +extern crate serde; +extern crate serde_json; + +use minisafe::config::Config as DaemonConfig; + +use minisafe_gui::{ + app::{ + self, + config::{default_datadir, ConfigError}, + context::{ConfigContext, Context}, + menu::Menu, + App, + }, + conversion::Converter, + installer::{self, Installer}, + loader::{self, Loader}, +}; + +#[derive(Debug, PartialEq)] +enum Arg { + ConfigPath(PathBuf), + DatadirPath(PathBuf), + Network(bitcoin::Network), +} + +fn parse_args(args: Vec) -> Result, Box> { + let mut res = Vec::new(); + for (i, arg) in args.iter().enumerate() { + if arg == "--conf" { + if let Some(a) = args.get(i + 1) { + res.push(Arg::ConfigPath(PathBuf::from(a))); + } else { + return Err("missing arg to --conf".into()); + } + } else if arg == "--datadir" { + if let Some(a) = args.get(i + 1) { + res.push(Arg::DatadirPath(PathBuf::from(a))); + } else { + return Err("missing arg to --datadir".into()); + } + } else if arg.contains("--") { + let network = bitcoin::Network::from_str(args[i].trim_start_matches("--"))?; + res.push(Arg::Network(network)); + } + } + + Ok(res) +} + +fn log_level_from_config(config: &app::Config) -> Result> { + if let Some(level) = &config.log_level { + match level.as_ref() { + "info" => Ok(log::LevelFilter::Info), + "debug" => Ok(log::LevelFilter::Debug), + "trace" => Ok(log::LevelFilter::Trace), + _ => Err(format!("Unknown loglevel '{:?}'.", level).into()), + } + } else if let Some(true) = config.debug { + Ok(log::LevelFilter::Debug) + } else { + Ok(log::LevelFilter::Info) + } +} + +pub struct GUI { + state: State, +} + +enum State { + Installer(Installer), + Loader(Loader), + App(App), +} + +#[derive(Debug)] +pub enum Message { + CtrlC, + Install(Box), + Load(Box), + Run(Box), +} + +async fn ctrl_c() -> Result<(), ()> { + if let Err(e) = tokio::signal::ctrl_c().await { + log::error!("{}", e); + }; + log::info!("Signal received, exiting"); + Ok(()) +} + +impl Application for GUI { + type Executor = executor::Default; + type Message = Message; + type Flags = Config; + + fn title(&self) -> String { + match self.state { + State::Installer(_) => String::from("Revault Installer"), + State::App(_) => String::from("Revault GUI"), + State::Loader(..) => String::from("Revault"), + } + } + + fn new(config: Config) -> (GUI, Command) { + match config { + Config::Install(config_path, network) => { + let (install, command) = Installer::new(config_path, network); + ( + Self { + state: State::Installer(install), + }, + Command::batch(vec![ + command.map(|msg| Message::Install(Box::new(msg))), + Command::perform(ctrl_c(), |_| Message::CtrlC), + ]), + ) + } + Config::Run(cfg) => { + let daemon_cfg = + DaemonConfig::from_file(Some(cfg.minisafed_config_path.clone())).unwrap(); + let (loader, command) = Loader::new(cfg, daemon_cfg); + ( + Self { + state: State::Loader(loader), + }, + Command::batch(vec![ + command.map(|msg| Message::Load(Box::new(msg))), + Command::perform(ctrl_c(), |_| Message::CtrlC), + ]), + ) + } + } + } + + fn update(&mut self, message: Self::Message) -> Command { + match (&mut self.state, message) { + (State::Installer(i), Message::CtrlC) => { + i.stop(); + Command::none() + } + (State::Loader(i), Message::CtrlC) => { + i.stop(); + Command::none() + } + (State::App(i), Message::CtrlC) => { + i.stop(); + Command::none() + } + (State::Installer(i), Message::Install(msg)) => { + if let installer::Message::Exit(path) = *msg { + let cfg = app::Config::from_file(&path).unwrap(); + let daemon_cfg = + DaemonConfig::from_file(Some(cfg.minisafed_config_path.clone())).unwrap(); + let (loader, command) = Loader::new(cfg, daemon_cfg); + self.state = State::Loader(loader); + command.map(|msg| Message::Load(Box::new(msg))) + } else { + i.update(*msg).map(|msg| Message::Install(Box::new(msg))) + } + } + (State::Loader(loader), Message::Load(msg)) => { + if let loader::Message::Synced(_info, minisafed) = *msg { + let config = ConfigContext { + gui: loader.gui_config.clone(), + daemon: loader.daemon_config.clone(), + }; + + let converter = Converter::new(config.daemon.bitcoin_config.network); + + let context = Context::new(config, minisafed, converter, Menu::Home); + + let (app, command) = App::new(context); + self.state = State::App(app); + command.map(|msg| Message::Run(Box::new(msg))) + } else { + loader.update(*msg).map(|msg| Message::Load(Box::new(msg))) + } + } + (State::App(i), Message::Run(msg)) => { + i.update(*msg).map(|msg| Message::Run(Box::new(msg))) + } + _ => Command::none(), + } + } + + fn should_exit(&self) -> bool { + match &self.state { + State::Installer(v) => v.should_exit(), + State::Loader(v) => v.should_exit(), + State::App(v) => v.should_exit(), + } + } + + fn subscription(&self) -> Subscription { + match &self.state { + State::Installer(v) => v.subscription().map(|msg| Message::Install(Box::new(msg))), + State::Loader(v) => v.subscription().map(|msg| Message::Load(Box::new(msg))), + State::App(v) => v.subscription().map(|msg| Message::Run(Box::new(msg))), + } + } + + fn view(&self) -> Element { + match &self.state { + State::Installer(v) => v.view().map(|msg| Message::Install(Box::new(msg))), + State::App(v) => v.view().map(|msg| Message::Run(Box::new(msg))), + State::Loader(v) => v.view().map(|msg| Message::Load(Box::new(msg))), + } + } + + fn scale_factor(&self) -> f64 { + 1.0 + } +} + +pub enum Config { + Run(app::Config), + Install(PathBuf, bitcoin::Network), +} + +impl Config { + pub fn new(datadir_path: PathBuf, network: bitcoin::Network) -> Result> { + let mut path = datadir_path.clone(); + path.push(network.to_string()); + path.push(app::config::DEFAULT_FILE_NAME); + match app::Config::from_file(&path) { + Ok(cfg) => Ok(Config::Run(cfg)), + Err(ConfigError::NotFound) => Ok(Config::Install(datadir_path, network)), + Err(e) => Err(format!("Failed to read configuration file: {}", e).into()), + } + } +} + +fn main() -> Result<(), Box> { + let args = parse_args(std::env::args().collect())?; + let config = match args.as_slice() { + [] => { + let datadir_path = default_datadir().unwrap(); + Config::new(datadir_path, bitcoin::Network::Bitcoin) + } + [Arg::Network(network)] => { + let datadir_path = default_datadir().unwrap(); + Config::new(datadir_path, *network) + } + [Arg::ConfigPath(path)] => Ok(Config::Run(app::Config::from_file(path)?)), + [Arg::DatadirPath(datadir_path)] => { + Config::new(datadir_path.clone(), bitcoin::Network::Bitcoin) + } + [Arg::DatadirPath(datadir_path), Arg::Network(network)] + | [Arg::Network(network), Arg::DatadirPath(datadir_path)] => { + Config::new(datadir_path.clone(), *network) + } + _ => { + return Err("Unknown args combination".into()); + } + }?; + + let level = if let Config::Run(cfg) = &config { + log_level_from_config(cfg)? + } else { + log::LevelFilter::Info + }; + setup_logger(level)?; + + let mut settings = Settings::with_flags(config); + settings.exit_on_close_request = false; + + if let Err(e) = GUI::run(settings) { + return Err(format!("Failed to launch UI: {}", e).into()); + }; + Ok(()) +} + +// This creates the log file automagically if it doesn't exist, and logs on stdout +// if None is given +pub fn setup_logger(log_level: log::LevelFilter) -> Result<(), fern::InitError> { + let dispatcher = fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "[{}][{}][{}] {}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_else(|e| { + println!("Can't get time since epoch: '{}'. Using a dummy value.", e); + std::time::Duration::from_secs(0) + }) + .as_secs(), + record.target(), + record.level(), + message + )) + }) + .level(log_level) + .level_for("iced_wgpu", log::LevelFilter::Off) + .level_for("wgpu_core", log::LevelFilter::Off) + .level_for("wgpu_hal", log::LevelFilter::Off) + .level_for("gfx_backend_vulkan", log::LevelFilter::Off) + .level_for("naga", log::LevelFilter::Off) + .level_for("mio", log::LevelFilter::Off); + + dispatcher.chain(std::io::stdout()).apply()?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_args() { + assert_eq!(true, parse_args(vec!["--meth".into()]).is_err()); + assert_eq!(true, parse_args(vec!["--datadir".into()]).is_err()); + assert_eq!(true, parse_args(vec!["--conf".into()]).is_err()); + assert_eq!( + Some(vec![ + Arg::DatadirPath(PathBuf::from(".")), + Arg::ConfigPath(PathBuf::from("hello.toml")), + ]), + parse_args( + "--datadir . --conf hello.toml" + .split(" ") + .map(|a| a.to_string()) + .collect() + ) + .ok() + ); + assert_eq!( + Some(vec![Arg::Network(bitcoin::Network::Regtest)]), + parse_args(vec!["--regtest".into()]).ok() + ); + assert_eq!( + Some(vec![ + Arg::DatadirPath(PathBuf::from("hello")), + Arg::Network(bitcoin::Network::Testnet) + ]), + parse_args( + "--datadir hello --testnet" + .split(" ") + .map(|a| a.to_string()) + .collect() + ) + .ok() + ); + assert_eq!( + Some(vec![ + Arg::Network(bitcoin::Network::Testnet), + Arg::DatadirPath(PathBuf::from("hello")) + ]), + parse_args( + "--testnet --datadir hello" + .split(" ") + .map(|a| a.to_string()) + .collect() + ) + .ok() + ); + } +} diff --git a/gui/src/ui/color.rs b/gui/src/ui/color.rs new file mode 100644 index 00000000..b64cfbbe --- /dev/null +++ b/gui/src/ui/color.rs @@ -0,0 +1,75 @@ +use iced::Color; + +pub const BACKGROUND: Color = Color::from_rgb( + 0xF6 as f32 / 255.0, + 0xF7 as f32 / 255.0, + 0xF8 as f32 / 255.0, +); + +pub const FOREGROUND: Color = Color::WHITE; + +pub const PRIMARY: Color = Color::BLACK; + +pub const SECONDARY: Color = DARK_GREY; + +pub const SUCCESS: Color = Color::from_rgb( + 0x29 as f32 / 255.0, + 0xBC as f32 / 255.0, + 0x97 as f32 / 255.0, +); + +#[allow(dead_code)] +pub const SUCCESS_LIGHT: Color = Color::from_rgba( + 0x29 as f32 / 255.0, + 0xBC as f32 / 255.0, + 0x97 as f32 / 255.0, + 0.5f32, +); + +pub const ALERT: Color = Color::from_rgb( + 0xF0 as f32 / 255.0, + 0x43 as f32 / 255.0, + 0x59 as f32 / 255.0, +); + +pub const ALERT_LIGHT: Color = Color::from_rgba( + 0xF0 as f32 / 255.0, + 0x43 as f32 / 255.0, + 0x59 as f32 / 255.0, + 0.5f32, +); + +pub const WARNING: Color = + Color::from_rgb(0xFF as f32 / 255.0, 0xa7 as f32 / 255.0, 0x0 as f32 / 255.0); + +pub const WARNING_LIGHT: Color = Color::from_rgba( + 0xFF as f32 / 255.0, + 0xa7 as f32 / 255.0, + 0x0 as f32 / 255.0, + 0.5f32, +); + +pub const CANCEL: Color = Color::from_rgb( + 0x34 as f32 / 255.0, + 0x37 as f32 / 255.0, + 0x3D as f32 / 255.0, +); + +pub const INFO: Color = Color::from_rgb( + 0x2A as f32 / 255.0, + 0x98 as f32 / 255.0, + 0xBD as f32 / 255.0, +); + +pub const INFO_LIGHT: Color = Color::from_rgba( + 0x2A as f32 / 255.0, + 0x98 as f32 / 255.0, + 0xBD as f32 / 255.0, + 0.5f32, +); + +pub const DARK_GREY: Color = Color::from_rgb( + 0x8c as f32 / 255.0, + 0x97 as f32 / 255.0, + 0xa6 as f32 / 255.0, +); diff --git a/gui/src/ui/component/button.rs b/gui/src/ui/component/button.rs new file mode 100644 index 00000000..ed727852 --- /dev/null +++ b/gui/src/ui/component/button.rs @@ -0,0 +1,62 @@ +use iced::pure::{ + container, row, + widget::{button, Container}, +}; +use iced::{Alignment, Color, Length, Vector}; + +use super::text::text; +use crate::ui::color; + +pub fn primary<'a, T: 'a>(icon: Option, t: &str) -> button::Button<'a, T> { + button::Button::new(content(icon, t)).style(Style::Primary) +} + +pub fn transparent<'a, T: 'a>(icon: Option, t: &str) -> button::Button<'a, T> { + button::Button::new(content(icon, t)).style(Style::Transparent) +} + +fn content<'a, T: 'a>(icon: Option, t: &str) -> Container<'a, T> { + match icon { + None => container(text(t)).width(Length::Fill).center_x().padding(5), + Some(i) => container( + row() + .push(i) + .push(text(t)) + .spacing(10) + .width(iced::Length::Fill) + .align_items(Alignment::Center), + ) + .width(iced::Length::Fill) + .center_x() + .padding(5), + } +} + +#[derive(Debug, Clone, Copy)] +enum Style { + Primary, + Transparent, +} + +impl button::StyleSheet for Style { + fn active(&self) -> button::Style { + match self { + Style::Primary => button::Style { + shadow_offset: Vector::default(), + background: color::PRIMARY.into(), + border_radius: 10.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + text_color: color::FOREGROUND, + }, + Style::Transparent => button::Style { + shadow_offset: Vector::default(), + background: Color::TRANSPARENT.into(), + border_radius: 10.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + text_color: color::DARK_GREY, + }, + } + } +} diff --git a/gui/src/ui/component/form.rs b/gui/src/ui/component/form.rs new file mode 100644 index 00000000..34933e55 --- /dev/null +++ b/gui/src/ui/component/form.rs @@ -0,0 +1,120 @@ +use iced::pure::{ + column, container, + widget::text_input::{Style, StyleSheet, TextInput}, + Element, +}; +use iced::Length; + +use crate::ui::{color, component::text::text}; + +#[derive(Debug, Clone)] +pub struct Value { + pub value: T, + pub valid: bool, +} + +impl std::default::Default for Value { + fn default() -> Self { + Self { + value: "".to_string(), + valid: true, + } + } +} + +pub struct Form<'a, Message> { + input: TextInput<'a, Message>, + warning: Option<&'a str>, + valid: bool, +} + +impl<'a, Message: 'a> Form<'a, Message> +where + Message: Clone, +{ + /// Creates a new [`Form`]. + /// + /// It expects: + /// - a placeholder + /// - the current value + /// - a function that produces a message when the [`Form`] changes + pub fn new(placeholder: &str, value: &Value, on_change: F) -> Self + where + F: 'static + Fn(String) -> Message, + { + Self { + input: TextInput::new(placeholder, &value.value, on_change), + warning: None, + valid: value.valid, + } + } + + /// Sets the [`Form`] with a warning message + pub fn warning(mut self, warning: &'a str) -> Self { + self.warning = Some(warning); + self + } + + /// Sets the padding of the [`Form`]. + pub fn padding(mut self, units: u16) -> Self { + self.input = self.input.padding(units); + self + } + + /// Sets the [`Form`] with a text size + pub fn size(mut self, size: u16) -> Self { + self.input = self.input.size(size); + self + } +} + +impl<'a, Message: 'a + Clone> From> for Element<'a, Message> { + fn from(form: Form<'a, Message>) -> Element<'a, Message> { + if !form.valid { + if let Some(message) = form.warning { + return container( + column() + .push(form.input.style(InvalidFormStyle)) + .push(text(message).color(color::ALERT)) + .width(Length::Fill) + .spacing(5), + ) + .width(Length::Fill) + .into(); + } + } + + container(form.input).width(Length::Fill).into() + } +} + +struct InvalidFormStyle; +impl StyleSheet for InvalidFormStyle { + fn active(&self) -> Style { + Style { + background: iced::Background::Color(color::FOREGROUND), + border_radius: 5.0, + border_width: 1.0, + border_color: color::ALERT, + } + } + + fn focused(&self) -> Style { + Style { + border_color: color::ALERT, + ..self.active() + } + } + + fn placeholder_color(&self) -> iced::Color { + iced::Color::from_rgb(0.7, 0.7, 0.7) + } + + fn value_color(&self) -> iced::Color { + iced::Color::from_rgb(0.3, 0.3, 0.3) + } + + fn selection_color(&self) -> iced::Color { + iced::Color::from_rgb(0.8, 0.8, 1.0) + } +} diff --git a/gui/src/ui/component/mod.rs b/gui/src/ui/component/mod.rs new file mode 100644 index 00000000..5daa4f0e --- /dev/null +++ b/gui/src/ui/component/mod.rs @@ -0,0 +1,24 @@ +pub mod button; +pub mod form; +pub mod text; + +use iced::pure::widget::{container, Column, Container}; +use iced::Length; + +use crate::ui::color; + +pub fn separation<'a, T: 'a>() -> Container<'a, T> { + Container::new(Column::new().push(iced::Text::new(" "))) + .style(SepStyle) + .height(Length::Units(1)) +} + +pub struct SepStyle; +impl container::StyleSheet for SepStyle { + fn style(&self) -> container::Style { + container::Style { + background: color::SECONDARY.into(), + ..container::Style::default() + } + } +} diff --git a/gui/src/ui/component/notification.rs b/gui/src/ui/component/notification.rs new file mode 100644 index 00000000..77ebb0a8 --- /dev/null +++ b/gui/src/ui/component/notification.rs @@ -0,0 +1,46 @@ +use crate::{color, icon}; +use iced::{container, tooltip, Container, Length, Row, Text, Tooltip}; + +pub fn warning<'a, T: 'a>(message: &str, error: &str) -> Container<'a, T> { + Container::new(Container::new( + Tooltip::new( + Row::new() + .push(icon::warning_icon()) + .push(Text::new(message)) + .spacing(20), + error, + tooltip::Position::Bottom, + ) + .style(TooltipWarningStyle), + )) + .padding(15) + .center_x() + .style(WarningStyle) + .width(Length::Fill) +} + +struct WarningStyle; +impl container::StyleSheet for WarningStyle { + fn style(&self) -> container::Style { + container::Style { + border_radius: 0.0, + text_color: iced::Color::BLACK.into(), + background: color::WARNING.into(), + border_color: color::WARNING, + ..container::Style::default() + } + } +} + +struct TooltipWarningStyle; +impl container::StyleSheet for TooltipWarningStyle { + fn style(&self) -> container::Style { + container::Style { + border_radius: 0.0, + border_width: 1.0, + text_color: color::WARNING.into(), + background: color::FOREGROUND.into(), + border_color: color::WARNING, + } + } +} diff --git a/gui/src/ui/component/text.rs b/gui/src/ui/component/text.rs new file mode 100644 index 00000000..ba5cda12 --- /dev/null +++ b/gui/src/ui/component/text.rs @@ -0,0 +1,17 @@ +use crate::ui::font; + +pub fn text(content: &str) -> iced::pure::widget::Text { + iced::pure::widget::Text::new(content) + .font(font::REGULAR) + .size(25) +} + +pub trait Text { + fn bold(self) -> Self; +} + +impl Text for iced::pure::widget::Text { + fn bold(self) -> Self { + self.font(font::BOLD) + } +} diff --git a/gui/src/ui/font.rs b/gui/src/ui/font.rs new file mode 100644 index 00000000..eb7555d5 --- /dev/null +++ b/gui/src/ui/font.rs @@ -0,0 +1,11 @@ +use iced::Font; + +pub const BOLD: Font = Font::External { + name: "Bold", + bytes: include_bytes!("../../static/fonts/OpenSans-Bold.ttf"), +}; + +pub const REGULAR: Font = Font::External { + name: "Regular", + bytes: include_bytes!("../../static/fonts/OpenSans-Regular.ttf"), +}; diff --git a/gui/src/ui/icon.rs b/gui/src/ui/icon.rs new file mode 100644 index 00000000..497bd5af --- /dev/null +++ b/gui/src/ui/icon.rs @@ -0,0 +1,165 @@ +use iced::pure::text; +use iced::{alignment, Font, Length, Text}; + +const ICONS: Font = Font::External { + name: "Icons", + bytes: include_bytes!("../../static/icons/bootstrap-icons.ttf"), +}; + +fn icon(unicode: char) -> Text { + text(&unicode.to_string()) + .font(ICONS) + .width(Length::Units(20)) + .horizontal_alignment(alignment::Horizontal::Center) + .size(20) +} + +pub fn vault_icon() -> Text { + icon('\u{F65A}') +} + +pub fn bitcoin_icon() -> Text { + icon('\u{F635}') +} + +pub fn history_icon() -> Text { + icon('\u{F292}') +} + +pub fn home_icon() -> Text { + icon('\u{F3FC}') +} + +pub fn unlock_icon() -> Text { + icon('\u{F600}') +} + +pub fn warning_octagon_icon() -> Text { + icon('\u{F337}') +} + +pub fn send_icon() -> Text { + icon('\u{F144}') +} + +pub fn connect_device_icon() -> Text { + icon('\u{F348}') +} + +pub fn connected_device_icon() -> Text { + icon('\u{F350}') +} + +pub fn deposit_icon() -> Text { + icon('\u{F123}') +} + +pub fn calendar_icon() -> Text { + icon('\u{F1E8}') +} + +pub fn turnback_icon() -> Text { + icon('\u{F131}') +} + +pub fn vaults_icon() -> Text { + icon('\u{F1C7}') +} + +pub fn settings_icon() -> Text { + icon('\u{F3E5}') +} + +pub fn block_icon() -> Text { + icon('\u{F1C8}') +} + +pub fn square_icon() -> Text { + icon('\u{F584}') +} + +pub fn square_check_icon() -> Text { + icon('\u{F26D}') +} + +pub fn circle_check_icon() -> Text { + icon('\u{F26B}') +} + +pub fn network_icon() -> Text { + icon('\u{F40D}') +} + +pub fn dot_icon() -> Text { + icon('\u{F287}') +} + +pub fn clipboard_icon() -> Text { + icon('\u{F28E}') +} + +pub fn shield_icon() -> Text { + icon('\u{F53F}') +} + +pub fn shield_notif_icon() -> Text { + icon('\u{F530}') +} + +pub fn shield_check_icon() -> Text { + icon('\u{F52F}') +} + +pub fn person_check_icon() -> Text { + icon('\u{F4D6}') +} + +pub fn person_icon() -> Text { + icon('\u{F4DA}') +} + +pub fn tooltip_icon() -> Text { + icon('\u{F431}') +} + +pub fn plus_icon() -> Text { + icon('\u{F4FE}') +} + +pub fn warning_icon() -> Text { + icon('\u{F33B}') +} + +pub fn trash_icon() -> Text { + icon('\u{F5DE}') +} + +pub fn key_icon() -> Text { + icon('\u{F44F}') +} + +pub fn cross_icon() -> Text { + icon('\u{F62A}') +} + +pub fn pencil_icon() -> Text { + icon('\u{F4CB}') +} + +#[allow(dead_code)] +pub fn stakeholder_icon() -> Text { + icon('\u{F4AE}') +} + +#[allow(dead_code)] +pub fn manager_icon() -> Text { + icon('\u{F4B4}') +} + +pub fn done_icon() -> Text { + icon('\u{F26B}') +} + +pub fn todo_icon() -> Text { + icon('\u{F28A}') +} diff --git a/gui/src/ui/mod.rs b/gui/src/ui/mod.rs new file mode 100644 index 00000000..66d5b965 --- /dev/null +++ b/gui/src/ui/mod.rs @@ -0,0 +1,6 @@ +pub mod color; +/// component are wrappers around iced elements; +pub mod component; +pub mod font; +pub mod icon; +pub mod util; diff --git a/gui/src/ui/util.rs b/gui/src/ui/util.rs new file mode 100644 index 00000000..e6bef844 --- /dev/null +++ b/gui/src/ui/util.rs @@ -0,0 +1,28 @@ +/// from hecjr idea on Discord +use iced::pure::{ + widget::{Column, Row}, + Element, +}; + +pub trait Collection<'a, Message>: Sized { + fn push(self, element: impl Into>) -> Self; + + fn push_maybe(self, element: Option>>) -> Self { + match element { + Some(element) => self.push(element), + None => self, + } + } +} + +impl<'a, Message> Collection<'a, Message> for Column<'a, Message> { + fn push(self, element: impl Into>) -> Self { + Self::push(self, element) + } +} + +impl<'a, Message> Collection<'a, Message> for Row<'a, Message> { + fn push(self, element: impl Into>) -> Self { + Self::push(self, element) + } +} diff --git a/gui/static/fonts/LICENSE.txt b/gui/static/fonts/LICENSE.txt new file mode 100644 index 00000000..75b52484 --- /dev/null +++ b/gui/static/fonts/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/gui/static/fonts/OpenSans-Bold.ttf b/gui/static/fonts/OpenSans-Bold.ttf new file mode 100644 index 00000000..efdd5e84 Binary files /dev/null and b/gui/static/fonts/OpenSans-Bold.ttf differ diff --git a/gui/static/fonts/OpenSans-BoldItalic.ttf b/gui/static/fonts/OpenSans-BoldItalic.ttf new file mode 100644 index 00000000..9bf9b4e9 Binary files /dev/null and b/gui/static/fonts/OpenSans-BoldItalic.ttf differ diff --git a/gui/static/fonts/OpenSans-ExtraBold.ttf b/gui/static/fonts/OpenSans-ExtraBold.ttf new file mode 100644 index 00000000..67fcf0fb Binary files /dev/null and b/gui/static/fonts/OpenSans-ExtraBold.ttf differ diff --git a/gui/static/fonts/OpenSans-ExtraBoldItalic.ttf b/gui/static/fonts/OpenSans-ExtraBoldItalic.ttf new file mode 100644 index 00000000..08672280 Binary files /dev/null and b/gui/static/fonts/OpenSans-ExtraBoldItalic.ttf differ diff --git a/gui/static/fonts/OpenSans-Italic.ttf b/gui/static/fonts/OpenSans-Italic.ttf new file mode 100644 index 00000000..11785670 Binary files /dev/null and b/gui/static/fonts/OpenSans-Italic.ttf differ diff --git a/gui/static/fonts/OpenSans-Light.ttf b/gui/static/fonts/OpenSans-Light.ttf new file mode 100644 index 00000000..6580d3a1 Binary files /dev/null and b/gui/static/fonts/OpenSans-Light.ttf differ diff --git a/gui/static/fonts/OpenSans-LightItalic.ttf b/gui/static/fonts/OpenSans-LightItalic.ttf new file mode 100644 index 00000000..1e0c3319 Binary files /dev/null and b/gui/static/fonts/OpenSans-LightItalic.ttf differ diff --git a/gui/static/fonts/OpenSans-Regular.ttf b/gui/static/fonts/OpenSans-Regular.ttf new file mode 100644 index 00000000..29bfd35a Binary files /dev/null and b/gui/static/fonts/OpenSans-Regular.ttf differ diff --git a/gui/static/fonts/OpenSans-SemiBold.ttf b/gui/static/fonts/OpenSans-SemiBold.ttf new file mode 100644 index 00000000..54e7059c Binary files /dev/null and b/gui/static/fonts/OpenSans-SemiBold.ttf differ diff --git a/gui/static/fonts/OpenSans-SemiBoldItalic.ttf b/gui/static/fonts/OpenSans-SemiBoldItalic.ttf new file mode 100644 index 00000000..aebcf142 Binary files /dev/null and b/gui/static/fonts/OpenSans-SemiBoldItalic.ttf differ diff --git a/gui/static/icons/LICENSE.md b/gui/static/icons/LICENSE.md new file mode 100644 index 00000000..0add0a0e --- /dev/null +++ b/gui/static/icons/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 The Bootstrap Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/gui/static/icons/README.md b/gui/static/icons/README.md new file mode 100644 index 00000000..54666a2f --- /dev/null +++ b/gui/static/icons/README.md @@ -0,0 +1,6 @@ +# Icons + +From the getbootstrap.com repository. +Converted from .woff to ttf with https://raw.githubusercontent.com/hanikesn/woff2otf/master/woff2otf.py + +Use http://mathew-kurian.github.io/CharacterMap/ to check Unicode. diff --git a/gui/static/icons/bootstrap-icons.ttf b/gui/static/icons/bootstrap-icons.ttf new file mode 100644 index 00000000..d8768998 Binary files /dev/null and b/gui/static/icons/bootstrap-icons.ttf differ diff --git a/gui/static/images/revault-colored-logo.svg b/gui/static/images/revault-colored-logo.svg new file mode 100644 index 00000000..16f74568 --- /dev/null +++ b/gui/static/images/revault-colored-logo.svg @@ -0,0 +1,144 @@ + +image/svg+xml \ No newline at end of file