Compare commits

...

4 Commits

Author SHA1 Message Date
Adam Perkowski
0efd7d7cd2
Merge 2c0a6ba80ea35cd2c63e695fbe43ff51db67cb48 into c12ae4a8ef3af3ff37b577735ac3717a63149b8c 2025-02-12 07:40:32 -05:00
Chris Titus
c12ae4a8ef dm fixes and posix 2025-02-11 21:00:19 -06:00
nyx
6b572b1ab4
refactor: misc restructering (#1015)
Changes:

Separate cli flags and put them in
their own module.

Add a rustfmt configuration file for
grouping imports into crates (so we
dont have to do this manually, and it
seems we were already doing it manually
so might as well)

Use comments to describe cli flags
instead of using ugly syntax (they both
work the same but one is less ugly and
more readable)

Add a --mouse flag to enable mouse
interaction (mouse interaction is now
off by default)

Add a --bypass-root flag to disable
the annoying popup when you run the
utility as root

Fix an issue where you can't reach the
bottom of the list in a subdir (i also
reduced the nesting in those functions
as well for readability)

Add help feature to clap dependency to
enable --help / -h

Add usage feature to clap dependency
to enable usage example when running
with --help / -h

Remove CLI arg examples from readme,
and add instructions on how to view
them on CLI to prevent it from bloating
up the readme
2025-02-11 10:16:05 -06:00
Adam Perkowski
2c0a6ba80e
ui: "unselect" the current tab when in search mode 2025-01-14 19:10:07 +01:00
11 changed files with 154 additions and 98 deletions

View File

@ -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 <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 <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 youre 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)

View File

@ -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,

View File

@ -35,8 +35,8 @@ install_nerd_font() {
FONT_URL="https://github.com/ryanoasis/nerd-fonts/releases/latest/download/Meslo.zip"
FONT_INSTALLED=$(fc-list | grep -i "Meslo")
# Check if Meslo Nerd-font is already installed
if [ -n "$FONT_INSTALLED" ]; then
# Replace -n test with standard test
if [ ! -z "$FONT_INSTALLED" ]; then
printf "%b\n" "${GREEN}Meslo Nerd-fonts are already installed.${RC}"
return 0
fi
@ -207,29 +207,37 @@ setupDisplayManager() {
done
printf "%b\n" "${GREEN}Current display manager: $currentdm${RC}"
if [ "$currentdm" = "none" ]; then
printf "%b\n" "${YELLOW}--------------------------${RC}"
printf "%b\n" "${YELLOW}Pick your Display Manager ${RC}"
printf "%b\n" "${YELLOW}1. SDDM ${RC}"
printf "%b\n" "${YELLOW}2. LightDM ${RC}"
printf "%b\n" "${YELLOW}3. GDM ${RC}"
printf "%b\n" "${YELLOW} ${RC}"
printf "%b" "${YELLOW}Please select one: ${RC}"
read -r choice
case "$choice" in
1)
DM="sddm"
;;
2)
DM="lightdm"
;;
3)
DM="gdm"
;;
*)
printf "%b\n" "${RED}Invalid selection! Please choose 1, 2, or 3.${RC}"
exit 1
;;
esac
while : ; do
printf "%b\n" "${YELLOW}--------------------------${RC}"
printf "%b\n" "${YELLOW}Pick your Display Manager ${RC}"
printf "%b\n" "${YELLOW}1. SDDM ${RC}"
printf "%b\n" "${YELLOW}2. LightDM ${RC}"
printf "%b\n" "${YELLOW}3. GDM ${RC}"
printf "%b\n" "${YELLOW}4. None ${RC}"
printf "%b" "${YELLOW}Please select one: ${RC}"
read -r choice
case "$choice" in
1)
DM="sddm"
break
;;
2)
DM="lightdm"
break
;;
3)
DM="gdm"
break
;;
4)
printf "%b\n" "${GREEN}No display manager will be installed${RC}"
return 0
;;
*)
printf "%b\n" "${RED}Invalid selection! Please choose 1, 2, 3, or 4.${RC}"
;;
esac
done
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm "$DM"

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
imports_granularity = "Crate"

View File

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

35
tui/src/cli.rs Normal file
View File

@ -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<PathBuf>,
/// 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,
}

View File

@ -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<PathBuf>,
#[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()?;

View File

@ -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<FloatingText<'a>> {
(Uid::effective().is_root()).then_some(FloatingText::new(
pub fn check_root_status(bypass_root: bool) -> Option<FloatingText<'static>> {
if bypass_root {
return None;
}
Uid::effective().is_root().then_some(FloatingText::new(
ROOT_WARNING.into(),
"Root User Warning",
true,

View File

@ -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);
}
@ -333,16 +345,24 @@ impl AppState {
.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())
let (tab_hl_style, highlight_symbol) = if let Focus::TabList = self.focus {
(
Style::default().reversed().fg(self.theme.tab_color()),
self.theme.tab_icon(),
)
} else if let Focus::Search = self.focus {
(Style::reset(), " ")
} else {
Style::new().fg(self.theme.tab_color())
(
Style::new().fg(self.theme.tab_color()),
self.theme.tab_icon(),
)
};
let tab_list = List::new(tabs)
.block(Block::bordered().border_set(border::ROUNDED))
.highlight_style(tab_hl_style)
.highlight_symbol(self.theme.tab_icon());
.highlight_symbol(highlight_symbol);
frame.render_stateful_widget(tab_list, left_chunks[1], &mut self.current_tab);
let chunks =
@ -440,6 +460,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 +621,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) {

View File

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

View File

@ -6,9 +6,10 @@ use std::{env, error::Error};
type DynError = Box<dyn Error>;
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()?);