summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/battery-monitor23
-rwxr-xr-xscripts/brightness-control18
-rwxr-xr-xscripts/browser29
-rwxr-xr-xscripts/ffut149
-rwxr-xr-xscripts/hyprwall32
-rwxr-xr-xscripts/nightlight-toggle40
-rwxr-xr-xscripts/pkg-aur-install71
-rwxr-xr-xscripts/pkg-install71
-rwxr-xr-xscripts/pkg-remove81
-rwxr-xr-xscripts/pkg-update107
-rwxr-xr-xscripts/rehis60
-rwxr-xr-xscripts/restart-app31
-rwxr-xr-xscripts/rofi-menu105
-rwxr-xr-xscripts/rofi-powermenu38
-rwxr-xr-xscripts/rofi-screenrecord21
-rwxr-xr-xscripts/screenrecord140
-rwxr-xr-xscripts/socialdl224
-rwxr-xr-xscripts/switch-audio35
-rwxr-xr-xscripts/tmux-sessionizer344
-rwxr-xr-xscripts/tx3
-rwxr-xr-xscripts/vic12
-rwxr-xr-xscripts/vidl78
-rwxr-xr-xscripts/volume-control28
23 files changed, 1740 insertions, 0 deletions
diff --git a/scripts/battery-monitor b/scripts/battery-monitor
new file mode 100755
index 0000000..32a18dd
--- /dev/null
+++ 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
new file mode 100755
index 0000000..1c6b02c
--- /dev/null
+++ 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
new file mode 100755
index 0000000..3ed9494
--- /dev/null
+++ 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
new file mode 100755
index 0000000..87cf2b8
--- /dev/null
+++ 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
new file mode 100755
index 0000000..11c098a
--- /dev/null
+++ 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
new file mode 100755
index 0000000..7e09303
--- /dev/null
+++ 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
new file mode 100755
index 0000000..555ce74
--- /dev/null
+++ 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
new file mode 100755
index 0000000..b86e803
--- /dev/null
+++ 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
new file mode 100755
index 0000000..b26a752
--- /dev/null
+++ 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
new file mode 100755
index 0000000..bdf47f5
--- /dev/null
+++ 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
new file mode 100755
index 0000000..bf7b443
--- /dev/null
+++ 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
new file mode 100755
index 0000000..84edebc
--- /dev/null
+++ 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
new file mode 100755
index 0000000..199a32c
--- /dev/null
+++ 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
new file mode 100755
index 0000000..6209944
--- /dev/null
+++ 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
new file mode 100755
index 0000000..61e516c
--- /dev/null
+++ 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
new file mode 100755
index 0000000..f2c3b28
--- /dev/null
+++ 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
new file mode 100755
index 0000000..f9f046e
--- /dev/null
+++ 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
new file mode 100755
index 0000000..8b45090
--- /dev/null
+++ 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
new file mode 100755
index 0000000..e7251da
--- /dev/null
+++ 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
new file mode 100755
index 0000000..c21969b
--- /dev/null
+++ b/scripts/tx
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+touch "$1" && chmod +x "$1"
diff --git a/scripts/vic b/scripts/vic
new file mode 100755
index 0000000..d6ca7b1
--- /dev/null
+++ 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
new file mode 100755
index 0000000..207746d
--- /dev/null
+++ 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
new file mode 100755
index 0000000..bf8745d
--- /dev/null
+++ 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