nirish

config files for niri window manager
Log | Files | Refs | README | LICENSE

commit b3474e85dcce03e7de0b4a4e30ff7fad1f38ee26
parent 607891801fbf2051a0c9062d15a445dd238c1e1b
Author: ryukamish <[email protected]>
Date:   Sat, 11 Apr 2026 18:25:38 +0530

add: small bash scripts for added functionality

Diffstat:
Ascripts/battery-monitor | 23+++++++++++++++++++++++
Ascripts/brightness-control | 18++++++++++++++++++
Ascripts/browser | 29+++++++++++++++++++++++++++++
Ascripts/ffut | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/hyprwall | 32++++++++++++++++++++++++++++++++
Ascripts/nightlight-toggle | 40++++++++++++++++++++++++++++++++++++++++
Ascripts/pkg-aur-install | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/pkg-install | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/pkg-remove | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/pkg-update | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/rehis | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/restart-app | 31+++++++++++++++++++++++++++++++
Ascripts/rofi-menu | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/rofi-powermenu | 38++++++++++++++++++++++++++++++++++++++
Ascripts/rofi-screenrecord | 21+++++++++++++++++++++
Ascripts/screenrecord | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/socialdl | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/switch-audio | 35+++++++++++++++++++++++++++++++++++
Ascripts/tmux-sessionizer | 344+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/tx | 3+++
Ascripts/vic | 12++++++++++++
Ascripts/vidl | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/volume-control | 28++++++++++++++++++++++++++++
23 files changed, 1740 insertions(+), 0 deletions(-)

diff --git a/scripts/battery-monitor b/scripts/battery-monitor @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Device path from 'upower -e' +BATTERY_PATH="/org/freedesktop/UPower/devices/DisplayDevice" + +# Get initial battery percentage +get_battery_percentage() { + busctl get-property org.freedesktop.UPower "$BATTERY_PATH" org.freedesktop.UPower.Device Percentage | + awk '{print int($2)}' + } + +while true; do + PERCENT=$(get_battery_percentage) + + if [[ $PERCENT -le 10 ]]; then + notify-send -u critical "Battery Low" "Battery at ${PERCENT}%" -i battery-caution -t 30000 + # Wait 5 minutes before rechecking to avoid spam + sleep 300 + else + # Check every minute otherwise + sleep 60 + fi +done diff --git a/scripts/brightness-control b/scripts/brightness-control @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Taken from: https://github.com/salanpro/hyprland/tree/047f998e710b0ffe8d841a89fb7749b50c931b2c/.config/hypr/scripts + +# Brightness up and down +if [[ "$1" == "up" ]]; then + brightnessctl set -e1 -n2 5%+ -m \ + | awk -F ',' '{print $4+0}' \ + | xargs -I[] notify-send -e -u low -h \ + string:x-canonical-private-synchronous:brightness_notif -h \ + int:value:[] "☀ Brightness: []%" +elif [[ "$1" == "down" ]]; then + brightnessctl set -e1 -n2 5%- -m \ + | awk -F ',' '{print $4+0}' \ + | xargs -I[] notify-send -e -u low -h \ + string:x-canonical-private-synchronous:brightness_notif -h \ + int:value:[] "☀ Brightness: []%" +fi diff --git a/scripts/browser b/scripts/browser @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +default_browser=$(xdg-settings get default-web-browser) + +if [ -z "$default_browser" ]; then + notify-send -u normal "Default Browser" "Default browser not set." + exit 1 +fi + +for path in /usr/local/share/applications /usr/share/applications ~/.local/share/applications; do + if [ -f "$path/$default_browser" ]; then + browser_path="$path/$default_browser" + break + fi +done + +if [ -z "$browser_path" ]; then + notify-send -u normal "Default Browser" "Could not find .desktop file for default browser." + exit 1 +fi + +browser_command=$(grep -m 1 '^Exec=' "$browser_path" | sed 's/^Exec=//; s/ .*$//') + +if [ -z "$browser_command" ]; then + notify-send -u normal "Default Browser" "Could not extract command from .desktop file." + exit 1 +elif [ -n "$browser_command" ]; then + $browser_command & disown +fi diff --git a/scripts/ffut b/scripts/ffut @@ -0,0 +1,149 @@ +#!/usr/bin/env bash + +# Strict mode for bash; used when wanting to find errors while scripting +set -euo pipefail +IFS=$'\n\t' + +RED="\033[1;31m" +RESET="\033[0m" + +# Already defining the input file as a variable +# as it was being a pain to check for the input file. +input_file="$1" + +# Usage for script +usage() { + clear + cat <<'EOF' + +TUI for ffmpeg to cut & change codec. + +Usage: ffut video_file + +EOF + +} + +# Bash expanded [] conditions of "video/*" to [..] which +# is a blank string. That was why it was always giving +# error message. +check_for_vid_file() { + if [[ ! $(file --mime-type -b "$input_file") =~ video ]]; then + printf "\n%b\n" "${RED}Error${RESET}: video file not provided (got: %s)." && exit 1 + fi +} + +# Check for audio file for audio codec converstion +check_audio_file() { + check_input_audio_file=$(file --mime-type -b "$input_file") + if [[ ! $(file --mime-type -b "$input_file") =~ audio ]]; then + printf "\n%b\n" "${RED}Error${RESET}: audio file not provided." && exit 1 + fi +} + +CommandArgs=$(gum choose --limit 1 --header="Choose: cut or codec" "cut" "codec" || exit 1) + +case "$CommandArgs" in +# Trimming videos +cut) + # Funcion call for checking video file + check_for_vid_file + # Take input from the user; what to do? + cut_opt=$(gum choose --limit 1 --header="Choose cut option:" "1. One point to Another" "2. One point to end of video" || exit 1) + case "$cut_opt" in + 1*) + # First asking from where to cut and till how much + # then takes the input of the data and applies it to + # ffmpeg command. Finally, asks if original file + # should be deleted. + seek_start=$(gum input --placeholder "Start: HH:MM:SS or MM:SS or sec") + seek_end=$(gum input --placeholder "End: HH:MM:SS or MM:SS or sec") + ffmpeg -ss "$seek_start" -to "$seek_end" -i "$input_file" -c copy "$(date +"%Y%m%d_%H%M ")$input_file" || exit 1 + gum confirm "Delete original file?" && rm "$1" + ;; + 2*) + # Just does the same thing as above but only asks for + # starting point and cuts to the end of the video. + seek_start=$(gum input --placeholder "Start: HH:MM:SS or MM:SS or sec") + ffmpeg -ss "$seek_start" -i "$input_file" -c copy "$(date +"%Y%m%d_%H%M ")$input_file" + gum confirm "Delete original file?" && rm "$1" + ;; + *) + # Anything which is not a file after cut option + # will give out usage output. + usage + exit 1 + ;; + esac + ;; +# Codec change +codec) + [[ check_for_vid_file || check_audio_file ]] + # Take input for changin either video or audio codec. + codec_opt=$(gum choose --limit 1 --header="Choose:" "video" "audio" || exit 1) + # On the basis of choice choose video or audio option. + case "$codec_opt" in + video) + video_codec_opt=$(gum choose --limit 1 --header="Choose: video format" "H.264" "H.265" "VP9 [webm]" || exit 1) + case "$video_codec_opt" in + H.264) + ffmpeg -i "$input_file" -c:v libx264 -crf 23 -c:a copy "$(date +"%Y%m%d_%H%M")_h264_$input_file" || exit 1 + ;; + H.265) + ffmpeg -i "$input_file" -c:v libx265 -crf 28 -c:a copy "$(date +"%Y%m%d_%H%M")_h265_$input_file" || exit 1 + ;; + "VP9 [webm]") + # Take the input file name, removes extension name with dot + # and changes codec to libvpx-vp9 and ext to webm + output_video_file=$(basename "$input_file" | sed 's/\.[^.]*$//') + output_video_file_name_webm=$(date +"%Y%m%d_%H%M")"_$output_video_file".webm + ffmpeg -i "$input_file" -c:v libvpx-vp9 -crf 30 -b:v 0 -cpu-used -0 -c:a copy -compression_level 10 "$output_video_file_name_webm" || exit 1 + ;; + *) + usage + exit 1 + ;; + esac + ;; + audio) + check_audio_file + audio_codec_opt=$(gum choose --limit 1 --header="Choose:" "AAC" "OPUS" "MP3" || exit 1) + case "$audio_codec_opt" in + AAC) + output_audio_file=$(basename "$input_file" | sed 's/\.[^.]*$//') + output_audio_file_aac="$output_audio_file".m4a + if ffmpeg -codecs 2>/dev/null | grep -q libfdk_aac; then + ffmpeg -i "$input_file" -c:v copy -c:a libfdk_aac -b:a 192k "$output_audio_file_aac" + else + ffmpeg -i "$input_file" -c:v copy -c:a aac -b:a 192k "$output_audio_file_aac" + fi + ;; + OPUS) + # Basically taking audio file and changing codec to libopus + # and file extension to .opus + output_audio_file=$(basename "$input_file" | sed 's/\.[^.]*$//') + output_audio_file_opus="$output_audio_file".opus + # opus codec for best efficiency + ffmpeg -i "$input_file" -c:a libopus -b:a 128k -vn "$output_audio_file_opus" + ;; + MP3) + # Same thing changing codec to libmp3lame and file + # extension to .mp3 + output_audio_file=$(basename "$input_file" | sed 's/\.[^.]*$//') + output_audio_file_mp3="$output_audio_file".mp3 + # High quality mp3 conversion + ffmpeg -i "$input_file" -c:a libmp3lame -q:a 2 -vn "$output_audio_file_mp3" + ;; + *) + usage + exit 1 + ;; + esac + ;; + esac + ;; +*) + usage + exit 1 + ;; +esac diff --git a/scripts/hyprwall b/scripts/hyprwall @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +WALLDIR="$HOME/Pictures/Wallpapers" +CACHE="$HOME/.cache/rofi-wallpaper" +mkdir -p "$CACHE" + +# Generate thumbnails (only missing ones) +find "$WALLDIR" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.webp" \) | while read -r img; do + name="$(basename "$img")" + thumb="$CACHE/$name.png" + [ ! -f "$thumb" ] && magick "$img" -resize 500x500^ -gravity center -extent 500x500 "$thumb" +done + +# This is the key: use -show-icons and tell rofi where the icons are +chosen=$(find "$WALLDIR" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.webp" \) | sort | + while read -r img; do + name="$(basename "$img")" + echo -e "$name\0icon\x1f$CACHE/$name.png" + done | + rofi -dmenu \ + -p "Wallpaper" \ + -i \ + -show-icons \ + -theme-str 'window { width: 90%; height: 80%; }' \ + -theme-str 'listview { columns: 5; lines: 3; spacing: 20px; }' \ + -theme-str 'element-icon { size: 200px; horizontal-align: 0.5; }' \ + -theme-str 'element-text { enabled: false; }') + +[ -z "$chosen" ] && exit + +fullpath="$WALLDIR/$chosen" +awww img "$fullpath" --transition-type wipe --transition-fps 60 --transition-angle 45 --transition-step 30 diff --git a/scripts/nightlight-toggle b/scripts/nightlight-toggle @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +hyprland_nightlight_toggle() { + # Default temperature values + ON_TEMP=4500 + OFF_TEMP=6000 + # Ensure hyprsunset is running + if ! pgrep -x hyprsunset; then + hyprsunset & + sleep 1 # Give it time to register + fi + # Query the current temperature + CURRENT_TEMP=$(hyprctl hyprsunset temperature 2>/dev/null | grep -oE '[0-9]+') + if [[ "$CURRENT_TEMP" == "$OFF_TEMP" ]]; then + hyprctl hyprsunset temperature $ON_TEMP + notify-send " Nightlight screen temperature" + else + hyprctl hyprsunset temperature $OFF_TEMP + notify-send " Daylight screen temperature" + fi +} + +niri_nightlight_toggle() { + if pgrep -x wlsunset; then + killall wlsunset + notify-send " Daylight screen temperature" + else + setsid -f wlsunset -T 4500 + notify-send " Nightlight screen temperature" + fi +} + +case "$1" in +*niri) + niri_nightlight_toggle + ;; +*hyprland) + hyprland_nightlight_toggle + ;; +esac diff --git a/scripts/pkg-aur-install b/scripts/pkg-aur-install @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Colors +readonly RED='\e[31m' +readonly GREEN='\e[32m' +readonly YELLOW='\e[33m' +readonly BLUE='\e[34m' +readonly BOLD='\e[1m' +readonly RESET='\e[0m' + +# Check required tools +for tool in fzf yay; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo -e "${RED}Error: $tool is not installed${RESET}" + [[ "$tool" == "yay" ]] && echo -e "${YELLOW}Install yay first: https://github.com/Jguer/yay${RESET}" + exit 1 + fi +done + +# FZF configuration +fzf_args=( + --multi + --preview 'yay -Sii {1}' + --preview-label-pos='bottom' + --preview-window 'down:65%:wrap' + --color 'pointer:green,marker:green' + --header='Select AUR packages to install (TAB to select multiple, ENTER to confirm)' + --border +) + +echo -e "${BLUE}${BOLD}Loading AUR package list...${RESET}\n" + +# Get package selections +pkg_names=$(yay -Slqa | fzf "${fzf_args[@]}" || true) + +# Check if user selected anything +if [[ -z "$pkg_names" ]]; then + echo -e "${YELLOW}No packages selected. Exiting.${RESET}" + exit 0 +fi + +# Show what will be installed +echo -e "${BLUE}${BOLD}Selected AUR packages:${RESET}" +echo "$pkg_names" | sed 's/^/ - /' +echo + +# Convert to array for safe handling +mapfile -t packages < <(echo "$pkg_names") + +# Install packages +echo -e "${GREEN}${BOLD}Installing ${#packages[@]} AUR package(s)...${RESET}\n" + +if yay -S --noconfirm "${packages[@]}"; then + echo + echo -e "${GREEN}${BOLD}✓ Successfully installed ${#packages[@]} AUR package(s)${RESET}" +else + echo + echo -e "${RED}${BOLD}✗ Installation failed or was incomplete${RESET}" +fi + +echo + +# Keep window open +if command -v gum >/dev/null 2>&1; then + gum spin --spinner "globe" --title "Done! Press any key to close..." -- bash -c 'read -n 1 -s' +else + echo -e "${BLUE}Press any key to close...${RESET}" + read -n 1 -s +fi diff --git a/scripts/pkg-install b/scripts/pkg-install @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Colors +RED='\e[31m' +GREEN='\e[32m' +YELLOW='\e[33m' +BLUE='\e[34m' +BOLD='\e[1m' +RESET='\e[0m' + +# Check required tools +for tool in fzf pacman; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo -e "${RED}Error: $tool is not installed${RESET}" + notify-send "ERROR" "$tool is not installed." + exit 1 + fi +done + +# FZF configuration +fzf_args=( + --multi + --preview 'pacman -Sii {1}' + --preview-label-pos='bottom' + --preview-window 'down:65%:wrap' + --color 'pointer:green,marker:green' + --header='Select packages to install (TAB to select multiple, ENTER to confirm)' + --border +) + +echo -e "${BLUE}${BOLD}Loading package list...${RESET}\n" + +# Get package selections +pkg_names=$(pacman -Slq | fzf "${fzf_args[@]}" || true) + +# Check if user selected anything +if [[ -z "$pkg_names" ]]; then + echo -e "${YELLOW}No packages selected. Exiting.${RESET}" + exit 0 +fi + +# Show what will be installed +echo -e "${BLUE}${BOLD}Selected packages:${RESET}" +echo "$pkg_names" | sed 's/^/ - /' +echo + +# Convert to array for safe handling +mapfile -t packages < <(echo "$pkg_names") + +# Install packages +echo -e "${GREEN}${BOLD}Installing ${#packages[@]} package(s)...${RESET}\n" + +if sudo pacman -S --noconfirm "${packages[@]}"; then + echo + echo -e "${GREEN}${BOLD}✓ Successfully installed ${#packages[@]} package(s)${RESET}" +else + echo + echo -e "${RED}${BOLD}✗ Installation failed or was incomplete${RESET}" +fi + +echo + +# Keep window open +if command -v gum >/dev/null 2>&1; then + gum spin --spinner "globe" --title "Done! Press any key to close..." -- bash -c 'read -n 1 -s' +else + echo -e "${BLUE}Press any key to close...${RESET}" + read -n 1 -s +fi diff --git a/scripts/pkg-remove b/scripts/pkg-remove @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Colors +RED='\e[31m' +GREEN='\e[32m' +YELLOW='\e[33m' +BLUE='\e[34m' +BOLD='\e[1m' +RESET='\e[0m' + +# Check required tools +for tool in fzf pacman; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo -e "${RED}Error: $tool is not installed${RESET}" + notify-send "ERROR" "$tool is not installed." + exit 1 + fi +done + +# FZF configuration +fzf_args=( + --multi + --preview 'pacman -Qi {1}' + --preview-label-pos='bottom' + --preview-window 'down:65%:wrap' + --color 'pointer:red,marker:red' + --header='Select packages to REMOVE (TAB to select multiple, ENTER to confirm)' + --border +) + +echo -e "${BLUE}${BOLD}Loading installed packages...${RESET}\n" + +# Get package selections (use pacman instead of yay for compatibility) +if command -v yay >/dev/null 2>&1; then + pkg_names=$(yay -Qqe | fzf "${fzf_args[@]}" || true) +else + pkg_names=$(pacman -Qqe | fzf "${fzf_args[@]}" || true) +fi + +# Check if user selected anything +if [[ -z "$pkg_names" ]]; then + echo -e "${YELLOW}No packages selected. Exiting.${RESET}" + exit 0 +fi + +# Show what will be removed +echo -e "${RED}${BOLD} WARNING: The following packages will be REMOVED:${RESET}" +echo "$pkg_names" | sed 's/^/ - /' +echo + +# Convert to array for safe handling +mapfile -t packages < <(echo "$pkg_names") + +# Remove packages +echo -e "${RED}${BOLD}Removing ${#packages[@]} package(s)...${RESET}\n" + +if sudo pacman -Rns --noconfirm "${packages[@]}"; then + echo + echo -e "${GREEN}${BOLD}✓ Successfully removed ${#packages[@]} package(s)${RESET}" + + # Check for new orphans + orphans=$(pacman -Qtdq 2>/dev/null || true) + if [[ -n "$orphans" ]]; then + echo -e "${YELLOW}ℹ Found new orphaned packages (run pkg-update to clean them)${RESET}" + fi +else + echo + echo -e "${RED}${BOLD}✗ Removal failed or was incomplete${RESET}" +fi + +echo + +# Keep window open +if command -v gum >/dev/null 2>&1; then + gum spin --spinner "globe" --title "Done! Press any key to close..." -- bash -c 'read -n 1 -s' +else + echo -e "${BLUE}Press any key to close...${RESET}" + read -n 1 -s +fi diff --git a/scripts/pkg-update b/scripts/pkg-update @@ -0,0 +1,107 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Colors for terminal output +RED='\e[31m' +GREEN='\e[32m' +YELLOW='\e[33m' +BLUE='\e[34m' +BOLD='\e[1m' +RESET='\e[0m' + +# Track if any errors occurred +ERRORS=0 + +# Function to print section headers +print_header() { + echo -e "${BOLD}${BLUE}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo -e "$1" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo -e "${RESET}" +} + +print_success() { + echo -e "${GREEN}✓ $1${RESET}" +} + +print_error() { + echo -e "${RED}✗ $1${RESET}" + ERRORS=$((ERRORS + 1)) +} + +print_info() { + echo -e "${YELLOW}ℹ $1${RESET}" +} + +# Main update process +print_header "System Package Update" + +# Update system packages +echo -e "${GREEN}Updating system packages...${RESET}\n" +if sudo pacman -Syu --noconfirm; then + print_success "System packages updated" +else + print_error "Failed to update system packages" +fi + +echo + +# Update AUR packages +print_header "AUR Package Update" + +if ! command -v yay >/dev/null 2>&1; then + print_info "yay is not installed, skipping AUR updates" +elif ! pacman -Qem >/dev/null 2>&1; then + print_info "No AUR packages installed, skipping AUR updates" +else + echo -e "${GREEN}Updating AUR packages...${RESET}\n" + if yay -Sua --noconfirm; then + print_success "AUR packages updated" + else + print_error "Failed to update some AUR packages" + fi +fi + +echo + +# Clean up orphans +print_header "Cleanup Orphaned Packages" + +orphans=$(pacman -Qtdq 2>/dev/null || true) + +if [[ -z "$orphans" ]]; then + print_info "No orphaned packages found" +else + echo -e "${YELLOW}Found orphaned packages:${RESET}" + echo "$orphans" | sed 's/^/ - /' + echo + echo -e "${GREEN}Removing orphaned packages...${RESET}\n" + if sudo pacman -Rns --noconfirm $orphans 2>/dev/null; then + print_success "Orphaned packages removed" + else + print_error "Failed to remove some orphaned packages" + fi +fi + +echo + +# Final summary +print_header "Update Summary" + +if [[ $ERRORS -eq 0 ]]; then + echo -e "${GREEN}${BOLD}✓ All operations completed successfully!${RESET}" +else + echo -e "${YELLOW}${BOLD} Completed with $ERRORS error(s)${RESET}" +fi + +echo + +# Keep window open until user dismisses +if command -v gum >/dev/null 2>&1; then + gum spin --spinner "globe" --title "Done! Press any key to close..." -- bash -c 'read -n 1 -s' +else + echo -e "${BLUE}Press any key to close this window...${RESET}" + read -n 1 -s +fi diff --git a/scripts/rehis b/scripts/rehis @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +set -euo pipefail + +RED="\e[31m" GREEN="\e[32m" RESET="\e[0m" + +msg() { + case "$1" in + error) + printf '%b\n' "${RED}$2${RESET}" + bash -c -- read -n 1 -s -r + ;; + done) + printf '%b\n' "${GREEN}$2${RESET}" + bash -c -- read -n 1 -s -r + ;; + esac +} + +current_shell=$(getent passwd "$USER" | cut -d: -f7 | xargs basename) +fzf_opts=( + "--height=50%" + "--border" + "--layout=reverse" +) + +if [ "$current_shell" == "bash" ]; then + # Get the history file path + if [ -s "$HOME/.local/share/bash/history" ]; then + history_file="$HOME/.local/share/bash/history" + elif [ -s "$HOME/.bash_history" ]; then + history_file="$HOME/.bash_history" + fi + # Check if the history file exists and is not empty + if [ -s "$history_file" ]; then + cat "$history_file" | + fzf "${fzf_opts[@]}" | + wl-copy || + msg error "Nothing selected to copy" + else + msg error "No history found" + fi +elif [ "$current_shell" == "zsh" ]; then + # Get the history file path + if [ -s "$HOME/.zsh_history" ]; then + history_file="$HOME/.zsh_history" + elif [ -s "$HOME/.local/share/zsh/history" ]; then + history_file="$HOME/.local/share/zsh/history" + fi + # Check if the history file exists and is not empty + if [ -n "$history_file" ]; then + cat "$history_file" | + cut -d';' -f2 | + fzf "${fzf_opts[@]}" | + wl-copy || + msg error "Nothing selected to copy" + else + msg error "No history found" + fi +fi diff --git a/scripts/restart-app b/scripts/restart-app @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# Exit if no app name provided +[ -z "$1" ] && exit 1 + +APP_NAME="$1" +shift # Remove app name, leaving any additional arguments + +# Check if app exists in PATH +command -v "$APP_NAME" >/dev/null 2>&1 || exit 1 + +# Check if app is running and kill it +if pgrep -x "$APP_NAME" >/dev/null 2>&1; then + # Send SIGTERM (graceful shutdown) + pkill -x "$APP_NAME" + + # Wait for up to 2 seconds for the process to terminate + for i in {1..20}; do + pgrep -x "$APP_NAME" >/dev/null 2>&1 || break + sleep 0.1 + done + + # Force kill if still running + if pgrep -x "$APP_NAME" >/dev/null 2>&1; then + pkill -9 -x "$APP_NAME" + sleep 0.1 + fi +fi + +# Start the app detached with any additional arguments +setsid -f "$APP_NAME" "$@" >/dev/null 2>&1 diff --git a/scripts/rofi-menu b/scripts/rofi-menu @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +menu_args=$(printf '%b' '󰕓 Devices\n Files\n Install\n󰔎 Theme\n Record Screen\n󰮞 Remove\n󰣇 Update' | rofi -dmenu -p " Options: " -i -theme-str "window {width: 20em;}") + +case "$menu_args" in +*Files) + rofi -show recursivebrowser -theme-str 'window {width: 42em;} listview {lines: 8;}' + ;; +*Install) + install_args=$(printf '%b' '󰣇 Pacman\n󰣇 AUR\n TUI' | rofi -dmenu -p "󰛀 Install: " -i -theme-str "window {width: 20em;}") + case "$install_args" in + *Pacman) + ~/.local/bin/terminal-floating ~/.local/bin/pkg-install + ;; + *AUR) + ~/.local/bin/terminal-floating ~/.local/bin/pkg-aur-install + ;; + *TUI) + ~/.local/bin/terminal-floating ~/.local/bin/tui-install + ;; + esac + ;; +*Remove) + remove_args=$(printf '%b' '󰣇 Packages\n TUI' | rofi -dmenu -p " Remove: " -i) + case "$remove_args" in + *Packages) + ~/.local/bin/terminal-floating ~/.local/bin/pkg-remove + ;; + *TUI) + ~/.local/bin/terminal-floating ~/.local/bin/tui-remove + ;; + esac + ;; + +*Theme) + # For the time being only GUI apps + # change theme i.e. Light & Dark + theme_args=$(printf '%b' '󰸉 Change Wallpaper\n Colorscheme' | rofi -dmenu -p '' -i) + case "$theme_args" in + *Change\ Wallpaper) + ~/.local/bin/hyprwall + ;; + *Colorscheme) + theme_args=$(printf '%b' 'Black Lotus\nCatppuccin Mocha\nDracula\nGruvbox Dark\nKanagawa\nRose Pine\nTokyonight' | rofi -dmenu -p 'Colorscheme: ' -i) + case "$theme_args" in + *Black\ Lotus) + ~/.local/bin/rofi-theme-switcher blacklotus + ;; + *Catppuccin\ Mocha) + ~/.local/bin/rofi-theme-switcher catppuccin-mocha + ;; + *Dracula) + ~/.local/bin/rofi-theme-switcher dracula + ;; + *Gruvbox\ Dark) + ~/.local/bin/rofi-theme-switcher gruvbox-dark + ;; + *Kanagawa) + ~/.local/bin/rofi-theme-switcher kanagawa + ;; + *Rose\ Pine) + ~/.local/bin/rofi-theme-switcher rose-pine + ;; + *Tokyonight) + ~/.local/bin/rofi-theme-switcher tokyonight + ;; + esac + ;; + esac + ;; +*Record\ Screen) + record_args=$(printf '%b' '󰍹 Display\n󰽟 Display + Audio\n󰖠 Webcam + Audio\n󰹑 Region\n󱄄 Region + Audio' | rofi -dmenu -p " Record: " -i -theme-str "window {width: 20em;}") + case "$record_args" in + *Display) + ~/.local/bin/screenrecord output + ;; + *Display\ +\ Audio) + ~/.local/bin/screenrecord output --with-audio + ;; + *Webcam\ +\ Audio) + ~/.local/bin/screenrecord output --with-webcam --with-audio + ;; + *Region) + ~/.local/bin/screenrecord region + ;; + *Region\ +\ Audio) + ~/.local/bin/screenrecord region --with-audio + ;; + esac + ;; +*Devices) + devices_args=$(printf '%b' 'Mount\nUnmount' | rofi -dmenu -p "󰕓 Choose: " -i) + case "$devices_args" in + Mount) + ~/.local/bin/terminal-floating ~/.local/bin/mount-devices Mount + ;; + Unmount) + ~/.local/bin/terminal-floating ~/.local/bin/mount-devices Unmount + ;; + esac + ;; +*Update) + ~/.local/bin/terminal-floating ~/.local/bin/pkg-update + ;; +esac diff --git a/scripts/rofi-powermenu b/scripts/rofi-powermenu @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +power_args=$(printf '%b' ' Lock\nLogout\n Restart\n󰒲 Suspend\n󰋊 Hibernate\n󰚦 Shutdown' | + rofi -dmenu -p "󰐥 " -i -theme-str 'window {width: 12em; padding: 2px; height: 16em;}') + +case "$power_args" in +*Lock) + if [[ "$XDG_CURRENT_DESKTOP" == "niri" ]]; then + command -v swaylock &>/dev/null && swaylock + elif [[ "$XDG_CURRENT_DESKTOP" == "Hyprland" ]]; then + hyprlock + fi + ;; +*Logout) + if [[ "$XDG_CURRENT_DESKTOP" == "niri" ]]; then + niri msg action quit + elif [[ "$XDG_CURRENT_DESKTOP" == "Hyprland" ]]; then + hyprctl dispatch exit + fi + ;; +*Restart) + systemctl reboot + ;; +*Suspend) + systemctl suspend + ;; +*Hibernate) + systemctl hibernate + ;; +*Shutdown) + if [[ "$XDG_CURRENT_DESKTOP" == "niri" ]]; then + systemctl poweroff + elif [[ "$XDG_CURRENT_DESKTOP" == "Hyprland" ]]; then + hyprctl dispatch killactive && sleep 0.1 && hyprctl dispatch killactive && sleep 0.1 && systemctl poweroff + # hyprctl clients -j | jq -r '.[].pid' | xargs -r kill -9; systemctl poweroff + fi + ;; +esac diff --git a/scripts/rofi-screenrecord b/scripts/rofi-screenrecord @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +screenrecord_args=$(printf '%b' '󰍹 DISPLAY\n󰍹 DISPLAY+AUDIO\n󰖠 DISPLAY+WEBCAM\n REGION\n REGION+AUDIO' | rofi -dmenu -p " Record Screen: " -i) + +case "$screenrecord_args" in +*DISPLAY) + screenrecord output + ;; +*DISPLAY+AUDIO) + screenrecord output --with-audio + ;; +*DISPLAY+WEBCAM) + screenrecord output --with-webcam --with-audio + ;; +*REGION) + screenrecord region + ;; +*REGION+AUDIO) + screenrecord region --with-audio + ;; +esac diff --git a/scripts/screenrecord b/scripts/screenrecord @@ -0,0 +1,140 @@ +#!/usr/bin/env bash + +OUTPUT_DIR="${XDG_VIDEOS_DIR:-$HOME/Videos/screenrecordings}" + +if [[ ! -d "$OUTPUT_DIR" ]]; then + mkdir -p "$OUTPUT_DIR" +fi + +SCOPE="" +AUDIO="false" +WEBCAM="false" + +for arg in "$@"; do + case "$arg" in + --with-audio) AUDIO="true" ;; + --with-webcam) WEBCAM="true" ;; + output | region) SCOPE="$arg" ;; + esac +done + +cleanup_webcam() { + pkill -f "WebcamOverlay" 2>/dev/null +} + +start_webcam_overlay() { + cleanup_webcam + + # Get monitor scale + local scale=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .scale') + + # Target width (base 360px, scaled to monitor) + local target_width=$(awk "BEGIN {printf \"%.0f\", 360 * $scale}") + + # Try preferred 16:9 resolutions in order, use first available + local preferred_resolutions=("640x360" "1280x720" "1920x1080") + local video_size_arg="" + local available_formats=$(v4l2-ctl --list-formats-ext -d /dev/video0 2>/dev/null) + + for resolution in "${preferred_resolutions[@]}"; do + if echo "$available_formats" | grep -q "$resolution"; then + video_size_arg="-video_size $resolution" + break + fi + done + + ffplay -f v4l2 $video_size_arg -framerate 30 /dev/video0 \ + -vf "scale=${target_width}:-1" \ + -window_title "WebcamOverlay" \ + -noborder \ + -fflags nobuffer -flags low_delay \ + -probesize 32 -analyzeduration 0 \ + -loglevel quiet & + sleep 1 +} + +start_screenrecording() { + local filename="$OUTPUT_DIR/screenrecording-$(date +'%Y-%m-%d_%H-%M-%S').mp4" + local audio_args="" + + # Merge audio tracks into one - separate tracks only play one at a time in most players + [[ "$AUDIO" == "true" ]] && audio_args="-a default_output|default_input" + + gpu-screen-recorder -w "$@" -f 60 -c mp4 -o "$filename" $audio_args & + toggle_screenrecording_indicator +} + +stop_screenrecording() { + pkill -SIGINT -f "gpu-screen-recorder" # SIGINT required to save video properly + + # Wait a maximum of 5 seconds to finish before hard killing + local count=0 + while pgrep -f "gpu-screen-recorder" >/dev/null && [ $count -lt 50 ]; do + sleep 0.1 + count=$((count + 1)) + done + + if pgrep -f "gpu-screen-recorder" >/dev/null; then + pkill -9 -f "gpu-screen-recorder" + cleanup_webcam + notify-send "Screen recording error" "Recording process had to be force-killed. Video may be corrupted." -u critical -t 5000 + else + cleanup_webcam + notify-send "Screen recording saved to $OUTPUT_DIR" -t 2000 + fi + toggle_screenrecording_indicator +} + +toggle_screenrecording_indicator() { + pkill -RTMIN+8 waybar +} + +screenrecording_active() { + pgrep -f "gpu-screen-recorder" >/dev/null || pgrep -x slurp >/dev/null || pgrep -f "WebcamOverlay" >/dev/null +} + +if screenrecording_active; then + if pgrep -x slurp >/dev/null; then + pkill -x slurp 2>/dev/null + elif pgrep -f "WebcamOverlay" >/dev/null && ! pgrep -f "gpu-screen-recorder" >/dev/null; then + cleanup_webcam + else + stop_screenrecording + fi +elif [[ "$SCOPE" == "output" ]]; then + [[ "$WEBCAM" == "true" ]] && start_webcam_overlay + + if ! output=$(slurp -o -f "%o"); then + [[ "$WEBCAM" == "true" ]] && cleanup_webcam + exit 1 + fi + + if [[ -z "$output" ]]; then + notify-send "Error" "Could not detect monitor" -u critical + [[ "$WEBCAM" == "true" ]] && cleanup_webcam + exit 1 + fi + + start_screenrecording "$output" +else + [[ "$WEBCAM" == "true" ]] && start_webcam_overlay + + scale=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .scale') + + if ! region=$(slurp -f "%wx%h+%x+%y"); then + [[ "$WEBCAM" == "true" ]] && cleanup_webcam + exit 1 + fi + + if [[ "$region" =~ ^([0-9]+)x([0-9]+)\+([0-9]+)\+([0-9]+)$ ]]; then + w=$(awk "BEGIN {printf \"%.0f\", ${BASH_REMATCH[1]} * $scale}") + h=$(awk "BEGIN {printf \"%.0f\", ${BASH_REMATCH[2]} * $scale}") + x=$(awk "BEGIN {printf \"%.0f\", ${BASH_REMATCH[3]} * $scale}") + y=$(awk "BEGIN {printf \"%.0f\", ${BASH_REMATCH[4]} * $scale}") + scaled_region="${w}x${h}+${x}+${y}" + else + scaled_region="$region" + fi + + start_screenrecording region -region "$scaled_region" +fi diff --git a/scripts/socialdl b/scripts/socialdl @@ -0,0 +1,224 @@ +#!/usr/bin/env bash + +# set -eou pipefail + +branding() { + echo + cat <<'EOF' + /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$ /$$$$$$$ /$$ + /$$__ $$ /$$__ $$ /$$__ $$|_ $$_/ /$$__ $$| $$ | $$__ $$| $$ +| $$ \__/| $$ \ $$| $$ \__/ | $$ | $$ \ $$| $$ | $$ \ $$| $$ +| $$$$$$ | $$ | $$| $$ | $$ | $$$$$$$$| $$ | $$ | $$| $$ + \____ $$| $$ | $$| $$ | $$ | $$__ $$| $$ | $$ | $$| $$ + /$$ \ $$| $$ | $$| $$ $$ | $$ | $$ | $$| $$ | $$ | $$| $$ +| $$$$$$/| $$$$$$/| $$$$$$/ /$$$$$$| $$ | $$| $$$$$$$$| $$$$$$$/| $$$$$$$$ + \______/ \______/ \______/ |______/|__/ |__/|________/|_______/ |________/ +EOF +echo +} + +# Check for dependencies +check_deps=$(which {gum,yt-dlp}) +if [ ! "$check_deps" ]; then + echo + gum log \ + --structured \ + --level error \ + "gum and yt-dlp not installed." + echo + gum spin \ + --spinner "globe" \ + --title "Done! Press any key to close..." \ + -- bash -c 'read -n 1 -s' + exit 1 +fi + +# Check for download directories +# if doesn't exist make them +insta_vid_dl_dir="$HOME/Videos/Instagram" +insta_img_dl_dir="$HOME/Pictures/Instagram" +x_vid_dl_dir="$HOME/Videos/X" +x_img_dl_dir="$HOME/Pictures/X" +reddit_img_dl_dir="$HOME/Pictures/Reddit" +reddit_vid_dl_dir="$HOME/Videos/Reddit" +# Making directories if doesn't exist +if [ ! -d "$insta_vid_dl_dir" ]; then + mkdir -p "$insta_vid_dl_dir" +elif [[ ! -d "$insta_img_dl_dir" ]]; then + mkdir -p "$insta_img_dl_dir" +elif [[ ! -d "$x_vid_dl_dir" ]]; then + mkdir -p "$x_vid_dl_dir" +elif [[ ! -d "$x_img_dl_dir" ]]; then + mkdir -p "$x_img_dl_dir" +elif [[ ! -d "$reddit_img_dl_dir" ]]; then + mkdir -p "$reddit_img_dl_dir" +elif [[ ! -d "$reddit_vid_dl_dir" ]]; then + mkdir -p "$reddit_vid_dl_dir" +fi + +# TODO: Add comments explaining the commands +# This here is a simple function which waits for the user +# to press a key and exits out +gum_done() { + gum spin \ + --spinner "globe" \ + --title "Done! Press any key to close..." \ + -- bash -c 'read -n 1 -s' +} + +gum_error() { + gum log --structured --level error "$1" +} +branding +# Platform to download from +platform_choice=$(gum choose \ + --header="Choose:" \ + --header.bold \ + --header.foreground="#FCB8EF" \ + "Instagram" "X" "Reddit") +# Apply methods to determine which platform is choosen +# Accordingly, choose how to download +case "$platform_choice" in + Instagram) + # Taking input whether downloading videos or images + insta_format_choice=$(gum choose \ + --header="Choose:" \ + --header.bold \ + --header.foreground="#FCB8EF" \ + "Video" "Image" || exit 1) + case "$insta_format_choice" in + Video) + # Taking instagram video url + insta_vid_url=$(gum input \ + --header.bold \ + --header.foreground="#FCB8EF" \ + --header="Paste Instagram video URL." \ + --placeholder="Enter URL here...") + if [[ -n "$insta_vid_url" ]]; then + yt-dlp "$insta_vid_url" -o "$insta_vid_dl_dir/%(title)s.%(ext)s" || exit 1 + echo + gum_done + echo + elif [[ -z "$insta_vid_url" ]]; then + echo + gum_error "Please input a URL." + echo + gum_done + fi + ;; + Image) + if command -v gallery-dl >/dev/null; then + # Taking instagram image url + insta_img_url=$(gum input \ + --header.bold \ + --header.foreground="#FCB8EF" \ + --header="Paste Instagram image URL." \ + --placeholder="Enter URL here...") + # Instagram requires login to download images; that is why + # cookies from browser is required + gallery-dl --cookies-from-browser chromium --destination "$insta_img_dl_dir" "$insta_img_url" + echo + gum_done + echo + else + gum_error "gallery-dl is not installed." + echo + gum_done + echo + fi + ;; + esac + ;; + X) + # Taking input whether to download video or image from X + x_format_choice=$(gum choose \ + --header="Choose:" \ + --header.bold \ + --header.foreground="#FCB8EF" \ + "Video" "Image" || exit 1) + case "$x_format_choice" in + Video) + # Taking input of X video url + x_vid_url=$(gum input \ + --header.bold \ + --header.foreground="#FCB8EF" \ + --header="Paste X video URL." \ + --placeholder="Paste URL here...") + if [ -z "$x_vid_url" ]; then + # Give out error if no url provided + echo + gum_error "Please enter a URL." + echo + gum_done + elif [[ -n "$x_vid_url" ]]; then + # Output directory with title and extension required as + # downloaded file is being downloaded to a particular + # directory not the dir where script is being executed + yt-dlp "$x_vid_url" -o "$x_vid_dl_dir/%(title)s.%(ext)s" || exit 1 + echo + gum_done + echo + fi + ;; + Image) + if command -v gallery-dl >/dev/null; then + # Take input of image url from instagram + x_img_url=$(gum input \ + --header.bold \ + --header.foreground="#FCB8EF" \ + --header="Paste X image URL." \ + --placeholder="Paste URL here...") + if [ -z "$x_img_url" ]; then + # If no url given then throw error and exit + echo + gum_error "Please enter a URL." + echo + elif [[ -n "$x_img_url" ]]; then + # FIX: Change the default browser to automatically continue + # if supported browser is default otherwise exit + gallery-dl --cookies-from-browser chromium --destination "$x_img_dl_dir" "$x_img_url" + echo + gum_done + echo + fi + else + echo + gum_error "gallery-dl is not installed." + echo + gum_done + fi + ;; + esac + ;; + Reddit) + reddit_format_choice=$(gum choose \ + --header="Choose:" \ + --header.bold \ + --header.foreground="#FCB8EF" \ + "Video" "Image" || exit 1) + case "$reddit_format_choice" in + Video) + reddit_vid_url=$(gum input \ + --header.bold \ + --header.foreground="#FCB8EF" \ + --header="Paste Reddit video URL." \ + --placeholder="Paste URL here...") + if [ -z "$reddit_vid_url" ]; then + echo + gum_error "Enter a URL..." + echo + elif [ -n "$reddit_vid_url" ]; then + yt-dlp "$reddit_vid_url" -o "$reddit_vid_dl_dir/%(title)s.%(ext)s" || exit 1 + echo + gum_done + echo + fi + ;; + Image) + echo + gum_error "Image download on Reddit not yet supported." + echo + ;; + esac + ;; +esac diff --git a/scripts/switch-audio b/scripts/switch-audio @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +focused_monitor="$(hyprctl monitors -j | jq -r '.[] | select(.focused == true).name')" + +sinks=$(pactl -f json list sinks | jq '[.[] | select((.ports | length == 0) or ([.ports[]? | .availability != "not available"] | any))]') +sinks_count=$(echo "$sinks" | jq '. | length') + +if [ "$sinks_count" -eq 0 ]; then + notify-send -u normal "$focused_monitor" "No audio devices found" + exit 1 +fi + +current_sink_name=$(pactl get-default-sink) +current_sink_index=$(echo "$sinks" | jq -r --arg name "$current_sink_name" 'map(.name) | index($name)') + +if [ "$current_sink_index" != "null" ]; then + next_sink_index=$(((current_sink_index + 1) % sinks_count)) +else + next_sink_index=0 +fi + +next_sink=$(echo "$sinks" | jq -r ".[$next_sink_index]") +next_sink_name=$(echo "$next_sink" | jq -r '.name') + +next_sink_description=$(echo "$next_sink" | jq -r '.description') +if [ "$next_sink_description" = "(null)" ] || [ "$next_sink_description" = "null" ] || [ -z "$next_sink_description" ]; then + sink_id=$(echo "$next_sink" | jq -r '.properties."object.id"') + next_sink_description=$(wpctl status | grep -E "\s+\*?\s+${sink_id}\." | sed -E 's/^.*[0-9]+\.\s+//' | sed -E 's/\s+\[.*$//') +fi + +if [ "$next_sink_name" != "$current_sink_name" ]; then + pactl set-default-sink "$next_sink_name" +fi + +notify-send -u low "Audio: $focused_monitor" "Switching to $next_sink_description" diff --git a/scripts/tmux-sessionizer b/scripts/tmux-sessionizer @@ -0,0 +1,344 @@ +#!/usr/bin/env bash +CONFIG_FILE_NAME="tmux-sessionizer.conf" +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/tmux-sessionizer" +CONFIG_FILE="$CONFIG_DIR/$CONFIG_FILE_NAME" +PANE_CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/tmux-sessionizer" +PANE_CACHE_FILE="$PANE_CACHE_DIR/panes.cache" + +# config file example +# ------------------------ +# # file: ~/.config/tmux-sessionizer/tmux-sessionizer.conf +# # If set this override the default TS_SEARCH_PATHS (~/ ~/personal ~/personal/dev/env/.config) +TS_SEARCH_PATHS=(~/git ~/Documents ~/.config ~/.local) +# # If set this add additional search paths to the default TS_SEARCH_PATHS +# # The number prefix is the depth for the Path [OPTIONAL] +# TS_EXTRA_SEARCH_PATHS=(~/ghq:3 ~/Git:3 ~/.config:2) +# # if set this override the TS_MAX_DEPTH (1) +# TS_MAX_DEPTH=2 +# This is not meant to override .tmux-sessionizer. At first i thought this +# would be a good command, but i don't think its ackshually what i want. +# +# Instead, its a list of commands to run on windows who's index is way outside +# of the first 10 windows. This allows you to create as many windows in your +# session as you would like without having your workflow interrupted by these +# programatic windows +# +# how to use: +# tmux-sessionizer -w 0 will execute the first command in window -t 69. +# TS_SESSION_COMMANDS=(<cmd1> <cmd2>) +# +# TS_LOG=true # will write logs to ~/.local/share/tmux-sessionizer/tmux-sessionizer.logs +# TS_LOG_FILE=<file> # will write logs to <file> Defaults to ~/.local/share/tmux-sessionizer/tmux-sessionizer.logs +# ------------------------ + +if [[ -f "$CONFIG_FILE" ]]; then + source "$CONFIG_FILE" +fi + +if [[ -f "$CONFIG_FILE_NAME" ]]; then + source "$CONFIG_FILE_NAME" +fi + +if [[ $TS_LOG != "true" ]]; then + if [[ -z $TS_LOG_FILE ]]; then + TS_LOG_FILE="$HOME/.local/share/tmux-sessionizer/tmux-sessionizer.logs" + fi + + mkdir -p "$(dirname "$TS_LOG_FILE")" +fi + +log() { + if [[ -z $TS_LOG ]]; then + return + elif [[ $TS_LOG == "echo" ]]; then + echo "$*" + elif [[ $TS_LOG == "file" ]]; then + echo "$*" >>"$TS_LOG_FILE" + fi +} + +session_idx="" +session_cmd="" +user_selected="" +split_type="" +VERSION="0.1.0" + +while [[ "$#" -gt 0 ]]; do + case "$1" in + -h | --help) + echo "Usage: tmux-sessionizer [OPTIONS] [SEARCH_PATH]" + echo "Options:" + echo " -h, --help Display this help message" + echo " -s, --session <name> session command index." + echo " --vsplit Create vertical split (horizontal layout) for session command" + echo " --hsplit Create horizontal split (vertical layout) for session command" + exit 0 + ;; + -s | --session) + session_idx="$2" + if [[ -z $session_idx ]]; then + echo "Session index cannot be empty" + exit 1 + fi + + if [[ -z $TS_SESSION_COMMANDS ]]; then + echo "TS_SESSION_COMMANDS is not set. Must have a command set to run when switching to a session" + exit 1 + fi + + if [[ -z "$session_idx" || "$session_idx" -lt 0 || "$session_idx" -ge "${#TS_SESSION_COMMANDS[@]}" ]]; then + echo "Error: Invalid index. Please provide an index between 0 and $((${#TS_SESSION_COMMANDS[@]} - 1))." + exit 1 + fi + + session_cmd="${TS_SESSION_COMMANDS[$session_idx]}" + + shift + ;; + --vsplit) + split_type="vsplit" + ;; + --hsplit) + split_type="hsplit" + ;; + -v | --version) + echo "tmux-sessionizer version $VERSION" + exit 0 + ;; + *) + user_selected="$1" + ;; + esac + shift +done + +log "tmux-sessionizer($VERSION): idx=$session_idx cmd=$session_cmd user_selected=$user_selected split_type=$split_type log=$TS_LOG log_file=$TS_LOG_FILE" + +# Validate split options are only used with session commands +if [[ -n "$split_type" && -z "$session_idx" ]]; then + echo "Error: --vsplit and --hsplit can only be used with -s/--session option" + exit 1 +fi + +sanity_check() { + if ! command -v tmux &>/dev/null; then + echo "tmux is not installed. Please install it first." + exit 1 + fi + + if ! command -v fzf &>/dev/null; then + echo "fzf is not installed. Please install it first." + exit 1 + fi +} + +switch_to() { + if [[ -z $TMUX ]]; then + log "attaching to session $1" + tmux attach-session -t "$1" + else + log "switching to session $1" + tmux switch-client -t "$1" + fi +} + +has_session() { + tmux list-sessions | grep -q "^$1:" +} + +hydrate() { + if [[ ! -z $session_cmd ]]; then + log "skipping hydrate for $1 -- using \"$session_cmd\" instead" + return + elif [ -f "$2/.tmux-sessionizer" ]; then + log "sourcing(local) $2/.tmux-sessionizer" + tmux send-keys -t "$1" "source $2/.tmux-sessionizer" c-M + elif [ -f "$HOME/.tmux-sessionizer" ]; then + log "sourcing(global) $HOME/.tmux-sessionizer" + tmux send-keys -t "$1" "source $HOME/.tmux-sessionizer" c-M + fi +} + +is_tmux_running() { + tmux_running=$(pgrep tmux) + + if [[ -z $TMUX ]] && [[ -z $tmux_running ]]; then + return 1 + fi + return 0 +} + +init_pane_cache() { + mkdir -p "$PANE_CACHE_DIR" + touch "$PANE_CACHE_FILE" +} + +get_pane_id() { + local session_idx="$1" + local split_type="$2" + init_pane_cache + grep "^${session_idx}:${split_type}:" "$PANE_CACHE_FILE" | cut -d: -f3 +} + +set_pane_id() { + local session_idx="$1" + local split_type="$2" + local pane_id="$3" + init_pane_cache + + # Remove existing entry if it exists + grep -v "^${session_idx}:${split_type}:" "$PANE_CACHE_FILE" >"${PANE_CACHE_FILE}.tmp" 2>/dev/null || true + mv "${PANE_CACHE_FILE}.tmp" "$PANE_CACHE_FILE" + + # Add new entry + echo "${session_idx}:${split_type}:${pane_id}" >>"$PANE_CACHE_FILE" +} + +cleanup_dead_panes() { + init_pane_cache + local temp_file="${PANE_CACHE_FILE}.tmp" + + while IFS=: read -r idx split pane_id; do + if tmux list-panes -a -F "#{pane_id}" 2>/dev/null | grep -q "^${pane_id}$"; then + echo "${idx}:${split}:${pane_id}" >>"$temp_file" + fi + done <"$PANE_CACHE_FILE" + + mv "$temp_file" "$PANE_CACHE_FILE" 2>/dev/null || touch "$PANE_CACHE_FILE" +} + +sanity_check + +# if TS_SEARCH_PATHS is not set use default +[[ -n "$TS_SEARCH_PATHS" ]] || TS_SEARCH_PATHS=(~/ ~/personal ~/personal/dev/env/.config) + +# Add any extra search paths to the TS_SEARCH_PATHS array +# e.g : EXTRA_SEARCH_PATHS=("$HOME/extra1:4" "$HOME/extra2") +# note : Path can be suffixed with :number to limit or extend the depth of the search for the Path + +if [[ ${#TS_EXTRA_SEARCH_PATHS[@]} -gt 0 ]]; then + TS_SEARCH_PATHS+=("${TS_EXTRA_SEARCH_PATHS[@]}") +fi + +# utility function to find directories +find_dirs() { + # list TMUX sessions + if [[ -n "${TMUX}" ]]; then + current_session=$(tmux display-message -p '#S') + tmux list-sessions -F "[TMUX] #{session_name}" 2>/dev/null | grep -vFx "[TMUX] $current_session" + else + tmux list-sessions -F "[TMUX] #{session_name}" 2>/dev/null + fi + + # note: TS_SEARCH_PATHS is an array of paths to search for directories + # if the path ends with :number, it will search for directories with a max depth of number ;) + # if there is no number, it will search for directories with a max depth defined by TS_MAX_DEPTH or 1 if not set + for entry in "${TS_SEARCH_PATHS[@]}"; do + # Check if entry as :number as suffix then adapt the maxdepth parameter + if [[ "$entry" =~ ^([^:]+):([0-9]+)$ ]]; then + path="${BASH_REMATCH[1]}" + depth="${BASH_REMATCH[2]}" + else + path="$entry" + fi + + [[ -d "$path" ]] && find "$path" -mindepth 1 -maxdepth "${depth:-${TS_MAX_DEPTH:-1}}" -path '*/.git' -prune -o -type d -print + done +} + +handle_session_cmd() { + log "executing session command $session_cmd with index $session_idx split_type=$split_type" + if ! is_tmux_running; then + echo "Error: tmux is not running. Please start tmux first before using session commands." + exit 1 + fi + + current_session=$(tmux display-message -p '#S') + + if [[ -n "$split_type" ]]; then + handle_split_session_cmd "$current_session" + else + handle_window_session_cmd "$current_session" + fi + exit 0 +} + +handle_window_session_cmd() { + local current_session="$1" + start_index=$((69 + $session_idx)) + target="$current_session:$start_index" + + log "target: $target command $session_cmd has-session=$(tmux has-session -t="$target" 2>/dev/null)" + if tmux has-session -t="$target" 2>/dev/null; then + switch_to "$target" + else + log "executing session command: tmux neww -dt $target $session_cmd" + tmux neww -dt $target "$session_cmd" + hydrate "$target" "$selected" + tmux select-window -t $target + fi +} + +handle_split_session_cmd() { + local current_session="$1" + cleanup_dead_panes + + # Check if pane already exists + local existing_pane_id=$(get_pane_id "$session_idx" "$split_type") + + if [[ -n "$existing_pane_id" ]] && tmux list-panes -a -F "#{pane_id}" 2>/dev/null | grep -q "^${existing_pane_id}$"; then + log "switching to existing pane $existing_pane_id" + tmux select-pane -t "$existing_pane_id" + if [[ -z $TMUX ]]; then + tmux attach-session -t "$current_session" + else + tmux switch-client -t "$current_session" + fi + else + # Create new split + local split_flag="" + if [[ "$split_type" == "vsplit" ]]; then + split_flag="-h" # horizontal layout (vertical split) + else + split_flag="-v" # vertical layout (horizontal split) + fi + + log "creating new split: tmux split-window $split_flag -c $(pwd) $session_cmd" + local new_pane_id=$(tmux split-window $split_flag -c "$(pwd)" -P -F "#{pane_id}" "$session_cmd") + + if [[ -n "$new_pane_id" ]]; then + set_pane_id "$session_idx" "$split_type" "$new_pane_id" + log "created pane $new_pane_id for session_idx=$session_idx split_type=$split_type" + fi + fi +} + +if [[ ! -z $session_cmd ]]; then + handle_session_cmd +elif [[ ! -z $user_selected ]]; then + selected="$user_selected" +else + selected=$(find_dirs | fzf) +fi + +if [[ -z $selected ]]; then + exit 0 +fi + +if [[ "$selected" =~ ^\[TMUX\]\ (.+)$ ]]; then + selected="${BASH_REMATCH[1]}" +fi + +selected_name=$(basename "$selected" | tr . _) + +if ! is_tmux_running; then + tmux new-session -ds "$selected_name" -c "$selected" + hydrate "$selected_name" "$selected" +fi + +if ! has_session "$selected_name"; then + tmux new-session -ds "$selected_name" -c "$selected" + hydrate "$selected_name" "$selected" +fi + +switch_to "$selected_name" diff --git a/scripts/tx b/scripts/tx @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +touch "$1" && chmod +x "$1" diff --git a/scripts/vic b/scripts/vic @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +RED="\e[31m" RESET="\e[0m" + +cmd=$(which "$1" 2>/dev/null) +editor="${EDITOR:-nvim}" + +if [ -z "$cmd" ]; then + printf '\n%b\n' "${RED}Error${RESET}: command not in path or wrong name." +else + "$editor" "$cmd" || exit 0 +fi diff --git a/scripts/vidl b/scripts/vidl @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +set -eou pipefail + +msg() { + case "$1" in + "error") gum log --structured --level error "$2"; exit 1 ;; + "info") gum log --structured --level info "$2" ;; + "success") gum log --structured --level success "$2" ;; + "complete") gum spin --spinner "globe" --title "Done! Press any key to close..." -- bash -c 'read -n 1 -s' ;; + esac +} + +Url=$(gum input --header.bold --header.foreground="#FCB8EF" --header="URL of video:" --placeholder="Enter URL..." || msg error "Please enter a url.") +VideoDownloadMode=$(gum choose --header="How many files to download:" --header.bold --header.foreground="#FCB8EF" "Single" "Batch" || msg error "Please choose a video download mode.") +DownloadDirectory=$(find "$HOME/Videos" -type d | fzf --layout=reverse --height=50% --border || msg error "Did not select a download directory.") + +if [ -n "$Url" ]; then + case "$VideoDownloadMode" in + Single) + VideoQuality=$(gum choose --header="Choose video quality:" --header.bold --header.foreground="#FCB8EF" "Default" "720p" "1080p" || msg error "Please choose video quality.") + case "$VideoQuality" in + Default) + SeekOptions=$(gum choose --header="Download a portion of video?" --header.bold --header.foreground="#FCB8EF" "1. No Seek" "2. One point to Another" "3. One point to End of video") + case "$SeekOptions" in + 1*) + yt-dlp -N 5 -o "$DownloadDirectory/%(title)s.%(ext)s" "$Url" + ;; + 2*) + StartSeek=$(gum input --header.bold --header.foreground="#FCB8EF" --header="Enter seek start point:" --placeholder="HH:MM:SS or MM:SS or seconds") + EndSeek=$(gum input --header.bold --header.foreground="#FCB8EF" --header="Enter seek end point:" --placeholder="HH:MM:SS or MM:SS or seconds") + yt-dlp --download-sections "*$StartSeek-$EndSeek" -o "$DownloadDirectory/%(title)s.%(ext)s" "$Url" + ;; + 3*) + StartSeek=$(gum input --header.bold --header.foreground="#FCB8EF" --header="Enter seek start point:" --placeholder="HH:MM:SS or MM:SS or seconds") + yt-dlp --download-sections "*$StartSeek-inf" -N 5 -o "$DownloadDirectory/%(title)s.%(ext)s" "$Url" + ;; + esac + ;; + 720p) + SeekOptions=$(gum choose --header="Download a portion of video?" --header.bold --header.foreground="#FCB8EF" "1. No Seek" "2. One point to Another" "3. One point to End of video") + case "$SeekOptions" in + 1*) + yt-dlp -f "best[height<=720]" -N 5 -o "$DownloadDirectory/%(title)s.%(ext)s" "$Url" + ;; + 2*) + StartSeek=$(gum input --header.bold --header.foreground="#FCB8EF" --header="Enter seek start point:" --placeholder="HH:MM:SS or MM:SS or seconds") + EndSeek=$(gum input --header.bold --header.foreground="#FCB8EF" --header="Enter seek end point:" --placeholder="HH:MM:SS or MM:SS or seconds") + yt-dlp -f "best[height<=720]" --download-sections "*$StartSeek-$EndSeek" -o "$DownloadDirectory/%(title)s.%(ext)s" "$Url" + ;; + 3*) + StartSeek=$(gum input --header.bold --header.foreground="#FCB8EF" --header="Enter seek start point:" --placeholder="HH:MM:SS or MM:SS or seconds") + yt-dlp -f "best[height<=720]" --download-sections "*$StartSeek-inf" -N 5 -o "$DownloadDirectory/%(title)s.%(ext)s" "$Url" + ;; + esac + ;; + 1080p) + SeekOptions=$(gum choose --header="Download a portion of video?" --header.bold --header.foreground="#FCB8EF" "1. No Seek" "2. One point to Another" "3. One point to End of video") + case "$SeekOptions" in + 1*) + yt-dlp -f "best[height<=1080]" -N 5 -o "$DownloadDirectory/%(title)s.%(ext)s" "$Url" + ;; + 2*) + StartSeek=$(gum input --header.bold --header.foreground="#FCB8EF" --header="Enter seek start point:" --placeholder="HH:MM:SS or MM:SS or seconds") + EndSeek=$(gum input --header.bold --header.foreground="#FCB8EF" --header="Enter seek end point:" --placeholder="HH:MM:SS or MM:SS or seconds") + yt-dlp -f "best[height<=1080]" --download-sections "*$StartSeek-$EndSeek" -o "$DownloadDirectory/%(title)s.%(ext)s" "$Url" + ;; + 3*) + StartSeek=$(gum input --header.bold --header.foreground="#FCB8EF" --header="Enter seek start point:" --placeholder="HH:MM:SS or MM:SS or seconds") + yt-dlp -f "best[height<=1080]" --download-sections "*$StartSeek-inf" -N 5 -o "$DownloadDirectory/%(title)s.%(ext)s" "$Url" + ;; + esac + ;; + esac + ;; + Batch) ;; + esac +fi diff --git a/scripts/volume-control b/scripts/volume-control @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Taken from: https://github.com/salanpro/hyprland/tree/047f998e710b0ffe8d841a89fb7749b50c931b2c/.config/hypr/scripts + +# Volume up and down +if [[ "$1" == "up" ]]; then + wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ \ + && wpctl get-volume @DEFAULT_AUDIO_SINK@ | \ + awk '{print int($2*100)}' | xargs -I[] \ + notify-send -e -u low -h string:x-canonical-private-synchronous:volume_notif -h \ + int:value:[] "󰓃 Volume: []%" +elif [[ "$1" == "down" ]]; then + wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%- \ + && wpctl get-volume @DEFAULT_AUDIO_SINK@ | \ + awk '{print int($2*100)}' | xargs -I[] \ + notify-send -e -u low -h string:x-canonical-private-synchronous:volume_notif -h \ + int:value:[] "󰓃 Volume: []%" +elif [[ "$1" == "mute" ]]; then + wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle \ + && (wpctl get-volume @DEFAULT_AUDIO_SINK@ \ + | grep -q MUTED && \ + notify-send -e -u low -h \ + string:x-canonical-private-synchronous:volume_notif \ + "󰝟 Muted" || wpctl get-volume @DEFAULT_AUDIO_SINK@ | \ + awk '{print int($2*100)}' | xargs -I[] \ + notify-send -e -u low -h string:x-canonical-private-synchronous:volume_notif -h \ + int:value:[] "󰓃 Volume: []%") +fi