From 0e6cc2aa67a83c9a05e2dc776140266a108c7c13 Mon Sep 17 00:00:00 2001 From: steven-na Date: Wed, 12 Aug 2026 01:07:10 -0700 Subject: Eww weather dropdown --- arch/eww/.config/eww/eww.scss | 60 +++++++++++++++++++++++ arch/eww/.config/eww/eww.yuck | 46 +++++++++++++++-- arch/eww/.config/eww/scripts/weather-fetch | 76 ++++++++++++++--------------- arch/eww/.config/eww/scripts/weather-hourly | 65 ++++++++++++++++++++++++ arch/eww/.config/eww/scripts/weather-notify | 53 -------------------- 5 files changed, 204 insertions(+), 96 deletions(-) create mode 100755 arch/eww/.config/eww/scripts/weather-hourly delete mode 100755 arch/eww/.config/eww/scripts/weather-notify (limited to 'arch') diff --git a/arch/eww/.config/eww/eww.scss b/arch/eww/.config/eww/eww.scss index 3164718..806d355 100644 --- a/arch/eww/.config/eww/eww.scss +++ b/arch/eww/.config/eww/eww.scss @@ -119,3 +119,63 @@ $warn: #f38ba8; .bat-normal { color: #f5c542; } /* yellow */ .bat-low { color: #e85555; } /* red */ .bat-charging { color: #7ddc9f; } /* light green */ + +// ── Weather hourly popup ───────────────────────────────────────────────── + +.weather-popup { + background-color: transparentize($bg, 0.5); + color: $fg; + border: 1px solid transparentize($fg, 0.85); + border-radius: 6px; + padding: 8px 10px; +} + +.hour-cell { + padding: 4px 6px; + border-radius: 4px; +} + +.hour-cell-current { + background-color: transparentize($accent, 0.8); +} + +.hour-time { + color: $muted; + font-size: 10px; +} + +.hour-cell-current .hour-time { + color: $accent; + font-weight: bold; +} + +.hour-icon { + font-size: 16px; + color: $fg; +} + +.hour-cell-current .hour-icon { + color: $accent; +} + +.hour-temp { + color: $fg; + font-size: 11px; +} + +.hour-cell-current .hour-temp { + color: $accent; + font-weight: bold; +} + +.hour-precip { + color: $accent; + font-size: 9px; +} + +.weather-summary { + color: $muted; + font-size: 10px; + padding-bottom: 4px; + border-bottom: 1px solid transparentize($fg, 0.9); +} diff --git a/arch/eww/.config/eww/eww.yuck b/arch/eww/.config/eww/eww.yuck index 832af3d..8a100c5 100644 --- a/arch/eww/.config/eww/eww.yuck +++ b/arch/eww/.config/eww/eww.yuck @@ -23,7 +23,7 @@ `df / | awk 'NR==2{printf "%d", $3/$2*100}'`) (defpoll volume :interval "1s" - `pamixer --get-volume 2>/dev/null || echo "0"`) + `pamixer --sink alsa_output.pci-0000_00_1f.3.analog-stereo --get-volume 2>/dev/null || echo "0"`) ;; Use sysfs operstate — works regardless of network manager backend (defpoll wifi-iface :interval "5s" @@ -47,6 +47,10 @@ :initial "+...°F" "scripts/weather-fetch") +(defpoll weather-hourly :interval "20m" + :initial "[]" + "scripts/weather-hourly") + ;; ── Left: Date + Music ─────────────────────────────────────────────── (defwidget music [] @@ -78,7 +82,7 @@ (defwidget audio-stat [] (eventbox - :onscroll "[ '{}' = 'up' ] && pamixer --increase 5 || pamixer --decrease 5" + :onscroll "[ '{}' = 'up' ] && pamixer --sink alsa_output.pci-0000_00_1f.3.analog-stereo --increase 5 || pamixer --sink alsa_output.pci-0000_00_1f.3.analog-stereo --decrease 5" (box :class "stat" :space-evenly false :spacing 3 (label :class "stat-label" :text "") (label :class "stat-val" :text "${volume}%")))) @@ -126,9 +130,31 @@ (defwidget weather-stat [] (eventbox - :onclick "scripts/weather-notify &" + :onclick "eww open --toggle weather-popup" (label :class "stat-val weather" :text weather-display))) +(defwidget hour-cell [entry] + (box :class {entry.current ? "hour-cell hour-cell-current" : "hour-cell"} + :orientation "v" :space-evenly false :spacing 2 :halign "center" + (label :class "hour-time" :text {entry.time}) + (label :class "hour-icon" :text {entry.icon}) + (label :class "hour-temp" + :text {entry.temp >= 0 ? "+${entry.temp}°" : "${entry.temp}°"}) + (label :class "hour-precip" + :visible {entry.precip >= 20} + :text "${entry.precip}%"))) + +(defwidget weather-popup-content [] + (box :class "weather-popup" :orientation "v" :space-evenly false :spacing 6 + (box :class "weather-summary" :space-evenly false :spacing 10 + (label :text "Feels ${weather-hourly.current.feels_like}°") + (label :text "H:${weather-hourly.current.high}° L:${weather-hourly.current.low}°") + (label :text "Humidity ${weather-hourly.current.humidity}%") + (label :text "Wind ${weather-hourly.current.wind}mph")) + (box :class "weather-ticker" :orientation "h" :space-evenly true :spacing 4 + (for entry in {weather-hourly.hours} + (hour-cell :entry entry))))) + (defwidget right-section [] (box :class "right" :halign "end" :space-evenly false :spacing 8 (bright-stat) @@ -170,3 +196,17 @@ :exclusive true :focusable false (bar)) + +(defwindow weather-popup + :monitor 0 + :windowtype "normal" + :stacking "overlay" + :focusable false + :exclusive false + :geometry (geometry + :x "65px" + :y "4px" + :width "640px" + :height "80px" + :anchor "top left") + (weather-popup-content)) diff --git a/arch/eww/.config/eww/scripts/weather-fetch b/arch/eww/.config/eww/scripts/weather-fetch index 9191edd..65d623a 100755 --- a/arch/eww/.config/eww/scripts/weather-fetch +++ b/arch/eww/.config/eww/scripts/weather-fetch @@ -2,63 +2,59 @@ # scripts/weather-fetch loc_file="$HOME/.config/eww/.weather-location" -loc=$(cat "$loc_file" 2>/dev/null | xargs | sed 's/ /%20/g') -url="wttr.in/${loc}?format=j1" +loc=$(cat "$loc_file" 2>/dev/null | xargs) +lat="${loc%%,*}" +lon="${loc##*,}" + +url="https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t_weather=true&temperature_unit=fahrenheit&timezone=auto" data=$(curl -sf "$url") [[ -z "$data" ]] && echo "?°F" && exit 1 -temp=$(echo "$data" | jq -r '.current_condition[0].temp_F') -code=$(echo "$data" | jq -r '.current_condition[0].weatherCode') - -# ── Night check ──────────────────────────────────────────────────────────── -sunrise_raw=$(echo "$data" | jq -r '.weather[0].astronomy[0].sunrise') -sunset_raw=$(echo "$data" | jq -r '.weather[0].astronomy[0].sunset') - -sunrise_epoch=$(date -d "today $sunrise_raw" +%s) -sunset_epoch=$(date -d "today $sunset_raw" +%s) -now_epoch=$(date +%s) +temp=$(echo "$data" | jq -r '.current_weather.temperature | round') +code=$(echo "$data" | jq -r '.current_weather.weathercode') +is_day=$(echo "$data" | jq -r '.current_weather.is_day') -is_night=false -[[ $now_epoch -ge $sunset_epoch || $now_epoch -lt $sunrise_epoch ]] && is_night=true # ── Moon phase ───────────────────────────────────────────────────────────── moon_icon() { local ref; ref=$(date -d "2000-01-06" +%s) # known new moon local days=$(( ($(date +%s) - ref) / 86400 )) local phase=$(( days % 29 )) - if [[ $phase -le 1 ]]; then echo "󰽤" # new - elif [[ $phase -le 7 ]]; then echo "󰽧" # waxing crescent - elif [[ $phase -le 8 ]]; then echo "󰽡" # first quarter - elif [[ $phase -le 14 ]]; then echo "󰽨" # waxing gibbous - elif [[ $phase -le 15 ]]; then echo "󰽢" # full - elif [[ $phase -le 21 ]]; then echo "󰽦" # waning gibbous - elif [[ $phase -le 22 ]]; then echo "󰽣" # last quarter - else echo "󰽥" # waning crescent + if [[ $phase -le 1 ]]; then echo "󰽤" # new + elif [[ $phase -le 7 ]]; then echo "󰽧" # waxing crescent + elif [[ $phase -le 8 ]]; then echo "󰽡" # first quarter + elif [[ $phase -le 14 ]]; then echo "󰽨" # waxing gibbous + elif [[ $phase -le 15 ]]; then echo "󰽢" # full + elif [[ $phase -le 21 ]]; then echo "󰽦" # waning gibbous + elif [[ $phase -le 22 ]]; then echo "󰽣" # last quarter + else echo "󰽥" # waning crescent fi } -# ── Icon selection ───────────────────────────────────────────────────────── -if $is_night; then +# ── Icon selection (WMO weather codes) ────────────────────────────────────── +if [[ "$is_day" == "1" ]]; then case "$code" in - 113) icon=$(moon_icon) ;; # clear → moon phase - 116) icon="" ;; # nf-fa-cloud_moon - 119|122) icon="" ;; # overcast (no moon visible) - 143|248|260) icon="" ;; # fog/mist - 200|386|389|392|395) icon="" ;; # nf-fa-cloud_moon_rain (thunder) - 227|230|323|326|329|332|335|338|368|371) icon="" ;; # snow (no night variant needed) - 182|185|281|284|311|314|317|320|350|362|365|374|377) icon="" ;; # nf-fa-cloud_moon_rain (sleet) - *) icon="" ;; # nf-fa-cloud_moon_rain (rain) + 0|1) icon="󰖙" ;; + 2) icon="󰖕" ;; + 3) icon="󰖐" ;; + 45|48) icon="" ;; + 56|57|66|67) icon="" ;; + 51|53|55|61|63|65|80|81|82) icon="" ;; + 71|73|75|77|85|86) icon="" ;; + 95|96|99) icon="" ;; + *) icon="" ;; esac else case "$code" in - 113) icon="󰖙" ;; - 116) icon="󰖕" ;; - 119|122) icon="󰖐" ;; - 143|248|260) icon="" ;; - 200|386|389|392|395) icon="" ;; - 227|230|323|326|329|332|335|338|368|371) icon="" ;; - 182|185|281|284|311|314|317|320|350|362|365|374|377) icon="" ;; - *) icon="" ;; + 0|1) icon=$(moon_icon) ;; + 2) icon="" ;; + 3) icon="" ;; + 45|48) icon="" ;; + 56|57|66|67) icon="" ;; + 51|53|55|61|63|65|80|81|82) icon="" ;; + 71|73|75|77|85|86) icon="" ;; + 95|96|99) icon="" ;; + *) icon="" ;; esac fi diff --git a/arch/eww/.config/eww/scripts/weather-hourly b/arch/eww/.config/eww/scripts/weather-hourly new file mode 100755 index 0000000..7ae91b5 --- /dev/null +++ b/arch/eww/.config/eww/scripts/weather-hourly @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# scripts/weather-hourly +# Emits {current:{...}, hours:[...12 entries...]} from Open-Meteo for the +# hourly ticker popup: prev hour, current hour, next 10 hours. + +loc_file="$HOME/.config/eww/.weather-location" +loc=$(cat "$loc_file" 2>/dev/null | xargs) +lat="${loc%%,*}" +lon="${loc##*,}" + +url="https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&hourly=temperature_2m,weathercode,is_day,precipitation_probability,apparent_temperature,relative_humidity_2m,windspeed_10m&daily=temperature_2m_max,temperature_2m_min&temperature_unit=fahrenheit&windspeed_unit=mph&past_hours=1&forecast_hours=11&timezone=auto" + +data=$(curl -sf "$url") +[[ -z "$data" ]] && echo '{"current":{},"hours":[]}' && exit 1 + +# ── Moon phase (duplicated from scripts/weather-fetch; kept self-contained) ─ +moon_icon() { + local ref; ref=$(date -d "2000-01-06" +%s) + local days=$(( ($(date +%s) - ref) / 86400 )) + local phase=$(( days % 29 )) + if [[ $phase -le 1 ]]; then echo "󰽤" + elif [[ $phase -le 7 ]]; then echo "󰽧" + elif [[ $phase -le 8 ]]; then echo "󰽡" + elif [[ $phase -le 14 ]]; then echo "󰽨" + elif [[ $phase -le 15 ]]; then echo "󰽢" + elif [[ $phase -le 21 ]]; then echo "󰽦" + elif [[ $phase -le 22 ]]; then echo "󰽣" + else echo "󰽥" + fi +} + +echo "$data" | jq -c --arg moon "$(moon_icon)" ' + def icon(code; day; moon): + if (code == 0 or code == 1) then (if day == 1 then "󰖙" else moon end) + elif code == 2 then (if day == 1 then "󰖕" else "" end) + elif code == 3 then (if day == 1 then "󰖐" else "" end) + elif ([45,48] | index(code) != null) then (if day == 1 then "" else "" end) + elif ([56,57,66,67] | index(code) != null) then (if day == 1 then "" else "" end) + elif ([51,53,55,61,63,65,80,81,82] | index(code) != null) then (if day == 1 then "" else "" end) + elif ([71,73,75,77,85,86] | index(code) != null) then (if day == 1 then "" else "" end) + elif (code == 95 or code == 96 or code == 99) then (if day == 1 then "" else "" end) + else (if day == 1 then "" else "" end) + end; + .hourly as $h + | .daily as $d + | { + current: { + feels_like: ($h.apparent_temperature[1] | round), + humidity: $h.relative_humidity_2m[1], + wind: ($h.windspeed_10m[1] | round), + high: ($d.temperature_2m_max[0] | round), + low: ($d.temperature_2m_min[0] | round) + }, + hours: ( + [range(0; ($h.time | length))] + | map({ + time: ($h.time[.] | strptime("%Y-%m-%dT%H:%M") | mktime | strftime("%-I%p")), + temp: ($h.temperature_2m[.] | round), + icon: icon($h.weathercode[.]; $h.is_day[.]; $moon), + current: (. == 1), + precip: $h.precipitation_probability[.] + }) + ) + } +' diff --git a/arch/eww/.config/eww/scripts/weather-notify b/arch/eww/.config/eww/scripts/weather-notify deleted file mode 100755 index 9710ad4..0000000 --- a/arch/eww/.config/eww/scripts/weather-notify +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash -# scripts/weather-notify - -loc_file="$HOME/.config/eww/.weather-location" -loc=$(cat "$loc_file" 2>/dev/null | xargs | sed 's/ /%20/g') -url="wttr.in/${loc}?format=j1" - -data=$(curl -sf "$url") -[[ -z "$data" ]] && notify-send "Weather" "Could not fetch weather data." && exit 1 - -temp=$(echo "$data" | jq -r '.current_condition[0].temp_F') -feels=$(echo "$data" | jq -r '.current_condition[0].FeelsLikeF') -desc=$(echo "$data" | jq -r '.current_condition[0].weatherDesc[0].value') -high=$(echo "$data" | jq -r '.weather[0].maxtempF') -low=$(echo "$data" | jq -r '.weather[0].mintempF') - -# ── Night / moon ─────────────────────────────────────────────────────────── -sunrise_raw=$(echo "$data" | jq -r '.weather[0].astronomy[0].sunrise') -sunset_raw=$(echo "$data" | jq -r '.weather[0].astronomy[0].sunset') - -sunrise_epoch=$(date -d "today $sunrise_raw" +%s) -sunset_epoch=$(date -d "today $sunset_raw" +%s) -now_epoch=$(date +%s) - -is_night=false -[[ $now_epoch -ge $sunset_epoch || $now_epoch -lt $sunrise_epoch ]] && is_night=true - -moon_phase() { - local ref; ref=$(date -d "2000-01-06" +%s) - local phase=$(( ( ($(date +%s) - ref) / 86400 ) % 29 )) - if [[ $phase -le 1 ]]; then echo "New Moon" - elif [[ $phase -le 7 ]]; then echo "Waxing Crescent" - elif [[ $phase -le 8 ]]; then echo "First Quarter" - elif [[ $phase -le 14 ]]; then echo "Waxing Gibbous" - elif [[ $phase -le 15 ]]; then echo "Full Moon" - elif [[ $phase -le 21 ]]; then echo "Waning Gibbous" - elif [[ $phase -le 22 ]]; then echo "Last Quarter" - else echo "Waning Crescent" - fi -} - -sign() { [ "$1" -ge 0 ] && echo "+" || echo ""; } - -line1="$(sign $temp)${temp}°F, ${desc}" -line2="Feels $(sign $feels)${feels}° · ↑$(sign $high)${high} ↓$(sign $low)${low}" - -notify-send --urgency=low --expire-time=5000 \ - --icon=weather-clear \ - "Weather" "${line1}\n${line2}" - -$is_night && notify-send --urgency=low --expire-time=5000 \ - --icon=weather-clear \ - "Moon" "$(moon_phase)" -- cgit v1.2.3