initial commit

This commit is contained in:
UniverseBow 2026-02-20 21:15:08 -08:00
commit 4a6050b930
6 changed files with 705 additions and 0 deletions

11
eww/cava.sh Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
cava -p <(cat <<EOF
[general]
bars = 26
[output]
method = raw
raw_target = /dev/stdout
data_format = ascii
ascii_max_range = 6
EOF
) | sed -u 's/;//g;s/0/▂/g;s/1/▃/g;s/2/▄/g;s/3/▅/g;s/4/▆/g;s/5/▇/g;s/6/█/g;'

180
eww/eww.scss Normal file
View file

@ -0,0 +1,180 @@
* {
all: unset;
font-family: Hack;
font-weight: 900;
}
label {
color: #232629;
font-size: 32px;
}
.box {
background-color: rgba(242, 242, 243, .6);
padding: 10px;
border-radius: 25px;
border: 4px solid rgba(196, 214, 226, .6);
box-shadow: inset 0 0 2px rgba(0,0,0,0.2);
}
/* Time, Date, and Weather */
label.time {
font-size: 35px;
text-shadow: 0 0 2px rgba(0,0,0,0.3);
}
label.date {
font-size: 26px;
text-shadow: 0 0 2px rgba(0,0,0,0.3);
}
/* status bar css */
.spacer {
margin-left: 5px;
margin-right: 5px;
border: 2px solid rgba(196, 214, 226, .6);
border-radius: 25px;
}
.system .label {
font-size: 18px;
margin-top: -35px;
}
.system .label2 {
font-size: 14px;
margin-top: -130px;
}
.system .progress {
margin-bottom: -90px;
}
.progress progress {
min-width: 8px;
border-radius: 25px;
}
.system progress {
background-color: #89b4fa;
box-shadow: 0 0 2px rgba(0,0,0,0.3);
}
.system .progress trough {
min-height: 120px;
min-width:10px;
box-shadow: inset 0 0 3px rgba(0,0,0,0.2);
}
.progress trough {
background-color: #ffffff;
border-radius: 25px;
}
/* ----------------------------------------------- */
/* Media Player Variables */
$bg-color: rgba(242, 242, 243, .5);
$primary-neon: #89b4fa; /* Cyan */
$secondary-neon: #232629; /* Magenta */
$text-color: #232629;
$font-family: "Hack";
.music-widget {
background-color: $bg-color;
border-radius: 25px;
padding: 15px;
color: $text-color;
font-family: $font-family;
border: 5px solid rgba(196, 214, 226, .6);
box-shadow: inset 0 0 1.5px rgba(0,0,0,0.2);
}
.cover-art {
background-size: cover;
background-position: center;
min-width: 155px;
min-height: 125px;
border-radius: 25px;
margin-right: 5px;
margin-bottom: 5px;
margin-top: 5px;
padding-right:10px ;
padding-bottom: 40px;
border: 5px solid rgba(190, 190, 210, 1);
box-shadow: 0 0 3px rgba(0,0,0,0.8);
}
.music-info {
padding: 5px 0;
margin-right: 10px;
}
.song-title {
font-size: 15px;
font-weight: bold;
color: $secondary-neon;
text-shadow: 0 0 1px rgba($secondary-neon, 0.3);
margin-bottom: 5px;
}
.artist {
font-size: 14px;
opacity: 0.70;
color: $primary-neon;
margin-bottom: 5px;
}
.controls {
margin: 8px 0;
font-size: 22px;
}
.controls button {
background: none;
border: none;
color: $secondary-neon;
padding: 0 10px;
transition: all 0.2s ease-in-out;
text-shadow: 0 20 60px rgba($secondary-neon, 0.1);
}
.controls button:hover {
color: $primary-neon;
text-shadow: 0 0 10px rgba($secondary-neon, 1);
}
.position {
font-size: 12px;
opacity: 0.75;
color: $text-color;
margin-bottom: 10px;
}
.progress-bar {
margin-left: 5px;
padding: 0;
border: none;
}
.progress-bar trough {
background-color: rgba($primary-neon, 0.1);
border-radius: 25px;
min-height: 12px;
min-width: 180px;
border: 1px solid rgba($primary-neon, 0.3);
box-shadow: inset 0 0 3px rgba(0,0,0,0.4);
}
.progress-bar highlight {
background-image: linear-gradient(to right, $primary-neon, $secondary-neon);
border-radius: 25px;
box-shadow: 0 0 3px $primary-neon;
}
.cava-visualizer {
margin-left: 5px;
font-family: "monospace";
font-size: 10px;
letter-spacing: 1px;
color: $primary-neon;
text-shadow: 0 0 6px rgba($primary-neon, 0.5);
margin-top: 10px;
text-shadow: 0 0 3px $primary-neon;
}

257
eww/eww.yuck Normal file
View file

@ -0,0 +1,257 @@
;; weather import
(defpoll ICON :interval "15m" `~/.config/eww/scripts/weather_info --icon`)
(defpoll STAT :interval "15m" `~/.config/eww/scripts/weather_info --stat`)
(defpoll TEMP :interval "5m" `~/.config/eww/scripts/weather_info --temp`)
(defpoll HEX :interval "15m" `~/.config/eww/scripts/weather_info --hex`)
(defpoll QUOTE :interval "15m" `~/.config/eww/scripts/weather_info --quote`)
(defpoll QUOTE2 :interval "15m" `~/.config/eww/scripts/weather_info --quote2`)
;;Time Date Weather ------------------------------------------------------
(defwidget time []
(box :class "box"
:orientation "h"
:space-evenly true
(label :class "time" :text "${time}" :halign "center" :hexpand true)))
(defpoll time :interval "1s"
"date '+%I:%M:%S %P'")
(defwindow time
:monitor 1
:geometry (geometry :x "0.5%"
:y "2%"
:width "270px"
:height "70px"
:anchor "bottom left")
:stacking "bottom"
(time))
(defpoll date :interval "1s"
"date '+%A %D'")
(defwidget date []
(box :class "box"
:orientation "h"
:space-evenly false
(label :class "date" :text "${date}" :xalign 0)))
(defwidget weather []
(box :class "box"
:orientation "h"
:space-evenly false
(label :class "date" :text "${ICON}${TEMP}" :halign "center" :hexpand true)))
(defwindow date
:monitor 1
:geometry (geometry :x "440" :y "2%"
:width "467px" :height "70px"
:anchor "bottom left")
:stacking "bottom"
(date))
(defwindow weather
:monitor 1
:geometry (geometry :x "285" :y "2%"
:width "145px" :height "70px"
:anchor "bottom left")
:stacking "bottom"
(weather))
;; --- Listeners & Polls ---
;; Use `deflisten` with `playerctl --follow` to react to player events
;; efficiently. When a player quits, `playerctl` exits, and the `|| echo`
;; part provides a default value, ensuring the UI updates correctly.
(deflisten song_title :initial "No Music Playing"
"playerctl metadata --format '{{title}}' --follow || echo 'No Music Playing'")
(deflisten song_artist :initial "Unknown Artist"
"playerctl metadata --format '{{artist}}' --follow || echo 'Unknown Artist'")
(deflisten player_status :initial "Stopped"
"playerctl metadata --format '{{status}}' --follow || echo 'Stopped'")
;; Polling is still needed for things that change continuously, like position,
;; but we can use longer, more efficient intervals.
(defpoll song_length_s :interval "2s"
:initial "1"
"playerctl metadata mpris:length | awk '{print $1 / 1000000}' || echo 1")
(defpoll song_position_s :initial "0" :interval "1s" "playerctl position || echo 0")
(defpoll formatted_position :interval "1s" "playerctl position -f '{{duration(position)}}' || echo '0:00'")
;; Cover art and volume can also be polled at a slower rate.
(defpoll cover_art_path :interval "1s" "$HOME/.config/eww/get-cover.sh")
;; Cava runs as a persistent listener, which is correct.
(deflisten cava :initial "" "bash $HOME/.config/eww/cava.sh")
;; --- Widget Definition ---
(defwidget music-widget []
(box :class "music-widget" :orientation "h" :space-evenly false
(box :class "cover-art" :style "background-image: url('${cover_art_path}');")
(box :class "music-info" :orientation "v" :space-evenly false
(label :class "song-title" :text song_title :limit-width 25)
(label :class "artist" :text song_artist :limit-width 25)
(box :class "controls" :orientation "h"
(button :onclick "playerctl previous" "󰒮")
(button :onclick "playerctl play-pause" {player_status == "Playing" ? "󰏤" : "󰐊"})
(button :onclick "playerctl next" "󰒭"))
(label :class "position" :text formatted_position)
(scale :class "progress-bar"
:min 0
:max song_length_s
:value song_position_s
:onchange "playerctl position {}")
(label :class "cava-visualizer" :text cava)
)
)
)
;; --- Window Definition ---
(defwindow music-widget
:monitor 1
:stacking "bottom"
:geometry (
geometry :x "467px"
:y "10px"
:width "440px"
:height "120px"
)
:wm-ignore true
(music-widget))
;;----------------------------------------------------------------------------------------
;;status widget
(defwidget spacer []
(box :class "spacer"))
(defwidget cpu []
(system-progress
:class "cpu"
:tip "CPU ${round(EWW_CPU.avg,1)}%"
:value {round(EWW_CPU.avg, 0)}
:icon ""
:pct "${round(EWW_CPU.avg, 0)}%"))
(defwidget cput []
(system-progress
:class "cput"
:tip "CPU Temp ${EWW_TEMPS["CORETEMP_PACKAGE_ID_0"]}°C"
:value {((EWW_TEMPS["CORETEMP_PACKAGE_ID_0"] - 25) / 75) * 100}
:icon ""
:pct "${EWW_TEMPS["CORETEMP_PACKAGE_ID_0"]}°"))
(defpoll gput :interval "2s" :initial "0"
"nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits")
(defwidget gput []
(system-progress
:class "gput"
:tip "GPU Temp ${gput}°C"
:value {((gput - 25) / 75) * 100}
:icon ""
:pct "${gput}°"))
(defpoll gpu :interval "2s" :initial "0"
"nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits")
(defwidget gpu []
(system-progress
:class "gpu"
:tip "GPU ${gpu}%"
:value {gpu}
:icon ""
:pct "${gpu}%"))
(defwidget ram []
(system-progress
:class "ram"
:tip "RAM ${round(EWW_RAM.used_mem / 1048576, 1)}MB"
:value {round(EWW_RAM.used_mem_perc, 0)}
:icon ""
:pct "${round(EWW_RAM.used_mem / 34359738368 * 100, 0)}%"))
(defwidget swap []
(system-progress
:class "swap"
:tip "Swap ${round((EWW_RAM.total_swap - EWW_RAM.free_swap) / 1048576, 1)}MB"
:value {round((EWW_RAM.total_swap - EWW_RAM.free_swap) / EWW_RAM.total_swap * 100, 0)}
:icon ""
:pct "${round((EWW_RAM.total_swap - EWW_RAM.free_swap) / EWW_RAM.total_swap * 100, 0)}%"))
(defwidget netdown []
(system-progress
:class "netdown"
:tip "Down ${round(EWW_NET["eno1"].NET_DOWN / 1048576, 1)} MB/s"
:value {min(max(log(EWW_NET["eno1"].NET_DOWN / 1024 + 1, 10000) * 100, 0), 100)}
:icon "󰇚"
:pct {EWW_NET["eno1"].NET_DOWN / 1048576 > 1 ? "${round(EWW_NET["eno1"].NET_DOWN / 1048576, 1)}M" : "${round(EWW_NET["eno1"].NET_DOWN / 1024, 0)}Kb"}))
(defwidget netup []
(system-progress
:class "netup"
:tip "Up ${round(EWW_NET["eno1"].NET_UP / 1048576, 1)} MB/s"
:value {min(max(log(EWW_NET["eno1"].NET_UP / 1024 + 1, 10000) * 100, 0), 100)}
:icon "󰕒"
:pct {EWW_NET["eno1"].NET_UP / 1048576 > 1 ? "${round(EWW_NET["eno1"].NET_UP / 1048576, 1)}M" : "${round(EWW_NET["eno1"].NET_UP / 1024, 0)}Kb"}))
(defwidget disk []
(system-progress
:class "disk"
:tip "SSD ${round(EWW_DISK["/"].used_perc, 1)}%"
:value {round(EWW_DISK["/"].used_perc, 0)}
:icon ""
:pct "${round(EWW_DISK["/"].used_perc, 1)}%"))
;;--------------------------------------------------------------------------------------------
(defwidget systemwidget []
(box
:class "system box"
:orientation "h"
:spacing 10
:space-evenly false
(box :orientation "v" :spacing 8 :space-evenly false :hexpand false
(image :path "/home/lorkan/cpu.svg" :image-width 40 :image-height 40)
(box :orientation "h" :spacing 8 :space-evenly true :hexpand true
(cpu)
(cput)))
(box :class "spacer" :orientation "v" :hexpand false)
(box :orientation "v" :spacing 8 :space-evenly false :hexpand false
(image :path "/home/lorkan/gpu.svg" :image-width 40 :image-height 40)
(box :orientation "h" :spacing 8 :space-evenly true :hexpand true
(gpu)
(gput)))
(box :class "spacer" :orientation "v" :hexpand false)
(box :orientation "v" :spacing 8 :space-evenly false :hexpand false
(image :class "svg" :path "/home/lorkan/ram.png" :image-width 40 :image-height 40)
(box :orientation "h" :spacing 8 :space-evenly true :hexpand true
(ram)
(swap)))
(box :class "spacer" :orientation "v" :hexpand false)
(box :orientation "h" :spacing 8 :space-evenly true :hexpand true
(netdown)
(netup)
(disk))))
(defwidget system-progress [class value icon pct tip]
(box
:class "progress"
:orientation "v"
:spacing 0
(progress
:orientation "v"
:flipped true
:class class
:value value
:halign "center"
:tooltip tip)
(box
:orientation "v"
(label :class "label" :text icon :halign "center")
(label :class "label2" :text pct :halign "center"))))
(defwindow status
:monitor 1
:geometry (geometry :x "10px" :y "10px" :width "80px" :height "30px"
:anchor "top l")
:stacking "bottom"
(systemwidget))

65
eww/get-cover.sh Executable file
View file

@ -0,0 +1,65 @@
#!/bin/bash
# This script fetches and caches album art for the current song.
# It's designed to be called by EWW and is optimized to minimize
# network and disk I/O.
WIDGET_DIR=$(dirname "$0")
DEFAULT_COVER="$WIDGET_DIR/assets/DEFAULTImage.jpeg"
CACHE_DIR="$HOME/.cache/eww/music-widget"
mkdir -p "$CACHE_DIR"
# Function to safely exit with the default cover
show_default_cover() {
echo "$DEFAULT_COVER"
exit 0
}
# Get the art URL. Exit if player is not running or no URL is available.
COVER_URL=$(playerctl metadata mpris:artUrl 2>/dev/null)
if [[ -z "$COVER_URL" ]]; then
show_default_cover
fi
# If it's a local file (file://), decode the path and use it directly.
if [[ "$COVER_URL" == file://* ]]; then
# URL-decode the path to handle special characters like spaces
LOCAL_PATH=$(printf '%b' "${COVER_URL#file://}")
if [[ -f "$LOCAL_PATH" ]]; then
echo "$LOCAL_PATH"
exit 0
else
show_default_cover
fi
fi
# For web URLs, we hash the URL to create a unique filename for caching.
URL_HASH=$(echo -n "$COVER_URL" | md5sum | awk '{print $1}')
# Try to get a file extension from the URL, default to .jpg if it's weird.
EXTENSION="${COVER_URL##*.}"
if [[ "$EXTENSION" == "$COVER_URL" ]] || [[ ${#EXTENSION} -gt 5 ]]; then
EXTENSION="jpg"
fi
CACHED_COVER="$CACHE_DIR/$URL_HASH.$EXTENSION"
# If the cover is not already cached, download it.
if [ ! -f "$CACHED_COVER" ]; then
# Download with a 5-second timeout. If it fails or the downloaded file is empty,
# clean up and show the default cover.
curl -s -L --max-time 5 "$COVER_URL" -o "$CACHED_COVER"
if [[ $? -ne 0 ]] || [[ ! -s "$CACHED_COVER" ]]; then
rm -f "$CACHED_COVER"
show_default_cover
fi
fi
# As a final sanity check, ensure the cached file is a valid image.
# If not, remove the junk file and show the default cover.
if file "$CACHED_COVER" | grep -qE 'image|jpeg|png|jpg|gif'; then
echo "$CACHED_COVER"
else
rm -f "$CACHED_COVER"
show_default_cover
fi
exit 0

147
eww/scripts/weather_info Executable file
View file

@ -0,0 +1,147 @@
#!/bin/bash
## Collect data
cache_dir="$HOME/.cache/eww/weather"
cache_weather_stat=${cache_dir}/weather-stat
cache_weather_degree=${cache_dir}/weather-degree
cache_weather_quote=${cache_dir}/weather-quote
cache_weather_hex=${cache_dir}/weather-hex
cache_weather_icon=${cache_dir}/weather-icon
## Weather data
KEY="527ef08abfd3b851a188de297cbae02c"
ID="5342992"
UNIT="imperial" # Available options : 'metric' or 'imperial'
## Make cache dir
if [[ ! -d "$cache_dir" ]]; then
mkdir -p ${cache_dir}
fi
## Get data
get_weather_data() {
weather=`curl -sf "http://api.openweathermap.org/data/2.5/weather?APPID="$KEY"&id="$ID"&units="$UNIT""`
echo ${weather}
if [ ! -z "$weather" ]; then
weather_temp=`echo "$weather" | jq ".main.temp" | cut -d "." -f 1`
weather_icon_code=`echo "$weather" | jq -r ".weather[].icon" | head -1`
weather_description=`echo "$weather" | jq -r ".weather[].description" | head -1 | sed -e "s/\b\(.\)/\u\1/g"`
#Big long if statement of doom
if [ "$weather_icon_code" == "50d" ]; then
weather_icon=" "
weather_quote="Forecast says it's misty \nMake sure you don't get lost on your way..."
weather_hex="#84afdb"
elif [ "$weather_icon_code" == "50n" ]; then
weather_icon=" "
weather_quote="Forecast says it's a misty night \nDon't go anywhere tonight or you might get lost..."
weather_hex="#84afdb"
elif [ "$weather_icon_code" == "01d" ]; then
weather_icon=" "
weather_quote="It's a sunny day, gonna be fun! \nDon't go wandering all by yourself though..."
weather_hex="#ffd86b"
elif [ "$weather_icon_code" == "01n" ]; then
weather_icon=" "
weather_quote="It's a clear night \nYou might want to take a evening stroll to relax..."
weather_hex="#fcdcf6"
elif [ "$weather_icon_code" == "02d" ]; then
weather_icon=" "
weather_quote="It's cloudy, sort of gloomy \nYou'd better get a book to read..."
weather_hex="#adadff"
elif [ "$weather_icon_code" == "02n" ]; then
weather_icon=" "
weather_quote="It's a cloudy night \nHow about some hot chocolate and a warm bed?"
weather_hex="#adadff"
elif [ "$weather_icon_code" == "03d" ]; then
weather_icon=" "
weather_quote="It's cloudy, sort of gloomy \nYou'd better get a book to read..."
weather_hex="#adadff"
elif [ "$weather_icon_code" == "03n" ]; then
weather_icon=" "
weather_quote="It's a cloudy night \nHow about some hot chocolate and a warm bed?"
weather_hex="#adadff"
elif [ "$weather_icon_code" == "04d" ]; then
weather_icon=" "
weather_quote="It's cloudy, sort of gloomy \nYou'd better get a book to read..."
weather_hex="#adadff"
elif [ "$weather_icon_code" == "04n" ]; then
weather_icon=" "
weather_quote="It's a cloudy night \nHow about some hot chocolate and a warm bed?"
weather_hex="#adadff"
elif [ "$weather_icon_code" == "09d" ]; then
weather_icon=" "
weather_quote="It's rainy, it's a great day! \nGet some ramen and watch as the rain falls..."
weather_hex="#6b95ff"
elif [ "$weather_icon_code" == "09n" ]; then
weather_icon=" "
weather_quote=" It's gonna rain tonight it seems \nMake sure your clothes aren't still outside..."
weather_hex="#6b95ff"
elif [ "$weather_icon_code" == "10d" ]; then
weather_icon=" "
weather_quote="It's rainy, it's a great day! \nGet some ramen and watch as the rain falls..."
weather_hex="#6b95ff"
elif [ "$weather_icon_code" == "10n" ]; then
weather_icon=" "
weather_quote=" It's gonna rain tonight it seems \nMake sure your clothes aren't still outside..."
weather_hex="#6b95ff"
elif [ "$weather_icon_code" == "11d" ]; then
weather_icon=""
weather_quote="There's storm for forecast today \nMake sure you don't get blown away..."
weather_hex="#ffeb57"
elif [ "$weather_icon_code" == "11n" ]; then
weather_icon=""
weather_quote="There's gonna be storms tonight \nMake sure you're warm in bed and the windows are shut..."
weather_hex="#ffeb57"
elif [ "$weather_icon_code" == "13d" ]; then
weather_icon=" "
weather_quote="It's gonna snow today \nYou'd better wear thick clothes and make a snowman as well!"
weather_hex="#e3e6fc"
elif [ "$weather_icon_code" == "13n" ]; then
weather_icon=" "
weather_quote="It's gonna snow tonight \nMake sure you get up early tomorrow to see the sights..."
weather_hex="#e3e6fc"
elif [ "$weather_icon_code" == "40d" ]; then
weather_icon=" "
weather_quote="Forecast says it's misty \nMake sure you don't get lost on your way..."
weather_hex="#84afdb"
elif [ "$weather_icon_code" == "40n" ]; then
weather_icon=" "
weather_quote="Forecast says it's a misty night \nDon't go anywhere tonight or you might get lost..."
weather_hex="#84afdb"
else
weather_icon=" "
weather_quote="Sort of odd, I don't know what to forecast \nMake sure you have a good time!"
weather_hex="#adadff"
fi
echo "$weather_icon" > ${cache_weather_icon}
echo "$weather_description" > ${cache_weather_stat}
echo "$weather_temp""°F" > ${cache_weather_degree}
echo -e "$weather_quote" > ${cache_weather_quote}
echo "$weather_hex" > ${cache_weather_hex}
else
echo "Weather Unavailable" > ${cache_weather_stat}
echo " " > ${cache_weather_icon}
echo -e "Ah well, no weather huh? \nEven if there's no weather, it's gonna be a great day!" > ${cache_weather_quote}
echo "-" > ${cache_weather_degree}
echo "#adadff" > ${tcache_weather_hex}
fi
}
## Execute
if [[ "$1" == "--getdata" ]]; then
get_weather_data
elif [[ "$1" == "--icon" ]]; then
cat ${cache_weather_icon}
elif [[ "$1" == "--temp" ]]; then
cat ${cache_weather_degree}
elif [[ "$1" == "--hex" ]]; then
cat ${cache_weather_hex}
elif [[ "$1" == "--stat" ]]; then
cat ${cache_weather_stat}
elif [[ "$1" == "--quote" ]]; then
cat ${cache_weather_quote} | head -n1
elif [[ "$1" == "--quote2" ]]; then
cat ${cache_weather_quote} | tail -n1
fi

45
eww/volumecontroller.sh Executable file
View file

@ -0,0 +1,45 @@
#!/usr/bin/env sh
# A script to control volume using pamixer.
#
# Usage:
# volumecontroller.sh <command> [value]
#
# Commands:
# up - Increase volume by 'step'
# down - Decrease volume by 'step'
# mute - Toggle mute
# get - Get current volume percentage
# set - Set volume to a specific percentage [value]
set -e # Exit immediately if a command exits with a non-zero status.
COMMAND="$1"
VALUE="$2"
STEP=5
case $COMMAND in
up)
pamixer -i "${VALUE:-$STEP}"
;;
down)
pamixer -d "${VALUE:-$STEP}"
;;
mute)
pamixer -t
;;
get)
pamixer --get-volume
;;
set)
if [ -z "$VALUE" ]; then
echo "Usage: $0 set <0-100>" >&2
exit 1
fi
pamixer --set-volume "$VALUE"
;;
*)
echo "Usage: $0 <up|down|mute|get|set> [value]" >&2
exit 1
;;
esac