From 208e7cb2b20a395ac633b96e0105f50ea6c33438 Mon Sep 17 00:00:00 2001 From: soraefir Date: Fri, 30 Oct 2026 15:50:10 +0100 Subject: [PATCH] Improve eww --- .../home/wayland/apps/eww/bar/css/_net.scss | 1 + .../apps/eww/bar/scripts/net/wifi-connect | 111 ++++++++++++++++++ .../home/wayland/apps/eww/bar/scripts/radio | 36 +++++- .../wayland/apps/eww/bar/windows/net.yuck | 9 +- modules/home/wayland/apps/eww/default.nix | 1 + 5 files changed, 154 insertions(+), 4 deletions(-) create mode 100755 modules/home/wayland/apps/eww/bar/scripts/net/wifi-connect diff --git a/modules/home/wayland/apps/eww/bar/css/_net.scss b/modules/home/wayland/apps/eww/bar/css/_net.scss index 88020f6..4ae5199 100644 --- a/modules/home/wayland/apps/eww/bar/css/_net.scss +++ b/modules/home/wayland/apps/eww/bar/css/_net.scss @@ -23,6 +23,7 @@ @include border-radius; font-size: 1.1em; } +.net-add-btn { margin-right: $popup-scale * 2pt; } // Bluetooth device list .bt-device-row { margin-bottom: $popup-scale * 4pt; } diff --git a/modules/home/wayland/apps/eww/bar/scripts/net/wifi-connect b/modules/home/wayland/apps/eww/bar/scripts/net/wifi-connect new file mode 100755 index 0000000..99df457 --- /dev/null +++ b/modules/home/wayland/apps/eww/bar/scripts/net/wifi-connect @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# Pick a nearby WiFi network in wofi and connect to it through wpa_supplicant +# (userControlled: the control socket is open to wheel). Networks added here +# live in wpa_supplicant's memory only — the config file is a sops secret and +# is never rewritten — so they are gone after a reboot. +detach_self "$@" + +eww close popup 2>/dev/null +eww update active-panel= 2>/dev/null + +iface="" +for s in /run/wpa_supplicant/*; do + [[ -S $s && ${s##*/} != p2p-* ]] && { iface=${s##*/}; break; } +done + +notify() { notify-send -a WiFi -i network-wireless "$@"; } +[[ -n $iface ]] || { notify "WiFi" "wpa_supplicant is not running"; exit 1; } + +wpa() { wpa_cli -g "/run/wpa_supplicant/$iface" "$@"; } +field() { awk -F= -v k="$1" '$1 == k { print substr($0, length(k) + 2); exit }'; } + +signal_icon() { + if (($1 >= -50)); then echo "󰤨" + elif (($1 >= -60)); then echo "󰤥" + elif (($1 >= -70)); then echo "󰤢" + elif (($1 >= -80)); then echo "󰤟" + else echo "󰤯"; fi +} + +# wpa_cli prints SSIDs with printf-style escapes (\xNN, \\, \") +unescape() { printf '%b' "$1"; } + +saved_id() { # SSID → network id + local id s + while IFS=$'\t' read -r id s _; do + [[ $(unescape "$s") == "$1" ]] && { echo "$id"; return; } + done < <(wpa list_networks | tail -n +2) +} + +wpa scan >/dev/null +sleep 2 + +current=$(unescape "$(wpa status | field ssid)") + +declare -A ssid_of flags_of +lines=() +# strongest BSS per SSID, strongest first; hidden networks are skipped +while IFS=$'\t' read -r sig flags raw; do + ssid=$(unescape "$raw") + [[ -n $ssid && $ssid != *$'\n'* ]] || continue + line="$(signal_icon "$sig") $ssid" + [[ $flags == *PSK* || $flags == *SAE* || $flags == *EAP* ]] && line+=" 󰌾" + [[ $ssid == "$current" ]] && line+=" 󰄬" + ssid_of[$line]=$ssid + flags_of[$line]=$flags + lines+=("$line") +done < <(wpa scan_results | awk -F'\t' ' + NR > 1 && $5 != "" { if (!($5 in best) || $3 > best[$5]) { best[$5] = $3; flags[$5] = $4 } } + END { for (s in best) printf "%s\t%s\t%s\n", best[s], flags[s], s }' | sort -t$'\t' -k1,1nr) + +((${#lines[@]})) || { notify "WiFi" "No networks found"; exit 0; } + +choice=$(printf '%s\n' "${lines[@]}" | wofi --dmenu -p "WiFi") +[[ -n $choice && -n ${ssid_of[$choice]+x} ]] || exit 0 +ssid=${ssid_of[$choice]} +flags=${flags_of[$choice]} +[[ $ssid == "$current" ]] && exit 0 + +id=$(saved_id "$ssid") +new=0 +if [[ -z $id ]]; then + [[ $flags == *EAP* ]] && { notify "$ssid" "Enterprise (EAP) networks are not supported"; exit 1; } + id=$(wpa add_network | tail -n 1) + [[ $id =~ ^[0-9]+$ ]] || { notify "$ssid" "Could not add the network"; exit 1; } + new=1 + wpa set_network "$id" ssid "$(printf '%s' "$ssid" | od -An -tx1 | tr -d ' \n')" >/dev/null + if [[ $flags == *PSK* || $flags == *SAE* ]]; then + psk=$(wofi --dmenu --password -p "Password for $ssid" 63)); then + wpa remove_network "$id" >/dev/null + [[ -n $psk ]] && notify "$ssid" "The password must be 8 to 63 characters" + exit 1 + fi + wpa set_network "$id" psk "\"$psk\"" >/dev/null + if [[ $flags == *SAE* ]]; then + wpa set_network "$id" key_mgmt WPA-PSK SAE >/dev/null + wpa set_network "$id" ieee80211w 1 >/dev/null + fi + else + wpa set_network "$id" key_mgmt NONE >/dev/null + fi +fi + +# select_network disables every other network; re-enable them afterwards so +# the saved ones still auto-connect later. +wpa select_network "$id" >/dev/null +for ((i = 0; i < 30; i++)); do + sleep 0.5 + st=$(wpa status) + if [[ $(field wpa_state <<<"$st") == COMPLETED && $(field id <<<"$st") == "$id" ]]; then + wpa enable_network all >/dev/null + notify "Connected" "$ssid" + exit 0 + fi +done + +((new)) && wpa remove_network "$id" >/dev/null +wpa enable_network all >/dev/null +wpa reassociate >/dev/null +notify "Could not connect to $ssid" "$( ((new)) && echo "Wrong password?" || echo "Timed out")" +exit 1 diff --git a/modules/home/wayland/apps/eww/bar/scripts/radio b/modules/home/wayland/apps/eww/bar/scripts/radio index e44d9d8..ce02aef 100755 --- a/modules/home/wayland/apps/eww/bar/scripts/radio +++ b/modules/home/wayland/apps/eww/bar/scripts/radio @@ -15,8 +15,9 @@ api="https://www.radiorecord.ru/api" unit=eww-radio state_dir=${XDG_STATE_HOME:-$HOME/.local/state}/eww cache_dir=${XDG_CACHE_HOME:-$HOME/.cache}/eww +img_dir=$cache_dir/radio-img id_file=$state_dir/radio-station -mkdir -p "$state_dir" "$cache_dir" +mkdir -p "$state_dir" "$cache_dir" "$img_dir" station_ids='[507,522,523,536,537,42532,42602]' # Extra stations: {id (unique integer >= 1000000), title, stream_hls, icon_fill_white ("" for none)} @@ -47,6 +48,32 @@ stations() { printf '%s\n' "$out" } +# GTK loads CSS url() images synchronously on the UI thread: a remote URL +# freezes the whole daemon until the download finishes (or times out). eww +# only ever gets file:// URIs of cached copies. +local_img() { # URL → file:// URI, "" if unavailable + local url=$1 f + case $url in + file://*) printf '%s' "$url"; return ;; + /*) printf 'file://%s' "$url"; return ;; + http://* | https://*) ;; + *) return ;; + esac + f=$img_dir/$(cksum <<<"$url" | cut -d' ' -f1) + if [[ ! -s $f ]]; then + curl -sfL --max-time 5 -o "$f.tmp" "$url" 2>/dev/null && mv "$f.tmp" "$f" || { rm -f "$f.tmp"; return; } + fi + printf 'file://%s' "$f" +} + +localize_stations() { + local url map='{}' + while IFS= read -r url; do + map=$(jq -c --arg k "$url" --arg v "$(local_img "$url")" '.[$k] = $v' <<<"$map") + done < <(jq -r '.[].icon_fill_white // empty' <<<"$STATIONS" | sort -u) + STATIONS=$(jq -c --argjson m "$map" 'map(.icon_fill_white = ($m[.icon_fill_white // ""] // ""))' <<<"$STATIONS") +} + station() { # ID FIELD jq -r --argjson id "${1:-0}" --arg f "$2" 'first(.[] | select(.id == $id) | .[$f]) // empty' <<<"$STATIONS" } @@ -141,7 +168,8 @@ song_playing() { # ID → SONG from the station history (radiorecord) or the tit if [[ $(station "$id" radiorecord) == true ]]; then fetched=$(curl -sf --compressed --max-time 8 "$api/station/history/?id=$id" 2>/dev/null \ | jq -c '.result.history[0] | {artist: (.artist // ""), song: (.song // ""), image600: (.image600 // "")}' 2>/dev/null) - [[ -n $fetched && $fetched != null ]] && SONG=$fetched + [[ -n $fetched && $fetched != null ]] \ + && SONG=$(jq -c --arg i "$(local_img "$(jq -r .image600 <<<"$fetched")")" '.image600 = $i' <<<"$fetched") else SONG=$(jq -cn --arg t "$(station "$id" title)" '{artist: "", song: $t, image600: ""}') fi @@ -159,7 +187,7 @@ media_event() { if [[ -z $player ]]; then MEDIA=$default_media MEDIA_PLAYER="" elif [[ $player != mpv* ]]; then # the radio and jellyfin are mpv, not "external" - MEDIA=$(media_json "$player" "$status" "$artist" "$title" "$art") + MEDIA=$(media_json "$player" "$status" "$artist" "$title" "$(local_img "$art")") MEDIA_PLAYER=$player fi } @@ -206,7 +234,9 @@ refresh() { } status_loop() { + find "$img_dir" -type f -mtime +7 -delete 2>/dev/null STATIONS=$(stations) + localize_stations SONG=$default_song MEDIA=$default_media MEDIA_PLAYER="" PLAYING="" LAST_SONG=0 LAST_VOL="" refresh mux_init diff --git a/modules/home/wayland/apps/eww/bar/windows/net.yuck b/modules/home/wayland/apps/eww/bar/windows/net.yuck index 10492a5..27adaf7 100644 --- a/modules/home/wayland/apps/eww/bar/windows/net.yuck +++ b/modules/home/wayland/apps/eww/bar/windows/net.yuck @@ -10,6 +10,7 @@ (box :orientation "h" :space-evenly false :valign "center" :class "sys-section-header" (box :class "section-accent ${accent}") (label :class "sys-label" :text title :hexpand true :halign "start") + (children) (button :class "net-toggle-btn ${enabled ? 'net-toggle-on' : 'net-toggle-off'}" :onclick onclick @@ -23,7 +24,13 @@ :title "WiFi" :accent "wifi-accent" :enabled {net.wifi.enabled} :onclick {net.wifi.enabled ? "rfkill block wlan" : "rfkill unblock wlan"} - :icon-on "󰤨" :icon-off "󰖪") + :icon-on "󰤨" :icon-off "󰖪" + (button + :class "net-toggle-btn net-toggle-on net-add-btn" + :visible {net.wifi.enabled} + :onclick "scripts/net/wifi-connect" + :tooltip "Connect to a network" + (label :text "󰐕"))) (box :orientation "v" :space-evenly false :visible {net.wifi.connected} (netinfo-row :label "SSID" :value {net.wifi.ssid}) diff --git a/modules/home/wayland/apps/eww/default.nix b/modules/home/wayland/apps/eww/default.nix index 866debc..9c33b92 100755 --- a/modules/home/wayland/apps/eww/default.nix +++ b/modules/home/wayland/apps/eww/default.nix @@ -21,6 +21,7 @@ let "scripts/net/bt" = mkScript "bt" ./bar/scripts/net/bt [ jq glib ]; "scripts/net/bt-toggle" = mkScript "bt-toggle" ./bar/scripts/net/bt-toggle [ bluez ]; "scripts/net/wg-toggle" = mkScript "wg-toggle" ./bar/scripts/net/wg-toggle [ ]; + "scripts/net/wifi-connect" = mkScript "wifi-connect" ./bar/scripts/net/wifi-connect [ eww wpa_supplicant wofi libnotify ]; "scripts/sys/gpu" = mkScript "gpu" ./bar/scripts/sys/gpu [ custom.amdgpu_top jq ]; "scripts/sys/memory" = mkScript "memory" ./bar/scripts/sys/memory [ ]; "scripts/sys/battery" = mkScript "battery" ./bar/scripts/sys/battery [ ];