Skip to content

Latest commit

 

History

History
2521 lines (2253 loc) · 78.1 KB

scripts.org

File metadata and controls

2521 lines (2253 loc) · 78.1 KB

Scripts

This is where my custom scripts live. That includes shell scripts, dmenu scripts, and potentially programming language-specific scripts. One of the things I strive for is to have the same script available in different formats. For example, I have a dmenu script for creating a QR code, which allows me to scan or generate a QR code with a few keystrokes. But sometimes I want to call this script from a terminal, so I also have a qr command available in ~/.local/bin. Likewise, I have ideas to port some scripts to tridactyl (a firefox extension) as well. Since those different versions of the same script differ only by UX, it is useful to have all the versions defined in the same place - here.

Note: For code reusability, I use some helper code blocks defined at the bottom of this document. These code blocks are utilized via org-sbe or via noweb references.

Wrappers around existing commands

git

git() {
    alternative-command git "$@"
}

git_root="$(git root 2>/dev/null)"
if [ "$git_root" = ~ ]; then
    if [ "$PWD" = ~ ] || [[ "${PWD#~/}" =~ ^\. ]]; then
        git "$@"
    else
        export GIT_CEILING_DIRECTORIES=~
        git "$@"
    fi
else
    git "$@"
fi

rm

for arg in "$@"; do
    [ ! -e "$arg" ] && continue
    arg="$(realpath "$arg")"
    if echo "$arg" \
            | grep -q '^/home' && echo "$arg" \
                | grep -qv '^/home/[^/]\+/[^/]\+'
    then
        echo "WTF: Trying to delete a home directory???"
        exit 1
    fi
done
/usr/bin/rm "$@"

pass

This is a wrapper around GNU pass.

shopt -s expand_aliases

# TODO: Use alternative-command instead
alias pass=/usr/bin/pass

clipctl disable >/dev/null || true

# Add a password read directly from stdin (without prompting to interactively
# enter it)
if [ "$1" = "add" ]; then
    dest=~/.password-store/"$2"
    destdir="$(dirname "$dest")"
    mkdir -p "$destdir"
    cat | gpg -e -o- -r "$(cat ~/.password-store/.gpg-id)" > "$dest".gpg
else
    pass "$@"
fi

clipctl enable >/dev/null || true

dmenu

Note: This depends on my custom build of dmenu with the height patch applied. The PKGFILE can be found here.

alternative-command dmenu -b -f -i -h 25 \
    -nb '#1e1e1e' -nf '#dddddd' -sb '#99a3ff' -sf '#1e1e1e' \
    -fn 'Ubuntu Mono-12' \
    "$@"

ipython

/usr/bin/ipython "$@" -i -- "$PYTHONSTARTUP"

gvim

# Fake gvim which will just launch a terminal with neovim in it

for arg in "$@"; do
    # Hacky solution that will open files in real gvim when opened by xdg-open
    if [ "$arg" = "-f" ]; then /usr/bin/gvim "$@"; exit; fi
done

# nvim is launched through the shell because otherwise it behaves weirdly
# All arguments are surrounded with ''
FISH_CMD="$(printf "%q " nvim "$@")"

if [ ! -t 0 ]; then
    # Input is from a PIPE
    pipe_input="$(mktemp -t XXXXXXXXXXXXXXXX)"
    cat > "$pipe_input"
    FISH_CMD="cat $pipe_input | $FISH_CMD"
fi

alacritty --class Alacritty,Float -e fish -C "$FISH_CMD"

if [ ! -t 0 ]; then
    # Input is from a PIPE
    rm -f "$pipe_input"
fi

redshift

/usr/bin/redshift -l 43.84:18.35

firefox

/usr/bin/firefox $(printarg --new-tab "$@" | uniq)

myemacs

# Run emacsclient in the terminal, unless the -c option is given, which will
# open the GUI.
# - Also enables proper color support.
# - Any additional arguments you provide are passed to emacsclient

export TERM='xterm-256color'
create_frame=""
socket_name=""
modified_options=()

print_help() {
    emacsclient --help
    echo
    echo "CUSTOM OPTIONS:"
    echo -e "--wait \t\t\tOverride --no-wait if it would otherwise be in effect"
    echo -e "--no-tty \t\tUndoes the effects of --tty"
}

declare -A custom_options_map

# Get custom option named $1 (success code = on, error code = off)
# If $2 is given, then the option named $1 is set to the truth
# value of $2 (empty is false, non-empty is true)
custom_option() {
    if [ "$#" = 2 ]; then
        custom_options_map["$1"]="$2"
    else
        [ -n "${custom_options_map["$1"]}" ]
    fi
}

# Since this is a wrapper around emacsclient, we need to parse and pass through
# all options that emacsclient recognizes
parsed_args="$(
    getopt --name "$BASH_SOURCE" \
        --options "h"VHtcrF:enw:qud:s:f:a:T: \
        --long "wait,no-tty",version,help,tty,create-frame,reuse-frame,frame-parameters:,eval,no-wait,timeout:,quiet,suppress-output,display:,parent-id:,socket-name:,server-file:,alternate-editor:,tramp: \
        -- "$@"
)"

eval set -- "$parsed_args"

while :; do
    case "$1" in
        -h | --help)
            print_help
            exit ;;
        -c | --create-frame)
            create_frame="true"
            modified_options+=("$1")
            shift 1 ;;
        -s | --socket-name)
            modified_options+=("$1" "$2")
            socket_name="$2"
            shift 2 ;;
        --no-wait)
            custom_option wait ""
            shift 1 ;;
        --wait)
            custom_option wait "true"
            shift 1 ;;
        -t | --tty)
            custom_option tty "true"
            shift 1 ;;
        --no-tty)
            custom_option tty ""
            shift 1 ;;
        --)
            shift 1
            break ;;
        *)
            modified_options+=("$1")
            shift 1 ;;
    esac
done

if [ ! -t 0 ]; then
    custom_option wait ""
fi

# Set reasonable default values for --tty option and --no-wait option
# based on --create-frame.
if [ ! -v custom_options_map["tty"] ]; then
    custom_option tty "$([ -z "$create_frame" ] && echo true)"
fi
if [ ! -v custom_options_map["wait"] ]; then
    custom_option wait "$([ -z "$create_frame" ] && echo true)"
fi

if custom_option tty; then
    modified_options+=(--tty)
fi

if ! custom_option wait; then
    modified_options+=(--no-wait)
fi

if [ -z "$socket_name" ]; then
    modified_options+=(--socket-name emacs)
fi

emacsclient() {
    exec -a "$BASH_SOURCE" command emacsclient "$@"
}

set -- "${modified_options[@]}" -- "$@"

if [ ! -t 0 ]; then
    file="$(mktemp /tmp/haris-pipe-XXXXXXXX)"
    tee "$file" <&0 >/dev/null &
    bg_pid="$!"
    set -- "$@" "$file"
    trap "kill $bg_pid &>/dev/null" EXIT INT ERR
fi

exec -a "$BASH_SOURCE" emacsclient \
     --alternate-editor ~/.local/lib/haris/emacs-alternate-editor \
     "$@"

if [ -n "$bg_pid" ]; then
    wait "$bg_pid"
fi

Helper program

is_running() {
    emacsclient --no-wait --socket-name emacs --eval '(ignore)' >/dev/null 2>&1
}

id="$(notify-send --print-id "Emacs" "Starting daemon...")"
systemctl restart --user emacs
until is_running; do
    sleep 2s
done
notify-send --print-id --replace-id="$id" "Emacs" "Daemon ready"

Shell completion

complete -c myemacs --wraps emacsclient
complete -c myemacs --long "wait" --description "Override --no-wait if it would otherwise be in effect"

myemacs-float

myemacs -c --frame-parameters='(quote (name . "EmacsFloat"))' "$@"

notify-send

export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/<<eval-real-uid()>>/bus"

# If notify-send can't communicate, it blocks. That's the reason for the timeout
timeout 1s alternative-command notify-send "$@"

sudo

# Sudo but with a hook that sends a notification when the prompt is shown

# TODO: Not robust enough. What if the wrapped command itself accepts a --stdin flag?

if [ -z "$SUDO_ASKPASS" ] && [[ ! "$*" =~ --stdin ]] && [[ ! "$*" =~ -S ]]; then
    export SUDO_ASKPASS="$(mktemp)"
    chmod u+x "$SUDO_ASKPASS"
    cat > "$SUDO_ASKPASS" <<'EOF'
<<sudo-askpass>>
EOF
    trap "rm -f $SUDO_ASKPASS" EXIT
    alternative-command sudo --askpass "$@"
else
    alternative-command sudo "$@"
fi
#!/usr/bin/env sh
askpass "[sudo] password for $(getent passwd "$USER" | cut -d: -f1)" "SUDO password" "Please enter your password"

ssh

# This checks whether ssh is run via sshpass or perhaps some other tool that
# assigns its own terminal to ssh.
is_tty_available() {
    { : >/dev/tty; } &>/dev/null
}

if [ -z "$SSH_ASKPASS" ] && is_tty_available; then
    export SSH_ASKPASS="$(mktemp)"
    export SSH_ASKPASS_REQUIRE="prefer"
    chmod u+x "$SSH_ASKPASS"
    cat > "$SSH_ASKPASS" <<'EOF'
<<ssh-askpass>>
EOF
    trap "rm -f $SSH_ASKPASS" EXIT
fi

alternative-command ssh "$@"
# The default prompt supplied as call args
prompt="$(echo "$1" | sed 's/: $//')"
askpass "$prompt" "SSH passphrase" "Please enter SSH passphrase"

alacritty

WINIT_X11_SCALE_FACTOR=1.5 /usr/bin/alacritty "$@"
#WINIT_X11_SCALE_FACTOR=1.5 prime-run /usr/bin/alacritty "$@"

alacritty-float

alacritty --class Alacritty,Float "$@"

sway

export XDG_CURRENT_DESKTOP=Sway
/usr/bin/sway --unsupported-gpu "$@"

xlock

pkill gpg-agent 2>/dev/null
/usr/bin/xlock "$@"

vscode

The name code is a bit too generic. I need something more specific in order to get better search matches.

code "$@"

lnav

export TERM=xterm-256color

# alternative-command lnav "$@"
/usr/bin/lnav "$@"

clipmenu

NOTE: The CM_DIR environment variable should be in sync with clipmenud configuration.

export CM_DIR=«eval-user-home()»/.local/share/haris/clipmenu
alternative-command clipmenu "$@"

Utilities

visual-editor

if xprop -id "$WINDOWID" | grep -q _NET_WM_STATE_FULLSCREEN; then
    myemacs --wait "$@"
elif which myemacs-float >/dev/null; then
    myemacs-float --wait "$@" >/dev/null
else
    editor "$@"
fi

editor

exists() {
    which "$1" >/dev/null
}
if exists myemacs; then
    myemacs "$@"
elif exists nvim; then
    nvim "$@"
elif exists vim; then
    vim "$@"
elif exists nvim; then
    vi "$@"
fi

abspath

pushd "$(dirname "$1")" >/dev/null
echo "$PWD/$(basename "$1")"
popd >/dev/null

askpass

# Usage: askpass [PROMPT] [WINDOW_TITLE] [NOTIFICATION]

prompt="$1"
window_title="$2"
notification="$3"

if [ "$TERM" != "dumb" ]; then
    if [ -n "$DISPLAY" ]; then
        id="$(notify-send "$window_title" "$notification" --print-id --expire-time 0)"
        trap "dunstify --close=$id" EXIT
    fi
    echo -n "$prompt: " >/dev/tty

    stty -echo </dev/tty
    head -1 </dev/tty | tr -d '\n'

    echo >/dev/tty
else
    {
        echo SETTITLE "$window_title"
        echo SETDESC "$prompt"
        echo SETPROMPT Password:
        echo GETPIN
        echo BYE
    } | pinentry -g 2>&1 | sed -n "/^D/ s/^D //p" | tr -d '\n'
fi

askpass-gui

TERM=dumb askpass "$@"

chx

chmod u+x "$@"

diffgpg

# Compare the contents of two GPG encrypted files

diff <(gpg --decrypt --output - "$1") <(gpg --decrypt --output - "$2") "${@:3}"

diffpiped

# Diff two files named $1 and $2, piped through a command given as the remaining
# arguments.

options=()

for arg in "$@"; do
    if [[ "$arg" =~ ^- ]]; then
        options=("${options[@]}" "$arg")
        shift
    else
        break
    fi
done

file1="$1"
file2="$2"

shift 2

tmp1="$(mktemp)"
tmp2="$(mktemp)"

cat "$file1" | "$@" > "$tmp1"
cat "$file2" | "$@" > "$tmp2"

diff --color=auto "${options[@]}" "$tmp1" "$tmp2"

rm -f "$tmp1" "$tmp2"
diffpiped --strip-trailing-cr <(echo -e "a\nb") <(echo -e "a\nB") sed 's/a/A/g'

fcmd

type "$@" 2>/dev/null | awk '{print $3}'
exit "${PIPESTATUS[0]}"

lscf

cat $(fcmd cf) | sed -n 's/\s*\(.*\)).*/\1/p'

n

# Print n of the received arguments, where n=$1

[ "$#" = 0 ] && exit 1

num="$1"
shift
printarg $(printarg "${@}" | head -"$num")

printarg

# Print commandline arguments passed to this function each on its own line
printf "%s\n" "$@"

rmws

# Remove whitespace from a file (or stdin if input is from a pipe) and write the
# output to stdout (or rewrite the file if the -i option is given)

[ ! -t 0 ] &&\
    cat | sed 's:\s\+$::' ||\
        sed 's:\s\+$::' "$@"

adhoc

# Create an ad-hoc file, edit it in $EDITOR and then print its path. If a '-'
# argument is given, the file contents are printed instead of its path.

destdir="/tmp/adhoc-files"
mkdir -p "$destdir"
cd "$destdir"

if [ "$#" != 0 ] && [ "$1" = "-" ]; then
    print_content=true
    files=("${@:2}")
else
    files=("$@")
fi

# Convert files to realpaths
readarray -t files < <(realpath "${files[@]}" 2>/dev/null)

if [ -z "$files" ]; then
    files=("$(mktemp "$destdir"/XXXXXXXXXXX)")
fi

term --class Float -e fish -c "nvim ${files[*]}"

if [ -n "$print_content" ]; then
    cat "${files[@]}"
else
    realpath "${files[@]}"
fi

cmd-with-notify

if [ "$TERM" = "linux" ]; then
    "$@"
    exit
fi

"$@" && notify-send "Command $* exited successfully." || {
        err=$?
        notify-send -u critical "Command $* exited with error $err."
    }
exit $err

Completions

complete --command cmd-with-notify -f -a '(complete -C(commandline -cp | sed "s:\S\+::"))'

xpreset

[ "$#" != 1 ] && exit 1

ln -sf .xinitrc-"$1" ~/.xinitrc

Fish completions

complete -c xpreset -f -a "(pushd ~; ls .xinitrc-* | string replace .xinitrc- ''; popd)"

xrandr-toggle

n="$(xrandr --listmonitors | head -1 | awk '{print $NF}')"

[ "$n" = 1 ] && {\
                 xrandr2 --auto
                 MSG='enabled'
} || {\
      xrandr2 --off
      MSG='disabled'
}
[ "$?" = 0 ] && notify-send "second monitor sucessfully $MSG" || notify-send -u "monitor operation unsuccessful"

# Fix wallpaper on second monitor
feh --bg-fill .wallpaper

xrandr2

# Wrapper for xrandr command with some options applied based on my current
# monitor configuration

xrandr --output HDMI-1-0 "$@" --pos 1920x1080

pipetest

# Create three tmux panes:
# - A SOURCE text file opened in vim
# - A SCRIPT file opened in vim
# - An output buffer that shows the results of processing SOURCE with SCRIPT
#
# The SOURCE is supplied to the SCRIPT via pipe, i.e. the output buffer shows
# the results of:
#
# SCRIPT < SOURCE
#
# The output automatically updates when one of SCRIPT, SOURCE changes.
#
# USAGE: pipetest [SOURCE] [SCRIPT]
#
# If the optional arguments SCRIPT and SOURCE are given, the SCRIPT and SOURCE
# buffers will have an initial content equal to the content of those files.

vim_executable=vim
if command -v nvim >/dev/null; then
    vim_executable=nvim
fi
temp_dir="$(mktemp -d)"

INITIAL_SOURCE="$1"
INITIAL_SCRIPT="$2"

[ -z "$INITIAL_SOURCE" ] && INITIAL_SOURCE=/dev/null
[ -z "$INITIAL_SCRIPT" ] && INITIAL_SCRIPT=/dev/null

if [ "$#" -gt 2 ]; then
    echo "Too many arguments" >&2
    exit 1
fi

# Create a temporary file with content from stdin
# Usage: create_file HANDLE <CONTENT
# HANDLE is the name of the bash variable that will point to the file's path
create_file() {
    local file
    file="$temp_dir/$1"
    if [ ! -t 0 ]; then
        cat > "$file"
    else
        touch "$file"
    fi

    declare -g "$1"="$file"
}

# Same as create_file but also marks it executable by the current user
create_file_x() {
    create_file "$@"
    chmod u+x "${!1}"
}

# Create a fifo so the first and second windows can notify the third window of
# changes
fifo="$temp_dir/fifo"
mkfifo "$fifo"

# The source file (first buffer) that is being piped to SCRIPT
create_file SOURCE < "$INITIAL_SOURCE"

# The script file (second buffer) that will process the file and generate
# output in the third buffer
create_file_x SCRIPT < "$INITIAL_SCRIPT"

#
# Notifies the output terminal that some of the first two buffers have changed.
#
create_file_x on_change <<EOF
<<pipetest_on_change>>
EOF

#
# Supplementary vimrc file that is loaded by each vim session started from this
# program.
#
create_file vimrc <<EOF
<<pipetest_vimrc>>
EOF

#
# Vim wrapper that loads our supplementary vimrc file.
#
create_file_x custom_vim <<EOF
<<pipetest_custom_vim>>
EOF

#
# Output script - script that is run in the third buffer that shows the output
# of the user SCRIPT when applied to the SOURCE file.
#
create_file_x output_script <<EOF
<<pipetest_output_script>>
EOF

#
# Main script that launches tmux and everything.
#
create_file_x main_script <<EOF
<<pipetest_main_script>>
EOF

tmux new "$main_script"

#
# Print the resulting script
#
cat "$SCRIPT"

#
# Remove created temporary directory
#
rm -rf "$temp_dir"

Helper scripts

These scripts are embedded into the pipetest script as heredocs, but we define them as code blocks here for better readability.

on_change

echo > "$fifo"

vimrc

" On write run the on_change script
autocmd BufWritePost * silent !$on_change
autocmd ExitPre $SOURCE,$SCRIPT silent !tmux kill-session

custom_vim

"$vim_executable" -c "source $vimrc" "\$@"

output_script

echo "This is the output."
echo "It will automatically refresh when either of the files change."
echo "Press Ctrl+C here or quit any of the two vim instances to exit."

trap "tmux kill-session" INT TERM EXIT

while :; do
    #stty -echo
    read _ < "$fifo"
    [ "$?" != "0" ] && break
    clear
    "$SCRIPT" <"$SOURCE"
done

main_script

tmux split-window -h "$custom_vim" "$SCRIPT"
tmux split-window -h sh -c 'cd "$(pwd)"; "$output_script"'
tmux select-pane -L
tmux select-layout even-horizontal

"$custom_vim" "$SOURCE"

consider extracting this script into its own project

auto-browser

browser='firefox -P «eval-user-name()»'
# If a firefox window is currently active, open the link in the active window
if xprop -id "$(xdotool getactivewindow)" | grep -qi 'firefox'; then
    where='--new-tab'
else # Otherwise open a new window
    where='--new-window'
fi

args="$(printf '%q ' "$@")"
i3-msg exec "$browser $where $args"

Dependencies

xdotool xorg-xprop

alternative-command

cmd="$(which -a "$1" | uniq | sed -n 2p)"

"$cmd" "${@:2}"

curltb

# Get https://termbin.com/<TERMBIN_BLOB> using curl
# Usage: curltb TERMBIN_BLOB [CURL_OPTIONS]

curl https://termbin.com/"$1" "${@:2}"

colortest

# Print out all 256 colors in the terminals

f=0
l=256

if [ -n "$1" ]; then
    l="$1"
fi

if [ -n "$2" ]; then
    f="$1"
    l="$2"
fi

(x=`tput op` y=`printf %40s`;for i in $(seq "$f" "$l");do o=00$i;echo -e ${o:${#o}-3:3} \
  `tput setaf $i;tput setab $i`${y// /=}$x;done)

myemacs-load

Wrapper to myemacs that will load its first argument instead of opening it. The remaining args simply passed to myemacs as usual. The main use for this is as a shebang for elisp scripts. NOTE: It must be implemented in such a convoluted way as a shell script, because process management in emacs is shit.

tmpfile="$(mktemp)"
cat >"$tmpfile" <<'EOF'
  <<stringify-arg-to-eval-option>>
EOF
if [ -t 0 ]; then
    input_file="$(realpath "$1")"
else
    input_file="$(mktemp)"
    trap "$(printf "%q " rm -rf "$input_file")" ERR EXIT
    cat >"$input_file"
fi

arg="$(
    emacs --quick --batch --load "$tmpfile" "$input_file" "${@:2}"
)"
myemacs --no-tty --no-wait --eval "$arg" | sed '/^<<do-not-print-token>>$/d'

rm -f "$tmpfile"
(let* ((file (car argv))
       (code `(progn
                (defvar argv)
                (defvar process-environment)
                (defvar default-directory)
                (let ((argv (list ,@(cdr argv)))
                      (process-environment (list ,@process-environment))
                      (default-directory ,default-directory))
                  (load ,file)
                  <<do-not-print-token>>))))
  (prin1 code))

Emacsclient prints the result of evaluation of --eval. The only non-hacky way I know to disable this is to give it the --suppress-output option. But this would also suppress desirable output like calls to (message). So I put this token as the last statement, and remove it from the output by piping to sed.

"1617875f9593bd741b9637ad26aabb9f42ff3df769564f7881d7603a80ea0ae2"

o

Wrapper command around the equivalent fish function.

fish -C "o $(printf "%q " "$@")"

get-os-type

Get the OS type.

cat /etc/lsb-release 2>/dev/null | grep -q 'Ubuntu'     && echo 'ubuntu' && exit
cat /etc/os-release  2>/dev/null | grep -q 'Arch Linux' && echo 'arch'   && exit
echo 'Unsupported OS' >&2
exit 1

urlencode

python -c 'import urllib.parse as p; print(p.quote(input()))'

urldecode

python -c 'import urllib.parse as p; print(p.unquote(input()))'

fifo

mkfifo /tmp/fifo 2>/dev/null

session_exists() {
    tmux has-session -t fifo 2>/dev/null
}

create_session() {
    tmux new -s fifo "$@" tail --follow=name /tmp/fifo
}

if [ -t 0 ]; then
    if session_exists; then
        tmux attach -t fifo
    else
        create_session
    fi
else
    if ! session_exists; then
        create_session -d
    fi
    tee /tmp/fifo
fi

clipboard-or-stdin

if [ -t 0 ]; then
    xsel -b -o
else
    cat
fi

narrate

set -e

clipboard-or-stdin

get-random-unused-port

# This piece of magic was generated by ChatGPT. Don't try to understand it, just
# accept it.
comm -23 <(seq 49152 65535 | sort) <(ss -tan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1

docker-ls-context

tag="$(date +%s%3N | sha256sum | head -c 32)"
docker image build --tag "$tag" --no-cache -f - "$@" <<'EOF'
«docker-ls-context/dockerfile»
EOF

docker run --rm "$tag"
docker rmi "$tag"
FROM busybox
WORKDIR /workdir
COPY . .
CMD find .

info

echo -ne "\033[1;32m" >&2
echo "$@" >&2
echo -ne "\033[0m" >&2

debug

echo -ne "\033[1;33m" >&2
bash -c 'printf "%q " "$@"' -s "$@" >&2
echo >&2
echo -ne "\033[0m" >&2

"$@"

Applications

These are programs that are meant to be used mostly interactively. As such, they are designed to be easily integrated into dmenu scripts.

cf

# Look up a configuration file by its user-friendly alias.

# Note: This script is statically parsed by lscf. Keep its structure intact.

for arg in "$@"; do
    case "$arg" in
        .haris)         echo ~/.haris ;;
        README.org)     echo ~/.haris/README.org ;;
        private.org)    echo ~/.haris/private/README.org ;;
        temporary.org)  echo ~/.haris/temporary.org ;;
        scripts.org)    echo ~/.haris/scripts.org ;;
        shells.org)     echo ~/.haris/shells.org ;;
        wm.org)         echo ~/.haris/wm.org ;;
        vcs.org)        echo ~/.haris/vcs.org ;;
        vim.org)        echo ~/.haris/vim.org ;;
        browser.org)    echo ~/.haris/browser.org ;;
        gui.org)        echo ~/.haris/gui.org ;;
        misc.org)       echo ~/.haris/misc.org ;;
        terminal.org)   echo ~/.haris/terminal.org ;;
        repl.org)       echo ~/.haris/repl.org ;;
        bootstrap.org)  echo ~/.haris/bootstrap/README.org ;;
        background.org) echo ~/.haris/background.org ;;
        alacritty)      echo ~/.haris/terminal.org ;;
        alias-tmp)      echo ~/.alias-tmp ;;
        alias-gui-tmp)  echo ~/.alias-gui-tmp ;;
        fish)           echo ~/.haris/shells.org ;;
        fish-private)   echo ~/.config/fish/private.fish ;;
        fish-tmp)       echo ~/.config/fish/tmp.fish ;;
        vifm)           echo ~/.haris/terminal.org ;;
        git)            echo ~/.haris/vcs.org ;;
        gh)             echo ~/.haris/vcs.org ;;
        hg)             echo ~/.haris/vcs.org ;;
        picom)          echo ~/.haris/wm.org ;;
        dunst)          echo ~/.haris/misc.org ;;
        tem)            echo ~/.haris/terminal.org ;;
        mime)           echo ~/.haris/README.org ;;
        zathura)        echo ~/.haris/gui.org ;;
        emacs)          echo ~/.haris/emacs.org ;;
        spacemacs)      echo ~/.spacemacs ;;
        cron)           echo ~/.crontab ;;
        octave)         echo ~/.octaverc ;;
        python)         echo ~/.startup.py ;;
        tuterm)         echo ~/.haris/terminal.org ;;
        xinit)          echo ~/.haris/wm.org ;;
        sxhkd)          echo ~/.haris/wm.org ;;
        mpv)            echo ~/.haris/gui.org ;;
        flameshot)      echo ~/.haris/gui.org ;;
        cheat)          echo ~/.haris/terminal.org ;;
        monero)         echo ~/.config/monero-project/monero-core.conf ;;
        xmrig)          echo ~/.config/xmrig.json ;;
        tmux)           echo ~/.tmux.conf ;;
    esac
done

elisp

# Run an elisp interpreter through emacs
<<add-create-frame-option-if-term-dumb>>
myemacs --eval "(ielm)" "$@"

eoctave

# Run octave interpreter through emacs
<<add-create-frame-option-if-term-dumb>>
myemacs --eval "(progn (run-octave) (delete-other-windows))" "$@"

epython

# Run a python interpreter through emacs
<<add-create-frame-option-if-term-dumb>>
myemacs --eval "(progn (call-interactively 'run-python) (delete-other-windows))" "$@"

eterm

# Run an emacs-hosted terminal via vterm
<<add-create-frame-option-if-term-dumb>>
myemacs --socket-name=vterm --eval '(multi-vterm)' "$@"

eman

(with-selected-frame (make-frame `((name . "EmacsMan")
                                   (display . ,(getenv "DISPLAY"))))
  (let ((man-buffer nil))
    (defvar haris/last-man-buffer nil "The last man buffer that was opened")
    (setq man-buffer
          (if argv
              (man (car argv))
            (progn
              (when (and (boundp 'haris/last-man-buffer)
                         (buffer-live-p haris/last-man-buffer))
                (switch-to-buffer haris/last-man-buffer))
              (call-interactively 'man))))
    (when man-buffer (setq haris/last-man-buffer man-buffer))))

Completions

complete -c eman --wraps man

etranslate

(with-selected-frame (make-frame `((name . "EmacsFloat")
                                   (display . ,(getenv "DISPLAY"))))
  (spacemacs/switch-to-scratch-buffer)
  (let ((-gts-after-buffer-render-hook gts-after-buffer-render-hook))
    (run-with-timer
     0.2
     nil
     (lambda ()
       ;; This is the only thing I've tried that doesn't report the buffer as read-only
       (call-interactively #'evil-delete-whole-line)
       (insert (shell-command-to-string "~/.local/lib/haris/etranslate-helper 2>/dev/null"))))

    (add-hook 'gts-after-buffer-render-hook
              (lambda ()
                (delete-other-windows (get-buffer-window nil t)))
              100)
    (gts-do-translate)
    (setq gts-after-buffer-render-hook -gts-after-buffer-render-hook)))

Helper script

This script prints the contents of the clipboard

# In order to get the time when the selection happened I use a timestamp
# reported by xclip and to get the current time I use a timestamp reported by
# xsel. I have no idea what these timestamps are relative to, but I have
# empirically determined that this works.

set -e

ts_current="$(
    timeout 0.5 \
        xsel --output -vvv 2>&1 >/dev/null \
            | grep '^xsel: Timestamp:' \
            | awk '{print $3}'
)"
ts_sel="$(timeout 0.5 xclip -target TIMESTAMP -out 2>/dev/null)"

# Uncomment for debugging:
# echo "current timestamp: $ts_current, selection timestamp: $ts_sel" >&2

elapsed_time_millis="$(expr "$ts_current" - "$ts_sel" 2>/dev/null)"

status="$?"
# Some programs (alacritty) don't handle the primary clipboard correctly,
# so ts_sel might not contain a valid timestamp
if [ "$status" != 0 ]; then
    exit "$status"
fi

if [ "$elapsed_time_millis" -lt "10000" ]; then
    xsel --primary --output
fi

erc

# Open emacs and run ERC in it

<<add-create-frame-option-if-term-dumb>>
myemacs --socket-name="irc" \
        --eval "(unless erc-server-connected (call-interactively 'erc-tls))" \
        "$@"

edocker

(let ((dir default-directory))
(with-selected-frame (make-frame `((name . "EmacsFloat")
                                   (display . ,(getenv "DISPLAY"))))
  (setq-local default-directory dir)
  (spacemacs/switch-to-scratch-buffer)
  (run-with-timer 0.3 nil 'docker)))

edocker-compose

(let ((dir default-directory))
  (with-selected-frame (make-frame `((name . "EmacsFloat")
                                   (display . ,(getenv "DISPLAY"))))
  (setq-local default-directory dir)
  (alert (format "%s" default-directory))
  (dired default-directory)
  (run-with-timer 0.3 nil 'docker-compose)))

ebluetooth

myemacs-float -c --eval '(bluetooth-list-devices)'

proced

<<add-create-frame-option-if-term-dumb>>
myemacs-float --eval '(progn (proced) (delete-other-windows))' "$@"

magit

(let ((dir default-directory))
  (with-selected-frame (make-frame `((name . "EmacsFloat")
                                     (display . ,(getenv "DISPLAY"))))
    (let ((default-directory dir))
      (magit-status))))

qr

# Copy, show or open the argument based on its content
copy_or_show_or_open() {
    notify-send 'QR Code:' "$@"
    echo "$1" | xsel -b
    if echo "$1" | grep -q '^https://'; then
        firefox --new-tab "$@"
    fi
}

if [ "$1" = 'in' ]; then
    copy_or_show_or_open "$(timeout 20s zbarcam /dev/video0 -1 | sed 's/^QR-Code://')"
elif [ "$1" = 'screen' -o "$1" = 's' ]; then
    copy_or_show_or_open "$(zbarimg -q <(flameshot screen --raw) | sed 's/^QR-Code://')"
else # out
    if [ -t 0 ] || [ "$TERM" = 'linux' ]; then
        input="$(xsel -b -o)"
    else
        input="$(cat)"
    fi
    echo "$input" | qrencode -s 10 -o - | feh -
fi

Dependencies

zbar qrencode

rb

# One-time reboot into selected OS

set -e # Quit if any command fails

index="$(grep "menuentry '\|submenu '" /boot/grub/grub.cfg |\
  grep -v -P '\t' |\
  grep -i -n "$1" |\
  head -1 | awk -F':' '{print $1}')"

if [ -z $index ]; then
    echo "No entry found"
else
    index=$(( $index - 1 ))
    echo "Selected menuentry: $index. Proceed?"
    read response
    if [ "$response" == 'y' ]; then
        sudo grub-reboot $index >/home/haris/src/grublog 2>&1
        reboot
    fi
fi

vimdiff

# Like regular vimdiff, but in nvim

nvim -d "$@"

vicc

# Find and open in vim a header file from the default include path

vim "$(echo "#include <$1>" | cpp -H 2>&1 >/dev/null | head -1 | sed 's/^. //')"

vipydoc

# Open alacritty with pydoc in it
# - All arguments are passed to pydoc
# - Alacritty window class tracks those defined in my i3 config

alacritty --class Alacritty,Float -e fish -C "pydoc $*" &

rgf

# Run rg and fzf to search through file contents and jump to a file

where="$1"
[ -z "$where" ] && where='.'

rg --column --line-number --no-heading --color=always --smart-case . | fzf --ansi

otp

import shlex
import subprocess

import dryparse
from dryparse.objects import Option
import sys
from urllib.parse import urlparse, parse_qs

@dryparse.command
def otp(name: str, *, new: bool = False, extract: Option("-x", "--extract", bool) = False):
    """Use and manage one-time passwords.

    :param name: name of the OTP
    :param new: store a new OTP instead of printing an existing one
    :param extract: Extract the secret from the URL given as argument
    """
    extract: bool

    def run(*args, **kwargs):
        return subprocess.run(*args, shell=True, encoding="utf-8", **kwargs)

    if new and extract:
        print("--new and --extract can't be used together", file=sys.stderr)
        sys.exit(1)

    if new:
        p = run(f"pass insert {name}/otp-secret")
        sys.exit(p.returncode)
    elif extract:
        query = parse_qs(urlparse(name).query)
        print(query["secret"][-1])
        return


    otp_secret = run(
        f"pass show {name}/otp-secret", stdout=subprocess.PIPE
    ).stdout.strip()

    p = run(f"oathtool --totp --base32 {shlex.quote(otp_secret)}")
    sys.exit(p.returncode)


otp = dryparse.parse(otp, sys.argv)
otp()

Dependencies

dryparse

imount

# List of all blocks
listing="$(lsblk --list -o PATH,LABEL)"

# ... with header removed
items="$(
    echo "$listing" \
        | tail -n +2 \
        | sed 's/.*/"&"/' \
        | nl --number-width=1 \
        | grep -v '/dev/loop'
)"
# number of lines
lineno="$(echo "$items" | wc -l)"

# Open file descriptor 3
exec 3>&1
# Show dialog and store id of selection
id=$(
    eval dialog  2>&1 1>&3 \
        --default-item $lineno \
        --menu '"Choose a device/partition:"' 60 50 $lineno $items
)
[ "$?" != 0 ] && exit 1     # Dialog exited with error
# Select mounting directory
mount_dir="$(dialog --fselect ~/mnt/ 60 50 2>&1 1>&3)"
[ "$?" != 0 ] && exit 1     # Dialog exited with error

# Clear but keep scrollback buffer
clear -x

if [ ! -d "$mount_dir" ]; then # Nonexisting mount directory
    read -n 1 -p\
         "The directory $mount_dir does not exist and will be created. Continue?  [y/n]: " \
         choice 1>/dev/null
    [ "$choice" != "y" ] && exit 1
    echo # newline
    mkdir "$mount_dir"
fi

# Get path to selected device
device="$(echo "$items" | sed -n ${id}p | sed 's_.*"\(\S*\)\s.*_\1_')"

read -n 1 -p\
     "$device will be mounted at $mount_dir. Continue? (requires sudo) [y/n]: " \
     choice
echo # newline

[ "$choice" != "y" ] && exit 1

# Mount the device at last
sudo mount "$device" "$mount_dir" -o umask=002,uid=$(id -u),gid=$(id -g)

echo -e "$mount_dir" > /tmp/imount_directory
chmod a+rw /tmp/imount_directory

kbind

# Temporarily bind keys

pkill -f '^sxhkd\.tmp '
myemacs-float --wait -c ~/.tmp.sxhkdrc

notify-send "kbind" "Applying config"
o --silent sxhkd.tmp

xpeek

Create a Xephyr session for the current user that tries to mimic a regular startx session as closely as possible.

# Option variables
host_display="$DISPLAY"
display=1"$(id -u "$USER")"

Xephyr :"$display" -once -resizeable \
    -screen "$(xdpyinfo | grep dimensions | grep -P '\d+x\d+' --only-matching | head -1)" \
    "$@" &
sleep 0.5s
windowid="$(xdotool getactivewindow)"
echo "WINDOW ID: $windowid"

export DISPLAY=:"$display"
i3 &
sxhkd &
dunst &

DISPLAY="$host_display" xev -id "$windowid" -event structure |
    while read line; do
        if grep "^ConfigureNotify event" <<<"$line"; then
            xrandr
        fi
    done

Dmenu

Main entrypoint (dmenu_run)

# If an argument is provided, run corresponding custom dmenu script
if [ -n "$1" ]; then
    script=~/.local/lib/dmenu/"$1"
    if [ -e "$script" ]; then
        "$script"
        exit
    fi
fi
# Otherwise open a generic dmenu where the user will choose what dmenu script
# or other program to run

export TERM=dumb

run_script() {
    if [ -n "$TMUX" ]; then
        ~/.local/lib/dmenu/"$1"
    else
        o --silent ~/.local/lib/dmenu/"$1"
    fi
}

extract_aliases() {
    grep -v '^\s*#' "$@" 2>/dev/null | sed "s_.* \(.*\)=\('\|\"\).*\2.*_\1_"
}

pull_desktop_apps() {
    # Print out desktop apps by reading *.desktop files and also cache them
    sh <<'EOF'
«pull_desktop_apps»
EOF
}
get_desktop_apps() {
    cache_file=~/.cache/.desktop-apps.txt
    age="$(date -d "now - $(stat -c '%Y' "$cache_file") seconds" +%s)"
    # Refresh the cache only if the file is older than a specified age (seconds)
    {
        if [ ! -f "$cache_file" -o  $age -gt 36000 ]; then
            pull_desktop_apps
        else
            cat "$cache_file"
        fi
        find ~/.local/bin -executable -type f -printf '%f\n' | sed 's/^/ /'
    } | sort | uniq
}
get_aliases() {
    extract_aliases ~/.alias ~/.alias-tmp
}
get_gui_aliases() {
    extract_aliases ~/.alias-gui ~/.alias-gui-tmp
}

aliases="$(get_aliases)"
gui_aliases="$(get_gui_aliases)"

# ┏━━━━━━━━━━━━━━━┓
# ┃ dmenu entries ┃
# ┗━━━━━━━━━━━━━━━┛
get_commands() {
    echo " Open"           # Open an URL or bookmark
    echo " Clipboard"      # Clipboard using clipmenu
    echo " Snippets"       # Text snippets
    echo " TODO"           # Open TODO file of a project
    echo " Windows"        # Choose windows
    echo " Pacman"         # Package management
    echo " Color"          # Pick a color
    echo " Unicode"        # Pick an icon
    echo " Kill Process"   # Kill process
    echo " Fix Wifi"      # Fix Wi-Fi drop issue on some networks
    echo " Config"        # Open documentation selection
    echo " System"        # System actions
    echo " Services"       # Control systemd services
    echo " Update cache"   # Update desktop app cache
    echo "Tem"              # Launch tem development environment
    echo "Octave"           # Launch octave in emacs
    echo " Python"         # Launch python interpreter in emacs
    echo " GPG"           # GPG addresses
    echo " OTP"            # Generate OTP for selected service
    echo " Books"         # Pick a book to read
    echo " IRC"           # Open emacs client for IRC
    echo "Quickmenu"        # Menu to quickly revisit recent activity
    echo "$aliases" | sed 's/^/ /'
    echo "$gui_aliases" | sed 's/^/ /'
    get_desktop_apps        # Programs extracted from *.desktop files
}

# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Actions based on user's choice ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
if [ -n "$1" ]; then
    choice="$1"
else
    choice="$(get_commands | dmenu)"
    # If it starts with a non-ascii character (as in get-commands), remove it and the space following it
    if grep -P "[^\x00-\x7F]" <<<"$choice"; then
        choice="$(cut -f2 -d' ' <<<"$choice")"
    fi
fi
case "$choice" in
    "")
        exit ;;
    "Open")
        run_script open ;;
    "Clipboard")
        clipmenu ;;
    "Snippets")
        run_script snips ;;
    "TODO")
        run_script todo ;;
    "Windows")
        ~/.local/lib/i3/i3-container-commander.py ;;
    "Pacman")
        run_script pacman ;;
    "Color")
        run_script color ;;
    "Unicode")
        run_script unicode ;;
    "Kill Process")
        run_script pkill ;;
    "Fix Wifi")
        fix-wifi ;;
    "Config")
        run_script config ;;
    "System")
        run_script system ;;
    "Services")
        run_script services ;;
    "Update cache")
        rm ~/.cache/.desktop-apps.txt ;;
    "Tem")
        alacritty -e fish -C 'pj tem; clear' ;;
    "Octave")
        eoctave -c ;;
    "Python")
        epython -c ;;
    "GPG")
        run_script gpg ;;
    "OTP")
        run_script otp ;;
    "Books")
        run_script books ;;
    "IRC")
        myemacs --socket-name="irc" -c ;;
    "Quickmenu")
        run_script quickmenu ;;
    # The rest: aliases and regular commands
    *)
        # If the command is an aliased GUI program, just run it
        for alias in $gui_aliases; do
            if [ "$choice" = "$alias" ]; then
                o "$choice"
                exit
            fi
        done
        # If the command is an aliased CLI program, open it in fish
        for alias in $aliases; do
            if [ "$choice" = "$alias" ]; then
                guirun alacritty --class Alacritty,Float -e fish -C "o --silent --attach $choice"
                exit
            fi
        done
        # Fallback, if the entry matches none of the above, just run the command
        fish -c "o $choice"
        ;;
esac

This code block separated so that it can be run in sh instead of bash and thus possibly benefit from a performance boost, in case sh happens to point to a more lightweight shell.

sed -n -e '/^Exec=/p' \
    /usr/share/applications/*.desktop \
    ~/.local/share/applications/*.desktop \
    | sed 's/Exec=\(.*\)/\1/' \
    | while read line; do
    echo -n ''  # Represents a GUI app
    basename "$(sh -c "eval set -- $line; echo \$1")"
done \
    | sort | uniq | tee "$cache_file"

Open

# Open a website in Firefox
# Suggests bookmarks managed by buku, but you can input any URL

edit=" Edit..."
sync=" Sync..."

choice="$(
    {
        echo "$edit"
        echo "$sync"
        unbuffer buku -p --format 30 | grep -v '^$' | sort
    } | dmenu -p 'Open:'
)"

[ -z "$choice" ] && exit

if [ "$choice" = "$edit" ]; then
    # Open this file for editing
    gvim "$0"
elif [ "$choice" = "$sync" ]; then
    alacritty --class Alacritty,Float -e fish -C "
        echo -e \"--- Importing bookmarks from Firefox ---\nDefault is: n y y \";
        buku --import ~/.mozilla/firefox/haris/bookmarks.html"
else
    # Try to open it as a bookmark in firefox
    url="$(
        buku --sreg "^$choice\$" -n 1 --format 10 |
        grep -v 'waiting for input'
    )"
    [ -z "$url" ] && url="$choice"
    # All google links shall be opened in firefox
    echo "$url" | grep -q 'google' && browser="$(echo "$browser" | sed 's_librewolf_/bin/firefox/')"
    echo "$url"
    auto-browser -P haris "$url"
fi

Snippets

edit=" Edit..."
edit_snips=" Edit snips..."
add=" Add..."

print_options() {
    echo "$edit"
    echo "$add"
    echo "$edit_clip"
}

# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Edit this - these are your entries ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
print_entries() {
    echo "$(cd ~/mail && command ls -1)"            # All my mail addresses
    cat ~/.local/snippets.txt | awk '{print $1}'    # TODO Snippets (temporary)
}

options="$(print_options)"
entries="$(print_entries)"

choice="$(echo "$options\n$entries" | dmenu )"

[ -z "$choice" ] && exit

if [ "$choice" = "$edit" ]; then
    gvim "$0"
elif [ "$choice" = "$edit_snips" ]; then
    gvim ~/.local/snippets.txt
elif [ "$choice" = "$add" ]; then
    choice="$(echo '' | dmenu)"
    [ -n "$choice" ] && echo "$choice" >> ~/.local/snippets.txt
else
    match="$(sed -n "/^$choice\[\s\|$\]/p" ~/.local/snippets.txt | sed 's/\S*\s*//')"
    print_match() { [ -n "$match" ] && echo "$match" || echo "$choice"; }
    print_match | xsel -b
    : # TODO integrate with tem;
fi

Use something more universal

Todo

edit=" Edit..."
lookup="$(
  ls ~/proj/*/TODO.org \
     ~/proj/drytoe/*/TODO.org \
     ~/data/personal/todos/*/TODO.org
)"
echo "$lookup"

entries="$(echo "$edit"
           echo "$lookup" |
           while read p; do
               basename "$(dirname "$p")"
           done)"

choice="$(
    echo "$entries" | dmenu -p TODO:
)"

[ "$?" != 0 ] && exit 1

if [ "$choice" = " Edit..." ]; then
    cd "$(dirname "$0")"; gvim "$0"
else
    myemacs-float "$(echo "$lookup" | grep "/$choice/TODO.org")"
fi

Pacman

install=" Install..."
about=" About..."
remove=" Remove..."
manage=" Manage..."
update=" Update..."
keyring=" Keyring..."
edit=" Edit..."

print_options() {
    echo "$install"
    echo "$about"
    echo "$update"
    echo "$remove"
    echo "$manage"
    echo "$keyring"
    echo "$edit"
}

choice="$(print_options | dmenu -l $(print_options | wc -l))"

case "$choice" in
    "$install")
        cache_file=~/.cache/.aur-package-list.txt
        age="$(date -d "now - $(stat -c '%Y' "$cache_file") seconds" +%s)"
        # Create cache file if it does not exist or is older than 5 hours
        if [ ! -f "$cache_file" ] || [ $age -gt 18000 ]; then
            curl -s 'https://aur.archlinux.org/packages.gz' \
                 -o - | gunzip -c > "$cache_file"
        fi
        # Pull the list of AUR packages
        list="$(cat "$cache_file")"
        # Prepend official packages to the list
        list="$(pacman -Ssq; echo "$list")"
        choice="$(echo "$list" | dmenu -l 20)"
        [ -z "$choice" ] && exit
        cmd="$(pacman -Ss "^$choice\$" >/dev/null && echo sudo pacman -S || echo paru)"
        alacritty --class Alacritty,Float -e fish -C "cmd-with-notify $cmd $choice" && exit
        ;;
    "$about")
        choice="$(echo "$(pacman -Qq)" | dmenu -p 'About:' -l 20)"
        [ -z "$choice" ] && exit
        alacritty --class Alacritty,Float -e fish -C "cmd-with-notify pacman -Qi $choice" && exit
        ;;
    "$update")
        alacritty --class Alacritty,Float -e fish -C "cmd-with-notify paru -Syu"
        ;;
    "$remove")
        choice="$(pacman -Qq | dmenu -l 20)"
        [ -z "$choice" ] && exit
        alacritty --class Alacritty,Float -e fish -C "cmd-with-notify sudo pacman -R $choice" && exit
        ;;
    "$manage")
        gvim "$(fcmd system-install)"
        ;;
    "$keyring")
        alacritty --class Alacritty,Float -e fish -C "cmd-with-notify sudo pacman -Sy archlinux-keyring && exit"
        ;;
    "$edit")
        gvim "$0"
        ;;
esac

Color

declare -A colors

colors[',k black']='         #1e1e1e'
colors[',r red']='           #ff5555'
colors[',g green']='         #5ac2a8'
colors[',y yellow']='        #f2b374'
colors[',b blue']='          #6980fa'
colors[',m magenta']='       #d098ff'
colors[',c cyan']='          #8cceff' # TODO Change to something darker
colors[',w white']='         #92aab7'
colors['.k brblack']='       #6b746b'
colors['.r brred']='         #ff8c8c'
colors['.g brgreen']='       #98eb98'
colors['.y bryellow']='      #e0d97b'
colors['.b brblue']='        #99a3ff'
colors['.m brmagenta']='     #f298c3'
colors['.c brcyan']='        #a6d9ff'
colors['.w brwhite']='       #dddddd'

get_entries() {
    echo ' Edit...'
    printf '%s\n' "${!colors[@]}" | sort | sed 's_.*_ &_'
}

entries=$(get_entries)

let n=$(echo "$entries" | wc -l)

choice="$(echo "$entries" | dmenu -l $n -p 'Color:')"

[ -z "$choice" ] && exit

if [ "$choice" = ' Edit...' ]; then
    gvim "$0"
    exit
fi

# Remove decoration from the choice
choice_filtered="$(echo $choice | sed 's_[^ ]* *\(.*\)_\1_')"
# Copy the color, after removing whitespace
echo -n "${colors["$choice_filtered"]}" | sed 's_[^ ]* *\(.*\)_\1_' | xsel -b

Unicode

# Choose a font-awesome icon from dmenu and copy it

from urllib.request import urlopen
from subprocess import run, PIPE
import os.path
import yaml

# ┏━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Prepare the icon list ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━┛

cache_file = os.path.expanduser('~/.cache/font-awesome-icon-list.yml')

# Read the yml file from cache, or download it from GitHub
if os.path.exists(cache_file):
    text = open(cache_file).read()
else:
    url = 'https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/metadata/icons.yml'
    data = urlopen(url).read()
    text = data.decode('utf-8')
    open(cache_file, 'w').write(text)

# Read the YAML file
data = yaml.load(text, yaml.Loader)

# ┏━━━━━━━━━━━━━━━━━━━━┓
# ┃ Add custom options ┃
# ┗━━━━━━━━━━━━━━━━━━━━┛
top_entries =   [
    ' Edit...',
    ' FontAwesome...',
    ' From code...',
    ' Get code...',
]

char_entries = []
# Create a (decorated) list of entries
for key in data.keys():
    unicode = int(data[key]['unicode'], base=16)
    char_entries.append(chr(unicode) + ' ' + key)

def add_custom(char):
    global char_entries
    char_entries.append(char + ' [custom]')

# ┏━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Add custom characters ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━┛
add_custom('├ |-')
add_custom('└ |_')
add_custom('─ --')
add_custom('┃┗━┛┏━┓ ||')
add_custom('š .sh')
add_custom('ć .ch meko')
add_custom('č .ch tvrdo')
add_custom('đ .dj')
add_custom('ž .zj')

# Form entry lists as multi-line strings
char_entries = '\n'.join(char_entries)
# Add options and character entries together
top_entries  = '\n'.join(top_entries) + '\n' + char_entries

# Run dmenu and get user choice
p = run(['dmenu'], stdout=PIPE, input=top_entries, encoding='utf-8')
choice = p.stdout[:-1]

def copy_to_clipboard(text):
    run(['xsel', '-b'], input=text, encoding='utf-8')

# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Actions based on user's choice ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
if choice[2:] == 'Edit...': # Open this file for editing
   run(['alacritty', '--class', 'Alacritty,Float', '-e', 'fish', '-C',
         'cd (dirname ' + __file__ + '); vim -c "norm 45z." ' + __file__])
elif choice[2:] == 'FontAwesome...':
    run(['firefox', 'https://fontawesome.com/search'])
elif choice[2:] == 'From code...':
    p = run(['dmenu', '-p', 'Code:'], stdout=PIPE, input=char_entries, encoding='utf-8')
    open('/home/haris/src/testlog', 'w').write(choice)
    code = p.stdout[:-1]
    if choice:
        copy_to_clipboard(chr(int(code, base=16)))
elif choice[2:] == 'Get code...':
    p = run(['dmenu', '-p', 'Character:'], stdout=PIPE, input=char_entries, encoding='utf-8')
    choice = p.stdout[:-1]
    if choice:
        copy_to_clipboard(str(ord(choice[0])))
elif choice:
    copy_to_clipboard(choice.split(' ')[0])

Kill Process

choice="$(ps -A -o comm --no-headers | dmenu)"

[ -z "$choice" ] && exit

process="$choice"

choice="$(echo " No\n Yes, kill $process" | dmenu -p 'Sure?' -l 2)"

[ "$choice" = " Yes, kill $process" ] && pkill "$process"

Config

from subprocess import run, PIPE
import os
import os.path
import sys

# Load regular configuration entries
entries = run('lscf', stdout=PIPE, encoding='utf-8').stdout.replace('-', ' ')
# Load dmenu scripts
dmenu_scripts = os.listdir(os.path.expanduser('~/.local/lib/dmenu/'))

# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Additional entries and customization ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
entries = (
    ' Edit...\n' +
    ' ' + entries.replace('\n', '\n ') +
    'dmenu\n dmenu ' +
    '\n dmenu '.join(dmenu_scripts)
)

# Run dmenu
choice = run(['dmenu', '-l', '20',  '-p', 'Config:'],
             input=entries, encoding='utf-8', stdout=PIPE).stdout

if not choice:
    sys.exit()

# Strip decoration from the entry
choice = choice[2:-1].replace(' ', '-')

def run_command(cmd):
    run(['alacritty', '--class', 'Alacritty,Float', '-e',
         'fish', '-C', cmd])

# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Actions based on user's choice ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
if choice == 'dmenu':
    EVAL = fr'''
        (progn (find-file "~/.haris/scripts.org")
               (call-interactively (swiper "^\\* Dmenu"))
               (evil-ex-nohiglight))
    '''
    run(["myemacs-float", "--eval", EVAL])
if choice.startswith('dmenu-'):
    submenu = choice.replace("dmenu-", "")
    submenu = submenu[0].upper() + submenu[1:]
    run(["notify-send", submenu])
    EVAL = fr'''
        (progn (find-file "~/.haris/scripts.org")
               (call-interactively (swiper "^\\*\\* {submenu}"))
               (evil-ex-nohiglight))
    '''
    run(["myemacs-float", "--eval", EVAL])
elif choice == 'Edit...':
    EVAL = '''
        (progn (find-file "~/.haris/scripts.org")
               (goto-char (org-find-property "CUSTOM_ID" "cf"))
               (evil-ex-nohiglight))
    '''
    run(["myemacs-float", "--eval", EVAL])
else:
    run("fish -c 'ecf {}'".format(choice), shell=True)
    sys.exit()

System

choice="$(echo "Suspend\nSuspend & Lock\nShutdown\nReboot..." | dmenu)"

# No choice, bye-bye
[ -z "$choice" ] && exit

if [ "$choice" = "Shutdown" ]; then

    choice="$(echo " No\n Yes, shutdown" | dmenu -p 'Sure?')"
    [ "$choice" = " Yes, shutdown" ] && shutdown now

elif [ "$choice" = "Suspend & Lock" ]; then
    xlock &  # NOTE: using the 'o' script here doesn't detach tmux for some reason
    sleep 1
    systemctl suspend -i
elif [ "$choice" = "Suspend" ]; then
    systemctl suspend -i
elif [ "$choice" = "Reboot..." ]; then
    print_entries() {
        # Extract only lines with menu entries from grub
        grep "menuentry '\|submenu '" /boot/grub/grub.cfg |
            # Only top-level menus are considered
            grep -v -P '\t' |
            # Take only the entry name
            sed "s_\S* '\([^']*\)'.*_\1_" |
            # Add numbers
            nl -w 1 -v 0 -n rn | sed -E 's/\s+/ /g'
    }

    entries="$(print_entries)"
    choice="$(echo "$entries" | dmenu -l $(echo "$entries" | wc -l) | egrep --only-matching '^[0-9]+')"
    [ -z "$choice" ] && exit

    # For convenience, grub-reboot should be allowed passwordless in sudoers.
    sudo grub-reboot "$choice"
    reboot || sudo reboot
fi

Services

# Unicode glyphs
started=""
stopped=""
arrow=""
restart=""
user=""

list() {
    systemctl "list-$1" \
              --all \
              --full \
              --plain \
              --no-pager \
              --no-legend \
              --type=service 2>&- \
              "${@:2}" \
        | cut -f1 -d' '
}

get_prefix() {
    if [ "$1" == "--user" ]; then \
        echo -n "$user "
    else
        echo -n "" # NOTE: This string contains a THIN SPACE unicode character
    fi
}

entries() {
    local opt="$1" running_services stopped_services
    readarray -t running_services < <(list units --state=running $opt)

    # Running services
    paste -d' ' <(
        systemctl show "${running_services[@]}" \
                  $opt \
            | grep ^Restart= \
            | sed -e "s/^Restart=no$/$started $arrow $stopped/" \
                  -e "s/^Restart=.*/$started $arrow $restart/" \
                  -e "s/^/$(get_prefix $opt)/"
    ) \
          <(printf "%s\n" "${running_services[@]}")

    # Stopped services
    {
        for state in exited failed; do
            list units      --state="$state" $opt
        done
    } | sed -e "s/^/$stopped $arrow $started /" \
            -e "s/^/$(get_prefix $opt)/"

    # Other services
    if [ -z "$opt" ]; then
        comm -23 \
             <(list unit-files --state=disabled $opt | sort) \
             <(printf "%s\n" "${running_services[@]}" | sort) \
            | sed "s/^/$(get_prefix $opt)$stopped $arrow $started /"
    fi
}
selection="$(
    {
        entries
        entries --user
    } | dmenu -p 'Services:' -l 20
)"

if [ -z "$selection" ]; then
    exit 1
fi

service="$(echo "$selection" | grep --only-matching '\S\+$')"

user_opt=''
sudo='sudo'
if grep -q "$user" <<<"$selection"; then
    echo here
    user_opt="--user"
    sudo=""
fi

cmd=''
if grep -q "$started $arrow $stopped" <<<"$selection"; then
    cmd=stop
elif grep -q "$stopped $arrow $started" <<<"$selection"; then
    cmd=start
else
    cmd=restart
fi

set -e

# Notify the user
service_name="${service%.service}"

# Execute the action
export HARIS_BACKGROUND_TASKS_SILENT=true
o sh -c "SUDO_ASKPASS=\"$(which askpass-gui)\" $sudo systemctl '$cmd' $user_opt '$service'"

jctl_session_name="$(
  tmux new -P -F'#{session_name}' -d journalctl --follow --lines=0 $user_opt -u "$service"
)"

action="$(
    case "$cmd" in
        start)
            notify-send --action 0 "Starting service..." "$service_name"
            ;;
        restart)
            notify-send --action 0 "Restarting service..." "$service_name"
            ;;
        stop)
            notify-send --action 0 "Stopping service..." "$service_name"
            ;;
    esac
)"

if [ "$action" = "0" ]; then
    alacritty-float -e tmux attach -t "$jctl_session_name"
else
    tmux kill-session -t "$jctl_session_name"
fi

GPG

# Get list of all public keys
keylist=($(gpg --list-public-keys Haris | grep '^\s' | sed 's/^\s*//g'))

entries="$(
    for key in "${keylist[@]}"; do
        # Get info for key
        keyinfo="$(gpg --list-public-keys | grep "$key" -A1)"
        # Get email of key owner
        email="$(echo "$keyinfo" | grep '<.*>' | sed 's/^.*\]//')"
        echo "$key" "$email"
    done
)"
let n="$(echo "$entries" | wc -l)"

choice="$(echo "$entries" | dmenu -l $n -p 'GPG:')"

echo "$choice" | awk '{print $1}' | xsel -b

OTP

# Select an app and copy its OTP to clipboard

cd ~

entries="$(
    fd 'otp-secret.gpg' .password-store -x echo {//} \
        | sed 's:^\.password-store/\?::'
)"

choice="$(echo "$entries" | dmenu -l 10 -p 'OTP:')"

[ -z "$choice" ] && exit 1

otp "$choice" | xsel -b -t 30000
notify-send "OTP" "Saved to clipboard"

Books

cd ~/data/literature

choice="$(fd --type file '' | dmenu -l 10 -p 'Book:')"

[ -z "$choice" ] && exit

sioyek "$choice"
sts="$?"

sleep 2s
trap 'kill "$pid"' EXIT
# trap 'printarg kill "$pid"' ERR
# Ensure that this script stays running as long as the program, so `o` doesn't
# time out
while :; do
    pid="$(pgrep -a '' | grep --fixed-strings "$choice" | cut -f1 -d' ' | head -1)"
    if [ -z "$pid" ]; then break; fi
    sleep 5s
done

Quickmenu

The quickmenu script is optional and must be provided separately at ~/.local/lib/dmenu/quickmenu.

Dependencies

# Root menu (dmenu_run)
clipmenu
# Open menu
xdotool xorg-xprop
# Open menu
buku

System maintenance

pacman-update-mirrorlist

# Update /etc/pacman.d/mirrorlist using reflector

sudo reflector --sort rate --save /etc/pacman.d/mirrorlist

texclean

# Array of extensions
extarray=($(sed -e '/^#/d' -e '/^$/d' ~/templates/latex/ignored_files))

if [ "$1" == '-r' ]; then
    shopt -s globstar
    rm -f ${extarray[*]/#/\*\*\/\*.} # **/*.extension
else
    rm -f ${extarray[*]/#/\*.} # **/*.extension
fi

exit

springclean

docker system prune
docker volume prune

rm -rf ~/.local/share/Trash
rm -rf ~/.local/share/*.xbel*

# I think this is created by KDE plasma
rm -rf ~/.local/share/baloo

sudo journalctl --vacuum-size=250M
paccache -vuk0 -r

createhome

# Top level home directory
dirs=(
    src
    tmp
    repo
)
mkdir -p "${dirs[@]}"

mkdir -p ~/mnt
cd ~/mnt

dirs=(
    cloud
    hdd
    phone
    ssd
    usb
    usb-guest
    usbb1
    usbb2
    vm
)

mkdir -p "${dirs[@]}"

cleanhome

# Clean home of directories like Downloads, Documents, regularly created by who
# knows.

rmdir ~/Desktop ~/Downloads ~/Documents ~/Pictures ~/Videos ~/Music \
      ~/Templates ~/Public ~/'VirtualBox VMs' ~/mpv_slicing.log

cleantex

# A script to clean tex build files

shopt -s globstar

rm **/*.aux **/*.log **/*.toc **/*.bbl **/*.fls **/*.idx **/*.ilg **/*.ind \
   **/*.nlo **/*.out **/*.synctex.gz **/*.fdb_latexmk 2>&1 | grep -v \
                                                                  'No such file or directory'

tangle

~/.haris/bootstrap/tangle.sh "$@"

Because this script is needed for bootstrapping my dotfiles on a new system, I also tangle it to a standalone destination that I also keep under version control.

#!/usr/bin/env -S emacs --script

(load-file (format "%s/tangle.el" (file-name-directory load-file-name)))

(haris/tangle--file-non-interactively (file-truename (elt argv 0)))

; NOTE: Although this file is kept under version control, it is tangled from
; ~/.haris/scripts.org, so don't modify it directly

tangle-all

~/.haris/bootstrap/tangle-all.sh "$@"

Because this script is needed for bootstrapping my dotfiles on a new system, I also tangle it to a standalone destination that I also keep under version control.

#!/usr/bin/env -S emacs --script

;; Tangle all my dotfiles. If --dest is used, then the files are tangled to
;; their final destinations under $HOME, instead of the directory returned by
;; (haris/tangle-home). Dependency installation scripts will be saved to the
;; destination returned by (haris/tangle-deps) regardless.

(setq command-switch-alist '(("--dest" . ignore)))
(setq dest (member "--dest" command-line-args))
(setq this-script-dir (file-name-directory load-file-name))

(load-file (format "%s/tangle.el" this-script-dir))
(haris/tangle-all dest :dotfiles-dir (expand-file-name
                                      (format "%s/.." this-script-dir)))

;; vim: filetype=lisp
;; -*- mode: emacs-lisp -*-
;; NOTE: Although this file is kept under version control, it is tangled from
;; ~/.haris/scripts.org, so don't modify it directly

website-healthcheck

Usage: website-healthcheck [–ignore-success]

Options: –notify-success Show a notification even if the website status is ‘alive’

notify_success="$([ "$1" = "--notify-success" ] && echo true)"

prod_status="$(curl -L https://veracioux.me/status)"
if [ "$?" != 0 ] || [ "$prod_status" != "alive" ]; then
    ~/.local/bin/notify-send --urgency=critical 'ERROR' 'Production website error'
elif [ -n "$notify_success" ]; then
    ~/.local/bin/notify-send 'Production website' 'Status: alive'
fi

stg_status="$(curl -L https://stg.veracioux.me/status)"
if [ "$?" != 0 ] || [ "$stg_status" != "alive" ]; then
    ~/.local/bin/notify-send --urgency-critical 'ERROR' 'Staging website error'
elif [ -n "$notify_success" ]; then
    ~/.local/bin/notify-send 'Staging website' 'Status: alive'
fi

vpn-toggle

restart_if_running() {
    if systemctl is-active --quiet "$1"; then
        sudo systemctl restart "$1"
    fi
}

if systemctl is-active --quiet protonvpn; then
  sudo systemctl stop protonvpn
else
  sudo systemctl start protonvpn
fi

restart_if_running stubby
restart_if_running dnsmasq

Miscellaneous

term

alacritty --working-directory "$PWD" "$@"

aurvote

ssh [email protected] vote "$@"

handle-low-bat

# Suspend when battery low

if acpi | grep -qP '\s1?[0-9]%' && acpi | grep -q 'Discharging'; then
    timeout 0.4 ~/.local/bin/notify-send 'Low battery' 'Suspending in 5s'
    sleep 5s
    systemctl suspend -i
fi

cronupd

# TODO change to ~/.crontab
cat ~/.crontab | crontab -
crontab -l

fix-wifi

sudo rfkill block wifi && sudo rfkill unblock wifi

guirun

Open a GUI window in the correct workspace.

# This roundabout way is because i3-msg exec can't handle commas in arguments

script="$(mktemp)"
chmod u+x "$script"
printf "%q " "$@" > "$script"

i3-msg exec "$script"
rm -f "$script"

snip

nvim ~/.vim/snips/"$1".snippets

renice-emacs

Modify niceness of emacs processes to give them higher priority.

for pid in $(pgrep emacs); do
    /usr/bin/renice -n -15 "$pid"
done

sxhkd

TODO: move to main README

I have three sets of bindings for sxhkd:

  • common; publicly available in my dotfiles repo
  • private; kept in a private repo and not publicly available
  • temporary; ad hoc bindings, not versioned at all

I want to be able to enable/disable each of those individually. That’s why I keep each in a separate config file. And, for each I run a separate process so I can conveniently stop/restart each by name.

sxhkd.private

exec -a sxhkd.private sxhkd -c ~/.private.sxhkdrc "$@"

sxhkd.tmp

exec -a sxhkd.tmp sxhkd -c ~/.tmp.sxhkdrc "$@"

Adapters

My main setup is Arch Linux. Sometimes I also use other setups like Ubuntu, which sometimes have differently named packages and/or executables. I wrap the executables here so they are available under the same executable names as on Arch Linux.

By default, the adapters are archived. In order to enable them, simply unarchive them if the current PC setup is the one they are intended for.

Ubuntu

docker-compose

docker compose "$@"

fd

fdfind "$@"

passmenu

/usr/share/doc/pass/examples/dmenu/passmenu "$@"

ipython

# Some platforms don't provide ipython as a symlink/wrapper to ipython3
ipython3 "$@"

pycharm

pycharm-professional

Helper code

These scripts are used as snippets or noweb references within this org file.

find-alt-cmd(name)

Returns the second executable with the given name, looked up in execpath.

(let ((counter 0) (executable))
  (locate-file name exec-path nil
               (lambda (path)
                 (if (file-executable-p path)
                     (setq counter (+ counter 1)))
                 (> counter 1))))

OS-specific code

;; Insert text only on macOS
(if (eq system-type 'darwin) text else)
;; Insert text only on Linux
(if (eq system-type 'darwin) text else)

eval-real-uid

(user-real-uid)

add-create-frame-option-if-term-dumb

set -- "$@" $([ "$TERM" = "dumb" ] && echo --create-frame || echo '')

eval-user-name

(user-login-name)

eval-user-home

(getenv "HOME")

Local variables