#!/bin/bash # Version 0.3.0 # Sweetbbak # ╭───────────────────────────────╮ # │ Catnip │ # ╰───────────────────────────────╯ # set -x # source user configuration # SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) # declare -a array # 0 is Off 1 is On show_menu=0 # trap - SIGPIPE # Show help menu print_help() { echo -e '\n ' exe="${0##*/}" echo -e "\e[3m\e[3m${exe} [OPTIONS] \e[23m" echo -e '\n ' echo -e "\e[3m\e[1m\e[4mOptions:\e[23m\e[24m " echo -e ' \e[3m-h, --help\e[23m Show help text' echo -e ' \e[3m-v, --version\e[23m Print version information' echo -e ' \e[3m-u, --update\e[23m Update from github - back the script up if you have made personal changes' echo -e ' \e[3m-f, --find\e[23m Command to use for finding images ' echo -e ' \e[3m-e, --ext\e[23m Extensions types to search the given directory for example cn -e png -e webp -e gif [options] ' echo -e ' \e[3m-w, --wall\e[23m Command to set wallpaper' echo -e ' \e[3m-s, --stdin\e[23m (Not Implemented, opt for auto-detect) Read from stdin' echo -e ' \e[3m-p, --preview\e[23m Manually set preview method ' } cleanup() { # re-enable line wrap | unhide cursor | clear | # set scroll region to default + cursor 0, 0 | restore main screen buffer printf '\e[?7h\e[?25h\e[2J\e[;r\e[?1049l' # restore echo input stty echo # restore from tput tput rmcup ; tput cnorm ; clear exit 0 } dependency_check() { for x in "$@"; do if ! command -v "$x" > /dev/null; then return 1 else return 0 fi done } dep_ch() { for dep; do command -v "$dep" >/dev/null || notify-send "Program \"$dep\" not found. Please install it." done } save_terminal_state() { tput smcup } prepare_terminal_state() { get_term_size tput smcup && clear && stty -echo # printf '\e[?25l' printf '\e[?1049h\e[?7l\e[?25l\e[2J\e[1;%sr' "$LINES" } unhide_cursor() { printf '\e[?25h' } hide_curs() { tput civis stty -echo } get_term_size() { read -r LINES COLUMNS < <(stty size) } clear_screen() { printf '\e[%sH\e[9999C\e[1;%sr' \ "$((LINES-2))" "$LINES" } update_script() { update=$(curl -s "https://raw.githubusercontent.com/sweetbbak/catnip/main/cn" || sfdfdf "Connection error") update="$(printf '%s\n' "$update" | diff -u "$(which cn)" -)" if [ -z "$update" ]; then printf "Script is up to date :)\n" else if printf '%s\n' "$update" | patch "$(which cn)" -; then printf "Script has been updated\n" else printf "Can't update for some reason!\n" fi fi exit 0 } # function to check if a given value exists in a given array in_array() { local haystack="${1}[@]" local needle=${2} for i in ${!haystack}; do if [[ ${i} == "${needle}" ]]; then return 0 fi done return 1 } # ╭───────────────────────────────╮ # │ ANSI Escape Codes │ # ╰───────────────────────────────╯ # Cursor to absolute position cup() { printf '\e[%s;%sH' "$1" "$2" ; } # Cursor 0, 0 cup_home() { printf '\e[H' ; } # Move cursor N lines cup_up() { printf '\e[%sA' "$1" ; } cup_down() { printf '\e[%sB' "$1" ; } cup_right() { printf '\e[%sC' "$1" ; } cup_left() { printf '\e[%sD' "$1" ; } # Save and reset cursor position cup_save() { printf '\e[s' ; } cup_restore() { printf '\e[u' ; } # Erase from cursor to end of line cup_x_eol() { printf '\e[K'; } # Erase from cursor to start of line cup_x_sol() { printf '\e[1K'; } # erase entire line cup_x_dl() { printf '\e[2K'; } # erase current line to bottom screen cup_clear_to_bottom() { printf '\e[J' ; } # erase current line to top screen cup_clear_to_top() { printf '\e[1J'; } # clear screen cup_x_clear() { printf '\e[2J'; } # clear screen and move to Home cup_screen_home() { printf '\e[2J\e[H'; } # GRADIENT + White on Background # for the ansi option: \e[0;38;2;r;g;bm or \e[0;48;2;r;g;bm : # lightblu='\e[0;48;2;063;127;255m' # blu2='\e[0;48;2;063;063;255m' # blu3='\e[0;48;2;063;000;255m' # purple='\e[0;48;2;063;000;191m' # NC='\e[0m' # Gradient + Foreground' # fg_blu_1='\e[0;38;2;127;191;255m' # lightest # fg_blu_2='\e[0;38;2;127;127;255m' # fg_blu_3='\e[0;38;2;127;063;255m' # fg_blu_4='\e[0;38;2;127;000;255m' # darkest # Gradient purple in reverse # fg_purp_3='\e[0;38;2;127;000;255m' # darkest # fg_purp_2='\e[0;38;2;127;063;255m' # fg_purp_1='\e[0;38;2;127;127;255m' # fg_purp_0='\e[0;38;2;127;191;255m' # lightest blu_gradient=( '\e[0;38;2;127;191;255m' '\e[0;38;2;127;127;255m' '\e[0;38;2;127;063;255m' '\e[0;38;2;127;000;255m' '\e[0;38;2;127;000;255m' '\e[0;38;2;127;063;255m' '\e[0;38;2;127;127;255m' '\e[0;38;2;127;191;255m' ) gradient() { str="${*}" # string=aaaaa ; ${string:0:5} var:index:length - starts at 0 indexing array_index=0 color_index=0 str_len="${#str}" # length to cut string into and change colors length="$((str_len / 8))" # color index loops in $length so if length=3 than its 0, 3, 6, 9 etc.. until string is ate up while [[ $color_index -le $str_len ]]; do printf '%b' "${blu_gradient[$array_index]}${str:$color_index:$length}" color_index="$((color_index + length))" array_index="$((array_index + 1))" done # needs a newline otherwise it leaves a % sign printf "%s\n" "" } garbled_load_text() { LC_ALL=C get_term_size clear local strlen strlen=$COLUMNS # strlen=100 yyy="$((COLUMNS / 2 - $((strlen / 2))))" xxx="$((LINES / 2))" for ((p=0;p<20;p++)); do tput el string=$(tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~' ?@[\]^_`{|}~' /tmp/myfifo; read -r "${var?}" < /tmp/myfifo; } # in theory we set VAR to an array asynchronously but idk im not that smart # assign VAR bar # ╭───────────────────────────────╮ # │ Application Functions │ # ╰───────────────────────────────╯ draw_box() { tput clear # get_term_size horiz="$((COLUMNS - 1))" # vert="$((LINES - 2 ))" vert="$LINES" itera=1 # box_char0="╭" # box_char1="╮" # box_char2="╰" # box_char3="╯" # box_char4="│" # box_char5="─" # box_char6="┑" # box_char7="┎" # box_char8="├" # box_char9="┤" # put corner left & right top # and then top line cup_home && cup_x_eol && printf '╭' perl -E "print '─' x $horiz" cup_right 1 && printf '╮' # input vertical lines on both sides for ((itera=1; itera= array_count )); then i=0 fi image="${array[$i]}" # image="$(realpath -- "$image")" image="${image// /\ }" # tput cup 1 1 ; tput ech "$((${#image}+9))" tput cup 1 1 ; tput ech "$((COLUMNS - 3))" gradient "${count} <=> ${image}" if [[ "$show_pre" -eq 1 ]]; then case "$img_protocol" in kitty) kitty +kitten icat --clear --scale-up --place "${size}x${size}@${lx}x${ly}" "${image}" ;; sixel|chafa) # need a new menu system for chafa unfortunately cant clear properly and draw menu tput cup 3 3 && chafa -c full -f sixels -s "${size}x${size}" "${image}" ;; iterm) tput cup 3 3 && chafa -c full -f iterm -s "${size}x${size}" "${image}" ;; ascii) tput cup 3 3 && chafa -s "${size}x${size}" "${image}" ;; esac fi } # remove spaces and escape them. consider printf %q clean_image() { image="${array[$i]}" image="${image// /\ }" } # ╭───────────────────────────────╮ # │ Program Functions │ # ╰───────────────────────────────╯ set_bg() { wallpaper="$wallpaper" [ -z "$wallpaper" ] && wallpaper="$1" case "$wallpaper" in swww) swww img "$image" --transition-type center;; feh) feh --bg-fill --no-fehbg "$image";; nitrogen) nitrogen "$image";; hyprpaper) hyprpaper "$image";; *) $wallpaper "$image";; esac } show_preview() { # Toggle Images on and off for speed if [[ "$show_pre" -eq 0 ]]; then show_pre=1 else show_pre=0 fi } clear_scr() { tput clear get_term_size draw_box show_image } # ╭───────────────────────────────╮ # │ Arguments && Defaults │ # ╰───────────────────────────────╯ # --this is the first part shit starts happening at-- # declare -a extensions # declare findcmd # while [ $# -gt 0 ]; do # case "$1" in # -h|--help) print_help && exit 0 ;; # -v|--version) show_version && exit 0 ;; # -f|--find) shift && findcmd="${1}" ;; # -e|--ext) shift && extensions+=("${1}");; # -s|--stdin) ;; # -w|--wall) shift && wallpaper="${1}" ;; # -p|--preview) shift && img_protocol="${1}";; # *) [ -d "$1" ] && dir="$1" ;; # esac # shift # done # notify-send "ok: ${extensions[*]}" redraw() { get_term_size tput clear clear draw_box show_image } # save term state & hide cursor + opts save_terminal_state prepare_terminal_state # handle cleanup on exit so term doesn't get borked trap redraw WINCH trap cleanup EXIT INT SIGINT # what find command to use # if [ -z "$findcmd" ]; then # if dependency_check fd; then # findcmd="fd" # elif dependency_check find; then # findcmd="find" # else # findcmd="bash" # fi # fi # need a more comprehensive terminal check for img protocol # TODO add swayimg, uberzeug/Uberzeugpp as an option case "$TERM" in *kitty*) img_protocol="kitty" ;; *) img_protocol="chafa" ;; esac # ╭───────────────────────────────╮ # │ UI Commands │ # ╰───────────────────────────────╯ show_cmd_menu() { # Toggle menu if [[ "$show_menu" -eq 0 ]]; then show_pre=1 else show_pre=0 fi } search() { tput cup "$((LINES / 2 + LINES / 4))" 0 XX=$(printf "%s\n" "${array[@]}" | fzf --cycle --reverse --height=5% \ --bind 'enter:execute(printf {n} 2> /dev/stdout)+abort') i="$XX" image="${array[$i]}" show_image redraw } find_images() { # unset array # unset dir # declare dir dir="${1}" case "$findcmd" in fd) mapfile -d '' array < <(fd . "$dir" -0 -tf -e png -e jpg -e gif -e svg -e jpeg) ;; find) mapfile -d '' array < <(find "$dir" -print0 -type f \( -iname \*.jpg -o -iname \*.png -o -iname \*.gif -o -iname \*.webp \)) ;; bash) # I gotta figure out how to make them path safe quickly # mapfile -d '' array < <(bash -c "cd ~/Pictures && shopt -s globstar dotglob; stat --format \"~/Pictures/%n\" -- **/*.{jpg,png,gif} | sort -rn") # mapfile array < <(bash -c "cd \"$dir\" && shopt -s globstar dotglob; printf '${dir}/%s\n' -- **/*.{jpg,png,gif} ") # mapfile array < <(bash -c "cd \"$dir\" && shopt -s globstar dotglob; dx=$(printf "${dir}/%s\n" -- **/*.{jpg,png,gif} | tr -d' ' '')") mapfile -d '' array < <(find "$dir" -print0 -type f \( -iname \*.jpg -o -iname \*.png -o -iname \*.gif -o -iname \*.webp \)) # array=$(cd "${dir}" && echo **/*.{jpg,png,gif} ) ;; esac redraw } pick-dir() { directory="${2:-$HOME}" tput cup "$((LINES / 2 + LINES / 4))" 0 dirpick=$(fd . "$directory" -td --absolute-path | fzf --cycle --reverse --height=5% \ --bind 'enter:execute(printf {} 2> /dev/stdout)+abort') unset "${array[@]}" find_images "$dirpick" show_image redraw } read_cmd() { cmd="$(printf "%s\n" "$@")" notify-send "sh -c $cmd" } cmdd() { # jump to the command line zone tput cup "$((LINES - 2 ))" 2 ; tput cnorm ; stty echo read -ren 33 keys tput civis ; stty -echo ; tput cup "$((LINES - 2 ))" 2 ; tput ech 33 # TODO add a way to add custom functions or interface with the shell directly case "$keys" in search) search ;; fzf) search ;; cd*) pick-dir "$keys" ;; set-bg) set_bg "$wallpaper" ;; test) read_cmd "$keys" "$image" ;; *) read_cmd "$keys" "$image" ;; esac } declare -a array=() declare -a extensions declare findcmd if [ -t 0 ]; then while [ $# -gt 0 ]; do case "$1" in -h|--help) print_help && exit 0 ;; -v|--version) show_version && exit 0 ;; -f|--find) shift && findcmd="${1}" ;; -e|--ext) shift && extensions+=("${1}");; -s|--stdin) ;; -w|--wall) shift && wallpaper="${1}" ;; -p|--preview) shift && img_protocol="${1}";; *) [ -d "$1" ] && dir="$1" ;; esac shift done if [ -z "$findcmd" ]; then if dependency_check fd; then findcmd="fd" elif dependency_check find; then findcmd="find" else findcmd="bash" fi fi if [ -z "${extensions[*]}" ]; then extensions=(-e png -e jpg -e gif -e webp) else extensions=("${extensions[@]/#/-e }") fi printf "%s " "${extensions[*]}" # sleep 10 dir="${dir:-$HOME/Pictures}" # if stdin isnt open find images "${array[@]/#/prefix_}" # mapfile -d '' array < <(fd . "$dir" -0 -tf -e png -e jpg -e gif -e svg -e jpeg) # mapfile -d '' array < <(fd . "$dir" -0 -tf "${extensions[@]/#/-e }") # mapfile -d '' array < <(fd . "$dir" -0 -tf "${extensions[@]}") find_images "$dir" else # else pipe is open mapfile array < <(cat -) exec 0<&- fi # ╭───────────────────────────────╮ # │ Intitialize Program │ # ╰───────────────────────────────╯ show_pre=1 show_menu=0 # cmd_mode=0 x=0 # size=0 get_term_size size=$((LINES + 20)) lx=2 ly=2 garbled_load_text # tput cup $((LINES / 2)) $((COLUMNS / 2)) && cat_logo && sleep .5 draw_box # sleep 10 while true; do # sleep to avoid reading keys too fast # read -r -s -N1 key < /dev/stdin 2> /dev/null read -r -s -N1 key < /dev/tty 2> /dev/null sleep .01 # read -r -s -N1 key # init and re-init the amount of images in the array // +our counter string array_count="${#array[@]}" count="(${i}/${array_count})" case "$key" in j|$'\x1b\x5b\x41') i=$((i-1)) ; show_image ;; k|$'\x1b\x5b\x42') i=$((i+1)) ; show_image ;; J) i=$((i-5)) ; show_image ;; K) i=$((i+5)) ; show_image ;; h) lx=$((lx-5)) ; show_image ;; l) lx=$((lx+5)) ; show_image ;; m) size=$((size+5)) ; show_image ;; n) size=$((size-5)) ; show_image ;; $' '|$'\x20') show_menu=1 ;; # space key # $'\x1b') echo Escape Key ;; # escape key i|:|\;) cmdd ;; # insert p) show_preview ;; # toggle preview on/off f|F) swww img "$image" --transition-type center;; # set as background o) swayimg "$image" ;; # open externally c) clear_scr && show_image ;; x) clean_image && wl-copy "$image" ;; $'\n'|$'\x0a') printf "%s\n" "${image}" 2>/dev/stdout && exit 0;; q) exit 0 ;; esac done