commit 1f2ef54e03393242f8055ad019185ee2503b9c1b Author: AB-UK Date: Fri Dec 26 03:37:21 2025 +0000 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8774a66 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3655 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[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 = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.17", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitstream-io" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.10.0", + "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 = "cc" +version = "1.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[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-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.10.0", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embed-resource" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d506610004cfc74a6f5ee7e8c632b355de5eca1f03ee5e5e0ec11b77d4eb3d61" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "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 = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[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", + "wasip2", +] + +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + +[[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.10.0", + "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.111", +] + +[[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 = "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.111", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[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 = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.0", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core 0.5.0", + "zune-jpeg 0.5.8", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.10.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[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.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "libxdo" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" +dependencies = [ + "libxdo-sys", +] + +[[package]] +name = "libxdo-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" +dependencies = [ + "libc", + "x11", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[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 = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "libxdo", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.17.16", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "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.10.0", + "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.10.0", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[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_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[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", +] + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.10.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[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", + "toml_edit 0.20.2", +] + +[[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.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.111", +] + +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "querystring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9318ead08c799aad12a55a3e78b82e0b6167271ffd1f627b758891282f739187" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +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 = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "thiserror 2.0.17", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serde_json" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signal-hook-tokio" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e" +dependencies = [ + "futures-core", + "libc", + "signal-hook", + "tokio", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[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 = "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.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[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.111", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", +] + +[[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_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d5572781bee8e3f994d7467084e1b1fd7a93ce66bd480f8156ba89dee55a2b" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.17.16", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "v2parser" +version = "0.4.0" +dependencies = [ + "base64", + "clap", + "futures", + "http", + "querystring", + "regex", + "serde", + "serde_json", + "signal-hook", + "signal-hook-tokio", + "tempfile", + "tokio", + "urlencoding", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +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 = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +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.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.111", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "win-test-tray" +version = "0.1.0" +dependencies = [ + "base64", + "embed-resource", + "image", + "reqwest", + "serde", + "serde_json", + "tokio", + "tray-icon", + "v2parser", + "windows", +] + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "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]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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", + "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_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[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 = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[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 = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "zmij" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0095ecd462946aa3927d9297b63ef82fb9a5316d7a37d134eeb36e58228615a" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5" +dependencies = [ + "zune-core 0.5.0", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ccc24eb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "win-test-tray" +version = "0.1.0" +edition = "2024" + +[dependencies] +tray-icon = "0.21" +image = "0.25" +reqwest = { version = "0.12", features = ["blocking"] } +base64 = "0.22" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +v2parser = { path = "../v2-uri-parser" } +tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros"] } + +[build-dependencies] +embed-resource = "2.5" + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.58", features = [ + "Win32_UI_WindowsAndMessaging", + "Win32_Foundation", + "Win32_Graphics_Gdi", + "Win32_System_LibraryLoader", + "Win32_UI_Controls", + "Win32_UI_HiDpi", + "Win32_UI_Shell", + "Win32_UI_Shell_Common", + "Win32_System_Com", +] } diff --git a/app.manifest b/app.manifest new file mode 100644 index 0000000..fee0c8a --- /dev/null +++ b/app.manifest @@ -0,0 +1,34 @@ + + + + Windows Test Tray Application + + + + + + + + + + true/pm + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/app.rc b/app.rc new file mode 100644 index 0000000..ddf7d61 --- /dev/null +++ b/app.rc @@ -0,0 +1 @@ +1 RT_MANIFEST app.manifest diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e5d2d51 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +fn main() { + if cfg!(target_os = "windows") { + embed_resource::compile("app.rc", embed_resource::NONE); + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..a459b64 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,85 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ServerSettings { + pub local_port: u16, + pub proxy_type: String, // "SOCKS" or "HTTP" + #[serde(default = "default_enabled")] + pub enabled: bool, +} + +fn default_enabled() -> bool { + true +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub subscription_url: String, + pub xray_binary_path: String, + #[serde(default)] + pub server_settings: HashMap, +} + +impl Default for Config { + fn default() -> Self { + Config { + subscription_url: String::new(), + xray_binary_path: String::new(), + server_settings: HashMap::new(), + } + } +} + +impl Config { + /// Get the config file path in AppData + pub fn get_config_path() -> Result { + // Get AppData\Roaming path + let appdata = std::env::var("APPDATA") + .map_err(|_| "Failed to get APPDATA environment variable".to_string())?; + + let mut config_dir = PathBuf::from(appdata); + config_dir.push("win-test-tray"); + + // Create directory if it doesn't exist + if !config_dir.exists() { + fs::create_dir_all(&config_dir) + .map_err(|e| format!("Failed to create config directory: {}", e))?; + } + + config_dir.push("config.json"); + Ok(config_dir) + } + + /// Load config from AppData + pub fn load() -> Result { + let config_path = Self::get_config_path()?; + + if !config_path.exists() { + return Ok(Config::default()); + } + + let content = fs::read_to_string(&config_path) + .map_err(|e| format!("Failed to read config file: {}", e))?; + + let config: Config = serde_json::from_str(&content) + .map_err(|e| format!("Failed to parse config JSON: {}", e))?; + + Ok(config) + } + + /// Save config to AppData + pub fn save(&self) -> Result<(), String> { + let config_path = Self::get_config_path()?; + + let json = serde_json::to_string_pretty(self) + .map_err(|e| format!("Failed to serialize config: {}", e))?; + + fs::write(&config_path, json) + .map_err(|e| format!("Failed to write config file: {}", e))?; + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f4d45b1 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,179 @@ +#![windows_subsystem = "windows"] // Commented out for debugging + +mod ui; +mod vpn; +mod config; +mod xray_manager; + +use tray_icon::menu::{MenuEvent, MenuItem}; +use tray_icon::TrayIcon; +use std::sync::{Arc, Mutex, LazyLock}; +use std::sync::atomic::{AtomicBool, Ordering}; + +#[cfg(windows)] +use windows::{ + Win32::{ + Foundation::HWND, + UI::WindowsAndMessaging::*, + }, +}; + +// Global tokio runtime for async operations +pub static TOKIO_RUNTIME: LazyLock = LazyLock::new(|| { + tokio::runtime::Runtime::new().expect("Failed to create tokio runtime") +}); + +// Flag to trigger menu update +pub static MENU_UPDATE_REQUESTED: AtomicBool = AtomicBool::new(false); + +/// Request menu update (can be called from any thread) +pub fn request_menu_update() { + MENU_UPDATE_REQUESTED.store(true, Ordering::Relaxed); +} + +/// Restart all xray servers based on current config +/// This stops all running servers and starts enabled ones +pub fn restart_xray_servers() { + // Stop all running servers first + TOKIO_RUNTIME.block_on(async { + let _ = xray_manager::stop_all_servers().await; + }); + + // Load config and start enabled servers + if let Ok(config) = config::Config::load() { + if !config.subscription_url.is_empty() && !config.xray_binary_path.is_empty() { + // Fetch subscription URIs synchronously + let subscription_uris = vpn::fetch_subscription_uris(&config.subscription_url); + let mut servers = vpn::fetch_and_process_vpn_list(&config.subscription_url); + vpn::assign_local_ports(&mut servers, &config.server_settings); + + // Update global VPN_SERVERS state + if let Ok(mut global_servers) = vpn::VPN_SERVERS.lock() { + *global_servers = Some(servers.clone()); + } + + // Start enabled servers + TOKIO_RUNTIME.block_on(async { + for server in servers.iter() { + if server.enabled { + let server_key = server.get_server_key(); + + if let Some(settings) = config.server_settings.get(&server_key) { + if let Some(uri) = subscription_uris.get(&server_key) { + match xray_manager::start_server( + &server_key, + uri, + settings.local_port, + &settings.proxy_type, + &config.xray_binary_path, + ).await { + Ok(_) => println!("Started server: {}", server.name), + Err(e) => eprintln!("Failed to start server {}: {}", server.name, e), + } + } + } + } + } + }); + } + } + + // Request menu update + request_menu_update(); +} + +/// Update tray icon menu with current running servers +pub fn update_tray_menu(tray_icon: &mut TrayIcon, settings_item: &MenuItem, quit_item: &MenuItem) { + let new_menu = ui::create_tray_menu_with_servers(settings_item, quit_item); + tray_icon.set_menu(Some(Box::new(new_menu))); +} + +fn main() { + // Enable DPI awareness at process start + #[cfg(windows)] + unsafe { + use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext; + let _ = SetProcessDpiAwarenessContext( + windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 + ); + } + + // Auto-start servers on first launch + restart_xray_servers(); + + // Create menu items + let settings_item = MenuItem::new("Settings", true, None); + let quit_item = MenuItem::new("Exit", true, None); + + // Create tray icon with running servers list + let mut tray_icon = ui::create_tray_icon_with_servers(&settings_item, &quit_item); + + // Event handling + let menu_channel = MenuEvent::receiver(); + + // Shared state for settings window + #[cfg(windows)] + let settings_window: Arc>> = Arc::new(Mutex::new(None)); + + // Windows message loop + #[cfg(windows)] + { + let settings_window_clone = settings_window.clone(); + + unsafe { + let mut msg = MSG::default(); + + // Process Windows messages + loop { + // Check if menu update requested + if MENU_UPDATE_REQUESTED.load(Ordering::Relaxed) { + update_tray_menu(&mut tray_icon, &settings_item, &quit_item); + MENU_UPDATE_REQUESTED.store(false, Ordering::Relaxed); + } + + // Check for menu events first + if let Ok(event) = menu_channel.try_recv() { + if event.id == settings_item.id() { + // Open or focus settings window + let mut window = settings_window_clone.lock().unwrap(); + if let Some(hwnd) = *window { + // Window already exists, bring it to front + if IsWindow(hwnd).as_bool() { + let _ = ShowWindow(hwnd, SW_RESTORE); + let _ = SetForegroundWindow(hwnd); + } else { + // Window was closed, create new one + *window = Some(ui::create_settings_window()); + } + } else { + // Create new settings window + *window = Some(ui::create_settings_window()); + } + } else if event.id == quit_item.id() { + // Stop all xray processes before exit + TOKIO_RUNTIME.block_on(async { + let _ = xray_manager::stop_all_servers().await; + }); + break; + } + } + + // Check if we need to update tray menu (poll for changes) + // This is not ideal but tray-icon doesn't support callbacks + // In a real app, you'd use a channel or event system + + // Process Windows messages + let result = GetMessageW(&mut msg, None, 0, 0); + if result.0 == 0 { + // WM_QUIT received + break; + } + + if result.0 > 0 { + let _ = TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + } + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..0b34f8f --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,5 @@ +pub mod tray; +pub mod settings_window; + +pub use tray::{create_tray_icon_with_servers, create_tray_menu_with_servers}; +pub use settings_window::create_settings_window; diff --git a/src/ui/settings_window.rs b/src/ui/settings_window.rs new file mode 100644 index 0000000..befe5bd --- /dev/null +++ b/src/ui/settings_window.rs @@ -0,0 +1,1227 @@ +#[cfg(windows)] +use windows::{ + core::{PCWSTR, w}, + Win32::{ + Foundation::{HWND, LPARAM, LRESULT, WPARAM, HINSTANCE, RECT, BOOL}, + Graphics::Gdi::{UpdateWindow, HBRUSH, SetBkMode, TRANSPARENT, HDC, GetStockObject, WHITE_BRUSH}, + System::LibraryLoader::GetModuleHandleW, + UI::WindowsAndMessaging::*, + }, +}; + +use crate::vpn::{VpnServer, VPN_SERVERS, fetch_and_process_vpn_list, assign_local_ports}; + +// Custom Windows message for updating server list +const WM_UPDATE_SERVERS: u32 = WM_USER + 1; + +// Control ID ranges +const ID_URL_EDIT: i32 = 1001; +const ID_UPDATE_BUTTON: i32 = 1002; +const ID_SCROLL_CONTAINER: i32 = 1003; +const ID_SCROLL_CONTAINER_CLASS: i32 = 1004; // Custom class for container +const ID_SAVE_BUTTON: i32 = 1005; +const ID_CANCEL_BUTTON: i32 = 1006; +const ID_XRAY_PATH_EDIT: i32 = 1007; +const ID_XRAY_BROWSE_BUTTON: i32 = 1008; +const ID_SERVER_CHECKBOX_BASE: i32 = 2000; // 2000, 2001, 2002... +const ID_SERVER_PORT_EDIT_BASE: i32 = 3000; // 3000, 3001, 3002... +const ID_SERVER_PROXY_COMBO_BASE: i32 = 4000; // 4000, 4001, 4002... +const ID_SERVER_LABEL_BASE: i32 = 5000; // 5000, 5001, 5002... for "Proxy Port:" labels + +// Layout constants for consistent formatting +const MARGIN: i32 = 15; +const FONT_SIZE: i32 = 32; // Reduced from 40 +const LABEL_HEIGHT: i32 = 45; // Reduced from 50 +const CONTROL_HEIGHT: i32 = 45; // Reduced from 50 +const ROW_HEIGHT: i32 = 55; // Reduced from 60 +const URL_LABEL_WIDTH: i32 = 200; + +// Windows notification codes +const EN_CHANGE: usize = 0x0300; +const CBN_SELCHANGE: usize = 1; +const BN_CLICKED: usize = 0; + +#[cfg(windows)] +pub unsafe fn create_settings_window() -> HWND { + // Convert strings to UTF-16 (wide chars) for Windows API + let class_name_str: Vec = "SettingsWindowClass\0" + .encode_utf16() + .collect(); + let class_name = PCWSTR::from_raw(class_name_str.as_ptr()); + + // Register window class with white background + let hinstance = unsafe { GetModuleHandleW(None).unwrap() }; + + let wc = WNDCLASSW { + lpfnWndProc: Some(settings_window_proc), + hInstance: hinstance.into(), + lpszClassName: class_name, + hbrBackground: unsafe { HBRUSH(GetStockObject(WHITE_BRUSH).0) }, + style: CS_HREDRAW | CS_VREDRAW, + ..Default::default() + }; + + unsafe { RegisterClassW(&wc) }; + + // Register custom class for scroll container with white background + let container_class_str: Vec = "ScrollContainerClass\0".encode_utf16().collect(); + let container_class = PCWSTR::from_raw(container_class_str.as_ptr()); + + let wc_container = WNDCLASSW { + lpfnWndProc: Some(container_window_proc), + hInstance: hinstance.into(), + lpszClassName: container_class, + hbrBackground: unsafe { HBRUSH(GetStockObject(WHITE_BRUSH).0) }, + style: CS_HREDRAW | CS_VREDRAW, + ..Default::default() + }; + + unsafe { RegisterClassW(&wc_container) }; + + let window_title_str: Vec = "Settings\0" + .encode_utf16() + .collect(); + let window_title = PCWSTR::from_raw(window_title_str.as_ptr()); + + // Create main window + let hwnd = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + class_name, + window_title, + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, + CW_USEDEFAULT, + 900, + 1200, // Increased from 1050 to 1200 (+15%) + None, + None, + hinstance, + None, + ).expect("Failed to create window") + }; + + println!("Settings window created: {:?}", hwnd); + + // Create controls + unsafe { create_controls(hwnd, hinstance.into()) }; + + unsafe { + let _ = ShowWindow(hwnd, SW_SHOW); + let _ = UpdateWindow(hwnd); + } + + hwnd +} + +// Window procedure for scroll container to handle background and scrolling +#[cfg(windows)] +unsafe extern "system" fn container_window_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + match msg { + WM_COMMAND => { + // Forward WM_COMMAND to parent window + unsafe { + if let Ok(parent) = GetParent(hwnd) { + return SendMessageW(parent, msg, wparam, lparam); + } + } + LRESULT(0) + } + WM_CTLCOLORSTATIC => { + unsafe { + let hdc = HDC(wparam.0 as *mut _); + SetBkMode(hdc, TRANSPARENT); + LRESULT(GetStockObject(WHITE_BRUSH).0 as isize) + } + } + WM_VSCROLL => { + // Handle vertical scrolling + let action = wparam.0 & 0xFFFF; + + unsafe { + let mut si = SCROLLINFO { + cbSize: std::mem::size_of::() as u32, + fMask: SIF_ALL, + nMin: 0, + nMax: 0, + nPage: 0, + nPos: 0, + nTrackPos: 0, + }; + + use windows::Win32::UI::WindowsAndMessaging::GetScrollInfo; + let _ = GetScrollInfo(hwnd, SB_VERT, &mut si); + + let old_pos = si.nPos; + + match action { + 0 => si.nPos -= 20, // SB_LINEUP + 1 => si.nPos += 20, // SB_LINEDOWN + 2 => si.nPos -= si.nPage as i32, // SB_PAGEUP + 3 => si.nPos += si.nPage as i32, // SB_PAGEDOWN + 5 => si.nPos = si.nTrackPos, // SB_THUMBTRACK + _ => {} + } + + // Clamp position + si.nPos = si.nPos.max(si.nMin); + si.nPos = si.nPos.min(si.nMax - si.nPage as i32 + 1); + + if si.nPos != old_pos { + use windows::Win32::UI::Controls::SetScrollInfo; + si.fMask = SIF_POS; + SetScrollInfo(hwnd, SB_VERT, &si, true); + + // Move child windows based on scroll position + let scroll_delta = old_pos - si.nPos; + + // Enumerate and move all child windows + unsafe extern "system" fn move_child(child: HWND, lparam: LPARAM) -> BOOL { + let delta = lparam.0 as i32; + let mut rect = RECT::default(); + unsafe { + if GetWindowRect(child, &mut rect).is_ok() { + let parent = GetParent(child).unwrap_or(HWND::default()); + let mut pt = windows::Win32::Foundation::POINT { x: rect.left, y: rect.top }; + use windows::Win32::Graphics::Gdi::ScreenToClient; + let _ = ScreenToClient(parent, &mut pt); + + let _ = SetWindowPos( + child, + None, + pt.x, + pt.y + delta, + 0, 0, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE, + ); + } + } + true.into() + } + + let _ = EnumChildWindows(hwnd, Some(move_child), LPARAM(scroll_delta as isize)); + } + } + LRESULT(0) + } + WM_MOUSEWHEEL => { + // Handle mouse wheel scrolling + let delta = ((wparam.0 >> 16) & 0xFFFF) as i16; + let scroll_lines = if delta > 0 { 0 } else { 1 }; // 0=SB_LINEUP, 1=SB_LINEDOWN + + // Send scroll message multiple times for smoother scrolling + for _ in 0..(delta.abs() / 40).max(1) { + unsafe { + SendMessageW(hwnd, WM_VSCROLL, WPARAM(scroll_lines), LPARAM(0)); + } + } + LRESULT(0) + } + _ => unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }, + } +} + +#[cfg(windows)] +unsafe fn create_controls(parent: HWND, hinstance: HINSTANCE) { + // Load config and set URL field + let config = crate::config::Config::load().unwrap_or_default(); + + // Create font for all controls + let hfont = unsafe { + use windows::Win32::Graphics::Gdi::{CreateFontW, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, FF_DONTCARE, FW_NORMAL}; + CreateFontW( + FONT_SIZE, + 0, 0, 0, + FW_NORMAL.0 as i32, + 0, 0, 0, + DEFAULT_CHARSET.0 as u32, + OUT_DEFAULT_PRECIS.0 as u32, + CLIP_DEFAULT_PRECIS.0 as u32, + DEFAULT_QUALITY.0 as u32, + (DEFAULT_PITCH.0 | FF_DONTCARE.0) as u32, + w!("Segoe UI"), + ) + }; + + // First row Y position + let row1_y = MARGIN; + + // Label "Subscription URL:" + let label_text: Vec = "Subscription URL:\0".encode_utf16().collect(); + let label = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("STATIC"), + PCWSTR::from_raw(label_text.as_ptr()), + WS_CHILD | WS_VISIBLE, + MARGIN, + row1_y, + URL_LABEL_WIDTH, + LABEL_HEIGHT, + parent, + None, + hinstance, + None, + ).ok() + }; + if let Some(lbl) = label { + unsafe { SendMessageW(lbl, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // URL Edit control - set loaded URL + let url_text_wide: Vec = format!("{}\0", config.subscription_url).encode_utf16().collect(); + let url_edit = unsafe { + CreateWindowExW( + WS_EX_CLIENTEDGE, + w!("EDIT"), + PCWSTR::from_raw(url_text_wide.as_ptr()), + WS_CHILD | WS_VISIBLE | WS_BORDER | WINDOW_STYLE(ES_AUTOHSCROLL as u32), + MARGIN + URL_LABEL_WIDTH + 10, + row1_y, + 450, + CONTROL_HEIGHT, + parent, + HMENU(ID_URL_EDIT as _), + hinstance, + None, + ).expect("Failed to create URL edit control") + }; + unsafe { SendMessageW(url_edit, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + + // Update button + let update_btn_text: Vec = "Update\0".encode_utf16().collect(); + let update_btn = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("BUTTON"), + PCWSTR::from_raw(update_btn_text.as_ptr()), + WS_CHILD | WS_VISIBLE | WINDOW_STYLE(BS_PUSHBUTTON as u32), + MARGIN + URL_LABEL_WIDTH + 10 + 450 + 10, + row1_y, + 120, + CONTROL_HEIGHT, + parent, + HMENU(ID_UPDATE_BUTTON as _), + hinstance, + None, + ).ok() + }; + if let Some(btn) = update_btn { + unsafe { SendMessageW(btn, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // Second row Y position + let row2_y = row1_y + CONTROL_HEIGHT + MARGIN; + + // Label "Xray Binary:" + let xray_label: Vec = "Xray Binary:\0".encode_utf16().collect(); + let xray_lbl = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("STATIC"), + PCWSTR::from_raw(xray_label.as_ptr()), + WS_CHILD | WS_VISIBLE, + MARGIN, + row2_y, + URL_LABEL_WIDTH, + LABEL_HEIGHT, + parent, + None, + hinstance, + None, + ).ok() + }; + if let Some(lbl) = xray_lbl { + unsafe { SendMessageW(lbl, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // Xray binary path edit control + let xray_path_text_wide: Vec = format!("{}\0", config.xray_binary_path).encode_utf16().collect(); + let xray_path_edit = unsafe { + CreateWindowExW( + WS_EX_CLIENTEDGE, + w!("EDIT"), + PCWSTR::from_raw(xray_path_text_wide.as_ptr()), + WS_CHILD | WS_VISIBLE | WS_BORDER | WINDOW_STYLE(ES_AUTOHSCROLL as u32), + MARGIN + URL_LABEL_WIDTH + 10, + row2_y, + 450, + CONTROL_HEIGHT, + parent, + HMENU(ID_XRAY_PATH_EDIT as _), + hinstance, + None, + ).expect("Failed to create Xray path edit control") + }; + unsafe { SendMessageW(xray_path_edit, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + + // Browse button + let browse_btn_text: Vec = "Browse...\0".encode_utf16().collect(); + let browse_btn = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("BUTTON"), + PCWSTR::from_raw(browse_btn_text.as_ptr()), + WS_CHILD | WS_VISIBLE | WINDOW_STYLE(BS_PUSHBUTTON as u32), + MARGIN + URL_LABEL_WIDTH + 10 + 450 + 10, + row2_y, + 120, + CONTROL_HEIGHT, + parent, + HMENU(ID_XRAY_BROWSE_BUTTON as _), + hinstance, + None, + ).ok() + }; + if let Some(btn) = browse_btn { + unsafe { SendMessageW(btn, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // Third row Y position + let row3_y = row2_y + CONTROL_HEIGHT + MARGIN; + + // Label "VPN Servers:" + let list_label: Vec = "VPN Servers:\0".encode_utf16().collect(); + let list_lbl = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("STATIC"), + PCWSTR::from_raw(list_label.as_ptr()), + WS_CHILD | WS_VISIBLE, + MARGIN, + row3_y, + 200, + LABEL_HEIGHT, + parent, + None, + hinstance, + None, + ).ok() + }; + if let Some(lbl) = list_lbl { + unsafe { SendMessageW(lbl, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // Server list container Y position + let container_y = row3_y + LABEL_HEIGHT + 10; + + // Get client area size to calculate container height dynamically + let mut client_rect = RECT::default(); + unsafe { GetClientRect(parent, &mut client_rect).ok() }; + let client_width = client_rect.right - client_rect.left; + let client_height = client_rect.bottom - client_rect.top; + + // Calculate container size based on window size + // Reserve space for Save/Cancel buttons at the bottom (60px) + const BUTTON_ROW_HEIGHT: i32 = 60; + let container_width = client_width - (2 * MARGIN); + let container_height = client_height - container_y - MARGIN - BUTTON_ROW_HEIGHT; + + // Scrollable container for server panels with custom class + let container_class_str: Vec = "ScrollContainerClass\0".encode_utf16().collect(); + unsafe { + CreateWindowExW( + WS_EX_CLIENTEDGE, + PCWSTR::from_raw(container_class_str.as_ptr()), + PCWSTR::null(), + WS_CHILD | WS_VISIBLE | WS_VSCROLL, + MARGIN, + container_y, + container_width, + container_height, + parent, + HMENU(ID_SCROLL_CONTAINER as _), + hinstance, + None, + ).expect("Failed to create scroll container") + }; + + // Bottom buttons row + let buttons_y = container_y + container_height + 10; + + // Save button + let save_btn_text: Vec = "Save\0".encode_utf16().collect(); + let save_btn = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("BUTTON"), + PCWSTR::from_raw(save_btn_text.as_ptr()), + WS_CHILD | WS_VISIBLE | WINDOW_STYLE(BS_PUSHBUTTON as u32), + client_width - 240, // Right side: 120px button + 10px margin + 120px button + buttons_y, + 110, + CONTROL_HEIGHT, + parent, + HMENU(ID_SAVE_BUTTON as _), + hinstance, + None, + ).ok() + }; + if let Some(btn) = save_btn { + unsafe { SendMessageW(btn, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // Cancel button + let cancel_btn_text: Vec = "Cancel\0".encode_utf16().collect(); + let cancel_btn = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("BUTTON"), + PCWSTR::from_raw(cancel_btn_text.as_ptr()), + WS_CHILD | WS_VISIBLE | WINDOW_STYLE(BS_PUSHBUTTON as u32), + client_width - 120, // Right side + buttons_y, + 110, + CONTROL_HEIGHT, + parent, + HMENU(ID_CANCEL_BUTTON as _), + hinstance, + None, + ).ok() + }; + if let Some(btn) = cancel_btn { + unsafe { SendMessageW(btn, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // Auto-load servers from subscription URL if available + if !config.subscription_url.is_empty() { + let url = config.subscription_url.clone(); + let saved_settings = config.server_settings.clone(); + let hwnd_raw = parent.0 as isize; + + std::thread::spawn(move || { + let mut servers = fetch_and_process_vpn_list(&url); + + // Assign settings (preserving saved ones) + assign_local_ports(&mut servers, &saved_settings); + + // Store servers globally + if let Ok(mut global_servers) = VPN_SERVERS.lock() { + *global_servers = Some(servers.clone()); + } + + // Update UI on main thread via PostMessage + unsafe { + let hwnd = HWND(hwnd_raw as *mut _); + let _ = PostMessageW(hwnd, WM_UPDATE_SERVERS, WPARAM(0), LPARAM(0)); + } + }); + } +} + +#[cfg(windows)] +unsafe extern "system" fn settings_window_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + match msg { + WM_COMMAND => { + let control_id = wparam.0 & 0xFFFF; + let notification_code = (wparam.0 >> 16) & 0xFFFF; + + // Update button clicked + if control_id == ID_UPDATE_BUTTON as usize && notification_code == 0 { + println!("Update button clicked!"); + + // Get text from edit control + let url_edit = unsafe { GetDlgItem(hwnd, ID_URL_EDIT) }; + if url_edit.is_ok() && !url_edit.as_ref().unwrap().is_invalid() { + let mut buffer = vec![0u16; 2048]; + let len = unsafe { + GetWindowTextW(url_edit.unwrap(), &mut buffer) + }; + + if len > 0 { + let url = String::from_utf16_lossy(&buffer[..len as usize]); + println!("URL entered: {}", url); + + // Fetch and process in background thread + let hwnd_raw = hwnd.0 as isize; + std::thread::spawn(move || { + let mut servers = fetch_and_process_vpn_list(&url); + + // Load config to get saved settings + let config = crate::config::Config::load().unwrap_or_default(); + + // Assign settings (preserving saved ones) + assign_local_ports(&mut servers, &config.server_settings); + + // Store servers globally + if let Ok(mut global_servers) = VPN_SERVERS.lock() { + *global_servers = Some(servers.clone()); + } + + // Update UI on main thread via PostMessage + unsafe { + let hwnd = HWND(hwnd_raw as *mut _); + let _ = PostMessageW(hwnd, WM_UPDATE_SERVERS, WPARAM(0), LPARAM(0)); + } + }); + } else { + println!("No URL entered"); + } + } + } + // Handle checkbox changes + else if control_id >= ID_SERVER_CHECKBOX_BASE as usize + && control_id < ID_SERVER_PORT_EDIT_BASE as usize + && notification_code == BN_CLICKED { + let server_index = control_id - ID_SERVER_CHECKBOX_BASE as usize; + + // Get checkbox state + if let Ok(container) = unsafe { GetDlgItem(hwnd, ID_SCROLL_CONTAINER) } { + if let Ok(checkbox) = unsafe { GetDlgItem(container, control_id as i32) } { + let state = unsafe { SendMessageW(checkbox, BM_GETCHECK, WPARAM(0), LPARAM(0)) }; + let checked = state.0 == 1; + + // Update global state + if let Ok(mut global_servers) = VPN_SERVERS.try_lock() { + if let Some(servers) = global_servers.as_mut() { + if let Some(server) = servers.get_mut(server_index) { + server.enabled = checked; + } + } + } + } + } + } + // Handle port edit changes + else if control_id >= ID_SERVER_PORT_EDIT_BASE as usize + && control_id < ID_SERVER_PROXY_COMBO_BASE as usize + && notification_code == EN_CHANGE { + let server_index = control_id - ID_SERVER_PORT_EDIT_BASE as usize; + + // Get container window first, then find edit control in container + if let Ok(container) = unsafe { GetDlgItem(hwnd, ID_SCROLL_CONTAINER) } { + if let Ok(edit) = unsafe { GetDlgItem(container, control_id as i32) } { + let mut buffer = vec![0u16; 16]; + let len = unsafe { GetWindowTextW(edit, &mut buffer) }; + + if len > 0 { + let port_text = String::from_utf16_lossy(&buffer[..len as usize]); + + // Parse port as u16 + if let Ok(port) = port_text.parse::() { + // Update global state + if let Ok(mut global_servers) = VPN_SERVERS.try_lock() { + if let Some(servers) = global_servers.as_mut() { + if let Some(server) = servers.get_mut(server_index) { + server.local_port = port; + } + } + } + } + } + } + } + } + // Handle proxy type combobox changes + else if control_id >= ID_SERVER_PROXY_COMBO_BASE as usize + && notification_code == CBN_SELCHANGE { + let server_index = control_id - ID_SERVER_PROXY_COMBO_BASE as usize; + + // Get container window first, then find combo control in container + if let Ok(container) = unsafe { GetDlgItem(hwnd, ID_SCROLL_CONTAINER) } { + if let Ok(combo) = unsafe { GetDlgItem(container, control_id as i32) } { + let sel_idx = unsafe { SendMessageW(combo, CB_GETCURSEL, WPARAM(0), LPARAM(0)) }; + + if sel_idx.0 >= 0 { + let proxy_type = if sel_idx.0 == 0 { "SOCKS" } else { "HTTP" }; + + // Update global state + if let Ok(mut global_servers) = VPN_SERVERS.try_lock() { + if let Some(servers) = global_servers.as_mut() { + if let Some(server) = servers.get_mut(server_index) { + server.proxy_type = proxy_type.to_string(); + } + } + } + } + } + } + } + // Handle Browse button for Xray binary + else if control_id == ID_XRAY_BROWSE_BUTTON as usize && notification_code == 0 { + + // Open file dialog + use windows::Win32::UI::Shell::Common::COMDLG_FILTERSPEC; + use windows::Win32::UI::Shell::{IFileOpenDialog, FileOpenDialog}; + use windows::Win32::System::Com::{CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_APARTMENTTHREADED}; + + unsafe { + // Initialize COM + let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED); + + // Create file open dialog + if let Ok(dialog) = CoCreateInstance::<_, IFileOpenDialog>(&FileOpenDialog, None, CLSCTX_ALL) { + // Set file type filter + let filter_spec = [ + COMDLG_FILTERSPEC { + pszName: w!("Executable Files"), + pszSpec: w!("*.exe"), + }, + COMDLG_FILTERSPEC { + pszName: w!("All Files"), + pszSpec: w!("*.*"), + }, + ]; + + let _ = dialog.SetFileTypes(&filter_spec); + let _ = dialog.SetFileTypeIndex(1); + + // Show dialog + if dialog.Show(hwnd).is_ok() { + if let Ok(result) = dialog.GetResult() { + if let Ok(path) = result.GetDisplayName(windows::Win32::UI::Shell::SIGDN_FILESYSPATH) { + let path_str = path.to_string().unwrap_or_default(); + + // Update edit control + if let Ok(xray_edit) = GetDlgItem(hwnd, ID_XRAY_PATH_EDIT) { + let path_wide: Vec = format!("{}\0", path_str).encode_utf16().collect(); + SetWindowTextW(xray_edit, PCWSTR::from_raw(path_wide.as_ptr())).ok(); + } + } + } + } + } + } + } + // Handle Save button + else if control_id == ID_SAVE_BUTTON as usize && notification_code == 0 { + + // Get URL from edit control + let url_edit = unsafe { GetDlgItem(hwnd, ID_URL_EDIT) }; + let subscription_url = if url_edit.is_ok() && !url_edit.as_ref().unwrap().is_invalid() { + let mut buffer = vec![0u16; 2048]; + let len = unsafe { GetWindowTextW(url_edit.unwrap(), &mut buffer) }; + if len > 0 { + String::from_utf16_lossy(&buffer[..len as usize]) + } else { + String::new() + } + } else { + String::new() + }; + + // Get Xray binary path from edit control + let xray_edit = unsafe { GetDlgItem(hwnd, ID_XRAY_PATH_EDIT) }; + let xray_binary_path = if xray_edit.is_ok() && !xray_edit.as_ref().unwrap().is_invalid() { + let mut buffer = vec![0u16; 2048]; + let len = unsafe { GetWindowTextW(xray_edit.unwrap(), &mut buffer) }; + if len > 0 { + String::from_utf16_lossy(&buffer[..len as usize]) + } else { + String::new() + } + } else { + String::new() + }; + + // Build server_settings HashMap from current servers + use std::collections::HashMap; + let mut server_settings = HashMap::new(); + + if let Ok(global_servers) = VPN_SERVERS.lock() { + if let Some(servers) = global_servers.as_ref() { + for server in servers { + let key = server.get_server_key(); + let settings = crate::config::ServerSettings { + local_port: server.local_port, + proxy_type: server.proxy_type.clone(), + enabled: server.enabled, + }; + server_settings.insert(key, settings); + } + } + } + + // Create and save config + let config = crate::config::Config { + subscription_url, + xray_binary_path, + server_settings, + }; + + match config.save() { + Ok(_) => { + // Restart xray servers with new config + crate::restart_xray_servers(); + + // Close window + unsafe { let _ = DestroyWindow(hwnd); } + } + Err(e) => { + // Show error message box + let error_msg: Vec = format!("Failed to save config:\n{}\0", e).encode_utf16().collect(); + let title: Vec = "Error\0".encode_utf16().collect(); + unsafe { + MessageBoxW( + hwnd, + PCWSTR::from_raw(error_msg.as_ptr()), + PCWSTR::from_raw(title.as_ptr()), + MB_OK | MB_ICONERROR, + ); + } + } + } + } + // Handle Cancel button + else if control_id == ID_CANCEL_BUTTON as usize && notification_code == 0 { + // Close window without saving + unsafe { let _ = DestroyWindow(hwnd); } + } + + LRESULT(0) + } + WM_CTLCOLORSTATIC => { + // Make static text background transparent + unsafe { + let hdc = HDC(wparam.0 as *mut _); + SetBkMode(hdc, TRANSPARENT); + // Return white brush to match window background + LRESULT(GetStockObject(WHITE_BRUSH).0 as isize) + } + } + WM_SIZE => { + // Resize controls when window is resized + let width = (lparam.0 & 0xFFFF) as i32; + let height = ((lparam.0 >> 16) & 0xFFFF) as i32; + + const BUTTON_ROW_HEIGHT: i32 = 60; + let row1_y = MARGIN; + let row2_y = row1_y + CONTROL_HEIGHT + MARGIN; + let row3_y = row2_y + CONTROL_HEIGHT + MARGIN; + let container_y = row3_y + LABEL_HEIGHT + 10; + let container_height = height - container_y - MARGIN - BUTTON_ROW_HEIGHT; + let buttons_y = container_y + container_height + 10; + + unsafe { + // Resize URL edit control + if let Ok(url_edit) = GetDlgItem(hwnd, ID_URL_EDIT) { + if !url_edit.is_invalid() { + SetWindowPos( + url_edit, + None, + 0, 0, + width - (MARGIN + URL_LABEL_WIDTH + 10 + 120 + 10 + MARGIN), + CONTROL_HEIGHT, + SWP_NOMOVE | SWP_NOZORDER, + ).ok(); + } + } + + // Move Update button to stay on the right + if let Ok(update_btn) = GetDlgItem(hwnd, ID_UPDATE_BUTTON) { + if !update_btn.is_invalid() { + SetWindowPos( + update_btn, + None, + width - 120 - MARGIN, + row1_y, + 0, 0, + SWP_NOSIZE | SWP_NOZORDER, + ).ok(); + } + } + + // Resize Xray path edit control + if let Ok(xray_edit) = GetDlgItem(hwnd, ID_XRAY_PATH_EDIT) { + if !xray_edit.is_invalid() { + SetWindowPos( + xray_edit, + None, + 0, 0, + width - (MARGIN + URL_LABEL_WIDTH + 10 + 120 + 10 + MARGIN), + CONTROL_HEIGHT, + SWP_NOMOVE | SWP_NOZORDER, + ).ok(); + } + } + + // Move Browse button to stay on the right + if let Ok(browse_btn) = GetDlgItem(hwnd, ID_XRAY_BROWSE_BUTTON) { + if !browse_btn.is_invalid() { + SetWindowPos( + browse_btn, + None, + width - 120 - MARGIN, + row2_y, + 0, 0, + SWP_NOSIZE | SWP_NOZORDER, + ).ok(); + } + } + + // Resize scroll container to fill remaining space + if let Ok(container) = GetDlgItem(hwnd, ID_SCROLL_CONTAINER) { + if !container.is_invalid() { + SetWindowPos( + container, + None, + 0, 0, + width - (2 * MARGIN), + container_height, + SWP_NOMOVE | SWP_NOZORDER, + ).ok(); + + // Only resize checkboxes, don't rebuild entire list + resize_server_list_items(container); + } + } + + // Move Save button + if let Ok(save_btn) = GetDlgItem(hwnd, ID_SAVE_BUTTON) { + if !save_btn.is_invalid() { + SetWindowPos( + save_btn, + None, + width - 240, + buttons_y, + 0, 0, + SWP_NOSIZE | SWP_NOZORDER, + ).ok(); + } + } + + // Move Cancel button + if let Ok(cancel_btn) = GetDlgItem(hwnd, ID_CANCEL_BUTTON) { + if !cancel_btn.is_invalid() { + SetWindowPos( + cancel_btn, + None, + width - 120, + buttons_y, + 0, 0, + SWP_NOSIZE | SWP_NOZORDER, + ).ok(); + } + } + } + LRESULT(0) + } + _ if msg == WM_UPDATE_SERVERS => { + // Custom message: rebuild server list UI + if let Ok(global_servers) = VPN_SERVERS.lock() { + if let Some(servers) = &*global_servers { + unsafe { + rebuild_server_list(hwnd, servers); + } + } + } + LRESULT(0) + } + WM_DESTROY => { + println!("Settings window destroyed"); + LRESULT(0) + } + WM_CLOSE => { + unsafe { let _ = DestroyWindow(hwnd); }; + LRESULT(0) + } + _ => unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }, + } +} + +// Rebuild the server list with custom panels +#[cfg(windows)] +unsafe fn rebuild_server_list(parent_hwnd: HWND, servers: &[VpnServer]) { + let container = unsafe { GetDlgItem(parent_hwnd, ID_SCROLL_CONTAINER) }; + if container.is_err() || container.as_ref().unwrap().is_invalid() { + return; + } + let container = container.unwrap(); + + // Destroy all existing child windows in container + unsafe { + let _ = EnumChildWindows( + container, + Some(destroy_child_window), + LPARAM(0), + ); + } + + let hinstance: HINSTANCE = unsafe { GetModuleHandleW(None).unwrap().into() }; + + // Create font for controls (reduced to match main font) + let hfont = unsafe { + use windows::Win32::Graphics::Gdi::{CreateFontW, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, FF_DONTCARE, FW_NORMAL}; + CreateFontW( + 30, // Reduced from 36 + 0, 0, 0, + FW_NORMAL.0 as i32, + 0, 0, 0, + DEFAULT_CHARSET.0 as u32, + OUT_DEFAULT_PRECIS.0 as u32, + CLIP_DEFAULT_PRECIS.0 as u32, + DEFAULT_QUALITY.0 as u32, + (DEFAULT_PITCH.0 | FF_DONTCARE.0) as u32, + w!("Segoe UI"), + ) + }; + + // Get container width for dynamic checkbox width + let mut container_rect = RECT::default(); + unsafe { GetClientRect(container, &mut container_rect).ok() }; + let container_width = container_rect.right - container_rect.left; + + const SERVER_ITEM_MARGIN: i32 = 10; + const LABEL_WIDTH: i32 = 130; // Increased from 100 to fit "Proxy Port:" + const PORT_EDIT_WIDTH: i32 = 90; // Increased from 80 + const COMBO_WIDTH: i32 = 130; // Increased from 120 + const RIGHT_CONTROLS_WIDTH: i32 = LABEL_WIDTH + PORT_EDIT_WIDTH + COMBO_WIDTH + 30; // +30 for spacing + let checkbox_width = container_width - RIGHT_CONTROLS_WIDTH - 20; // Dynamic width + + for (idx, server) in servers.iter().enumerate() { + let y_pos = idx as i32 * ROW_HEIGHT + SERVER_ITEM_MARGIN; + + // Checkbox (enabled/disabled) - dynamic width + let checkbox_text = format!("{} - {} ({}:{})\0", + server.name, server.address, server.protocol, server.port); + let checkbox_text_wide: Vec = checkbox_text.encode_utf16().collect(); + + let checkbox = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("BUTTON"), + PCWSTR::from_raw(checkbox_text_wide.as_ptr()), + WS_CHILD | WS_VISIBLE | WINDOW_STYLE(BS_AUTOCHECKBOX as u32), + 10, + y_pos, + checkbox_width, + CONTROL_HEIGHT, + container, + HMENU((ID_SERVER_CHECKBOX_BASE + idx as i32) as _), + hinstance, + None, + ).ok() + }; + + if let Some(cb) = checkbox { + unsafe { SendMessageW(cb, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // Set checkbox state + if let Ok(checkbox) = unsafe { GetDlgItem(container, ID_SERVER_CHECKBOX_BASE + idx as i32) } { + unsafe { + SendMessageW( + checkbox, + BM_SETCHECK, + WPARAM(if server.enabled { 1 } else { 0 }), + LPARAM(0), + ); + } + } + + let right_controls_x = 10 + checkbox_width + 10; + + // Label "Proxy Port:" with ID for resizing + let port_label: Vec = "Proxy Port:\0".encode_utf16().collect(); + let port_lbl = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("STATIC"), + PCWSTR::from_raw(port_label.as_ptr()), + WS_CHILD | WS_VISIBLE, + right_controls_x, + y_pos + 5, + LABEL_WIDTH, + CONTROL_HEIGHT, + container, + HMENU((ID_SERVER_LABEL_BASE + idx as i32) as _), // Add ID + hinstance, + None, + ).ok() + }; + if let Some(lbl) = port_lbl { + unsafe { SendMessageW(lbl, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // Edit control for proxy port + let port_text_wide: Vec = format!("{}\0", server.local_port).encode_utf16().collect(); + let port_edit = unsafe { + CreateWindowExW( + WS_EX_CLIENTEDGE, + w!("EDIT"), + PCWSTR::from_raw(port_text_wide.as_ptr()), + WS_CHILD | WS_VISIBLE | WS_BORDER | WINDOW_STYLE(ES_AUTOHSCROLL as u32 | ES_NUMBER as u32), + right_controls_x + LABEL_WIDTH + 5, + y_pos, + PORT_EDIT_WIDTH, + CONTROL_HEIGHT, + container, + HMENU((ID_SERVER_PORT_EDIT_BASE + idx as i32) as _), + hinstance, + None, + ).ok() + }; + if let Some(edit) = port_edit { + unsafe { SendMessageW(edit, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + } + + // ComboBox for proxy type (SOCKS/HTTP) + let combo = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + w!("COMBOBOX"), + PCWSTR::null(), + WS_CHILD | WS_VISIBLE | WINDOW_STYLE(CBS_DROPDOWNLIST as u32 | WS_VSCROLL.0), + right_controls_x + LABEL_WIDTH + 5 + PORT_EDIT_WIDTH + 10, + y_pos, + COMBO_WIDTH, + 200, // Dropdown height + container, + HMENU((ID_SERVER_PROXY_COMBO_BASE + idx as i32) as _), + hinstance, + None, + ).ok() + }; + + if let Some(cb) = combo { + unsafe { SendMessageW(cb, WM_SETFONT, WPARAM(hfont.0 as usize), LPARAM(1)); } + + // Add items + let socks_text: Vec = "SOCKS\0".encode_utf16().collect(); + let http_text: Vec = "HTTP\0".encode_utf16().collect(); + + unsafe { + SendMessageW(cb, CB_ADDSTRING, WPARAM(0), LPARAM(socks_text.as_ptr() as isize)); + SendMessageW(cb, CB_ADDSTRING, WPARAM(0), LPARAM(http_text.as_ptr() as isize)); + + // Set current selection + let sel_idx = if server.proxy_type == "SOCKS" { 0 } else { 1 }; + SendMessageW(cb, CB_SETCURSEL, WPARAM(sel_idx), LPARAM(0)); + } + } + } + + // Update scroll range + let total_height = servers.len() as i32 * ROW_HEIGHT + SERVER_ITEM_MARGIN * 2; + let mut rect = RECT::default(); + unsafe { GetClientRect(container, &mut rect).ok() }; + let visible_height = rect.bottom - rect.top; + + if total_height > visible_height { + let si = SCROLLINFO { + cbSize: std::mem::size_of::() as u32, + fMask: SIF_RANGE | SIF_PAGE, + nMin: 0, + nMax: total_height, + nPage: visible_height as u32, + nPos: 0, + nTrackPos: 0, + }; + unsafe { + use windows::Win32::UI::Controls::SetScrollInfo; + SetScrollInfo(container, SB_VERT, &si, true); + } + } +} + +// Resize server list items without rebuilding (for window resize performance) +#[cfg(windows)] +unsafe fn resize_server_list_items(container: HWND) { + // Get container width + let mut container_rect = RECT::default(); + unsafe { GetClientRect(container, &mut container_rect).ok() }; + let container_width = container_rect.right - container_rect.left; + + const LABEL_WIDTH: i32 = 130; + const PORT_EDIT_WIDTH: i32 = 90; + const COMBO_WIDTH: i32 = 130; + const RIGHT_CONTROLS_WIDTH: i32 = LABEL_WIDTH + PORT_EDIT_WIDTH + COMBO_WIDTH + 30; + let checkbox_width = container_width - RIGHT_CONTROLS_WIDTH - 20; + let right_controls_x = 10 + checkbox_width + 10; + + // Get server count from global state + if let Ok(global_servers) = VPN_SERVERS.lock() { + if let Some(servers) = &*global_servers { + for idx in 0..servers.len() { + let y_pos = idx as i32 * ROW_HEIGHT + 10; // SERVER_ITEM_MARGIN = 10 + + // Resize and reposition checkbox + if let Ok(checkbox) = unsafe { GetDlgItem(container, ID_SERVER_CHECKBOX_BASE + idx as i32) } { + if !checkbox.is_invalid() { + unsafe { + SetWindowPos( + checkbox, + None, + 10, + y_pos, + checkbox_width, + CONTROL_HEIGHT, + SWP_NOZORDER, + ).ok(); + } + } + } + + // Reposition label + if let Ok(label) = unsafe { GetDlgItem(container, ID_SERVER_LABEL_BASE + idx as i32) } { + if !label.is_invalid() { + unsafe { + SetWindowPos( + label, + None, + right_controls_x, + y_pos + 5, + LABEL_WIDTH, + CONTROL_HEIGHT, + SWP_NOZORDER, + ).ok(); + } + } + } + + // Reposition port edit + if let Ok(port_edit) = unsafe { GetDlgItem(container, ID_SERVER_PORT_EDIT_BASE + idx as i32) } { + if !port_edit.is_invalid() { + unsafe { + SetWindowPos( + port_edit, + None, + right_controls_x + LABEL_WIDTH + 5, + y_pos, + PORT_EDIT_WIDTH, + CONTROL_HEIGHT, + SWP_NOZORDER, + ).ok(); + } + } + } + + // Reposition combo + if let Ok(combo) = unsafe { GetDlgItem(container, ID_SERVER_PROXY_COMBO_BASE + idx as i32) } { + if !combo.is_invalid() { + unsafe { + SetWindowPos( + combo, + None, + right_controls_x + LABEL_WIDTH + 5 + PORT_EDIT_WIDTH + 10, + y_pos, + COMBO_WIDTH, + CONTROL_HEIGHT, + SWP_NOZORDER, + ).ok(); + } + } + } + } + } + } +} + +// Helper function to destroy child windows +#[cfg(windows)] +unsafe extern "system" fn destroy_child_window(hwnd: HWND, _: LPARAM) -> BOOL { + unsafe { DestroyWindow(hwnd).ok() }; + true.into() +} diff --git a/src/ui/tray.rs b/src/ui/tray.rs new file mode 100644 index 0000000..3662133 --- /dev/null +++ b/src/ui/tray.rs @@ -0,0 +1,143 @@ +use tray_icon::{ + menu::{Menu, MenuItem, PredefinedMenuItem}, + TrayIconBuilder, +}; + +pub fn create_tray_menu_with_servers( + settings_item: &MenuItem, + quit_item: &MenuItem, +) -> Menu { + // Create tray menu + let tray_menu = Menu::new(); + + // Add running servers section + let running_servers = crate::xray_manager::get_running_servers(); + if !running_servers.is_empty() { + // Get server names from global VPN_SERVERS + if let Ok(global_servers) = crate::vpn::VPN_SERVERS.lock() { + if let Some(servers) = global_servers.as_ref() { + for server in servers { + let server_key = server.get_server_key(); + if running_servers.contains(&server_key) { + let status_text = format!("✓ {} ({}:{})", server.name, server.proxy_type, server.local_port); + let server_item = MenuItem::new(status_text, false, None); + tray_menu.append(&server_item).unwrap(); + } + } + } + } + tray_menu.append(&PredefinedMenuItem::separator()).unwrap(); + } + + // Append settings and quit items + tray_menu.append_items(&[ + settings_item, + &PredefinedMenuItem::separator(), + quit_item, + ]).unwrap(); + + tray_menu +} + +pub fn create_tray_icon_with_servers( + settings_item: &MenuItem, + quit_item: &MenuItem, +) -> tray_icon::TrayIcon { + // Create menu + let tray_menu = create_tray_menu_with_servers(settings_item, quit_item); + + // Create icon (32x32 red square) + let icon = create_icon(); + + // Create tray icon with context menu + TrayIconBuilder::new() + .with_menu(Box::new(tray_menu)) + .with_tooltip("VPN Manager") + .with_icon(icon) + .build() + .unwrap() +} + +pub fn create_tray_icon( + settings_item: &MenuItem, + quit_item: &MenuItem, +) -> tray_icon::TrayIcon { + // Create tray menu + let tray_menu = Menu::new(); + + // Append items to menu + tray_menu.append_items(&[ + settings_item, + &PredefinedMenuItem::separator(), + quit_item, + ]).unwrap(); + + // Create icon (32x32 red square) + let icon = create_icon(); + + // Create tray icon with context menu + TrayIconBuilder::new() + .with_menu(Box::new(tray_menu)) + .with_tooltip("VPN Manager") + .with_icon(icon) + .build() + .unwrap() +} + +fn create_icon() -> tray_icon::Icon { + // Create yellow star icon 32x32 + let width = 32; + let height = 32; + let mut rgba = Vec::with_capacity((width * height * 4) as usize); + + // Define colors + let bg = [0, 0, 0, 0]; // Transparent background + let star = [255, 215, 0, 255]; // Gold/Yellow + let border = [218, 165, 32, 255]; // Darker gold + + let cx = 16.0; + let cy = 16.0; + + for y in 0..height { + for x in 0..width { + let px = x as f32; + let py = y as f32; + + // Calculate angle and distance from center + let dx = px - cx; + let dy = py - cy; + let angle = dy.atan2(dx); + let dist = (dx * dx + dy * dy).sqrt(); + + // 5-pointed star calculation + // Star has 5 points, so we check angle modulo (2π/5) + let point_angle = (angle + std::f32::consts::PI * 2.5) % (std::f32::consts::PI * 2.0 / 5.0); + let normalized = point_angle / (std::f32::consts::PI * 2.0 / 5.0); + + // Star shape: outer radius at points, inner radius between points + let outer_radius = 12.0; + let inner_radius = 5.0; + + // Calculate radius at this angle (sine wave between inner and outer) + let target_radius = if normalized < 0.5 { + inner_radius + (outer_radius - inner_radius) * (normalized * 2.0) + } else { + outer_radius - (outer_radius - inner_radius) * ((normalized - 0.5) * 2.0) + }; + + // Check if point is inside star + let is_star = dist <= target_radius; + let is_border = dist <= target_radius + 0.8 && dist > target_radius - 0.5; + + if is_star && !is_border { + rgba.extend_from_slice(&star); + } else if is_border { + rgba.extend_from_slice(&border); + } else { + rgba.extend_from_slice(&bg); + } + } + } + + tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to create icon") +} diff --git a/src/vpn/mod.rs b/src/vpn/mod.rs new file mode 100644 index 0000000..25f3f54 --- /dev/null +++ b/src/vpn/mod.rs @@ -0,0 +1,169 @@ +use std::sync::Mutex; +use std::collections::HashSet; +use serde::{Deserialize, Serialize}; + +// Global state for VPN servers +pub static VPN_SERVERS: Mutex>> = Mutex::new(None); + +// VPN server information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VpnServer { + pub protocol: String, + pub address: String, + pub port: u16, + pub name: String, + pub enabled: bool, + pub local_port: u16, // User-defined local port + pub proxy_type: String, // "HTTP" or "SOCKS" +} + +impl VpnServer { + /// Get unique server key for stable identification + pub fn get_server_key(&self) -> String { + format!("{}://{}:{}", self.protocol, self.address, self.port) + } +} + +// Assign local ports to servers, preserving saved settings from config +pub fn assign_local_ports(servers: &mut [VpnServer], saved_settings: &std::collections::HashMap) { + let mut used_ports = HashSet::new(); + + // First pass: assign saved settings (port + proxy type + enabled) + for server in servers.iter_mut() { + let key = server.get_server_key(); + if let Some(settings) = saved_settings.get(&key) { + server.local_port = settings.local_port; + server.proxy_type = settings.proxy_type.clone(); + server.enabled = settings.enabled; + used_ports.insert(settings.local_port); + } + } + + // Second pass: assign new ports to servers without saved settings + let mut next_port = 1080; + for server in servers.iter_mut() { + if server.local_port == 0 { // Not assigned yet + while used_ports.contains(&next_port) { + next_port += 1; + } + server.local_port = next_port; + used_ports.insert(next_port); + next_port += 1; + } + } +} + +// Fetch and process VPN subscription list +pub fn fetch_and_process_vpn_list(url: &str) -> Vec { + let mut servers = Vec::new(); + + // Fetch content from URL + match reqwest::blocking::get(url) { + Ok(response) => { + match response.text() { + Ok(content) => { + // Decode from base64 + match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, content.trim()) { + Ok(decoded_bytes) => { + match String::from_utf8(decoded_bytes) { + Ok(decoded_text) => { + // Parse VPN links + for line in decoded_text.lines() { + let trimmed = line.trim(); + if let Some(server) = parse_vpn_uri(trimmed) { + servers.push(server); + } + } + } + Err(_) => {} + } + } + Err(_) => {} + } + } + Err(_) => {} + } + } + Err(_) => {} + } + + servers +} + +// Fetch subscription and return HashMap of server_key -> original_uri +pub fn fetch_subscription_uris(url: &str) -> std::collections::HashMap { + let mut uris = std::collections::HashMap::new(); + + // Fetch content from URL + match reqwest::blocking::get(url) { + Ok(response) => { + match response.text() { + Ok(content) => { + // Decode from base64 + match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, content.trim()) { + Ok(decoded_bytes) => { + match String::from_utf8(decoded_bytes) { + Ok(decoded_text) => { + // Parse VPN links + for line in decoded_text.lines() { + let trimmed = line.trim(); + if let Some(server) = parse_vpn_uri(trimmed) { + let key = server.get_server_key(); + uris.insert(key, trimmed.to_string()); + } + } + } + Err(_) => {} + } + } + Err(_) => {} + } + } + Err(_) => {} + } + } + Err(_) => {} + } + + uris +} + +// Parse VPN URI using v2parser (supports vless, vmess, trojan, shadowsocks, socks) +fn parse_vpn_uri(uri: &str) -> Option { + // Check if it's a supported protocol + let is_supported = uri.starts_with("vless://") + || uri.starts_with("vmess://") + || uri.starts_with("trojan://") + || uri.starts_with("ss://") + || uri.starts_with("shadowsocks://") + || uri.starts_with("socks://"); + + if !is_supported { + return None; + } + + // Use v2parser to get metadata + match std::panic::catch_unwind(|| v2parser::parser::get_metadata(uri)) { + Ok(metadata_json) => { + if let Ok(metadata) = serde_json::from_str::(&metadata_json) { + let protocol = metadata["protocol"].as_str()?.to_uppercase(); + let address = metadata["address"].as_str()?.to_string(); + let port = metadata["port"].as_u64()? as u16; + let name = metadata["name"].as_str().unwrap_or("Unnamed").to_string(); + + Some(VpnServer { + protocol, + address, + port, + name, + enabled: false, // Default to disabled, will be enabled from config + local_port: 0, // Will be assigned by assign_local_ports + proxy_type: "SOCKS".to_string(), // Default to SOCKS + }) + } else { + None + } + } + Err(_) => None, + } +} diff --git a/src/xray_manager.rs b/src/xray_manager.rs new file mode 100644 index 0000000..66caab5 --- /dev/null +++ b/src/xray_manager.rs @@ -0,0 +1,72 @@ +use std::collections::HashMap; +use std::sync::{Mutex, LazyLock}; +use v2parser::xray_runner::XrayRunner; +use v2parser::parser; + +// Global state for running xray processes +pub static XRAY_PROCESSES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + +/// Start xray server for a specific VPN server +/// Returns Ok if successful +pub async fn start_server( + server_key: &str, + uri: &str, + local_port: u16, + proxy_type: &str, + xray_binary_path: &str, +) -> Result<(), String> { + // Determine ports based on proxy type + let (socks_port, http_port) = match proxy_type { + "SOCKS" => (Some(local_port), None), + "HTTP" => (None, Some(local_port)), + _ => (Some(local_port), None), // Default to SOCKS + }; + + // Generate xray config from URI + let config_json = parser::create_json_config(uri, socks_port, http_port); + + // Create and start xray runner + let mut runner = XrayRunner::new(); + runner.start(&config_json, xray_binary_path) + .await + .map_err(|e| format!("Failed to start xray: {}", e))?; + + // Store runner in global state + if let Ok(mut processes) = XRAY_PROCESSES.lock() { + processes.insert(server_key.to_string(), runner); + } + + Ok(()) +} + +/// Stop xray server for a specific server +pub async fn stop_server(server_key: &str) -> Result<(), String> { + if let Ok(mut processes) = XRAY_PROCESSES.lock() { + if let Some(mut runner) = processes.remove(server_key) { + runner.stop() + .await + .map_err(|e| format!("Failed to stop xray: {}", e))?; + } + } + Ok(()) +} + +/// Stop all running xray servers +pub async fn stop_all_servers() -> Result<(), String> { + if let Ok(mut processes) = XRAY_PROCESSES.lock() { + for (_key, mut runner) in processes.drain() { + let _ = runner.stop().await; // Ignore errors during bulk shutdown + } + } + Ok(()) +} + +/// Get list of running server keys +pub fn get_running_servers() -> Vec { + if let Ok(processes) = XRAY_PROCESSES.lock() { + processes.keys().cloned().collect() + } else { + Vec::new() + } +}