diff --git a/Cargo.lock b/Cargo.lock index 14defc5..9a21a57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,7 +32,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -184,6 +184,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "boot-diags" +version = "0.1.0" +dependencies = [ + "anyhow", + "check_elevation", + "clap", + "color-eyre", + "core", + "crossterm", + "futures", + "ratatui", + "serde", + "tokio", + "toml", + "tracing", + "tracing-error", + "tracing-subscriber", + "vergen-gix", +] + [[package]] name = "bstr" version = "1.11.3" @@ -270,10 +291,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "clap" -version = "4.5.26" +name = "check_elevation" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "34f7310b71e7b968cdadd13480b9e4f2def9f173f67fd7317e8eddb8d7a4ba00" +dependencies = [ + "windows", +] + +[[package]] +name = "clap" +version = "4.5.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -281,9 +311,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -412,7 +442,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -445,6 +475,7 @@ dependencies = [ "libc", "once_cell", "pretty_assertions", + "rand", "ratatui", "raw-cpuid", "regex", @@ -465,9 +496,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -510,9 +541,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -564,10 +595,10 @@ name = "deja-vu" version = "0.2.0" dependencies = [ "anyhow", + "check_elevation", "clap", "color-eyre", "core", - "rand", "ratatui", "serde", "serde_json", @@ -905,7 +936,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] @@ -1700,9 +1743,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1750,9 +1793,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.24" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bb0c2e28117985a4d90e3bc70092bc8f226f434c7ec7e23dd9ff99c5c5721a" +checksum = "c607c728e28764fecde611a2764a3a5db19ae21dcec46f292244f5cc5c085a81" dependencies = [ "jiff-tzdb-platform", "log", @@ -1909,7 +1952,7 @@ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -2012,7 +2055,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2137,7 +2180,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2180,20 +2223,20 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ - "libc", "rand_chacha", "rand_core", + "zerocopy 0.8.14", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -2201,11 +2244,12 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" dependencies = [ - "getrandom", + "getrandom 0.3.1", + "zerocopy 0.8.14", ] [[package]] @@ -2254,7 +2298,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror", ] @@ -2334,9 +2378,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", @@ -2353,9 +2397,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -2403,9 +2447,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -2599,13 +2643,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", - "getrandom", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2898,9 +2942,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" @@ -2971,11 +3015,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -3056,6 +3100,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3087,13 +3140,32 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3102,7 +3174,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -3111,28 +3198,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3145,24 +3250,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3171,13 +3300,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.24" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "write16" version = "1.0.0" @@ -3238,7 +3376,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +dependencies = [ + "zerocopy-derive 0.8.14", ] [[package]] @@ -3252,6 +3399,17 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 39fe348..aef6a91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,19 @@ -# This file is part of Deja-vu. +# This file is part of Deja-Vu. # -# Deja-vu is free software: you can redistribute it and/or modify it +# Deja-Vu is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# Deja-vu is distributed in the hope that it will be useful, but +# Deja-Vu is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with Deja-vu. If not, see . +# along with Deja-Vu. If not, see . [workspace] -members = ["core", "deja_vu", "pe_menu"] -default-members = ["deja_vu", "pe_menu"] +members = ["core", "boot_diags", "deja_vu", "pe_menu"] +default-members = ["boot_diags", "deja_vu", "pe_menu"] resolver = "2" diff --git a/boot_diags/Cargo.toml b/boot_diags/Cargo.toml new file mode 100644 index 0000000..4cd14e3 --- /dev/null +++ b/boot_diags/Cargo.toml @@ -0,0 +1,47 @@ +# This file is part of Deja-Vu. +# +# Deja-Vu is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Deja-Vu is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Deja-Vu. If not, see . + +[package] +name = "boot-diags" +authors = ["2Shirt <2xShirt@gmail.com>"] +edition = "2021" +license = "GPL" +version = "0.1.0" + +[dependencies] +core = { path = "../core" } +clap = { version = "4.4.5", features = [ + "derive", + "cargo", + "wrap_help", + "unicode", + "string", + "unstable-styles", + ] } +color-eyre = "0.6.3" +crossterm = { version = "0.28.1", features = ["event-stream"] } +futures = "0.3.30" +ratatui = "0.29.0" +serde = { version = "1.0.217", features = ["derive"] } +tokio = { version = "1.43.0", features = ["full"] } +toml = "0.8.13" +tracing = "0.1.41" +tracing-error = "0.2.0" +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] } +check_elevation = "0.2.4" + +[build-dependencies] +anyhow = "1.0.86" +vergen-gix = { version = "1.0.0", features = ["build", "cargo"] } diff --git a/boot_diags/build.rs b/boot_diags/build.rs new file mode 100644 index 0000000..8988d39 --- /dev/null +++ b/boot_diags/build.rs @@ -0,0 +1,28 @@ +// This file is part of Deja-Vu. +// +// Deja-Vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-Vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-Vu. If not, see . +// +use anyhow::Result; +use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, GixBuilder}; + +fn main() -> Result<()> { + let build = BuildBuilder::all_build()?; + let gix = GixBuilder::all_git()?; + let cargo = CargoBuilder::all_cargo()?; + Emitter::default() + .add_instructions(&build)? + .add_instructions(&gix)? + .add_instructions(&cargo)? + .emit() +} diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs new file mode 100644 index 0000000..998c62e --- /dev/null +++ b/boot_diags/src/app.rs @@ -0,0 +1,1045 @@ +// This file is part of Deja-Vu. +// +// Deja-Vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-Vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-Vu. If not, see . +// +use crate::diags; +use core::{ + action::Action, + components::{ + footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, state::StatefulList, + title::Title, Component, + }, + config::Config, + line::{get_disk_description_right, get_part_description, DVLine}, + state::{CloneSettings, Mode}, + system::{ + boot::{self, SafeMode}, + cpu::get_cpu_name, + disk::PartitionTableType, + drivers, + }, + tasks::{Task, TaskResult, TaskType, Tasks}, + tui::{Event, Tui}, +}; +use std::{ + env, + iter::zip, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use color_eyre::Result; +use ratatui::{ + crossterm::event::KeyEvent, + layout::{Constraint, Direction, Layout}, + prelude::Rect, + style::Color, +}; +use tokio::sync::mpsc; +use tracing::{debug, info, warn}; + +pub struct App { + // TUI + action_rx: mpsc::UnboundedReceiver, + action_tx: mpsc::UnboundedSender, + components: Vec>, + config: Config, + diag_groups: diags::Groups, + frame_rate: f64, + last_tick_key_events: Vec, + should_quit: bool, + should_suspend: bool, + tick_rate: f64, + // App + clone: CloneSettings, + cur_mode: Mode, + list: StatefulList, + boot_modes: Vec, + selections: Vec>, + system32: String, + tasks: Tasks, +} + +impl App { + pub fn new(tick_rate: f64, frame_rate: f64) -> Result { + let (action_tx, action_rx) = mpsc::unbounded_channel(); + let disk_list_arc = Arc::new(Mutex::new(Vec::new())); + let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); + let mut list = StatefulList::default(); + list.set_items(vec![ + Mode::BootScan, + Mode::BootSetup, + Mode::InjectDrivers, + Mode::SetBootMode, + ]); + Ok(Self { + // TUI + action_rx, + action_tx, + components: vec![ + Box::new(Title::new("Boot Diagnostics")), + Box::new(FpsCounter::new()), + Box::new(Left::new()), + Box::new(Right::new()), + Box::new(Footer::new()), + Box::new(popup::Popup::new()), + ], + config: Config::new()?, + diag_groups: diags::Groups::new(), + frame_rate, + last_tick_key_events: Vec::new(), + should_quit: false, + should_suspend: false, + tick_rate, + // App + clone: CloneSettings::new(disk_list_arc), + cur_mode: Mode::Home, + list, + boot_modes: vec![SafeMode::Enable, SafeMode::Disable], + system32: String::new(), + selections: vec![None, None], + tasks, + }) + } + + pub fn inject_driver(&mut self, index: usize) { + if let Some(driver) = self.clone.driver_list.get(index) { + if let Some(disk_index) = self.clone.disk_index_dest { + let disk_list = self.clone.disk_list.lock().unwrap(); + if let Some(disk) = disk_list.get(disk_index) { + if let Some(boot_index) = self.clone.part_index_boot { + if let Ok(task) = boot::inject_driver( + driver, + disk.get_part_letter(boot_index).as_str(), + &self.system32, + ) { + self.tasks.add(task); + } + } + } + } + } + } + + pub fn next_mode(&mut self) -> Mode { + match self.cur_mode { + Mode::Home => Mode::ScanDisks, + Mode::InstallDrivers => Mode::ScanDisks, + Mode::ScanDisks => Mode::SelectDisks, + Mode::SelectDisks => Mode::SelectParts, + Mode::SelectParts => Mode::DiagMenu, + Mode::BootDiags | Mode::BootSetup => Mode::DiagMenu, + Mode::BootScan => Mode::BootDiags, + Mode::InjectDrivers | Mode::SetBootMode => Mode::DiagMenu, + Mode::Done => Mode::DiagMenu, + Mode::Failed => Mode::Failed, + // Default to current mode + _ => self.cur_mode, + } + } + + pub fn set_boot_mode(&mut self, boot_mode: SafeMode) { + let new_mode = match boot_mode { + SafeMode::Disable => "Normal", + SafeMode::Enable => "Safe Mode (minimal)", + }; + info!("Setting boot mode to: {new_mode}"); + let disk_list = self.clone.disk_list.lock().unwrap(); + if let Some(disk_index) = self.clone.disk_index_dest { + if let Some(disk) = disk_list.get(disk_index) { + if let Some(boot_index) = self.clone.part_index_boot { + if let Ok(task) = boot::set_mode( + disk.get_part_letter(boot_index).as_str(), + boot_mode, + &self.system32, + &disk.part_type, + ) { + self.tasks.add(task); + }; + } + } + } + } + + pub fn set_mode(&mut self, new_mode: Mode) -> Result<()> { + info!("Setting mode to {new_mode:?}"); + self.cur_mode = new_mode; + match new_mode { + Mode::DiagMenu => { + self.selections[0] = None; + self.selections[1] = None; + self.list.select_first_item(); + } + Mode::BootScan => { + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Gathering info..."), + ))?; + + // Get System32 path + let system32 = if cfg!(windows) { + if let Ok(path) = env::var("SYSTEMROOT") { + format!("{path}\\System32") + } else { + self.action_tx.send(Action::Error(String::from( + "ERROR\n\n\nFailed to find SYSTEMROOT", + )))?; + return Ok(()); + } + } else { + String::from(".") + }; + + // Add tasks + let disk_list = self.clone.disk_list.lock().unwrap(); + if let Some(disk_index) = self.clone.disk_index_dest { + if let Some(disk) = disk_list.get(disk_index) { + let table_type = disk.part_type.clone(); + let letter_boot = disk.get_part_letter(self.clone.part_index_boot.unwrap()); + let letter_os = disk.get_part_letter(self.clone.part_index_os.unwrap()); + + // Safety check + if letter_boot.is_empty() || letter_os.is_empty() { + self.action_tx.send(Action::Error(String::from( + "ERROR\n\n\nFailed to get drive letters for the destination", + )))?; + return Ok(()); + } + + // BCD + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\bcdedit.exe")), + vec![ + String::from("/store"), + format!( + "{letter_boot}:{}\\Boot\\BCD", + if table_type == PartitionTableType::Guid { + "\\EFI\\Microsoft" + } else { + "" + } + ), + String::from("/enum"), + ], + )); + + // Bitlocker + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\manage-bde.exe")), + vec![String::from("-status"), format!("{letter_os}:")], + )); + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\manage-bde.exe")), + vec![ + String::from("-protectors"), + String::from("-get"), + format!("{letter_os}:"), + ], + )); + + // DISM Health + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\dism.exe")), + vec![format!("{letter_os}:")], + )); + + // Filesystem Health + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\chkdsk.exe")), + vec![format!("{letter_os}:")], + )); + + // Registry + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\reg.exe")), + vec![ + String::from("load"), + String::from("HKLM\\TmpSoftware"), + format!("{letter_os}:\\Windows\\System32\\config\\SOFTWARE"), + ], + )); + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\reg.exe")), + vec![ + String::from("load"), + String::from("HKLM\\TmpSystem"), + format!("{letter_os}:\\Windows\\System32\\config\\SYSTEM"), + ], + )); + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\reg.exe")), + vec![ + String::from("load"), + String::from("HKU\\TmpDefault"), + format!("{letter_os}:\\Windows\\System32\\config\\DEFAULT"), + ], + )); + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\reg.exe")), + vec![String::from("unload"), String::from("HKLM\\TmpSoftware")], + )); + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\reg.exe")), + vec![String::from("unload"), String::from("HKLM\\TmpSystem")], + )); + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}\\reg.exe")), + vec![String::from("unload"), String::from("HKU\\TmpDefault")], + )); + + // Files/Folders + // TODO: Check for critical folders (e.g. /Windows, /Windows/System32, etc) + } + } + } + Mode::InjectDrivers | Mode::InstallDrivers => self.clone.scan_drivers(), + Mode::ScanDisks => { + if self.tasks.idle() { + self.tasks.add(TaskType::ScanDisks); + } + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Scanning Disks..."), + ))?; + } + _ => {} + } + Ok(()) + } + + pub async fn run(&mut self) -> Result<()> { + let mut tui = Tui::new()? + // .mouse(true) // uncomment this line to enable mouse support + .tick_rate(self.tick_rate) + .frame_rate(self.frame_rate); + tui.enter()?; + + for component in &mut self.components { + component.register_action_handler(self.action_tx.clone())?; + } + for component in &mut self.components { + component.register_config_handler(self.config.clone())?; + } + for component in &mut self.components { + component.init(tui.size()?)?; + } + + let action_tx = self.action_tx.clone(); + + // Late init + self.system32 = if cfg!(windows) { + if let Ok(path) = env::var("SYSTEMROOT") { + format!("{path}/System32") + } else { + self.action_tx.send(Action::Error(String::from( + "ERROR\n\n\nFailed to find SYSTEMROOT", + )))?; + return Ok(()); + } + } else { + String::from(".") + }; + action_tx.send(Action::SetMode(Mode::ScanDisks))?; + + loop { + self.handle_events(&mut tui).await?; + self.handle_actions(&mut tui)?; + if self.should_suspend { + tui.suspend()?; + action_tx.send(Action::Resume)?; + action_tx.send(Action::ClearScreen)?; + // tui.mouse(true); + tui.enter()?; + } else if self.should_quit { + tui.stop()?; + break; + } + } + tui.exit()?; + Ok(()) + } + + async fn handle_events(&mut self, tui: &mut Tui) -> Result<()> { + let Some(event) = tui.next_event().await else { + return Ok(()); + }; + let action_tx = self.action_tx.clone(); + match event { + Event::Quit => action_tx.send(Action::Quit)?, + Event::Tick => action_tx.send(Action::Tick)?, + Event::Render => action_tx.send(Action::Render)?, + Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?, + Event::Key(key) => self.handle_key_event(key)?, + _ => {} + } + for component in &mut self.components { + if let Some(action) = component.handle_events(Some(event.clone()))? { + action_tx.send(action)?; + } + } + Ok(()) + } + + fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> { + let action_tx = self.action_tx.clone(); + let Some(keymap) = self.config.keybindings.get(&self.cur_mode) else { + return Ok(()); + }; + if let Some(action) = keymap.get(&vec![key]) { + info!("Got action: {action:?}"); + action_tx.send(action.clone())?; + } else { + // If the key was not handled as a single key action, + // then consider it for multi-key combinations. + self.last_tick_key_events.push(key); + + // Check for multi-key combinations + if let Some(action) = keymap.get(&self.last_tick_key_events) { + info!("Got action: {action:?}"); + action_tx.send(action.clone())?; + } + } + Ok(()) + } + + fn handle_actions(&mut self, tui: &mut Tui) -> Result<()> { + while let Ok(action) = self.action_rx.try_recv() { + if action != Action::Tick && action != Action::Render { + debug!("{action:?}"); + } + match action { + Action::Tick => { + self.last_tick_key_events.drain(..); + // Check background task(s) + if let Some(task) = self.tasks.poll()? { + self.handle_task(&task)?; + } + } + Action::Quit => self.should_quit = true, + Action::Suspend => self.should_suspend = true, + Action::Resume => self.should_suspend = false, + Action::ClearScreen => tui.terminal.clear()?, + Action::KeyUp => self.list.previous(), + Action::KeyDown => self.list.next(), + Action::Error(ref msg) => { + self.action_tx + .send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?; + self.action_tx.send(Action::SetMode(Mode::Failed))?; + } + Action::BootScan => self.action_tx.send(Action::SetMode(Mode::BootScan))?, + Action::InstallDriver => { + self.action_tx.send(Action::SetMode(Mode::InstallDrivers))?; + } + Action::NextScreen => { + let next_mode = self.next_mode(); + self.cur_mode = next_mode; + self.action_tx.send(Action::DismissPopup)?; + self.action_tx.send(Action::SetMode(next_mode))?; + } + Action::Process => match self.cur_mode { + Mode::DiagMenu => { + // Use highlighted entry + if let Some(new_mode) = self.list.get_selected() { + self.action_tx.send(Action::SetMode(new_mode))?; + } + } + Mode::Done => { + self.action_tx.send(Action::NextScreen)?; + } + Mode::BootDiags | Mode::BootSetup => { + let new_mode = self.next_mode(); + self.action_tx.send(Action::SetMode(new_mode))?; + } + _ => {} + }, + Action::Resize(w, h) => self.handle_resize(tui, w, h)?, + Action::Render => self.render(tui)?, + Action::ScanDisks => self.action_tx.send(Action::SetMode(Mode::ScanDisks))?, + Action::Select(one, two) => match self.cur_mode { + Mode::InjectDrivers => { + if let Some(index) = one { + self.inject_driver(index); + } + } + Mode::InstallDrivers => { + if let Some(index) = one { + if let Some(driver) = self.clone.driver_list.get(index).cloned() { + drivers::load(&driver.inf_paths); + self.clone.driver = Some(driver); + } + } + } + Mode::SelectDisks => { + self.clone.disk_index_dest = one; + } + Mode::SelectParts => { + self.clone.part_index_boot = one; + self.clone.part_index_os = two; + } + Mode::SetBootMode => { + if let Some(index) = one { + if let Some(boot_mode) = self.boot_modes.get(index) { + self.set_boot_mode(boot_mode.to_owned()); + } + } + } + _ => {} + }, + Action::SetMode(new_mode) => { + self.set_mode(new_mode)?; + self.action_tx + .send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?; + self.action_tx.send(build_left_items(self))?; + self.action_tx.send(build_right_items(self))?; + self.action_tx.send(Action::Select(None, None))?; + } + Action::TasksComplete => { + if self.cur_mode == Mode::BootDiags { + self.action_tx.send(Action::DismissPopup)?; + } else { + self.action_tx.send(Action::NextScreen)?; + } + } + _ => {} + } + for component in &mut self.components { + if let Some(action) = component.update(action.clone())? { + self.action_tx.send(action)?; + }; + } + } + Ok(()) + } + + fn handle_resize(&mut self, tui: &mut Tui, w: u16, h: u16) -> Result<()> { + tui.resize(Rect::new(0, 0, w, h))?; + self.render(tui)?; + Ok(()) + } + + fn handle_task(&mut self, task: &Task) -> Result<()> { + info!("Handling Task: {task:?}"); + match self.cur_mode { + Mode::BootScan => { + if let TaskType::CommandWait(cmd_path, _cmd_args) = &task.task_type { + let mut cmd_name = ""; + if let Some(path) = cmd_path.file_name() { + if let Some(cmd_str) = path.to_str() { + cmd_name = cmd_str; + } + }; + let diag_title = match diags::get_type(cmd_name) { + diags::Type::BootConfigData => Some("Boot Files"), + diags::Type::Bitlocker => Some("Bitlocker"), + diags::Type::FileSystem => { + if let Some(result) = &task.result { + let passed: bool; + let info: String; + match result { + TaskResult::Error(msg) => { + passed = false; + info = msg.to_owned(); + } + TaskResult::Output(stdout, _stderr, success) => { + passed = *success; + info = parse_chkdsk(stdout); + } + } + self.diag_groups.update( + String::from("Filesystem"), + passed, + info.to_owned(), + ); + } + None // Don't set title since we handle the logic here + } + diags::Type::Registry => Some("Registry"), + diags::Type::FilesAndFolders => Some("Critical Files"), + _ => { + warn!("Unrecognized command: {:?}", &cmd_path); + None + } + }; + if let Some(title) = diag_title { + // Just use command output + if let Some(result) = &task.result { + let passed: bool; + let info: String; + match result { + TaskResult::Error(msg) => { + passed = false; + info = msg.to_owned(); + } + TaskResult::Output(stdout, stderr, success) => { + passed = *success; + let div = if !(stdout.is_empty() || stderr.is_empty()) { + "\n\n-----------\n\n" + } else { + "" + }; + info = format!("{stdout}{div}{stderr}"); + } + } + self.diag_groups + .update(title.to_string(), passed, info.to_owned()); + } + } + } + } + _ => { + match task.task_type { + TaskType::CommandNoWait(_, _) + | TaskType::CommandWait(_, _) + | TaskType::Diskpart(_) => { + // Check result + if let Some(result) = &task.result { + match result { + TaskResult::Error(msg) => { + self.action_tx + .send(Action::Error(format!("{task:?} Failed: {msg}")))?; + } + TaskResult::Output(stdout, stderr, success) => { + if !success { + let msg = if !stdout.is_empty() { + stdout.clone() + } else if !stderr.is_empty() { + stderr.clone() + } else { + String::from("Unknown Error") + }; + self.action_tx.send(Action::Error(format!( + "{task:?} Failed: {msg}" + )))?; + } + } + } + } + } + _ => {} + } + } + } + Ok(()) + } + + fn render(&mut self, tui: &mut Tui) -> Result<()> { + tui.draw(|frame| { + if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] { + let component_areas = vec![ + header, // Title Bar + header, // FPS Counter + left, right, footer, popup, + ]; + for (component, area) in zip(self.components.iter_mut(), component_areas) { + if let Err(err) = component.draw(frame, area) { + let _ = self + .action_tx + .send(Action::Error(format!("Failed to draw: {err:?}"))); + } + } + }; + })?; + Ok(()) + } +} + +fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { + // Cut the given rectangle into three vertical pieces + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ]) + .split(r); + + // Then cut the middle vertical piece into three width-wise pieces + Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ]) + .split(popup_layout[1])[1] // Return the middle chunk +} + +fn get_chunks(r: Rect) -> Vec { + let mut chunks: Vec = Vec::with_capacity(6); + + // Main sections + chunks.extend( + Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Min(1), + Constraint::Length(3), + ]) + .split(r) + .to_vec(), + ); + + // Left/Right + chunks.extend( + Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(centered_rect(90, 90, chunks[1])) + .to_vec(), + ); + + // Popup + chunks.push(centered_rect(60, 25, r)); + + // Done + chunks +} + +fn build_footer_string(cur_mode: Mode) -> String { + match cur_mode { + Mode::BootDiags => String::from("(r) to refresh / (q) to quit"), + Mode::BootScan | Mode::BootSetup | Mode::Home | Mode::ScanDisks => { + String::from("(q) to quit") + } + Mode::InstallDrivers | Mode::InjectDrivers | Mode::SetBootMode => { + String::from("(Enter) to select / (q) to quit") + } + Mode::DiagMenu | Mode::SelectParts => { + String::from("(Enter) to select / (s) to start over / (q) to quit") + } + Mode::Done => String::from("(Enter) to continue / (q) to quit"), + Mode::SelectDisks => String::from( + "(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit", + ), + Mode::Failed => String::from("(Enter) or (q) to quit"), + // Invalid states + Mode::Confirm + | Mode::Clone + | Mode::PEMenu + | Mode::PreClone + | Mode::PostClone + | Mode::SelectTableType => { + panic!("This shouldn't happen?") + } + } +} + +fn build_left_items(app: &App) -> Action { + let mut items = Vec::new(); + let mut labels = vec![String::new(), String::new()]; + let select_num: usize; + let title: String; + match app.cur_mode { + Mode::Home => { + select_num = 0; + title = String::from("Home"); + } + Mode::DiagMenu => { + select_num = 0; + title = String::from("Troubleshooting"); + app.list.items.iter().for_each(|mode| { + let (name, _) = get_mode_strings(*mode); + items.push(name); + }); + } + Mode::InstallDrivers => { + select_num = 1; + title = String::from("Install Drivers"); + app.clone + .driver_list + .iter() + .for_each(|driver| items.push(driver.to_string())); + } + Mode::InjectDrivers => { + select_num = 1; + title = String::from("Select Drivers"); + app.clone + .driver_list + .iter() + .for_each(|driver| items.push(driver.to_string())); + } + Mode::SelectDisks => { + select_num = 1; + title = String::from("Select Disk"); + let disk_list = app.clone.disk_list.lock().unwrap(); + disk_list + .iter() + .for_each(|disk| items.push(disk.description.to_string())); + } + Mode::BootScan | Mode::ScanDisks => { + select_num = 0; + title = String::from("Processing"); + } + Mode::SelectParts => { + select_num = 2; + title = String::from("Select Boot and OS Partitions"); + labels[0] = String::from("boot"); + labels[1] = String::from("os"); + let disk_list = app.clone.disk_list.lock().unwrap(); + if let Some(index) = app.clone.disk_index_dest { + if let Some(disk) = disk_list.get(index) { + disk.get_parts().iter().for_each(|part| { + items.push(part.to_string()); + }); + } + } + } + Mode::BootDiags => { + select_num = 0; + let (new_title, _) = get_mode_strings(app.cur_mode); + title = new_title; + app.diag_groups.get().iter().for_each(|group| { + items.push(if group.passed { + group.title.clone() + } else { + format!("{} - Issues detected!", group.title) + }); + }); + } + Mode::BootSetup => { + select_num = 0; + let (new_title, _) = get_mode_strings(app.cur_mode); + title = new_title; + } + Mode::SetBootMode => { + select_num = 1; + title = String::from("Set Boot Mode"); + app.boot_modes.iter().for_each(|entry| { + items.push(format!("{:?} Safe Mode", entry)); + }); + } + Mode::Done | Mode::Failed => { + select_num = 0; + title = String::from("Done"); + } + // Invalid states + Mode::SelectTableType + | Mode::PEMenu + | Mode::Confirm + | Mode::PreClone + | Mode::Clone + | Mode::PostClone => { + panic!("This shouldn't happen?") + } + }; + Action::UpdateLeft(title, labels, items, select_num) +} + +fn build_right_items(app: &App) -> Action { + let mut items: Vec> = Vec::new(); + let mut labels: Vec> = Vec::new(); + let mut start_index = 0; + // TODO: DELETE THIS SECTION + start_index = 1; + items.push(vec![ + DVLine { + line_parts: vec![format!("Mode: {:?}", app.cur_mode)], + line_colors: vec![Color::Reset], + }, + DVLine { + line_parts: vec![format!( + "Parts: {:?} // {:?}", + app.clone.part_index_boot, app.clone.part_index_os + )], + line_colors: vec![Color::Reset], + }, + DVLine { + line_parts: vec![format!( + "Selected: {:?} // {:?}", + app.list.selected(), + app.list.get_selected(), + )], + line_colors: vec![Color::Reset], + }, + DVLine { + line_parts: vec![String::from("-----")], + line_colors: vec![Color::Reset], + }, + ]); + // TODO: DELETE THIS SECTION + match app.cur_mode { + Mode::DiagMenu => { + let mut header_lines: Vec = Vec::new(); + if let Some(index) = app.clone.disk_index_dest { + let disk_list = app.clone.disk_list.lock().unwrap(); + if let Some(disk) = disk_list.get(index) { + let mut parts: Vec = Vec::new(); + if let Some(index) = app.clone.part_index_boot { + parts.push(index); + } + if let Some(index) = app.clone.part_index_os { + parts.push(index); + } + // Disk Details + header_lines.append(&mut vec![ + DVLine { + line_parts: vec![String::from("Disk")], + line_colors: vec![Color::Cyan], + }, + DVLine { + line_parts: vec![String::new()], + line_colors: vec![Color::Reset], + }, + ]); + header_lines.append(&mut get_disk_description_right(&disk, Some(parts))); + + // Add header + if !header_lines.is_empty() { + items[0].append(&mut header_lines); + // TODO: Replace line above with lines below + // items.push(header_lines); + // start_index = 1; + } + } + } + app.list.items.iter().for_each(|mode| { + let (name, description) = get_mode_strings(*mode); + items.push(vec![ + DVLine { + line_parts: vec![name], + line_colors: vec![Color::Cyan], + }, + DVLine { + line_parts: vec![String::new()], + line_colors: vec![Color::Reset], + }, + DVLine { + line_parts: vec![description], + line_colors: vec![Color::Reset], + }, + ]); + }); + } + Mode::BootDiags => { + app.diag_groups.get().iter().for_each(|group| { + let mut lines = Vec::new(); + group.info.iter().for_each(|text| { + text.lines().for_each(|line| { + lines.push(DVLine { + line_parts: vec![String::from(line)], + line_colors: vec![Color::Reset], + }); + }); + }); + items.push(lines); + }); + } + Mode::InjectDrivers | Mode::InstallDrivers => { + items.push(vec![DVLine { + line_parts: vec![String::from("CPU")], + line_colors: vec![Color::Cyan], + }]); + items.push(vec![DVLine { + line_parts: vec![get_cpu_name()], + line_colors: vec![Color::Reset], + }]); + start_index = 2; + } + Mode::SelectDisks => { + let dest_dv_line = DVLine { + line_parts: vec![String::from("Disk")], + line_colors: vec![Color::Cyan], + }; + labels.push(vec![dest_dv_line]); + let disk_list = app.clone.disk_list.lock().unwrap(); + disk_list + .iter() + .for_each(|disk| items.push(get_disk_description_right(&disk, None))); + } + Mode::SelectParts => { + vec!["Boot", "OS"].iter().for_each(|s| { + labels.push(vec![DVLine { + line_parts: vec![String::from(*s)], + line_colors: vec![Color::Cyan], + }]) + }); + if let Some(index) = app.clone.disk_index_dest { + start_index = 1; + let disk_list = app.clone.disk_list.lock().unwrap(); + if let Some(disk) = disk_list.get(index) { + // Disk Details + items.push(get_disk_description_right(&disk, None)); + + // Partition Details + disk.parts + .iter() + .for_each(|part| items.push(get_part_description(&part))); + } + } + } + Mode::SetBootMode => { + app.boot_modes.iter().for_each(|mode| { + match mode { + SafeMode::Disable => { + items.push(vec![DVLine { + line_parts: vec![String::from("Disable Safe Mode")], + line_colors: vec![Color::Reset], + }]); + } + SafeMode::Enable => { + items.push(vec![DVLine { + line_parts: vec![String::from("Enable Safe Mode (minimal)")], + line_colors: vec![Color::Reset], + }]); + } + }; + }); + } + _ => {} + } + Action::UpdateRight(labels, start_index, items) +} + +fn get_mode_strings(mode: Mode) -> (String, String) { + match mode { + Mode::BootScan | Mode::BootDiags => ( + String::from("Boot Diagnostics"), + String::from("Check for common Windows boot issues"), + ), + Mode::BootSetup => ( + String::from("Boot Setup"), + String::from("Create or recreate boot files"), + ), + Mode::InjectDrivers => ( + String::from("Inject Drivers"), + String::from("Inject drivers into existing Windows environment"), + ), + Mode::SetBootMode => ( + String::from("Toggle Safe Mode"), + String::from("Enable or disable safe mode"), + ), + _ => panic!("This shouldn't happen"), + } +} + +fn parse_chkdsk(output: &str) -> String { + // Split lines + let lines: Vec<_> = output.split("\r\n").collect(); + + // Omit progress lines and unhelpful messages + lines + .into_iter() + .filter(|line| { + !(line.contains("\r") + || line.contains("Class not registered") + || line.contains("/F parameter") + || line.contains("Running CHKDSK") + || line.contains("Total duration:") + || line.contains("Failed to transfer logged messages")) + }) + .collect::>() + .join("\n") +} diff --git a/boot_diags/src/diags.rs b/boot_diags/src/diags.rs new file mode 100644 index 0000000..05c5dbb --- /dev/null +++ b/boot_diags/src/diags.rs @@ -0,0 +1,102 @@ +// This file is part of Deja-Vu. +// +// Deja-Vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-Vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-Vu. If not, see . + +use std::collections::HashMap; + +pub enum Type { + Bitlocker, + BootConfigData, + FileSystem, + FilesAndFolders, + Registry, + Unknown, +} + +#[derive(Debug)] +pub struct Groups { + items: HashMap, + order: Vec, +} + +impl Groups { + pub fn new() -> Self { + Groups { + items: HashMap::new(), + order: Vec::new(), + } + } + + pub fn get(&self) -> Vec<&Line> { + let mut lines = Vec::new(); + self.order.iter().for_each(|key| { + if let Some(line) = self.items.get(key) { + lines.push(line); + } + }); + lines + } + + pub fn update(&mut self, title: String, passed: bool, info: String) { + if let Some(line) = self.items.get_mut(&title) { + line.update(passed, info); + } else { + self.order.push(title.clone()); + self.items.insert( + title.clone(), + Line { + title, + passed, + info: vec![info], + }, + ); + } + } +} + +#[derive(Clone, Debug)] +pub struct Line { + pub title: String, + pub passed: bool, + pub info: Vec, +} + +impl Line { + pub fn update(&mut self, passed: bool, info: String) { + self.passed &= passed; // We fail if any tests in this group fail + self.info.push(info); + } +} + +pub fn get_type(cmd_name: &str) -> Type { + if cmd_name == "exa" { + return Type::BootConfigData; + } + if cmd_name == "bcdedit.exe" { + return Type::BootConfigData; + } + if cmd_name == "dir" { + return Type::FilesAndFolders; + } + if cmd_name == "reg.exe" { + return Type::Registry; + } + if cmd_name == "chkdsk.exe" { + return Type::FileSystem; + } + if cmd_name == "manage-bde.exe" { + return Type::Bitlocker; + } + Type::Unknown +} diff --git a/boot_diags/src/main.rs b/boot_diags/src/main.rs new file mode 100644 index 0000000..7128445 --- /dev/null +++ b/boot_diags/src/main.rs @@ -0,0 +1,48 @@ +// This file is part of Deja-Vu. +// +// Deja-Vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-Vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-Vu. If not, see . +// +use clap::Parser; +use color_eyre::Result; +use core; + +use crate::app::App; + +mod app; +mod diags; + +#[tokio::main] +async fn main() -> Result<()> { + let mut msg = None; + if cfg!(windows) { + use check_elevation::is_elevated; + if !is_elevated().expect("Failed to get elevation status.") { + msg.replace("Administrator privedges required for Deja-Vu."); + } + }; + match msg { + Some(text) => { + println!("{text}"); + } + None => { + core::errors::init()?; + core::logging::init()?; + + let args = core::cli::Cli::parse(); + let mut app = App::new(args.tick_rate, args.frame_rate)?; + app.run().await?; + } + } + Ok(()) +} diff --git a/config/config.json5 b/config/config.json5 index 986126d..802c914 100644 --- a/config/config.json5 +++ b/config/config.json5 @@ -1,5 +1,5 @@ { - "app_title": "Deja-vu", + "app_title": "Deja-Vu", "clone_app_path": "C:/Program Files/Some Clone Tool/app.exe", "conemu_path": "C:/Program Files/ConEmu/ConEmu64.exe", "keybindings": { @@ -82,7 +82,7 @@ "": "Suspend" }, "Done": { - "": "Quit", + "": "Process", "": "Quit", "": "Quit", "": "Quit", @@ -95,11 +95,66 @@ "": "Quit", "": "Suspend" }, + "DiagMenu": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "ScanDisks", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "BootDiags": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "BootScan", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "BootScan": { + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "BootSetup": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "InjectDrivers": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "SetBootMode": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, "PEMenu": { "": "Process", "": "KeyUp", "": "KeyDown", "": "Quit", + "": "Restart", + "

": "Shutdown", "": "OpenTerminal", "": "Quit", "": "Quit", diff --git a/core/Cargo.toml b/core/Cargo.toml index e724ecf..8d99e89 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,17 +1,17 @@ -# This file is part of Deja-vu. +# This file is part of Deja-Vu. # -# Deja-vu is free software: you can redistribute it and/or modify it +# Deja-Vu is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# Deja-vu is distributed in the hope that it will be useful, but +# Deja-Vu is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with Deja-vu. If not, see . +# along with Deja-Vu. If not, see . [package] name = "core" @@ -42,6 +42,7 @@ lazy_static = "1.5.0" libc = "0.2.158" once_cell = "1.20.2" pretty_assertions = "1.4.0" +rand = "0.9.0" ratatui = { version = "0.29.0", features = ["serde", "macros"] } raw-cpuid = "11.2.0" regex = "1.11.1" diff --git a/core/build.rs b/core/build.rs index c3ced6b..8988d39 100644 --- a/core/build.rs +++ b/core/build.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use anyhow::Result; use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, GixBuilder}; diff --git a/core/src/action.rs b/core/src/action.rs index 604edd7..486cdbb 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use serde::{Deserialize, Serialize}; use strum::Display; @@ -20,6 +20,8 @@ use crate::{components::popup::Type, line::DVLine, state::Mode, system::disk::Di #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] pub enum Action { + // App (Boot-Diags) + BootScan, // App (Clone) Highlight(usize), InstallDriver, @@ -27,6 +29,7 @@ pub enum Action { ScanDisks, Select(Option, Option), // indicies for (source, dest) etc SelectRight(Option, Option), // indicies for right info pane + TasksComplete, UpdateDiskList(Vec), UpdateFooter(String), UpdateLeft(String, Vec, Vec, usize), // (title, labels, items, select_num) @@ -35,8 +38,10 @@ pub enum Action { // 1: For a single choice // 2: For two selections (obviously) UpdateRight(Vec>, usize, Vec>), // (labels, start_index, items) - items before start are always shown - // App (PEMenu) + // App (PE-Menu) OpenTerminal, + Restart, + Shutdown, // Screens DismissPopup, DisplayPopup(Type, String), diff --git a/core/src/cli.rs b/core/src/cli.rs index be4d597..f154dfe 100644 --- a/core/src/cli.rs +++ b/core/src/cli.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use clap::Parser; diff --git a/core/src/components.rs b/core/src/components.rs index 6d1fd97..e46c56a 100644 --- a/core/src/components.rs +++ b/core/src/components.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use color_eyre::Result; use crossterm::event::{KeyEvent, MouseEvent}; diff --git a/core/src/components/footer.rs b/core/src/components/footer.rs index 7355961..d266be2 100644 --- a/core/src/components/footer.rs +++ b/core/src/components/footer.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use color_eyre::Result; use ratatui::{ diff --git a/core/src/components/fps.rs b/core/src/components/fps.rs index cdb822e..32e4770 100644 --- a/core/src/components/fps.rs +++ b/core/src/components/fps.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use std::time::Instant; diff --git a/core/src/components/left.rs b/core/src/components/left.rs index 0f1c833..eb27244 100644 --- a/core/src/components/left.rs +++ b/core/src/components/left.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use color_eyre::Result; use crossterm::event::KeyEvent; @@ -138,14 +138,10 @@ impl Component for Left { .areas(area); // Title - let title_text = if self.selections[1].is_some() || self.select_num == 1 { - "Confirm Selections" - } else { - self.title_text.as_str() - }; - let title = - Paragraph::new(Line::from(Span::styled(title_text, Style::default())).centered()) - .block(Block::default().borders(Borders::NONE)); + let title = Paragraph::new( + Line::from(Span::styled(self.title_text.as_str(), Style::default())).centered(), + ) + .block(Block::default().borders(Borders::NONE)); frame.render_widget(title, title_area); // Body (Blank) diff --git a/core/src/components/popup.rs b/core/src/components/popup.rs index c2c0980..4b951a7 100644 --- a/core/src/components/popup.rs +++ b/core/src/components/popup.rs @@ -1,19 +1,20 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use color_eyre::Result; +use rand::random; use ratatui::{ prelude::*, widgets::{Block, Borders, Clear, Paragraph, Wrap}, @@ -95,3 +96,19 @@ impl Component for Popup { Ok(()) } } + +pub fn fortune() -> String { + String::from(match random::() / 4 { + 0 => "FUN FACT\n\n\nComputers barely work.", + 1 => "CRASH OVERRIDE\n\n\n\"Hack the planet!\"", + 2 => "CATS\n\n\n\"All your base are belong to us!\"", + 3 => "HMM\n\n\nThis has all happened before...\n\nThis will all happen again.", + 4 => "CYPHER\n\n\n\"I don’t even see the code. All I see is blonde, brunette, red-head.\"", + 5 => "CONGRATULATIONS\n\n\nYour did it!", + 6 => "DID YOU KNOW?\n\n\nmacOS includes a built-in screen reader!", + 7 => "TIP OF THE DAY\n\n\nNever go full Snappy!", + 8 => "WORDS OF WISDOM\n\n\n\nIt’s not DNS,\n\nThere’s no way it’s DNS,\n\nIt was DNS.", + 9 => "HAL 9000\n\n\n\"I'm sorry Dave, I'm afraid I can't do that.\"", + _ => "COMPLETE\n\n\nThank you for using this tool!", + }) +} diff --git a/core/src/components/right.rs b/core/src/components/right.rs index 569d0a1..e7c18b6 100644 --- a/core/src/components/right.rs +++ b/core/src/components/right.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use color_eyre::Result; use crossterm::event::KeyEvent; diff --git a/core/src/components/state.rs b/core/src/components/state.rs index b7b139c..d2862a9 100644 --- a/core/src/components/state.rs +++ b/core/src/components/state.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use std::collections::HashMap; @@ -55,7 +55,7 @@ impl StatefulList { self.state.selected() } - fn select_first_item(&mut self) { + pub fn select_first_item(&mut self) { if self.items.is_empty() { self.state.select(None); } else { diff --git a/core/src/components/title.rs b/core/src/components/title.rs index ee4f088..f8e7dee 100644 --- a/core/src/components/title.rs +++ b/core/src/components/title.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use color_eyre::Result; use ratatui::{ diff --git a/core/src/config.rs b/core/src/config.rs index 49eb824..2878c20 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // #![allow(dead_code)] // Remove this once you start using the code @@ -149,8 +149,8 @@ pub fn get_config_dir() -> PathBuf { } fn project_directory() -> Option { - ProjectDirs::from("com", "Deja-vu", "deja-vu") - //ProjectDirs::from("com", "Deja-vu", env!("CARGO_PKG_NAME")) + ProjectDirs::from("com", "Deja-Vu", "deja-vu") + //ProjectDirs::from("com", "Deja-Vu", env!("CARGO_PKG_NAME")) } #[derive(Clone, Debug, Default, Deref, DerefMut)] diff --git a/core/src/errors.rs b/core/src/errors.rs index 6d1753f..6285edc 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use std::env; diff --git a/core/src/lib.rs b/core/src/lib.rs index 2e49e5e..f954f22 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // pub mod action; pub mod cli; diff --git a/core/src/line.rs b/core/src/line.rs index 038a848..c618451 100644 --- a/core/src/line.rs +++ b/core/src/line.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use ratatui::{ style::{Color, Style}, @@ -45,7 +45,10 @@ impl DVLine { } } -pub fn get_disk_description_right(disk: &Disk) -> Vec { +pub fn get_disk_description_right( + disk: &Disk, + boot_os_indicies: Option>, +) -> Vec { let mut description: Vec = vec![ DVLine { line_parts: vec![format!( @@ -67,12 +70,29 @@ pub fn get_disk_description_right(disk: &Disk) -> Vec { line_colors: vec![Color::Blue], }, ]; - for line in &disk.parts_description { - description.push(DVLine { - line_parts: vec![line.clone()], - line_colors: vec![Color::Reset], + disk.parts_description + .iter() + .enumerate() + .for_each(|(index, line)| { + let mut line_parts = vec![line.clone()]; + let mut line_colors = vec![Color::Reset]; + if let Some(indicies) = &boot_os_indicies { + let boot_index = indicies.get(0); + if boot_index.is_some_and(|i| i == &index) { + line_parts.push(String::from(" <-- Boot Partition")); + line_colors.push(Color::Cyan); + } + let boot_index = indicies.get(1); + if boot_index.is_some_and(|i| i == &index) { + line_parts.push(String::from(" <-- OS Partition")); + line_colors.push(Color::Cyan); + } + } + description.push(DVLine { + line_parts, + line_colors, + }); }); - } description } diff --git a/core/src/logging.rs b/core/src/logging.rs index 741e981..02c8b3a 100644 --- a/core/src/logging.rs +++ b/core/src/logging.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use color_eyre::Result; use tracing_error::ErrorLayer; diff --git a/core/src/state.rs b/core/src/state.rs index fab9ede..8c8eb8d 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use std::sync::{Arc, Mutex}; @@ -30,6 +30,13 @@ pub enum Mode { Home, Done, Failed, + // Boot Diags + DiagMenu, + BootDiags, + BootScan, + BootSetup, + InjectDrivers, + SetBootMode, // Clone ScanDisks, InstallDrivers, diff --git a/core/src/system.rs b/core/src/system.rs index 1d9692d..f8c5db1 100644 --- a/core/src/system.rs +++ b/core/src/system.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // pub mod boot; pub mod cpu; diff --git a/core/src/system/boot.rs b/core/src/system/boot.rs index 2bc098f..3a87d2d 100644 --- a/core/src/system/boot.rs +++ b/core/src/system/boot.rs @@ -1,33 +1,40 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use super::{disk::PartitionTableType, drivers::Driver}; -use crate::tasks::Task; +use crate::tasks::TaskType; use color_eyre::Result; use std::path::PathBuf; +#[derive(Clone, Debug, Default)] +pub enum SafeMode { + #[default] + Disable, + Enable, +} + pub fn configure_disk( letter_boot: &str, letter_os: &str, system32: &str, table_type: PartitionTableType, -) -> Vec { +) -> Vec { let mut tasks = Vec::new(); // Create - tasks.push(Task::Command( + tasks.push(TaskType::CommandWait( PathBuf::from(format!("{system32}/bcdboot.exe")), vec![ format!("{letter_os}:\\Windows"), @@ -43,7 +50,7 @@ pub fn configure_disk( // Update boot sector (for legacy setups) if table_type == PartitionTableType::Legacy { - tasks.push(Task::Command( + tasks.push(TaskType::CommandWait( PathBuf::from(format!("{system32}/bootsect.exe")), vec![ String::from("/nt60"), @@ -55,34 +62,19 @@ pub fn configure_disk( } // Lock in safe mode - let bcd_path = match table_type { - PartitionTableType::Guid => { - format!("{letter_boot}:\\EFI\\Microsoft\\Boot\\BCD") - } - PartitionTableType::Legacy => { - format!("{letter_boot}:\\Boot\\BCD") - } - }; - tasks.push(Task::Command( - PathBuf::from(format!("{system32}/bcdedit.exe")), - vec![ - String::from("/store"), - bcd_path, - String::from("/set"), - String::from("{default}"), - String::from("safeboot"), - String::from("minimal"), - ], - )); + tasks.push( + set_mode(letter_boot, SafeMode::Enable, system32, &table_type) + .expect("Failed to create set_mode task."), + ); // Done tasks } -pub fn inject_driver(driver: &Driver, letter_os: &str, system32: &str) -> Result { +pub fn inject_driver(driver: &Driver, letter_os: &str, system32: &str) -> Result { //if let Some(driver_path_str) = driver.path.to_str() { let driver_path = driver.path.to_str().unwrap(); - Ok(Task::Command( + Ok(TaskType::CommandWait( PathBuf::from(format!("{system32}/dism.exe")), vec![ format!("/image:{letter_os}:\\"), @@ -92,3 +84,39 @@ pub fn inject_driver(driver: &Driver, letter_os: &str, system32: &str) -> Result ], )) } + +pub fn set_mode( + letter_boot: &str, + mode: SafeMode, + system32: &str, + table_type: &PartitionTableType, +) -> Result { + let bcd_path = match table_type { + PartitionTableType::Guid => { + format!("{letter_boot}:\\EFI\\Microsoft\\Boot\\BCD") + } + PartitionTableType::Legacy => { + format!("{letter_boot}:\\Boot\\BCD") + } + }; + + // Build CommandWait + let mut cmd_args = vec![String::from("/store"), bcd_path]; + match mode { + SafeMode::Disable => { + cmd_args.push(String::from("/deletevalue")); + cmd_args.push(String::from("{default}")); + cmd_args.push(String::from("safeboot")); + } + SafeMode::Enable => { + cmd_args.push(String::from("/set")); + cmd_args.push(String::from("{default}")); + cmd_args.push(String::from("safeboot")); + cmd_args.push(String::from("minimal")); + } + } + Ok(TaskType::CommandWait( + PathBuf::from(format!("{system32}/bcdedit.exe")), + cmd_args, + )) +} diff --git a/core/src/system/cpu.rs b/core/src/system/cpu.rs index 1187d2c..0e2a316 100644 --- a/core/src/system/cpu.rs +++ b/core/src/system/cpu.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // #[must_use] pub fn get_cpu_name() -> String { diff --git a/core/src/system/disk.rs b/core/src/system/disk.rs index 96a8ae0..3450add 100644 --- a/core/src/system/disk.rs +++ b/core/src/system/disk.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use serde::{Deserialize, Serialize}; use std::{ diff --git a/core/src/system/diskpart.rs b/core/src/system/diskpart.rs index 9521982..ae1c97a 100644 --- a/core/src/system/diskpart.rs +++ b/core/src/system/diskpart.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use std::{ collections::HashMap, @@ -129,7 +129,7 @@ pub fn get_partition_details( #[must_use] pub fn build_dest_format_script(disk_id: usize, part_type: &PartitionTableType) -> String { let disk_id = format!("{disk_id}"); - let mut script = vec!["select disk {disk_id}", "clean"]; + let mut script = vec!["automount enable noerr", "select disk {disk_id}", "clean"]; match part_type { PartitionTableType::Guid => { script.push("convert gpt"); diff --git a/core/src/system/drivers.rs b/core/src/system/drivers.rs index a95c342..8ddfa6c 100644 --- a/core/src/system/drivers.rs +++ b/core/src/system/drivers.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use std::{env, fmt, fs::read_dir, path::PathBuf, process::Command}; diff --git a/core/src/tasks.rs b/core/src/tasks.rs index 50bca0d..b0bd7a9 100644 --- a/core/src/tasks.rs +++ b/core/src/tasks.rs @@ -1,21 +1,22 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use std::{ collections::VecDeque, + fmt, path::PathBuf, process::{Command, Stdio}, sync::{Arc, Mutex}, @@ -34,23 +35,69 @@ use crate::{ }; #[derive(Clone, Debug)] -pub enum Task { - Command(PathBuf, Vec), // (command, args) - Diskpart(String), // (script_as_string) +pub enum TaskResult { + Error(String), + Output(String, String, bool), // stdout, stderr, success +} + +#[derive(Clone, Debug)] +pub enum TaskType { + CommandNoWait(PathBuf, Vec), // (command, args) + CommandWait(PathBuf, Vec), // (command, args) + Diskpart(String), // (script_as_string) ScanDisks, Sleep, UpdateDestDisk(usize), // (disk_index) UpdateDiskList, } +impl fmt::Display for TaskType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TaskType::CommandNoWait(cmd_path, _) | TaskType::CommandWait(cmd_path, _) => { + write!( + f, + "Command(\"{}\")", + cmd_path.file_name().unwrap().to_string_lossy() + ) + } + TaskType::Diskpart(_) => { + write!(f, "Diskpart") + } + TaskType::ScanDisks => write!(f, "ScanDisks"), + TaskType::Sleep => write!(f, "Sleep"), + TaskType::UpdateDestDisk(_) => write!(f, "UpdateDestDisk"), + TaskType::UpdateDiskList => write!(f, "UpdateDiskList"), + } + } +} + +#[derive(Debug)] +pub struct Task { + pub handle: Option>, + pub result: Option, + pub task_type: TaskType, +} + +impl Task { + pub fn new(task_type: TaskType) -> Task { + Task { + handle: None, + result: None, + task_type, + } + } +} + #[derive(Debug)] pub struct Tasks { action_tx: mpsc::UnboundedSender, disk_list: Arc>>, - handle: Option>, + cur_handle: Option>, + cur_task: Option, task_list: VecDeque, - task_rx: mpsc::UnboundedReceiver, // Used to forward Actions from Tasks to App - task_tx: mpsc::UnboundedSender, // Used to forward Actions from Tasks to App + task_rx: mpsc::UnboundedReceiver, + task_tx: mpsc::UnboundedSender, } impl Tasks { @@ -62,159 +109,181 @@ impl Tasks { Tasks { action_tx, disk_list: disk_list_arc, - handle: None, + cur_handle: None, + cur_task: None, task_list: VecDeque::new(), task_rx, task_tx, } } - pub fn add(&mut self, task: Task) { - info!("Adding task: {:?}", &task); - self.task_list.push_back(task); + pub fn add(&mut self, task_type: TaskType) { + info!("Adding task: {:?}", &task_type); + self.task_list.push_back(Task::new(task_type)); } pub fn idle(&self) -> bool { - self.handle.is_none() + self.cur_handle.is_none() } - pub fn poll(&mut self) -> Result<()> { - // Forward any actions to main app - if let Ok(action) = self.task_rx.try_recv() { - let result = self.action_tx.send(action.clone()); - assert!(result.is_ok(), "Failed to send Action: {action:?}"); + pub fn poll(&mut self) -> Result> { + let mut return_task: Option = None; + // Handle task channel item(s) + if let Ok(result) = self.task_rx.try_recv() { + if let Some(mut task) = self.cur_task.take() { + task.result.replace(result); + self.cur_task.replace(task); + } } // Check status of current task (if one is running). - // NOTE: Action::NextScreen is sent once all tasks are complete - if let Some(handle) = self.handle.take() { - if handle.is_finished() { + // NOTE: Action::TasksComplete is sent once all tasks are complete + if let Some(task_handle) = self.cur_handle.take() { + if task_handle.is_finished() { + // Need to return task with handle + if let Some(mut cur_task) = self.cur_task.take() { + cur_task.handle = Some(task_handle); + return_task = Some(cur_task); + } if self.task_list.is_empty() { // No tasks remain - self.task_tx.send(Action::NextScreen)?; + self.action_tx.send(Action::TasksComplete)?; } else { // Start next task self.start()?; } } else { - // Task not complete, return handle - self.handle = Some(handle); + // TaskType not complete, return handle + self.cur_handle.replace(task_handle); } } else if !self.task_list.is_empty() { // No current task but one is available self.start()?; } - Ok(()) + Ok(return_task) } pub fn start(&mut self) -> Result<()> { - if let Some(task) = self.task_list.pop_front() { - let task_str = format!("{task:?}"); + self.cur_task = self.task_list.pop_front(); + if let Some(task) = self.cur_task.take() { let task_tx = self.task_tx.clone(); - match task { - Task::Command(ref cmd_path, ref cmd_args) => { - let cmd_path = cmd_path.clone(); - let cmd_args = cmd_args.clone(); - if cfg!(windows) { - self.handle = Some(thread::spawn(move || { - let result = Command::new(cmd_path) - .args(cmd_args) - .stdout(Stdio::piped()) - .output(); - if let Some(action) = match result { - Ok(output) => { - if output.status.success() { - None - } else { - // Command returned an error status - let mut msg = String::new(); - if let Ok(stdout) = String::from_utf8(output.stdout) { - msg = String::from(stdout.trim()); - } - if msg.is_empty() { - msg = String::from("Generic error"); - } - Some(Action::Error(format!("Command failed: {msg}",))) - } - } - Err(err) => { - Some(Action::Error(format!("Failed to run command: {err:?}"))) - } - } { - let msg = format!("{:?}", &action); - let result = task_tx.send(action); - assert!(result.is_ok(), "Failed to send Action: {msg}"); - } - })); - } else { - // Simulate task if not running under Windows - self.handle = Some(thread::spawn(|| sleep(Duration::from_millis(250)))); - } + match task.task_type { + TaskType::CommandNoWait(ref cmd_path, ref cmd_args) => { + self.cur_handle = None; + run_task_command(cmd_path.clone(), cmd_args.clone(), task_tx); } - Task::Diskpart(ref script) => { - if cfg!(windows) { - let script = String::from(script); - self.handle = Some(thread::spawn(move || { - let output = diskpart::run_script_raw(script.as_str()); - if !output.status.success() - && task_tx - .send(Action::Error(String::from( - "Diskpart script returned an error", - ))) - .is_err() - { - panic!("Failed to send Action: {task_str:?}"); - } - })); - } else { - // Simulate task if not running under Windows - self.handle = Some(thread::spawn(|| sleep(Duration::from_millis(250)))); - } + TaskType::CommandWait(ref cmd_path, ref cmd_args) => { + self.cur_handle = Some(run_task_command( + cmd_path.clone(), + cmd_args.clone(), + task_tx, + )); } - Task::ScanDisks => { + TaskType::Diskpart(ref script) => { + self.cur_handle = Some(run_task_diskpart(&script, task_tx)); + } + TaskType::ScanDisks => { let disk_list_arc = self.disk_list.clone(); // Queue UpdateDiskList for various components - self.add(Task::UpdateDiskList); + self.add(TaskType::UpdateDiskList); - self.handle = Some(thread::spawn(move || { + self.cur_handle = Some(thread::spawn(move || { let mut disks = disk_list_arc.lock().unwrap(); - *disks = disk::get_disks(); + *disks = disk::get_disks() })); } - Task::Sleep => { - self.handle = Some(thread::spawn(|| sleep(Duration::from_millis(250)))); + TaskType::Sleep => { + self.cur_handle = Some(thread::spawn(|| sleep(Duration::from_millis(250)))); } - Task::UpdateDestDisk(index) => { + TaskType::UpdateDestDisk(index) => { self.action_tx.send(Action::DisplayPopup( popup::Type::Info, String::from("Refreshing disk info"), ))?; // Queue UpdateDiskList for various components - self.add(Task::Sleep); - self.add(Task::UpdateDiskList); + self.add(TaskType::Sleep); + self.add(TaskType::UpdateDiskList); // Update destination disk ~in-place let disk_list_arc = self.disk_list.clone(); - self.handle = Some(thread::spawn(move || { + self.cur_handle = Some(thread::spawn(move || { let mut disks = disk_list_arc.lock().unwrap(); let old_disk = &mut disks[index]; disks[index] = disk::refresh_disk_info(old_disk); })); } - Task::UpdateDiskList => { + TaskType::UpdateDiskList => { let disks = self.disk_list.lock().unwrap(); let disks_copy = disks.clone(); let action_tx = self.action_tx.clone(); - self.handle = Some(thread::spawn(move || { + self.cur_handle = Some(thread::spawn(move || { if let Err(err) = action_tx.send(Action::UpdateDiskList(disks_copy)) { panic!("Failed to send Action: {err:?}"); } })); } } + // Done + self.cur_task.replace(task); } Ok(()) } } + +fn parse_bytes_as_str(bytes: Vec) -> String { + match String::from_utf8(bytes) { + Ok(s) => s.trim().to_string(), + Err(_) => String::from("Failed to parse bytes as UTF-8 text"), + } +} + +fn run_task_command( + cmd_path: PathBuf, + cmd_args: Vec, + task_tx: mpsc::UnboundedSender, +) -> JoinHandle<()> { + if cfg!(windows) { + thread::spawn(move || { + let result = Command::new(cmd_path) + .args(cmd_args) + .stdout(Stdio::piped()) + .output(); + match result { + Err(e) => { + task_tx + .send(TaskResult::Error(format!("{:?}", &e))) + .expect("Failed to propegate error?"); + } + Ok(output) => { + let stderr = parse_bytes_as_str(output.stderr.to_owned()); + let stdout = parse_bytes_as_str(output.stdout.to_owned()); + let task_result = TaskResult::Output(stdout, stderr, output.status.success()); + let err_str = format!("Failed to send TaskResult: {:?}", &task_result); + task_tx.send(task_result).expect(err_str.as_str()); + } + } + }) + } else { + // Simulate task if not running under Windows + thread::spawn(|| sleep(Duration::from_millis(250))) + } +} + +fn run_task_diskpart(script: &str, task_tx: mpsc::UnboundedSender) -> JoinHandle<()> { + if cfg!(windows) { + let script = script.to_owned(); + thread::spawn(move || { + let output = diskpart::run_script_raw(&script); + let stderr = parse_bytes_as_str(output.stderr.to_owned()); + let stdout = parse_bytes_as_str(output.stdout.to_owned()); + let task_result = TaskResult::Output(stdout, stderr, output.status.success()); + let err_str = format!("Failed to send TaskResult: {:?}", &task_result); + task_tx.send(task_result).expect(err_str.as_str()); + }) + } else { + // Simulate task if not running under Windows + thread::spawn(|| sleep(Duration::from_millis(250))) + } +} diff --git a/core/src/tests/mod.rs b/core/src/tests/mod.rs index cf96690..6750d5d 100644 --- a/core/src/tests/mod.rs +++ b/core/src/tests/mod.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // pub mod sample_output; diff --git a/core/src/tests/sample_output.rs b/core/src/tests/sample_output.rs index 2d2681b..4ffdfda 100644 --- a/core/src/tests/sample_output.rs +++ b/core/src/tests/sample_output.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // #[allow(dead_code)] pub static DETAIL_DISK_GPT: &str = "Disk 2 is now the selected disk. diff --git a/core/src/tui.rs b/core/src/tui.rs index 9bc93c7..cf563b0 100644 --- a/core/src/tui.rs +++ b/core/src/tui.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // #![allow(dead_code)] // Remove this once you start using the code diff --git a/deja_vu/Cargo.toml b/deja_vu/Cargo.toml index 7756600..13af3e8 100644 --- a/deja_vu/Cargo.toml +++ b/deja_vu/Cargo.toml @@ -1,17 +1,17 @@ -# This file is part of Deja-vu. +# This file is part of Deja-Vu. # -# Deja-vu is free software: you can redistribute it and/or modify it +# Deja-Vu is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# Deja-vu is distributed in the hope that it will be useful, but +# Deja-Vu is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with Deja-vu. If not, see . +# along with Deja-Vu. If not, see . [package] name = "deja-vu" @@ -39,7 +39,7 @@ tokio-util = "0.7.11" tracing = "0.1.41" tracing-error = "0.2.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] } -rand = "0.8.5" +check_elevation = "0.2.4" [build-dependencies] anyhow = "1.0.86" diff --git a/deja_vu/build.rs b/deja_vu/build.rs index c3ced6b..8988d39 100644 --- a/deja_vu/build.rs +++ b/deja_vu/build.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use anyhow::Result; use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, GixBuilder}; diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index ae6ef72..635b62a 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . use core::{ action::Action, @@ -26,17 +26,17 @@ use core::{ boot, cpu::get_cpu_name, disk::PartitionTableType, diskpart::build_dest_format_script, drivers, }, - tasks::{Task, Tasks}, + tasks::{Task, TaskResult, TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ env, iter::zip, + path::PathBuf, sync::{Arc, Mutex}, }; use color_eyre::Result; -use rand::random; use ratatui::{ crossterm::event::KeyEvent, layout::{Constraint, Direction, Layout}, @@ -115,7 +115,13 @@ impl App { | Mode::Clone | Mode::PostClone => None, // Invalid states - Mode::PEMenu => panic!("This shouldn't happen?"), + Mode::BootDiags + | Mode::BootScan + | Mode::BootSetup + | Mode::DiagMenu + | Mode::InjectDrivers + | Mode::PEMenu + | Mode::SetBootMode => panic!("This shouldn't happen?"), }; new_mode } @@ -134,7 +140,13 @@ impl App { Mode::PostClone | Mode::Done => Mode::Done, Mode::Failed => Mode::Failed, // Invalid states - Mode::PEMenu => panic!("This shouldn't happen?"), + Mode::BootDiags + | Mode::BootScan + | Mode::BootSetup + | Mode::DiagMenu + | Mode::InjectDrivers + | Mode::PEMenu + | Mode::SetBootMode => panic!("This shouldn't happen?"), }; if new_mode == self.cur_mode { @@ -153,7 +165,7 @@ impl App { Mode::ScanDisks => { self.prev_mode = self.cur_mode; if self.tasks.idle() { - self.tasks.add(Task::ScanDisks); + self.tasks.add(TaskType::ScanDisks); } self.action_tx.send(Action::DisplayPopup( popup::Type::Info, @@ -166,13 +178,33 @@ impl App { String::from("Formatting destination disk"), ))?; + // Get System32 path + let system32 = if cfg!(windows) { + if let Ok(path) = env::var("SYSTEMROOT") { + format!("{path}/System32") + } else { + self.action_tx.send(Action::Error(String::from( + "ERROR\n\n\nFailed to find SYSTEMROOT", + )))?; + return Ok(()); + } + } else { + String::from(".") + }; + + // (Re)Enable volume mounting + self.tasks.add(TaskType::CommandWait( + PathBuf::from(format!("{system32}/mountvol.exe")), + vec![String::from("/e")], + )); + // Build Diskpart script to format destination disk let disk_list = self.clone.disk_list.lock().unwrap(); if let Some(disk_index) = self.clone.disk_index_dest { if let Some(disk) = disk_list.get(disk_index) { let table_type = self.clone.table_type.clone().unwrap(); let diskpart_script = build_dest_format_script(disk.id, &table_type); - self.tasks.add(Task::Diskpart(diskpart_script)); + self.tasks.add(TaskType::Diskpart(diskpart_script)); } } } @@ -181,12 +213,12 @@ impl App { popup::Type::Info, String::from("Running Clone Tool"), ))?; - self.tasks.add(Task::Command( + self.tasks.add(TaskType::CommandWait( self.config.clone_app_path.clone(), Vec::new(), )); if let Some(dest_index) = self.clone.disk_index_dest { - self.tasks.add(Task::UpdateDestDisk(dest_index)); + self.tasks.add(TaskType::UpdateDestDisk(dest_index)); } } Mode::PostClone => { @@ -248,7 +280,7 @@ impl App { } Mode::Done => { self.action_tx - .send(Action::DisplayPopup(popup::Type::Success, fortune()))?; + .send(Action::DisplayPopup(popup::Type::Success, popup::fortune()))?; } _ => {} } @@ -343,12 +375,9 @@ impl App { match action { Action::Tick => { self.last_tick_key_events.drain(..); - match self.cur_mode { - Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { - // Check background task - self.tasks.poll()?; // Once all are complete Action::NextScreen is sent - } - _ => {} + // Check background task(s) + if let Some(task) = self.tasks.poll()? { + self.handle_task(&task)?; } } Action::Quit => self.should_quit = true, @@ -369,6 +398,9 @@ impl App { Mode::Confirm => { self.action_tx.send(Action::NextScreen)?; } + Mode::Done => { + self.action_tx.send(Action::Quit)?; + } _ => {} }, Action::Resize(w, h) => self.handle_resize(tui, w, h)?, @@ -468,6 +500,7 @@ impl App { _ => {} }; } + Action::TasksComplete => self.action_tx.send(Action::NextScreen)?, _ => {} } for component in &mut self.components { @@ -485,6 +518,39 @@ impl App { Ok(()) } + fn handle_task(&mut self, task: &Task) -> Result<()> { + match task.task_type { + TaskType::CommandWait(_, _) | TaskType::Diskpart(_) => { + // Check result + if let Some(result) = &task.result { + match result { + TaskResult::Error(msg) => { + self.action_tx + .send(Action::Error(format!("{} Failed: {msg}", task.task_type)))?; + } + TaskResult::Output(stdout, stderr, success) => { + if !success { + let msg = if !stdout.is_empty() { + stdout.clone() + } else if !stderr.is_empty() { + stderr.clone() + } else { + String::from("Unknown Error") + }; + self.action_tx.send(Action::Error(format!( + "{} Failed: {msg}", + task.task_type + )))?; + } + } + } + } + } + _ => {} + } + Ok(()) + } + fn render(&mut self, tui: &mut Tui) -> Result<()> { tui.draw(|frame| { if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] { @@ -566,14 +632,21 @@ fn build_footer_string(cur_mode: Mode) -> String { String::from("(q) to quit") } Mode::SelectParts => String::from("(Enter) to select / (s) to start over / (q) to quit"), + Mode::InstallDrivers => String::from("(Enter) to select / (q) to quit"), Mode::SelectDisks => String::from( "(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit", ), Mode::SelectTableType => String::from("(Enter) to select / (b) to go back / (q) to quit"), Mode::Confirm => String::from("(Enter) to confirm / (b) to go back / (q) to quit"), - Mode::Done | Mode::Failed | Mode::InstallDrivers => String::from("(Enter) or (q) to quit"), + Mode::Done | Mode::Failed => String::from("(Enter) or (q) to quit"), // Invalid states - Mode::PEMenu => panic!("This shouldn't happen?"), + Mode::BootDiags + | Mode::BootScan + | Mode::BootSetup + | Mode::DiagMenu + | Mode::InjectDrivers + | Mode::PEMenu + | Mode::SetBootMode => panic!("This shouldn't happen?"), } } @@ -638,7 +711,13 @@ fn build_left_items(app: &App, cur_mode: Mode) -> Action { title = String::from("Done"); } // Invalid states - Mode::PEMenu => panic!("This shouldn't happen?"), + Mode::BootDiags + | Mode::BootScan + | Mode::BootSetup + | Mode::DiagMenu + | Mode::InjectDrivers + | Mode::PEMenu + | Mode::SetBootMode => panic!("This shouldn't happen?"), }; Action::UpdateLeft(title, labels, items, select_num) } @@ -691,7 +770,7 @@ fn build_right_items(app: &App, cur_mode: Mode) -> Action { let disk_list = app.clone.disk_list.lock().unwrap(); disk_list .iter() - .for_each(|disk| items.push(get_disk_description_right(&disk))); + .for_each(|disk| items.push(get_disk_description_right(&disk, None))); } Mode::SelectParts => { vec!["Boot", "OS"].iter().for_each(|s| { @@ -705,7 +784,7 @@ fn build_right_items(app: &App, cur_mode: Mode) -> Action { let disk_list = app.clone.disk_list.lock().unwrap(); if let Some(disk) = disk_list.get(index) { // Disk Details - items.push(get_disk_description_right(&disk)); + items.push(get_disk_description_right(&disk, None)); // Partition Details disk.parts @@ -718,19 +797,3 @@ fn build_right_items(app: &App, cur_mode: Mode) -> Action { } Action::UpdateRight(labels, start_index, items) } - -fn fortune() -> String { - String::from(match random::() / 4 { - 0 => "FUN FACT\n\n\nComputers barely work.", - 1 => "CRASH OVERRIDE\n\n\n\"Hack the planet!\"", - 2 => "CATS\n\n\n\"All your base are belong to us!\"", - 3 => "HMM\n\n\nThis has all happened before...\n\nThis will all happen again.", - 4 => "CYPHER\n\n\n\"I don’t even see the code. All I see is blonde, brunette, red-head.\"", - 5 => "CONGRATULATIONS\n\n\nYour did it!", - 6 => "DID YOU KNOW?\n\n\nmacOS includes a built-in screen reader!", - 7 => "TIP OF THE DAY\n\n\nNever go full Snappy!", - 8 => "WORDS OF WISDOM\n\n\n\nIt’s not DNS,\n\nThere’s no way it’s DNS,\n\nIt was DNS.", - 9 => "HAL 9000\n\n\n\"I'm sorry Dave, I'm afraid I can't do that.\"", - _ => "COMPLETE\n\n\nThank you for using this tool!", - }) -} diff --git a/deja_vu/src/main.rs b/deja_vu/src/main.rs index 8bcfda9..39999d2 100644 --- a/deja_vu/src/main.rs +++ b/deja_vu/src/main.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use clap::Parser; use color_eyre::Result; @@ -23,11 +23,25 @@ mod app; #[tokio::main] async fn main() -> Result<()> { - core::errors::init()?; - core::logging::init()?; + let mut msg = None; + if cfg!(windows) { + use check_elevation::is_elevated; + if !is_elevated().expect("Failed to get elevation status.") { + msg.replace("Administrator privedges required for Deja-Vu."); + } + }; + match msg { + Some(text) => { + println!("{text}"); + } + None => { + core::errors::init()?; + core::logging::init()?; - let args = core::cli::Cli::parse(); - let mut app = App::new(args.tick_rate, args.frame_rate)?; - app.run().await?; + let args = core::cli::Cli::parse(); + let mut app = App::new(args.tick_rate, args.frame_rate)?; + app.run().await?; + } + } Ok(()) } diff --git a/include/menu_entries/01_deja-vu.toml b/include/menu_entries/10_deja-vu.toml similarity index 100% rename from include/menu_entries/01_deja-vu.toml rename to include/menu_entries/10_deja-vu.toml diff --git a/include/menu_entries/11_boot-diags.toml b/include/menu_entries/11_boot-diags.toml new file mode 100644 index 0000000..1f30f6f --- /dev/null +++ b/include/menu_entries/11_boot-diags.toml @@ -0,0 +1,5 @@ +name = 'Boot-Diagnostics' +command = 'X:\tools\boot-diags.exe' +description = "Boot issue assessment tool" +use_conemu = true +separator = false diff --git a/include/menu_entries/02_separator.toml b/include/menu_entries/20_separator.toml similarity index 100% rename from include/menu_entries/02_separator.toml rename to include/menu_entries/20_separator.toml diff --git a/include/menu_entries/03_ntpwedit.toml b/include/menu_entries/30_ntpwedit.toml similarity index 100% rename from include/menu_entries/03_ntpwedit.toml rename to include/menu_entries/30_ntpwedit.toml diff --git a/include/menu_entries/04_clone-tool.toml b/include/menu_entries/31_clone-tool.toml similarity index 100% rename from include/menu_entries/04_clone-tool.toml rename to include/menu_entries/31_clone-tool.toml diff --git a/include/menu_entries/05_taskmgr.toml b/include/menu_entries/32_taskmgr.toml similarity index 100% rename from include/menu_entries/05_taskmgr.toml rename to include/menu_entries/32_taskmgr.toml diff --git a/pe_menu/Cargo.toml b/pe_menu/Cargo.toml index cf7bcfb..a3f1a55 100644 --- a/pe_menu/Cargo.toml +++ b/pe_menu/Cargo.toml @@ -1,17 +1,17 @@ -# This file is part of Deja-vu. +# This file is part of Deja-Vu. # -# Deja-vu is free software: you can redistribute it and/or modify it +# Deja-Vu is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# Deja-vu is distributed in the hope that it will be useful, but +# Deja-Vu is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with Deja-vu. If not, see . +# along with Deja-Vu. If not, see . [package] name = "pe-menu" diff --git a/pe_menu/build.rs b/pe_menu/build.rs index c3ced6b..8988d39 100644 --- a/pe_menu/build.rs +++ b/pe_menu/build.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use anyhow::Result; use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, GixBuilder}; diff --git a/pe_menu/src/app.rs b/pe_menu/src/app.rs index 5f9c060..b817f2f 100644 --- a/pe_menu/src/app.rs +++ b/pe_menu/src/app.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use core::{ action::Action, @@ -22,7 +22,7 @@ use core::{ config::Config, line::DVLine, state::Mode, - tasks::{Task, Tasks}, + tasks::{TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ @@ -230,25 +230,55 @@ impl App { // Run selected tool if let Some(tool) = self.list.get_selected() { info!("Run tool: {:?}", &tool); - self.tasks.add(build_command(&self, &tool)); + self.tasks.add(build_tool_command(&self, &tool)); } } Action::Resize(w, h) => self.handle_resize(tui, w, h)?, Action::Render => self.render(tui)?, Action::SetMode(mode) => { self.mode = mode; - self.action_tx - .send(Action::UpdateFooter(String::from("(Enter) to select")))?; + self.action_tx.send(Action::UpdateFooter(String::from( + "(Enter) to select / (t) for terminal / (p) to power off / (r) to restart", + )))?; self.action_tx.send(build_left_items(self))?; self.action_tx.send(build_right_items(self))?; self.action_tx.send(Action::Select(None, None))?; } Action::OpenTerminal => { - self.tasks.add(Task::Command( + self.tasks.add(TaskType::CommandNoWait( PathBuf::from("cmd.exe"), vec![String::from("-new_console:n")], )); } + Action::Restart => { + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Restarting..."), + ))?; + self.tasks.add(TaskType::CommandWait( + PathBuf::from("X:/Windows/System32/sync64.exe"), + vec![String::from("-r")], + )); + self.tasks.add(TaskType::CommandWait( + PathBuf::from("X:/Windows/System32/wpeutil.exe"), + vec![String::from("reboot")], + )); + } + Action::Shutdown => { + // NOTE: Using 'Powering off' to match the key pressed + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Powering off..."), + ))?; + self.tasks.add(TaskType::CommandWait( + PathBuf::from("X:/Windows/System32/sync64.exe"), + vec![String::from("-r")], + )); + self.tasks.add(TaskType::CommandWait( + PathBuf::from("X:/Windows/System32/wpeutil.exe"), + vec![String::from("shutdown")], + )); + } _ => {} } for component in &mut self.components { @@ -341,14 +371,15 @@ fn get_chunks(r: Rect) -> Vec { chunks } -pub fn build_command(app: &App, tool: &Tool) -> Task { +pub fn build_tool_command(app: &App, tool: &Tool) -> TaskType { let cmd_path: PathBuf; let mut cmd_args: Vec = Vec::new(); let start_index: usize; if tool.use_conemu { cmd_path = app.config.conemu_path.clone(); - cmd_args.push(String::from("-new_console:n")); + cmd_args.push(String::from("-run")); cmd_args.push(tool.command.clone()); + cmd_args.push(String::from("-new_console:n")); start_index = 1; } else { cmd_path = PathBuf::from(tool.command.clone()); @@ -361,7 +392,7 @@ pub fn build_command(app: &App, tool: &Tool) -> Task { }); } } - Task::Command(cmd_path, cmd_args) + TaskType::CommandNoWait(cmd_path, cmd_args) } fn build_left_items(app: &App) -> Action { diff --git a/pe_menu/src/main.rs b/pe_menu/src/main.rs index 8bcfda9..82b0996 100644 --- a/pe_menu/src/main.rs +++ b/pe_menu/src/main.rs @@ -1,17 +1,17 @@ -// This file is part of Deja-vu. +// This file is part of Deja-Vu. // -// Deja-vu is free software: you can redistribute it and/or modify it +// Deja-Vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Deja-vu is distributed in the hope that it will be useful, but +// Deja-Vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . +// along with Deja-Vu. If not, see . // use clap::Parser; use color_eyre::Result;