Compare commits

...

2 commits

Author SHA1 Message Date
086946b357
WIP #43 2025-01-23 00:00:05 -08:00
00bc5bc607
WIP: Compiles and probably working? 2025-01-22 23:03:37 -08:00
8 changed files with 134 additions and 91 deletions

View file

@ -1,6 +1,7 @@
{ {
"app_title": "Deja-vu", "app_title": "Deja-vu",
"clone_app_path": "C:/Program Files/Some Clone Tool/app.exe", "clone_app_path": "C:/Program Files/Some Clone Tool/app.exe",
"conemu_path": "C:/Program Files/ConEmu/ConEmu64.exe",
"keybindings": { "keybindings": {
"Home": { "Home": {
"<q>": "Quit", "<q>": "Quit",

View file

@ -29,7 +29,11 @@ pub enum Action {
SelectRight(Option<usize>, Option<usize>), // indicies for right info pane SelectRight(Option<usize>, Option<usize>), // indicies for right info pane
UpdateDiskList(Vec<Disk>), UpdateDiskList(Vec<Disk>),
UpdateFooter(String), UpdateFooter(String),
UpdateLeft(String, Vec<String>, Vec<String>, bool), // (title, labels, items, select_one) UpdateLeft(String, Vec<String>, Vec<String>, 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<Vec<DVLine>>, usize, Vec<Vec<DVLine>>), // (labels, start_index, items) - items before start are always shown UpdateRight(Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>), // (labels, start_index, items) - items before start are always shown
// Screens // Screens
DismissPopup, DismissPopup,

View file

@ -30,7 +30,7 @@ pub struct Left {
config: Config, config: Config,
labels: Vec<String>, labels: Vec<String>,
list: StatefulList<String>, list: StatefulList<String>,
select_one: bool, select_num: usize,
selections: Vec<Option<usize>>, selections: Vec<Option<usize>>,
selections_saved: Vec<Option<usize>>, selections_saved: Vec<Option<usize>>,
title_text: String, title_text: String,
@ -39,7 +39,7 @@ pub struct Left {
impl Left { impl Left {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
select_one: false, select_num: 0,
labels: vec![String::from("one"), String::from("two")], labels: vec![String::from("one"), String::from("two")],
selections: vec![None, None], selections: vec![None, None],
selections_saved: vec![None, None], selections_saved: vec![None, None],
@ -75,12 +75,14 @@ impl Component for Left {
Action::KeyUp => self.list.previous(), Action::KeyUp => self.list.previous(),
Action::KeyDown => self.list.next(), Action::KeyDown => self.list.next(),
Action::Process => { 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]) { match (self.selections[0], self.selections[1]) {
(None, None) => { (None, None) => {
// Making first selection // Making first selection
command_tx.send(Action::Select(self.list.selected(), None))?; command_tx.send(Action::Select(self.list.selected(), None))?;
if self.select_one { if self.select_num == 1 {
// Confirm selection // Confirm selection
command_tx.send(Action::NextScreen)?; command_tx.send(Action::NextScreen)?;
} }
@ -115,14 +117,14 @@ impl Component for Left {
self.selections[0] = None; self.selections[0] = None;
self.selections[1] = None; self.selections[1] = None;
} }
Action::UpdateLeft(title, labels, items, select_one) => { Action::UpdateLeft(title, labels, items, select_num) => {
self.title_text = title; self.title_text = title;
self.labels = labels self.labels = labels
.iter() .iter()
.map(|label| format!(" ~{}~", label.to_lowercase())) .map(|label| format!(" ~{}~", label.to_lowercase()))
.collect(); .collect();
self.list.set_items(items); self.list.set_items(items);
self.select_one = select_one; self.select_num = select_num;
} }
_ => {} _ => {}
} }
@ -136,7 +138,7 @@ impl Component for Left {
.areas(area); .areas(area);
// Title // 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" "Confirm Selections"
} else { } else {
self.title_text.as_str() self.title_text.as_str()

View file

@ -20,7 +20,6 @@ use ratatui::{
widgets::{Block, Borders, Padding, Paragraph, Wrap}, widgets::{Block, Borders, Padding, Paragraph, Wrap},
}; };
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use tracing::info;
use super::{state::StatefulList, Component}; use super::{state::StatefulList, Component};
use crate::{action::Action, config::Config, line::DVLine}; use crate::{action::Action, config::Config, line::DVLine};

View file

@ -44,6 +44,8 @@ pub struct Config {
pub app_title: String, pub app_title: String,
#[serde(default)] #[serde(default)]
pub clone_app_path: PathBuf, pub clone_app_path: PathBuf,
#[serde(default)]
pub conemu_path: PathBuf,
#[serde(default, flatten)] #[serde(default, flatten)]
pub config: AppConfig, pub config: AppConfig,
#[serde(default)] #[serde(default)]
@ -76,6 +78,10 @@ impl Config {
"clone_app_path", "clone_app_path",
String::from("C:\\Program Files\\Some Clone Tool\\app.exe"), 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("config_dir", config_dir.to_str().unwrap())?
.set_default("data_dir", data_dir.to_str().unwrap())?; .set_default("data_dir", data_dir.to_str().unwrap())?;

View file

@ -433,9 +433,9 @@ impl App {
self.set_mode(new_mode)?; self.set_mode(new_mode)?;
self.action_tx self.action_tx
.send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?; .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 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); let (labels, start, items) = build_right_items(self, self.cur_mode);
self.action_tx self.action_tx
.send(Action::UpdateRight(labels, start, items))?; .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<String>, Vec<String>, bool) { fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec<String>, Vec<String>, usize) {
let select_num: usize;
let title: String; let title: String;
let mut items = Vec::new(); let mut items = Vec::new();
let mut labels: Vec<String> = Vec::new(); let mut labels: Vec<String> = Vec::new();
let mut select_one = false;
match cur_mode { match cur_mode {
Mode::Home => { Mode::Home => {
select_num = 0;
title = String::from("Home"); title = String::from("Home");
} }
Mode::InstallDrivers => { Mode::InstallDrivers => {
select_num = 1;
title = String::from("Install Drivers"); title = String::from("Install Drivers");
select_one = true;
app.clone app.clone
.driver_list .driver_list
.iter() .iter()
.for_each(|driver| items.push(driver.to_string())); .for_each(|driver| items.push(driver.to_string()));
} }
Mode::SelectDisks => { Mode::SelectDisks => {
select_num = 2;
title = String::from("Select Source and Destination Disks"); title = String::from("Select Source and Destination Disks");
labels.push(String::from("source")); labels.push(String::from("source"));
labels.push(String::from("dest")); labels.push(String::from("dest"));
@ -604,18 +606,21 @@ fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec<String>, Vec<Stri
.for_each(|disk| items.push(disk.description.to_string())); .for_each(|disk| items.push(disk.description.to_string()));
} }
Mode::SelectTableType => { Mode::SelectTableType => {
select_num = 1;
title = String::from("Select Partition Table Type"); title = String::from("Select Partition Table Type");
select_one = true;
items.push(format!("{}", PartitionTableType::Guid)); items.push(format!("{}", PartitionTableType::Guid));
items.push(format!("{}", PartitionTableType::Legacy)); items.push(format!("{}", PartitionTableType::Legacy));
} }
Mode::Confirm => { Mode::Confirm => {
select_num = 0;
title = String::from("Confirm Selections"); title = String::from("Confirm Selections");
} }
Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => {
select_num = 0;
title = String::from("Processing"); title = String::from("Processing");
} }
Mode::SelectParts => { Mode::SelectParts => {
select_num = 2;
title = String::from("Select Boot and OS Partitions"); title = String::from("Select Boot and OS Partitions");
labels.push(String::from("boot")); labels.push(String::from("boot"));
labels.push(String::from("os")); labels.push(String::from("os"));
@ -628,11 +633,14 @@ fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec<String>, Vec<Stri
} }
} }
} }
Mode::Done | Mode::Failed => title = String::from("Done"), Mode::Done | Mode::Failed => {
select_num = 0;
title = String::from("Done");
}
// Invalid states // Invalid states
Mode::PEMenu => panic!("This shouldn't happen?"), 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<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) { fn build_right_items(app: &App, cur_mode: Mode) -> (Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) {

View file

@ -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 <https://www.gnu.org/licenses/>.
con_emu = 'X:\Program Files\ConEmu\ConEmu64.exe'
tools = []

View file

@ -43,41 +43,7 @@ use serde::Deserialize;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::{debug, info}; use tracing::{debug, info};
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
pub struct Menu {
con_emu: String,
tools: Vec<Tool>,
}
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<PathBuf> = 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)]
pub struct Tool { pub struct Tool {
name: String, name: String,
command: String, command: String,
@ -99,17 +65,18 @@ pub struct App {
should_suspend: bool, should_suspend: bool,
tick_rate: f64, tick_rate: f64,
// App // App
list: StatefulList<usize>, list: StatefulList<Tool>,
menu: Menu,
mode: Mode, mode: Mode,
selections: Vec<Option<usize>>, tasks: Tasks,
} }
impl App { impl App {
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> { pub fn new(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 tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone());
let mut list = StatefulList::default();
list.set_items(load_tools());
Ok(Self { Ok(Self {
// TUI // TUI
action_rx, action_rx,
@ -129,10 +96,9 @@ impl App {
should_suspend: false, should_suspend: false,
tick_rate, tick_rate,
// App // App
list: StatefulList::default(), list,
menu: Menu::load(),
mode: Mode::default(), mode: Mode::default(),
selections: vec![None, None], tasks,
}) })
} }
@ -229,17 +195,40 @@ 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::KeyUp => self.list.previous(), Action::KeyUp => {
Action::KeyDown => self.list.next(), 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) => { Action::Error(ref msg) => {
self.action_tx self.action_tx
.send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?; .send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?;
self.action_tx.send(Action::SetMode(Mode::Failed))?; self.action_tx.send(Action::SetMode(Mode::Failed))?;
} }
Action::Process => { Action::Process => {
if let Some(index) = self.list.selected() { // Run selected tool
//TODO: Run selected tool? if let Some(tool) = self.list.get_selected() {
info!("Run tool at index: {index}"); info!("Run tool: {:?}", &tool);
self.tasks.add(build_command(&self, &tool));
} }
} }
Action::Resize(w, h) => self.handle_resize(tui, w, h)?, Action::Resize(w, h) => self.handle_resize(tui, w, h)?,
@ -248,16 +237,18 @@ impl App {
self.mode = mode; self.mode = mode;
self.action_tx self.action_tx
.send(Action::UpdateFooter(String::from("(Enter) to select")))?; .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 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); let (labels, start, items) = build_right_items(self);
self.action_tx self.action_tx
.send(Action::UpdateRight(labels, start, items))?; .send(Action::UpdateRight(labels, start, items))?;
self.action_tx.send(Action::Select(None, None))?;
} }
Action::UpdateFooter(_) Action::UpdateFooter(_)
| Action::UpdateLeft(_, _, _, _) | Action::UpdateLeft(_, _, _, _)
| Action::UpdateRight(_, _, _) => { | Action::UpdateRight(_, _, _)
| Action::Highlight(_) => {
info!("Processing Action: {:?}", action); info!("Processing Action: {:?}", action);
} }
_ => {} _ => {}
@ -352,23 +343,53 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
chunks chunks
} }
fn build_left_items(app: &App) -> (String, Vec<String>, Vec<String>, bool) { pub fn build_command(app: &App, tool: &Tool) -> Task {
let cmd_path: PathBuf;
let mut cmd_args: Vec<String> = Vec::new();
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(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<String>, Vec<String>, usize) {
let title = String::from("Tools"); let title = String::from("Tools");
let labels = vec![String::new(), String::new()]; let labels = vec![String::new(), String::new()];
let items = app let items = app
.menu .list
.tools .items
.iter() .iter()
.map(|tool| tool.name.clone()) .map(|tool| {
if tool.separator {
String::from("──────────────")
} else {
tool.name.clone()
}
})
// ─
.collect(); .collect();
(title, labels, items, false) (title, labels, items, 0)
} }
fn build_right_items(app: &App) -> (Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) { fn build_right_items(app: &App) -> (Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) {
let labels: Vec<Vec<DVLine>> = Vec::new(); let labels: Vec<Vec<DVLine>> = Vec::new();
let items = app let items = app
.menu .list
.tools .items
.iter() .iter()
.map(|entry| { .map(|entry| {
vec![ vec![
@ -390,3 +411,22 @@ fn build_right_items(app: &App) -> (Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) {
let start_index = 0; let start_index = 0;
(labels, start_index, items) (labels, start_index, items)
} }
pub fn load_tools() -> Vec<Tool> {
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<PathBuf> = 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()
}