From 5a7b10ca366e564c5d41703ce9e2caf182d3bcd6 Mon Sep 17 00:00:00 2001 From: TrudeEH Date: Wed, 5 Jun 2024 19:12:53 +0100 Subject: [PATCH] Add more dmenu scripts --- dotfiles/.local/bin/dmenu-bookmarks | 76 +++++++ dotfiles/.local/bin/dmenu-fm | 324 ++++++++++++++++++++++++++++ dotfiles/.local/bin/dmenu-menus | 17 -- dotfiles/.local/bin/dmenu-mpdmenu | 76 +++++++ dotfiles/.local/bin/dmenu-pass | 60 ++++++ dotfiles/.local/bin/dmenu-todo | 28 +++ 6 files changed, 564 insertions(+), 17 deletions(-) create mode 100755 dotfiles/.local/bin/dmenu-bookmarks create mode 100755 dotfiles/.local/bin/dmenu-fm delete mode 100755 dotfiles/.local/bin/dmenu-menus create mode 100755 dotfiles/.local/bin/dmenu-mpdmenu create mode 100755 dotfiles/.local/bin/dmenu-pass create mode 100755 dotfiles/.local/bin/dmenu-todo diff --git a/dotfiles/.local/bin/dmenu-bookmarks b/dotfiles/.local/bin/dmenu-bookmarks new file mode 100755 index 00000000..25c9180b --- /dev/null +++ b/dotfiles/.local/bin/dmenu-bookmarks @@ -0,0 +1,76 @@ +#! /bin/bash + +# set prefered launcher +PREFERED_LAUNCHER=dmenu +# set path where urls will be stored +URL_FILE_PATH=$HOME/dotfiles/bookmarks +# name of file urls will be stored in +URL_FILE_NAME=urls + +show_usage() { + printf "bmks: unix bookmark management that sucks less + +usage: +bmks help + show this help message +bmks add + add a new bookmark +bmks del + remove a bookmark +bmks ls + show all bookmarks +bmks dmenu + manual switch for displaying bookmarks in dmenu +bmks fzf + manual switch for displaying bookmarks in fzf + +Configuration is done by directly editing the script. Two launchers are available (dmenu and fzf). You can specify which one to use by adding to the PREFERED_LAUNCHER variable directly in the script. Both will display a menu that will allow you to choose a bookmark and open it in your default browser. + +If you would prefer to have your bookmarks stored in alternate location there are also variables that can be changed for that. The default is /home/user/.bmks/urls\n" +} + +bmks_add() { + [ -z "$url" ] && printf "Error: url must be provided\n\n" && show_usage && exit 0 + printf "Description: " + read description + [ -z "$description" ] && echo "$url" >> $URL_FILE_PATH/$URL_FILE_NAME + [ -n "$description" ] && echo "$description - $url" >> $URL_FILE_PATH/$URL_FILE_NAME +} + +bmks_ls() { + bmks_check + cat $URL_FILE_PATH/$URL_FILE_NAME | sort +} + +bmks_del() { + bmks_check + case $PREFERED_LAUNCHER in + dmenu) sed -i "/$(cat $URL_FILE_PATH/$URL_FILE_NAME | sort | dmenu -l $(cat $URL_FILE_PATH/$URL_FILE_NAME | wc -l))/d" $URL_FILE_PATH/$URL_FILE_NAME ;; + fzf) sed -i "/$(cat $URL_FILE_PATH/$URL_FILE_NAME | sort | fzf)/d" $URL_FILE_PATH/$URL_FILE_NAME ;; + esac +} + +bmks_display() { + bmks_check + case $PREFERED_LAUNCHER in + dmenu) cat $URL_FILE_PATH/$URL_FILE_NAME | sort | dmenu -l $(cat $URL_FILE_PATH/$URL_FILE_NAME | wc -l) | awk '{print $(NF)}' | xargs -I '{}' $BROWSER {} ;; + fzf) cat $URL_FILE_PATH/$URL_FILE_NAME | sort | fzf | awk '{print $(NF)}' | xargs -I '{}' $BROWSER {} ;; + esac +} + +bmks_check() { + [ ! -s $URL_FILE_PATH/$URL_FILE_NAME ] && printf "Error: No bookmarks found to display. Try adding some!\n\n" && show_usage && exit 0 +} + +[ ! -d $URL_FILE_PATH ] && mkdir $URL_FILE_PATH +[ ! -f $URL_FILE_PATH/$URL_FILE_NAME ] && touch $URL_FILE_PATH/$URL_FILE_NAME + +case "$1" in + "help") show_usage ;; + "add") url=$2; bmks_add ;; + "del") bmks_del ;; + "ls") bmks_ls ;; + "dmenu") PREFERED_LAUNCHER=$1; bmks_display ;; + "fzf") PREFERED_LAUNCHER=$1; bmks_display ;; + *) bmks_display ;; +esac diff --git a/dotfiles/.local/bin/dmenu-fm b/dotfiles/.local/bin/dmenu-fm new file mode 100755 index 00000000..82d2f229 --- /dev/null +++ b/dotfiles/.local/bin/dmenu-fm @@ -0,0 +1,324 @@ +#!/bin/sh + +PROGRAM_NAME=$(basename $0) # This will be used in a few messages + +main() { + parse_opts "$@" + [ -z "$mode" ] && mode=open # The default mode + + if [ -n "$copy" -a "$copy" != false -a "$cat" = true ]; then + prompt_copy_contents "$@" + elif [ "$open" = true ]; then + prompt_open "$@" + elif [ "$cat" = true ]; then + prompt_print_contents "$@" + elif [ "$print" = true ]; then + prompt_print "$@" + elif [ -n "$copy" -a "$copy" != false ]; then + prompt_copy "$@" + else + prompt_$mode "$@" + fi +} + +prompt_base() { + [ -z "$length" ] && length=10 + + if [ "$case_sensitivity" = sensitive ]; then + backtrack() { sed 's|\(.*/'$sel'[^/]*\).*|\1|'; } + s=+i + else + backtrack() { perl -pe 's|(.*/'$sel'[^/]*).*|$1|i'; } + i=-i + fi + + [ -z "$menu" ] && menu="dmenu" + if [ "$menu" = dmenu ]; then menu() { $menu $i -l $length -p "$@"; } + elif [ "$menu" = fzf ]; then menu() { $menu $s $i --header="$@"; } + else menu() { $menu; }; fi + + if [ "$path" = "full" ]; then prompt() { p="$target"; } + else prompt() { p="$(printf '%s' "$target" | sed 's|^'"$HOME"'|~|')"; }; fi + + # Only GNU `ls` supports `--group-directories-first` + if [ "$(ls --version | head -1 | cut -d " " -f 2-3)" = "(GNU coreutils)" ] + then + list() { ls --group-directories-first "$@"; } + else + list() { + (ls -l "$@" | grep "^d" + ls -l "$@" | grep -vE "^d|^total") | tr -s " " | cut -d " " -f 9- + } + fi + + # Commonly used functions in DFM + truepath() { sh -c "realpath -s "$sel""; } + slash() { printf '%s' "$target/$sel" | rev | cut -b 1-2; } + check() { file -E "$@" | grep "(No such file or directory)$"; } + fullcmd() { + printf '%s\n' "$PWD" > "$cache_file" + printf '%s' "$target" | sed -e "s/'/'\\\\''/g;s/\(.*\)/'\1'/" | cmd + } + + while true; do + p="$prompt" # Reset the prompt to have it update + [ -z "$p" ] && prompt # Make the prompt if it does not exist + + # This is where the file manager actually first opens. + sel="$(printf '%s' "$(list "$target"; list -A "$target" | grep '^\.')" | + menu "$p")" + + # Exit if the user presses Escape, Control-C, etc. + exit_code=$? + if [ "$exit_code" -ne 0 ]; then + printf '%s\n' "$target" > "$cache_file" + exit $exit_code + fi + + if [ $(printf '%s' "$sel" | wc -l) -eq 0 ]; then + # If the input box is empty, go to the parent directory + if [ "$sel" = "" ]; then + newt="$(realpath -s "$target/..")" + # Relative directories + elif [ -e "$target/$sel" -a "$(slash)" != // ]; then + newt="$(realpath -s "$target/$sel")" + elif [ ! -e "$target/$sel" -a $(printf '%s' "$target" | + grep $i "$(sh -c "printf '%s' "$sel"")" | wc -l) -eq 1 ] + then + # Go to a lower directory using the input box + if [ ! -e "$(truepath)" ]; then + newt="$(printf '%s' "$target" | backtrack)" + # Go to certain directories like `~` `$HOME`, etc. + else + newt="$(truepath)" + fi + # Go to a directory when the input box begins with `/` + elif [ -e "$(truepath)" ] && + [ ! -e "$target/$sel" -o "$(slash)" = "//" ] + then + newt="$(truepath)" + else + # This applies to wildcards + newt="$(realpath -s "$target/$sel")" + fi + else + newt="$sel" + fi + + # If the current working directory is not empty + if [ $(ls | wc -l) -ge 1 ]; then + target="$newt" + if [ ! -d "$target" ]; then + # Check if the user used a wildcard + if [ $(printf '%s' "$target" | grep "*" | wc -l) -ge 1 -a\ + $(check "$target" | wc -l) -eq 1 ] + then + IFS= # Needed to make wildcards work + ls "$PWD"/$sel 1> /dev/null 2>& 1 + # Target is a file or directory + if [ $? -ne 0 ]; then + target="$PWD" + # Target is a wildcard + else + target=$(ls -d "$PWD"/$sel) fullcmd + exit 0 + fi + # No such file or directory + elif [ $(printf '%s' "$target" | wc -l) -eq 0 -a\ + $(check "$target" | wc -l) -eq 1 ] + then + target="$PWD" + # More than one selection + elif [ $(printf '%s' "$target" | wc -l) -gt 0 ]; then + target=$(printf '%s' "$target" | sed 's|^|'"$PWD"/'|') + fullcmd + exit + # Exactly one selection + else + fullcmd + exit + fi + # Target is a directory + else + PWD="$target" + fi + fi + done +} + +prompt_print() { + cmd () { xargs ls -d; } + prompt_base "$@" +} + +prompt_print_contents() { + cmd() { xargs cat; } + prompt_base "$@" +} + +prompt_open() { + if [ -x "$(command -v sesame)" ]; then cmd() { xargs sesame; } + else cmd() { xargs xdg-open; }; fi + prompt_base "$@" +} + +prompt_copy() { + cmd() { tr '\n' ' ' | xclip -r -i -selection $copy; } + prompt_base "$@" +} + +prompt_copy_contents() { + if [ "$(file -b "$target" | cut -d " " -f2)" = "image" ]; then + cmd() { xargs xclip -i -selection $copy -t image/png; } + else + cmd() { xargs xclip -r -i -selection $copy; } + fi + prompt_base "$@" +} + +help() { + printf "Usage:\t$0 [options] [target] [prompt] + +Options: + +Modes: +-p|--print │ Print the output of the selection +-o|--open │ Open the appropriate program for the selection (default) + + --cat │ Concatenate the selections before using a mode +-c|--copy=[CLIPBOARD] │ Copy the output of the selection + --no-copy │ Do not copy (always overrides \`--copy\`) + │ +-r|--restore │ Start from the previous path restored from the last run +-s|--sensitive │ Use case-sensitive matching +-i|--insensitive │ Use case-insensitive matching (default) +-m|--menu=MENU │ Choose which menu program to use (default: dmenu) +-l|--length=LENGTH │ Specify the length of dmenu (default: 10) + │ +-f|--full │ Use the full path for the prompt +-a|--abbreviated │ Use the abbreviated path for the prompt (default) + │ +-h|--help │ Print this help message and exit +"; } + +parse_opts() { + : "${config_dir:=${XDG_CONFIG_HOME:-$HOME/.config}/$PROGRAM_NAME}" + : "${config_file:=$config_dir/$PROGRAM_NAME.conf}" + [ -f "$config_file" ] && . "$config_file" + + : "${cache_dir:=${XDG_CACHE_DIR:-$HOME/.cache}/$PROGRAM_NAME}" + : "${cache_file:=$cache_dir/path}" + # Create the cache file if it doesn't exist + if [ ! -f "$cache_file" ]; then + mkdir -p "$(dirname "$cache_file")" && + touch "$cache_file" + fi + + needs_arg() { + if [ -z "$OPTARG" ]; then + printf '%s\n' "No arg for --$OPT option" >&2 + exit 2 + fi + } + + while getopts hpcosim:l:far-: OPT; do + # Support long options: https://stackoverflow.com/a/28466267/519360 + if [ "$OPT" = "-" ]; then + OPT="${OPTARG%%=*}" + OPTARG="${OPTARG#$OPT}" + OPTARG="${OPTARG#=}" + fi + case "$OPT" in + h|help) + help + exit 0 + ;; + p|print) + print=true + ;; + c|copy) + shift + [ $(printf '%s' "$OPT" | wc -c) -eq 1 ] && OPTARG="$1" + case "$OPTARG" in + primary|secondary|clipboard|buffer-cut) + copy="$OPTARG" + ;; + *) + copy=clipboard + ;; + esac + [ -n "$1" -a "$OPTARG" = "$1" -a "$copy" = "$OPTARG" ] && shift + ;; + no-copy) + copy=false + ;; + cat) + cat=true + ;; + o|open) + open=true + ;; + s|sensitive) + case_sensitivity="sensitive" + ;; + i|insensitive) + case_sensitivity="insensitive" + ;; + m|menu) + needs_arg + menu="$OPTARG" + ;; + l|length) + needs_arg + length=$OPTARG + ;; + f|full) + path="full" + ;; + a|abbreviated) + path="abbreviated" + ;; + r|restore) + restore=true + ;; + ??*) + printf '%s\n' "Illegal option --$OPT" >&2 + exit 2 + ;; + ?) # Error reported via `getopts` + exit 2 + ;; + esac + done + shift $((OPTIND-1)) # Remove option arguments from the argument list + + if [ -n "$1" ]; then + target="$1" + elif [ -z "$target" ]; then + if [ "$restore" = true -a -s "$cache_file" ]; then + target="$(cat "$cache_file")" + else + target="$PWD" + fi + fi + + if [ -d "$target" ]; then + target="$(realpath -s "$target")" + PWD="$target" + else + # Zero out cache file. + [ "$restore" = true -a -s "$cache_file" ] && > "$cache_file" + printf '%s\n' "$PROGRAM_NAME: \`$target\` is not a directory." >&2 + exit 2 + fi + + [ -n "$2" ] && prompt="$2" + # If the prompt is the same as the target, uset the prompt so that it can + # update. This is useful if you set a prompt in your configuration file but + # want to use the default prompt + if [ -n "$prompt" ] && [ "$(realpath -s "$prompt")" = "$target" ]; then + unset prompt + fi +} + +main "$@" diff --git a/dotfiles/.local/bin/dmenu-menus b/dotfiles/.local/bin/dmenu-menus deleted file mode 100755 index fa3fa694..00000000 --- a/dotfiles/.local/bin/dmenu-menus +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -script_path="$HOME/.local/bin" - -prompt="-p Menus:" - -# list only executable non-binay files - -run_dmenu="$(find $script_path/dmenu-* -maxdepth 1 -type f -executable \ - -exec grep -Iq . {} \; -print \ - | sed 's|^'$script_path/'||' \ - | sort \ - | dmenu $prompt)" - -eval "$script_path/$run_dmenu" - -exit 0 diff --git a/dotfiles/.local/bin/dmenu-mpdmenu b/dotfiles/.local/bin/dmenu-mpdmenu new file mode 100755 index 00000000..d32d9576 --- /dev/null +++ b/dotfiles/.local/bin/dmenu-mpdmenu @@ -0,0 +1,76 @@ +#!/bin/bash + +all_name='[ALL]' +mode=library + +d_artist() { + mpc list artist | sort -f | dmenu -p artist "${dmenu_args[@]}" +} + +d_album() { + local artist="$1" + local albums + + mapfile -t albums < <(mpc list album artist "$artist") + if (( ${#albums[@]} > 1 )) ; then + { + printf '%s\n' "$all_name" + printf '%s\n' "${albums[@]}" | sort -f + } | dmenu -p album "${dmenu_args[@]}" + else + # We only have one album, so just use that. + printf '%s\n' "${albums[0]}" + fi +} + +d_playlist() { + local format="%position% %title%" + local extra_format="(%artist% - %album%)" + local track + local num_extras + + # If all tracks are from the same artist and album, no need to display that + num_extras=$(mpc playlist -f "$extra_format" | sort | uniq | wc -l) + (( num_extras == 1 )) || format+=" $extra_format" + + track=$(mpc playlist -f "$format" | dmenu -p track "${dmenu_args[@]}") + printf '%s' "${track%% *}" +} + +i=2 + +for arg do + if [[ $arg == :: ]]; then + dmenu_args=( "${@:$i}" ) + break + fi + + case "$arg" in + -l) mode=library ;; + -p) mode=playlist ;; + esac + + let i++ +done + +case "$mode" in + library) + artist=$(d_artist) + [[ $artist ]] || exit 1 + + album=$(d_album "$artist") + [[ $album ]] || exit 2 + + mpc clear + if [[ $album == "$all_name" ]]; then + mpc find artist "$artist" | sort | mpc add + else + mpc find artist "$artist" album "$album" | sort | mpc add + fi + + mpc play >/dev/null + ;; + playlist) + mpc play "$(d_playlist)" >/dev/null + ;; +esac diff --git a/dotfiles/.local/bin/dmenu-pass b/dotfiles/.local/bin/dmenu-pass new file mode 100755 index 00000000..38472199 --- /dev/null +++ b/dotfiles/.local/bin/dmenu-pass @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +shopt -s nullglob globstar + +typeit=0 +if [[ $1 == "--type" ]]; then + typeit=1 + shift +fi + + +STARTDIR=${PASSWORD_STORE_DIR-~/.password-store} +BASEDIR=$STARTDIR +DONE=0 +LEVEL=0 +PREVSELECTION="" +SELECTION="" + +while [ "$DONE" -eq 0 ] ; do + password_files=( "$STARTDIR"/* ) + password_files=( "${password_files[@]#"$STARTDIR"/}" ) + password_files=( "${password_files[@]%.gpg}" ) + + if [ "$LEVEL" -ne 0 ] ; then + password_files=(".." "${password_files[@]}") + fi + entry=$(printf '%s\n' "${password_files[@]}" | dmenu "$@" -l 15) + + echo "entry: $entry" + if [ -z "$entry" ] ; then + DONE=1 + exit + fi + + if [ "$entry" != ".." ] ; then + PREVSELECTION=$SELECTION + SELECTION="$SELECTION/$entry" + + # check if another dir + if [ -d "$STARTDIR/$entry" ] ; then + STARTDIR="$STARTDIR/$entry" + LEVEL=$((LEVEL+1)) + else + # not a directory so it must be a real password entry + + if [[ $typeit -eq 0 ]]; then + pass show -c "$SELECTION" 2>/dev/null + else + xdotool - <<<"type --clearmodifiers -- $(pass show "$SELECTION" | head -n 1)" + fi + DONE=1 + fi + + else + LEVEL=$((LEVEL-1)) + SELECTION=$PREVSELECTION + STARTDIR="$BASEDIR/$SELECTION" + fi +done + diff --git a/dotfiles/.local/bin/dmenu-todo b/dotfiles/.local/bin/dmenu-todo new file mode 100755 index 00000000..17d74153 --- /dev/null +++ b/dotfiles/.local/bin/dmenu-todo @@ -0,0 +1,28 @@ +#!/bin/sh +# +# Write/remove a task to do later. +# +# Select an existing entry to remove it from the file, or type a new entry to +# add it. +# + +file="$HOME/.todo" +touch "$file" +height=$(wc -l "$file" | awk '{print $1}') +prompt="Add/delete a task: " + +cmd=$(dmenu -l "$height" -p "$prompt" "$@" < "$file") +while [ -n "$cmd" ]; do + if grep -q "^$cmd\$" "$file"; then + grep -v "^$cmd\$" "$file" > "$file.$$" + mv "$file.$$" "$file" + height=$(( height - 1 )) + else + echo "$cmd" >> "$file" + height=$(( height + 1 )) + fi + + cmd=$(dmenu -l "$height" -p "$prompt" "$@" < "$file") +done + +exit 0