diff --git a/.config/config.json5 b/.config/config.json5 index f982a30..d2f61e0 100644 --- a/.config/config.json5 +++ b/.config/config.json5 @@ -1,5 +1,14 @@ { "keybindings": { + "InstallDrivers": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", // Quit the application + "": "Quit", // Another way to quit + "": "Quit", // Yet another way to quit + "": "Suspend" // Suspend the application + }, "ScanDisks": { "": "Quit", // Quit the application "": "Quit", // Another way to quit @@ -7,6 +16,7 @@ "": "Suspend" // Suspend the application }, "SelectDisks": { + "": "InstallDriver", "": "ScanDisks", "": "Process", "": "KeyUp", diff --git a/src/action.rs b/src/action.rs index 635a1ba..78849e2 100644 --- a/src/action.rs +++ b/src/action.rs @@ -16,7 +16,11 @@ use serde::{Deserialize, Serialize}; use strum::Display; -use crate::{app::Mode, components::popup::Type, system::disk::Disk}; +use crate::{ + app::Mode, + components::popup::Type, + system::{disk::Disk, drivers::Driver}, +}; #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] pub enum Action { @@ -31,6 +35,8 @@ pub enum Action { DisplayPopup(Type, String), Error(String), Help, + InstallDriver, + SelectDriver(Driver), KeyUp, KeyDown, SetMode(Mode), diff --git a/src/app.rs b/src/app.rs index 5e3b20c..6fc28a0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -41,7 +41,10 @@ use crate::{ Component, }, config::Config, - system::disk::{get_disks, Disk}, + system::{ + disk::{get_disks, Disk}, + drivers::{self, Driver}, + }, tui::{Event, Tui}, }; @@ -51,6 +54,7 @@ pub struct App { frame_rate: f64, components: Vec>, disks: Arc>>, + driver: Option, should_quit: bool, should_suspend: bool, cur_mode: Mode, @@ -65,6 +69,7 @@ pub struct App { pub enum Mode { #[default] ScanDisks, + InstallDrivers, SelectDisks, SelectParts, Confirm, @@ -86,6 +91,7 @@ impl App { Box::new(Popup::new()), ], disks: Arc::new(Mutex::new(Vec::new())), + driver: None, should_quit: false, should_suspend: false, config: Config::new()?, @@ -100,6 +106,7 @@ impl App { pub fn next_mode(&mut self) -> Option { let new_mode = match self.cur_mode { + Mode::InstallDrivers => Mode::ScanDisks, Mode::ScanDisks => Mode::SelectDisks, Mode::SelectDisks | Mode::SelectParts => { if self.selections[1].is_some() { @@ -228,6 +235,14 @@ impl App { Action::Suspend => self.should_suspend = true, Action::Resume => self.should_suspend = false, Action::ClearScreen => tui.terminal.clear()?, + Action::InstallDriver => { + self.action_tx.send(Action::SetMode(Mode::InstallDrivers))? + } + Action::SelectDriver(ref driver) => { + self.driver = Some(driver.clone()); + drivers::load(&driver.inf_paths); + self.action_tx.send(Action::NextScreen)?; + } Action::Resize(w, h) => self.handle_resize(tui, w, h)?, Action::Render => self.render(tui)?, Action::PrevScreen => { diff --git a/src/components/footer.rs b/src/components/footer.rs index 0a5d602..cc17393 100644 --- a/src/components/footer.rs +++ b/src/components/footer.rs @@ -57,14 +57,15 @@ impl Component for Footer { } Action::SetMode(new_mode) => { self.footer_text = match new_mode { - Mode::ScanDisks => String::from("(q) to quit"), - Mode::SelectDisks => String::from( - "(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit", - ), Mode::Confirm => { String::from("(Enter) to confirm / (b) to go back / (q) to quit") } Mode::Done => String::from("(Enter) or (q) to quit"), + Mode::InstallDrivers => String::from("(Enter) to select / (q) to quit"), + Mode::ScanDisks => String::from("(q) to quit"), + Mode::SelectDisks => String::from( + "(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit", + ), _ => String::from("(Enter) to select / (b) to go back / (q) to quit"), } } diff --git a/src/components/left.rs b/src/components/left.rs index 5e06eaf..6a926c7 100644 --- a/src/components/left.rs +++ b/src/components/left.rs @@ -18,15 +18,25 @@ use crossterm::event::KeyEvent; use ratatui::{prelude::*, widgets::*}; use tokio::sync::mpsc::UnboundedSender; -use super::{state::StatefulList, Component}; -use crate::{action::Action, app::Mode, config::Config, system::disk::Disk}; +use super::{popup::Type, state::StatefulList, Component}; +use crate::{ + action::Action, + app::Mode, + config::Config, + system::{ + disk::{Disk, Partition}, + drivers::{self, Driver}, + }, +}; #[derive(Default)] pub struct Left { command_tx: Option>, config: Config, title_text: String, - item_list: StatefulList, + list_disks: StatefulList, + list_drivers: StatefulList, + list_parts: StatefulList, mode: Mode, selections: Vec>, } @@ -66,16 +76,23 @@ impl Component for Left { Action::Render => { // add any logic here that should run on every render } - Action::KeyUp => self.item_list.previous(), - Action::KeyDown => self.item_list.next(), + Action::KeyUp => self.list_disks.previous(), + Action::KeyDown => self.list_disks.next(), Action::Process => match self.mode { Mode::Confirm => { if let Some(command_tx) = self.command_tx.clone() { command_tx.send(Action::NextScreen)?; } } - Mode::SelectDisks | Mode::SelectParts => { - if let Some(index) = self.item_list.state.selected() { + Mode::InstallDrivers | Mode::SelectDisks | Mode::SelectParts => { + let selection: Option; + match self.mode { + Mode::InstallDrivers => selection = self.list_drivers.state.selected(), + Mode::SelectDisks => selection = self.list_disks.state.selected(), + Mode::SelectParts => selection = self.list_parts.state.selected(), + _ => panic!("This shouldn't happen!"), + } + if let Some(index) = selection { if let Some(command_tx) = self.command_tx.clone() { let mut selection_one: Option = None; let mut selection_two: Option = None; @@ -95,24 +112,26 @@ impl Component for Left { } } - // Send selection(s) + // Send selection(s) if needed // NOTE: This is needed to keep the app and all components in sync match self.mode { - Mode::SelectDisks => { - command_tx - .send(Action::Select(selection_one, selection_two))?; + Mode::InstallDrivers => { + // Only need to select one entry + if let Some(driver) = self.list_drivers.pop_selected() { + command_tx.send(Action::SelectDriver(driver.clone()))?; + } } - Mode::SelectParts => { + Mode::SelectDisks | Mode::SelectParts => { command_tx .send(Action::Select(selection_one, selection_two))?; + + // Advance screen if both selections made + if selection_two.is_some() { + command_tx.send(Action::NextScreen)?; + } } _ => {} }; - - // Advance screen if both selections made - if selection_two.is_some() { - command_tx.send(Action::NextScreen)?; - } } } } @@ -123,9 +142,23 @@ impl Component for Left { let prev_mode = self.mode; self.mode = new_mode; match new_mode { + Mode::InstallDrivers => { + self.list_drivers.set_items(drivers::scan()); + self.selections[0] = None; + self.selections[1] = None; + self.title_text = String::from("Install Drivers"); + if self.list_drivers.items.is_empty() { + if let Some(command_tx) = self.command_tx.clone() { + command_tx.send(Action::DisplayPopup( + Type::Error, + String::from("No drivers available to install"), + ))?; + } + } + } Mode::ScanDisks => { + self.list_disks.clear_items(); self.title_text = String::from(""); - self.item_list.clear_items(); } Mode::SelectDisks => { self.selections[0] = None; @@ -151,7 +184,7 @@ impl Component for Left { Mode::Done => self.title_text = String::from("Done"), } } - Action::UpdateDiskList(disks) => self.item_list.set_items(disks), + Action::UpdateDiskList(disks) => self.list_disks.set_items(disks), _ => {} } Ok(None) @@ -170,7 +203,7 @@ impl Component for Left { frame.render_widget(title, title_area); // Body (scan disks) - if self.item_list.items.is_empty() { + if self.list_disks.items.is_empty() { let paragraph = Paragraph::new(String::new()).block( Block::default() .borders(Borders::ALL) @@ -198,8 +231,29 @@ impl Component for Left { // Body (list) let mut list_items = Vec::::new(); - if !self.item_list.items.is_empty() { - for (index, item) in self.item_list.items.iter().enumerate() { + let list_items_strings: Vec = match self.mode { + Mode::InstallDrivers => self + .list_drivers + .items + .iter() + .map(|i| format!("{i}")) + .collect(), + Mode::SelectDisks => self + .list_disks + .items + .iter() + .map(|i| format!("{i}")) + .collect(), + Mode::SelectParts => self + .list_parts + .items + .iter() + .map(|i| format!("{i}")) + .collect(), + _ => panic!("This shouldn't happen."), + }; + if !list_items_strings.is_empty() { + for (index, item) in list_items_strings.iter().enumerate() { let mut item_style = Style::default(); let mut item_text_tail = ""; if let Some(selection_one) = self.selections[0] { @@ -223,7 +277,7 @@ impl Component for Left { .highlight_style(Style::new().green().bold()) .highlight_symbol(" --> ") .repeat_highlight_symbol(false); - frame.render_stateful_widget(list, body_area, &mut self.item_list.state); + frame.render_stateful_widget(list, body_area, &mut self.list_disks.state); // Done Ok(()) diff --git a/src/components/popup.rs b/src/components/popup.rs index a1e1f35..f783dfc 100644 --- a/src/components/popup.rs +++ b/src/components/popup.rs @@ -34,6 +34,7 @@ pub enum Type { pub struct Popup { command_tx: Option>, config: Config, + mode: Mode, popup_type: Type, popup_text: String, } @@ -71,7 +72,20 @@ impl Component for Popup { self.popup_type = new_type; self.popup_text = String::from(new_text); } - Action::SetMode(Mode::ScanDisks) => self.popup_text = String::from("Scanning Disks..."), + Action::Process => { + if !self.popup_text.is_empty() && self.mode == Mode::InstallDrivers { + self.popup_text = String::from(""); + if let Some(command_tx) = self.command_tx.clone() { + command_tx.send(Action::NextScreen)?; + } + } + } + Action::SetMode(mode) => { + if mode == Mode::ScanDisks { + self.popup_text = String::from("Scanning Disks..."); + } + self.mode = mode; + } _ => {} } Ok(None) diff --git a/src/system/drivers.rs b/src/system/drivers.rs index d1f2d0b..a95c342 100644 --- a/src/system/drivers.rs +++ b/src/system/drivers.rs @@ -15,10 +15,11 @@ // use std::{env, fmt, fs::read_dir, path::PathBuf, process::Command}; +use serde::{Deserialize, Serialize}; use tracing::info; use walkdir::WalkDir; -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct Driver { pub name: String, pub path: PathBuf, @@ -51,15 +52,17 @@ impl fmt::Display for Driver { /// /// Will panic if a driver fails to load pub fn load(inf_paths: &Vec) { - // Load drivers into live environment - for inf in inf_paths { - let inf = inf.clone(); - if let Ok(path) = inf.into_os_string().into_string() { - info!("Installing driver: {}", &path); - Command::new("drvload") - .arg(path) - .output() - .expect("Failed to load driver"); + if cfg!(windows) { + // Load drivers into live environment + for inf in inf_paths { + let inf = inf.clone(); + if let Ok(path) = inf.into_os_string().into_string() { + info!("Installing driver: {}", &path); + Command::new("drvload") + .arg(path) + .output() + .expect("Failed to load driver"); + } } } }