diff --git a/README.md b/README.md index c0568171..a614f4bc 100644 --- a/README.md +++ b/README.md @@ -27,25 +27,18 @@ curl -fsSL https://christitus.com/linuxdev | sh ### CLI arguments -Linutil supports various command-line arguments to customize its behavior. Here are some common arguments you can use: - -- `-c, --config ` : Path to the configuration file. -- `--override-validation` : Show all available options, disregarding compatibility checks (UNSAFE). -- `--size-bypass` : Bypass the terminal size limit. -- `-y, --skip-confirmation` : Skip confirmation prompt before executing commands. -- `-t, --theme ` : Set the theme to use in the application [default: `default`] [possible values: `default`, `compatible`]. -- `-h, --help` : Print help. - -For more detailed usage, run: - -```bash -curl -fsSL https://christitus.com/linux | sh -s -- --help -``` +View available options by running: ```bash linutil --help ``` +For installer options: + +```bash +curl -fsSL https://christitus.com/linux | sh -s -- --help +``` + ## ⬇️ Installation Linutil is also available as a package in various repositories: @@ -142,7 +135,7 @@ Docs are now [here](https://github.com/Chris-Titus-Docs/linutil-docs) ## 🏅 Thanks to All Contributors -Thank you to everyone who has contributed to the development of Linutil. Your efforts are greatly appreciated, and you’re helping make this tool better for everyone! +Thank you to everyone who has contributed to the development of Linutil. Your efforts are greatly appreciated, and you're helping make this tool better for everyone! [![Contributors](https://contrib.rocks/image?repo=ChrisTitusTech/linutil)](https://github.com/ChrisTitusTech/linutil/graphs/contributors) diff --git a/core/src/inner.rs b/core/src/inner.rs index 16d12cbe..8621ea2b 100644 --- a/core/src/inner.rs +++ b/core/src/inner.rs @@ -120,7 +120,7 @@ enum EntryType { impl Entry { fn is_supported(&self) -> bool { - self.preconditions.as_deref().map_or(true, |preconditions| { + self.preconditions.as_deref().is_none_or(|preconditions| { preconditions.iter().all( |Precondition { matches, diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..d9ba5fdb --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +imports_granularity = "Crate" \ No newline at end of file diff --git a/tui/Cargo.toml b/tui/Cargo.toml index 9143baf8..1041fc29 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -14,7 +14,7 @@ default = ["tips"] tips = ["rand"] [dependencies] -clap = { version = "4.5.20", features = ["derive", "std"], default-features = false } +clap = { version = "4.5.20", features = ["derive", "std", "help", "usage"], default-features = false } oneshot = { version = "0.1.8", features = ["std"], default-features = false } portable-pty = "0.8.1" ratatui = { version = "0.29.0", features = ["crossterm"], default-features = false } diff --git a/tui/src/cli.rs b/tui/src/cli.rs new file mode 100644 index 00000000..564b2d68 --- /dev/null +++ b/tui/src/cli.rs @@ -0,0 +1,35 @@ +use crate::theme::Theme; +use clap::Parser; +use std::path::PathBuf; + +#[derive(Debug, Parser, Clone)] +pub struct Args { + /// Path to the configuration file + #[arg(short, long)] + pub config: Option, + + /// Set the theme to use in the application + #[arg(short, long, value_enum)] + #[arg(default_value_t = Theme::Default)] + pub theme: Theme, + + /// Skip confirmation prompt before executing commands + #[arg(short = 'y', long)] + pub skip_confirmation: bool, + + /// Show all available options, disregarding compatibility checks (UNSAFE) + #[arg(short = 'u', long)] + pub override_validation: bool, + + /// Bypass the terminal size limit + #[arg(short = 's', long)] + pub size_bypass: bool, + + /// Enable mouse interaction + #[arg(short = 'm', long)] + pub mouse: bool, + + /// Bypass root user check + #[arg(short = 'r', long)] + pub bypass_root: bool, +} diff --git a/tui/src/main.rs b/tui/src/main.rs index 76ef584f..11ace649 100644 --- a/tui/src/main.rs +++ b/tui/src/main.rs @@ -1,3 +1,4 @@ +mod cli; mod confirmation; mod filter; mod float; @@ -11,7 +12,7 @@ mod theme; #[cfg(feature = "tips")] mod tips; -use crate::theme::Theme; +use crate::cli::Args; use clap::Parser; use ratatui::{ backend::CrosstermBackend, @@ -26,40 +27,17 @@ use ratatui::{ use state::AppState; use std::{ io::{stdout, Result, Stdout}, - path::PathBuf, time::Duration, }; -// Linux utility toolbox -#[derive(Debug, Parser)] -pub struct Args { - #[arg(short, long, help = "Path to the configuration file")] - config: Option, - #[arg(short, long, value_enum)] - #[arg(default_value_t = Theme::Default)] - #[arg(help = "Set the theme to use in the application")] - theme: Theme, - #[arg( - short = 'y', - long, - help = "Skip confirmation prompt before executing commands" - )] - skip_confirmation: bool, - #[arg(long, default_value_t = false)] - #[clap(help = "Show all available options, disregarding compatibility checks (UNSAFE)")] - override_validation: bool, - #[arg(long, default_value_t = false)] - #[clap(help = "Bypass the terminal size limit")] - size_bypass: bool, -} - fn main() -> Result<()> { let args = Args::parse(); - - let mut state = AppState::new(args); + let mut state = AppState::new(args.clone()); stdout().execute(EnterAlternateScreen)?; - stdout().execute(EnableMouseCapture)?; + if args.mouse { + stdout().execute(EnableMouseCapture)?; + } enable_raw_mode()?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; @@ -70,7 +48,9 @@ fn main() -> Result<()> { // restore terminal disable_raw_mode()?; terminal.backend_mut().execute(LeaveAlternateScreen)?; - terminal.backend_mut().execute(DisableMouseCapture)?; + if args.mouse { + terminal.backend_mut().execute(DisableMouseCapture)?; + } terminal.backend_mut().execute(ResetColor)?; terminal.show_cursor()?; diff --git a/tui/src/root.rs b/tui/src/root.rs index 46432d06..23468ef2 100644 --- a/tui/src/root.rs +++ b/tui/src/root.rs @@ -8,8 +8,12 @@ This means you have full system access and commands can potentially damage your Please proceed with caution and make sure you understand what each script does before executing it."; #[cfg(unix)] -pub fn check_root_status<'a>() -> Option> { - (Uid::effective().is_root()).then_some(FloatingText::new( +pub fn check_root_status(bypass_root: bool) -> Option> { + if bypass_root { + return None; + } + + Uid::effective().is_root().then_some(FloatingText::new( ROOT_WARNING.into(), "Root User Warning", true, diff --git a/tui/src/state.rs b/tui/src/state.rs index 4bcb9c48..3baffef3 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -66,6 +66,7 @@ pub struct AppState { tip: &'static str, size_bypass: bool, skip_confirmation: bool, + mouse_enabled: bool, } pub enum Focus { @@ -94,8 +95,18 @@ enum SelectedItem { None, } +enum ScrollDir { + Up, + Down, +} + impl AppState { pub fn new(args: Args) -> Self { + #[cfg(unix)] + let root_warning = check_root_status(args.bypass_root); + #[cfg(not(unix))] + let root_warning = None; + let tabs = linutil_core::get_tabs(!args.override_validation); let root_id = tabs[0].tree.root().id(); @@ -122,10 +133,11 @@ impl AppState { tip: crate::tips::get_random_tip(), size_bypass: args.size_bypass, skip_confirmation: args.skip_confirmation, + mouse_enabled: args.mouse, }; #[cfg(unix)] - if let Some(root_warning) = check_root_status() { + if let Some(root_warning) = root_warning { state.spawn_float(root_warning, FLOAT_SIZE, FLOAT_SIZE); } @@ -440,6 +452,10 @@ impl AppState { } pub fn handle_mouse(&mut self, event: &MouseEvent) -> bool { + if !self.mouse_enabled { + return true; + } + if !self.drawable { return true; } @@ -597,24 +613,35 @@ impl AppState { true } - fn scroll_down(&mut self) { - if let Some(selected) = self.selection.selected() { - if selected == self.filter.item_list().len() - 1 { - self.selection.select_first(); - } else { - self.selection.select_next(); - } - } + fn scroll(&mut self, direction: ScrollDir) { + let Some(selected) = self.selection.selected() else { + return; + }; + let list_len = if !self.at_root() { + self.filter.item_list().len() + 1 + } else { + self.filter.item_list().len() + }; + + if list_len == 0 { + return; + }; + + let next_selection = match direction { + ScrollDir::Up if selected == 0 => list_len - 1, + ScrollDir::Down if selected >= list_len - 1 => 0, + ScrollDir::Up => selected - 1, + ScrollDir::Down => selected + 1, + }; + self.selection.select(Some(next_selection)); } fn scroll_up(&mut self) { - if let Some(selected) = self.selection.selected() { - if selected == 0 { - self.selection.select_last(); - } else { - self.selection.select_previous(); - } - } + self.scroll(ScrollDir::Up) + } + + fn scroll_down(&mut self) { + self.scroll(ScrollDir::Down) } fn toggle_multi_select(&mut self) { diff --git a/xtask/src/docgen.rs b/xtask/src/docgen.rs index 385c1134..15a5e2af 100644 --- a/xtask/src/docgen.rs +++ b/xtask/src/docgen.rs @@ -2,8 +2,7 @@ use std::fs; use linutil_core::Command; -use crate::path; -use crate::DynError; +use crate::{path, DynError}; pub const USER_GUIDE: &str = "userguide.md"; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index fecab8c8..a1be3f7c 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -6,9 +6,10 @@ use std::{env, error::Error}; type DynError = Box; pub mod tasks { - use crate::docgen::USER_GUIDE; - use crate::docgen::{userguide, write}; - use crate::DynError; + use crate::{ + docgen::{userguide, write, USER_GUIDE}, + DynError, + }; pub fn docgen() -> Result<(), DynError> { write(USER_GUIDE, &userguide()?);