From 6938960f3e22f4cc5784a55ebab932a8c06401b4 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 1 Dec 2024 16:10:24 -0800 Subject: [PATCH 01/26] Add initial boot diagnostic sections --- deja_vu/config/config.json5 | 31 +++++++++++++++++++++++ deja_vu/src/app.rs | 43 +++++++++++++++++++++++++++++--- deja_vu/src/cli.rs | 15 ++++++++++- deja_vu/src/components/footer.rs | 8 ++++++ deja_vu/src/components/left.rs | 18 +++++++++++++ deja_vu/src/components/popup.rs | 2 +- deja_vu/src/components/right.rs | 2 +- deja_vu/src/components/title.rs | 17 ++++++------- deja_vu/src/main.rs | 2 +- 9 files changed, 121 insertions(+), 17 deletions(-) diff --git a/deja_vu/config/config.json5 b/deja_vu/config/config.json5 index bb9e684..c2cc437 100644 --- a/deja_vu/config/config.json5 +++ b/deja_vu/config/config.json5 @@ -87,5 +87,36 @@ "": "Quit", // Yet another way to quit "": "Suspend" // Suspend the application }, + // Diagnostic modes + "DiagMenu": { + "": "Quit", // Quit the application + "": "Quit", // Another way to quit + "": "Quit", // Yet another way to quit + "": "Suspend" // Suspend the application + }, + "BootDiags": { + "": "Quit", // Quit the application + "": "Quit", // Another way to quit + "": "Quit", // Yet another way to quit + "": "Suspend" // Suspend the application + }, + "BootSetup": { + "": "Quit", // Quit the application + "": "Quit", // Another way to quit + "": "Quit", // Yet another way to quit + "": "Suspend" // Suspend the application + }, + "InjectDrivers": { + "": "Quit", // Quit the application + "": "Quit", // Another way to quit + "": "Quit", // Yet another way to quit + "": "Suspend" // Suspend the application + }, + "ToggleSafeBoot": { + "": "Quit", // Quit the application + "": "Quit", // Another way to quit + "": "Quit", // Yet another way to quit + "": "Suspend" // Suspend the application + }, } } diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index f68b066..dc1eec7 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -31,6 +31,7 @@ use tracing::{debug, info}; use crate::{ action::Action, + cli, components::{ footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, title::Title, Component, }, @@ -57,6 +58,7 @@ pub struct App { should_suspend: bool, tick_rate: f64, // App + cli_cmd: cli::Command, cur_mode: Mode, disk_index_dest: Option, disk_index_source: Option, @@ -84,10 +86,16 @@ pub enum Mode { PostClone, Done, Failed, + // Diagnostic modes + DiagMenu, + BootDiags, + BootSetup, + InjectDrivers, + ToggleSafeBoot, } impl App { - pub fn new(tick_rate: f64, frame_rate: f64) -> Result { + pub fn new(cli_cmd: cli::Command, 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 mut tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); @@ -104,6 +112,7 @@ impl App { Box::new(Footer::new()), Box::new(popup::Popup::new()), ], + cli_cmd, config: Config::new()?, frame_rate, last_tick_key_events: Vec::new(), @@ -111,7 +120,10 @@ impl App { should_suspend: false, tick_rate, // App - cur_mode: Mode::ScanDisks, + cur_mode: match cli_cmd { + cli::Command::Clone => Mode::ScanDisks, + cli::Command::Diagnose => Mode::DiagMenu, + }, disk_index_dest: None, disk_index_source: None, disk_list: disk_list_arc, @@ -127,6 +139,7 @@ impl App { pub fn next_mode(&mut self) -> Option { let new_mode = match (self.prev_mode, self.cur_mode) { + // Clone states (_, Mode::InstallDrivers) => Mode::ScanDisks, (_, Mode::ScanDisks) => Mode::SelectDisks, (_, Mode::SelectDisks | Mode::SelectTableType | Mode::SelectParts) => { @@ -143,6 +156,17 @@ impl App { (Mode::SelectParts, Mode::Confirm) => Mode::PostClone, (_, Mode::PostClone | Mode::Done) => Mode::Done, (_, Mode::Failed) => Mode::Failed, + + // Diagnostic states + ( + _, + Mode::DiagMenu + | Mode::BootDiags + | Mode::BootSetup + | Mode::InjectDrivers + | Mode::ToggleSafeBoot, + ) => Mode::DiagMenu, + // Invalid states (_, Mode::Confirm) => panic!("This shouldn't happen."), }; @@ -247,7 +271,7 @@ impl App { // Inject driver(s) (if selected) if let Some(driver) = &self.driver { - if let Ok(task) = boot::inject_driver(&driver, &letter_os, &system32) { + if let Ok(task) = boot::inject_driver(driver, &letter_os, &system32) { self.tasks.add(task); } else { self.action_tx.send(Action::Error(format!( @@ -288,6 +312,19 @@ impl App { } let action_tx = self.action_tx.clone(); + // Init based on cli::Command + match self.cli_cmd { + cli::Command::Clone => { + action_tx.send(Action::SetMode(Mode::ScanDisks))?; + } + cli::Command::Diagnose => { + action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Boot Diagnostics?"), + ))?; + } + } + loop { self.handle_events(&mut tui).await?; self.handle_actions(&mut tui)?; diff --git a/deja_vu/src/cli.rs b/deja_vu/src/cli.rs index be4d597..8d886d2 100644 --- a/deja_vu/src/cli.rs +++ b/deja_vu/src/cli.rs @@ -13,13 +13,17 @@ // You should have received a copy of the GNU General Public License // along with Deja-vu. If not, see . // -use clap::Parser; +use clap::{Parser, Subcommand}; use crate::config::{get_config_dir, get_data_dir}; #[derive(Parser, Debug)] #[command(author, version = version(), about)] pub struct Cli { + /// App mode + #[command(subcommand)] + pub cli_cmd: Command, + /// Tick rate, i.e. number of ticks per second #[arg(short, long, value_name = "FLOAT", default_value_t = 4.0)] pub tick_rate: f64, @@ -29,6 +33,15 @@ pub struct Cli { pub frame_rate: f64, } +#[derive(Clone, Copy, Debug, Subcommand)] +pub enum Command { + /// Clone Windows from one disk to another + Clone, + + /// Diagnose Windows boot issues + Diagnose, +} + const VERSION_MESSAGE: &str = concat!( env!("CARGO_PKG_VERSION"), "-", diff --git a/deja_vu/src/components/footer.rs b/deja_vu/src/components/footer.rs index 1530cd7..6b92fac 100644 --- a/deja_vu/src/components/footer.rs +++ b/deja_vu/src/components/footer.rs @@ -53,6 +53,7 @@ impl Component for Footer { fn update(&mut self, action: Action) -> Result> { if let Action::SetMode(new_mode) = action { self.text = match new_mode { + // Clone modes Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { String::from("(q) to quit") } @@ -69,6 +70,13 @@ impl Component for Footer { Mode::Done | Mode::Failed | Mode::InstallDrivers => { String::from("(Enter) or (q) to quit") } + + // Diagnostic modes + Mode::DiagMenu + | Mode::BootDiags + | Mode::BootSetup + | Mode::InjectDrivers + | Mode::ToggleSafeBoot => String::from("(q) to quit"), } } Ok(None) diff --git a/deja_vu/src/components/left.rs b/deja_vu/src/components/left.rs index 8ccfa12..7b18095 100644 --- a/deja_vu/src/components/left.rs +++ b/deja_vu/src/components/left.rs @@ -218,6 +218,16 @@ impl Component for Left { self.title_text = String::from("Confirm Selections (Again)"); } (_, Mode::Done | Mode::Failed) => self.title_text = String::from("Done"), + // Diagnostic states + ( + _, + Mode::DiagMenu + | Mode::BootDiags + | Mode::BootSetup + | Mode::InjectDrivers + | Mode::ToggleSafeBoot, + ) => self.title_text = String::from("Boot Diagnostics"), + // Invalid states (_, Mode::Confirm) => panic!("This shouldn't happen."), } @@ -381,6 +391,14 @@ impl Component for Left { _ => panic!("This shouldn't happen."), } } + Mode::DiagMenu + | Mode::BootDiags + | Mode::BootSetup + | Mode::InjectDrivers + | Mode::ToggleSafeBoot => { + // Diagnostic modes + return Ok(()); + } } // Done diff --git a/deja_vu/src/components/popup.rs b/deja_vu/src/components/popup.rs index 933dbde..545f803 100644 --- a/deja_vu/src/components/popup.rs +++ b/deja_vu/src/components/popup.rs @@ -45,7 +45,7 @@ pub struct Popup { impl Popup { pub fn new() -> Self { Self { - popup_text: String::from("Scanning Disks..."), + popup_text: String::new(), ..Default::default() } } diff --git a/deja_vu/src/components/right.rs b/deja_vu/src/components/right.rs index d053b25..61814d3 100644 --- a/deja_vu/src/components/right.rs +++ b/deja_vu/src/components/right.rs @@ -86,7 +86,7 @@ impl Component for Right { }, Action::Process => { if self.prev_mode == Mode::SelectDisks && self.cur_mode == Mode::Confirm { - self.selected_disks = self.selections.clone(); + self.selected_disks.clone_from(&self.selections); } } Action::Select(one, two) => { diff --git a/deja_vu/src/components/title.rs b/deja_vu/src/components/title.rs index 1394186..01799ec 100644 --- a/deja_vu/src/components/title.rs +++ b/deja_vu/src/components/title.rs @@ -46,19 +46,16 @@ impl Component for Title { Ok(()) } - fn update(&mut self, action: Action) -> Result> { - match action { - _ => {} - } - Ok(None) - } + // fn update(&mut self, action: Action) -> Result> { + // match action { + // _ => {} + // } + // Ok(None) + // } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { // Title Block - let title_text = Span::styled( - "WizardKit: Clone Tool", - Style::default().fg(Color::LightCyan), - ); + let title_text = Span::styled("WizardKit: Deja-Vu", Style::default().fg(Color::LightCyan)); let title = Paragraph::new(Line::from(title_text).centered()) .block(Block::default().borders(Borders::ALL)); frame.render_widget(title, area); diff --git a/deja_vu/src/main.rs b/deja_vu/src/main.rs index 72a1b88..326069a 100644 --- a/deja_vu/src/main.rs +++ b/deja_vu/src/main.rs @@ -37,7 +37,7 @@ async fn main() -> Result<()> { crate::logging::init()?; let args = Cli::parse(); - let mut app = App::new(args.tick_rate, args.frame_rate)?; + let mut app = App::new(args.cli_cmd, args.tick_rate, args.frame_rate)?; app.run().await?; Ok(()) } From 783e31a5827a999c4fed672672e1d961d57456a3 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 1 Dec 2024 16:49:59 -0800 Subject: [PATCH 02/26] Set title based on CLI Mode --- deja_vu/src/components/title.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/deja_vu/src/components/title.rs b/deja_vu/src/components/title.rs index 01799ec..9d89163 100644 --- a/deja_vu/src/components/title.rs +++ b/deja_vu/src/components/title.rs @@ -21,17 +21,25 @@ use ratatui::{ use tokio::sync::mpsc::UnboundedSender; use super::Component; -use crate::{action::Action, config::Config}; +use crate::{action::Action, cli, config::Config}; #[derive(Default)] pub struct Title { command_tx: Option>, config: Config, + title_text: String, } impl Title { - pub fn new() -> Self { - Self::default() + pub fn new(cli_cmd: cli::Command) -> Self { + let title_text = match cli_cmd { + cli::Command::Clone => String::from("Deja-Vu: Clone"), + cli::Command::Diagnose => String::from("Deja-Vu: Diagnostics"), + }; + Self { + title_text, + ..Default::default() + } } } @@ -55,7 +63,7 @@ impl Component for Title { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { // Title Block - let title_text = Span::styled("WizardKit: Deja-Vu", Style::default().fg(Color::LightCyan)); + let title_text = Span::styled(&self.title_text, Style::default().fg(Color::LightCyan)); let title = Paragraph::new(Line::from(title_text).centered()) .block(Block::default().borders(Borders::ALL)); frame.render_widget(title, area); From 3674fbdee760894ffcca360652fac754614d7073 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 1 Dec 2024 16:50:28 -0800 Subject: [PATCH 03/26] Expand boot diagnostic sections --- deja_vu/config/config.json5 | 5 +++++ deja_vu/src/app.rs | 20 +++++++----------- deja_vu/src/components/left.rs | 38 ++++++++++++++++++++-------------- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/deja_vu/config/config.json5 b/deja_vu/config/config.json5 index c2cc437..94e8666 100644 --- a/deja_vu/config/config.json5 +++ b/deja_vu/config/config.json5 @@ -89,30 +89,35 @@ }, // Diagnostic modes "DiagMenu": { + "": "Process", "": "Quit", // Quit the application "": "Quit", // Another way to quit "": "Quit", // Yet another way to quit "": "Suspend" // Suspend the application }, "BootDiags": { + "": "Process", "": "Quit", // Quit the application "": "Quit", // Another way to quit "": "Quit", // Yet another way to quit "": "Suspend" // Suspend the application }, "BootSetup": { + "": "Process", "": "Quit", // Quit the application "": "Quit", // Another way to quit "": "Quit", // Yet another way to quit "": "Suspend" // Suspend the application }, "InjectDrivers": { + "": "Process", "": "Quit", // Quit the application "": "Quit", // Another way to quit "": "Quit", // Yet another way to quit "": "Suspend" // Suspend the application }, "ToggleSafeBoot": { + "": "Process", "": "Quit", // Quit the application "": "Quit", // Another way to quit "": "Quit", // Yet another way to quit diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index dc1eec7..04021f8 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -105,7 +105,7 @@ impl App { action_rx, action_tx, components: vec![ - Box::new(Title::new()), + Box::new(Title::new(cli_cmd)), Box::new(FpsCounter::new()), Box::new(Left::new()), Box::new(Right::new()), @@ -158,14 +158,11 @@ impl App { (_, Mode::Failed) => Mode::Failed, // Diagnostic states - ( - _, - Mode::DiagMenu - | Mode::BootDiags - | Mode::BootSetup - | Mode::InjectDrivers - | Mode::ToggleSafeBoot, - ) => Mode::DiagMenu, + (_, Mode::DiagMenu) => Mode::BootDiags, + (_, Mode::BootDiags) => Mode::BootSetup, + (_, Mode::BootSetup) => Mode::InjectDrivers, + (_, Mode::InjectDrivers) => Mode::ToggleSafeBoot, + (_, Mode::ToggleSafeBoot) => Mode::Done, // Invalid states (_, Mode::Confirm) => panic!("This shouldn't happen."), @@ -318,10 +315,7 @@ impl App { action_tx.send(Action::SetMode(Mode::ScanDisks))?; } cli::Command::Diagnose => { - action_tx.send(Action::DisplayPopup( - popup::Type::Info, - String::from("Boot Diagnostics?"), - ))?; + action_tx.send(Action::SetMode(Mode::DiagMenu))?; } } diff --git a/deja_vu/src/components/left.rs b/deja_vu/src/components/left.rs index 7b18095..4a9bbf7 100644 --- a/deja_vu/src/components/left.rs +++ b/deja_vu/src/components/left.rs @@ -163,6 +163,15 @@ impl Component for Left { } } } + Mode::DiagMenu + | Mode::BootDiags + | Mode::BootSetup + | Mode::InjectDrivers + | Mode::ToggleSafeBoot => { + if let Some(command_tx) = self.command_tx.clone() { + command_tx.send(Action::NextScreen)?; + } + } _ => {} }, Action::Select(Some(index), None) => self.selections[0] = Some(index), @@ -219,14 +228,11 @@ impl Component for Left { } (_, Mode::Done | Mode::Failed) => self.title_text = String::from("Done"), // Diagnostic states - ( - _, - Mode::DiagMenu - | Mode::BootDiags - | Mode::BootSetup - | Mode::InjectDrivers - | Mode::ToggleSafeBoot, - ) => self.title_text = String::from("Boot Diagnostics"), + (_, Mode::DiagMenu) => self.title_text = String::from("Main Menu"), + (_, Mode::BootDiags) => self.title_text = String::from("Boot Diagnostics"), + (_, Mode::BootSetup) => self.title_text = String::from("Boot Setup"), + (_, Mode::InjectDrivers) => self.title_text = String::from("Inject Drivers"), + (_, Mode::ToggleSafeBoot) => self.title_text = String::from("Toggle Safe Mode"), // Invalid states (_, Mode::Confirm) => panic!("This shouldn't happen."), @@ -280,10 +286,18 @@ impl Component for Left { // Body match self.mode { + // Clone modes Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone + // Diagnostic modes + | Mode::DiagMenu + | Mode::BootDiags + | Mode::BootSetup + | Mode::InjectDrivers + | Mode::ToggleSafeBoot + // Done | Mode::Done | Mode::Failed => { // Leave blank @@ -391,14 +405,6 @@ impl Component for Left { _ => panic!("This shouldn't happen."), } } - Mode::DiagMenu - | Mode::BootDiags - | Mode::BootSetup - | Mode::InjectDrivers - | Mode::ToggleSafeBoot => { - // Diagnostic modes - return Ok(()); - } } // Done From 76a27cd17973574bbff5eb57d2ede6257b61d4d7 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 11 Jan 2025 16:24:54 -0800 Subject: [PATCH 04/26] WIP from 2024-12-01 --- deja_vu/config/config.json5 | 3 ++ deja_vu/src/action.rs | 3 +- deja_vu/src/app.rs | 20 +++++++--- deja_vu/src/components/left.rs | 67 ++++++++++++++++++++++++++++++---- 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/deja_vu/config/config.json5 b/deja_vu/config/config.json5 index 94e8666..2bf9438 100644 --- a/deja_vu/config/config.json5 +++ b/deja_vu/config/config.json5 @@ -90,6 +90,9 @@ // Diagnostic modes "DiagMenu": { "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "InstallDriver", "": "Quit", // Quit the application "": "Quit", // Another way to quit "": "Quit", // Yet another way to quit diff --git a/deja_vu/src/action.rs b/deja_vu/src/action.rs index ba18a79..ba6b760 100644 --- a/deja_vu/src/action.rs +++ b/deja_vu/src/action.rs @@ -25,10 +25,11 @@ use crate::{ }, }; -#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] pub enum Action { // App InstallDriver, + #[default] Process, ScanDisks, Select(Option, Option), // indicies for (source, dest) or (boot, os) diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 04021f8..6336a39 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -138,9 +138,13 @@ impl App { } pub fn next_mode(&mut self) -> Option { + info!( + "Prev Mode: {:?} // Cur Mode: {:?}", + self.prev_mode, self.cur_mode + ); let new_mode = match (self.prev_mode, self.cur_mode) { // Clone states - (_, Mode::InstallDrivers) => Mode::ScanDisks, + (_, Mode::InstallDrivers) => self.prev_mode, (_, Mode::ScanDisks) => Mode::SelectDisks, (_, Mode::SelectDisks | Mode::SelectTableType | Mode::SelectParts) => { if self.selections[1].is_some() { @@ -158,11 +162,14 @@ impl App { (_, Mode::Failed) => Mode::Failed, // Diagnostic states - (_, Mode::DiagMenu) => Mode::BootDiags, - (_, Mode::BootDiags) => Mode::BootSetup, - (_, Mode::BootSetup) => Mode::InjectDrivers, - (_, Mode::InjectDrivers) => Mode::ToggleSafeBoot, - (_, Mode::ToggleSafeBoot) => Mode::Done, + ( + _, + Mode::DiagMenu + | Mode::BootDiags + | Mode::BootSetup + | Mode::InjectDrivers + | Mode::ToggleSafeBoot, + ) => Mode::DiagMenu, // Invalid states (_, Mode::Confirm) => panic!("This shouldn't happen."), @@ -315,6 +322,7 @@ impl App { action_tx.send(Action::SetMode(Mode::ScanDisks))?; } cli::Command::Diagnose => { + self.prev_mode = Mode::DiagMenu; action_tx.send(Action::SetMode(Mode::DiagMenu))?; } } diff --git a/deja_vu/src/components/left.rs b/deja_vu/src/components/left.rs index 4a9bbf7..8d61f72 100644 --- a/deja_vu/src/components/left.rs +++ b/deja_vu/src/components/left.rs @@ -1,3 +1,5 @@ +use std::fmt; + // This file is part of Deja-vu. // // Deja-vu is free software: you can redistribute it and/or modify it @@ -33,10 +35,23 @@ use crate::{ }, }; +#[derive(Clone, Default)] +pub struct DiagMenu { + name: String, + action: Action, +} + +impl fmt::Display for DiagMenu { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", &self.name) + } +} + #[derive(Default)] pub struct Left { command_tx: Option>, config: Config, + diag_menu: StatefulList, disk_id_dest: Option, table_type: Option, title_text: String, @@ -50,7 +65,28 @@ pub struct Left { impl Left { pub fn new() -> Self { + let menu_entries = vec![ + DiagMenu { + name: String::from("Boot Diagnostics"), + action: Action::SetMode(Mode::BootDiags), + }, + DiagMenu { + name: String::from("Boot Setup"), + action: Action::SetMode(Mode::BootSetup), + }, + DiagMenu { + name: String::from("Inject Drivers"), + action: Action::SetMode(Mode::InjectDrivers), + }, + DiagMenu { + name: String::from("Toggle Safe Mode"), + action: Action::SetMode(Mode::ToggleSafeBoot), + }, + ]; + let mut diag_menu = StatefulList::default(); + diag_menu.set_items(menu_entries); Self { + diag_menu, selections: vec![None, None], title_text: String::from("Home"), ..Default::default() @@ -77,6 +113,7 @@ impl Component for Left { fn update(&mut self, action: Action) -> Result> { match action { Action::KeyUp => match self.mode { + Mode::DiagMenu => self.diag_menu.previous(), Mode::InstallDrivers => self.list_drivers.previous(), Mode::SelectDisks => self.list_disks.previous(), Mode::SelectTableType => self.list_table_types.previous(), @@ -84,6 +121,7 @@ impl Component for Left { _ => {} }, Action::KeyDown => match self.mode { + Mode::DiagMenu => self.diag_menu.next(), Mode::InstallDrivers => self.list_drivers.next(), Mode::SelectDisks => self.list_disks.next(), Mode::SelectTableType => self.list_table_types.next(), @@ -97,12 +135,14 @@ impl Component for Left { command_tx.send(Action::NextScreen)?; } } - Mode::InstallDrivers + Mode::DiagMenu + | Mode::InstallDrivers | Mode::SelectDisks | Mode::SelectTableType | Mode::SelectParts => { // Menu selection sections let selection: Option = match self.mode { + Mode::DiagMenu => self.diag_menu.selected(), Mode::InstallDrivers => self.list_drivers.selected(), Mode::SelectDisks => self.list_disks.selected(), Mode::SelectTableType => self.list_table_types.selected(), @@ -136,6 +176,12 @@ impl Component for Left { // Send selection(s) if needed // NOTE: This is needed to keep the app and all components in sync match self.mode { + Mode::DiagMenu => { + // Only need to select one entry + if let Some(entry) = self.diag_menu.get_selected() { + command_tx.send(entry.action)?; + } + } Mode::InstallDrivers => { // Only need to select one entry if let Some(driver) = self.list_drivers.get_selected() { @@ -163,11 +209,7 @@ impl Component for Left { } } } - Mode::DiagMenu - | Mode::BootDiags - | Mode::BootSetup - | Mode::InjectDrivers - | Mode::ToggleSafeBoot => { + Mode::BootDiags | Mode::BootSetup | Mode::InjectDrivers | Mode::ToggleSafeBoot => { if let Some(command_tx) = self.command_tx.clone() { command_tx.send(Action::NextScreen)?; } @@ -292,7 +334,6 @@ impl Component for Left { | Mode::Clone | Mode::PostClone // Diagnostic modes - | Mode::DiagMenu | Mode::BootDiags | Mode::BootSetup | Mode::InjectDrivers @@ -324,13 +365,20 @@ impl Component for Left { // Bail early return Ok(()); } - Mode::InstallDrivers + Mode::DiagMenu + | Mode::InstallDrivers | Mode::SelectDisks | Mode::SelectTableType | Mode::SelectParts => { // List modes let mut list_items = Vec::::new(); let list_items_strings: Vec = match self.mode { + Mode::DiagMenu=> self + .diag_menu + .items + .iter() + .map(|i| format!("{i}")) + .collect(), Mode::InstallDrivers => self .list_drivers .items @@ -388,6 +436,9 @@ impl Component for Left { .highlight_symbol(" --> ") .repeat_highlight_symbol(false); match self.mode { + Mode::DiagMenu=> { + frame.render_stateful_widget(list, body_area, &mut self.diag_menu.state); + } Mode::InstallDrivers => { frame.render_stateful_widget(list, body_area, &mut self.list_drivers.state); } From aa4ac8d5e860d7da35fd0ef5f559f3671c4bc8f0 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Fri, 24 Jan 2025 23:58:15 -0800 Subject: [PATCH 05/26] WIP: Initial boot-diags outline Currently not working Update footer lines WIP 47 --- Cargo.lock | 20 ++ Cargo.toml | 4 +- boot_diags/Cargo.toml | 46 +++ boot_diags/build.rs | 28 ++ boot_diags/src/app.rs | 661 +++++++++++++++++++++++++++++++++++ boot_diags/src/main.rs | 33 ++ config/config.json5 | 46 +++ core/src/action.rs | 4 +- core/src/components/left.rs | 12 +- core/src/components/state.rs | 2 +- core/src/line.rs | 32 +- core/src/state.rs | 6 + deja_vu/src/app.rs | 35 +- 13 files changed, 904 insertions(+), 25 deletions(-) create mode 100644 boot_diags/Cargo.toml create mode 100644 boot_diags/build.rs create mode 100644 boot_diags/src/app.rs create mode 100644 boot_diags/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 14defc5..4570983 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,6 +184,26 @@ dependencies = [ "generic-array", ] +[[package]] +name = "boot-diags" +version = "0.2.0" +dependencies = [ + "anyhow", + "clap", + "color-eyre", + "core", + "crossterm", + "futures", + "ratatui", + "serde", + "tokio", + "toml", + "tracing", + "tracing-error", + "tracing-subscriber", + "vergen-gix", +] + [[package]] name = "bstr" version = "1.11.3" diff --git a/Cargo.toml b/Cargo.toml index 39fe348..f5655e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,6 @@ # 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..11a2402 --- /dev/null +++ b/boot_diags/Cargo.toml @@ -0,0 +1,46 @@ +# 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.2.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"] } + +[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..c3ced6b --- /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..0e00113 --- /dev/null +++ b/boot_diags/src/app.rs @@ -0,0 +1,661 @@ +// 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 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::{cpu::get_cpu_name, drivers}, + tasks::{Task, Tasks}, + tui::{Event, Tui}, +}; +use std::{ + iter::zip, + 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}; + +pub struct App { + // TUI + action_rx: mpsc::UnboundedReceiver, + action_tx: mpsc::UnboundedSender, + components: Vec>, + config: Config, + 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, + selections: Vec>, + 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::BootDiags, + Mode::BootSetup, + Mode::InjectDrivers, + Mode::ToggleSafeMode, + ]); + 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()?, + 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, + selections: vec![None, None], + tasks, + }) + } + + 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::InjectDrivers | Mode::ToggleSafeMode => { + Mode::DiagMenu + } + Mode::Failed => Mode::Failed, + // Default to current mode + _ => self.cur_mode, + } + } + + 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::InjectDrivers | Mode::InstallDrivers => self.clone.scan_drivers(), + Mode::ScanDisks => { + if self.tasks.idle() { + self.tasks.add(Task::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(); + 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) + match self.cur_mode { + Mode::ScanDisks => { + // Check background task + self.tasks.poll()?; // Once all are complete Action::NextScreen is sent + } + _ => {} + } + } + 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::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::BootDiags + | Mode::BootSetup + | Mode::InjectDrivers + | Mode::ToggleSafeMode => { + 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::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; + } + _ => {} + }, + 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))?; + } + _ => {} + } + 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, // 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 + | Mode::BootSetup + | Mode::Home + | Mode::PEMenu + | Mode::ScanDisks + | Mode::ToggleSafeMode => String::from("(q) to quit"), + Mode::InstallDrivers | Mode::InjectDrivers => { + 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::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::PreClone + | Mode::PostClone + | Mode::Done + | 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::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 | Mode::BootSetup | Mode::ToggleSafeMode => { + select_num = 0; + let (new_title, _) = get_mode_strings(app.cur_mode); + title = new_title; + } + 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::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))); + } + } + } + _ => {} + } + Action::UpdateRight(labels, start_index, items) +} + +fn get_mode_strings(mode: Mode) -> (String, String) { + match mode { + 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::ToggleSafeMode => ( + String::from("Toggle Safe Mode"), + String::from("Enable or disable safe mode"), + ), + _ => panic!("This shouldn't happen"), + } +} diff --git a/boot_diags/src/main.rs b/boot_diags/src/main.rs new file mode 100644 index 0000000..8bcfda9 --- /dev/null +++ b/boot_diags/src/main.rs @@ -0,0 +1,33 @@ +// 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; + +#[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(()) +} diff --git a/config/config.json5 b/config/config.json5 index 986126d..5e4bf8a 100644 --- a/config/config.json5 +++ b/config/config.json5 @@ -95,6 +95,52 @@ "": "Quit", "": "Suspend" }, + "DiagMenu": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "ScanDisks", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "BootDiags": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "BootSetup": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "InjectDrivers": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "ToggleSafeMode": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, "PEMenu": { "": "Process", "": "KeyUp", diff --git a/core/src/action.rs b/core/src/action.rs index 604edd7..c4f6a4f 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -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) + // TODO: Add actions? // App (Clone) Highlight(usize), InstallDriver, @@ -35,7 +37,7 @@ 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, // Screens DismissPopup, diff --git a/core/src/components/left.rs b/core/src/components/left.rs index 0f1c833..ca8c1e0 100644 --- a/core/src/components/left.rs +++ b/core/src/components/left.rs @@ -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/state.rs b/core/src/components/state.rs index b7b139c..caf768b 100644 --- a/core/src/components/state.rs +++ b/core/src/components/state.rs @@ -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/line.rs b/core/src/line.rs index 038a848..2a6235a 100644 --- a/core/src/line.rs +++ b/core/src/line.rs @@ -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/state.rs b/core/src/state.rs index fab9ede..1794229 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -30,6 +30,12 @@ pub enum Mode { Home, Done, Failed, + // Boot Diags + DiagMenu, + BootDiags, + BootSetup, + InjectDrivers, + ToggleSafeMode, // Clone ScanDisks, InstallDrivers, diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index ae6ef72..df7d968 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -115,7 +115,12 @@ impl App { | Mode::Clone | Mode::PostClone => None, // Invalid states - Mode::PEMenu => panic!("This shouldn't happen?"), + Mode::BootDiags + | Mode::BootSetup + | Mode::DiagMenu + | Mode::InjectDrivers + | Mode::PEMenu + | Mode::ToggleSafeMode => panic!("This shouldn't happen?"), }; new_mode } @@ -134,7 +139,12 @@ impl App { Mode::PostClone | Mode::Done => Mode::Done, Mode::Failed => Mode::Failed, // Invalid states - Mode::PEMenu => panic!("This shouldn't happen?"), + Mode::BootDiags + | Mode::BootSetup + | Mode::DiagMenu + | Mode::InjectDrivers + | Mode::PEMenu + | Mode::ToggleSafeMode => panic!("This shouldn't happen?"), }; if new_mode == self.cur_mode { @@ -566,14 +576,20 @@ 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::BootSetup + | Mode::DiagMenu + | Mode::InjectDrivers + | Mode::PEMenu + | Mode::ToggleSafeMode => panic!("This shouldn't happen?"), } } @@ -638,7 +654,12 @@ 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::BootSetup + | Mode::DiagMenu + | Mode::InjectDrivers + | Mode::PEMenu + | Mode::ToggleSafeMode => panic!("This shouldn't happen?"), }; Action::UpdateLeft(title, labels, items, select_num) } @@ -691,7 +712,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 +726,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 From 1ce7f76229386b624fb7b7e099eb8834f2c4af45 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 25 Jan 2025 22:59:06 -0800 Subject: [PATCH 06/26] WIP: Add BootMode menu logic --- boot_diags/src/app.rs | 64 +++++++++++++++++++++++++++++++++---------- config/config.json5 | 2 +- core/src/state.rs | 2 +- deja_vu/src/app.rs | 8 +++--- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index 0e00113..ae089df 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -41,6 +41,13 @@ use ratatui::{ use tokio::sync::mpsc; use tracing::{debug, info}; +#[derive(Clone, Debug, Default)] +pub enum SafeMode { + #[default] + Disable, + Enable, +} + pub struct App { // TUI action_rx: mpsc::UnboundedReceiver, @@ -56,6 +63,7 @@ pub struct App { clone: CloneSettings, cur_mode: Mode, list: StatefulList, + boot_modes: Vec, selections: Vec>, tasks: Tasks, } @@ -70,7 +78,7 @@ impl App { Mode::BootDiags, Mode::BootSetup, Mode::InjectDrivers, - Mode::ToggleSafeMode, + Mode::SetBootMode, ]); Ok(Self { // TUI @@ -94,6 +102,7 @@ impl App { clone: CloneSettings::new(disk_list_arc), cur_mode: Mode::Home, list, + boot_modes: vec![SafeMode::Enable, SafeMode::Disable], selections: vec![None, None], tasks, }) @@ -106,7 +115,7 @@ impl App { Mode::ScanDisks => Mode::SelectDisks, Mode::SelectDisks => Mode::SelectParts, Mode::SelectParts => Mode::DiagMenu, - Mode::BootDiags | Mode::BootSetup | Mode::InjectDrivers | Mode::ToggleSafeMode => { + Mode::BootDiags | Mode::BootSetup | Mode::InjectDrivers | Mode::SetBootMode => { Mode::DiagMenu } Mode::Failed => Mode::Failed, @@ -263,10 +272,7 @@ impl App { self.action_tx.send(Action::SetMode(new_mode))?; } } - Mode::BootDiags - | Mode::BootSetup - | Mode::InjectDrivers - | Mode::ToggleSafeMode => { + Mode::BootDiags | Mode::BootSetup | Mode::InjectDrivers => { let new_mode = self.next_mode(); self.action_tx.send(Action::SetMode(new_mode))?; } @@ -291,6 +297,12 @@ impl App { self.clone.part_index_boot = one; self.clone.part_index_os = two; } + Mode::SetBootMode => { + if let Some(index) = one { + // TODO: Make real + info!("Setting boot mode to: {:?}", self.boot_modes.get(index)); + } + } _ => {} }, Action::SetMode(new_mode) => { @@ -395,13 +407,10 @@ fn get_chunks(r: Rect) -> Vec { fn build_footer_string(cur_mode: Mode) -> String { match cur_mode { - Mode::BootDiags - | Mode::BootSetup - | Mode::Home - | Mode::PEMenu - | Mode::ScanDisks - | Mode::ToggleSafeMode => String::from("(q) to quit"), - Mode::InstallDrivers | Mode::InjectDrivers => { + Mode::BootDiags | Mode::BootSetup | Mode::Home | Mode::PEMenu | 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 => { @@ -483,11 +492,18 @@ fn build_left_items(app: &App) -> Action { } } } - Mode::BootDiags | Mode::BootSetup | Mode::ToggleSafeMode => { + Mode::BootDiags | 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"); @@ -633,6 +649,24 @@ fn build_right_items(app: &App) -> Action { } } } + 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) @@ -652,7 +686,7 @@ fn get_mode_strings(mode: Mode) -> (String, String) { String::from("Inject Drivers"), String::from("Inject drivers into existing Windows environment"), ), - Mode::ToggleSafeMode => ( + Mode::SetBootMode => ( String::from("Toggle Safe Mode"), String::from("Enable or disable safe mode"), ), diff --git a/config/config.json5 b/config/config.json5 index 5e4bf8a..5f3f432 100644 --- a/config/config.json5 +++ b/config/config.json5 @@ -132,7 +132,7 @@ "": "Quit", "": "Suspend" }, - "ToggleSafeMode": { + "SetBootMode": { "": "Process", "": "KeyUp", "": "KeyDown", diff --git a/core/src/state.rs b/core/src/state.rs index 1794229..8892f81 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -35,7 +35,7 @@ pub enum Mode { BootDiags, BootSetup, InjectDrivers, - ToggleSafeMode, + SetBootMode, // Clone ScanDisks, InstallDrivers, diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index df7d968..a71427c 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -120,7 +120,7 @@ impl App { | Mode::DiagMenu | Mode::InjectDrivers | Mode::PEMenu - | Mode::ToggleSafeMode => panic!("This shouldn't happen?"), + | Mode::SetBootMode => panic!("This shouldn't happen?"), }; new_mode } @@ -144,7 +144,7 @@ impl App { | Mode::DiagMenu | Mode::InjectDrivers | Mode::PEMenu - | Mode::ToggleSafeMode => panic!("This shouldn't happen?"), + | Mode::SetBootMode => panic!("This shouldn't happen?"), }; if new_mode == self.cur_mode { @@ -589,7 +589,7 @@ fn build_footer_string(cur_mode: Mode) -> String { | Mode::DiagMenu | Mode::InjectDrivers | Mode::PEMenu - | Mode::ToggleSafeMode => panic!("This shouldn't happen?"), + | Mode::SetBootMode => panic!("This shouldn't happen?"), } } @@ -659,7 +659,7 @@ fn build_left_items(app: &App, cur_mode: Mode) -> Action { | Mode::DiagMenu | Mode::InjectDrivers | Mode::PEMenu - | Mode::ToggleSafeMode => panic!("This shouldn't happen?"), + | Mode::SetBootMode => panic!("This shouldn't happen?"), }; Action::UpdateLeft(title, labels, items, select_num) } From b2b90bb946d2529f74ce190ea1ccd2a56990fa72 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 25 Jan 2025 23:56:35 -0800 Subject: [PATCH 07/26] WIP: Add boot mode options to boot-diags --- boot_diags/src/app.rs | 55 +++++++++++++++++++++++++++------- core/src/system/boot.rs | 66 +++++++++++++++++++++++++++++------------ 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index ae089df..bdbf32c 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -22,11 +22,16 @@ use core::{ config::Config, line::{get_disk_description_right, get_part_description, DVLine}, state::{CloneSettings, Mode}, - system::{cpu::get_cpu_name, drivers}, + system::{ + boot::{self, SafeMode}, + cpu::get_cpu_name, + drivers, + }, tasks::{Task, Tasks}, tui::{Event, Tui}, }; use std::{ + env, iter::zip, sync::{Arc, Mutex}, }; @@ -41,13 +46,6 @@ use ratatui::{ use tokio::sync::mpsc; use tracing::{debug, info}; -#[derive(Clone, Debug, Default)] -pub enum SafeMode { - #[default] - Disable, - Enable, -} - pub struct App { // TUI action_rx: mpsc::UnboundedReceiver, @@ -65,6 +63,7 @@ pub struct App { list: StatefulList, boot_modes: Vec, selections: Vec>, + system32: String, tasks: Tasks, } @@ -103,6 +102,7 @@ impl App { cur_mode: Mode::Home, list, boot_modes: vec![SafeMode::Enable, SafeMode::Disable], + system32: String::new(), selections: vec![None, None], tasks, }) @@ -124,6 +124,25 @@ impl App { } } + pub fn set_boot_mode(&mut self, boot_mode: SafeMode) { + info!("Setting boot mode to: {:?}", boot_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; @@ -166,7 +185,22 @@ impl App { } 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)?; @@ -299,8 +333,9 @@ impl App { } Mode::SetBootMode => { if let Some(index) = one { - // TODO: Make real - info!("Setting boot mode to: {:?}", self.boot_modes.get(index)); + if let Some(boot_mode) = self.boot_modes.get(index) { + self.set_boot_mode(boot_mode.to_owned()); + } } } _ => {} diff --git a/core/src/system/boot.rs b/core/src/system/boot.rs index 2bc098f..94edf3e 100644 --- a/core/src/system/boot.rs +++ b/core/src/system/boot.rs @@ -18,6 +18,13 @@ use crate::tasks::Task; 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, @@ -55,25 +62,10 @@ 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 @@ -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 Command + 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(Task::Command( + PathBuf::from(format!("{system32}/bcdedit.exe")), + cmd_args, + )) +} From de7520324b8b52f19949edb96b38653cd380b639 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 26 Jan 2025 00:33:03 -0800 Subject: [PATCH 08/26] WIP: Add InjetDrivers section --- boot_diags/src/app.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index bdbf32c..a047505 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -108,6 +108,25 @@ impl App { }) } + 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, @@ -306,7 +325,7 @@ impl App { self.action_tx.send(Action::SetMode(new_mode))?; } } - Mode::BootDiags | Mode::BootSetup | Mode::InjectDrivers => { + Mode::BootDiags | Mode::BootSetup => { let new_mode = self.next_mode(); self.action_tx.send(Action::SetMode(new_mode))?; } @@ -316,6 +335,11 @@ impl App { 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() { From 46311a3ede83b2057c4b72e9fc64c9f920492f43 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 28 Jan 2025 18:42:43 -0800 Subject: [PATCH 09/26] Fix version number --- boot_diags/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boot_diags/Cargo.toml b/boot_diags/Cargo.toml index 11a2402..3781fa0 100644 --- a/boot_diags/Cargo.toml +++ b/boot_diags/Cargo.toml @@ -18,7 +18,7 @@ name = "boot-diags" authors = ["2Shirt <2xShirt@gmail.com>"] edition = "2021" license = "GPL" -version = "0.2.0" +version = "0.1.0" [dependencies] core = { path = "../core" } From dd7c2fd7ab96a9c7e097c9b4f699b927562371c6 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 28 Jan 2025 19:37:12 -0800 Subject: [PATCH 10/26] Move fortune() to core lib --- core/Cargo.toml | 1 + core/src/components/popup.rs | 17 +++++++++++++++++ deja_vu/Cargo.toml | 1 - deja_vu/src/app.rs | 19 +------------------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index e724ecf..0b92777 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -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/src/components/popup.rs b/core/src/components/popup.rs index c2c0980..c95cce2 100644 --- a/core/src/components/popup.rs +++ b/core/src/components/popup.rs @@ -14,6 +14,7 @@ // 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/deja_vu/Cargo.toml b/deja_vu/Cargo.toml index 7756600..5cee449 100644 --- a/deja_vu/Cargo.toml +++ b/deja_vu/Cargo.toml @@ -39,7 +39,6 @@ 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" [build-dependencies] anyhow = "1.0.86" diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index a71427c..4c33576 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -36,7 +36,6 @@ use std::{ }; use color_eyre::Result; -use rand::random; use ratatui::{ crossterm::event::KeyEvent, layout::{Constraint, Direction, Layout}, @@ -258,7 +257,7 @@ impl App { } Mode::Done => { self.action_tx - .send(Action::DisplayPopup(popup::Type::Success, fortune()))?; + .send(Action::DisplayPopup(popup::Type::Success, popup::fortune()))?; } _ => {} } @@ -739,19 +738,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!", - }) -} From acccdccb3848c01464c3a36f1268865e50db01d8 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 28 Jan 2025 19:42:16 -0800 Subject: [PATCH 11/26] Adjust Mode::Done logic --- boot_diags/src/app.rs | 17 ++++++++--------- config/config.json5 | 2 +- deja_vu/src/app.rs | 3 +++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index a047505..dcf8b84 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -134,9 +134,9 @@ impl App { Mode::ScanDisks => Mode::SelectDisks, Mode::SelectDisks => Mode::SelectParts, Mode::SelectParts => Mode::DiagMenu, - Mode::BootDiags | Mode::BootSetup | Mode::InjectDrivers | Mode::SetBootMode => { - Mode::DiagMenu - } + Mode::BootDiags | Mode::BootSetup => Mode::DiagMenu, // TODO: add Mode::ProgressReport + Mode::InjectDrivers | Mode::SetBootMode => Mode::DiagMenu, + Mode::Done => Mode::DiagMenu, Mode::Failed => Mode::Failed, // Default to current mode _ => self.cur_mode, @@ -325,6 +325,9 @@ impl App { 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))?; @@ -475,17 +478,13 @@ fn build_footer_string(cur_mode: Mode) -> String { 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::PreClone - | Mode::PostClone - | Mode::Done - | Mode::SelectTableType => { + Mode::Confirm | Mode::Clone | Mode::PreClone | Mode::PostClone | Mode::SelectTableType => { panic!("This shouldn't happen?") } } diff --git a/config/config.json5 b/config/config.json5 index 5f3f432..62d9b3a 100644 --- a/config/config.json5 +++ b/config/config.json5 @@ -82,7 +82,7 @@ "": "Suspend" }, "Done": { - "": "Quit", + "": "Process", "": "Quit", "": "Quit", "": "Quit", diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 4c33576..8715929 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -378,6 +378,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)?, From 14b40410d47b8a637cb969b3fc1be27f108baae5 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 28 Jan 2025 19:42:31 -0800 Subject: [PATCH 12/26] Bump crate versions --- Cargo.lock | 141 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4570983..1296869 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,7 +32,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -186,7 +186,7 @@ dependencies = [ [[package]] name = "boot-diags" -version = "0.2.0" +version = "0.1.0" dependencies = [ "anyhow", "clap", @@ -291,9 +291,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.26" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -301,9 +301,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", @@ -432,7 +432,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", ] @@ -465,6 +465,7 @@ dependencies = [ "libc", "once_cell", "pretty_assertions", + "rand", "ratatui", "raw-cpuid", "regex", @@ -485,9 +486,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", ] @@ -530,9 +531,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" @@ -587,7 +588,6 @@ dependencies = [ "clap", "color-eyre", "core", - "rand", "ratatui", "serde", "serde_json", @@ -925,7 +925,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", ] [[package]] @@ -1720,9 +1732,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", @@ -1770,9 +1782,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", @@ -1929,7 +1941,7 @@ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -2157,7 +2169,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2200,20 +2212,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", @@ -2221,11 +2233,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]] @@ -2274,7 +2287,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", ] @@ -2354,9 +2367,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", @@ -2373,9 +2386,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" @@ -2423,9 +2436,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", @@ -2619,13 +2632,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", @@ -2918,9 +2931,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" @@ -2991,11 +3004,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]] @@ -3076,6 +3089,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" @@ -3191,13 +3213,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" @@ -3258,7 +3289,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]] @@ -3272,6 +3312,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" From 1336c20dc265105ca1a7f97915748c819666ff79 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 4 Feb 2025 21:23:01 -0800 Subject: [PATCH 13/26] Use clearer language in logging --- boot_diags/src/app.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index dcf8b84..78d3725 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -144,7 +144,11 @@ impl App { } pub fn set_boot_mode(&mut self, boot_mode: SafeMode) { - info!("Setting boot mode to: {:?}", boot_mode); + 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) { From 167ec52d6fe52be72bffd73c9ac687fc9abc2377 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Wed, 5 Feb 2025 20:27:28 -0800 Subject: [PATCH 14/26] Refactor tasks Retain info about tasks to use in app logic --- boot_diags/src/app.rs | 10 ++- core/src/tasks.rs | 194 +++++++++++++++++++++++++----------------- 2 files changed, 123 insertions(+), 81 deletions(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index 78d3725..c8897e6 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -295,9 +295,15 @@ impl App { self.last_tick_key_events.drain(..); // Check background task(s) match self.cur_mode { + Mode::BootDiags => { + // Check result of background task (if finished) + if let Some(handle) = self.tasks.poll()? { + // TODO: Impl logic + } + } Mode::ScanDisks => { - // Check background task - self.tasks.poll()?; // Once all are complete Action::NextScreen is sent + // Check background task (Once all are complete Action::NextScreen is sent) + self.tasks.poll()?; } _ => {} } diff --git a/core/src/tasks.rs b/core/src/tasks.rs index 50bca0d..09e8cb7 100644 --- a/core/src/tasks.rs +++ b/core/src/tasks.rs @@ -43,11 +43,17 @@ pub enum Task { UpdateDiskList, } +#[derive(Debug)] +pub struct TaskHandle { + task: Task, + handle: JoinHandle<()>, +} + #[derive(Debug)] pub struct Tasks { action_tx: mpsc::UnboundedSender, disk_list: Arc>>, - handle: Option>, + handle: 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 @@ -78,7 +84,8 @@ impl Tasks { self.handle.is_none() } - pub fn poll(&mut self) -> Result<()> { + pub fn poll(&mut self) -> Result> { + let mut return_handle: Option = None; // Forward any actions to main app if let Ok(action) = self.task_rx.try_recv() { let result = self.action_tx.send(action.clone()); @@ -87,8 +94,9 @@ impl Tasks { // 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() { + if let Some(task_handle) = self.handle.take() { + if task_handle.handle.is_finished() { + return_handle = Some(task_handle); if self.task_list.is_empty() { // No tasks remain self.task_tx.send(Action::NextScreen)?; @@ -98,78 +106,28 @@ impl Tasks { } } else { // Task not complete, return handle - self.handle = Some(handle); + self.handle = Some(task_handle); } } else if !self.task_list.is_empty() { // No current task but one is available self.start()?; } - Ok(()) + Ok(return_handle) } pub fn start(&mut self) -> Result<()> { if let Some(task) = self.task_list.pop_front() { - let task_str = format!("{task:?}"); 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)))); - } + Task::Command(cmd_path, cmd_args) => { + self.handle = Some(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)))); - } + Task::Diskpart(script) => { + self.handle = Some(run_task_diskpart(&script, task_tx)); } Task::ScanDisks => { let disk_list_arc = self.disk_list.clone(); @@ -177,13 +135,19 @@ impl Tasks { // Queue UpdateDiskList for various components self.add(Task::UpdateDiskList); - self.handle = Some(thread::spawn(move || { - let mut disks = disk_list_arc.lock().unwrap(); - *disks = disk::get_disks(); - })); + self.handle = Some(TaskHandle { + task: task.clone(), + handle: thread::spawn(move || { + let mut disks = disk_list_arc.lock().unwrap(); + *disks = disk::get_disks() + }), + }); } Task::Sleep => { - self.handle = Some(thread::spawn(|| sleep(Duration::from_millis(250)))); + self.handle = Some(TaskHandle { + task: task.clone(), + handle: thread::spawn(|| sleep(Duration::from_millis(250))), + }); } Task::UpdateDestDisk(index) => { self.action_tx.send(Action::DisplayPopup( @@ -197,24 +161,96 @@ impl Tasks { // Update destination disk ~in-place let disk_list_arc = self.disk_list.clone(); - self.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); - })); + self.handle = Some(TaskHandle { + task: task.clone(), + handle: 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 => { 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 || { - if let Err(err) = action_tx.send(Action::UpdateDiskList(disks_copy)) { - panic!("Failed to send Action: {err:?}"); - } - })); + self.handle = Some(TaskHandle { + handle: thread::spawn(move || { + if let Err(err) = action_tx.send(Action::UpdateDiskList(disks_copy)) { + panic!("Failed to send Action: {err:?}"); + } + }), + task: task.clone(), + }); } } } Ok(()) } } + +fn run_task_command( + cmd_path: PathBuf, + cmd_args: Vec, + task_tx: mpsc::UnboundedSender, +) -> TaskHandle { + let task = Task::Command(cmd_path.clone(), cmd_args.clone()); + let handle = if cfg!(windows) { + 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 + thread::spawn(|| sleep(Duration::from_millis(250))) + }; + TaskHandle { handle, task } +} + +fn run_task_diskpart(script: &str, task_tx: mpsc::UnboundedSender) -> TaskHandle { + let task = Task::Diskpart(String::from(script)); + let task_str = format!("{:?}", &task); + let handle = if cfg!(windows) { + let script = String::from(script); + thread::spawn(move || { + let output = diskpart::run_script_raw(&script); + 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 + thread::spawn(|| sleep(Duration::from_millis(250))) + }; + TaskHandle { handle, task } +} From cd38712ea9f903bc3c43b8547e0dcc7205b0e4ee Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 9 Feb 2025 14:41:10 -0800 Subject: [PATCH 15/26] Enable automatic volume mounting before cloning --- core/src/system/diskpart.rs | 2 +- deja_vu/src/app.rs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/core/src/system/diskpart.rs b/core/src/system/diskpart.rs index 9521982..ab827c5 100644 --- a/core/src/system/diskpart.rs +++ b/core/src/system/diskpart.rs @@ -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/deja_vu/src/app.rs b/deja_vu/src/app.rs index 8715929..b96d8bc 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -32,6 +32,7 @@ use core::{ use std::{ env, iter::zip, + path::PathBuf, sync::{Arc, Mutex}, }; @@ -170,11 +171,31 @@ impl App { ))?; } Mode::PreClone => { + // 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(".") + }; + self.action_tx.send(Action::DisplayPopup( popup::Type::Info, String::from("Formatting destination disk"), ))?; + // (Re)Enable volume mounting + self.tasks.add(Task::Command( + 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 { From 1c9ed12d4955615b3cf7baae203e476f1a3aae8d Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 9 Feb 2025 14:50:45 -0800 Subject: [PATCH 16/26] Rebrand as Deja-Vu --- Cargo.toml | 8 ++++---- boot_diags/Cargo.toml | 8 ++++---- boot_diags/build.rs | 8 ++++---- boot_diags/src/app.rs | 8 ++++---- boot_diags/src/main.rs | 8 ++++---- config/config.json5 | 2 +- core/Cargo.toml | 8 ++++---- core/build.rs | 8 ++++---- core/src/action.rs | 8 ++++---- core/src/cli.rs | 8 ++++---- core/src/components.rs | 8 ++++---- core/src/components/footer.rs | 8 ++++---- core/src/components/fps.rs | 8 ++++---- core/src/components/left.rs | 8 ++++---- core/src/components/popup.rs | 8 ++++---- core/src/components/right.rs | 8 ++++---- core/src/components/state.rs | 8 ++++---- core/src/components/title.rs | 8 ++++---- core/src/config.rs | 12 ++++++------ core/src/errors.rs | 8 ++++---- core/src/lib.rs | 8 ++++---- core/src/line.rs | 8 ++++---- core/src/logging.rs | 8 ++++---- core/src/state.rs | 8 ++++---- core/src/system.rs | 8 ++++---- core/src/system/boot.rs | 8 ++++---- core/src/system/cpu.rs | 8 ++++---- core/src/system/disk.rs | 8 ++++---- core/src/system/diskpart.rs | 8 ++++---- core/src/system/drivers.rs | 8 ++++---- core/src/tasks.rs | 8 ++++---- core/src/tests/mod.rs | 8 ++++---- core/src/tests/sample_output.rs | 8 ++++---- core/src/tui.rs | 8 ++++---- deja_vu/Cargo.toml | 8 ++++---- deja_vu/build.rs | 8 ++++---- deja_vu/src/app.rs | 8 ++++---- deja_vu/src/main.rs | 8 ++++---- pe_menu/Cargo.toml | 8 ++++---- pe_menu/build.rs | 8 ++++---- pe_menu/src/app.rs | 8 ++++---- pe_menu/src/main.rs | 8 ++++---- 42 files changed, 167 insertions(+), 167 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5655e7..aef6a91 100644 --- a/Cargo.toml +++ b/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 . [workspace] members = ["core", "boot_diags", "deja_vu", "pe_menu"] diff --git a/boot_diags/Cargo.toml b/boot_diags/Cargo.toml index 3781fa0..cf73a7a 100644 --- a/boot_diags/Cargo.toml +++ b/boot_diags/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 = "boot-diags" diff --git a/boot_diags/build.rs b/boot_diags/build.rs index c3ced6b..8988d39 100644 --- a/boot_diags/build.rs +++ b/boot_diags/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/boot_diags/src/app.rs b/boot_diags/src/app.rs index c8897e6..220e587 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/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, diff --git a/boot_diags/src/main.rs b/boot_diags/src/main.rs index 8bcfda9..82b0996 100644 --- a/boot_diags/src/main.rs +++ b/boot_diags/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; diff --git a/config/config.json5 b/config/config.json5 index 62d9b3a..1a27554 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": { diff --git a/core/Cargo.toml b/core/Cargo.toml index 0b92777..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" 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 c4f6a4f..b578031 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; 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 ca8c1e0..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; diff --git a/core/src/components/popup.rs b/core/src/components/popup.rs index c95cce2..4b951a7 100644 --- a/core/src/components/popup.rs +++ b/core/src/components/popup.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 rand::random; 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 caf768b..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; 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 2a6235a..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}, 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 8892f81..e4bc8c9 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}; 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 94edf3e..5dfd4dd 100644 --- a/core/src/system/boot.rs +++ b/core/src/system/boot.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 super::{disk::PartitionTableType, drivers::Driver}; use crate::tasks::Task; 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 ab827c5..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, 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 09e8cb7..7e885e5 100644 --- a/core/src/tasks.rs +++ b/core/src/tasks.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::{ 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 5cee449..ae4ab91 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" 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 b96d8bc..463565e 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, diff --git a/deja_vu/src/main.rs b/deja_vu/src/main.rs index 8bcfda9..82b0996 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; 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..9ec8c58 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, 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; From 1fab68500b089e02fa4952206593c4a1c8d2ec77 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 9 Feb 2025 22:31:36 -0800 Subject: [PATCH 17/26] WIP: IRL WinPE testing --- config/config.json5 | 2 ++ core/src/action.rs | 2 ++ pe_menu/src/app.rs | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/config/config.json5 b/config/config.json5 index 1a27554..15471b4 100644 --- a/config/config.json5 +++ b/config/config.json5 @@ -146,6 +146,8 @@ "": "KeyUp", "": "KeyDown", "": "Quit", + "": "Restart", + "

": "Shutdown", "": "OpenTerminal", "": "Quit", "": "Quit", diff --git a/core/src/action.rs b/core/src/action.rs index b578031..b2928a2 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -39,6 +39,8 @@ pub enum Action { UpdateRight(Vec>, usize, Vec>), // (labels, start_index, items) - items before start are always shown // App (PE-Menu) OpenTerminal, + Restart, + Shutdown, // Screens DismissPopup, DisplayPopup(Type, String), diff --git a/pe_menu/src/app.rs b/pe_menu/src/app.rs index 9ec8c58..4922332 100644 --- a/pe_menu/src/app.rs +++ b/pe_menu/src/app.rs @@ -249,6 +249,35 @@ impl App { vec![String::from("-new_console:n")], )); } + Action::Restart => { + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Restarting..."), + ))?; + self.tasks.add(Task::Command( + PathBuf::from("X:/Windows/System32/sync64.exe"), + vec![String::from("-r")], + )); + self.tasks.add(Task::Command( + 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(Task::Command( + PathBuf::from("X:/Windows/System32/sync64.exe"), + vec![String::from("-r")], + )); + self.tasks.add(Task::Command( + PathBuf::from("X:/Windows/System32/wpeutil.exe"), + vec![String::from("shutdown")], + )); + } _ => {} } for component in &mut self.components { From 72130109cbea54d431f2922b1d4acacaf7059017 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 11 Feb 2025 21:36:00 -0800 Subject: [PATCH 18/26] Refactor tasks (again) Avoids copying/recreating the TaskType enum (previously the Task enum). --- boot_diags/src/app.rs | 6 +- core/src/system/boot.rs | 16 ++--- core/src/tasks.rs | 139 ++++++++++++++++++++-------------------- deja_vu/src/app.rs | 12 ++-- pe_menu/src/app.rs | 16 ++--- 5 files changed, 96 insertions(+), 93 deletions(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index 220e587..6854bf8 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -27,7 +27,7 @@ use core::{ cpu::get_cpu_name, drivers, }, - tasks::{Task, Tasks}, + tasks::{TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ @@ -178,7 +178,7 @@ impl App { Mode::InjectDrivers | Mode::InstallDrivers => self.clone.scan_drivers(), Mode::ScanDisks => { if self.tasks.idle() { - self.tasks.add(Task::ScanDisks); + self.tasks.add(TaskType::ScanDisks); } self.action_tx.send(Action::DisplayPopup( popup::Type::Info, @@ -297,7 +297,7 @@ impl App { match self.cur_mode { Mode::BootDiags => { // Check result of background task (if finished) - if let Some(handle) = self.tasks.poll()? { + if let Some(task) = self.tasks.poll()? { // TODO: Impl logic } } diff --git a/core/src/system/boot.rs b/core/src/system/boot.rs index 5dfd4dd..97862ef 100644 --- a/core/src/system/boot.rs +++ b/core/src/system/boot.rs @@ -14,7 +14,7 @@ // 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; @@ -30,11 +30,11 @@ pub fn configure_disk( letter_os: &str, system32: &str, table_type: PartitionTableType, -) -> Vec { +) -> Vec { let mut tasks = Vec::new(); // Create - tasks.push(Task::Command( + tasks.push(TaskType::Command( PathBuf::from(format!("{system32}/bcdboot.exe")), vec![ format!("{letter_os}:\\Windows"), @@ -50,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::Command( PathBuf::from(format!("{system32}/bootsect.exe")), vec![ String::from("/nt60"), @@ -71,10 +71,10 @@ pub fn configure_disk( 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::Command( PathBuf::from(format!("{system32}/dism.exe")), vec![ format!("/image:{letter_os}:\\"), @@ -90,7 +90,7 @@ pub fn set_mode( mode: SafeMode, system32: &str, table_type: &PartitionTableType, -) -> Result { +) -> Result { let bcd_path = match table_type { PartitionTableType::Guid => { format!("{letter_boot}:\\EFI\\Microsoft\\Boot\\BCD") @@ -115,7 +115,7 @@ pub fn set_mode( cmd_args.push(String::from("minimal")); } } - Ok(Task::Command( + Ok(TaskType::Command( PathBuf::from(format!("{system32}/bcdedit.exe")), cmd_args, )) diff --git a/core/src/tasks.rs b/core/src/tasks.rs index 7e885e5..13ba8d5 100644 --- a/core/src/tasks.rs +++ b/core/src/tasks.rs @@ -34,7 +34,7 @@ use crate::{ }; #[derive(Clone, Debug)] -pub enum Task { +pub enum TaskType { Command(PathBuf, Vec), // (command, args) Diskpart(String), // (script_as_string) ScanDisks, @@ -44,16 +44,26 @@ pub enum Task { } #[derive(Debug)] -pub struct TaskHandle { - task: Task, - handle: JoinHandle<()>, +pub struct Task { + pub handle: Option>, + pub task_type: TaskType, +} + +impl Task { + pub fn new(task_type: TaskType) -> Task { + Task { + handle: 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 @@ -68,24 +78,25 @@ 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> { - let mut return_handle: Option = None; + pub fn poll(&mut self) -> Result> { + let mut return_task: Option = None; // Forward any actions to main app if let Ok(action) = self.task_rx.try_recv() { let result = self.action_tx.send(action.clone()); @@ -94,9 +105,13 @@ impl Tasks { // Check status of current task (if one is running). // NOTE: Action::NextScreen is sent once all tasks are complete - if let Some(task_handle) = self.handle.take() { - if task_handle.handle.is_finished() { - return_handle = Some(task_handle); + 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)?; @@ -105,85 +120,76 @@ impl Tasks { self.start()?; } } else { - // Task not complete, return handle - self.handle = Some(task_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(return_handle) + Ok(return_task) } pub fn start(&mut self) -> Result<()> { - if let Some(task) = self.task_list.pop_front() { + 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(cmd_path, cmd_args) => { - self.handle = Some(run_task_command( + match task.task_type { + TaskType::Command(ref cmd_path, ref cmd_args) => { + self.cur_handle = Some(run_task_command( cmd_path.clone(), cmd_args.clone(), task_tx, )); } - Task::Diskpart(script) => { - self.handle = Some(run_task_diskpart(&script, task_tx)); + TaskType::Diskpart(ref script) => { + self.cur_handle = Some(run_task_diskpart(&script, task_tx)); } - Task::ScanDisks => { + 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(TaskHandle { - task: task.clone(), - handle: thread::spawn(move || { - let mut disks = disk_list_arc.lock().unwrap(); - *disks = disk::get_disks() - }), - }); + self.cur_handle = Some(thread::spawn(move || { + let mut disks = disk_list_arc.lock().unwrap(); + *disks = disk::get_disks() + })); } - Task::Sleep => { - self.handle = Some(TaskHandle { - task: task.clone(), - handle: 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(TaskHandle { - task: task.clone(), - handle: 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); - }), - }); + 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(TaskHandle { - handle: thread::spawn(move || { - if let Err(err) = action_tx.send(Action::UpdateDiskList(disks_copy)) { - panic!("Failed to send Action: {err:?}"); - } - }), - task: task.clone(), - }); + 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(()) } @@ -193,9 +199,8 @@ fn run_task_command( cmd_path: PathBuf, cmd_args: Vec, task_tx: mpsc::UnboundedSender, -) -> TaskHandle { - let task = Task::Command(cmd_path.clone(), cmd_args.clone()); - let handle = if cfg!(windows) { +) -> JoinHandle<()> { + if cfg!(windows) { thread::spawn(move || { let result = Command::new(cmd_path) .args(cmd_args) @@ -227,14 +232,13 @@ fn run_task_command( } else { // Simulate task if not running under Windows thread::spawn(|| sleep(Duration::from_millis(250))) - }; - TaskHandle { handle, task } + } } -fn run_task_diskpart(script: &str, task_tx: mpsc::UnboundedSender) -> TaskHandle { - let task = Task::Diskpart(String::from(script)); +fn run_task_diskpart(script: &str, task_tx: mpsc::UnboundedSender) -> JoinHandle<()> { + let task = TaskType::Diskpart(String::from(script)); let task_str = format!("{:?}", &task); - let handle = if cfg!(windows) { + if cfg!(windows) { let script = String::from(script); thread::spawn(move || { let output = diskpart::run_script_raw(&script); @@ -251,6 +255,5 @@ fn run_task_diskpart(script: &str, task_tx: mpsc::UnboundedSender) -> Ta } else { // Simulate task if not running under Windows thread::spawn(|| sleep(Duration::from_millis(250))) - }; - TaskHandle { handle, task } + } } diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 463565e..cbbe506 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -26,7 +26,7 @@ use core::{ boot, cpu::get_cpu_name, disk::PartitionTableType, diskpart::build_dest_format_script, drivers, }, - tasks::{Task, Tasks}, + tasks::{TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ @@ -163,7 +163,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, @@ -191,7 +191,7 @@ impl App { ))?; // (Re)Enable volume mounting - self.tasks.add(Task::Command( + self.tasks.add(TaskType::Command( PathBuf::from(format!("{system32}/mountvol.exe")), vec![String::from("/e")], )); @@ -202,7 +202,7 @@ impl App { 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)); } } } @@ -211,12 +211,12 @@ impl App { popup::Type::Info, String::from("Running Clone Tool"), ))?; - self.tasks.add(Task::Command( + self.tasks.add(TaskType::Command( 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 => { diff --git a/pe_menu/src/app.rs b/pe_menu/src/app.rs index 4922332..c8d4389 100644 --- a/pe_menu/src/app.rs +++ b/pe_menu/src/app.rs @@ -22,7 +22,7 @@ use core::{ config::Config, line::DVLine, state::Mode, - tasks::{Task, Tasks}, + tasks::{TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ @@ -244,7 +244,7 @@ impl App { self.action_tx.send(Action::Select(None, None))?; } Action::OpenTerminal => { - self.tasks.add(Task::Command( + self.tasks.add(TaskType::Command( PathBuf::from("cmd.exe"), vec![String::from("-new_console:n")], )); @@ -254,11 +254,11 @@ impl App { popup::Type::Info, String::from("Restarting..."), ))?; - self.tasks.add(Task::Command( + self.tasks.add(TaskType::Command( PathBuf::from("X:/Windows/System32/sync64.exe"), vec![String::from("-r")], )); - self.tasks.add(Task::Command( + self.tasks.add(TaskType::Command( PathBuf::from("X:/Windows/System32/wpeutil.exe"), vec![String::from("reboot")], )); @@ -269,11 +269,11 @@ impl App { popup::Type::Info, String::from("Powering off..."), ))?; - self.tasks.add(Task::Command( + self.tasks.add(TaskType::Command( PathBuf::from("X:/Windows/System32/sync64.exe"), vec![String::from("-r")], )); - self.tasks.add(Task::Command( + self.tasks.add(TaskType::Command( PathBuf::from("X:/Windows/System32/wpeutil.exe"), vec![String::from("shutdown")], )); @@ -370,7 +370,7 @@ fn get_chunks(r: Rect) -> Vec { chunks } -pub fn build_command(app: &App, tool: &Tool) -> Task { +pub fn build_command(app: &App, tool: &Tool) -> TaskType { let cmd_path: PathBuf; let mut cmd_args: Vec = Vec::new(); let start_index: usize; @@ -390,7 +390,7 @@ pub fn build_command(app: &App, tool: &Tool) -> Task { }); } } - Task::Command(cmd_path, cmd_args) + TaskType::Command(cmd_path, cmd_args) } fn build_left_items(app: &App) -> Action { From 2829fbcac10720694ad01dd4f0b6a583d96bcd9d Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Wed, 12 Feb 2025 00:05:30 -0800 Subject: [PATCH 19/26] Move task result handling to app(s) from core Allows different handling in deja-vu vs boot-diags --- core/src/tasks.rs | 85 ++++++++++++++++++++++++---------------------- deja_vu/src/app.rs | 35 +++++++++++++++++-- 2 files changed, 76 insertions(+), 44 deletions(-) diff --git a/core/src/tasks.rs b/core/src/tasks.rs index 13ba8d5..296ea6a 100644 --- a/core/src/tasks.rs +++ b/core/src/tasks.rs @@ -33,6 +33,12 @@ use crate::{ system::{disk, diskpart}, }; +#[derive(Clone, Debug)] +pub enum TaskResult { + Error(String), + Output(String, String, bool), // stdout, stderr, success +} + #[derive(Clone, Debug)] pub enum TaskType { Command(PathBuf, Vec), // (command, args) @@ -46,6 +52,7 @@ pub enum TaskType { #[derive(Debug)] pub struct Task { pub handle: Option>, + pub result: Option, pub task_type: TaskType, } @@ -53,6 +60,7 @@ impl Task { pub fn new(task_type: TaskType) -> Task { Task { handle: None, + result: None, task_type, } } @@ -65,8 +73,8 @@ pub struct Tasks { 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 { @@ -97,10 +105,12 @@ impl Tasks { pub fn poll(&mut self) -> Result> { let mut return_task: Option = None; - // 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:?}"); + // 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). @@ -114,7 +124,7 @@ impl Tasks { } if self.task_list.is_empty() { // No tasks remain - self.task_tx.send(Action::NextScreen)?; + self.action_tx.send(Action::NextScreen)?; } else { // Start next task self.start()?; @@ -195,10 +205,17 @@ impl Tasks { } } +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, + task_tx: mpsc::UnboundedSender, ) -> JoinHandle<()> { if cfg!(windows) { thread::spawn(move || { @@ -206,27 +223,19 @@ fn run_task_command( .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}",))) - } + 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()); } - 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 { @@ -235,22 +244,16 @@ fn run_task_command( } } -fn run_task_diskpart(script: &str, task_tx: mpsc::UnboundedSender) -> JoinHandle<()> { - let task = TaskType::Diskpart(String::from(script)); - let task_str = format!("{:?}", &task); +fn run_task_diskpart(script: &str, task_tx: mpsc::UnboundedSender) -> JoinHandle<()> { if cfg!(windows) { - let script = String::from(script); + let script = script.to_owned(); thread::spawn(move || { let output = diskpart::run_script_raw(&script); - 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:?}"); - } + 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 diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index cbbe506..7ee37dd 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -26,7 +26,7 @@ use core::{ boot, cpu::get_cpu_name, disk::PartitionTableType, diskpart::build_dest_format_script, drivers, }, - tasks::{TaskType, Tasks}, + tasks::{TaskResult, TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ @@ -375,8 +375,37 @@ impl App { 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 (Action::NextScreen is sent when task(s) are done) + if let Some(task) = self.tasks.poll()? { + match task.task_type { + TaskType::Command(_, _) | TaskType::Diskpart(_) => { + 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}"), + ))?; + } + } + } + } + } + _ => {} + } + } } _ => {} } From 121beeaa78746408e1ae100f776c3d9d4d1ef2ea Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Wed, 12 Feb 2025 19:17:08 -0800 Subject: [PATCH 20/26] Send TasksComplete instead of NextScreen Should allow for better per-app workflow --- boot_diags/src/app.rs | 11 ++++++++--- core/src/action.rs | 1 + core/src/tasks.rs | 4 ++-- deja_vu/src/app.rs | 1 + 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index 6854bf8..b0d7a05 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -296,14 +296,12 @@ impl App { // Check background task(s) match self.cur_mode { Mode::BootDiags => { - // Check result of background task (if finished) if let Some(task) = self.tasks.poll()? { // TODO: Impl logic } } Mode::ScanDisks => { - // Check background task (Once all are complete Action::NextScreen is sent) - self.tasks.poll()?; + let _ = self.tasks.poll()?; } _ => {} } @@ -385,6 +383,13 @@ impl App { 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 { diff --git a/core/src/action.rs b/core/src/action.rs index b2928a2..5db5638 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -29,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) diff --git a/core/src/tasks.rs b/core/src/tasks.rs index 296ea6a..92fe95b 100644 --- a/core/src/tasks.rs +++ b/core/src/tasks.rs @@ -114,7 +114,7 @@ impl Tasks { } // Check status of current task (if one is running). - // NOTE: Action::NextScreen is sent once all tasks are complete + // 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 @@ -124,7 +124,7 @@ impl Tasks { } if self.task_list.is_empty() { // No tasks remain - self.action_tx.send(Action::NextScreen)?; + self.action_tx.send(Action::TasksComplete)?; } else { // Start next task self.start()?; diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 7ee37dd..0c3cb20 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -530,6 +530,7 @@ impl App { _ => {} }; } + Action::TasksComplete => self.action_tx.send(Action::NextScreen)?, _ => {} } for component in &mut self.components { From f272b0c482b8942e8dee2b870ff92287e89f4a6b Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 15 Feb 2025 17:06:19 -0800 Subject: [PATCH 21/26] WIP: New data structs for boot diags --- boot_diags/src/app.rs | 129 ++++++++++++++++++++++++++++++++++------ boot_diags/src/diags.rs | 56 +++++++++++++++++ boot_diags/src/main.rs | 1 + config/config.json5 | 7 +++ core/src/action.rs | 2 +- core/src/state.rs | 1 + deja_vu/src/app.rs | 75 ++++++++++++----------- 7 files changed, 217 insertions(+), 54 deletions(-) create mode 100644 boot_diags/src/diags.rs diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index b0d7a05..509bcfd 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -13,6 +13,7 @@ // You should have received a copy of the GNU General Public License // along with Deja-Vu. If not, see . // +use crate::diags::Groups as DiagGroups; use core::{ action::Action, components::{ @@ -27,7 +28,7 @@ use core::{ cpu::get_cpu_name, drivers, }, - tasks::{TaskType, Tasks}, + tasks::{Task, TaskResult, TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ @@ -44,7 +45,7 @@ use ratatui::{ style::Color, }; use tokio::sync::mpsc; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; pub struct App { // TUI @@ -52,6 +53,7 @@ pub struct App { action_tx: mpsc::UnboundedSender, components: Vec>, config: Config, + diag_groups: DiagGroups, frame_rate: f64, last_tick_key_events: Vec, should_quit: bool, @@ -74,7 +76,7 @@ impl App { let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); let mut list = StatefulList::default(); list.set_items(vec![ - Mode::BootDiags, + Mode::BootScan, Mode::BootSetup, Mode::InjectDrivers, Mode::SetBootMode, @@ -92,6 +94,7 @@ impl App { Box::new(popup::Popup::new()), ], config: Config::new()?, + diag_groups: DiagGroups::new(), frame_rate, last_tick_key_events: Vec::new(), should_quit: false, @@ -134,7 +137,8 @@ impl App { Mode::ScanDisks => Mode::SelectDisks, Mode::SelectDisks => Mode::SelectParts, Mode::SelectParts => Mode::DiagMenu, - Mode::BootDiags | Mode::BootSetup => Mode::DiagMenu, // TODO: add Mode::ProgressReport + Mode::BootDiags | Mode::BootSetup => Mode::DiagMenu, + Mode::BootScan => Mode::BootDiags, Mode::InjectDrivers | Mode::SetBootMode => Mode::DiagMenu, Mode::Done => Mode::DiagMenu, Mode::Failed => Mode::Failed, @@ -175,6 +179,15 @@ impl App { self.selections[1] = None; self.list.select_first_item(); } + Mode::BootScan => { + if self.tasks.idle() { + self.tasks.add(TaskType::Sleep); + } + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Gathering info..."), + ))?; + } Mode::InjectDrivers | Mode::InstallDrivers => self.clone.scan_drivers(), Mode::ScanDisks => { if self.tasks.idle() { @@ -294,16 +307,8 @@ impl App { Action::Tick => { self.last_tick_key_events.drain(..); // Check background task(s) - match self.cur_mode { - Mode::BootDiags => { - if let Some(task) = self.tasks.poll()? { - // TODO: Impl logic - } - } - Mode::ScanDisks => { - let _ = self.tasks.poll()?; - } - _ => {} + if let Some(task) = self.tasks.poll()? { + self.handle_task(&task)?; } } Action::Quit => self.should_quit = true, @@ -317,6 +322,7 @@ impl App { .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))?; } @@ -407,6 +413,94 @@ impl App { Ok(()) } + fn handle_task(&mut self, task: &Task) -> Result<()> { + match self.cur_mode { + Mode::BootDiags => { + if let TaskType::Command(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; + } + }; + match cmd_name { + "bcdedit" => { + // Boot config + let title = "Boot Files"; + 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()); + } + } + "manage-bde" => { + // Bitlocker + } + "chkdsk" => { + // File system + } + "reg" => { + // Registry + } + "dir" => { + // Files/Folders + } + _ => { + warn!("Unrecognized command: {:?}", &cmd_path) + } + } + } + } + _ => { + match task.task_type { + TaskType::Command(_, _) | 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())[..] { @@ -484,7 +578,8 @@ fn get_chunks(r: Rect) -> Vec { fn build_footer_string(cur_mode: Mode) -> String { match cur_mode { - Mode::BootDiags | Mode::BootSetup | Mode::Home | Mode::PEMenu | Mode::ScanDisks => { + Mode::BootDiags => String::from("(r) to refresh / (q) to quit"), + Mode::BootScan | Mode::BootSetup | Mode::Home | Mode::PEMenu | Mode::ScanDisks => { String::from("(q) to quit") } Mode::InstallDrivers | Mode::InjectDrivers | Mode::SetBootMode => { @@ -547,7 +642,7 @@ fn build_left_items(app: &App) -> Action { .iter() .for_each(|disk| items.push(disk.description.to_string())); } - Mode::ScanDisks => { + Mode::BootScan | Mode::ScanDisks => { select_num = 0; title = String::from("Processing"); } @@ -747,7 +842,7 @@ fn build_right_items(app: &App) -> Action { fn get_mode_strings(mode: Mode) -> (String, String) { match mode { - Mode::BootDiags => ( + Mode::BootScan | Mode::BootDiags => ( String::from("Boot Diagnostics"), String::from("Check for common Windows boot issues"), ), diff --git a/boot_diags/src/diags.rs b/boot_diags/src/diags.rs new file mode 100644 index 0000000..33c5275 --- /dev/null +++ b/boot_diags/src/diags.rs @@ -0,0 +1,56 @@ +// 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 struct Groups { + items: HashMap, +} + +impl Groups { + pub fn new() -> Self { + Groups { + items: HashMap::new(), + } + } + + 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.items.insert( + title.clone(), + Line { + title, + passed, + info: vec![info], + }, + ); + } + } +} + +pub struct Line { + title: String, + passed: bool, + 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); + } +} diff --git a/boot_diags/src/main.rs b/boot_diags/src/main.rs index 82b0996..3cefdda 100644 --- a/boot_diags/src/main.rs +++ b/boot_diags/src/main.rs @@ -20,6 +20,7 @@ use core; use crate::app::App; mod app; +mod diags; #[tokio::main] async fn main() -> Result<()> { diff --git a/config/config.json5 b/config/config.json5 index 15471b4..802c914 100644 --- a/config/config.json5 +++ b/config/config.json5 @@ -109,6 +109,13 @@ "": "Process", "": "KeyUp", "": "KeyDown", + "": "BootScan", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "BootScan": { "": "Quit", "": "Quit", "": "Quit", diff --git a/core/src/action.rs b/core/src/action.rs index 5db5638..486cdbb 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -21,7 +21,7 @@ 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) - // TODO: Add actions? + BootScan, // App (Clone) Highlight(usize), InstallDriver, diff --git a/core/src/state.rs b/core/src/state.rs index e4bc8c9..8c8eb8d 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -33,6 +33,7 @@ pub enum Mode { // Boot Diags DiagMenu, BootDiags, + BootScan, BootSetup, InjectDrivers, SetBootMode, diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 0c3cb20..293be47 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -26,7 +26,7 @@ use core::{ boot, cpu::get_cpu_name, disk::PartitionTableType, diskpart::build_dest_format_script, drivers, }, - tasks::{TaskResult, TaskType, Tasks}, + tasks::{Task, TaskResult, TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ @@ -116,6 +116,7 @@ impl App { | Mode::PostClone => None, // Invalid states Mode::BootDiags + | Mode::BootScan | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers @@ -140,6 +141,7 @@ impl App { Mode::Failed => Mode::Failed, // Invalid states Mode::BootDiags + | Mode::BootScan | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers @@ -373,41 +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 (Action::NextScreen is sent when task(s) are done) - if let Some(task) = self.tasks.poll()? { - match task.task_type { - TaskType::Command(_, _) | TaskType::Diskpart(_) => { - 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}"), - ))?; - } - } - } - } - } - _ => {} - } - } - } - _ => {} + // Check background task(s) + if let Some(task) = self.tasks.poll()? { + self.handle_task(&task)?; } } Action::Quit => self.should_quit = true, @@ -548,6 +518,37 @@ impl App { Ok(()) } + fn handle_task(&mut self, task: &Task) -> Result<()> { + match task.task_type { + TaskType::Command(_, _) | 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())[..] { @@ -638,6 +639,7 @@ fn build_footer_string(cur_mode: Mode) -> String { Mode::Done | Mode::Failed => String::from("(Enter) or (q) to quit"), // Invalid states Mode::BootDiags + | Mode::BootScan | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers @@ -708,6 +710,7 @@ fn build_left_items(app: &App, cur_mode: Mode) -> Action { } // Invalid states Mode::BootDiags + | Mode::BootScan | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers From 2b2a5160fdcf9078118f0a0a93ae194b21ed1821 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 15 Feb 2025 20:30:40 -0800 Subject: [PATCH 22/26] Add boot diag tasks --- boot_diags/src/app.rs | 188 +++++++++++++++++++++++++++++++++++----- boot_diags/src/diags.rs | 52 ++++++++++- deja_vu/src/app.rs | 10 +-- 3 files changed, 219 insertions(+), 31 deletions(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index 509bcfd..1ac444d 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License // along with Deja-Vu. If not, see . // -use crate::diags::Groups as DiagGroups; +use crate::diags; use core::{ action::Action, components::{ @@ -26,6 +26,7 @@ use core::{ system::{ boot::{self, SafeMode}, cpu::get_cpu_name, + disk::PartitionTableType, drivers, }, tasks::{Task, TaskResult, TaskType, Tasks}, @@ -34,6 +35,7 @@ use core::{ use std::{ env, iter::zip, + path::PathBuf, sync::{Arc, Mutex}, }; @@ -53,7 +55,7 @@ pub struct App { action_tx: mpsc::UnboundedSender, components: Vec>, config: Config, - diag_groups: DiagGroups, + diag_groups: diags::Groups, frame_rate: f64, last_tick_key_events: Vec, should_quit: bool, @@ -94,7 +96,7 @@ impl App { Box::new(popup::Popup::new()), ], config: Config::new()?, - diag_groups: DiagGroups::new(), + diag_groups: diags::Groups::new(), frame_rate, last_tick_key_events: Vec::new(), should_quit: false, @@ -180,13 +182,126 @@ impl App { self.list.select_first_item(); } Mode::BootScan => { - if self.tasks.idle() { - self.tasks.add(TaskType::Sleep); - } 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::Command( + 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::Command( + PathBuf::from(format!("{system32}/manage-bde.exe")), + vec![String::from("-status"), format!("{letter_os}:")], + )); + self.tasks.add(TaskType::Command( + PathBuf::from(format!("{system32}/manage-bde.exe")), + vec![ + String::from("-protectors"), + String::from("-get"), + format!("{letter_os}:"), + ], + )); + + // DISM Health + self.tasks.add(TaskType::Command( + PathBuf::from(format!("{system32}/dism.exe")), + vec![format!("{letter_os}:")], + )); + + // Filesystem Health + self.tasks.add(TaskType::Command( + PathBuf::from(format!("{system32}/chkdsk.exe")), + vec![format!("{letter_os}:")], + )); + + // Registry + self.tasks.add(TaskType::Command( + 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::Command( + 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::Command( + 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::Command( + PathBuf::from(format!("{system32}/reg.exe")), + vec![String::from("unload"), String::from("HKLM\\TmpSoftware")], + )); + self.tasks.add(TaskType::Command( + PathBuf::from(format!("{system32}/reg.exe")), + vec![String::from("unload"), String::from("HKLM\\TmpSystem")], + )); + self.tasks.add(TaskType::Command( + 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 => { @@ -414,8 +529,9 @@ impl App { } fn handle_task(&mut self, task: &Task) -> Result<()> { + info!("Handling Task: {task:?}"); match self.cur_mode { - Mode::BootDiags => { + Mode::BootScan => { if let TaskType::Command(cmd_path, cmd_args) = &task.task_type { let mut cmd_name = ""; if let Some(path) = cmd_path.file_name() { @@ -423,9 +539,9 @@ impl App { cmd_name = cmd_str; } }; - match cmd_name { - "bcdedit" => { - // Boot config + let diag_type = diags::get_type(cmd_name); + match diag_type { + diags::Type::BootConfigData => { let title = "Boot Files"; if let Some(result) = &task.result { let passed: bool; @@ -449,18 +565,10 @@ impl App { .update(title.to_string(), passed, info.to_owned()); } } - "manage-bde" => { - // Bitlocker - } - "chkdsk" => { - // File system - } - "reg" => { - // Registry - } - "dir" => { - // Files/Folders - } + diags::Type::Bitlocker => {} + diags::Type::FileSystem => {} + diags::Type::Registry => {} + diags::Type::FilesAndFolders => {} _ => { warn!("Unrecognized command: {:?}", &cmd_path) } @@ -660,7 +768,19 @@ fn build_left_items(app: &App) -> Action { } } } - Mode::BootDiags | Mode::BootSetup => { + 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; @@ -715,6 +835,14 @@ fn build_right_items(app: &App) -> Action { )], line_colors: vec![Color::Reset], }, + DVLine { + line_parts: vec![String::from("-----")], + line_colors: vec![Color::Reset], + }, + DVLine { + line_parts: vec![format!("diag_groups: {:?}", &app.diag_groups)], + line_colors: vec![Color::Yellow], + }, DVLine { line_parts: vec![String::from("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")], line_colors: vec![Color::Reset], @@ -774,6 +902,20 @@ fn build_right_items(app: &App) -> Action { ]); }); } + 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")], diff --git a/boot_diags/src/diags.rs b/boot_diags/src/diags.rs index 33c5275..70b5741 100644 --- a/boot_diags/src/diags.rs +++ b/boot_diags/src/diags.rs @@ -15,21 +15,44 @@ 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 { @@ -42,10 +65,11 @@ impl Groups { } } +#[derive(Clone, Debug)] pub struct Line { - title: String, - passed: bool, - info: Vec, + pub title: String, + pub passed: bool, + pub info: Vec, } impl Line { @@ -54,3 +78,25 @@ impl Line { self.info.push(info); } } + +pub fn get_type(cmd_name: &str) -> Type { + if cmd_name == "exa" { + return Type::BootConfigData; + } + if cmd_name == "bcdedit" { + return Type::BootConfigData; + } + if cmd_name == "dir" { + return Type::FilesAndFolders; + } + if cmd_name == "reg" { + return Type::Registry; + } + if cmd_name == "chkdsk" { + return Type::FileSystem; + } + if cmd_name == "manage-bde" { + return Type::Bitlocker; + } + Type::Unknown +} diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 293be47..9c7a125 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -173,6 +173,11 @@ impl App { ))?; } Mode::PreClone => { + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Formatting destination disk"), + ))?; + // Get System32 path let system32 = if cfg!(windows) { if let Ok(path) = env::var("SYSTEMROOT") { @@ -187,11 +192,6 @@ impl App { String::from(".") }; - self.action_tx.send(Action::DisplayPopup( - popup::Type::Info, - String::from("Formatting destination disk"), - ))?; - // (Re)Enable volume mounting self.tasks.add(TaskType::Command( PathBuf::from(format!("{system32}/mountvol.exe")), From c5a9dc80cb8bf001a5af2a9bc4684e54a54854fa Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 16 Feb 2025 23:16:26 -0800 Subject: [PATCH 23/26] Update PEMenu --- boot_diags/src/app.rs | 9 +++++++-- .../menu_entries/{01_deja-vu.toml => 10_deja-vu.toml} | 0 include/menu_entries/11_boot-diags.toml | 5 +++++ .../{02_separator.toml => 20_separator.toml} | 0 .../menu_entries/{03_ntpwedit.toml => 30_ntpwedit.toml} | 0 .../{04_clone-tool.toml => 31_clone-tool.toml} | 0 .../menu_entries/{05_taskmgr.toml => 32_taskmgr.toml} | 0 pe_menu/src/app.rs | 8 +++++--- 8 files changed, 17 insertions(+), 5 deletions(-) rename include/menu_entries/{01_deja-vu.toml => 10_deja-vu.toml} (100%) create mode 100644 include/menu_entries/11_boot-diags.toml rename include/menu_entries/{02_separator.toml => 20_separator.toml} (100%) rename include/menu_entries/{03_ntpwedit.toml => 30_ntpwedit.toml} (100%) rename include/menu_entries/{04_clone-tool.toml => 31_clone-tool.toml} (100%) rename include/menu_entries/{05_taskmgr.toml => 32_taskmgr.toml} (100%) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index 1ac444d..7e2cbea 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -687,7 +687,7 @@ fn get_chunks(r: Rect) -> Vec { 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::PEMenu | Mode::ScanDisks => { + Mode::BootScan | Mode::BootSetup | Mode::Home | Mode::ScanDisks => { String::from("(q) to quit") } Mode::InstallDrivers | Mode::InjectDrivers | Mode::SetBootMode => { @@ -702,7 +702,12 @@ fn build_footer_string(cur_mode: Mode) -> String { ), Mode::Failed => String::from("(Enter) or (q) to quit"), // Invalid states - Mode::Confirm | Mode::Clone | Mode::PreClone | Mode::PostClone | Mode::SelectTableType => { + Mode::Confirm + | Mode::Clone + | Mode::PEMenu + | Mode::PreClone + | Mode::PostClone + | Mode::SelectTableType => { panic!("This shouldn't happen?") } } 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/src/app.rs b/pe_menu/src/app.rs index c8d4389..0e5345c 100644 --- a/pe_menu/src/app.rs +++ b/pe_menu/src/app.rs @@ -237,8 +237,9 @@ impl App { 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))?; @@ -376,8 +377,9 @@ pub fn build_command(app: &App, tool: &Tool) -> TaskType { 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()); From f1c3743584b4227f62d9c4b113bd246712791b45 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 22 Feb 2025 20:44:28 -0800 Subject: [PATCH 24/26] Split TaskType::Command into wait and no-wait variants Allows for apps to be simply launched --- boot_diags/src/app.rs | 28 +++++++++++++++------------- core/src/system/boot.rs | 10 +++++----- core/src/tasks.rs | 33 ++++++++++++++++++++++++++++++--- deja_vu/src/app.rs | 14 ++++++++------ pe_menu/src/app.rs | 16 ++++++++-------- 5 files changed, 66 insertions(+), 35 deletions(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index 7e2cbea..4c29f20 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -218,7 +218,7 @@ impl App { } // BCD - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/bcdedit.exe")), vec![ String::from("/store"), @@ -235,11 +235,11 @@ impl App { )); // Bitlocker - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/manage-bde.exe")), vec![String::from("-status"), format!("{letter_os}:")], )); - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/manage-bde.exe")), vec![ String::from("-protectors"), @@ -249,19 +249,19 @@ impl App { )); // DISM Health - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/dism.exe")), vec![format!("{letter_os}:")], )); // Filesystem Health - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/chkdsk.exe")), vec![format!("{letter_os}:")], )); // Registry - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/reg.exe")), vec![ String::from("load"), @@ -269,7 +269,7 @@ impl App { format!("{letter_os}:\\Windows\\System32\\config\\SOFTWARE"), ], )); - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/reg.exe")), vec![ String::from("load"), @@ -277,7 +277,7 @@ impl App { format!("{letter_os}:\\Windows\\System32\\config\\SYSTEM"), ], )); - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/reg.exe")), vec![ String::from("load"), @@ -285,15 +285,15 @@ impl App { format!("{letter_os}:\\Windows\\System32\\config\\DEFAULT"), ], )); - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/reg.exe")), vec![String::from("unload"), String::from("HKLM\\TmpSoftware")], )); - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/reg.exe")), vec![String::from("unload"), String::from("HKLM\\TmpSystem")], )); - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/reg.exe")), vec![String::from("unload"), String::from("HKU\\TmpDefault")], )); @@ -532,7 +532,7 @@ impl App { info!("Handling Task: {task:?}"); match self.cur_mode { Mode::BootScan => { - if let TaskType::Command(cmd_path, cmd_args) = &task.task_type { + 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() { @@ -577,7 +577,9 @@ impl App { } _ => { match task.task_type { - TaskType::Command(_, _) | TaskType::Diskpart(_) => { + TaskType::CommandNoWait(_, _) + | TaskType::CommandWait(_, _) + | TaskType::Diskpart(_) => { // Check result if let Some(result) = &task.result { match result { diff --git a/core/src/system/boot.rs b/core/src/system/boot.rs index 97862ef..3a87d2d 100644 --- a/core/src/system/boot.rs +++ b/core/src/system/boot.rs @@ -34,7 +34,7 @@ pub fn configure_disk( let mut tasks = Vec::new(); // Create - tasks.push(TaskType::Command( + tasks.push(TaskType::CommandWait( PathBuf::from(format!("{system32}/bcdboot.exe")), vec![ format!("{letter_os}:\\Windows"), @@ -50,7 +50,7 @@ pub fn configure_disk( // Update boot sector (for legacy setups) if table_type == PartitionTableType::Legacy { - tasks.push(TaskType::Command( + tasks.push(TaskType::CommandWait( PathBuf::from(format!("{system32}/bootsect.exe")), vec![ String::from("/nt60"), @@ -74,7 +74,7 @@ pub fn configure_disk( 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(TaskType::Command( + Ok(TaskType::CommandWait( PathBuf::from(format!("{system32}/dism.exe")), vec![ format!("/image:{letter_os}:\\"), @@ -100,7 +100,7 @@ pub fn set_mode( } }; - // Build Command + // Build CommandWait let mut cmd_args = vec![String::from("/store"), bcd_path]; match mode { SafeMode::Disable => { @@ -115,7 +115,7 @@ pub fn set_mode( cmd_args.push(String::from("minimal")); } } - Ok(TaskType::Command( + Ok(TaskType::CommandWait( PathBuf::from(format!("{system32}/bcdedit.exe")), cmd_args, )) diff --git a/core/src/tasks.rs b/core/src/tasks.rs index 92fe95b..b0bd7a9 100644 --- a/core/src/tasks.rs +++ b/core/src/tasks.rs @@ -16,6 +16,7 @@ use std::{ collections::VecDeque, + fmt, path::PathBuf, process::{Command, Stdio}, sync::{Arc, Mutex}, @@ -41,14 +42,36 @@ pub enum TaskResult { #[derive(Clone, Debug)] pub enum TaskType { - Command(PathBuf, Vec), // (command, args) - Diskpart(String), // (script_as_string) + 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>, @@ -145,7 +168,11 @@ impl Tasks { if let Some(task) = self.cur_task.take() { let task_tx = self.task_tx.clone(); match task.task_type { - TaskType::Command(ref cmd_path, ref cmd_args) => { + TaskType::CommandNoWait(ref cmd_path, ref cmd_args) => { + self.cur_handle = None; + run_task_command(cmd_path.clone(), cmd_args.clone(), task_tx); + } + TaskType::CommandWait(ref cmd_path, ref cmd_args) => { self.cur_handle = Some(run_task_command( cmd_path.clone(), cmd_args.clone(), diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 9c7a125..635b62a 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -193,7 +193,7 @@ impl App { }; // (Re)Enable volume mounting - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from(format!("{system32}/mountvol.exe")), vec![String::from("/e")], )); @@ -213,7 +213,7 @@ impl App { popup::Type::Info, String::from("Running Clone Tool"), ))?; - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( self.config.clone_app_path.clone(), Vec::new(), )); @@ -520,13 +520,13 @@ impl App { fn handle_task(&mut self, task: &Task) -> Result<()> { match task.task_type { - TaskType::Command(_, _) | TaskType::Diskpart(_) => { + 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}")))?; + .send(Action::Error(format!("{} Failed: {msg}", task.task_type)))?; } TaskResult::Output(stdout, stderr, success) => { if !success { @@ -537,8 +537,10 @@ impl App { } else { String::from("Unknown Error") }; - self.action_tx - .send(Action::Error(format!("{task:?} Failed: {msg}")))?; + self.action_tx.send(Action::Error(format!( + "{} Failed: {msg}", + task.task_type + )))?; } } } diff --git a/pe_menu/src/app.rs b/pe_menu/src/app.rs index 0e5345c..b817f2f 100644 --- a/pe_menu/src/app.rs +++ b/pe_menu/src/app.rs @@ -230,7 +230,7 @@ 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)?, @@ -245,7 +245,7 @@ impl App { self.action_tx.send(Action::Select(None, None))?; } Action::OpenTerminal => { - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandNoWait( PathBuf::from("cmd.exe"), vec![String::from("-new_console:n")], )); @@ -255,11 +255,11 @@ impl App { popup::Type::Info, String::from("Restarting..."), ))?; - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from("X:/Windows/System32/sync64.exe"), vec![String::from("-r")], )); - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from("X:/Windows/System32/wpeutil.exe"), vec![String::from("reboot")], )); @@ -270,11 +270,11 @@ impl App { popup::Type::Info, String::from("Powering off..."), ))?; - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from("X:/Windows/System32/sync64.exe"), vec![String::from("-r")], )); - self.tasks.add(TaskType::Command( + self.tasks.add(TaskType::CommandWait( PathBuf::from("X:/Windows/System32/wpeutil.exe"), vec![String::from("shutdown")], )); @@ -371,7 +371,7 @@ fn get_chunks(r: Rect) -> Vec { chunks } -pub fn build_command(app: &App, tool: &Tool) -> TaskType { +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; @@ -392,7 +392,7 @@ pub fn build_command(app: &App, tool: &Tool) -> TaskType { }); } } - TaskType::Command(cmd_path, cmd_args) + TaskType::CommandNoWait(cmd_path, cmd_args) } fn build_left_items(app: &App) -> Action { From f22501ce7d13a93fbf808eb292b9db45ee3f94d7 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 25 Feb 2025 02:48:19 -0800 Subject: [PATCH 25/26] Add elevation check at start --- Cargo.lock | 109 ++++++++++++++++++++++++++++++++++++----- boot_diags/Cargo.toml | 1 + boot_diags/src/main.rs | 24 +++++++-- deja_vu/Cargo.toml | 1 + deja_vu/src/main.rs | 24 +++++++-- 5 files changed, 138 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1296869..9a21a57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,7 @@ name = "boot-diags" version = "0.1.0" dependencies = [ "anyhow", + "check_elevation", "clap", "color-eyre", "core", @@ -289,6 +290,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "check_elevation" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f7310b71e7b968cdadd13480b9e4f2def9f173f67fd7317e8eddb8d7a4ba00" +dependencies = [ + "windows", +] + [[package]] name = "clap" version = "4.5.27" @@ -585,6 +595,7 @@ name = "deja-vu" version = "0.2.0" dependencies = [ "anyhow", + "check_elevation", "clap", "color-eyre", "core", @@ -937,7 +948,7 @@ dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2044,7 +2055,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3129,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]] @@ -3144,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]] @@ -3153,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" @@ -3187,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" diff --git a/boot_diags/Cargo.toml b/boot_diags/Cargo.toml index cf73a7a..4cd14e3 100644 --- a/boot_diags/Cargo.toml +++ b/boot_diags/Cargo.toml @@ -40,6 +40,7 @@ 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" diff --git a/boot_diags/src/main.rs b/boot_diags/src/main.rs index 3cefdda..7128445 100644 --- a/boot_diags/src/main.rs +++ b/boot_diags/src/main.rs @@ -24,11 +24,25 @@ mod diags; #[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/deja_vu/Cargo.toml b/deja_vu/Cargo.toml index ae4ab91..13af3e8 100644 --- a/deja_vu/Cargo.toml +++ b/deja_vu/Cargo.toml @@ -39,6 +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"] } +check_elevation = "0.2.4" [build-dependencies] anyhow = "1.0.86" diff --git a/deja_vu/src/main.rs b/deja_vu/src/main.rs index 82b0996..39999d2 100644 --- a/deja_vu/src/main.rs +++ b/deja_vu/src/main.rs @@ -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(()) } From 367a290b6d45e455edb3a3ea70863f4267d38a4c Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 2 Mar 2025 20:39:50 -0800 Subject: [PATCH 26/26] WIP: Expand boot diag text parsing --- boot_diags/src/app.rs | 111 ++++++++++++++++++++++++++-------------- boot_diags/src/diags.rs | 8 +-- 2 files changed, 76 insertions(+), 43 deletions(-) diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index 4c29f20..998c62e 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -190,7 +190,7 @@ impl App { // Get System32 path let system32 = if cfg!(windows) { if let Ok(path) = env::var("SYSTEMROOT") { - format!("{path}/System32") + format!("{path}\\System32") } else { self.action_tx.send(Action::Error(String::from( "ERROR\n\n\nFailed to find SYSTEMROOT", @@ -219,11 +219,11 @@ impl App { // BCD self.tasks.add(TaskType::CommandWait( - PathBuf::from(format!("{system32}/bcdedit.exe")), + PathBuf::from(format!("{system32}\\bcdedit.exe")), vec![ String::from("/store"), format!( - "{letter_boot}{}\\Boot\\BCD", + "{letter_boot}:{}\\Boot\\BCD", if table_type == PartitionTableType::Guid { "\\EFI\\Microsoft" } else { @@ -236,11 +236,11 @@ impl App { // Bitlocker self.tasks.add(TaskType::CommandWait( - PathBuf::from(format!("{system32}/manage-bde.exe")), + 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")), + PathBuf::from(format!("{system32}\\manage-bde.exe")), vec![ String::from("-protectors"), String::from("-get"), @@ -250,19 +250,19 @@ impl App { // DISM Health self.tasks.add(TaskType::CommandWait( - PathBuf::from(format!("{system32}/dism.exe")), + PathBuf::from(format!("{system32}\\dism.exe")), vec![format!("{letter_os}:")], )); // Filesystem Health self.tasks.add(TaskType::CommandWait( - PathBuf::from(format!("{system32}/chkdsk.exe")), + PathBuf::from(format!("{system32}\\chkdsk.exe")), vec![format!("{letter_os}:")], )); // Registry self.tasks.add(TaskType::CommandWait( - PathBuf::from(format!("{system32}/reg.exe")), + PathBuf::from(format!("{system32}\\reg.exe")), vec![ String::from("load"), String::from("HKLM\\TmpSoftware"), @@ -270,7 +270,7 @@ impl App { ], )); self.tasks.add(TaskType::CommandWait( - PathBuf::from(format!("{system32}/reg.exe")), + PathBuf::from(format!("{system32}\\reg.exe")), vec![ String::from("load"), String::from("HKLM\\TmpSystem"), @@ -278,7 +278,7 @@ impl App { ], )); self.tasks.add(TaskType::CommandWait( - PathBuf::from(format!("{system32}/reg.exe")), + PathBuf::from(format!("{system32}\\reg.exe")), vec![ String::from("load"), String::from("HKU\\TmpDefault"), @@ -286,15 +286,15 @@ impl App { ], )); self.tasks.add(TaskType::CommandWait( - PathBuf::from(format!("{system32}/reg.exe")), + 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")), + 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")), + PathBuf::from(format!("{system32}\\reg.exe")), vec![String::from("unload"), String::from("HKU\\TmpDefault")], )); @@ -539,10 +539,10 @@ impl App { cmd_name = cmd_str; } }; - let diag_type = diags::get_type(cmd_name); - match diag_type { - diags::Type::BootConfigData => { - let title = "Boot Files"; + 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; @@ -551,26 +551,48 @@ impl App { passed = false; info = msg.to_owned(); } - TaskResult::Output(stdout, stderr, success) => { + 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}"); + info = parse_chkdsk(stdout); } } - self.diag_groups - .update(title.to_string(), passed, info.to_owned()); + self.diag_groups.update( + String::from("Filesystem"), + passed, + info.to_owned(), + ); } + None // Don't set title since we handle the logic here } - diags::Type::Bitlocker => {} - diags::Type::FileSystem => {} - diags::Type::Registry => {} - diags::Type::FilesAndFolders => {} + diags::Type::Registry => Some("Registry"), + diags::Type::FilesAndFolders => Some("Critical Files"), _ => { - warn!("Unrecognized command: {:?}", &cmd_path) + 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()); } } } @@ -846,14 +868,6 @@ fn build_right_items(app: &App) -> Action { line_parts: vec![String::from("-----")], line_colors: vec![Color::Reset], }, - DVLine { - line_parts: vec![format!("diag_groups: {:?}", &app.diag_groups)], - line_colors: vec![Color::Yellow], - }, - DVLine { - line_parts: vec![String::from("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")], - line_colors: vec![Color::Reset], - }, ]); // TODO: DELETE THIS SECTION match app.cur_mode { @@ -1010,3 +1024,22 @@ fn get_mode_strings(mode: Mode) -> (String, String) { _ => 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 index 70b5741..05c5dbb 100644 --- a/boot_diags/src/diags.rs +++ b/boot_diags/src/diags.rs @@ -83,19 +83,19 @@ pub fn get_type(cmd_name: &str) -> Type { if cmd_name == "exa" { return Type::BootConfigData; } - if cmd_name == "bcdedit" { + if cmd_name == "bcdedit.exe" { return Type::BootConfigData; } if cmd_name == "dir" { return Type::FilesAndFolders; } - if cmd_name == "reg" { + if cmd_name == "reg.exe" { return Type::Registry; } - if cmd_name == "chkdsk" { + if cmd_name == "chkdsk.exe" { return Type::FileSystem; } - if cmd_name == "manage-bde" { + if cmd_name == "manage-bde.exe" { return Type::Bitlocker; } Type::Unknown