diff --git a/home/oli/.config/i3blocks/config b/home/oli/.config/i3blocks/config new file mode 100644 index 0000000..8d7083e --- /dev/null +++ b/home/oli/.config/i3blocks/config @@ -0,0 +1,70 @@ +# Global stuff +separator=true +separator_block_width=8 + +[fortune] +markup=pango +command=~/bin/i3blocks/fortune +interval=300 + +[time] +markup=pango +color=#b1e5c8 +background=#2E8B57 +command=echo " $(date +'%A, %d %B %R %Z') " +interval=10 + +[wifi] +markup=pango +command=~/bin/i3blocks/wifi +interval=20 + +[cputemp] +markup=pango +command=~/bin/i3blocks/cputemp +interval=20 + +# CPU load +[cpu] +markup=pango +command=~/bin/i3blocks/cpu +interval=3 + +[mem] +markup=pango +command=~/bin/i3blocks/mem +interval=30 + +[audio] +markup=pango +command=~/bin/i3blocks/audio +interval=once +signal=1 + +[mic_mute] +markup=pango +command=~/bin/i3blocks/mic_mute +interval=once +signal=2 + +# Indoor temperature and humidity +[homeassistant-slow] +markup=pango +command=~/bin/i3blocks/homeassistant-slow +interval=60 + +# Power draw +[homeassistant-fast] +markup=pango +command=~/bin/i3blocks/homeassistant-fast +interval=10 + +[archupdate] +markup=pango +command=~/bin/i3blocks/archupdates +interval=28800 # 4h + +[weather] +markup=pango +command=~/bin/i3blocks/weather +interval=900 diff --git a/home/oli/.config/sway/config b/home/oli/.config/sway/config index ee47b0b..1efbf31 100644 --- a/home/oli/.config/sway/config +++ b/home/oli/.config/sway/config @@ -44,7 +44,7 @@ output * bg ~/data/local/wallpaper/anders-jilden-AkUR27wtaxs-unsplash.jpg fill # You can get the names of your outputs by running: swaymsg -t get_outputs # BSP-style window tiling -exec_always autotiling +exec_always autotiling-rs # This will lock your screen after 300 seconds of inactivity, then turn off # your displays after another 300 seconds, and turn your screens back on when @@ -197,11 +197,12 @@ bindsym ctrl+alt+shift+r mode "RESIZE" # Volume control with PipeWire # https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h # Get the device ID with `wpctl status` or use @DEFAULT_AUDIO_SINK@ -bindsym --locked XF86AudioMute exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle -# bindsym --release XF86AudioMute exec pkill -USR1 swaybar +# The signal is mapped to a i3block configuration +bindsym --locked XF86AudioMute exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle && pkill -SIGRTMIN+1 i3blocks # `-l` sets max volume level -bindsym --locked XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02- -l 1.0 -bindsym --locked XF86AudioRaiseVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02+ -l 1.0 +bindsym --locked XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02- -l 1.0 && pkill -SIGRTMIN+1 i3blocks +bindsym --locked XF86AudioRaiseVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02+ -l 1.0 && pkill -SIGRTMIN+1 i3blocks + # FIXME: https://wiki.archlinux.org/title/MPRIS # bindsym --locked XF86AudioPause exec wpctl set-mute 48 toggle @@ -212,8 +213,10 @@ bindsym Mod4+Shift+Space exec grimshot --notify save window # man 5 sway-bar bar { position bottom - font "BerkeleyMono Nerd Font Mono Normal Bold 11" - status_command ~/.config/sway/status.sh + # font "BerkeleyMono Nerd Font Mono Normal Bold 10" + font "BerkeleyMono Nerd Font Mono Normal Regular 11" + # status_command ~/.config/sway/status.sh + status_command i3blocks strip_workspace_name no strip_workspace_numbers no colors { diff --git a/home/oli/bin/i3blocks/_utils b/home/oli/bin/i3blocks/_utils new file mode 100644 index 0000000..7607b7a --- /dev/null +++ b/home/oli/bin/i3blocks/_utils @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# +# Shared utilities for i3blocks scripts. +# Source this file to get color variables and the icon() helper. +# Colors are available immediately on source — no function call needed. + +# Generic +ok_fg="#f0f0f0" +warn1_bg="#facc15" +warn1_fg="#36454f" +warn2_bg="#cc8d22" +warn2_fg="#36454f" +crit_bg="#710f3d" +crit_fg="#c0c8c6" + +# Audio +mute_bg="#303030" +mute_fg="#e0e0e0" + +# Weather +frost_bg="#5b8dd9" +frost_fg="#f0f0f0" +cold_bg="#7ec8e3" +cold_fg="#1a1a2e" +warm_bg="#facc15" +warm_fg="#36454f" +hot_bg="#cc3300" +hot_fg="#f0f0f0" + +# Wrap a glyph in Pango markup for consistent icon sizing and vertical alignment +icon() { + echo "$1" +} diff --git a/home/oli/bin/i3blocks/archupdates b/home/oli/bin/i3blocks/archupdates new file mode 100755 index 0000000..0da4bbb --- /dev/null +++ b/home/oli/bin/i3blocks/archupdates @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +. ~/bin/i3blocks/_utils + +# Check if updated Arch packages can be installed +pkgupdate() { + local pkgcount=$(checkupdates | wc -l) + if [[ $pkgcount -ge 1 && $pkgcount -lt 20 ]]; then + local bg=$mute_bg + local fg=$mute_fg + elif [[ $pkgcount -ge 20 ]]; then + local bg=$warn1_bg + local fg=$warn1_fg + fi + local stat="$pkgcount pkg updates" + + if [[ -n "$bg" ]]; then + echo -e "$stat\n" + fi +} + +pkgupdate diff --git a/home/oli/bin/i3blocks/audio b/home/oli/bin/i3blocks/audio new file mode 100755 index 0000000..e37390a --- /dev/null +++ b/home/oli/bin/i3blocks/audio @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# +# Display audio volume/mute status +# Depends on signals in sway config for XF86AudioMute, XF86AudioLowerVolume and +# XF86AudioRaiseVolume + +. ~/bin/i3blocks/_utils + +# Get default audio sink volume and mute status +audio_volume() { + local wpctl_output + wpctl_output=$(wpctl get-volume @DEFAULT_AUDIO_SINK@) + local volume + volume=$(echo "$wpctl_output" | awk '{print $NF * 100}') + local muted + muted=$(echo "$wpctl_output" | grep -c '\[MUTED\]') + + local bg fg icon stat + + if [[ "$muted" -ge 1 ]]; then + bg=$mute_bg + fg=$mute_fg + stat="$(printf '%.0f' "$volume")%" + elif [[ $(echo "$volume >= 80" | bc -l) == "1" ]]; then + bg=$crit_bg + fg=$crit_fg + stat="$(printf '%.0f' "$volume")%" + elif [[ $(echo "$volume >= 65" | bc -l) == "1" ]]; then + bg=$warn2_bg + fg=$warn2_fg + stat="$(printf '%.0f' "$volume")%" + elif [[ $(echo "$volume >= 50" | bc -l) == "1" ]]; then + bg=$warn1_bg + fg=$warn1_fg + stat="$(printf '%.0f' "$volume")%" + else + stat="$(printf '%.0f' "$volume")%" + fi + + if [[ -n "$bg" ]]; then + echo -e "SND $stat \n" + else + echo -e "SND $stat \n" + fi +} + +case "$BLOCK_BUTTON" in + 1) wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle ;; + 4) wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ --limit 1.0 ;; + 5) wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- ;; +esac + +audio_volume diff --git a/home/oli/bin/i3blocks/cpu b/home/oli/bin/i3blocks/cpu new file mode 100755 index 0000000..d26a69f --- /dev/null +++ b/home/oli/bin/i3blocks/cpu @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# +# Displays per-core CPU usage as Unicode block characters. +# Uses a temp file to track deltas between intervals. +# +# ▄ = load below 50% +# █ = load at or above 50% +# Colors escalate through warn1 -> warn2 -> crit thresholds. + +PREV_FILE="/tmp/i3blocks_cpu_prev" + +. ~/bin/i3blocks/_utils +# Read current idle and total ticks for each core from /proc/stat +declare -a cur_idle cur_total +i=0 +while IFS=' ' read -r cpu user nice system idle iowait irq softirq rest; do + [[ "$cpu" =~ ^cpu[0-9]+$ ]] || continue + cur_idle[$i]=$((idle + iowait)) + cur_total[$i]=$((user + nice + system + idle + iowait + irq + softirq)) + ((i++)) +done < /proc/stat +num_cores=$i + +# Load previous snapshot if available +declare -a prev_idle prev_total +if [[ -f "$PREV_FILE" ]]; then + i=0 + while IFS=' ' read -r p_idle p_total; do + prev_idle[$i]=$p_idle + prev_total[$i]=$p_total + ((i++)) + done < "$PREV_FILE" +fi + +# Save current snapshot for next run +for ((i=0; i "$PREV_FILE" + +# Build output — one block character per core +output="" +for ((i=0; i${char}" + else + output+="${char}" + fi +done + +echo -e "CPU $output\n" diff --git a/home/oli/bin/i3blocks/cputemp b/home/oli/bin/i3blocks/cputemp new file mode 100755 index 0000000..b0f2f09 --- /dev/null +++ b/home/oli/bin/i3blocks/cputemp @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +. ~/bin/i3blocks/_utils + +# Get CPU temperature +temp_cpu() { + local cpu=$(expr $(cat /sys/class/thermal/thermal_zone1/temp) / 1000) + if [[ $(echo "$cpu >= 60" | bc -l ) == "1" ]] && [[ $(echo "$cpu < 70" | bc -l ) == "1" ]]; then + local bg=$warn1_bg + local fg=$warn1_fg + elif [[ $(echo "$cpu >= 70" | bc -l ) == "1" ]] && [[ $(echo "$cpu < 80" | bc -l ) == "1" ]]; then + local bg=$warn2_bg + local fg=$warn2_fg + elif [[ $(echo "$cpu >= 80" | bc -l ) == "1" ]]; then + local bg=$crit_bg + local fg=$crit_fg + fi + local stat="CPU $cpu°C" + + if [[ -n "$bg" ]]; then + echo -e "$stat\n" + else + echo -e "$stat\n" + fi +} + +temp_cpu diff --git a/home/oli/bin/i3blocks/fortune b/home/oli/bin/i3blocks/fortune new file mode 100755 index 0000000..3a889d5 --- /dev/null +++ b/home/oli/bin/i3blocks/fortune @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +echo -e "$(fortune -s -n 60 | sed 's/^[[:space:]]*//' | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g' | sed 's/[[:space:]]*$//')\n" diff --git a/home/oli/bin/i3blocks/homeassistant-fast b/home/oli/bin/i3blocks/homeassistant-fast new file mode 100755 index 0000000..25de801 --- /dev/null +++ b/home/oli/bin/i3blocks/homeassistant-fast @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# +# Needs a .env.homeassistant with an API token in ~/.config/sway/.env.homeassistant +# TODO: +# - HA optimize API calls: https://community.home-assistant.io/t/get-limited-number-of-states-entities-when-using-api-states/830323 + +homeassistant_url="http://10.7.1.11:8123/api/states" +. ~/.config/sway/.env.homeassistant + +. ~/bin/i3blocks/_utils + +ha() { + local power=$(curl -s -H "Authorization: Bearer $ha_token" -H "Content-Type: application/json" $homeassistant_url/sensor.inspelning_oli_power | jq -r '.state') + local stat="PWR ${power}W" + echo "$stat" +} + +ha diff --git a/home/oli/bin/i3blocks/homeassistant-slow b/home/oli/bin/i3blocks/homeassistant-slow new file mode 100755 index 0000000..e3a2ca0 --- /dev/null +++ b/home/oli/bin/i3blocks/homeassistant-slow @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# +# Needs a .env.homeassistant with an API token in ~/.config/sway/.env.homeassistant +# TODO: +# - HA optimize API calls: https://community.home-assistant.io/t/get-limited-number-of-states-entities-when-using-api-states/830323 + +homeassistant_url="http://10.7.1.11:8123/api/states" +. ~/.config/sway/.env.homeassistant + +. ~/bin/i3blocks/_utils + +ha() { + local temp_indoor=$(curl -s -H "Authorization: Bearer $ha_token" -H "Content-Type: application/json" $homeassistant_url/sensor.vindstyrka_oli_temperature | jq -r '.state') + local humidity_indoor=$(curl -s -H "Authorization: Bearer $ha_token" -H "Content-Type: application/json" $homeassistant_url/sensor.vindstyrka_oli_humidity | jq -r '.state') + local stat="IN ${temp_indoor}°C ${humidity_indoor}%" + echo $stat +} + +ha diff --git a/home/oli/bin/i3blocks/mem b/home/oli/bin/i3blocks/mem new file mode 100755 index 0000000..f40a11b --- /dev/null +++ b/home/oli/bin/i3blocks/mem @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +. ~/bin/i3blocks/_utils + +# Get total memory usage and show highest consumer +mem(){ + local memused=$(free | grep Mem | awk '{printf "%.0f\n", $3/$2 * 100.0}') + local memproc=$(basename "$(ps --no-headers -A --sort -rss -o cmd | head -1 | awk {'print $1'})") + if [[ $(echo "$memused >= 50" | bc -l ) == "1" ]] && [[ $(echo "$memused < 60" | bc -l ) == "1" ]]; then + local bg=$warn1_bg + local fg=$warn1_fg + elif [[ $(echo "$memused >= 60" | bc -l ) == "1" ]] && [[ $(echo "$memused < 70" | bc -l ) == "1" ]]; then + local bg=$warn2_bg + local fg=$warn2_fg + elif [[ $(echo "$memused >= 70" | bc -l ) == "1" ]]; then + local bg=$crit_bg + local fg=$crit_fg + fi + local stat="MEM ${memused}% [$memproc]" + + if [[ -n "$bg" ]]; then + echo -e " $stat \n" + else + echo -e "$stat\n" + fi +} + +mem diff --git a/home/oli/bin/i3blocks/mic_mute b/home/oli/bin/i3blocks/mic_mute new file mode 100755 index 0000000..56f86b2 --- /dev/null +++ b/home/oli/bin/i3blocks/mic_mute @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# +# Shows microphone mute status for the default audio source. +# Left click toggles mute. + +. ~/bin/i3blocks/_utils + +mic_mute() { + if [[ "$BLOCK_BUTTON" == "1" ]]; then + wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle + fi + + local wpctl_output + wpctl_output=$(wpctl get-volume @DEFAULT_AUDIO_SOURCE@) + local muted + muted=$(echo "$wpctl_output" | grep -c '\[MUTED\]') + + local bg fg icon stat + + if [[ "$muted" -ge 1 ]]; then + bg=$mute_bg + fg=$mute_fg + stat="MUT" + else + bg=$crit_bg + fg=$crit_fg + stat="REC" + fi + + echo -e "MIC $stat \n" +} + +mic_mute diff --git a/home/oli/bin/i3blocks/weather b/home/oli/bin/i3blocks/weather new file mode 100755 index 0000000..bee2a4a --- /dev/null +++ b/home/oli/bin/i3blocks/weather @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# +# Fetches current weather for Herzogenbuchsee, Switzerland via open-meteo.com. +# No API key required. +# +# WMO weather codes: https://open-meteo.com/en/docs#weathervariables + +# Coordinates for Herzogenbuchsee, CH +LAT="47.1897" +LON="7.7058" +# NOTE: MeteoSwiss ICON-CH1 model (~1 km resolution) via open-meteo, matching +# the MeteoSwiss mobile app data source. +API_URL="https://api.open-meteo.com/v1/forecast" +MODEL="meteoswiss_icon_ch1" + +. ~/bin/i3blocks/_utils + +# Map WMO weather interpretation codes to icons (Nerd Font nf-weather-*) +wmo_icon() { + local code=$1 + case $code in + 0) echo "clear" ;; # Clear sky + 1) echo "mostly clear" ;; # Mainly clear + 2) echo "partly cloudy" ;; # Partly cloudy + 3) echo "overcast" ;; # Overcast + 45|48) echo "fog" ;; # Fog / depositing rime fog + 51|53|55) echo "drizzle" ;; # Drizzle: light, moderate, dense + 56|57) echo "freezing drizzle" ;; # Freezing drizzle: light, dense + 61|63|65) echo "rain" ;; # Rain: slight, moderate, heavy + 66|67) echo "freezing rain" ;; # Freezing rain: light, heavy + 71|73|75|77) echo "snow" ;; # Snow: slight, moderate, heavy, snow grains + 80|81|82) echo "showers" ;; # Rain showers: slight, moderate, violent + 85|86) echo "snow showers" ;; # Snow showers: slight, heavy + 95) echo "thunderstorm" ;; # Thunderstorm + 96|99) echo "thunderstorm+hail" ;; # Thunderstorm with hail: slight, heavy + *) echo "?" ;; + esac +} + +weather() { + local response + response=$(curl -sf "${API_URL}?latitude=${LAT}&longitude=${LON}¤t=temperature_2m,apparent_temperature,weather_code,wind_speed_10m&daily=sunrise,sunset&wind_speed_unit=kmh&timezone=Europe%2FZurich&models=${MODEL}") + + if [[ $? -ne 0 || -z "$response" ]]; then + echo " N/A" + exit 0 + fi + + local temp apparent code wind sunrise sunset + temp=$(echo "$response" | jq -r '.current.temperature_2m') + apparent=$(echo "$response" | jq -r '.current.apparent_temperature') + code=$(echo "$response" | jq -r '.current.weather_code') + wind=$(echo "$response" | jq -r '.current.wind_speed_10m') + sunrise=$(echo "$response" | jq -r '.daily.sunrise[0]') + sunset=$(echo "$response" | jq -r '.daily.sunset[0]') + + # Compare current time against sunrise/sunset (format: YYYY-MM-DDTHH:MM) + local now sunrise_epoch sun_label sun_time + now=$(date +%s) + sunrise_epoch=$(date -d "$sunrise" +%s) + + if [[ $now -lt $sunrise_epoch ]]; then + sun_label="" + sun_time=$(date -d "$sunrise" +%H:%M) + else + sun_label="" + sun_time=$(date -d "$sunset" +%H:%M) + fi + + local label + label=$(wmo_icon "$code") + + local bg fg stat + + if [[ $(echo "$temp < 0" | bc -l) == "1" ]]; then + bg=$frost_bg + fg=$frost_fg + elif [[ $(echo "$temp < 8" | bc -l) == "1" ]]; then + bg=$cold_bg + fg=$cold_fg + elif [[ $(echo "$temp >= 28" | bc -l) == "1" ]]; then + bg=$hot_bg + fg=$hot_fg + elif [[ $(echo "$temp >= 22" | bc -l) == "1" ]]; then + bg=$warm_bg + fg=$warm_fg + fi + + # NOTE: apparent temperature shown in parentheses as "feels like" + stat="$label $(printf '%.0f' "$temp")°C ($(printf '%.0f' "$apparent")°C) $(printf '%.0f' "$wind") km/h $sun_label $sun_time" + + if [[ -n "$bg" ]]; then + echo -e "OUT $stat " + else + echo -e "OUT $stat " + fi +} + +weather diff --git a/home/oli/bin/i3blocks/wifi b/home/oli/bin/i3blocks/wifi new file mode 100755 index 0000000..3044716 --- /dev/null +++ b/home/oli/bin/i3blocks/wifi @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +. ~/bin/i3blocks/_utils + +# Get WiFi signal strength +wifi() { + local wifidbm_orig=$(iwctl station wlan0 show | awk '/[[:space:]]RSSI/{print $2}') + local wifidbm=$((wifidbm_orig * -1)) + if [[ $(echo "$wifidbm >= 50" | bc -l ) == "1" ]] && [[ $(echo "$wifidbm < 60" | bc -l ) == "1" ]]; then + local bg=$warn1_bg + local fg=$warn1_fg + elif [[ $(echo "$wifidbm >= 60" | bc -l ) == "1" ]] && [[ $(echo "$wifidbm < 70" | bc -l ) == "1" ]]; then + local bg=$warn2_bg + local fg=$warn2_fg + elif [[ $(echo "$wifidbm >= 70" | bc -l ) == "1" ]]; then + local bg=$crit_bg + local fg=$crit_fg + fi + local stat="NET -${wifidbm} dBm" + + if [[ -n "$bg" ]]; then + echo -e "$stat\n" + else + echo -e "$stat\n" + fi +} + +wifi