From 00bc5bc607ed5137f3c867c966d72c027bf3e651 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Wed, 22 Jan 2025 23:03:37 -0800 Subject: [PATCH] WIP: Compiles and probably working? --- config/config.json5 | 1 + core/src/action.rs | 6 +- core/src/components/left.rs | 20 ++--- core/src/components/right.rs | 1 - core/src/config.rs | 6 ++ deja_vu/src/app.rs | 24 ++++-- include/pe-menu.toml | 17 ---- pe_menu/src/app.rs | 153 ++++++++++++++++++++++------------- 8 files changed, 134 insertions(+), 94 deletions(-) delete mode 100644 include/pe-menu.toml diff --git a/config/config.json5 b/config/config.json5 index 055ecfa..0d54fcf 100644 --- a/config/config.json5 +++ b/config/config.json5 @@ -1,6 +1,7 @@ { "app_title": "Deja-vu", "clone_app_path": "C:/Program Files/Some Clone Tool/app.exe", + "conemu_path": "C:/Program Files/ConEmu/ConEmu64.exe", "keybindings": { "Home": { "": "Quit", diff --git a/core/src/action.rs b/core/src/action.rs index 48257c4..06c937a 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -29,7 +29,11 @@ pub enum Action { SelectRight(Option, Option), // indicies for right info pane UpdateDiskList(Vec), UpdateFooter(String), - UpdateLeft(String, Vec, Vec, bool), // (title, labels, items, select_one) + UpdateLeft(String, Vec, Vec, usize), // (title, labels, items, select_num) + // NOTE: select_num should be set to 0, 1, or 2. + // 0: For repeating selections + // 1: For a single choice + // 2: For two selections (obviously) UpdateRight(Vec>, usize, Vec>), // (labels, start_index, items) - items before start are always shown // Screens DismissPopup, diff --git a/core/src/components/left.rs b/core/src/components/left.rs index beff17a..a2f49d3 100644 --- a/core/src/components/left.rs +++ b/core/src/components/left.rs @@ -30,7 +30,7 @@ pub struct Left { config: Config, labels: Vec, list: StatefulList, - select_one: bool, + select_num: usize, selections: Vec>, selections_saved: Vec>, title_text: String, @@ -39,7 +39,7 @@ pub struct Left { impl Left { pub fn new() -> Self { Self { - select_one: false, + select_num: 0, labels: vec![String::from("one"), String::from("two")], selections: vec![None, None], selections_saved: vec![None, None], @@ -75,15 +75,15 @@ impl Component for Left { Action::KeyUp => self.list.previous(), Action::KeyDown => self.list.next(), Action::Process => { - if let Some(command_tx) = self.command_tx.clone() { + if self.select_num == 0 { + // Selections aren't being used so this is a no-op + } else if let Some(command_tx) = self.command_tx.clone() { match (self.selections[0], self.selections[1]) { (None, None) => { // Making first selection command_tx.send(Action::Select(self.list.selected(), None))?; - if self.select_one { - // Confirm selection - command_tx.send(Action::NextScreen)?; - } + // Confirm selection + command_tx.send(Action::NextScreen)?; } (Some(first_index), None) => { if let Some(second_index) = self.list.selected() { @@ -115,14 +115,14 @@ impl Component for Left { self.selections[0] = None; self.selections[1] = None; } - Action::UpdateLeft(title, labels, items, select_one) => { + Action::UpdateLeft(title, labels, items, select_num) => { self.title_text = title; self.labels = labels .iter() .map(|label| format!(" ~{}~", label.to_lowercase())) .collect(); self.list.set_items(items); - self.select_one = select_one; + self.select_num = select_num; } _ => {} } @@ -136,7 +136,7 @@ impl Component for Left { .areas(area); // Title - let title_text = if self.selections[1].is_some() || self.select_one { + let title_text = if self.selections[1].is_some() || self.select_num == 1 { "Confirm Selections" } else { self.title_text.as_str() diff --git a/core/src/components/right.rs b/core/src/components/right.rs index 6342cb2..80622c2 100644 --- a/core/src/components/right.rs +++ b/core/src/components/right.rs @@ -20,7 +20,6 @@ use ratatui::{ widgets::{Block, Borders, Padding, Paragraph, Wrap}, }; use tokio::sync::mpsc::UnboundedSender; -use tracing::info; use super::{state::StatefulList, Component}; use crate::{action::Action, config::Config, line::DVLine}; diff --git a/core/src/config.rs b/core/src/config.rs index ea13801..49eb824 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -44,6 +44,8 @@ pub struct Config { pub app_title: String, #[serde(default)] pub clone_app_path: PathBuf, + #[serde(default)] + pub conemu_path: PathBuf, #[serde(default, flatten)] pub config: AppConfig, #[serde(default)] @@ -76,6 +78,10 @@ impl Config { "clone_app_path", String::from("C:\\Program Files\\Some Clone Tool\\app.exe"), )? + .set_default( + "conemu_path", + String::from("C:\\Program Files\\ConEmu\\ConEmu64.exe"), + )? .set_default("config_dir", config_dir.to_str().unwrap())? .set_default("data_dir", data_dir.to_str().unwrap())?; diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 80f7efc..fe3de1f 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -433,9 +433,9 @@ impl App { self.set_mode(new_mode)?; self.action_tx .send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?; - let (title, labels, items, select_one) = build_left_items(self, self.cur_mode); + let (title, labels, items, select_num) = build_left_items(self, self.cur_mode); self.action_tx - .send(Action::UpdateLeft(title, labels, items, select_one))?; + .send(Action::UpdateLeft(title, labels, items, select_num))?; let (labels, start, items) = build_right_items(self, self.cur_mode); self.action_tx .send(Action::UpdateRight(labels, start, items))?; @@ -577,24 +577,26 @@ fn build_footer_string(cur_mode: Mode) -> String { } } -fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec, Vec, bool) { +fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec, Vec, usize) { + let select_num: usize; let title: String; let mut items = Vec::new(); let mut labels: Vec = Vec::new(); - let mut select_one = false; match cur_mode { Mode::Home => { + select_num = 0; title = String::from("Home"); } Mode::InstallDrivers => { + select_num = 1; title = String::from("Install Drivers"); - select_one = true; app.clone .driver_list .iter() .for_each(|driver| items.push(driver.to_string())); } Mode::SelectDisks => { + select_num = 2; title = String::from("Select Source and Destination Disks"); labels.push(String::from("source")); labels.push(String::from("dest")); @@ -604,18 +606,21 @@ fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec, Vec { + select_num = 1; title = String::from("Select Partition Table Type"); - select_one = true; items.push(format!("{}", PartitionTableType::Guid)); items.push(format!("{}", PartitionTableType::Legacy)); } Mode::Confirm => { + select_num = 0; title = String::from("Confirm Selections"); } Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { + select_num = 0; title = String::from("Processing"); } Mode::SelectParts => { + select_num = 2; title = String::from("Select Boot and OS Partitions"); labels.push(String::from("boot")); labels.push(String::from("os")); @@ -628,11 +633,14 @@ fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec, Vec title = String::from("Done"), + Mode::Done | Mode::Failed => { + select_num = 0; + title = String::from("Done"); + } // Invalid states Mode::PEMenu => panic!("This shouldn't happen?"), }; - (title, labels, items, select_one) + (title, labels, items, select_num) } fn build_right_items(app: &App, cur_mode: Mode) -> (Vec>, usize, Vec>) { diff --git a/include/pe-menu.toml b/include/pe-menu.toml deleted file mode 100644 index 45f8b6b..0000000 --- a/include/pe-menu.toml +++ /dev/null @@ -1,17 +0,0 @@ -# This file is part of Deja-vu. -# -# Deja-vu is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Deja-vu is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Deja-vu. If not, see . - -con_emu = 'X:\Program Files\ConEmu\ConEmu64.exe' -tools = [] diff --git a/pe_menu/src/app.rs b/pe_menu/src/app.rs index b6b5d67..f4abaa8 100644 --- a/pe_menu/src/app.rs +++ b/pe_menu/src/app.rs @@ -43,41 +43,7 @@ use serde::Deserialize; use tokio::sync::mpsc; use tracing::{debug, info}; -#[derive(Debug, Deserialize)] -pub struct Menu { - con_emu: String, - tools: Vec, -} - -impl Menu { - #[must_use] - pub fn load() -> Menu { - // Main config - let exe_path = env::current_exe().expect("Failed to find main executable"); - let contents = fs::read_to_string(exe_path.with_file_name("pe-menu.toml")) - .expect("Failed to load config file"); - let mut menu: Menu = toml::from_str(&contents).expect("Failed to parse config file"); - - // Tools - let tool_config_path = exe_path.parent().unwrap().join("menu_entries"); - let mut entries: Vec = std::fs::read_dir(tool_config_path) - .expect("Failed to find any tool configs") - .map(|res| res.map(|e| e.path())) - .filter_map(Result::ok) - .collect(); - entries.sort(); - entries.iter().for_each(|entry| { - let contents = fs::read_to_string(&entry).expect("Failed to read tool config file"); - let tool: Tool = toml::from_str(&contents).expect("Failed to parse tool config file"); - menu.tools.push(tool); - }); - - // Done - menu - } -} - -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct Tool { name: String, command: String, @@ -99,17 +65,18 @@ pub struct App { should_suspend: bool, tick_rate: f64, // App - list: StatefulList, - menu: Menu, + list: StatefulList, mode: Mode, - 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 mut tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); + let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); + let mut list = StatefulList::default(); + list.set_items(load_tools()); Ok(Self { // TUI action_rx, @@ -129,10 +96,9 @@ impl App { should_suspend: false, tick_rate, // App - list: StatefulList::default(), - menu: Menu::load(), + list, mode: Mode::default(), - selections: vec![None, None], + tasks, }) } @@ -229,17 +195,40 @@ impl App { 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::KeyUp => { + self.list.previous(); + if let Some(tool) = self.list.get_selected() { + if tool.separator { + // Skip over separator + self.list.previous(); + if let Some(index) = self.list.selected() { + self.action_tx.send(Action::Highlight(index))?; + } + } + } + } + Action::KeyDown => { + self.list.next(); + if let Some(tool) = self.list.get_selected() { + if tool.separator { + // Skip over separator + self.list.next(); + if let Some(index) = self.list.selected() { + self.action_tx.send(Action::Highlight(index))?; + } + } + } + } Action::Error(ref msg) => { self.action_tx .send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?; self.action_tx.send(Action::SetMode(Mode::Failed))?; } Action::Process => { - if let Some(index) = self.list.selected() { - //TODO: Run selected tool? - info!("Run tool at index: {index}"); + // Run selected tool + if let Some(tool) = self.list.get_selected() { + info!("Run tool: {:?}", &tool); + self.tasks.add(build_command(&self, &tool)); } } Action::Resize(w, h) => self.handle_resize(tui, w, h)?, @@ -248,16 +237,18 @@ impl App { self.mode = mode; self.action_tx .send(Action::UpdateFooter(String::from("(Enter) to select")))?; - let (title, labels, items, select_one) = build_left_items(self); + let (title, labels, items, select_num) = build_left_items(self); self.action_tx - .send(Action::UpdateLeft(title, labels, items, select_one))?; + .send(Action::UpdateLeft(title, labels, items, select_num))?; let (labels, start, items) = build_right_items(self); self.action_tx .send(Action::UpdateRight(labels, start, items))?; + self.action_tx.send(Action::Select(None, None))?; } Action::UpdateFooter(_) | Action::UpdateLeft(_, _, _, _) - | Action::UpdateRight(_, _, _) => { + | Action::UpdateRight(_, _, _) + | Action::Highlight(_) => { info!("Processing Action: {:?}", action); } _ => {} @@ -352,23 +343,52 @@ fn get_chunks(r: Rect) -> Vec { chunks } -fn build_left_items(app: &App) -> (String, Vec, Vec, bool) { +pub fn build_command(app: &App, tool: &Tool) -> Task { + let cmd_path: PathBuf; + let mut cmd_args: Vec = Vec::new(); + let start_index: usize; + if tool.use_conemu { + cmd_path = app.config.conemu_path.clone(); + cmd_args.push(tool.command.clone()); + start_index = 1; + } else { + cmd_path = PathBuf::from(tool.command.clone()); + start_index = 0; + } + if let Some(args) = &tool.args { + if args.len() > start_index { + args[start_index..].iter().for_each(|a| { + cmd_args.push(a.clone()); + }); + } + } + Task::Command(cmd_path, cmd_args) +} + +fn build_left_items(app: &App) -> (String, Vec, Vec, usize) { let title = String::from("Tools"); let labels = vec![String::new(), String::new()]; let items = app - .menu - .tools + .list + .items .iter() - .map(|tool| tool.name.clone()) + .map(|tool| { + if tool.separator { + String::from("──────────────") + } else { + tool.name.clone() + } + }) + // ─ .collect(); - (title, labels, items, false) + (title, labels, items, 0) } fn build_right_items(app: &App) -> (Vec>, usize, Vec>) { let labels: Vec> = Vec::new(); let items = app - .menu - .tools + .list + .items .iter() .map(|entry| { vec![ @@ -390,3 +410,22 @@ fn build_right_items(app: &App) -> (Vec>, usize, Vec>) { let start_index = 0; (labels, start_index, items) } + +pub fn load_tools() -> Vec { + let exe_path = env::current_exe().expect("Failed to find main executable"); + let tool_config_path = exe_path.parent().unwrap().join("menu_entries"); + let mut entries: Vec = std::fs::read_dir(tool_config_path) + .expect("Failed to find any tool configs") + .map(|res| res.map(|e| e.path())) + .filter_map(Result::ok) + .collect(); + entries.sort(); + entries + .iter() + .map(|entry| { + let contents = fs::read_to_string(&entry).expect("Failed to read tool config file"); + let tool: Tool = toml::from_str(&contents).expect("Failed to parse tool config file"); + tool + }) + .collect() +}