Merge branch 'main' into numlock

This commit is contained in:
JEEVITHA KANNAN K S 2024-08-21 11:50:29 +05:30
commit 69dca9ae3d
No known key found for this signature in database
GPG Key ID: 5904C34A2F7CE333
44 changed files with 569 additions and 236 deletions

View File

@ -1,7 +1,7 @@
# Pull Request
## Title
[Provide a succinct and descriptive title for the pull request.]
<!--[Provide a succinct and descriptive title for the pull request.]-->
## Type of Change
- [ ] New feature
@ -13,20 +13,20 @@
- [ ] UI/UX improvement
## Description
[Provide a detailed explanation of the changes you have made. Include the reasons behind these changes and any relevant context. Link any related issues.]
<!--[Provide a detailed explanation of the changes you have made. Include the reasons behind these changes and any relevant context. Link any related issues.]-->
## Testing
[Detail the testing you have performed to ensure that these changes function as intended. Include information about any added tests.]
<!--[Detail the testing you have performed to ensure that these changes function as intended. Include information about any added tests.]-->
## Impact
[Discuss the impact of your changes on the project. This might include effects on performance, new dependencies, or changes in behaviour.]
<!--[Discuss the impact of your changes on the project. This might include effects on performance, new dependencies, or changes in behaviour.]-->
## Issue related to PR
[What issue/discussion is related to this PR (if any)]
<!--[What issue/discussion is related to this PR (if any)]-->
- Resolves #
## Additional Information
[Any additional information that reviewers should be aware of.]
<!--[Any additional information that reviewers should be aware of.]-->
## Checklist
- [ ] My code adheres to the coding and style guidelines of the project.

View File

@ -17,39 +17,45 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Cache Cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-registry-
- name: Cache Cargo index
uses: actions/cache@v4
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-index-
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-musl
- name: Install cross-rs for cross-compilation
run: cargo install cross
- name: Build x86_64 binary
run: cargo build --target-dir=build --release --verbose --target=x86_64-unknown-linux-musl
- name: Build aarch64 binary
run: cross build --target-dir=build --release --verbose --target=aarch64-unknown-linux-musl
- name: Move binaries to build directory
run: |
mv build/x86_64-unknown-linux-musl/release/linutil build/linutil
mv build/aarch64-unknown-linux-musl/release/linutil build/linutil-aarch64
- name: Pull latest changes
run: |
git config --global user.email "github-actions@github.com"
git config --global user.name "GitHub Actions"
git pull origin main
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Commit Linutil
file_pattern: "build/linutil build/linutil-aarch64"
if: success()
file_pattern: "build/linutil"
add_options: '--force'
if: success()

4
.gitignore vendored
View File

@ -1,2 +1,4 @@
/target
rust/target
/build
rust/target
rust/build

153
Cargo.lock generated
View File

@ -269,6 +269,22 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "filedescriptor"
version = "0.8.2"
@ -302,6 +318,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "home"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "iana-time-zone"
version = "0.1.60"
@ -344,6 +369,16 @@ dependencies = [
"quote",
]
[[package]]
name = "indexmap"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "ioctl-rs"
version = "0.1.6"
@ -395,6 +430,12 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lock_api"
version = "0.4.12"
@ -420,6 +461,12 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memoffset"
version = "0.6.5"
@ -626,6 +673,19 @@ dependencies = [
"winapi",
]
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.5.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "rustversion"
version = "1.0.17"
@ -644,6 +704,35 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [
"serde",
]
[[package]]
name = "serial"
version = "0.4.0"
@ -832,6 +921,40 @@ dependencies = [
"syn",
]
[[package]]
name = "toml"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tui"
version = "0.1.0"
@ -844,8 +967,11 @@ dependencies = [
"oneshot",
"portable-pty",
"ratatui",
"serde",
"tempdir",
"toml",
"tui-term",
"which",
]
[[package]]
@ -992,6 +1118,18 @@ version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "which"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075"
dependencies = [
"either",
"home",
"rustix",
"winsafe",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -1162,6 +1300,15 @@ version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.10.1"
@ -1171,6 +1318,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "zerocopy"
version = "0.7.34"

View File

@ -14,6 +14,9 @@ ratatui = "0.27.0"
tui-term = "0.1.12"
include_dir = "0.7.4"
tempdir = "0.3.7"
serde = { version = "1.0.205", features = ["derive"] }
toml = "0.8.19"
which = "6.0.2"
[[bin]]
name = "linutil"

4
build.rs Normal file
View File

@ -0,0 +1,4 @@
fn main() {
// Rebuild program if any file in commands directory changes.
println!("cargo:rerun-if-changed=src/commands");
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

7
linutil.desktop Executable file
View File

@ -0,0 +1,7 @@
[Desktop Entry]
Name=Linutil
Exec=sh -c "$HOME/.local/bin/linutil"
Icon=utilities-terminal
Type=Application
Terminal=true
Categories=Utility;

View File

@ -1,6 +1,6 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
setupAlacritty() {
echo "Install Alacritty if not already installed..."

View File

@ -1,5 +1,5 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
makeDWM(){
cd $HOME && git clone https://github.com/ChrisTitusTech/dwm-titus.git # CD to Home directory to install dwm-titus
@ -24,4 +24,4 @@ setupDWM() {
checkEnv
setupDWM
makeDWM
makeDWM

View File

@ -1,6 +1,6 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
setupKitty() {
echo "Install Kitty if not already installed..."

View File

@ -1,6 +1,6 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
setupRofi() {
echo "Install Rofi if not already installed..."

View File

@ -0,0 +1,29 @@
name = "Applications Setup"
[[data]]
name = "Alacritty"
script = "alacritty-setup.sh"
[[data]]
name = "Bash Prompt"
command = "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\""
[[data]]
name = "DWM-Titus"
script = "dwmtitus-setup.sh"
[[data]]
name = "Kitty"
script = "kitty-setup.sh"
[[data]]
name = "Neovim"
command = "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\""
[[data]]
name = "Rofi"
script = "rofi-setup.sh"
[[data]]
name = "ZSH Prompt"
script = "zsh-setup.sh"

View File

@ -1,6 +1,6 @@
#!/bin/sh
. ./common-script.sh
. ../common-script.sh
# Function to install zsh
install_zsh() {
@ -48,4 +48,4 @@ EOL
checkEnv
setup_zsh_config
install_zsh
install_zsh

View File

@ -1,6 +1,6 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
installPkg() {
echo "Install UFW if not already installed..."

View File

@ -0,0 +1,5 @@
name = "Security"
[[data]]
name = "Firewall Baselines (CTT)"
script = "firewall-baselines.sh"

View File

@ -1,6 +1,6 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
# Check if the home directory and linuxtoolbox folder exist, create them if they don't
LINUXTOOLBOXDIR="$HOME/linuxtoolbox"

View File

@ -1,6 +1,6 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
installDepend() {
## Check for dependencies.

View File

@ -1,6 +1,6 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
# Check if the home directory and linuxtoolbox folder exist, create them if they don't
LINUXTOOLBOXDIR="$HOME/linuxtoolbox"

View File

@ -1,6 +1,6 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
removeSnaps() {
case $PACKAGER in

View File

@ -1,6 +1,6 @@
#!/bin/sh -e
. ./common-script.sh
. ../common-script.sh
fastUpdate() {
case ${PACKAGER} in

View File

@ -0,0 +1,37 @@
name = "System Setup"
[[data]]
name = "Arch Linux"
[[data.preconditions]]
matches = true
data = "command_exists"
values = ["pacman"]
[[data.entries]]
name = "Yay AUR Helper"
script = "arch/yay-setup.sh"
[[data.entries]]
name = "Paru AUR Helper"
script = "arch/paru-setup.sh"
[[data]]
name = "Full System Update"
script = "system-update.sh"
[[data]]
name = "Build Prerequisites"
script = "1-compile-setup.sh"
[[data]]
name = "Gaming Dependencies"
script = "2-gaming-setup.sh"
[[data]]
name = "Global Theme"
script = "3-global-theme.sh"
[[data]]
name = "Remove Snaps"
script = "4-remove-snaps.sh"

1
src/commands/tabs.toml Normal file
View File

@ -0,0 +1 @@
directories = ["system-setup", "applications-setup", "security", "utils"]

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
# Function to auto-detect displays and set common resolution
auto_detect_displays() {

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
# Function to change monitor orientation
change_orientation() {

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
RESET='\033[0m'
BOLD='\033[1m'

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
# Function to duplicate displays
duplicate_displays() {

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
RESET='\033[0m'
BOLD='\033[1m'

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
# Function to extend displays
extend_displays() {

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
# Function to manage monitor arrangement
manage_arrangement() {

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
# Function to reset scaling back to 1 (native resolution) for all monitors
reset_scaling() {

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
# Function to scale smaller monitors to the highest resolution of a bigger monitor
scale_monitors() {

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
# Function to set a monitor as primary
set_primary_monitor() {

View File

@ -1,5 +1,5 @@
#!/bin/bash
source ./utils/monitor-control/utility_functions.sh
source ./utility_functions.sh
RESET='\033[0m'
BOLD='\033[1m'

View File

@ -0,0 +1,66 @@
name = "Utilities"
[[data]]
name = "WiFi Manager"
script = "wifi-control.sh"
[[data]]
name = "Bluetooth Manager"
script = "bluetooth-control.sh"
[[data]]
name = "Monitor Control"
[[data.preconditions]]
matches = true
data = { environment = "XDG_SESSION_TYPE" }
values = ["x11"]
[[data.entries]]
name = "Set Resolution"
script = "monitor-control/set_resolutions.sh"
[[data.entries]]
name = "Set Resolution"
script = "monitor-control/set_resolutions.sh"
[[data.entries]]
name = "Duplicate Displays"
script = "monitor-control/duplicate_displays.sh"
[[data.entries]]
name = "Extend Displays"
script = "monitor-control/extend_displays.sh"
[[data.entries]]
name = "Auto Detect Displays"
script = "monitor-control/auto_detect_displays.sh"
[[data.entries]]
name = "Enable Monitor"
script = "monitor-control/enable_monitor.sh"
[[data.entries]]
name = "Disable Monitor"
script = "monitor-control/disable_monitor.sh"
[[data.entries]]
name = "Set Primary Monitor"
script = "monitor-control/set_primary_monitor.sh"
[[data.entries]]
name = "Change Orientation"
script = "monitor-control/change_orientation.sh"
[[data.entries]]
name = "Manage Arrangement"
script = "monitor-control/manage_arrangement.sh"
[[data.entries]]
name = "Scale Monitors"
script = "monitor-control/scale_monitor.sh"
[[data.entries]]
name = "Reset Scaling"
script = "monitor-control/reset_scaling.sh"
matches = true

View File

@ -7,7 +7,6 @@ use ratatui::{
widgets::{Block, Borders, List},
Frame,
};
use std::path::PathBuf;
pub struct FloatingText {
text: Vec<String>,
@ -19,7 +18,7 @@ impl FloatingText {
Self { text, scroll: 0 }
}
pub fn from_command(command: &Command, mut full_path: PathBuf) -> Option<Self> {
pub fn from_command(command: &Command) -> Option<Self> {
let lines = match command {
Command::Raw(cmd) => {
// Reconstruct the line breaks and file formatting after the
@ -27,9 +26,8 @@ impl FloatingText {
cmd.lines().map(|line| line.to_string()).collect()
}
Command::LocalFile(file_path) => {
full_path.push(file_path);
let file_contents = std::fs::read_to_string(&full_path)
.map_err(|_| format!("File not found: {:?}", &full_path))
let file_contents = std::fs::read_to_string(file_path)
.map_err(|_| format!("File not found: {:?}", file_path))
.unwrap();
file_contents.lines().map(|line| line.to_string()).collect()
}

View File

@ -34,20 +34,21 @@ struct Args {
#[arg(default_value_t = Theme::Default)]
#[arg(help = "Set the theme to use in the application")]
theme: Theme,
#[arg(long, default_value_t = false)]
#[clap(help = "Show all available options, disregarding compatibility checks (UNSAFE)")]
override_validation: bool,
}
fn main() -> std::io::Result<()> {
let args = Args::parse();
let theme = args.theme;
let commands_dir = include_dir!("src/commands");
let temp_dir: TempDir = TempDir::new("linutil_scripts").unwrap();
commands_dir
.extract(temp_dir.path())
.expect("Failed to extract the saved directory");
let mut state = AppState::new(theme, temp_dir.path().to_owned());
let mut state = AppState::new(args.theme, temp_dir.path(), args.override_validation);
stdout().execute(EnterAlternateScreen)?;
enable_raw_mode()?;

View File

@ -13,7 +13,7 @@ use ratatui::{
};
use std::{
io::Write,
path::Path,
path::PathBuf,
sync::{Arc, Mutex},
thread::JoinHandle,
};
@ -22,10 +22,10 @@ use tui_term::{
widget::PseudoTerminal,
};
#[derive(Clone)]
#[derive(Clone, Hash, Eq, PartialEq)]
pub enum Command {
Raw(&'static str),
LocalFile(&'static str),
Raw(String),
LocalFile(PathBuf),
None, // Directory
}
@ -126,7 +126,7 @@ impl FloatContent for RunningCommand {
}
impl RunningCommand {
pub fn new(command: Command, temp_path: &Path) -> Self {
pub fn new(command: Command) -> Self {
let pty_system = NativePtySystem::default();
// Build the command based on the provided Command enum variant
@ -137,13 +137,14 @@ impl RunningCommand {
cmd.arg(prompt);
}
Command::LocalFile(file) => {
cmd.arg(file);
cmd.arg(&file);
if let Some(parent) = file.parent() {
cmd.cwd(parent);
}
}
Command::None => panic!("Command::None was treated as a command"),
}
cmd.cwd(temp_path);
// Open a pseudo-terminal with initial size
let pair = pty_system
.openpty(PtySize {

View File

@ -2,7 +2,7 @@ use crate::{
float::{Float, FloatContent},
floating_text::FloatingText,
running_command::{Command, RunningCommand},
tabs::{ListNode, TABS},
tabs::{ListNode, Tab},
theme::Theme,
};
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
@ -14,15 +14,15 @@ use ratatui::{
widgets::{Block, Borders, List, ListState, Paragraph},
Frame,
};
use std::path::PathBuf;
use std::path::Path;
pub struct AppState {
/// Selected theme
pub theme: Theme,
/// Path to the root of the unpacked files in /tmp
temp_path: PathBuf,
theme: Theme,
/// Currently focused area
focus: Focus,
/// List of tabs
tabs: Vec<Tab>,
/// Current tab
current_tab: ListState,
/// Current search query
@ -51,12 +51,13 @@ struct ListEntry {
}
impl AppState {
pub fn new(theme: Theme, temp_path: PathBuf) -> Self {
let root_id = TABS[0].tree.root().id();
pub fn new(theme: Theme, temp_path: &Path, override_validation: bool) -> Self {
let tabs = crate::tabs::get_tabs(temp_path, !override_validation);
let root_id = tabs[0].tree.root().id();
let mut state = Self {
theme,
temp_path,
focus: Focus::List,
tabs,
current_tab: ListState::default().with_selected(Some(0)),
search_query: String::new(),
items: vec![],
@ -67,7 +68,8 @@ impl AppState {
state
}
pub fn draw(&mut self, frame: &mut Frame) {
let longest_tab_display_len = TABS
let longest_tab_display_len = self
.tabs
.iter()
.map(|tab| tab.name.len() + self.theme.tab_icon().len())
.max()
@ -85,7 +87,11 @@ impl AppState {
.constraints([Constraint::Length(3), Constraint::Min(1)])
.split(horizontal[0]);
let tabs = TABS.iter().map(|tab| tab.name).collect::<Vec<_>>();
let tabs = self
.tabs
.iter()
.map(|tab| tab.name.as_str())
.collect::<Vec<_>>();
let tab_hl_style = if let Focus::TabList = self.focus {
Style::default().reversed().fg(self.theme.tab_color())
@ -186,7 +192,7 @@ impl AppState {
self.focus = Focus::List
}
KeyCode::Char('j') | KeyCode::Down
if self.current_tab.selected().unwrap() + 1 < TABS.len() =>
if self.current_tab.selected().unwrap() + 1 < self.tabs.len() =>
{
self.current_tab.select_next();
self.refresh_tab();
@ -224,7 +230,7 @@ impl AppState {
}
pub fn update_items(&mut self) {
if self.search_query.is_empty() {
let curr = TABS[self.current_tab.selected().unwrap()]
let curr = self.tabs[self.current_tab.selected().unwrap()]
.tree
.get(*self.visit_stack.last().unwrap())
.unwrap();
@ -241,7 +247,7 @@ impl AppState {
self.items.clear();
let query_lower = self.search_query.to_lowercase();
for tab in TABS.iter() {
for tab in self.tabs.iter() {
let mut stack = vec![tab.tree.root().id()];
while let Some(node_id) = stack.pop() {
let node = tab.tree.get(node_id).unwrap();
@ -259,7 +265,7 @@ impl AppState {
stack.extend(node.children().map(|child| child.id()));
}
}
self.items.sort_by(|a, b| a.node.name.cmp(b.node.name));
self.items.sort_by(|a, b| a.node.name.cmp(&b.node.name));
}
}
/// Checks ehther the current tree node is the root node (can we go up the tree or no)
@ -299,14 +305,14 @@ impl AppState {
}
fn enable_preview(&mut self) {
if let Some(command) = self.get_selected_command(false) {
if let Some(preview) = FloatingText::from_command(&command, self.temp_path.clone()) {
if let Some(preview) = FloatingText::from_command(&command) {
self.spawn_float(preview, 80, 80);
}
}
}
fn handle_enter(&mut self) {
if let Some(cmd) = self.get_selected_command(true) {
let command = RunningCommand::new(cmd, &self.temp_path);
let command = RunningCommand::new(cmd);
self.spawn_float(command, 80, 80);
}
}
@ -323,7 +329,10 @@ impl AppState {
self.update_items();
}
fn refresh_tab(&mut self) {
self.visit_stack = vec![TABS[self.current_tab.selected().unwrap()].tree.root().id()];
self.visit_stack = vec![self.tabs[self.current_tab.selected().unwrap()]
.tree
.root()
.id()];
self.selection.select(Some(0));
self.update_items();
}

View File

@ -1,179 +1,190 @@
use std::sync::LazyLock;
use ego_tree::{tree, Tree};
use crate::running_command::Command;
use ego_tree::{NodeMut, Tree};
use serde::Deserialize;
use std::path::{Path, PathBuf};
#[derive(Deserialize)]
struct TabList {
directories: Vec<PathBuf>,
}
#[derive(Deserialize)]
struct TabEntry {
name: String,
data: Vec<Entry>,
}
#[derive(Deserialize)]
struct Entry {
name: String,
#[allow(dead_code)]
#[serde(default)]
description: String,
#[serde(default)]
preconditions: Option<Vec<Precondition>>,
#[serde(default)]
entries: Option<Vec<Entry>>,
#[serde(default)]
command: Option<String>,
#[serde(default)]
script: Option<PathBuf>,
}
impl Entry {
fn is_supported(&self) -> bool {
self.preconditions.as_deref().map_or(true, |preconditions| {
preconditions.iter().all(
|Precondition {
matches,
data,
values,
}| {
match data {
SystemDataType::Environment(var_name) => std::env::var(var_name)
.map_or(false, |var| values.contains(&var) == *matches),
SystemDataType::File(path) => {
std::fs::read_to_string(path).map_or(false, |data| {
values
.iter()
.any(|matching_value| data.contains(matching_value))
== *matches
})
}
SystemDataType::CommandExists => values
.iter()
.all(|command| which::which(command).is_ok() == *matches),
}
},
)
})
}
}
#[derive(Deserialize)]
struct Precondition {
// If true, the data must be contained within the list of values.
// Otherwise, the data must not be contained within the list of values
matches: bool,
data: SystemDataType,
values: Vec<String>,
}
#[derive(Deserialize)]
enum SystemDataType {
#[serde(rename = "environment")]
Environment(String),
#[serde(rename = "file")]
File(PathBuf),
#[serde(rename = "command_exists")]
CommandExists,
}
#[derive(Hash, Eq, PartialEq)]
pub struct Tab {
pub name: &'static str,
pub name: String,
pub tree: Tree<ListNode>,
}
#[derive(Clone)]
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct ListNode {
pub name: &'static str,
pub name: String,
pub command: Command,
}
pub static TABS: LazyLock<Vec<Tab>> = LazyLock::new(|| {
vec![
Tab {
name: "System Setup",
tree: tree!(ListNode {
name: "root",
pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec<Tab> {
let tab_files = TabList::get_tabs(command_dir);
let tabs = tab_files.into_iter().map(|path| {
let directory = path.parent().unwrap().to_owned();
let data = std::fs::read_to_string(path).expect("Failed to read tab data");
let mut tab_data: TabEntry = toml::from_str(&data).expect("Failed to parse tab data");
if validate {
filter_entries(&mut tab_data.data);
}
(tab_data, directory)
});
let tabs: Vec<Tab> = tabs
.map(|(TabEntry { name, data }, directory)| {
let mut tree = Tree::new(ListNode {
name: "root".to_string(),
command: Command::None,
} => {
ListNode {
name: "Arch Linux",
command: Command::None,
} => {
ListNode {
name: "Yay AUR Helper",
command: Command::LocalFile("system-setup/arch/yay-setup.sh"),
},
ListNode {
name: "Paru AUR Helper",
command: Command::LocalFile("system-setup/arch/paru-setup.sh"),
}
},
ListNode {
name: "Full System Update",
command: Command::LocalFile("system-update.sh"),
},
ListNode {
name: "Build Prerequisites",
command: Command::LocalFile("system-setup/1-compile-setup.sh"),
},
ListNode {
name: "Gaming Dependencies",
command: Command::LocalFile("system-setup/2-gaming-setup.sh"),
},
ListNode {
name: "Global Theme",
command: Command::LocalFile("system-setup/3-global-theme.sh"),
},
ListNode {
name: "Remove Snaps",
command: Command::LocalFile("system-setup/5-remove-snaps.sh"),
}
}),
},
Tab {
name: "Applications Setup",
tree: tree!(ListNode {
name: "root",
});
let mut root = tree.root_mut();
create_directory(data, &mut root, &directory);
Tab { name, tree }
})
.collect();
if tabs.is_empty() {
panic!("No tabs found");
}
tabs
}
fn filter_entries(entries: &mut Vec<Entry>) {
entries.retain_mut(|entry| {
if !entry.is_supported() {
return false;
}
if let Some(entries) = &mut entry.entries {
filter_entries(entries);
!entries.is_empty()
} else {
true
}
});
}
fn create_directory(data: Vec<Entry>, node: &mut NodeMut<ListNode>, command_dir: &Path) {
for entry in data {
if [
entry.entries.is_some(),
entry.command.is_some(),
entry.script.is_some(),
]
.iter()
.filter(|&&x| x)
.count()
> 1
{
panic!("Entry must have only one data type");
}
if let Some(entries) = entry.entries {
let mut node = node.append(ListNode {
name: entry.name,
command: Command::None,
} => {
ListNode {
name: "Alacritty",
command: Command::LocalFile("applications-setup/alacritty-setup.sh"),
},
ListNode {
name: "Bash Prompt",
command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\""),
},
ListNode {
name: "DWM-Titus",
command: Command::LocalFile("applications-setup/dwmtitus-setup.sh")
},
ListNode {
name: "Kitty",
command: Command::LocalFile("applications-setup/kitty-setup.sh")
},
ListNode {
name: "Neovim",
command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\""),
},
ListNode {
name: "Rofi",
command: Command::LocalFile("applications-setup/rofi-setup.sh"),
},
ListNode {
name: "ZSH Prompt",
command: Command::LocalFile("applications-setup/zsh-setup.sh"),
}
}),
},
Tab {
name: "Security",
tree: tree!(ListNode {
name: "root",
command: Command::None,
} => {
ListNode {
name: "Firewall Baselines (CTT)",
command: Command::LocalFile("security/firewall-baselines.sh"),
}
}),
},
Tab {
name: "Utilities",
tree: tree!(ListNode {
name: "root",
command: Command::None,
} => {
ListNode {
name: "Wifi Manager",
command: Command::LocalFile("utils/wifi-control.sh"),
},
ListNode {
name: "Bluetooth Manager",
command: Command::LocalFile("utils/bluetooth-control.sh"),
},
ListNode {
name: "Toggle Numlock On Startup",
command: Command::LocalFile("utils/numlock.sh"),
},
ListNode {
name: "MonitorControl(xorg)",
command: Command::None,
} => {
ListNode {
name: "Set Resolution",
command: Command::LocalFile("utils/monitor-control/set_resolutions.sh"),
},
ListNode {
name: "Duplicate Displays",
command: Command::LocalFile("utils/monitor-control/duplicate_displays.sh"),
},
ListNode {
name: "Extend Displays",
command: Command::LocalFile("utils/monitor-control/extend_displays.sh"),
},
ListNode {
name: "Auto Detect Displays",
command: Command::LocalFile("utils/monitor-control/auto_detect_displays.sh"),
},
ListNode {
name: "Enable Monitor",
command: Command::LocalFile("utils/monitor-control/enable_monitor.sh"),
},
ListNode {
name: "Disable Monitor",
command: Command::LocalFile("utils/monitor-control/disable_monitor.sh"),
},
ListNode {
name: "Set Primary Monitor",
command: Command::LocalFile("utils/monitor-control/set_primary_monitor.sh"),
},
ListNode {
name: "Change Orientation",
command: Command::LocalFile("utils/monitor-control/change_orientation.sh"),
},
ListNode {
name: "Manage Arrangement",
command: Command::LocalFile("utils/monitor-control/manage_arrangement.sh"),
},
ListNode {
name: "Scale Monitors",
command: Command::LocalFile("utils/monitor-control/scale_monitor.sh"),
},
ListNode {
name: "Reset Scaling",
command: Command::LocalFile("utils/monitor-control/reset_scaling.sh"),
}
},
}),
},
]
});
});
create_directory(entries, &mut node, command_dir);
} else if let Some(command) = entry.command {
node.append(ListNode {
name: entry.name,
command: Command::Raw(command),
});
} else if let Some(script) = entry.script {
let dir = command_dir.join(script);
if !dir.exists() {
panic!("Script {} does not exist", dir.display());
}
node.append(ListNode {
name: entry.name,
command: Command::LocalFile(dir),
});
} else {
panic!("Entry must have data");
}
}
}
impl TabList {
fn get_tabs(command_dir: &Path) -> Vec<PathBuf> {
let tab_files = std::fs::read_to_string(command_dir.join("tabs.toml"))
.expect("Failed to read tabs.toml");
let data: Self = toml::from_str(&tab_files).expect("Failed to parse tabs.toml");
data.directories
.into_iter()
.map(|path| command_dir.join(path).join("tab_data.toml"))
.collect()
}
}