diff --git a/Cargo.lock b/Cargo.lock index cd42703..e1b40d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aho-corasick" version = "1.1.5" @@ -17,6 +27,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" + +[[package]] +name = "alloc-stdlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -59,6 +84,30 @@ dependencies = [ "password-hash", ] +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-compression" version = "0.4.48" @@ -71,6 +120,136 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "atoi" version = "2.0.0" @@ -157,6 +336,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -175,11 +360,35 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +dependencies = [ + "serde_core", +] [[package]] name = "blake2" @@ -199,12 +408,70 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + [[package]] name = "byteorder" version = "1.5.0" @@ -216,6 +483,77 @@ name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.2", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.21", +] + +[[package]] +name = "cargo_toml" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f4b26e751e711a5302649417f2da046dce6391b2ea30a4820f37462314f0b9" +dependencies = [ + "semver", + "serde", + "toml 1.1.6+spec-1.1.0", +] [[package]] name = "cc" @@ -233,6 +571,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfb" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3" +dependencies = [ + "fnv", + "uuid", + "web-time", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.5" @@ -245,6 +604,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20" version = "0.10.2" @@ -256,6 +626,19 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.45" @@ -265,7 +648,28 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", ] [[package]] @@ -285,12 +689,75 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "cookie" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.1", + "libc", +] + [[package]] name = "core_detect" version = "1.0.0" @@ -339,6 +806,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.14" @@ -361,14 +837,123 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "cssparser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a" +dependencies = [ + "quote", + "syn 3.0.6", +] + +[[package]] +name = "ctor" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" + +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.6", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.21", +] + [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] [[package]] name = "derive_arbitrary" @@ -381,6 +966,27 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + [[package]] name = "digest" version = "0.10.7" @@ -392,6 +998,39 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.2", + "block2", + "libc", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.7" @@ -403,12 +1042,86 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dom_query" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc" +dependencies = [ + "bit-set", + "cssparser", + "foldhash 0.2.0", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.18.0" @@ -418,6 +1131,26 @@ dependencies = [ "serde", ] +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.6+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + [[package]] name = "encoding_rs" version = "0.8.42" @@ -432,12 +1165,50 @@ dependencies = [ "simdutf8", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + [[package]] name = "errno" version = "0.3.14" @@ -458,6 +1229,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastnbt" version = "2.6.3" @@ -476,6 +1257,25 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + [[package]] name = "find-msvc-tools" version = "0.1.14" @@ -489,7 +1289,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.9.1", "zlib-rs", ] @@ -516,6 +1316,39 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -584,6 +1417,19 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.34" @@ -624,6 +1470,105 @@ dependencies = [ "slab", ] +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -647,6 +1592,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -656,11 +1613,159 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.1", "wasm-bindgen", ] +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "h2" version = "0.4.19" @@ -673,13 +1778,19 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.5" @@ -688,7 +1799,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -706,18 +1817,40 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "html5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" +dependencies = [ + "log", + "markup5ever", +] + [[package]] name = "http" version = "1.5.0" @@ -843,7 +1976,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -855,6 +1988,16 @@ dependencies = [ "cc", ] +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + [[package]] name = "icu_collections" version = "2.3.0" @@ -938,6 +2081,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -959,6 +2108,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.14.2" @@ -967,6 +2127,26 @@ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4" +dependencies = [ + "cfb", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", ] [[package]] @@ -975,12 +2155,152 @@ version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jiff" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "js-sys" version = "0.3.106" @@ -992,6 +2312,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-patch" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 2.0.21", +] + +[[package]] +name = "jsonptr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "jsonwebtoken" version = "9.3.1" @@ -1007,18 +2349,94 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "keyboard-types" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" +dependencies = [ + "bitflags 2.13.2", + "serde", +] + +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "log", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zeroize", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + [[package]] name = "libc" version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "libc", +] + [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -1063,6 +2481,17 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" +[[package]] +name = "markup5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1094,6 +2523,15 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1110,6 +2548,16 @@ dependencies = [ "unicase", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "miniz_oxide" version = "0.9.1" @@ -1131,6 +2579,27 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "muda" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.21", + "windows-sys 0.61.2", +] + [[package]] name = "multer" version = "3.1.0" @@ -1154,6 +2623,42 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.2", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1197,12 +2702,289 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.2", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "open" +version = "5.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +dependencies = [ + "dunce", + "is-wsl", + "libc", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "parking" version = "2.2.1" @@ -1229,7 +3011,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1259,18 +3041,161 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "plist" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb" +dependencies = [ + "base64 0.23.1", + "indexmap 2.14.2", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.6" @@ -1295,6 +3220,65 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.15+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -1304,6 +3288,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.12" @@ -1318,7 +3311,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.21", "tokio", "tracing", "web-time", @@ -1340,7 +3333,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.21", "tinyvec", "tracing", "web-time", @@ -1369,6 +3362,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -1392,7 +3391,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ - "chacha20", + "chacha20 0.10.2", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -1431,13 +3430,61 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.2", +] + +[[package]] +name = "redox_users" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" +dependencies = [ + "libredox", + "thiserror 2.0.21", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1494,11 +3541,69 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", "webpki-roots", ] +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", +] + +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + [[package]] name = "ring" version = "0.17.14" @@ -1519,13 +3624,22 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", @@ -1579,6 +3693,66 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1593,7 +3767,7 @@ dependencies = [ "fastnbt", "futures", "hex", - "reqwest", + "reqwest 0.12.28", "scopenet-shared", "serde", "serde_json", @@ -1605,6 +3779,32 @@ dependencies = [ "zip", ] +[[package]] +name = "scopenet-launcher" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "chacha20poly1305", + "futures", + "keyring", + "rand 0.8.8", + "reqwest 0.12.28", + "scopenet-core", + "scopenet-shared", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tauri-plugin-opener", + "tauri-plugin-single-instance", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", +] + [[package]] name = "scopenet-panel" version = "0.1.0" @@ -1618,7 +3818,7 @@ dependencies = [ "jsonwebtoken", "percent-encoding", "rand 0.8.8", - "reqwest", + "reqwest 0.12.28", "scopenet-core", "scopenet-shared", "serde", @@ -1626,7 +3826,7 @@ dependencies = [ "sha1", "sqlx", "tempfile", - "thiserror", + "thiserror 2.0.21", "tokio", "tower", "tower-http", @@ -1645,6 +3845,71 @@ dependencies = [ "serde_json", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d" +dependencies = [ + "bitflags 2.13.2", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde" version = "1.0.229" @@ -1655,6 +3920,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + [[package]] name = "serde_bytes" version = "0.11.19" @@ -1685,6 +3962,17 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "serde_json" version = "1.0.151" @@ -1709,6 +3997,35 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1721,6 +4038,70 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f" +dependencies = [ + "base64 0.23.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.2", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "sha1" version = "0.10.7" @@ -1788,10 +4169,16 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 2.0.21", "time", ] +[[package]] +name = "siphasher" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" + [[package]] name = "slab" version = "0.4.12" @@ -1814,6 +4201,54 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "spin" version = "0.9.9" @@ -1852,7 +4287,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap", + "indexmap 2.14.2", "log", "memchr", "once_cell", @@ -1860,7 +4295,7 @@ dependencies = [ "serde", "sha2", "smallvec", - "thiserror", + "thiserror 2.0.21", "tokio", "tokio-stream", "tracing", @@ -1888,7 +4323,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.5.0", "hex", "once_cell", "proc-macro2", @@ -1922,7 +4357,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.21", "tracing", "url", ] @@ -1933,12 +4368,63 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "swift-rs" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45c444e496845d3f2a351146bff59aae4975b2280238df1dfaa0c7d1846f38e" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.119" @@ -1981,6 +4467,385 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37f381f4e048e6cdf038b5705f8cf14ad108279d46eb968140a7b291aba9400" +dependencies = [ + "bitflags 2.13.2", + "block2", + "core-foundation 0.10.1", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb20ef8e6f97d6fe03039666cd82861f09a38b56291fa2f0f9fe140023f61c1a" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest 0.13.5", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.21", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows 0.62.2", +] + +[[package]] +name = "tauri-build" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5ae674f48836f5dd2eeaf9e221c095bd3e78a8d0439c77ca93b48740a1b644" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3926b73ac01df7c14f19ff872cd56e67213a87acc0000ea37ddb300f3d11a3e3" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.18.1", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.119", + "tauri-utils", + "thiserror 2.0.21", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d21d0fa2c0529972ea6ecaf7404fcf40efac098eda0beb06ca682f41e33a6aa5" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9ff3ebb9fda56ceb93d46f6cbb126bae82951bf43498543a5163dde5def49a1" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "walkdir", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7527c5fd1678032d116c1a82e5d609c67370e56f858ae6cfd376bbca37fe0490" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.21", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "466eb32466fe5021b6728b06862f9ff14a9f7e1af890047654e03a96d7c0e1bb" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.21", + "toml 1.1.6+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4cf2c5723e6fe265a907591d5fae6b8b94edde39ad3876fdf425acf8d71f9b6" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.21", + "url", + "windows 0.61.3", + "zbus", +] + +[[package]] +name = "tauri-plugin-single-instance" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bed24ed0f31939002fad4568e04ba87ba717cd22f85318b867fc427ab88e11b" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.21", + "tokio", + "tracing", + "windows-sys 0.60.2", + "zbus", +] + +[[package]] +name = "tauri-runtime" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e6d5ef76985b32ff012a43b58df205d25a285e400ca878afcacddc5d5f3238" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.21", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.62.2", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa829eb69860cf1fb0bedb21d2d5f1effce3527d054a68ca1cd7355c654400a3" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.62.2", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd257082deff79550de3409ed146104aeaf86fe38649615b04a304144939677" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.21", + "toml 1.1.6+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.6+spec-1.1.0", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -1994,13 +4859,42 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tendril" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +dependencies = [ + "new_debug_unreachable", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -2131,6 +5025,102 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "1.1.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +dependencies = [ + "indexmap 2.14.2", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.2", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.2", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap 2.14.2", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tower" version = "0.5.3" @@ -2154,7 +5144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "async-compression", - "bitflags", + "bitflags 2.13.2", "bytes", "futures-core", "futures-util", @@ -2250,18 +5240,57 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tray-icon" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.21", + "windows-sys 0.61.2", +] + [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + [[package]] name = "unicase" version = "2.9.0" @@ -2274,6 +5303,22 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -2290,6 +5335,19 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b" +dependencies = [ + "icu_properties", + "regex", + "serde", + "url", ] [[package]] @@ -2322,12 +5380,48 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2343,6 +5437,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.129" @@ -2412,6 +5515,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.106" @@ -2432,6 +5548,62 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web_atoms" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + [[package]] name = "webpki-roots" version = "1.0.9" @@ -2441,6 +5613,143 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webview2-com" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f89fca7a704cee10dcb3654c1dbb8941d1783132f1917358af75bec37a7d7e6" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "webview2-com-sys" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a07132775117d6065853d9d1178157b8c90e228de47129d6bce2c7edebedfb" +dependencies = [ + "thiserror 2.0.21", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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-vibrancy" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111e51caca442cafd9bab396628ac4d814880eaea4e63dfd76a12e9f342b5580" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "windows-sys 0.61.2", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -2449,9 +5758,31 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -2476,19 +5807,63 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", ] [[package]] @@ -2497,7 +5872,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -2506,7 +5890,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2515,7 +5899,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", ] [[package]] @@ -2524,7 +5917,22 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2533,70 +5941,303 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "writeable" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +[[package]] +name = "wry" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a819957a01b3119af85e638a38d242af76dbc87d130dca67bfd0441072e21ff0" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.21", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + [[package]] name = "yoke" version = "0.8.3" @@ -2620,6 +6261,76 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.6", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + [[package]] name = "zerocopy" version = "0.8.59" @@ -2711,9 +6422,9 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap", + "indexmap 2.14.2", "memchr", - "thiserror", + "thiserror 2.0.21", "zopfli", ] @@ -2740,3 +6451,44 @@ dependencies = [ "log", "simd-adler32", ] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.6", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.6", + "winnow 1.0.4", +] diff --git a/Cargo.toml b/Cargo.toml index 5678c7b..7238d2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["crates/shared", "crates/core", "panel/server"] +members = ["crates/shared", "crates/core", "panel/server", "launcher/src-tauri"] [workspace.package] version = "0.1.0" diff --git a/crates/core/src/launch.rs b/crates/core/src/launch.rs index a714f9a..d6625ff 100644 --- a/crates/core/src/launch.rs +++ b/crates/core/src/launch.rs @@ -268,7 +268,10 @@ fn write_argfile(path: &Path, args: &[String]) -> Result<()> { Ok(()) } -pub fn spawn(cmd: &Command, java_major: u32) -> Result { +/// Start the game. With `capture` the game's stdout/stderr are piped back to +/// us (for the console); without it they're discarded, which is required +/// when the launcher exits while the game keeps running. +pub fn spawn(cmd: &Command, java_major: u32, capture: bool) -> Result { std::fs::create_dir_all(&cmd.cwd)?; let mut proc = tokio::process::Command::new(&cmd.java); let total_len: usize = cmd.args.iter().map(|a| a.len() + 3).sum(); @@ -280,10 +283,8 @@ pub fn spawn(cmd: &Command, java_major: u32) -> Result { } else { proc.args(&cmd.args); } - proc.current_dir(&cmd.cwd) - .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()); + let out = || if capture { std::process::Stdio::piped() } else { std::process::Stdio::null() }; + proc.current_dir(&cmd.cwd).stdin(std::process::Stdio::null()).stdout(out()).stderr(out()); #[cfg(windows)] proc.creation_flags(0x0800_0000); proc.spawn().with_context(|| format!("starting {}", cmd.java.display())) diff --git a/crates/core/tests/online.rs b/crates/core/tests/online.rs new file mode 100644 index 0000000..6dbc830 --- /dev/null +++ b/crates/core/tests/online.rs @@ -0,0 +1,81 @@ +//! Real-network installs against Mojang / Fabric / Forge / NeoForge. +//! Ignored by default; CI runs them with `cargo test -p scopenet-core --test online -- --ignored`. + +use scopenet_core::install::{install, InstallSpec}; +use scopenet_core::launch::{self, Auth, GcPreset, LaunchOptions}; +use scopenet_core::{progress, Layout}; +use scopenet_shared::Loader; + +async fn run(mc: &str, loader: Loader) { + let root = std::env::var("SCOPENET_TEST_ROOT").map(std::path::PathBuf::from).unwrap_or_else(|_| std::env::temp_dir().join("scopenet-online")); + let layout = Layout::new(&root); + let client = scopenet_core::http::client(); + let game_dir = layout.instance_dir(&format!("{mc}-{}", loader.as_str())); + let spec = InstallSpec { mc_version: mc.into(), loader, loader_version: None, java_override: None, game_dir: game_dir.clone(), concurrency: 16, deep_verify: false }; + let installed = install(&client, &layout, &spec, &progress::noop()).await.unwrap_or_else(|e| panic!("{mc} {loader:?}: {e:#}")); + + for path in installed.classpath(&layout) { + assert!(path.exists(), "missing classpath entry {}", path.display()); + } + assert!(installed.java.exists(), "java missing at {}", installed.java.display()); + + let opts = LaunchOptions { + auth: Auth { username: "CiBot".into(), uuid: scopenet_shared::offline_uuid("CiBot"), access_token: "0".into(), user_type: "legacy".into(), xuid: None }, + game_dir, + memory_min_mb: 512, + memory_max_mb: 2048, + gc: GcPreset::G1, + extra_jvm_args: vec![], + resolution: None, + fullscreen: false, + join_server: Some(("example.org".into(), 25565)), + version_label: "ci".into(), + launcher_name: "scopenet".into(), + launcher_version: "ci".into(), + }; + let cmd = launch::build(&installed, &layout, &opts); + let joined = cmd.args.join(" "); + assert!(!joined.contains("${"), "unsubstituted placeholder in: {joined}"); + + // The downloaded runtime must actually run. + let out = std::process::Command::new(scopenet_core::java::sibling_exe(&installed.java, false)).arg("-version").output().unwrap(); + assert!(out.status.success()); + println!("{mc} {loader:?}: OK ({} classpath entries, java {})", installed.classpath(&layout).len(), installed.java_major); +} + +#[tokio::test] +#[ignore] +async fn vanilla_latest_release() { + let m = scopenet_core::meta::mojang_manifest(&scopenet_core::http::client()).await.unwrap(); + run(&m.latest.release, Loader::Vanilla).await; +} + +#[tokio::test] +#[ignore] +async fn vanilla_legacy_1_8_9() { + run("1.8.9", Loader::Vanilla).await; +} + +#[tokio::test] +#[ignore] +async fn fabric_1_21_1() { + run("1.21.1", Loader::Fabric).await; +} + +#[tokio::test] +#[ignore] +async fn quilt_1_20_1() { + run("1.20.1", Loader::Quilt).await; +} + +#[tokio::test] +#[ignore] +async fn forge_1_20_1() { + run("1.20.1", Loader::Forge).await; +} + +#[tokio::test] +#[ignore] +async fn neoforge_1_21_1() { + run("1.21.1", Loader::NeoForge).await; +} diff --git a/launcher/index.html b/launcher/index.html new file mode 100644 index 0000000..cbc4f0e --- /dev/null +++ b/launcher/index.html @@ -0,0 +1,13 @@ + + + + + + + ScopeNet Launcher + + +
+ + + diff --git a/launcher/package-lock.json b/launcher/package-lock.json new file mode 100644 index 0000000..95442a3 --- /dev/null +++ b/launcher/package-lock.json @@ -0,0 +1,1501 @@ +{ + "name": "scopenet-launcher", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scopenet-launcher", + "version": "0.1.0", + "dependencies": { + "@fontsource-variable/inter": "^5.3.0", + "@fontsource-variable/jetbrains-mono": "^5.3.0", + "@fontsource-variable/outfit": "^5.3.0", + "@fontsource-variable/plus-jakarta-sans": "^5.3.0", + "@fontsource-variable/space-grotesk": "^5.3.0", + "@tauri-apps/api": "^2.12.0", + "@tauri-apps/plugin-dialog": "^2.8.0", + "@tauri-apps/plugin-opener": "^2.6.0" + }, + "devDependencies": { + "@lucide/svelte": "^1.48.0", + "@sveltejs/vite-plugin-svelte": "^7.3.1", + "@tauri-apps/cli": "^2.12.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.57.1", + "svelte-check": "^4.7.6", + "typescript": "~5.9.0", + "vite": "^8.3.1" + } + }, + "node_modules/@fontsource-variable/inter": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz", + "integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/jetbrains-mono": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz", + "integrity": "sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/outfit": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/outfit/-/outfit-5.3.0.tgz", + "integrity": "sha512-fPb5XnYK0k7SbR++HEmm4+ZqDszmLKQ+sPRUr8oQ/KNC0vy6r6hazJFe8iYozv4cqlHbvJ/JECkJWrfDodUMVQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/plus-jakarta-sans": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/plus-jakarta-sans/-/plus-jakarta-sans-5.3.0.tgz", + "integrity": "sha512-/l/4r0yyWK9JzAlmA0LiYgGgmJe/Gswt4jTJEzr5QhJfwMbvJZDmYWyW0M4X7yCK69BajMVlV/Lx8g7WDc1+sw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/space-grotesk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/space-grotesk/-/space-grotesk-5.3.0.tgz", + "integrity": "sha512-2IxmvfB08i9vnGB3Ym/AXvhRE+8XOjWMXIyDum03c+tPwH0FUoMNQfGpU8NXPxjbws0Vvss3AH0Zqt4oJBBAdw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lucide/svelte": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.48.0.tgz", + "integrity": "sha512-bLimVDjehxu3fpu9XEUpqEkBk2sJAHAT2sVcLHhlM70EdNsXuj9ZnYENWv5xgw3+05h4i5TirAxTiTgnZZf8gw==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.151.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", + "integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", + "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", + "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", + "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", + "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", + "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", + "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", + "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", + "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", + "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", + "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", + "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", + "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", + "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", + "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", + "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.13.tgz", + "integrity": "sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/load-config": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.3.tgz", + "integrity": "sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.3.1.tgz", + "integrity": "sha512-ZPsLN8B1e/En+Ak5s4V7srFDT532oS0qieLsQwu63NGKsS+iAjoO2Js1BochlHlglcU+Pt7WAO3C5Ee+4f6gVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "magic-string": "^1.0.0", + "obug": "^2.1.0", + "vitefu": "^1.1.2" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.46.4", + "vite": "^8.0.0-beta.7 || ^8.0.0" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.12.0.tgz", + "integrity": "sha512-fUSxFX8VABYe9W9K5ROdEaKVkNUFRcMSRofjruvaxztXA+I0XnQHcYHF8m/a6iExRUxsUf0HsFWhPBuH9IgzJw==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.12.0.tgz", + "integrity": "sha512-GIShnAR4C16kSTA8NO02fo8fOw/I5z5sAUEB5S2HlPHFnBkbihL4c9a/MpkxOm1+q5S+tFd3X4QUjHWxfyGzpQ==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.12.0", + "@tauri-apps/cli-darwin-x64": "2.12.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.12.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.12.0", + "@tauri-apps/cli-linux-arm64-musl": "2.12.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.12.0", + "@tauri-apps/cli-linux-x64-gnu": "2.12.0", + "@tauri-apps/cli-linux-x64-musl": "2.12.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.12.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.12.0", + "@tauri-apps/cli-win32-x64-msvc": "2.12.0" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.12.0.tgz", + "integrity": "sha512-v5J+jISihLGtih1+3yWagbsL+5dbLZORu4vaSI0vhY0Heb+MAW+T/1s1RyKxrQrwEPGBG5kqwfDDTnSbmVkuqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.12.0.tgz", + "integrity": "sha512-g27I07pfW/dMS8vcNqB9lamgKF0TqLlcy8e7t9qDGsB1iaeyjJWb2TgBF4tM9ZwLqPAZUXbsz28oXljO12WC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.12.0.tgz", + "integrity": "sha512-gXyQ710fNc99YuxFxNyeX9PLSrIpbtgBBj7GQo9tSeRT6fIK40q3/gm7X5jqZo7mWLR2m0R/2ALd3vAWo0Z32Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.12.0.tgz", + "integrity": "sha512-H9cyXuq9WgxoHh2NZ+R+oN+W78x5UwaBDEp0/fmqUUkaBoioCuOZErM9xKiIV6bpNAhmw1RoWZ49u37q/vaiAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.12.0.tgz", + "integrity": "sha512-5tFuk3JKMBa1M/iN4gN32OYlSfhY3aqb5PO3zCugxaT+sfecyMKpc3EM9VDbhmwWBE9QD4Tfj7wGhlqx2yy2pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.12.0.tgz", + "integrity": "sha512-LOrMyV+nb7pKq8P0BZtTBQuyzm1ORjn16R9MlupmxuT5LqxtuYJlw3VMwbaeXCToJ8jbP5u9JnkfmbqDlvETXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.12.0.tgz", + "integrity": "sha512-aMIkI+X9qvD2+k80QZB3tuOYT41/cEheikC510CwAIZ31KLXORsRASYYzM4mi1/58ICmtzSLFhCC08keir7COw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.12.0.tgz", + "integrity": "sha512-9gl3NhUbfuGhSKjeGCypKSdtoezOQRcDUrkhTWtLQEjnajets6peO6NadgI2DF0OzbiiFOzrnnIGMwBS+Ap6Iw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.12.0.tgz", + "integrity": "sha512-1W+v86wsYLuyBDN0iDessD4CD9o+yNyO0m8Dp3+/vfjiZrJ/v7R7/aTRBeikaq2tLGgxeag0gxnFggBNBGw91A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.12.0.tgz", + "integrity": "sha512-SsBjvq4f7YaBGqcWoE3cycoItPf0SrQeqFqWWgfxoeZWAIz01E31LDSeZ2p/etJiIY0R9oi045chkFfb0O7WGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.12.0.tgz", + "integrity": "sha512-9zzDKYUH4TKKBgRdDdVQX8fiRO5gJ8hD1nGsHU9DOHG5Jpm3arjbu9Xg1frR20EusG3Sl8X0dppoO/4EoUXUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.8.0.tgz", + "integrity": "sha512-HSSD5C8UFE9ijD1wue84F7jjaIReFmj8AAPtS+xyjYRltJafIc4BiWHY7SJ6vbbew6u+YljEJHfyNfy5VwywXg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.6.0.tgz", + "integrity": "sha512-KIl9qkBGyAUrbzfCtdk+NRPTXUOwpMyUKsKN57H6/qA0q8auVywXpfUyWClqX6I/iH8PHmqAUa/KK9YZPWVQew==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@tsconfig/svelte": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", + "integrity": "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.9.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.4.tgz", + "integrity": "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==", + "dev": true, + "license": "MIT" + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.4.0.tgz", + "integrity": "sha512-CQPcneEHw/czj32uwQC52nYnax8H4SXt25FCM+LIwGZ49cMIxpOj3bP8wmzcI+Z0HIy5KkClaPhRLfCySzwHPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.2.tgz", + "integrity": "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rolldown": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", + "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.151.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.11", + "@rolldown/binding-android-arm64": "1.2.11", + "@rolldown/binding-darwin-arm64": "1.2.11", + "@rolldown/binding-darwin-x64": "1.2.11", + "@rolldown/binding-freebsd-x64": "1.2.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", + "@rolldown/binding-linux-arm64-gnu": "1.2.11", + "@rolldown/binding-linux-arm64-musl": "1.2.11", + "@rolldown/binding-linux-ppc64-gnu": "1.2.11", + "@rolldown/binding-linux-s390x-gnu": "1.2.11", + "@rolldown/binding-linux-x64-gnu": "1.2.11", + "@rolldown/binding-linux-x64-musl": "1.2.11", + "@rolldown/binding-openharmony-arm64": "1.2.11", + "@rolldown/binding-win32-arm64-msvc": "1.2.11", + "@rolldown/binding-win32-x64-msvc": "1.2.11" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.57.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.57.1.tgz", + "integrity": "sha512-Uqj49lWKB+iSSnneuwiYYJ7MZgkB+eXr0LXBhv4uDuAkXqnWmq65Sxflfvp0Lc6MdKjMUxGaeOKWJqz5SNiVIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "@jridgewell/sourcemap-codec": "^1.6.0", + "@sveltejs/acorn-typescript": "^1.0.13", + "@types/estree": "^1.0.9", + "acorn": "^8.18.0", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.9.2", + "esm-env": "^1.2.1", + "esrap": "^2.3.6", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.6.tgz", + "integrity": "sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "^0.2.3", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.0.0 || ^6.0.0" + } + }, + "node_modules/svelte/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", + "integrity": "sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.9", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.5.tgz", + "integrity": "sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/launcher/package.json b/launcher/package.json new file mode 100644 index 0000000..bd79a93 --- /dev/null +++ b/launcher/package.json @@ -0,0 +1,33 @@ +{ + "name": "scopenet-launcher", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json", + "tauri": "tauri" + }, + "dependencies": { + "@fontsource-variable/inter": "^5.3.0", + "@fontsource-variable/jetbrains-mono": "^5.3.0", + "@fontsource-variable/outfit": "^5.3.0", + "@fontsource-variable/plus-jakarta-sans": "^5.3.0", + "@fontsource-variable/space-grotesk": "^5.3.0", + "@tauri-apps/api": "^2.12.0", + "@tauri-apps/plugin-dialog": "^2.8.0", + "@tauri-apps/plugin-opener": "^2.6.0" + }, + "devDependencies": { + "@lucide/svelte": "^1.48.0", + "@sveltejs/vite-plugin-svelte": "^7.3.1", + "@tauri-apps/cli": "^2.12.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.57.1", + "svelte-check": "^4.7.6", + "typescript": "~5.9.0", + "vite": "^8.3.1" + } +} diff --git a/launcher/src-tauri/Cargo.toml b/launcher/src-tauri/Cargo.toml new file mode 100644 index 0000000..a738ecb --- /dev/null +++ b/launcher/src-tauri/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "scopenet-launcher" +description = "ScopeNet Minecraft launcher" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +name = "scopenet_launcher_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +scopenet-shared.workspace = true +scopenet-core.workspace = true +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true +reqwest.workspace = true +futures.workspace = true +uuid.workspace = true +base64.workspace = true +rand.workspace = true +tracing.workspace = true +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } +tauri = { version = "2", features = [] } +tauri-plugin-dialog = "2" +tauri-plugin-opener = "2" +tauri-plugin-single-instance = "2" +chacha20poly1305 = "0.10" + +# Secrets are encrypted with a key kept in the OS credential store. +[target.'cfg(windows)'.dependencies] +keyring = { version = "3", features = ["windows-native"] } + +[target.'cfg(target_os = "macos")'.dependencies] +keyring = { version = "3", features = ["apple-native"] } diff --git a/launcher/src-tauri/build.rs b/launcher/src-tauri/build.rs new file mode 100644 index 0000000..3343069 --- /dev/null +++ b/launcher/src-tauri/build.rs @@ -0,0 +1,7 @@ +fn main() { + // Re-run when build-time branding changes (set by CI). + for var in ["SCOPENET_PANEL_URL", "SCOPENET_REPO", "SCOPENET_LOCK_PANEL"] { + println!("cargo:rerun-if-env-changed={var}"); + } + tauri_build::build() +} diff --git a/launcher/src-tauri/capabilities/default.json b/launcher/src-tauri/capabilities/default.json new file mode 100644 index 0000000..79318e5 --- /dev/null +++ b/launcher/src-tauri/capabilities/default.json @@ -0,0 +1,20 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Permissions for the main launcher window", + "windows": ["main"], + "permissions": [ + "core:default", + "core:window:allow-minimize", + "core:window:allow-toggle-maximize", + "core:window:allow-close", + "core:window:allow-hide", + "core:window:allow-show", + "core:window:allow-set-focus", + "core:window:allow-unminimize", + "core:window:allow-start-dragging", + "core:window:allow-is-maximized", + "dialog:allow-open", + "opener:allow-open-url" + ] +} diff --git a/launcher/src-tauri/icons/128x128.png b/launcher/src-tauri/icons/128x128.png new file mode 100644 index 0000000..ead83d9 Binary files /dev/null and b/launcher/src-tauri/icons/128x128.png differ diff --git a/launcher/src-tauri/icons/128x128@2x.png b/launcher/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..b4a477f Binary files /dev/null and b/launcher/src-tauri/icons/128x128@2x.png differ diff --git a/launcher/src-tauri/icons/32x32.png b/launcher/src-tauri/icons/32x32.png new file mode 100644 index 0000000..b97c12a Binary files /dev/null and b/launcher/src-tauri/icons/32x32.png differ diff --git a/launcher/src-tauri/icons/64x64.png b/launcher/src-tauri/icons/64x64.png new file mode 100644 index 0000000..6b35dac Binary files /dev/null and b/launcher/src-tauri/icons/64x64.png differ diff --git a/launcher/src-tauri/icons/icon.icns b/launcher/src-tauri/icons/icon.icns new file mode 100644 index 0000000..1472e68 Binary files /dev/null and b/launcher/src-tauri/icons/icon.icns differ diff --git a/launcher/src-tauri/icons/icon.ico b/launcher/src-tauri/icons/icon.ico new file mode 100644 index 0000000..b1ebe49 Binary files /dev/null and b/launcher/src-tauri/icons/icon.ico differ diff --git a/launcher/src-tauri/icons/icon.png b/launcher/src-tauri/icons/icon.png new file mode 100644 index 0000000..877cff5 Binary files /dev/null and b/launcher/src-tauri/icons/icon.png differ diff --git a/launcher/src-tauri/icons/logo.svg b/launcher/src-tauri/icons/logo.svg new file mode 100644 index 0000000..0635e30 --- /dev/null +++ b/launcher/src-tauri/icons/logo.svg @@ -0,0 +1 @@ + diff --git a/launcher/src-tauri/src/accounts.rs b/launcher/src-tauri/src/accounts.rs new file mode 100644 index 0000000..bd8535c --- /dev/null +++ b/launcher/src-tauri/src/accounts.rs @@ -0,0 +1,261 @@ +//! Player accounts: panel (username/password), Microsoft, and local offline. + +use crate::secrets::Secret; +use crate::state::AppState; +use anyhow::{anyhow, bail, Context, Result}; +use scopenet_shared::{offline_uuid, valid_username, AuthResponse, LoginRequest, RegisterRequest}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Account { + pub id: String, + /// "panel" | "microsoft" | "offline" + pub kind: String, + pub username: String, + pub uuid: String, + /// Panel accounts: which panel they belong to. + #[serde(default)] + pub panel_url: Option, + #[serde(default)] + pub role: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct AccountsFile { + pub accounts: Vec, + pub active: Option, +} + +impl AccountsFile { + pub fn load(path: &Path) -> Self { + std::fs::read(path).ok().and_then(|b| serde_json::from_slice(&b).ok()).unwrap_or_default() + } + + pub fn save(&self, path: &Path) -> Result<()> { + std::fs::write(path, serde_json::to_vec_pretty(self)?)?; + Ok(()) + } + + pub fn active(&self) -> Option<&Account> { + let id = self.active.as_ref()?; + self.accounts.iter().find(|a| &a.id == id) + } + + /// Add or replace (same kind + uuid + panel) and make it active. + pub fn upsert(&mut self, account: Account) -> Account { + if let Some(existing) = self + .accounts + .iter_mut() + .find(|a| a.kind == account.kind && a.uuid == account.uuid && a.panel_url == account.panel_url) + { + let id = existing.id.clone(); + *existing = Account { id: id.clone(), ..account }; + self.active = Some(id); + return existing.clone(); + } + self.active = Some(account.id.clone()); + self.accounts.push(account.clone()); + account + } +} + +fn now() -> i64 { + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64 +} + +async fn panel_error(resp: reqwest::Response) -> anyhow::Error { + let status = resp.status(); + let body: serde_json::Value = resp.json().await.unwrap_or_default(); + anyhow!("{}", body.get("error").and_then(|e| e.as_str()).map(String::from).unwrap_or_else(|| format!("panel returned {status}"))) +} + +pub async fn login_panel(state: &AppState, username: &str, password: &str) -> Result { + let panel = state.panel_url().ok_or_else(|| anyhow!("no panel configured"))?; + let resp = state + .http + .post(format!("{panel}/api/v1/auth/login")) + .json(&LoginRequest { username: username.trim().into(), password: password.into() }) + .send() + .await + .context("couldn't reach the server")?; + if !resp.status().is_success() { + return Err(panel_error(resp).await); + } + let auth: AuthResponse = resp.json().await?; + save_panel_account(state, &panel, auth) +} + +pub async fn register_panel(state: &AppState, username: &str, password: &str, email: Option) -> Result<(Option, bool)> { + let panel = state.panel_url().ok_or_else(|| anyhow!("no panel configured"))?; + let resp = state + .http + .post(format!("{panel}/api/v1/auth/register")) + .json(&RegisterRequest { username: username.trim().into(), password: password.into(), email }) + .send() + .await + .context("couldn't reach the server")?; + if !resp.status().is_success() { + return Err(panel_error(resp).await); + } + let auth: AuthResponse = resp.json().await?; + if auth.pending { + return Ok((None, true)); + } + Ok((Some(save_panel_account(state, &panel, auth)?), false)) +} + +fn save_panel_account(state: &AppState, panel: &str, auth: AuthResponse) -> Result { + let account = Account { + id: uuid::Uuid::new_v4().to_string(), + kind: "panel".into(), + username: auth.user.username.clone(), + uuid: auth.user.uuid.clone(), + panel_url: Some(panel.to_string()), + role: Some(auth.user.role.clone()), + }; + let account = state.accounts.write().unwrap().upsert(account); + state.secrets.set(&account.id, Secret { panel_token: Some(auth.token), ..Default::default() })?; + state.save_accounts()?; + Ok(account) +} + +pub fn add_offline(state: &AppState, username: &str) -> Result { + let allowed = state.manifest.read().unwrap().as_ref().map(|m| m.auth.offline_local).unwrap_or(true); + if !allowed { + bail!("offline accounts are disabled on this server"); + } + let name = username.trim(); + if !valid_username(name) { + bail!("usernames are 3–16 letters, numbers or underscores"); + } + let account = Account { + id: uuid::Uuid::new_v4().to_string(), + kind: "offline".into(), + username: name.into(), + uuid: offline_uuid(name), + panel_url: None, + role: None, + }; + let account = state.accounts.write().unwrap().upsert(account); + state.save_accounts()?; + Ok(account) +} + +pub fn ms_client_id(state: &AppState) -> Result { + state + .manifest + .read() + .unwrap() + .as_ref() + .and_then(|m| m.auth.microsoft.then(|| m.auth.microsoft_client_id.clone()).flatten()) + .filter(|c| !c.is_empty()) + .ok_or_else(|| anyhow!("Microsoft sign-in isn't enabled on this server")) +} + +pub fn save_ms_session(state: &AppState, session: scopenet_core::msa::MsaSession) -> Result { + let uuid = dashed(&session.profile.id); + let account = Account { + id: uuid::Uuid::new_v4().to_string(), + kind: "microsoft".into(), + username: session.profile.name.clone(), + uuid, + panel_url: None, + role: None, + }; + let account = state.accounts.write().unwrap().upsert(account); + state.secrets.set( + &account.id, + Secret { + ms_refresh_token: Some(session.refresh_token), + mc_access_token: Some(session.mc_access_token), + mc_expires_at: session.mc_expires_at, + xuid: session.xuid, + ..Default::default() + }, + )?; + state.save_accounts()?; + Ok(account) +} + +fn dashed(id: &str) -> String { + let id = id.replace('-', ""); + if id.len() != 32 { + return id; + } + format!("{}-{}-{}-{}-{}", &id[0..8], &id[8..12], &id[12..16], &id[16..20], &id[20..32]) +} + +/// Credentials passed to the game. +pub async fn game_auth(state: &AppState, account: &Account) -> Result { + use scopenet_core::launch::Auth; + match account.kind.as_str() { + "microsoft" => { + let mut secret = state.secrets.get(&account.id); + if secret.mc_access_token.is_none() || secret.mc_expires_at < now() + 300 { + let refresh = secret.ms_refresh_token.clone().ok_or_else(|| anyhow!("please sign in to Microsoft again"))?; + let client_id = ms_client_id(state)?; + let session = scopenet_core::msa::refresh(&state.http, &client_id, &refresh).await?; + if session.profile.name != account.username { + let mut accounts = state.accounts.write().unwrap(); + if let Some(a) = accounts.accounts.iter_mut().find(|a| a.id == account.id) { + a.username = session.profile.name.clone(); + } + } + state.save_accounts().ok(); + secret = Secret { + ms_refresh_token: Some(session.refresh_token), + mc_access_token: Some(session.mc_access_token), + mc_expires_at: session.mc_expires_at, + xuid: session.xuid, + ..secret + }; + state.secrets.set(&account.id, secret.clone())?; + } + let active_name = state.accounts.read().unwrap().accounts.iter().find(|a| a.id == account.id).map(|a| a.username.clone()); + Ok(Auth { + username: active_name.unwrap_or_else(|| account.username.clone()), + uuid: account.uuid.clone(), + access_token: secret.mc_access_token.unwrap_or_default(), + user_type: "msa".into(), + xuid: secret.xuid, + }) + } + _ => Ok(Auth { + username: account.username.clone(), + uuid: account.uuid.clone(), + // Offline mode: any token works; servers in offline mode ignore it. + access_token: "0".into(), + user_type: "legacy".into(), + xuid: None, + }), + } +} + +/// Panel token for requests, when the active account belongs to this panel. +pub fn panel_token(state: &AppState) -> Option { + let panel = state.panel_url()?; + let accounts = state.accounts.read().unwrap(); + let active = accounts.active()?; + if active.kind != "panel" || active.panel_url.as_deref() != Some(panel.as_str()) { + return None; + } + state.secrets.get(&active.id).panel_token +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn upsert_replaces_same_identity() { + let mut f = AccountsFile::default(); + let a = Account { id: "1".into(), kind: "offline".into(), username: "Steve".into(), uuid: "u".into(), panel_url: None, role: None }; + f.upsert(a.clone()); + f.upsert(Account { id: "2".into(), ..a.clone() }); + assert_eq!(f.accounts.len(), 1); + assert_eq!(f.active.as_deref(), Some("1")); + assert_eq!(dashed("b50ad385829d3141a2167e7d7539ba7f"), "b50ad385-829d-3141-a216-7e7d7539ba7f"); + } +} diff --git a/launcher/src-tauri/src/commands.rs b/launcher/src-tauri/src/commands.rs new file mode 100644 index 0000000..43a6e85 --- /dev/null +++ b/launcher/src-tauri/src/commands.rs @@ -0,0 +1,343 @@ +//! Everything the UI can ask the backend to do. + +use crate::accounts::{self, Account}; +use crate::game; +use crate::settings::Settings; +use crate::state::{build, AppState}; +use crate::updater; +use scopenet_core::msa::DeviceCode; +use scopenet_core::ping::ServerStatus; +use scopenet_shared::LauncherManifest; +use serde::Serialize; +use tauri::{AppHandle, Manager, State}; +use tauri_plugin_opener::OpenerExt; + +/// Commands return `Err(String)`; the UI shows it as-is. +type Res = Result; + +fn err(e: impl std::fmt::Display) -> String { + e.to_string() +} + +/// anyhow's alternate format keeps the context chain readable. +fn aerr(e: anyhow::Error) -> String { + format!("{e:#}") +} + +#[derive(Serialize)] +pub struct AppInfo { + version: String, + default_panel_url: Option, + panel_locked: bool, + repo: Option, + os: String, + arch: String, + total_ram_mb: u64, + data_dir: String, +} + +#[derive(Serialize)] +pub struct Bootstrap { + app: AppInfo, + panel_url: Option, + settings: Settings, + accounts: Vec, + active_account: Option, + manifest: Option, + game_running: Option, +} + +#[tauri::command] +pub fn bootstrap(state: State<'_, AppState>) -> Bootstrap { + let accounts = state.accounts.read().unwrap().clone(); + Bootstrap { + app: AppInfo { + version: env!("CARGO_PKG_VERSION").into(), + default_panel_url: build::PANEL_URL.filter(|u| !u.is_empty()).map(String::from), + panel_locked: build::panel_locked(), + repo: build::REPO.map(String::from), + os: std::env::consts::OS.into(), + arch: std::env::consts::ARCH.into(), + total_ram_mb: scopenet_core::sys::total_memory_mb(), + data_dir: state.data_dir.to_string_lossy().into_owned(), + }, + panel_url: state.panel_url(), + settings: state.settings.read().unwrap().clone(), + accounts: accounts.accounts, + active_account: accounts.active, + manifest: state.manifest.read().unwrap().clone(), + game_running: state.game.lock().unwrap().as_ref().map(|g| g.instance_id.clone()), + } +} + +async fn fetch_manifest(state: &AppState, panel: &str) -> anyhow::Result { + let mut req = state.http.get(format!("{panel}/api/v1/launcher/manifest")); + if let Some(t) = accounts::panel_token(state) { + req = req.bearer_auth(t); + } + let resp = req.send().await.map_err(|e| anyhow::anyhow!("couldn't reach {panel}: {e}"))?; + if !resp.status().is_success() { + anyhow::bail!("{panel} returned {}", resp.status()); + } + let m: LauncherManifest = resp.json().await.map_err(|_| anyhow::anyhow!("{panel} doesn't look like a ScopeNet panel"))?; + Ok(m) +} + +#[tauri::command] +pub async fn refresh_manifest(state: State<'_, AppState>) -> Res { + let panel = state.panel_url().ok_or("no panel configured")?; + let m = fetch_manifest(&state, &panel).await.map_err(aerr)?; + state.cache_manifest(&m); + Ok(m) +} + +#[tauri::command] +pub async fn set_panel_url(state: State<'_, AppState>, url: String) -> Res { + if build::panel_locked() { + return Err("this launcher is locked to its server".into()); + } + let mut url = url.trim().trim_end_matches('/').to_string(); + if !url.starts_with("http://") && !url.starts_with("https://") { + url = format!("https://{url}"); + } + let m = fetch_manifest(&state, &url).await.map_err(aerr)?; + state.settings.write().unwrap().panel_url = Some(url); + state.save_settings().map_err(aerr)?; + state.cache_manifest(&m); + Ok(m) +} + +#[tauri::command] +pub fn save_settings(state: State<'_, AppState>, settings: Settings) -> Res<()> { + let mut current = state.settings.write().unwrap(); + let panel = current.panel_url.clone(); + *current = Settings { panel_url: panel, ..settings }; + drop(current); + state.save_settings().map_err(aerr) +} + +// ---- accounts ---- + +#[tauri::command] +pub async fn login_panel(state: State<'_, AppState>, username: String, password: String) -> Res { + accounts::login_panel(&state, &username, &password).await.map_err(aerr) +} + +#[derive(Serialize)] +pub struct RegisterResult { + account: Option, + pending: bool, +} + +#[tauri::command] +pub async fn register_panel(state: State<'_, AppState>, username: String, password: String, email: Option) -> Res { + let (account, pending) = accounts::register_panel(&state, &username, &password, email).await.map_err(aerr)?; + Ok(RegisterResult { account, pending }) +} + +#[tauri::command] +pub fn add_offline(state: State<'_, AppState>, username: String) -> Res { + accounts::add_offline(&state, &username).map_err(aerr) +} + +#[tauri::command] +pub async fn ms_start(state: State<'_, AppState>) -> Res { + let client_id = accounts::ms_client_id(&state).map_err(aerr)?; + let code = scopenet_core::msa::start_device_code(&state.http, &client_id).await.map_err(aerr)?; + *state.device_code.lock().unwrap() = Some(code.clone()); + Ok(code) +} + +#[tauri::command] +pub async fn ms_finish(state: State<'_, AppState>) -> Res { + let client_id = accounts::ms_client_id(&state).map_err(aerr)?; + let code = state.device_code.lock().unwrap().clone().ok_or("start the sign-in first")?; + let (tx, rx) = tokio::sync::oneshot::channel(); + *state.ms_cancel.lock().unwrap() = Some(tx); + let session = tokio::select! { + r = scopenet_core::msa::finish_device_code(&state.http, &client_id, &code) => r.map_err(aerr)?, + _ = rx => return Err("cancelled".into()), + }; + *state.device_code.lock().unwrap() = None; + accounts::save_ms_session(&state, session).map_err(aerr) +} + +#[tauri::command] +pub fn ms_cancel(state: State<'_, AppState>) { + if let Some(tx) = state.ms_cancel.lock().unwrap().take() { + tx.send(()).ok(); + } +} + +#[tauri::command] +pub fn select_account(state: State<'_, AppState>, id: String) -> Res<()> { + let mut accounts = state.accounts.write().unwrap(); + if !accounts.accounts.iter().any(|a| a.id == id) { + return Err("account not found".into()); + } + accounts.active = Some(id); + drop(accounts); + state.save_accounts().map_err(aerr) +} + +#[tauri::command] +pub fn remove_account(state: State<'_, AppState>, id: String) -> Res<()> { + let mut accounts = state.accounts.write().unwrap(); + accounts.accounts.retain(|a| a.id != id); + if accounts.active.as_deref() == Some(id.as_str()) { + accounts.active = accounts.accounts.first().map(|a| a.id.clone()); + } + drop(accounts); + state.secrets.remove(&id).map_err(aerr)?; + state.save_accounts().map_err(aerr) +} + +// ---- game ---- + +#[tauri::command] +pub fn launch(app: AppHandle, state: State<'_, AppState>, instance_id: String) -> Res<()> { + start_task(app, &state, instance_id, true, false) +} + +#[tauri::command] +pub fn repair_instance(app: AppHandle, state: State<'_, AppState>, instance_id: String) -> Res<()> { + start_task(app, &state, instance_id, false, true) +} + +fn start_task(app: AppHandle, state: &AppState, instance_id: String, start: bool, deep: bool) -> Res<()> { + if state.game.lock().unwrap().is_some() { + return Err("Minecraft is already running".into()); + } + let mut task = state.task.lock().unwrap(); + if task.as_ref().is_some_and(|t| !t.is_finished()) { + return Err("already preparing an instance".into()); + } + let app2 = app.clone(); + let handle = tauri::async_runtime::spawn(async move { + if let Err(e) = game::run(app2.clone(), instance_id.clone(), start, deep).await { + tracing::error!("launch failed: {e:#}"); + game::emit_state(&app2, &instance_id, "error", Some(format!("{e:#}"))); + } + }); + *task = Some(handle.inner().abort_handle()); + Ok(()) +} + +#[tauri::command] +pub fn cancel_launch(app: AppHandle, state: State<'_, AppState>, instance_id: String) { + if let Some(t) = state.task.lock().unwrap().take() { + t.abort(); + } + game::emit_state(&app, &instance_id, "idle", Some("Cancelled".into())); +} + +#[tauri::command] +pub fn kill_game(state: State<'_, AppState>) { + if let Some(g) = state.game.lock().unwrap().take() { + g.kill.send(()).ok(); + } +} + +#[derive(Serialize)] +pub struct InstanceLocal { + installed: bool, + revision: i64, + size: u64, +} + +#[tauri::command] +pub async fn instance_local(state: State<'_, AppState>, instance_id: String) -> Res { + let dir = state.layout.instance_dir(&instance_id); + let s = scopenet_core::sync::load_state(&dir); + let size = tokio::task::spawn_blocking(move || scopenet_core::sys::dir_size(&dir)).await.unwrap_or(0); + Ok(InstanceLocal { installed: s.revision > 0, revision: s.revision, size }) +} + +#[tauri::command] +pub async fn ping_server(host: String, port: u16) -> Res { + scopenet_core::ping::ping(&host, port).await.map_err(aerr) +} + +#[tauri::command] +pub fn open_folder(app: AppHandle, state: State<'_, AppState>, kind: String, instance_id: Option) -> Res<()> { + let base = match &instance_id { + Some(id) => state.layout.instance_dir(id), + None => state.data_dir.clone(), + }; + let path = match kind.as_str() { + "instance" | "data" => base, + "logs" | "screenshots" | "mods" | "resourcepacks" | "shaderpacks" | "saves" | "crash-reports" => base.join(&kind), + _ => return Err("unknown folder".into()), + }; + std::fs::create_dir_all(&path).map_err(err)?; + app.opener().open_path(path.to_string_lossy(), None::<&str>).map_err(err) +} + +#[tauri::command] +pub fn open_file(app: AppHandle, path: String) -> Res<()> { + app.opener().open_path(path, None::<&str>).map_err(err) +} + +#[derive(Serialize)] +pub struct Storage { + shared: u64, + runtimes: u64, + instances: Vec<(String, u64)>, +} + +#[tauri::command] +pub async fn storage_info(state: State<'_, AppState>) -> Res { + let layout = state.layout.clone(); + tokio::task::spawn_blocking(move || { + use scopenet_core::sys::dir_size; + let shared = dir_size(&layout.libraries()) + dir_size(&layout.assets()) + dir_size(&layout.versions()); + let runtimes = dir_size(&layout.runtimes()); + let instances = std::fs::read_dir(layout.instances()) + .map(|rd| rd.filter_map(|e| e.ok()).map(|e| (e.file_name().to_string_lossy().into_owned(), dir_size(&e.path()))).collect()) + .unwrap_or_default(); + Storage { shared, runtimes, instances } + }) + .await + .map_err(err) +} + +#[tauri::command] +pub async fn delete_instance_data(state: State<'_, AppState>, instance_id: String) -> Res<()> { + if state.game.lock().unwrap().as_ref().is_some_and(|g| g.instance_id == instance_id) { + return Err("close the game first".into()); + } + let dir = state.layout.instance_dir(&instance_id); + tokio::fs::remove_dir_all(dir).await.or_else(|e| if e.kind() == std::io::ErrorKind::NotFound { Ok(()) } else { Err(e) }).map_err(err) +} + +#[tauri::command] +pub async fn clear_cache(state: State<'_, AppState>) -> Res<()> { + let cache = state.layout.cache(); + tokio::fs::remove_dir_all(cache).await.ok(); + Ok(()) +} + +#[tauri::command] +pub async fn detect_java(path: String) -> Option { + scopenet_core::java::detect_major(std::path::Path::new(&path)).await +} + +#[tauri::command] +pub async fn check_update(state: State<'_, AppState>) -> Res> { + updater::check(&state.http).await.map_err(aerr) +} + +#[tauri::command] +pub async fn install_update(app: AppHandle, state: State<'_, AppState>, url: String) -> Res<()> { + updater::download_and_run(&state.http, &url).await.map_err(aerr)?; + app.exit(0); + Ok(()) +} + +#[tauri::command] +pub fn show_window(app: AppHandle) { + if let Some(w) = app.get_webview_window("main") { + w.show().ok(); + w.set_focus().ok(); + } +} diff --git a/launcher/src-tauri/src/game.rs b/launcher/src-tauri/src/game.rs new file mode 100644 index 0000000..9f1ef4e --- /dev/null +++ b/launcher/src-tauri/src/game.rs @@ -0,0 +1,286 @@ +//! The Play button: install → sync → configure → start → watch. + +use crate::accounts; +use crate::state::{AppState, RunningGame}; +use anyhow::{anyhow, Context, Result}; +use scopenet_core::install::{self, InstallSpec}; +use scopenet_core::launch::{self, LaunchOptions}; +use scopenet_core::progress::{Event, Reporter, Stage}; +use scopenet_core::{options, servers_dat, sync}; +use scopenet_shared::{InstanceManifest, LaunchEvent}; +use serde::Serialize; +use std::collections::VecDeque; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; +use tauri::{AppHandle, Emitter, Manager}; +use tokio::io::{AsyncBufReadExt, BufReader}; + +#[derive(Serialize, Clone)] +pub struct StatePayload { + pub instance_id: String, + /// "preparing" | "running" | "idle" | "error" + pub state: String, + pub message: Option, +} + +#[derive(Serialize, Clone)] +pub struct ExitPayload { + pub instance_id: String, + pub code: Option, + pub crashed: bool, + pub tail: Vec, + pub crash_report: Option, +} + +pub fn emit_state(app: &AppHandle, id: &str, state: &str, message: Option) { + app.emit("launch://state", StatePayload { instance_id: id.into(), state: state.into(), message }).ok(); +} + +/// Fetch the instance definition, falling back to the last copy we saw so +/// the game still starts without internet. +async fn instance_manifest(state: &AppState, id: &str) -> Result { + let cache = state.layout.instance_dir(id).join(".scopenet").join("manifest.json"); + let fetched = async { + let panel = state.panel_url().ok_or_else(|| anyhow!("no panel configured"))?; + let mut req = state.http.get(format!("{panel}/api/v1/launcher/instances/{id}")); + if let Some(token) = accounts::panel_token(state) { + req = req.bearer_auth(token); + } + let resp = req.send().await?; + if resp.status().as_u16() == 404 { + anyhow::bail!("this instance no longer exists or you don't have access to it"); + } + Ok::<_, anyhow::Error>(resp.error_for_status()?.json::().await?) + } + .await; + match fetched { + Ok(m) => { + std::fs::create_dir_all(cache.parent().unwrap()).ok(); + std::fs::write(&cache, serde_json::to_vec(&m)?).ok(); + Ok(m) + } + Err(e) => { + let bytes = std::fs::read(&cache).map_err(|_| e)?; + tracing::warn!("panel unreachable, using cached instance manifest"); + Ok(serde_json::from_slice(&bytes)?) + } + } +} + +fn reporter(app: &AppHandle) -> Reporter { + let app = app.clone(); + Arc::new(move |ev: Event| { + app.emit("launch://progress", ev).ok(); + }) +} + +/// Install/verify an instance and (when `start`) run the game. +pub async fn run(app: AppHandle, instance_id: String, start: bool, deep: bool) -> Result<()> { + let state = app.state::(); + let report = reporter(&app); + emit_state(&app, &instance_id, "preparing", None); + + let account = state.accounts.read().unwrap().active().cloned(); + let account = match (start, account) { + (true, None) => anyhow::bail!("sign in first"), + (_, a) => a, + }; + let auth = match &account { + Some(a) if start => Some(accounts::game_auth(&state, a).await?), + _ => None, + }; + + let manifest = instance_manifest(&state, &instance_id).await?; + let inst = &manifest.instance; + let settings = state.settings.read().unwrap().clone(); + let advanced = state.manifest.read().unwrap().as_ref().map(|m| m.branding.features.allow_advanced_java).unwrap_or(true); + let ov = settings.instance_overrides.get(&instance_id).cloned().unwrap_or_default(); + let game_dir = state.layout.instance_dir(&instance_id); + let panel = state.panel_url().unwrap_or_default(); + + let java_override = if advanced { + ov.java_path.clone().or(settings.java_path.clone()).filter(|p| !p.trim().is_empty()).map(PathBuf::from) + } else { + None + }; + + let spec = InstallSpec { + mc_version: inst.mc_version.clone(), + loader: inst.loader, + loader_version: inst.loader_version.clone(), + java_override, + game_dir: game_dir.clone(), + concurrency: settings.concurrent_downloads.clamp(1, 32) as usize, + deep_verify: deep, + }; + let installed = install::install(&state.http, &state.layout, &spec, &report).await?; + sync::sync(&state.http, &panel, &game_dir, inst.revision, &manifest.files, spec.concurrency, deep, &report).await?; + + if let Some(server) = &inst.server { + if server.inject && !server.address.is_empty() { + let name = if server.name.is_empty() { inst.name.clone() } else { server.name.clone() }; + servers_dat::upsert(&game_dir, &name, &servers_dat::format_address(&server.address, server.port)).ok(); + } + } + if settings.keybinds_enabled && installed.version.is_modern() && !settings.keybinds.is_empty() { + options::apply_keybinds(&game_dir, &settings.keybinds).ok(); + } + + if !start { + emit_state(&app, &instance_id, "idle", Some("Instance is up to date".into())); + return Ok(()); + } + let auth = auth.unwrap(); + + report(Event::Stage { stage: Stage::Launching, label: "Starting Minecraft".into() }); + let pick = |a: u32, b: u32, c: u32| if a > 0 { a } else if b > 0 { b } else { c }; + let max = pick(ov.memory_max_mb, settings.memory_max_mb, inst.memory.max_mb).max(512); + let min = pick(ov.memory_min_mb, settings.memory_min_mb, inst.memory.min_mb).min(max); + let mut jvm = launch::split_args(&inst.jvm_args); + if advanced { + jvm.extend(launch::split_args(&settings.jvm_args)); + jvm.extend(launch::split_args(&ov.jvm_args)); + } + let branding = state.manifest.read().unwrap().as_ref().map(|m| m.branding.clone()).unwrap_or_default(); + let opts = LaunchOptions { + auth: auth.clone(), + game_dir: game_dir.clone(), + memory_min_mb: min, + memory_max_mb: max, + gc: settings.gc, + extra_jvm_args: jvm, + resolution: settings.custom_resolution.then_some((settings.width, settings.height)), + fullscreen: settings.fullscreen, + join_server: inst.server.as_ref().filter(|s| s.auto_join && !s.address.is_empty()).map(|s| (s.address.clone(), s.port)), + version_label: if branding.version_label.is_empty() { branding.name.clone() } else { branding.version_label.clone() }, + launcher_name: "scopenet".into(), + launcher_version: env!("CARGO_PKG_VERSION").into(), + }; + let cmd = launch::build(&installed, &state.layout, &opts); + tracing::info!("launching: {} {}", cmd.java.display(), launch::redact(&cmd.args, &auth.access_token).join(" ")); + + let close_after = settings.after_launch == "close"; + let mut child = launch::spawn(&cmd, installed.java_major, !close_after).context("couldn't start Java")?; + + if settings.send_stats && !panel.is_empty() { + let http = state.http.clone(); + let token = accounts::panel_token(&state); + let ev = LaunchEvent { instance_id: instance_id.clone(), kind: "launch".into(), username: Some(auth.username.clone()) }; + tokio::spawn(async move { + let mut req = http.post(format!("{panel}/api/v1/launcher/events")).json(&ev); + if let Some(t) = token { + req = req.bearer_auth(t); + } + req.send().await.ok(); + }); + } + + if close_after { + tokio::time::sleep(Duration::from_millis(1500)).await; + app.exit(0); + return Ok(()); + } + + let (kill_tx, kill_rx) = tokio::sync::oneshot::channel(); + *state.game.lock().unwrap() = Some(RunningGame { instance_id: instance_id.clone(), kill: kill_tx }); + emit_state(&app, &instance_id, "running", None); + + if let Some(window) = app.get_webview_window("main") { + match settings.after_launch.as_str() { + "hide" => { + window.hide().ok(); + } + "minimize" => { + window.minimize().ok(); + } + _ => {} + } + } + + // Stream logs to the UI in small batches (cheap IPC), keep a tail for crash reports. + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); + for stream in [child.stdout.take().map(|s| Box::new(s) as Box), child.stderr.take().map(|s| Box::new(s) as Box)].into_iter().flatten() { + let tx = tx.clone(); + tokio::spawn(async move { + let mut lines = BufReader::new(stream).lines(); + while let Ok(Some(line)) = lines.next_line().await { + if tx.send(line).is_err() { + break; + } + } + }); + } + drop(tx); + + let app2 = app.clone(); + let id2 = instance_id.clone(); + tokio::spawn(async move { + let mut tail: VecDeque = VecDeque::with_capacity(200); + let mut batch: Vec = Vec::new(); + let mut kill_rx = kill_rx; + let mut tick = tokio::time::interval(Duration::from_millis(150)); + let mut streams_open = true; + let mut kill_armed = true; + let mut killed = false; + let status = loop { + tokio::select! { + line = rx.recv(), if streams_open => match line { + Some(l) => { + if tail.len() == 200 { tail.pop_front(); } + tail.push_back(l.clone()); + batch.push(l); + } + None => streams_open = false, + }, + _ = tick.tick() => { + if !batch.is_empty() { + app2.emit("game://log", std::mem::take(&mut batch)).ok(); + } + }, + res = &mut kill_rx, if kill_armed => { + kill_armed = false; + if res.is_ok() { + killed = true; + child.kill().await.ok(); + } + }, + status = child.wait() => break status.ok(), + } + }; + // Drain whatever is left. + while let Ok(l) = rx.try_recv() { + tail.push_back(l.clone()); + batch.push(l); + } + if !batch.is_empty() { + app2.emit("game://log", batch).ok(); + } + let code = status.and_then(|s| s.code()); + let crashed = !killed && code.is_some_and(|c| c != 0); + let crash_report = if crashed { newest_crash_report(&app2, &id2) } else { None }; + let st = app2.state::(); + *st.game.lock().unwrap() = None; + if let Some(w) = app2.get_webview_window("main") { + w.show().ok(); + w.unminimize().ok(); + if crashed { + w.set_focus().ok(); + } + } + app2.emit("game://exit", ExitPayload { instance_id: id2.clone(), code, crashed, tail: tail.into_iter().collect(), crash_report }).ok(); + emit_state(&app2, &id2, "idle", None); + }); + Ok(()) +} + +fn newest_crash_report(app: &AppHandle, id: &str) -> Option { + let dir = app.state::().layout.instance_dir(id).join("crash-reports"); + let newest = std::fs::read_dir(dir) + .ok()? + .filter_map(|e| e.ok()) + .filter_map(|e| Some((e.metadata().ok()?.modified().ok()?, e.path()))) + .max_by_key(|(t, _)| *t)?; + // Only if it was written in the last few minutes (i.e. by this session). + (newest.0.elapsed().ok()? < Duration::from_secs(300)).then(|| newest.1.to_string_lossy().into_owned()) +} diff --git a/launcher/src-tauri/src/lib.rs b/launcher/src-tauri/src/lib.rs new file mode 100644 index 0000000..cbcda4e --- /dev/null +++ b/launcher/src-tauri/src/lib.rs @@ -0,0 +1,78 @@ +//! ScopeNet launcher (Tauri shell). The heavy lifting lives in +//! `scopenet-core`; this crate wires it to the UI. + +mod accounts; +mod commands; +mod game; +mod secrets; +mod settings; +mod state; +mod updater; + +use tauri::Manager; + +pub fn run() { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into())) + .init(); + + tauri::Builder::default() + // A second launch just focuses the existing window. + .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { + if let Some(w) = app.get_webview_window("main") { + w.show().ok(); + w.unminimize().ok(); + w.set_focus().ok(); + } + })) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_opener::init()) + .setup(|app| { + let data_dir = app.path().app_data_dir()?; + app.manage(state::AppState::new(data_dir)); + // The window starts hidden and the UI reveals it once painted (no + // white flash). If the UI never does, show it anyway. + let handle = app.handle().clone(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs(4)); + if let Some(w) = handle.get_webview_window("main") { + if !w.is_visible().unwrap_or(true) { + tracing::warn!("UI didn't signal ready; showing window"); + w.show().ok(); + } + } + }); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + commands::bootstrap, + commands::refresh_manifest, + commands::set_panel_url, + commands::save_settings, + commands::login_panel, + commands::register_panel, + commands::add_offline, + commands::ms_start, + commands::ms_finish, + commands::ms_cancel, + commands::select_account, + commands::remove_account, + commands::launch, + commands::repair_instance, + commands::cancel_launch, + commands::kill_game, + commands::instance_local, + commands::ping_server, + commands::open_folder, + commands::open_file, + commands::storage_info, + commands::delete_instance_data, + commands::clear_cache, + commands::detect_java, + commands::check_update, + commands::install_update, + commands::show_window, + ]) + .run(tauri::generate_context!()) + .expect("error while running the launcher"); +} diff --git a/launcher/src-tauri/src/main.rs b/launcher/src-tauri/src/main.rs new file mode 100644 index 0000000..61a622c --- /dev/null +++ b/launcher/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Hide the console window on Windows release builds. +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + scopenet_launcher_lib::run() +} diff --git a/launcher/src-tauri/src/secrets.rs b/launcher/src-tauri/src/secrets.rs new file mode 100644 index 0000000..7ec0120 --- /dev/null +++ b/launcher/src-tauri/src/secrets.rs @@ -0,0 +1,132 @@ +//! Encrypted storage for tokens (panel sessions, Microsoft refresh tokens). +//! +//! Secrets live in `secrets.bin`, encrypted with ChaCha20-Poly1305. The key +//! is kept in the OS credential store (Windows Credential Manager / macOS +//! Keychain), so copying the data folder to another machine doesn't leak +//! sessions. Where no credential store exists we fall back to a key file. + +use anyhow::{anyhow, Result}; +use base64::Engine; +use chacha20poly1305::aead::{Aead, KeyInit}; +use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; +use rand::RngCore; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Mutex; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(default)] +pub struct Secret { + pub panel_token: Option, + pub ms_refresh_token: Option, + pub mc_access_token: Option, + pub mc_expires_at: i64, + pub xuid: Option, +} + +pub struct Secrets { + path: PathBuf, + key: [u8; 32], + data: Mutex>, +} + +const SERVICE: &str = "net.scopenet.launcher"; + +fn load_or_create_key(dir: &std::path::Path) -> [u8; 32] { + let b64 = base64::engine::general_purpose::STANDARD; + #[cfg(any(windows, target_os = "macos"))] + { + if let Ok(entry) = keyring::Entry::new(SERVICE, "secrets-key") { + if let Ok(existing) = entry.get_password() { + if let Ok(bytes) = b64.decode(existing) { + if let Ok(key) = <[u8; 32]>::try_from(bytes.as_slice()) { + return key; + } + } + } + let mut key = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut key); + if entry.set_password(&b64.encode(key)).is_ok() { + return key; + } + tracing::warn!("credential store unavailable, falling back to a key file"); + } + } + let _ = SERVICE; + let path = dir.join("secrets.key"); + if let Ok(bytes) = std::fs::read(&path).map(|b| b64.decode(b).unwrap_or_default()) { + if let Ok(key) = <[u8; 32]>::try_from(bytes.as_slice()) { + return key; + } + } + let mut key = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut key); + std::fs::write(&path, b64.encode(key)).ok(); + key +} + +impl Secrets { + pub fn open(dir: &std::path::Path) -> Self { + let key = load_or_create_key(dir); + let path = dir.join("secrets.bin"); + let data = std::fs::read(&path).ok().and_then(|raw| decrypt(&key, &raw).ok()).unwrap_or_default(); + Self { path, key, data: Mutex::new(data) } + } + + pub fn get(&self, id: &str) -> Secret { + self.data.lock().unwrap().get(id).cloned().unwrap_or_default() + } + + pub fn set(&self, id: &str, secret: Secret) -> Result<()> { + let mut data = self.data.lock().unwrap(); + data.insert(id.to_string(), secret); + self.flush(&data) + } + + pub fn remove(&self, id: &str) -> Result<()> { + let mut data = self.data.lock().unwrap(); + data.remove(id); + self.flush(&data) + } + + fn flush(&self, data: &HashMap) -> Result<()> { + let plain = serde_json::to_vec(data)?; + let cipher = ChaCha20Poly1305::new(Key::from_slice(&self.key)); + let mut nonce = [0u8; 12]; + rand::thread_rng().fill_bytes(&mut nonce); + let mut out = nonce.to_vec(); + out.extend(cipher.encrypt(Nonce::from_slice(&nonce), plain.as_slice()).map_err(|_| anyhow!("encryption failed"))?); + let tmp = self.path.with_extension("bin.tmp"); + std::fs::write(&tmp, out)?; + std::fs::rename(tmp, &self.path)?; + Ok(()) + } +} + +fn decrypt(key: &[u8; 32], raw: &[u8]) -> Result> { + if raw.len() < 13 { + return Err(anyhow!("secrets file too short")); + } + let cipher = ChaCha20Poly1305::new(Key::from_slice(key)); + let plain = cipher.decrypt(Nonce::from_slice(&raw[..12]), &raw[12..]).map_err(|_| anyhow!("can't decrypt secrets"))?; + Ok(serde_json::from_slice(&plain)?) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn roundtrip() { + let dir = std::env::temp_dir().join(format!("scopenet-secrets-{}", uuid::Uuid::new_v4())); + std::fs::create_dir_all(&dir).unwrap(); + let s = Secrets::open(&dir); + s.set("a", Secret { panel_token: Some("tok".into()), ..Default::default() }).unwrap(); + let raw = std::fs::read(dir.join("secrets.bin")).unwrap(); + assert!(!String::from_utf8_lossy(&raw).contains("tok"), "stored encrypted"); + let again = Secrets::open(&dir); + assert_eq!(again.get("a").panel_token.as_deref(), Some("tok")); + std::fs::remove_dir_all(dir).ok(); + } +} diff --git a/launcher/src-tauri/src/settings.rs b/launcher/src-tauri/src/settings.rs new file mode 100644 index 0000000..a956d1e --- /dev/null +++ b/launcher/src-tauri/src/settings.rs @@ -0,0 +1,107 @@ +//! Player settings, stored as JSON in the app data directory. + +use scopenet_core::launch::GcPreset; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; +use std::path::Path; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +#[serde(default)] +pub struct InstanceOverride { + /// 0 = use the global / instance default. + pub memory_max_mb: u32, + pub memory_min_mb: u32, + pub jvm_args: String, + pub java_path: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub struct Settings { + /// Panel URL chosen by the player (ignored when one is baked in and locked). + pub panel_url: Option, + pub selected_instance: Option, + + // ---- Game ---- + /// 0 = use the value the admin set for the instance. + pub memory_max_mb: u32, + pub memory_min_mb: u32, + pub custom_resolution: bool, + pub width: u32, + pub height: u32, + pub fullscreen: bool, + /// "minimize" | "hide" | "keep" | "close" + pub after_launch: String, + pub show_console: bool, + + // ---- Java ---- + pub java_path: Option, + pub gc: GcPreset, + pub jvm_args: String, + + // ---- Appearance ---- + /// "server" (admin's theme) | "custom" + pub theme_mode: String, + pub accent: Option, + pub background_video: bool, + pub reduce_motion: bool, + /// None = follow the admin's setting. + pub glass: Option, + pub ui_scale: u32, + + // ---- Launcher ---- + pub concurrent_downloads: u32, + pub check_updates: bool, + pub send_stats: bool, + + // ---- Controls ---- + pub keybinds_enabled: bool, + pub keybinds: BTreeMap, + + pub instance_overrides: HashMap, +} + +impl Default for Settings { + fn default() -> Self { + Self { + panel_url: None, + selected_instance: None, + memory_max_mb: 0, + memory_min_mb: 0, + custom_resolution: false, + width: 1280, + height: 720, + fullscreen: false, + after_launch: "minimize".into(), + show_console: false, + java_path: None, + gc: GcPreset::G1, + jvm_args: String::new(), + theme_mode: "server".into(), + accent: None, + background_video: true, + reduce_motion: false, + glass: None, + ui_scale: 100, + concurrent_downloads: 12, + check_updates: true, + send_stats: true, + keybinds_enabled: false, + keybinds: BTreeMap::new(), + instance_overrides: HashMap::new(), + } + } +} + +impl Settings { + pub fn load(path: &Path) -> Self { + std::fs::read(path).ok().and_then(|b| serde_json::from_slice(&b).ok()).unwrap_or_default() + } + + pub fn save(&self, path: &Path) -> anyhow::Result<()> { + let tmp = path.with_extension("json.tmp"); + std::fs::write(&tmp, serde_json::to_vec_pretty(self)?)?; + std::fs::rename(tmp, path)?; + Ok(()) + } +} diff --git a/launcher/src-tauri/src/state.rs b/launcher/src-tauri/src/state.rs new file mode 100644 index 0000000..09a19b6 --- /dev/null +++ b/launcher/src-tauri/src/state.rs @@ -0,0 +1,87 @@ +use crate::accounts::AccountsFile; +use crate::secrets::Secrets; +use crate::settings::Settings; +use scopenet_core::msa::DeviceCode; +use scopenet_core::Layout; +use scopenet_shared::LauncherManifest; +use std::path::PathBuf; +use std::sync::{Mutex, RwLock}; + +/// Values baked in at build time by CI (see `.github/workflows/release.yml`). +pub mod build { + /// Default panel URL for this build. + pub const PANEL_URL: Option<&str> = option_env!("SCOPENET_PANEL_URL"); + /// `owner/repo` used to check GitHub releases for updates. + pub const REPO: Option<&str> = option_env!("SCOPENET_REPO"); + /// When set to "1", players can't change the panel URL. + pub const LOCK_PANEL: Option<&str> = option_env!("SCOPENET_LOCK_PANEL"); + + pub fn panel_locked() -> bool { + LOCK_PANEL == Some("1") && PANEL_URL.is_some_and(|u| !u.is_empty()) + } +} + +pub struct RunningGame { + pub instance_id: String, + pub kill: tokio::sync::oneshot::Sender<()>, +} + +pub struct AppState { + pub data_dir: PathBuf, + pub layout: Layout, + pub http: reqwest::Client, + pub settings: RwLock, + pub accounts: RwLock, + pub secrets: Secrets, + pub manifest: RwLock>, + pub device_code: Mutex>, + pub ms_cancel: Mutex>>, + /// Install/launch pipeline in progress (abortable). + pub task: Mutex>, + pub game: Mutex>, +} + +impl AppState { + pub fn new(data_dir: PathBuf) -> Self { + std::fs::create_dir_all(&data_dir).ok(); + let settings = Settings::load(&data_dir.join("settings.json")); + let accounts = AccountsFile::load(&data_dir.join("accounts.json")); + let manifest = std::fs::read(data_dir.join("manifest.json")).ok().and_then(|b| serde_json::from_slice(&b).ok()); + Self { + layout: Layout::new(data_dir.join("minecraft")), + http: scopenet_core::http::client(), + secrets: Secrets::open(&data_dir), + settings: RwLock::new(settings), + accounts: RwLock::new(accounts), + manifest: RwLock::new(manifest), + device_code: Mutex::new(None), + ms_cancel: Mutex::new(None), + task: Mutex::new(None), + game: Mutex::new(None), + data_dir, + } + } + + /// The panel this launcher talks to (baked-in when locked). + pub fn panel_url(&self) -> Option { + let chosen = self.settings.read().unwrap().panel_url.clone().filter(|u| !u.is_empty()); + let baked = build::PANEL_URL.filter(|u| !u.is_empty()).map(String::from); + let url = if build::panel_locked() { baked } else { chosen.or(baked) }; + url.map(|u| u.trim_end_matches('/').to_string()) + } + + pub fn save_settings(&self) -> anyhow::Result<()> { + self.settings.read().unwrap().save(&self.data_dir.join("settings.json")) + } + + pub fn save_accounts(&self) -> anyhow::Result<()> { + self.accounts.read().unwrap().save(&self.data_dir.join("accounts.json")) + } + + pub fn cache_manifest(&self, m: &LauncherManifest) { + *self.manifest.write().unwrap() = Some(m.clone()); + if let Ok(bytes) = serde_json::to_vec(m) { + std::fs::write(self.data_dir.join("manifest.json"), bytes).ok(); + } + } +} diff --git a/launcher/src-tauri/src/updater.rs b/launcher/src-tauri/src/updater.rs new file mode 100644 index 0000000..fe80dc5 --- /dev/null +++ b/launcher/src-tauri/src/updater.rs @@ -0,0 +1,93 @@ +//! Self-update via GitHub Releases: find a newer `*-setup.exe`, download +//! it and hand over to the installer. + +use crate::state::build; +use anyhow::{anyhow, Context, Result}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize)] +pub struct UpdateInfo { + pub version: String, + pub notes: String, + pub url: String, + pub size: u64, +} + +#[derive(Deserialize)] +struct Release { + tag_name: String, + #[serde(default)] + body: String, + #[serde(default)] + draft: bool, + #[serde(default)] + prerelease: bool, + assets: Vec, +} + +#[derive(Deserialize)] +struct Asset { + name: String, + browser_download_url: String, + size: u64, +} + +fn parse(v: &str) -> Vec { + v.trim_start_matches('v').split(['.', '-', '+']).map_while(|p| p.parse().ok()).collect() +} + +pub fn is_newer(candidate: &str, current: &str) -> bool { + parse(candidate) > parse(current) +} + +pub async fn check(http: &reqwest::Client) -> Result> { + let Some(repo) = build::REPO.filter(|r| !r.is_empty()) else { return Ok(None) }; + let release: Release = http + .get(format!("https://api.github.com/repos/{repo}/releases/latest")) + .header("Accept", "application/vnd.github+json") + .send() + .await? + .error_for_status()? + .json() + .await?; + if release.draft || release.prerelease || !is_newer(&release.tag_name, env!("CARGO_PKG_VERSION")) { + return Ok(None); + } + let asset = release + .assets + .iter() + .find(|a| a.name.to_lowercase().ends_with("-setup.exe")) + .ok_or_else(|| anyhow!("release {} has no installer", release.tag_name))?; + Ok(Some(UpdateInfo { + version: release.tag_name.trim_start_matches('v').to_string(), + notes: release.body, + url: asset.browser_download_url.clone(), + size: asset.size, + })) +} + +/// Download the installer and start it. The caller exits the app afterwards. +pub async fn download_and_run(http: &reqwest::Client, url: &str) -> Result<()> { + if !url.starts_with("https://github.com/") && !url.starts_with("https://objects.githubusercontent.com/") { + anyhow::bail!("refusing to download an update from an unexpected host"); + } + let dest = std::env::temp_dir().join("scopenet-launcher-setup.exe"); + let dl = scopenet_core::http::Download::new(url, dest.clone(), None, None); + scopenet_core::http::download_one(http, &dl, &|_| {}).await.context("downloading update")?; + // /P = passive (progress only), /R = relaunch when done (Tauri NSIS). + std::process::Command::new(&dest).args(["/P", "/R"]).spawn().context("starting the installer")?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn versions() { + assert!(is_newer("v0.2.0", "0.1.9")); + assert!(is_newer("1.0.0", "0.9.12")); + assert!(!is_newer("v0.1.0", "0.1.0")); + assert!(!is_newer("0.1.0-beta", "0.1.0")); + } +} diff --git a/launcher/src-tauri/tauri.conf.json b/launcher/src-tauri/tauri.conf.json new file mode 100644 index 0000000..38df9c5 --- /dev/null +++ b/launcher/src-tauri/tauri.conf.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "ScopeNet Launcher", + "version": "0.1.0", + "identifier": "net.scopenet.launcher", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "../dist" + }, + "app": { + "withGlobalTauri": false, + "windows": [ + { + "label": "main", + "title": "ScopeNet Launcher", + "width": 1120, + "height": 700, + "minWidth": 960, + "minHeight": 600, + "decorations": false, + "shadow": true, + "center": true, + "visible": false, + "backgroundColor": "#0b0d17" + } + ], + "security": { + "csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: http: data: blob:; media-src 'self' https: http: blob:; font-src 'self' data:; connect-src 'self' ipc: http://ipc.localhost" + } + }, + "bundle": { + "active": true, + "targets": ["nsis"], + "publisher": "ScopeNet", + "category": "Game", + "shortDescription": "Minecraft launcher", + "longDescription": "A fast, customisable Minecraft launcher connected to your community's admin panel.", + "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], + "windows": { + "webviewInstallMode": { "type": "downloadBootstrapper", "silent": true }, + "nsis": { + "installMode": "currentUser", + "displayLanguageSelector": false, + "installerIcon": "icons/icon.ico" + } + } + } +} diff --git a/launcher/src/App.svelte b/launcher/src/App.svelte new file mode 100644 index 0000000..011df9a --- /dev/null +++ b/launcher/src/App.svelte @@ -0,0 +1,73 @@ + + + + +
+ + {#if fatal && !app.manifest} +

Can't reach the server

{fatal}

+ {:else if !app.ready} +
+ {:else if !app.panelUrl || !app.manifest} + + {:else if app.accounts.length === 0} + + {:else} +
+ +
+ {#if app.view === 'settings'}{:else}{/if} +
+
+ {/if} +
+ +{#if app.update && !updateDismissed && app.view !== 'settings' && app.accounts.length} +
+ + Launcher {app.update.version} is available + + +
+{/if} + + + + + + + + + diff --git a/launcher/src/app.css b/launcher/src/app.css new file mode 100644 index 0000000..c700fc1 --- /dev/null +++ b/launcher/src/app.css @@ -0,0 +1,154 @@ +/* Sizes are in rem so the "UI scale" setting scales everything (1rem = 15px at 100%). */ +:root { + --accent: #7c5cff; + --accent-2: #22d3ee; + --bg: #0b0d17; + --surface: #141828; + --text: #eef0ff; + --muted: #8b90b3; + --success: #34d399; + --danger: #f43f5e; + --warn: #fbbf24; + --radius: 14px; + --radius-sm: 9px; + --font: 'Outfit Variable', system-ui, sans-serif; + --mono: 'JetBrains Mono Variable', ui-monospace, monospace; + --line: color-mix(in srgb, var(--text) 8%, transparent); + --line-strong: color-mix(in srgb, var(--text) 14%, transparent); + --panel: color-mix(in srgb, var(--surface) 62%, transparent); + --panel-solid: var(--surface); + --blur: blur(18px) saturate(1.3); + --ease: cubic-bezier(0.2, 0.8, 0.2, 1); + font-size: 15px; + color-scheme: dark; +} + +.no-glass { + --panel: var(--surface); + --blur: none; +} + +* { box-sizing: border-box; } + +html, body { + margin: 0; + height: 100%; + overflow: hidden; + background: var(--bg); + color: var(--text); + font-family: var(--font); + -webkit-font-smoothing: antialiased; + user-select: none; + -webkit-user-select: none; +} + +#app { height: 100%; } + +h1, h2, h3, h4 { margin: 0; letter-spacing: -0.015em; } +p { margin: 0; } +a { color: var(--accent); text-decoration: none; } +code, .mono { font-family: var(--mono); } +.selectable, input, textarea { user-select: text; -webkit-user-select: text; } + +::-webkit-scrollbar { width: 0.6rem; height: 0.6rem; } +::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--text) 14%, transparent); border-radius: 1rem; border: 0.15rem solid transparent; background-clip: padding-box; } +::-webkit-scrollbar-track { background: transparent; } + +.reduce-motion *, .reduce-motion *::before, .reduce-motion *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; +} + +/* ---------- surfaces ---------- */ +.glass { + background: var(--panel); + backdrop-filter: var(--blur); + -webkit-backdrop-filter: var(--blur); + border: 1px solid var(--line); + border-radius: var(--radius); +} + +/* ---------- controls ---------- */ +button { + font: inherit; + color: var(--text); + cursor: pointer; + border: 1px solid var(--line-strong); + background: color-mix(in srgb, var(--text) 6%, transparent); + border-radius: var(--radius-sm); + padding: 0.6rem 1.05rem; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + font-weight: 560; + font-size: 0.93rem; + transition: background 0.15s, border-color 0.15s, transform 0.1s var(--ease), opacity 0.15s, box-shadow 0.2s; + white-space: nowrap; +} +button:hover { background: color-mix(in srgb, var(--text) 11%, transparent); } +button:active { transform: scale(0.98); } +button:disabled { opacity: 0.45; cursor: not-allowed; transform: none; } +button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } + +button.primary { + background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 60%, var(--accent-2))); + border-color: transparent; + color: white; + box-shadow: 0 0.5rem 1.6rem -0.6rem var(--accent); +} +button.primary:hover { filter: brightness(1.1); } +button.ghost { background: transparent; border-color: transparent; color: color-mix(in srgb, var(--text) 75%, transparent); } +button.ghost:hover { background: color-mix(in srgb, var(--text) 7%, transparent); color: var(--text); } +button.danger { color: color-mix(in srgb, var(--danger) 70%, white); background: color-mix(in srgb, var(--danger) 12%, transparent); border-color: color-mix(in srgb, var(--danger) 30%, transparent); } +button.danger:hover { background: color-mix(in srgb, var(--danger) 20%, transparent); } +button.icon { padding: 0.5rem; } +button.sm { padding: 0.4rem 0.75rem; font-size: 0.85rem; } + +input, select, textarea { + font: inherit; + font-size: 0.93rem; + width: 100%; + color: var(--text); + background: color-mix(in srgb, var(--bg) 70%, transparent); + border: 1px solid var(--line-strong); + border-radius: var(--radius-sm); + padding: 0.65rem 0.8rem; + transition: border-color 0.15s, background 0.15s; +} +input:hover, select:hover, textarea:hover { border-color: color-mix(in srgb, var(--text) 22%, transparent); } +input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); background: color-mix(in srgb, var(--bg) 90%, transparent); } +input::placeholder, textarea::placeholder { color: color-mix(in srgb, var(--muted) 80%, transparent); } +select { appearance: none; padding-right: 2.2rem; background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%); background-position: calc(100% - 1.15rem) 55%, calc(100% - 0.8rem) 55%; background-size: 0.33rem 0.33rem; background-repeat: no-repeat; } +select option { background: var(--surface); } +input[type='range'] { padding: 0; border: none; background: transparent; accent-color: var(--accent); } +input[type='checkbox'] { width: auto; accent-color: var(--accent); } +textarea { resize: vertical; min-height: 5rem; line-height: 1.5; } + +label.field { display: flex; flex-direction: column; gap: 0.45rem; font-size: 0.85rem; font-weight: 520; color: color-mix(in srgb, var(--text) 80%, transparent); } +.help { font-size: 0.8rem; color: var(--muted); font-weight: 400; line-height: 1.45; } + +.chip { + display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.78rem; font-weight: 620; + padding: 0.28rem 0.7rem; border-radius: 99rem; background: color-mix(in srgb, var(--text) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--text) 12%, transparent); backdrop-filter: var(--blur); white-space: nowrap; +} +.chip.accent { background: color-mix(in srgb, var(--accent) 22%, transparent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); } + +.segmented { display: inline-flex; padding: 0.2rem; gap: 0.15rem; border-radius: var(--radius-sm); background: color-mix(in srgb, var(--bg) 70%, transparent); border: 1px solid var(--line-strong); } +.segmented button { border: none; background: transparent; padding: 0.45rem 0.9rem; font-size: 0.87rem; color: var(--muted); border-radius: calc(var(--radius-sm) - 2px); } +.segmented button.active { background: color-mix(in srgb, var(--text) 12%, transparent); color: var(--text); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); } + +.col > .segmented { align-self: flex-start; } +.row { display: flex; align-items: center; gap: 0.75rem; } +.col { display: flex; flex-direction: column; gap: 0.75rem; } +.spacer { flex: 1; } +.muted { color: var(--muted); } +.small { font-size: 0.87rem; } +.tiny { font-size: 0.78rem; } + +.spin { animation: spin 0.9s linear infinite; } +@keyframes spin { to { transform: rotate(360deg); } } +@keyframes fade-up { from { opacity: 0; transform: translateY(0.6rem); } to { opacity: 1; transform: none; } } +@keyframes fade { from { opacity: 0; } } diff --git a/launcher/src/components/Avatar.svelte b/launcher/src/components/Avatar.svelte new file mode 100644 index 0000000..64e9cba --- /dev/null +++ b/launcher/src/components/Avatar.svelte @@ -0,0 +1,18 @@ + + +{#if account && !failed} + (failed = true)} /> +{:else} + {account?.username.slice(0, 1).toUpperCase() ?? '?'} +{/if} + + diff --git a/launcher/src/components/Background.svelte b/launcher/src/components/Background.svelte new file mode 100644 index 0000000..c01b42d --- /dev/null +++ b/launcher/src/components/Background.svelte @@ -0,0 +1,50 @@ + + + (hidden = document.hidden)} /> + + + + diff --git a/launcher/src/components/Console.svelte b/launcher/src/components/Console.svelte new file mode 100644 index 0000000..1a3dc1b --- /dev/null +++ b/launcher/src/components/Console.svelte @@ -0,0 +1,51 @@ + + +
+
+ Game output + {app.logs.length.toLocaleString()} lines + + + + + +
+
box && (follow = box.scrollHeight - box.scrollTop - box.clientHeight < 40)}> + {#each shown as line, i (i)}
{line}
{:else}
Logs appear here while the game is running.
{/each} +
+ {#if !follow} + + {/if} +
+ + diff --git a/launcher/src/components/CrashDialog.svelte b/launcher/src/components/CrashDialog.svelte new file mode 100644 index 0000000..d3c25bf --- /dev/null +++ b/launcher/src/components/CrashDialog.svelte @@ -0,0 +1,39 @@ + + + (app.crash = null)}> + {#if c} +

The game exited with code {c.code}. {hint ?? 'The last lines of the log are below — they usually explain what went wrong.'}

+ {#if hint}
{hint}
{/if} +
{c.tail.slice(-40).join('\n')}
+ {/if} + {#snippet footer()} + + {#if c?.crash_report}{/if} + + + {/snippet} +
+ + diff --git a/launcher/src/components/InstanceSettings.svelte b/launcher/src/components/InstanceSettings.svelte new file mode 100644 index 0000000..f019fea --- /dev/null +++ b/launcher/src/components/InstanceSettings.svelte @@ -0,0 +1,84 @@ + + + (app.instanceSettings = null)}> + {#if inst && app.settings && ov} +
+ {#each folders as f} + + {/each} +
+ + + + {#if advanced} + + + {/if} + +
+ {local?.installed ? `Uses ${bytes(local.size)} on disk` : 'Not downloaded yet'} + + + +
+ {/if} +
+ + diff --git a/launcher/src/components/Modal.svelte b/launcher/src/components/Modal.svelte new file mode 100644 index 0000000..0bed86f --- /dev/null +++ b/launcher/src/components/Modal.svelte @@ -0,0 +1,30 @@ + + + open && e.key === 'Escape' && close()} /> + +{#if open} + +{/if} + + diff --git a/launcher/src/components/NewsFeed.svelte b/launcher/src/components/NewsFeed.svelte new file mode 100644 index 0000000..525cf2d --- /dev/null +++ b/launcher/src/components/NewsFeed.svelte @@ -0,0 +1,50 @@ + + +
+

Latest news

+
+ {#each sorted as n, i (n.id)} +
+ {#if n.image_url}{/if} +
+
+ {#if n.tag}{n.tag}{/if} + {#if n.pinned}{/if} + + {niceDate(n.date)} +
+

{n.title}

+

{n.body}

+ {#if n.link} + + {/if} +
+
+ {/each} +
+
+ + diff --git a/launcher/src/components/PlayButton.svelte b/launcher/src/components/PlayButton.svelte new file mode 100644 index 0000000..d0045e5 --- /dev/null +++ b/launcher/src/components/PlayButton.svelte @@ -0,0 +1,91 @@ + + + { + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && !busy && !running && !otherRunning && !noAccount && app.view === 'home') play(inst.id); +}} /> + +
+ {#if busy} +
+
+ + {#if l.stage === 'launching' || !l.total}{/if} + {l.label || 'Preparing…'} + + +
+
+
+ {l.stage ? stageNames[l.stage] : ''}{l.filesTotal ? ` · ${l.filesDone.toLocaleString()} / ${l.filesTotal.toLocaleString()} files` : ''} + {#if l.total}{bytes(l.done)} / {bytes(l.total)}{#if l.speed > 0} · {bytes(l.speed)}/s{#if eta} · {eta < 60 ? `${eta}s` : `${Math.ceil(eta / 60)}m`} left{/if}{/if}{/if} +
+
+ {:else if running} + + {:else} + + {/if} + {#if mine && l.state === 'error'} +
+ + {l.message} + +
+ {/if} +
+ + diff --git a/launcher/src/components/Rail.svelte b/launcher/src/components/Rail.svelte new file mode 100644 index 0000000..9a73614 --- /dev/null +++ b/launcher/src/components/Rail.svelte @@ -0,0 +1,91 @@ + + + menu && !(e.target as HTMLElement).closest('.account') && (menu = false)} /> + + + + diff --git a/launcher/src/components/ServerStatus.svelte b/launcher/src/components/ServerStatus.svelte new file mode 100644 index 0000000..88d5243 --- /dev/null +++ b/launcher/src/components/ServerStatus.svelte @@ -0,0 +1,88 @@ + + +
+ {#if status?.favicon}{:else}{/if} +
+
+ {server.name || server.address} + {status ? 'Online' : failed ? 'Offline' : 'Checking…'} +
+ {#if status} +
{#each parseMotd(status.motd.split('\n')[0]) as s}{s.text}{/each}
+ {:else} + + {/if} +
+ {#if status} +
+ {status.players_online}/{status.players_max} + + {#each [1, 2, 3, 4] as i}{/each} + {status.latency_ms}ms + +
+ {/if} +
+ + diff --git a/launcher/src/components/SignIn.svelte b/launcher/src/components/SignIn.svelte new file mode 100644 index 0000000..b3ce8ac --- /dev/null +++ b/launcher/src/components/SignIn.svelte @@ -0,0 +1,160 @@ + + + + + diff --git a/launcher/src/components/TitleBar.svelte b/launcher/src/components/TitleBar.svelte new file mode 100644 index 0000000..c610fc7 --- /dev/null +++ b/launcher/src/components/TitleBar.svelte @@ -0,0 +1,37 @@ + + +
+
+ {#if b?.logo_url} + + {:else} + + {/if} + {b?.name ?? 'Launcher'} + {#if app.offline} Offline{/if} +
+
+ + + +
+
+ + diff --git a/launcher/src/components/Toasts.svelte b/launcher/src/components/Toasts.svelte new file mode 100644 index 0000000..6b9e2df --- /dev/null +++ b/launcher/src/components/Toasts.svelte @@ -0,0 +1,22 @@ + + +
+ {#each app.toasts as t (t.id)} +
+ {#if t.kind === 'ok'}{:else if t.kind === 'error'}{:else}{/if} + {t.text} +
+ {/each} +
+ + diff --git a/launcher/src/components/Toggle.svelte b/launcher/src/components/Toggle.svelte new file mode 100644 index 0000000..0b3d49f --- /dev/null +++ b/launcher/src/components/Toggle.svelte @@ -0,0 +1,22 @@ + + + + + diff --git a/launcher/src/lib/format.ts b/launcher/src/lib/format.ts new file mode 100644 index 0000000..dc84238 --- /dev/null +++ b/launcher/src/lib/format.ts @@ -0,0 +1,28 @@ +export function bytes(n: number): string { + if (!n) return '0 B'; + const u = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.min(u.length - 1, Math.floor(Math.log(n) / Math.log(1024))); + return `${(n / 1024 ** i).toFixed(i > 1 ? 1 : 0)} ${u[i]}`; +} + +export function gb(mb: number): string { + return `${(mb / 1024).toFixed(mb % 1024 === 0 ? 0 : 1)} GB`; +} + +export const loaderName: Record = { + vanilla: 'Vanilla', + fabric: 'Fabric', + quilt: 'Quilt', + forge: 'Forge', + neoforge: 'NeoForge', +}; + +export function shortLoaderVersion(mc: string, v: string | null): string { + return v ? v.replace(`${mc}-`, '') : ''; +} + +export function niceDate(iso: string | null): string { + if (!iso) return ''; + const d = new Date(iso + (iso.length === 10 ? 'T00:00:00' : '')); + return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: d.getFullYear() === new Date().getFullYear() ? undefined : 'numeric' }); +} diff --git a/launcher/src/lib/keys.ts b/launcher/src/lib/keys.ts new file mode 100644 index 0000000..bdbef9d --- /dev/null +++ b/launcher/src/lib/keys.ts @@ -0,0 +1,90 @@ +// Minecraft (1.13+) key names used in options.txt, and friendly labels. + +export interface Binding { id: string; label: string; default: string } + +export const BIND_GROUPS: { title: string; binds: Binding[] }[] = [ + { + title: 'Movement', + binds: [ + { id: 'key.forward', label: 'Walk forwards', default: 'key.keyboard.w' }, + { id: 'key.left', label: 'Strafe left', default: 'key.keyboard.a' }, + { id: 'key.back', label: 'Walk backwards', default: 'key.keyboard.s' }, + { id: 'key.right', label: 'Strafe right', default: 'key.keyboard.d' }, + { id: 'key.jump', label: 'Jump', default: 'key.keyboard.space' }, + { id: 'key.sneak', label: 'Sneak', default: 'key.keyboard.left.shift' }, + { id: 'key.sprint', label: 'Sprint', default: 'key.keyboard.left.control' }, + ], + }, + { + title: 'Gameplay', + binds: [ + { id: 'key.attack', label: 'Attack / destroy', default: 'key.mouse.left' }, + { id: 'key.use', label: 'Use item / place block', default: 'key.mouse.right' }, + { id: 'key.pickItem', label: 'Pick block', default: 'key.mouse.middle' }, + { id: 'key.drop', label: 'Drop item', default: 'key.keyboard.q' }, + { id: 'key.swapOffhand', label: 'Swap to offhand', default: 'key.keyboard.f' }, + ], + }, + { + title: 'Interface', + binds: [ + { id: 'key.inventory', label: 'Inventory', default: 'key.keyboard.e' }, + { id: 'key.chat', label: 'Open chat', default: 'key.keyboard.t' }, + { id: 'key.command', label: 'Open command', default: 'key.keyboard.slash' }, + { id: 'key.playerlist', label: 'Player list', default: 'key.keyboard.tab' }, + { id: 'key.advancements', label: 'Advancements', default: 'key.keyboard.l' }, + { id: 'key.socialInteractions', label: 'Social interactions', default: 'key.keyboard.p' }, + { id: 'key.screenshot', label: 'Screenshot', default: 'key.keyboard.f2' }, + { id: 'key.togglePerspective', label: 'Toggle perspective', default: 'key.keyboard.f5' }, + { id: 'key.fullscreen', label: 'Toggle fullscreen', default: 'key.keyboard.f11' }, + ], + }, + { + title: 'Hotbar', + binds: Array.from({ length: 9 }, (_, i) => ({ id: `key.hotbar.${i + 1}`, label: `Hotbar slot ${i + 1}`, default: `key.keyboard.${i + 1}` })), + }, +]; + +const SPECIAL: Record = { + Space: 'space', ShiftLeft: 'left.shift', ShiftRight: 'right.shift', ControlLeft: 'left.control', ControlRight: 'right.control', + AltLeft: 'left.alt', AltRight: 'right.alt', Tab: 'tab', Enter: 'enter', Backspace: 'backspace', CapsLock: 'caps.lock', + ArrowUp: 'up', ArrowDown: 'down', ArrowLeft: 'left', ArrowRight: 'right', Backquote: 'grave.accent', Minus: 'minus', + Equal: 'equal', BracketLeft: 'left.bracket', BracketRight: 'right.bracket', Backslash: 'backslash', Semicolon: 'semicolon', + Quote: 'apostrophe', Comma: 'comma', Period: 'period', Slash: 'slash', Insert: 'insert', Delete: 'delete', Home: 'home', + End: 'end', PageUp: 'page.up', PageDown: 'page.down', NumpadAdd: 'keypad.add', NumpadSubtract: 'keypad.subtract', + NumpadMultiply: 'keypad.multiply', NumpadDivide: 'keypad.divide', NumpadEnter: 'keypad.enter', NumpadDecimal: 'keypad.decimal', +}; + +/** KeyboardEvent.code → Minecraft key name (null for unsupported keys). */ +export function fromKeyboard(code: string): string | null { + if (/^Key[A-Z]$/.test(code)) return `key.keyboard.${code.slice(3).toLowerCase()}`; + if (/^Digit\d$/.test(code)) return `key.keyboard.${code.slice(5)}`; + if (/^F\d{1,2}$/.test(code)) return `key.keyboard.${code.toLowerCase()}`; + if (/^Numpad\d$/.test(code)) return `key.keyboard.keypad.${code.slice(6)}`; + return SPECIAL[code] ? `key.keyboard.${SPECIAL[code]}` : null; +} + +export function fromMouse(button: number): string { + return ['key.mouse.left', 'key.mouse.middle', 'key.mouse.right', 'key.mouse.4', 'key.mouse.5'][button] ?? `key.mouse.${button + 1}`; +} + +/** Human label for a Minecraft key name. */ +export function keyLabel(key: string): string { + if (!key || key === 'key.keyboard.unknown') return 'Not bound'; + const mouse = key.match(/^key\.mouse\.(.+)$/); + if (mouse) return { left: 'Left click', right: 'Right click', middle: 'Middle click' }[mouse[1]] ?? `Mouse ${mouse[1]}`; + const k = key.replace('key.keyboard.', ''); + const names: Record = { + 'left.shift': 'Left Shift', 'right.shift': 'Right Shift', 'left.control': 'Left Ctrl', 'right.control': 'Right Ctrl', + 'left.alt': 'Left Alt', 'right.alt': 'Right Alt', 'caps.lock': 'Caps Lock', 'page.up': 'Page Up', 'page.down': 'Page Down', + 'grave.accent': '`', 'left.bracket': '[', 'right.bracket': ']', slash: '/', backslash: '\\', semicolon: ';', apostrophe: "'", + comma: ',', period: '.', minus: '-', equal: '=', up: '↑', down: '↓', left: '←', right: '→', + }; + if (names[k]) return names[k]; + if (k.startsWith('keypad.')) return `Num ${k.slice(7)}`; + return k.length === 1 ? k.toUpperCase() : k.charAt(0).toUpperCase() + k.slice(1); +} + +export function defaults(): Record { + return Object.fromEntries(BIND_GROUPS.flatMap((g) => g.binds.map((b) => [b.id, b.default]))); +} diff --git a/launcher/src/lib/mock.ts b/launcher/src/lib/mock.ts new file mode 100644 index 0000000..a85ff44 --- /dev/null +++ b/launcher/src/lib/mock.ts @@ -0,0 +1,167 @@ +// In-browser stand-in for the Rust backend, used by `npm run dev` outside +// Tauri and for UI screenshots. Scenario via ?mock=setup|login|main. +import { mockEmit } from './tauri'; +import type { Account, Bootstrap, Manifest, Settings } from './types'; + +const scenario = new URLSearchParams(location.search).get('mock') ?? 'main'; + +const settings: Settings = { + panel_url: scenario === 'setup' ? null : 'https://panel.scopenet.gg', + selected_instance: null, + memory_max_mb: 0, + memory_min_mb: 0, + custom_resolution: false, + width: 1280, + height: 720, + fullscreen: false, + after_launch: 'minimize', + show_console: false, + java_path: null, + gc: 'g1', + jvm_args: '', + theme_mode: 'server', + accent: null, + background_video: true, + reduce_motion: false, + glass: null, + ui_scale: 100, + concurrent_downloads: 12, + check_updates: true, + send_stats: true, + keybinds_enabled: false, + keybinds: {}, + instance_overrides: {}, +}; + +const manifest: Manifest = { + api_version: 1, + panel_version: '0.1.0', + user: null, + auth: { panel_accounts: true, registration: 'approval', microsoft: true, microsoft_client_id: 'demo', offline_local: true }, + branding: { + name: 'ScopeNet', + tagline: 'Your worlds, one click away.', + logo_url: null, + icon_url: null, + background: { kind: 'gradient', url: null, blur: 0, dim: 45 }, + colors: { accent: '#7c5cff', accent_2: '#22d3ee', background: '#0b0d17', surface: '#141828', text: '#eef0ff', muted: '#8b90b3', success: '#34d399', danger: '#f43f5e' }, + font: 'Outfit', + radius: 14, + glass: true, + version_label: 'ScopeNet', + news: [ + { id: '1', title: 'Season 4 is live!', body: 'A brand new world with custom terrain, player shops and weekly events. Join now and claim your spot before the land rush.', image_url: null, link: 'https://example.com', date: '2026-09-26', pinned: true, tag: 'Update' }, + { id: '2', title: 'Build contest: Autumn Castles', body: 'Submit your best castle build on the Creative server by October 10th. Top three win VIP for a month.', image_url: null, link: null, date: '2026-09-21', pinned: false, tag: 'Event' }, + { id: '3', title: 'Performance boost', body: 'We switched the modpack to Sodium + Lithium. Expect way higher FPS on older machines.', image_url: null, link: null, date: '2026-09-14', pinned: false, tag: 'Changelog' }, + ], + links: [ + { label: 'Discord', url: 'https://discord.gg/example', icon: 'discord' }, + { label: 'Website', url: 'https://example.com', icon: 'website' }, + { label: 'Store', url: 'https://example.com/store', icon: 'store' }, + ], + features: { news: true, server_status: true, allow_user_theme: true, allow_advanced_java: true }, + custom_css: '', + }, + instances: [ + { id: 'survival-smp', name: 'Survival SMP', description: 'Our main survival world — season 4. Claims, shops & weekend events.', icon_url: null, banner_url: null, mc_version: '1.21.1', loader: 'fabric', loader_version: '0.16.9', revision: 3, server: { name: 'ScopeNet SMP', address: 'play.scopenet.gg', port: 25565, auto_join: true, inject: true }, memory: { min_mb: 2048, max_mb: 6144 }, jvm_args: '', featured: true, source_label: 'Modrinth · Fabulously Optimized 6.2', file_count: 48, total_size: 184_000_000 }, + { id: 'creative', name: 'Creative Build', description: 'Plots, WorldEdit and weekly build contests.', icon_url: null, banner_url: null, mc_version: '1.20.1', loader: 'vanilla', loader_version: null, revision: 1, server: { name: 'Creative', address: 'creative.scopenet.gg', port: 25565, auto_join: false, inject: true }, memory: { min_mb: 1024, max_mb: 4096 }, jvm_args: '', featured: false, source_label: 'Minecraft 1.20.1', file_count: 0, total_size: 0 }, + { id: 'modded', name: 'Create: Above & Beyond', description: 'Factory building with Create and friends.', icon_url: null, banner_url: null, mc_version: '1.20.1', loader: 'forge', loader_version: '1.20.1-47.3.0', revision: 2, server: null, memory: { min_mb: 4096, max_mb: 8192 }, jvm_args: '', featured: false, source_label: 'CurseForge · Create: Above & Beyond 1.4', file_count: 212, total_size: 612_000_000 }, + ], +}; + +const accounts: Account[] = scenario === 'main' || scenario === 'progress' ? [ + { id: 'a1', kind: 'panel', username: 'Notch', uuid: 'b50ad385-829d-3141-a216-7e7d7539ba7f', panel_url: 'https://panel.scopenet.gg', role: 'admin' }, + { id: 'a2', kind: 'offline', username: 'Steve', uuid: '5627dd98-e6be-3c21-b8a8-e92344183641', panel_url: null, role: null }, +] : []; +let active: string | null = accounts[0]?.id ?? null; + +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); +let running = false; + +async function fakeLaunch(id: string) { + const stages: [string, string, number, number][] = [ + ['java', 'Installing Java 21', 48_000_000, 212], + ['game', 'Downloading Minecraft 1.21.1', 420_000_000, 3412], + ['loader', 'Installing Fabric 0.16.9', 6_000_000, 14], + ['files', 'Syncing instance files', 184_000_000, 48], + ]; + for (const [stage, label, total, files] of stages) { + mockEmit('launch://progress', { type: 'stage', stage, label }); + for (let i = 1; i <= 20; i++) { + await sleep(scenario === 'progress' ? 400 : 60); + mockEmit('launch://progress', { type: 'progress', stage, done: (total * i) / 20, total, files_done: Math.round((files * i) / 20), files_total: files }); + } + } + mockEmit('launch://progress', { type: 'stage', stage: 'launching', label: 'Starting Minecraft' }); + await sleep(500); + running = true; + mockEmit('launch://state', { instance_id: id, state: 'running', message: null }); + const lines = ['[main/INFO]: Loading Minecraft 1.21.1 with Fabric Loader 0.16.9', '[Render thread/INFO]: Setting user: Notch', '[Render thread/INFO]: Backend library: LWJGL version 3.3.3', '[Render thread/INFO]: Reloading ResourceManager: vanilla, fabric', '[Render thread/INFO]: Sound engine started', '[Render thread/INFO]: Connecting to play.scopenet.gg, 25565']; + for (const l of lines) { + await sleep(300); + mockEmit('game://log', [`[12:00:0${lines.indexOf(l)}] ${l}`]); + } +} + +export async function mockInvoke(cmd: string, args: Record): Promise { + await sleep(40); + switch (cmd) { + case 'bootstrap': { + const b: Bootstrap = { + app: { version: '0.1.0', default_panel_url: null, panel_locked: false, repo: 'scopeddlol/SCOPENET-MC', os: 'windows', arch: 'x86_64', total_ram_mb: 16384, data_dir: 'C:\\Users\\you\\AppData\\Roaming\\net.scopenet.launcher' }, + panel_url: settings.panel_url, + settings, + accounts, + active_account: active, + manifest: scenario === 'setup' ? null : manifest, + game_running: running ? 'survival-smp' : null, + }; + return structuredClone(b); + } + case 'refresh_manifest': + case 'set_panel_url': + if (cmd === 'set_panel_url') settings.panel_url = args.url; + return structuredClone(manifest); + case 'save_settings': + Object.assign(settings, args.settings); + return null; + case 'login_panel': + case 'add_offline': { + const a: Account = { id: crypto.randomUUID(), kind: cmd === 'login_panel' ? 'panel' : 'offline', username: args.username, uuid: crypto.randomUUID(), panel_url: null, role: 'player' }; + accounts.push(a); + active = a.id; + return a; + } + case 'register_panel': + return { account: null, pending: true }; + case 'ms_start': + return { user_code: 'F7K2-QX9M', verification_uri: 'https://microsoft.com/link', expires_in: 900, interval: 5, message: '' }; + case 'ms_finish': + await sleep(600_000); + return null; + case 'select_account': + active = args.id; + return null; + case 'launch': + fakeLaunch(args.instanceId); + return null; + case 'kill_game': + running = false; + mockEmit('game://exit', { instance_id: 'survival-smp', code: 0, crashed: false, tail: [], crash_report: null }); + mockEmit('launch://state', { instance_id: 'survival-smp', state: 'idle', message: null }); + return null; + case 'ping_server': + await sleep(200); + return { online: true, version: 'Paper 1.21.1', players_online: 42, players_max: 100, sample: ['Notch', 'jeb_', 'Dinnerbone'], motd: '§b§lScopeNet §r§7» §fSeason 4 is live!', favicon: null, latency_ms: 23 }; + case 'instance_local': + return { installed: args.instanceId !== 'modded', revision: 3, size: 1_240_000_000 }; + case 'storage_info': + return { shared: 2_400_000_000, runtimes: 410_000_000, instances: [['survival-smp', 1_240_000_000], ['creative', 320_000_000]] }; + case 'check_update': + return null; + case 'detect_java': + return 21; + default: + return null; + } +} diff --git a/launcher/src/lib/motd.ts b/launcher/src/lib/motd.ts new file mode 100644 index 0000000..43db84a --- /dev/null +++ b/launcher/src/lib/motd.ts @@ -0,0 +1,37 @@ +// Render Minecraft § formatting codes as HTML-safe segments. +const COLORS: Record = { + '0': '#000000', '1': '#0000AA', '2': '#00AA00', '3': '#00AAAA', '4': '#AA0000', '5': '#AA00AA', '6': '#FFAA00', '7': '#AAAAAA', + '8': '#555555', '9': '#5555FF', a: '#55FF55', b: '#55FFFF', c: '#FF5555', d: '#FF55FF', e: '#FFFF55', f: '#FFFFFF', +}; + +export interface Segment { text: string; color?: string; bold?: boolean; italic?: boolean; underline?: boolean; strike?: boolean } + +export function parseMotd(input: string): Segment[] { + const out: Segment[] = []; + let cur: Segment = { text: '' }; + for (let i = 0; i < input.length; i++) { + const ch = input[i]; + if (ch === '§' && i + 1 < input.length) { + const code = input[i + 1].toLowerCase(); + if (cur.text) out.push(cur); + const base: Segment = { ...cur, text: '' }; + if (code === '#' && /^[0-9a-f]{6}$/i.test(input.slice(i + 2, i + 8))) { + cur = { text: '', color: '#' + input.slice(i + 2, i + 8) }; + i += 7; + continue; + } + if (COLORS[code]) cur = { text: '', color: COLORS[code] }; + else if (code === 'l') cur = { ...base, bold: true }; + else if (code === 'o') cur = { ...base, italic: true }; + else if (code === 'n') cur = { ...base, underline: true }; + else if (code === 'm') cur = { ...base, strike: true }; + else if (code === 'r') cur = { text: '' }; + else cur = base; + i++; + continue; + } + cur.text += ch; + } + if (cur.text) out.push(cur); + return out; +} diff --git a/launcher/src/lib/store.svelte.ts b/launcher/src/lib/store.svelte.ts new file mode 100644 index 0000000..e7980b9 --- /dev/null +++ b/launcher/src/lib/store.svelte.ts @@ -0,0 +1,243 @@ +import { errorText, invoke, listen } from './tauri'; +import { applyTheme } from './theme'; +import type { Account, Bootstrap, Instance, Manifest, ProgressEvent, Settings, Stage, UpdateInfo } from './types'; + +export interface LaunchState { + state: 'idle' | 'preparing' | 'running' | 'error'; + stage: Stage | null; + label: string; + done: number; + total: number; + filesDone: number; + filesTotal: number; + speed: number; + message: string | null; +} + +export interface ExitInfo { instance_id: string; code: number | null; crashed: boolean; tail: string[]; crash_report: string | null } + +const idle = (): LaunchState => ({ state: 'idle', stage: null, label: '', done: 0, total: 0, filesDone: 0, filesTotal: 0, speed: 0, message: null }); + +export const app = $state({ + ready: false, + boot: null as Bootstrap | null, + manifest: null as Manifest | null, + settings: null as Settings | null, + accounts: [] as Account[], + active: null as string | null, + panelUrl: null as string | null, + offline: false, + view: 'home' as 'home' | 'settings', + settingsTab: 'account', + selected: null as string | null, + launch: idle(), + launchingId: null as string | null, + running: null as string | null, + logs: [] as string[], + consoleOpen: false, + crash: null as ExitInfo | null, + update: null as UpdateInfo | null, + addAccount: false, + instanceSettings: null as string | null, + toasts: [] as { id: number; text: string; kind: 'ok' | 'error' | 'info' }[], +}); + +let toastId = 1; +export function toast(text: string, kind: 'ok' | 'error' | 'info' = 'ok') { + const id = toastId++; + app.toasts.push({ id, text, kind }); + setTimeout(() => { + const i = app.toasts.findIndex((t) => t.id === id); + if (i >= 0) app.toasts.splice(i, 1); + }, kind === 'error' ? 7000 : 3500); +} + +export const activeAccount = () => app.accounts.find((a) => a.id === app.active) ?? null; +export const instances = (): Instance[] => app.manifest?.instances ?? []; +export const selectedInstance = (): Instance | null => instances().find((i) => i.id === app.selected) ?? instances()[0] ?? null; + +/** Resolve panel-relative media URLs ("/uploads/x.png"). */ +export function abs(url: string | null | undefined): string | null { + if (!url) return null; + if (url.startsWith('/') && app.panelUrl) return app.panelUrl + url; + return url; +} + +function theme() { + if (app.manifest) applyTheme(app.manifest.branding, app.settings); +} + +function pickSelected() { + const list = instances(); + const wanted = app.settings?.selected_instance; + app.selected = list.find((i) => i.id === wanted)?.id ?? list.find((i) => i.featured)?.id ?? list[0]?.id ?? null; +} + +export async function init() { + const boot = await invoke('bootstrap'); + app.boot = boot; + app.settings = boot.settings; + app.accounts = boot.accounts; + app.active = boot.active_account; + app.manifest = boot.manifest; + app.panelUrl = boot.panel_url; + app.running = boot.game_running; + theme(); + pickSelected(); + subscribe(); + app.ready = true; + invoke('show_window').catch(() => {}); + if (app.panelUrl) await refresh(); + if (boot.settings.check_updates && boot.app.repo) { + invoke('check_update').then((u) => (app.update = u)).catch(() => {}); + } +} + +export async function refresh() { + try { + app.manifest = await invoke('refresh_manifest'); + app.offline = false; + theme(); + pickSelected(); + } catch (e) { + app.offline = true; + if (!app.manifest) throw e; + } +} + +export async function connect(url: string) { + app.manifest = await invoke('set_panel_url', { url }); + const boot = await invoke('bootstrap'); + app.panelUrl = boot.panel_url; + app.offline = false; + theme(); + pickSelected(); +} + +let saveTimer: ReturnType | undefined; +/** Persist settings (debounced) and re-apply the theme immediately. */ +export function saveSettings() { + theme(); + clearTimeout(saveTimer); + saveTimer = setTimeout(() => { + if (app.settings) invoke('save_settings', { settings: $state.snapshot(app.settings) }).catch((e) => toast(errorText(e), 'error')); + }, 300); +} + +export function selectInstance(id: string) { + app.selected = id; + if (app.settings) { + app.settings.selected_instance = id; + saveSettings(); + } +} + +export async function accountAdded(account: Account) { + const i = app.accounts.findIndex((a) => a.id === account.id); + if (i >= 0) app.accounts[i] = account; + else app.accounts.push(account); + app.active = account.id; + app.addAccount = false; + await refresh().catch(() => {}); +} + +export async function switchAccount(id: string) { + await invoke('select_account', { id }); + app.active = id; + await refresh().catch(() => {}); +} + +export async function removeAccount(id: string) { + await invoke('remove_account', { id }); + app.accounts = app.accounts.filter((a) => a.id !== id); + if (app.active === id) app.active = app.accounts[0]?.id ?? null; + await refresh().catch(() => {}); +} + +export async function play(id: string) { + app.launch = { ...idle(), state: 'preparing', label: 'Getting ready…' }; + app.launchingId = id; + app.logs = []; + try { + await invoke('launch', { instanceId: id }); + } catch (e) { + app.launch = { ...idle(), state: 'error', message: errorText(e) }; + } +} + +export async function repair(id: string) { + app.launch = { ...idle(), state: 'preparing', label: 'Verifying files…' }; + app.launchingId = id; + try { + await invoke('repair_instance', { instanceId: id }); + } catch (e) { + app.launch = { ...idle(), state: 'error', message: errorText(e) }; + } +} + +export async function cancel() { + if (app.launchingId) await invoke('cancel_launch', { instanceId: app.launchingId }); +} + +export async function kill() { + await invoke('kill_game'); +} + +let subscribed = false; +let last = { t: 0, done: 0, stage: '' as string }; + +function subscribe() { + if (subscribed) return; + subscribed = true; + + listen('launch://progress', (ev) => { + const l = app.launch; + if (ev.type === 'stage') { + l.stage = ev.stage; + l.label = ev.label; + l.done = l.total = l.filesDone = l.filesTotal = 0; + l.speed = 0; + } else if (ev.type === 'progress') { + const now = performance.now(); + if (last.stage !== ev.stage || ev.done < last.done) last = { t: now, done: ev.done, stage: ev.stage }; + else if (now - last.t > 500) { + const inst = ((ev.done - last.done) / (now - last.t)) * 1000; + l.speed = l.speed ? l.speed * 0.6 + inst * 0.4 : inst; + last = { t: now, done: ev.done, stage: ev.stage }; + } + l.stage = ev.stage; + l.done = ev.done; + l.total = ev.total; + l.filesDone = ev.files_done; + l.filesTotal = ev.files_total; + } else if (ev.type === 'log') { + pushLogs([ev.line]); + } + }); + + listen<{ instance_id: string; state: LaunchState['state']; message: string | null }>('launch://state', (ev) => { + if (ev.state === 'running') { + app.running = ev.instance_id; + app.launch = { ...idle(), state: 'running' }; + if (app.settings?.show_console) app.consoleOpen = true; + } else if (ev.state === 'error') { + app.launch = { ...idle(), state: 'error', message: ev.message }; + } else if (ev.state === 'idle') { + if (app.running === ev.instance_id) app.running = null; + if (app.launch.state !== 'error') app.launch = idle(); + if (ev.message && ev.message !== 'Cancelled') toast(ev.message); + } + }); + + listen('game://log', (lines) => pushLogs(lines)); + + listen('game://exit', (ev) => { + app.running = null; + if (ev.crashed) app.crash = ev; + }); +} + +function pushLogs(lines: string[]) { + app.logs.push(...lines); + if (app.logs.length > 3000) app.logs.splice(0, app.logs.length - 3000); +} diff --git a/launcher/src/lib/tauri.ts b/launcher/src/lib/tauri.ts new file mode 100644 index 0000000..3cfd60d --- /dev/null +++ b/launcher/src/lib/tauri.ts @@ -0,0 +1,57 @@ +// Thin wrapper over Tauri IPC. When the UI runs in a normal browser +// (vite dev / screenshots), calls go to an in-memory mock instead. +import { invoke as tauriInvoke } from '@tauri-apps/api/core'; +import { listen as tauriListen, type UnlistenFn } from '@tauri-apps/api/event'; + +export const inTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window; + +type Handler = (payload: any) => void; +const mockListeners = new Map>(); + +export function mockEmit(event: string, payload: unknown) { + mockListeners.get(event)?.forEach((h) => h(payload)); +} + +export async function invoke(cmd: string, args?: Record): Promise { + if (inTauri) return tauriInvoke(cmd, args); + const { mockInvoke } = await import('./mock'); + return mockInvoke(cmd, args ?? {}) as Promise; +} + +export async function listen(event: string, handler: (payload: T) => void): Promise { + if (inTauri) return tauriListen(event, (e) => handler(e.payload)); + const set = mockListeners.get(event) ?? new Set(); + set.add(handler as Handler); + mockListeners.set(event, set); + return () => set.delete(handler as Handler); +} + +export async function windowAction(action: 'minimize' | 'maximize' | 'close' | 'drag') { + if (!inTauri) return; + const { getCurrentWindow } = await import('@tauri-apps/api/window'); + const w = getCurrentWindow(); + if (action === 'minimize') await w.minimize(); + else if (action === 'maximize') await w.toggleMaximize(); + else if (action === 'close') await w.close(); + else await w.startDragging(); +} + +export async function openUrl(url: string) { + if (!inTauri) { + window.open(url, '_blank'); + return; + } + const { openUrl } = await import('@tauri-apps/plugin-opener'); + await openUrl(url); +} + +export async function pickFile(title: string, filters?: { name: string; extensions: string[] }[]): Promise { + if (!inTauri) return null; + const { open } = await import('@tauri-apps/plugin-dialog'); + const r = await open({ title, multiple: false, directory: false, filters }); + return typeof r === 'string' ? r : null; +} + +export function errorText(e: unknown): string { + return typeof e === 'string' ? e : e instanceof Error ? e.message : JSON.stringify(e); +} diff --git a/launcher/src/lib/theme.ts b/launcher/src/lib/theme.ts new file mode 100644 index 0000000..a7bcf23 --- /dev/null +++ b/launcher/src/lib/theme.ts @@ -0,0 +1,54 @@ +import type { Branding, Settings } from './types'; + +let styleEl: HTMLStyleElement | null = null; + +/** Push the admin's branding (and the player's overrides) into CSS variables. */ +export function applyTheme(b: Branding, s: Settings | null) { + const root = document.documentElement; + const custom = s?.theme_mode === 'custom' && b.features.allow_user_theme; + const accent = (custom && s?.accent) || b.colors.accent; + const vars: Record = { + '--accent': accent, + '--accent-2': custom && s?.accent ? shift(accent) : b.colors.accent_2, + '--bg': b.colors.background, + '--surface': b.colors.surface, + '--text': b.colors.text, + '--muted': b.colors.muted, + '--success': b.colors.success, + '--danger': b.colors.danger, + '--radius': `${b.radius}px`, + '--radius-sm': `${Math.max(4, Math.round(b.radius * 0.65))}px`, + '--font': `'${b.font} Variable', '${b.font}', 'Outfit Variable', system-ui, sans-serif`, + }; + for (const [k, v] of Object.entries(vars)) root.style.setProperty(k, v); + const glass = s?.glass ?? b.glass; + root.classList.toggle('no-glass', !glass); + root.classList.toggle('reduce-motion', !!s?.reduce_motion); + root.style.fontSize = `${(s?.ui_scale ?? 100) * 0.15}px`; + + if (!styleEl) { + styleEl = document.createElement('style'); + styleEl.id = 'admin-css'; + document.head.appendChild(styleEl); + } + styleEl.textContent = b.custom_css ?? ''; +} + +/** A second accent derived from the first by rotating its hue. */ +function shift(hex: string): string { + const m = hex.replace('#', '').match(/.{2}/g); + if (!m) return hex; + let [r, g, b] = m.map((x) => parseInt(x, 16) / 255); + const max = Math.max(r, g, b), min = Math.min(r, g, b); + let h = 0; + const l = (max + min) / 2; + const d = max - min; + const sat = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1)); + if (d) { + if (max === r) h = ((g - b) / d) % 6; + else if (max === g) h = (b - r) / d + 2; + else h = (r - g) / d + 4; + } + h = (h * 60 + 50 + 360) % 360; + return `hsl(${h} ${Math.round(sat * 100)}% ${Math.round(l * 100)}%)`; +} diff --git a/launcher/src/lib/types.ts b/launcher/src/lib/types.ts new file mode 100644 index 0000000..96332ae --- /dev/null +++ b/launcher/src/lib/types.ts @@ -0,0 +1,112 @@ +export type Loader = 'vanilla' | 'fabric' | 'quilt' | 'forge' | 'neoforge'; + +export interface ServerEntry { name: string; address: string; port: number; auto_join: boolean; inject: boolean } + +export interface Instance { + id: string; + name: string; + description: string; + icon_url: string | null; + banner_url: string | null; + mc_version: string; + loader: Loader; + loader_version: string | null; + revision: number; + server: ServerEntry | null; + memory: { min_mb: number; max_mb: number }; + jvm_args: string; + featured: boolean; + source_label: string; + file_count: number; + total_size: number; +} + +export interface NewsItem { id: string; title: string; body: string; image_url: string | null; link: string | null; date: string | null; pinned: boolean; tag: string | null } + +export interface Branding { + name: string; + tagline: string; + logo_url: string | null; + icon_url: string | null; + background: { kind: 'gradient' | 'image' | 'video'; url: string | null; blur: number; dim: number }; + colors: { accent: string; accent_2: string; background: string; surface: string; text: string; muted: string; success: string; danger: string }; + font: string; + radius: number; + glass: boolean; + version_label: string; + news: NewsItem[]; + links: { label: string; url: string; icon: string }[]; + features: { news: boolean; server_status: boolean; allow_user_theme: boolean; allow_advanced_java: boolean }; + custom_css: string; +} + +export interface AuthConfig { + panel_accounts: boolean; + registration: 'closed' | 'open' | 'approval'; + microsoft: boolean; + microsoft_client_id: string | null; + offline_local: boolean; +} + +export interface Manifest { + api_version: number; + panel_version: string; + branding: Branding; + auth: AuthConfig; + instances: Instance[]; + user: { id: number; username: string; uuid: string; role: string; groups: string[] } | null; +} + +export interface Account { id: string; kind: 'panel' | 'microsoft' | 'offline'; username: string; uuid: string; panel_url: string | null; role: string | null } + +export type Gc = 'default' | 'g1' | 'zgc'; + +export interface InstanceOverride { memory_max_mb: number; memory_min_mb: number; jvm_args: string; java_path: string | null } + +export interface Settings { + panel_url: string | null; + selected_instance: string | null; + memory_max_mb: number; + memory_min_mb: number; + custom_resolution: boolean; + width: number; + height: number; + fullscreen: boolean; + after_launch: 'minimize' | 'hide' | 'keep' | 'close'; + show_console: boolean; + java_path: string | null; + gc: Gc; + jvm_args: string; + theme_mode: 'server' | 'custom'; + accent: string | null; + background_video: boolean; + reduce_motion: boolean; + glass: boolean | null; + ui_scale: number; + concurrent_downloads: number; + check_updates: boolean; + send_stats: boolean; + keybinds_enabled: boolean; + keybinds: Record; + instance_overrides: Record; +} + +export interface Bootstrap { + app: { version: string; default_panel_url: string | null; panel_locked: boolean; repo: string | null; os: string; arch: string; total_ram_mb: number; data_dir: string }; + panel_url: string | null; + settings: Settings; + accounts: Account[]; + active_account: string | null; + manifest: Manifest | null; + game_running: string | null; +} + +export type Stage = 'preparing' | 'java' | 'game' | 'loader' | 'files' | 'launching'; +export type ProgressEvent = + | { type: 'stage'; stage: Stage; label: string } + | { type: 'progress'; stage: Stage; done: number; total: number; files_done: number; files_total: number } + | { type: 'log'; line: string }; + +export interface ServerStatus { online: boolean; version: string; players_online: number; players_max: number; sample: string[]; motd: string; favicon: string | null; latency_ms: number } +export interface DeviceCode { user_code: string; verification_uri: string; expires_in: number; interval: number; message: string } +export interface UpdateInfo { version: string; notes: string; url: string; size: number } diff --git a/launcher/src/main.ts b/launcher/src/main.ts new file mode 100644 index 0000000..67d88d3 --- /dev/null +++ b/launcher/src/main.ts @@ -0,0 +1,15 @@ +import '@fontsource-variable/outfit'; +import '@fontsource-variable/inter'; +import '@fontsource-variable/space-grotesk'; +import '@fontsource-variable/plus-jakarta-sans'; +import '@fontsource-variable/jetbrains-mono'; +import './app.css'; +import { mount } from 'svelte'; +import App from './App.svelte'; + +// The launcher is a desktop app: no browser context menu or text-drag. +document.addEventListener('contextmenu', (e) => { + if (!(e.target as HTMLElement).closest('input, textarea, .selectable')) e.preventDefault(); +}); + +mount(App, { target: document.getElementById('app')! }); diff --git a/launcher/src/pages/Home.svelte b/launcher/src/pages/Home.svelte new file mode 100644 index 0000000..48b2605 --- /dev/null +++ b/launcher/src/pages/Home.svelte @@ -0,0 +1,81 @@ + + +
+ {#if inst} + {#key inst.id} +
+

{greet}, {activeAccount()?.username ?? 'player'}

+
+ {inst.mc_version} + {loaderName[inst.loader]} {shortLoaderVersion(inst.mc_version, inst.loader_version)} + {#if inst.file_count} {inst.file_count} mods & files{/if} +
+

{inst.name}

+

{inst.description || b.tagline}

+
+ +
+ +
+ + + {#if inst.server && b.features.server_status}{/if} +
+
+ {/key} + {:else} +
+

{b.name}

+

{instances().length === 0 ? "Nothing to play yet — your server admin hasn't published any instances you can access." : ''}

+
+ {/if} + + + + {#if app.consoleOpen}{/if} +
+ + diff --git a/launcher/src/pages/Login.svelte b/launcher/src/pages/Login.svelte new file mode 100644 index 0000000..fd6360c --- /dev/null +++ b/launcher/src/pages/Login.svelte @@ -0,0 +1,33 @@ + + + + + diff --git a/launcher/src/pages/Settings.svelte b/launcher/src/pages/Settings.svelte new file mode 100644 index 0000000..d337e47 --- /dev/null +++ b/launcher/src/pages/Settings.svelte @@ -0,0 +1,430 @@ + + + + +
+ + +
+ {#key app.settingsTab} +
+ {#if app.settingsTab === 'account'} +

Accounts

+

Switch between accounts or add another one.

+
+ {#each app.accounts as a (a.id)} +
+ +
{a.username}{kindLabel[a.kind]}{a.role === 'admin' ? ' · Admin' : ''}
+ {#if a.id === app.active} + Active + {:else} + + {/if} + +
+ {:else} +

No accounts yet.

+ {/each} + +
+ {:else if app.settingsTab === 'game'} +

Game

+

Applies to every instance unless you override it in the instance's settings.

+
+ + +
+
+ + {#if s.custom_resolution} +
+ + × + +
+ {/if} + +
+
+ + +
+ {:else if app.settingsTab === 'java'} +

Java & performance

+

The launcher downloads the right Java for each Minecraft version automatically.

+
+ Garbage collector +
+ {#each gcPresets as g} + + {/each} +
+
+ {#if advanced} +
+ + +
+ {/if} +
+ +
+ {:else if app.settingsTab === 'controls'} +

Controls

+

Set your Minecraft keybinds once — the launcher applies them to every instance before it starts.

+
+ +
+
+ {#each BIND_GROUPS as group} +
+

{group.title}

+ {#each group.binds as bind} + {@const key = binds[bind.id]} +
+ {bind.label} + +
+ {/each} +
+ {/each} +
+
+ + Press Esc while choosing to unbind. Shortcut in the launcher: Ctrl + Enter to play. +
+ {:else if app.settingsTab === 'appearance'} +

Appearance

+

Make the launcher feel like yours{themeAllowed ? '' : ' — your server has locked the theme'}.

+ {#if themeAllowed} +
+
+ + +
+ {#if s.theme_mode === 'custom'} +
+ {#each accents as c} + + {/each} + +
+ {/if} +
+ {/if} +
+ s.glass ?? b?.glass ?? true, (v) => (s.glass = v)} onchange={saveSettings} + label="Frosted glass effects" help="Turn off for better performance on older graphics cards." /> + + + +
+ {:else if app.settingsTab === 'launcher'} +

Launcher

+

Connection and behaviour.

+
+ + +
+
+ + +
+ {:else if app.settingsTab === 'storage'} +

Storage

+

Game files are shared between instances, so extra instances of the same version take almost no space.

+ {#if storage} +
+
{bytes(storageTotal)}used in total + +
+
+ + + {#each storage.instances as [, n]}{/each} +
+
+ Game files {bytes(storage.shared)} + Java {bytes(storage.runtimes)} + Instances {bytes(storage.instances.reduce((a, [, n]) => a + n, 0))} +
+
+
+ {#each storage.instances as [id, n]} +
+
{instName(id)}{bytes(n)} · worlds, mods & settings
+ + +
+ {:else} +

No instances downloaded yet.

+ {/each} +
+ + {:else} +

Measuring…

+ {/if} + {:else if app.settingsTab === 'about'} +

About

+
+
+
+
{b?.name ?? 'ScopeNet'} Launcher

Version {app.boot?.app.version} · {app.boot?.app.os} {app.boot?.app.arch}

+
+ {#if app.update} +
+ +
Version {app.update.version} is available

{bytes(app.update.size)}

+ +
+ {#if app.update.notes}
{app.update.notes}
{/if} + {:else if app.boot?.app.repo} + + {/if} +

Built with Tauri, Rust and Svelte. Minecraft is a trademark of Mojang Studios; this launcher is not affiliated with Mojang or Microsoft.

+ {#if app.boot?.app.repo}{/if} +
+ {/if} +
+ {/key} +
+
+ + diff --git a/launcher/src/pages/Setup.svelte b/launcher/src/pages/Setup.svelte new file mode 100644 index 0000000..4c276f3 --- /dev/null +++ b/launcher/src/pages/Setup.svelte @@ -0,0 +1,45 @@ + + +
+
+
+

Connect to your server

+

Enter the panel address your community gave you. You only need to do this once.

+ + {#if error}
{error}
{/if} + +
+
+ + diff --git a/launcher/svelte.config.js b/launcher/svelte.config.js new file mode 100644 index 0000000..36c98fd --- /dev/null +++ b/launcher/svelte.config.js @@ -0,0 +1,3 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +export default { preprocess: vitePreprocess() }; diff --git a/launcher/tsconfig.json b/launcher/tsconfig.json new file mode 100644 index 0000000..2c84844 --- /dev/null +++ b/launcher/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "types": ["svelte", "vite/client"] + }, + "include": ["src/**/*.ts", "src/**/*.svelte"] +} diff --git a/launcher/vite.config.ts b/launcher/vite.config.ts new file mode 100644 index 0000000..753a81a --- /dev/null +++ b/launcher/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; + +// https://tauri.app/start/frontend/vite/ +export default defineConfig({ + plugins: [svelte()], + clearScreen: false, + server: { port: 1420, strictPort: true, watch: { ignored: ['**/src-tauri/**'] } }, + envPrefix: ['VITE_', 'TAURI_ENV_'], + build: { + target: 'es2022', + cssCodeSplit: false, + sourcemap: false, + }, +});