diff --git a/hyprland/.local/bin/weather.sh b/hyprland/.local/bin/weather.sh new file mode 100755 index 0000000..c6ff881 --- /dev/null +++ b/hyprland/.local/bin/weather.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Print the current weather as a short icon+temp string, e.g. "🌦️ +83°F". +# Caches the wttr.in response for CACHE_MAX_AGE seconds so the lock screen and +# waybar don't hit the network on every refresh. Override the location with +# WEATHER_CITY or edit CITY below. +set -u + +CITY="${WEATHER_CITY:-Denver}" +CACHE_DIR="${XDG_RUNTIME_DIR:-/tmp}" +CACHE="${CACHE_DIR}/wttr.cache" +CACHE_MAX_AGE=1800 # 30 minutes +URL="https://wttr.in/${CITY}?format=%c+%t" + +cache_age() { + [ -f "${CACHE}" ] || return 1 + now=$(date +%s) + mtime=$(stat -c %Y "${CACHE}") + [ "$(( now - mtime ))" -lt "${CACHE_MAX_AGE}" ] +} + +if cache_age; then + cat "${CACHE}" + exit 0 +fi + +TMP="${CACHE}.tmp.$$" +if curl -fsS --max-time 10 "${URL}" > "${TMP}" 2>/dev/null; then + mv "${TMP}" "${CACHE}" + cat "${CACHE}" +else + rm -f "${TMP}" || true + # Offline: fall back to whatever we cached last, even if stale. + [ -f "${CACHE}" ] && cat "${CACHE}" +fi diff --git a/hyprlock/.config/hypr/hyprlock.conf b/hyprlock/.config/hypr/hyprlock.conf index a095b3f..0e61e90 100644 --- a/hyprlock/.config/hypr/hyprlock.conf +++ b/hyprlock/.config/hypr/hyprlock.conf @@ -7,14 +7,17 @@ $font = JetBrainsMono Nerd Font # General general { - disable_loading_bar = false hide_cursor = false } # Authentication +# Fingerprint is disabled: GDM's login worker (pam/gdm-fingerprint) holds the +# device for the whole session, so hyprlock can never claim it -- the failed +# claim blocked the auth thread and made unlocks appear stuck. Fingerprint is +# still available at the GDM login screen; hyprlock is password-only. auth { fingerprint { - enabled = true + enabled = false ready_message = Scan fingerprint to unlock present_message = Scanning... retry_delay = 250 # in milliseconds @@ -45,7 +48,7 @@ label { # Date label { monitor = - text = cmd[update:43200000] echo "$(date + "%A, %d %B %Y")" + text = cmd[update:43200000] echo "$(date +'%A, %d %B %Y')" color = $text font_size = 25 font_family = $font @@ -54,6 +57,30 @@ label { valign = top } +# Battery +label { + monitor = + text = cmd[update:60000] $HOME/.local/bin/hyprlock-battery.sh + color = $subtext1 + font_size = 20 + font_family = $font + position = -30, -190 + halign = right + valign = top +} + +# Weather +label { + monitor = + text = cmd[update:600000] $HOME/.local/bin/weather.sh + color = $subtext1 + font_size = 20 + font_family = $font + position = -30, -215 + halign = right + valign = top +} + # User Avatar image { monitor = @@ -66,6 +93,18 @@ image { valign = center } +# Notifications (latest mako notification; empty when none) +label { + monitor = + text = cmd[update:5000] $HOME/.local/bin/hyprlock-notifications.sh + color = $subtext0 + font_size = 16 + font_family = $font + position = 0, 60 + halign = center + valign = bottom +} + # Input Field input-field { monitor = diff --git a/hyprlock/.local/bin/hyprlock b/hyprlock/.local/bin/hyprlock new file mode 100755 index 0000000..b67d486 --- /dev/null +++ b/hyprlock/.local/bin/hyprlock @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Guard wrapper for hyprlock (0.9.x): upstream has crashes on DPMS-off / monitor +# power transitions while the lock screen is running (hyprlock#991, #953, #695). +# If the real hyprlock dies, this restarts it so the session doesn't end up in a +# "lockscreen app died" state. Requires misc:allow_session_lock_restore = true +# in Hyprland config for the replacement lock to be accepted. + +set -u + +REAL_HYPRLOCK=/usr/bin/hyprlock +MAX_RESTARTS=20 + +restart=0 +while true; do + "$REAL_HYPRLOCK" "$@" + code=$? + + # 0: exited cleanly (e.g. unlocked) -- done. + # 1: auth failed, retry -- let the loop back off and try again. + if [ "$code" -eq 0 ]; then + exit 0 + fi + + restart=$((restart + 1)) + if [ "$restart" -ge "$MAX_RESTARTS" ]; then + exit "$code" + fi + + # Brief pause so a crash loop doesn't burn CPU (fade back in quickly). + sleep 0.5 +done diff --git a/hyprlock/.local/bin/hyprlock-battery.sh b/hyprlock/.local/bin/hyprlock-battery.sh new file mode 100755 index 0000000..6067144 --- /dev/null +++ b/hyprlock/.local/bin/hyprlock-battery.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Battery status for the hyprlock screen, e.g. " 18%". +# Uses the first battery found under /sys/class/power_supply (BAT0/BAT1, ...); +# prints nothing on desktops without a battery. +set -u + +bat="" +for b in /sys/class/power_supply/BAT*; do + if [ -r "${b}/capacity" ]; then + bat="${b}" + break + fi +done + +[ -z "${bat}" ] && exit 0 + +cap="$(cat "${bat}/capacity" 2>/dev/null || echo 0)" +status="$(cat "${bat}/status" 2>/dev/null || echo Unknown)" + +case "${status}" in + Charging|Full) + icon="" + ;; + *) + if [ "${cap}" -ge 80 ]; then + icon="" + elif [ "${cap}" -ge 60 ]; then + icon="" + elif [ "${cap}" -ge 40 ]; then + icon="" + elif [ "${cap}" -ge 20 ]; then + icon="" + else + icon="" + fi + ;; +esac + +printf '%s %s%%' "${icon}" "${cap}" diff --git a/hyprlock/.local/bin/hyprlock-notifications.sh b/hyprlock/.local/bin/hyprlock-notifications.sh new file mode 100755 index 0000000..85f9bd8 --- /dev/null +++ b/hyprlock/.local/bin/hyprlock-notifications.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Latest mako notification for the hyprlock screen, Pango-escaped, e.g. +# "Battery test: Battery at 18%". Prints nothing when there are no +# notifications, so the label renders empty. +set -u + +makoctl list -j 2>/dev/null | jq -r ' + if length == 0 then + empty + else + .[-1] as $n | + (if $n.app_name != null and $n.app_name != "" then $n.app_name + ": " else "" end) + + ($n.summary // "") + + (if $n.body != null and $n.body != "" then " — " + $n.body[0:80] else "" end) + end' 2>/dev/null | sed 's/&/\&/g; s//\>/g'