Compare commits

..

3 commits

Author SHA1 Message Date
3674fbdee7
Expand boot diagnostic sections 2024-12-01 16:50:28 -08:00
783e31a582
Set title based on CLI Mode 2024-12-01 16:49:59 -08:00
6938960f3e
Add initial boot diagnostic sections 2024-12-01 16:10:24 -08:00
9 changed files with 138 additions and 21 deletions

View file

@ -87,5 +87,41 @@
"<Ctrl-c>": "Quit", // Yet another way to quit "<Ctrl-c>": "Quit", // Yet another way to quit
"<Ctrl-z>": "Suspend" // Suspend the application "<Ctrl-z>": "Suspend" // Suspend the application
}, },
// Diagnostic modes
"DiagMenu": {
"<Enter>": "Process",
"<q>": "Quit", // Quit the application
"<Ctrl-d>": "Quit", // Another way to quit
"<Ctrl-c>": "Quit", // Yet another way to quit
"<Ctrl-z>": "Suspend" // Suspend the application
},
"BootDiags": {
"<Enter>": "Process",
"<q>": "Quit", // Quit the application
"<Ctrl-d>": "Quit", // Another way to quit
"<Ctrl-c>": "Quit", // Yet another way to quit
"<Ctrl-z>": "Suspend" // Suspend the application
},
"BootSetup": {
"<Enter>": "Process",
"<q>": "Quit", // Quit the application
"<Ctrl-d>": "Quit", // Another way to quit
"<Ctrl-c>": "Quit", // Yet another way to quit
"<Ctrl-z>": "Suspend" // Suspend the application
},
"InjectDrivers": {
"<Enter>": "Process",
"<q>": "Quit", // Quit the application
"<Ctrl-d>": "Quit", // Another way to quit
"<Ctrl-c>": "Quit", // Yet another way to quit
"<Ctrl-z>": "Suspend" // Suspend the application
},
"ToggleSafeBoot": {
"<Enter>": "Process",
"<q>": "Quit", // Quit the application
"<Ctrl-d>": "Quit", // Another way to quit
"<Ctrl-c>": "Quit", // Yet another way to quit
"<Ctrl-z>": "Suspend" // Suspend the application
},
} }
} }

View file

@ -31,6 +31,7 @@ use tracing::{debug, info};
use crate::{ use crate::{
action::Action, action::Action,
cli,
components::{ components::{
footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, title::Title, Component, footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, title::Title, Component,
}, },
@ -57,6 +58,7 @@ pub struct App {
should_suspend: bool, should_suspend: bool,
tick_rate: f64, tick_rate: f64,
// App // App
cli_cmd: cli::Command,
cur_mode: Mode, cur_mode: Mode,
disk_index_dest: Option<usize>, disk_index_dest: Option<usize>,
disk_index_source: Option<usize>, disk_index_source: Option<usize>,
@ -84,10 +86,16 @@ pub enum Mode {
PostClone, PostClone,
Done, Done,
Failed, Failed,
// Diagnostic modes
DiagMenu,
BootDiags,
BootSetup,
InjectDrivers,
ToggleSafeBoot,
} }
impl App { impl App {
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> { pub fn new(cli_cmd: cli::Command, tick_rate: f64, frame_rate: f64) -> Result<Self> {
let (action_tx, action_rx) = mpsc::unbounded_channel(); let (action_tx, action_rx) = mpsc::unbounded_channel();
let disk_list_arc = Arc::new(Mutex::new(Vec::new())); let disk_list_arc = Arc::new(Mutex::new(Vec::new()));
let mut tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); let mut tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone());
@ -97,13 +105,14 @@ impl App {
action_rx, action_rx,
action_tx, action_tx,
components: vec![ components: vec![
Box::new(Title::new()), Box::new(Title::new(cli_cmd)),
Box::new(FpsCounter::new()), Box::new(FpsCounter::new()),
Box::new(Left::new()), Box::new(Left::new()),
Box::new(Right::new()), Box::new(Right::new()),
Box::new(Footer::new()), Box::new(Footer::new()),
Box::new(popup::Popup::new()), Box::new(popup::Popup::new()),
], ],
cli_cmd,
config: Config::new()?, config: Config::new()?,
frame_rate, frame_rate,
last_tick_key_events: Vec::new(), last_tick_key_events: Vec::new(),
@ -111,7 +120,10 @@ impl App {
should_suspend: false, should_suspend: false,
tick_rate, tick_rate,
// App // 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_dest: None,
disk_index_source: None, disk_index_source: None,
disk_list: disk_list_arc, disk_list: disk_list_arc,
@ -127,6 +139,7 @@ impl App {
pub fn next_mode(&mut self) -> Option<Mode> { pub fn next_mode(&mut self) -> Option<Mode> {
let new_mode = match (self.prev_mode, self.cur_mode) { let new_mode = match (self.prev_mode, self.cur_mode) {
// Clone states
(_, Mode::InstallDrivers) => Mode::ScanDisks, (_, Mode::InstallDrivers) => Mode::ScanDisks,
(_, Mode::ScanDisks) => Mode::SelectDisks, (_, Mode::ScanDisks) => Mode::SelectDisks,
(_, Mode::SelectDisks | Mode::SelectTableType | Mode::SelectParts) => { (_, Mode::SelectDisks | Mode::SelectTableType | Mode::SelectParts) => {
@ -143,6 +156,14 @@ impl App {
(Mode::SelectParts, Mode::Confirm) => Mode::PostClone, (Mode::SelectParts, Mode::Confirm) => Mode::PostClone,
(_, Mode::PostClone | Mode::Done) => Mode::Done, (_, Mode::PostClone | Mode::Done) => Mode::Done,
(_, Mode::Failed) => Mode::Failed, (_, 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,
// Invalid states // Invalid states
(_, Mode::Confirm) => panic!("This shouldn't happen."), (_, Mode::Confirm) => panic!("This shouldn't happen."),
}; };
@ -247,7 +268,7 @@ impl App {
// Inject driver(s) (if selected) // Inject driver(s) (if selected)
if let Some(driver) = &self.driver { 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); self.tasks.add(task);
} else { } else {
self.action_tx.send(Action::Error(format!( self.action_tx.send(Action::Error(format!(
@ -288,6 +309,16 @@ impl App {
} }
let action_tx = self.action_tx.clone(); 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::SetMode(Mode::DiagMenu))?;
}
}
loop { loop {
self.handle_events(&mut tui).await?; self.handle_events(&mut tui).await?;
self.handle_actions(&mut tui)?; self.handle_actions(&mut tui)?;

View file

@ -13,13 +13,17 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Deja-vu. If not, see <https://www.gnu.org/licenses/>. // along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
// //
use clap::Parser; use clap::{Parser, Subcommand};
use crate::config::{get_config_dir, get_data_dir}; use crate::config::{get_config_dir, get_data_dir};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version = version(), about)] #[command(author, version = version(), about)]
pub struct Cli { pub struct Cli {
/// App mode
#[command(subcommand)]
pub cli_cmd: Command,
/// Tick rate, i.e. number of ticks per second /// Tick rate, i.e. number of ticks per second
#[arg(short, long, value_name = "FLOAT", default_value_t = 4.0)] #[arg(short, long, value_name = "FLOAT", default_value_t = 4.0)]
pub tick_rate: f64, pub tick_rate: f64,
@ -29,6 +33,15 @@ pub struct Cli {
pub frame_rate: f64, 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!( const VERSION_MESSAGE: &str = concat!(
env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_VERSION"),
"-", "-",

View file

@ -53,6 +53,7 @@ impl Component for Footer {
fn update(&mut self, action: Action) -> Result<Option<Action>> { fn update(&mut self, action: Action) -> Result<Option<Action>> {
if let Action::SetMode(new_mode) = action { if let Action::SetMode(new_mode) = action {
self.text = match new_mode { self.text = match new_mode {
// Clone modes
Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => {
String::from("(q) to quit") String::from("(q) to quit")
} }
@ -69,6 +70,13 @@ impl Component for Footer {
Mode::Done | Mode::Failed | Mode::InstallDrivers => { Mode::Done | Mode::Failed | Mode::InstallDrivers => {
String::from("(Enter) or (q) to quit") 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) Ok(None)

View file

@ -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), Action::Select(Some(index), None) => self.selections[0] = Some(index),
@ -218,6 +227,13 @@ impl Component for Left {
self.title_text = String::from("Confirm Selections (Again)"); self.title_text = String::from("Confirm Selections (Again)");
} }
(_, Mode::Done | Mode::Failed) => self.title_text = String::from("Done"), (_, Mode::Done | Mode::Failed) => self.title_text = String::from("Done"),
// Diagnostic states
(_, 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 // Invalid states
(_, Mode::Confirm) => panic!("This shouldn't happen."), (_, Mode::Confirm) => panic!("This shouldn't happen."),
} }
@ -270,10 +286,18 @@ impl Component for Left {
// Body // Body
match self.mode { match self.mode {
// Clone modes
Mode::ScanDisks Mode::ScanDisks
| Mode::PreClone | Mode::PreClone
| Mode::Clone | Mode::Clone
| Mode::PostClone | Mode::PostClone
// Diagnostic modes
| Mode::DiagMenu
| Mode::BootDiags
| Mode::BootSetup
| Mode::InjectDrivers
| Mode::ToggleSafeBoot
// Done
| Mode::Done | Mode::Done
| Mode::Failed => { | Mode::Failed => {
// Leave blank // Leave blank

View file

@ -45,7 +45,7 @@ pub struct Popup {
impl Popup { impl Popup {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
popup_text: String::from("Scanning Disks..."), popup_text: String::new(),
..Default::default() ..Default::default()
} }
} }

View file

@ -86,7 +86,7 @@ impl Component for Right {
}, },
Action::Process => { Action::Process => {
if self.prev_mode == Mode::SelectDisks && self.cur_mode == Mode::Confirm { 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) => { Action::Select(one, two) => {

View file

@ -21,17 +21,25 @@ use ratatui::{
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use super::Component; use super::Component;
use crate::{action::Action, config::Config}; use crate::{action::Action, cli, config::Config};
#[derive(Default)] #[derive(Default)]
pub struct Title { pub struct Title {
command_tx: Option<UnboundedSender<Action>>, command_tx: Option<UnboundedSender<Action>>,
config: Config, config: Config,
title_text: String,
} }
impl Title { impl Title {
pub fn new() -> Self { pub fn new(cli_cmd: cli::Command) -> Self {
Self::default() 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()
}
} }
} }
@ -46,19 +54,16 @@ impl Component for Title {
Ok(()) Ok(())
} }
fn update(&mut self, action: Action) -> Result<Option<Action>> { // fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action { // match action {
_ => {} // _ => {}
} // }
Ok(None) // Ok(None)
} // }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
// Title Block // Title Block
let title_text = Span::styled( let title_text = Span::styled(&self.title_text, Style::default().fg(Color::LightCyan));
"WizardKit: Clone Tool",
Style::default().fg(Color::LightCyan),
);
let title = Paragraph::new(Line::from(title_text).centered()) let title = Paragraph::new(Line::from(title_text).centered())
.block(Block::default().borders(Borders::ALL)); .block(Block::default().borders(Borders::ALL));
frame.render_widget(title, area); frame.render_widget(title, area);

View file

@ -37,7 +37,7 @@ async fn main() -> Result<()> {
crate::logging::init()?; crate::logging::init()?;
let args = Cli::parse(); 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?; app.run().await?;
Ok(()) Ok(())
} }