diff --git a/Cargo.lock b/Cargo.lock index e05cbcd0..a87ca14f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,21 +20,6 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstream" version = "0.6.14" @@ -114,12 +99,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "cassowary" version = "0.3.0" @@ -135,32 +114,12 @@ dependencies = [ "rustversion", ] -[[package]] -name = "cc" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052" - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.52.5", -] - [[package]] name = "clap" version = "4.5.16" @@ -220,12 +179,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - [[package]] name = "crossterm" version = "0.27.0" @@ -327,29 +280,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "include_dir" version = "0.7.4" @@ -409,15 +339,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -502,15 +423,6 @@ dependencies = [ "pin-utils", ] -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -959,7 +871,6 @@ dependencies = [ name = "tui" version = "0.1.0" dependencies = [ - "chrono", "clap", "crossterm", "ego-tree", @@ -971,6 +882,7 @@ dependencies = [ "tempdir", "toml", "tui-term", + "unicode-width", "which", ] @@ -1064,60 +976,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - [[package]] name = "which" version = "6.0.3" @@ -1152,15 +1010,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.5", -] - [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 5dd60034..f146e377 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -chrono = "0.4.33" clap = { version = "4.5.16", features = ["derive"] } crossterm = "0.27.0" ego-tree = "0.6.2" @@ -17,7 +16,22 @@ tempdir = "0.3.7" serde = { version = "1.0.205", features = ["derive"] } toml = "0.8.19" which = "6.0.3" +unicode-width = "0.1.13" + +[build-dependencies] +chrono = "0.4.33" [[bin]] name = "linutil" path = "src/main.rs" + + + +[profile.release] +opt-level = 3 +debug = false +lto = true +codegen-units = 1 +panic = "abort" +strip = true +incremental = false \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..1a258e69 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +## Supported Versions + +It is recommended that you run the stable version as this is more tested and used by most. The dev branch is bleed-edge commits that are not well tested and aren't meant to be used in production environments + +| Version | Supported | +| ------- | ------------------ | +| latest | :white_check_mark: | +| dev | :x: | + +## Reporting a Vulnerability + +I'd recommend making an Issue for reporting a bug. If you would like privately submit the bug you can email me at contact@christitus.com diff --git a/build.rs b/build.rs index dc9c06dd..ee7af066 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,12 @@ fn main() { // Rebuild program if any file in commands directory changes. println!("cargo:rerun-if-changed=src/commands"); + // Rerun build script if any code is modified + println!("cargo:rerun-if-changed=src"); + + // Add current date as a variable to be displayed in the 'Linux Toolbox' text. + println!( + "cargo:rustc-env=BUILD_DATE={}", + chrono::Local::now().format("%Y-%m-%d") + ); } diff --git a/build/linutil b/build/linutil index d0e35f11..bc011cb2 100755 Binary files a/build/linutil and b/build/linutil differ diff --git a/src/commands/applications-setup/dwmtitus-setup.sh b/src/commands/applications-setup/dwmtitus-setup.sh index 548d9073..6a1fb162 100644 --- a/src/commands/applications-setup/dwmtitus-setup.sh +++ b/src/commands/applications-setup/dwmtitus-setup.sh @@ -5,7 +5,6 @@ makeDWM() { cd "$HOME" && git clone https://github.com/ChrisTitusTech/dwm-titus.git # CD to Home directory to install dwm-titus # This path can be changed (e.g. to linux-toolbox directory) cd dwm-titus/ # Hardcoded path, maybe not the best. - $ESCALATION_TOOL ./setup.sh # Run setup $ESCALATION_TOOL make clean install # Run make clean install } @@ -13,15 +12,294 @@ setupDWM() { echo "Installing DWM-Titus if not already installed" case "$PACKAGER" in # Install pre-Requisites pacman) - $ESCALATION_TOOL "$PACKAGER" -S --needed --noconfirm base-devel libx11 libxinerama libxft imlib2 + $ESCALATION_TOOL "$PACKAGER" -S --needed --noconfirm base-devel libx11 libxinerama libxft imlib2 libxcb + ;; + apt) + $ESCALATION_TOOL "$PACKAGER" install -y build-essential libx11-dev libxinerama-dev libxft-dev libimlib2-dev libxcb1-dev + ;; + dnf) + $ESCALATION_TOOL "$PACKAGER" groupinstall -y "Development Tools" + $ESCALATION_TOOL "$PACKAGER" install -y libx11-devel libxinerama-devel libxft-devel imlib2-devel libxcb-devel ;; *) - $ESCALATION_TOOL "$PACKAGER" install -y build-essential libx11-dev libxinerama-dev libxft-dev libimlib2-dev + echo "Unsupported package manager: $PACKAGER" + exit 1 ;; esac } +install_nerd_font() { + FONT_DIR="$HOME/.local/share/fonts" + FONT_ZIP="$FONT_DIR/Meslo.zip" + FONT_URL="https://github.com/ryanoasis/nerd-fonts/releases/latest/download/Meslo.zip" + FONT_INSTALLED=$(fc-list | grep -i "Meslo") + + # Check if Meslo Nerd-font is already installed + if [ -n "$FONT_INSTALLED" ]; then + echo "Meslo Nerd-fonts are already installed." + return 0 + fi + + echo "Installing Meslo Nerd-fonts" + + # Create the fonts directory if it doesn't exist + if [ ! -d "$FONT_DIR" ]; then + mkdir -p "$FONT_DIR" || { + echo "Failed to create directory: $FONT_DIR" + return 1 + } + else + echo "$FONT_DIR exists, skipping creation." + fi + + # Check if the font zip file already exists + if [ ! -f "$FONT_ZIP" ]; then + # Download the font zip file + wget -P "$FONT_DIR" "$FONT_URL" || { + echo "Failed to download Meslo Nerd-fonts from $FONT_URL" + return 1 + } + else + echo "Meslo.zip already exists in $FONT_DIR, skipping download." + fi + + # Unzip the font file if it hasn't been unzipped yet + if [ ! -d "$FONT_DIR/Meslo" ]; then + unzip "$FONT_ZIP" -d "$FONT_DIR" || { + echo "Failed to unzip $FONT_ZIP" + return 1 + } + else + echo "Meslo font files already unzipped in $FONT_DIR, skipping unzip." + fi + + # Remove the zip file + rm "$FONT_ZIP" || { + echo "Failed to remove $FONT_ZIP" + return 1 + } + + # Rebuild the font cache + fc-cache -fv || { + echo "Failed to rebuild font cache" + return 1 + } + + echo "Meslo Nerd-fonts installed successfully" +} + +picom_animations() { + # Clone the repository in the home/build directory + mkdir -p ~/build + if [ ! -d ~/build/picom ]; then + if ! git clone https://github.com/FT-Labs/picom.git ~/build/picom; then + echo "Failed to clone the repository" + return 1 + fi + else + echo "Repository already exists, skipping clone" + fi + + cd ~/build/picom || { echo "Failed to change directory to picom"; return 1; } + + # Build the project + if ! meson setup --buildtype=release build; then + echo "Meson setup failed" + return 1 + fi + + if ! ninja -C build; then + echo "Ninja build failed" + return 1 + fi + + # Install the built binary + if ! sudo ninja -C build install; then + echo "Failed to install the built binary" + return 1 + fi + + echo "Picom animations installed successfully" +} + +clone_config_folders() { + # Ensure the target directory exists + [ ! -d ~/.config ] && mkdir -p ~/.config + + # Iterate over all directories in config/* + for dir in config/*/; do + # Extract the directory name + dir_name=$(basename "$dir") + + # Clone the directory to ~/.config/ + if [ -d "$dir" ]; then + cp -r "$dir" ~/.config/ + echo "Cloned $dir_name to ~/.config/" + else + echo "Directory $dir_name does not exist, skipping" + fi + done +} + +configure_backgrounds() { + # Set the variable BG_DIR to the path where backgrounds will be stored + BG_DIR="$HOME/Pictures/backgrounds" + + # Check if the ~/Pictures directory exists + if [ ! -d "~/Pictures" ]; then + # If it doesn't exist, print an error message and return with a status of 1 (indicating failure) + echo "Pictures directory does not exist" + mkdir ~/Pictures + echo "Directory was created in Home folder" + fi + + # Check if the backgrounds directory (BG_DIR) exists + if [ ! -d "$BG_DIR" ]; then + # If the backgrounds directory doesn't exist, attempt to clone a repository containing backgrounds + if ! git clone https://github.com/ChrisTitusTech/nord-background.git ~/Pictures; then + # If the git clone command fails, print an error message and return with a status of 1 + echo "Failed to clone the repository" + return 1 + fi + # Rename the cloned directory to 'backgrounds' + mv ~/Pictures/nord-background ~/Pictures/backgrounds + # Print a success message indicating that the backgrounds have been downloaded + echo "Downloaded desktop backgrounds to $BG_DIR" + else + # If the backgrounds directory already exists, print a message indicating that the download is being skipped + echo "Path $BG_DIR exists for desktop backgrounds, skipping download of backgrounds" + fi +} + +setupDisplayManager() { + echo "Setting up Xorg" + case "$PACKAGER" in + pacman) + $ESCALATION_TOOL "$PACKAGER" -S --needed --noconfirm xorg-xinit xorg-server + ;; + apt) + $ESCALATION_TOOL "$PACKAGER" install -y xorg xinit + ;; + dnf) + $ESCALATION_TOOL "$PACKAGER" install -y xorg-x11-xinit xorg-x11-server-Xorg + ;; + *) + echo "Unsupported package manager: $PACKAGER" + exit 1 + ;; + esac + echo "Xorg installed successfully" + echo "Setting up Display Manager" + currentdm="none" + for dm in gdm sddm lightdm; do + if systemctl is-active --quiet $dm.service; then + currentdm=$dm + break + fi + done + echo "Current display manager: $currentdm" + if [ "$currentdm" = "none" ]; then + DM="sddm" + echo "No display manager found, installing $DM" + case "$PACKAGER" in + pacman) + $ESCALATION_TOOL "$PACKAGER" -S --needed --noconfirm $DM + ;; + apt) + $ESCALATION_TOOL "$PACKAGER" install -y $DM + ;; + dnf) + $ESCALATION_TOOL "$PACKAGER" install -y $DM + ;; + *) + echo "Unsupported package manager: $PACKAGER" + exit 1 + ;; + esac + echo "$DM installed successfully" + systemctl enable $DM + + # Clear the screen + clear + + # Prompt user for auto-login + echo "Do you want to enable auto-login?" + echo "Use arrow keys or j/k to navigate, Enter to select" + options=("Yes" "No") + selected=0 + + # Function to print menu + print_menu() { + for i in "${!options[@]}"; do + if [ $i -eq $selected ]; then + echo "> ${options[$i]}" + else + echo " ${options[$i]}" + fi + done + } + + # Handle user input + while true; do + print_menu + read -rsn1 key + case "$key" in + $'\x1B') # ESC sequence for arrow keys + read -rsn2 key + case "$key" in + '[A' | 'k') ((selected > 0)) && ((selected--));; # Up arrow or k + '[B' | 'j') ((selected < ${#options[@]}-1)) && ((selected++));; # Down arrow or j + esac + ;; + '') break;; # Enter key + esac + clear + done + + if [ "${options[$selected]}" = "Yes" ]; then + echo "Configuring SDDM for autologin" + SDDM_CONF="/etc/sddm.conf" + if [ ! -f "$SDDM_CONF" ]; then + echo "[Autologin]" | sudo tee -a "$SDDM_CONF" + echo "User=$USER" | sudo tee -a "$SDDM_CONF" + echo "Session=dwm" | sudo tee -a "$SDDM_CONF" + else + sudo sed -i '/^\[Autologin\]/d' "$SDDM_CONF" + sudo sed -i '/^User=/d' "$SDDM_CONF" + sudo sed -i '/^Session=/d' "$SDDM_CONF" + echo "[Autologin]" | sudo tee -a "$SDDM_CONF" + echo "User=$USER" | sudo tee -a "$SDDM_CONF" + echo "Session=dwm" | sudo tee -a "$SDDM_CONF" + fi + echo "Checking if autologin group exists" + if ! getent group autologin > /dev/null; then + echo "Creating autologin group" + sudo groupadd autologin + else + echo "Autologin group already exists" + fi + echo "Adding user with UID 1000 to autologin group" + USER_UID_1000=$(getent passwd 1000 | cut -d: -f1) + if [ -n "$USER_UID_1000" ]; then + sudo usermod -aG autologin "$USER_UID_1000" + echo "User $USER_UID_1000 added to autologin group" + else + echo "No user with UID 1000 found - Auto login not possible" + fi + else + echo "Auto-login configuration skipped" + fi + fi + + + +} + checkEnv checkEscalationTool +setupDisplayManager setupDWM makeDWM +install_nerd_font +clone_config_folders +configure_backgrounds \ No newline at end of file diff --git a/src/commands/gaming/diablo-ii/d2r-loot-filters.sh b/src/commands/gaming/diablo-ii/d2r-loot-filters.sh new file mode 100755 index 00000000..1ec0ca63 --- /dev/null +++ b/src/commands/gaming/diablo-ii/d2r-loot-filters.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +# Search for possible Diablo II Resurrected folder locations +echo "Searching for Diablo II Resurrected folders..." +possible_paths=$(find $HOME -type d -path "*/drive_c/Program Files (x86)/Diablo II Resurrected" 2>/dev/null) + +if [ -z "$possible_paths" ]; then + echo "Error: No Diablo II Resurrected folders found." + exit 1 +fi + +# Display possible paths and allow selection +echo "Possible Diablo II Resurrected folder locations:" +mapfile -t paths_array <<< "$possible_paths" +selected=0 +total=${#paths_array[@]} + +print_menu() { + clear + local max_display=$((total < 10 ? total : 10)) + local start=$((selected - max_display/2)) + if ((start < 0)); then start=0; fi + if ((start + max_display > total)); then start=$((total - max_display)); fi + if ((start < 0)); then start=0; fi + + echo "Please select the Diablo II: Resurrected installation path:" + for i in $(seq 0 $((max_display - 1))); do + if ((i + start >= total)); then break; fi + if [ $((i + start)) -eq $selected ]; then + echo "> ${paths_array[$((i + start))]}" + else + echo " ${paths_array[$((i + start))]}" + fi + done +} + +select_path() { + local last_selected=-1 + + while true; do + if [ $last_selected -ne $selected ]; then + print_menu + last_selected=$selected + fi + + read -rsn1 key + case "$key" in + $'\x1B') # ESC key + read -rsn2 key + case "$key" in + '[A' | 'k') + if ((selected > 0)); then + ((selected--)) + fi + ;; + '[B' | 'j') + if ((selected < total - 1)); then + ((selected++)) + fi + ;; + esac + ;; + '') # Enter key + d2r_path="${paths_array[$selected]}" + break + ;; + esac + done + + clear # Clear the screen after selection +} + +# Use the select_path function +select_path + +# Validate the path +if [ ! -d "$d2r_path" ]; then + echo "Error: The specified path does not exist." + exit 1 +fi + +# Create the mods folder if it doesn't exist +mods_path="$d2r_path/mods" +mkdir -p "$mods_path" + +# Download the latest release +echo "Downloading the latest loot filter..." +wget -q --show-progress https://github.com/ChrisTitusTech/d2r-loot-filter/releases/latest/download/lootfilter.zip -O /tmp/lootfilter.zip + +# Extract the contents to the mods folder +echo "Extracting loot filter to $mods_path..." +unzip -q -o /tmp/lootfilter.zip -d "$mods_path" + +# Clean up +rm /tmp/lootfilter.zip + +echo "Loot filter installed successfully in $mods_path" + +# Add instructions for setting launch options +echo +echo "To complete the setup, please follow these steps to add launch options in Battle.net:" +echo "1. Open the Battle.net launcher" +echo "2. Select Diablo II: Resurrected" +echo "3. Click the gear icon next to the 'Play' button" +echo "4. Select 'Game Settings'" +echo "5. In the 'Additional command line arguments' field, enter: -mod lootfilter -txt" +echo "6. Click 'Done' to save the changes" +echo +echo "After completing these steps, launch Diablo II: Resurrected through Battle.net to use the loot filter." \ No newline at end of file diff --git a/src/commands/gaming/tab_data.toml b/src/commands/gaming/tab_data.toml new file mode 100644 index 00000000..ba997c8c --- /dev/null +++ b/src/commands/gaming/tab_data.toml @@ -0,0 +1,8 @@ +name = "Gaming" + +[[data]] +name = "Diablo II Ressurected" + +[[data.entries]] +name = "Loot Filter Install" +script = "diablo-ii/d2r-loot-filters.sh" diff --git a/src/commands/system-setup/system-update.sh b/src/commands/system-setup/system-update.sh index f72e613b..b53db1c2 100755 --- a/src/commands/system-setup/system-update.sh +++ b/src/commands/system-setup/system-update.sh @@ -5,29 +5,39 @@ fastUpdate() { case ${PACKAGER} in pacman) + checkAURHelper $AUR_HELPER -S --needed --noconfirm rate-mirrors-bin + if [ -s /etc/pacman.d/mirrorlist ]; then $ESCALATION_TOOL cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak fi - + # If for some reason DTYPE is still unknown use always arch so the rate-mirrors does not fail dtype_local=${DTYPE} if [ "${DTYPE}" = "unknown" ]; then dtype_local="arch" fi + $ESCALATION_TOOL rate-mirrors --top-mirrors-number-to-retest=5 --disable-comments --save /etc/pacman.d/mirrorlist --allow-root ${dtype_local} + if [ $? -ne 0 ] || [ ! -s /etc/pacman.d/mirrorlist ]; then + echo -e "${RED}Rate-mirrors failed, restoring backup.${RC}" + $ESCALATION_TOOL cp /etc/pacman.d/mirrorlist.bak /etc/pacman.d/mirrorlist + fi ;; + apt-get|nala) $ESCALATION_TOOL apt-get update if ! command_exists nala; then $ESCALATION_TOOL apt-get install -y nala || { echo -e "${YELLOW}Falling back to apt-get${RC}"; PACKAGER="apt-get"; } fi + if [ "${PACKAGER}" = "nala" ]; then $ESCALATION_TOOL cp /etc/apt/sources.list /etc/apt/sources.list.bak $ESCALATION_TOOL nala update PACKAGER="nala" fi + $ESCALATION_TOOL ${PACKAGER} upgrade -y ;; dnf) @@ -90,4 +100,4 @@ checkEnv checkEscalationTool fastUpdate updateSystem -updateFlatpaks +updateFlatpaks \ No newline at end of file diff --git a/src/commands/tabs.toml b/src/commands/tabs.toml index ab72befa..5b4fcca4 100644 --- a/src/commands/tabs.toml +++ b/src/commands/tabs.toml @@ -1 +1,7 @@ -directories = ["system-setup", "applications-setup", "security", "utils"] +directories = [ + "applications-setup", + "gaming", + "security", + "system-setup", + "utils" +] diff --git a/src/commands/utils/bluetooth-control.sh b/src/commands/utils/bluetooth-control.sh index e5e02fc6..ddb97c65 100644 --- a/src/commands/utils/bluetooth-control.sh +++ b/src/commands/utils/bluetooth-control.sh @@ -1,4 +1,33 @@ -#!/bin/bash +#!/bin/sh -e + +. ../common-script.sh + +# Function to check bluetoothctl is installed +setupBluetooth() { + echo "Install bluetoothctl if not already installed..." + if ! command_exists bluetoothctl; then + case ${PACKAGER} in + pacman) + $ESCALATION_TOOL "${PACKAGER}" -S --noconfirm bluez-utils + ;; + *) + $ESCALATION_TOOL "${PACKAGER}" install -y bluez + ;; + esac + else + echo "Bluetoothctl is already installed." + fi + + # Check if bluetooth service is running + if ! systemctl is-active --quiet bluetooth; then + echo "Bluetooth service is not running. Starting it now..." + $ESCALATION_TOOL systemctl start bluetooth + + if systemctl is-active --quiet bluetooth; then + echo "bluetooth service started successfully." + fi + fi +} # Function to display colored text colored_echo() { @@ -128,4 +157,6 @@ remove_device() { } # Initialize +checkEnv +setupBluetooth main_menu diff --git a/src/commands/utils/monitor-control/auto_detect_displays.sh b/src/commands/utils/monitor-control/auto_detect_displays.sh index a86ddfc1..86fcdcd9 100755 --- a/src/commands/utils/monitor-control/auto_detect_displays.sh +++ b/src/commands/utils/monitor-control/auto_detect_displays.sh @@ -1,5 +1,6 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh # Function to auto-detect displays and set common resolution auto_detect_displays() { diff --git a/src/commands/utils/monitor-control/change_orientation.sh b/src/commands/utils/monitor-control/change_orientation.sh index 93927149..4ec802d4 100755 --- a/src/commands/utils/monitor-control/change_orientation.sh +++ b/src/commands/utils/monitor-control/change_orientation.sh @@ -1,10 +1,11 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh # Function to change monitor orientation change_orientation() { monitor_list=$(detect_connected_monitors) - IFS=$'\n' read -r -d '' -a monitor_array <<<"$monitor_list" + IFS=$'\n' read -r -a monitor_array <<<"$monitor_list" clear echo -e "${BLUE}=========================================${RESET}" diff --git a/src/commands/utils/monitor-control/disable_monitor.sh b/src/commands/utils/monitor-control/disable_monitor.sh index f999d408..b455848b 100755 --- a/src/commands/utils/monitor-control/disable_monitor.sh +++ b/src/commands/utils/monitor-control/disable_monitor.sh @@ -1,5 +1,6 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh RESET='\033[0m' BOLD='\033[1m' @@ -12,7 +13,7 @@ CYAN='\033[36m' # Function to disable a monitor disable_monitor() { monitor_list=$(detect_connected_monitors) - IFS=$'\n' read -r -d '' -a monitor_array <<<"$monitor_list" + IFS=$'\n' read -r -a monitor_array <<<"$monitor_list" clear echo -e "${BLUE}=========================================${RESET}" diff --git a/src/commands/utils/monitor-control/duplicate_displays.sh b/src/commands/utils/monitor-control/duplicate_displays.sh index 4332a0f9..c5cdf01e 100755 --- a/src/commands/utils/monitor-control/duplicate_displays.sh +++ b/src/commands/utils/monitor-control/duplicate_displays.sh @@ -1,15 +1,16 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh # Function to duplicate displays duplicate_displays() { - primary=$(detect_connected_monitors | head -n 1) - for monitor in $(detect_connected_monitors | tail -n +2); do - if confirm_action "Duplicate $monitor to $primary?"; then - echo "Duplicating $monitor to $primary" - execute_command "xrandr --output $monitor --same-as $primary" - fi - done + primary=$(detect_connected_monitors | head -n 1) + for monitor in $(detect_connected_monitors | tail -n +2); do + if confirm_action "Duplicate $monitor to $primary?"; then + echo "Duplicating $monitor to $primary" + execute_command "xrandr --output $monitor --same-as $primary" + fi + done } duplicate_displays diff --git a/src/commands/utils/monitor-control/enable_monitor.sh b/src/commands/utils/monitor-control/enable_monitor.sh index bf770053..3525771f 100755 --- a/src/commands/utils/monitor-control/enable_monitor.sh +++ b/src/commands/utils/monitor-control/enable_monitor.sh @@ -1,5 +1,6 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh RESET='\033[0m' BOLD='\033[1m' @@ -12,7 +13,7 @@ CYAN='\033[36m' # Function to enable a monitor enable_monitor() { monitor_list=$(detect_connected_monitors) - IFS=$'\n' read -r -d '' -a monitor_array <<<"$monitor_list" + IFS=$'\n' read -r -a monitor_array <<<"$monitor_list" clear echo -e "${BLUE}=========================================${RESET}" diff --git a/src/commands/utils/monitor-control/extend_displays.sh b/src/commands/utils/monitor-control/extend_displays.sh index d93b2eb3..4e215bfa 100755 --- a/src/commands/utils/monitor-control/extend_displays.sh +++ b/src/commands/utils/monitor-control/extend_displays.sh @@ -1,5 +1,6 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh # Function to extend displays extend_displays() { diff --git a/src/commands/utils/monitor-control/manage_arrangement.sh b/src/commands/utils/monitor-control/manage_arrangement.sh index 305e5210..19049814 100755 --- a/src/commands/utils/monitor-control/manage_arrangement.sh +++ b/src/commands/utils/monitor-control/manage_arrangement.sh @@ -1,10 +1,11 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh # Function to manage monitor arrangement manage_arrangement() { monitor_list=$(detect_connected_monitors) - IFS=$'\n' read -r -d '' -a monitor_array <<<"$monitor_list" + IFS=$'\n' read -r -a monitor_array <<<"$monitor_list" clear echo -e "${BLUE}=========================================${RESET}" diff --git a/src/commands/utils/monitor-control/reset_scaling.sh b/src/commands/utils/monitor-control/reset_scaling.sh index 3a80e856..81308065 100755 --- a/src/commands/utils/monitor-control/reset_scaling.sh +++ b/src/commands/utils/monitor-control/reset_scaling.sh @@ -1,5 +1,6 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh # Function to reset scaling back to 1 (native resolution) for all monitors reset_scaling() { @@ -8,7 +9,7 @@ reset_scaling() { echo -e "${BLUE}=========================================${RESET}" monitor_list=$(detect_connected_monitors) - IFS=$'\n' read -r -d '' -a monitor_array <<<"$monitor_list" + IFS=$'\n' read -r -a monitor_array <<<"$monitor_list" for monitor in "${monitor_array[@]}"; do echo -e "${CYAN}Resetting scaling for $monitor to 1x1 (native resolution)${RESET}" diff --git a/src/commands/utils/monitor-control/scale_monitor.sh b/src/commands/utils/monitor-control/scale_monitor.sh index eb680187..708e1fb3 100755 --- a/src/commands/utils/monitor-control/scale_monitor.sh +++ b/src/commands/utils/monitor-control/scale_monitor.sh @@ -1,5 +1,6 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh # Function to scale smaller monitors to the highest resolution of a bigger monitor scale_monitors() { @@ -8,7 +9,7 @@ scale_monitors() { echo -e "${BLUE}=========================================${RESET}" monitor_list=$(detect_connected_monitors) - IFS=$'\n' read -r -d '' -a monitor_array <<<"$monitor_list" + IFS=$'\n' read -r -a monitor_array <<<"$monitor_list" # Get the highest resolution among all monitors max_width=0 diff --git a/src/commands/utils/monitor-control/set_primary_monitor.sh b/src/commands/utils/monitor-control/set_primary_monitor.sh index f10c8d34..752dc784 100755 --- a/src/commands/utils/monitor-control/set_primary_monitor.sh +++ b/src/commands/utils/monitor-control/set_primary_monitor.sh @@ -1,10 +1,11 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh # Function to set a monitor as primary set_primary_monitor() { monitor_list=$(detect_connected_monitors) - IFS=$'\n' read -r -d '' -a monitor_array <<<"$monitor_list" + IFS=$'\n' read -r -a monitor_array <<<"$monitor_list" clear echo -e "${BLUE}=========================================${RESET}" diff --git a/src/commands/utils/monitor-control/set_resolutions.sh b/src/commands/utils/monitor-control/set_resolutions.sh index 3f0e0fcd..2f3cb6a7 100755 --- a/src/commands/utils/monitor-control/set_resolutions.sh +++ b/src/commands/utils/monitor-control/set_resolutions.sh @@ -1,5 +1,6 @@ -#!/bin/bash -source ./utility_functions.sh +#!/bin/sh -e + +. ./utility_functions.sh RESET='\033[0m' BOLD='\033[1m' @@ -12,7 +13,7 @@ CYAN='\033[36m' # Function to set resolutions set_resolutions() { monitor_list=$(detect_connected_monitors) - IFS=$'\n' read -r -d '' -a monitor_array <<<"$monitor_list" + IFS=$'\n' read -r -a monitor_array <<<"$monitor_list" while true; do clear diff --git a/src/commands/utils/monitor-control/utility_functions.sh b/src/commands/utils/monitor-control/utility_functions.sh index 9ef00ed2..bb480390 100755 --- a/src/commands/utils/monitor-control/utility_functions.sh +++ b/src/commands/utils/monitor-control/utility_functions.sh @@ -1,4 +1,39 @@ -#!/bin/bash +#!/bin/sh -e + +. ../../common-script.sh + +# Function to check bluetoothctl is installed +setup_xrandr() { + echo "Install xrandr if not already installed..." + if ! command_exists xrandr; then + case ${PACKAGER} in + pacman) + $ESCALATION_TOOL "${PACKAGER}" -S --noconfirm xorg-xrandr + ;; + apt-get) + $ESCALATION_TOOL "${PACKAGER}" install -y x11-xserver-utils + ;; + *) + $ESCALATION_TOOL "${PACKAGER}" install -y xorg-x11-server-utils + ;; + esac + else + echo "xrandr is already installed." + fi +} + +# Function to display colored text +colored_echo() { + local color=$1 + local text=$2 + case $color in + red) echo -e "\033[31m$text\033[0m" ;; + green) echo -e "\033[32m$text\033[0m" ;; + yellow) echo -e "\033[33m$text\033[0m" ;; + blue) echo -e "\033[34m$text\033[0m" ;; + *) echo "$text" ;; + esac +} # Function to execute xrandr commands and handle errors execute_command() { @@ -47,3 +82,6 @@ confirm_action() { return 1 fi } + +checkEnv +setup_xrandr \ No newline at end of file diff --git a/src/commands/utils/numlock.sh b/src/commands/utils/numlock.sh index b1ac782a..2b23c3f1 100755 --- a/src/commands/utils/numlock.sh +++ b/src/commands/utils/numlock.sh @@ -1,12 +1,15 @@ #!/bin/sh -e +. ../common-script.sh + # setleds can be used in all distros # This method works by calling a script using systemd service # Create a script to toggle numlock + create_file() { echo "Creating script..." - sudo tee "/usr/local/bin/numlock" >/dev/null <<'EOF' + $ESCALATION_TOOL tee "/usr/local/bin/numlock" >/dev/null <<'EOF' #!/bin/bash for tty in /dev/tty{1..6} @@ -15,13 +18,13 @@ do done EOF - sudo chmod +x /usr/local/bin/numlock + $ESCALATION_TOOL chmod +x /usr/local/bin/numlock } # Create a systemd service to run the script on boot create_service() { echo "Creating service..." - sudo tee "/etc/systemd/system/numlock.service" >/dev/null <<'EOF' + $ESCALATION_TOOL tee "/etc/systemd/system/numlock.service" >/dev/null <<'EOF' [Unit] Description=numlock @@ -35,7 +38,7 @@ WantedBy=multi-user.target EOF } -main() { +numlockSetup() { # Check if the script and service files exists if [ ! -f "/usr/local/bin/numlock" ]; then create_file @@ -48,13 +51,14 @@ main() { printf "Do you want to enable Numlock on boot? (y/n): " read -r confirm if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then - sudo systemctl enable numlock.service --quiet + $ESCALATION_TOOL systemctl enable numlock.service --quiet echo "Numlock will be enabled on boot" else - sudo systemctl disable numlock.service --quiet + $ESCALATION_TOOL systemctl disable numlock.service --quiet echo "Numlock will not be enabled on boot" fi } -main +checkEscalationTool +numlockSetup diff --git a/src/commands/utils/tab_data.toml b/src/commands/utils/tab_data.toml index 209a4015..67ba893f 100644 --- a/src/commands/utils/tab_data.toml +++ b/src/commands/utils/tab_data.toml @@ -25,10 +25,6 @@ matches = true data = { environment = "DISPLAY" } values = [":0", ":1", ":2", ":3", ":4", ":5", ":6", ":7", ":8", ":9"] -[[data.entries]] -name = "Set Resolution" -script = "monitor-control/set_resolutions.sh" - [[data.entries]] name = "Duplicate Displays" script = "monitor-control/duplicate_displays.sh" diff --git a/src/commands/utils/wifi-control.sh b/src/commands/utils/wifi-control.sh index 1f80804d..739b127d 100644 --- a/src/commands/utils/wifi-control.sh +++ b/src/commands/utils/wifi-control.sh @@ -1,5 +1,36 @@ -@@ -0,0 +1,168 @@ -#!/bin/bash +#!/bin/sh -e + +. ../common-script.sh + +# Function to check if NetworkManager is installed +setupNetworkManager() { + echo "Install NetworkManger if not already installed..." + if ! command_exists nmcli; then + case ${PACKAGER} in + pacman) + $ESCALATION_TOOL "${PACKAGER}" -S --noconfirm networkmanager + ;; + dnf) + $ESCALATION_TOOL "${PACKAGER}" install -y NetworkManager-1 + ;; + *) + $ESCALATION_TOOL "${PACKAGER}" install -y network-manager + ;; + esac + else + echo "NetworkManager is already installed." + fi + + # Check if NetworkManager service is running + if ! systemctl is-active --quiet NetworkManager; then + echo "NetworkManager service is not running. Starting it now..." + $ESCALATION_TOOL systemctl start NetworkManager + + if systemctl is-active --quiet NetworkManager; then + echo "NetworkManager service started successfully." + fi + fi +} # Function to display colored text colored_echo() { @@ -166,4 +197,6 @@ remove_network() { } # Initialize +checkEnv +setupNetworkManager main_menu diff --git a/src/filter.rs b/src/filter.rs new file mode 100644 index 00000000..3e90af8f --- /dev/null +++ b/src/filter.rs @@ -0,0 +1,160 @@ +use crate::{state::ListEntry, tabs::Tab, theme::Theme}; +use crossterm::event::{KeyCode, KeyEvent}; +use ego_tree::NodeId; +use ratatui::{ + layout::Rect, + style::Style, + text::Span, + widgets::{Block, Borders, Paragraph}, + Frame, +}; +use unicode_width::UnicodeWidthChar; + +pub enum SearchAction { + None, + Exit, + Update, +} + +pub struct Filter { + search_input: Vec, + in_search_mode: bool, + input_position: usize, + items: Vec, +} + +impl Filter { + pub fn new() -> Self { + Self { + search_input: vec![], + in_search_mode: false, + input_position: 0, + items: vec![], + } + } + pub fn item_list(&self) -> &[ListEntry] { + &self.items + } + pub fn activate_search(&mut self) { + self.in_search_mode = true; + } + pub fn deactivate_search(&mut self) { + self.in_search_mode = false; + } + pub fn update_items(&mut self, tabs: &[Tab], current_tab: usize, node: NodeId) { + if self.search_input.is_empty() { + let curr = tabs[current_tab].tree.get(node).unwrap(); + + self.items = curr + .children() + .map(|node| ListEntry { + node: node.value().clone(), + id: node.id(), + has_children: node.has_children(), + }) + .collect(); + } else { + self.items.clear(); + + let query_lower = self.search_input.iter().collect::().to_lowercase(); + for tab in tabs.iter() { + let mut stack = vec![tab.tree.root().id()]; + while let Some(node_id) = stack.pop() { + let node = tab.tree.get(node_id).unwrap(); + + if node.value().name.to_lowercase().contains(&query_lower) + && !node.has_children() + { + self.items.push(ListEntry { + node: node.value().clone(), + id: node.id(), + has_children: false, + }); + } + + stack.extend(node.children().map(|child| child.id())); + } + } + self.items.sort_by(|a, b| a.node.name.cmp(&b.node.name)); + } + } + pub fn draw_searchbar(&self, frame: &mut Frame, area: Rect, theme: &Theme) { + //Set the search bar text (If empty use the placeholder) + let display_text = if !self.in_search_mode && self.search_input.is_empty() { + Span::raw("Press / to search") + } else { + Span::raw(self.search_input.iter().collect::()) + }; + + let search_color = if self.in_search_mode { + theme.focused_color() + } else { + theme.unfocused_color() + }; + + //Create the search bar widget + let search_bar = Paragraph::new(display_text) + .block(Block::default().borders(Borders::ALL).title("Search")) + .style(Style::default().fg(search_color)); + + //Render the search bar (First chunk of the screen) + frame.render_widget(search_bar, area); + + // Render cursor in search bar + if self.in_search_mode { + let cursor_position: usize = self.search_input[..self.input_position] + .iter() + .map(|c| c.width().unwrap_or(1)) + .sum(); + let x = area.x + cursor_position as u16 + 1; + let y = area.y + 1; + frame.set_cursor(x, y); + } + } + // Handles key events. Returns true if search must be exited + pub fn handle_key(&mut self, event: &KeyEvent) -> SearchAction { + //Insert user input into the search bar + match event.code { + KeyCode::Char(c) => self.insert_char(c), + KeyCode::Backspace => self.remove_previous(), + KeyCode::Delete => self.remove_next(), + KeyCode::Left => return self.cursor_left(), + KeyCode::Right => return self.cursor_right(), + KeyCode::Esc => { + self.input_position = 0; + self.search_input.clear(); + return SearchAction::Exit; + } + KeyCode::Enter => return SearchAction::Exit, + _ => return SearchAction::None, + }; + SearchAction::Update + } + fn cursor_left(&mut self) -> SearchAction { + self.input_position = self.input_position.saturating_sub(1); + SearchAction::None + } + fn cursor_right(&mut self) -> SearchAction { + if self.input_position < self.search_input.len() { + self.input_position += 1; + } + SearchAction::None + } + fn insert_char(&mut self, input: char) { + self.search_input.insert(self.input_position, input); + self.cursor_right(); + } + fn remove_previous(&mut self) { + let current = self.input_position; + if current > 0 { + self.search_input.remove(current - 1); + self.cursor_left(); + } + } + fn remove_next(&mut self) { + let current = self.input_position; + if current < self.search_input.len() { + self.search_input.remove(current); + } + } +} diff --git a/src/floating_text.rs b/src/floating_text.rs index 1dc8520e..482a8822 100644 --- a/src/floating_text.rs +++ b/src/floating_text.rs @@ -68,8 +68,18 @@ impl FloatContent for FloatingText { .text .iter() .skip(self.scroll) + .flat_map(|line| { + if line.is_empty() { + return vec![String::new()]; + } + line.chars() + .collect::>() + .chunks(inner_area.width as usize) + .map(|chunk| chunk.iter().collect()) + .collect::>() + }) .take(inner_area.height as usize) - .map(|line| Line::from(line.as_str())) + .map(Line::from) .collect(); // Create list widget diff --git a/src/main.rs b/src/main.rs index 0f3fb38f..85c0278f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod filter; mod float; mod floating_text; mod running_command; diff --git a/src/search.rs b/src/search.rs deleted file mode 100644 index 68827c24..00000000 --- a/src/search.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crossterm::event::{KeyCode, KeyEvent}; -use ratatui::{ - layout::Rect, - style::Style, - text::Span, - widgets::{Block, Borders, Paragraph}, - Frame, -}; - -use crate::state::AppState; - -pub struct SearchBar { - search_input: String, - in_search_mode: bool, -} - -impl SearchBar { - pub fn new() -> Self { - SearchBar { - search_input: String::new(), - in_search_mode: false, - } - } - - pub fn activate_search(&mut self) { - self.in_search_mode = true; - } - - pub fn deactivate_search(&mut self) { - self.in_search_mode = false; - } - - pub fn is_search_active(&self) -> bool { - self.in_search_mode - } - - pub fn draw(&self, frame: &mut Frame, area: Rect, state: &AppState) { - //Set the search bar text (If empty use the placeholder) - let display_text = if !self.in_search_mode && self.search_input.is_empty() { - Span::raw("Press / to search") - } else { - Span::raw(&self.search_input) - }; - - //Create the search bar widget - let mut search_bar = Paragraph::new(display_text) - .block(Block::default().borders(Borders::ALL).title("Search")) - .style(Style::default().fg(state.theme.unfocused_color)); - - //Change the color if in search mode - if self.in_search_mode { - search_bar = search_bar - .clone() - .style(Style::default().fg(state.theme.focused_color)); - } - - //Render the search bar (First chunk of the screen) - frame.render_widget(search_bar, area); - } - - pub fn handle_key(&mut self, event: KeyEvent) -> String { - //Insert user input into the search bar - match event.code { - KeyCode::Char(c) => { - self.search_input.push(c); - } - KeyCode::Backspace => { - self.search_input.pop(); - } - KeyCode::Esc => { - self.search_input = String::new(); - self.in_search_mode = false; - } - KeyCode::Enter => { - self.in_search_mode = false; - } - _ => {} - } - self.search_input.clone() - } -} diff --git a/src/state.rs b/src/state.rs index 66f99972..432a5af2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,5 @@ use crate::{ + filter::{Filter, SearchAction}, float::{Float, FloatContent}, floating_text::FloatingText, running_command::{Command, RunningCommand}, @@ -9,9 +10,9 @@ use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; use ego_tree::NodeId; use ratatui::{ layout::{Constraint, Direction, Layout}, - style::{Color, Style, Stylize}, - text::{Line, Span}, - widgets::{Block, Borders, List, ListState, Paragraph}, + style::{Style, Stylize}, + text::Line, + widgets::{Block, Borders, List, ListState}, Frame, }; use std::path::Path; @@ -25,16 +26,13 @@ pub struct AppState { tabs: Vec, /// Current tab current_tab: ListState, - /// Current search query - search_query: String, - /// Current items - items: Vec, /// This stack keeps track of our "current dirrectory". You can think of it as `pwd`. but not /// just the current directory, all paths that took us here, so we can "cd .." visit_stack: Vec, /// This is the state asociated with the list widget, used to display the selection in the /// widget selection: ListState, + filter: Filter, } pub enum Focus { @@ -44,10 +42,10 @@ pub enum Focus { FloatingWindow(Float), } -struct ListEntry { - node: ListNode, - id: NodeId, - has_children: bool, +pub struct ListEntry { + pub node: ListNode, + pub id: NodeId, + pub has_children: bool, } impl AppState { @@ -59,10 +57,9 @@ impl AppState { focus: Focus::List, tabs, current_tab: ListState::default().with_selected(Some(0)), - search_query: String::new(), - items: vec![], visit_stack: vec![root_id], selection: ListState::default().with_selected(Some(0)), + filter: Filter::new(), }; state.update_items(); state @@ -110,20 +107,7 @@ impl AppState { .constraints([Constraint::Length(3), Constraint::Min(1)].as_ref()) .split(horizontal[1]); - // Render search bar - let search_text = match self.focus { - Focus::Search => Span::raw(&self.search_query), - _ if !self.search_query.is_empty() => Span::raw(&self.search_query), - _ => Span::raw("Press / to search"), - }; - let search_bar = Paragraph::new(search_text) - .block(Block::default().borders(Borders::ALL)) - .style(Style::default().fg(if let Focus::Search = self.focus { - Color::Blue - } else { - Color::DarkGray - })); - frame.render_widget(search_bar, chunks[0]); + self.filter.draw_searchbar(frame, chunks[0], &self.theme); let mut items: Vec = Vec::new(); if !self.at_root() { @@ -132,7 +116,7 @@ impl AppState { ); } - items.extend(self.items.iter().map( + items.extend(self.filter.item_list().iter().map( |ListEntry { node, has_children, .. }| { @@ -153,10 +137,11 @@ impl AppState { } else { Style::new() }) - .block(Block::default().borders(Borders::ALL).title(format!( - "Linux Toolbox - {}", - chrono::Local::now().format("%Y-%m-%d") - ))) + .block( + Block::default() + .borders(Borders::ALL) + .title(format!("Linux Toolbox - {}", env!("BUILD_DATE"))), + ) .scroll_padding(1); frame.render_stateful_widget(list, chunks[1], &mut self.selection); @@ -171,21 +156,11 @@ impl AppState { self.focus = Focus::List; } } - Focus::Search => { - match key.code { - KeyCode::Char(c) => self.search_query.push(c), - KeyCode::Backspace => { - self.search_query.pop(); - } - KeyCode::Esc => { - self.search_query = String::new(); - self.exit_search(); - } - KeyCode::Enter => self.exit_search(), - _ => return true, - } - self.update_items(); - } + Focus::Search => match self.filter.handle_key(key) { + SearchAction::Exit => self.exit_search(), + SearchAction::Update => self.update_items(), + _ => {} + }, _ if key.code == KeyCode::Char('q') => return false, Focus::TabList => match key.code { KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right | KeyCode::Tab => { @@ -202,8 +177,8 @@ impl AppState { self.refresh_tab(); } KeyCode::Char('/') => self.enter_search(), - KeyCode::Char('t') => self.theme = self.theme.next(), - KeyCode::Char('T') => self.theme = self.theme.prev(), + KeyCode::Char('t') => self.theme.next(), + KeyCode::Char('T') => self.theme.prev(), _ => {} }, Focus::List if key.kind != KeyEventKind::Release => match key.code { @@ -220,53 +195,20 @@ impl AppState { } KeyCode::Char('/') => self.enter_search(), KeyCode::Tab => self.focus = Focus::TabList, - KeyCode::Char('t') => self.theme = self.theme.next(), - KeyCode::Char('T') => self.theme = self.theme.prev(), + KeyCode::Char('t') => self.theme.next(), + KeyCode::Char('T') => self.theme.prev(), _ => {} }, _ => {} }; true } - pub fn update_items(&mut self) { - if self.search_query.is_empty() { - let curr = self.tabs[self.current_tab.selected().unwrap()] - .tree - .get(*self.visit_stack.last().unwrap()) - .unwrap(); - - self.items = curr - .children() - .map(|node| ListEntry { - node: node.value().clone(), - id: node.id(), - has_children: node.has_children(), - }) - .collect(); - } else { - self.items.clear(); - - let query_lower = self.search_query.to_lowercase(); - for tab in self.tabs.iter() { - let mut stack = vec![tab.tree.root().id()]; - while let Some(node_id) = stack.pop() { - let node = tab.tree.get(node_id).unwrap(); - - if node.value().name.to_lowercase().contains(&query_lower) - && !node.has_children() - { - self.items.push(ListEntry { - node: node.value().clone(), - id: node.id(), - has_children: false, - }); - } - - stack.extend(node.children().map(|child| child.id())); - } - } - self.items.sort_by(|a, b| a.node.name.cmp(&b.node.name)); - } + fn update_items(&mut self) { + self.filter.update_items( + &self.tabs, + self.current_tab.selected().unwrap(), + *self.visit_stack.last().unwrap(), + ); } /// Checks ehther the current tree node is the root node (can we go up the tree or no) /// Returns `true` if we can't go up the tree (we are at the tree root) @@ -292,7 +234,7 @@ impl AppState { selected_index = selected_index.saturating_sub(1); } - if let Some(item) = self.items.get(selected_index) { + if let Some(item) = self.filter.item_list().get(selected_index) { if !item.has_children { return Some(item.node.command.clone()); } else if change_directory { @@ -321,11 +263,13 @@ impl AppState { } fn enter_search(&mut self) { self.focus = Focus::Search; + self.filter.activate_search(); self.selection.select(None); } fn exit_search(&mut self) { self.selection.select(Some(0)); self.focus = Focus::List; + self.filter.deactivate_search(); self.update_items(); } fn refresh_tab(&mut self) { diff --git a/src/theme.rs b/src/theme.rs index bb5b6af2..84fa15b4 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -86,17 +86,15 @@ impl Theme { } impl Theme { - #[allow(unused)] - pub fn next(self) -> Self { - let position = self as usize; + pub fn next(&mut self) { + let position = *self as usize; let types = Theme::value_variants(); - types[(position + 1) % types.len()].into() + *self = types[(position + 1) % types.len()]; } - #[allow(unused)] - pub fn prev(self) -> Self { - let position = self as usize; + pub fn prev(&mut self) { + let position = *self as usize; let types = Theme::value_variants(); - types[(position + types.len() - 1) % types.len()].into() + *self = types[(position + types.len() - 1) % types.len()]; } }