Merge branch 'main' of ssh://github.com/ChrisTitusTech/linutil into generic-popup-title

This commit is contained in:
afonsofrancof 2024-10-03 21:07:12 +01:00
commit 34ffbc5e8d
No known key found for this signature in database
62 changed files with 1401 additions and 974 deletions

View File

@ -1,66 +0,0 @@
tag-prefix: ""
categories:
- title: '🚀 Features'
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '📚 Documentation'
label: 'documentation'
- title: '🔒 Security'
label: 'security'
- title: '🧰 GitHub Actions'
label: 'github actions'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
template: |
## Changes
$CHANGES
change-title-escapes: '\<*_&"'''
autolabeler:
- label: 'documentation'
files:
- '*.md'
branch:
- '/docs{0,1}\/.+/'
- label: 'bug'
branch:
- '/fix\/.+/'
title:
- '/fix/i'
- label: 'enhancement'
branch:
- '/feature\/.+/'
body:
- '/[A-Z]+-[0-9]+/'
- label: 'documentation'
files:
- '**/*.md'
- 'docs/**/*'
- label: 'security'
branch:
- '/security\/.+/'
include-labels:
- 'documentation'
- 'bug'
- 'enhancement'
- 'security'
- 'github actions'
replacers:
- search: /"/g
replace: ''
- search: /'/g
replace: ''
- search: /`/g
replace: ''
exclude-labels:
- 'skip-changelog'
filter-by-commitish: false

20
.github/release.yml vendored Normal file
View File

@ -0,0 +1,20 @@
changelog:
categories:
- title: '🚀 Features'
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '📚 Documentation'
label: 'documentation'
- title: '🔒 Security'
label: 'security'
- title: '🧰 GitHub Actions'
label: 'github actions'
exclude:
labels:
- 'skip-changelog'

View File

@ -19,17 +19,27 @@ jobs:
id: get_sh_files id: get_sh_files
run: | run: |
sh_files=$(git diff --name-only origin/${{ github.base_ref }} HEAD core/tabs | grep '\.sh$' || true) sh_files=$(git diff --name-only origin/${{ github.base_ref }} HEAD core/tabs | grep '\.sh$' || true)
echo "::set-output name=sh_files::$sh_files" if [ -n "$sh_files" ]; then
echo "$sh_files" > changed_files
echo "changed=1" >> $GITHUB_OUTPUT
else
echo "changed=0" >> $GITHUB_OUTPUT
fi
- name: Install devscripts - name: Install devscripts
if: steps.get_sh_files.outputs.sh_files != '' if: steps.get_sh_files.outputs.changed == 1
run: sudo apt-get update && sudo apt-get install devscripts run: sudo apt-get update && sudo apt-get install devscripts
- name: Check for bashisms - name: Check for bashisms
if: steps.get_sh_files.outputs.sh_files != '' if: steps.get_sh_files.outputs.changed == 1
run: | run: |
for file in ${{ steps.get_sh_files.outputs.sh_files }}; do echo "Running for:\n$(cat changed_files)\n"
if [[ -f "$file" ]]; then for file in $(cat changed_files); do
checkbashisms "$file" if [[ -f "$file" ]]; then
fi checkbashisms "$file"
fi
done done
- name: Remove the created file
if: steps.get_sh_files.outputs.changed == 1
run: rm changed_files

View File

@ -1,8 +1,6 @@
name: LinUtil Release name: LinUtil Release
on: on:
push:
branches: ["main"]
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@ -55,12 +53,6 @@ jobs:
mv build/x86_64-unknown-linux-musl/release/linutil build/linutil mv build/x86_64-unknown-linux-musl/release/linutil build/linutil
mv build/aarch64-unknown-linux-musl/release/linutil build/linutil-aarch64 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 - uses: stefanzweifel/git-auto-commit-action@v5
with: with:
commit_message: Commit Linutil commit_message: Commit Linutil
@ -75,15 +67,6 @@ jobs:
echo "version=$version" >> $GITHUB_ENV echo "version=$version" >> $GITHUB_ENV
shell: bash shell: bash
- name: Generate Release Notes
id: generate_notes
uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
config-name: release-drafter.yml
version: ${{ env.version }}
- name: Create and Upload Release - name: Create and Upload Release
id: create_release id: create_release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
@ -91,12 +74,11 @@ jobs:
tag_name: ${{ env.version }} tag_name: ${{ env.version }}
name: Pre-Release ${{ env.version }} name: Pre-Release ${{ env.version }}
body: | body: |
${{ steps.generate_notes.outputs.body }}
![GitHub Downloads (specific asset, specific tag)](https://img.shields.io/github/downloads/ChrisTitusTech/linutil/${{ env.version }}/linutil) ![GitHub Downloads (specific asset, specific tag)](https://img.shields.io/github/downloads/ChrisTitusTech/linutil/${{ env.version }}/linutil)
![GitHub Downloads (specific asset, specific tag)](https://img.shields.io/github/downloads/ChrisTitusTech/linutil/${{ env.version }}/linutil-aarch64) ![GitHub Downloads (specific asset, specific tag)](https://img.shields.io/github/downloads/ChrisTitusTech/linutil/${{ env.version }}/linutil-aarch64)
append_body: false append_body: true
generate_release_notes: true
files: | files: |
./build/linutil ./build/linutil
./build/linutil-aarch64 ./build/linutil-aarch64
@ -106,3 +88,23 @@ jobs:
env: env:
version: ${{ env.version }} version: ${{ env.version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install vhs
run: |
wget 'https://github.com/charmbracelet/vhs/releases/download/v0.8.0/vhs_0.8.0_amd64.deb'
sudo apt install -y ffmpeg
sudo snap install ttyd --classic
sudo dpkg -i 'vhs_0.8.0_amd64.deb'
- name: Build the preview
run: |
export PATH="$(pwd)/build:$PATH"
vhs docs/assets/preview.tape -o docs/assets/preview.gif
- name: Upload the preview
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Preview for ${{ env.version }}
file_pattern: "docs/assets/preview.gif"
add_options: "--force"
if: success()

108
Cargo.lock generated
View File

@ -194,9 +194,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.17" version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -204,9 +204,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.17" version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -216,9 +216,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.13" version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -328,12 +328,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.15" version = "0.2.15"
@ -491,19 +485,19 @@ checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]] [[package]]
name = "linutil_core" name = "linutil_core"
version = "24.9.22" version = "24.9.28"
dependencies = [ dependencies = [
"ego-tree", "ego-tree",
"include_dir", "include_dir",
"serde", "serde",
"tempdir", "temp-dir",
"toml", "toml",
"which", "which",
] ]
[[package]] [[package]]
name = "linutil_tui" name = "linutil_tui"
version = "24.9.22" version = "24.9.28"
dependencies = [ dependencies = [
"ansi-to-tui", "ansi-to-tui",
"anstyle", "anstyle",
@ -514,12 +508,13 @@ dependencies = [
"linutil_core", "linutil_core",
"oneshot", "oneshot",
"portable-pty", "portable-pty",
"rand 0.8.5", "rand",
"ratatui", "ratatui",
"temp-dir",
"tree-sitter-bash", "tree-sitter-bash",
"tree-sitter-highlight", "tree-sitter-highlight",
"tui-term", "tui-term",
"unicode-width", "unicode-width 0.2.0",
"zips", "zips",
] ]
@ -716,19 +711,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -737,7 +719,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha",
"rand_core 0.6.4", "rand_core",
] ]
[[package]] [[package]]
@ -747,24 +729,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core 0.6.4", "rand_core",
] ]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.4" version = "0.6.4"
@ -792,16 +759,7 @@ dependencies = [
"strum_macros", "strum_macros",
"unicode-segmentation", "unicode-segmentation",
"unicode-truncate", "unicode-truncate",
"unicode-width", "unicode-width 0.1.14",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
] ]
[[package]] [[package]]
@ -842,15 +800,6 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.37" version = "0.38.37"
@ -1063,14 +1012,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tempdir" name = "temp-dir"
version = "0.3.7" version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" checksum = "bc1ee6eef34f12f765cb94725905c6312b6610ab2b0940889cfe58dae7bc3c72"
dependencies = [
"rand 0.4.6",
"remove_dir_all",
]
[[package]] [[package]]
name = "termios" name = "termios"
@ -1205,14 +1150,20 @@ checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [ dependencies = [
"itertools", "itertools",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width 0.1.14",
] ]
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.13" version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
@ -1229,12 +1180,11 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]] [[package]]
name = "vt100" name = "vt100"
version = "0.15.2" version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/ChrisTitusTech/vt100-rust#e41fb3d8fb5fd01dd2d076c9a25823a31656012f"
checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de"
dependencies = [ dependencies = [
"itoa", "itoa",
"log", "log",
"unicode-width", "unicode-width 0.1.14",
"vte", "vte",
] ]

View File

@ -1,6 +1,6 @@
[workspace.package] [workspace.package]
license = "MIT" license = "MIT"
version = "24.9.22" version = "24.9.28"
[workspace.dependencies] [workspace.dependencies]
ego-tree = "0.6.2" ego-tree = "0.6.2"
@ -9,6 +9,9 @@ ego-tree = "0.6.2"
members = ["tui", "core"] members = ["tui", "core"]
resolver = "2" resolver = "2"
[patch.crates-io]
vt100 = { git = "https://github.com/ChrisTitusTech/vt100-rust" }
[profile.release] [profile.release]
opt-level = "z" opt-level = "z"
debug = false debug = false

View File

@ -7,7 +7,7 @@
<!-- TODO: crates.io package here + <br> --> <!-- TODO: crates.io package here + <br> -->
[![Crates.io Version](https://img.shields.io/crates/v/linutil_tui?style=for-the-badge&color=%23af3a03)](https://crates.io/crates/linutil_tui) [![linutil AUR Version](https://img.shields.io/aur/version/linutil?style=for-the-badge&label=%5BAUR%5D%20linutil&color=%23230567ff)](https://aur.archlinux.org/packages/linutil) [![linutil-bin AUR Version](https://img.shields.io/aur/version/linutil-bin?style=for-the-badge&label=%5BAUR%5D%20linutil-bin&color=%23230567ff)](https://aur.archlinux.org/packages/linutil-bin) [![Crates.io Version](https://img.shields.io/crates/v/linutil_tui?style=for-the-badge&color=%23af3a03)](https://crates.io/crates/linutil_tui) [![linutil AUR Version](https://img.shields.io/aur/version/linutil?style=for-the-badge&label=%5BAUR%5D%20linutil&color=%23230567ff)](https://aur.archlinux.org/packages/linutil) [![linutil-bin AUR Version](https://img.shields.io/aur/version/linutil-bin?style=for-the-badge&label=%5BAUR%5D%20linutil-bin&color=%23230567ff)](https://aur.archlinux.org/packages/linutil-bin)
![Preview](docs/assets/preview.png) ![Preview](docs/assets/preview.gif)
**Linutil** is a distro-agnostic toolbox designed to simplify everyday Linux tasks. It helps you set up applications and optimize your system for specific use cases. The utility is actively developed in Rust 🦀, providing performance and reliability. **Linutil** is a distro-agnostic toolbox designed to simplify everyday Linux tasks. It helps you set up applications and optimize your system for specific use cases. The utility is actively developed in Rust 🦀, providing performance and reliability.

View File

@ -1,5 +0,0 @@
What to do when you have a Cargo.lock merge conflict?
1. `git checkout origin/main -- Cargo.lock` to get the original Cargo.lock
2. `cargo build` to update Cargo.lock
3. `git add Cargo.lock`
4. continue the merge as normal

View File

@ -13,7 +13,7 @@ include = [
[dependencies] [dependencies]
include_dir = "0.7.4" include_dir = "0.7.4"
tempdir = "0.3.7" temp-dir = "0.1.14"
serde = { version = "1.0.205", features = ["derive"], default-features = false } serde = { version = "1.0.205", features = ["derive"], default-features = false }
toml = { version = "0.8.19", features = ["parse"], default-features = false } toml = { version = "0.8.19", features = ["parse"], default-features = false }
which = "6.0.3" which = "6.0.3"

View File

@ -1,31 +1,38 @@
use crate::{Command, ListNode, Tab};
use ego_tree::{NodeMut, Tree};
use include_dir::{include_dir, Dir};
use serde::Deserialize;
use std::{ use std::{
fs::File, fs::File,
io::{BufRead, BufReader, Read}, io::{BufRead, BufReader, Read},
os::unix::fs::PermissionsExt, os::unix::fs::PermissionsExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc,
}; };
use tempdir::TempDir;
use crate::{Command, ListNode, Tab};
use ego_tree::{NodeMut, Tree};
use include_dir::{include_dir, Dir};
use serde::Deserialize;
use temp_dir::TempDir;
const TAB_DATA: Dir = include_dir!("$CARGO_MANIFEST_DIR/tabs"); const TAB_DATA: Dir = include_dir!("$CARGO_MANIFEST_DIR/tabs");
pub fn get_tabs(validate: bool) -> Vec<Tab> { pub fn get_tabs(validate: bool) -> (TempDir, Vec<Tab>) {
let tab_files = TabList::get_tabs(); let (temp_dir, tab_files) = TabList::get_tabs();
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 { let tabs: Vec<_> = tab_files
filter_entries(&mut tab_data.data); .into_iter()
} .map(|path| {
(tab_data, directory) 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)
})
.collect();
let tabs: Vec<Tab> = tabs let tabs: Vec<Tab> = tabs
.into_iter()
.map( .map(
|( |(
TabEntry { TabEntry {
@ -35,12 +42,12 @@ pub fn get_tabs(validate: bool) -> Vec<Tab> {
}, },
directory, directory,
)| { )| {
let mut tree = Tree::new(ListNode { let mut tree = Tree::new(Rc::new(ListNode {
name: "root".to_string(), name: "root".to_string(),
description: String::new(), description: String::new(),
command: Command::None, command: Command::None,
task_list: String::new(), task_list: String::new(),
}); }));
let mut root = tree.root_mut(); let mut root = tree.root_mut();
create_directory(data, &mut root, &directory, validate); create_directory(data, &mut root, &directory, validate);
Tab { Tab {
@ -55,7 +62,7 @@ pub fn get_tabs(validate: bool) -> Vec<Tab> {
if tabs.is_empty() { if tabs.is_empty() {
panic!("No tabs found"); panic!("No tabs found");
} }
tabs (temp_dir, tabs)
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -164,28 +171,28 @@ fn filter_entries(entries: &mut Vec<Entry>) {
fn create_directory( fn create_directory(
data: Vec<Entry>, data: Vec<Entry>,
node: &mut NodeMut<ListNode>, node: &mut NodeMut<Rc<ListNode>>,
command_dir: &Path, command_dir: &Path,
validate: bool, validate: bool,
) { ) {
for entry in data { for entry in data {
match entry.entry_type { match entry.entry_type {
EntryType::Entries(entries) => { EntryType::Entries(entries) => {
let mut node = node.append(ListNode { let mut node = node.append(Rc::new(ListNode {
name: entry.name, name: entry.name,
description: entry.description, description: entry.description,
command: Command::None, command: Command::None,
task_list: String::new(), task_list: String::new(),
}); }));
create_directory(entries, &mut node, command_dir, validate); create_directory(entries, &mut node, command_dir, validate);
} }
EntryType::Command(command) => { EntryType::Command(command) => {
node.append(ListNode { node.append(Rc::new(ListNode {
name: entry.name, name: entry.name,
description: entry.description, description: entry.description,
command: Command::Raw(command), command: Command::Raw(command),
task_list: String::new(), task_list: String::new(),
}); }));
} }
EntryType::Script(script) => { EntryType::Script(script) => {
let script = command_dir.join(script); let script = command_dir.join(script);
@ -194,7 +201,7 @@ fn create_directory(
} }
if let Some((executable, args)) = get_shebang(&script, validate) { if let Some((executable, args)) = get_shebang(&script, validate) {
node.append(ListNode { node.append(Rc::new(ListNode {
name: entry.name, name: entry.name,
description: entry.description, description: entry.description,
command: Command::LocalFile { command: Command::LocalFile {
@ -203,7 +210,7 @@ fn create_directory(
file: script, file: script,
}, },
task_list: entry.task_list, task_list: entry.task_list,
}); }));
} }
} }
} }
@ -246,19 +253,20 @@ fn is_executable(path: &Path) -> bool {
} }
impl TabList { impl TabList {
fn get_tabs() -> Vec<PathBuf> { fn get_tabs() -> (TempDir, Vec<PathBuf>) {
let temp_dir = TempDir::new("linutil_scripts").unwrap().into_path(); let temp_dir = TempDir::new().unwrap();
TAB_DATA TAB_DATA
.extract(&temp_dir) .extract(&temp_dir)
.expect("Failed to extract the saved directory"); .expect("Failed to extract the saved directory");
let tab_files = let tab_files = std::fs::read_to_string(temp_dir.path().join("tabs.toml"))
std::fs::read_to_string(temp_dir.join("tabs.toml")).expect("Failed to read tabs.toml"); .expect("Failed to read tabs.toml");
let data: Self = toml::from_str(&tab_files).expect("Failed to parse tabs.toml"); let data: Self = toml::from_str(&tab_files).expect("Failed to parse tabs.toml");
let tab_paths = data
data.directories .directories
.iter() .iter()
.map(|path| temp_dir.join(path).join("tab_data.toml")) .map(|path| temp_dir.path().join(path).join("tab_data.toml"))
.collect() .collect();
(temp_dir, tab_paths)
} }
} }

View File

@ -1,5 +1,7 @@
mod inner; mod inner;
use std::rc::Rc;
use ego_tree::Tree; use ego_tree::Tree;
use std::path::PathBuf; use std::path::PathBuf;
@ -20,7 +22,7 @@ pub enum Command {
#[derive(Clone, Hash, Eq, PartialEq)] #[derive(Clone, Hash, Eq, PartialEq)]
pub struct Tab { pub struct Tab {
pub name: String, pub name: String,
pub tree: Tree<ListNode>, pub tree: Tree<Rc<ListNode>>,
pub multi_selectable: bool, pub multi_selectable: bool,
} }

View File

@ -3,10 +3,25 @@
. ../../common-script.sh . ../../common-script.sh
installMeld() { installMeld() {
cd "$HOME" && git clone https://gitlab.gnome.org/GNOME/meld.git if ! command_exists meld; then
echo "PATH=\$PATH:$HOME/meld/bin" | "$ESCALATION_TOOL" tee -a /etc/environment printf "%b\n" "${YELLOW}Installing Meld...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm meld
;;
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" -y install meld
;;
*)
. ../setup-flatpak.sh
flatpak install -y flathub org.gnome.meld
;;
esac
else
printf "%b\n" "${GREEN}Meld is already installed.${RC}"
fi
} }
checkEnv checkEnv
checkEscalationTool checkEscalationTool
installMeld installMeld

View File

@ -4,15 +4,6 @@
gitpath="$HOME/.local/share/neovim" gitpath="$HOME/.local/share/neovim"
checkNeovimVer() {
# lazy.nvim requires nvim >= 0.8.0
nvim_version=$(nvim --version | head -n 1 | awk '{print $2}')
if [ "$(printf "%s\n" "$nvim_version" "0.8.0" | sort -V | head -n 1)" != "0.8.0" ]; then
printf "%b\n" "${RED}Neovim version $nvim_version not supported.${RC}"
exit 1
fi
}
cloneNeovim() { cloneNeovim() {
# Check if the dir exists before attempting to clone into it. # Check if the dir exists before attempting to clone into it.
if [ -d "$gitpath" ]; then if [ -d "$gitpath" ]; then
@ -30,7 +21,10 @@ installNeovim() {
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm neovim ripgrep fzf python-virtualenv luarocks go shellcheck git "$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm neovim ripgrep fzf python-virtualenv luarocks go shellcheck git
;; ;;
apt-get|nala) apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y neovim ripgrep fd-find python3-venv luarocks golang-go shellcheck git "$ESCALATION_TOOL" "$PACKAGER" install -y ripgrep fd-find python3-venv luarocks golang-go shellcheck git
curl -sSLo /tmp/nvim.appimage https://github.com/neovim/neovim/releases/latest/download/nvim.appimage
chmod u+x /tmp/nvim.appimage
"$ESCALATION_TOOL" mv /tmp/nvim.appimage /usr/local/bin/nvim
;; ;;
dnf|zypper) dnf|zypper)
"$ESCALATION_TOOL" "$PACKAGER" install -y neovim ripgrep fzf python3-virtualenv luarocks golang ShellCheck git "$ESCALATION_TOOL" "$PACKAGER" install -y neovim ripgrep fzf python3-virtualenv luarocks golang ShellCheck git
@ -60,7 +54,6 @@ linkNeovimConfig() {
checkEnv checkEnv
checkEscalationTool checkEscalationTool
installNeovim installNeovim
checkNeovimVer
cloneNeovim cloneNeovim
backupNeovimConfig backupNeovimConfig
linkNeovimConfig linkNeovimConfig

View File

@ -21,7 +21,7 @@ installVsCode() {
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install code "$ESCALATION_TOOL" "$PACKAGER" --non-interactive install code
;; ;;
pacman) pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm code "$AUR_HELPER" -S --needed --noconfirm visual-studio-code-bin
;; ;;
dnf) dnf)
"$ESCALATION_TOOL" rpm --import https://packages.microsoft.com/keys/microsoft.asc "$ESCALATION_TOOL" rpm --import https://packages.microsoft.com/keys/microsoft.asc
@ -40,4 +40,5 @@ installVsCode() {
checkEnv checkEnv
checkEscalationTool checkEscalationTool
checkAURHelper
installVsCode installVsCode

View File

@ -17,7 +17,6 @@ installVsCodium() {
printf "%b\n" "[gitlab.com_paulcarroty_vscodium_repo]\nname=gitlab.com_paulcarroty_vscodium_repo\nbaseurl=https://download.vscodium.com/rpms/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/-/raw/master/pub.gpg\nmetadata_expire=1h" | "$ESCALATION_TOOL" tee -a /etc/zypp/repos.d/vscodium.repo printf "%b\n" "[gitlab.com_paulcarroty_vscodium_repo]\nname=gitlab.com_paulcarroty_vscodium_repo\nbaseurl=https://download.vscodium.com/rpms/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/-/raw/master/pub.gpg\nmetadata_expire=1h" | "$ESCALATION_TOOL" tee -a /etc/zypp/repos.d/vscodium.repo
"$ESCALATION_TOOL" "$PACKAGER" refresh "$ESCALATION_TOOL" "$PACKAGER" refresh
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install codium "$ESCALATION_TOOL" "$PACKAGER" --non-interactive install codium
;; ;;
pacman) pacman)
"$AUR_HELPER" -S --noconfirm vscodium-bin "$AUR_HELPER" -S --noconfirm vscodium-bin

View File

@ -1,246 +0,0 @@
#!/bin/sh -e
. ../common-script.sh
install_chrome() {
if ! command_exists google-chrome; then
printf "%b\n" "${YELLOW}Installing Google Chrome..${RC}."
case "$PACKAGER" in
apt-get|nala)
curl -O https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
"$ESCALATION_TOOL" "$PACKAGER" install -y ./google-chrome-stable_current_amd64.deb
;;
zypper)
"$ESCALATION_TOOL" "$PACKAGER" addrepo http://dl.google.com/linux/chrome/rpm/stable/x86_64 Google-Chrome
"$ESCALATION_TOOL" "$PACKAGER" refresh
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install google-chrome-stable
;;
pacman)
"$AUR_HELPER" -S --noconfirm google-chrome
;;
dnf)
"$ESCALATION_TOOL" "$PACKAGER" install -y fedora-workstation-repositories
"$ESCALATION_TOOL" "$PACKAGER" config-manager --set-enabled google-chrome
"$ESCALATION_TOOL" "$PACKAGER" install -y google-chrome-stable
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
;;
esac
else
printf "%b\n" "${GREEN}Google Chrome Browser is already installed.${RC}"
fi
}
install_thorium() {
if ! command_exists thorium-browser; then
printf "%b\n" "${YELLOW}Installing Thorium Browser...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" rm -fv /etc/apt/sources.list.d/thorium.list
"$ESCALATION_TOOL" curl http://dl.thorium.rocks/debian/dists/stable/thorium.list -o /etc/apt/sources.list.d/thorium.list
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" install -y thorium-browser
;;
zypper|dnf)
url=$(curl -s https://api.github.com/repos/Alex313031/Thorium/releases/latest | grep -oP '(?<=browser_download_url": ")[^"]*\.rpm')
echo "$url" && curl -L "$url" -o thorium-latest.rpm
"$ESCALATION_TOOL" "$PACKAGER" install -y thorium-latest.rpm && rm thorium-latest.rpm
;;
pacman)
"$AUR_HELPER" -S --needed --noconfirm thorium-browser-bin
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
;;
esac
else
printf "%b\n" "${GREEN}Thorium Browser is already installed.${RC}"
fi
}
install_firefox() {
if ! command_exists firefox; then
printf "%b\n" "${YELLOW}Installing Mozilla Firefox...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y firefox-esr
;;
zypper)
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install MozillaFirefox
;;
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --noconfirm firefox
;;
dnf)
"$ESCALATION_TOOL" "$PACKAGER" install -y firefox
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Firefox Browser is already installed.${RC}"
fi
}
install_librewolf() {
if ! command_exists librewolf; then
printf "%b\n" "${YELLOW}Installing Librewolf...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y gnupg lsb-release apt-transport-https ca-certificates
distro=`if echo " una bookworm vanessa focal jammy bullseye vera uma " | grep -q " $(lsb_release -sc) "; then lsb_release -sc; else echo focal; fi`
curl -fsSL https://deb.librewolf.net/keyring.gpg | "$ESCALATION_TOOL" gpg --dearmor -o /usr/share/keyrings/librewolf.gpg
echo "Types: deb
URIs: https://deb.librewolf.net
Suites: $distro
Components: main
Architectures: amd64
Signed-By: /usr/share/keyrings/librewolf.gpg" | "$ESCALATION_TOOL" tee /etc/apt/sources.list.d/librewolf.sources > /dev/null
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" install -y librewolf
;;
dnf)
curl -fsSL https://rpm.librewolf.net/librewolf-repo.repo | pkexec tee /etc/yum.repos.d/librewolf.repo > /dev/null
"$ESCALATION_TOOL" "$PACKAGER" install -y librewolf
;;
zypper)
"$ESCALATION_TOOL" rpm --import https://rpm.librewolf.net/pubkey.gpg
"$ESCALATION_TOOL" zypper ar -ef https://rpm.librewolf.net librewolf
"$ESCALATION_TOOL" zypper refresh
"$ESCALATION_TOOL" zypper --non-interactive install librewolf
;;
pacman)
"$AUR_HELPER" -S --needed --noconfirm librewolf-bin
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}LibreWolf Browser is already installed.${RC}"
fi
}
install_brave() {
if ! command_exists brave; then
printf "%b\n" "${YELLOW}Installing Brave...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y curl
"$ESCALATION_TOOL" curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main"| "$ESCALATION_TOOL" tee /etc/apt/sources.list.d/brave-browser-release.list
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" install -y brave-browser
;;
zypper)
"$ESCALATION_TOOL" "$PACKAGER" install -y curl
"$ESCALATION_TOOL" rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc
"$ESCALATION_TOOL" "$PACKAGER" addrepo https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo
"$ESCALATION_TOOL" "$PACKAGER" refresh
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install brave-browser
;;
pacman)
"$AUR_HELPER" -S --noconfirm brave-bin
;;
dnf)
"$ESCALATION_TOOL" "$PACKAGER" install -y dnf-plugins-core
"$ESCALATION_TOOL" "$PACKAGER" config-manager --add-repo https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo
"$ESCALATION_TOOL" rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc
"$ESCALATION_TOOL" "$PACKAGER" install -y brave-browser
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Brave Browser is already installed.${RC}"
fi
}
install_vivaldi() {
if ! command_exists vivaldi; then
printf "%b\n" "${YELLOW}Installing Vivaldi...${RC}"
curl -fsSL https://downloads.vivaldi.com/snapshot/install-vivaldi.sh | sh
if [ $? -eq 0 ]; then
printf "%b\n" "${GREEN}Vivaldi installed successfully!${RC}"
else
printf "%b\n" "${RED}Vivaldi installation failed!${RC}"
fi
else
printf "%b\n" "${GREEN}Vivaldi Browser is already installed.${RC}"
fi
}
install_chromium() {
if ! command_exists chromium; then
printf "%b\n" "${YELLOW}Installing Chromium...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --noconfirm chromium
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y chromium
;;
esac
else
printf "%b\n" "${GREEN}Chromium Browser is already installed.${RC}"
fi
}
install_lynx() {
if ! command_exists lynx; then
printf "%b\n" "${YELLOW}Installing Lynx...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --noconfirm lynx
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y lynx
;;
esac
else
printf "%b\n" "${GREEN}Lynx TUI Browser is already installed.${RC}"
fi
}
browserSetup() {
clear
printf "%b\n" "Browser Installation Script"
printf "%b\n" "----------------------------"
printf "%b\n" "Select the browsers you want to install:"
printf "%b\n" "1. Google Chrome"
printf "%b\n" "2. Mozilla Firefox"
printf "%b\n" "3. Librewolf"
printf "%b\n" "4. Brave"
printf "%b\n" "5. Vivaldi"
printf "%b\n" "6. Chromium"
printf "%b\n" "7. Thorium"
printf "%b\n" "8. Lynx"
printf "%b\n" "----------------------------"
printf "%b" "Enter your choices (e.g. 1 3 5): "
read -r choice
for ch in $choice; do
case $ch in
1) install_chrome ;;
2) install_firefox ;;
3) install_librewolf ;;
4) install_brave ;;
5) install_vivaldi ;;
6) install_chromium ;;
7) install_thorium ;;
8) install_lynx;;
*) printf "%b\n" "${RED}Invalid option: $ch ${RC}" ;;
esac
done
printf "%b\n" "${GREEN}Installation complete!${RC}"
}
checkEnv
checkEscalationTool
checkAURHelper
browserSetup

View File

@ -0,0 +1,45 @@
#!/bin/sh -e
. ../../common-script.sh
installBrave() {
if ! command_exists brave; then
printf "%b\n" "${YELLOW}Installing Brave...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y curl
"$ESCALATION_TOOL" curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main" | "$ESCALATION_TOOL" tee /etc/apt/sources.list.d/brave-browser-release.list
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" install -y brave-browser
;;
zypper)
"$ESCALATION_TOOL" "$PACKAGER" install -y curl
"$ESCALATION_TOOL" rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc
"$ESCALATION_TOOL" "$PACKAGER" addrepo https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo
"$ESCALATION_TOOL" "$PACKAGER" refresh
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install brave-browser
;;
pacman)
"$AUR_HELPER" -S --needed --noconfirm brave-bin
;;
dnf)
"$ESCALATION_TOOL" "$PACKAGER" install -y dnf-plugins-core
"$ESCALATION_TOOL" "$PACKAGER" config-manager --add-repo https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo
"$ESCALATION_TOOL" rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc
"$ESCALATION_TOOL" "$PACKAGER" install -y brave-browser
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Brave Browser is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
checkAURHelper
installBrave

View File

@ -0,0 +1,23 @@
#!/bin/sh -e
. ../../common-script.sh
installChromium() {
if ! command_exists chromium; then
printf "%b\n" "${YELLOW}Installing Chromium...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm chromium
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y chromium
;;
esac
else
printf "%b\n" "${GREEN}Chromium Browser is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
installChromium

View File

@ -0,0 +1,33 @@
#!/bin/sh -e
. ../../common-script.sh
installFirefox() {
if ! command_exists firefox; then
printf "%b\n" "${YELLOW}Installing Mozilla Firefox...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y firefox-esr
;;
zypper)
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install MozillaFirefox
;;
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm firefox
;;
dnf)
"$ESCALATION_TOOL" "$PACKAGER" install -y firefox
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Firefox Browser is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
installFirefox

View File

@ -0,0 +1,39 @@
#!/bin/sh -e
. ../../common-script.sh
installChrome() {
if ! command_exists google-chrome; then
printf "%b\n" "${YELLOW}Installing Google Chrome...${RC}"
case "$PACKAGER" in
apt-get|nala)
curl -O https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
"$ESCALATION_TOOL" "$PACKAGER" install -y ./google-chrome-stable_current_amd64.deb
;;
zypper)
"$ESCALATION_TOOL" "$PACKAGER" addrepo http://dl.google.com/linux/chrome/rpm/stable/x86_64 Google-Chrome
"$ESCALATION_TOOL" "$PACKAGER" refresh
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install google-chrome-stable
;;
pacman)
"$AUR_HELPER" -S --needed --noconfirm google-chrome
;;
dnf)
"$ESCALATION_TOOL" "$PACKAGER" install -y fedora-workstation-repositories
"$ESCALATION_TOOL" "$PACKAGER" config-manager --set-enabled google-chrome
"$ESCALATION_TOOL" "$PACKAGER" install -y google-chrome-stable
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Google Chrome Browser is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
checkAURHelper
installChrome

View File

@ -0,0 +1,48 @@
#!/bin/sh -e
. ../../common-script.sh
installLibreWolf() {
if ! command_exists librewolf; then
printf "%b\n" "${YELLOW}Installing Librewolf...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y gnupg lsb-release apt-transport-https ca-certificates
distro=`if echo " una bookworm vanessa focal jammy bullseye vera uma " | grep -q " $(lsb_release -sc) "; then lsb_release -sc; else echo focal; fi`
curl -fsSL https://deb.librewolf.net/keyring.gpg | "$ESCALATION_TOOL" gpg --dearmor -o /usr/share/keyrings/librewolf.gpg
echo "Types: deb
URIs: https://deb.librewolf.net
Suites: $distro
Components: main
Architectures: amd64
Signed-By: /usr/share/keyrings/librewolf.gpg" | "$ESCALATION_TOOL" tee /etc/apt/sources.list.d/librewolf.sources > /dev/null
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" install -y librewolf
;;
dnf)
curl -fsSL https://rpm.librewolf.net/librewolf-repo.repo | pkexec tee /etc/yum.repos.d/librewolf.repo > /dev/null
"$ESCALATION_TOOL" "$PACKAGER" install -y librewolf
;;
zypper)
"$ESCALATION_TOOL" rpm --import https://rpm.librewolf.net/pubkey.gpg
"$ESCALATION_TOOL" zypper ar -ef https://rpm.librewolf.net librewolf
"$ESCALATION_TOOL" zypper refresh
"$ESCALATION_TOOL" zypper --non-interactive install librewolf
;;
pacman)
"$AUR_HELPER" -S --needed --noconfirm librewolf-bin
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}LibreWolf Browser is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
checkAURHelper
installLibreWolf

View File

@ -0,0 +1,23 @@
#!/bin/sh -e
. ../../common-script.sh
installLynx() {
if ! command_exists lynx; then
printf "%b\n" "${YELLOW}Installing Lynx...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm lynx
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y lynx
;;
esac
else
printf "%b\n" "${GREEN}Lynx TUI Browser is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
installLynx

View File

@ -0,0 +1,35 @@
#!/bin/sh -e
. ../../common-script.sh
installThrorium() {
if ! command_exists thorium-browser; then
printf "%b\n" "${YELLOW}Installing Thorium Browser...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" rm -fv /etc/apt/sources.list.d/thorium.list
"$ESCALATION_TOOL" curl http://dl.thorium.rocks/debian/dists/stable/thorium.list -o /etc/apt/sources.list.d/thorium.list
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" install -y thorium-browser
;;
zypper|dnf)
url=$(curl -s https://api.github.com/repos/Alex313031/Thorium/releases/latest | grep -oP '(?<=browser_download_url": ")[^"]*\.rpm')
echo "$url" && curl -L "$url" -o thorium-latest.rpm
"$ESCALATION_TOOL" "$PACKAGER" install -y thorium-latest.rpm && rm thorium-latest.rpm
;;
pacman)
"$AUR_HELPER" -S --needed --noconfirm thorium-browser-bin
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
;;
esac
else
printf "%b\n" "${GREEN}Thorium Browser is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
checkAURHelper
installThrorium

View File

@ -0,0 +1,23 @@
#!/bin/sh -e
. ../../common-script.sh
installLynx() {
if ! command_exists lynx; then
printf "%b\n" "${YELLOW}Installing Lynx...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm lynx
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y lynx
;;
esac
else
printf "%b\n" "${GREEN}Lynx TUI Browser is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
installLynx

View File

@ -10,7 +10,7 @@ installSlack() {
"$AUR_HELPER" -S --needed --noconfirm slack-desktop "$AUR_HELPER" -S --needed --noconfirm slack-desktop
;; ;;
*) *)
. ./setup-flatpak.sh . ../setup-flatpak.sh
flatpak install -y flathub com.slack.Slack flatpak install -y flathub com.slack.Slack
;; ;;
esac esac

View File

@ -10,7 +10,7 @@ installZoom() {
"$AUR_HELPER" -S --needed --noconfirm zoom "$AUR_HELPER" -S --needed --noconfirm zoom
;; ;;
*) *)
. ./setup-flatpak.sh . ../setup-flatpak.sh
flatpak install -y flathub us.zoom.Zoom flatpak install -y flathub us.zoom.Zoom
;; ;;
esac esac

View File

@ -6,14 +6,14 @@ setupDWM() {
printf "%b\n" "${YELLOW}Installing DWM-Titus...${RC}" printf "%b\n" "${YELLOW}Installing DWM-Titus...${RC}"
case "$PACKAGER" in # Install pre-Requisites case "$PACKAGER" in # Install pre-Requisites
pacman) pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm base-devel libx11 libxinerama libxft imlib2 libxcb git unzip flameshot lxappearance feh "$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm base-devel libx11 libxinerama libxft imlib2 libxcb git unzip flameshot lxappearance feh mate-polkit
;; ;;
apt-get|nala) apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y build-essential libx11-dev libxinerama-dev libxft-dev libimlib2-dev libx11-xcb-dev libfontconfig1 libx11-6 libxft2 libxinerama1 libxcb-res0-dev git unzip flameshot lxappearance feh "$ESCALATION_TOOL" "$PACKAGER" install -y build-essential libx11-dev libxinerama-dev libxft-dev libimlib2-dev libx11-xcb-dev libfontconfig1 libx11-6 libxft2 libxinerama1 libxcb-res0-dev git unzip flameshot lxappearance feh mate-polkit
;; ;;
dnf) dnf)
"$ESCALATION_TOOL" "$PACKAGER" groupinstall -y "Development Tools" "$ESCALATION_TOOL" "$PACKAGER" groupinstall -y "Development Tools"
"$ESCALATION_TOOL" "$PACKAGER" install -y libX11-devel libXinerama-devel libXft-devel imlib2-devel libxcb-devel unzip flameshot lxappearance feh # no need to include git here as it should be already installed via "Development Tools" "$ESCALATION_TOOL" "$PACKAGER" install -y libX11-devel libXinerama-devel libXft-devel imlib2-devel libxcb-devel unzip flameshot lxappearance feh mate-polkit # no need to include git here as it should be already installed via "Development Tools"
;; ;;
*) *)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}" printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
@ -66,6 +66,10 @@ install_nerd_font() {
# Unzip the font file if it hasn't been unzipped yet # Unzip the font file if it hasn't been unzipped yet
if [ ! -d "$FONT_DIR/Meslo" ]; then if [ ! -d "$FONT_DIR/Meslo" ]; then
mkdir -p "$FONT_DIR/Meslo" || {
printf "%b\n" "${RED}Failed to create directory: $FONT_DIR/Meslo${RC}"
return 1
}
unzip "$FONT_ZIP" -d "$FONT_DIR" || { unzip "$FONT_ZIP" -d "$FONT_DIR" || {
printf "%b\n" "${RED}Failed to unzip $FONT_ZIP${RC}" printf "%b\n" "${RED}Failed to unzip $FONT_ZIP${RC}"
return 1 return 1
@ -203,8 +207,14 @@ setupDisplayManager() {
done done
printf "%b\n" "${GREEN}Current display manager: $currentdm${RC}" printf "%b\n" "${GREEN}Current display manager: $currentdm${RC}"
if [ "$currentdm" = "none" ]; then if [ "$currentdm" = "none" ]; then
DM="sddm" printf "%b\n" "${YELLOW}--------------------------${RC}"
printf "%b\n" "${YELLOW}No display manager found, installing $DM${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 DM
case "$PACKAGER" in case "$PACKAGER" in
pacman) pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm "$DM" "$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm "$DM"
@ -222,47 +232,7 @@ setupDisplayManager() {
esac esac
printf "%b\n" "${GREEN}$DM installed successfully${RC}" printf "%b\n" "${GREEN}$DM installed successfully${RC}"
systemctl enable "$DM" systemctl enable "$DM"
# Prompt user for auto-login
# Using printf instead of echo -n as It's more posix-compliant.
printf "Do you want to enable auto-login? (Y/n) "
read -r answer
case "$answer" in
[Yy]*)
printf "%b\n" "${YELLOW}Configuring SDDM for autologin${RC}"
SDDM_CONF="/etc/sddm.conf"
if [ ! -f "$SDDM_CONF" ]; then
echo "[Autologin]" | "$ESCALATION_TOOL" tee -a "$SDDM_CONF"
echo "User=$USER" | "$ESCALATION_TOOL" tee -a "$SDDM_CONF"
echo "Session=dwm" | "$ESCALATION_TOOL" tee -a "$SDDM_CONF"
else
"$ESCALATION_TOOL" sed -i '/^\[Autologin\]/d' "$SDDM_CONF"
"$ESCALATION_TOOL" sed -i '/^User=/d' "$SDDM_CONF"
"$ESCALATION_TOOL" sed -i '/^Session=/d' "$SDDM_CONF"
echo "[Autologin]" | "$ESCALATION_TOOL" tee -a "$SDDM_CONF"
echo "User=$USER" | "$ESCALATION_TOOL" tee -a "$SDDM_CONF"
echo "Session=dwm" | "$ESCALATION_TOOL" tee -a "$SDDM_CONF"
fi
printf "%b\n" "{YELLOW}Checking if autologin group exists${RC}"
if ! getent group autologin > /dev/null; then
printf "%b\n" "${YELLOW}Creating autologin group${RC}"
"$ESCALATION_TOOL" groupadd autologin
else
printf "%b\n" "${GREEN}Autologin group already exists${RC}"
fi
printf "%b\n" "${YELLOW}Adding user with UID 1000 to autologin group${RC}"
USER_UID_1000=$(getent passwd 1000 | cut -d: -f1)
if [ -n "$USER_UID_1000" ]; then
"$ESCALATION_TOOL" usermod -aG autologin "$USER_UID_1000"
printf "%b\n" "${GREEN}User $USER_UID_1000 added to autologin group${RC}"
else
printf "%b\n" "${RED}No user with UID 1000 found - Auto login not possible${RC}"
fi
;;
*)
printf "%b\n" "${GREEN}Auto-login configuration skipped${RC}"
;;
esac
fi fi
} }
@ -291,5 +261,6 @@ setupDWM
makeDWM makeDWM
install_slstatus install_slstatus
install_nerd_font install_nerd_font
picom_animations
clone_config_folders clone_config_folders
configure_backgrounds configure_backgrounds

View File

@ -15,9 +15,9 @@ installLinutil() {
printf "%b" "Enter your choice: " printf "%b" "Enter your choice: "
read -r choice read -r choice
case $choice in case $choice in
1) "$AUR_HELPER" -S --noconfirm linutil ;; 1) "$AUR_HELPER" -S --needed --noconfirm linutil ;;
2) "$AUR_HELPER" -S --noconfirm linutil-bin ;; 2) "$AUR_HELPER" -S --needed --noconfirm linutil-bin ;;
3) "$AUR_HELPER" -S --noconfirm linutil-git ;; 3) "$AUR_HELPER" -S --needed --noconfirm linutil-git ;;
*) *)
printf "%b\n" "${RED}Invalid choice:${RC} $choice" printf "%b\n" "${RED}Invalid choice:${RC} $choice"
exit 1 exit 1
@ -37,13 +37,17 @@ installLinutil() {
pacman) pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm rustup "$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm rustup
;; ;;
dnf)
"$ESCALATION_TOOL" "$PACKAGER" install -y rustup
;;
zypper) zypper)
"$ESCALATION_TOOL" "$PACKAGER" install -n curl gcc make "$ESCALATION_TOOL" "$PACKAGER" install -n curl gcc make
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. $HOME/.cargo/env . $HOME/.cargo/env
;; ;;
*) *)
"$ESCALATION_TOOL" "$PACKAGER" install -y rustup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. $HOME/.cargo/env
;; ;;
esac esac
fi fi
@ -59,4 +63,4 @@ installLinutil() {
checkEnv checkEnv
checkEscalationTool checkEscalationTool
checkAURHelper checkAURHelper
installLinutil installLinutil

View File

@ -1,203 +0,0 @@
#!/bin/sh -e
. ../common-script.sh
install_onlyoffice() {
if ! command_exists onlyoffice-desktopeditors; then
printf "%b\n" "${YELLOW}Installing Only Office..${RC}."
case "$PACKAGER" in
apt-get|nala)
curl -O https://download.onlyoffice.com/install/desktop/editors/linux/onlyoffice-desktopeditors_amd64.deb
"$ESCALATION_TOOL" "$PACKAGER" install -y ./onlyoffice-desktopeditors_amd64.deb
;;
zypper|dnf)
. ./setup-flatpak.sh
flatpak install -y flathub org.onlyoffice.desktopeditors
;;
pacman)
"$AUR_HELPER" -S --needed --noconfirm onlyoffice
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Only Office is already installed.${RC}"
fi
}
install_libreoffice() {
if ! command_exists libreoffice; then
printf "%b\n" "${YELLOW}Installing Libre Office...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y libreoffice-core
;;
zypper|dnf)
. ./setup-flatpak.sh
flatpak install -y flathub org.libreoffice.LibreOffice
;;
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm libreoffice-fresh
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Libre Office is already installed.${RC}"
fi
}
install_wpsoffice() {
if ! command_exists com.wps.Office; then
printf "%b\n" "${YELLOW}Installing WPS Office...${RC}"
case "$PACKAGER" in
pacman)
"$AUR_HELPER" -S --noconfirm wps-office
;;
*)
. ./setup-flatpak.sh
flatpak install flathub com.wps.Office
;;
esac
else
printf "%b\n" "${GREEN}WPS Office is already installed.${RC}"
fi
}
# needs to be updated every year for latest version
install_freeoffice() {
if ! command_exists softmaker-freeoffice-2024 freeoffice softmaker; then
printf "%b\n" "${YELLOW}Installing Free Office...${RC}"
case "$PACKAGER" in
apt-get|nala)
curl -O https://www.softmaker.net/down/softmaker-freeoffice-2024_1218-01_amd64.deb
"$ESCALATION_TOOL" "$PACKAGER" install -y ./softmaker-freeoffice-2024_1218-01_amd64.deb
;;
zypper)
"$ESCALATION_TOOL" "$PACKAGER" addrepo -f https://shop.softmaker.com/repo/rpm SoftMaker
"$ESCALATION_TOOL" "$PACKAGER" --gpg-auto-import-keys refresh
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install softmaker-freeoffice-2024
;;
pacman)
"$AUR_HELPER" -S --noconfirm freeoffice
;;
dnf)
"$ESCALATION_TOOL" curl -O -qO /etc/yum.repos.d/softmaker.repo https://shop.softmaker.com/repo/softmaker.repo
"$ESCALATION_TOOL" "$PACKAGER" install -y softmaker-freeoffice-2024
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Free Office is already installed.${RC}"
fi
}
install_evince() {
if ! command_exists evince; then
printf "%b\n" "${YELLOW}Installing Evince...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --noconfirm evince
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y evince
;;
esac
else
printf "%b\n" "${GREEN}Evince is already installed.${RC}"
fi
}
install_okular() {
if ! command_exists okular; then
printf "%b\n" "${YELLOW}Installing Okular...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --noconfirm okular
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y okular
;;
esac
else
printf "%b\n" "${GREEN}Okular is already installed.${RC}"
fi
}
install_pdfstudioviewer() {
if ! command_exists pdfstudioviewer2024/pdfstudioviewer2024; then
printf "%b\n" "${YELLOW}Installing PDF Studio Viewer...${RC}"
curl -O https://download.qoppa.com/pdfstudioviewer/PDFStudioViewer_linux64.sh
"$ESCALATION_TOOL" chmod +x PDFStudioViewer_linux64.sh
if sh PDFStudioViewer_linux64.sh; then
printf "%b\n" "${GREEN}PDF Studio Viewer installed successfully!${RC}"
else
printf "%b\n" "${RED}Installation failed!${RC}"
fi
rm PDFStudioViewer_linux64.sh
else
printf "%b\n" "${GREEN}PDF Studio Viewer is already installed.${RC}"
fi
}
install_pdfstudio() {
if ! command_exists pdfstudio2024/pdfstudio2024; then
printf "%b\n" "${YELLOW}Installing PDF Studio...${RC}"
curl -O https://download.qoppa.com/pdfstudio/PDFStudio_linux64.sh
"$ESCALATION_TOOL" chmod +x PDFStudio_linux64.sh
if sh PDFStudio_linux64.sh; then
printf "%b\n" "${GREEN}PDF Studio installed successfully!${RC}"
else
printf "%b\n" "${RED}PDF Studio installation failed!${RC}"
fi
rm PDFStudio_linux64.sh
else
printf "%b\n" "${GREEN}PDF Studio is already installed.${RC}"
fi
}
officeSuiteSetup() {
clear
printf "%b\n" "Office Suite Setup Script"
printf "%b\n" "----------------------------"
printf "%b\n" "Select the suite you want to install:"
printf "%b\n" "1. OnlyOffice"
printf "%b\n" "2. LibreOffice"
printf "%b\n" "3. WPS Office"
printf "%b\n" "4. Free Office"
printf "%b\n" "Select the PDF Suite you want to install:"
printf "%b\n" "----------------------------"
printf "%b\n" "5. Evince"
printf "%b\n" "6. Okular"
printf "%b\n" "7. PDF Studio Viewer"
printf "%b\n" "8. PDF Studio (Paid Software)"
printf "%b\n" "----------------------------"
printf "%b" "Enter your choices (e.g., 1 3 5): "
read -r choice
for ch in $choice; do
case $ch in
1) install_onlyoffice ;;
2) install_libreoffice ;;
3) install_wpsoffice ;;
4) install_freeoffice ;;
5) install_evince ;;
6) install_okular ;;
7) install_pdfstudioviewer ;;
8) install_pdfstudio ;;
*) printf "%b\n" "${RED}Invalid option: $ch ${RC}" ;;
esac
done
printf "%b\n" "${GREEN}Installation complete!${RC}"
}
checkEnv
checkEscalationTool
checkAURHelper
officeSuiteSetup

View File

@ -0,0 +1,38 @@
#!/bin/sh -e
. ../common-script.sh
installFreeOffice() {
if ! command_exists softmaker-freeoffice-2024 freeoffice softmaker; then
printf "%b\n" "${YELLOW}Installing Free Office...${RC}"
case "$PACKAGER" in
apt-get|nala)
curl -O https://www.softmaker.net/down/softmaker-freeoffice-2024_1218-01_amd64.deb
"$ESCALATION_TOOL" "$PACKAGER" install -y ./softmaker-freeoffice-2024_1218-01_amd64.deb
;;
zypper)
"$ESCALATION_TOOL" "$PACKAGER" addrepo -f https://shop.softmaker.com/repo/rpm SoftMaker
"$ESCALATION_TOOL" "$PACKAGER" --gpg-auto-import-keys refresh
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install softmaker-freeoffice-2024
;;
pacman)
"$AUR_HELPER" -S --needed --noconfirm freeoffice
;;
dnf)
"$ESCALATION_TOOL" curl -O -qO /etc/yum.repos.d/softmaker.repo https://shop.softmaker.com/repo/softmaker.repo
"$ESCALATION_TOOL" "$PACKAGER" install -y softmaker-freeoffice-2024
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Free Office is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
checkAurHelper
installFreeOffice

View File

@ -0,0 +1,31 @@
#!/bin/sh -e
. ../common-script.sh
installLibreOffice() {
if ! command_exists libreoffice; then
printf "%b\n" "${YELLOW}Installing Libre Office...${RC}"
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y libreoffice-core
;;
zypper|dnf)
. ./setup-flatpak.sh
flatpak install -y flathub org.libreoffice.LibreOffice
;;
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm libreoffice-fresh
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Libre Office is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
installLibreOffice

View File

@ -0,0 +1,33 @@
#!/bin/sh -e
. ../common-script.sh
installOnlyOffice() {
if ! command_exists onlyoffice-desktopeditors; then
printf "%b\n" "${YELLOW}Installing Only Office..${RC}."
case "$PACKAGER" in
apt-get|nala)
curl -O https://download.onlyoffice.com/install/desktop/editors/linux/onlyoffice-desktopeditors_amd64.deb
"$ESCALATION_TOOL" "$PACKAGER" install -y ./onlyoffice-desktopeditors_amd64.deb
;;
zypper|dnf)
. ./setup-flatpak.sh
flatpak install -y flathub org.onlyoffice.desktopeditors
;;
pacman)
"$AUR_HELPER" -S --needed --noconfirm onlyoffice
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Only Office is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
checkAurHelper
installOnlyOffice

View File

@ -0,0 +1,25 @@
#!/bin/sh -e
. ../common-script.sh
installWpsOffice() {
if ! command_exists com.wps.Office; then
printf "%b\n" "${YELLOW}Installing WPS Office...${RC}"
case "$PACKAGER" in
pacman)
"$AUR_HELPER" -S --needed --noconfirm wps-office
;;
*)
. ./setup-flatpak.sh
flatpak install flathub com.wps.Office
;;
esac
else
printf "%b\n" "${GREEN}WPS Office is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
checkAurHelper
installWpsOffice

View File

@ -0,0 +1,23 @@
#!/bin/sh -e
. ../common-script.sh
installEvince() {
if ! command_exists evince; then
printf "%b\n" "${YELLOW}Installing Evince...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm evince
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y evince
;;
esac
else
printf "%b\n" "${GREEN}Evince is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
installEvince

View File

@ -0,0 +1,23 @@
#!/bin/sh -e
. ../common-script.sh
installOkular() {
if ! command_exists okular; then
printf "%b\n" "${YELLOW}Installing Okular...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm okular
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y okular
;;
esac
else
printf "%b\n" "${GREEN}Okular is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
installOkular

View File

@ -0,0 +1,23 @@
#!/bin/sh -e
. ../common-script.sh
installPdfstudio() {
if ! command_exists pdfstudio2024; then
printf "%b\n" "${YELLOW}Installing PDF Studio...${RC}"
curl -O https://download.qoppa.com/pdfstudio/PDFStudio_linux64.sh
"$ESCALATION_TOOL" chmod +x PDFStudio_linux64.sh
if sh PDFStudio_linux64.sh; then
printf "%b\n" "${GREEN}PDF Studio installed successfully!${RC}"
else
printf "%b\n" "${RED}PDF Studio installation failed!${RC}"
fi
rm PDFStudio_linux64.sh
else
printf "%b\n" "${GREEN}PDF Studio is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
installPdfstudio

View File

@ -0,0 +1,23 @@
#!/bin/sh -e
. ../common-script.sh
installPdfstudioviewer() {
if ! command_exists pdfstudioviewer2024; then
printf "%b\n" "${YELLOW}Installing PDF Studio Viewer...${RC}"
curl -O https://download.qoppa.com/pdfstudioviewer/PDFStudioViewer_linux64.sh
"$ESCALATION_TOOL" chmod +x PDFStudioViewer_linux64.sh
if sh PDFStudioViewer_linux64.sh; then
printf "%b\n" "${GREEN}PDF Studio Viewer installed successfully!${RC}"
else
printf "%b\n" "${RED}Installation failed!${RC}"
fi
rm PDFStudioViewer_linux64.sh
else
printf "%b\n" "${GREEN}PDF Studio Viewer is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
installPdfstudioviewer

View File

@ -37,7 +37,7 @@ task_list = "I"
name = "Meld" name = "Meld"
description = "Meld is a visual diff and merge tool that helps compare files, directories, and version-controlled projects." description = "Meld is a visual diff and merge tool that helps compare files, directories, and version-controlled projects."
script = "Developer-tools/meld-setup.sh" script = "Developer-tools/meld-setup.sh"
task_list = "I" task_list = "I FI"
[[data.entries]] [[data.entries]]
name = "Ngrok" name = "Ngrok"
@ -90,6 +90,102 @@ description = "Thunderbird is a free, open-source email client that offers power
script = "communication-apps/thunderbird-setup.sh" script = "communication-apps/thunderbird-setup.sh"
task_list = "I" task_list = "I"
[[data]]
name = "Office Suites"
[[data.entries]]
name = "LibreOffice"
script = "office-suites/libreoffice.sh"
task_list = "I"
[[data.entries]]
name = "OnlyOffice"
script = "office-suites/onlyoffice.sh"
task_list = "I"
[[data.entries]]
name = "FreeOffice"
script = "office-suites/freeoffice.sh"
task_list = "I"
[[data.entries]]
name = "WPS Office"
script = "office-suites/wpsoffice.sh"
task_list = "I"
[[data]]
name = "PDF Suites"
[[data.entries]]
name = "Evince"
script = "pdf-suites/evince.sh"
task_list = "I"
[[data.entries]]
name = "Okular"
script = "pdf-suites/okular.sh"
task_list = "I"
[[data.entries]]
name = "PDF Studio"
script = "pdf-suites/pdfstudio.sh"
task_list = "I"
[[data.entries]]
name = "PDF Studio Viewer"
script = "pdf-suites/pdfstudioviewer.sh"
[[data]]
name = "Web Browsers"
[[data.entries]]
name = "Brave"
description = "Brave is a free and open-source web browser developed by Brave Software, Inc. based on the Chromium web browser."
script = "browsers/brave.sh"
task_list = "I"
[[data.entries]]
name = "Chromium"
description = "Chromium is an open-source web browser project started by Google, to provide the source code for the proprietary Google Chrome browser."
script = "browsers/chromium.sh"
task_list = "I"
[[data.entries]]
name = "Google Chrome"
description = "Google Chrome is a fast, secure, and free web browser, built for the modern web."
script = "browsers/google-chrome.sh"
task_list = "I"
[[data.entries]]
name = "LibreWolf"
description = "LibreWolf is a fork of Firefox, focused on privacy, security, and freedom."
script = "browsers/librewolf.sh"
task_list = "I"
[[data.entries]]
name = "Lynx"
description = "Lynx is a highly configurable text-based web browser for use on cursor-addressable character cell terminals."
script = "browsers/lynx.sh"
task_list = "I"
[[data.entries]]
name = "Mozilla Firefox"
description = "Mozilla Firefox is a free and open-source web browser developed by the Mozilla Foundation."
script = "browsers/firefox.sh"
task_list = "I"
[[data.entries]]
name = "Thorium"
description = "Thorium is a Chromium-based browser focused on privacy and performance."
script = "browsers/thorium.sh"
task_list = "I"
[[data.entries]]
name = "Vivaldi"
description = "Vivaldi is a freeware, cross-platform web browser developed by Vivaldi Technologies."
script = "browsers/vivaldi.sh"
task_list = "I"
[[data]] [[data]]
name = "Alacritty" name = "Alacritty"
description = "Alacritty is a modern terminal emulator that comes with sensible defaults, but allows for extensive configuration. By integrating with other applications, rather than reimplementing their functionality, it manages to provide a flexible set of features with high performance. The supported platforms currently consist of BSD, Linux, macOS and Windows.\nThis command installs and condifures alacritty terminal emulator." description = "Alacritty is a modern terminal emulator that comes with sensible defaults, but allows for extensive configuration. By integrating with other applications, rather than reimplementing their functionality, it manages to provide a flexible set of features with high performance. The supported platforms currently consist of BSD, Linux, macOS and Windows.\nThis command installs and condifures alacritty terminal emulator."
@ -110,15 +206,10 @@ task_list = "I FM"
[[data]] [[data]]
name = "Bottles" name = "Bottles"
description = "Bottles allows Windows software, like applications and games, to run on Linux.\nBottles also provides tools to categorize, organize and optimize your applications."
script = "bottles-setup.sh" script = "bottles-setup.sh"
task_list = "FI" task_list = "FI"
[[data]]
name = "Web Browsers"
description = "An interactive script to install popular browsers."
script = "browser-setup.sh"
task_list = "I"
[[data]] [[data]]
name = "DWM-Titus" name = "DWM-Titus"
description = "DWM is a dynamic window manager for X.\nIt manages windows in tiled, monocle and floating layouts.\nAll of the layouts can be applied dynamically, optimising the environment for the application in use and the task performed.\nThis command installs and configures DWM and a desktop manager.\nThe list of patches applied can be found in CTT's DWM repository\nhttps://github.com/ChrisTitusTech/dwm-titus" description = "DWM is a dynamic window manager for X.\nIt manages windows in tiled, monocle and floating layouts.\nAll of the layouts can be applied dynamically, optimising the environment for the application in use and the task performed.\nThis command installs and configures DWM and a desktop manager.\nThe list of patches applied can be found in CTT's DWM repository\nhttps://github.com/ChrisTitusTech/dwm-titus"
@ -127,6 +218,7 @@ task_list = "I PFM SS"
[[data]] [[data]]
name = "Docker" name = "Docker"
description = "Docker is an open platform that uses OS-level virtualization to deliver software in packages called containers."
script = "docker-setup.sh" script = "docker-setup.sh"
task_list = "I SS" task_list = "I SS"
@ -144,6 +236,7 @@ task_list = "I"
[[data]] [[data]]
name = "Grub Theme" name = "Grub Theme"
description = "Installs ChrisTitusTech's Top 5 Bootloader Themes script to allow for easy customization of GRUB."
script = "grub-theme.sh" script = "grub-theme.sh"
task_list = "PFM" task_list = "PFM"
@ -175,12 +268,6 @@ matches = true
data = "command_exists" data = "command_exists"
values = [ "linutil" ] values = [ "linutil" ]
[[data]]
name = "Office Suite"
description = "An office suite installer is a software package that installs productivity tools such as word processing, spreadsheets, presentations, and pdf viewers for business and personal use."
script = "office-suite-setup.sh"
task_list = "I"
[[data]] [[data]]
name = "Rofi" name = "Rofi"
description = "Rofi is a window switcher, run dialog, ssh-launcher and dmenu replacement that started as a clone of simpleswitcher, written by Sean Pringle and later expanded by Dave Davenport.\nThis command installs and configures rofi with configuration from CTT's DWM repo.\nhttps://github.com/ChrisTitusTech/dwm-titus" description = "Rofi is a window switcher, run dialog, ssh-launcher and dmenu replacement that started as a clone of simpleswitcher, written by Sean Pringle and later expanded by Dave Davenport.\nThis command installs and configures rofi with configuration from CTT's DWM repo.\nhttps://github.com/ChrisTitusTech/dwm-titus"

View File

@ -27,7 +27,7 @@ checkAURHelper() {
done done
printf "%b\n" "${YELLOW}Installing yay as AUR helper...${RC}" printf "%b\n" "${YELLOW}Installing yay as AUR helper...${RC}"
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm base-devel "$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm base-devel git
cd /opt && "$ESCALATION_TOOL" git clone https://aur.archlinux.org/yay-bin.git && "$ESCALATION_TOOL" chown -R "$USER":"$USER" ./yay-bin cd /opt && "$ESCALATION_TOOL" git clone https://aur.archlinux.org/yay-bin.git && "$ESCALATION_TOOL" chown -R "$USER":"$USER" ./yay-bin
cd yay-bin && makepkg --noconfirm -si cd yay-bin && makepkg --noconfirm -si
@ -125,11 +125,11 @@ checkDistro() {
} }
checkEnv() { checkEnv() {
checkCommandRequirements 'curl groups sudo' checkEscalationTool
checkCommandRequirements "curl groups $ESCALATION_TOOL"
checkPackageManager 'nala apt-get dnf pacman zypper' checkPackageManager 'nala apt-get dnf pacman zypper'
checkCurrentDirectoryWritable checkCurrentDirectoryWritable
checkSuperUser checkSuperUser
checkDistro checkDistro
checkEscalationTool
checkAURHelper checkAURHelper
} }

View File

@ -7,7 +7,7 @@ installDepend() {
pacman) pacman)
if ! command_exists paru; then if ! command_exists paru; then
printf "%b\n" "${YELLOW}Installing paru as AUR helper...${RC}" printf "%b\n" "${YELLOW}Installing paru as AUR helper...${RC}"
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm base-devel "$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm base-devel git
cd /opt && "$ESCALATION_TOOL" git clone https://aur.archlinux.org/paru.git && "$ESCALATION_TOOL" chown -R "$USER": ./paru cd /opt && "$ESCALATION_TOOL" git clone https://aur.archlinux.org/paru.git && "$ESCALATION_TOOL" chown -R "$USER": ./paru
cd paru && makepkg --noconfirm -si cd paru && makepkg --noconfirm -si
printf "%b\n" "${GREEN}Paru installed${RC}" printf "%b\n" "${GREEN}Paru installed${RC}"

View File

@ -7,7 +7,7 @@ installDepend() {
pacman) pacman)
if ! command_exists yay; then if ! command_exists yay; then
printf "%b\n" "${YELLOW}Installing yay as AUR helper...${RC}" printf "%b\n" "${YELLOW}Installing yay as AUR helper...${RC}"
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm base-devel "$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm base-devel git
cd /opt && "$ESCALATION_TOOL" git clone https://aur.archlinux.org/yay-bin.git && "$ESCALATION_TOOL" chown -R "$USER": ./yay-bin cd /opt && "$ESCALATION_TOOL" git clone https://aur.archlinux.org/yay-bin.git && "$ESCALATION_TOOL" chown -R "$USER": ./yay-bin
cd yay-bin && makepkg --noconfirm -si cd yay-bin && makepkg --noconfirm -si
printf "%b\n" "${GREEN}Yay installed${RC}" printf "%b\n" "${GREEN}Yay installed${RC}"

View File

@ -9,7 +9,7 @@ installDepend() {
case "$PACKAGER" in case "$PACKAGER" in
pacman) pacman)
#Check for multilib #Check for multilib
if ! grep -q "^\s*$$multilib$$" /etc/pacman.conf; then if ! grep -q "^\s*\[multilib\]" /etc/pacman.conf; then
echo "[multilib]" | "$ESCALATION_TOOL" tee -a /etc/pacman.conf echo "[multilib]" | "$ESCALATION_TOOL" tee -a /etc/pacman.conf
echo "Include = /etc/pacman.d/mirrorlist" | "$ESCALATION_TOOL" tee -a /etc/pacman.conf echo "Include = /etc/pacman.d/mirrorlist" | "$ESCALATION_TOOL" tee -a /etc/pacman.conf
"$ESCALATION_TOOL" "$PACKAGER" -Syu "$ESCALATION_TOOL" "$PACKAGER" -Syu
@ -36,9 +36,15 @@ installDepend() {
"$ESCALATION_TOOL" "$PACKAGER" install -y "$DEPENDENCIES" "$DISTRO_DEPS" "$ESCALATION_TOOL" "$PACKAGER" install -y "$DEPENDENCIES" "$DISTRO_DEPS"
;; ;;
dnf) dnf)
"$ESCALATION_TOOL" "$PACKAGER" install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm -y if [ "$(rpm -E %fedora)" -le 41 ]; then
"$ESCALATION_TOOL" "$PACKAGER" config-manager --enable fedora-cisco-openh264 -y "$ESCALATION_TOOL" "$PACKAGER" install ffmpeg ffmpeg-libs -y
"$ESCALATION_TOOL" "$PACKAGER" install -y "$DEPENDENCIES" "$ESCALATION_TOOL" "$PACKAGER" install -y "$DEPENDENCIES"
else
printf "%b\n" "${CYAN}Fedora < 41 detected. Installing rpmfusion repos.${RC}"
"$ESCALATION_TOOL" "$PACKAGER" install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-"$(rpm -E %fedora)".noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-"$(rpm -E %fedora)".noarch.rpm -y
"$ESCALATION_TOOL" "$PACKAGER" config-manager --enable fedora-cisco-openh264 -y
"$ESCALATION_TOOL" "$PACKAGER" install -y "$DEPENDENCIES"
fi
;; ;;
zypper) zypper)
"$ESCALATION_TOOL" "$PACKAGER" -n install "$DEPENDENCIES" "$ESCALATION_TOOL" "$PACKAGER" -n install "$DEPENDENCIES"

View File

@ -59,7 +59,7 @@ updateSystem() {
printf "%b\n" "${GREEN}Updating system${RC}" printf "%b\n" "${GREEN}Updating system${RC}"
case "$PACKAGER" in case "$PACKAGER" in
apt-get|nala) apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" update -y "$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" upgrade -y "$ESCALATION_TOOL" "$PACKAGER" upgrade -y
;; ;;
dnf) dnf)

View File

@ -43,11 +43,13 @@ task_list = "PFM"
[[data.entries]] [[data.entries]]
name = "Multimedia Codecs" name = "Multimedia Codecs"
description = "This script is designed to install multimedia codecs, and to ensure RPM Fusion repositories are installed."
script = "fedora/multimedia-codecs.sh" script = "fedora/multimedia-codecs.sh"
task_list = "I" task_list = "I"
[[data.entries]] [[data.entries]]
name = "Nvidia Proprietary Drivers" name = "Nvidia Proprietary Drivers"
description = "This script is designed to download the proprietary NVIDIA drivers in Fedora."
script = "fedora/nvidia-proprietary-driver-setup.sh" script = "fedora/nvidia-proprietary-driver-setup.sh"
task_list = "I" task_list = "I"
@ -71,6 +73,7 @@ task_list = "I"
[[data]] [[data]]
name = "Full System Cleanup" name = "Full System Cleanup"
description = "This script is designed to remove unnecessary packages, clean old cache files, remove temporary files, and to empty the trash."
script = "system-cleanup.sh" script = "system-cleanup.sh"
task_list = "RP PFM" task_list = "RP PFM"
@ -97,3 +100,9 @@ name = "Remove Snaps"
description = "This script is designed to remove snap" description = "This script is designed to remove snap"
script = "remove-snaps.sh" script = "remove-snaps.sh"
task_list = "RP" task_list = "RP"
[[data]]
name = "TTY Fonts"
description = "This Script will set the default TTY font to Terminus size 32 Bold"
script = "terminus-tty.sh"
task_list = "I PFM"

View File

@ -0,0 +1,66 @@
#!/bin/sh -e
. ../common-script.sh
InstallTermiusFonts() {
if [ ! -f "/usr/share/kbd/consolefonts/ter-c18b.psf.gz" ] &&
[ ! -f "/usr/share/consolefonts/Uni3-TerminusBold18x10.psf.gz" ] &&
[ ! -f "/usr/lib/kbd/consolefonts/ter-p32n.psf.gz" ]; then
printf "%b\n" "${YELLOW}Installing Terminus Fonts...${RC}"
case "$PACKAGER" in
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm terminus-font
;;
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y fonts-terminus
;;
dnf)
"$ESCALATION_TOOL" "$PACKAGER" install -y terminus-fonts-console
;;
*)
printf "%b\n" "${RED}Unsupported package manager: ""$PACKAGER""${RC}"
exit 1
;;
esac
else
printf "%b\n" "${GREEN}Terminus Fonts is already installed.${RC}"
fi
}
SetTermiusFonts() {
case "$DTYPE" in
arch)
printf "%b\n" "${YELLOW}Updating FONT= line in /etc/vconsole.conf...${RC}"
"$ESCALATION_TOOL" sed -i 's/^FONT=.*/FONT=ter-v32b/' /etc/vconsole.conf
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
"$ESCALATION_TOOL" setfont -C /dev/tty1 ter-v32b
fi
printf "%b\n" "${GREEN}Terminus font set for TTY.${RC}"
;;
debian)
printf "%b\n" "${YELLOW}Updating console-setup configuration...${RC}"
"$ESCALATION_TOOL" sed -i 's/^CODESET=.*/CODESET="guess"/' /etc/default/console-setup
"$ESCALATION_TOOL" sed -i 's/^FONTFACE=.*/FONTFACE="TerminusBold"/' /etc/default/console-setup
"$ESCALATION_TOOL" sed -i 's/^FONTSIZE=.*/FONTSIZE="16x32"/' /etc/default/console-setup
printf "%b\n" "${GREEN}Console-setup configuration updated for Terminus font.${RC}"
# Editing console-setup requires initramfs to be regenerated
"$ESCALATION_TOOL" update-initramfs -u
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
"$ESCALATION_TOOL" setfont -C /dev/tty1 /usr/share/consolefonts/Uni3-TerminusBold32x16.psf.gz
fi
printf "%b\n" "${GREEN}Terminus font has been set for TTY.${RC}"
;;
fedora)
printf "%b\n" "${YELLOW}Updating FONT= line in /etc/vconsole.conf...${RC}"
"$ESCALATION_TOOL" sed -i 's/^FONT=.*/FONT=ter-v32b/' /etc/vconsole.conf
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
"$ESCALATION_TOOL" setfont -C /dev/tty1 ter-v32b
fi
printf "%b\n" "${GREEN}Terminus font has been set for TTY.${RC}"
;;
esac
}
checkEnv
InstallTermiusFonts
SetTermiusFonts

View File

@ -29,7 +29,7 @@ list_sessions() {
8) session="openbox.desktop" ;; 8) session="openbox.desktop" ;;
9) session="i3.desktop" ;; 9) session="i3.desktop" ;;
10) 10)
printf "%b" "Enter custom session name (e.g., mysession.desktop): " printf "%b" "Enter custom session name (e.g., mysession): "
read -r session ;; read -r session ;;
*) *)
printf "%b\n" "Invalid option selected." printf "%b\n" "Invalid option selected."
@ -43,9 +43,9 @@ configure_lightdm() {
printf "%b" "Enter username for LightDM autologin: " printf "%b" "Enter username for LightDM autologin: "
read -r user read -r user
"$ESCALATION_TOOL" "printf '[Seat:*]' > /etc/lightdm/lightdm.conf.d/50-autologin.conf" printf "%b\n" '[Seat:*]' | "$ESCALATION_TOOL" tee -a /etc/lightdm/lightdm.conf
"$ESCALATION_TOOL" "printf 'autologin-user=$user' >> /etc/lightdm/lightdm.conf.d/50-autologin.conf" printf "%s\n" "autologin-user=$user" | "$ESCALATION_TOOL" tee -a /etc/lightdm/lightdm.conf
"$ESCALATION_TOOL" "printf 'autologin-user-timeout=0' >> /etc/lightdm/lightdm.conf.d/50-autologin.conf" printf "%b\n" 'autologin-user-timeout=0' | "$ESCALATION_TOOL" tee -a /etc/lightdm/lightdm.conf
printf "%b\n" "LightDM has been configured for autologin." printf "%b\n" "LightDM has been configured for autologin."
} }
@ -53,7 +53,8 @@ configure_lightdm() {
# Function to remove LightDM autologin # Function to remove LightDM autologin
remove_lightdm_autologin() { remove_lightdm_autologin() {
printf "%b\n" "Removing LightDM autologin configuration..." printf "%b\n" "Removing LightDM autologin configuration..."
"$ESCALATION_TOOL" rm -f /etc/lightdm/lightdm.conf.d/50-autologin.conf "$ESCALATION_TOOL" sed -i'' '/^\[Seat:\*]/d' /etc/lightdm/lightdm.conf
"$ESCALATION_TOOL" sed -i'' '/^autologin-/d' /etc/lightdm/lightdm.conf
printf "%b\n" "LightDM autologin configuration has been removed." printf "%b\n" "LightDM autologin configuration has been removed."
} }
@ -63,9 +64,9 @@ configure_gdm() {
printf "%b" "Enter username for GDM autologin: " printf "%b" "Enter username for GDM autologin: "
read -r user read -r user
"$ESCALATION_TOOL" "printf '[daemon]' > /etc/gdm/custom.conf" printf "%b\n" '[daemon]' | "$ESCALATION_TOOL" tee -a /etc/gdm/custom.conf
"$ESCALATION_TOOL" "printf 'AutomaticLoginEnable = true' >> /etc/gdm/custom.conf" printf "%b\n" 'AutomaticLoginEnable = true' | "$ESCALATION_TOOL" tee -a /etc/gdm/custom.conf
"$ESCALATION_TOOL" "printf 'AutomaticLogin = $user' >> /etc/gdm/custom.conf" printf "%s\n" "AutomaticLogin = $user" | "$ESCALATION_TOOL" tee -a /etc/gdm/custom.conf
printf "%b\n" "GDM has been configured for autologin." printf "%b\n" "GDM has been configured for autologin."
} }
@ -73,8 +74,8 @@ configure_gdm() {
# Function to remove GDM autologin # Function to remove GDM autologin
remove_gdm_autologin() { remove_gdm_autologin() {
printf "%b\n" "Removing GDM autologin configuration..." printf "%b\n" "Removing GDM autologin configuration..."
"$ESCALATION_TOOL" sed -i '/AutomaticLoginEnable/d' /etc/gdm/custom.conf "$ESCALATION_TOOL" sed -i'' '/AutomaticLoginEnable/d' /etc/gdm/custom.conf
"$ESCALATION_TOOL" sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf "$ESCALATION_TOOL" sed -i'' '/AutomaticLogin/d' /etc/gdm/custom.conf
printf "%b\n" "GDM autologin configuration has been removed." printf "%b\n" "GDM autologin configuration has been removed."
} }
@ -85,9 +86,9 @@ configure_sddm() {
read -r user read -r user
list_sessions # Show session options list_sessions # Show session options
"$ESCALATION_TOOL" "printf '[Autologin]' > /etc/sddm.conf" printf "%b\n" '[Autologin]' | "$ESCALATION_TOOL" tee -a /etc/sddm.conf
"$ESCALATION_TOOL" "printf 'User=$user' >> /etc/sddm.conf" printf "%s\n" "User=$user" | "$ESCALATION_TOOL" tee -a /etc/sddm.conf
"$ESCALATION_TOOL" "printf 'Session=$session' >> /etc/sddm.conf" printf "%s\n" "Session=$session" | "$ESCALATION_TOOL" tee -a /etc/sddm.conf
printf "%b\n" "SDDM has been configured for autologin." printf "%b\n" "SDDM has been configured for autologin."
} }
@ -95,7 +96,7 @@ configure_sddm() {
# Function to remove SDDM autologin # Function to remove SDDM autologin
remove_sddm_autologin() { remove_sddm_autologin() {
printf "%b\n" "Removing SDDM autologin configuration..." printf "%b\n" "Removing SDDM autologin configuration..."
"$ESCALATION_TOOL" sed -i '/\[Autologin\]/,+2d' /etc/sddm.conf "$ESCALATION_TOOL" sed -i'' '/\[Autologin\]/,+2d' /etc/sddm.conf
printf "%b\n" "SDDM autologin configuration has been removed." printf "%b\n" "SDDM autologin configuration has been removed."
} }
@ -106,8 +107,8 @@ configure_lxdm() {
read -r user read -r user
list_sessions # Show session options list_sessions # Show session options
"$ESCALATION_TOOL" sed -i "s/^#.*autologin=.*$/autologin=${user}/" /etc/lxdm/lxdm.conf "$ESCALATION_TOOL" sed -i'' "s/^#.*autologin=.*$/autologin=${user}/" /etc/lxdm/lxdm.conf
"$ESCALATION_TOOL" sed -i "s|^#.*session=.*$|session=/usr/bin/${session}|; s|^session=.*$|session=/usr/bin/${session}|" /etc/lxdm/lxdm.conf "$ESCALATION_TOOL" sed -i'' "s|^#.*session=.*$|session=/usr/bin/${session}|; s|^session=.*$|session=/usr/bin/${session}|" /etc/lxdm/lxdm.conf
printf "%b\n" "LXDM has been configured for autologin." printf "%b\n" "LXDM has been configured for autologin."
} }
@ -115,8 +116,8 @@ configure_lxdm() {
# Function to remove LXDM autologin # Function to remove LXDM autologin
remove_lxdm_autologin() { remove_lxdm_autologin() {
printf "%b\n" "Removing LXDM autologin configuration..." printf "%b\n" "Removing LXDM autologin configuration..."
"$ESCALATION_TOOL" sed -i "s/^autologin=.*$/#autologin=/" /etc/lxdm/lxdm.conf "$ESCALATION_TOOL" sed -i'' "s/^autologin=.*$/#autologin=/" /etc/lxdm/lxdm.conf
"$ESCALATION_TOOL" sed -i "s/^session=.*$/#session=/" /etc/lxdm/lxdm.conf "$ESCALATION_TOOL" sed -i'' "s/^session=.*$/#session=/" /etc/lxdm/lxdm.conf
printf "%b\n" "LXDM autologin configuration has been removed." printf "%b\n" "LXDM autologin configuration has been removed."
} }

View File

@ -21,7 +21,7 @@ select_drive() {
# Function to get UUID and FSTYPE of the selected drive # Function to get UUID and FSTYPE of the selected drive
get_uuid_fstype() { get_uuid_fstype() {
UUID=$(blkid -s UUID -o value "${partition}") UUID=$("$ESCALATION_TOOL" blkid -s UUID -o value "${partition}")
FSTYPE=$(lsblk -no FSTYPE "${partition}") FSTYPE=$(lsblk -no FSTYPE "${partition}")
NAME=$(lsblk -no NAME "${partition}") NAME=$(lsblk -no NAME "${partition}")

View File

@ -34,6 +34,7 @@ installAutoCpufreq() {
cd auto-cpufreq cd auto-cpufreq
printf "%b\n" "${YELLOW}Running auto-cpufreq installer...${RC}" printf "%b\n" "${YELLOW}Running auto-cpufreq installer...${RC}"
"$ESCALATION_TOOL" ./auto-cpufreq-installer "$ESCALATION_TOOL" ./auto-cpufreq-installer
"$ESCALATION_TOOL" auto-cpufreq --install
cd .. cd ..
fi fi
@ -94,4 +95,4 @@ apply_or_remove_auto_cpufreq() {
checkEnv checkEnv
checkEscalationTool checkEscalationTool
installAutoCpufreq installAutoCpufreq
apply_or_remove_auto_cpufreq apply_or_remove_auto_cpufreq

View File

@ -95,6 +95,7 @@ script = "user-account-manager/remove_from_group.sh"
[[data]] [[data]]
name = "Auto Mount Drive" name = "Auto Mount Drive"
description = "This utility is designed to help with automating the process of mounting a drive on to your system."
script = "auto-mount.sh" script = "auto-mount.sh"
task_list = "PFM" task_list = "PFM"

BIN
docs/assets/preview.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

88
docs/assets/preview.tape Normal file
View File

@ -0,0 +1,88 @@
# VHS documentation
#
# Output:
# Output <path>.gif Create a GIF output at the given <path>
# Output <path>.mp4 Create an MP4 output at the given <path>
# Output <path>.webm Create a WebM output at the given <path>
#
# Require:
# Require <string> Ensure a program is on the $PATH to proceed
#
# Settings:
# Set FontSize <number> Set the font size of the terminal
# Set FontFamily <string> Set the font family of the terminal
# Set Height <number> Set the height of the terminal
# Set Width <number> Set the width of the terminal
# Set LetterSpacing <float> Set the font letter spacing (tracking)
# Set LineHeight <float> Set the font line height
# Set LoopOffset <float>% Set the starting frame offset for the GIF loop
# Set Theme <json|string> Set the theme of the terminal
# Set Padding <number> Set the padding of the terminal
# Set Framerate <number> Set the framerate of the recording
# Set PlaybackSpeed <float> Set the playback speed of the recording
# Set MarginFill <file|#000000> Set the file or color the margin will be filled with.
# Set Margin <number> Set the size of the margin. Has no effect if MarginFill isn't set.
# Set BorderRadius <number> Set terminal border radius, in pixels.
# Set WindowBar <string> Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight)
# Set WindowBarSize <number> Set window bar size, in pixels. Default is 40.
# Set TypingSpeed <time> Set the typing speed of the terminal. Default is 50ms.
#
# Sleep:
# Sleep <time> Sleep for a set amount of <time> in seconds
#
# Type:
# Type[@<time>] "<characters>" Type <characters> into the terminal with a
# <time> delay between each character
#
# Keys:
# Escape[@<time>] [number] Press the Escape key
# Backspace[@<time>] [number] Press the Backspace key
# Delete[@<time>] [number] Press the Delete key
# Insert[@<time>] [number] Press the Insert key
# Down[@<time>] [number] Press the Down key
# Enter[@<time>] [number] Press the Enter key
# Space[@<time>] [number] Press the Space key
# Tab[@<time>] [number] Press the Tab key
# Left[@<time>] [number] Press the Left Arrow key
# Right[@<time>] [number] Press the Right Arrow key
# Up[@<time>] [number] Press the Up Arrow key
# Down[@<time>] [number] Press the Down Arrow key
# PageUp[@<time>] [number] Press the Page Up key
# PageDown[@<time>] [number] Press the Page Down key
# Ctrl+<key> Press the Control key + <key> (e.g. Ctrl+C)
#
# Display:
# Hide Hide the subsequent commands from the output
# Show Show the subsequent commands in the output
Output preview.gif
Require linutil
Require sh
Set Shell "bash"
Set FontSize 32
Set Width 3200
Set Height 1800
Sleep 1s
Type "linutil -t compatible" Sleep 1s Enter
Sleep 5s
Left Sleep 2s
Down Sleep 1s
Down Sleep 1s
Down Sleep 1s
Down Sleep 2s
Right Sleep 3s
Type "/" Sleep 1s
Type@200ms "System Cleanup" Sleep 1s Enter
Sleep 2s
Enter Sleep 3s
Type "y" Sleep 15s # CONFIRMATION PROMPT
Escape

View File

@ -69,7 +69,7 @@ Note that crates installed using `cargo install` require manual updating with `c
After you've ran the command, you should see a GUI on your screen; It will look something like this: After you've ran the command, you should see a GUI on your screen; It will look something like this:
![preview](assets/preview.png) ![preview](assets/preview.gif)
!!! info !!! info

View File

@ -8,6 +8,7 @@
- **Gaming Setup**: Configures Steam, Lutris, etc. - **Gaming Setup**: Configures Steam, Lutris, etc.
- **Global Theming**: Sets up and manages global themes. - **Global Theming**: Sets up and manages global themes.
- **Remove Snaps**: Removes snap packages. - **Remove Snaps**: Removes snap packages.
- **TTY Fonts**: This Script will set the default TTY font to Terminus size 32 Bold
### Arch Setup ### Arch Setup

View File

@ -15,16 +15,17 @@ default = ["tips"]
tips = ["rand"] tips = ["rand"]
[dependencies] [dependencies]
clap = { version = "4.5.16", features = ["derive"] } clap = { version = "4.5.18", features = ["derive"] }
crossterm = "0.28.1" crossterm = "0.28.1"
ego-tree = { workspace = true } ego-tree = { workspace = true }
oneshot = "0.1.8" oneshot = "0.1.8"
portable-pty = "0.8.1" portable-pty = "0.8.1"
ratatui = "0.28.1" ratatui = "0.28.1"
tui-term = "0.1.12" tui-term = "0.1.12"
unicode-width = "0.1.13" temp-dir = "0.1.14"
unicode-width = "0.2.0"
rand = { version = "0.8.5", optional = true } rand = { version = "0.8.5", optional = true }
linutil_core = { path = "../core", version = "24.9.22" } linutil_core = { path = "../core", version = "24.9.28" }
tree-sitter-highlight = "0.23.0" tree-sitter-highlight = "0.23.0"
tree-sitter-bash = "0.23.1" tree-sitter-bash = "0.23.1"
anstyle = "1.0.8" anstyle = "1.0.8"

126
tui/src/confirmation.rs Normal file
View File

@ -0,0 +1,126 @@
use std::borrow::Cow;
use crate::{float::FloatContent, hint::Shortcut};
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{
layout::Alignment,
prelude::*,
widgets::{Block, Borders, Clear, List},
};
pub enum ConfirmStatus {
Confirm,
Abort,
None,
}
pub struct ConfirmPrompt {
pub names: Box<[String]>,
pub status: ConfirmStatus,
scroll: usize,
}
impl ConfirmPrompt {
pub fn new(names: &[&str]) -> Self {
let max_count_str = format!("{}", names.len());
let names = names
.iter()
.zip(1..)
.map(|(name, n)| {
let count_str = format!("{n}");
let space_str = (0..(max_count_str.len() - count_str.len()))
.map(|_| ' ')
.collect::<String>();
format!("{space_str}{n}. {name}")
})
.collect();
Self {
names,
status: ConfirmStatus::None,
scroll: 0,
}
}
pub fn scroll_down(&mut self) {
if self.scroll < self.names.len() - 1 {
self.scroll += 1;
}
}
pub fn scroll_up(&mut self) {
if self.scroll > 0 {
self.scroll -= 1;
}
}
}
impl FloatContent for ConfirmPrompt {
fn draw(&mut self, frame: &mut Frame, area: Rect) {
let block = Block::default()
.borders(Borders::ALL)
.title(" Confirm selections ")
.title_bottom(" [y] to continue, [n] to abort ")
.title_alignment(Alignment::Center)
.title_style(Style::default().bold())
.style(Style::default());
frame.render_widget(block.clone(), area);
let inner_area = block.inner(area);
let paths_text = self
.names
.iter()
.skip(self.scroll)
.map(|p| {
let span = Span::from(Cow::<'_, str>::Borrowed(p));
Line::from(span).style(Style::default())
})
.collect::<Text>();
frame.render_widget(Clear, inner_area);
frame.render_widget(List::new(paths_text), inner_area);
}
fn handle_key_event(&mut self, key: &KeyEvent) -> bool {
use KeyCode::*;
self.status = match key.code {
Char('y') | Char('Y') => ConfirmStatus::Confirm,
Char('n') | Char('N') | Esc => ConfirmStatus::Abort,
Char('j') => {
self.scroll_down();
ConfirmStatus::None
}
Char('k') => {
self.scroll_up();
ConfirmStatus::None
}
_ => ConfirmStatus::None,
};
false
}
fn is_finished(&self) -> bool {
use ConfirmStatus::*;
match self.status {
Confirm | Abort => true,
None => false,
}
}
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
(
"Confirmation prompt",
Box::new([
Shortcut::new("Continue", ["Y", "y"]),
Shortcut::new("Abort", ["N", "n"]),
Shortcut::new("Scroll up", ["j"]),
Shortcut::new("Scroll down", ["k"]),
Shortcut::new("Close linutil", ["CTRL-c", "q"]),
]),
)
}
}

View File

@ -4,23 +4,23 @@ use ratatui::{
Frame, Frame,
}; };
use crate::hint::ShortcutList; use crate::hint::Shortcut;
pub trait FloatContent { pub trait FloatContent {
fn draw(&mut self, frame: &mut Frame, area: Rect); fn draw(&mut self, frame: &mut Frame, area: Rect);
fn handle_key_event(&mut self, key: &KeyEvent) -> bool; fn handle_key_event(&mut self, key: &KeyEvent) -> bool;
fn is_finished(&self) -> bool; fn is_finished(&self) -> bool;
fn get_shortcut_list(&self) -> ShortcutList; fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>);
} }
pub struct Float { pub struct Float<Content: FloatContent + ?Sized> {
content: Box<dyn FloatContent>, pub content: Box<Content>,
width_percent: u16, width_percent: u16,
height_percent: u16, height_percent: u16,
} }
impl Float { impl<Content: FloatContent + ?Sized> Float<Content> {
pub fn new(content: Box<dyn FloatContent>, width_percent: u16, height_percent: u16) -> Self { pub fn new(content: Box<Content>, width_percent: u16, height_percent: u16) -> Self {
Self { Self {
content, content,
width_percent, width_percent,
@ -60,6 +60,7 @@ impl Float {
| KeyCode::Char('p') | KeyCode::Char('p')
| KeyCode::Char('d') | KeyCode::Char('d')
| KeyCode::Char('g') | KeyCode::Char('g')
| KeyCode::Char('q')
| KeyCode::Esc | KeyCode::Esc
if self.content.is_finished() => if self.content.is_finished() =>
{ {
@ -69,7 +70,7 @@ impl Float {
} }
} }
pub fn get_shortcut_list(&self) -> ShortcutList { pub fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
self.content.get_shortcut_list() self.content.get_shortcut_list()
} }
} }

View File

@ -4,10 +4,7 @@ use std::{
io::{Cursor, Read as _, Seek, SeekFrom, Write as _}, io::{Cursor, Read as _, Seek, SeekFrom, Write as _},
}; };
use crate::{ use crate::{float::FloatContent, hint::Shortcut};
float::FloatContent,
hint::{Shortcut, ShortcutList},
};
use linutil_core::Command; use linutil_core::Command;
@ -279,16 +276,16 @@ impl FloatContent for FloatingText {
true true
} }
fn get_shortcut_list(&self) -> ShortcutList { fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
ShortcutList { (
scope_name: self.mode_title.clone(), &self.mode_title,
hints: vec![ Box::new([
Shortcut::new(vec!["j", "Down"], "Scroll down"), Shortcut::new("Scroll down", ["j", "Down"]),
Shortcut::new(vec!["k", "Up"], "Scroll up"), Shortcut::new("Scroll up", ["k", "Up"]),
Shortcut::new(vec!["h", "Left"], "Scroll left"), Shortcut::new("Scroll left", ["h", "Left"]),
Shortcut::new(vec!["l", "Right"], "Scroll right"), Shortcut::new("Scroll right", ["l", "Right"]),
Shortcut::new(vec!["Enter", "p", "d", "g"], "Close window"), Shortcut::new("Close window", ["Enter", "p", "q", "d", "g"]),
], ]),
} )
} }
} }

View File

@ -1,20 +1,10 @@
use std::borrow::Cow;
use ratatui::{ use ratatui::{
layout::{Margin, Rect},
style::{Style, Stylize}, style::{Style, Stylize},
text::{Line, Span}, text::{Line, Span},
widgets::{Block, Borders, Paragraph},
Frame,
}; };
use crate::state::{AppState, Focus};
pub const SHORTCUT_LINES: usize = 2;
pub struct ShortcutList {
pub scope_name: String,
pub hints: Vec<Shortcut>,
}
pub struct Shortcut { pub struct Shortcut {
pub key_sequences: Vec<Span<'static>>, pub key_sequences: Vec<Span<'static>>,
pub desc: &'static str, pub desc: &'static str,
@ -32,54 +22,63 @@ fn add_spacing(list: Vec<Vec<Span>>) -> Line {
pub fn span_vec_len(span_vec: &[Span]) -> usize { pub fn span_vec_len(span_vec: &[Span]) -> usize {
span_vec.iter().rfold(0, |init, s| init + s.width()) span_vec.iter().rfold(0, |init, s| init + s.width())
} }
impl ShortcutList {
pub fn draw(&self, frame: &mut Frame, area: Rect) {
let block = Block::default()
.title(format!(" {} ", self.scope_name))
.borders(Borders::all());
let inner_area = area.inner(Margin::new(1, 1));
let shortcut_spans: Vec<Vec<Span>> = self.hints.iter().map(|h| h.to_spans()).collect();
let mut lines: Vec<Line> = Vec::with_capacity(SHORTCUT_LINES); pub fn create_shortcut_list(
shortcuts: impl IntoIterator<Item = Shortcut>,
render_width: u16,
) -> Box<[Line<'static>]> {
let hints = shortcuts.into_iter().collect::<Box<[Shortcut]>>();
let shortcut_list = (0..SHORTCUT_LINES - 1).fold(shortcut_spans, |mut acc, _| { let mut shortcut_spans: Vec<Vec<Span<'static>>> = hints.iter().map(|h| h.to_spans()).collect();
let split_idx = acc
.iter() let mut lines: Vec<Line<'static>> = vec![];
.scan(0_usize, |total_len, s| {
loop {
let split_idx = shortcut_spans
.iter()
.scan(0usize, |total_len, s| {
// take at least one so that we guarantee that we drain the list
// otherwise, this might lock up if there's a shortcut that exceeds the window width
if *total_len == 0 {
*total_len += span_vec_len(s) + 4;
Some(())
} else {
*total_len += span_vec_len(s); *total_len += span_vec_len(s);
if *total_len > inner_area.width as usize { if *total_len > render_width as usize {
None None
} else { } else {
*total_len += 4; *total_len += 4;
Some(1) Some(())
} }
}) }
.count(); })
.count();
let new_shortcut_list = acc.split_off(split_idx); let rest = shortcut_spans.split_off(split_idx);
lines.push(add_spacing(acc)); lines.push(add_spacing(shortcut_spans));
new_shortcut_list if rest.is_empty() {
}); break;
lines.push(add_spacing(shortcut_list)); } else {
shortcut_spans = rest;
let p = Paragraph::new(lines).block(block); }
frame.render_widget(p, area);
} }
lines.into_boxed_slice()
} }
impl Shortcut { impl Shortcut {
pub fn new(key_sequences: Vec<&'static str>, desc: &'static str) -> Self { pub fn new<const N: usize>(desc: &'static str, key_sequences: [&'static str; N]) -> Self {
Self { Self {
key_sequences: key_sequences key_sequences: key_sequences
.iter() .iter()
.map(|s| Span::styled(*s, Style::default().bold())) .map(|s| Span::styled(Cow::<'static, str>::Borrowed(s), Style::default().bold()))
.collect(), .collect(),
desc, desc,
} }
} }
fn to_spans(&self) -> Vec<Span> { fn to_spans(&self) -> Vec<Span<'static>> {
let mut ret: Vec<_> = self let mut ret: Vec<_> = self
.key_sequences .key_sequences
.iter() .iter()
@ -95,77 +94,3 @@ impl Shortcut {
ret ret
} }
} }
fn get_list_item_shortcut(state: &AppState) -> Vec<Shortcut> {
if state.selected_item_is_dir() {
vec![Shortcut::new(
vec!["l", "Right", "Enter"],
"Go to selected dir",
)]
} else {
vec![
Shortcut::new(vec!["l", "Right", "Enter"], "Run selected command"),
Shortcut::new(vec!["p"], "Enable preview"),
Shortcut::new(vec!["d"], "Command Description"),
]
}
}
pub fn draw_shortcuts(state: &AppState, frame: &mut Frame, area: Rect) {
match state.focus {
Focus::Search => ShortcutList {
scope_name: "Search bar".to_string(),
hints: vec![Shortcut::new(vec!["Enter"], "Finish search")],
},
Focus::List => {
let mut hints = Vec::new();
hints.push(Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil"));
if state.at_root() {
hints.push(Shortcut::new(vec!["h", "Left"], "Focus tab list"));
hints.extend(get_list_item_shortcut(state));
} else if state.selected_item_is_up_dir() {
hints.push(Shortcut::new(
vec!["l", "Right", "Enter", "h", "Left"],
"Go to parent directory",
));
} else {
hints.push(Shortcut::new(vec!["h", "Left"], "Go to parent directory"));
hints.extend(get_list_item_shortcut(state));
}
hints.push(Shortcut::new(vec!["k", "Up"], "Select item above"));
hints.push(Shortcut::new(vec!["j", "Down"], "Select item below"));
hints.push(Shortcut::new(vec!["t"], "Next theme"));
hints.push(Shortcut::new(vec!["T"], "Previous theme"));
if state.is_current_tab_multi_selectable() {
hints.push(Shortcut::new(vec!["v"], "Toggle multi-selection mode"));
hints.push(Shortcut::new(vec!["Space"], "Select multiple commands"));
}
hints.push(Shortcut::new(vec!["Tab"], "Next tab"));
hints.push(Shortcut::new(vec!["Shift-Tab"], "Previous tab"));
hints.push(Shortcut::new(vec!["g"], "Important actions guide"));
ShortcutList {
scope_name: "Command list".to_string(),
hints,
}
}
Focus::TabList => ShortcutList {
scope_name: "Tab list".to_string(),
hints: vec![
Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil"),
Shortcut::new(vec!["l", "Right", "Enter"], "Focus action list"),
Shortcut::new(vec!["k", "Up"], "Select item above"),
Shortcut::new(vec!["j", "Down"], "Select item below"),
Shortcut::new(vec!["t"], "Next theme"),
Shortcut::new(vec!["T"], "Previous theme"),
Shortcut::new(vec!["Tab"], "Next tab"),
Shortcut::new(vec!["Shift-Tab"], "Previous tab"),
],
},
Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
}
.draw(frame, area);
}

View File

@ -1,3 +1,4 @@
mod confirmation;
mod filter; mod filter;
mod float; mod float;
mod floating_text; mod floating_text;

View File

@ -1,7 +1,4 @@
use crate::{ use crate::{float::FloatContent, hint::Shortcut};
float::FloatContent,
hint::{Shortcut, ShortcutList},
};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use linutil_core::Command; use linutil_core::Command;
use oneshot::{channel, Receiver}; use oneshot::{channel, Receiver};
@ -40,6 +37,7 @@ pub struct RunningCommand {
writer: Box<dyn Write + Send>, writer: Box<dyn Write + Send>,
/// Only set after the process has ended /// Only set after the process has ended
status: Option<ExitStatus>, status: Option<ExitStatus>,
scroll_offset: usize,
} }
impl FloatContent for RunningCommand { impl FloatContent for RunningCommand {
@ -105,6 +103,12 @@ impl FloatContent for RunningCommand {
KeyCode::Enter if self.is_finished() => { KeyCode::Enter if self.is_finished() => {
return true; return true;
} }
KeyCode::PageUp => {
self.scroll_offset = self.scroll_offset.saturating_add(10);
}
KeyCode::PageDown => {
self.scroll_offset = self.scroll_offset.saturating_sub(10);
}
// Pass other key events to the terminal // Pass other key events to the terminal
_ => self.handle_passthrough_key_event(key), _ => self.handle_passthrough_key_event(key),
} }
@ -120,17 +124,25 @@ impl FloatContent for RunningCommand {
} }
} }
fn get_shortcut_list(&self) -> ShortcutList { fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
if self.is_finished() { if self.is_finished() {
ShortcutList { (
scope_name: "Finished command".to_string(), "Finished command",
hints: vec![Shortcut::new(vec!["Enter", "q"], "Close window")], Box::new([
} Shortcut::new("Close window", ["Enter", "q"]),
Shortcut::new("Scroll up", ["Page up"]),
Shortcut::new("Scroll down", ["Page down"]),
]),
)
} else { } else {
ShortcutList { (
scope_name: "Running command".to_string(), "Running command",
hints: vec![Shortcut::new(vec!["CTRL-c"], "Kill the command")], Box::new([
} Shortcut::new("Kill the command", ["CTRL-c"]),
Shortcut::new("Scroll up", ["Page up"]),
Shortcut::new("Scroll down", ["Page down"]),
]),
)
} }
} }
} }
@ -223,6 +235,7 @@ impl RunningCommand {
pty_master: pair.master, pty_master: pair.master,
writer, writer,
status: None, status: None,
scroll_offset: 0,
} }
} }
@ -240,10 +253,12 @@ impl RunningCommand {
// Process the buffer with a parser with the current screen size // Process the buffer with a parser with the current screen size
// We don't actually need to create a new parser every time, but it is so much easier this // We don't actually need to create a new parser every time, but it is so much easier this
// way, and doesn't cost that much // way, and doesn't cost that much
let mut parser = vt100::Parser::new(size.height, size.width, 0); let mut parser = vt100::Parser::new(size.height, size.width, 200);
let mutex = self.buffer.lock(); let mutex = self.buffer.lock();
let buffer = mutex.as_ref().unwrap(); let buffer = mutex.as_ref().unwrap();
parser.process(buffer); parser.process(buffer);
// Adjust the screen content based on the scroll offset
parser.screen_mut().set_scrollback(self.scroll_offset);
parser.screen().clone() parser.screen().clone()
} }
@ -300,8 +315,6 @@ impl RunningCommand {
KeyCode::Tab => vec![9], KeyCode::Tab => vec![9],
KeyCode::Home => vec![27, 91, 72], KeyCode::Home => vec![27, 91, 72],
KeyCode::End => vec![27, 91, 70], KeyCode::End => vec![27, 91, 70],
KeyCode::PageUp => vec![27, 91, 53, 126],
KeyCode::PageDown => vec![27, 91, 54, 126],
KeyCode::BackTab => vec![27, 91, 90], KeyCode::BackTab => vec![27, 91, 90],
KeyCode::Delete => vec![27, 91, 51, 126], KeyCode::Delete => vec![27, 91, 51, 126],
KeyCode::Insert => vec![27, 91, 50, 126], KeyCode::Insert => vec![27, 91, 50, 126],

View File

@ -1,23 +1,26 @@
use crate::{ use crate::{
confirmation::{ConfirmPrompt, ConfirmStatus},
filter::{Filter, SearchAction}, filter::{Filter, SearchAction},
float::{Float, FloatContent}, float::{Float, FloatContent},
floating_text::FloatingText, floating_text::FloatingText,
hint::{draw_shortcuts, SHORTCUT_LINES}, hint::{create_shortcut_list, Shortcut},
running_command::RunningCommand, running_command::RunningCommand,
theme::Theme, theme::Theme,
}; };
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use ego_tree::NodeId; use ego_tree::NodeId;
use linutil_core::{Command, ListNode, Tab}; use linutil_core::{ListNode, Tab};
#[cfg(feature = "tips")] #[cfg(feature = "tips")]
use rand::Rng; use rand::Rng;
use ratatui::{ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout}, layout::{Alignment, Constraint, Direction, Flex, Layout},
style::{Style, Stylize}, style::{Style, Stylize},
text::{Line, Span}, text::{Line, Span, Text},
widgets::{Block, Borders, List, ListState, Paragraph}, widgets::{Block, Borders, List, ListState, Paragraph},
Frame, Frame,
}; };
use std::rc::Rc;
use temp_dir::TempDir;
const MIN_WIDTH: u16 = 77; const MIN_WIDTH: u16 = 77;
const MIN_HEIGHT: u16 = 19; const MIN_HEIGHT: u16 = 19;
@ -30,13 +33,15 @@ FM - file modification
I - installation (privileged) I - installation (privileged)
MP - package manager actions MP - package manager actions
SI - full system installation SI - full system installation
SS - systemd actions (privileged) SS - systemd actions (privileged)
RP - package removal RP - package removal
P* - privileged * P* - privileged *
"; ";
pub struct AppState { pub struct AppState {
/// This must be passed to retain the temp dir until the end of the program
_temp_dir: TempDir,
/// Selected theme /// Selected theme
theme: Theme, theme: Theme,
/// Currently focused area /// Currently focused area
@ -53,7 +58,7 @@ pub struct AppState {
selection: ListState, selection: ListState,
filter: Filter, filter: Filter,
multi_select: bool, multi_select: bool,
selected_commands: Vec<Command>, selected_commands: Vec<Rc<ListNode>>,
drawable: bool, drawable: bool,
#[cfg(feature = "tips")] #[cfg(feature = "tips")]
tip: &'static str, tip: &'static str,
@ -63,20 +68,23 @@ pub enum Focus {
Search, Search,
TabList, TabList,
List, List,
FloatingWindow(Float), FloatingWindow(Float<dyn FloatContent>),
ConfirmationPrompt(Float<ConfirmPrompt>),
} }
pub struct ListEntry { pub struct ListEntry {
pub node: ListNode, pub node: Rc<ListNode>,
pub id: NodeId, pub id: NodeId,
pub has_children: bool, pub has_children: bool,
} }
impl AppState { impl AppState {
pub fn new(theme: Theme, override_validation: bool) -> Self { pub fn new(theme: Theme, override_validation: bool) -> Self {
let tabs = linutil_core::get_tabs(!override_validation); let (temp_dir, tabs) = linutil_core::get_tabs(!override_validation);
let root_id = tabs[0].tree.root().id(); let root_id = tabs[0].tree.root().id();
let mut state = Self { let mut state = Self {
_temp_dir: temp_dir,
theme, theme,
focus: Focus::List, focus: Focus::List,
tabs, tabs,
@ -90,9 +98,83 @@ impl AppState {
#[cfg(feature = "tips")] #[cfg(feature = "tips")]
tip: get_random_tip(), tip: get_random_tip(),
}; };
state.update_items(); state.update_items();
state state
} }
fn get_list_item_shortcut(&self) -> Box<[Shortcut]> {
if self.selected_item_is_dir() {
Box::new([Shortcut::new("Go to selected dir", ["l", "Right", "Enter"])])
} else {
Box::new([
Shortcut::new("Run selected command", ["l", "Right", "Enter"]),
Shortcut::new("Enable preview", ["p"]),
Shortcut::new("Command Description", ["d"]),
])
}
}
pub fn get_keybinds(&self) -> (&str, Box<[Shortcut]>) {
match self.focus {
Focus::Search => (
"Search bar",
Box::new([Shortcut::new("Finish search", ["Enter"])]),
),
Focus::List => {
let mut hints = Vec::new();
hints.push(Shortcut::new("Exit linutil", ["q", "CTRL-c"]));
if self.at_root() {
hints.push(Shortcut::new("Focus tab list", ["h", "Left"]));
hints.extend(self.get_list_item_shortcut());
} else if self.selected_item_is_up_dir() {
hints.push(Shortcut::new(
"Go to parent directory",
["l", "Right", "Enter", "h", "Left"],
));
} else {
hints.push(Shortcut::new("Go to parent directory", ["h", "Left"]));
hints.extend(self.get_list_item_shortcut());
}
hints.push(Shortcut::new("Select item above", ["k", "Up"]));
hints.push(Shortcut::new("Select item below", ["j", "Down"]));
hints.push(Shortcut::new("Next theme", ["t"]));
hints.push(Shortcut::new("Previous theme", ["T"]));
if self.is_current_tab_multi_selectable() {
hints.push(Shortcut::new("Toggle multi-selection mode", ["v"]));
hints.push(Shortcut::new("Select multiple commands", ["Space"]));
}
hints.push(Shortcut::new("Next tab", ["Tab"]));
hints.push(Shortcut::new("Previous tab", ["Shift-Tab"]));
hints.push(Shortcut::new("Important actions guide", ["g"]));
("Command list", hints.into_boxed_slice())
}
Focus::TabList => (
"Tab list",
Box::new([
Shortcut::new("Exit linutil", ["q", "CTRL-c"]),
Shortcut::new("Focus action list", ["l", "Right", "Enter"]),
Shortcut::new("Select item above", ["k", "Up"]),
Shortcut::new("Select item below", ["j", "Down"]),
Shortcut::new("Next theme", ["t"]),
Shortcut::new("Previous theme", ["T"]),
Shortcut::new("Next tab", ["Tab"]),
Shortcut::new("Previous tab", ["Shift-Tab"]),
]),
),
Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
Focus::ConfirmationPrompt(ref prompt) => prompt.get_shortcut_list(),
}
}
pub fn draw(&mut self, frame: &mut Frame) { pub fn draw(&mut self, frame: &mut Frame) {
let terminal_size = frame.area(); let terminal_size = frame.area();
@ -153,12 +235,26 @@ impl AppState {
.unwrap_or(0) .unwrap_or(0)
.max(str1.len() + str2.len()); .max(str1.len() + str2.len());
let (keybind_scope, shortcuts) = self.get_keybinds();
let keybind_render_width = terminal_size.width - 2;
let keybinds_block = Block::default()
.title(format!(" {} ", keybind_scope))
.borders(Borders::all());
let keybinds = create_shortcut_list(shortcuts, keybind_render_width);
let n_lines = keybinds.len() as u16;
let keybind_para = Paragraph::new(Text::from_iter(keybinds)).block(keybinds_block);
let vertical = Layout::default() let vertical = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
Constraint::Percentage(100), Constraint::Percentage(0),
Constraint::Min(2 + SHORTCUT_LINES as u16), Constraint::Max(n_lines as u16 + 2),
]) ])
.flex(Flex::Legacy)
.margin(0) .margin(0)
.split(frame.area()); .split(frame.area());
@ -220,7 +316,7 @@ impl AppState {
|ListEntry { |ListEntry {
node, has_children, .. node, has_children, ..
}| { }| {
let is_selected = self.selected_commands.contains(&node.command); let is_selected = self.selected_commands.contains(node);
let (indicator, style) = if is_selected { let (indicator, style) = if is_selected {
(self.theme.multi_select_icon(), Style::default().bold()) (self.theme.multi_select_icon(), Style::default().bold())
} else { } else {
@ -301,20 +397,24 @@ impl AppState {
frame.render_stateful_widget(disclaimer_list, list_chunks[1], &mut self.selection); frame.render_stateful_widget(disclaimer_list, list_chunks[1], &mut self.selection);
if let Focus::FloatingWindow(float) = &mut self.focus { match &mut self.focus {
float.draw(frame, chunks[1]); Focus::FloatingWindow(float) => float.draw(frame, chunks[1]),
Focus::ConfirmationPrompt(prompt) => prompt.draw(frame, chunks[1]),
_ => {}
} }
draw_shortcuts(self, frame, vertical[1]); frame.render_widget(keybind_para, vertical[1]);
} }
pub fn handle_key(&mut self, key: &KeyEvent) -> bool { pub fn handle_key(&mut self, key: &KeyEvent) -> bool {
// This should be defined first to allow closing // This should be defined first to allow closing
// the application even when not drawable ( If terminal is small ) // the application even when not drawable ( If terminal is small )
// Exit on 'q' or 'Ctrl-c' input // Exit on 'q' or 'Ctrl-c' input
if matches!(self.focus, Focus::TabList | Focus::List) if matches!(
&& (key.code == KeyCode::Char('q') self.focus,
|| key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c')) Focus::TabList | Focus::List | Focus::ConfirmationPrompt(_)
) && (key.code == KeyCode::Char('q')
|| key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c'))
{ {
return false; return false;
} }
@ -355,11 +455,29 @@ impl AppState {
self.focus = Focus::List; self.focus = Focus::List;
} }
} }
Focus::ConfirmationPrompt(confirm) => {
confirm.content.handle_key_event(key);
match confirm.content.status {
ConfirmStatus::Abort => {
self.focus = Focus::List;
// selected command was pushed to selection list if multi-select was
// enabled, need to clear it to prevent state corruption
if !self.multi_select {
self.selected_commands.clear()
}
}
ConfirmStatus::Confirm => self.handle_confirm_command(),
ConfirmStatus::None => {}
}
}
Focus::Search => match self.filter.handle_key(key) { Focus::Search => match self.filter.handle_key(key) {
SearchAction::Exit => self.exit_search(), SearchAction::Exit => self.exit_search(),
SearchAction::Update => self.update_items(), SearchAction::Update => self.update_items(),
SearchAction::None => {} SearchAction::None => {}
}, },
Focus::TabList => match key.code { Focus::TabList => match key.code {
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.focus = Focus::List, KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.focus = Focus::List,
@ -381,19 +499,14 @@ impl AppState {
KeyCode::Char('g') => self.toggle_task_list_guide(), KeyCode::Char('g') => self.toggle_task_list_guide(),
_ => {} _ => {}
}, },
Focus::List if key.kind != KeyEventKind::Release => match key.code { Focus::List if key.kind != KeyEventKind::Release => match key.code {
KeyCode::Char('j') | KeyCode::Down => self.selection.select_next(), KeyCode::Char('j') | KeyCode::Down => self.selection.select_next(),
KeyCode::Char('k') | KeyCode::Up => self.selection.select_previous(), KeyCode::Char('k') | KeyCode::Up => self.selection.select_previous(),
KeyCode::Char('p') | KeyCode::Char('P') => self.enable_preview(), KeyCode::Char('p') | KeyCode::Char('P') => self.enable_preview(),
KeyCode::Char('d') | KeyCode::Char('D') => self.enable_description(), KeyCode::Char('d') | KeyCode::Char('D') => self.enable_description(),
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.handle_enter(), KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.handle_enter(),
KeyCode::Char('h') | KeyCode::Left => { KeyCode::Char('h') | KeyCode::Left => self.go_back(),
if self.at_root() {
self.focus = Focus::TabList;
} else {
self.enter_parent_directory();
}
}
KeyCode::Char('/') => self.enter_search(), KeyCode::Char('/') => self.enter_search(),
KeyCode::Char('t') => self.theme.next(), KeyCode::Char('t') => self.theme.next(),
KeyCode::Char('T') => self.theme.prev(), KeyCode::Char('T') => self.theme.prev(),
@ -402,10 +515,12 @@ impl AppState {
KeyCode::Char(' ') if self.multi_select => self.toggle_selection(), KeyCode::Char(' ') if self.multi_select => self.toggle_selection(),
_ => {} _ => {}
}, },
_ => (), _ => (),
}; };
true true
} }
fn toggle_multi_select(&mut self) { fn toggle_multi_select(&mut self) {
if self.is_current_tab_multi_selectable() { if self.is_current_tab_multi_selectable() {
self.multi_select = !self.multi_select; self.multi_select = !self.multi_select;
@ -414,21 +529,24 @@ impl AppState {
} }
} }
} }
fn toggle_selection(&mut self) { fn toggle_selection(&mut self) {
if let Some(list_node) = self.get_selected_list_node() { if let Some(command) = self.get_selected_node() {
if self.selected_commands.contains(&list_node.command) { if self.selected_commands.contains(&command) {
self.selected_commands.retain(|c| c != &list_node.command); self.selected_commands.retain(|c| c != &command);
} else { } else {
self.selected_commands.push(list_node.command); self.selected_commands.push(command);
} }
} }
} }
pub fn is_current_tab_multi_selectable(&self) -> bool { pub fn is_current_tab_multi_selectable(&self) -> bool {
let index = self.current_tab.selected().unwrap_or(0); let index = self.current_tab.selected().unwrap_or(0);
self.tabs self.tabs
.get(index) .get(index)
.map_or(false, |tab| tab.multi_selectable) .map_or(false, |tab| tab.multi_selectable)
} }
fn update_items(&mut self) { fn update_items(&mut self) {
self.filter.update_items( self.filter.update_items(
&self.tabs, &self.tabs,
@ -448,12 +566,21 @@ impl AppState {
self.visit_stack.len() == 1 self.visit_stack.len() == 1
} }
fn go_back(&mut self) {
if self.at_root() {
self.focus = Focus::TabList;
} else {
self.enter_parent_directory();
}
}
fn enter_parent_directory(&mut self) { fn enter_parent_directory(&mut self) {
self.visit_stack.pop(); self.visit_stack.pop();
self.selection.select(Some(0)); self.selection.select(Some(0));
self.update_items(); self.update_items();
} }
fn get_selected_node(&self) -> Option<&ListNode> {
fn get_selected_node(&self) -> Option<Rc<ListNode>> {
let mut selected_index = self.selection.selected().unwrap_or(0); let mut selected_index = self.selection.selected().unwrap_or(0);
if !self.at_root() && selected_index == 0 { if !self.at_root() && selected_index == 0 {
@ -465,14 +592,17 @@ impl AppState {
if let Some(item) = self.filter.item_list().get(selected_index) { if let Some(item) = self.filter.item_list().get(selected_index) {
if !item.has_children { if !item.has_children {
return Some(&item.node); return Some(item.node.clone());
} }
} }
None None
} }
pub fn get_selected_list_node(&self) -> Option<ListNode> {
self.get_selected_node().cloned() fn get_selected_description(&self) -> Option<String> {
self.get_selected_node()
.map(|node| node.description.clone())
} }
pub fn go_to_selected_dir(&mut self) { pub fn go_to_selected_dir(&mut self) {
let mut selected_index = self.selection.selected().unwrap_or(0); let mut selected_index = self.selection.selected().unwrap_or(0);
@ -493,6 +623,7 @@ impl AppState {
} }
} }
} }
pub fn selected_item_is_dir(&self) -> bool { pub fn selected_item_is_dir(&self) -> bool {
let mut selected_index = self.selection.selected().unwrap_or(0); let mut selected_index = self.selection.selected().unwrap_or(0);
@ -512,15 +643,18 @@ impl AppState {
pub fn selected_item_is_cmd(&self) -> bool { pub fn selected_item_is_cmd(&self) -> bool {
// Any item that is not a directory or up directory (..) must be a command // Any item that is not a directory or up directory (..) must be a command
!(self.selected_item_is_up_dir() || self.selected_item_is_dir()) self.selection.selected().is_some()
&& !(self.selected_item_is_up_dir() || self.selected_item_is_dir())
} }
pub fn selected_item_is_up_dir(&self) -> bool { pub fn selected_item_is_up_dir(&self) -> bool {
let selected_index = self.selection.selected().unwrap_or(0); let selected_index = self.selection.selected().unwrap_or(0);
!self.at_root() && selected_index == 0 !self.at_root() && selected_index == 0
} }
fn enable_preview(&mut self) { fn enable_preview(&mut self) {
if let Some(list_node) = self.get_selected_list_node() { if let Some(list_node) = self.get_selected_node() {
let mut preview_title = "[Preview] - ".to_string(); let mut preview_title = "[Preview] - ".to_string();
preview_title.push_str(list_node.name.as_str()); preview_title.push_str(list_node.name.as_str());
if let Some(preview) = FloatingText::from_command(&list_node.command, preview_title) { if let Some(preview) = FloatingText::from_command(&list_node.command, preview_title) {
@ -528,9 +662,10 @@ impl AppState {
} }
} }
} }
fn enable_description(&mut self) { fn enable_description(&mut self) {
if let Some(list_node) = self.get_selected_list_node() { if let Some(command_description) = self.get_selected_description() {
let description = FloatingText::new(list_node.description, "Command Description"); let description = FloatingText::new(command_description, "Command Description");
self.spawn_float(description, 80, 80); self.spawn_float(description, 80, 80);
} }
} }
@ -538,31 +673,53 @@ impl AppState {
fn handle_enter(&mut self) { fn handle_enter(&mut self) {
if self.selected_item_is_cmd() { if self.selected_item_is_cmd() {
if self.selected_commands.is_empty() { if self.selected_commands.is_empty() {
if let Some(list_node) = self.get_selected_list_node() { if let Some(node) = self.get_selected_node() {
self.selected_commands.push(list_node.command); self.selected_commands.push(node);
} }
} }
let command = RunningCommand::new(self.selected_commands.clone());
self.spawn_float(command, 80, 80); let cmd_names = self
self.selected_commands.clear(); .selected_commands
.iter()
.map(|node| node.name.as_str())
.collect::<Vec<_>>();
let prompt = ConfirmPrompt::new(&cmd_names[..]);
self.focus = Focus::ConfirmationPrompt(Float::new(Box::new(prompt), 40, 40));
} else { } else {
self.go_to_selected_dir(); self.go_to_selected_dir();
} }
} }
fn handle_confirm_command(&mut self) {
let commands = self
.selected_commands
.iter()
.map(|node| node.command.clone())
.collect();
let command = RunningCommand::new(commands);
self.spawn_float(command, 80, 80);
self.selected_commands.clear();
}
fn spawn_float<T: FloatContent + 'static>(&mut self, float: T, width: u16, height: u16) { fn spawn_float<T: FloatContent + 'static>(&mut self, float: T, width: u16, height: u16) {
self.focus = Focus::FloatingWindow(Float::new(Box::new(float), width, height)); self.focus = Focus::FloatingWindow(Float::new(Box::new(float), width, height));
} }
fn enter_search(&mut self) { fn enter_search(&mut self) {
self.focus = Focus::Search; self.focus = Focus::Search;
self.filter.activate_search(); self.filter.activate_search();
self.selection.select(None); self.selection.select(None);
} }
fn exit_search(&mut self) { fn exit_search(&mut self) {
self.selection.select(Some(0)); self.selection.select(Some(0));
self.focus = Focus::List; self.focus = Focus::List;
self.filter.deactivate_search(); self.filter.deactivate_search();
self.update_items(); self.update_items();
} }
fn refresh_tab(&mut self) { fn refresh_tab(&mut self) {
self.visit_stack = vec![self.tabs[self.current_tab.selected().unwrap()] self.visit_stack = vec![self.tabs[self.current_tab.selected().unwrap()]
.tree .tree