Feature: Provider logos in VPN notifications

vpn-notify picks the icon by VPN name: Papirus's Mullvad and ProtonVPN
icons, plus Tailscale and UniFi logos shipped in .local/share/vpn-notify.
Anything else keeps the generic lock icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 11:14:46 +01:00
parent 24d7bb280e
commit d10a1aec05
3 changed files with 45 additions and 4 deletions

View File

@@ -13,6 +13,11 @@ RETRIES=5
ICON_ON=/usr/share/icons/hicolor/scalable/apps/nm-vpn-active-lock-symbolic.svg ICON_ON=/usr/share/icons/hicolor/scalable/apps/nm-vpn-active-lock-symbolic.svg
ICON_OFF=/usr/share/icons/hicolor/scalable/apps/nm-vpn-standalone-lock-symbolic.svg ICON_OFF=/usr/share/icons/hicolor/scalable/apps/nm-vpn-standalone-lock-symbolic.svg
# Provider logos: Papirus has Mullvad's and Proton's; Tailscale's and UniFi's
# live in this repo (.local/share/vpn-notify).
PAPIRUS_APPS=/usr/share/icons/Papirus/64x64/apps
LOCAL_ICONS=$HOME/.local/share/vpn-notify
# Friendly name for an interface: NetworkManager connection name if it has one # Friendly name for an interface: NetworkManager connection name if it has one
vpn_name() { vpn_name() {
local name local name
@@ -20,6 +25,19 @@ vpn_name() {
echo "${name:-$1}" echo "${name:-$1}"
} }
# Logo for a VPN by its name (as vpn_name/active_vpns give it), or $2 if
# it's not one of the known providers or the logo is missing
vpn_icon() {
local icon
case "${1,,}" in
*mullvad*) icon=$PAPIRUS_APPS/mullvad-vpn.svg ;;
*proton*) icon=$PAPIRUS_APPS/protonvpn-logo.svg ;;
tailscale) icon=$LOCAL_ICONS/tailscale.svg ;;
*unifi*) icon=$LOCAL_ICONS/unifi.svg ;;
esac
[[ -n $icon && -f $icon ]] && echo "$icon" || echo "$2"
}
active_vpns() { active_vpns() {
ip -d -o link show up 2>/dev/null \ ip -d -o link show up 2>/dev/null \
| awk -F': ' '/ (wireguard|tun) / { sub(/@.*/, "", $2); print $2 }' \ | awk -F': ' '/ (wireguard|tun) / { sub(/@.*/, "", $2); print $2 }' \
@@ -71,14 +89,14 @@ reconnect() {
for ((attempt = 1; attempt <= RETRIES; attempt++)); do for ((attempt = 1; attempt <= RETRIES; attempt++)); do
nmcli connection down "$uuid" >/dev/null 2>&1 nmcli connection down "$uuid" >/dev/null 2>&1
if nmcli -w 20 connection up "$uuid" >/dev/null 2>&1 && sleep 2 && tunnel_works "$uuid"; then if nmcli -w 20 connection up "$uuid" >/dev/null 2>&1 && sleep 2 && tunnel_works "$uuid"; then
notify "🔄 VPN reconnected" "$ICON_ON" \ notify "🔄 VPN reconnected" "$(vpn_icon "$name" "$ICON_ON")" \
"“$name” was reconnected after switching to “$(current_network_name)”." "“$name” was reconnected after switching to “$(current_network_name)”."
return 0 return 0
fi fi
sleep $((attempt * 3)) sleep $((attempt * 3))
done done
notify "⚠️ VPN reconnect failed" "$ICON_OFF" \ notify "⚠️ VPN reconnect failed" "$(vpn_icon "$name" "$ICON_OFF")" \
"Couldn't reconnect “$name” after $RETRIES attempts." -u critical "Couldn't reconnect “$name” after $RETRIES attempts." -u critical
return 1 return 1
} }
@@ -123,11 +141,11 @@ while sleep "$INTERVAL"; do
[[ $cur == "$prev" ]] && continue [[ $cur == "$prev" ]] && continue
while read -r vpn; do while read -r vpn; do
[[ -n $vpn ]] && notify "🔒 VPN connected" "$ICON_ON" "You are now connected to “$vpn”." [[ -n $vpn ]] && notify "🔒 VPN connected" "$(vpn_icon "$vpn" "$ICON_ON")" "You are now connected to “$vpn”."
done < <(comm -13 <(echo "$prev") <(echo "$cur")) done < <(comm -13 <(echo "$prev") <(echo "$cur"))
while read -r vpn; do while read -r vpn; do
[[ -n $vpn ]] && notify "🔓 VPN disconnected" "$ICON_OFF" "“$vpn” has been disconnected." [[ -n $vpn ]] && notify "🔓 VPN disconnected" "$(vpn_icon "$vpn" "$ICON_OFF")" "“$vpn” has been disconnected."
done < <(comm -23 <(echo "$prev") <(echo "$cur")) done < <(comm -23 <(echo "$prev") <(echo "$cur"))
prev=$cur prev=$cur

View File

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect x="2" y="2" width="60" height="60" rx="14" fill="#242424"/>
<g fill="#ffffff" fill-opacity="0.25">
<circle cx="16" cy="16" r="6"/>
<circle cx="32" cy="16" r="6"/>
<circle cx="48" cy="16" r="6"/>
<circle cx="16" cy="48" r="6"/>
<circle cx="48" cy="48" r="6"/>
</g>
<g fill="#ffffff">
<circle cx="16" cy="32" r="6"/>
<circle cx="32" cy="32" r="6"/>
<circle cx="48" cy="32" r="6"/>
<circle cx="32" cy="48" r="6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 536 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect x="2" y="2" width="60" height="60" rx="14" fill="#006fff"/>
<path d="M17 14h9v20a6 6 0 0 0 12 0V26h9v8a15 15 0 0 1-30 0z" fill="#ffffff"/>
<rect x="38" y="19" width="4" height="4" fill="#ffffff"/>
<rect x="42" y="14" width="5" height="5" fill="#ffffff"/>
<rect x="42" y="22" width="5" height="4" fill="#ffffff" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 416 B