Convert to workspace with both deja-vu and pe-menu
This commit is contained in:
parent
83badb9c43
commit
d7c070aad8
49 changed files with 1193 additions and 68 deletions
3
.envrc
3
.envrc
|
|
@ -1,3 +0,0 @@
|
||||||
export DEJA_VU_CONFIG=`pwd`/.config
|
|
||||||
export DEJA_VU_DATA=`pwd`/.data
|
|
||||||
export DEJA_VU_LOG_LEVEL=debug
|
|
||||||
112
Cargo.lock
generated
112
Cargo.lock
generated
|
|
@ -338,6 +338,19 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compact_str"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
|
||||||
|
dependencies = [
|
||||||
|
"castaway",
|
||||||
|
"cfg-if",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compact_str"
|
name = "compact_str"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -432,6 +445,23 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"futures-core",
|
||||||
|
"libc",
|
||||||
|
"mio 0.8.11",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
|
|
@ -441,7 +471,7 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"mio",
|
"mio 1.0.2",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix",
|
"rustix",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -519,7 +549,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"config",
|
"config",
|
||||||
"crossterm",
|
"crossterm 0.28.1",
|
||||||
"derive_deref",
|
"derive_deref",
|
||||||
"directories",
|
"directories",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
@ -529,7 +559,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"ratatui",
|
"ratatui 0.28.1",
|
||||||
"raw-cpuid",
|
"raw-cpuid",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -1465,6 +1495,15 @@ version = "1.70.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
|
@ -1624,6 +1663,18 @@ dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
@ -1761,6 +1812,18 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361"
|
checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pe-menu"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm 0.27.0",
|
||||||
|
"futures",
|
||||||
|
"ratatui 0.26.3",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
|
@ -1864,6 +1927,26 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ratatui"
|
||||||
|
version = "0.26.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cassowary",
|
||||||
|
"compact_str 0.7.1",
|
||||||
|
"crossterm 0.27.0",
|
||||||
|
"itertools 0.12.1",
|
||||||
|
"lru",
|
||||||
|
"paste",
|
||||||
|
"stability",
|
||||||
|
"strum",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-truncate",
|
||||||
|
"unicode-width 0.1.14",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui"
|
name = "ratatui"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
|
|
@ -1872,10 +1955,10 @@ checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cassowary",
|
"cassowary",
|
||||||
"compact_str",
|
"compact_str 0.8.0",
|
||||||
"crossterm",
|
"crossterm 0.28.1",
|
||||||
"instability",
|
"instability",
|
||||||
"itertools",
|
"itertools 0.13.0",
|
||||||
"lru",
|
"lru",
|
||||||
"paste",
|
"paste",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -2126,7 +2209,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 0.8.11",
|
||||||
|
"mio 1.0.2",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2164,6 +2248,16 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stability"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.82",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
@ -2348,7 +2442,7 @@ dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 1.0.2",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
|
|
@ -2544,7 +2638,7 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools",
|
"itertools 0.13.0",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width 0.1.14",
|
"unicode-width 0.1.14",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
54
Cargo.toml
54
Cargo.toml
|
|
@ -13,54 +13,6 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
# along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
[package]
|
[workspace]
|
||||||
name = "deja-vu"
|
members = ["deja_vu", "pe_menu"]
|
||||||
version = "0.2.0"
|
resolver = "2"
|
||||||
edition = "2021"
|
|
||||||
description = "Clone/Install Windows, create/edit boot files, and troubleshoot boot issues."
|
|
||||||
authors = ["2Shirt <2xShirt@gmail.com>"]
|
|
||||||
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
better-panic = "0.3.0"
|
|
||||||
clap = { version = "4.4.5", features = [
|
|
||||||
"derive",
|
|
||||||
"cargo",
|
|
||||||
"wrap_help",
|
|
||||||
"unicode",
|
|
||||||
"string",
|
|
||||||
"unstable-styles",
|
|
||||||
] }
|
|
||||||
color-eyre = "0.6.3"
|
|
||||||
config = "0.14.0"
|
|
||||||
crossterm = { version = "0.28.1", features = ["serde", "event-stream"] }
|
|
||||||
derive_deref = "1.1.1"
|
|
||||||
directories = "5.0.1"
|
|
||||||
futures = "0.3.30"
|
|
||||||
human-panic = "2.0.1"
|
|
||||||
json5 = "0.4.1"
|
|
||||||
lazy_static = "1.5.0"
|
|
||||||
libc = "0.2.158"
|
|
||||||
once_cell = "1.20.2"
|
|
||||||
pretty_assertions = "1.4.0"
|
|
||||||
ratatui = { version = "0.28.1", features = ["serde", "macros"] }
|
|
||||||
raw-cpuid = "11.2.0"
|
|
||||||
regex = "1.11.1"
|
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
|
||||||
serde_json = "1.0.125"
|
|
||||||
signal-hook = "0.3.17"
|
|
||||||
strip-ansi-escapes = "0.2.0"
|
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
|
||||||
tempfile = "3.13.0"
|
|
||||||
tokio = { version = "1.39.3", features = ["full"] }
|
|
||||||
tokio-util = "0.7.11"
|
|
||||||
tracing = "0.1.40"
|
|
||||||
tracing-error = "0.2.0"
|
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
|
|
||||||
walkdir = "2.5.0"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
anyhow = "1.0.86"
|
|
||||||
vergen-gix = { version = "1.0.0", features = ["build", "cargo"] }
|
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -1,11 +1,11 @@
|
||||||
# Deja-vu #
|
# deja-vu #
|
||||||
|
|
||||||
Clone/Install Windows, create/edit boot files, and troubleshoot boot issues. This tool can and will delete data on selected destination devices; care should be taken to avoid unintended data loss.
|
Clone/Install Windows, create/edit boot files, and troubleshoot boot issues. This tool can and will delete data; care should be taken to avoid unintended data loss.
|
||||||
|
|
||||||
## deva-vu.toml ##
|
# pe-menu #
|
||||||
|
|
||||||
Please update this configuration file with the full path to the cloning tool of your choice.
|
Menu to launch various applications (like deja-vu).
|
||||||
|
|
||||||
### NOTES ###
|
## NOTES ##
|
||||||
|
|
||||||
This tool is under active development and is not considered ready for production use!
|
This tool is under active development and is not considered ready for production use!
|
||||||
|
|
|
||||||
30
build_win_release.sh
Executable file
30
build_win_release.sh
Executable file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# 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 <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
## Helper script to build and assemble the items needed for a release
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o errtrace
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
TEMP_DIR="$(mktemp -d)"
|
||||||
|
|
||||||
|
cargo build --release --target=x86_64-pc-windows-gnu
|
||||||
|
cp -nv target/x86_64-pc-windows-gnu/release/*exe "${TEMP_DIR}"/
|
||||||
|
cp -nrv include/* "${TEMP_DIR}"/
|
||||||
|
tar cavf "deja-vu_$(date +%Y-%m-%d).txz" -C "${TEMP_DIR}" .
|
||||||
3
deja_vu/.envrc
Normal file
3
deja_vu/.envrc
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export DEJA_VU_CONFIG=`pwd`/config
|
||||||
|
export DEJA_VU_DATA=`pwd`/data
|
||||||
|
export DEJA_VU_LOG_LEVEL=debug
|
||||||
66
deja_vu/Cargo.toml
Normal file
66
deja_vu/Cargo.toml
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "deja-vu"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Clone/Install Windows, create/edit boot files, and troubleshoot boot issues."
|
||||||
|
authors = ["2Shirt <2xShirt@gmail.com>"]
|
||||||
|
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
better-panic = "0.3.0"
|
||||||
|
clap = { version = "4.4.5", features = [
|
||||||
|
"derive",
|
||||||
|
"cargo",
|
||||||
|
"wrap_help",
|
||||||
|
"unicode",
|
||||||
|
"string",
|
||||||
|
"unstable-styles",
|
||||||
|
] }
|
||||||
|
color-eyre = "0.6.3"
|
||||||
|
config = "0.14.0"
|
||||||
|
crossterm = { version = "0.28.1", features = ["serde", "event-stream"] }
|
||||||
|
derive_deref = "1.1.1"
|
||||||
|
directories = "5.0.1"
|
||||||
|
futures = "0.3.30"
|
||||||
|
human-panic = "2.0.1"
|
||||||
|
json5 = "0.4.1"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
libc = "0.2.158"
|
||||||
|
once_cell = "1.20.2"
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
|
ratatui = { version = "0.28.1", features = ["serde", "macros"] }
|
||||||
|
raw-cpuid = "11.2.0"
|
||||||
|
regex = "1.11.1"
|
||||||
|
serde = { version = "1.0.208", features = ["derive"] }
|
||||||
|
serde_json = "1.0.125"
|
||||||
|
signal-hook = "0.3.17"
|
||||||
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
|
tempfile = "3.13.0"
|
||||||
|
tokio = { version = "1.39.3", features = ["full"] }
|
||||||
|
tokio-util = "0.7.11"
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-error = "0.2.0"
|
||||||
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
|
||||||
|
walkdir = "2.5.0"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
anyhow = "1.0.86"
|
||||||
|
vergen-gix = { version = "1.0.0", features = ["build", "cargo"] }
|
||||||
23
include/drivers/README.md
Normal file
23
include/drivers/README.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
## Drivers ##
|
||||||
|
|
||||||
|
This tool expects there to be zero or more folders here. Each folder can contain multiple files/folders within.
|
||||||
|
|
||||||
|
### Example ###
|
||||||
|
|
||||||
|
- Intel RST - 13th Gen/
|
||||||
|
- HsaComponent/
|
||||||
|
- iaStorHsaComponent.cat
|
||||||
|
- iaStorHsaComponent.inf
|
||||||
|
- HsaExtension/
|
||||||
|
- iaStorHsa_Ext.cat
|
||||||
|
- iaStorHsa_Ext.inf
|
||||||
|
- iaStorVD.cat
|
||||||
|
- iaStorVD.inf
|
||||||
|
- iaStorVD.sys
|
||||||
|
- RstMwEventLogMsg.dll
|
||||||
|
- RstMwService.exe
|
||||||
|
- VirtIO /
|
||||||
|
- Some.cat
|
||||||
|
- Some.inf
|
||||||
|
- Some.sys
|
||||||
|
- etc..
|
||||||
4
include/menu_entries/01_deja-vu.toml
Normal file
4
include/menu_entries/01_deja-vu.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name = 'Deja-Vu'
|
||||||
|
command = 'X:\tools\deja-vu.exe'
|
||||||
|
use_conemu = true
|
||||||
|
separator = false
|
||||||
4
include/menu_entries/02_separator.toml
Normal file
4
include/menu_entries/02_separator.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name = ''
|
||||||
|
command = ''
|
||||||
|
use_conemu = false
|
||||||
|
separator = true
|
||||||
4
include/menu_entries/03_ntpwedit.toml
Normal file
4
include/menu_entries/03_ntpwedit.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name = 'NTPWEdit'
|
||||||
|
command = 'X:\Program Files\NTPWEdit\ntpwedit.exe'
|
||||||
|
use_conemu = false
|
||||||
|
separator = false
|
||||||
4
include/menu_entries/04_clone-tool.toml
Normal file
4
include/menu_entries/04_clone-tool.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name = 'Some Clone Tool'
|
||||||
|
command = 'X:\Program Files\Some\Tool.exe'
|
||||||
|
use_conemu = false
|
||||||
|
separator = false
|
||||||
4
include/menu_entries/05_taskmgr.toml
Normal file
4
include/menu_entries/05_taskmgr.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name = 'Task Manager'
|
||||||
|
command = 'X:\Windows\System32\taskmgr.exe'
|
||||||
|
use_conemu = false
|
||||||
|
separator = false
|
||||||
32
include/pe-menu.cmd
Normal file
32
include/pe-menu.cmd
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
@echo off
|
||||||
|
:: 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
wpeutil EnableFirewall
|
||||||
|
|
||||||
|
:loop
|
||||||
|
cls
|
||||||
|
pe-menu.exe
|
||||||
|
if %errorlevel% NEQ 0 (
|
||||||
|
echo.
|
||||||
|
echo "ERROR: pe-menu crashed"
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
)
|
||||||
|
goto loop
|
||||||
|
|
||||||
|
:done
|
||||||
|
popd
|
||||||
17
include/pe-menu.toml
Normal file
17
include/pe-menu.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
con_emu = 'X:\Program Files\ConEmu\ConEmu64.exe'
|
||||||
|
tools = []
|
||||||
29
pe_menu/Cargo.toml
Normal file
29
pe_menu/Cargo.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "pe-menu"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["2Shirt <2xShirt@gmail.com>"]
|
||||||
|
license = "GPL"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
crossterm = { version = "0.27.0", features = ["event-stream"] }
|
||||||
|
futures = "0.3.30"
|
||||||
|
ratatui = "0.26.0"
|
||||||
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
|
tokio = { version = "1.35.1", features = ["full"] }
|
||||||
|
toml = "0.8.13"
|
||||||
366
pe_menu/src/app.rs
Normal file
366
pe_menu/src/app.rs
Normal file
|
|
@ -0,0 +1,366 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
use ratatui::widgets::ListState;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{
|
||||||
|
env, error, fs, io,
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Command, Output},
|
||||||
|
thread::{self, JoinHandle},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Application result type.
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;
|
||||||
|
|
||||||
|
/// Application exit reasons
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub enum QuitReason {
|
||||||
|
#[default]
|
||||||
|
Exit,
|
||||||
|
Poweroff,
|
||||||
|
Restart,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
con_emu: String,
|
||||||
|
tools: Vec<Tool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic for many reasons
|
||||||
|
#[must_use]
|
||||||
|
pub fn load() -> Option<Config> {
|
||||||
|
// Main config
|
||||||
|
let exe_path = env::current_exe().expect("Failed to find main executable");
|
||||||
|
let contents = fs::read_to_string(exe_path.with_file_name("pe-menu.toml"))
|
||||||
|
.expect("Failed to load config file");
|
||||||
|
let mut new_config: Config =
|
||||||
|
toml::from_str(&contents).expect("Failed to parse config file");
|
||||||
|
|
||||||
|
// Tools
|
||||||
|
let tool_config_path = exe_path.parent().unwrap().join("menu_entries");
|
||||||
|
let mut entries: Vec<PathBuf> = std::fs::read_dir(tool_config_path)
|
||||||
|
.expect("Failed to find any tool configs")
|
||||||
|
.map(|res| res.map(|e| e.path()))
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
entries.sort();
|
||||||
|
for entry in entries {
|
||||||
|
let contents = fs::read_to_string(&entry).expect("Failed to read tool config file");
|
||||||
|
let tool: Tool = toml::from_str(&contents).expect("Failed to parse tool config file");
|
||||||
|
new_config.tools.push(tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done
|
||||||
|
Some(new_config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `PopUp`
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct PopUp {
|
||||||
|
pub title: String,
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PopUp {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(title: &str, body: &str) -> PopUp {
|
||||||
|
PopUp {
|
||||||
|
title: String::from(title),
|
||||||
|
body: String::from(body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Tool`
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Tool {
|
||||||
|
name: String,
|
||||||
|
command: String,
|
||||||
|
args: Option<Vec<String>>,
|
||||||
|
use_conemu: bool,
|
||||||
|
separator: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `MenuEntry`
|
||||||
|
#[derive(Default, Debug, Clone, PartialEq)]
|
||||||
|
pub struct MenuEntry {
|
||||||
|
pub name: String,
|
||||||
|
pub command: String,
|
||||||
|
pub args: Vec<String>,
|
||||||
|
pub use_conemu: bool,
|
||||||
|
pub separator: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuEntry {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(
|
||||||
|
name: &str,
|
||||||
|
command: &str,
|
||||||
|
args: Option<Vec<String>>,
|
||||||
|
use_conemu: bool,
|
||||||
|
separator: bool,
|
||||||
|
) -> MenuEntry {
|
||||||
|
let mut my_args = Vec::new();
|
||||||
|
if let Some(a) = args {
|
||||||
|
my_args.clone_from(&a);
|
||||||
|
}
|
||||||
|
MenuEntry {
|
||||||
|
name: String::from(name),
|
||||||
|
command: String::from(command),
|
||||||
|
args: my_args,
|
||||||
|
use_conemu,
|
||||||
|
separator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `StatefulList`
|
||||||
|
#[derive(Default, Debug, Clone, PartialEq)]
|
||||||
|
pub struct StatefulList<T> {
|
||||||
|
pub state: ListState,
|
||||||
|
pub items: Vec<T>,
|
||||||
|
pub last_selected: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> StatefulList<T> {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> StatefulList<T> {
|
||||||
|
StatefulList {
|
||||||
|
state: ListState::default(),
|
||||||
|
items: Vec::new(),
|
||||||
|
last_selected: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_selected(&self) -> Option<&T> {
|
||||||
|
if let Some(i) = self.state.selected() {
|
||||||
|
self.items.get(i)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_selected(&mut self) -> Option<T> {
|
||||||
|
if let Some(i) = self.state.selected() {
|
||||||
|
Some(self.items[i].clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_first_item(&mut self) {
|
||||||
|
if self.items.is_empty() {
|
||||||
|
self.state.select(None);
|
||||||
|
} else {
|
||||||
|
self.state.select(Some(0));
|
||||||
|
}
|
||||||
|
self.last_selected = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_items(&mut self, items: Vec<T>) {
|
||||||
|
// Clear list and rebuild with provided items
|
||||||
|
self.items.clear();
|
||||||
|
for item in items {
|
||||||
|
self.items.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset state and select first item (if available)
|
||||||
|
self.state = ListState::default();
|
||||||
|
self.select_first_item();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
if self.items.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = match self.state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i >= self.items.len() - 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => self.last_selected.unwrap_or(0),
|
||||||
|
};
|
||||||
|
self.state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
if self.items.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = match self.state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i == 0 {
|
||||||
|
self.items.len() - 1
|
||||||
|
} else {
|
||||||
|
i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => self.last_selected.unwrap_or(0),
|
||||||
|
};
|
||||||
|
self.state.select(Some(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Application.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct App {
|
||||||
|
pub config: Config,
|
||||||
|
pub main_menu: StatefulList<MenuEntry>,
|
||||||
|
pub popup: Option<PopUp>,
|
||||||
|
pub quit_reason: QuitReason,
|
||||||
|
pub running: bool,
|
||||||
|
pub thread_pool: Vec<JoinHandle<Result<Output, io::Error>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for App {
|
||||||
|
fn default() -> Self {
|
||||||
|
let config = Config::load();
|
||||||
|
Self {
|
||||||
|
config: config.unwrap(),
|
||||||
|
running: true,
|
||||||
|
quit_reason: QuitReason::Exit,
|
||||||
|
main_menu: StatefulList::new(),
|
||||||
|
popup: None,
|
||||||
|
thread_pool: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
/// Constructs a new instance of [`App`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut app = Self::default();
|
||||||
|
|
||||||
|
// Add MenuEntries
|
||||||
|
for tool in &app.config.tools {
|
||||||
|
app.main_menu.items.push(MenuEntry::new(
|
||||||
|
&tool.name,
|
||||||
|
&tool.command,
|
||||||
|
tool.args.clone(),
|
||||||
|
tool.use_conemu,
|
||||||
|
tool.separator,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
app.main_menu.select_first_item();
|
||||||
|
|
||||||
|
// Done
|
||||||
|
app
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles the tick event of the terminal.
|
||||||
|
pub fn tick(&self) {}
|
||||||
|
|
||||||
|
/// Actually exit application
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic if wpeutil fails to reboot or shutdown
|
||||||
|
pub fn exit(&self) -> Result<(), &'static str> {
|
||||||
|
let mut argument: Option<String> = None;
|
||||||
|
match self.quit_reason {
|
||||||
|
QuitReason::Exit => {}
|
||||||
|
QuitReason::Poweroff => argument = Some(String::from("shutdown")),
|
||||||
|
QuitReason::Restart => argument = Some(String::from("reboot")),
|
||||||
|
}
|
||||||
|
if let Some(a) = argument {
|
||||||
|
Command::new("wpeutil")
|
||||||
|
.arg(a)
|
||||||
|
.output()
|
||||||
|
.expect("Failed to run exit command");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set running to false to quit the application.
|
||||||
|
pub fn quit(&mut self, reason: QuitReason) {
|
||||||
|
self.running = false;
|
||||||
|
self.quit_reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic if command fails to run
|
||||||
|
pub fn open_terminal(&mut self) {
|
||||||
|
Command::new("cmd.exe")
|
||||||
|
.arg("-new_console:n")
|
||||||
|
.output()
|
||||||
|
.expect("Failed to run command");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic if menu entry isn't found
|
||||||
|
pub fn run_tool(&mut self) {
|
||||||
|
// Command
|
||||||
|
let tool: &MenuEntry;
|
||||||
|
if let Some(index) = self.main_menu.state.selected() {
|
||||||
|
tool = &self.main_menu.items[index];
|
||||||
|
} else {
|
||||||
|
self.popup = Some(PopUp::new(
|
||||||
|
"Failed to find menu entry",
|
||||||
|
"Check for an updated version of Deja-Vu",
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let command = if tool.use_conemu {
|
||||||
|
self.config.con_emu.clone()
|
||||||
|
} else {
|
||||||
|
tool.command.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Separators
|
||||||
|
if tool.separator {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args
|
||||||
|
let mut args = tool.args.clone();
|
||||||
|
if tool.use_conemu {
|
||||||
|
args.insert(0, tool.command.clone());
|
||||||
|
args.push(String::from("-new_console:n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check path
|
||||||
|
let command_path = PathBuf::from(&command);
|
||||||
|
if let Ok(true) = command_path.try_exists() {
|
||||||
|
// File path exists
|
||||||
|
} else {
|
||||||
|
// File path doesn't exist or is a broken symlink/etc
|
||||||
|
// The latter case would be Ok(false) rather than Err(_)
|
||||||
|
self.popup = Some(PopUp::new("Tool Missing", &format!("Tool path: {command}")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run
|
||||||
|
// TODO: This really needs refactored to use channels so we can properly check if the
|
||||||
|
// command fails.
|
||||||
|
let new_thread = thread::spawn(move || Command::new(command_path).args(args).output());
|
||||||
|
self.thread_pool.push(new_thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
116
pe_menu/src/event.rs
Normal file
116
pe_menu/src/event.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crossterm::event::{Event as CrosstermEvent, KeyEvent, MouseEvent};
|
||||||
|
use futures::{FutureExt, StreamExt};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::app::AppResult;
|
||||||
|
|
||||||
|
/// Terminal events.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Event {
|
||||||
|
/// Terminal tick.
|
||||||
|
Tick,
|
||||||
|
/// Key press.
|
||||||
|
Key(KeyEvent),
|
||||||
|
/// Mouse click/scroll.
|
||||||
|
Mouse(MouseEvent),
|
||||||
|
/// Terminal resize.
|
||||||
|
Resize(u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Terminal event handler.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Handler {
|
||||||
|
/// Event sender channel.
|
||||||
|
sender: mpsc::UnboundedSender<Event>,
|
||||||
|
/// Event receiver channel.
|
||||||
|
receiver: mpsc::UnboundedReceiver<Event>,
|
||||||
|
/// Event handler thread.
|
||||||
|
handler: tokio::task::JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler {
|
||||||
|
/// Constructs a new instance of [`Handler`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic if `sender_clone ` doesn't unwrap
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(tick_rate: u64) -> Self {
|
||||||
|
let tick_rate = Duration::from_millis(tick_rate);
|
||||||
|
let (sender, receiver) = mpsc::unbounded_channel();
|
||||||
|
let sender_clone = sender.clone();
|
||||||
|
let handler = tokio::spawn(async move {
|
||||||
|
let mut reader = crossterm::event::EventStream::new();
|
||||||
|
let mut tick = tokio::time::interval(tick_rate);
|
||||||
|
loop {
|
||||||
|
let tick_delay = tick.tick();
|
||||||
|
let crossterm_event = reader.next().fuse();
|
||||||
|
tokio::select! {
|
||||||
|
() = sender_clone.closed() => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ = tick_delay => {
|
||||||
|
sender_clone.send(Event::Tick).unwrap();
|
||||||
|
}
|
||||||
|
Some(Ok(evt)) = crossterm_event => {
|
||||||
|
match evt {
|
||||||
|
CrosstermEvent::Key(key) => {
|
||||||
|
if key.kind == crossterm::event::KeyEventKind::Press {
|
||||||
|
sender_clone.send(Event::Key(key)).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CrosstermEvent::Mouse(mouse) => {
|
||||||
|
sender_clone.send(Event::Mouse(mouse)).unwrap();
|
||||||
|
},
|
||||||
|
CrosstermEvent::Resize(x, y) => {
|
||||||
|
sender_clone.send(Event::Resize(x, y)).unwrap();
|
||||||
|
},
|
||||||
|
CrosstermEvent::FocusGained | CrosstermEvent::FocusLost | CrosstermEvent::Paste(_) => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
sender,
|
||||||
|
receiver,
|
||||||
|
handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive the next event from the handler thread.
|
||||||
|
///
|
||||||
|
/// This function will always block the current thread if
|
||||||
|
/// there is no data available and it's possible for more data to be sent.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return error if a event is not found
|
||||||
|
pub async fn next(&mut self) -> AppResult<Event> {
|
||||||
|
self.receiver
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.ok_or(Box::new(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"This is an IO error",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
61
pe_menu/src/handler.rs
Normal file
61
pe_menu/src/handler.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
use crate::app::{App, QuitReason};
|
||||||
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
|
|
||||||
|
/// Handles the key events and updates the state of [`App`].
|
||||||
|
pub fn handle_key_events(key_event: KeyEvent, app: &mut App) {
|
||||||
|
match key_event.code {
|
||||||
|
KeyCode::F(5) => app.quit(QuitReason::Exit),
|
||||||
|
KeyCode::Char('p' | 'P') => app.quit(QuitReason::Poweroff),
|
||||||
|
KeyCode::Char('r' | 'R') => app.quit(QuitReason::Restart),
|
||||||
|
KeyCode::Char('t' | 'T') => app.open_terminal(),
|
||||||
|
KeyCode::Up => {
|
||||||
|
if app.popup.is_none() {
|
||||||
|
app.main_menu.previous();
|
||||||
|
if let Some(e) = app.main_menu.get_selected() {
|
||||||
|
if e.separator {
|
||||||
|
// Skip over separators
|
||||||
|
app.main_menu.previous();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
if app.popup.is_none() {
|
||||||
|
app.main_menu.next();
|
||||||
|
if let Some(e) = app.main_menu.get_selected() {
|
||||||
|
if e.separator {
|
||||||
|
// Skip over separators
|
||||||
|
app.main_menu.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
if app.popup.is_some() {
|
||||||
|
// Clear popup and return to main menu
|
||||||
|
app.popup = None;
|
||||||
|
} else {
|
||||||
|
app.run_tool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Esc | KeyCode::Char('q' | 'Q') => {
|
||||||
|
app.popup = None;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
pe_menu/src/lib.rs
Normal file
29
pe_menu/src/lib.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
/// Application.
|
||||||
|
pub mod app;
|
||||||
|
|
||||||
|
/// Terminal events handler.
|
||||||
|
pub mod event;
|
||||||
|
|
||||||
|
/// Widget renderer.
|
||||||
|
pub mod ui;
|
||||||
|
|
||||||
|
/// Terminal user interface.
|
||||||
|
pub mod tui;
|
||||||
|
|
||||||
|
/// Event handler.
|
||||||
|
pub mod handler;
|
||||||
52
pe_menu/src/main.rs
Normal file
52
pe_menu/src/main.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
use pe_menu::app::{App, AppResult};
|
||||||
|
use pe_menu::event::{Event, Handler};
|
||||||
|
use pe_menu::handler::handle_key_events;
|
||||||
|
use pe_menu::tui::Tui;
|
||||||
|
use ratatui::backend::CrosstermBackend;
|
||||||
|
use ratatui::Terminal;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> AppResult<()> {
|
||||||
|
// Create an application.
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
// Initialize the terminal user interface.
|
||||||
|
let backend = CrosstermBackend::new(io::stderr());
|
||||||
|
let terminal = Terminal::new(backend)?;
|
||||||
|
let events = Handler::new(250);
|
||||||
|
let mut tui = Tui::new(terminal, events);
|
||||||
|
tui.init()?;
|
||||||
|
|
||||||
|
// Start the main loop.
|
||||||
|
while app.running {
|
||||||
|
// Render the user interface.
|
||||||
|
tui.draw(&mut app)?;
|
||||||
|
// Handle events.
|
||||||
|
match tui.events.next().await? {
|
||||||
|
Event::Tick => app.tick(),
|
||||||
|
Event::Key(key_event) => handle_key_events(key_event, &mut app),
|
||||||
|
Event::Mouse(_) | Event::Resize(_, _) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit the user interface.
|
||||||
|
tui.exit()?;
|
||||||
|
app.exit()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
111
pe_menu/src/tui.rs
Normal file
111
pe_menu/src/tui.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
use crate::app::{App, AppResult};
|
||||||
|
use crate::event::Handler;
|
||||||
|
use crate::ui;
|
||||||
|
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
||||||
|
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
|
||||||
|
use ratatui::backend::Backend;
|
||||||
|
use ratatui::Terminal;
|
||||||
|
use std::io;
|
||||||
|
use std::panic;
|
||||||
|
|
||||||
|
/// Representation of a terminal user interface.
|
||||||
|
///
|
||||||
|
/// It is responsible for setting up the terminal,
|
||||||
|
/// initializing the interface and handling the draw events.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Tui<B: Backend> {
|
||||||
|
/// Interface to the Terminal.
|
||||||
|
terminal: Terminal<B>,
|
||||||
|
/// Terminal event handler.
|
||||||
|
pub events: Handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Backend> Tui<B> {
|
||||||
|
/// Constructs a new instance of [`Tui`].
|
||||||
|
pub fn new(terminal: Terminal<B>, events: Handler) -> Self {
|
||||||
|
Self { terminal, events }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the terminal interface.
|
||||||
|
///
|
||||||
|
/// It enables the raw mode and sets terminal properties.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return error if `enable_raw_mode` fails
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic if `reset` fails
|
||||||
|
pub fn init(&mut self) -> AppResult<()> {
|
||||||
|
terminal::enable_raw_mode()?;
|
||||||
|
crossterm::execute!(io::stderr(), EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
|
|
||||||
|
// Define a custom panic hook to reset the terminal properties.
|
||||||
|
// This way, you won't have your terminal messed up if an unexpected error happens.
|
||||||
|
let panic_hook = panic::take_hook();
|
||||||
|
panic::set_hook(Box::new(move |panic| {
|
||||||
|
Self::reset().expect("failed to reset the terminal");
|
||||||
|
panic_hook(panic);
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.terminal.hide_cursor()?;
|
||||||
|
self.terminal.clear()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Draw`] the terminal interface by [`rendering`] the widgets.
|
||||||
|
///
|
||||||
|
/// [`Draw`]: ratatui::Terminal::draw
|
||||||
|
/// [`rendering`]: crate::ui::render
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return error if `draw` fails
|
||||||
|
pub fn draw(&mut self, app: &mut App) -> AppResult<()> {
|
||||||
|
self.terminal.draw(|frame| ui::render(app, frame))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets the terminal interface.
|
||||||
|
///
|
||||||
|
/// This function is also used for the panic hook to revert
|
||||||
|
/// the terminal properties if unexpected errors occur.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return error if `disable_raw_mode` fails
|
||||||
|
fn reset() -> AppResult<()> {
|
||||||
|
terminal::disable_raw_mode()?;
|
||||||
|
crossterm::execute!(io::stderr(), LeaveAlternateScreen, DisableMouseCapture)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exits the terminal interface.
|
||||||
|
///
|
||||||
|
/// It disables the raw mode and reverts back the terminal properties.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return error if either `reset` or `show_cursor` fails
|
||||||
|
pub fn exit(&mut self) -> AppResult<()> {
|
||||||
|
Self::reset()?;
|
||||||
|
self.terminal.show_cursor()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
127
pe_menu/src/ui.rs
Normal file
127
pe_menu/src/ui.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
use crate::app::App;
|
||||||
|
use ratatui::{
|
||||||
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
style::{Color, Style, Stylize},
|
||||||
|
text::{Line, Span},
|
||||||
|
widgets::{Block, Borders, Clear, HighlightSpacing, List, ListItem, Padding, Paragraph, Wrap},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Renders the user interface widgets.
|
||||||
|
pub fn render(app: &mut App, frame: &mut Frame) {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Min(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
])
|
||||||
|
.split(frame.size());
|
||||||
|
|
||||||
|
// Title Block
|
||||||
|
let title_text = Span::styled("WizardKit: PE Menu", Style::default().fg(Color::LightCyan));
|
||||||
|
let title = Paragraph::new(Line::from(title_text).centered())
|
||||||
|
.block(Block::default().borders(Borders::ALL));
|
||||||
|
frame.render_widget(title, chunks[0]);
|
||||||
|
|
||||||
|
// Main Block
|
||||||
|
let main_chunk = centered_rect(65, 90, chunks[1]);
|
||||||
|
render_main_pane(frame, app, main_chunk);
|
||||||
|
|
||||||
|
// Bottom Block
|
||||||
|
let footer_text = Span::styled(
|
||||||
|
"(Enter) to select / (p) to poweroff / (r) to restart / (t) for terminal",
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
);
|
||||||
|
let footer = Paragraph::new(Line::from(footer_text).centered())
|
||||||
|
.block(Block::default().borders(Borders::ALL));
|
||||||
|
frame.render_widget(footer, chunks[2]);
|
||||||
|
|
||||||
|
// Popup blocks
|
||||||
|
if app.popup.is_some() {
|
||||||
|
render_popup_pane(frame, app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 render_main_pane(frame: &mut Frame, app: &mut App, chunk: Rect) {
|
||||||
|
let mut list_items = Vec::<ListItem>::new();
|
||||||
|
for entry in &app.main_menu.items {
|
||||||
|
let text = if entry.separator {
|
||||||
|
if entry.name.is_empty() {
|
||||||
|
String::from("....................")
|
||||||
|
} else {
|
||||||
|
entry.name.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entry.name.clone()
|
||||||
|
};
|
||||||
|
list_items.push(ListItem::new(format!(" {text}\n\n\n")));
|
||||||
|
}
|
||||||
|
let list = List::new(list_items)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.padding(Padding::new(20, 20, 5, 5)),
|
||||||
|
)
|
||||||
|
.highlight_spacing(HighlightSpacing::Always)
|
||||||
|
.highlight_style(Style::new().green().bold())
|
||||||
|
.highlight_symbol(" --> ")
|
||||||
|
.repeat_highlight_symbol(false);
|
||||||
|
frame.render_stateful_widget(list, chunk, &mut app.main_menu.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_popup_pane(frame: &mut Frame, app: &mut App) {
|
||||||
|
let popup_block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().red().bold());
|
||||||
|
if let Some(popup) = &app.popup {
|
||||||
|
let scan_paragraph = Paragraph::new(vec![
|
||||||
|
Line::from(Span::raw(&popup.title)),
|
||||||
|
Line::default(),
|
||||||
|
Line::from(Span::raw(&popup.body)),
|
||||||
|
])
|
||||||
|
.block(popup_block)
|
||||||
|
.centered()
|
||||||
|
.wrap(Wrap { trim: false });
|
||||||
|
let area = centered_rect(60, 25, frame.size());
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
frame.render_widget(scan_paragraph, area);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue