chore: clean up config
6ff7b44a
26 file(s) · +17 −1192
| 28 | 28 | ||
| 29 | 29 | -- Spawn one of those scripts. They resolve their own siblings relative to |
|
| 30 | 30 | -- themselves, so an absolute path is all they need — PATH is not involved. |
|
| 31 | - | local function script(name) |
|
| 32 | - | return oxwm.spawn({ scripts .. "/" .. name }) |
|
| 31 | + | -- Extra arguments are passed straight through: script("txtcliphist.sh", "sel"). |
|
| 32 | + | local function script(name, ...) |
|
| 33 | + | local cmd = { scripts .. "/" .. name } |
|
| 34 | + | for _, arg in ipairs({ ... }) do |
|
| 35 | + | cmd[#cmd + 1] = arg |
|
| 36 | + | end |
|
| 37 | + | return oxwm.spawn(cmd) |
|
| 33 | 38 | end |
|
| 34 | 39 | ||
| 35 | 40 | -- Color palette - customize these to match your theme |
|
| 283 | 288 | -- Straight to the wifi picker, skipping the menu |
|
| 284 | 289 | oxwm.key.bind({ modkey, "Control", "Shift" }, "N", script("dm-wifi-connect")) |
|
| 285 | 290 | ||
| 291 | + | -- Clipboard history, kept as plain text in ~/.cache/cliphist. |
|
| 292 | + | -- Mod+Shift+V pushes the current primary selection (whatever is highlighted) |
|
| 293 | + | -- into the clipboard and appends it to the history file; Mod+V picks an |
|
| 294 | + | -- earlier entry back out through dmenu and recopies it. |
|
| 295 | + | oxwm.key.bind({ modkey }, "P", script("txtcliphist.sh", "add")) |
|
| 296 | + | oxwm.key.bind({ modkey, "Shift" }, "P", script("txtcliphist.sh", "sel")) |
|
| 297 | + | ||
| 286 | 298 | -- Keybind overlay - Shows important keybindings on screen |
|
| 287 | 299 | oxwm.key.bind({ modkey, "Shift" }, "Slash", oxwm.show_keybinds()) |
|
| 288 | 300 | ||
| 292 | 304 | ||
| 293 | 305 | -- Layout management |
|
| 294 | 306 | oxwm.key.bind({ modkey }, "F", oxwm.layout.set("normie")) |
|
| 295 | - | oxwm.key.bind({ modkey }, "C", oxwm.layout.set("tiling")) |
|
| 307 | + | --oxwm.key.bind({ modkey }, "C", oxwm.layout.set("tiling")) |
|
| 296 | 308 | -- Cycle through layouts |
|
| 297 | 309 | oxwm.key.bind({ modkey }, "N", oxwm.layout.cycle()) |
|
| 298 | 310 | ||
| 304 | 316 | -- Enable tiled resize mode: Mod+RMB drag adjusts mfact instead of floating |
|
| 305 | 317 | -- oxwm.tiled_resize_mode(true) |
|
| 306 | 318 | -- Increment/Decrement number of master windows |
|
| 307 | - | oxwm.key.bind({ modkey }, "I", oxwm.inc_num_master(1)) |
|
| 308 | - | oxwm.key.bind({ modkey }, "P", oxwm.inc_num_master(-1)) |
|
| 319 | + | --oxwm.key.bind({ modkey }, "I", oxwm.inc_num_master(1)) |
|
| 320 | + | --oxwm.key.bind({ modkey }, "P", oxwm.inc_num_master(-1)) |
|
| 309 | 321 | ||
| 310 | 322 | -- Gaps toggle |
|
| 311 | 323 | oxwm.key.bind({ modkey }, "A", oxwm.toggle_gaps()) |
|
| 1 | - | # backend = "glx"; |
|
| 2 | - | # glx-copy-from-front = false; |
|
| 3 | - | # |
|
| 4 | - | # active-opacity = 1; |
|
| 5 | - | # inactive-opacity = 1; |
|
| 6 | - | # frame-opacity = 1; |
|
| 7 | - | # inactive-opacity-override = false; |
|
| 8 | - | # blur-background = true; |
|
| 9 | - | # blur-background-exclude = [ |
|
| 10 | - | # "window_type = 'dock'", |
|
| 11 | - | # ]; |
|
| 12 | - | # |
|
| 13 | - | # blur-method = "dual_kawase"; |
|
| 14 | - | # blur-strength = 8; |
|
| 15 | - | ||
| 16 | - | fading = true; |
|
| 17 | - | fade-delta = 2; |
|
| 18 | - | no-fading-openclose = false; |
|
| 19 | - | ||
| 20 | - | fade-exclude = [ ]; |
|
| 21 | - | ||
| 22 | - | wintypes: |
|
| 23 | - | { |
|
| 24 | - | dock = { |
|
| 25 | - | shadow = false; |
|
| 26 | - | }; |
|
| 27 | - | }; |
|
| 28 | - | ||
| 29 | - | # opacity-rule = [ |
|
| 30 | - | # "90:class_g = 'URxvt'", |
|
| 31 | - | # "90:class_g = 'Alacritty' && focused", |
|
| 32 | - | # "70:class_g = 'Alacritty' && !focused" |
|
| 33 | - | # ]; |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: wireplumber (wpctl), gum |
|
| 3 | - | # One menu over the vol-* / audio-output scripts. |
|
| 4 | - | set -u |
|
| 5 | - | ||
| 6 | - | DIR=$(dirname "$0") |
|
| 7 | - | ||
| 8 | - | CHOICE=$(gum choose \ |
|
| 9 | - | "Volume up" \ |
|
| 10 | - | "Volume down" \ |
|
| 11 | - | "Set volume" \ |
|
| 12 | - | "Toggle mute" \ |
|
| 13 | - | "Toggle mic mute" \ |
|
| 14 | - | "Select output" \ |
|
| 15 | - | "Show status" \ |
|
| 16 | - | "Quit") || exit 0 |
|
| 17 | - | ||
| 18 | - | case "$CHOICE" in |
|
| 19 | - | "Volume up") "$DIR/vol-up" ;; |
|
| 20 | - | "Volume down") "$DIR/vol-down" ;; |
|
| 21 | - | "Set volume") |
|
| 22 | - | PCT=$(gum input --placeholder "0-100" --prompt "Volume % ") || exit 0 |
|
| 23 | - | case "$PCT" in |
|
| 24 | - | ""|*[!0-9]*) gum style --foreground 214 "Not a number: $PCT"; exit 1 ;; |
|
| 25 | - | esac |
|
| 26 | - | [ "$PCT" -gt 100 ] && PCT=100 |
|
| 27 | - | wpctl set-volume @DEFAULT_AUDIO_SINK@ "${PCT}%" && "$DIR/vol-status" -n |
|
| 28 | - | ;; |
|
| 29 | - | "Toggle mute") "$DIR/vol-mute" ;; |
|
| 30 | - | "Toggle mic mute") "$DIR/vol-mute" -s ;; |
|
| 31 | - | "Select output") "$DIR/audio-output" ;; |
|
| 32 | - | "Show status") |
|
| 33 | - | gum style --border normal --padding "1 2" --border-foreground 212 \ |
|
| 34 | - | "Volume: $("$DIR/vol-status")" \ |
|
| 35 | - | "$("$DIR/audio-output" --list)" |
|
| 36 | - | ;; |
|
| 37 | - | *) exit 0 ;; |
|
| 38 | - | esac |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: wireplumber (wpctl), gum |
|
| 3 | - | # Pick which sink audio plays out of. |
|
| 4 | - | # audio-output interactive picker |
|
| 5 | - | # audio-output --list print sinks (* marks the current default) |
|
| 6 | - | # audio-output --auto clear the pin, let wireplumber pick by priority |
|
| 7 | - | # audio-output ID|NAME select by wpctl id, or by a substring of the name |
|
| 8 | - | set -u |
|
| 9 | - | ||
| 10 | - | # wpctl has no machine-readable listing, so carve the Sinks block out of `status`. |
|
| 11 | - | # Output: "<*|space>\t<id>\t<name>" |
|
| 12 | - | sinks() { |
|
| 13 | - | wpctl status 2>/dev/null | awk ' |
|
| 14 | - | /^Audio/ { audio = 1; next } |
|
| 15 | - | /^[A-Z]/ { audio = 0 } |
|
| 16 | - | audio && /Sinks:/ && !/endpoints:/ { insec = 1; next } |
|
| 17 | - | audio && /Devices:|Sources:|Streams:|endpoints:|Filters:/ { insec = 0; next } |
|
| 18 | - | insec { |
|
| 19 | - | line = $0 |
|
| 20 | - | prefix = line; sub(/[0-9].*$/, "", prefix) |
|
| 21 | - | def = (prefix ~ /\*/) ? "*" : " " |
|
| 22 | - | sub(/^[^0-9]*/, "", line) |
|
| 23 | - | if (line !~ /^[0-9]+\./) next |
|
| 24 | - | id = line; sub(/\..*$/, "", id) |
|
| 25 | - | name = line; sub(/^[0-9]+\.[[:space:]]*/, "", name) |
|
| 26 | - | sub(/[[:space:]]*\[vol:.*$/, "", name) |
|
| 27 | - | sub(/[[:space:]]+$/, "", name) |
|
| 28 | - | print def "\t" id "\t" name |
|
| 29 | - | } |
|
| 30 | - | ' |
|
| 31 | - | } |
|
| 32 | - | ||
| 33 | - | name_of() { |
|
| 34 | - | printf '%s\n' "$SINKS" | awk -F'\t' -v id="$1" '$2 == id { print $3; exit }' |
|
| 35 | - | } |
|
| 36 | - | ||
| 37 | - | SINKS=$(sinks) |
|
| 38 | - | if [ -z "$SINKS" ]; then |
|
| 39 | - | echo "audio-output: no sinks found (is pipewire/wireplumber running?)" >&2 |
|
| 40 | - | exit 1 |
|
| 41 | - | fi |
|
| 42 | - | ||
| 43 | - | use_auto() { |
|
| 44 | - | wpctl clear-default || exit 1 |
|
| 45 | - | gum style --foreground 212 "Output: automatic (wireplumber picks)" |
|
| 46 | - | exit 0 |
|
| 47 | - | } |
|
| 48 | - | ||
| 49 | - | use_sink() { |
|
| 50 | - | wpctl set-default "$1" || exit 1 |
|
| 51 | - | gum style --foreground 212 "Output: $(name_of "$1")" |
|
| 52 | - | exit 0 |
|
| 53 | - | } |
|
| 54 | - | ||
| 55 | - | case "${1:-}" in |
|
| 56 | - | --list|-l) |
|
| 57 | - | printf '%s\n' "$SINKS" | awk -F'\t' '{ printf "%s %-6s %s\n", $1, $2, $3 }' |
|
| 58 | - | exit 0 |
|
| 59 | - | ;; |
|
| 60 | - | --auto|-a) use_auto ;; |
|
| 61 | - | "") ;; |
|
| 62 | - | *) |
|
| 63 | - | ID=$(printf '%s\n' "$SINKS" | awk -F'\t' -v q="$1" ' |
|
| 64 | - | $2 == q { print $2; found = 1; exit } |
|
| 65 | - | index(tolower($3), tolower(q)) { print $2; found = 1; exit }') |
|
| 66 | - | if [ -z "$ID" ]; then |
|
| 67 | - | echo "audio-output: no sink matching '$1'" >&2 |
|
| 68 | - | exit 1 |
|
| 69 | - | fi |
|
| 70 | - | use_sink "$ID" |
|
| 71 | - | ;; |
|
| 72 | - | esac |
|
| 73 | - | ||
| 74 | - | CHOICE=$(printf '%s\n' "$SINKS" \ |
|
| 75 | - | | awk -F'\t' '{ printf "%s %-6s %s\n", $1, $2, $3 }' \ |
|
| 76 | - | | { cat; echo " auto (let wireplumber choose)"; } \ |
|
| 77 | - | | gum choose --header "Select audio output") || exit 0 |
|
| 78 | - | ||
| 79 | - | [ -n "$CHOICE" ] || exit 0 |
|
| 80 | - | ||
| 81 | - | ID=$(printf '%s\n' "$CHOICE" | awk '{ print $2 }') |
|
| 82 | - | [ "$ID" = "auto" ] && use_auto |
|
| 83 | - | use_sink "$ID" |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: dmenu, libnotify, ifupdown-ng, openrc, doas |
|
| 3 | - | # dmenu counterpart of eth0-connect. The confirm prompt runs as the calling |
|
| 4 | - | # user; only the switch itself is handed to doas. |
|
| 5 | - | set -u |
|
| 6 | - | ||
| 7 | - | IFACE="${1:-eth0}" |
|
| 8 | - | WIFI="${2:-wlan0}" |
|
| 9 | - | NOTIFY_ID=7303 |
|
| 10 | - | ||
| 11 | - | notify() { |
|
| 12 | - | if command -v dunstify >/dev/null 2>&1; then |
|
| 13 | - | dunstify -r "$NOTIFY_ID" ${2:+-u "$2"} "Network" "$1" |
|
| 14 | - | else |
|
| 15 | - | notify-send ${2:+-u "$2"} "Network" "$1" |
|
| 16 | - | fi |
|
| 17 | - | } |
|
| 18 | - | ||
| 19 | - | fail() { notify "$1" critical; exit 1; } |
|
| 20 | - | ||
| 21 | - | doas -n true 2>/dev/null || fail "doas needs a password; add a nopass rule for this user." |
|
| 22 | - | ||
| 23 | - | [ "$(printf 'No\nYes\n' | dmenu -c -i -l 2 -p "Switch to $IFACE and disable $WIFI?")" = "Yes" ] || exit 0 |
|
| 24 | - | ||
| 25 | - | notify " Switching to wired..." |
|
| 26 | - | ||
| 27 | - | doas sh -c ' |
|
| 28 | - | ifdown "$2" 2>/dev/null |
|
| 29 | - | ip link set "$2" down 2>/dev/null |
|
| 30 | - | # Same reason as dm-wifi-disable: the supervisor respawns the daemon |
|
| 31 | - | # after a bare kill, so stop the service instead of pkill-ing it. |
|
| 32 | - | rc-service -s -q wpa_supplicant stop 2>/dev/null |
|
| 33 | - | ifup "$1" |
|
| 34 | - | ' _ "$IFACE" "$WIFI" |
|
| 35 | - | ||
| 36 | - | if ip addr show "$IFACE" | grep -q "inet "; then |
|
| 37 | - | IP=$(ip -4 addr show "$IFACE" | awk '/inet /{print $2; exit}') |
|
| 38 | - | notify " $IFACE up — $IP" |
|
| 39 | - | else |
|
| 40 | - | fail " $IFACE failed to come up" |
|
| 41 | - | fi |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: dmenu, libnotify, ifupdown-ng, doas |
|
| 3 | - | # dmenu counterpart of eth0-disable. |
|
| 4 | - | set -u |
|
| 5 | - | ||
| 6 | - | IFACE="${1:-eth0}" |
|
| 7 | - | NOTIFY_ID=7304 |
|
| 8 | - | ||
| 9 | - | notify() { |
|
| 10 | - | if command -v dunstify >/dev/null 2>&1; then |
|
| 11 | - | dunstify -r "$NOTIFY_ID" ${2:+-u "$2"} "Network" "$1" |
|
| 12 | - | else |
|
| 13 | - | notify-send ${2:+-u "$2"} "Network" "$1" |
|
| 14 | - | fi |
|
| 15 | - | } |
|
| 16 | - | ||
| 17 | - | fail() { notify "$1" critical; exit 1; } |
|
| 18 | - | ||
| 19 | - | doas -n true 2>/dev/null || fail "doas needs a password; add a nopass rule for this user." |
|
| 20 | - | ||
| 21 | - | [ "$(printf 'No\nYes\n' | dmenu -c -i -l 2 -p "Disable $IFACE?")" = "Yes" ] || exit 0 |
|
| 22 | - | ||
| 23 | - | notify " Disabling $IFACE..." |
|
| 24 | - | ||
| 25 | - | doas sh -c ' |
|
| 26 | - | ifdown "$1" 2>/dev/null |
|
| 27 | - | ip link set "$1" down |
|
| 28 | - | ' _ "$IFACE" |
|
| 29 | - | ||
| 30 | - | notify " $IFACE disabled" |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: dmenu, libnotify |
|
| 3 | - | # dmenu counterpart of net-menu. Everything here runs as the calling user; |
|
| 4 | - | # the individual scripts escalate with doas only for the parts that need it. |
|
| 5 | - | set -u |
|
| 6 | - | ||
| 7 | - | # Launched from a keybinding, PATH may not include this directory — resolve |
|
| 8 | - | # the sibling scripts relative to this one instead. |
|
| 9 | - | DIR=$(dirname "$0") |
|
| 10 | - | ||
| 11 | - | menu() { dmenu -c -i "$@"; } |
|
| 12 | - | ||
| 13 | - | # BusyBox's `ip` has no `-br`, so build the brief view by hand. Each line is |
|
| 14 | - | # prefixed with a Nerd Font icon for what that interface is and how it is |
|
| 15 | - | # doing: // wireless connected/no lease/down, / wired up/down. |
|
| 16 | - | net_status() { |
|
| 17 | - | for path in /sys/class/net/*; do |
|
| 18 | - | iface=${path##*/} |
|
| 19 | - | [ "$iface" = "lo" ] && continue |
|
| 20 | - | ||
| 21 | - | state=$(cat "$path/operstate" 2>/dev/null || echo unknown) |
|
| 22 | - | addrs=$(ip -4 addr show "$iface" 2>/dev/null | |
|
| 23 | - | awk '/inet /{ printf "%s%s", (n++ ? " " : ""), $2 }') |
|
| 24 | - | ||
| 25 | - | # Container veths and other virtual links with no address are noise; |
|
| 26 | - | # a virtual interface that does carry one (tailscale0, docker0) is not. |
|
| 27 | - | [ -z "$addrs" ] && [ ! -e "$path/device" ] && continue |
|
| 28 | - | ||
| 29 | - | # Tunnels sit at operstate "unknown" for their whole life, so an |
|
| 30 | - | # address is what actually says the link is carrying traffic. |
|
| 31 | - | up=0 |
|
| 32 | - | case "$state" in |
|
| 33 | - | up) up=1 ;; |
|
| 34 | - | unknown) [ -n "$addrs" ] && up=1 ;; |
|
| 35 | - | esac |
|
| 36 | - | ||
| 37 | - | if [ -d "$path/wireless" ] || [ -e "$path/phy80211" ]; then |
|
| 38 | - | if [ "$up" -eq 0 ]; then icon="" |
|
| 39 | - | elif [ -z "$addrs" ]; then icon="" |
|
| 40 | - | else icon="" |
|
| 41 | - | fi |
|
| 42 | - | else |
|
| 43 | - | if [ "$up" -eq 1 ]; then icon=""; else icon=""; fi |
|
| 44 | - | fi |
|
| 45 | - | ||
| 46 | - | printf '%s %-12s %-8s %s\n' "$icon" "$iface" "$state" "$addrs" |
|
| 47 | - | done |
|
| 48 | - | } |
|
| 49 | - | ||
| 50 | - | CHOICE=$(printf '%s\n' \ |
|
| 51 | - | "Connect to WiFi" \ |
|
| 52 | - | "Disable WiFi" \ |
|
| 53 | - | "Connect eth0" \ |
|
| 54 | - | "Disable eth0" \ |
|
| 55 | - | "Show status" \ |
|
| 56 | - | "Quit" | menu -l 6 -p "Network:") |
|
| 57 | - | ||
| 58 | - | case "$CHOICE" in |
|
| 59 | - | "Connect to WiFi") "$DIR/dm-wifi-connect" ;; |
|
| 60 | - | "Disable WiFi") "$DIR/dm-wifi-disable" ;; |
|
| 61 | - | "Connect eth0") "$DIR/dm-eth0-connect" ;; |
|
| 62 | - | "Disable eth0") "$DIR/dm-eth0-disable" ;; |
|
| 63 | - | "Show status") |
|
| 64 | - | # dmenu is the only display surface we have; the selection is ignored. |
|
| 65 | - | STATUS=$(net_status) |
|
| 66 | - | LINES=$(printf '%s\n' "$STATUS" | grep -c .) |
|
| 67 | - | printf '%s\n' "$STATUS" | menu -l "$LINES" -p "Status:" >/dev/null |
|
| 68 | - | ;; |
|
| 69 | - | *) exit 0 ;; |
|
| 70 | - | esac |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: dmenu, libnotify, iw, wpa_supplicant, udhcpc, openrc, doas |
|
| 3 | - | # dmenu counterpart of wifi-connect. All prompting happens as the calling |
|
| 4 | - | # user so dmenu keeps the X session and notifications keep the user's bus; |
|
| 5 | - | # only scanning, the config write and the bring-up are handed to doas. |
|
| 6 | - | set -u |
|
| 7 | - | ||
| 8 | - | CONF=/etc/wpa_supplicant/wpa_supplicant.conf |
|
| 9 | - | NOTIFY_ID=7301 |
|
| 10 | - | ||
| 11 | - | menu() { dmenu -c -i "$@"; } |
|
| 12 | - | ||
| 13 | - | # Typed text with no dmenu-visible input still has to be hidden, and this |
|
| 14 | - | # build has no password patch — paint the prompt's foreground onto its |
|
| 15 | - | # background so the passphrase never renders. |
|
| 16 | - | menu_secret() { |
|
| 17 | - | dmenu -c -p "$1" -nb '#000000' -nf '#000000' -sb '#000000' -sf '#000000' </dev/null |
|
| 18 | - | } |
|
| 19 | - | ||
| 20 | - | notify() { |
|
| 21 | - | # dunstify can replace an earlier notification in place, so the progress |
|
| 22 | - | # line becomes the result line instead of stacking up. |
|
| 23 | - | if command -v dunstify >/dev/null 2>&1; then |
|
| 24 | - | dunstify -r "$NOTIFY_ID" ${2:+-u "$2"} "WiFi" "$1" |
|
| 25 | - | else |
|
| 26 | - | notify-send ${2:+-u "$2"} "WiFi" "$1" |
|
| 27 | - | fi |
|
| 28 | - | } |
|
| 29 | - | ||
| 30 | - | fail() { notify "$1" critical; exit 1; } |
|
| 31 | - | ||
| 32 | - | # doas asks for a passphrase on a tty, and launched from a keybinding there |
|
| 33 | - | # is none — fail with something readable instead of hanging invisibly. |
|
| 34 | - | doas -n true 2>/dev/null || fail "doas needs a password; add a nopass rule for this user." |
|
| 35 | - | ||
| 36 | - | # Ask the kernel which interfaces are wireless rather than assuming wlan0. |
|
| 37 | - | IFACES=$(iw dev | awk '$1 == "Interface" { print $2 }') |
|
| 38 | - | ||
| 39 | - | case $(printf '%s' "$IFACES" | grep -c .) in |
|
| 40 | - | 0) fail "No wireless interfaces found." ;; |
|
| 41 | - | 1) IFACE=$IFACES ;; |
|
| 42 | - | *) IFACE=$(printf '%s\n' "$IFACES" | menu -l 5 -p "Interface:") ;; |
|
| 43 | - | esac |
|
| 44 | - | ||
| 45 | - | [ -n "$IFACE" ] || exit 1 |
|
| 46 | - | ||
| 47 | - | doas ip link set "$IFACE" up 2>/dev/null |
|
| 48 | - | ||
| 49 | - | notify " Scanning for networks on $IFACE..." |
|
| 50 | - | ||
| 51 | - | # `scan dump` reuses cached results if the card is busy (already associated), |
|
| 52 | - | # which is when a live scan tends to fail. |
|
| 53 | - | SCAN=$(doas iw dev "$IFACE" scan 2>/dev/null || doas iw dev "$IFACE" scan dump 2>/dev/null) |
|
| 54 | - | ||
| 55 | - | # Only real SSID lines: bare "SSID: " is a hidden network, and "SSID List" |
|
| 56 | - | # under extended capabilities is not a network at all. |
|
| 57 | - | SSIDS=$(printf '%s\n' "$SCAN" | sed -n 's/^[[:space:]]*SSID: \(..*\)$/\1/p' | sort -u) |
|
| 58 | - | ||
| 59 | - | if [ -n "$SSIDS" ]; then |
|
| 60 | - | # dmenu returns whatever was typed when nothing matches, so a hidden or |
|
| 61 | - | # out-of-range network can just be typed into the same prompt. |
|
| 62 | - | SSID=$(printf '%s\n' "$SSIDS" | menu -l 10 -p "Network:") |
|
| 63 | - | else |
|
| 64 | - | notify "No networks found on $IFACE — type one in." |
|
| 65 | - | SSID=$(menu -p "Network:" </dev/null) |
|
| 66 | - | fi |
|
| 67 | - | ||
| 68 | - | [ -n "$SSID" ] || exit 1 |
|
| 69 | - | ||
| 70 | - | PASS=$(menu_secret "Password (empty = open):") |
|
| 71 | - | ||
| 72 | - | # Build the new config before touching anything: a rejected passphrase must |
|
| 73 | - | # not blank out the config that is currently working. The PSK never lands on |
|
| 74 | - | # disk unprivileged — it goes to the root shell over a pipe. |
|
| 75 | - | if [ -z "$PASS" ]; then |
|
| 76 | - | NEW=$(printf 'network={\n\tssid="%s"\n\tkey_mgmt=NONE\n}\n' "$SSID") |
|
| 77 | - | else |
|
| 78 | - | NEW=$(wpa_passphrase "$SSID" "$PASS" 2>/dev/null | grep -v '^[[:space:]]*#psk=') || |
|
| 79 | - | fail "Rejected by wpa_passphrase — password must be 8-63 characters." |
|
| 80 | - | fi |
|
| 81 | - | ||
| 82 | - | [ -n "$NEW" ] || fail "Could not generate a config for $SSID." |
|
| 83 | - | ||
| 84 | - | printf '%s\n' "$NEW" | doas sh -c ' |
|
| 85 | - | [ -f "$1" ] && cp "$1" "$1.bak" |
|
| 86 | - | cat > "$1" |
|
| 87 | - | chmod 600 "$1" |
|
| 88 | - | ' _ "$CONF" || fail "Could not write $CONF." |
|
| 89 | - | ||
| 90 | - | notify " Connecting to $SSID..." |
|
| 91 | - | ||
| 92 | - | doas sh -c ' |
|
| 93 | - | # wpa_supplicant is a supervised OpenRC service on this host and will not |
|
| 94 | - | # reread its config on its own, so restart it rather than signalling it. |
|
| 95 | - | # This also covers the case where dm-wifi-disable stopped it outright. |
|
| 96 | - | # ifup is no help here: wlan0 has no stanza in /etc/network/interfaces. |
|
| 97 | - | rc-service wpa_supplicant restart >/dev/null 2>&1 |
|
| 98 | - | ||
| 99 | - | # Give the supplicant time to associate before asking for a lease — a |
|
| 100 | - | # DHCP request sent before association just burns its whole timeout. |
|
| 101 | - | i=0 |
|
| 102 | - | while [ "$i" -lt 20 ]; do |
|
| 103 | - | iw dev "$1" link 2>/dev/null | grep -q "^Connected to" && break |
|
| 104 | - | sleep 1 |
|
| 105 | - | i=$((i + 1)) |
|
| 106 | - | done |
|
| 107 | - | ||
| 108 | - | # Replace any DHCP client left over from a previous connect; without this |
|
| 109 | - | # they stack up, one per run, all renewing the same lease. |
|
| 110 | - | PIDFILE=/var/run/udhcpc.$1.pid |
|
| 111 | - | if [ -f "$PIDFILE" ]; then |
|
| 112 | - | kill "$(cat "$PIDFILE")" 2>/dev/null |
|
| 113 | - | rm -f "$PIDFILE" |
|
| 114 | - | fi |
|
| 115 | - | ip addr flush dev "$1" 2>/dev/null |
|
| 116 | - | ||
| 117 | - | # Same invocation ifupdown-ng uses for a dhcp stanza. udhcpc daemonises |
|
| 118 | - | # once it has the lease, so this returns with the address already set. |
|
| 119 | - | udhcpc -b -R -p "$PIDFILE" -i "$1" -x "hostname:$(hostname)" >/dev/null 2>&1 |
|
| 120 | - | ' _ "$IFACE" |
|
| 121 | - | ||
| 122 | - | if ip addr show "$IFACE" | grep -q "inet "; then |
|
| 123 | - | IP=$(ip -4 addr show "$IFACE" | awk '/inet /{print $2; exit}') |
|
| 124 | - | notify "$("$(dirname "$0")/wifi-status" "$IFACE") on $IFACE — $IP" |
|
| 125 | - | else |
|
| 126 | - | fail " Failed to connect to $SSID" |
|
| 127 | - | fi |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: dmenu, libnotify, openrc, ifupdown-ng, doas |
|
| 3 | - | # dmenu counterpart of wifi-disable. The confirm prompt runs as the calling |
|
| 4 | - | # user; only the teardown is handed to doas. |
|
| 5 | - | set -u |
|
| 6 | - | ||
| 7 | - | IFACE="${1:-wlan0}" |
|
| 8 | - | NOTIFY_ID=7302 |
|
| 9 | - | ||
| 10 | - | notify() { |
|
| 11 | - | if command -v dunstify >/dev/null 2>&1; then |
|
| 12 | - | dunstify -r "$NOTIFY_ID" ${2:+-u "$2"} "WiFi" "$1" |
|
| 13 | - | else |
|
| 14 | - | notify-send ${2:+-u "$2"} "WiFi" "$1" |
|
| 15 | - | fi |
|
| 16 | - | } |
|
| 17 | - | ||
| 18 | - | fail() { notify "$1" critical; exit 1; } |
|
| 19 | - | ||
| 20 | - | # doas asks for a passphrase on a tty, and launched from a keybinding there |
|
| 21 | - | # is none — fail with something readable instead of hanging invisibly. |
|
| 22 | - | doas -n true 2>/dev/null || fail "doas needs a password; add a nopass rule for this user." |
|
| 23 | - | ||
| 24 | - | # Default to No so a stray Enter never takes the link down. |
|
| 25 | - | [ "$(printf 'No\nYes\n' | dmenu -c -i -l 2 -p "Disable WiFi on $IFACE?")" = "Yes" ] || exit 0 |
|
| 26 | - | ||
| 27 | - | notify " Disabling $IFACE..." |
|
| 28 | - | ||
| 29 | - | doas sh -c ' |
|
| 30 | - | # wpa_supplicant runs under supervise-daemon (supervisor= in |
|
| 31 | - | # /etc/init.d/wpa_supplicant), which respawns the daemon a couple of |
|
| 32 | - | # seconds after any kill. Stopping the service is what takes the |
|
| 33 | - | # supervisor down with it; pkill only ever kills the child. |
|
| 34 | - | rc-service -s -q wpa_supplicant stop |
|
| 35 | - | ||
| 36 | - | # wlan0 has no stanza in /etc/network/interfaces, so ifdown is a no-op on |
|
| 37 | - | # this host and leaves the DHCP client behind. Try it anyway for hosts |
|
| 38 | - | # where the interface *is* configured, then clean up by pidfile. |
|
| 39 | - | ifdown "$1" 2>/dev/null |
|
| 40 | - | ||
| 41 | - | PIDFILE=/var/run/udhcpc.$1.pid |
|
| 42 | - | if [ -f "$PIDFILE" ]; then |
|
| 43 | - | kill "$(cat "$PIDFILE")" 2>/dev/null |
|
| 44 | - | rm -f "$PIDFILE" |
|
| 45 | - | fi |
|
| 46 | - | ||
| 47 | - | ip addr flush dev "$1" 2>/dev/null |
|
| 48 | - | ip link set "$1" down |
|
| 49 | - | ' _ "$IFACE" |
|
| 50 | - | ||
| 51 | - | notify " $IFACE disabled" |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: gum, ifupdown-ng |
|
| 3 | - | # Bringing interfaces up/down needs root. |
|
| 4 | - | set -u |
|
| 5 | - | ||
| 6 | - | IFACE="${1:-eth0}" |
|
| 7 | - | WIFI="${2:-wlan0}" |
|
| 8 | - | ||
| 9 | - | if [ "$(id -u)" -ne 0 ]; then |
|
| 10 | - | exec doas "$0" "$@" |
|
| 11 | - | fi |
|
| 12 | - | ||
| 13 | - | gum confirm "Switch to $IFACE and disable $WIFI?" || exit 0 |
|
| 14 | - | ||
| 15 | - | gum spin --spinner dot --title "Switching to wired..." -- sh -c ' |
|
| 16 | - | ifdown "$2" 2>/dev/null |
|
| 17 | - | ip link set "$2" down 2>/dev/null |
|
| 18 | - | pkill wpa_supplicant 2>/dev/null |
|
| 19 | - | ifup "$1" |
|
| 20 | - | ' _ "$IFACE" "$WIFI" |
|
| 21 | - | ||
| 22 | - | if ip addr show "$IFACE" | grep -q "inet "; then |
|
| 23 | - | IP=$(ip -4 addr show "$IFACE" | grep inet | awk '{print $2}') |
|
| 24 | - | gum style --foreground 46 --bold " $IFACE up — $IP" |
|
| 25 | - | else |
|
| 26 | - | gum style --foreground 196 --bold " $IFACE failed to come up" |
|
| 27 | - | fi |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: gum, ifupdown-ng |
|
| 3 | - | # Bringing interfaces down needs root. |
|
| 4 | - | set -u |
|
| 5 | - | ||
| 6 | - | IFACE="${1:-eth0}" |
|
| 7 | - | ||
| 8 | - | if [ "$(id -u)" -ne 0 ]; then |
|
| 9 | - | exec doas "$0" "$@" |
|
| 10 | - | fi |
|
| 11 | - | ||
| 12 | - | gum confirm "Disable $IFACE?" || exit 0 |
|
| 13 | - | ||
| 14 | - | gum spin --spinner dot --title "Disabling $IFACE..." -- sh -c ' |
|
| 15 | - | ifdown "$1" 2>/dev/null |
|
| 16 | - | ip link set "$1" down |
|
| 17 | - | ' _ "$IFACE" |
|
| 18 | - | ||
| 19 | - | gum style --foreground 214 " $IFACE disabled" |
| 1 | - | #!/usr/bin/env bash |
|
| 2 | - | ||
| 3 | - | LOCATION=$1 |
|
| 4 | - | ||
| 5 | - | # data=$(curl -sSL https://api.weather.gov/points/$LOCATION) |
|
| 6 | - | # echo $data |
|
| 7 | - | ||
| 8 | - | curl -sSL https://api.weather.gov/points/$LOCATION | jq -r '.properties.forecast' |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Resolve the sibling scripts relative to this one rather than through PATH. |
|
| 3 | - | DIR=$(dirname "$0") |
|
| 4 | - | ||
| 5 | - | # BusyBox's `ip` has no `-br`, so build the brief view by hand. Each line is |
|
| 6 | - | # prefixed with a Nerd Font icon for what that interface is and how it is |
|
| 7 | - | # doing: // wireless connected/no lease/down, / wired up/down. |
|
| 8 | - | net_status() { |
|
| 9 | - | for path in /sys/class/net/*; do |
|
| 10 | - | iface=${path##*/} |
|
| 11 | - | [ "$iface" = "lo" ] && continue |
|
| 12 | - | ||
| 13 | - | state=$(cat "$path/operstate" 2>/dev/null || echo unknown) |
|
| 14 | - | addrs=$(ip -4 addr show "$iface" 2>/dev/null | |
|
| 15 | - | awk '/inet /{ printf "%s%s", (n++ ? " " : ""), $2 }') |
|
| 16 | - | ||
| 17 | - | # Container veths and other virtual links with no address are noise; |
|
| 18 | - | # a virtual interface that does carry one (tailscale0, docker0) is not. |
|
| 19 | - | [ -z "$addrs" ] && [ ! -e "$path/device" ] && continue |
|
| 20 | - | ||
| 21 | - | # Tunnels sit at operstate "unknown" for their whole life, so an |
|
| 22 | - | # address is what actually says the link is carrying traffic. |
|
| 23 | - | up=0 |
|
| 24 | - | case "$state" in |
|
| 25 | - | up) up=1 ;; |
|
| 26 | - | unknown) [ -n "$addrs" ] && up=1 ;; |
|
| 27 | - | esac |
|
| 28 | - | ||
| 29 | - | if [ -d "$path/wireless" ] || [ -e "$path/phy80211" ]; then |
|
| 30 | - | if [ "$up" -eq 0 ]; then icon="" |
|
| 31 | - | elif [ -z "$addrs" ]; then icon="" |
|
| 32 | - | else icon="" |
|
| 33 | - | fi |
|
| 34 | - | else |
|
| 35 | - | if [ "$up" -eq 1 ]; then icon=""; else icon=""; fi |
|
| 36 | - | fi |
|
| 37 | - | ||
| 38 | - | printf '%s %-12s %-8s %s\n' "$icon" "$iface" "$state" "$addrs" |
|
| 39 | - | done |
|
| 40 | - | } |
|
| 41 | - | ||
| 42 | - | CHOICE=$(gum choose "Connect to WiFi" "Disable WiFi" "Connect eth0" "Disable eth0" "Show status" "Quit") |
|
| 43 | - | ||
| 44 | - | case "$CHOICE" in |
|
| 45 | - | "Connect to WiFi") "$DIR/wifi-connect" ;; |
|
| 46 | - | "Disable WiFi") "$DIR/wifi-disable" ;; |
|
| 47 | - | "Connect eth0") "$DIR/eth0-connect" ;; |
|
| 48 | - | "Disable eth0") "$DIR/eth0-disable" ;; |
|
| 49 | - | "Show status") |
|
| 50 | - | gum style --border normal --padding "1 2" --border-foreground 212 \ |
|
| 51 | - | "$(net_status)" |
|
| 52 | - | ;; |
|
| 53 | - | *) exit 0 ;; |
|
| 54 | - | esac |
| 1 | - | #!/usr/bin/env bash |
|
| 2 | - | ||
| 3 | - | folder="$HOME/notes" |
|
| 4 | - | ||
| 5 | - | mkdir -p "$folder" |
|
| 6 | - | ||
| 7 | - | # BusyBox setsid has no -f; background with & instead. |
|
| 8 | - | launch () { |
|
| 9 | - | setsid "$term" -e nvim "$1" >/dev/null 2>&1 & |
|
| 10 | - | } |
|
| 11 | - | ||
| 12 | - | : "${TERMINAL:=st}" |
|
| 13 | - | term="$(command -v "$TERMINAL")" || { |
|
| 14 | - | echo "notes: terminal '$TERMINAL' not found" >&2 |
|
| 15 | - | exit 1 |
|
| 16 | - | } |
|
| 17 | - | ||
| 18 | - | newnote () { \ |
|
| 19 | - | dir="$(find "$folder" -type d | sort | dmenu -c -l 5 -i -p 'Choose directory: ')" || exit 0 |
|
| 20 | - | : "${dir:=$folder}" |
|
| 21 | - | name="$(echo "" | dmenu -c -sb "#a3be8c" -nf "#d8dee9" -p "Enter a name: " <&-)" || exit 0 |
|
| 22 | - | : "${name:=$(date +%F_%H-%M-%S)}" |
|
| 23 | - | launch "${dir%/}/${name}.md" |
|
| 24 | - | } |
|
| 25 | - | ||
| 26 | - | selected () { \ |
|
| 27 | - | choice=$( |
|
| 28 | - | { echo "New" |
|
| 29 | - | find "$folder" -type f -name '*.md' -exec stat -c '%Y %n' {} + 2>/dev/null \ |
|
| 30 | - | | sort -nr | cut -d' ' -f2- | sed "s|^$folder/||" |
|
| 31 | - | } | dmenu -c -l 5 -i -p "Choose note or create new: " |
|
| 32 | - | ) |
|
| 33 | - | case $choice in |
|
| 34 | - | New) newnote ;; |
|
| 35 | - | *.md) launch "$folder/$choice" ;; |
|
| 36 | - | *) exit ;; |
|
| 37 | - | esac |
|
| 38 | - | } |
|
| 39 | - | ||
| 40 | - | selected |
| 1 | - | #!/usr/bin/env bash |
|
| 2 | - | # basic stats checker script mostly adapted from statusbar, may need changes to work on your system |
|
| 3 | - | ||
| 4 | - | CPU_TEMP=$(sensors | awk ' |
|
| 5 | - | /^Tdie|^Tctl|^Package id|^Core 0|^CPU|^temp1/ { |
|
| 6 | - | gsub(/[+°C]/, ""); |
|
| 7 | - | for (i=1; i<=NF; i++) { |
|
| 8 | - | if ($i ~ /^[0-9]+(\.[0-9]+)?$/) { |
|
| 9 | - | gsub(/\..*/, "", $i); |
|
| 10 | - | print $i; |
|
| 11 | - | exit; |
|
| 12 | - | } |
|
| 13 | - | } |
|
| 14 | - | }') |
|
| 15 | - | ||
| 16 | - | MEM_USE=$(free -m | awk '/^Mem/ {print ($3)/1024}'| cut -c-4) |
|
| 17 | - | CPU_USE=$(iostat -c | awk 'NR>=4 && NR <=4' | awk '{print "User CPU: "$1"%\nSystem CPU: ",$3"%"}') |
|
| 18 | - | GPU_TEMP=$(sensors | awk '/^edge/ {print substr($2,2)}') |
|
| 19 | - | ||
| 20 | - | ||
| 21 | - | DISK1=$(df -h ~/ | awk 'NR==2 {print $4}') |
|
| 22 | - | DISK2=$(df -h / | awk 'NR==2 {print $4}') |
|
| 23 | - | ||
| 24 | - | # [[ -n $(amixer get Capture | awk '/\[on\]/') ]] && MIC="on" || MIC="off" |
|
| 25 | - | UPDATE=$(stat -c %y /lib/apk/db/installed | awk '{print $1}') |
|
| 26 | - | KERNEL=$(uname -r) |
|
| 27 | - | ||
| 28 | - | echo -e "Stats $CPU_USE |
|
| 29 | - | ||
| 30 | - | GPU temp: $GPU_TEMP |
|
| 31 | - | CPU temp: $CPU_TEMP°C |
|
| 32 | - | ||
| 33 | - | Memory use: ${MEM_USE}g |
|
| 34 | - | ||
| 35 | - | Home remaining: $DISK1 |
|
| 36 | - | Root remaining: $DISK2 |
|
| 37 | - | ||
| 38 | - | Last update: $UPDATE $KERNEL" \ |
|
| 39 | - | | bat -l cpuinfo --style=grid |
|
| 40 | - |
| 1 | - | #!/usr/bin/env bash |
|
| 2 | - | ||
| 3 | - | case "$(printf "kill\nzzz\nreboot\nshutdown" | dmenu -i -c -l 4)" in |
|
| 4 | - | kill) ps -u "$USER" -o pid,comm,%cpu,%mem | dmenu -i -c -l 10 -p Kill: | awk '{print $1}' | xargs -r kill ;; |
|
| 5 | - | zzz) slock systemctl suspend -i ;; |
|
| 6 | - | reboot) systemctl reboot -i ;; |
|
| 7 | - | shutdown) shutdown now ;; |
|
| 8 | - | *) exit 1 ;; |
|
| 9 | - | esac |
| 1 | - | #!/usr/bin/env bash |
|
| 2 | - | ||
| 3 | - | histfile="$HOME/.cache/cliphist" |
|
| 4 | - | placeholder="<NEWLINE>" |
|
| 5 | - | ||
| 6 | - | highlight() { |
|
| 7 | - | clip=$(xclip -o -selection primary | xclip -i -f -selection clipboard 2>/dev/null) ;} |
|
| 8 | - | ||
| 9 | - | output() { |
|
| 10 | - | clip=$(xclip -i -f -selection clipboard 2>/dev/null) ;} |
|
| 11 | - | ||
| 12 | - | write() { |
|
| 13 | - | [ -f "$histfile" ] || notify-send "Creating $histfile"; touch $histfile |
|
| 14 | - | [ -z "$clip" ] && exit 0 |
|
| 15 | - | multiline=$(echo "$clip" | sed ':a;N;$!ba;s/\n/'"$placeholder"'/g') |
|
| 16 | - | grep -Fxq "$multiline" "$histfile" || echo "$multiline" >> "$histfile" |
|
| 17 | - | notification=$(echo \"$multiline\") ;} |
|
| 18 | - | ||
| 19 | - | sel() { |
|
| 20 | - | selection=$(tac "$histfile" | dmenu -b -l 5 -i -p "Clipboard history:") |
|
| 21 | - | [ -n "$selection" ] && echo "$selection" | sed "s/$placeholder/\n/g" | xclip -i -selection clipboard && notification="Copied to clipboard!" ;} |
|
| 22 | - | ||
| 23 | - | case "$1" in |
|
| 24 | - | add) highlight && write ;; |
|
| 25 | - | out) output && write ;; |
|
| 26 | - | sel) sel ;; |
|
| 27 | - | *) printf "$0 | File: $histfile\n\nadd - copies primary selection to clipboard, and adds to history file\nout - pipe commands to copy output to clipboard, and add to history file\nsel - select from history file with dmenu and recopy!\n" ; exit 0 ;; |
|
| 28 | - | esac |
|
| 29 | - | ||
| 30 | - | [ -z "$notification" ] || notify-send "$notification" |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: wireplumber (wpctl) |
|
| 3 | - | # Lower the default sink. Usage: vol-down [STEP_PERCENT] (default 5) |
|
| 4 | - | set -u |
|
| 5 | - | ||
| 6 | - | STEP="${1:-5}" |
|
| 7 | - | ||
| 8 | - | wpctl set-volume @DEFAULT_AUDIO_SINK@ "${STEP}%-" || exit 1 |
|
| 9 | - | exec "$(dirname "$0")/vol-status" -n |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: wireplumber (wpctl) |
|
| 3 | - | # Mute the default sink. Usage: vol-mute [toggle|on|off] (default toggle) |
|
| 4 | - | # Pass -s / --source to act on the default microphone instead. |
|
| 5 | - | set -u |
|
| 6 | - | ||
| 7 | - | TARGET="@DEFAULT_AUDIO_SINK@" |
|
| 8 | - | case "${1:-}" in |
|
| 9 | - | -s|--source) TARGET="@DEFAULT_AUDIO_SOURCE@"; shift ;; |
|
| 10 | - | esac |
|
| 11 | - | ||
| 12 | - | case "${1:-toggle}" in |
|
| 13 | - | toggle|"") ARG=toggle ;; |
|
| 14 | - | on|1|mute) ARG=1 ;; |
|
| 15 | - | off|0|unmute) ARG=0 ;; |
|
| 16 | - | *) echo "usage: vol-mute [-s] [toggle|on|off]" >&2; exit 2 ;; |
|
| 17 | - | esac |
|
| 18 | - | ||
| 19 | - | wpctl set-mute "$TARGET" "$ARG" || exit 1 |
|
| 20 | - | ||
| 21 | - | # vol-status carries the icon logic for both devices, so hand the reporting |
|
| 22 | - | # (and the notification) off to it either way. |
|
| 23 | - | if [ "$TARGET" = "@DEFAULT_AUDIO_SOURCE@" ]; then |
|
| 24 | - | exec "$(dirname "$0")/vol-status" -s -n |
|
| 25 | - | fi |
|
| 26 | - | ||
| 27 | - | exec "$(dirname "$0")/vol-status" -n |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: wireplumber (wpctl), optionally libnotify |
|
| 3 | - | # Prints the default sink's volume with a Nerd Font icon for its level. |
|
| 4 | - | # Shared by vol-up / vol-down / vol-mute and the status bar. |
|
| 5 | - | # Usage: vol-status [-n] [-p] [-s] |
|
| 6 | - | # -n also fire a desktop notification |
|
| 7 | - | # -p plain output, no Nerd Font icon (for terminals without one) |
|
| 8 | - | # -s report the default source (microphone) instead of the sink |
|
| 9 | - | set -u |
|
| 10 | - | ||
| 11 | - | NOTIFY=0 |
|
| 12 | - | PLAIN=0 |
|
| 13 | - | TARGET="@DEFAULT_AUDIO_SINK@" |
|
| 14 | - | LABEL="Volume" |
|
| 15 | - | ||
| 16 | - | while [ $# -gt 0 ]; do |
|
| 17 | - | case "$1" in |
|
| 18 | - | -n|--notify) NOTIFY=1 ;; |
|
| 19 | - | -p|--plain) PLAIN=1 ;; |
|
| 20 | - | -s|--source) TARGET="@DEFAULT_AUDIO_SOURCE@"; LABEL="Microphone" ;; |
|
| 21 | - | *) echo "usage: vol-status [-n] [-p] [-s]" >&2; exit 2 ;; |
|
| 22 | - | esac |
|
| 23 | - | shift |
|
| 24 | - | done |
|
| 25 | - | ||
| 26 | - | RAW=$(wpctl get-volume "$TARGET" 2>/dev/null) |
|
| 27 | - | if [ -z "$RAW" ]; then |
|
| 28 | - | echo "vol-status: no default device (is pipewire/wireplumber running?)" >&2 |
|
| 29 | - | exit 1 |
|
| 30 | - | fi |
|
| 31 | - | ||
| 32 | - | # "Volume: 0.45" or "Volume: 0.45 [MUTED]" |
|
| 33 | - | PCT=$(printf '%s\n' "$RAW" | awk '{ printf "%d", $2 * 100 + 0.5 }') |
|
| 34 | - | case "$RAW" in |
|
| 35 | - | *MUTED*) MUTED=1 ;; |
|
| 36 | - | *) MUTED=0 ;; |
|
| 37 | - | esac |
|
| 38 | - | ||
| 39 | - | # Two icon sets: speaker glyphs for the sink, mic glyphs for the source. |
|
| 40 | - | # ICON is the Nerd Font glyph in the bar; FDO is the freedesktop icon name |
|
| 41 | - | # the notification daemon looks up in the icon theme. |
|
| 42 | - | if [ "$TARGET" = "@DEFAULT_AUDIO_SOURCE@" ]; then |
|
| 43 | - | if [ "$MUTED" -eq 1 ]; then |
|
| 44 | - | ICON=""; FDO="microphone-sensitivity-muted"; TEXT="muted" |
|
| 45 | - | else |
|
| 46 | - | ICON=""; FDO="microphone-sensitivity-high"; TEXT="$PCT%" |
|
| 47 | - | fi |
|
| 48 | - | elif [ "$MUTED" -eq 1 ]; then |
|
| 49 | - | ICON=""; FDO="audio-volume-muted"; TEXT="muted ($PCT%)" |
|
| 50 | - | else |
|
| 51 | - | TEXT="$PCT%" |
|
| 52 | - | if [ "$PCT" -ge 66 ]; then ICON=""; FDO="audio-volume-high" |
|
| 53 | - | elif [ "$PCT" -ge 33 ]; then ICON=""; FDO="audio-volume-medium" |
|
| 54 | - | elif [ "$PCT" -gt 0 ]; then ICON=""; FDO="audio-volume-low" |
|
| 55 | - | else ICON=""; FDO="audio-volume-muted" |
|
| 56 | - | fi |
|
| 57 | - | fi |
|
| 58 | - | ||
| 59 | - | if [ "$PLAIN" -eq 1 ]; then |
|
| 60 | - | echo "$TEXT" |
|
| 61 | - | else |
|
| 62 | - | echo "$ICON $TEXT" |
|
| 63 | - | fi |
|
| 64 | - | ||
| 65 | - | # Only bother with notifications when there is a session to show them in. |
|
| 66 | - | [ "$NOTIFY" -eq 1 ] || exit 0 |
|
| 67 | - | [ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ] || exit 0 |
|
| 68 | - | command -v notify-send >/dev/null 2>&1 || exit 0 |
|
| 69 | - | ||
| 70 | - | notify-send \ |
|
| 71 | - | -a volume \ |
|
| 72 | - | -i "$FDO" \ |
|
| 73 | - | -h string:x-canonical-private-synchronous:volume \ |
|
| 74 | - | -h "int:value:$PCT" \ |
|
| 75 | - | "$ICON $LABEL" "$TEXT" |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: wireplumber (wpctl) |
|
| 3 | - | # Raise the default sink. Usage: vol-up [STEP_PERCENT] (default 5) |
|
| 4 | - | # VOL_MAX caps the ceiling (1.0 = 100%); raise it if you want overamplification. |
|
| 5 | - | set -u |
|
| 6 | - | ||
| 7 | - | STEP="${1:-5}" |
|
| 8 | - | MAX="${VOL_MAX:-1.0}" |
|
| 9 | - | ||
| 10 | - | wpctl set-volume -l "$MAX" @DEFAULT_AUDIO_SINK@ "${STEP}%+" || exit 1 |
|
| 11 | - | exec "$(dirname "$0")/vol-status" -n |
| 1 | - | #!/usr/bin/env bash |
|
| 2 | - | ||
| 3 | - | set -euo pipefail |
|
| 4 | - | ||
| 5 | - | gridpoint=${WEATHER_GRIDPOINT:-MRX/23,5} |
|
| 6 | - | base_url="https://api.weather.gov/gridpoints/${gridpoint}/forecast" |
|
| 7 | - | ||
| 8 | - | cloudy= |
|
| 9 | - | fog= |
|
| 10 | - | hail= |
|
| 11 | - | hazy= |
|
| 12 | - | storm= |
|
| 13 | - | clear_night= |
|
| 14 | - | rain= |
|
| 15 | - | snow= |
|
| 16 | - | clear_day= |
|
| 17 | - | ||
| 18 | - | usage() { |
|
| 19 | - | cat <<'USAGE' |
|
| 20 | - | usage: weather.sh [-h|--hourly] [-j|--json] |
|
| 21 | - | ||
| 22 | - | -h, --hourly use the hourly forecast instead of the daily one |
|
| 23 | - | -j, --json print { "text": ..., "tooltip": ... } (waybar friendly) |
|
| 24 | - | ||
| 25 | - | env: |
|
| 26 | - | WEATHER_GRIDPOINT NWS gridpoint, default MRX/23,5 |
|
| 27 | - | USAGE |
|
| 28 | - | } |
|
| 29 | - | ||
| 30 | - | hourly=false |
|
| 31 | - | json=false |
|
| 32 | - | while (($#)); do |
|
| 33 | - | case $1 in |
|
| 34 | - | -h | --hourly) hourly=true ;; |
|
| 35 | - | -j | --json) json=true ;; |
|
| 36 | - | --help) usage && exit 0 ;; |
|
| 37 | - | *) usage >&2 && exit 1 ;; |
|
| 38 | - | esac |
|
| 39 | - | shift |
|
| 40 | - | done |
|
| 41 | - | ||
| 42 | - | $hourly && weather_url="$base_url/hourly" || weather_url="$base_url" |
|
| 43 | - | ||
| 44 | - | data=$(curl -sSL --fail --max-time 10 -H 'Accept: application/geo+json' "$weather_url" | |
|
| 45 | - | jq -r '.properties.periods[0] | [.icon, .temperature, .temperatureUnit, .shortForecast, .isDaytime] | @tsv') || { |
|
| 46 | - | echo "weather: request failed" >&2 |
|
| 47 | - | exit 1 |
|
| 48 | - | } |
|
| 49 | - | ||
| 50 | - | IFS=$'\t' read -r icon_url temp unit short is_day <<<"$data" |
|
| 51 | - | ||
| 52 | - | # https://api.weather.gov/icons/land/night/tsra_hi,20/bkn?size=small |
|
| 53 | - | # ^period ^condition |
|
| 54 | - | icon_path=${icon_url%%\?*} # drop the query string |
|
| 55 | - | icon_path=${icon_path#*/icons/} # land/night/tsra_hi,20/bkn |
|
| 56 | - | icon_path=${icon_path#*/} # night/tsra_hi,20/bkn |
|
| 57 | - | period=${icon_path%%/*} # day|night |
|
| 58 | - | condition=${icon_path#*/} |
|
| 59 | - | condition=${condition%%/*} # first of the (up to two) conditions |
|
| 60 | - | condition=${condition%%,*} # drop the ",20" precip chance |
|
| 61 | - | ||
| 62 | - | night=false |
|
| 63 | - | [[ $period == night || $is_day == false ]] && night=true |
|
| 64 | - | ||
| 65 | - | # NWS icon vocabulary -> glyph. |
|
| 66 | - | case $condition in |
|
| 67 | - | skc | few | hot | cold) $night && glyph=$clear_night || glyph=$clear_day ;; |
|
| 68 | - | sct | bkn | ovc | wind_skc | wind_few | wind_sct | wind_bkn | wind_ovc) glyph=$cloudy ;; |
|
| 69 | - | tsra | tsra_sct | tsra_hi | tornado | hurricane | tropical_storm) glyph=$storm ;; |
|
| 70 | - | rain | rain_showers | rain_showers_hi) glyph=$rain ;; |
|
| 71 | - | snow | blizzard | rain_snow | snow_sleet) glyph=$snow ;; |
|
| 72 | - | sleet | fzra | rain_fzra | snow_fzra | rain_sleet) glyph=$hail ;; |
|
| 73 | - | fog) glyph=$fog ;; |
|
| 74 | - | haze | smoke | dust) glyph=$hazy ;; |
|
| 75 | - | *) glyph="" ;; |
|
| 76 | - | esac |
|
| 77 | - | ||
| 78 | - | # Fall back to a keyword match on the forecast text, most specific first. |
|
| 79 | - | if [[ -z $glyph ]]; then |
|
| 80 | - | case ${short,,} in |
|
| 81 | - | *thunder* | *tstm* | *tornado* | *hurricane* | *tropical*) glyph=$storm ;; |
|
| 82 | - | *hail* | *sleet* | *freezing* | *ice*) glyph=$hail ;; |
|
| 83 | - | *snow* | *flurr* | *blizzard* | *wintry*) glyph=$snow ;; |
|
| 84 | - | *rain* | *shower* | *drizzle*) glyph=$rain ;; |
|
| 85 | - | *fog* | *mist*) glyph=$fog ;; |
|
| 86 | - | *haze* | *hazy* | *smoke* | *dust*) glyph=$hazy ;; |
|
| 87 | - | *cloud* | *overcast*) glyph=$cloudy ;; |
|
| 88 | - | *clear* | *sunny* | *fair* | *hot*) $night && glyph=$clear_night || glyph=$clear_day ;; |
|
| 89 | - | *) $night && glyph=$clear_night || glyph=$clear_day ;; |
|
| 90 | - | esac |
|
| 91 | - | fi |
|
| 92 | - | ||
| 93 | - | if $json; then |
|
| 94 | - | jq -cn --arg text "$glyph $temp°$unit" --arg tooltip "$short" \ |
|
| 95 | - | '{text: $text, tooltip: $tooltip}' |
|
| 96 | - | else |
|
| 97 | - | printf '%s %s°%s\n' "$glyph" "$temp" "$unit" |
|
| 98 | - | fi |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: gum, iw, wpa_supplicant, udhcpc, openrc |
|
| 3 | - | # Scanning and writing wpa_supplicant.conf both need root. |
|
| 4 | - | set -u |
|
| 5 | - | ||
| 6 | - | CONF=/etc/wpa_supplicant/wpa_supplicant.conf |
|
| 7 | - | ||
| 8 | - | if [ "$(id -u)" -ne 0 ]; then |
|
| 9 | - | exec doas "$0" "$@" |
|
| 10 | - | fi |
|
| 11 | - | ||
| 12 | - | # Ask the kernel which interfaces are wireless rather than assuming wlan0. |
|
| 13 | - | IFACES=$(iw dev | awk '$1 == "Interface" { print $2 }') |
|
| 14 | - | ||
| 15 | - | case $(printf '%s' "$IFACES" | grep -c .) in |
|
| 16 | - | 0) gum style --foreground 196 "No wireless interfaces found."; exit 1 ;; |
|
| 17 | - | 1) IFACE=$IFACES ;; |
|
| 18 | - | *) IFACE=$(printf '%s\n' "$IFACES" | gum choose --header "Interface:") ;; |
|
| 19 | - | esac |
|
| 20 | - | ||
| 21 | - | if [ -z "$IFACE" ]; then |
|
| 22 | - | exit 1 |
|
| 23 | - | fi |
|
| 24 | - | ||
| 25 | - | ip link set "$IFACE" up 2>/dev/null |
|
| 26 | - | ||
| 27 | - | SCAN=$(mktemp) |
|
| 28 | - | trap 'rm -f "$SCAN"' EXIT INT TERM |
|
| 29 | - | ||
| 30 | - | # The scan has to run inside the spinner, and into a file — gum spin owns |
|
| 31 | - | # stdout while it renders. `scan dump` reuses cached results if the card is |
|
| 32 | - | # busy (already associated), which is when a live scan tends to fail. |
|
| 33 | - | gum spin --spinner dot --title "Scanning for networks..." -- sh -c ' |
|
| 34 | - | iw dev "$1" scan > "$2" 2>/dev/null || iw dev "$1" scan dump > "$2" 2>/dev/null |
|
| 35 | - | ' _ "$IFACE" "$SCAN" |
|
| 36 | - | ||
| 37 | - | # Only real SSID lines: bare "SSID: " is a hidden network, and "SSID List" |
|
| 38 | - | # under extended capabilities is not a network at all. |
|
| 39 | - | SSIDS=$(sed -n 's/^[[:space:]]*SSID: \(..*\)$/\1/p' "$SCAN" | sort -u) |
|
| 40 | - | ||
| 41 | - | SSID="" |
|
| 42 | - | if [ -n "$SSIDS" ]; then |
|
| 43 | - | # Never pipe an empty list into gum filter: with no input it falls back to |
|
| 44 | - | # listing the current directory, which looks like a network list but isn't. |
|
| 45 | - | SSID=$(printf '%s\n' "$SSIDS" | gum filter --placeholder "Select a network...") |
|
| 46 | - | else |
|
| 47 | - | gum style --foreground 214 "No networks found on $IFACE — enter one manually." |
|
| 48 | - | fi |
|
| 49 | - | ||
| 50 | - | if [ -z "$SSID" ]; then |
|
| 51 | - | SSID=$(gum input --placeholder "SSID" --prompt "Network name: ") |
|
| 52 | - | fi |
|
| 53 | - | ||
| 54 | - | if [ -z "$SSID" ]; then |
|
| 55 | - | gum style --foreground 196 "No SSID entered, aborting." |
|
| 56 | - | exit 1 |
|
| 57 | - | fi |
|
| 58 | - | ||
| 59 | - | PASS=$(gum input --password --placeholder "leave empty for an open network" --prompt "Password: ") |
|
| 60 | - | ||
| 61 | - | # Build the new config off to the side: a rejected passphrase must not blank |
|
| 62 | - | # out the config that is currently working. |
|
| 63 | - | NEW=$(mktemp) |
|
| 64 | - | trap 'rm -f "$SCAN" "$NEW"' EXIT INT TERM |
|
| 65 | - | ||
| 66 | - | if [ -z "$PASS" ]; then |
|
| 67 | - | printf 'network={\n\tssid="%s"\n\tkey_mgmt=NONE\n}\n' "$SSID" > "$NEW" |
|
| 68 | - | elif ! wpa_passphrase "$SSID" "$PASS" 2>/dev/null | grep -v '^[[:space:]]*#psk=' > "$NEW"; then |
|
| 69 | - | gum style --foreground 196 "Rejected by wpa_passphrase — password must be 8-63 characters." |
|
| 70 | - | exit 1 |
|
| 71 | - | fi |
|
| 72 | - | ||
| 73 | - | if [ ! -s "$NEW" ]; then |
|
| 74 | - | gum style --foreground 196 "Could not generate a config for $SSID, aborting." |
|
| 75 | - | exit 1 |
|
| 76 | - | fi |
|
| 77 | - | ||
| 78 | - | [ -f "$CONF" ] && cp "$CONF" "$CONF.bak" |
|
| 79 | - | cat "$NEW" > "$CONF" |
|
| 80 | - | chmod 600 "$CONF" |
|
| 81 | - | ||
| 82 | - | gum spin --spinner dot --title "Connecting to $SSID..." -- sh -c ' |
|
| 83 | - | # wpa_supplicant is a supervised OpenRC service on this host and will not |
|
| 84 | - | # reread its config on its own, so restart it rather than signalling it. |
|
| 85 | - | # This also covers the case where wifi-disable stopped it outright. |
|
| 86 | - | # ifup is no help here: wlan0 has no stanza in /etc/network/interfaces. |
|
| 87 | - | rc-service wpa_supplicant restart >/dev/null 2>&1 |
|
| 88 | - | ||
| 89 | - | # Give the supplicant time to associate before asking for a lease — a |
|
| 90 | - | # DHCP request sent before association just burns its whole timeout. |
|
| 91 | - | i=0 |
|
| 92 | - | while [ "$i" -lt 20 ]; do |
|
| 93 | - | iw dev "$1" link 2>/dev/null | grep -q "^Connected to" && break |
|
| 94 | - | sleep 1 |
|
| 95 | - | i=$((i + 1)) |
|
| 96 | - | done |
|
| 97 | - | ||
| 98 | - | # Replace any DHCP client left over from a previous connect; without this |
|
| 99 | - | # they stack up, one per run, all renewing the same lease. |
|
| 100 | - | PIDFILE=/var/run/udhcpc.$1.pid |
|
| 101 | - | if [ -f "$PIDFILE" ]; then |
|
| 102 | - | kill "$(cat "$PIDFILE")" 2>/dev/null |
|
| 103 | - | rm -f "$PIDFILE" |
|
| 104 | - | fi |
|
| 105 | - | ip addr flush dev "$1" 2>/dev/null |
|
| 106 | - | ||
| 107 | - | # Same invocation ifupdown-ng uses for a dhcp stanza. udhcpc daemonises |
|
| 108 | - | # once it has the lease, so this returns with the address already set. |
|
| 109 | - | udhcpc -b -R -p "$PIDFILE" -i "$1" -x "hostname:$(hostname)" >/dev/null 2>&1 |
|
| 110 | - | ' _ "$IFACE" |
|
| 111 | - | ||
| 112 | - | if ip addr show "$IFACE" | grep -q "inet "; then |
|
| 113 | - | gum style --foreground 46 --bold "$("$(dirname "$0")/wifi-status" "$IFACE") connected on $IFACE" |
|
| 114 | - | else |
|
| 115 | - | gum style --foreground 196 --bold " Failed to connect to $SSID" |
|
| 116 | - | fi |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: gum, openrc, ifupdown-ng |
|
| 3 | - | # Stopping the wpa_supplicant service and downing the link both need root. |
|
| 4 | - | set -u |
|
| 5 | - | ||
| 6 | - | IFACE="${1:-wlan0}" |
|
| 7 | - | ||
| 8 | - | if [ "$(id -u)" -ne 0 ]; then |
|
| 9 | - | exec doas "$0" "$@" |
|
| 10 | - | fi |
|
| 11 | - | ||
| 12 | - | gum confirm "Disable WiFi on $IFACE?" || exit 0 |
|
| 13 | - | ||
| 14 | - | gum spin --spinner dot --title "Disabling $IFACE..." -- sh -c ' |
|
| 15 | - | # wpa_supplicant runs under supervise-daemon (supervisor= in |
|
| 16 | - | # /etc/init.d/wpa_supplicant), which respawns the daemon a couple of |
|
| 17 | - | # seconds after any kill. Stopping the service is what takes the |
|
| 18 | - | # supervisor down with it; pkill only ever kills the child. |
|
| 19 | - | rc-service -s -q wpa_supplicant stop |
|
| 20 | - | ||
| 21 | - | # wlan0 has no stanza in /etc/network/interfaces, so ifdown is a no-op on |
|
| 22 | - | # this host and leaves the DHCP client behind. Try it anyway for hosts |
|
| 23 | - | # where the interface *is* configured, then clean up by pidfile. |
|
| 24 | - | ifdown "$1" 2>/dev/null |
|
| 25 | - | ||
| 26 | - | PIDFILE=/var/run/udhcpc.$1.pid |
|
| 27 | - | if [ -f "$PIDFILE" ]; then |
|
| 28 | - | kill "$(cat "$PIDFILE")" 2>/dev/null |
|
| 29 | - | rm -f "$PIDFILE" |
|
| 30 | - | fi |
|
| 31 | - | ||
| 32 | - | ip addr flush dev "$1" 2>/dev/null |
|
| 33 | - | ip link set "$1" down |
|
| 34 | - | ' _ "$IFACE" |
|
| 35 | - | ||
| 36 | - | gum style --foreground 214 " $IFACE disabled" |
| 1 | - | #!/bin/sh |
|
| 2 | - | # Requires: iw |
|
| 3 | - | # Prints the link state with a Nerd Font icon, for the status bar and menus. |
|
| 4 | - | # Wireless is the subject: the icon tracks signal strength while connected, |
|
| 5 | - | # so it moves as the link does. When wifi is not carrying traffic it falls |
|
| 6 | - | # back to reporting a live wired link rather than saying "off" while the |
|
| 7 | - | # machine is plainly online. |
|
| 8 | - | # Usage: wifi-status [-p|--plain] [-w|--wifi-only] [IFACE] |
|
| 9 | - | # -p plain output, no Nerd Font icon (for terminals without one) |
|
| 10 | - | # -w never fall back to wired, even with no wifi |
|
| 11 | - | # IFACE wireless interface to report (default: the first one the kernel |
|
| 12 | - | # lists); naming one implies -w, since the question was about it |
|
| 13 | - | set -u |
|
| 14 | - | ||
| 15 | - | PLAIN=0 |
|
| 16 | - | WIFI_ONLY=0 |
|
| 17 | - | ||
| 18 | - | while [ $# -gt 0 ]; do |
|
| 19 | - | case "$1" in |
|
| 20 | - | -p|--plain) PLAIN=1 ;; |
|
| 21 | - | -w|--wifi-only) WIFI_ONLY=1 ;; |
|
| 22 | - | -*) echo "usage: wifi-status [-p] [-w] [IFACE]" >&2; exit 2 ;; |
|
| 23 | - | *) break ;; |
|
| 24 | - | esac |
|
| 25 | - | shift |
|
| 26 | - | done |
|
| 27 | - | ||
| 28 | - | # Every exit path prints through this, so -p only has to be handled once. |
|
| 29 | - | say() { |
|
| 30 | - | if [ "$PLAIN" -eq 1 ]; then |
|
| 31 | - | echo "$2" |
|
| 32 | - | else |
|
| 33 | - | echo "$1 $2" |
|
| 34 | - | fi |
|
| 35 | - | } |
|
| 36 | - | ||
| 37 | - | # A wired interface counts only if it is a real NIC (a `device` symlink rules |
|
| 38 | - | # out docker0, tailscale0 and the veths), is up, and holds a v4 address — |
|
| 39 | - | # that last part is what says it can actually carry traffic. |
|
| 40 | - | wired_iface() { |
|
| 41 | - | for path in /sys/class/net/*; do |
|
| 42 | - | iface=${path##*/} |
|
| 43 | - | [ "$iface" = "lo" ] && continue |
|
| 44 | - | [ -d "$path/wireless" ] || [ -e "$path/phy80211" ] && continue |
|
| 45 | - | [ -e "$path/device" ] || continue |
|
| 46 | - | [ "$(cat "$path/operstate" 2>/dev/null)" = "up" ] || continue |
|
| 47 | - | ip addr show "$iface" 2>/dev/null | grep -q "inet " || continue |
|
| 48 | - | echo "$iface" |
|
| 49 | - | return 0 |
|
| 50 | - | done |
|
| 51 | - | return 1 |
|
| 52 | - | } |
|
| 53 | - | ||
| 54 | - | # wired link carrying traffic. Only consulted once wifi has come up empty, |
|
| 55 | - | # so a working wifi connection always wins the block. |
|
| 56 | - | fallback() { |
|
| 57 | - | [ "$WIFI_ONLY" -eq 1 ] && return 1 |
|
| 58 | - | WIRED=$(wired_iface) || return 1 |
|
| 59 | - | say "" "$WIRED" |
|
| 60 | - | exit 0 |
|
| 61 | - | } |
|
| 62 | - | ||
| 63 | - | if [ -n "${1:-}" ]; then |
|
| 64 | - | # An explicitly named interface is the whole question — no wired fallback, |
|
| 65 | - | # so callers like wifi-connect report on the link they just brought up. |
|
| 66 | - | IFACE="$1" |
|
| 67 | - | WIFI_ONLY=1 |
|
| 68 | - | else |
|
| 69 | - | # Ask the kernel which interfaces are wireless rather than assuming wlan0. |
|
| 70 | - | IFACE=$(iw dev 2>/dev/null | awk '$1 == "Interface" { print $2; exit }') |
|
| 71 | - | fi |
|
| 72 | - | ||
| 73 | - | # no wireless hardware at all, card present but down/unassociated, |
|
| 74 | - | # associated without a lease, connected at rising signal strength. |
|
| 75 | - | if [ -z "$IFACE" ]; then |
|
| 76 | - | fallback |
|
| 77 | - | say "" "none" |
|
| 78 | - | exit 0 |
|
| 79 | - | fi |
|
| 80 | - | ||
| 81 | - | # `iw link` prints "Not connected." while the card is up but unassociated, and |
|
| 82 | - | # fails outright once wifi-disable has downed the link. |
|
| 83 | - | LINK=$(iw dev "$IFACE" link 2>/dev/null) || { fallback; say "" "off"; exit 0; } |
|
| 84 | - | ||
| 85 | - | case "$LINK" in |
|
| 86 | - | "Connected to"*) ;; |
|
| 87 | - | *) fallback; say "" "off"; exit 0 ;; |
|
| 88 | - | esac |
|
| 89 | - | ||
| 90 | - | SSID=$(printf '%s\n' "$LINK" | sed -n 's/^[[:space:]]*SSID: \(..*\)$/\1/p') |
|
| 91 | - | [ -n "$SSID" ] || SSID="unknown" |
|
| 92 | - | ||
| 93 | - | # Associated but without a lease carries no traffic, so wired wins here too; |
|
| 94 | - | # without one, name the network but say the lease is missing rather than |
|
| 95 | - | # showing an SSID that looks connected. |
|
| 96 | - | if ! ip addr show "$IFACE" 2>/dev/null | grep -q "inet "; then |
|
| 97 | - | fallback |
|
| 98 | - | say "" "$SSID (no ip)" |
|
| 99 | - | exit 0 |
|
| 100 | - | fi |
|
| 101 | - | ||
| 102 | - | # "signal: -47 dBm" — the thresholds are the usual rule of thumb: better than |
|
| 103 | - | # -50 is excellent, -70 is the edge of usable. |
|
| 104 | - | DBM=$(printf '%s\n' "$LINK" | awk '/signal:/ { printf "%d", $2; exit }') |
|
| 105 | - | ||
| 106 | - | if [ -z "$DBM" ]; then ICON="" # no signal line (some drivers) — assume good |
|
| 107 | - | elif [ "$DBM" -ge -50 ]; then ICON="" |
|
| 108 | - | elif [ "$DBM" -ge -60 ]; then ICON="" |
|
| 109 | - | elif [ "$DBM" -ge -70 ]; then ICON="" |
|
| 110 | - | else ICON="" |
|
| 111 | - | fi |
|
| 112 | - | ||
| 113 | - | say "$ICON" "$SSID" |
| 39 | 39 | export CLICOLOR=1 |
|
| 40 | 40 | ||
| 41 | 41 | # Go |
|
| 42 | - | export GOROOT="/usr/local/go" |
|
| 43 | 42 | export GOPATH="$HOME/.local/share/go" |
|
| 44 | 43 | export GOMODCACHE="$HOME/.local/share/go-mod-cache" |
|
| 45 | - | export GOPRIVATE="*gitlab*" |
|
| 46 | 44 | ||
| 47 | 45 | # bat |
|
| 48 | 46 | export BAT_THEME="ansi" |