diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index db04dd3..96c9336 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2,20 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" -dependencies = [ - "cpp_demangle", - "fallible-iterator", - "gimli", - "object", - "rustc-demangle", - "smallvec", -] - [[package]] name = "adler" version = "1.0.2" @@ -85,17 +71,6 @@ dependencies = [ "system-deps 6.0.3", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -108,48 +83,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "bindgen" -version = "0.59.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", -] - -[[package]] -name = "bindgen" -version = "0.60.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "clap", - "env_logger", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "which", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -265,15 +198,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfb" version = "0.6.1" @@ -319,41 +243,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clang-sys" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags", - "clap_lex", - "indexmap", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "cocoa" version = "0.24.1" @@ -448,15 +337,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cpp_demangle" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c76f98bdfc7f66172e6c7065f981ebb576ffc903fe4c0561d9f0c2509226dc6" -dependencies = [ - "cfg-if", -] - [[package]] name = "cpufeatures" version = "0.2.5" @@ -659,12 +539,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - [[package]] name = "embed_plist" version = "1.2.2" @@ -680,46 +554,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "env_logger" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fastrand" version = "1.8.0" @@ -992,7 +826,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows", + "windows 0.39.0", ] [[package]] @@ -1027,16 +861,6 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "gimli" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" -dependencies = [ - "fallible-iterator", - "stable_deref_trait", -] - [[package]] name = "gio" version = "0.15.12" @@ -1142,17 +966,6 @@ dependencies = [ "system-deps 6.0.3", ] -[[package]] -name = "goblin" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6b4de4a8eb6c46a8c77e1d3be942cb9a8bf073c22374578e5ba4b08ed0ff68" -dependencies = [ - "log", - "plain", - "scroll", -] - [[package]] name = "gtk" version = "0.15.5" @@ -1269,12 +1082,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "ico" version = "0.2.0" @@ -1462,39 +1269,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" -[[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 = "libproc" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b799ad155d75ce914c467ee5627b62247c20d4aedbd446f821484cebf3cded7" -dependencies = [ - "bindgen 0.59.2", - "errno", - "libc", -] - [[package]] name = "line-wrap" version = "0.1.1" @@ -1544,30 +1324,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - -[[package]] -name = "mach2" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" -dependencies = [ - "libc", -] - -[[package]] -name = "mach_o_sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e854583a83f20cf329bb9283366335387f7db59d640d1412167e05fedb98826" - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1612,16 +1368,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memmap" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -1631,12 +1377,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1712,16 +1452,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1821,28 +1551,12 @@ dependencies = [ "objc", ] -[[package]] -name = "object" -version = "0.30.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" -dependencies = [ - "flate2", - "memchr", -] - [[package]] name = "once_cell" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" -[[package]] -name = "os_str_bytes" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" - [[package]] name = "overload" version = "0.1.1" @@ -1903,12 +1617,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "percent-encoding" version = "2.2.0" @@ -2041,12 +1749,6 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "plist" version = "1.3.1" @@ -2156,20 +1858,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-maps" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d17946c951c8e8c4233375fdbfc212b215bd14ea1b18388eae8c95bb03a0174" -dependencies = [ - "anyhow", - "bindgen 0.60.1", - "libc", - "libproc", - "mach2", - "winapi", -] - [[package]] name = "quote" version = "1.0.33" @@ -2269,18 +1957,6 @@ dependencies = [ "cty", ] -[[package]] -name = "read-process-memory" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8497683b2f0b6887786f1928c118f26ecc6bb3d78bbb6ed23e8e7ba110af3bb0" -dependencies = [ - "libc", - "log", - "mach", - "winapi", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -2333,29 +2009,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" -[[package]] -name = "remoteprocess" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3ed67d3fade907d519c2edd07329a8f16296a64f91b6f3c35570769cf6b25d" -dependencies = [ - "addr2line", - "goblin", - "lazy_static", - "libc", - "libproc", - "log", - "mach", - "mach_o_sys", - "memmap", - "nix", - "object", - "proc-maps", - "read-process-memory", - "regex", - "winapi", -] - [[package]] name = "remove_dir_all" version = "0.5.3" @@ -2365,18 +2018,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc_version" version = "0.3.3" @@ -2434,26 +2075,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scroll" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" -dependencies = [ - "scroll_derive", -] - -[[package]] -name = "scroll_derive" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.105", -] - [[package]] name = "selectors" version = "0.22.0" @@ -2675,12 +2296,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - [[package]] name = "signal-hook" version = "0.3.17" @@ -2907,7 +2522,7 @@ dependencies = [ "serde", "unicode-segmentation", "uuid 1.3.0", - "windows", + "windows 0.39.0", "windows-implement", "x11-dl", ] @@ -2965,7 +2580,7 @@ dependencies = [ "uuid 1.3.0", "webkit2gtk", "webview2-com", - "windows", + "windows 0.39.0", ] [[package]] @@ -3040,7 +2655,7 @@ dependencies = [ "thiserror", "uuid 1.3.0", "webview2-com", - "windows", + "windows 0.39.0", ] [[package]] @@ -3059,7 +2674,7 @@ dependencies = [ "uuid 1.3.0", "webkit2gtk", "webview2-com", - "windows", + "windows 0.39.0", "wry", ] @@ -3088,7 +2703,7 @@ dependencies = [ "thiserror", "url", "walkdir", - "windows", + "windows 0.39.0", ] [[package]] @@ -3116,15 +2731,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "termios" version = "0.2.2" @@ -3144,24 +2750,19 @@ dependencies = [ "lazy_static", "portable-pty", "regex", - "remoteprocess", "serde", "serde_json", "signal-hook", "signal-hook-tokio", "tauri", "tauri-build", + "thiserror", "tokio", "uuid 1.3.0", "window-vibrancy", + "windows 0.52.0", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thin-slice" version = "0.1.1" @@ -3170,22 +2771,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.105", + "syn 2.0.39", ] [[package]] @@ -3524,7 +3125,7 @@ checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.39.0", "windows-implement", ] @@ -3549,22 +3150,11 @@ dependencies = [ "serde", "serde_json", "thiserror", - "windows", + "windows 0.39.0", "windows-bindgen", "windows-metadata", ] -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3622,6 +3212,16 @@ dependencies = [ "windows_x86_64_msvc 0.39.0", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets", +] + [[package]] name = "windows-bindgen" version = "0.39.0" @@ -3632,6 +3232,15 @@ dependencies = [ "windows-tokens", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-implement" version = "0.39.0" @@ -3654,15 +3263,30 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.0", "windows_aarch64_msvc 0.42.0", "windows_i686_gnu 0.42.0", "windows_i686_msvc 0.42.0", "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.0", "windows_x86_64_msvc 0.42.0", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows-tokens" version = "0.39.0" @@ -3675,6 +3299,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.39.0" @@ -3687,6 +3317,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.39.0" @@ -3699,6 +3335,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.39.0" @@ -3711,6 +3353,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.39.0" @@ -3723,12 +3371,24 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.39.0" @@ -3741,6 +3401,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winreg" version = "0.10.1" @@ -3793,7 +3459,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.39.0", "windows-implement", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6d0c8b7..ee95295 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,7 +22,7 @@ infer = "0.12.0" tokio = { version = "1.25.0", features = ["full"] } dirs-next = "2.0.0" uuid = "1.3.0" -remoteprocess = "0.4.11" +thiserror = "1.0.50" [target.'cfg(unix)'.dependencies] signal-hook = "0.3.17" @@ -33,6 +33,7 @@ futures = "0.3.29" lazy_static = "1.4.0" regex = "1.8.4" window-vibrancy = "0.3.2" +windows = { version = "0.52.0", features = ["Win32_Foundation", "Win32_System", "Win32_System_Threading", "Win32_System_ProcessStatus", "Win32_System_Diagnostics_ToolHelp", "Win32_System_Diagnostics"] } [features] default = ["custom-protocol"] diff --git a/src-tauri/src/command/app.rs b/src-tauri/src/command/app.rs deleted file mode 100644 index 9382b81..0000000 --- a/src-tauri/src/command/app.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[tauri::command] -pub fn close_app(app: tauri::AppHandle) { - app.exit(0); -} diff --git a/src-tauri/src/command/mod.rs b/src-tauri/src/command/mod.rs deleted file mode 100644 index 7fd2f03..0000000 --- a/src-tauri/src/command/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod app; -pub mod option; -pub mod term; -pub mod window; diff --git a/src-tauri/src/command/option.rs b/src-tauri/src/command/option.rs deleted file mode 100644 index 0704e1a..0000000 --- a/src-tauri/src/command/option.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::configuration::deserialized::Option; -use std::sync::{Arc, Mutex}; - -#[tauri::command] -pub async fn get_configuration(option: tauri::State<'_, Arc>>) -> Result { - Ok(option.lock().unwrap().clone()) -} diff --git a/src-tauri/src/command/term.rs b/src-tauri/src/command/term.rs deleted file mode 100644 index cd918ce..0000000 --- a/src-tauri/src/command/term.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::configuration::deserialized::Option; -use crate::state::pty_manager::PtyManager; -use std::sync::{Arc, Mutex}; - -use crate::common::errors::PtyError; - -#[tauri::command] -pub async fn terminal_input( - pty_manager: tauri::State<'_, Mutex>, - id: String, - content: String, -) -> Result<(), PtyError> { - if let Ok(mut pty_manager) = pty_manager.lock() { - pty_manager.write(&id, &content)?; - Ok(()) - } else { - Err(PtyError::ManagerUnresponding) - } -} - -#[tauri::command] -pub async fn create_terminal( - app: tauri::AppHandle, - pty_manager: tauri::State<'_, Mutex>, - cols: u16, - rows: u16, - id: String, - profile_uuid: String, - option: tauri::State<'_, Arc>>, -) -> Result<(), PtyError> { - if let Some(profile) = option - .lock() - .unwrap() - .profiles - .iter() - .find(|profile| profile.uuid == profile_uuid) - { - if let Ok(mut pty_manager) = pty_manager.lock() { - if pty_manager.app.is_none() { - pty_manager.app = Some(Arc::new(app)); - } - - pty_manager.create_pty(cols, rows, id, profile.clone())?; - Ok(()) - } else { - Err(PtyError::ManagerUnresponding) - } - } else { - todo!() - } -} - -#[tauri::command] -pub async fn resize_terminal( - pty_manager: tauri::State<'_, Mutex>, - id: String, - cols: u16, - rows: u16, -) -> Result<(), PtyError> { - if let Ok(mut pty_manager) = pty_manager.lock() { - pty_manager.resize(&id, cols, rows)?; - Ok(()) - } else { - Err(PtyError::ManagerUnresponding) - } -} - -#[tauri::command] -pub async fn check_close_availability( - pty_manager: tauri::State<'_, Mutex>, - app_config: tauri::State<'_, Arc>>, - id: String, -) -> Result { - let app_config = app_config.lock().unwrap(); - - if app_config.close_confirmation.tab { - if let Ok(pty_manager) = pty_manager.lock() { - Ok(app_config - .close_confirmation - .excluded_process - .contains(&pty_manager.get_running_process(&id)?)) - } else { - Err(PtyError::ManagerUnresponding) - } - } else { - Ok(true) - } -} - -#[tauri::command] -pub async fn close_terminal( - pty_manager: tauri::State<'_, Mutex>, - id: String, -) -> Result<(), PtyError> { - if let Ok(mut pty_manager) = pty_manager.lock() { - pty_manager.close(&id)?; - Ok(()) - } else { - Err(PtyError::ManagerUnresponding) - } -} - -#[tauri::command] -pub async fn get_pty_title( - pty_manager: tauri::State<'_, Mutex>, - id: String, -) -> Result { - pty_manager - .lock() - .or(Err(PtyError::ManagerUnresponding))? - .get_running_process(&id) -} diff --git a/src-tauri/src/command/window.rs b/src-tauri/src/command/window.rs deleted file mode 100644 index cf39ea4..0000000 --- a/src-tauri/src/command/window.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[tauri::command] -pub fn close_window(window: tauri::Window) -> Result<(), ()> { - window.close().unwrap(); - - Ok(()) -} diff --git a/src-tauri/src/common/commands/mod.rs b/src-tauri/src/common/commands/mod.rs new file mode 100644 index 0000000..299d4b6 --- /dev/null +++ b/src-tauri/src/common/commands/mod.rs @@ -0,0 +1,7 @@ +mod pty; +mod utils; +mod window; + +pub use pty::*; +pub use utils::*; +pub use window::*; diff --git a/src-tauri/src/common/commands/pty.rs b/src-tauri/src/common/commands/pty.rs new file mode 100644 index 0000000..e941b2a --- /dev/null +++ b/src-tauri/src/common/commands/pty.rs @@ -0,0 +1,171 @@ +use crate::common::payloads::{PtySendData, PtyTitleChanged}; +use crate::common::states::Ptys; +use crate::configuration::deserialized::Option; +use std::sync::Arc; +use tauri::Manager; +use tokio::sync::Mutex; + +use crate::common::error::PtyError; + +use crate::pty::pty::Pty; + +#[tauri::command] +pub async fn pty_open( + app: tauri::AppHandle, + id: String, + profile_uuid: String, + option: tauri::State<'_, Arc>>, + ptys: tauri::State<'_, Ptys>, +) -> Result<(), PtyError> { + let id_cloned = id.clone(); + let id_cloned_twice = id.clone(); + let id_cloned_thrice = id.clone(); + let app_cloned = app.clone(); + let app_cloned_twice = app.clone(); + + let locked_option = option.lock().await; + let opening_profile = locked_option + .profiles + .iter() + .find(|profile| profile.uuid == profile_uuid) + .ok_or(PtyError::UnknownPty)?; + + let should_update_title = opening_profile.terminal_options.title_is_running_process; + + ptys.0.lock().await.insert( + id.clone(), + Pty::build_and_run( + &opening_profile.command, + move |readed| { + app.emit_all( + "js_pty_data", + PtySendData { + data: readed, + id: &id_cloned, + }, + ) + .ok(); + }, + move |tab_title| { + if should_update_title { + app_cloned + .emit_all( + "js_pty_title_update", + PtyTitleChanged { + id: &id_cloned_twice, + title: tab_title, + }, + ) + .ok(); + } + }, + move || { + app_cloned_twice + .emit_all("js_pty_closed", id_cloned_thrice) + .ok(); + }, + ) + .await?, + ); + + Ok(()) +} + +#[tauri::command] +pub async fn pty_close(ptys: tauri::State<'_, Ptys>, id: String) -> Result<(), PtyError> { + ptys.0 + .lock() + .await + .get(&id) + .ok_or(PtyError::UnknownPty)? + .kill() + .await +} + +#[tauri::command] +pub async fn pty_write( + id: String, + content: String, + ptys: tauri::State<'_, Ptys>, +) -> Result<(), PtyError> { + ptys.0 + .lock() + .await + .get_mut(&id) + .ok_or(PtyError::UnknownPty)? + .write(&content) +} + +#[tauri::command] +pub async fn pty_resize( + ptys: tauri::State<'_, Ptys>, + id: String, + cols: u16, + rows: u16, +) -> Result<(), PtyError> { + ptys.0 + .lock() + .await + .get(&id) + .ok_or(PtyError::UnknownPty)? + .resize(cols, rows) + .await +} + +#[tauri::command] +pub async fn pty_get_closable( + ptys: tauri::State<'_, Ptys>, + app_config: tauri::State<'_, Arc>>, + id: String, +) -> Result { + let app_config = app_config.lock().await; + + if app_config.close_confirmation.tab { + let locked_ptys = ptys.0.lock().await; + let pty = locked_ptys.get(&id).ok_or(PtyError::UnknownPty)?; + + Ok(pty.closed.load(std::sync::atomic::Ordering::Relaxed) + || app_config + .close_confirmation + .excluded_process + .contains(&*pty.title.lock().await)) + } else { + Ok(true) + } +} + +#[tauri::command] +pub async fn pty_get_title(ptys: tauri::State<'_, Ptys>, id: String) -> Result { + Ok(ptys + .0 + .lock() + .await + .get_mut(&id) + .ok_or(PtyError::UnknownPty)? + .title + .lock() + .await + .to_string()) +} + +#[tauri::command] +pub async fn pty_pause(ptys: tauri::State<'_, Ptys>, id: String) -> Result<(), PtyError> { + ptys.0 + .lock() + .await + .get(&id) + .ok_or(PtyError::UnknownPty)? + .pause(); + Ok(()) +} + +#[tauri::command] +pub async fn pty_resume(ptys: tauri::State<'_, Ptys>, id: String) -> Result<(), PtyError> { + ptys.0 + .lock() + .await + .get(&id) + .ok_or(PtyError::UnknownPty)? + .resume(); + Ok(()) +} diff --git a/src-tauri/src/common/commands/utils.rs b/src-tauri/src/common/commands/utils.rs new file mode 100644 index 0000000..e5df6fa --- /dev/null +++ b/src-tauri/src/common/commands/utils.rs @@ -0,0 +1,15 @@ +use crate::configuration::deserialized::Option; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[tauri::command] +pub fn utils_close_app(app: tauri::AppHandle) { + app.exit(0); +} + +#[tauri::command] +pub async fn utils_get_configuration( + option: tauri::State<'_, Arc>>, +) -> Result { + Ok(option.lock().await.clone()) +} diff --git a/src-tauri/src/common/commands/window.rs b/src-tauri/src/common/commands/window.rs new file mode 100644 index 0000000..b9ab679 --- /dev/null +++ b/src-tauri/src/common/commands/window.rs @@ -0,0 +1,4 @@ +#[tauri::command] +pub fn window_close(window: tauri::Window) { + window.close().ok(); +} diff --git a/src-tauri/src/common/error.rs b/src-tauri/src/common/error.rs new file mode 100644 index 0000000..f3bd342 --- /dev/null +++ b/src-tauri/src/common/error.rs @@ -0,0 +1,22 @@ +#[derive(Debug, thiserror::Error)] +pub enum PtyError { + #[error("There is no terminal corresponding to this ID.")] + UnknownPty, + #[error("A problem occurred while creating the terminal: {0}.")] + Creation(String), + #[error("A problem occurred while writing to the terminal: {0}.")] + Write(String), + #[error("A problem occurred while resizing the terminal: {0}.")] + Resize(String), + #[error("A problem occurred while closing the terminal: {0}.")] + Kill(String), +} + +impl serde::Serialize for PtyError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/src-tauri/src/common/errors.rs b/src-tauri/src/common/errors.rs deleted file mode 100644 index df6ba9d..0000000 --- a/src-tauri/src/common/errors.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[derive(Debug)] -pub enum PtyError { - Kill(String), - Resize(String), - Write(String), - Create(String), - CloseableStatus(String), - ManagerUnresponding, -} - -impl serde::Serialize for PtyError { - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } -} - -impl std::fmt::Display for PtyError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::Kill(r) => write!(f, "Unable to terminate the process. Reason: {}", r), - Self::Resize(r) => write!(f, "Unable to resize the given terminal. Reason: {}", r), - Self::Write(r) => write!(f, "Unable to write to the given terminal. Reason: {}", r), - Self::Create(r) => write!(f, "Unable to create a new terminal. Reason: {}", r), - Self::CloseableStatus(r) => write!( - f, - "Unable to verify if the terminal is closable. Reason: {}", - r - ), - Self::ManagerUnresponding => write!(f, "Terminal manager is unresponding."), - } - } -} diff --git a/src-tauri/src/common/mod.rs b/src-tauri/src/common/mod.rs index 34df27b..7a51d27 100644 --- a/src-tauri/src/common/mod.rs +++ b/src-tauri/src/common/mod.rs @@ -1,3 +1,5 @@ -pub mod errors; +pub mod commands; +pub mod error; pub mod payloads; +pub mod states; pub mod utils; diff --git a/src-tauri/src/common/payloads.rs b/src-tauri/src/common/payloads.rs index f451f11..01e32cd 100644 --- a/src-tauri/src/common/payloads.rs +++ b/src-tauri/src/common/payloads.rs @@ -1,11 +1,11 @@ #[derive(serde::Serialize, Clone)] -pub struct PtySendData { - pub id: String, - pub data: String, +pub struct PtySendData<'a> { + pub id: &'a str, + pub data: &'a str, } #[derive(serde::Serialize, Clone)] -pub struct PtyTitleChanged { - pub id: String, - pub title: String, +pub struct PtyTitleChanged<'a> { + pub id: &'a str, + pub title: &'a str, } diff --git a/src-tauri/src/common/states.rs b/src-tauri/src/common/states.rs new file mode 100644 index 0000000..6d150d2 --- /dev/null +++ b/src-tauri/src/common/states.rs @@ -0,0 +1,6 @@ +use crate::pty::pty::Pty; +use std::collections::HashMap; +use tokio::sync::Mutex; + +#[derive(Default)] +pub struct Ptys(pub Mutex>); diff --git a/src-tauri/src/common/utils.rs b/src-tauri/src/common/utils.rs index e625071..0bac1fd 100644 --- a/src-tauri/src/common/utils.rs +++ b/src-tauri/src/common/utils.rs @@ -1,85 +1,29 @@ use crate::configuration::deserialized::TerminalTheme; -#[cfg(target_os = "windows")] -use std::ffi::OsString; - -pub fn parse_theme(location: String) -> (Option, Option) { - let theme_path = if std::path::Path::new(&format!( - "{}/tess/themes/{}", - dirs_next::config_dir().unwrap_or_default().display(), - location - )) - .exists() - { - Some(format!( - "{}/tess/themes/{}", - dirs_next::config_dir().unwrap_or_default().display(), - location - )) - } else if std::path::Path::new(&location).exists() { - Some(location) - } else { - None - }; - - theme_path.map_or((None, None), |theme_path| { - let app_theme = if std::path::Path::new(&format!("{}/style.css", theme_path)).exists() { - Some(format!("{}/style.css", theme_path)) - } else { - None - }; - let terminal_theme = - if std::path::Path::new(&format!("{}/terminal.json", theme_path)).exists() { - Some( +#[must_use] +pub fn parse_theme(location: &str) -> (Option, Option) { + dirs_next::config_dir() + .unwrap_or_default() + .join("tess/themes") + .join(location) + .canonicalize() + .map_or((None, None), |theme_path| { + let app_theme = theme_path + .join("style.css") + .canonicalize() + .map_or(None, |app_theme_file| { + app_theme_file.to_str().map(String::from) + }); + let terminal_theme = theme_path.join("terminal.json").canonicalize().map_or( + None, + |terminal_theme_file| { serde_json::from_str( - &std::fs::read_to_string(std::path::Path::new(&format!( - "{}/terminal.json", - theme_path - ))) - .unwrap_or_default(), + &std::fs::read_to_string(terminal_theme_file).unwrap_or_default(), ) - .unwrap_or_default(), - ) - } else { - None - }; - - (app_theme, terminal_theme) - }) -} - -#[cfg(target_os = "windows")] -pub fn get_leader_process_name(process: remoteprocess::Process) -> Option { - if let Ok(childs) = process.child_processes() { - let mut childs = childs - .iter() - .filter(|tmp| { - tmp.1 == process.pid - && remoteprocess::Process::new(tmp.0) - .is_ok_and(|sub_process| sub_process.exe().is_ok()) - }) - .collect::>(); - - if childs.is_empty() { - std::path::Path::new(&process.exe().ok().unwrap_or_default()) - .file_name() - .map(|x| x.to_owned()) - } else { - childs.sort(); + .ok() + }, + ); - if let Ok(child_process) = - remoteprocess::Process::new(childs.as_slice().last().unwrap().0) - { - get_leader_process_name(child_process) - } else { - std::path::Path::new(&process.exe().ok().unwrap_or_default()) - .file_name() - .map(|x| x.to_owned()) - } - } - } else { - std::path::Path::new(&process.exe().ok().unwrap_or_default()) - .file_name() - .map(|x| x.to_owned()) - } + (app_theme, terminal_theme) + }) } diff --git a/src-tauri/src/configuration/deserialized.rs b/src-tauri/src/configuration/deserialized.rs index 19922bd..72b6f10 100644 --- a/src-tauri/src/configuration/deserialized.rs +++ b/src-tauri/src/configuration/deserialized.rs @@ -53,7 +53,7 @@ impl<'de> serde::Deserialize<'de> for Option { fn deserialize>(deserializer: D) -> Result { let partial_option = PartialOption::deserialize(deserializer).unwrap_or_default(); - let (app_theme, terminal_theme) = parse_theme(partial_option.theme.clone()); + let (app_theme, terminal_theme) = parse_theme(&partial_option.theme); let app_theme = app_theme.unwrap_or_default(); let terminal_theme = terminal_theme.unwrap_or_default(); @@ -122,7 +122,7 @@ impl<'de> serde::Deserialize<'de> for Option { let profile_theme = partial_profile.theme.map_or_else( || terminal_theme.clone(), |partial_profile_theme| { - parse_theme(partial_profile_theme) + parse_theme(&partial_profile_theme) .1 .unwrap_or_else(|| terminal_theme.clone()) }, @@ -135,7 +135,7 @@ impl<'de> serde::Deserialize<'de> for Option { background_transparency: partial_profile .background_transparency .unwrap_or(partial_option.background_transparency), - uuid: uuid::Uuid::parse_str(partial_profile.uuid.unwrap_or_default().as_str()) + uuid: uuid::Uuid::parse_str(&partial_profile.uuid.unwrap_or_default()) .unwrap_or_else(|_| uuid::Uuid::new_v4()) .to_string(), command: partial_profile.command, @@ -199,7 +199,7 @@ impl<'de> serde::Deserialize<'de> for Option { }); Ok(Self { - theme: partial_option.theme.clone(), + theme: partial_option.theme, terminal_theme, app_theme, diff --git a/src-tauri/src/configuration/partial.rs b/src-tauri/src/configuration/partial.rs index b437067..933e256 100644 --- a/src-tauri/src/configuration/partial.rs +++ b/src-tauri/src/configuration/partial.rs @@ -102,11 +102,10 @@ where Complex(BackgroundMedia), } - Ok(Representation::deserialize(data).map_or( - None, - |todo_name_to_find| match todo_name_to_find { + Ok( + Representation::deserialize(data).map_or(None, |representation| match representation { Representation::Simple(path) => BackgroundMedia::deserialize_from_string(path), Representation::Complex(background) => Some(background), - }, - )) + }), + ) } diff --git a/src-tauri/src/configuration/types.rs b/src-tauri/src/configuration/types.rs index 4c06e48..e3cba36 100644 --- a/src-tauri/src/configuration/types.rs +++ b/src-tauri/src/configuration/types.rs @@ -122,6 +122,7 @@ pub struct BackgroundMedia { } impl BackgroundMedia { + #[must_use] pub fn deserialize_from_string(value: String) -> Option { std::fs::read(&value).map_or(None, |file| { if infer::is_image(&file) { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3fae476..23cb129 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,6 +1,4 @@ -pub mod command; pub mod common; pub mod configuration; pub mod logger; pub mod pty; -pub mod state; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index eb6a451..553dd7a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,17 +4,21 @@ )] use tauri::{Manager, WindowEvent}; -use tess::command::{app::*, option::*, term::*, window::*}; use tess::configuration::deserialized::Option; use tess::configuration::types::BackgroundType; use tess::logger::Logger; +use tess::common::commands; + #[cfg(target_family = "unix")] use futures::stream::StreamExt; #[cfg(target_family = "unix")] use signal_hook::consts::signal::*; -use std::sync::{Arc, Mutex}; +use tess::common::states::Ptys; + +use std::sync::Arc; +use tokio::sync::Mutex; #[tokio::main] async fn main() { @@ -33,34 +37,41 @@ async fn main() { )); let option = Arc::from(Mutex::from(if let Ok(config_file) = config_file { - // TODO: Log error - serde_json::from_str(&config_file).unwrap_or_default() + let parsed_option = serde_json::from_str(&config_file); + if parsed_option.is_err() { + logger.warn(&format!( + "Malformed configuration file: {}.", + parsed_option.as_ref().err().unwrap() + )); + } + parsed_option.unwrap_or_default() } else { Option::default() })); tauri::async_runtime::set(tokio::runtime::Handle::current()); let app = tauri::Builder::default() - .manage(Mutex::new(tess::state::pty_manager::PtyManager::new())) .manage(option.clone()) + .manage(Ptys::default()) .invoke_handler(tauri::generate_handler![ - create_terminal, - terminal_input, - resize_terminal, - close_terminal, - close_window, - close_app, - check_close_availability, - get_pty_title, - get_configuration + commands::pty_open, + commands::pty_close, + commands::pty_write, + commands::pty_resize, + commands::pty_get_title, + commands::pty_get_closable, + commands::pty_resume, + commands::pty_pause, + commands::utils_close_app, + commands::utils_get_configuration, + commands::window_close ]) - .build(tauri::generate_context!()); - - let app_handle = app.as_ref().unwrap().app_handle(); + .build(tauri::generate_context!()) + .unwrap(); - match &option.lock().unwrap().background { + match &option.lock().await.background { BackgroundType::Media(media) => { - app_handle.fs_scope().allow_file(&media.location).ok(); + app.fs_scope().allow_file(&media.location).ok(); } #[cfg(target_family = "unix")] BackgroundType::Blurred => { @@ -68,7 +79,7 @@ async fn main() { } #[cfg(target_os = "windows")] BackgroundType::Mica => { - if window_vibrancy::apply_mica(app_handle.get_window("main").unwrap()).is_err() { + if window_vibrancy::apply_mica(app.get_window("main").unwrap()).is_err() { logger.warn( "Cannot apply mica background effect. Switching back to transparent background", ); @@ -76,8 +87,7 @@ async fn main() { } #[cfg(target_os = "windows")] BackgroundType::Acrylic => { - if window_vibrancy::apply_acrylic(app_handle.get_window("main").unwrap(), None).is_err() - { + if window_vibrancy::apply_acrylic(app.get_window("main").unwrap(), None).is_err() { logger.warn("Cannot apply acrylic background effect. Switching back to transparent background"); } } @@ -88,18 +98,13 @@ async fn main() { _ => {} } - app_handle - .get_window("main") - .unwrap() - .set_decorations(true) - .ok(); + app.get_window("main").unwrap().set_decorations(true).ok(); - app_handle - .fs_scope() - .allow_file(&option.lock().unwrap().app_theme) + app.fs_scope() + .allow_file(&option.lock().await.app_theme) .ok(); - app.unwrap().run(move |app, event| match event { + app.run(move |app, event| match event { tauri::RunEvent::Ready => { #[cfg(debug_assertions)] app.get_window("main").unwrap().open_devtools(); @@ -108,20 +113,22 @@ async fn main() { { let app_cloned = app.clone(); tokio::spawn(async move { - if let Ok(mut signals_stream) = signal_hook_tokio::Signals::new(&[SIGQUIT, SIGTERM]) { - while let Some(_) = signals_stream.next().await { + if let Ok(mut signals_stream) = + signal_hook_tokio::Signals::new([SIGQUIT, SIGTERM]) + { + while signals_stream.next().await.is_some() { let windows_count = app_cloned.windows().len(); if windows_count > 1 { app_cloned .get_window("main") .unwrap() - .emit("request_app_exit", windows_count) + .emit("js_app_request_exit", windows_count) .ok(); } else { app_cloned .get_window("main") .unwrap() - .emit("request_window_closing", ()) + .emit("js_window_request_closing", ()) .ok(); } } @@ -131,21 +138,26 @@ async fn main() { }); } - logger.info(&format!("Launched in {}ms", start.elapsed().as_millis())); + logger.info(&format!("Launched in {}ms.", start.elapsed().as_millis())); } tauri::RunEvent::WindowEvent { label, event: WindowEvent::CloseRequested { api, .. }, .. } => { - if option.lock().unwrap().close_confirmation.window { - app.get_window(&label) - .unwrap() - .emit("request_window_closing", "") - .ok(); + let option_cloned = option.clone(); + tokio::task::block_in_place(move || { + tokio::runtime::Handle::current().block_on(async { + if option_cloned.lock().await.close_confirmation.window { + app.get_window(&label) + .unwrap() + .emit("js_window_request_closing", "") + .ok(); - api.prevent_close() - } + api.prevent_close() + } + }) + }) } _ => (), }) diff --git a/src-tauri/src/pty.rs b/src-tauri/src/pty.rs deleted file mode 100644 index fb2c08c..0000000 --- a/src-tauri/src/pty.rs +++ /dev/null @@ -1,317 +0,0 @@ -use portable_pty::Child; -use portable_pty::{native_pty_system, CommandBuilder, PtySize}; -use std::sync::{Arc, Mutex, RwLock}; - -use crate::common::errors::PtyError; -use crate::configuration::deserialized::Profile; -use tauri::{AppHandle, Manager}; - -use crate::common::payloads::{PtySendData, PtyTitleChanged}; - -use std::ffi::OsString; - -#[cfg(target_os = "windows")] -use crate::common::utils::get_leader_process_name; -#[cfg(target_os = "windows")] -use lazy_static::lazy_static; -#[cfg(target_os = "windows")] -use regex::{Captures, Regex}; -#[cfg(target_os = "windows")] -use remoteprocess::Process; - -pub struct Pty { - pub app: Arc, - pair: Arc>, - child: Option>>>, - #[cfg(target_family = "unix")] - current_leader: Arc>, - #[cfg(target_os = "windows")] - leader_programm_name: Arc>, - writer: Option>, - is_running: Arc>, - title_is_running_process: bool, - running_process: Arc>, -} - -unsafe impl Send for Pty {} -unsafe impl Sync for Pty {} - -impl Pty { - pub fn new( - app: Arc, - profile: Profile, - cols: u16, - rows: u16, - id: String, - ) -> Result { - let pty_system = native_pty_system(); - - if let Ok(pair) = pty_system.openpty(PtySize { - rows, - cols, - pixel_width: 0, - pixel_height: 0, - }) { - let mut pty = Self { - app, - pair: Arc::new(Mutex::from(pair)), - child: None, - #[cfg(target_family = "unix")] - current_leader: Arc::new(RwLock::new(0)), - #[cfg(target_os = "windows")] - leader_programm_name: Arc::new(Mutex::from(OsString::new())), - writer: None, - is_running: Arc::new(RwLock::new(true)), - title_is_running_process: profile.terminal_options.title_is_running_process, - running_process: Arc::new(Mutex::from(String::new())), - }; - - pty.run(profile, id)?; - - Ok(pty) - } else { - Err(PtyError::Create(String::from( - "An error occured when creating a new terminal.", - ))) - } - } - - pub fn run(&mut self, profile: Profile, id: String) -> Result<(), PtyError> { - #[cfg(target_os = "windows")] - lazy_static! { - static ref PROGRAMM_PARSING_REGEX: Regex = Regex::new("%([[:word:]]*)%").unwrap(); - } - - #[cfg(target_os = "windows")] - let cmd: String = PROGRAMM_PARSING_REGEX - .replace_all(&profile.command, |env_variable: &Captures| { - std::env::var(&env_variable[1]).unwrap_or_default() - }) - .into(); - - #[cfg(target_family = "unix")] - let cmd = profile.command; - - #[allow(unused_mut)] - let mut command_builder = CommandBuilder::from_argv( - cmd.split(' ') - .map(std::ffi::OsString::from) - .collect::>(), - ); - - #[cfg(target_family = "unix")] - command_builder.env("TERM", "xterm-256color"); - - let locked_pair = self.pair.lock().unwrap(); - - if let Ok(child) = locked_pair.slave.spawn_command(command_builder) { - #[cfg(target_os = "windows")] - let tmp_pty_pid = child.process_id().unwrap(); - - let child = Arc::from(Mutex::from(child)); - let child_clone = child.clone(); - - if let Ok(mut reader) = locked_pair.master.try_clone_reader() { - let app = self.app.as_ref().clone(); - let cloned_app = app.clone(); - - let id_cloned = id.clone(); - - let running_process_cloned = self.running_process.clone(); - - self.child = Some(child); - self.writer = Some(locked_pair.master.take_writer().unwrap()); - - let is_running = self.is_running.clone(); - let is_running_cloned = is_running.clone(); - - let title_is_running_process = self.title_is_running_process; - - #[cfg(target_os = "windows")] - let leader_programm_name = self.leader_programm_name.clone(); - - #[cfg(target_family = "unix")] - let cloned_pair = self.pair.clone(); - #[cfg(target_family = "unix")] - let current_process_leader_pid = self.current_leader.clone(); - - std::thread::spawn(move || { - while *is_running.read().unwrap() { - let mut buffer = [0; 4096]; - - if reader.read(&mut buffer).is_ok() { - app.emit_all( - "terminalData", - PtySendData { - data: String::from_utf8(buffer.to_vec()).unwrap_or_default(), - id: id.clone(), - }, - ) - .ok(); - } - } - }); - - std::thread::spawn(move || { - while *is_running_cloned.read().unwrap() { - if let Ok(Some(_)) = child_clone.as_ref().lock().unwrap().try_wait() { - *is_running_cloned.write().unwrap() = false; - - cloned_app - .emit_all("terminal_closed", id_cloned.clone()) - .ok(); - - break; - } - - #[cfg(target_os = "windows")] - if let Some(leader_process_name) = - get_leader_process_name(Process::new(tmp_pty_pid).unwrap()) - { - let mut leader_programm_name_locked = - leader_programm_name.lock().unwrap(); - - if leader_process_name != *leader_programm_name_locked { - *leader_programm_name_locked = leader_process_name; - - if title_is_running_process { - cloned_app - .emit_all( - "terminalTitleChanged", - PtyTitleChanged { - id: id_cloned.clone(), - title: leader_programm_name_locked - .clone() - .into_string() - .unwrap(), - }, - ) - .ok(); - } - - if let Ok(mut locked_running_process_clone) = - running_process_cloned.lock() - { - *locked_running_process_clone = - leader_programm_name_locked.clone().into_string().unwrap() - } - } - } - - #[cfg(target_family = "unix")] - { - if let Some(process_leader_pid) = - cloned_pair.lock().unwrap().master.process_group_leader() - { - if process_leader_pid != *current_process_leader_pid.read().unwrap() - { - if let Ok(mut process_leader_title) = std::fs::read_to_string( - format!("/proc/{}/comm", process_leader_pid), - ) { - process_leader_title.pop(); - - if process_leader_title != "tokio-runtime-w" { - *current_process_leader_pid.write().unwrap() = - process_leader_pid; - - if title_is_running_process { - cloned_app - .emit_all( - "terminalTitleChanged", - PtyTitleChanged { - id: id_cloned.clone(), - title: process_leader_title.clone(), - }, - ) - .ok(); - } - - if let Ok(mut locked_running_process_clone) = - running_process_cloned.lock() - { - *locked_running_process_clone = - process_leader_title; - } - } - } - } - } - } - - std::thread::sleep(std::time::Duration::from_millis(16)); - } - }); - }; - - Ok(()) - } else { - Err(PtyError::Create(String::from( - "An error occured when creating a new terminal.", - ))) - } - } - - pub fn write(&mut self, content: &str) -> Result<(), PtyError> { - if matches!(write!(self.writer.as_mut().unwrap(), "{}", content), Ok(())) { - Ok(()) - } else { - Err(PtyError::Write(String::from( - "pty doesn't accept the incoming data", - ))) - } - } - - pub fn resize(&self, cols: u16, rows: u16) -> Result<(), PtyError> { - if matches!( - self.pair - .lock() - .unwrap() - .master - .resize(portable_pty::PtySize { - cols, - rows, - pixel_height: 0, - pixel_width: 0 - }), - Ok(()) - ) { - Ok(()) - } else { - Err(PtyError::Resize(String::from( - "pty doesn't accept the resize operation.", - ))) - } - } - - pub fn close(&mut self) -> Result<(), PtyError> { - self.child.as_deref().map_or_else( - || todo!(), - |child| { - *self.is_running.write().unwrap() = false; - - child.lock().map_or_else( - |_| todo!(), - |mut child| { - if child.try_wait().is_ok_and(|x| x.is_some()) - || matches!(child.kill(), Ok(())) - { - Ok(()) - } else { - todo!() - } - }, - ) - }, - ) - } - - pub fn running_process(&self) -> Result { - Ok(self - .running_process - .lock() - .or(Err(PtyError::CloseableStatus(String::from( - "Cannot get running process.", - ))))? - .to_string()) - } -} diff --git a/src-tauri/src/pty/mod.rs b/src-tauri/src/pty/mod.rs new file mode 100644 index 0000000..91905e4 --- /dev/null +++ b/src-tauri/src/pty/mod.rs @@ -0,0 +1,2 @@ +pub mod pty; +mod utils; diff --git a/src-tauri/src/pty/pty.rs b/src-tauri/src/pty/pty.rs new file mode 100644 index 0000000..95fb0cb --- /dev/null +++ b/src-tauri/src/pty/pty.rs @@ -0,0 +1,231 @@ +use std::ffi::OsString; +use std::io::{Read, Write}; +use std::sync::{mpsc, Arc}; +use std::time::Duration; + +use portable_pty::{native_pty_system, Child, CommandBuilder, MasterPty, PtySize}; +use tokio::sync::Mutex; + +use std::sync::atomic::{AtomicBool, Ordering}; + +use crate::common::error::PtyError; + + +#[cfg(target_os = "windows")] +use regex::Regex; +#[cfg(target_os = "windows")] +use crate::pty::utils; +#[cfg(target_os = "windows")] +use regex::Captures; + +pub struct Pty { + writer: Box, + child: Arc>>, + master: Arc>>, + paused: Arc, + + pub title: Arc>, + pub closed: Arc, +} + +unsafe impl Send for Pty {} +unsafe impl Sync for Pty {} + +impl Pty { + pub async fn build_and_run( + command: &str, + on_read: impl Fn(&str) + std::marker::Send + 'static, + on_tab_title_update: impl Fn(&str) + std::marker::Send + 'static, + once_exit: impl FnOnce() + std::marker::Send + 'static, + ) -> Result { + #[cfg(target_os = "windows")] + lazy_static::lazy_static! { + static ref PROGRAMM_PARSING_REGEX: Regex = Regex::new("%([[:word:]]*)%").unwrap(); + } + + #[cfg(target_os = "windows")] + let builded_command = CommandBuilder::from_argv( + PROGRAMM_PARSING_REGEX + .replace_all(command, |env_variable: &Captures| { + std::env::var(&env_variable[1]).unwrap_or_default() + }) + .split(' ') + .map(std::ffi::OsString::from) + .collect::>(), + ); + + #[cfg(target_family = "unix")] + #[allow(unused_mut)] + let mut builded_command = CommandBuilder::from_argv( + command + .split(' ') + .map(std::ffi::OsString::from) + .collect::>(), + ); + + #[cfg(target_family = "unix")] + builded_command.env("TERM", "xterm-256color"); + + let pty_pair = native_pty_system() + .openpty(PtySize::default()) + .map_err(|err| PtyError::Creation(err.to_string()))?; + + let writer = pty_pair + .master + .take_writer() + .map_err(|err| PtyError::Creation(err.to_string()))?; + let mut reader = pty_pair + .master + .try_clone_reader() + .map_err(|err| PtyError::Creation(err.to_string()))?; + let master = Arc::new(Mutex::from(pty_pair.master)); + + let child = Arc::from(Mutex::new( + pty_pair + .slave + .spawn_command(builded_command) + .map_err(|err| PtyError::Creation(err.to_string()))?, + )); + + let cloned_child = child.clone(); + let title = Arc::new(Mutex::from(String::new())); + let cloned_title = title.clone(); + + let closed = Arc::new(AtomicBool::new(false)); + let closed_cloned = closed.clone(); + + let (exit_sender, exit_receiver) = mpsc::channel::<()>(); + let paused = Arc::from(AtomicBool::new(false)); + let paused_cloned = paused.clone(); + + #[cfg(target_family = "unix")] + let cloned_master = master.clone(); + #[cfg(target_os = "windows")] + let shell_pid = child.lock().await.process_id().ok_or(PtyError::Creation("PID not found".to_owned()))?; + + std::thread::spawn(move || { + let mut buf = [0; 4096]; + let mut remaining = 0; + + loop { + if !paused_cloned.load(Ordering::Relaxed) { + buf[remaining..].fill(0); + + if reader + .read(&mut buf[remaining..]) + .is_ok_and(|bytes| bytes > 0) + { + match std::str::from_utf8(&buf) { + Ok(parsed_buf) => { + on_read(parsed_buf); + remaining = 0; + } + Err(utf8) => { + on_read(unsafe { + std::str::from_utf8_unchecked(&buf[..utf8.valid_up_to()]) + }); + remaining = buf[utf8.valid_up_to()..].len(); + buf.rotate_left(utf8.valid_up_to()); + } + } + } + } + + if exit_receiver.try_recv().is_ok() { + break; + } + } + }); + + tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_millis(20)); + + loop { + interval.tick().await; + + if matches!(cloned_child.lock().await.try_wait(), Ok(Some(_))) { + exit_sender.send(()).ok(); + + closed_cloned.store(true, Ordering::Relaxed); + once_exit(); + + break; + } + + #[cfg(target_family = "unix")] + let process_leader_pid = cloned_master.lock().await.process_group_leader(); + #[cfg(target_os = "windows")] + let process_leader_pid = Some(utils::get_leader_pid(shell_pid)); + + if let Some(fetched_leader_pid) = process_leader_pid { + let fetched_title = tokio::task::spawn_blocking(move || { + super::utils::get_process_title(fetched_leader_pid) + }) + .await + .unwrap(); + + let mut current_process_title = cloned_title.lock().await; + + if fetched_title + .as_ref() + .is_some_and(|fetched_title| fetched_title != &*current_process_title) + { + let fetched_title = fetched_title.unwrap(); + + on_tab_title_update(&fetched_title); + *current_process_title = fetched_title; + } + } + } + }); + + Ok(Self { + writer, + child, + master, + paused, + title, + closed, + }) + } + + pub fn write(&mut self, content: &str) -> Result<(), PtyError> { + self.writer + .write(content.as_bytes()) + .map_err(|err| PtyError::Write(err.to_string())) + .map(|_| ()) + } + + pub async fn kill(&self) -> Result<(), PtyError> { + if self.closed.load(Ordering::Relaxed) { + Ok(()) + } else { + self.closed.store(true, Ordering::Relaxed); + self.child + .lock() + .await + .kill() + .map_err(|err| PtyError::Kill(err.to_string())) + } + } + + pub async fn resize(&self, cols: u16, rows: u16) -> Result<(), PtyError> { + self.master + .lock() + .await + .resize(PtySize { + rows, + cols, + ..Default::default() + }) + .map_err(|err| PtyError::Resize(err.to_string())) + } + + pub fn pause(&self) { + self.paused.store(true, Ordering::Relaxed); + } + + pub fn resume(&self) { + self.paused.store(false, Ordering::Relaxed); + } +} diff --git a/src-tauri/src/pty/utils.rs b/src-tauri/src/pty/utils.rs new file mode 100644 index 0000000..4135ffb --- /dev/null +++ b/src-tauri/src/pty/utils.rs @@ -0,0 +1,77 @@ +#[cfg(target_os = "windows")] +pub fn get_leader_pid(shell_pid: u32) -> u32 { + use std::mem::size_of; + use windows::Win32::System::Diagnostics::ToolHelp::{ + CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, + }; + + use windows::Win32::System::Diagnostics::ToolHelp::TH32CS_SNAPPROCESS; + + let mut leader_pid = shell_pid; + + let handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).unwrap() }; + let mut process_entry = PROCESSENTRY32W { + dwSize: size_of::().try_into().unwrap_or_default(), + ..Default::default() + }; + + let mut next_entry_result: windows::core::Result<()> = + unsafe { Process32FirstW(handle, &mut process_entry) }; + while next_entry_result.is_ok() { + if process_entry.th32ParentProcessID == leader_pid { + leader_pid = process_entry.th32ProcessID; + } + + next_entry_result = unsafe { Process32NextW(handle, &mut process_entry) }; + } + + leader_pid +} + +#[cfg(target_os = "windows")] +pub fn get_process_title(pid: u32) -> Option { + use std::{ffi::OsString, os::windows::ffi::OsStringExt}; + use windows::Win32::{ + Foundation::CloseHandle, + System::Threading::{PROCESS_QUERY_INFORMATION, PROCESS_VM_READ}, + }; + + unsafe { + windows::Win32::System::Threading::OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + false, + pid, + ) + } + .map_or(None, |handle| { + let mut path = [0; 4096]; + let path_len = unsafe { + windows::Win32::System::ProcessStatus::GetModuleFileNameExW(handle, None, &mut path) + }; + + unsafe { CloseHandle(handle).ok() }; + + std::path::PathBuf::from(OsString::from_wide(&path[..path_len as usize])) + .file_name() + .and_then(|filename| filename.to_os_string().into_string().ok()) + }) +} + +#[cfg(target_family = "unix")] +pub fn get_process_title(pid: i32) -> Option { + #[cfg(target_family = "unix")] + { + std::fs::read_to_string(format!("/proc/{pid}/comm")).map_or( + None, + |mut process_leader_title| { + process_leader_title.pop(); + + if process_leader_title == "tokio-runtime-w" { + None + } else { + Some(process_leader_title) + } + }, + ) + } +} diff --git a/src-tauri/src/state/mod.rs b/src-tauri/src/state/mod.rs deleted file mode 100644 index 2d4ce59..0000000 --- a/src-tauri/src/state/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod pty_manager; diff --git a/src-tauri/src/state/pty_manager.rs b/src-tauri/src/state/pty_manager.rs deleted file mode 100644 index 9815ee8..0000000 --- a/src-tauri/src/state/pty_manager.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::collections::HashMap; -use std::sync::Arc; - -use crate::configuration::deserialized::Profile; -use crate::pty::Pty; - -use crate::common::errors::PtyError; - -pub struct PtyManager { - ptys: HashMap, - pub app: Option>, -} - -impl Default for PtyManager { - fn default() -> Self { - Self::new() - } -} - -impl PtyManager { - #[must_use] - pub fn new() -> Self { - Self { - ptys: HashMap::new(), - app: None, - } - } - - pub fn create_pty( - &mut self, - cols: u16, - rows: u16, - id: String, - profile: Profile, - ) -> Result<(), PtyError> { - if let Some(app_ref) = self.app.as_ref() { - let pty = Pty::new(app_ref.clone(), profile, cols, rows, id.clone())?; - - self.ptys.insert(id, pty); - - Ok(()) - } else { - Err(PtyError::Create(String::from( - "Unable to give app access to the new process", - ))) - } - } - - pub fn write(&mut self, id: &str, content: &str) -> Result<(), PtyError> { - self.ptys - .get_mut(id) - .ok_or_else(|| PtyError::Write(String::from("Unable to access to the terminal.")))? - .write(content)?; - Ok(()) - } - - pub fn resize(&mut self, id: &str, cols: u16, rows: u16) -> Result<(), PtyError> { - self.ptys - .get_mut(id) - .ok_or_else(|| PtyError::Resize(String::from("Unable to access to the terminal.")))? - .resize(cols, rows)?; - Ok(()) - } - - pub fn close(&mut self, id: &str) -> Result<(), PtyError> { - self.ptys - .remove(id) - .ok_or_else(|| PtyError::Kill(String::from("Unable to access to the terminal.")))? - .close() - } - - pub fn get_running_process(&self, id: &str) -> Result { - self.ptys - .get(id) - .ok_or_else(|| { - PtyError::CloseableStatus(String::from("Unable to access to the terminal.")) - })? - .running_process() - } -} diff --git a/src/index.html b/src/index.html index 3f403d9..28d2efb 100644 --- a/src/index.html +++ b/src/index.html @@ -3,8 +3,9 @@ - Vite + TS + Tess +
@@ -23,7 +24,5 @@
- - diff --git a/src/style/constants.scss b/src/style/constants.scss index f891b01..e03f41d 100644 --- a/src/style/constants.scss +++ b/src/style/constants.scss @@ -14,7 +14,8 @@ --tab-progressbar-background: #050a19; --tab-action-button-background: var(--tab-focused-background); - --toast-background: #192033; + --toast-background: #050a19; + --toast-button-hovered-background: #192033; --popup-background: #050a19; --popup-button-background: #161e32; @@ -25,7 +26,5 @@ --popup-do-not-show-again-checkbox-checked-background: #156ce6; --action-button-background: transparent; - --action-button-hovered-background: #050a19; - - --shadow-color: #192033; // Delete this ?? + --action-button-hovered-background: #050a19; } \ No newline at end of file diff --git a/src/style/style.scss b/src/style/style.scss index 70d7bbf..1464b4c 100644 --- a/src/style/style.scss +++ b/src/style/style.scss @@ -9,12 +9,6 @@ @import "../../node_modules/xterm/css/xterm.css"; -@font-face { - font-family: "Inter"; - font-weight: 400; - src: url(../fonts/Inter-Regular.ttf); -} - @font-face { font-family: "Inter"; font-weight: 500; @@ -27,12 +21,6 @@ src: url(../fonts/Inter-SemiBold.ttf); } -/*@font-face { - font-family: "Inter"; - font-weight: 700; - src: url(../fonts/Inter-Bold.ttf); -}*/ - body { margin: 0; @@ -41,10 +29,13 @@ body { display: flex; flex-direction: column; overflow: hidden; - font-family: "Inter"; - letter-spacing: 0.6px; - word-spacing: 0.66px; + font-family: Inter; + letter-spacing: 0.8px; + word-spacing: 0.8px; + line-height: 16px; background: var(--app-background); + font-weight: 500; + color: var(--text-color); } @@ -54,7 +45,7 @@ body { } ::-webkit-scrollbar-thumb { - background:rgba(212, 212, 212, 0.14); + background:rgba(212, 212, 212, 0.15); border-radius: 6px; } diff --git a/src/style/tab.scss b/src/style/tab.scss index 35a379d..6f83aa3 100644 --- a/src/style/tab.scss +++ b/src/style/tab.scss @@ -4,7 +4,6 @@ width: 16rem; border-top-left-radius: 2px; border-top-right-radius: 2px; - color: var(--text-color); font-weight: 500; justify-content: center; align-items: center; @@ -12,10 +11,8 @@ -webkit-user-select: none; transition: transform 200ms, background 140ms; font-size: 12px; - letter-spacing: 1px; text-transform: capitalize; position: relative; - min-width: 0; overflow: hidden; background: var(--tabs-background); @@ -59,7 +56,7 @@ position: absolute; justify-content: center; align-items: center; - transition: background 100ms; + transition: background 140ms; opacity: 0; display: flex; color: var(--icon-color); diff --git a/src/style/toasts.scss b/src/style/toasts.scss index 45c50aa..14b7af4 100644 --- a/src/style/toasts.scss +++ b/src/style/toasts.scss @@ -1,107 +1,115 @@ .toasts { - width: calc(100% - 32px); position: absolute; - bottom: 0; + bottom: 18px; + right: 18px; z-index: 10; display: flex; align-items:end; flex-direction: column; row-gap: 12px; - padding-bottom: 16px; pointer-events: none; - margin-left: 16px; .toast { - padding-top: 9px; - padding-bottom: 9px; - padding-left: 10px; - padding-right: 10px; + padding-inline: 13px; + padding-block: 12px; border-radius: 4px; box-shadow: rgba(0, 0, 0, 0.28) 0px 5px 10px; - height: fit-content; background: var(--toast-background); animation: toast-inserted 140ms forwards; display: flex; justify-content: center; align-items: center; - color: var(--text-color); pointer-events: all; - max-width: 260px; + max-width: 270px; position: relative; .icon { position: absolute; - top: 4.5px; left: 0; fill: none; display: flex; justify-content: center; align-items: center; - padding-left: 10px; + padding-left: 13px; color: var(--icon-color); svg { - height: 16px; - width: 16px; + height: 20px; + width: 20px; } } .content { - padding-left: 24px; - display: flex; - justify-content: center; - row-gap: 2px; - flex-direction: column; + padding-left: 33px; overflow: hidden; - font-weight: 600; .title { font-size: 13px; + font-weight: 600; } .message { - font-size: 11px; - text-overflow: ellipsis; - max-height: 14px; - white-space: nowrap; - overflow: hidden; - transition: max-height 200ms; - -webkit-line-clamp: 13; - -webkit-box-orient: vertical; - display: -webkit-box; + margin-top: 4px; + font-size: 12px; + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 200ms; + + span { + text-align: justify; + min-height: 17px; + } } } - &.text-expanded .content { - .message { - max-height: 182px; - white-space: normal; + &.with-text { + .icon { + top: 20.5px; } - } - - &:hover .actions { - opacity: unset; - } - &.with-text { .actions { + top: 8px; + .close { box-shadow: unset; } .expand { - box-shadow: -9px 0 7px var(--shadow-color); + box-shadow: -9px 0 7px var(--toast-background); + + svg { + transform: rotate(-90deg); + transition: transform 140ms; + } } } + } + + &.text-expanded { + .actions .expand svg { + transform: rotate(0deg); + } - .icon { - top: 8.5px !important; + .content { + .message { + grid-template-rows: 1fr; + + span { + transition-delay: 200ms; + transition-property: white-space; + } + } } } + &:hover .actions { + opacity: unset; + } + .actions { - right: 7px; - top: 7px; + right: 8px; + margin-top: auto; + margin-bottom: auto; position: absolute; height: 20px; display: flex; @@ -121,12 +129,11 @@ cursor: pointer; &:hover { - background: var(--action-button-hovered-background) !important; + background: var(--toast-button-hovered-background) !important; } } .close { - background: var(--action-button-background); height: 20px; width: 20px; transition: background 100ms; @@ -139,7 +146,6 @@ } .expand { - background: var(--action-button-background); height: 20px; width: 20px; diff --git a/src/ts/manager/view.ts b/src/ts/app.ts similarity index 73% rename from src/ts/manager/view.ts rename to src/ts/app.ts index 6c859bb..a71397d 100644 --- a/src/ts/manager/view.ts +++ b/src/ts/app.ts @@ -1,23 +1,21 @@ -import { TabsManager } from "./tabs"; +import { TabsManager } from "./manager/tabs"; import { listen, Event } from '@tauri-apps/api/event' import { v4 as uuid } from 'uuid'; import { invoke } from '@tauri-apps/api/tauri' -import { terminalDataPayload, terminalTitleChangedPayload } from "../schema/term"; -import { View } from "../class/views"; -import { Toaster } from "./toast"; +import { terminalTitleChangedPayload } from "./schema/term"; +import { View } from "./class/views"; +import { Toaster } from "./manager/toast"; import { Option, ShortcutAction } from "ts/schema/option"; -import { PopupManager } from "./popup"; +import { PopupManager } from "./manager/popup"; import { TerminalPane } from "ts/class/panes"; -import { ShortcutsManager } from "./shortcuts"; +import { ShortcutsManager } from "./manager/shortcuts"; import { clipboard } from "@tauri-apps/api"; -import { PopupBuilder, PopupButton } from "../class/popup"; +import { PopupBuilder, PopupButton } from "./class/popup"; -export class ViewsManager { - // TODO: Finish - +export class App { private target: Element; private tabsManager: TabsManager; @@ -41,12 +39,11 @@ export class ViewsManager { this.shortcutsManager = new ShortcutsManager(option.shortcuts, (action) => { this.onShortcutExecuted(action) }); - listen("terminalData", (e) => { this.onTerminalReceiveData(e); }); - listen("terminalTitleChanged", (e) => { this.onTerminalTitleChanged(e); }) - listen("terminal_closed", (e) => { this.onTerminalProcessExited(e); }); + listen("js_pty_title_update", (e) => { this.onTerminalTitleChanged(e); }) + listen("js_pty_closed", (e) => { this.onTerminalProcessExited(e); }); - listen("request_window_closing", () => { this.closeViews(); }); - listen("request_app_exit", (e) => { this.closeAllWindows(e); }); + listen("js_window_request_closing", () => { this.closeViews(); }); + listen("js_app_request_exit", (e) => { this.closeAllWindows(e); }); this.toaster = new Toaster(toastTarget); @@ -59,34 +56,32 @@ export class ViewsManager { let popupResult = await this.popupManager.sendPopup(new PopupBuilder(`Confirm close of ${e.payload} windows`).withMessage(`Are you sure to close the app?`).withButtons(confirmButton, cancelButton)); if (popupResult.action == "confirm") { - invoke("close_app"); + invoke("utils_close_app"); } } private onTabFocused(id: string) { - let viewToFocus = this.views.find((view) => view.id! == id); - - if (viewToFocus) { - this.focusedView = viewToFocus; + let view = this.views.find((view) => view.id! == id); - viewToFocus.focus(); + if (view) { + this.focusedView = view; + view.focus(); this.views.forEach((view) => { if (view.id != id) { view.unfocus(); } }) - } else { - // TODO: Handle unknown view and close the tab } } - private onTabRequestClose(id: string) { + private async onTabRequestClose(id: string) { let view = this.views.find((view) => view.id == id); if (view) { - view.requestClosingAll() - } else { - this.toaster.toast("Orphaned tab", "It looks like this tab is orphaned.") + view.requestClosingAll().catch((err) => { + this.toaster.toast("Interaction error", err, "error"); + throw err; + }) } } @@ -96,22 +91,13 @@ export class ViewsManager { view.element!.remove(); this.views.splice(this.views.indexOf(view), 1); if (this.views.length == 0) { - invoke("close_window") + invoke("window_close") } } this.tabsManager.closeTab(uuid); } - private onTerminalReceiveData(e: Event) { - this.views.forEach((view) => { - let term = view.getTerm(e.payload.id); - if (term) { - term.term.write(e.payload.data); - } - }) - } - private onTerminalTitleChanged(e: Event) { this.views.forEach((view) => { if (view.getTerm(e.payload.id)) { @@ -121,7 +107,9 @@ export class ViewsManager { } private onTerminalPaneInput(id: string, data: string) { - invoke("terminal_input", {content: data, id: id}); + invoke("pty_write", {content: data, id: id}).catch((err) => { + this.toaster.toast("Interaction error", err, "error") + }); } private onTerminalProcessExited(e: Event) { @@ -146,11 +134,13 @@ export class ViewsManager { case "executeMacro": let macro = this.option.macros.find(macro => macro.uuid == action[1]); if (macro) { - invoke("terminal_input", {content: macro.content, id: this.focusedView!.focusedPane!.id}); + invoke("pty_write", {content: macro.content, id: this.focusedView!.focusedPane!.id}).catch((err) => { + this.toaster.toast("Macro error", err, "error"); + }); } break; default: - this.toaster.toast("Shortcut error", action[0] + " is not yet implemented"); + this.toaster.toast("Unknown shortcut", action[0] + " is not yet implemented"); } } else { switch (action) { @@ -160,7 +150,9 @@ export class ViewsManager { case "paste": let clipboardContent = await clipboard.readText(); if (clipboardContent) { - invoke("terminal_input", {content: clipboardContent, id: this.focusedView!.focusedPane!.id}); + invoke("pty_write", {content: clipboardContent, id: this.focusedView!.focusedPane!.id}).catch((err) => { + this.toaster.toast("Interaction error", err, "error"); + }); } break; case "openDefaultProfile": @@ -185,7 +177,7 @@ export class ViewsManager { this.closeViews() break; default: - this.toaster.toast("Shortcut error", action + " is not yet implemented"); + this.toaster.toast("Unknown shortcut", action + " is not yet implemented"); } } } @@ -203,7 +195,7 @@ export class ViewsManager { await view.closeAll() } - invoke("close_window"); + invoke("window_close"); } } } @@ -214,7 +206,7 @@ export class ViewsManager { let profile = this.option.profiles.find(profile => profile.uuid == profileId); if (profile) { - let view = new View(viewId, this.popupManager, (id) => { this.onViewsClosed(id); }, (title) => { this.tabsManager.setTitle(viewId, title); }) + let view = new View(viewId, this.popupManager, this.toaster, (id) => { this.onViewsClosed(id); }, (title) => { this.tabsManager.setTitle(viewId, title); }) view.openPane(paneId, profile, (e, term) => { return this.shortcutsManager.onKeyPress(e, term); }).then(() => { this.views.push(view); @@ -228,10 +220,10 @@ export class ViewsManager { if (focus) { this.tabsManager.select(viewId); } }).catch((err) => { - this.toaster.toast("Unable to create view", err); + this.toaster.toast("Unable to create a view", err, "error"); }) } else { - this.toaster.toast("Unable to create view", `An error occur while opening a view. Reason: no profile corresponding to id ${profileId}`); + this.toaster.toast("Unable to create a view", `There is no profile corresponding to ID: '${profileId}'`, "error"); } } @@ -240,10 +232,8 @@ export class ViewsManager { if (view) { view.requestClosingOne(paneId).catch((err) => { - this.toaster.toast("Unable to close a view's pane", err); + this.toaster.toast("Unable to close a view's pane", err, "error"); }) - } else { - // TODO: Handle error } } diff --git a/src/ts/class/panes.ts b/src/ts/class/panes.ts index 746421a..b3a880f 100644 --- a/src/ts/class/panes.ts +++ b/src/ts/class/panes.ts @@ -5,6 +5,7 @@ import { PopupManager } from 'ts/manager/popup'; import { PopupBuilder, PopupButton } from './popup'; import { Terminal as Xterm } from "xterm"; +import { Toaster } from 'ts/manager/toast'; export class TerminalPane { element: Element; @@ -46,8 +47,8 @@ export class TerminalPane { return element } - async initializeTerm(customKeyEventHanlder: ((e: KeyboardEvent, term: Xterm) => boolean)) { - let terminal = new Terminal(this.id, this.profile.terminalOptions, this.profile.theme, customKeyEventHanlder); + async initializeTerm(customKeyEventHanlder: ((e: KeyboardEvent, term: Xterm) => boolean), toaster: Toaster) { + let terminal = new Terminal(this.id, this.profile.terminalOptions, this.profile.theme, customKeyEventHanlder, toaster); await terminal.launch(this.element.querySelector(".internal-term")!, this.profile.uuid); @@ -55,33 +56,29 @@ export class TerminalPane { } async requestClosing(popupManager: PopupManager, viewElement: HTMLElement) : Promise { - if (!await invoke("check_close_availability", {id: this.id})) { + if (!await invoke("pty_get_closable", {id: this.id})) { let cancelButton = new PopupButton("cancel", "dismiss"); let confirmButton = new PopupButton("confirm", "validate"); - let closeAuthorized = (await popupManager.sendPopup(new PopupBuilder(`Confirm close of ${await invoke("get_pty_title", {id: this.id})}`).withMessage("Are you sure to close this tab?").withButtons(cancelButton, confirmButton), viewElement)).action == "confirm"; + let closeAuthorized = (await popupManager.sendPopup(new PopupBuilder(`Confirm close of ${await invoke("pty_get_title", {id: this.id})}`).withMessage("Are you sure to close this tab?").withButtons(cancelButton, confirmButton), viewElement)).action == "confirm"; if (closeAuthorized) { - await invoke("close_terminal", {id: this.id}); + await invoke("pty_close", {id: this.id}); this.term!.close(); } return closeAuthorized; } else { - await invoke("close_terminal", {id: this.id}); + await invoke("pty_close", {id: this.id}); this.term!.close(); return true; } } async forceClosing() { - await invoke("close_terminal", {id: this.id}); + await invoke("pty_close", {id: this.id}); this.term!.close(); } - write(data: string) { - this.term!.term.write(data); - } - unfocus() { this.term?.unfocus() } diff --git a/src/ts/class/terminal.ts b/src/ts/class/terminal.ts index 6685c99..7bdad22 100644 --- a/src/ts/class/terminal.ts +++ b/src/ts/class/terminal.ts @@ -3,18 +3,20 @@ import { CanvasAddon } from 'xterm-addon-canvas'; import { Terminal as Xterm } from "xterm"; import { invoke } from '@tauri-apps/api/tauri' import { TerminalOptions, TerminalTheme } from "ts/schema/option"; +import { Toaster } from "ts/manager/toast"; +import { terminalDataPayload } from "ts/schema/term"; +import { UnlistenFn, listen } from "@tauri-apps/api/event"; export class Terminal { id: string; term: Xterm; fitAddon: FitAddon; canvasResizeObserver: ResizeObserver | undefined; + toaster: Toaster; + unlisten: UnlistenFn | undefined = undefined; - constructor(id: string, options: TerminalOptions, theme: TerminalTheme, customKeyEventHandler: ((e: KeyboardEvent, term: Xterm) => boolean)) { - // TODO: Finish - // TODO: Load all addons - + constructor(id: string, options: TerminalOptions, theme: TerminalTheme, customKeyEventHandler: ((e: KeyboardEvent, term: Xterm) => boolean), toaster: Toaster) { theme = Object.assign({}, theme); theme.background = "rgba(0,0,0,0)"; @@ -39,13 +41,38 @@ export class Terminal { this.term.attachCustomKeyEventHandler((e) => { if (e.key == "F10") { - invoke("terminal_input", {content: "\x1b[21~", id: id}); + invoke("pty_write", {content: "\x1b[21~", id: id}); return false; } else { return customKeyEventHandler(e, this.term); } }) + + this.toaster = toaster; + + let bufferedBytes = 0; + let paused = false; + listen("js_pty_data", ((e) => { + if (e.payload.id == this.id) { + bufferedBytes += e.payload.data.length; + + this.term.write(e.payload.data, () => { + bufferedBytes = Math.max(bufferedBytes - e.payload.data.length, 0); + if (bufferedBytes < 16384 && paused) { + invoke("pty_resume", {id: id}); + paused = false; + } + }) + + if (bufferedBytes > 131072 && !paused) { + invoke("pty_pause", {id: id}); + paused = true; + } + } + })).then((unlisten) => { + this.unlisten = unlisten; + }) } async launch(target: HTMLElement, profile_id: string) { @@ -54,13 +81,15 @@ export class Terminal { if (proposedDimensions && !isNaN(proposedDimensions.cols) && !isNaN(proposedDimensions.rows)) { this.term.resize(proposedDimensions.cols + 1, proposedDimensions.rows + 1); - await invoke("resize_terminal", {cols: this.term.cols, rows: this.term.rows, id: this.id}); + invoke("pty_resize", {cols: this.term.cols, rows: this.term.rows, id: this.id}).catch((err) => { + this.toaster.toast("Terminal error", err, "error"); + }); } }); this.canvasResizeObserver.observe(target); - await invoke("create_terminal", {cols: this.term.cols, rows: this.term.rows, id: this.id, profileUuid: profile_id}); + await invoke("pty_open", {id: this.id, profileUuid: profile_id}); this.term.open(target); @@ -76,11 +105,17 @@ export class Terminal { if (proposedDimensions && !isNaN(proposedDimensions.cols) && !isNaN(proposedDimensions.rows)) { this.term.resize(proposedDimensions.cols + 1, proposedDimensions.rows + 1); - await invoke("resize_terminal", {cols: this.term.cols, rows: this.term.rows, id: this.id}); - - onRenderDisposable.dispose() + invoke("pty_resize", {cols: this.term.cols, rows: this.term.rows, id: this.id}).then(() => { + onRenderDisposable.dispose() + }).catch((err) => { + this.toaster.toast("Terminal error", err, "error"); + }) } }) + + setTimeout(() => { + this.term.clearTextureAtlas() + }, 50) } focus() { @@ -92,6 +127,7 @@ export class Terminal { } close() { + this.unlisten!(); this.canvasResizeObserver!.disconnect(); try { this.term.dispose() } catch (_) {} } diff --git a/src/ts/class/views.ts b/src/ts/class/views.ts index c900a54..2cfd085 100644 --- a/src/ts/class/views.ts +++ b/src/ts/class/views.ts @@ -4,6 +4,7 @@ import { Profile } from "ts/schema/option"; import { PopupManager } from "ts/manager/popup"; import { Terminal as Xterm } from "xterm"; +import { Toaster } from "ts/manager/toast"; export class View { // TODO: Implement pane page type @@ -22,13 +23,16 @@ export class View { closingAllRequested: boolean = false; + toaster: Toaster; - constructor (viewId: string, popupManager: PopupManager, closedEvent: ((id: string) => void), focusedPaneTitleChangedEvent: ((title: string) => void)) { + + constructor (viewId: string, popupManager: PopupManager, toaster: Toaster, closedEvent: ((id: string) => void), focusedPaneTitleChangedEvent: ((title: string) => void)) { this.id = viewId; this.element = this.generateComponents(); this.closedEvent = closedEvent; this.focusedPaneTitleChangedEvent = focusedPaneTitleChangedEvent; this.popupManager = popupManager; + this.toaster = toaster; } async openPane(paneId: string) : Promise; @@ -36,14 +40,12 @@ export class View { async openPane(paneId: string, profile?: Profile, customKeyEventHandler?: ((e: KeyboardEvent, term: Xterm) => boolean)) { if (profile) { let pane = new TerminalPane(paneId, profile); - await pane.initializeTerm(customKeyEventHandler!); + await pane.initializeTerm(customKeyEventHandler!, this.toaster); this.panes.push(pane) this.element!.appendChild(pane.element); this.focusedPane = pane; - } else { - console.log("not yet implemented") } } @@ -72,11 +74,13 @@ export class View { if (!this.closingAllRequested) { this.closingAllRequested = true; - for await (let pane of this.panes) { - await this.requestClosingOne(pane.id) + try { + for await (let pane of this.panes) { + await this.requestClosingOne(pane.id) + } + } finally { + this.closingAllRequested = false; } - - this.closingAllRequested = false; } } @@ -95,14 +99,6 @@ export class View { } } - writeToTerm(termId: string, data: string) { - let pane = this.panes.find((pane) => pane.id == termId) - - if (pane && pane instanceof TerminalPane) { - pane.write(data) - } - } - getTerm(id: string) : Terminal | undefined { return this.panes.find((pane) => pane.id == id && pane instanceof TerminalPane) ? (this.panes.find((pane) => pane.id == id && pane instanceof TerminalPane) as TerminalPane).term! : undefined } diff --git a/src/ts/main.ts b/src/ts/main.ts index a5766ed..b672357 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -1,4 +1,4 @@ -import { ViewsManager } from './manager/view'; +import { App } from './app'; import { invoke, convertFileSrc } from '@tauri-apps/api/tauri'; import { Option } from './schema/option'; @@ -7,8 +7,7 @@ window.addEventListener("contextmenu", (e) => { e.preventDefault(); }) - -invoke