Weather&Audio
This commit is contained in:
@@ -43,3 +43,38 @@ calendar {
|
|||||||
.datetime {
|
.datetime {
|
||||||
padding: $gaps-window;
|
padding: $gaps-window;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clock window accents
|
||||||
|
.weather-accent { background-color: $base0A; }
|
||||||
|
.vol-accent { background-color: $base0D; }
|
||||||
|
|
||||||
|
// Weather section
|
||||||
|
.weather-main { margin-bottom: 8pt; }
|
||||||
|
.weather-icon { font-size: 2.2em; margin-right: 12pt; @include color-accent; }
|
||||||
|
.weather-temp { font-size: 1.3em; font-weight: bold; @include color-base; }
|
||||||
|
.weather-desc { font-size: 0.78em; @include color-body; }
|
||||||
|
.weather-stats { margin-top: 4pt; }
|
||||||
|
|
||||||
|
// Volume section
|
||||||
|
.ctrl-row { margin-bottom: 4pt; }
|
||||||
|
.ctrl-icon { font-size: 1.1em; min-width: 22pt; @include color-body; }
|
||||||
|
.ctrl-muted { @include color-inactive; }
|
||||||
|
.ctrl-value { font-size: 0.72em; min-width: 28pt; @include color-active; }
|
||||||
|
|
||||||
|
scale.ctrl-slider {
|
||||||
|
padding: 2pt 4pt;
|
||||||
|
trough {
|
||||||
|
@include border-radius;
|
||||||
|
@include background-base2;
|
||||||
|
min-height: 5px;
|
||||||
|
highlight { background-color: $base0C; @include border-radius; }
|
||||||
|
}
|
||||||
|
slider {
|
||||||
|
background-color: $base07;
|
||||||
|
min-height: 11px;
|
||||||
|
min-width: 11px;
|
||||||
|
@include border-radius;
|
||||||
|
margin: -3px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
(include "windows/sys.yuck")
|
(include "windows/sys.yuck")
|
||||||
(include "windows/net.yuck")
|
(include "windows/net.yuck")
|
||||||
|
(include "windows/clock.yuck")
|
||||||
(include "windows/popup.yuck")
|
(include "windows/popup.yuck")
|
||||||
(include "windows/radio.yuck")
|
(include "windows/radio.yuck")
|
||||||
(include "windows/powermenu.yuck")
|
(include "windows/powermenu.yuck")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
(eventbox
|
(eventbox
|
||||||
:onhover "${EWW_CMD} update date_rev=true"
|
:onhover "${EWW_CMD} update date_rev=true"
|
||||||
:onhoverlost "${EWW_CMD} update date_rev=false"
|
:onhoverlost "${EWW_CMD} update date_rev=false"
|
||||||
:onclick "(sleep 0.1 && scripts/panel-toggle calendar)"
|
:onclick "(sleep 0.1 && scripts/panel-toggle clock)"
|
||||||
:onrightclick "(sleep 0.1 && eww-open-on-current-screen powermenu --toggle)"
|
:onrightclick "(sleep 0.1 && eww-open-on-current-screen powermenu --toggle)"
|
||||||
(box
|
(box
|
||||||
:class "datetime"
|
:class "datetime"
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Toggle the shared popup window between panels (sys, net, calendar).
|
|
||||||
# Same panel clicked again → close. Different panel → close and reopen
|
|
||||||
# so the window always appears fully rendered with no transparent flash.
|
|
||||||
PANEL="$1"
|
PANEL="$1"
|
||||||
CURRENT=$(eww state 2>/dev/null | grep '^active-panel:' | sed 's/^active-panel: //' | tr -d '"')
|
CURRENT=$(eww state 2>/dev/null | grep '^active-panel:' | sed 's/^active-panel: //' | tr -d '"')
|
||||||
|
|
||||||
@@ -9,7 +6,6 @@ if [ "$CURRENT" = "$PANEL" ]; then
|
|||||||
eww close popup
|
eww close popup
|
||||||
eww update active-panel=""
|
eww update active-panel=""
|
||||||
else
|
else
|
||||||
eww close popup 2>/dev/null
|
|
||||||
eww update active-panel="$PANEL"
|
eww update active-panel="$PANEL"
|
||||||
SCREEN=$(hyprctl monitors -j 2>/dev/null | jq -r '.[] | select(.focused == true) | .name' | head -n1)
|
SCREEN=$(hyprctl monitors -j 2>/dev/null | jq -r '.[] | select(.focused == true) | .name' | head -n1)
|
||||||
[ -n "$SCREEN" ] && eww open popup --screen "$SCREEN" || eww open popup
|
[ -n "$SCREEN" ] && eww open popup --screen "$SCREEN" || eww open popup
|
||||||
|
|||||||
33
modules/home/wayland/apps/eww/bar/scripts/volume
Executable file
33
modules/home/wayland/apps/eww/bar/scripts/volume
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
volicons=("" "" "")
|
||||||
|
|
||||||
|
vol() { wpctl get-volume @DEFAULT_AUDIO_"$1"@ | awk '{print int($2*100)}'; }
|
||||||
|
ismuted() { wpctl get-volume @DEFAULT_AUDIO_"$1"@ | rg -qi muted; echo -n $?; }
|
||||||
|
setvol() { wpctl set-volume @DEFAULT_AUDIO_"$1"@ "$(awk -v n="$2" 'BEGIN{print n/100}')"; }
|
||||||
|
setmute() { wpctl set-mute @DEFAULT_AUDIO_"$1"@ toggle; }
|
||||||
|
|
||||||
|
gen_output() {
|
||||||
|
percent=$(vol "SINK")
|
||||||
|
lvl=$(awk -v n="$percent" 'BEGIN{print int(n/34)}')
|
||||||
|
sink_muted=$(ismuted "SINK")
|
||||||
|
source_muted=$(ismuted "SOURCE")
|
||||||
|
|
||||||
|
[ "$sink_muted" = 0 ] && icon="" || icon="${volicons[$lvl]}"
|
||||||
|
[ "$source_muted" = 0 ] && mic_icon="" || mic_icon=""
|
||||||
|
|
||||||
|
printf '{"icon":"%s","percent":%s,"sink_muted":%s,"mic_icon":"%s","microphone":%s,"source_muted":%s}\n' \
|
||||||
|
"$icon" "$percent" "$([ "$sink_muted" = 0 ] && echo true || echo false)" \
|
||||||
|
"$mic_icon" "$(vol SOURCE)" "$([ "$source_muted" = 0 ] && echo true || echo false)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
mute) setmute "$2" ;;
|
||||||
|
setvol) setvol "$2" "$3" ;;
|
||||||
|
*)
|
||||||
|
gen_output
|
||||||
|
pw-cli -m 2>/dev/null | rg --line-buffered "PipeWire:Interface:Client" | while read -r _; do
|
||||||
|
gen_output
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
esac
|
||||||
100
modules/home/wayland/apps/eww/bar/scripts/weather
Executable file
100
modules/home/wayland/apps/eww/bar/scripts/weather
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
FALLBACK='{"temp":0,"feelslike":0,"humidity":0,"wind":0,"desc":"Unavailable","icon":"","city":""}'
|
||||||
|
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/eww"
|
||||||
|
LOC_CACHE="$CACHE_DIR/weather-location"
|
||||||
|
UA="eww-bar/1.0 cedric.hoelzl@gmail.com"
|
||||||
|
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
|
||||||
|
get_location() {
|
||||||
|
# Cache location for 1 hour; IP rarely changes
|
||||||
|
if [ -f "$LOC_CACHE" ] && [ -n "$(find "$LOC_CACHE" -mmin -60 2>/dev/null)" ]; then
|
||||||
|
cat "$LOC_CACHE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
data=$(curl -sf --max-time 5 "http://ip-api.com/json?fields=lat,lon,city") || return 1
|
||||||
|
echo "$data" | tee "$LOC_CACHE"
|
||||||
|
}
|
||||||
|
|
||||||
|
icon_for() {
|
||||||
|
case "$1" in
|
||||||
|
*thunder*) echo "" ;;
|
||||||
|
*snow*|*sleet*) echo "" ;;
|
||||||
|
heavyrain*|*heavyrainshowers*) echo "" ;;
|
||||||
|
*rain*|*shower*) echo "" ;;
|
||||||
|
fog*) echo "" ;;
|
||||||
|
cloudy*) echo "" ;;
|
||||||
|
partlycloudy*) echo "" ;;
|
||||||
|
fair*|clearsky*) echo "" ;;
|
||||||
|
*) echo "" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
desc_for() {
|
||||||
|
case "$(echo "$1" | sed 's/_day//;s/_night//;s/_polartwilight//')" in
|
||||||
|
clearsky) echo "Clear sky" ;;
|
||||||
|
fair) echo "Fair" ;;
|
||||||
|
partlycloudy) echo "Partly cloudy" ;;
|
||||||
|
cloudy) echo "Cloudy" ;;
|
||||||
|
fog) echo "Foggy" ;;
|
||||||
|
lightrain) echo "Light rain" ;;
|
||||||
|
rain) echo "Rain" ;;
|
||||||
|
heavyrain) echo "Heavy rain" ;;
|
||||||
|
lightrainshowers) echo "Light showers" ;;
|
||||||
|
rainshowers) echo "Rain showers" ;;
|
||||||
|
heavyrainshowers) echo "Heavy showers" ;;
|
||||||
|
lightrainandthunder) echo "Light rain & thunder" ;;
|
||||||
|
rainandthunder) echo "Rain & thunder" ;;
|
||||||
|
heavyrainandthunder) echo "Heavy rain & thunder" ;;
|
||||||
|
*showersandthunder) echo "Showers & thunder" ;;
|
||||||
|
lightsleet|lightsleetshowers) echo "Light sleet" ;;
|
||||||
|
sleet|sleetshowers) echo "Sleet" ;;
|
||||||
|
heavysleet|heavysleetshowers) echo "Heavy sleet" ;;
|
||||||
|
*sleetandthunder) echo "Sleet & thunder" ;;
|
||||||
|
lightsnow|lightsnowshowers) echo "Light snow" ;;
|
||||||
|
snow|snowshowers) echo "Snow" ;;
|
||||||
|
heavysnow|heavysnowshowers) echo "Heavy snow" ;;
|
||||||
|
*snowandthunder) echo "Snow & thunder" ;;
|
||||||
|
*) echo "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch() {
|
||||||
|
loc=$(get_location) || { echo "$FALLBACK"; return; }
|
||||||
|
lat=$(echo "$loc" | jq -r '.lat')
|
||||||
|
lon=$(echo "$loc" | jq -r '.lon')
|
||||||
|
city=$(echo "$loc" | jq -r '.city')
|
||||||
|
|
||||||
|
data=$(curl -sf --max-time 8 \
|
||||||
|
-H "User-Agent: $UA" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=$lat&lon=$lon") \
|
||||||
|
|| { echo "$FALLBACK"; return; }
|
||||||
|
|
||||||
|
d='.properties.timeseries[0].data'
|
||||||
|
temp=$( echo "$data" | jq -r "${d}.instant.details.air_temperature | round")
|
||||||
|
humidity=$(echo "$data" | jq -r "${d}.instant.details.relative_humidity | round")
|
||||||
|
wind_ms=$( echo "$data" | jq -r "${d}.instant.details.wind_speed")
|
||||||
|
wind=$( echo "$wind_ms" | awk '{printf "%d", $1 * 3.6}')
|
||||||
|
code=$( echo "$data" | jq -r \
|
||||||
|
"(${d}.next_1_hours.summary.symbol_code) // (${d}.next_6_hours.summary.symbol_code) // \"cloudy\"")
|
||||||
|
|
||||||
|
# Simplified apparent temperature: wind chill below 10°C, else = temp
|
||||||
|
feelslike=$(echo "$temp $wind" | awk '{
|
||||||
|
t=$1; v=$2
|
||||||
|
if (v > 4.8 && t < 10)
|
||||||
|
printf "%d", 13.12 + 0.6215*t - 11.37*(v^0.16) + 0.3965*t*(v^0.16)
|
||||||
|
else
|
||||||
|
printf "%d", t
|
||||||
|
}')
|
||||||
|
|
||||||
|
icon=$(icon_for "$code")
|
||||||
|
desc=$(desc_for "$code")
|
||||||
|
|
||||||
|
printf '{"temp":%s,"feelslike":%s,"humidity":%s,"wind":%s,"desc":"%s","icon":"%s","city":"%s"}\n' \
|
||||||
|
"$temp" "$feelslike" "$humidity" "$wind" "$desc" "$icon" "$city"
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch
|
||||||
|
while true; do sleep 600; fetch; done
|
||||||
72
modules/home/wayland/apps/eww/bar/windows/clock.yuck
Normal file
72
modules/home/wayland/apps/eww/bar/windows/clock.yuck
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
(deflisten weather
|
||||||
|
:initial '{"temp":0,"feelslike":0,"humidity":0,"wind":0,"desc":"","icon":"","city":""}'
|
||||||
|
"scripts/weather")
|
||||||
|
|
||||||
|
(deflisten volume
|
||||||
|
:initial '{"icon":"","percent":50,"sink_muted":false,"mic_icon":"","microphone":50,"source_muted":false}'
|
||||||
|
"scripts/volume")
|
||||||
|
|
||||||
|
; --- Weather ---
|
||||||
|
|
||||||
|
(defwidget weather-section []
|
||||||
|
(box :orientation "v" :space-evenly false :class "sys-section"
|
||||||
|
(section-header
|
||||||
|
:title {weather.city != "" ? "Weather · ${weather.city}" : "Weather"}
|
||||||
|
:accent "weather-accent")
|
||||||
|
(box :orientation "h" :space-evenly false :valign "center" :class "weather-main"
|
||||||
|
(label :class "weather-icon" :text {weather.icon})
|
||||||
|
(box :orientation "v" :space-evenly false
|
||||||
|
(label :class "weather-temp" :text "${weather.temp}°C")
|
||||||
|
(label :class "weather-desc" :text {weather.desc})))
|
||||||
|
(box :orientation "h" :space-evenly true :class "weather-stats"
|
||||||
|
(box :orientation "v" :space-evenly false :halign "center"
|
||||||
|
(label :class "gpu-stat-value" :text "${weather.feelslike}°C")
|
||||||
|
(label :class "gpu-stat-label" :text "feels like"))
|
||||||
|
(box :orientation "v" :space-evenly false :halign "center"
|
||||||
|
(label :class "gpu-stat-value" :text "${weather.humidity}%")
|
||||||
|
(label :class "gpu-stat-label" :text "humidity"))
|
||||||
|
(box :orientation "v" :space-evenly false :halign "center"
|
||||||
|
(label :class "gpu-stat-value" :text "${weather.wind} km/h")
|
||||||
|
(label :class "gpu-stat-label" :text "wind")))))
|
||||||
|
|
||||||
|
; --- Volume ---
|
||||||
|
|
||||||
|
(defwidget vol-row [icon value onchange onclick muted]
|
||||||
|
(box :orientation "h" :space-evenly false :valign "center" :class "ctrl-row"
|
||||||
|
(button
|
||||||
|
:class "ctrl-icon ${muted ? 'ctrl-muted' : ''}"
|
||||||
|
:onclick onclick
|
||||||
|
(label :text icon))
|
||||||
|
(scale
|
||||||
|
:min 0 :max 100 :value value
|
||||||
|
:hexpand true :class "ctrl-slider"
|
||||||
|
:onchange onchange)
|
||||||
|
(label :class "ctrl-value" :halign "end" :text "${value}%")))
|
||||||
|
|
||||||
|
(defwidget volume-section []
|
||||||
|
(box :orientation "v" :space-evenly false :class "sys-section"
|
||||||
|
(section-header :title "Volume" :accent "vol-accent")
|
||||||
|
(vol-row
|
||||||
|
:icon {volume.icon}
|
||||||
|
:value {volume.percent}
|
||||||
|
:muted {volume.sink_muted}
|
||||||
|
:onchange "scripts/volume setvol SINK {}"
|
||||||
|
:onclick "scripts/volume mute SINK")
|
||||||
|
(vol-row
|
||||||
|
:icon {volume.mic_icon}
|
||||||
|
:value {volume.microphone}
|
||||||
|
:muted {volume.source_muted}
|
||||||
|
:onchange "scripts/volume setvol SOURCE {}"
|
||||||
|
:onclick "scripts/volume mute SOURCE")))
|
||||||
|
|
||||||
|
; --- Root ---
|
||||||
|
|
||||||
|
(defwidget clock-win []
|
||||||
|
(box :class "sys-win" :orientation "v" :space-evenly false
|
||||||
|
(weather-section)
|
||||||
|
(box :class "section-sep")
|
||||||
|
(calendar)
|
||||||
|
(box :class "section-sep")
|
||||||
|
(volume-section)))
|
||||||
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
|
|
||||||
(defwidget popup-win []
|
(defwidget popup-win []
|
||||||
(box :space-evenly false :orientation "v"
|
(box :space-evenly false :orientation "v"
|
||||||
(box :visible {active-panel == "sys"}
|
(revealer :reveal {active-panel == "sys"} :transition "slidedown" :duration 120
|
||||||
(sys-win))
|
(sys-win))
|
||||||
(box :visible {active-panel == "net"}
|
(revealer :reveal {active-panel == "net"} :transition "slidedown" :duration 120
|
||||||
(net-win))
|
(net-win))
|
||||||
(box :visible {active-panel == "calendar"} :class "sys-win"
|
(revealer :reveal {active-panel == "clock"} :transition "slidedown" :duration 120
|
||||||
(calendar))))
|
(clock-win))))
|
||||||
|
|
||||||
(defwindow popup
|
(defwindow popup
|
||||||
:monitor 0
|
:monitor 0
|
||||||
|
|||||||
Reference in New Issue
Block a user