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",
"clone_app_path": "C:/Program Files/Some Clone Tool/app.exe",
"conemu_path": "C:/Program Files/ConEmu/ConEmu64.exe",
"keybindings": {
"Home": {
"<q>": "Quit",

View file

@ -29,7 +29,11 @@ pub enum Action {
SelectRight(Option<usize>, Option<usize>), // indicies for right info pane
UpdateDiskList(Vec<Disk>),
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
// Screens
DismissPopup,

View file

@ -30,7 +30,7 @@ pub struct Left {
config: Config,
labels: Vec<String>,
list: StatefulList<String>,
select_one: bool,
select_num: usize,
selections: Vec<Option<usize>>,
selections_saved: Vec<Option<usize>>,
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,12 +75,14 @@ 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 {
if self.select_num == 1 {
// Confirm selection
command_tx.send(Action::NextScreen)?;
}
@ -115,14 +117,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 +138,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()

View file

@ -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};

View file

@ -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())?;

View file

@ -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<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 mut items = Vec::new();
let mut labels: Vec<String> = 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<String>, Vec<Stri
.for_each(|disk| items.push(disk.description.to_string()));
}
Mode::SelectTableType => {
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<String>, Vec<Stri
}
}
}
Mode::Done | Mode::Failed => 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<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 tracing::{debug, info};
#[derive(Debug, 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)]
#[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<usize>,
menu: Menu,
list: StatefulList<Tool>,
mode: Mode,
selections: Vec<Option<usize>>,
tasks: Tasks,
}
impl App {
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> {
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,53 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
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 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<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) {
let labels: Vec<Vec<DVLine>> = Vec::new();
let items = app
.menu
.tools
.list
.items
.iter()
.map(|entry| {
vec![
@ -390,3 +411,22 @@ fn build_right_items(app: &App) -> (Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) {
let start_index = 0;
(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()
}