summary refs log tree commit diff
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2021-07-29 23:45:43 +0200
committerRobert Günzler <r@gnzler.io>2021-07-29 23:48:28 +0200
commite77881a72b735ca12aa055054656afdfbd6a8fc8 (patch)
tree53ed4778acca18f55141e4efbdc0a841b64116a5
parent3079e935c793a9aa1831fd165e28636eb7a87dae (diff)
bin: format scripts
-rwxr-xr-xbin/beep62
-rwxr-xr-xbin/enter20
-rwxr-xr-xbin/grimshot223
-rwxr-xr-xbin/gui-enter10
-rwxr-xr-xbin/hibiki/prompt6
-rwxr-xr-xbin/mnts43
-rwxr-xr-xbin/pomo68
-rwxr-xr-xbin/rm10
-rwxr-xr-xbin/safe-env10
-rwxr-xr-xbin/semver85
-rwxr-xr-xbin/soundswitch259
-rwxr-xr-xbin/swx141
-rwxr-xr-xbin/trash98
-rwxr-xr-xbin/xdg-exec2
14 files changed, 606 insertions, 431 deletions
diff --git a/bin/beep b/bin/beep
index 4d0e16b..4dc53fb 100755
--- a/bin/beep
+++ b/bin/beep
@@ -1,16 +1,56 @@
 #!/bin/sh
+
 vol=${BEEP_VOL:-0.5}
-snd=${BEEP_SND:-$HOME/Music/sounds/miui/complete.ogg}
-subject=${BEEP_SUB:-beep}
-message=${BEEP_MSG:-"<i>ringing the bell...</i>"}
-icon=${BEEP_ICN:-/usr/share/icons/Adwaita/scalable/status/task-due-symbolic.svg}
+snd=${BEEP_SND:-$HOME/Music/sounds/miui/}
+subject=${BEEP_SUB:-Beep}
+message=${BEEP_MSG:-"<i>The bell rang</i>"}
+icon=${BEEP_ICN:-"$(iconez -p $PPID | head -n1)"}
+nobel=
+nonotify=
+while getopts v:s:S:M:i:bNh opt; do
+	case "$opt" in
+		v) vol=${OPTARG} ;;
+		s) snd=${OPTARG} ;;
+		# S) subject=${OPTARG} ;;
+		# M) message=${OPTARG} ;;
+		# i) icon=${OPTARG} ;;
+		N) nonotify=1 ;;
+		b) nobel=1 ;;
+		h | ?)
+			printf "usage: %s [-h] -- NOTIFY_SEND_ARGS\n" "$(basename "$0")"
+			printf "\n"
+			printf "  -v \t volume (default: ${vol})\n"
+			printf "  -s \t sound file (default: ${snd})\n"
+			printf "  -b \t don't sound terminal bell (default: false)\n"
+			printf "  -N \t don't send notification (default: false)\n"
+			printf "\n"
+			exit 2
+			;;
+	esac
+done
+shift $((OPTIND - 1))
+
+(
+	echo ${PPID}
 
-notify-send -a beep -u normal -i "${icon}" \
-	"${subject}" "${message}" &
+	[ -z "${nobel}" ] && tput bel &
 
-tput bel &
+	if [ -z "${nonotify}" ]; then
+		if [ -z "$*" ]; then
+			notify-send -a beep -u normal -i "${icon}" "${subject}" "${message}" &
+		else
+			notify-send -a beep -u normal -i "${icon}" "$@" &
+		fi
+	fi
 
-exec paplay \
-	--property=media.role=event \
-	--volume="${vol}" \
-	"${snd}"
+	if [ ! -f "${snd}" ]; then
+		case "${1}" in
+			done) snd=${snd}/complete.ogg ;;
+			*) snd=${snd}/bell.ogg ;;
+		esac
+	fi
+	paplay \
+		--property=media.role=event \
+		--volume="${vol}" \
+		"${snd}"
+) &
diff --git a/bin/enter b/bin/enter
index 77d79e3..68db3d5 100755
--- a/bin/enter
+++ b/bin/enter
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 set -e
 [ -n "$DEBUG" ] && set -x
 
@@ -11,6 +11,7 @@ become=
 verbose=
 cwd=$PWD
 no_tty=
+read_only=
 
 usage() {
 	printf "usage: %s OPTIONS -- [ARGS]\n" "$(basename "$0")"
@@ -24,12 +25,13 @@ usage() {
 	printf "  -v (be verbose)\n"
 	printf "  -c CWD\n"
 	printf "  -T (not tty)\n"
+	printf "  -W (readonly)\n"
 	printf "\n"
 	printf "Enter the container defined in Dockerfile, with \$PWD mounted at /src\n"
 	exit 2
 }
 
-while getopts t:i:f:o:c:vBDTh o; do
+while getopts t:i:f:o:c:vBDTWh o; do
 	case $o in
 		t) target="$OPTARG" ;;
 		i) img="$OPTARG" ;;
@@ -40,6 +42,7 @@ while getopts t:i:f:o:c:vBDTh o; do
 		B) become=1 ;;
 		v) verbose=1 ;;
 		T) no_tty=1 ;;
+		W) read_only=1 ;;
 		h | ?) usage ;;
 	esac
 done
@@ -85,6 +88,9 @@ extra_args=
 [ -z "$no_tty" ] \
 	&& extra_args="${extra_args} --tty"
 
+[ -n "$read_only" ] \
+	&& read_only=",ro"
+
 ep="sh"
 [ -n "$1" ] && {
 	ep="$1"
@@ -93,8 +99,14 @@ ep="sh"
 
 [ -n "$verbose" ] && { set -x; }
 
-cname=$(mktemp --dry-run enterXXXX)
+if dev=$(echo "$@" | grep -Po '\/dev\/[^ ]*'); then
+	extra_args="${extra_args} --device=${dev}"
+fi
+
+cname=$(mktemp --dry-run enter_XXXX)
 exec docker run --name "$cname" --rm -i \
+	--env-file <(safe-env) \
 	--uts host \
+	--ipc host \
 	--entrypoint="$ep" \
-	--mount=type=bind,src="$cwd",dst=/src -w /src $extra_args "$img" $@
+	--mount=type=bind,src="$cwd",dst=/src"$read_only" -w /src $extra_args "$img" $@
diff --git a/bin/grimshot b/bin/grimshot
index 4d4dd81..af7bcd1 100755
--- a/bin/grimshot
+++ b/bin/grimshot
@@ -20,143 +20,150 @@
 ## `grimshot check` - verify if tools are installed
 
 _slurp() {
-  slurp -d -c '#4E437188' -w 3
+	slurp -d -c '#4E437188' -w 3
+}
+_silence_notifications() {
+	once -k mako
+	trap '{ once mako; }' EXIT
 }
 
 ACTION=${1:-usage}
 SUBJECT=${2:-screen}
 FILE=${3:-$(xdg-user-dir PICTURES)/screenshots/$(now).png}
 case "$ACTION" in
-  usage|help|-h|--help)
-  echo "Usage:"
-  echo "  grimshot copy|save|rec win|screen|area|output [FILE]"
-  echo "Troubleshoot:"
-  echo "  grimshot check"
-  exit
+	usage | help | -h | --help)
+		echo "Usage:"
+		echo "  grimshot copy|save|rec win|screen|area|output [FILE]"
+		echo "Troubleshoot:"
+		echo "  grimshot check"
+		exit
+		;;
 esac
 
 notify() {
-  notify-send -i "${TMP:-${FILE}}" -t 3000 -a grimshot "$@"
-  # notify-send -i "${TMP:-${FILE}}" -t 3000 "$@"
+	notify-send -i "${TMP:-${FILE}}" -t 3000 -a grimshot "$@"
+	# notify-send -i "${TMP:-${FILE}}" -t 3000 "$@"
 }
 notifyOk() {
-  TITLE=${2:-"Screenshot"}
-  MESSAGE=${1:-"OK"}
-  notify "$TITLE" "$MESSAGE"
+	TITLE=${2:-"Screenshot"}
+	MESSAGE=${1:-"OK"}
+	notify "$TITLE" "$MESSAGE"
 }
 notifyError() {
-  TITLE=${2:-"Screenshot"}
-  MESSAGE=${1:-"Error taking screenshot with grim"}
-  TMP=/usr/share/icons/Adwaita/scalable/apps/applets-screenshooter-symbolic.svg
-  notify -u critical "$TITLE" "$MESSAGE"
+	TITLE=${2:-"Screenshot"}
+	MESSAGE=${1:-"Error taking screenshot with grim"}
+	TMP=/usr/share/icons/Adwaita/scalable/apps/applets-screenshooter-symbolic.svg
+	notify -u critical "$TITLE" "$MESSAGE"
 }
 
 die() {
-  MSG=${1:-Bye}
-  notifyError "Error: $MSG"
-  exit 2
+	MSG=${1:-Bye}
+	notifyError "Error: $MSG"
+	exit 2
 }
 
 check() {
-  COMMAND=$1
-  command -v "$COMMAND" > /dev/null 2>&1
-  if [ $? ]; then
-    RESULT="OK"
-  else
-    RESULT="NOT FOUND"
-  fi
-  echo "   $COMMAND: $RESULT"
+	COMMAND=$1
+	command -v "$COMMAND" >/dev/null 2>&1
+	if [ $? ]; then
+		RESULT="OK"
+	else
+		RESULT="NOT FOUND"
+	fi
+	echo "   $COMMAND: $RESULT"
 }
 
 takeScreenshot() {
-  FILE=$1
-  GEOM=$2
-  OUTPUT=$3
-  if [ -n "$GEOM" ]; then
-    grim -g "$GEOM" "$FILE" || die "Unable to invoke grim"
-  elif [ -n "$OUTPUT" ]; then
-    grim -o "$OUTPUT" "$FILE" || die "Unable to invoke grim"
-  else
-    grim "$FILE" || die "Unable to invoke grim"
-  fi
+	FILE=$1
+	GEOM=$2
+	OUTPUT=$3
+	if [ -n "$GEOM" ]; then
+		grim -g "$GEOM" "$FILE" || die "Unable to invoke grim"
+	elif [ -n "$OUTPUT" ]; then
+		grim -o "$OUTPUT" "$FILE" || die "Unable to invoke grim"
+	else
+		grim "$FILE" || die "Unable to invoke grim"
+	fi
 }
 
 recordScreen() {
-  FILE=$1
-  GEOM=$2
-  OUTPUT=$3
-  REC_OPTS="-d /dev/dri/renderD128"
-  if [ -n "$GEOM" ]; then
-    wf-recorder ${REC_OPTS} -g "${GEOM}" -f "$FILE" || die "Unable to invoke wf-recorder"
-  elif [ -n "$OUTPUT" ]; then
-    wf-recorder ${REC_OPTS} -o "${OUTPUT}" -f "$FILE" || die "Unable to invoke wf-recorder"
-  else
-    wf-recorder ${REC_OPTS} -f "$FILE" || die "Unable to invoke wf-recorder"
-  fi
+	_silence_notifications
+
+	FILE=$1
+	GEOM=$2
+	OUTPUT=$3
+	REC_OPTS="-d /dev/dri/renderD128"
+	if [ -n "$GEOM" ]; then
+		wf-recorder ${REC_OPTS} -g "${GEOM}" -f "$FILE" || die "Unable to invoke wf-recorder"
+	elif [ -n "$OUTPUT" ]; then
+		wf-recorder ${REC_OPTS} -o "${OUTPUT}" -f "$FILE" || die "Unable to invoke wf-recorder"
+	else
+		wf-recorder ${REC_OPTS} -f "$FILE" || die "Unable to invoke wf-recorder"
+	fi
 }
 
-if [ "$ACTION" = "check" ] ; then
-  echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..."
-  check grim
-  check slurp
-  check wf-recorder
-  check swaymsg
-  check swq
-  check wl-copy
-  check jq
-  check notify-send
-  check mktemp
-  exit
-elif [ "$SUBJECT" = "area" ] ; then
-  GEOM=$(_slurp)
-  [ -z "$GEOM" ] && die "Selection cancelled"
-  WHAT="Area"
-elif [ "$SUBJECT" = "win" ] ; then
-  FOCUSED=$(swaymsg -t get_tree | jq -r 'recurse(.nodes[]?, .floating_nodes[]?) | select(.focused)')
-  GEOM=$(echo "$FOCUSED" | jq -r '.rect | "\(.x),\(.y) \(.width)x\(.height)"')
-  APP_ID=$(echo "$FOCUSED" | jq -r '.app_id')
-  WHAT="$APP_ID window"
-elif [ "$SUBJECT" = "screen" ] ; then
-  GEOM=""
-  WHAT="Screen"
-elif [ "$SUBJECT" = "output" ] ; then
-  GEOM=""
-  OUTPUT="$(swq current output)"
-  WHAT="output $OUTPUT"
+if [ "$ACTION" = "check" ]; then
+	echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..."
+	check grim
+	check slurp
+	check wf-recorder
+	check swaymsg
+	check swq
+	check wl-copy
+	check jq
+	check notify-send
+	check mktemp
+	exit
+elif [ "$SUBJECT" = "area" ]; then
+	GEOM=$(_slurp)
+	[ -z "$GEOM" ] && die "Selection cancelled"
+	WHAT="Area"
+elif [ "$SUBJECT" = "win" ]; then
+	FOCUSED=$(swaymsg -t get_tree | jq -r 'recurse(.nodes[]?, .floating_nodes[]?) | select(.focused)')
+	GEOM=$(echo "$FOCUSED" | jq -r '.rect | "\(.x),\(.y) \(.width)x\(.height)"')
+	APP_ID=$(echo "$FOCUSED" | jq -r '.app_id')
+	WHAT="$APP_ID window"
+elif [ "$SUBJECT" = "screen" ]; then
+	GEOM=""
+	WHAT="Screen"
+elif [ "$SUBJECT" = "output" ]; then
+	GEOM=""
+	OUTPUT="$(swq current output)"
+	WHAT="output $OUTPUT"
 else
-  die "Unknown subject to take a screen shot from" "$SUBJECT"
+	die "Unknown subject to take a screen shot from" "$SUBJECT"
 fi
 
-if [ "$ACTION" = "copy" ] ; then
-  TMP=$(mktemp) || die "Unable to create temp file: is mktemp installed?"
-  takeScreenshot "$TMP" "$GEOM" "$OUTPUT"
-  wl-copy --type image/png < "$TMP"  || die "Clipboard error"
-  notifyOk "$WHAT copied to clipboard"
-  rm "$TMP"
-elif [ "$ACTION" = "save" ] ; then
-  takeScreenshot "$FILE" "$GEOM" "$OUTPUT"
-  if [ $? ]; then
-    TITLE="Screenshot of $SUBJECT"
-    MESSAGE=$(basename "$FILE")
-    wl-copy --type image/png < "$FILE"  || die "Clipboard error"
-    notifyOk "$MESSAGE" "$TITLE"
-  else
-    notifyError "Error taking screenshot with grim"
-  fi
-elif [ "$ACTION" = "rec" ] ; then
-  echo "$FILE" | awk '/png$/{exit 0}' && \
-    FILE=$(xdg-user-dir VIDEOS)/grimshot/$(now).mp4
-  # create dir if not exists
-  [ -d "$(dirname "$FILE")" ] || mkdir -p "$(dirname "$FILE")"
-  recordScreen "$FILE" "$GEOM" "$OUTPUT"
-  wl-copy "$FILE" || die "Clipboard error"
-  if [ $? ]; then
-    TITLE="Screen recording of $SUBJECT"
-    MESSAGE=$(basename "$FILE")
-    notifyOk "$MESSAGE" "$TITLE"
-  else
-    notifyError "Error recording screen with wf-recorder"
-  fi
+if [ "$ACTION" = "copy" ]; then
+	TMP=$(mktemp) || die "Unable to create temp file: is mktemp installed?"
+	takeScreenshot "$TMP" "$GEOM" "$OUTPUT"
+	wl-copy --type image/png <"$TMP" || die "Clipboard error"
+	notifyOk "$WHAT copied to clipboard"
+	rm "$TMP"
+elif [ "$ACTION" = "save" ]; then
+	takeScreenshot "$FILE" "$GEOM" "$OUTPUT"
+	if [ $? ]; then
+		TITLE="Screenshot of $SUBJECT"
+		MESSAGE=$(basename "$FILE")
+		wl-copy --type image/png <"$FILE" || die "Clipboard error"
+		notifyOk "$MESSAGE" "$TITLE"
+	else
+		notifyError "Error taking screenshot with grim"
+	fi
+elif [ "$ACTION" = "rec" ]; then
+	echo "$FILE" | awk '/png$/{exit 0}' \
+		&& FILE=$(xdg-user-dir VIDEOS)/grimshot/$(now).mp4
+	# create dir if not exists
+	[ -d "$(dirname "$FILE")" ] || mkdir -p "$(dirname "$FILE")"
+	recordScreen "$FILE" "$GEOM" "$OUTPUT"
+	wl-copy "$FILE" || die "Clipboard error"
+	if [ $? ]; then
+		TITLE="Screen recording of $SUBJECT"
+		MESSAGE=$(basename "$FILE")
+		notifyOk "$MESSAGE" "$TITLE"
+	else
+		notifyError "Error recording screen with wf-recorder"
+	fi
 else
-  die "Unknown action" "$ACTION"
+	die "Unknown action" "$ACTION"
 fi
diff --git a/bin/gui-enter b/bin/gui-enter
index e14bafb..ac7a479 100755
--- a/bin/gui-enter
+++ b/bin/gui-enter
@@ -13,7 +13,7 @@ usage() {
 }
 
 case "$1" in
--h)	usage && exit 0 ;;
+	-h) usage && exit 0 ;;
 esac
 
 user="${1}"
@@ -26,15 +26,17 @@ marker_bins="
 	oguri
 	kanshi
 	waybar
-	gammastep
+	wlsunset
 	pipewire
 	swayidle
 "
 pid=
 for bin in ${marker_bins}; do
-	pid=$(cat "/run/user/"${uid}"/"${bin}".pid")
+	pid=$(cat "/run/user/${uid}/${bin}.pid")
 	kill -0 "${pid}" && break
 done
 
+# grep -zE '^(SWAY|I3|WAYLAND|DBUS|XDG|PATH)' /proc/"${pid}"/environ | tr '\0' '\n'
+
 env=$(grep -zE '^(SWAY|I3|WAYLAND|DBUS|XDG|PATH)' /proc/"${pid}"/environ | tr '\0' ' ' || true)
-exec doas -u "${user}" env ${env} "${@}"
+exec doas -nu "${user}" -- env ${env} "${@}"
diff --git a/bin/hibiki/prompt b/bin/hibiki/prompt
index 4109817..d23fc28 100755
--- a/bin/hibiki/prompt
+++ b/bin/hibiki/prompt
@@ -16,6 +16,8 @@ JOBS='`[ \j -gt 0 ] && printf " ∆\j"`'
 
 # show git-bug tickets
 # shellcheck disable=SC2016
-BUGS='`exit=$?; git rev-parse >/dev/null 2>&1 && { bugn=$(git bug ls --status=open | wc -l); [ $bugn -gt 0 ] && printf " ⚑${bugn}"; }; exit $exit`'
+# BUGS='`exit=$?; git rev-parse >/dev/null 2>&1 && { bugn=$(git bug ls --status=open | wc -l); [ $bugn -gt 0 ] && printf " ⚑${bugn}"; }; exit $exit`'
+BUGS=''
 
-echo "${SET_TERM_TITLE}${color}\w${RESET}${JOBS}${BUGS} ❱ "
+# ❱⫶
+echo "⋮ ${SET_TERM_TITLE}${color}\w${RESET}${JOBS}${BUGS} ❯ "
diff --git a/bin/mnts b/bin/mnts
index 817d372..56eb185 100755
--- a/bin/mnts
+++ b/bin/mnts
@@ -5,9 +5,10 @@
 #
 
 [ -n "$DEBUG" ] && set -x
+[ "$1" = "-o" ] && open=1
 
 # check requirements
-for app in awk jq lsblk notify-send udisksctl; do
+for app in awk jq lsblk notify-send udisksctl swx vifm; do
 	if ! command -v "$app" >/dev/null; then
 		printf "%s: %s not installed but required\n" "$(basename "$0")" "$app" >&2
 		exit 1
@@ -16,21 +17,35 @@ done
 
 export MENU_PROMPT="󰕓 mnts"
 
-handle() {
-	line=$(awk '{print $0}')
-	[ -z "${line}" ] && exit 0
-
-	path=$(echo "${line}"|awk -F\\t '{print $3}'|xargs)
-	mode=$(echo "${line}"|awk -F\\t '{if(length($3)<1) print "mount";else print "unmount";}'|xargs)
-	drv=$(echo "${line}"|awk -F\\t '{print $1}'|xargs)
-	label=$(echo "${line}"|awk -F\\t '{print $2}'|xargs)
-	[ -n "${DEBUG}" ] && printf 'DEBUG: mode:%s, drv:%s, path:%s\n' "${mode}" "${drv}" "${path}";
-	_outp=$(udisksctl "${mode}" -b "${drv}" 2>&1)
-	if [ $? = 0 ]; then \
-		notify-send -a mnts mnts "${_outp}"
+mount() {
+	line=$1
+	path=$(echo "${line}" | awk -F\\t '{print $3}' | xargs)
+	mode=$(echo "${line}" | awk -F\\t '{if(length($3)<1) print "mount";else print "unmount";}' | xargs)
+	drv=$(echo "${line}" | awk -F\\t '{print $1}' | xargs)
+	label=$(echo "${line}" | awk -F\\t '{print $2}' | xargs)
+	[ -n "${DEBUG}" ] && printf 'DEBUG: mode:%s, drv:%s, path:%s\n' "${mode}" "${drv}" "${path}"
+	logfile=$(mktemp --tmpdir -t "mnts-${label}-XXXXX")
+	if udisksctl "${mode}" -b "${drv}" >"${logfile}" 2>&1; then
+		if [ "${mode}" = "mount" ]; then
+			op=plus
+			_op="+"
+		else
+			op=remove
+			_op="-"
+		fi
+		notify-send -i /usr/share/icons/mdi/scalable/harddisk-${op}.svg -a mnts mnts "$(cat "${logfile}")"
+		printf "%s\t%s\t%s\n" "${_op}" "${drv}" "${path}"
+		[ -n "${open}" ] && [ "${mode}" = "mount" ] && swx -t -- vifm "${path}"
 	else
-		notify-send -a mnts -u critical -t 30000 mnts "${mode}ing ${label} failed:\n<i>${_outp}</i>"
+		notify-send -i /usr/share/icons/mdi/scalable/harddisk.svg -a mnts -u critical -t 30000 mnts "${mode}ing ${label} failed:\n<i>$(cat "${logfile}")</i>"
 	fi
+	rm "${logfile}"
+}
+
+handle() {
+	while read -r line; do
+		mount "${line}" &
+	done
 }
 
 lsblk -fJ \
diff --git a/bin/pomo b/bin/pomo
index b14ffed..c26729e 100755
--- a/bin/pomo
+++ b/bin/pomo
@@ -1,8 +1,19 @@
 #!/bin/sh
 
-WORK=15m
-BREAK=3m
-PAUSE=5m
+[ "$1" = "-h" ] || [ "$1" = "--help" ] && {
+	echo "usage: $(basename $(readlink -f -- "$0")) [-1] WORK BREAK PAUSE"
+	echo
+	echo "  WORK  (default: 15m)"
+	echo "  BREAK (default: 3m)"
+	echo "  PAUSE (default: 5m)"
+	echo
+	exit 0
+}
+
+[ "$1" = "-1" ] && _once=1 && shift
+WORK=${1:-15}m
+BREAK=${2:-3}m
+PAUSE=${3:-5}m
 
 WORK_BELL=/home/robert/Music/sounds/215415__unfa__ping.wav
 BREAK_BELL=/home/robert/Music/sounds/320655__rhodesmas__level-up-01.wav
@@ -10,43 +21,52 @@ PAUSE_BELL=/home/robert/Music/sounds/32304__acclivity__shipsbell.wav
 END_BELL=/home/robert/Music/sounds/105228__chimerical__old-fashion-radio-jingle-2.wav
 
 log() {
-    echo "$@"
-    notify-send -a pomo "pomo" "$@"
+	echo "$@"
+	notify-send -i /usr/share/icons/mdi/scalable/progress-clock.svg -a pomo "pomo" "$@"
 }
 play() {
-    pw-play --media-role=Event --volume=0.4 "$@"
+	paplay --property=media.role=event --volume=0.2 "$@"
 }
 nag() {
-    swaynag --type=warning --dismiss-button=ok "$@"
+	swaynag --type=warning --dismiss-button=ok "$@"
 }
 
 _work() {
-    log "[$(date +%k:%M:%S)] work for ${WORK}"
-    play "${WORK_BELL}" 1>&2 2>/dev/null &
-    sleep "${WORK}"
+	play "${WORK_BELL}" 1>&2 2>/dev/null &
+	log "[$(date +%k:%M:%S)] work for ${WORK}"
+	sleep "${WORK}"
 }
 
 _break() {
-    log "[$(date +%k:%M:%S)] take a ${BREAK} break"
-    play "${BREAK_BELL}" 1>&2 2>/dev/null &
-    nag -m "pomo: take a ${BREAK} break"
-    sleep "${BREAK}"
+	play "${BREAK_BELL}" 1>&2 2>/dev/null &
+	nag -m "pomo: take a ${BREAK} break"
+	log "[$(date +%k:%M:%S)] take a ${BREAK} break"
+	sleep "${BREAK}"
 }
 
 _pause() {
-    log "[$(date +%k:%M:%S)] pause for ${PAUSE}"
-    play "${PAUSE_BELL}" 1>&2 2>/dev/null &
-    nag -m "pomo: pause for ${PAUSE}"
-    sleep "${PAUSE}"
+	play "${PAUSE_BELL}" 1>&2 2>/dev/null &
+	nag -m "pomo: pause for ${PAUSE}"
+	log "[$(date +%k:%M:%S)] pause for ${PAUSE}"
+	sleep "${PAUSE}"
+}
+
+_end() {
+	aplay "${END_BELL}" 1>&2 2>/dev/null &
+	nag -m "[$(date +%k:%M:%S)] the end"
 }
 
 trap '{ printf "\n"; log "[$(date +%k:%M:%S)] goodbye"; exit 0; }' INT
 
+if [ -n "${_once}" ]; then
+	_work && _end
+	exit 0
+fi
+
 while true; do
-    _work && _break
-    _work && _break
-    _work && _break
-    _work && _pause
-    aplay "${END_BELL}" 1>&2 2>/dev/null &
-    nag -m "[$(date +%k:%M:%S)] the end"
+	_work && _break
+	_work && _break
+	_work && _break
+	_work && _pause
+	_end
 done
diff --git a/bin/rm b/bin/rm
index f3a604c..b164647 100755
--- a/bin/rm
+++ b/bin/rm
@@ -1,8 +1,8 @@
 #!/bin/bash
 
-if [ ! -t 1 ]; then
+if [ ! -t 1 ] || [ -n "$UNSAFE_RM" ]; then
 	# shellcheck disable=SC2068
-	exec /bin/rm $@
+	exec /bin/rm "$@"
 fi
 
 # stop me from being stupid
@@ -20,7 +20,7 @@ done
 
 if [ -n "${force}" ] && [ -n "${recurse}" ]; then
 	tput setaf 1
-	ls -l "${!OPTIND}" >&2
+	exa --color=never -a "${!OPTIND}" >&2
 	echo "!"
 	echo "! recursive force not allowed" >&2
 	echo "!"
@@ -30,9 +30,9 @@ fi
 
 if [ -n "${recurse}" ]; then
 	tput setaf 1
-	ls -l "${!OPTIND}" >&2
+	exa --color=never -a "${!OPTIND}" >&2
 	tput sgr0
 fi
 
 # shellcheck disable=SC2068
-exec /bin/rm -I --one-file-system $@
+exec /bin/rm -I --one-file-system "$@"
diff --git a/bin/safe-env b/bin/safe-env
new file mode 100755
index 0000000..2056503
--- /dev/null
+++ b/bin/safe-env
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+env | grep -v \
+	-e '^[} _]' \
+	-e BASH_FUNC \
+	-e SHELL \
+	-e XDG \
+	-e JAVA -e JDK \
+	-e GOPATH \
+	-e HOME -e MANPATH -e HOME -e PWD -e USER -e PATH
diff --git a/bin/semver b/bin/semver
index 876ae65..4072662 100755
--- a/bin/semver
+++ b/bin/semver
@@ -18,7 +18,6 @@ simple_tag = True
 force = False
 changelog = False
 
-
 def usage():
     print(f"usage: {sys.argv[0]} OPTIONS COMMANDS")
     print("""
@@ -28,7 +27,7 @@ OPTIONS:
 \t-h\tshow this help
 \t-a\tannotate tag
 \t-f\toverwrite existing tag
-\t-c\tdon't write changelog
+\t-c\twrite CHANGELOG
 
 COMMANDS:
 \tpatch
@@ -61,40 +60,49 @@ if any(item in ["-h", "--help", "help"] for item in argv):
 
 if subprocess.run(["git", "branch", "--show-current"],
                   stdout=subprocess.PIPE).stdout.decode().strip() != "master":
-    print("WARNING! Not on the master branch.")
+    print("WARNING: not on master branch.")
 
 subprocess.run(["git", "pull", "--rebase"])
 
-p = subprocess.run(["git", "describe", "--abbrev=0", "--tags"],
-                   stdout=subprocess.PIPE)
-describe = p.stdout.decode().strip()
+describe = subprocess.run(["git", "describe", "--abbrev=0", "--tags"],
+                   stdout=subprocess.PIPE).stdout.decode().strip()
 # TODO also ask to re-append the trailing portion.... e.g. -rev1
 
-prepend = ''
-if describe[0] == 'v':
-    describe = describe[1:]
-    prepend = 'v'
+prepend = None
 
-old_version = describe.split("-")[0].split(".")
-if len(old_version) == 2:
-    [major, minor] = old_version
-    [major, minor] = map(int, [major, minor])
-    patch = 0
-elif len(old_version) == 3:
-    [major, minor, patch] = old_version
-    [major, minor, patch] = map(int, [major, minor, patch])
+if describe:
+    if describe[0] == 'v':
+        describe = describe[1:]
+        prepend = 'v'
 
-else:
-    print(
-        f"WARNING: git describe returned invalid semver version: \"{describe}\""
-    )
-    sys.exit(1)
-    # old_version = [0, 0, 0]
+    old_version = describe.split("-")[0].split(".")
+    if len(old_version) == 2:
+        [major, minor] = old_version
+        [major, minor] = map(int, [major, minor])
+        patch = 0
+    elif len(old_version) == 3:
+        [major, minor, patch] = old_version
+        [major, minor, patch] = map(int, [major, minor, patch])
 
-p = subprocess.run(
-    ["git", "shortlog", "--no-merges", f"{prepend}{describe}..HEAD"],
-    stdout=subprocess.PIPE)
-shortlog = p.stdout.decode()
+    else:
+        print(
+            f"WARNING: git describe returned invalid semver version: \"{describe}\""
+        )
+        sys.exit(1)
+
+    old_version_revision = subprocess.run(["git", "log", "--pretty=%H", "-1", describe],
+                                        stdout=subprocess.PIPE).stdout.decode().strip()
+    head_revision = subprocess.run(["git", "log", "--pretty=%H", "-1", "HEAD"],
+                                        stdout=subprocess.PIPE).stdout.decode().strip()
+    if old_version_revision == head_revision:
+        print(f"WARNING: HEAD already tagged as {describe}")
+        if not force:
+            sys.exit(0)
+
+if not simple_tag or changelog:
+    shortlog = subprocess.run(
+        ["git", "shortlog", "--no-merges", f"{prepend}{describe}..HEAD"],
+        stdout=subprocess.PIPE).stdout.decode()
 
 new_version = None
 
@@ -109,28 +117,20 @@ elif argv[0] == "major":
 else:
     new_version = argv[0]
 
-if new_version is None:
+if not new_version:
     if len(old_version) == 2 and patch == 0:
         new_version = f"{major}.{minor}"
     else:
         new_version = f"{major}.{minor}.{patch}"
 
-if prepend != '':
+if prepend and (new_version[0] != 'v'):
     new_version = prepend + new_version
 
-p = None
 if os.path.exists("contrib/_incr_version"):
     p = subprocess.run(["contrib/_incr_version", describe, new_version])
-elif os.path.exists(".git/_incr_version"):
-    print("Warning: _incr_version found at legacy path")
-    p = subprocess.run([".git/_incr_version", describe, new_version])
-else:
-    print("Warning: no _incr_version script. " +
-          "Does this project have any specific release requirements?")
-
-if p and p.returncode != 0:
-    print("Error: _incr_version returned nonzero exit code")
-    sys.exit(1)
+    if p.returncode != 0:
+        print("Error: _incr_version returned nonzero exit code")
+        sys.exit(1)
 
 args = ["git", "tag"]
 if force:
@@ -148,6 +148,8 @@ else:
     args.extend(["-m", new_version, new_version])
     subprocess.run(args)
 
+print(f"Tagged {new_version}")
+
 if changelog:
     import time
     now = time.strftime("%Y-%m-%d", time.localtime())
@@ -158,3 +160,4 @@ if changelog:
         f.write(shortlog)
         f.write(data)
         f.flush()
+    print("Generated CHANGELOG")
diff --git a/bin/soundswitch b/bin/soundswitch
index acd287a..e39a292 100755
--- a/bin/soundswitch
+++ b/bin/soundswitch
@@ -1,14 +1,17 @@
 #!/bin/sh
 #
 # DESCRIPTION:
-#  dmenu-based pulseaudio helper
+#  dmenu-based audio helper
 #
 
+# TODO
+# - auto-select the currently active profile
+
 set -e
 [ -n "$DEBUG" ] && set -x
 
 # check requirements
-for tool in jq swq pactl perl; do
+for tool in jq swq pactl perl pw-dump; do
 	if ! command -v "$tool" >/dev/null; then
 		printf "%s: %s not installed but required\n" "$(basename "$0")" "$tool" >&2
 		exit 1
@@ -17,9 +20,9 @@ done
 
 DEFAULT_PROFILE="output:analog-stereo+input:analog-stereo"
 HDMI_PROFILE="output:hdmi-stereo"
-export MENU_PROMPT="soundswitch"
 
 _menu() {
+	export MENU_PROMPT="soundswitch"
 	menu -l 8
 }
 
@@ -49,24 +52,44 @@ usage() {
 	exit 2
 }
 _args="$*"
-while getopts hIOgfpHRmMvV name; do
+while getopts hIOgfpHRmMvVB name; do
 	case ${name} in
-	g) 	mode=global ;;
-	f) 	mode=focused ;;
-	p) 	mode=profile ;;
-	H) 	mode=hdmi ;;
-	R)	mode=reset ;;
-	O)	output=1 ;;
-	I)	input=1 ;;
-	# quick actions, to make move to pipewire easier
-	m)	mode=none ; output=1 ; mute=1 ;;
-	M)	mode=none ; input=1 ; mute=1 ;;
-	v)	mode=none ; output=1 ; volume_down=1 ;;
-	V)	mode=none ; output=1 ; volume_up=1 ;;
-	h|?) 	echo "$*" ; usage ;;
+		g) mode=global ;;
+		f) mode=focused ;;
+		p) mode=profile output=1 input=1 ;;
+		H) mode=hdmi output=1 input=1 ;;
+		R) mode=reset ;;
+		B) mode=mute output=1 input=1 ;;
+		O) output=1 ;;
+		I) input=1 ;;
+		# quick actions, to make move to pipewire easier
+		m)
+			mode=none
+			output=1
+			mute=1
+			;;
+		M)
+			mode=none
+			input=1
+			mute=1
+			;;
+		v)
+			mode=none
+			output=1
+			volume_down=1
+			;;
+		V)
+			mode=none
+			output=1
+			volume_up=1
+			;;
+		h | ?)
+			echo "$*"
+			usage
+			;;
 	esac
 done
-shift $((OPTIND-1))
+shift $((OPTIND - 1))
 
 # ask mode
 _mode_global="󰣨 global"
@@ -74,17 +97,20 @@ _mode_focused="󰻿 focused"
 _mode_profile_card="󱂇 card profile"
 _mode_profile_hdmi="󰡁 hdmi profile"
 _mode_profile_reset="󰕍 reset profile"
+_mode_mute="󰂛 mute"
 if [ -z "$mode" ]; then
-	printf "$_mode_global\n$_mode_focused\n$_mode_profile_card\n$_mode_profile_hdmi\n$_mode_profile_reset\n" | _menu | {
+	printf "$_mode_global\n$_mode_focused\n$_mode_profile_card\n$_mode_profile_hdmi\n$_mode_profile_reset\n$_mode_mute\n" | _menu | {
 		read -r line
 		case ${line} in
-		$_mode_global)        exec "$0" -g "$*" ;;
-		$_mode_focused)       exec "$0" -f "$*" ;;
-		$_mode_profile_card)  exec "$0" -p "$*" ;;
-		$_mode_profile_hdmi)  exec "$0" -H "$*" ;;
-		$_mode_profile_reset) exec "$0" -R "$*" ;;
-		*)
-			usage ;;
+			$_mode_global) exec "$0" -g "$*" ;;
+			$_mode_focused) exec "$0" -f "$*" ;;
+			$_mode_profile_card) exec "$0" -p "$*" ;;
+			$_mode_profile_hdmi) exec "$0" -H "$*" ;;
+			$_mode_profile_reset) exec "$0" -R "$*" ;;
+			$_mode_mute) exec "$0" -B "$*" ;;
+			*)
+				usage
+				;;
 		esac
 	}
 	exit $?
@@ -98,9 +124,9 @@ if [ -n "$mode" ]; then
 		printf "$_mode_output\n$_mode_input\n" | _menu | {
 			read -r line
 			case ${line} in
-			$_mode_output) exec "$0" -O $_args ;;
-			$_mode_input)  exec "$0" -I $_args ;;
-			*)      usage ;;
+				$_mode_output) exec "$0" -O $_args ;;
+				$_mode_input) exec "$0" -I $_args ;;
+				*) usage ;;
 			esac
 		}
 	fi
@@ -112,110 +138,123 @@ fi
 [ -n "$volume_down" ] && exec pactl set-sink-volume '@DEFAULT_SINK@' '-5%'
 [ -n "$volume_up" ] && exec pactl set-sink-volume '@DEFAULT_SINK@' '+5%'
 
-[ -n "$output" ] && keyword="sink"   && what="sink-input"
-[ -n "$input" ]  && keyword="source" && what="source-output"
+[ -n "$output" ] && keyword="sink" && what="sink-input"
+[ -n "$input" ] && keyword="source" && what="source-output"
 
 # returns the sink_id
-selects()
-{
-	pactl list short "${1}s" | cut -f2 | menu
+selects() {
+	pactl list short "${1}s" | cut -f2 | _menu
 }
-notif()
-{
+notif() {
 	topic=$1
 	mesg=$2
 	urgency=normal
 	[ -n "$3" ] && urgency=critical
-	notify-send -u ${urgency} -a soundswitch "soundswitch: ${topic}" "${mesg}"
+	notify-send -i /usr/share/icons/mdi/scalable/cast-audio.svg -u ${urgency} -a soundswitch "soundswitch: ${topic}" "${mesg}"
 }
 
 case ${mode} in
-global)
-	target=$(selects "${keyword}")
-	if [ -z "${target}" ]; then
-		notif "focused" "no target ${what}" err
-		exit 1
-	fi
+	global)
+		target=$(selects "${keyword}")
+		if [ -z "${target}" ]; then
+			notif "focused" "no target ${what}" err
+			exit 1
+		fi
 
-	notif "global" "switch to <i>${target}</i>"
-	# set new default
-	pactl "set-default-${keyword}" "${target}"
-	# move all attached to the new target
-	pactl list "${what}s" short | cut -f1 \
-		| xargs -i pactl "move-${what}" "{}" "${target}"
-	;;
-
-focused)
-	# parse pid of currently focused container
-	# then get the sink-inputs for it and parse the sink-input-id
-	focused_binary=$(swq current con | jq -r '.pid' | xargs ps -o comm=)
-	what_id=$(printf "%s\n" "${focused_binary}" \
-		| xargs pactl list "${what}s" \
+		notif "global" "switch to <i>${target}</i>"
+		# set new default
+		pactl "set-default-${keyword}" "${target}"
+		# move all attached to the new target
+		pactl list "${what}s" short | cut -f1 \
+			| xargs -i pactl "move-${what}" "{}" "${target}"
+		;;
+
+	focused)
+		# parse pid of currently focused container
+		# then get the sink-inputs for it and parse the sink-input-id
+		focused_binary=$(swq current con | jq -r '.pid' | xargs ps -o comm=)
+		what_id=$(printf "%s\n" "${focused_binary}" \
+			| xargs pactl list "${what}s" \
 			| tr '\n' '\r' \
 			| perl -pe 's/[^\r]*index: ([0-9]+).+?application\.process\.binary = "([^\r]+)"\r.+?(?=index:|$)/\2=\1\r/g' \
 			| tr '\r' '\n' \
 			| grep "^{}" \
-		| cut -d'=' -f2)
-	if [ -z "${what_id}" ]; then
-		notif "focused" "no ${what}" err
-		exit 1
-	fi
+			| cut -d'=' -f2)
+		if [ -z "${what_id}" ]; then
+			notif "focused" "no ${what}" err
+			exit 1
+		fi
 
-	# select where to switch to
-	target=$(selects "${keyword}")
-	if [ -z "${target}" ]; then
-		exit 1
-	fi
+		# select where to switch to
+		target=$(selects "${keyword}")
+		if [ -z "${target}" ]; then
+			exit 1
+		fi
 
-	notif "${focused_binary}" "switch to <i>${target}</i>"
-        pactl move-sink-input "${what_id}" "${target}"
-	;;
+		notif "${focused_binary}" "switch to <i>${target}</i>"
+		pactl move-sink-input "${what_id}" "${target}"
+		;;
 
-profile)
-	target_card=$(selects "card")
-	if [ -z "${target_card}" ]; then
-		exit 1
-	fi
+	profile)
+		target_card=$(selects "card")
+		if [ -z "${target_card}" ]; then
+			exit 1
+		fi
 
-	# [^\r]#([0-9]+).*?Name: ([^\r]+).*?Profiles:\r(.*?)Active.*?(?=Card #|$)
-	target_profile=$(pactl list cards \
-		| tr '\n' '\r' \
-		| perl -pe 's/[^\r]#([0-9]+).*?Name: ([^\r]+).*?Profiles:\r(.*?)Active.*?(?=Card #|$)/\2~\3`/g' \
-		| tr -d '\t' \
-		| tr '`' '\n' \
-		| grep "${target_card}" \
-		| cut -d'~' -f2- \
-		| tr '\r' '\n' \
-		| xargs -L1 \
-		| _menu \
-		| perl -pe 's/^\s*([^\s]+):\s.*$/\1/')
-	if [ -z "${target_profile}" ]; then
-		exit 1
-	fi
+		# [^\r]#([0-9]+).*?Name: ([^\r]+).*?Profiles:\r(.*?)Active.*?(?=Card #|$)
+		target_profile=$(pactl list cards \
+			| tr '\n' '\r' \
+			| perl -pe 's/[^\r]#([0-9]+).*?Name: ([^\r]+).*?Profiles:\r(.*?)Active.*?(?=Card #|$)/\2~\3`/g' \
+			| tr -d '\t' \
+			| tr '`' '\n' \
+			| grep "${target_card}" \
+			| cut -d'~' -f2- \
+			| tr '\r' '\n' \
+			| xargs -L1 \
+			| _menu \
+			| perl -pe 's/^\s*([^\s]+):\s.*$/\1/')
+		if [ -z "${target_profile}" ]; then
+			exit 1
+		fi
 
-	notif "profile" "switch <i>${target_card}</i> to ${target_profile} profile"
-	pactl set-card-profile "${target_card}" "${target_profile}"
-	;;
+		notif "profile" "switch <i>${target_card}</i> to ${target_profile} profile"
+		pactl set-card-profile "${target_card}" "${target_profile}"
+		;;
 
-hdmi)
-	target_card=$(selects "card")
-	if [ -z "${target_card}" ]; then
-		exit 1
-	fi
+	hdmi)
+		target_card=$(selects "card")
+		if [ -z "${target_card}" ]; then
+			exit 1
+		fi
 
-	notif "hdmi" "switch <i>${target_card}</i> to HDMI profile"
-	pactl set-card-profile "${target_card}" ${HDMI_PROFILE}
-	exit $?
-	;;
+		notif "hdmi" "switch <i>${target_card}</i> to HDMI profile"
+		pactl set-card-profile "${target_card}" ${HDMI_PROFILE}
+		exit $?
+		;;
 
-reset)
-	target_card=$(selects "card")
-	if [ -z "${target_card}" ]; then
-		exit 1
-	fi
+	reset)
+		target_card=$(selects "card")
+		if [ -z "${target_card}" ]; then
+			exit 1
+		fi
+
+		notif "reset" "switch <i>${target_card}</i> to default profile"
+		pactl set-card-profile "${target_card}" ${DEFAULT_PROFILE}
+		;;
+
+	mute)
+		_data=$(pw-dump -N | jq -r '.. | select(.type?=="PipeWire:Interface:Node" and .info.props."media.class"?=="Stream/Output/Audio" and .info.props."stream.is-live"?==true) | @text')
+		_id=$(printf "${_data}" | jq -r '. | [ .info.props."application.name", .info.props."application.process.id", .id ] | @tsv' \
+			| awk '{ system("swaymsg -t get_tree | jq -r \".. | select(.pid?==" $2 ") | .name\" | grep -v Picture-in-Picture | tr -d \"\\n\""); printf " [%s]", $3 }' \
+			| _menu \
+			| perl -pe 's/^.*\[([0-9]+)\]$/\1/')
+		if [ -z "${_id}" ]; then
+			exit 1
+		fi
 
-	notif "reset" "switch <i>${target_card}</i> to default profile"
-	pactl set-card-profile "${target_card}" ${DEFAULT_PROFILE}
-	;;
+		_oid=$(printf "${_data}" | jq -r --argjson id "${_id}" '.. | select(.id?==$id) | .info.props."object.id"')
+		_target_state=$(printf "${_data}" | jq -r --argjson id "${_id}" '.. | select(.id?==$id) | .info.params.Props[0] | .mute = (.mute | not) | @text')
+		pw-cli set-param "${_oid}" Props "${_target_state}"
+		;;
 
 esac
diff --git a/bin/swx b/bin/swx
index 01003fc..1ba942b 100755
--- a/bin/swx
+++ b/bin/swx
@@ -30,55 +30,59 @@ mark=
 term_title=
 while getopts b:w:o:g:I:r:stfF:aSM:T:h name; do
 	case "$name" in
-	b)	# border
-		border="${OPTARG}" ;;
-	w)	# move to workspace
-		wspace="${OPTARG}" ;;
-	o)	# move to output
-		output="${OPTARG}" ;;
-	g)	# geometry
-		geometry="${OPTARG}" ;;
-	t)	# termify
-		termify=1 ;;
-	r)	# rotate
-		if ! command -v cage >/dev/null; then
-			printf "%s: cage not installed but required to rotate containers" "$0" >&2
-			exit 1
-		fi
-		rotate="${OPTARG}" ;;
-	s)	# set container to sticky
-		sticky=1 ;;
-	f)      # set container to float
-		floating=1 ;;
-	F)
-		floating="${OPTARG}" ;;
-	a)	# auto-tiling enabled
-		autotile=1 ;;
-	S)	# launch into scratchpad
-		into_scratchpad=1 ;;
-	M)	# mark
-		mark="${OPTARG}" ;;
-	T)	# set terminal title
-		term_title="${OPTARG}" ;;
+		b) # border
+			border="${OPTARG}" ;;
+		w) # move to workspace
+			wspace="${OPTARG}" ;;
+		o) # move to output
+			output="${OPTARG}" ;;
+		g) # geometry
+			geometry="${OPTARG}" ;;
+		t) # termify
+			termify=1 ;;
+		r) # rotate
+			if ! command -v cage >/dev/null; then
+				printf "%s: cage not installed but required to rotate containers" "$0" >&2
+				exit 1
+			fi
+			rotate="${OPTARG}"
+			;;
+		s) # set container to sticky
+			sticky=1 ;;
+		f) # set container to float
+			floating=1 ;;
+		F)
+			floating="${OPTARG}"
+			;;
+		a) # auto-tiling enabled
+			autotile=1 ;;
+		S) # launch into scratchpad
+			into_scratchpad=1 ;;
+		M) # mark
+			mark="${OPTARG}" ;;
+		T) # set terminal title
+			term_title="${OPTARG}" ;;
 
-	h|?)	printf "usage: %s [-bwogIrsfoSMT] COMMAND\n" "$(basename "$0")"
-		printf "\n"
-		printf "  -t \t\trun COMMAND in \$TERM\n"
-		printf "  -b BORDER \tset border property\n"
-		printf "  -w WORKSPACE \tmove to workspace\n"
-		printf "  -o OUTPUT \tmove to output\n"
-		printf "  -g GEOMETRY \tresize/position window\n"
-		printf "  -r ROTATION \trotate the container, requires cage(1)\n"
-		printf "  -s \t\tmake COMMAND a sticky window, appearing on all workspaces\n"
-		printf "  -f \t\tmake COMMAND a floating window\n"
-		printf "  -F OPTIONS\tmake COMMAND a floating window\n\t\tsee centercon(1) for possible options\n"
-		printf "  -a \t\tauto-tiling; always open COMMAND on the shorter edge of\n\t\tthe current container\n"
-		printf "  -S \t\tmove the container into the scratchpad\n"
-		printf "  -M MARK\t\set MARK on the container\n"
-		printf "  -T TERM_TITLE\t\set a title on the terminal (only makes sense with -t)\n"
-		printf "\n"
-		printf "See sway(5) for possible options for BORDER, WORKSPACE, OUTPUT and GEOMETRY.\n"
-		exit 2 ;;
+		h | ?)
+			printf "usage: %s [-bwogIrsfoSMT] COMMAND\n" "$(basename "$0")"
+			printf "\n"
+			printf "  -t \t\trun COMMAND in \$TERM\n"
+			printf "  -b BORDER \tset border property\n"
+			printf "  -w WORKSPACE \tmove to workspace\n"
+			printf "  -o OUTPUT \tmove to output\n"
+			printf "  -g GEOMETRY \tresize/position window\n"
+			printf "  -r ROTATION \trotate the container, requires cage(1)\n"
+			printf "  -s \t\tmake COMMAND a sticky window, appearing on all workspaces\n"
+			printf "  -f \t\tmake COMMAND a floating window\n"
+			printf "  -F OPTIONS\tmake COMMAND a floating window\n\t\tsee centercon(1) for possible options\n"
+			printf "  -a \t\tauto-tiling; always open COMMAND on the shorter edge of\n\t\tthe current container\n"
+			printf "  -S \t\tmove the container into the scratchpad\n"
+			printf "  -M MARK\t\set MARK on the container\n"
+			printf "  -T TERM_TITLE\t\set a title on the terminal (only makes sense with -t)\n"
+			printf "\n"
+			printf "See sway(5) for possible options for BORDER, WORKSPACE, OUTPUT and GEOMETRY.\n"
+			exit 2
+			;;
 	esac
 	# block until we have a con_id matching on pid of the spawned process
 	patience=1
@@ -93,15 +97,19 @@ shift
 
 command="${arg0} ${*}"
 
-# needs to be termified?
+# needs to be termified
 # spawn a $TERM instance and exec that
 if [ -n "${termify}" ]; then
-	case ${TERM:-foot} in
-	alacritty) term_args="--title=${term_title:-${arg0}} --class=${term_title:-${arg0}} --command" ;;
-	foot)      term_args="--title=${term_title:-${arg0}} --app-id=${term_title:-${arg0}} --" ;;
-	*)         term_args="" ;;
-	esac
-	command="${TERM} ${term_args} ${command}"
+	if [ -n "${floating}" ]; then
+		term_args="floaterm"
+	else
+		term_args="foot"
+	fi
+	term_args="${term_args} --title=${term_title:-${arg0}} --app-id=${term_title:-${arg0}}"
+	if [ -n "${SWX_TERM_ARGS}" ]; then
+		term_args="${term_args} ${SWX_TERM_ARGS}"
+	fi
+	command="${term_args} -- ${command}"
 fi
 
 # needs to be rotated
@@ -109,7 +117,7 @@ fi
 if [ -n "${rotate}" ]; then
 	# figure out how many "-r" flags we need
 	rotate_args=""
-	while [ $(( (rotate / 90) >= 1 )) = 1 ]; do
+	while [ $(((rotate / 90) >= 1)) = 1 ]; do
 		rotate_args="${rotate_args} -r"
 		rotate=$((rotate - 90))
 	done
@@ -124,21 +132,24 @@ fi
 
 if [ -n "${autotile}" ]; then
 	layout=$(swq current con | jq -r '.layout')
-	 # layout=$(swq current workspace | jq -r '.. | select(.type?=="con" and .name?==null) | .layout')
+	# layout=$(swq current workspace | jq -r '.. | select(.type?=="con" and .name?==null) | .layout')
 	# notify-send swx "${layout}"
 	case "${layout}" in
-	split*)
-		if [ "$(swq current con | jq '.rect | .width > .height')" = "true" ]; then
-			swaymsg splith
-		else
-			swaymsg splitv
-		fi
-		;;
+		split*)
+			if [ "$(swq current con | jq '.rect | .width > .height')" = "true" ]; then
+				swaymsg splith
+			else
+				swaymsg splitv
+			fi
+			;;
 	esac
 fi
 
 swaymsg exec -- "${command}" >/dev/null
-[ $? ] || { printf "error: failed to spawn \"${command}\"\n"; exit 1; }
+[ $? ] || {
+	printf "error: failed to spawn \"${command}\"\n"
+	exit 1
+}
 [ -z "$patience" ] && exit 0
 # waiting for container to appear
 con_id="$(swaymsg -t subscribe '["window"]' | jq -s '.[]|select(.change=="new")|.container.id')"
diff --git a/bin/trash b/bin/trash
index b3c534d..5a040cb 100755
--- a/bin/trash
+++ b/bin/trash
@@ -3,72 +3,86 @@
 set -e
 [ -n "$DEBUG" ] && set -x
 
+rmbin=/bin/rm
+
 TRASH="$HOME/.trash"
 if [ ! -d "$TRASH" ]; then
-    mkdir -p "$TRASH"
+	mkdir -p "$TRASH"
 fi
 
+ls_trash() {
+	# shellcheck disable=SC2068
+	fd -H . "${TRASH:?}" $@
+}
+
 empty_trash() {
-    if [ ! -d "$TRASH" ] || [ -z "$(ls "$TRASH")" ]; then
-        exit 0
-    fi
-    echo " +++ trash +++"
-    ls "$TRASH"
-    printf " +++ take it out? [yN] "; read -r yn
-    if [ "$yn" = "y" ]; then
-        printf ' +++ removing %s/* ' "$TRASH"
-        rm -rf "${TRASH:?}/*"
-        echo 'done.'
-    fi
+	if [ ! -d "$TRASH" ] || [ -z "$(ls_trash)" ]; then
+		exit 0
+	fi
+	echo " +++ trash +++"
+	ls_trash
+	read -r yn
+	if [ "$yn" = "y" ]; then
+		printf ' +++ removing everything under %s (%d)... ' "$TRASH" "$(fd . "${TRASH:?}" | wc -l)"
+		ls_trash -x $rmbin -r
+		echo 'done.'
+	fi
 }
 
 install_auto_empty() {
-    case "$SHELL" in
-        *zsh) logoutfile="$ZDOTDIR/.zlogout" ;;
-        *bash) logoutfile="$HOME/.bash_logout" ;;
-        *)
-            echo "unknown shell $SHELL" 1>&2; exit 1 ;;
-    esac
-    cat > "$logoutfile" <<EOF
+	case "$SHELL" in
+		*zsh) logoutfile="$ZDOTDIR/.zlogout" ;;
+		*bash) logoutfile="$HOME/.bash_logout" ;;
+		*)
+			echo "unknown shell $SHELL" 1>&2
+			exit 1
+			;;
+	esac
+	cat >"$logoutfile" <<EOF
 # auto-empty-trash routine of $0
 yes | $0 --empty
 EOF
 }
 
 ask() {
-    r=$(printf 'yes\nno\n' | MENU_PROMPT="󰩹 delete $1" menu)
-    [ "${r}" = "yes" ] && return 0
-    return 1
+	r=$(printf 'yes\nno\n' | MENU_PROMPT="󰩹 delete $1" menu)
+	[ "${r}" = "yes" ] && return 0
+	return 1
 }
 
 interactive=${TRASH_ASK:-}
 trashrm=${TRASH_RM:-}
 
 case "$1" in
-    -e|--empty)
-        empty_trash
-        exit 0 ;;
-    --install)
-        install_auto_empty
-        exit 0 ;;
-    -i)
-        interactive=1
-        shift ;;
-    -h|--help)
-        echo "usage: trash [-e|--empty] FILE..."
-        exit 1 ;;
+	-e | --empty)
+		empty_trash
+		exit 0
+		;;
+	--install)
+		install_auto_empty
+		exit 0
+		;;
+	-i)
+		interactive=1
+		shift
+		;;
+	-h | --help)
+		echo "usage: trash [-e|--empty] FILE..."
+		exit 1
+		;;
 esac
 
 # main
 while test $# -gt 0; do
-    f="$1" ; shift
+	f="$1"
+	shift
 
-    [ -n "${interactive}" ] && { ask "$f" || continue; }
+	[ -n "${interactive}" ] && { ask "$f" || continue; }
 
-    echo "+ $(basename "$f")"
-    if [ -z "${trashrm}" ]; then
-        rm "$f"
-    else
-        mv "$f" "$TRASH/"
-    fi
+	echo "+ $(basename "$f")"
+	if [ -n "${trashrm}" ]; then
+		[ -d "$f" ] && $rmbin -r "$f" || $rmbin "$f"
+	else
+		mv "$f" "$TRASH/"
+	fi
 done
diff --git a/bin/xdg-exec b/bin/xdg-exec
index e8f3ccb..32801de 100755
--- a/bin/xdg-exec
+++ b/bin/xdg-exec
@@ -21,4 +21,4 @@ bin=$(awk '
 /^TryExec=/{split($0,l,"="); if(not system("command -v l[2]")){exit 1}}
 /^Exec=/{printf gensub(/Exec=([^%]+).*$/, "\\1", "")}
 ' "$desktop" | sed 's/%.$//')
-exec ${bin} $@
+exec ${bin} "$@"