summary refs log tree commit diff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/2av184
-rwxr-xr-xbin/audm84
-rwxr-xr-xbin/blum100
-rwxr-xr-xbin/dots28
-rwxr-xr-xbin/dskm26
-rwxr-xr-xbin/genup91
-rwxr-xr-xbin/hr22
-rw-r--r--bin/libfuzl.sh114
-rwxr-xr-xbin/mailsync148
-rwxr-xr-xbin/mktoes27
l---------bin/msmtp-queue1
-rwxr-xr-xbin/msmtp-queue-stat17
l---------bin/msmtpq1
-rwxr-xr-xbin/netm1560
-rwxr-xr-xbin/pasm71
-rwxr-xr-xbin/play-mpv112
-rwxr-xr-xbin/play-music23
-rwxr-xr-xbin/rsyncez19
-rwxr-xr-xbin/susm30
-rwxr-xr-xbin/svcm108
-rwxr-xr-xbin/todo216
-rwxr-xr-xbin/urlopen89
-rwxr-xr-xbin/vpnm124
23 files changed, 3095 insertions, 0 deletions
diff --git a/bin/2av1 b/bin/2av1
new file mode 100755
index 0000000..19ace0b
--- /dev/null
+++ b/bin/2av1
@@ -0,0 +1,84 @@
+#!/bin/bash
+set -eu
+[ -n "${DEBUG:-}" ] && set -x
+[ -z "${DEBUG:-}" ] && export SVT_LOG=1
+
+BITRATE=${BITRATE:-128000}
+EXT=${EXT:-webm}
+
+case "${VCODEC:-av1}" in
+vp9) vcodec_args=(-c:v libvpx-vp9 -crf "${CRF:-31}" -b:v 0) ;;
+av1) vcodec_args=(-c:v libsvtav1 -crf "${CRF:-30}" -svtav1-params "tune=0:fast-decode=1") ;;
+esac
+for f in "$@"; do
+	f=$(readlink -e "$f")
+	op=${OUTDIR:-${f%/*}}
+	on=${f##*/}
+	on=${on%.*}.${EXT}
+	o="${op}/${on}"
+	[ "$f" = "$o" ] && o="${op}/${on%.*}.new.${EXT}"
+
+	hr
+
+	printf '๐Ÿท๏ธ  %s\n' "$on"
+
+	# duration
+	duration=$(ffprobe -v error -of csv=p=0 -show_entries format=duration -sexagesimal "$f")
+
+	# bitrate
+	br=$(ffprobe -v error -of default=noprint_wrappers=1:nokey=1 \
+		-select_streams a \
+		-show_entries stream=bit_rate \
+		"${f}")
+	[ -z "$br" ] && br=$BITRATE
+	[ $br = "N/A" ] && br=$((BITRATE + 1))
+	[ $br -gt $BITRATE ] && br="$BITRATE"
+
+	printf '๐Ÿงพ  duration=%s bitrate=%d\n' \
+		"$duration" \
+		"$br"
+
+	startt=$(date +%s)
+
+	# transcode
+	ffmpeg -hide_banner -loglevel warning -stats \
+		-i "$f" \
+		${OPTS:-} \
+		-c:a libopus \
+		-b:a "$br" \
+		${vcodec_args[@]} \
+		-map 0 \
+		-map_metadata 0 \
+		"${o}"
+
+	set +x
+
+	# # progress
+	# total_frames=$(ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of csv=p=0 -i "${1:?}")
+	# while [ -e "/proc/$ffmpeg_pid" ]; do
+	# 	sleep 2
+	# 	[ -e "$vstats_file" ] || continue
+	# 	current_frames=$(tail -1 "$vstats_file" | grep -o 'frame=[ 0-9]*' | sed 's,frame=[ ]*,,')
+	# 	[ -z "$current_frames" ] && continue
+	# 	current_percent=$((100 * current_frames / total_frames))
+	# 	printf '==> %d/%d %d%% ]]\n\n' "$current_frames" "$total_frames" "$current_percent"
+	# done
+
+	# duration
+	endt=$(date +%s)
+	printf '๐Ÿ  took='
+	date -u -d@"$((endt - startt))" +%H:%M:%S
+
+	# results
+	du -h "$f" "$o"
+	in_size=$(stat -c '%s' "$f")
+	out_size=$(stat -c '%s' "$o")
+	awk -v fs="$in_size" -v os="$out_size" \
+		'BEGIN{p=((100/fs) * (fs-os)); printf("โ™ป๏ธ  %.2f%% reduction\n", p);}'
+
+	if [ $in_size -gt $out_size ]; then
+		trash-put -v "${f}"
+	else
+		trash-put -v "${o}"
+	fi || true
+done
diff --git a/bin/audm b/bin/audm
new file mode 100755
index 0000000..58a5a91
--- /dev/null
+++ b/bin/audm
@@ -0,0 +1,84 @@
+#!/bin/bash
+set -eu
+
+# shellcheck disable=2034
+description="audio/sound manager"
+
+export FUZL_PROMPT=audm
+export FUZL_ICON=๓ฐŸฅ
+. libfuzl.sh
+
+main() {
+	local sink source
+
+	sink=$(inspect_node '@DEFAULT_SINK@' | awk -F\\t '{print $2}')
+	fuzl_action_add 'change_sink' "๓ฐ“ƒ" "$sink"
+
+	source=$(inspect_node '@DEFAULT_SOURCE@' | awk -F\\t '{print $2}')
+	fuzl_action_add 'change_source' "๓ฐฐ" "$source"
+
+	fuzl_action_add 'change_profile' "๓ฐ”ก" "Change profile"
+
+	fuzl_action_menu || exit 1
+}
+
+#
+# helpers
+#
+
+inspect_node() {
+	local nid
+	nid=$(wpctl inspect "${1:?}" | awk '/^id/{match($0,/[0-9]+,/); print substr($0,RSTART,RLENGTH-1);}')
+
+	test -n "$nid" || exit 1 # TODO handle error
+	pw-dump | jq -r --argjson nid "$nid" \
+		'.[]|select(.id==$nid)|.info.props|[.["object.id"],.["node.description"],.["node.name"]]|join("\t")'
+}
+
+list_nodes() {
+	pw-dump | jq -rc --arg class "${1:?}" \
+		'.[]|select((.info.props["media.class"]//"")|test($class))|.info.props|[.["object.id"],.["node.description"],.["node.name"]]|join("\t")'
+}
+
+#
+# actions
+#
+
+audm_change_sink() {
+	local id
+	id=$(list_nodes 'Audio/Sink' |
+		FUZL_PROMPT='sink' fuzl_menu $FUZL_MENU_FILTER --with-nth='{2}   [{3}]' --accept-nth=1)
+
+	test -n "$id" || main
+	fuzl_try -- wpctl set-default "$id"
+}
+
+audm_change_source() {
+	local id
+	id=$(list_nodes 'Audio/Source' |
+		FUZL_PROMPT='source' fuzl_menu $FUZL_MENU_FILTER --with-nth='{2}   [{3}]' --accept-nth=1)
+
+	test -n "$id" || main
+	fuzl_try -- wpctl set-default "$id"
+}
+
+audm_change_profile() {
+	local device did dname profile
+
+	device=$(pw-dump | jq -rc --arg class "Audio/Device" \
+		'.[]|select((.info.props["media.class"]//"")|test($class))|.info.props|[.["object.id"],.["device.description"],.["device.nick"]]|join("\t")' |
+		FUZL_PROMPT='device' fuzl_menu $FUZL_MENU_FILTER --with-nth='{2}  {3}' --accept-nth='{1}\t{2}')
+	test -n "$device" || main
+
+	did=$(printf %s "$device" | awk -F\\t '{print $1}')
+	dname=$(printf %s "$device" | awk -F\\t '{print $2}')
+
+	profile=$(pw-dump | jq -r --argjson did "$did" \
+		'.[]|select(.id==$did)|.info.params.EnumProfile[]|[.index,.description]|join("\t")' |
+		FUZL_PROMPT="profile ($dname)" fuzl_menu $FUZL_MENU_FILTER --with-nth=2 --accept-nth=1)
+	test -n "$profile" || audm_change_profile
+
+	fuzl_try -- wpctl set-profile "$did" "$profile"
+}
+
+main
diff --git a/bin/blum b/bin/blum
new file mode 100755
index 0000000..19c2aef
--- /dev/null
+++ b/bin/blum
@@ -0,0 +1,100 @@
+#!/bin/bash
+set -eu
+
+description="bluetooth manager"
+
+export FUZL_PROMPT=blum
+export FUZL_ICON=๏Š“
+. libfuzl.sh
+
+main() {
+	local connected
+
+	if ! bt_enabled; then
+		fuzl_action_add "enable" "๓ฐท—" "Enable Bluetooth"
+	else
+		connected=$(bt_devices Connected | bt_devices_getname)
+		if [ -n "$connected" ]; then
+			fuzl_action_add "disconnect" "๓ฐ‚ฑ" "Connected to $connected"
+		fi
+		fuzl_action_add "devices" "๓ฐพฐ" "Available devices"
+		fuzl_action_add "scan" "๓ฑ„™" "Scan"
+		fuzl_action_add "repl" "๎ž•" "Run REPL"
+		fuzl_action_add "disable" "๓ฐท˜" "Disable Bluetooth"
+		fuzl_action_add "tether_amagiri" "๓ฐ€ƒ" "Tether amagiri"
+	fi
+
+	fuzl_action_menu || exit 1
+}
+
+bt_enabled() {
+	pgrep -cx bluetoothd >/dev/null 2>&1
+}
+
+bt_devices() {
+	echo "devices" "${1:-}" | bluetoothctl |
+		awk '/^Device /{match($0, /[0-9A-F:]{17}/); mac=substr($0,RSTART,RLENGTH); name=substr($0,RSTART+RLENGTH+1); print mac "\t" name}'
+}
+
+bt_devices_getmac() {
+	awk -F '\t' '{print $1}'
+}
+
+bt_devices_getname() {
+	awk -F '\t' '{print $2}'
+}
+
+#
+# actions
+#
+
+blum_enable() {
+	fuzl_try -- doas -n rc-service bluetooth start
+	fuzl_notification "enabled" "-"
+	main
+}
+
+blum_disable() {
+	fuzl_try -- doas -n rc-service bluetooth stop
+	fuzl_notification "disabled" "-"
+}
+
+blum_disconnect() {
+	echo "disconnect" | fuzl_try -- bluetoothctl
+	fuzl_notification "disconnected" "-"
+}
+
+blum_devices() {
+	device=$(bt_devices |
+		FUZL_PROMPT='devices' fuzl_menu $FUZL_MENU_FILTER)
+
+	[ -n "$device" ] || main
+	device_mac=$(printf %s "$device" | bt_devices_getmac)
+
+	echo "connect" "$device_mac" | fuzl_try -- bluetoothctl
+	fuzl_notification "connected" "$device"
+
+}
+
+blum_scan() {
+	fuzl_loading "scanning"
+	echo "scan" "on" | bluetoothctl || return
+	sleep 2
+	fuzl_loading_cancel # TODO: this kills everything
+	main
+}
+
+blum_repl() {
+	riverctl spawn 'footclient -- bluetoothctl'
+}
+
+blum_tether_amagiri() {
+	local amagiri_mac='3C:38:F4:5D:3D:49'
+	local amagiri_net='e4e75ff7-b907-43b7-98b7-e0bf94a56d08'
+	echo "connect" "$amagiri_mac" | fuzl_try -- bluetoothctl
+	fuzl_notification "connected" "amagiri"
+	fuzl_try -- nmcli connection up "$amagiri_net"
+	fuzl_notification "network up" "amagiri"
+}
+
+main
diff --git a/bin/dots b/bin/dots
new file mode 100755
index 0000000..cee0d73
--- /dev/null
+++ b/bin/dots
@@ -0,0 +1,28 @@
+#!/bin/sh
+set -eu
+[ -n "${DEBUG:-}" ] && set -x
+case "${1:-status}" in
+git)
+	[ $# -gt 0 ] && shift
+	exec /usr/bin/git --git-dir=$HOME/.dotfiles --work-tree=$HOME $*
+	;;
+st | status)
+	exec $0 git status -sb --untracked-files=no
+	;;
+add)
+	shift
+	exec $0 git add -f $*
+	;;
+commit | snapshot | record)
+	shift
+	$0 git commit --all --trailer=Automated-commit-by:dots --trailer=signoff --gpg-sign --message=* $*
+	echo
+	$0 git log --stat --format=full -1
+	echo
+	;;
+push)
+	set -x
+	$0 git push inazuma || true
+	$0 git push origin || true
+	;;
+esac
diff --git a/bin/dskm b/bin/dskm
new file mode 100755
index 0000000..c5a0abf
--- /dev/null
+++ b/bin/dskm
@@ -0,0 +1,26 @@
+#!/bin/bash
+set -eu
+
+# shellcheck disable=2034
+description="desk height manager"
+
+export FUZL_PROMPT=dskm
+export FUZL_ICON=๓ฑˆน
+. libfuzl.sh
+
+main() {
+	local pos
+
+	pos=$(
+		idesk config |
+			awk '/positions:/{ok=1; next} ok==1{print gensub(":", "", "g", $1)}' |
+			fuzl_menu $FUZL_MENU_FILTER
+	)
+	test -n "$pos" || return
+
+	fuzl_loading "moving desk to position: $pos"
+	idesk "$pos"
+	fuzl_loading_cancel
+}
+
+main
diff --git a/bin/genup b/bin/genup
new file mode 100755
index 0000000..c759a10
--- /dev/null
+++ b/bin/genup
@@ -0,0 +1,91 @@
+#!/bin/sh
+. /lib/gentoo/functions.sh
+
+set -e
+[ -n "${DEBUG:-}" ] && set -x
+
+if [ "$UID" != 0 ]; then
+	eerror "need to be root"
+	exit 1
+fi
+
+# builtin emerge args
+eargs=
+
+# order:
+# sync -> portage update -> update -> update unstable -> clean -> flatpak
+
+if test $# -eq 0; then
+	dosync=1
+	doupdate=1
+	dolive=1
+	doclean=1
+	doflatpak=1
+fi
+
+printonly=
+while getopts spulcfKXBh opt; do
+	case ${opt} in
+	s) dosync=1 ;;
+	u) doupdate=1 ;;
+	l) dolive=1 ;;
+	c) doclean=1 ;;
+	f) doflatpak=1 ;;
+	B) eargs="$eargs -X www-client/librewolf" ;;
+	K) eargs="$eargs -X sys-kernel/* -X virtual/dist-kernel" ;;
+	X) printonly=1 ;;
+	h | ?)
+		printf "usage: %s [-hsulcfBKX]\n" "$(basename "$0")"
+		printf "\n"
+		exit 2
+		;;
+	esac
+done
+shift $((OPTIND - 1))
+
+_() {
+	[ -n "$printonly" ] && printf "%s\n" "$*" && return
+	eval $*
+}
+
+ebegin syncing
+[ -n "${dosync:-}" ] &&
+	_ emerge --sync &&
+	eend
+
+ebegin updating portage
+emerge --pretend --quiet --update sys-apps/portage | grep -qE '\[.*U.*\]' &&
+	_ emerge -a --oneshot --getbinpkg sys-apps/portage &&
+	eend
+
+ebegin updating @world
+[ -n "${doupdate:-}" ] &&
+	_ emerge -ag --update --deep --newuse --keep-going "$@" $eargs @world &&
+	eend
+
+# --binpkg-respect-use=y
+# _ revdep-ebuild -- "$@" $eargs &&
+
+ebegin updating unstable
+[ -n "${dolive:-}" ] &&
+	_ emerge -a1 --keep-going @live-rebuild &&
+	eend
+
+ebegin depcleaning
+[ -n "${doclean:-}" ] &&
+	_ emerge -a --depclean "$@" &&
+	_ eclean --deep packages &&
+	_ eclean --deep distfiles &&
+	eend
+
+ebegin updating @preserved-rebuild
+[ -n "${doupdate:-}" ] &&
+	_ emerge -a --keep-going "$@" $eargs @preserved-rebuild &&
+	eend
+
+ebegin updating flatpaks
+[ -x "$(command -v flatpak)" ] && [ -n "${doflatpak:-}" ] &&
+	_ flatpak upgrade &&
+	_ flatpak uninstall --unused &&
+	_ flatpak repair &&
+	eend
diff --git a/bin/hr b/bin/hr
new file mode 100755
index 0000000..8231ae1
--- /dev/null
+++ b/bin/hr
@@ -0,0 +1,22 @@
+#!/bin/bash
+set -eu
+function print_hr() {
+	local str cols
+	local start line end
+
+	str="${1:-}"
+	[[ -n $str ]] && str=" $str "
+	cols=$((${COLUMNS:-$(tput cols)} - ${#str} - 3))
+	# start=$'\e(0'; end=$'\e(B'; line="qqq"
+	start=''; end=''; line="โ”€"
+	while ((${#line}<$cols)); do line+=$line; done
+
+	printf "โ•พ"
+	printf "%s%s%s" "$start" "${line:0:$((cols/2))}" "$end"
+	printf "%s%s%s" "$start" "${line:0:$((cols/2))}" "$end"
+	printf "%s" "$str"
+	printf "%s%s%s" "$start" "${line:0:1}" "$end"
+	printf "โ•ผ"
+	printf "\n"
+}
+print_hr "$@"
diff --git a/bin/libfuzl.sh b/bin/libfuzl.sh
new file mode 100644
index 0000000..a28c861
--- /dev/null
+++ b/bin/libfuzl.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+# local is well supported
+# shellcheck disable=3043
+
+set -eu
+
+# pick up configuration
+FUZL_PROMPT=${FUZL_PROMPT:-fuzl}
+FUZL_ICON=${FUZL_ICON:-๓ฐˆฒ}
+
+fuzl_try() {
+	[ "$1" == "--" ] && shift 1
+	if ! { stderr=$(
+		set +x
+		eval "$*" 2>&1 1>&3
+	); } 3>&1; then
+		fuzl_notification "$* failed :(" "${stderr:--}" -c error
+		exit 1
+	fi
+}
+
+export FUZL_MENU_FILTER=filter
+export FUZL_MENU_PASSWORD=passw
+export FUZL_MENU_MESSAGE=msg
+export FUZL_MENU_INPUT=input
+
+# Shows a menu
+fuzl_menu() {
+	local form="$1"
+	shift 1
+
+	local prompt options_list maxw
+
+	# If a prompt_icon is provided, prepend it.
+	prompt="$FUZL_PROMPT "
+	[ -n "$FUZL_ICON" ] && prompt="$FUZL_ICON $FUZL_PROMPT "
+
+	options_list=$(cat)
+	maxw=$(echo -e "$prompt\n$options_list" | wc -L)
+	[ "$maxw" -lt 15 ] && maxw=15
+	[ "$maxw" -gt 150 ] && maxw=100
+
+	case "$form" in
+	"$FUZL_MENU_FILTER") # Selection menu with a list of options passed via STDIN
+		echo -ne "$options_list" | fuzzel -d -w "$maxw" -p "$prompt" "$@"
+		;;
+	"$FUZL_MENU_PASSWORD") # Password input menu (minimalist)
+		echo -n | fuzzel -d -w "$maxw" -p "$prompt" --password
+		;;
+	"$FUZL_MENU_MESSAGE") # Message box menu (no input bar)
+		echo -ne "$options_list" | fuzzel -d -w "$maxw" -p "$prompt" "$@"
+		;;
+	"$FUZL_MENU_INPUT") # Text input menu (no list view)
+		echo -n | fuzzel -d -p "$prompt" "$@" --lines 0
+		;;
+	esac
+}
+
+FUZL_NOTIFICATION_ID=
+
+fuzl_notification() {
+	local summary="${1:?missing summary}"
+	local body="${2:?missing body}"
+	shift 2
+
+	FUZL_NOTIFICATION_ID=$(
+		fyi "$@" -p -a "${FUZL_PROMPT}" "${FUZL_PROMPT}: $summary" "$body" |
+			cut -d= -f2
+	)
+}
+fuzl_notification_cancel() {
+	fyi --close "$FUZL_NOTIFICATION_ID" || true
+	FUZL_NOTIFICATION_ID=
+}
+
+FUZL_LOADING_PID=
+
+fuzl_loading() {
+	local summary="${1:?missing summary}"
+	echo -e "..." | FUZL_PROMPT="$summary" fuzl_menu $FUZL_MENU_MESSAGE &
+	FUZL_LOADING_PID=$!
+}
+fuzl_loading_cancel() {
+	local pgid
+	pgid="$(ps -o pgid= "$FUZL_LOADING_PID" || return)"
+	kill -- -"$pgid" || true
+	FUZL_LOADING_PID=
+}
+
+FUZL_ACTIONS=()
+
+fuzl_action_add() {
+	local action="${1:?}" icon="${2:?}"
+	shift 2
+	# prevent multiple occurences
+	printf '%s\n' "${FUZL_ACTIONS[@]}" | grep -q "^$action" && return
+	FUZL_ACTIONS+=("${action}\t${icon}\t${*}")
+}
+
+fuzl_action_menu() {
+	local action
+
+	action=$(printf '%s\n' "${FUZL_ACTIONS[@]}" |
+		FUZL_PROMPT="$FUZL_PROMPT: $*" fuzl_menu $FUZL_MENU_FILTER --with-nth='{2}  {3..}' --accept-nth=1)
+
+	# reset actions
+	FUZL_ACTIONS=()
+
+	test -n "$action" || return
+	eval "${FUZL_PROMPT}_${action}" "'$*'"
+}
+
+# setup common debug helper
+[ -z "${DEBUG:-}" ] || set -x
diff --git a/bin/mailsync b/bin/mailsync
new file mode 100755
index 0000000..2100962
--- /dev/null
+++ b/bin/mailsync
@@ -0,0 +1,148 @@
+#!/bin/bash
+set -eu
+[ -n "${DEBUG:-}" ] && set -x
+
+# check requirements
+for tool in mbsync fyi python awk; do
+	if ! command -v "$tool" >/dev/null; then
+		printf '%s: %s not installed but required\n' "$(basename "$0")" "$tool" >&2
+		exit 1
+	fi
+done
+
+maildir="${HOME}/mail"
+cachefile=${XDG_CACHE_HOME:-$HOME/.cache}/mailsync.lastrun
+
+accounts=(gnzler)
+list=
+verbose=
+while getopts a:lvh opt; do
+	case "${opt}" in
+	a) accounts=("${OPTARG}") ;;
+	l) list=1 ;;
+	v) verbose=1 ;;
+	h | ?)
+		printf "usage: %s [-h] [ACCOUNT...]\n" "$(basename "$0")"
+		printf "\n"
+		printf "  -a ACCOUNTS, accunts to sync (default: \"%s\")\n" "${accounts[*]}"
+		printf "  -l list mail\n"
+		printf "  -v be verbose"
+		printf "\n"
+		exit 2
+		;;
+	esac
+done
+shift $((OPTIND - 1))
+
+if [ -n "${1:-}" ]; then
+	accounts=("$*")
+fi
+
+notifyclose() {
+	fyi --close "$1"
+}
+notifysend() {
+	local summary="${1:?missing summary}" body="${2:?missing body}"
+	shift 2
+
+	fyi --print-id $@ -a mailsync \
+		-i "${ico:-}" \
+		"$summary" "<i>$body</i>" | cut -d= -f2
+}
+try() {
+	[ "$1" = "--" ] && shift 1
+	if ! { error=$(
+		set +x
+		eval $* 2>&3
+	); } 3>&1; then
+		notifysend "failed :(" "$error" -c error
+		return 1
+	fi
+}
+
+
+decode() {
+	python -c 'import sys;import email.header;print(("".join(list(map(lambda s: s[0] if (type(s[0]) == str) else s[0].decode(s[1] or "utf-8"), email.header.decode_header(((sys.stdin.readlines() or [""])[0]).strip()))))))'
+}
+
+list_msg() {
+	local acct=$2
+	local args=(-type f -not -name '\.*')
+	local cachefile="${cachefile}.${acct}"
+
+	find "${maildir}"/"${acct}"/{INBOX,lists}/new/ "${args[@]}" 2>/dev/null || true
+}
+
+run_notify() {
+	local acct=$1
+	local new_mail
+	local num
+	local nid
+
+	new_mail=$(list_msg new "${acct}")
+	num=$(echo "$new_mail" | wc -l)
+	nid=$(notifysend "${acct}: new mail" "found ${num} new messages" --expire-time=6000)
+}
+
+run_sync() {
+	local acct=$1
+	local nid
+	local ret
+
+	# exit if we're already running
+	pgrep mbsync >/dev/null && {
+		echo "mbsync is already running."
+		notifysend "${acct}: mailsync stopped" "mbsync is already running"
+		return
+	}
+
+	# flush msmtp
+	nid=$(notifysend "${acct}: flushing queue" "running msmtp-queue" --expire-time=0)
+	try -- msmtp-queue -r || true
+
+	# run sync
+	nid=$(notifysend "${acct}: syncing mail" "running mbsync" --expire-time=0 --replaces=$nid)
+	mbsync_args=
+	[ -n "${verbose}" ] && mbsync_args="--verbose"
+	try -- mbsync $mbsync_args --config="$HOME/.config/mbsyncrc" "$acct"
+
+	# create cachefile
+	touch "${cachefile}.${acct}"
+
+	# index new messages
+	if command -v notmuch >/dev/null 2>&1; then
+		nid=$(notifysend "${acct}: syncing mail" "running notmuch" --expire-time=0 --replaces=$nid)
+		try -- notmuch new
+	fi
+
+	# update address book
+	if command -v maildir-rank-addr >/dev/null 2>&1; then
+		nid=$(notifysend "${acct}: syncing mail" "running maildir-rank-addr" --expire-time=0 --replaces=$nid)
+		maildir-rank-addr \
+			--maildir=${maildir}/${acct} \
+			--outputpath=${maildir}/addressbook \
+			--addresses='(r|robert)@gnzler\.(de|io),robert(|\.)guenzler@gmail.com,therobotbase@gmail.com,robertg@balena.io,robert.gunzler@postman.com,robert@unikraft.io' \
+			--filters='r\+.*@gnzler\.(de|io)'
+	fi
+
+	notifyclose "$nid"
+}
+
+# only list new mail, don't sync
+if [ -z "${accounts[*]}" ]; then
+	echo "no account specified" >&2
+	exit 1
+fi
+
+for acct in "${accounts[@]}"; do
+	(
+		if [ -n "${list}" ]; then
+			list_msg "${list}" "${acct}"
+			exit 0 # exit ()& subshell
+		fi
+		run_sync "${acct}"
+		run_notify "${acct}"
+	) &
+done
+
+wait
diff --git a/bin/mktoes b/bin/mktoes
new file mode 100755
index 0000000..7eb47cf
--- /dev/null
+++ b/bin/mktoes
@@ -0,0 +1,27 @@
+#!/bin/sh
+set -eu
+target=${1:?target}
+size=${2:-normal}
+
+case $size in
+normal) px=128 ;;
+large) px=256 ;;
+x-large) px=512 ;;
+xx-large) px=1024 ;;
+*) exit 1 ;;
+esac
+
+md5=$(echo "file://$(readlink -e "$target")" | md5sum | cut -d' ' -f1)
+dir=${XDG_CACHE_HOME:-$HOME/.cache}/thumbnails/$size
+path=$dir/$md5.png
+printf '%s' $path
+[ -n "${MKTOES_NOGEN:-}" ] && exit 0
+
+test -f $path && exit 0
+mkdir -p $dir
+chmod 700 $dir
+
+tmp=$dir/.$$.$md5.png
+ffmpeg -hide_banner -i "$target" -vf scale=$px:$px:force_original_aspect_ratio=decrease $tmp 2>/dev/null
+mv -f $tmp $path
+chmod 600 $path
diff --git a/bin/msmtp-queue b/bin/msmtp-queue
new file mode 120000
index 0000000..2604954
--- /dev/null
+++ b/bin/msmtp-queue
@@ -0,0 +1 @@
+/usr/share/msmtp/msmtpq/msmtp-queue
\ No newline at end of file
diff --git a/bin/msmtp-queue-stat b/bin/msmtp-queue-stat
new file mode 100755
index 0000000..bb83784
--- /dev/null
+++ b/bin/msmtp-queue-stat
@@ -0,0 +1,17 @@
+#!/bin/sh
+set -eu
+
+throttle_by=10
+throttle_cookie=/tmp/throttle.msmtp-queue-stat
+throttle() {
+	last_called=$(stat -c %Y $throttle_cookie 2>/dev/null || printf 0)
+	now=$(date +%s)
+	touch $throttle_cookie
+	if [ $((now - last_called)) -gt $throttle_by ]; then
+		"$@"
+	else
+		false
+	fi
+}
+
+throttle sh -c "msmtpq --q-mgmt -d | grep id= | wc -l | tee $throttle_cookie" || cat "$throttle_cookie"
diff --git a/bin/msmtpq b/bin/msmtpq
new file mode 120000
index 0000000..2e81615
--- /dev/null
+++ b/bin/msmtpq
@@ -0,0 +1 @@
+/usr/share/msmtp/msmtpq/msmtpq
\ No newline at end of file
diff --git a/bin/netm b/bin/netm
new file mode 100755
index 0000000..fab7e95
--- /dev/null
+++ b/bin/netm
@@ -0,0 +1,1560 @@
+#!/usr/bin/env bash
+#    __  __                 __   ________  ___       _   __    __
+#   / / / /_  ______  _____/ /  /_  __/  |/  /      / | / /___/ /_
+#  / /_/ / / / / __ \/ ___/ /    / / / /|_/ /_____ /  |/ / _  / __/
+# / __  / /_/ / /_/ / /  / /___ / / / /  / /_____ / /|  /  __/ /_
+#/_/ /_/\__, / .___/_/  /_____//_/ /_/  /_/      /_/ |_/\___/\__/
+#      /____/_/
+#
+
+# Copyright ยฉ 2025 Djalel Oukid (sniper1720)
+#
+# Mods:
+# - replace rofi with fuzzel
+# - remove all theme references
+# - fix wifi interface detection
+# - hide extra search icon
+# - replace close and check icons (due to problematic width)
+
+# --- Dependencies Check ---
+if ! command -v fuzzel &>/dev/null; then
+	echo "Error: fuzzel is not installed. Please install it to use this script." >&2
+	exit 1
+fi
+if ! command -v nmcli &>/dev/null; then
+	echo "Error: nmcli is not installed. Please install NetworkManager and nmcli." >&2
+	exit 1
+fi
+
+# --- Icon Variables (Nerd Fonts - Minimalist & Beautiful) ---
+# --- Icons: General UI ---
+icon_search="${icon_search:-""}"
+icon_close="${icon_close:-"๓ฐ…— "}"
+icon_check="${icon_check:-"๏…Š "}"
+icon_on="${icon_on:-"๏ˆ…"}"
+icon_off="${icon_off:-"๏ˆ„"}"
+icon_info="${icon_info:-"๓ฐ–"}"
+icon_refresh="${icon_refresh:-"๓ฐ‘"}"
+icon_config="${icon_config:-"๎˜•"}"
+
+# --- Icons: Network Types ---
+icon_network="${icon_network:-"๓ฑ‚‡"}"
+icon_wifi_prompt="${icon_wifi_prompt:-"๓ฑšพ"}"
+icon_ethernet="${icon_ethernet:-"๓ฐˆ€"}"
+icon_vpn="${icon_vpn:-"๓ฐ–ƒ"}"
+icon_wireguard="${icon_wireguard:-"๓ฐ–†"}"
+icon_hotspot="${icon_hotspot:-"๓ฑ„™"}"
+icon_airplane="${icon_airplane:-"๓ฐ€"}"
+
+# --- Icons: Wi-Fi Signal ---
+icon_wifi_full="${icon_wifi_full:-"๓ฐคจ"}"
+icon_wifi_good="${icon_wifi_good:-"๓ฐคฅ"}"
+icon_wifi_medium="${icon_wifi_medium:-"๓ฐคข"}"
+icon_wifi_low="${icon_wifi_low:-"๓ฐค "}"
+icon_wifi_disconnected="${icon_wifi_disconnected:-"๓ฐคฎ"}"
+icon_wifi_enable="${icon_wifi_enable:-"๓ฐ–ฉ"}"
+icon_wifi_disable="${icon_wifi_disable:-"๓ฐ–ช"}"
+
+# --- Icons: Security & Status ---
+icon_wifi_secure="${icon_wifi_secure:-"๏€ฃ"}"
+icon_wifi_open="${icon_wifi_open:-"๓ฐค "}"
+icon_unlock="${icon_unlock:-"๏‹ผ"}"
+icon_password="${icon_password:-"๏‚„"}"
+icon_eye="${icon_eye:-"๓ฐ›"}"
+icon_eye_closed="${icon_eye_closed:-"๓ฐ›‘"}"
+icon_bookmark_saved="${icon_bookmark_saved:-"๓ฐขญ"}"
+icon_saved="${icon_saved:-"๓ฐ‹‹"}"
+
+# --- Icons: Menus & Actions ---
+icon_connect="${icon_connect:-"๓ฐšซ"}"
+icon_disconnect="${icon_disconnect:-"qa"}"
+icon_vpn_disconnect="${icon_vpn_disconnect:-"๓ฐ–‚"}"
+icon_trash="${icon_trash:-"๏’Ž"}"
+icon_pen="${icon_pen:-"๓ฐ‘•"}"
+icon_import="${icon_import:-"๓ฐ‹บ"}"
+icon_qrcode="${icon_qrcode:-"๓ฐฒ"}"
+
+# --- Icons: Details & Config ---
+icon_active_details="${icon_active_details:-"๓ฐ‹ผ"}"
+icon_status_chart="${icon_status_chart:-"๓ฑ–ซ"}"
+icon_interface="${icon_interface:-"๓ฐ›จ"}"
+icon_devices="${icon_devices:-"๓ฐ‹ฝ"}"
+icon_chip="${icon_chip:-"๓ฐขฎ"}"
+icon_ipv4_config="${icon_ipv4_config:-"๓ฐ’“"}"
+icon_ipv4_dns="${icon_ipv4_dns:-"๓ฐ’"}"
+icon_ipv6_config="${icon_ipv6_config:-"๓ฐ’“"}" # Reuse
+icon_ipv6_dns="${icon_ipv6_dns:-"๓ฐ’"}"       # Reuse
+icon_auto_ip="${icon_auto_ip:-"๓ฐ‘˜"}"
+icon_auto_dns="${icon_auto_dns:-"๓ฐ’"}"
+icon_address="${icon_address:-"๓ฐ’“"}"
+icon_gateway="${icon_gateway:-"๓ฐžก"}"
+icon_plug="${icon_plug:-"๓ฑ˜–"}"
+icon_wireless="${icon_wireless:-"๓ฐ‘ฉ"}"
+icon_automatic="${icon_automatic:-"๓ฐ‘˜"}"
+
+# --- Icons: Miscellaneous ---
+icon_hidden_network="${icon_hidden_network:-"๓ฐฒŠ"}"
+icon_connect_wired="${icon_connect_wired:-"๓ฑ‚‡"}"
+icon_wired_status="${icon_wired_status:-"๓ฐˆ"}"
+
+# --- Translatable / Customizable Messages ---
+tr_checking_wifi_status='Checking Wi-Fi status... Please be patient.'
+tr_scanning_networks='Scanning networks... Please be patient.'
+tr_connecting_to='Connecting... Please be patient.'
+tr_disconnecting_from='Disconnecting... Please be patient.'
+tr_submenu_message='More options'
+tr_disable_message='Disable Wi-Fi'
+tr_enable_message='Enable Wi-Fi'
+tr_interface_message='Interface:'
+tr_known_connections_message='Known connections' # FIX: Removed colon
+tr_available_networks_message='Available Networks'
+tr_available_vpn_profiles_message='Available VPN Profiles'
+tr_autoconnect_message='Autoconnect'
+tr_ipv4_config_message='IPv4 Configuration'
+tr_ipv6_config_message='IPv6 Configuration'
+tr_dns4_message='DNS IPv4'
+tr_dns6_message='DNS IPv6'
+tr_connection_details_message='Connection Details' # New Header
+tr_ip_addr='IP Address'
+tr_gateway='Gateway'
+tr_signal_strength='Signal Strength'
+tr_speed='Link Speed'
+tr_frequency='Frequency'
+tr_mac_addr='MAC Address'
+tr_device='Interface'
+tr_autoip_message='Automatic IP'
+tr_autodns_message='Automatic DNS'
+tr_address_message='Addresses'
+tr_gateway_message='Gateway:'
+tr_forget_message='Forget connection'
+tr_wireguard_enable_message='Toggle VPN'
+tr_rename_connection_message='Rename connection'
+tr_hidden_message='Connect to a hidden network'
+tr_refresh_scan_message='Refresh Scan'                     # New message for refresh option
+tr_no_known_wifi_connections='No known Wi-Fi connections.' # New message
+tr_no_configured_vpns='No VPN connections configured.'     # New message
+tr_no_active_vpns='No active VPN connections.'             # New message
+tr_no_saved_connections='No saved connections found.'      # New message
+tr_no_active_connection='No active connection.'            # New message
+tr_no_ethernet_device='No Ethernet device found.'          # New message
+tr_no_wifi_networks_found='No Wi-Fi networks found.'       # New message for empty scan
+tr_wired_status_message='Wired Status'                     # New message for wired status option
+tr_manage_wired_profile='Manage Active Wired Profile'      # New message for wired profile management
+tr_connect_wired_connection='Connect to Wired Connection:' # Refined prompt
+tr_manage_wired_connections='Manage Wired Connections'     # New message for wired connections management
+tr_import_vpn_message='Import VPN from file'
+tr_import_vpn_prompt='Enter full path to .conf or .ovpn file:'
+tr_status_message=''
+tr_status_connected_to='Connected to'
+tr_status_connected='Connected'
+tr_status_disconnected='Disconnected'
+
+tr_status_disabled='Disabled'
+tr_connect_now_message='Connect Now'
+tr_disconnect_message='Disconnect Now'
+tr_notice_import_success_summary='VPN Imported'
+tr_notice_import_success_body='Successfully imported VPN connection.'
+tr_notice_import_error_summary='Import Error'
+tr_notice_import_error_body='Failed to import VPN connection.'
+tr_notice_file_not_found_body='File not found at specified path.'
+
+# Airplane Mode
+tr_airplane_mode_message='Airplane Mode'
+tr_airplane_on='Airplane Mode Enabled'
+tr_airplane_off='Airplane Mode Disabled'
+
+# QR Code
+tr_edit_password_message='Edit Password' # New translation
+tr_password_prompt='Enter password for'
+tr_password_updated='Password Updated'
+tr_password_update_failed='Failed to update password'
+tr_connection_failed_retry='Connection failed. Update password?'
+tr_qrcode_message='Share via QR Code'
+tr_qrcode_generating='Generating QR Code...'
+tr_qrcode_error='Could not generate QR code. Is qrencode installed?'
+tr_qrcode_no_password='Cannot share network without saved password.'
+
+# Hotspot
+tr_hotspot_message='Create Hotspot'
+tr_hotspot_ssid_prompt='Enter Hotspot SSID:'
+tr_hotspot_password_prompt='Enter Hotspot Password (min 8 chars):'
+tr_hotspot_creating='Creating Hotspot...'
+tr_hotspot_success='Hotspot created successfully!'
+tr_hotspot_error='Failed to create hotspot.'
+tr_notice_unknown_vpn_type_body='Unknown VPN file type. Use .conf or .ovpn.'
+
+# Main menu prompt now includes network and search icons
+tr_main_menu_prompt="$icon_network Network Manager: $icon_search"
+tr_wifi_menu_prompt="$icon_wifi_prompt Wi-Fi: $icon_search"
+tr_wired_menu_prompt="$icon_ethernet Wired: $icon_search"                               # NEW: Wired sub-menu prompt
+tr_vpn_menu_prompt="$icon_vpn_disconnect VPN: $icon_search"                             # NEW: VPN sub-menu prompt
+tr_saved_connections_menu_prompt="$icon_bookmark_saved Saved Connections: $icon_search" # NEW: Saved Connections sub-menu prompt
+tr_status_menu_prompt="$icon_status_chart Status: $icon_search"                         # NEW: Status sub-menu prompt
+
+tr_select_interface_prompt='Select Interface:'
+tr_ask_password_prompt='Enter password:'        # Refined prompt
+tr_menu_dns_prompt='Enter DNS (e.g., 8.8.8.8):' # Refined prompt
+tr_menu_dns_sure_prompt_1='Remove DNS '
+tr_menu_dns_sure_prompt_2='?'
+tr_menu_ip_config_addresses_prompt='Enter address (e.g., 192.168.1.10/24):' # Refined prompt
+tr_menu_ip_config_gateway_prompt='Enter gateway (e.g., 192.168.1.1):'       # Refined prompt
+tr_menu_addresses_prompt='Type or select address (e.g., 192.168.1.10/24):'  # Refined prompt
+tr_menu_addresses_sure_prompt_1='Remove address '
+tr_menu_addresses_sure_prompt_2='?'
+tr_forget_connection_sure_prompt_1='Forget '
+tr_forget_connection_sure_prompt_2='?'
+tr_forget_connection_confirm='Yes, forget'                   # New confirmation text
+tr_rename_connection_prompt='Enter new name for connection:' # Refined prompt
+tr_connect_hidden_prompt='Enter hidden network name:'        # Refined prompt
+
+tr_notice_connected_summary='Connected'
+tr_notice_disconnected_summary='Disconnected'
+tr_notice_error_summary='Connection Error'
+tr_notice_connected_body='Successfully connected to'
+tr_notice_disconnected_body='Successfully disconnected from'
+tr_notice_error_body='Failed to connect to'
+tr_notice_error_disconnect_body='Failed to disconnect from'
+
+# NEW: Messages for password input actions
+tr_show_password_message='Show Password'
+tr_hide_password_message='Hide Password'
+tr_confirm_password_message='Confirm Password'
+tr_edit_password_message='Edit Password'
+tr_edit_password_prompt='Enter new password:'
+
+# --- Global Variables ---
+program_name="$(basename "$0")"
+LOADING_ROFI_PID=""
+mapfile -t interfaces < <(nmcli --colors no -t -f TYPE,DEVICE device status | awk -F ':' '$1 == "wifi" {print $2}')
+if [ -z "${interfaces[0]}" ]; then
+	echo "$program_name: No Wi-Fi interfaces detected." >&2
+	exit 2
+fi
+interface_to_use="${interfaces[0]}"
+
+# --- Helper Functions ---
+
+# Function to display a menu (form 1: selection, 2: text input with list, 3: password input, 4: message box, 5: text input no list)
+# Now accepts optional extra flags to pass to rofi.
+display_menu() {
+	local form="$1"
+	local prompt_text="$2"
+	local prompt_icon="${3:-}" # Optional: icon to prepend to prompt
+	local extra_flags="${4:-}" # Optional: extra flags for the fuzzel command
+	local rofi_prompt rofi_flags options_list
+
+	# If a prompt_icon is provided and the prompt_text doesn't already start with it, prepend it.
+	if [ -n "$prompt_icon" ] && ! echo "$prompt_text" | grep -qE "^$prompt_icon"; then
+		rofi_prompt="$prompt_icon $prompt_text"
+	else
+		rofi_prompt="$prompt_text"
+	fi
+
+	local result
+
+	case $form in
+	1 | 2 | 4)
+		options_list=$(cat)
+		maxw=$(($(wc -L <<<"$rofi_prompt\n$options_list") + 2))
+		;&
+	1) # Selection menu with a list of options passed via STDIN
+		result=$(echo -e "$options_list" | fuzzel -di -w $maxw $extra_flags -p "$rofi_prompt")
+		;;
+	2) # Generic text input menu with a list
+		result=$(echo -e "$options_list" | fuzzel -d -w $maxw $extra_flags -p "$rofi_prompt")
+		;;
+	3) # Password input menu (minimalist)
+		result=$(fuzzel -d --password $extra_flags -p "$rofi_prompt")
+		;;
+	4) # Message box menu (no input bar)
+		result=$(echo -e "$options_list" | fuzzel -di -w $maxw --hide-prompt $extra_flags -p "$rofi_prompt")
+		;;
+	5) # Text input menu (no list view)
+		result=$(fuzzel -d $extra_flags -p "$rofi_prompt")
+		;;
+	esac
+	echo "$result"
+}
+
+# --- Rofi-based Notification System ---
+
+# Shows a non-blocking "loading" Rofi window and stores its PID.
+show_loading_notification() {
+	local message="$1"
+	# Use dmenu mode to get the themed window, but completely override the
+	# mainbox layout to show only a single, centered textbox.
+	echo "..." | fuzzel -d -p "$message" &
+	LOADING_ROFI_PID=$!
+}
+
+# Kills the loading notification Rofi window if it's running.
+kill_loading_notification() {
+	if [ -n "$LOADING_ROFI_PID" ] && ps -p "$LOADING_ROFI_PID" >/dev/null; then
+		kill "$LOADING_ROFI_PID"
+		LOADING_ROFI_PID=""
+	fi
+}
+
+# Shows a final, blocking Rofi message for results (Success, Error, etc.).
+# Optionally sends a system notification if notify-send is available.
+send_notification() {
+	local summary="$1"
+	local body="$2"
+
+	# Try system notification first (non-blocking)
+	if command -v notify-send &>/dev/null; then
+		fyi -a netm "$icon_network $summary" "$body"
+	else
+		# Fallback to Rofi message
+		show_message "$body" "$summary"
+	fi
+}
+
+# Function to show a message (stays open until dismissed, with "OK" button)
+# Now accepts an optional custom_prompt and a flag to include the "OK" button.
+show_message() {
+	local msg="$1"
+	local custom_prompt="${2:-Info}" # Default to "Info" if no custom prompt is provided
+	local include_ok="${3:-true}"    # Default to true
+	local options="$msg"
+
+	if [ "$include_ok" = "true" ]; then
+		options+="\n$icon_check OK"
+	fi
+	# Use form 4 to disable the input bar for these messages
+	echo -e "$options" | display_menu 4 "$custom_prompt" ""
+}
+
+# Function to display an informational message with a "Back" button
+# Now accepts an optional custom_prompt
+display_info_message() {
+	local msg="$1"
+	local custom_prompt="${2:-Info}" # Default to "Info" if no custom prompt is provided
+	local options="$msg\n$icon_close Back"
+	# Use form 4 to disable the input bar for these messages
+	local chosen=$(echo -e "$options" | display_menu 4 "$custom_prompt" "")
+	if [[ "$chosen" =~ "^$icon_close Back" ]]; then
+		return
+	fi
+}
+
+# Handles password input with a dynamic, interactive menu for showing, hiding, and editing.
+ask_password() {
+	local password_input
+	local password_shown="false" # 'true' or 'false'
+
+	# Step 1: Initial password prompt
+	password_input=$(echo "" | display_menu 3 "$tr_ask_password_prompt" "")
+
+	# If user cancels the initial prompt (presses Esc or enters nothing), return empty.
+	if [ -z "$password_input" ]; then
+		return 1
+	fi
+
+	# Step 2: Interactive action menu loop
+	while true; do
+		local options=""
+		local password_display=""
+
+		# Determine how to display the password
+		if [ "$password_shown" = "true" ]; then
+			password_display="$password_input"
+			options+="$icon_eye_closed $tr_hide_password_message: $password_display\n"
+		else
+			# Create a bullet point string of the same length as the password
+			password_display=$(printf 'โ€ข%.0s' $(seq 1 ${#password_input}))
+			options+="$icon_eye $tr_show_password_message: $password_display\n"
+		fi
+
+		# Add other actions
+		options+="$icon_pen $tr_edit_password_message\n"
+		options+="$icon_unlock $tr_confirm_password_message\n"
+		options+="$icon_close Back"
+
+		# Display the interactive menu
+		local action_choice=$(echo -e "$options" | display_menu 1 "Password Actions" "")
+
+		# Handle user's choice
+		if [ -z "$action_choice" ] || [[ "$action_choice" =~ "^$icon_close Back" ]]; then
+			# User cancelled at action menu
+			return 1
+		fi
+
+		case "$action_choice" in
+		*"$tr_show_password_message"* | *"$tr_hide_password_message"*)
+			# Toggle password visibility
+			if [ "$password_shown" = "true" ]; then
+				password_shown="false"
+			else
+				password_shown="true"
+			fi
+			# Loop continues, redrawing the menu
+			;;
+		*"$tr_edit_password_message"*)
+			# Ask for a new password, pre-filling the input with the current password.
+			local new_password=$(echo "" | display_menu 2 "$tr_edit_password_prompt" "" "-filter \"$password_input\"")
+			# Only update if the user entered something and didn't cancel
+			if [ -n "$new_password" ]; then
+				password_input="$new_password"
+				password_shown="false" # Hide new password by default
+			fi
+			# Loop continues, redrawing the menu
+			;;
+		*"$tr_confirm_password_message"*)
+			# Return the confirmed password
+			echo "$password_input"
+			return
+			;;
+		esac
+	done
+}
+
+# Show details for the active connection
+show_connection_details() {
+	local active_ssid="$1"
+	local device="$2"
+
+	show_loading_notification "Gathering details..."
+
+	# Get details using nmcli device show
+	# We grep for specific fields
+	local info=$(nmcli -t -f GENERAL,IP4,IP6 device show "$device")
+
+	# Extract fields (hacky but effective for standard nmcli output)
+	# MAC often contains colons, so we strip the field name "GENERAL.HWADDR:"
+	local ipv4=$(echo "$info" | grep "IP4.ADDRESS\[1\]" | cut -d':' -f2)
+	local gateway=$(echo "$info" | grep "IP4.GATEWAY" | cut -d':' -f2)
+	local hwaddr=$(echo "$info" | grep "GENERAL.HWADDR" | sed 's/^GENERAL.HWADDR://')
+	local state=$(echo "$info" | grep "GENERAL.STATE" | cut -d':' -f2)
+
+	# Wifi specific details
+	local wifi_info=$(nmcli -t -f IN-USE,SSID,MODE,CHAN,RATE,SIGNAL,BARS,SECURITY device wifi list | grep "^\*")
+	# Format: *:SSID:Infra:Chan:Rate:Signal:Bars:Sec
+	local chan=$(echo "$wifi_info" | cut -d':' -f4)
+	local rate=$(echo "$wifi_info" | cut -d':' -f5)
+	local signal=$(echo "$wifi_info" | cut -d':' -f6)
+	local bars=$(echo "$wifi_info" | cut -d':' -f7)
+	local freq=""
+
+	# Determine frequency band roughly by channel
+	if [ "$chan" -gt 14 ]; then freq="5 GHz"; else freq="2.4 GHz"; fi
+
+	kill_loading_notification
+
+	local details=""
+	details+="$icon_address $tr_ip_addr: $ipv4\n"
+	details+="$icon_gateway $tr_gateway: $gateway\n"
+	details+="$icon_wifi_full $tr_signal_strength: $signal% ($bars)\n"
+	details+="$icon_on $tr_speed: $rate\n"
+	details+="$icon_wireless $tr_frequency: $freq (Ch $chan)\n"
+	details+="$icon_devices $tr_mac_addr: $hwaddr\n"
+	details+="$icon_chip $tr_device: $device"
+
+	display_info_message "$details" "$tr_connection_details_message"
+}
+
+# --- Dedicated Wi-Fi Scan Function ---
+perform_wifi_scan() {
+	show_loading_notification "$tr_scanning_networks"
+
+	# Get list of known SSIDs, delimited by |
+	# We use tr to replace newline with |
+	local known_ssids=$(nmcli -t -f 802-11-wireless.ssid connection show | tr '\n' '|')
+	# Add leading/trailing pipe for exact matching
+	known_ssids="|$known_ssids"
+
+	local scan_output=$(nmcli --colors no --get-values SECURITY,SIGNAL,SSID,IN-USE device wifi list --rescan auto | awk -F ':' \
+		-v icon_wifi_secure="$icon_wifi_secure" \
+		-v icon_wifi_open="$icon_wifi_open" \
+		-v icon_wifi_full="$icon_wifi_full" \
+		-v icon_wifi_good="$icon_wifi_good" \
+		-v icon_wifi_medium="$icon_wifi_medium" \
+		-v icon_wifi_low="$icon_wifi_low" \
+		-v icon_check="$icon_check" \
+		-v icon_unlock="$icon_unlock" \
+		-v known_ssids="$known_ssids" \
+		'
+    BEGIN { x = 1 }
+    {
+        # 1. First Icon: Signal Strength (Always shown)
+        wifi_signal_icon = icon_wifi_low;
+        if ($2 > 75) wifi_signal_icon = icon_wifi_full;
+        else if ($2 > 50) wifi_signal_icon = icon_wifi_good;
+        else if ($2 > 25) wifi_signal_icon = icon_wifi_medium;
+
+        ssid = $3;
+        if (ssid == "") ssid = "<hidden>";
+
+        # 2. Second Icon: Status (Priority: Active > Secure > Open/Known)
+        # User Logic: 
+        # - Active -> Checkmark
+        # - Secure (Even if known) -> Lock
+        # - Open -> Unlock
+
+        status_icon = icon_unlock; # Default to Unlock (Open)
+
+        if ($4 == "*") {
+             status_icon = icon_check; # Active -> Check
+        } else if ($1 ~ /^WPA/) {
+             status_icon = icon_wifi_secure; # Secure -> Lock
+        }
+        # Else remains Unlock (Open)
+
+        # Layout: [Signal] [Status] SSID (Sig%)
+        # Note: We purposely put two icons on the left as requested.
+        formatted_entry = wifi_signal_icon " " status_icon " " ssid " (" $2 "%)";
+
+        if ($4 == "*") {
+            networks[0] = formatted_entry;
+        } else {
+            networks[x++] = formatted_entry;
+        }
+    }
+    END {
+        if (networks[0] != "") {
+            print networks[0];
+        }
+        for (i = 1; i < x; i++) {
+            print networks[i];
+        }
+    }
+    ')
+	kill_loading_notification
+	echo "$scan_output"
+}
+
+# --- Menu for Available Wi-Fi Networks ---
+menu_available_wifi_networks() {
+	local wifi_list options chosen
+	while true; do
+		wifi_list=$(perform_wifi_scan)
+
+		options="$icon_refresh  $tr_refresh_scan_message\n"
+		if [ -z "$wifi_list" ]; then
+			options+="$icon_wifi_disconnected  $tr_no_wifi_networks_found\n"
+		else
+			options+="$wifi_list\n"
+		fi
+		options+="$icon_close Back" # Always at the bottom
+
+		# Prompt for this menu is just the search icon
+		chosen=$(echo -e "$options" | display_menu 1 "$tr_available_networks_message" "$icon_search")
+
+		# Handle Esc or "Back" selection
+		if [ -z "$chosen" ] || [[ "$chosen" =~ "^$icon_close Back" ]]; then
+			return # Go back to parent menu (menu_wifi)
+		fi
+
+		case "$chosen" in
+		"$icon_refresh  $tr_refresh_scan_message")
+			continue # Restart the while loop to re-run scan and redisplay menu
+			;;
+		"$icon_wifi_disconnected  $tr_no_wifi_networks_found")
+			# Do nothing if "No Wi-Fi networks found" is selected
+			continue
+			;;
+		*)
+			# This block should only be reached when a network is explicitly chosen
+			connect_wifi "$chosen"
+			;;
+		esac
+	done
+}
+
+# --- Main Wi-Fi menu (toggle, hidden, known, available networks) ---
+menu_wifi() {
+	local connection_state options chosen
+	while true; do
+		show_loading_notification "$tr_checking_wifi_status"
+		connection_state=$(nmcli --colors no --get-values WIFI general)
+		local active_connection_info=$(nmcli -t -f NAME,DEVICE connection show --active | grep -E "wl*" | head -n 1)
+		kill_loading_notification
+
+		local status_line=""
+		if [ "$connection_state" = "disabled" ]; then
+			status_line="$icon_wifi_disable $tr_status_message $tr_status_disabled"
+			options="$icon_wifi_enable  $tr_enable_message\n"
+		else
+			if [ -n "$active_connection_info" ]; then
+				local active_ssid=$(echo "$active_connection_info" | cut -d':' -f1)
+				local active_device=$(echo "$active_connection_info" | cut -d':' -f2)
+				status_line="$icon_wifi_full $tr_status_message $tr_status_connected_to '$active_ssid' ($active_device)"
+			else
+				status_line="$icon_wifi_disconnected $tr_status_message $tr_status_disconnected"
+			fi
+			options="$icon_wifi_disable  $tr_disable_message\n"
+			${interfaces[1]:+options+="$icon_interface  $tr_interface_message ${interface_to_use}\n"}
+			# Add "Available Networks" as a sub-menu
+			options+="$icon_wireless  $tr_available_networks_message\n"
+		fi
+
+		local full_options="$status_line\n"
+		full_options+="$options"
+		full_options+="$icon_hotspot  $tr_hotspot_message\n"                  # NEW: Create Hotspot
+		full_options+="$icon_bookmark_saved  $tr_known_connections_message\n" # FIX: Using icon_bookmark_saved
+		full_options+="$icon_hidden_network  $tr_hidden_message\n"            # FIX: Using icon_hidden_network
+		full_options+="$icon_close Back"                                      # Always at the bottom
+
+		chosen=$(echo -e "$full_options" | display_menu 1 "$tr_wifi_menu_prompt" "") # Pass "" for prompt_icon
+
+		# Handle Esc or "Back" selection
+		if [ -z "$chosen" ] || [[ "$chosen" =~ "^$icon_close Back" ]]; then
+			return # Go back to parent menu (main_menu)
+		fi
+
+		case "$chosen" in
+		"$icon_wifi_enable  $tr_enable_message")
+			nmcli radio wifi on
+			;;
+		"$icon_wifi_disable  $tr_disable_message")
+			nmcli radio wifi off
+			;;
+		*"$tr_interface_message"*) select_interface ;;
+		"$icon_wireless  $tr_available_networks_message") menu_available_wifi_networks ;;
+		*"$tr_known_connections_message"*) menu_known_connections "wifi" ;;
+		*"$tr_hidden_message"*) connect_hidden ;;
+		*"$tr_hotspot_message"*) create_hotspot ;;
+		*"$tr_status_message"*)
+			# Show details if connected
+			if [ -n "$active_connection_info" ]; then
+				local active_ssid=$(echo "$active_connection_info" | cut -d':' -f1)
+				local active_device=$(echo "$active_connection_info" | cut -d':' -f2)
+				show_connection_details "$active_ssid" "$active_device"
+			fi
+			;;
+		*)
+			show_message "Invalid option selected: $chosen"
+			;;
+		esac
+	done
+}
+
+select_interface() {
+	local chosen_interface=$( (
+		for ((i = 0; i < ${#interfaces[@]}; i++)); do echo "$icon_interface  ${interfaces[$i]}"; done
+		echo "$icon_close Back"
+	) | display_menu 1 "$tr_select_interface_prompt" "") # Pass "" for prompt_icon
+
+	if [ -z "$chosen_interface" ]; then
+		return
+	elif [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+		return
+	else
+		interface_to_use="${chosen_interface:3}"
+	fi
+}
+
+# Connect to a hidden Wi-Fi network
+connect_hidden() {
+	local wifi_name=$(echo "" | display_menu 5 "$tr_connect_hidden_prompt" "") # Pass "" for prompt_icon
+
+	if [ -z "$wifi_name" ]; then
+		return # User cancelled or pressed back
+	fi
+
+	local wifi_password=$(ask_password)
+
+	if [ -z "$wifi_password" ]; then
+		show_message "Connection cancelled. No password provided."
+		return
+	fi
+
+	show_loading_notification "$tr_connecting_to"
+	if nmcli --wait 15 device wifi connect "$wifi_name" hidden yes password "$wifi_password"; then
+		kill_loading_notification
+		send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$wifi_name'"
+		exit 0
+	else
+		kill_loading_notification
+		send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$wifi_name'"
+	fi
+}
+
+# Connect to a visible Wi-Fi network
+connect_wifi() {
+	local chosen_entry="$1"
+
+	if [[ "$chosen_entry" == *"$icon_check"* ]]; then
+		local ssid_from_active=$(nmcli -t -f active,ssid dev wifi | grep "^yes" | cut -d':' -f2)
+		local active_uuid=$(nmcli -t -f UUID,TYPE,ACTIVE connection show | grep ":802-11-wireless:yes" | cut -d':' -f1 | head -n1)
+
+		if [ -n "$active_uuid" ]; then
+			if [ -z "$ssid_from_active" ]; then
+				local temp_ssid=$(echo "$chosen_entry" | sed -E 's/ \([0-9]+%\).*$//')
+				ssid_from_active=$(echo "$temp_ssid" | sed -E 's/^(๓ฐ„ฌ |๓ฐคซ |๓ฐคช |๓ฐคฉ |๓ฐคจ |๓ฐคง |๓ฐคฆ |๓ฐคฅ |๓ฐคค |๓ฐคฃ |๓ฐคข |๓ฐคก |๓ฐค  )+//' | xargs)
+			fi
+			menu_connection "$ssid_from_active" "$active_uuid"
+			return
+		fi
+	fi
+
+	local temp_ssid=$(echo "$chosen_entry" | sed -E 's/ \([0-9]+%\).*$//')
+	local wifi_ssid=$(echo "$temp_ssid" | sed -E 's/^(๓ฐคจ |๓ฐคฅ |๓ฐคข |๓ฐคฏ |๓ฐคซ |๓ฐคช |๏€ฃ |๏‹ผ |๏’ž )+//' | xargs)
+
+	local is_secure=$(echo "$chosen_entry" | grep -q "$icon_wifi_secure" && echo "yes" || echo "no")
+
+	# 1. Check if already connected
+	local active_ssid=$(nmcli -t -f active,ssid dev wifi | grep "^yes" | cut -d':' -f2)
+	if [ "$wifi_ssid" = "$active_ssid" ]; then
+		local active_uuid=$(nmcli -t -f UUID,TYPE,ACTIVE connection show | grep ":802-11-wireless:yes" | cut -d':' -f1 | head -n1)
+		if [ -n "$active_uuid" ]; then
+			menu_connection "$wifi_ssid" "$active_uuid"
+			return
+		fi
+		show_message "Already connected to $wifi_ssid."
+		return
+	fi
+
+	# 2. Check if we have a saved connection (even if not active)
+	local saved_uuid=""
+	# Use standard fields (UUID, TYPE) that are guaranteed to exist
+	local saved_list=$(nmcli -t -f UUID,TYPE connection show)
+
+	while IFS=: read -r uuid type; do
+		# Check against both known Wi-Fi type strings
+		if [[ "$type" == "802-11-wireless" || "$type" == "wifi" ]]; then
+			# Get the SSID for this connection specifically
+			local ssid_check=$(nmcli -t -f 802-11-wireless.ssid connection show "$uuid" 2>/dev/null)
+			# FIX: output is '802-11-wireless.ssid:SSID', we must strip the prefix
+			ssid_check="${ssid_check#*:}"
+
+			if [ "$ssid_check" = "$wifi_ssid" ]; then
+				saved_uuid="$uuid"
+				break
+			fi
+		fi
+	done <<<"$saved_list"
+
+	if [ -n "$saved_uuid" ]; then
+		menu_connection "$wifi_ssid" "$saved_uuid"
+		return
+	fi
+
+	# 3. New Connection: Ask for password if secure
+	local connection_result
+	if [ "$is_secure" = "yes" ]; then
+		local wifi_password=$(ask_password)
+		if [ -z "$wifi_password" ]; then
+			show_message "Connection cancelled. No password provided."
+			return
+		fi
+
+		show_loading_notification "$tr_connecting_to"
+		nmcli --wait 15 device wifi connect "$wifi_ssid" ifname "$interface_to_use" password "$wifi_password"
+		connection_result=$?
+	else
+		show_loading_notification "$tr_connecting_to"
+		nmcli --wait 15 device wifi connect "$wifi_ssid" ifname "$interface_to_use"
+		connection_result=$?
+	fi
+	kill_loading_notification
+
+	if [ $connection_result -eq 0 ]; then
+		send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$wifi_ssid'"
+		exit 0
+	else
+		send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$wifi_ssid'"
+	fi
+}
+
+# --- IP Configuration Menus ---
+
+# Manage individual IP addresses for a connection
+menu_addresses() {
+	local connection_uuid="$1"
+	local ipv="$2"
+	local -a addresses_list
+	local sure chosen
+
+	while true; do
+		mapfile -t addresses_list < <(nmcli --get-values ipv${ipv}.addresses connection show "$connection_uuid" | sed 's/,/\n/g')
+		local options=$(printf "%s\n" "${addresses_list[@]}")
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$tr_menu_addresses_prompt" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		local found=0
+		for addr in "${addresses_list[@]}"; do
+			if [ "$chosen" = "$addr" ]; then
+				found=1
+				sure=$(echo -e "$icon_check\n$icon_close Back" | display_menu 1 "$tr_menu_addresses_sure_prompt_1 ${chosen}$tr_menu_addresses_sure_prompt_2" "") # Pass "" for prompt_icon
+				if [ -z "$sure" ] || [[ "$sure" =~ ^"$icon_close Back" ]]; then continue; fi                                                                     # User cancelled confirmation
+				if [[ "$sure" =~ ^"$icon_check" ]]; then
+					nmcli connection modify uuid "$connection_uuid" -ipv${ipv}.addresses "$chosen"
+					if [ ${#addresses_list[@]} -eq 1 ]; then
+						nmcli connection modify uuid "$connection_uuid" ipv${ipv}.gateway ''
+						nmcli connection modify uuid "$connection_uuid" ipv${ipv}.method auto
+					fi
+				fi
+				break
+			fi
+		done
+
+		if [ "$found" -eq 0 ]; then
+			nmcli connection modify uuid "$connection_uuid" +ipv${ipv}.addresses "$chosen"
+			nmcli connection modify uuid "$connection_uuid" ipv${ipv}.method manual
+		fi
+	done
+}
+
+# Configure IP method (auto/manual), gateway
+menu_ip_config() {
+	local chosen_connection_name="$1"
+	local connection_uuid="$2"
+	local ipv="$3"
+	local autoip_state autodns_state message new_gateway chosen
+
+	while true; do
+		autoip_state="$([ "$(nmcli --get-values ipv${ipv}.method connection show "$connection_uuid")" = "auto" ] && echo "$icon_on" || echo "$icon_off")"
+
+		local current_gateway=$(nmcli --get-values ipv${ipv}.gateway connection show "$connection_uuid")
+		local current_addresses=$(nmcli --get-values ipv${ipv}.addresses connection show "$connection_uuid" | sed 's/,/\n/g')
+
+		local options=""
+		options+="$icon_auto_ip  $tr_autoip_message  $autoip_state\n"
+
+		if [ "$autoip_state" = "$icon_on" ]; then
+			autodns_state="$([ "$(nmcli --get-values ipv${ipv}.ignore-auto-dns connection show "$connection_uuid")" = "no" ] && echo "$icon_on" || echo "$icon_off")"
+			options+="$icon_auto_dns  $tr_autodns_message  $autodns_state\n"
+		else
+			options+="$icon_address  $tr_address_message: ${current_addresses:-N/A}\n"
+			options+="$icon_gateway  $tr_gateway_message ${current_gateway:-N/A}\n"
+			options+="$icon_gateway  $tr_gateway_message ${current_gateway:-N/A}\n"
+		fi
+		options+="$icon_ipv4_dns  DNS Configuration\n"
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$chosen_connection_name (IPv$ipv)" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		case "$chosen" in
+		*"DNS Configuration"*)
+			menu_dns "$connection_uuid" "$ipv"
+			;;
+		*"$tr_autoip_message"*)
+			if [ "$autoip_state" = "$icon_on" ]; then
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.method manual
+			else
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.method auto
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.gateway ''
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.addresses ''
+			fi
+			;;
+		*"$tr_autodns_message"*)
+			if [ "$autodns_state" = "$icon_on" ]; then
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.ignore-auto-dns yes
+			else
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.ignore-auto-dns no
+			fi
+			;;
+		*"$tr_address_message"*)
+			menu_addresses "$connection_uuid" "$ipv"
+			;;
+		*"$tr_gateway_message"*)
+			new_gateway=$(echo "$icon_close Back" | display_menu 1 "$tr_menu_ip_config_gateway_prompt" "") # Pass "" for prompt_icon
+			if [ -z "$new_gateway" ] || [[ "$new_gateway" =~ ^"$icon_close Back" ]]; then continue; fi
+			nmcli connection modify uuid "$connection_uuid" ipv${ipv}.gateway "$new_gateway"
+			;;
+		esac
+	done
+}
+
+# Configure DNS servers for a connection
+menu_dns() {
+	local connection_uuid="$1"
+	local ipv="$2"
+	local -a dns_list
+	local sure chosen
+
+	while true; do
+		mapfile -t dns_list < <(nmcli --get-values ipv${ipv}.dns connection show "$connection_uuid" | sed 's/,/\n/g')
+		local options=$(printf "%s\n" "${dns_list[@]}")
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$tr_menu_dns_prompt" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		local found=0
+		for dns_entry in "${dns_list[@]}"; do
+			if [ "$chosen" = "$dns_entry" ]; then
+				found=1
+				sure=$(echo -e "$icon_check\n$icon_close Back" | display_menu 1 "$tr_menu_dns_sure_prompt_1 ${chosen}$tr_menu_dns_sure_prompt_2" "") # Pass "" for prompt_icon
+				if [ -z "$sure" ] || [[ "$sure" =~ ^"$icon_close Back" ]]; then continue; fi                                                         # User cancelled confirmation
+				if [[ "$sure" =~ ^"$icon_check" ]]; then
+					nmcli connection modify uuid "$connection_uuid" -ipv${ipv}.dns "$chosen"
+				fi
+				break
+			fi
+		done
+
+		if [ "$found" -eq 0 ]; then
+			nmcli connection modify uuid "$connection_uuid" +ipv${ipv}.dns "$chosen"
+		fi
+	done
+}
+
+forget_connection() {
+	local chosen_connection_name="$1"
+	local connection_uuid="$2"
+	local options="$icon_check $tr_forget_connection_confirm\n$icon_close Back"
+	local sure=$(echo -e "$options" | display_menu 4 "$tr_forget_connection_sure_prompt_1 ${chosen_connection_name}$tr_forget_connection_sure_prompt_2" "")
+
+	if [ -z "$sure" ] || [[ "$sure" =~ ^"$icon_close Back" ]]; then return 1; fi # User cancelled or pressed back
+	if [[ "$sure" =~ ^"$icon_check $tr_forget_connection_confirm" ]]; then
+		nmcli connection delete uuid "$connection_uuid" && return 0
+	fi
+	return 1
+}
+
+rename_connection() {
+	local connection_uuid="$1"
+	local new_name=$(echo "" | display_menu 5 "$tr_rename_connection_prompt" "") # Pass "" for prompt_icon
+
+	if [ -z "$new_name" ]; then return 1; fi # User cancelled or pressed back
+
+	nmcli connection modify uuid "$connection_uuid" connection.id "$new_name"
+	return $?
+}
+
+edit_connection_password() {
+	local connection_uuid="$1"
+	local connection_name="$2"
+
+	# Prompt for new password
+	local pass=$(ask_password "$connection_name")
+
+	if [ -z "$pass" ]; then return 1; fi # Cancelled
+
+	# Update the connection
+	if nmcli connection modify uuid "$connection_uuid" wifi-sec.psk "$pass"; then
+		send_notification "$tr_password_updated" "$tr_notice_connected_body '$connection_name'"
+		return 0
+	else
+		send_notification "$tr_password_update_failed" "$tr_notice_error_body '$connection_name'"
+		return 1
+	fi
+}
+
+# Menu for individual connection settings (Wi-Fi)
+menu_connection() {
+	local chosen_connection_name="$1"
+	local connection_uuid="$2"
+	local autoconnect_state chosen
+
+	while true; do
+		autoconnect_state="$([ "$(nmcli --get-values connection.autoconnect connection show "$connection_uuid")" = "yes" ] && echo "$icon_on" || echo "$icon_off")"
+
+		# Check if this connection is currently active
+		local is_active=$(nmcli -t -f UUID connection show --active | grep -q "$connection_uuid" && echo "yes" || echo "no")
+
+		local options="$icon_automatic  $tr_autoconnect_message  $autoconnect_state\n"
+
+		if [ "$is_active" = "yes" ]; then
+			options+="$icon_wifi_disconnected  $tr_disconnect_message\n"
+		else
+			options+="$icon_connect  $tr_connect_now_message\n"
+		fi
+
+		options+="$icon_ipv4_config  $tr_ipv4_config_message\n"
+		options+="$icon_ipv6_config  $tr_ipv6_config_message\n"
+		options+="$icon_trash  $tr_forget_message\n"
+		options+="$icon_pen  $tr_rename_connection_message\n"
+		options+="$icon_password  $tr_edit_password_message\n" # NEW: Edit Password option
+		# Only show QR code for Wi-Fi connections
+		if [ "$(nmcli -g connection.type connection show "$connection_uuid")" = "802-11-wireless" ]; then
+			options+="$icon_qrcode  $tr_qrcode_message\n"
+		fi
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$chosen_connection_name" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		case "$chosen" in
+		*"$tr_autoconnect_message"*)
+			if [ "$autoconnect_state" = "$icon_on" ]; then
+				nmcli connection modify uuid "$connection_uuid" autoconnect no
+			else
+				nmcli connection modify uuid "$connection_uuid" autoconnect yes
+			fi
+			;;
+		"$icon_connect  $tr_connect_now_message")
+			show_loading_notification "$tr_connecting_to"
+			if nmcli connection up uuid "$connection_uuid"; then
+				kill_loading_notification
+				send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$chosen_connection_name'"
+				exit 0
+			else
+				kill_loading_notification
+				# Smart Recovery: Prompt to update password
+				# Check if user wants to retry with new password
+				local retry_choice=$(echo -e "$icon_check Yes\n$icon_close No" | display_menu 2 "$tr_connection_failed_retry" "")
+				if [[ "$retry_choice" =~ ^"$icon_check" ]]; then
+					if edit_connection_password "$connection_uuid" "$chosen_connection_name"; then
+						# Retry connection
+						show_loading_notification "$tr_connecting_to"
+						if nmcli connection up uuid "$connection_uuid"; then
+							kill_loading_notification
+							send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$chosen_connection_name'"
+							exit 0
+						fi
+						kill_loading_notification
+					fi
+				fi
+				send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$chosen_connection_name'"
+			fi
+			;;
+		"$icon_wifi_disconnected  $tr_disconnect_message")
+			# Disconnect logic
+			show_loading_notification "Disconnecting..."
+			if nmcli connection down uuid "$connection_uuid"; then
+				kill_loading_notification
+				send_notification "Disconnected" "Disconnected from '$chosen_connection_name'"
+				return
+			else
+				kill_loading_notification
+				show_message "Failed to disconnect."
+			fi
+			;;
+		*"$tr_ipv4_config_message"*) menu_ip_config "$chosen_connection_name" "$connection_uuid" "4" ;;
+		*"$tr_ipv6_config_message"*) menu_ip_config "$chosen_connection_name" "$connection_uuid" "6" ;;
+		*"$tr_forget_message"*) forget_connection "$chosen_connection_name" "$connection_uuid" && return ;;
+		*"$tr_rename_connection_message"*)
+			if rename_connection "$connection_uuid"; then
+				show_message "Connection renamed."
+				chosen_connection_name=$(nmcli --get-values connection.id connection show "$connection_uuid")
+			else
+				show_message "Failed to rename connection."
+			fi
+			;;
+		*"$tr_edit_password_message"*)
+			edit_connection_password "$connection_uuid" "$chosen_connection_name"
+			;;
+		*"$tr_qrcode_message"*)
+			local ssid=$(nmcli -g 802-11-wireless.ssid connection show "$connection_uuid")
+			local security=$(nmcli -g 802-11-wireless-security.key-mgmt connection show "$connection_uuid" | sed 's/wpa-psk/WPA/; s/None/nopass/')
+			local password=$(nmcli -s -g 802-11-wireless-security.psk connection show "$connection_uuid")
+			show_qrcode "$ssid" "$security" "$password"
+			;;
+		esac
+	done
+}
+
+# Menu for Wireguard connection settings
+menu_wireguard_connection() {
+	local chosen_connection_name="$1"
+	local connection_uuid="$2"
+	local state autoconnect_state chosen
+
+	while true; do
+		state="$([ "$(nmcli --get-values GENERAL.STATE connection show "$connection_uuid")" = "activated" ] && echo "$icon_on" || echo "$icon_off")"
+		autoconnect_state="$([ "$(nmcli --get-values connection.autoconnect connection show "$connection_uuid")" = "yes" ] && echo "$icon_on" || echo "$icon_off")"
+		local options="$icon_plug  $tr_wireguard_enable_message  $state\n"
+		options+="$icon_automatic  $tr_autoconnect_message  $autoconnect_state\n"
+		options+="$icon_pen  $tr_rename_connection_message\n"
+		options+="$icon_trash  $tr_forget_message\n"
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$chosen_connection_name" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		case "$chosen" in
+		*"$tr_wireguard_enable_message"*)
+			if [ "$state" = "$icon_on" ]; then
+				nmcli connection down uuid "$connection_uuid"
+			else
+				nmcli connection up uuid "$connection_uuid"
+			fi
+			;;
+		*"$tr_autoconnect_message"*)
+			if [ "$autoconnect_state" = "$icon_on" ]; then
+				nmcli connection modify uuid "$connection_uuid" autoconnect no
+			else
+				nmcli connection modify uuid "$connection_uuid" autoconnect yes
+			fi
+			;;
+		*"$tr_rename_connection_message"*)
+			if rename_connection "$connection_uuid"; then
+				show_message "Connection renamed."
+				chosen_connection_name=$(nmcli --get-values connection.id connection show "$connection_uuid")
+			else
+				show_message "Failed to rename connection."
+			fi
+			;;
+		*"$tr_forget_message"*) forget_connection "$chosen_connection_name" "$connection_uuid" && return ;;
+		esac
+	done
+}
+
+# Menu for listing and managing known/saved connections
+menu_known_connections() {
+	local connection_filter="$1"
+	local icon_for_type
+	local menu_type_function
+	local profiles_list_raw chosen
+	local prompt_to_use
+
+	case "$connection_filter" in
+	"wifi")
+		icon_for_type="$icon_wireless"
+		menu_type_function="menu_connection"
+		profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon="$icon_for_type" '$1 ~ /^(wifi|802-11-wireless).*/ {print $2 "\\0" icon "  " $3}')
+		prompt_to_use="$tr_known_connections_message" # Use descriptive text for prompt
+		;;
+	"wireguard")
+		icon_for_type="$icon_wireguard"
+		menu_type_function="menu_wireguard_connection"
+		profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon="$icon_for_type" '$1 == "wireguard" {print $2 "\\0" icon "  " $3}')
+		prompt_to_use="$tr_vpn_menu_prompt" # Use new VPN prompt
+		;;
+	"ethernet")
+		icon_for_type="$icon_ethernet"
+		menu_type_function="menu_connection"
+		profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon="$icon_for_type" '$1 ~ /^(ethernet|802-3-ethernet).*/ {print $2 "\\0" icon "  " $3}')
+		prompt_to_use="$tr_manage_wired_connections"
+		;;
+	*) # All saved connections
+		icon_for_type="$icon_saved"
+		menu_type_function="menu_connection"
+		profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon_saved="$icon_saved" -v icon_wireless="$icon_wireless" -v icon_ethernet="$icon_ethernet" -v icon_vpn_disconnect="$icon_vpn_disconnect" '{
+                icon_local = icon_saved;
+                if ($1 == "wifi" || $1 == "802-11-wireless") icon_local = icon_wireless;
+                else if ($1 == "ethernet" || $1 == "802-3-ethernet") icon_local = icon_ethernet;
+                else if ($1 == "vpn" || $1 == "wireguard") icon_local = icon_vpn_disconnect;
+                print $2 "\\0" icon_local "  " $3 " (" $1 ")"
+            }')
+		prompt_to_use="$tr_saved_connections_menu_prompt" # Use new Saved Connections prompt
+		;;
+	esac
+
+	mapfile -t profiles_list < <(echo "$profiles_list_raw")
+
+	if [ "${#profiles_list[@]}" -eq 0 ] || ([ "${#profiles_list[@]}" -eq 1 ] && [ -z "${profiles_list[0]}" ]); then
+		display_info_message "$tr_no_saved_connections" "$prompt_to_use" # Pass custom prompt
+		return
+	fi
+
+	while true; do
+		local options=$(for i in "${profiles_list[@]}"; do echo -e "$i" | cut --delimiter $'\0' --fields 2; done)
+		options+="\n$icon_close Back" # FIX: Added newline before the Back entry
+
+		# Prompt for this menu is explicitly set by prompt_to_use, no extra icon prepended by display_menu
+		chosen=$(echo -e "$options" | display_menu 1 "$prompt_to_use" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		for i in "${profiles_list[@]}"; do
+			if [ "$chosen" = "$(echo -e "$i" | cut --delimiter $'\0' --fields 2)" ]; then
+				local conn_uuid=$(echo -e "$i" | cut --delimiter $'\0' --fields 1)
+				local conn_name_display=$(echo "$chosen" | sed -E 's/^(๓ฐ‹‹|๓ฐ–ฉ|๓ฐˆ€|๓ฐ–‚) //; s/ \(.+\)$//') # Changed ๓ฐ–ƒ to ๓ฐ–‚
+				eval "$menu_type_function \"$(sed 's/"/\\"/g' <<<"$conn_name_display")\" \"$conn_uuid\""
+				break
+			fi
+		done
+	done
+}
+
+# NEW: Menu for connecting to a specific wired connection
+menu_connect_wired_connection() {
+	local profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon="$icon_ethernet" '$1 ~ /^(ethernet|802-3-ethernet).*/ {print $2 "\\0" icon "  " " " $3}')
+	mapfile -t profiles_list < <(echo "$profiles_list_raw")
+
+	if [ "${#profiles_list[@]}" -eq 0 ] || ([ "${#profiles_list[@]}" -eq 1 ] && [ -z "${profiles_list[0]}" ]); then
+		display_info_message "No saved wired connections to connect to." "$tr_connect_wired_connection" # Pass custom prompt
+		return
+	fi
+
+	local options=$(for i in "${profiles_list[@]}"; do echo -e "$i" | cut --delimiter $'\0' --fields 2; done)
+	options+="\n$icon_close Back"
+
+	# Prompt for this menu is just the search icon
+	local chosen=$(echo -e "$options" | display_menu 1 "$tr_connect_wired_connection" "$icon_search")
+
+	if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+		return # User cancelled or pressed back
+	fi
+
+	local conn_uuid_to_connect=""
+	local conn_name_to_connect=""
+
+	for i in "${profiles_list[@]}"; do
+		if [ "$chosen" = "$(echo -e "$i" | cut --delimiter $'\0' --fields 2)" ]; then
+			conn_uuid_to_connect=$(echo -e "$i" | cut --delimiter $'\0' --fields 1)
+			conn_name_to_connect=$(echo -e "$i" | cut --delimiter $'\0' --fields 2 | sed -E 's/^(๓ฐˆ€) //') # Remove icon
+			break
+		fi
+	done
+
+	if [ -n "$conn_uuid_to_connect" ]; then
+		show_loading_notification "$tr_connecting_to '$conn_name_to_connect'..."
+		if nmcli connection up uuid "$conn_uuid_to_connect"; then
+			kill_loading_notification
+			send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$conn_name_to_connect'"
+			exit 0
+		else
+			kill_loading_notification
+			send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$conn_name_to_connect'"
+		fi
+	else
+		show_message "Could not find connection profile for $conn_name_to_connect." "$tr_connect_wired_connection" # Pass custom prompt
+	fi
+}
+
+# NEW: Wired Menu
+menu_wired() {
+	while true; do
+		local active_wired_conn=$(nmcli -t -f TYPE,DEVICE connection show --active | grep -vE "(wireless|vpn|wireguard)" | head -n 1)
+		local status_line=""
+		if [ -n "$active_wired_conn" ]; then
+			local active_device=$(echo "$active_wired_conn" | cut -d':' -f2)
+			status_line="$icon_ethernet $tr_status_message $tr_status_connected ($active_device)"
+		else
+			status_line="$icon_ethernet $tr_status_message $tr_status_disconnected"
+		fi
+
+		local options="$status_line\n"
+		options+="$icon_connect_wired Connect to Wired Connection\n" # FIX: Using icon_connect_wired
+		options+="$icon_config $tr_manage_wired_connections\n"       # Option to manage profiles
+		options+="$icon_close Back"
+
+		local choice=$(echo -e "$options" | display_menu 1 "$tr_wired_menu_prompt" "") # Pass "" for prompt_icon
+
+		if [ -z "$choice" ] || [[ "$choice" =~ ^"$icon_close Back" ]]; then
+			return # Go back to parent menu (main_menu)
+		fi
+
+		case "$choice" in
+		*"Connect to Wired Connection"*)
+			menu_connect_wired_connection # Call the function to connect to a wired network
+			;;
+		*"$tr_manage_wired_connections"*)
+			menu_known_connections "ethernet" # Reuse existing function for managing wired profiles
+			;;
+		*"$tr_status_message"*) # Do nothing if status line is selected
+			;;
+		*)
+			show_message "Invalid option selected: $choice" "$tr_wired_menu_prompt" # Pass custom prompt
+			;;
+		esac
+	done
+}
+
+# --- Status Functions ---
+
+show_active_connection_details() {
+	local device="$1"
+	local details_raw=$(nmcli --colors no device show "$device")
+
+	# Display the raw list in a filterable menu
+	local prompt_text="$icon_active_details Active Connection Details: $icon_search"
+	echo -e "${details_raw}\n${icon_close} Back" | display_menu 1 "$prompt_text" ""
+}
+
+status_menu() {
+	local options="$icon_active_details Active Connection Details\n$icon_devices All Device Status\n$icon_close Back"
+	# Use new status menu prompt
+	local choice=$(echo -e "$options" | display_menu 1 "$tr_status_menu_prompt" "") # Pass "" for prompt_icon
+
+	if [ -z "$choice" ] || [[ "$choice" =~ ^"$icon_close Back" ]]; then
+		return # User cancelled or pressed back
+	fi
+
+	case "$choice" in
+	*"Active Connection Details")
+		local active_conn_device=$(nmcli -t -f DEVICE connection show --active | head -n 1)
+		if [ -z "$active_conn_device" ]; then
+			display_info_message "$tr_no_active_connection"
+		else
+			show_active_connection_details "$active_conn_device"
+		fi
+		;;
+	*"All Device Status")
+		local device_status=$(nmcli device status)
+		display_info_message "$device_status" "$tr_status_menu_prompt" # Pass custom prompt
+		;;
+	esac
+}
+
+# --- Airplane Mode ---
+toggle_airplane_mode() {
+	local wifi_state=$(nmcli radio wifi)
+	local wwan_state=$(nmcli radio wwan 2>/dev/null || echo "disabled") # WWAN may not exist
+
+	if [ "$wifi_state" = "enabled" ] || [ "$wwan_state" = "enabled" ]; then
+		# Disable all radios
+		nmcli radio wifi off
+		nmcli radio wwan off 2>/dev/null
+		show_message "$tr_airplane_on" "$tr_airplane_mode_message"
+	else
+		# Enable all radios
+		nmcli radio wifi on
+		nmcli radio wwan on 2>/dev/null
+		show_message "$tr_airplane_off" "$tr_airplane_mode_message"
+	fi
+}
+
+# --- QR Code Sharing ---
+show_qrcode() {
+	local ssid="$1"
+	local security="$2"
+	local password="$3"
+
+	if ! command -v qrencode &>/dev/null; then
+		show_message "$tr_qrcode_error" "$tr_qrcode_message"
+		return
+	fi
+
+	if [ -z "$password" ]; then
+		show_message "$tr_qrcode_no_password" "$tr_qrcode_message"
+		return
+	fi
+
+	local qr_string="WIFI:T:${security};S:${ssid};P:${password};;"
+	local qr_file="/tmp/hyprltm-net-qr-${ssid}.png"
+
+	show_loading_notification "$tr_qrcode_generating"
+	qrencode -o "$qr_file" -s 10 -m 2 "$qr_string"
+	kill_loading_notification
+
+	# Display QR code inside Rofi
+	# -i: Enable icons
+	# -theme-str: Override theme to show large icon and no text input
+	# Display QR code inside Rofi
+	# -i: Enable icons
+	# -show-icons: Required to show icons in dmenu mode
+	# -theme-str: Override theme to show large icon and no text input
+
+	# We use the successful configuration from the test script
+	local rofi_override="
+        window { width: 500px; }
+        listview { lines: 1; scrollbar: false; }
+        element { orientation: vertical; padding: 20px; children: [ element-icon, element-text ]; }
+        element-icon { enabled: true; size: 300px; horizontal-align: 0.5; }
+        element-text { horizontal-align: 0.5; }
+        entry { enabled: false; } 
+        inputbar { enabled: false; }
+    "
+
+	# We pass an entry with the icon set to the QR file path
+	# Format: Valid Text \0icon\x1f/path/to/image.png
+	# We use "Scan to Connect" as the text under the QR code
+	echo -e "Scan to Connect\0icon\x1f${qr_file}" |
+		fuzzel --dmenu -i -show-icons -p "$tr_qrcode_message"
+
+	# Cleanup after Rofi closes
+	rm -f "$qr_file" 2>/dev/null
+}
+
+# Get password for a known Wi-Fi connection
+get_wifi_password() {
+	local ssid="$1"
+	local uuid=$(nmcli -t -f NAME,UUID connection show | grep "^${ssid}:" | cut -d':' -f2)
+	if [ -n "$uuid" ]; then
+		nmcli -s -g 802-11-wireless-security.psk connection show "$uuid" 2>/dev/null
+	fi
+}
+
+# --- Hotspot Creation ---
+create_hotspot() {
+	local ssid=$(echo "" | display_menu 5 "$tr_hotspot_ssid_prompt" "")
+	if [ -z "$ssid" ]; then
+		return
+	fi
+
+	local password=$(echo "" | display_menu 3 "$tr_hotspot_password_prompt" "")
+	if [ -z "$password" ] || [ ${#password} -lt 8 ]; then
+		show_message "Password must be at least 8 characters." "$tr_hotspot_message"
+		return
+	fi
+
+	show_loading_notification "$tr_hotspot_creating"
+	if nmcli device wifi hotspot ifname "$interface_to_use" ssid "$ssid" password "$password"; then
+		kill_loading_notification
+		show_message "$tr_hotspot_success\nSSID: $ssid" "$tr_hotspot_message"
+	else
+		kill_loading_notification
+		show_message "$tr_hotspot_error" "$tr_hotspot_message"
+	fi
+}
+
+# --- VPN Functions ---
+
+import_vpn() {
+	local vpn_file_path=$(echo "" | display_menu 5 "$tr_import_vpn_prompt" "")
+
+	if [ -z "$vpn_file_path" ]; then
+		return
+	fi
+
+	if [ ! -f "$vpn_file_path" ]; then
+		send_notification "$tr_notice_import_error_summary" "$tr_notice_file_not_found_body"
+		return
+	fi
+
+	local vpn_type=""
+	case "$vpn_file_path" in
+	*.conf) vpn_type="wireguard" ;;
+	*.ovpn) vpn_type="openvpn" ;;
+	*)
+		send_notification "$tr_notice_import_error_summary" "$tr_notice_unknown_vpn_type_body"
+		return
+		;;
+	esac
+
+	if nmcli connection import type "$vpn_type" file "$vpn_file_path"; then
+		send_notification "$tr_notice_import_success_summary" "$tr_notice_import_success_body"
+	else
+		send_notification "$tr_notice_import_error_summary" "$tr_notice_import_error_body"
+	fi
+}
+
+toggle_vpn_connection() {
+	local uuid="$1"
+	local name="$2"
+	local state="$3"
+
+	if [ "$state" = "activated" ]; then
+		show_loading_notification "$tr_disconnecting_from '$name'..."
+		if nmcli connection down uuid "$uuid"; then
+			kill_loading_notification
+			send_notification "$tr_notice_disconnected_summary" "$tr_notice_disconnected_body '$name'"
+			exit 0
+		else
+			kill_loading_notification
+			send_notification "$tr_notice_error_summary" "$tr_notice_error_disconnect_body '$name'"
+		fi
+	else
+		show_loading_notification "$tr_connecting_to"
+		if nmcli connection up uuid "$uuid"; then
+			kill_loading_notification
+			send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$name'"
+			exit 0
+		else
+			kill_loading_notification
+			send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$name'"
+		fi
+	fi
+}
+
+menu_available_vpns() {
+	while true; do
+		mapfile -t vpn_list < <(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' '$1 == "vpn" || $1 == "wireguard" {print $2 "\\0" $3}')
+
+		if [ "${#vpn_list[@]}" -eq 0 ] || [ -z "${vpn_list[0]}" ]; then
+			display_info_message "$tr_no_configured_vpns" "$tr_available_vpn_profiles_message"
+			return
+		fi
+
+		local options=""
+		for i in "${vpn_list[@]}"; do
+			local uuid=$(echo -e "$i" | awk 'BEGIN{FS="\x00"}{print$1}')
+			local name=$(echo -e "$i" | awk 'BEGIN{FS="\x00"}{print$2}')
+			local state=$(nmcli --get-values GENERAL.STATE connection show uuid "$uuid")
+			local state_icon="$([ "$state" = "activated" ] && echo "$icon_on" || echo "$icon_off")"
+			options+="$state_icon  $name\n"
+		done
+		options+="$icon_close Back"
+
+		local chosen=$(echo -e "$options" | display_menu 1 "$tr_available_vpn_profiles_message" "$icon_search")
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return
+		fi
+
+		local chosen_name=$(echo "$chosen" | sed -E 's/^(๏ˆ…|๏ˆ„)  //')
+		for i in "${vpn_list[@]}"; do
+			local uuid=$(echo -e "$i" | awk 'BEGIN{FS="\x00"}{print$1}')
+			local name=$(echo -e "$i" | awk 'BEGIN{FS="\x00"}{print$2}')
+			if [ "$name" = "$chosen_name" ]; then
+				local state=$(nmcli --get-values GENERAL.STATE connection show uuid "$uuid")
+				toggle_vpn_connection "$uuid" "$name" "$state"
+				break
+			fi
+		done
+	done
+}
+
+vpn_menu() {
+	while true; do
+		local options="$icon_vpn_disconnect  $tr_available_vpn_profiles_message\n"
+		options+="$icon_import  $tr_import_vpn_message\n"
+		options+="$icon_close Back"
+
+		local choice=$(echo -e "$options" | display_menu 1 "$tr_vpn_menu_prompt" "")
+
+		if [ -z "$choice" ] || [[ "$choice" =~ ^"$icon_close Back" ]]; then
+			return
+		fi
+
+		case "$choice" in
+		*"$tr_available_vpn_profiles_message"*)
+			menu_available_vpns
+			;;
+		*"$tr_import_vpn_message"*)
+			import_vpn
+			;;
+		esac
+	done
+}
+
+main_menu() {
+	local options="$icon_wifi_full  Wi-Fi\n"
+	options+="$icon_ethernet  Wired\n"
+	options+="$icon_vpn_disconnect  VPN\n"
+	options+="$icon_bookmark_saved  Saved Connections\n"
+	options+="$icon_status_chart  Status\n"
+	options+="$icon_airplane  $tr_airplane_mode_message\n"
+	options+="$icon_close Exit"
+
+	while true; do                                                                 # Keep looping for the main menu
+		local choice=$(echo -e "$options" | display_menu 1 "$tr_main_menu_prompt" "") # Pass "" for prompt_icon
+
+		if [ -z "$choice" ]; then
+			# If user presses Esc on the main menu, exit the script
+			exit 0
+		fi
+
+		case "$choice" in
+		"$icon_close Exit") exit 0 ;; # Only exit if 'Exit' is explicitly chosen
+		*"Wi-Fi")
+			menu_wifi
+			;;
+		*"Wired")
+			menu_wired # Call the new wired sub-menu
+			;;
+		*"VPN")
+			vpn_menu
+			;;
+		*"Saved Connections")
+			menu_known_connections "all"
+			;;
+		*"Status")
+			status_menu
+			;;
+		*"$tr_airplane_mode_message")
+			toggle_airplane_mode
+			;;
+		esac
+	done # End of main menu while loop
+}
+
+# --- Run ---
+[ -n "${DEBUG:-}" ] && set -x
+main_menu
diff --git a/bin/pasm b/bin/pasm
new file mode 100755
index 0000000..7555167
--- /dev/null
+++ b/bin/pasm
@@ -0,0 +1,71 @@
+#!/bin/sh
+# local is well supported
+# shellcheck disable=3043
+set -eux
+
+# shellcheck disable=2034
+description="rbw-based password manager"
+
+FUZL_PROMPT=pasm
+FUZL_ICON=๓ฐˆฒ
+. libfuzl.sh
+
+main() {
+	rbw unlocked 2>/dev/null ||
+		rbw unlock || exit 1
+
+	local eid
+	eid=$(rbw_select_entry)
+
+	fuzl_action_add "copy" "๓ฐ€„" "copy username/password"
+	rbw_has_totp "$eid" && fuzl_action_add "totp" "๓ฐŽƒ" "copy totp code"
+	fuzl_action_add "url" "๓ฐŒน" "copy url"
+
+	fuzl_action_menu "$eid" || exit 1
+}
+
+#
+# helpers
+#
+
+rbw_select_entry() {
+	rbw list --raw |
+		jq -r '.[] | select(.type=="Login") | [.id,.name,.user,(.uris[0]//"-")]|join("%")' |
+		awk -F% '{print $1 "\t" ($2 " โ†’") "\t" ("๓ฐ€„ " $3) "\t" ("๓ฐŒน " $4)}' |
+		fuzl_menu $FUZL_MENU_FILTER --with-nth='{2}       {3..}' --accept-nth=1 -w 80
+}
+
+rbw_has_totp() {
+	local eid=${1:?missing eid}
+	# can't use fuzl_try here because we need the exit status
+	rbw get -l "$eid" | grep -q totp
+}
+rbw_entry() {
+	local eid=${1:?missing eid}
+	fuzl_try -- rbw get --raw "$eid" | jq -r '.name'
+}
+
+#
+# actions
+#
+
+pasm_copy() {
+	local eid=${1:?missing eid}
+	fuzl_try -- rbw get -f password "$eid" | wl-copy -n
+	fuzl_try -- rbw get -f username "$eid" | wl-copy -p -n
+	fuzl_notification "$(rbw_entry "$eid")" "copied to clipboard"
+}
+
+pasm_totp() {
+	local eid=${1:?missing eid}
+	fuzl_try -- rbw totp "$eid" | wl-copy -n
+	fuzl_notification "$(rbw_entry "$eid")" "copied totp to clipboard"
+}
+
+pasm_url() {
+	local eid=${1:?missing eid}
+	fuzl_try -- rbw get -f uris "$eid" | head -n1 | wl-copy -n
+	fuzl_notification "$(rbw_entry "$eid")" "copied url to clipboard"
+}
+
+main
diff --git a/bin/play-mpv b/bin/play-mpv
new file mode 100755
index 0000000..21f20c6
--- /dev/null
+++ b/bin/play-mpv
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+
+"""
+This script emulates "unique application" functionality. When starting
+playback with this script, it will try to reuse an already running instance of
+mpv (but only if that was started with umpv). Other mpv instances (not started
+by umpv) are ignored, and the script doesn't know about them.
+
+This only takes filenames as arguments. Custom options can't be used; the script
+interprets them as filenames. If mpv is already running, the files passed to
+umpv are appended to mpv's internal playlist. If a file does not exist or is
+otherwise not playable, mpv will skip the playlist entry when attempting to
+play it (from the GUI perspective, it's silently ignored).
+
+If mpv isn't running yet, this script will start mpv and let it control the
+current terminal. It will not write output to stdout/stderr, because this
+will typically just fill ~/.xsession-errors with garbage.
+
+mpv will terminate if there are no more files to play, and running the umpv
+script after that will start a new mpv instance.
+
+Note: you can supply custom mpv path and options with the MPV environment
+      variable. The environment variable will be split on whitespace, and the
+      first item is used as path to mpv binary and the rest is passed as options
+      _if_ the script starts mpv. If mpv is not started by the script (i.e. mpv
+      is already running), this will be ignored.
+"""
+
+import os
+import shlex
+import socket
+import string
+import subprocess
+import sys
+from collections.abc import Iterable
+from typing import BinaryIO
+
+from itertools import tee
+
+def notify(files: Iterable[str]) -> None:
+    for f in files:
+        subprocess.Popen([
+            'fyi',
+            'play-mpv',
+            f,
+        ], start_new_session=True)
+
+def is_url(filename: str) -> bool:
+    parts = filename.split("://", 1)
+    if len(parts) < 2:
+        return False
+    # protocol prefix has no special characters => it's an URL
+    allowed_symbols = string.ascii_letters + string.digits + "_"
+    prefix = parts[0]
+    return all(c in allowed_symbols for c in prefix)
+
+def get_socket_path() -> str:
+    base_dir = (
+        os.getenv("UMPV_SOCKET_DIR") or
+        os.getenv("XDG_RUNTIME_DIR") or
+        os.getenv("HOME") or
+        os.getenv("TMPDIR")
+    )
+
+    if not base_dir:
+        raise Exception("Could not determine a base directory for the socket. "
+                        "Ensure that one of the following environment variables is set: "
+                        "UMPV_SOCKET_DIR, XDG_RUNTIME_DIR, HOME or TMPDIR.")
+
+    return os.path.join(base_dir, ".umpv")
+
+def send_files_to_mpv(conn: socket.socket | BinaryIO, files: Iterable[str]) -> None:
+    notify(tee(files)[0])
+
+    try:
+        send = conn.send if isinstance(conn, socket.socket) else conn.write
+        for f in files:
+            f = f.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
+            send(f'raw loadfile "{f}" append-play\n'.encode())
+
+    except Exception:
+        print("mpv is terminating or the connection was lost.", file=sys.stderr)
+        sys.exit(1)
+
+def start_mpv(files: Iterable[str], socket_path: str) -> None:
+    [files, files_copy] = tee(files, 2)
+    notify(files_copy)
+
+    mpv = "mpv"
+    mpv_command = shlex.split(os.getenv("MPV", mpv))
+    mpv_command.extend([
+        "--profile=builtin-pseudo-gui",
+        f"--input-ipc-server={socket_path}",
+        "--",
+    ])
+    mpv_command.extend(files)
+    print(mpv_command)
+    # subprocess.Popen(mpv_command, start_new_session=True)
+
+def main() -> None:
+    files = (os.path.abspath(f) if not is_url(f) else f for f in sys.argv[1:])
+    socket_path = get_socket_path()
+
+    try:
+        with socket.socket(socket.AF_UNIX) as sock:
+            sock.connect(socket_path)
+            send_files_to_mpv(sock, files)
+    except (FileNotFoundError, ConnectionRefusedError):
+        start_mpv(files, socket_path)
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/play-music b/bin/play-music
new file mode 100755
index 0000000..14e91f1
--- /dev/null
+++ b/bin/play-music
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -eux
+
+music_dir=/run/media/robert/fwdrive/music
+
+mpa() {
+	mpv --profile=mpa \
+		--directory-filter-types=audio \
+		--autocreate-playlist=filter \
+		--wayland-app-id=mpv.play-music \
+		"$@"
+}
+
+case "$1" in
+shuffle)
+	mpa --shuffle $music_dir
+	;;
+select)
+	cd $music_dir
+	subdir=$(find . -type d -printf "%P\n" | shuf | fuzzel -d -w 80)
+	mpa "$subdir"
+	;;
+esac
diff --git a/bin/rsyncez b/bin/rsyncez
new file mode 100755
index 0000000..0275971
--- /dev/null
+++ b/bin/rsyncez
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+set -e
+[ -n "$DEBUG" ] && set -x
+
+[ "$1" = "-mac" ] && shift && mac_args='--rsync-path=/usr/local/bin/rsync'     # force brew version
+[ "$1" = "-mac2" ] && shift && mac_args='--rsync-path=/opt/homebrew/bin/rsync' # force brew version
+exec rsync \
+	--verbose \
+	-hhh \
+	--info=flist2,name,progress \
+	--partial \
+	--rsh="ssh -x -T -o Compression=yes -o RemoteCommand=none" \
+	--archive \
+	--protect-args \
+	${mac_args} \
+	"$@"
+
+# -c chacha20-poly1305@openssh.com
diff --git a/bin/susm b/bin/susm
new file mode 100755
index 0000000..197013a
--- /dev/null
+++ b/bin/susm
@@ -0,0 +1,30 @@
+#!/bin/bash
+set -eu
+
+# shellcheck disable=2034
+description="suspend manager"
+
+FUZL_PROMPT=susm
+. libfuzl.sh
+
+main() {
+	local action
+
+	action=$( (
+		printf '%s\t%s\n' ๓ฐ’ฒ 'suspend'
+		printf '%s\t%s\n' ๓ฐค 'reboot'
+		printf '%s\t%s\n' ๏€‘ 'poweroff'
+		printf '%s\t%s\n' ๓ฐŒพ 'lock'
+	) | fuzl_menu $FUZL_MENU_FILTER --with='{1}  {2}' --accept-nth=2)
+
+	case "$action" in
+	suspend | poweroff | reboot)
+		exec loginctl $action
+		;;
+	lock)
+		exec waylock
+		;;
+	esac
+}
+
+main
diff --git a/bin/svcm b/bin/svcm
new file mode 100755
index 0000000..b7daf39
--- /dev/null
+++ b/bin/svcm
@@ -0,0 +1,108 @@
+#!/bin/bash
+set -eu
+
+# shellcheck disable=2034
+description="service manager"
+
+export FUZL_PROMPT=svcm
+export FUZL_ICON=๓ฐฒฝ
+. libfuzl.sh
+
+SVCM_SCOPE=usr
+
+main() {
+	fuzl_action_add "usr" "๓ฐ€„" "User"
+	fuzl_action_add "sys" "๓ฐŸ€" "System"
+	fuzl_action_menu || return
+
+	select_service
+}
+
+#
+# helpers
+#
+
+select_service() {
+	local rl
+	rl=$(runlevel_current)
+
+	local srv
+	srv=$(get_services |
+		FUZL_PROMPT="$FUZL_PROMPT [$rl]" fuzl_menu $FUZL_MENU_FILTER || true)
+
+	test -n "$srv" || main
+
+	if [[ "$srv" = runlevel:* ]]; then
+		runlevel_start "$(cut -c10- <<<"$srv")"
+		return
+	fi
+
+	fuzl_action_add "restart" "๓ฐœ‰" "Restart"
+	fuzl_action_add "start" "๓ฐˆธ" "Start"
+	fuzl_action_add "stop" "๏„ด" "Stop"
+	fuzl_action_menu "$srv" || select_service
+}
+
+get_services() {
+	case "${SVCM_SCOPE:?missing scope}" in
+	usr)
+		fuzl_try -- rc-service -U -l | sort -u
+		find ~/.config/rc/runlevels/ -mindepth 1 -type d -printf 'runlevel:%P\n'
+		;;
+	sys)
+		fuzl_try -- rc-service -l | sort -u
+		find /etc/runlevels/ -mindepth 1 -type d -printf 'runlevel:%P\n'
+		;;
+	esac
+}
+
+runlevel_current() {
+	case "${SVCM_SCOPE:?missing scope}" in
+	usr) fuzl_try -- rc-status -U -r ;;
+	sys) fuzl_try -- rc-status -r ;;
+	esac
+}
+runlevel_start() {
+	local rl=${1:?missing runlevel}
+	case "${SVCM_SCOPE:?missing scope}" in
+	usr) fuzl_try -- openrc -U "$rl" ;;
+	sys) fuzl_try -- doas -n openrc "$rl" ;;
+	esac
+}
+
+#
+# actions
+#
+
+svcm_usr() {
+	SVCM_SCOPE=usr
+}
+svcm_sys() {
+	SVCM_SCOPE=sys
+}
+
+svcm_start() {
+	local srv=${1:?missing service}
+	case "${SVCM_SCOPE:?missing scope}" in
+	usr) fuzl_try -- rc-service -U "$srv" start ;;
+	sys) fuzl_try -- doas -n rc-service "$srv" start ;;
+	esac
+}
+
+svcm_stop() {
+	local srv=${1:?missing service}
+	case "${SVCM_SCOPE:?missing scope}" in
+	usr) fuzl_try -- rc-service -U "$srv" stop ;;
+	sys) fuzl_try -- doas -n rc-service "$srv" stop ;;
+	esac
+}
+
+svcm_restart() {
+	local srv=${1:?missing service}
+	case "${SVCM_SCOPE:?missing scope}" in
+	usr) fuzl_try -- rc-service -U "$srv" restart ;;
+	sys) fuzl_try -- doas -n rc-service "$srv" stop ;;
+	esac
+}
+
+main
diff --git a/bin/todo b/bin/todo
new file mode 100755
index 0000000..7436779
--- /dev/null
+++ b/bin/todo
@@ -0,0 +1,216 @@
+#!/bin/bash
+set -e
+[ -n "$DEBUG" ] && set -x
+
+_todo_files=("todo.txt" "TODO")
+_todo_file=""
+
+function has_todo_file() {
+	cwd=$(pwd)
+	for f in "${_todo_files[@]}"; do
+		test -f "$cwd/$f" && _todo_file="$cwd/$f" && return 0
+	done
+	return 1
+}
+
+function print_org() {
+	awk '
+function filename() {
+    n = split(FILENAME, a, "/")
+    return a[n]
+}
+function res() {
+    return "\033[0m"
+}
+function bold(s) {
+    return "\033[1m" s "\033[22m"
+}
+function strike(s) {
+    return "\033[9m" s "\033[29m"
+}
+function red(s) {
+    return "\033[31m" s res()
+}
+function green(s) {
+    return "\033[32m" s res()
+}
+function blue(s) {
+    return "\033[34m" s res()
+}
+function magenta(s) {
+    return "\033[35m" s res()
+}
+function osc8(uri, s) {
+    return "\033]8;;" uri "\033\\\\" s "\033]8;;\033\\"
+}
+function link(uri) {
+    return osc8(uri, "\033[2m" uri res())
+}
+{
+    if ($1 ~ /^#/) next
+
+    // support todotxt files
+    if (filename() == "todo.txt") {
+        status = "TODO"
+        if ($1 ~ /^x/) {
+            status = "DONE"
+            $0 = gensub(/^[x] /, "", "1")
+            $0 = strike($0)
+        }
+
+	$0 = gensub(/ (\+[^ ]+)/, bold(blue(" \\1")), "g")
+        $0 = gensub(/ (@[^ ]+)/, bold(blue(" \\1")), "g")
+        $0 = gensub(/(https?:\/\/[^ ]+)/, link("\\1"), "g")
+        tasks[status][i++] = $0
+        next
+    }
+
+    // support TODO files
+    if ($1 ~ /^(-|*)/) {
+        // detect status strings
+        match($0, /\s(TODO|WIP|WAITING|DONE)\s/, arr)
+
+        if (filename() == "TODO" && arr[1] == "") {
+	    $0 = gensub(/(^[\w]*(-|\*))/, "\\1 TODO", "1")
+        }
+
+        $0 = gensub(/(TODO)/, red("\\1"), "1")
+        $0 = gensub(/(WIP)/, blue("\\1"), "1")
+        $0 = gensub(/(DONE)/, green("\\1"), "1")
+        $0 = gensub(/(\?\?\?)/, bold(magenta("\\1")), "1")
+        tasks[arr[1]][i++] = $0
+    }
+}
+END {
+    order[1] = "TODO"
+    order[2] = "WIP"
+    order[3] = "DONE"
+    for (i in order) {
+        for (t in tasks[order[i]]) {
+            print "  " tasks[order[i]][t]
+        }
+    }
+}' $1
+}
+
+function print_hr() {
+	local str cols
+	local start line end
+
+	str="$1"
+	[[ -n $str ]] && str=" $str "
+	cols=$((${COLUMNS:-$(tput cols)} - ${#str} - 3))
+	start=$'\e(0'
+	end=$'\e(B'
+	line="qqq"
+	while ((${#line} < $cols)); do line+=$line; done
+
+	printf "โ•พ"
+	printf "%s%s%s" "$start" "${line:0:$((cols / 2))}" "$end"
+	printf "%s%s%s" "$start" "${line:0:$((cols / 2))}" "$end"
+	printf "%s" "$str"
+	printf "%s%s%s" "$start" "${line:0:1}" "$end"
+	printf "โ•ผ"
+	printf "\n"
+}
+
+function print_shell_hook() {
+	case $1 in
+	bash)
+		cat <<-EOF
+			# Hook to run todo on directory change.
+			__todo_oldpwd="\$(\builtin pwd -L)"
+
+			function __todo_hook() {
+			    \builtin local -r retval="$?"
+			    \builtin local pwd_tmp
+			    pwd_tmp="\$(\builtin pwd -L)"
+			    if [[ \${__todo_oldpwd} != "\${pwd_tmp}" ]]; then
+			        __todo_oldpwd="\${pwd_tmp}"
+			        \command todo
+			    fi
+			    return "\${retval}"
+			}
+
+			# Initialize hook.
+			if [[ \${PROMPT_COMMAND:=} != *'__todo_hook'* ]]; then
+			    PROMPT_COMMAND="__todo_hook;\${PROMPT_COMMAND#;}"
+			fi
+
+			# Run at startup.
+			\command sleep .1 && \command todo
+		EOF
+		;;
+	zsh)
+		cat <<-EOF
+			__todo_oldpwd="$(\builtin pwd -L)"
+			function __todo_hook() {
+				\builtin local -r retval="$?"
+				\builtin local pwd_tmp
+				if [[ \${__todo_oldpwd} != "\${pwd_tmp}" ]]; then
+					__todo_oldpwd="\${pwd_tmp}"
+					\command ${0}
+				fi
+				return "\${retval}"
+			}
+			typeset -ag precmd_functions;
+			if [[ -z \${precmd_functions[(r)__todo_hook]} ]]; then
+				precmd_functions+=__todo_hook;
+			fi
+		EOF
+		;;
+	cd)
+		echo "cd() { builtin cd \"\$@\" && $0; }"
+		;;
+	-h | --help | *)
+		echo "usage: todo hook <integration>"
+		echo ""
+		echo "use like this: eval \$(todo hook <integration>)"
+		echo ""
+		echo "supported integrations:"
+		echo " - zsh: run todo as a precmd hook"
+		echo "   (this can get quite annoying when you're in a direcory that has a todo file)"
+		echo " - cd: run todo as a post-cd hook"
+		echo ""
+		exit 1
+		;;
+	esac
+}
+
+function main() {
+	case "$1" in
+	hook)
+		shift
+		print_shell_hook "$1"
+		;;
+	-h | --help)
+		echo "usage: todo [-r] [hook <integration>]"
+		echo "A quite stupid todo/agenda script that reminds you of things you need to do"
+		echo ""
+		echo " -r, --recursive    walk the file tree, searching for todos"
+		echo " <integration>      shell integration, you should eval this"
+		echo ""
+		exit 0
+		;;
+	-r | --recursive | -R)
+		while read FILE; do
+			print_hr "[$FILE]"
+			print_org $FILE
+		done < <(rg --files ${_todo_files[@]/#/-g })
+		;;
+	*)
+		if [ -f "$1" ]; then
+			print_hr "[$1]"
+			print_org $1
+			exit 0
+		fi
+		if has_todo_file; then
+			print_hr "[$(basename $_todo_file)]"
+			print_org $_todo_file
+			print_hr
+		fi
+		;;
+	esac
+}
+
+main $@
diff --git a/bin/urlopen b/bin/urlopen
new file mode 100755
index 0000000..2b0479c
--- /dev/null
+++ b/bin/urlopen
@@ -0,0 +1,89 @@
+#!/bin/bash
+set -eu
+
+# shellcheck disable=2034
+description="url handler"
+
+FUZL_PROMPT=urlopen
+. libfuzl.sh
+
+main() {
+	case "$#" in
+	1)
+		fuzl_action_add "open" "๏’„" "open"
+		fuzl_action_add "ask" "๓ฐฎซ" "ask"
+		fuzl_action_add "clipboard" "๓ฐ…Œ" "clipboard"
+		fuzl_action_add "primary" "๓ฑЁ" "primary"
+		fuzl_action_menu "$(get_text "$*")"
+		;;
+	2)
+		local action
+
+		action=${1:?missing action}
+		shift
+		eval "urlopen_$action" "'$(get_text "$*")'"
+		;;
+	esac
+}
+
+#
+# helpers
+#
+
+get_text() {
+	local text="$*"
+	[ -z "$text" ] && read -r text
+	printf '%s' "$text"
+}
+
+get_mimetype() {
+	local text=${1:?missing url}
+	case "$text" in
+	http*) printf %s 'x-scheme-handler/http' ;;
+	*) file -bdE --mime-type "$text" ;;
+	esac
+}
+open_default() {
+	local text=${1:?missing url}
+	local application
+	case "$(get_mimetype "$text")" in
+	x-scheme-handler/http*) application=librewolf ;;
+	esac
+	case "$text" in
+	*linear.app*) application=linear ;;
+	*) application=librewolf ;;
+	esac
+	if [ -z "$application" ]; then
+		gio open "$text"
+	else
+		desktop_file=$(rg -Lil -e "$application" -- /usr/share/applications/ /var/lib/flatpak/exports/share/applications/ ~/.local/share/applications/ | tail -1)
+		gio launch "$desktop_file" "$text"
+	fi
+}
+#
+# actions
+#
+
+urlopen_ask() {
+	local text=${1:?missing url}
+	env porta open-uri "$text" ask
+}
+
+urlopen_open() {
+	local text=${1:?missing url}
+	open_default "$text"
+}
+
+urlopen_clipboard() {
+	local text=${1:?missing url}
+	wl-copy -n "$text"
+	# fuzl_notification "copied to clipboard" "$text"
+}
+
+urlopen_primary() {
+	local text=${1:?missing url}
+	wl-copy -np "$text"
+	# fuzl_notification "copied to primary" "$text"
+}
+
+main $@
diff --git a/bin/vpnm b/bin/vpnm
new file mode 100755
index 0000000..f09b41a
--- /dev/null
+++ b/bin/vpnm
@@ -0,0 +1,124 @@
+#!/bin/bash
+set -eu
+
+# shellcheck disable=2034
+description="vpn manager"
+
+FUZL_PROMPT=vpnm
+FUZL_ICON=๓ฐฆ
+. libfuzl.sh
+
+main() {
+	local conn
+
+	conn=$(get_current_connection)
+	[ -n "$conn" ] && fuzl_action_add "down" "๓ฐฆ" "Connected to $conn"
+	fuzl_action_add "networks" "๓ฐพฐ" "Available networks"
+	fuzl_action_add "profile" "๓ฑ—ผ" "Switch profile"
+
+	fuzl_action_menu || return
+}
+
+#
+# helpers
+#
+
+get_emoji_flag() {
+	code=
+	# TODO: calc flag
+	# U\1F1E6 A
+	# U\200D zero-w joiner
+	case "$*" in
+	au-*) code=$'๐Ÿฆ˜' ;;
+	be-*) code=$'๐Ÿ‡ง๐Ÿ‡ช' ;;
+	br-*) code=$'๐Ÿ‡ง๐Ÿ‡ท' ;;
+	ca-*) code=$'๐Ÿ‡จ๐Ÿ‡ฆ' ;;
+	de-*) code=$'๐Ÿ‡ฉ๐Ÿ‡ช' ;;
+	fr-*) code=$'๐Ÿ‡ซ๐Ÿ‡ท' ;;
+	gb-*) code=$'๐Ÿ‡ฌ๐Ÿ‡ง' ;;
+	jp-*) code=$'๐Ÿ‡ฏ๐Ÿ‡ต' ;;
+	no-*) code=$'๐Ÿ‡ณ๐Ÿ‡ด' ;;
+	se-*) code=$'๐Ÿ‡ธ๐Ÿ‡ช' ;;
+	us-*) code=$'๐Ÿ‡บ๐Ÿ‡ธ' ;;
+	*tail2d6ce*) code=$'๓ฐ œ' ;;
+	*) code=$'๐ŸŒ' ;;
+	esac
+	echo -n $code
+}
+
+get_vpn_connections() {
+	(
+		tailscale exit-node list |
+			awk '/.tail2d6ce.ts.net/{print $2, $1}'
+		tailscale exit-node list |
+			awk -F '[ ]{2,}' '/.mullvad.ts.net/{print $2, $1, ($3 "/" $4)}' |
+			sort -u
+	) | while read -r conn ip location; do
+		printf '%s\t' "$ip"
+		printf '%s\t' "$(get_emoji_flag "$conn")"
+		if grep -q 'mullvad.ts.net' <<<"$conn"; then
+			printf '%s\t' "$location"
+		else
+			printf '%s\t' "$conn"
+		fi
+		printf '\n'
+	done
+}
+
+get_current_connection() {
+	tailscale exit-node list |
+		grep -e selected |
+		grep -v -e Any | (
+		awk '/.tail2d6ce.ts.net/{print $2, $1}'
+		awk -F '[ ]{2,}' '/.mullvad.ts.net/{print $2, $1, ($3 "/" $4)}'
+	) | while read -r conn ip location; do
+		printf '%s' "$(get_emoji_flag "$conn")"
+		printf ' '
+		if grep -q 'mullvad.ts.net' <<<"$conn"; then
+			printf '%s' "$location"
+		else
+			printf '%s' "$conn"
+		fi
+	done
+}
+
+vpnup() {
+	fuzl_try -- tailscale set --exit-node=${1:?}
+	fuzl_try -- pkill -RTMIN+2 -x waybar
+}
+
+vpndown() {
+	fuzl_try -- tailscale set --exit-node=
+	fuzl_try -- pkill -RTMIN+2 -x waybar
+}
+
+#
+# actions
+#
+
+vpnm_down() {
+	fuzl_try -- vpndown
+}
+
+vpnm_networks() {
+	ip=$(get_vpn_connections |
+		fuzl_menu $FUZL_MENU_FILTER --with-nth='{2}  {3}' --accept-nth=1)
+
+	[ -n "$ip" ] || main
+	fuzl_try -- vpnup "$ip"
+
+}
+
+vpnm_profile() {
+	profile=$(tailscale switch --list 2>/dev/null |
+		tail -n+2 |
+		awk '{printf "%s\t%s\n", $3, $2}' |
+		fuzl_menu $FUZL_MENU_FILTER --accept-nth=1)
+
+	[ -n "$profile" ] || main
+	fuzl_try -- tailscale switch "$profile"
+	sleep 2
+	fuzl_try -- pkill -SIGRTMIN+2 waybar
+}
+
+main