diff --git a/Cargo.lock b/Cargo.lock index da773c0..f86e442 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,43 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", + "zeroize", +] + [[package]] name = "ahash" version = "0.8.12" @@ -57,6 +94,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -107,6 +153,18 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "argon2" +version = "0.6.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7af50940b73bf4e16c15c448a2b121c63f2d68e3e54b6a8731673cb4aa0cdff5" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -131,12 +189,41 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bcrypt-pbkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -152,6 +239,34 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.11.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061f1a09225e328e1ffbb378d2d49923c0ca5fee19fb5ac1cc9c1e9d52b93690" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", + "zeroize", +] + +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -161,6 +276,16 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "blowfish" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -174,10 +299,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] -name = "bytes" -version = "1.10.1" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "calloop" @@ -205,6 +336,15 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.40" @@ -244,6 +384,44 @@ dependencies = [ "libc", ] +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "rand_core", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer", + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "4.5.48" @@ -302,6 +480,12 @@ dependencies = [ "error-code", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "colorchoice" version = "1.0.4" @@ -327,6 +511,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "copypasta" version = "0.10.2" @@ -393,6 +583,21 @@ dependencies = [ "libc", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -440,6 +645,34 @@ dependencies = [ "yeslogic-fontconfig-sys", ] +[[package]] +name = "crypto-bigint" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" +dependencies = [ + "cpubits", + "ctutils", + "getrandom 0.4.3", + "hybrid-array", + "num-traits", + "rand_core", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom 0.4.3", + "hybrid-array", + "rand_core", +] + [[package]] name = "cstr" version = "0.2.12" @@ -450,6 +683,25 @@ dependencies = [ "quote", ] +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", + "subtle", +] + [[package]] name = "cursor-icon" version = "1.2.0" @@ -459,6 +711,82 @@ dependencies = [ "serde", ] +[[package]] +name = "curve25519-dalek" +version = "5.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c906a87e53a36ff795d72e06e8162a83c5436e3ea89e942a9cb9fc083f0a384f" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "des" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "ctutils", +] + [[package]] name = "dirs" version = "6.0.0" @@ -534,6 +862,69 @@ dependencies = [ "wio", ] +[[package]] +name = "ecdsa" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", + "zeroize", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1685663e23882cd8517dcbcb1c23a6ebff4433c22dfb681d760219b62cd1b849" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "signature", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65" +dependencies = [ + "base16ct", + "crypto-bigint", + "crypto-common", + "digest", + "ff", + "group", + "hkdf", + "hybrid-array", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embed-resource" version = "3.0.6" @@ -548,6 +939,18 @@ dependencies = [ "winreg", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -585,6 +988,22 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "find-msvc-tools" version = "0.1.3" @@ -659,12 +1078,116 @@ dependencies = [ "libc", ] +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[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", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[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 = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "generic-array" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4e5aa225bc56696909483320f0ff9b600f1a971b52e07a17d70f3d9b43254b" +dependencies = [ + "generic-array 0.14.9", + "rustversion", + "typenum", +] + [[package]] name = "gethostname" version = "1.0.2" @@ -694,10 +1217,33 @@ checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasi 0.14.7+wasi-0.2.4", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" +dependencies = [ + "polyval", +] + [[package]] name = "gl_generator" version = "0.14.0" @@ -720,7 +1266,7 @@ dependencies = [ "objc2 0.6.3", "objc2-app-kit 0.3.2", "once_cell", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows-sys 0.59.0", "x11rb", "xkeysym", @@ -780,6 +1326,17 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "hashbrown" version = "0.16.0" @@ -798,6 +1355,36 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -807,6 +1394,42 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "ctutils", + "subtle", + "typenum", + "zeroize", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.11.4" @@ -837,6 +1460,28 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding", + "hybrid-array", +] + +[[package]] +name = "internal-russh-num-bigint" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8" +dependencies = [ + "num-integer", + "num-traits", + "rand", + "rand_core", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -891,6 +1536,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures", +] + +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common", + "rand_core", +] + [[package]] name = "keyboard-types" version = "0.7.0" @@ -936,9 +1601,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -1001,6 +1666,12 @@ dependencies = [ "nu-ansi-term", ] +[[package]] +name = "md5" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" + [[package]] name = "memchr" version = "2.7.6" @@ -1047,6 +1718,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ml-kem" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "pkcs8", + "rand_core", + "sha3", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + [[package]] name = "ndk" version = "0.9.0" @@ -1108,12 +1804,14 @@ dependencies = [ "png", "polling", "pulldown-cmark", + "russh", "serde", "serde_json", "serde_yaml", "signal-hook", "tauri-winrt-notification", "tempfile", + "tokio", "toml", "toml_edit 0.24.0+spec-1.1.0", "unicode-width", @@ -1185,6 +1883,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "notify" version = "8.2.0" @@ -1228,6 +1938,34 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[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-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_enum" version = "0.7.4" @@ -1523,6 +2261,67 @@ dependencies = [ "libredox", ] +[[package]] +name = "p256" +version = "0.14.0-rc.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bb40a5099e2c38a09dd29321a7a7f045f165a54317679c7cdfb0cbaf8f6b1e" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.14.0-rc.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "492f329d7eb11d22dadc988626b9ea1f503b4ab043a8b1e4e2cc4ae45dabdd70" +dependencies = [ + "ecdsa", + "elliptic-curve", + "fiat-crypto", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.14.0-rc.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff42e4ace5424e3b6d7cb82514be89866b85af87015f80341e37dcc21a66ce6e" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "pageant" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5" +dependencies = [ + "base16ct", + "byteorder", + "bytes", + "delegate", + "futures", + "log", + "rand", + "sha2", + "thiserror 2.0.18", + "tokio", + "windows 0.62.2", + "windows-strings 0.5.1", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1546,12 +2345,50 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "password-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" +dependencies = [ + "phc", +] + +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -1578,6 +2415,12 @@ 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 = "piper" version = "0.2.4" @@ -1589,6 +2432,35 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs5" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d440a804ec8d6fafbb6b84471e013286658d373248927692ab3366686220ca" +dependencies = [ + "aes", + "aes-gcm", + "cbc", + "der", + "pbkdf2", + "rand_core", + "scrypt", + "sha2", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der", + "pkcs5", + "rand_core", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1622,6 +2494,55 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" +dependencies = [ + "cpufeatures", + "universal-hash", + "zeroize", +] + +[[package]] +name = "polyval" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b20f20e954175de5f463f67781b35583397d916b1d148738923711b2ad16bee8" +dependencies = [ + "cpubits", + "cpufeatures", + "universal-hash", +] + +[[package]] +name = "primefield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" +dependencies = [ + "crypto-bigint", + "crypto-common", + "ff", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "primeorder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" +dependencies = [ + "elliptic-curve", + "once_cell", + "primefield", + "serdect", + "wnaf", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -1675,6 +2596,29 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1707,7 +2651,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1727,6 +2671,123 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +[[package]] +name = "rfc6979" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" +dependencies = [ + "crypto-bigint", + "hmac", +] + +[[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 = "russh" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6c3c1dd0a9ad3a9915a2f6e907d158f9a7e9ac32ddc772b003faafc027d9a8" +dependencies = [ + "aes", + "bitflags 2.9.4", + "block-padding", + "byteorder", + "bytes", + "cbc", + "cipher", + "crypto-bigint", + "ctr", + "curve25519-dalek", + "data-encoding", + "delegate", + "der", + "digest", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "enum_dispatch", + "futures", + "generic-array 1.4.4", + "getrandom 0.4.3", + "ghash", + "hex-literal", + "hmac", + "inout", + "internal-russh-num-bigint", + "keccak", + "log", + "md5", + "ml-kem", + "module-lattice", + "num-bigint", + "p256", + "p384", + "p521", + "pageant", + "pbkdf2", + "pkcs5", + "pkcs8", + "polyval", + "rand", + "rand_core", + "ring", + "russh-cryptovec", + "russh-util", + "salsa20", + "scrypt", + "sec1", + "sha1", + "sha2", + "sha3", + "signature", + "spki", + "ssh-encoding", + "ssh-key", + "subtle", + "thiserror 2.0.18", + "tokio", + "typenum", + "universal-hash", + "zeroize", +] + +[[package]] +name = "russh-cryptovec" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aec6cb630dbe85d72ffd7bcd95f07e1bd69f9f270ee8adfa1afe443a6331438" +dependencies = [ + "log", + "nix", + "ssh-encoding", + "windows-sys 0.61.2", +] + +[[package]] +name = "russh-util" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668424a5dde0bcb45b55ba7de8476b93831b4aa2fa6947e145f3b053e22c60b6" +dependencies = [ + "chrono", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -1785,6 +2846,16 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "salsa20" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c" +dependencies = [ + "cfg-if", + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1806,6 +2877,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" +dependencies = [ + "cfg-if", + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "sctk-adwaita" version = "0.10.1" @@ -1818,6 +2901,20 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "sec1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +dependencies = [ + "base16ct", + "ctutils", + "der", + "hybrid-array", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.27" @@ -1890,6 +2987,48 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1915,6 +3054,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -1978,6 +3127,83 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "ssh-cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d801accda99469cde6d73da741422610fdf6508a72d9a69d1b55cb241c720597" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "chacha20", + "cipher", + "ctutils", + "des", + "poly1305", + "ssh-encoding", + "zeroize", +] + +[[package]] +name = "ssh-encoding" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e" +dependencies = [ + "base64ct", + "bytes", + "ctutils", + "digest", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "ssh-key" +version = "0.7.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a32fae177b74a22aa9c5b01bf7e68b33545be32d9e381e248058d2adc15ce3" +dependencies = [ + "argon2", + "bcrypt-pbkdf", + "ctutils", + "ed25519-dalek", + "hex", + "hmac", + "p256", + "p384", + "p521", + "rand_core", + "sec1", + "sha1", + "sha2", + "signature", + "ssh-cipher", + "ssh-encoding", + "zeroize", +] + [[package]] name = "strict-num" version = "0.1.1" @@ -1990,6 +3216,12 @@ 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 = "2.0.106" @@ -2007,8 +3239,8 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" dependencies = [ - "thiserror 2.0.17", - "windows", + "thiserror 2.0.18", + "windows 0.61.3", "windows-version", ] @@ -2036,11 +3268,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2056,9 +3288,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2090,6 +3322,32 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.9.11+spec-1.1.0" @@ -2170,6 +3428,12 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "unicase" version = "2.9.0" @@ -2194,12 +3458,28 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common", + "ctutils", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2518,11 +3798,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -2531,7 +3823,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", ] [[package]] @@ -2543,8 +3844,21 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -2553,9 +3867,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -2598,10 +3923,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -2611,6 +3946,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -2620,6 +3964,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -2722,6 +4075,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-version" version = "0.1.7" @@ -2956,6 +4318,17 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wnaf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1" +dependencies = [ + "ff", + "group", + "hybrid-array", +] + [[package]] name = "x11-clipboard" version = "0.9.3" @@ -3065,3 +4438,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" diff --git a/nebula_app/Cargo.toml b/nebula_app/Cargo.toml index c775907..f692679 100644 --- a/nebula_app/Cargo.toml +++ b/nebula_app/Cargo.toml @@ -46,6 +46,7 @@ polling = "3.11.0" # Markdown viewer: CommonMark + GFM (tables / task lists / strikethrough) # event parser; `display::markdown_view` renders the folded block AST. pulldown-cmark = { version = "0.13", default-features = false } +russh = { version = "0.62.2", default-features = false, features = ["ring"] } # preserve_order: we rewrite the user's claude settings.json (ai_hook # self-heal) and must not alphabetize their keys in the process. serde_json = { version = "1", features = ["preserve_order"] } @@ -55,6 +56,7 @@ signal-hook = { version = "0.4.3", default-features = false } tempfile = "3.12.0" toml.workspace = true toml_edit.workspace = true +tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] } unicode-width = "0.2.0" winit = { version = "0.30.9", default-features = false, features = ["rwh_06", "serde"] } global-hotkey = "0.8.0" diff --git a/nebula_app/src/main.rs b/nebula_app/src/main.rs index 8dff124..ef95cb4 100644 --- a/nebula_app/src/main.rs +++ b/nebula_app/src/main.rs @@ -60,6 +60,8 @@ mod ssh_credentials; mod mux; #[cfg(windows)] mod ssh; +#[cfg(windows)] +mod ssh_session; mod string; mod window_context; diff --git a/nebula_app/src/ssh_credentials.rs b/nebula_app/src/ssh_credentials.rs index 71e23a2..50a8f5b 100644 --- a/nebula_app/src/ssh_credentials.rs +++ b/nebula_app/src/ssh_credentials.rs @@ -304,6 +304,16 @@ pub fn store_password(destination: &str, password: &[u8]) -> std::io::Result<()> windows_store::save_password(destination, password) } +#[cfg(windows)] +pub fn load_stored_password(destination: &str) -> std::io::Result>> { + windows_store::load_password(destination) +} + +#[cfg(not(windows))] +pub fn load_stored_password(_destination: &str) -> std::io::Result>> { + Ok(None) +} + #[cfg(windows)] pub fn forget_password(destination: &str) -> std::io::Result<()> { windows_store::delete_password(destination) diff --git a/nebula_app/src/ssh_session.rs b/nebula_app/src/ssh_session.rs new file mode 100644 index 0000000..55415e6 --- /dev/null +++ b/nebula_app/src/ssh_session.rs @@ -0,0 +1,340 @@ +//! 由 SSH 通道直接驱动的远端终端会话。 +//! +//! 远端 Pane 不创建本地伪终端,但继续使用统一的输入、缩放和关闭消息协议, +//! 从而让渲染与键盘处理保持传输层无关。 + +use std::io; +use std::sync::Arc; +use std::sync::mpsc::Receiver; +use std::time::Duration; + +use log::{error, warn}; +use nebula_terminal::event::{Event as TerminalEvent, WindowSize}; +use nebula_terminal::event_loop::{EventLoopSender, Msg, StreamProcessor}; +use nebula_terminal::sync::FairMutex; +use nebula_terminal::term::Term; +use russh::client; +use russh::keys::ssh_key; +use russh::{ChannelMsg, Disconnect}; + +use crate::event::EventProxy; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SshDestination { + pub original: String, + pub user: String, + pub host: String, + pub port: u16, +} + +impl SshDestination { + pub fn parse(value: &str) -> io::Result { + let original = value.trim().to_owned(); + let address = original.strip_prefix("ssh://").unwrap_or(&original).to_owned(); + let (user, host_port) = address.rsplit_once('@').ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "SSH destination requires user@host") + })?; + if user.is_empty() || host_port.is_empty() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "SSH destination is incomplete", + )); + } + + let (host, port) = if let Some(rest) = host_port.strip_prefix('[') { + let (host, suffix) = rest.split_once(']').ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "invalid bracketed SSH address") + })?; + let port = suffix + .strip_prefix(':') + .map(str::parse) + .transpose() + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid SSH port"))? + .unwrap_or(22); + (host.to_owned(), port) + } else if let Some((host, port)) = host_port.rsplit_once(':') { + if host.contains(':') { + (host_port.to_owned(), 22) + } else { + let port = port + .parse() + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid SSH port"))?; + (host.to_owned(), port) + } + } else { + (host_port.to_owned(), 22) + }; + if host.is_empty() { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "SSH host is empty")); + } + + Ok(Self { original, user: user.to_owned(), host, port }) + } +} + +struct ClientHandler { + host: String, + port: u16, +} + +impl client::Handler for ClientHandler { + type Error = russh::Error; + + async fn check_server_key( + &mut self, + server_public_key: &ssh_key::PublicKey, + ) -> Result { + match russh::keys::known_hosts::check_known_hosts(&self.host, self.port, server_public_key) + { + Ok(true) => Ok(true), + Ok(false) => { + if confirm_new_host(&self.host, self.port, server_public_key) { + if let Err(err) = russh::keys::known_hosts::learn_known_hosts( + &self.host, + self.port, + server_public_key, + ) { + warn!("Failed to store SSH host key: {err}"); + } + Ok(true) + } else { + Ok(false) + } + }, + Err(err) => { + warn!("SSH host key verification failed: {err}"); + show_host_key_changed(&self.host, self.port, &err.to_string()); + Ok(false) + }, + } + } +} + +/// 启动密码认证的 SSH Pane。返回 `Unsupported` 时保留兼容路径, +/// 避免尚未覆盖的认证方式在迁移期间失效。 +pub fn spawn_password_session( + destination: SshDestination, + initial_size: WindowSize, + terminal: Arc>>, + event_proxy: EventProxy, +) -> io::Result { + let Some(password) = crate::ssh_credentials::load_stored_password(&destination.original)? + else { + return Err(io::Error::new( + io::ErrorKind::Unsupported, + "no stored password for direct SSH authentication", + )); + }; + let (sender, receiver) = EventLoopSender::standalone()?; + std::thread::Builder::new().name(format!("SSH {}", destination.host)).spawn(move || { + run_session(destination, password, initial_size, terminal, event_proxy, receiver) + })?; + Ok(sender) +} + +fn run_session( + destination: SshDestination, + mut password: Vec, + initial_size: WindowSize, + terminal: Arc>>, + event_proxy: EventProxy, + receiver: Receiver, +) { + let runtime = + match tokio::runtime::Builder::new_multi_thread().enable_all().worker_threads(2).build() { + Ok(runtime) => runtime, + Err(err) => { + render_error(&terminal, &event_proxy, &format!("SSH runtime failed: {err}")); + return; + }, + }; + runtime.block_on(async move { + if let Err(err) = run_session_async( + &destination, + &password, + initial_size, + terminal.clone(), + event_proxy.clone(), + receiver, + ) + .await + { + error!("Direct SSH session failed for {}: {err}", destination.original); + render_error(&terminal, &event_proxy, &format!("SSH connection failed: {err}")); + } + password.fill(0); + terminal.lock().exit(); + event_proxy.send_event(TerminalEvent::Wakeup.into()); + }); +} + +async fn run_session_async( + destination: &SshDestination, + password: &[u8], + initial_size: WindowSize, + terminal: Arc>>, + event_proxy: EventProxy, + receiver: Receiver, +) -> Result<(), Box> { + let config = Arc::new(client::Config { + inactivity_timeout: Some(Duration::from_secs(30)), + keepalive_interval: Some(Duration::from_secs(5)), + keepalive_max: 6, + ..Default::default() + }); + let handler = ClientHandler { host: destination.host.clone(), port: destination.port }; + let mut session = + client::connect(config, (destination.host.as_str(), destination.port), handler).await?; + + let password = String::from_utf8(password.to_vec())?; + let auth = session.authenticate_password(destination.user.clone(), password).await?; + if !auth.success() { + let _ = crate::ssh_credentials::forget_password(&destination.original); + return Err("password authentication was rejected".into()); + } + + let mut channel = session.channel_open_session().await?; + channel + .request_pty( + true, + "xterm-256color", + u32::from(initial_size.num_cols), + u32::from(initial_size.num_lines), + u32::from(initial_size.cell_width) * u32::from(initial_size.num_cols), + u32::from(initial_size.cell_height) * u32::from(initial_size.num_lines), + &[], + ) + .await?; + channel.request_shell(true).await?; + + let (input_tx, mut input_rx) = tokio::sync::mpsc::unbounded_channel(); + tokio::task::spawn_blocking(move || { + while let Ok(message) = receiver.recv() { + if input_tx.send(message).is_err() { + break; + } + } + }); + + let mut stream = StreamProcessor::default(); + stream.resize(initial_size); + loop { + tokio::select! { + message = input_rx.recv() => match message { + Some(Msg::Input(bytes)) => channel.data(bytes.as_ref()).await?, + Some(Msg::Resize(size)) => { + stream.resize(size); + channel.window_change( + u32::from(size.num_cols), + u32::from(size.num_lines), + u32::from(size.cell_width) * u32::from(size.num_cols), + u32::from(size.cell_height) * u32::from(size.num_lines), + ).await?; + }, + Some(Msg::Shutdown) | None => { + let _ = channel.eof().await; + break; + }, + }, + message = channel.wait() => match message { + Some(ChannelMsg::Data { data }) | Some(ChannelMsg::ExtendedData { data, .. }) => { + stream.feed(&mut *terminal.lock(), &event_proxy, data.as_ref()); + event_proxy.send_event(TerminalEvent::Wakeup.into()); + }, + Some(ChannelMsg::ExitStatus { .. }) | None => break, + _ => {}, + }, + } + } + let _ = session.disconnect(Disconnect::ByApplication, "", "English").await; + Ok(()) +} + +fn render_error( + terminal: &Arc>>, + event_proxy: &EventProxy, + message: &str, +) { + let mut stream = StreamProcessor::default(); + let text = format!("\r\n\x1b[31m{message}\x1b[0m\r\n"); + stream.feed(&mut *terminal.lock(), event_proxy, text.as_bytes()); + event_proxy.send_event(TerminalEvent::Wakeup.into()); +} + +#[cfg(windows)] +fn confirm_new_host(host: &str, port: u16, key: &ssh_key::PublicKey) -> bool { + use std::ptr::null_mut; + use windows_sys::Win32::UI::WindowsAndMessaging::{ + IDYES, MB_ICONQUESTION, MB_SETFOREGROUND, MB_YESNO, MessageBoxW, + }; + + let fingerprint = key.fingerprint(ssh_key::HashAlg::Sha256); + let text = wide(&format!( + "首次连接到 {host}:{port}。\n\n主机密钥:{fingerprint}\n\n是否信任并保存此主机密钥?" + )); + let title = wide("Nebula SSH"); + unsafe { + MessageBoxW( + null_mut(), + text.as_ptr(), + title.as_ptr(), + MB_YESNO | MB_ICONQUESTION | MB_SETFOREGROUND, + ) == IDYES + } +} + +#[cfg(not(windows))] +fn confirm_new_host(_host: &str, _port: u16, _key: &ssh_key::PublicKey) -> bool { + false +} + +#[cfg(windows)] +fn show_host_key_changed(host: &str, port: u16, detail: &str) { + use std::ptr::null_mut; + use windows_sys::Win32::UI::WindowsAndMessaging::{ + MB_ICONERROR, MB_OK, MB_SETFOREGROUND, MessageBoxW, + }; + let text = wide(&format!( + "{host}:{port} 的主机密钥与已保存记录不一致。\n\n连接已终止,以避免连接到错误的主机。\n\n{detail}" + )); + let title = wide("Nebula SSH"); + unsafe { + MessageBoxW( + null_mut(), + text.as_ptr(), + title.as_ptr(), + MB_OK | MB_ICONERROR | MB_SETFOREGROUND, + ); + } +} + +#[cfg(not(windows))] +fn show_host_key_changed(_host: &str, _port: u16, _detail: &str) {} + +#[cfg(windows)] +fn wide(value: &str) -> Vec { + value.encode_utf16().chain(std::iter::once(0)).collect() +} + +#[cfg(test)] +mod tests { + use super::SshDestination; + + #[test] + fn parses_saved_destinations() { + let plain = SshDestination::parse("root@example.com").unwrap(); + assert_eq!( + (plain.user.as_str(), plain.host.as_str(), plain.port), + ("root", "example.com", 22) + ); + + let uri = SshDestination::parse("ssh://alice@example.com:2200").unwrap(); + assert_eq!( + (uri.user.as_str(), uri.host.as_str(), uri.port), + ("alice", "example.com", 2200) + ); + + let ipv6 = SshDestination::parse("ssh://root@[2001:db8::1]:2222").unwrap(); + assert_eq!((ipv6.host.as_str(), ipv6.port), ("2001:db8::1", 2222)); + } +} diff --git a/nebula_app/src/window_context.rs b/nebula_app/src/window_context.rs index 371d53b..cc52a9c 100644 --- a/nebula_app/src/window_context.rs +++ b/nebula_app/src/window_context.rs @@ -543,6 +543,49 @@ impl WindowContext { }) } + #[cfg(windows)] + /// 创建由远端 PTY 通道驱动的 Pane,并复用本地终端的解析、渲染和事件协议。 + /// 这样传输层只负责字节流,输入、缩放与终端状态无需维护两套实现。 + fn create_ssh_pane( + size_info: &crate::display::SizeInfo, + window_id: WindowId, + config: &UiConfig, + proxy: &EventLoopProxy, + pane_id: PaneId, + destination: crate::ssh_session::SshDestination, + ) -> Result> { + let window_route = Arc::new(AtomicU64::new(window_id.into())); + let event_proxy = EventProxy::new_tab(proxy.clone(), window_route.clone(), pane_id); + let terminal = Arc::new(FairMutex::new(Term::new( + config.term_options(), + size_info, + event_proxy.clone(), + ))); + let sender = crate::ssh_session::spawn_password_session( + destination, + (*size_info).into(), + terminal.clone(), + event_proxy.clone(), + )?; + if config.cursor.style().blinking { + event_proxy.send_event(TerminalEvent::CursorBlinkingChange.into()); + } + Ok(Pane { + terminal, + notifier: Notifier(sender), + search_state: Default::default(), + inline_search_state: Default::default(), + id: pane_id, + title: String::from("ssh"), + nebula_state: NebulaPaneState::default(), + intro_cols: None, + shell_pid: 0, + window_route, + #[cfg(not(windows))] + master_fd: -1, + }) + } + /// A pane-shaped stub for document-viewer tabs: a real (empty) `Term` so /// the shared event pipeline has state to borrow, but NO PTY behind it — /// the notifier is a sink, so keystrokes routed here are swallowed @@ -856,6 +899,47 @@ impl WindowContext { /// `nebula ssh` is typed into that shell's PTY so OpenSSH remains inside /// Nebula's ConPTY instead of becoming the pane's GUI-subsystem root. fn spawn_tab_ssh(&mut self, host: String) { + #[cfg(windows)] + if let Ok(destination) = crate::ssh_session::SshDestination::parse(&host) { + let pane_id = self.next_pane_id; + match Self::create_ssh_pane( + &self.display.size_info, + self.display.window.id(), + &self.config, + &self.proxy, + pane_id, + destination, + ) { + Ok(pane) => { + self.next_pane_id += 1; + self.panes.push(pane); + let at = (self.active_tab + 1).min(self.tabs.len()); + self.tabs.insert(at, TabEntry { + layout: Layout::Leaf(pane_id), + active_pane: pane_id, + has_bell: false, + custom_name: Some(host), + doc: None, + }); + self.active_tab = at; + self.resize_active_layout(); + self.dirty = true; + return; + }, + Err(err) + if err + .downcast_ref::() + .is_some_and(|err| err.kind() == std::io::ErrorKind::Unsupported) => + { + log::info!("直连 SSH 暂无可用认证方式,回退到兼容连接路径"); + }, + Err(err) => { + error!("创建直连 SSH Pane 失败: {err}"); + return; + }, + } + } + let Ok(exe) = std::env::current_exe() else { error!("Cannot locate nebula.exe for the SSH AskPass helper"); return; diff --git a/nebula_terminal/src/event_loop.rs b/nebula_terminal/src/event_loop.rs index 3e7571a..fe222a5 100644 --- a/nebula_terminal/src/event_loop.rs +++ b/nebula_terminal/src/event_loop.rs @@ -28,6 +28,83 @@ pub(crate) const READ_BUFFER_SIZE: usize = 0x10_0000; /// Max bytes to read from the PTY while the terminal is locked. const MAX_LOCKED_READ: usize = u16::MAX as usize; +/// 本地 PTY 与远端传输共用的有状态终端字节流处理器。 +/// OSC 提取必须紧贴 VT 解析,避免两类会话产生不同的目录、命令和图片状态。 +#[derive(Default)] +pub struct StreamProcessor { + parser: ansi::Processor, + cwd_sniffer: crate::osc_cwd::CwdSniffer, + window_size: Option, +} + +impl StreamProcessor { + pub fn resize(&mut self, window_size: WindowSize) { + self.window_size = Some(window_size); + } + + pub fn next_sync_timeout(&self) -> Option { + self.parser.sync_timeout().sync_timeout() + } + + pub fn sync_bytes_count(&self) -> usize { + self.parser.sync_bytes_count() + } + + pub fn stop_sync(&mut self, terminal: &mut Term) { + self.parser.stop_sync(terminal); + } + + pub fn feed( + &mut self, + terminal: &mut Term, + event_proxy: &U, + bytes: &[u8], + ) { + let osc_events = self.cwd_sniffer.feed(bytes); + let mut latest_cwd = None; + let mut advanced = 0; + for (offset, event) in osc_events { + match event { + OscEvent::Cwd(cwd) => latest_cwd = Some(cwd), + OscEvent::CommandStart => event_proxy.send_event(Event::CommandStart), + OscEvent::CommandDone => event_proxy.send_event(Event::CommandDone), + OscEvent::Notify(text) => event_proxy.send_event(Event::Notify(text)), + OscEvent::PromptMark => { + self.parser.advance(terminal, &bytes[advanced..offset]); + advanced = offset; + terminal.nebula_add_prompt_mark(); + }, + OscEvent::InlineImage { png, width, height } => { + self.parser.advance(terminal, &bytes[advanced..offset]); + advanced = offset; + let (cell_w, cell_h) = self.window_size.map_or((9.0, 20.0), |ws| { + (f32::from(ws.cell_width), f32::from(ws.cell_height)) + }); + let max_w = terminal.columns() as f32 * cell_w; + let scale = (max_w / width as f32).min(1.0); + let disp_w = width as f32 * scale; + let disp_h = height as f32 * scale; + let rows = (disp_h / cell_h).ceil().max(1.0) as usize; + let abs_line = terminal.nebula_cursor_abs_line(); + for _ in 0..=rows { + self.parser.advance(terminal, b"\r\n"); + } + event_proxy.send_event(Event::InlineImage { + png: std::sync::Arc::new(png), + abs_line, + width: disp_w, + height: disp_h, + }); + }, + } + } + self.parser.advance(terminal, &bytes[advanced..]); + if let Some(cwd) = latest_cwd { + event_proxy.send_event(Event::CwdReport(cwd)); + } + } +} + /// Messages that may be sent to the `EventLoop`. #[derive(Debug)] pub enum Msg { @@ -95,7 +172,7 @@ where match msg { Msg::Input(input) => state.write_list.push_back(input), Msg::Resize(window_size) => { - state.window_size = Some(window_size); + state.stream.resize(window_size); self.pty.on_resize(window_size); }, Msg::Shutdown => return false, @@ -167,70 +244,7 @@ where writer.write_all(&buf[..unprocessed]).unwrap(); } - // Tee the bytes through the OSC sniffer first: vte drops OSC 7 / - // 9;9 (cwd) and 133;A (prompt marks). Prompt marks must be applied - // while the grid cursor sits exactly at the mark's byte position, - // so `parser.advance` is split at each mark offset. - let osc_events = state.cwd_sniffer.feed(&buf[..unprocessed]); - let mut latest_cwd = None; - let mut advanced = 0; - for (offset, event) in osc_events { - match event { - OscEvent::Cwd(cwd) => latest_cwd = Some(cwd), - // No cursor semantics — just relay upstream. - OscEvent::CommandStart => self.event_proxy.send_event(Event::CommandStart), - OscEvent::CommandDone => self.event_proxy.send_event(Event::CommandDone), - OscEvent::Notify(text) => self.event_proxy.send_event(Event::Notify(text)), - OscEvent::PromptMark => { - state.parser.advance(&mut **terminal, &buf[advanced..offset]); - advanced = offset; - terminal.nebula_add_prompt_mark(); - }, - OscEvent::InlineImage { png, width, height } => { - state.parser.advance(&mut **terminal, &buf[advanced..offset]); - advanced = offset; - - // Convert image pixels into grid rows via the cached - // cell size (default matches a typical 14px mono font - // until the first resize lands, which in practice - // happens before any image can arrive). - let (cell_w, cell_h) = state - .window_size - .map_or((9.0, 20.0), |ws| { - (f32::from(ws.cell_width), f32::from(ws.cell_height)) - }); - let max_w = terminal.columns() as f32 * cell_w; - let scale = (max_w / width as f32).min(1.0); - let disp_w = width as f32 * scale; - let disp_h = height as f32 * scale; - let rows = (disp_h / cell_h).ceil().max(1.0) as usize; - - // Anchor at the current cursor row, then feed real - // newlines through the parser so the image's rows - // exist in the grid (scrolling included) and the - // prompt continues below the picture. - let abs_line = terminal.nebula_cursor_abs_line(); - for _ in 0..=rows { - state.parser.advance(&mut **terminal, b"\r\n"); - } - - self.event_proxy.send_event(Event::InlineImage { - png: std::sync::Arc::new(png), - abs_line, - width: disp_w, - height: disp_h, - }); - }, - } - } - - // Parse the remaining bytes (or, without marks, the whole chunk). - state.parser.advance(&mut **terminal, &buf[advanced..unprocessed]); - - // New tabs/splits inherit the newest reported directory. - if let Some(cwd) = latest_cwd { - self.event_proxy.send_event(Event::CwdReport(cwd)); - } + state.stream.feed(&mut **terminal, &self.event_proxy, &buf[..unprocessed]); processed += unprocessed; unprocessed = 0; @@ -242,7 +256,7 @@ where } // Queue terminal redraw unless all processed bytes were synchronized. - if state.parser.sync_bytes_count() < processed && processed > 0 { + if state.stream.sync_bytes_count() < processed && processed > 0 { self.event_proxy.send_event(Event::Wakeup); } @@ -305,9 +319,10 @@ where 'event_loop: loop { // Wakeup the event loop when a synchronized update timeout was reached. - let handler = state.parser.sync_timeout(); - let timeout = - handler.sync_timeout().map(|st| st.saturating_duration_since(Instant::now())); + let timeout = state + .stream + .next_sync_timeout() + .map(|st| st.saturating_duration_since(Instant::now())); events.clear(); if let Err(err) = self.poll.wait(&mut events, timeout) { @@ -322,7 +337,7 @@ where // Handle synchronized update timeout. if events.is_empty() && self.rx.peek().is_none() { - state.parser.stop_sync(&mut *self.terminal.lock()); + state.stream.stop_sync(&mut *self.terminal.lock()); self.event_proxy.send_event(Event::Wakeup); continue; } @@ -466,6 +481,12 @@ pub struct EventLoopSender { } impl EventLoopSender { + /// 为非 PTY 传输创建消息通道,继续复用应用既有的输入、缩放和关闭协议。 + pub fn standalone() -> io::Result<(Self, Receiver)> { + let (sender, receiver) = mpsc::channel(); + Ok((Self { sender, poller: Arc::new(Poller::new()?) }, receiver)) + } + pub fn send(&self, msg: Msg) -> Result<(), EventLoopSendError> { self.sender.send(msg).map_err(EventLoopSendError::Send)?; self.poller.notify().map_err(EventLoopSendError::Io) @@ -489,12 +510,7 @@ impl EventLoopSender { pub struct State { write_list: VecDeque>, writing: Option, - parser: ansi::Processor, - /// Tees OSC 7 / 9;9 / 133;A / 1337 events out of the byte stream. - cwd_sniffer: crate::osc_cwd::CwdSniffer, - /// Latest PTY size, cached off `Msg::Resize`: inline images need the cell - /// pixel size to convert image pixels into grid rows. - window_size: Option, + stream: StreamProcessor, } impl State {