summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsteven-na <noreply.github@stvnc.dev>2026-08-12 01:07:10 -0700
committersteven-na <noreply.github@stvnc.dev>2026-08-12 01:07:10 -0700
commit0e6cc2aa67a83c9a05e2dc776140266a108c7c13 (patch)
treefe5fc46e2db503a037495361c4268c09b7b5f95d
parenta5177ddfccc72ed3f4ec536d69341fbdc41457bd (diff)
Eww weather dropdown
-rw-r--r--arch/eww/.config/eww/eww.scss60
-rw-r--r--arch/eww/.config/eww/eww.yuck46
-rwxr-xr-xarch/eww/.config/eww/scripts/weather-fetch76
-rwxr-xr-xarch/eww/.config/eww/scripts/weather-hourly65
-rwxr-xr-xarch/eww/.config/eww/scripts/weather-notify53
5 files changed, 204 insertions, 96 deletions
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}&current_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)"