Compare commits
No commits in common. "cf87ac32af095a8be4cece0e28b2038338107009" and "7e12223344238ece92a1f703d22a89c705afc70a" have entirely different histories.
cf87ac32af
...
7e12223344
11 changed files with 10 additions and 796 deletions
32
Cargo.lock
generated
32
Cargo.lock
generated
|
|
@ -2661,9 +2661,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.23.0"
|
version = "3.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.3.2",
|
"getrandom 0.3.2",
|
||||||
|
|
@ -3126,28 +3126,6 @@ dependencies = [
|
||||||
"wit-bindgen-rt",
|
"wit-bindgen-rt",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "win-installer"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"clap",
|
|
||||||
"color-eyre",
|
|
||||||
"core",
|
|
||||||
"crossterm",
|
|
||||||
"futures",
|
|
||||||
"ratatui",
|
|
||||||
"serde",
|
|
||||||
"tempfile",
|
|
||||||
"tokio",
|
|
||||||
"toml",
|
|
||||||
"tracing",
|
|
||||||
"tracing-error",
|
|
||||||
"tracing-subscriber",
|
|
||||||
"vergen-gix",
|
|
||||||
"xml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
@ -3376,12 +3354,6 @@ version = "0.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xml"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "838dd679b10a4180431ce7c2caa6e5585a7c8f63154c19ae99345126572e80cc"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaml-rust2"
|
name = "yaml-rust2"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,6 @@
|
||||||
# along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
# along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["core", "boot_diags", "deja_vu", "pe_menu", "win_installer"]
|
members = ["core", "boot_diags", "deja_vu", "pe_menu"]
|
||||||
default-members = ["core", "boot_diags", "deja_vu", "pe_menu", "win_installer"]
|
default-members = ["core", "boot_diags", "deja_vu", "pe_menu"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
|
||||||
|
|
@ -657,10 +657,7 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::PreClone
|
| Mode::PreClone
|
||||||
| Mode::PostClone
|
| Mode::PostClone
|
||||||
| Mode::ScanWinImages
|
| Mode::SelectTableType => {
|
||||||
| Mode::SelectTableType
|
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetUserName => {
|
|
||||||
panic!("This shouldn't happen?")
|
panic!("This shouldn't happen?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -773,10 +770,7 @@ fn build_left_items(app: &App) -> Action {
|
||||||
| Mode::Confirm
|
| Mode::Confirm
|
||||||
| Mode::PreClone
|
| Mode::PreClone
|
||||||
| Mode::Clone
|
| Mode::Clone
|
||||||
| Mode::PostClone
|
| Mode::PostClone => {
|
||||||
| Mode::ScanWinImages
|
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetUserName => {
|
|
||||||
panic!("This shouldn't happen?")
|
panic!("This shouldn't happen?")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,6 @@ pub enum Mode {
|
||||||
Clone,
|
Clone,
|
||||||
SelectParts,
|
SelectParts,
|
||||||
PostClone,
|
PostClone,
|
||||||
// Windows Installer
|
|
||||||
ScanWinImages,
|
|
||||||
SelectWinImage,
|
|
||||||
SetUserName,
|
|
||||||
// WinPE
|
// WinPE
|
||||||
PEMenu,
|
PEMenu,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,10 +129,7 @@ impl App {
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::Process
|
| Mode::Process
|
||||||
| Mode::ScanWinImages
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetBootMode
|
|
||||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,10 +154,7 @@ impl App {
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::Process
|
| Mode::Process
|
||||||
| Mode::ScanWinImages
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetBootMode
|
|
||||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if new_mode == self.cur_mode {
|
if new_mode == self.cur_mode {
|
||||||
|
|
@ -640,10 +634,7 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::Process
|
| Mode::Process
|
||||||
| Mode::ScanWinImages
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetBootMode
|
|
||||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -716,10 +707,7 @@ fn build_left_items(app: &App, cur_mode: Mode) -> Action {
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::Process
|
| Mode::Process
|
||||||
| Mode::ScanWinImages
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetBootMode
|
|
||||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
|
||||||
};
|
};
|
||||||
Action::UpdateLeft(title, labels, items, select_type)
|
Action::UpdateLeft(title, labels, items, select_type)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
# 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 = "win-installer"
|
|
||||||
authors = ["2Shirt <2xShirt@gmail.com>"]
|
|
||||||
edition = "2024"
|
|
||||||
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"] }
|
|
||||||
tempfile = "3.23.0"
|
|
||||||
xml = "1.1.0"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
anyhow = "1.0.86"
|
|
||||||
vergen-gix = { version = "1.0.0", features = ["build", "cargo"] }
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// 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 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()
|
|
||||||
}
|
|
||||||
|
|
@ -1,346 +0,0 @@
|
||||||
// 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 core::{
|
|
||||||
action::Action,
|
|
||||||
components::{Component, footer::Footer, left::Left, popup, right::Right, title::Title},
|
|
||||||
config::Config,
|
|
||||||
state::Mode,
|
|
||||||
tasks::{TaskType, Tasks},
|
|
||||||
tui::{Event, Tui},
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
iter::zip,
|
|
||||||
path::PathBuf,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use color_eyre::Result;
|
|
||||||
use ratatui::{
|
|
||||||
crossterm::event::KeyEvent,
|
|
||||||
layout::{Constraint, Direction, Layout},
|
|
||||||
prelude::Rect,
|
|
||||||
};
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tracing::{debug, info};
|
|
||||||
|
|
||||||
use crate::state::State;
|
|
||||||
|
|
||||||
pub struct App<'a> {
|
|
||||||
// TUI
|
|
||||||
action_rx: mpsc::UnboundedReceiver<Action>,
|
|
||||||
action_tx: mpsc::UnboundedSender<Action>,
|
|
||||||
components: Vec<Box<dyn Component>>,
|
|
||||||
config: Config,
|
|
||||||
frame_rate: f64,
|
|
||||||
last_tick_key_events: Vec<KeyEvent>,
|
|
||||||
should_quit: bool,
|
|
||||||
should_suspend: bool,
|
|
||||||
tick_rate: f64,
|
|
||||||
// App
|
|
||||||
state: State<'a>,
|
|
||||||
mode: Mode,
|
|
||||||
tasks: Tasks,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App<'_> {
|
|
||||||
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> {
|
|
||||||
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());
|
|
||||||
Ok(Self {
|
|
||||||
// TUI
|
|
||||||
action_rx,
|
|
||||||
action_tx,
|
|
||||||
components: vec![
|
|
||||||
Box::new(Title::new("Windows Install Tool")),
|
|
||||||
Box::new(Left::new()),
|
|
||||||
Box::new(Right::new()),
|
|
||||||
Box::new(Footer::new()),
|
|
||||||
Box::new(popup::Popup::new()),
|
|
||||||
],
|
|
||||||
config: Config::new()?,
|
|
||||||
frame_rate,
|
|
||||||
last_tick_key_events: Vec::new(),
|
|
||||||
should_quit: false,
|
|
||||||
should_suspend: false,
|
|
||||||
tick_rate,
|
|
||||||
// App
|
|
||||||
mode: Mode::default(),
|
|
||||||
state: State::new(disk_list_arc),
|
|
||||||
tasks,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_mode(&mut self) -> Mode {
|
|
||||||
match self.mode {
|
|
||||||
Mode::Home | Mode::InstallDrivers => Mode::ScanDisks,
|
|
||||||
Mode::ScanDisks => Mode::SelectDisks,
|
|
||||||
Mode::SelectDisks => Mode::SelectTableType,
|
|
||||||
Mode::SelectTableType => Mode::ScanWinImages,
|
|
||||||
Mode::ScanWinImages => Mode::SelectWinImage,
|
|
||||||
Mode::SelectWinImage => Mode::SetUserName,
|
|
||||||
Mode::SetUserName => Mode::Confirm,
|
|
||||||
Mode::Confirm => Mode::Process, // i.e. format, apply, etc
|
|
||||||
Mode::Process | Mode::Done => Mode::Done,
|
|
||||||
Mode::Failed => Mode::Failed,
|
|
||||||
// Invalid States
|
|
||||||
Mode::BootDiags
|
|
||||||
| Mode::BootScan
|
|
||||||
| Mode::BootSetup
|
|
||||||
| Mode::Clone
|
|
||||||
| Mode::DiagMenu
|
|
||||||
| Mode::InjectDrivers
|
|
||||||
| Mode::LogView
|
|
||||||
| Mode::PEMenu
|
|
||||||
| Mode::PreClone
|
|
||||||
| Mode::PostClone
|
|
||||||
| Mode::SelectParts
|
|
||||||
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
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.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)
|
|
||||||
self.tasks.poll()?;
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
// if let Some(tool) = self.list.get_selected()
|
|
||||||
// && tool.separator
|
|
||||||
// {
|
|
||||||
// // Skip over separator
|
|
||||||
// self.list.previous();
|
|
||||||
// if let Some(index) = self.list.selected() {
|
|
||||||
// self.action_tx.send(Action::Highlight(index))?;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Action::KeyDown => {
|
|
||||||
// self.list.next();
|
|
||||||
// if let Some(tool) = self.list.get_selected()
|
|
||||||
// && tool.separator
|
|
||||||
// {
|
|
||||||
// // Skip over separator
|
|
||||||
// self.list.next();
|
|
||||||
// if let Some(index) = self.list.selected() {
|
|
||||||
// self.action_tx.send(Action::Highlight(index))?;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
Action::Error(ref msg) => {
|
|
||||||
self.action_tx
|
|
||||||
.send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?;
|
|
||||||
self.action_tx.send(Action::SetMode(Mode::Failed))?;
|
|
||||||
}
|
|
||||||
// Action::Process => {
|
|
||||||
// // Run selected tool
|
|
||||||
// if let Some(tool) = self.list.get_selected() {
|
|
||||||
// info!("Run tool: {:?}", &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;
|
|
||||||
if self.mode == Mode::ScanDisks {
|
|
||||||
self.state.reset();
|
|
||||||
}
|
|
||||||
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))?;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
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 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, 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<Rect> {
|
|
||||||
let mut chunks: Vec<Rect> = 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
// 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 clap::Parser;
|
|
||||||
use color_eyre::Result;
|
|
||||||
|
|
||||||
use crate::app::App;
|
|
||||||
|
|
||||||
mod app;
|
|
||||||
mod state;
|
|
||||||
mod wim;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
// 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::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use core::system::{disk::Disk, drivers};
|
|
||||||
|
|
||||||
use crate::wim::WimSources;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct State<'a> {
|
|
||||||
pub disk_index_dest: Option<usize>,
|
|
||||||
pub disk_list: Arc<Mutex<Vec<Disk>>>,
|
|
||||||
pub driver: Option<drivers::Driver>,
|
|
||||||
pub driver_list: Vec<drivers::Driver>,
|
|
||||||
pub part_index_boot: Option<usize>,
|
|
||||||
pub wim_file_index: Option<usize>,
|
|
||||||
pub wim_image_index: Option<usize>,
|
|
||||||
pub wim_sources: Arc<Mutex<WimSources<'a>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State<'_> {
|
|
||||||
pub fn new(disk_list: Arc<Mutex<Vec<Disk>>>) -> Self {
|
|
||||||
State {
|
|
||||||
disk_list,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.disk_index_dest = None;
|
|
||||||
self.part_index_boot = None;
|
|
||||||
self.wim_file_index = None;
|
|
||||||
self.wim_image_index = None;
|
|
||||||
if let Ok(mut sources) = self.wim_sources.lock() {
|
|
||||||
sources.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_drivers(&mut self) {
|
|
||||||
self.driver_list = drivers::scan();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,224 +0,0 @@
|
||||||
// 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::{
|
|
||||||
collections::HashMap, fmt, fs::File, io::BufReader, path::Path, process::Command,
|
|
||||||
sync::LazyLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
use xml::reader::{EventReader, XmlEvent};
|
|
||||||
|
|
||||||
static WIN_BUILDS: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
|
|
||||||
HashMap::from([
|
|
||||||
// Windows 10
|
|
||||||
("10240", "1507 \"Threshold 1\""),
|
|
||||||
("10586", "1511 \"Threshold 2\""),
|
|
||||||
("14393", "1607 \"Redstone 1\""),
|
|
||||||
("15063", "1703 \"Redstone 2\""),
|
|
||||||
("16299", "1709 \"Redstone 3\""),
|
|
||||||
("17134", "1803 \"Redstone 4\""),
|
|
||||||
("17763", "1809 \"Redstone 5\""),
|
|
||||||
("18362", "1903 / 19H1"),
|
|
||||||
("18363", "1909 / 19H2"),
|
|
||||||
("19041", "2004 / 20H1"),
|
|
||||||
("19042", "20H2"),
|
|
||||||
("19043", "21H1"),
|
|
||||||
("19044", "21H2"),
|
|
||||||
("19045", "22H2"),
|
|
||||||
// Windows 11
|
|
||||||
("22000", "21H2"),
|
|
||||||
("22621", "22H2"),
|
|
||||||
("22631", "23H2"),
|
|
||||||
("26100", "24H2"),
|
|
||||||
("26200", "25H2"),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct WimFile<'a> {
|
|
||||||
path: &'a Path,
|
|
||||||
images: Vec<WimImage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct WimImage {
|
|
||||||
build: String,
|
|
||||||
index: String,
|
|
||||||
name: String,
|
|
||||||
spbuild: String,
|
|
||||||
version: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WimImage {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.build.clear();
|
|
||||||
self.index.clear();
|
|
||||||
self.name.clear();
|
|
||||||
self.spbuild.clear();
|
|
||||||
self.version.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for WimImage {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
// Windows 11 Home (24H2, 26100.xxxx)
|
|
||||||
let s = if self.version.is_empty() {
|
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
format!("{}, ", self.version)
|
|
||||||
};
|
|
||||||
write!(f, "{} ({}{}.{})", self.name, s, self.build, self.spbuild)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct WimSources<'a> {
|
|
||||||
pub local: Vec<WimFile<'a>>,
|
|
||||||
pub network: Vec<WimFile<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WimSources<'_> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.local.clear();
|
|
||||||
self.network.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_wim_xml(wim_file: &str) -> std::io::Result<File> {
|
|
||||||
let tmp_file = NamedTempFile::new()?;
|
|
||||||
let _ = Command::new("wiminfo")
|
|
||||||
.args([
|
|
||||||
wim_file,
|
|
||||||
"--extract-xml",
|
|
||||||
tmp_file.path().as_os_str().to_str().unwrap(),
|
|
||||||
])
|
|
||||||
.output()
|
|
||||||
.expect("Failed to extract XML data");
|
|
||||||
let file = File::open(tmp_file.path())?;
|
|
||||||
|
|
||||||
Ok(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_wim_file(wim_file: &str) -> std::io::Result<WimFile<'_>> {
|
|
||||||
let mut wim_images: Vec<WimImage> = Vec::new();
|
|
||||||
let wim_path = Path::new(wim_file);
|
|
||||||
if !wim_path.exists() {
|
|
||||||
return Err(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::NotFound,
|
|
||||||
"Failed to read WIM file",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let xml_file = get_wim_xml(wim_file).expect("Failed to open XML file");
|
|
||||||
let file = BufReader::new(xml_file);
|
|
||||||
|
|
||||||
let mut current_element = String::new();
|
|
||||||
let mut image = WimImage::new();
|
|
||||||
|
|
||||||
let parser = EventReader::new(file);
|
|
||||||
let mut depth = 0;
|
|
||||||
for e in parser {
|
|
||||||
match e {
|
|
||||||
Ok(XmlEvent::StartElement {
|
|
||||||
name, attributes, ..
|
|
||||||
}) => {
|
|
||||||
println!("{:spaces$}+{name}", "", spaces = depth * 2);
|
|
||||||
depth += 1;
|
|
||||||
current_element = name.local_name.to_uppercase();
|
|
||||||
|
|
||||||
if current_element == "IMAGE" {
|
|
||||||
// Update index
|
|
||||||
if let Some(attr) = attributes.first()
|
|
||||||
&& attr.name.to_string().to_lowercase() == "index"
|
|
||||||
{
|
|
||||||
image.index = attr.value.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(XmlEvent::Characters(char_data)) => {
|
|
||||||
if current_element == "BUILD" {
|
|
||||||
let build = char_data.trim();
|
|
||||||
image.build = build.to_string();
|
|
||||||
image.version = WIN_BUILDS.get(build).map_or("", |v| v).to_string();
|
|
||||||
println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2);
|
|
||||||
}
|
|
||||||
if current_element == "NAME" {
|
|
||||||
image.name = char_data.trim().to_string();
|
|
||||||
println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2);
|
|
||||||
}
|
|
||||||
if current_element == "SPBUILD" {
|
|
||||||
image.spbuild = char_data.trim().to_string();
|
|
||||||
println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(XmlEvent::EndElement { name }) => {
|
|
||||||
depth -= 1;
|
|
||||||
println!("{:spaces$}-{name}", "", spaces = depth * 2);
|
|
||||||
|
|
||||||
if name.local_name.to_uppercase() == "IMAGE" {
|
|
||||||
// Append image to list
|
|
||||||
if !image.build.is_empty() && !image.name.is_empty() && !image.index.is_empty()
|
|
||||||
{
|
|
||||||
wim_images.push(image.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset image
|
|
||||||
image.reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error: {e}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let wim_file = WimFile {
|
|
||||||
path: wim_path,
|
|
||||||
images: wim_images,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(wim_file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn main() -> std::io::Result<()> {
|
|
||||||
// let mut sources = WimSources::new();
|
|
||||||
// if let Ok(wim_file) = parse_wim_file("./23H2.wim")
|
|
||||||
// && !wim_file.images.is_empty()
|
|
||||||
// {
|
|
||||||
// sources.local.push(wim_file);
|
|
||||||
// }
|
|
||||||
// if let Ok(wim_file) = parse_wim_file("./24H2.wim") {
|
|
||||||
// sources.local.push(wim_file);
|
|
||||||
// }
|
|
||||||
// dbg!(&sources);
|
|
||||||
// sources.local.iter().for_each(|f| {
|
|
||||||
// println!("-- {} --", f.path.to_string_lossy());
|
|
||||||
// f.images.iter().for_each(|i| {
|
|
||||||
// println!("* {i}");
|
|
||||||
// });
|
|
||||||
// println!();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
Loading…
Reference in a new issue