Add install driver sections

This commit is contained in:
2Shirt 2024-11-03 21:08:59 -08:00
parent c8faa283c8
commit 9414f873bd
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
7 changed files with 143 additions and 40 deletions

View file

@ -1,5 +1,14 @@
{ {
"keybindings": { "keybindings": {
"InstallDrivers": {
"<Enter>": "Process",
"<Up>": "KeyUp",
"<Down>": "KeyDown",
"<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
},
"ScanDisks": { "ScanDisks": {
"<q>": "Quit", // Quit the application "<q>": "Quit", // Quit the application
"<Ctrl-d>": "Quit", // Another way to quit "<Ctrl-d>": "Quit", // Another way to quit
@ -7,6 +16,7 @@
"<Ctrl-z>": "Suspend" // Suspend the application "<Ctrl-z>": "Suspend" // Suspend the application
}, },
"SelectDisks": { "SelectDisks": {
"<i>": "InstallDriver",
"<r>": "ScanDisks", "<r>": "ScanDisks",
"<Enter>": "Process", "<Enter>": "Process",
"<Up>": "KeyUp", "<Up>": "KeyUp",

View file

@ -16,7 +16,11 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::Display; 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)] #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
pub enum Action { pub enum Action {
@ -31,6 +35,8 @@ pub enum Action {
DisplayPopup(Type, String), DisplayPopup(Type, String),
Error(String), Error(String),
Help, Help,
InstallDriver,
SelectDriver(Driver),
KeyUp, KeyUp,
KeyDown, KeyDown,
SetMode(Mode), SetMode(Mode),

View file

@ -41,7 +41,10 @@ use crate::{
Component, Component,
}, },
config::Config, config::Config,
system::disk::{get_disks, Disk}, system::{
disk::{get_disks, Disk},
drivers::{self, Driver},
},
tui::{Event, Tui}, tui::{Event, Tui},
}; };
@ -51,6 +54,7 @@ pub struct App {
frame_rate: f64, frame_rate: f64,
components: Vec<Box<dyn Component>>, components: Vec<Box<dyn Component>>,
disks: Arc<Mutex<Vec<Disk>>>, disks: Arc<Mutex<Vec<Disk>>>,
driver: Option<Driver>,
should_quit: bool, should_quit: bool,
should_suspend: bool, should_suspend: bool,
cur_mode: Mode, cur_mode: Mode,
@ -65,6 +69,7 @@ pub struct App {
pub enum Mode { pub enum Mode {
#[default] #[default]
ScanDisks, ScanDisks,
InstallDrivers,
SelectDisks, SelectDisks,
SelectParts, SelectParts,
Confirm, Confirm,
@ -86,6 +91,7 @@ impl App {
Box::new(Popup::new()), Box::new(Popup::new()),
], ],
disks: Arc::new(Mutex::new(Vec::new())), disks: Arc::new(Mutex::new(Vec::new())),
driver: None,
should_quit: false, should_quit: false,
should_suspend: false, should_suspend: false,
config: Config::new()?, config: Config::new()?,
@ -100,6 +106,7 @@ impl App {
pub fn next_mode(&mut self) -> Option<Mode> { pub fn next_mode(&mut self) -> Option<Mode> {
let new_mode = match self.cur_mode { let new_mode = match self.cur_mode {
Mode::InstallDrivers => Mode::ScanDisks,
Mode::ScanDisks => Mode::SelectDisks, Mode::ScanDisks => Mode::SelectDisks,
Mode::SelectDisks | Mode::SelectParts => { Mode::SelectDisks | Mode::SelectParts => {
if self.selections[1].is_some() { if self.selections[1].is_some() {
@ -228,6 +235,14 @@ impl App {
Action::Suspend => self.should_suspend = true, Action::Suspend => self.should_suspend = true,
Action::Resume => self.should_suspend = false, Action::Resume => self.should_suspend = false,
Action::ClearScreen => tui.terminal.clear()?, 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::Resize(w, h) => self.handle_resize(tui, w, h)?,
Action::Render => self.render(tui)?, Action::Render => self.render(tui)?,
Action::PrevScreen => { Action::PrevScreen => {

View file

@ -57,14 +57,15 @@ impl Component for Footer {
} }
Action::SetMode(new_mode) => { Action::SetMode(new_mode) => {
self.footer_text = match 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 => { Mode::Confirm => {
String::from("(Enter) to confirm / (b) to go back / (q) to quit") String::from("(Enter) to confirm / (b) to go back / (q) to quit")
} }
Mode::Done => String::from("(Enter) or (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"), _ => String::from("(Enter) to select / (b) to go back / (q) to quit"),
} }
} }

View file

@ -18,15 +18,25 @@ use crossterm::event::KeyEvent;
use ratatui::{prelude::*, widgets::*}; use ratatui::{prelude::*, widgets::*};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use super::{state::StatefulList, Component}; use super::{popup::Type, state::StatefulList, Component};
use crate::{action::Action, app::Mode, config::Config, system::disk::Disk}; use crate::{
action::Action,
app::Mode,
config::Config,
system::{
disk::{Disk, Partition},
drivers::{self, Driver},
},
};
#[derive(Default)] #[derive(Default)]
pub struct Left { pub struct Left {
command_tx: Option<UnboundedSender<Action>>, command_tx: Option<UnboundedSender<Action>>,
config: Config, config: Config,
title_text: String, title_text: String,
item_list: StatefulList<Disk>, list_disks: StatefulList<Disk>,
list_drivers: StatefulList<Driver>,
list_parts: StatefulList<Partition>,
mode: Mode, mode: Mode,
selections: Vec<Option<usize>>, selections: Vec<Option<usize>>,
} }
@ -66,16 +76,23 @@ impl Component for Left {
Action::Render => { Action::Render => {
// add any logic here that should run on every render // add any logic here that should run on every render
} }
Action::KeyUp => self.item_list.previous(), Action::KeyUp => self.list_disks.previous(),
Action::KeyDown => self.item_list.next(), Action::KeyDown => self.list_disks.next(),
Action::Process => match self.mode { Action::Process => match self.mode {
Mode::Confirm => { Mode::Confirm => {
if let Some(command_tx) = self.command_tx.clone() { if let Some(command_tx) = self.command_tx.clone() {
command_tx.send(Action::NextScreen)?; command_tx.send(Action::NextScreen)?;
} }
} }
Mode::SelectDisks | Mode::SelectParts => { Mode::InstallDrivers | Mode::SelectDisks | Mode::SelectParts => {
if let Some(index) = self.item_list.state.selected() { let selection: Option<usize>;
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() { if let Some(command_tx) = self.command_tx.clone() {
let mut selection_one: Option<usize> = None; let mut selection_one: Option<usize> = None;
let mut selection_two: Option<usize> = None; let mut selection_two: Option<usize> = 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 // NOTE: This is needed to keep the app and all components in sync
match self.mode { match self.mode {
Mode::SelectDisks => { Mode::InstallDrivers => {
command_tx // Only need to select one entry
.send(Action::Select(selection_one, selection_two))?; if let Some(driver) = self.list_drivers.pop_selected() {
command_tx.send(Action::SelectDriver(driver.clone()))?;
}
} }
Mode::SelectParts => { Mode::SelectDisks | Mode::SelectParts => {
command_tx command_tx
.send(Action::Select(selection_one, selection_two))?; .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; let prev_mode = self.mode;
self.mode = new_mode; self.mode = new_mode;
match 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 => { Mode::ScanDisks => {
self.list_disks.clear_items();
self.title_text = String::from(""); self.title_text = String::from("");
self.item_list.clear_items();
} }
Mode::SelectDisks => { Mode::SelectDisks => {
self.selections[0] = None; self.selections[0] = None;
@ -151,7 +184,7 @@ impl Component for Left {
Mode::Done => self.title_text = String::from("Done"), 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) Ok(None)
@ -170,7 +203,7 @@ impl Component for Left {
frame.render_widget(title, title_area); frame.render_widget(title, title_area);
// Body (scan disks) // Body (scan disks)
if self.item_list.items.is_empty() { if self.list_disks.items.is_empty() {
let paragraph = Paragraph::new(String::new()).block( let paragraph = Paragraph::new(String::new()).block(
Block::default() Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
@ -198,8 +231,29 @@ impl Component for Left {
// Body (list) // Body (list)
let mut list_items = Vec::<ListItem>::new(); let mut list_items = Vec::<ListItem>::new();
if !self.item_list.items.is_empty() { let list_items_strings: Vec<String> = match self.mode {
for (index, item) in self.item_list.items.iter().enumerate() { 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_style = Style::default();
let mut item_text_tail = ""; let mut item_text_tail = "";
if let Some(selection_one) = self.selections[0] { if let Some(selection_one) = self.selections[0] {
@ -223,7 +277,7 @@ impl Component for Left {
.highlight_style(Style::new().green().bold()) .highlight_style(Style::new().green().bold())
.highlight_symbol(" --> ") .highlight_symbol(" --> ")
.repeat_highlight_symbol(false); .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 // Done
Ok(()) Ok(())

View file

@ -34,6 +34,7 @@ pub enum Type {
pub struct Popup { pub struct Popup {
command_tx: Option<UnboundedSender<Action>>, command_tx: Option<UnboundedSender<Action>>,
config: Config, config: Config,
mode: Mode,
popup_type: Type, popup_type: Type,
popup_text: String, popup_text: String,
} }
@ -71,7 +72,20 @@ impl Component for Popup {
self.popup_type = new_type; self.popup_type = new_type;
self.popup_text = String::from(new_text); 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) Ok(None)

View file

@ -15,10 +15,11 @@
// //
use std::{env, fmt, fs::read_dir, path::PathBuf, process::Command}; use std::{env, fmt, fs::read_dir, path::PathBuf, process::Command};
use serde::{Deserialize, Serialize};
use tracing::info; use tracing::info;
use walkdir::WalkDir; use walkdir::WalkDir;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Driver { pub struct Driver {
pub name: String, pub name: String,
pub path: PathBuf, pub path: PathBuf,
@ -51,15 +52,17 @@ impl fmt::Display for Driver {
/// ///
/// Will panic if a driver fails to load /// Will panic if a driver fails to load
pub fn load(inf_paths: &Vec<PathBuf>) { pub fn load(inf_paths: &Vec<PathBuf>) {
// Load drivers into live environment if cfg!(windows) {
for inf in inf_paths { // Load drivers into live environment
let inf = inf.clone(); for inf in inf_paths {
if let Ok(path) = inf.into_os_string().into_string() { let inf = inf.clone();
info!("Installing driver: {}", &path); if let Ok(path) = inf.into_os_string().into_string() {
Command::new("drvload") info!("Installing driver: {}", &path);
.arg(path) Command::new("drvload")
.output() .arg(path)
.expect("Failed to load driver"); .output()
.expect("Failed to load driver");
}
} }
} }
} }