danshari!

This commit is contained in:
2026-03-12 03:49:47 +01:00
parent ec92891e4d
commit 55090c6415
62 changed files with 19 additions and 11 deletions
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env bash
# Description:
# Change the desktop wallpaper and generate a blurred version.
#
# Requirs:
# - zenity (for file selection dialog)
# - imagemagick (for image processing)
# - swww (wallpaper daemon)
# - notify-send (for notifications)
# - change-colortheme (from scripts/change-colortheme)
# - flock (usually part of util-linux)
set -euo pipefail
# Lock
exec {LOCK_FD}>/tmp/"$(basename "$0")".lock
flock -n "$LOCK_FD" || {
echo "Another instance is running. Exiting."
# notify-send -a "change-wallpaper" "Error" "Another instance is running. Exiting."
exit 1
}
# Open a file selection dialog if no argument is provided
if [ -z "${1-}" ]; then
image=$(zenity --file-selection --title="Open File" --file-filter="*.jpg *.jpeg *.png *.webp *.bmp *.jfif *.tiff *.avif *.heic *.heif")
else
image="$1"
fi
[ -z "$image" ] && exit 1
[ ! -f "$image" ] && exit 1
# Obtain screen resolution
screen_width=${2-}
screen_height=${3-}
[ -z "$screen_width" ] && {
if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then
screen_width=$(hyprctl -j monitors | jq '.[0].resolution.x')
elif [ "$XDG_CURRENT_DESKTOP" = "niri" ]; then
screen_width=$(niri msg focused-output | grep 'Current mode' | awk '{print $3}' | cut -d'x' -f1)
fi
}
[ -z "$screen_height" ] && {
if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then
screen_height=$(hyprctl -j monitors | jq '.[0].resolution.y')
elif [ "$XDG_CURRENT_DESKTOP" = "niri" ]; then
screen_height=$(niri msg focused-output | grep 'Current mode' | awk '{print $3}' | cut -d'x' -f2)
fi
}
## Default to 2k
screen_width=${screen_width:-2560}
screen_height=${screen_height:-1440}
# $HOME/.config/wallpaper-chooser/config.json:
# ```json
# "sort": {
# "type": "date",
# "reverse": true
# }
# ```
# So in order to let the most recently used wallpapers appear first:
touch "$image" 2>/dev/null || true # ignore errors
# Copy image to local wallpaper directory
## Format of current and cached wallpaper
wallpaper_ext="png"
## Generate a random name for the current wallpaper
set +o pipefail # SIGPIPE is expected here
random_name=$(tr -dc 'a-zA-Z0-9' </dev/urandom | head -c 16)
set -o pipefail
## Directory to store current wallpaper
current_dir="$HOME/.local/share/wallpaper/current"
## Path to current wallpaper image
wallpaper_image="$current_dir/wallpaper-${random_name}.${wallpaper_ext}"
mkdir -p "$current_dir"
## Batch copy using a temporary file to avoid incomplete file being used
temp_img=$(mktemp --suffix=."$wallpaper_ext")
trap 'rm -f "$temp_img"' EXIT
magick "$image" -resize "${screen_width}x${screen_height}^" -gravity center -extent "${screen_width}x${screen_height}" "$temp_img"
cp "$temp_img" "$wallpaper_image"
## Generate hash for caching,
## based on content of the source image and resolution of the resized image
hash="$(md5sum "$image" | awk '{print $1}')-${screen_width}x${screen_height}"
# Clean up old wallpapers in the same directory of current wallpaper.
# Only keep the newly added one so the wallpaper-daemon can pick it up directly
find "$current_dir" -type f -name "wallpaper-*" ! -name "$(basename "$wallpaper_image")" -delete
# Generate blurred wallpaper
## Similarly, store blurred version of current wallpaper in separate directory
blur_dir="$HOME/.local/share/wallpaper/blurred"
## Directory to cache blurred wallpapers, so that we don't need to regenerate
## them every time when switching wallpapers. This makes it possible to have
## an auto-played slideshow with blurred wallpapers without noticeable delay.
blur_cache_dir="$HOME/.local/share/wallpaper/blurred-cache"
mkdir -p "$blur_dir" "$blur_cache_dir"
blurred_image="$blur_dir/blurred-${random_name}.${wallpaper_ext}"
blurred_cache_image="$blur_cache_dir/${hash}.${wallpaper_ext}"
## Time consuming task (magick -blur) in background
(
# notify-send -a "change-wallpaper" "Generating Blurred Wallpaper" "This may take a few seconds..."
function apply_blured {
find "$blur_dir" -type f -name "blurred-*" ! -name "$(basename "$blurred_image")" -delete
if [ "$XDG_CURRENT_DESKTOP" = "niri" ]; then
swww img -n backdrop "$blurred_image" --transition-type fade --transition-duration 2 >/dev/null 2>/dev/null
fi
# notify-send -a "change-wallpaper" "Blurred Wallpaper Applied" "$blurred_image" -i "$blurred_image"
}
### Check if cached blurred image exists
if [ -f "$blurred_cache_image" ]; then
# sleep 1 # Some ugly workaround
if ! cp -f "$blurred_cache_image" "$blurred_image"; then
echo "Could not copy cached blurred image"
# exit 1 # Non-critical error
else
apply_blured
exit 0
fi
fi
sigma=$(magick identify -format "%w %h" "$wallpaper_image" | awk -v f=0.01 '{
m=($1>$2)?$1:$2;
s=m*f;
if(s<2) s=2;
if(s>200) s=200;
printf "%.2f", s
}')
### Batch processing using a temporary file to avoid incomplete file being used
temp_blurred=$(mktemp --suffix=."$wallpaper_ext") || exit 1
trap 'rm -f "${temp_blurred}"' EXIT
magick "$wallpaper_image" -blur 0x"$sigma" "$temp_blurred" || {
echo "Could not create blurred image"
exit 1
}
mv -f "$temp_blurred" "$blurred_image" || {
echo "Could not move blurred image to cache directory"
exit 1
}
cp -f "$blurred_image" "$blurred_cache_image" || {
echo "Could not cache blurred image"
# exit 1 # Non-critical error
}
apply_blured
) &
# Apply wallpaper
skip_colortheme=0
if [ "${4-}" = "--skip-colortheme" ]; then
skip_colortheme=1
fi
if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then
swww img -n background "$wallpaper_image" --transition-type fade --transition-duration 2 >/dev/null 2>/dev/null
# notify-send -a "change-wallpaper" "Wallpaper Changed" "$image" -i "$wallpaper_image"
if [ "$skip_colortheme" = 0 ]; then
change-colortheme -i "$wallpaper_image" || exit 1
fi
elif [ "$XDG_CURRENT_DESKTOP" = "niri" ]; then
### Handled in wallpaper-daemon
# swww img -n background "$wallpaper_image" --transition-type fade --transition-duration 2 > /dev/null 2> /dev/null
# notify-send -a "change-wallpaper" "Wallpaper Changed" "$image" -i "$wallpaper_image"
if [ "$skip_colortheme" = 0 ]; then
change-colortheme -i "$wallpaper_image" || exit 1
fi
else
echo "Unsupported desktop environment: $XDG_CURRENT_DESKTOP"
exit 1
fi
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env bash
# Description:
# Fetch and cache current weather data using OpenWeather API for use in eww widgets.
#
# Requirements:
# - Environment variables:
# - OPENWEATHER_API_KEY
# - OPENWEATHER_LAT
# - OPENWEATHER_LON
## Collect data
cache_dir="$HOME/.cache/eww/weather"
cache_weather_stat=${cache_dir}/weather-stat
cache_weather_degree=${cache_dir}/weather-degree
cache_weather_hex=${cache_dir}/weather-hex
cache_weather_icon=${cache_dir}/weather-icon
cache_weather_updatetime=${cache_dir}/weather-updatetime
if [[ -z "$OPENWEATHER_API_KEY" ]]; then
echo "Please set the OPENWEATHER_API_KEY environment variable."
exit 1
fi
if [[ -z "$OPENWEATHER_LAT" ]]; then
echo "Please set the OPENWEATHER_LAT environment variable."
exit 1
fi
if [[ -z "$OPENWEATHER_LON" ]]; then
echo "Please set the OPENWEATHER_LON environment variable."
exit 1
fi
## Weather data
KEY=$OPENWEATHER_API_KEY
LAT=$OPENWEATHER_LAT
LON=$OPENWEATHER_LON
UNITS=metric
## 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/3.0/onecall?lat=${LAT}&lon=${LON}&exclude=minutely,hourly,daily&appid=${KEY}&units=${UNITS}"`
echo ${weather} >&2
weather=$(echo "$weather" | jq -r ".current")
if [ ! -z "$weather" ]; then
weather_temp=`echo "$weather" | jq ".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_hex="#7aa2f7"
elif [ "$weather_icon_code" == "50n" ]; then
weather_icon=" "
weather_hex="#7aa2f7"
elif [ "$weather_icon_code" == "01d" ]; then
weather_icon=" "
weather_hex="#e0af68"
elif [ "$weather_icon_code" == "01n" ]; then
weather_icon=" "
weather_hex="#c0caf5"
elif [ "$weather_icon_code" == "02d" ]; then
weather_icon=" "
weather_hex="#7aa2f7"
elif [ "$weather_icon_code" == "02n" ]; then
weather_icon=" "
weather_hex="#7aa2f7"
elif [ "$weather_icon_code" == "03d" ]; then
weather_icon=" "
weather_hex="#7aa2f7"
elif [ "$weather_icon_code" == "03n" ]; then
weather_icon=" "
weather_hex="#7aa2f7"
elif [ "$weather_icon_code" == "04d" ]; then
weather_icon=" "
weather_hex="#7aa2f7"
elif [ "$weather_icon_code" == "04n" ]; then
weather_icon=" "
weather_hex="#7aa2f7"
elif [ "$weather_icon_code" == "09d" ]; then
weather_icon=""
weather_hex="#7dcfff"
elif [ "$weather_icon_code" == "09n" ]; then
weather_icon=""
weather_hex="#7dcfff"
elif [ "$weather_icon_code" == "10d" ]; then
weather_icon=""
weather_hex="#7dcfff"
elif [ "$weather_icon_code" == "10n" ]; then
weather_icon=""
weather_hex="#7dcfff"
elif [ "$weather_icon_code" == "11d" ]; then
weather_icon=""
weather_hex="#ff9e64"
elif [ "$weather_icon_code" == "11n" ]; then
weather_icon=""
weather_hex="#ff9e64"
elif [ "$weather_icon_code" == "13d" ]; then
weather_icon=" "
weather_hex="#c0caf5"
elif [ "$weather_icon_code" == "13n" ]; then
weather_icon=" "
weather_hex="#c0caf5"
elif [ "$weather_icon_code" == "40d" ]; then
weather_icon=" "
weather_hex="#7dcfff"
elif [ "$weather_icon_code" == "40n" ]; then
weather_icon=" "
weather_hex="#7dcfff"
else
weather_icon=" "
weather_hex="#c0caf5"
fi
echo "$weather_icon" > ${cache_weather_icon}
echo "$weather_description" > ${cache_weather_stat}
echo "$weather_temp""°C" > ${cache_weather_degree}
echo "$weather_hex" > ${cache_weather_hex}
date "+%Y-%m-%d %H:%M:%S" | tee ${cache_weather_updatetime} >/dev/null
else
echo "Weather Unavailable" > ${cache_weather_stat}
echo " " > ${cache_weather_icon}
echo "-" > ${cache_weather_degree}
echo "#adadff" > ${cache_weather_hex}
date "+%Y-%m-%d %H:%M:%S" | tee ${cache_weather_updatetime} >/dev/null
fi
}
check_network() {
local max=12
local cnt=0
while [ $cnt -lt $max ]; do
if ping -c1 8.8.8.8 &>/dev/null || ping -c1 1.1.1.1 &>/dev/null; then
return 0
fi
echo "Waiting for network connection... (attempt: $((cnt + 1))/$max)" >&2
sleep 5
((cnt++))
done
echo "Network connection failed after $max attempts." >&2
return 1
}
## Execute
if [[ -z "$1" ]]; then
if check_network; then
get_weather_data
fi
elif [[ "$1" == "--icon" ]]; then
cat ${cache_weather_icon}
elif [[ "$1" == "--temp" ]]; then
cat ${cache_weather_degree}
elif [[ "$1" == "--hex" ]]; then
tail -F ${cache_weather_hex}
elif [[ "$1" == "--stat" ]]; then
cat ${cache_weather_stat}
elif [[ "$1" == "--updatetime" ]]; then
cat ${cache_weather_updatetime}
fi
+36
View File
@@ -0,0 +1,36 @@
#!/bin/sh
# Description:
# Toggle the visibility of lyrics widgets in eww.
# - Ensure only one of the two widgets (lyrics or lyrics-single) is open at a time.
# - Cycle through the following states:
# 1. Both widgets closed -> Open 'lyrics'
# 2. 'lyrics' open -> Close 'lyrics' and open 'lyrics-single' (if waybar is running)
# 3. 'lyrics-single' open -> Close 'lyrics-single'
# 4. Both widgets open -> Close both
LYRICS=$(eww active-windows | grep "lyrics:")
LYRICS_SINGLE=$(eww active-windows | grep "lyrics-single:")
# both are closed
if [ -z "$LYRICS" ] && [ -z "$LYRICS_SINGLE" ]; then
eww open lyrics
# only lyrics is open
elif [ -n "$LYRICS" ] && [ -z "$LYRICS_SINGLE" ]; then
eww close lyrics
# if waybar is running, open lyrics-single
if pgrep -x "waybar" -u "$USER" > /dev/null; then
sleep 0.5
eww open lyrics-single
fi
# only lyrics-single is open
elif [ -z "$LYRICS" ] && [ -n "$LYRICS_SINGLE" ]; then
eww close lyrics-single
# both are open
elif [ -n "$LYRICS" ] && [ -n "$LYRICS_SINGLE" ]; then
eww close lyrics
eww close lyrics-single
fi
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Description:
# Script to record screen.
# Inspired by https://github.com/end-4/dots-hyprland
#
# Requirements:
# - wf-recorder
# - slurp
[ -z "$codec" ] && codec="av1_nvenc"
[ -z "$pixel_format" ] && pixel_format="p010le"
[ -z "$frame_rate" ] && frame_rate="60"
[ -z "$codec_params" ] && codec_params=\
"preset=p4 rc=constqp qp=18 \
color_range=tv"
[ -z "$filter_args" ] && filter_args=""
getdate() {
date '+%Y-%m-%d_%H.%M.%S'
}
getaudiooutput() {
pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2
}
getactivemonitor() {
if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then
hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name'
elif [ "$XDG_CURRENT_DESKTOP" = "niri" ]; then
niri msg focused-output | head -n 1 | sed -n 's/.*(\(.*\)).*/\1/p'
fi
}
recorder_args=(
--codec "$codec"
--pixel-format "$pixel_format"
--framerate "$frame_rate"
-f './recording_'"$(getdate)"'.mkv'
)
for param in $codec_params; do
recorder_args+=(-p "$param")
done
for filter in $filter_args; do
recorder_args+=(-F "$filter")
done
mkdir -p "$(xdg-user-dir VIDEOS)"
cd "$(xdg-user-dir VIDEOS)" || exit
if pgrep -x wf-recorder -u "$USER" > /dev/null; then
notify-send "Recording Stopped" "Stopped" -a 'record-script' &
pkill -x wf-recorder -u "$USER"
else
notify-send "Starting recording" 'recording_'"$(getdate)"'.mkv' -a 'record-script' &
if [[ "$1" == "--sound" ]]; then
wf-recorder --geometry "$(slurp)" --audio="$(getaudiooutput)" "${recorder_args[@]}" & disown
elif [[ "$1" == "--fullscreen-sound" ]]; then
wf-recorder -o "$(getactivemonitor)" --audio="$(getaudiooutput)" "${recorder_args[@]}" & disown
elif [[ "$1" == "--fullscreen" ]]; then
wf-recorder -o "$(getactivemonitor)" "${recorder_args[@]}" & disown
else
wf-recorder --geometry "$(slurp)" "${recorder_args[@]}" & disown
fi
fi
+390
View File
@@ -0,0 +1,390 @@
#!/usr/bin/env python3
# Description:
# A wallpaper daemon script that integrates with swww and Niri/Hyprland to
# automatically switch between normal and blurred wallpapers based on window focus.
#
# Requirements:
# - swww (or awww case you are from the future)
# - niri/hyprland (obviously)
# - watchdog (python3 package)
import socket
import json
import subprocess
import threading
from sys import exit
from time import sleep
from os import environ, getuid
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
NORMAL_WALLPAPER_DIR = Path("~/.local/share/wallpaper/current").expanduser()
BLURRED_WALLPAPER_DIR = Path("~/.local/share/wallpaper/blurred").expanduser()
def getFirstFile(dir: Path, pattern: str = "*") -> Path | None:
"""`find $dir -type f | head -n 1`"""
return next(dir.glob(pattern), None)
def getNiriSocket():
return environ["NIRI_SOCKET"]
def _log(msg: str):
# print(msg)
# logFile = Path("/tmp/niri-autoblur.log")
# try:
# with logFile.open("a") as f:
# f.write(msg + "\n")
# except Exception:
# pass
pass
def swwwLoadImg(namespace: str, wallpaper: Path):
cmd = [
"swww",
"img",
"-n",
namespace,
str(wallpaper),
"--transition-type",
"fade",
"--transition-duration",
"0.5",
]
_log(f"[SWWW] {' '.join(cmd)}")
ret = 0
try:
ret = subprocess.run(cmd, check=True).returncode
except Exception as e:
_log(f"[SWWW] failed to set wallpaper: {e}")
return False
if ret != 0:
_log(f"[SWWW] failed to set wallpaper, exit code: {ret}")
return False
return True
def swwwStartDaemon(namespace: str):
# Check if daemon is already running
cmd = ["pgrep", "-f", f"swww-daemon -n {namespace}", "-u", str(getuid())]
try:
output = subprocess.check_output(cmd, text=True)
pids = output.strip().splitlines()
if pids:
_log(f"[SWWW] daemon already running with PIDs: {', '.join(pids)}")
return True
except subprocess.CalledProcessError:
# pgrep returns non-zero exit code if no process is found
pass
except Exception as e:
_log(f"[SWWW] failed to check if daemon is running: {e}")
pass
try:
subprocess.Popen(
["swww-daemon", "-n", namespace],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
_log(f"[SWWW] daemon started for namespace: {namespace}")
return True
except Exception as e:
_log(f"[SWWW] failed to start daemon non-blockingly: {e}")
return False
class AutoBlur:
_interval: float
_normalDir: Path
_blurredDir: Path
_isBlurred = threading.Event()
_thread: threading.Thread | None = None
_lastWallpaper: Path | None = None
_isFirst = True
_applyLock: threading.Lock
def __init__(self, normalDir, blurredDir, interval=0.2):
self._interval = interval
self._normalDir = normalDir
self._blurredDir = blurredDir
self._applyLock = threading.Lock()
# Niri will send "WindowsChanged" event on connect, so no need to init here
# init state
# self.setBlurred(AutoBlur.initIsBlurred())
# Start watching dirs
self.addWatchDir()
class WatchdogHandler(FileSystemEventHandler):
_callback = None
def __init__(self, callback):
if callback is None:
raise ValueError("callback cannot be None")
super().__init__()
self._callback = callback
def on_created(self, event):
if not event.is_directory:
src_path = str(event.src_path)
path = Path(src_path)
_log(f"[Watchdog] file created: {path}")
self._callback(path) # type: ignore
def on_moved(self, event):
if not event.is_directory:
dest_path = str(event.dest_path)
path = Path(dest_path)
_log(f"[Watchdog] file moved to: {path}")
self._callback(path) # type: ignore
def addWatchDir(self):
normalHandler = self.WatchdogHandler(self._onNormalDirEvent)
blurredHandler = self.WatchdogHandler(self._onBlurredDirEvent)
observer = Observer()
observer.schedule(normalHandler, str(self._normalDir), recursive=False)
observer.schedule(blurredHandler, str(self._blurredDir), recursive=False)
observer.start()
_log(f"[Watchdog] watching dirs: {self._normalDir}, {self._blurredDir}")
def _onNormalDirEvent(self, path: Path):
if not self._isBlurred.is_set():
self._apply(path)
def _onBlurredDirEvent(self, path: Path):
if self._isBlurred.is_set():
self._apply(path)
@staticmethod
def initIsBlurred() -> bool:
"""[ $(niri msg focused-window | wc -l) -gt 1 ]"""
cmd = ["niri", "msg", "focused-window"]
try:
output = subprocess.check_output(cmd, text=True)
lines = output.strip().splitlines()
return len(lines) > 1
except Exception as e:
_log(f"[initIsBlurred] failed to check focused window, assuming none: {e}")
return False
def setBlurred(self, isBlurred: bool) -> None:
# Cache state, avoid starting thread unnecessarily
if not self._isFirst and self._isBlurred.is_set() == isBlurred:
_log("[AutoBlur] state unchanged")
return
self._isFirst = False
if isBlurred:
self._isBlurred.set()
_log("[AutoBlur] set to blurred")
else:
self._isBlurred.clear()
_log("[AutoBlur] set to normal")
if self._thread is None or not self._thread.is_alive():
self._thread = threading.Thread(target=self._run, daemon=True)
self._thread.start()
def _run(self) -> None:
"""Wait until wallpapers are ready & apply the correct one according to the current state"""
while True:
setBlurred = self._isBlurred.is_set()
if setBlurred:
wallpaper = getFirstFile(self._blurredDir)
else:
wallpaper = getFirstFile(self._normalDir)
if wallpaper is not None and wallpaper.exists():
success = self._apply(wallpaper)
if setBlurred != self._isBlurred.is_set():
# State changed during apply, loop again immediately
continue
if success:
# Applied successfully
break
sleep(self._interval)
def _apply(self, wallpaper: Path) -> bool:
with self._applyLock:
if wallpaper == self._lastWallpaper:
return True
if not swwwLoadImg("background", wallpaper):
return False
self._lastWallpaper = wallpaper
return True
autoBlurInst = AutoBlur(NORMAL_WALLPAPER_DIR, BLURRED_WALLPAPER_DIR)
def handleEvent(event_name, payload):
if event_name == "WindowFocusChanged":
_log(f"[EventHandler] WindowFocusChanged event received")
id = payload.get("id", "")
if isinstance(id, int):
_log(f"[EventHandler] focused window id: {id}")
autoBlurInst.setBlurred(True)
elif isinstance(id, str) and id == "None":
_log("[EventHandler] no focused window")
autoBlurInst.setBlurred(False)
else:
_log(f"[EventHandler] unknown id: {id}, assuming no focused window")
autoBlurInst.setBlurred(False)
elif event_name == "WindowsChanged":
_log(f"[EventHandler] WindowsChanged event received")
windows = payload.get("windows", [])
for window in windows:
if window.get("is_focused", False):
_log(f"[EventHandler] found focused window")
autoBlurInst.setBlurred(True)
return
_log("[EventHandler] no focused window found")
autoBlurInst.setBlurred(False)
elif event_name == "WindowOpenedOrChanged":
_log(f"[EventHandler] WindowOpenedOrChanged event received")
window = payload.get("window", {})
if window.get("is_focused", False):
_log(f"[EventHandler] opened/changed focused window")
autoBlurInst.setBlurred(True)
else:
_log(f"[EventHandler] unhandled event: {event_name}")
def printEvent(eventName, payload):
_log(
f"[EventHandler] event: {eventName}, payload:\n{json.dumps(payload, indent=2, ensure_ascii=False)}"
)
def connectNiri(niriSocket: str, handler) -> bool:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.connect(niriSocket)
except Exception as e:
sock.close()
_log(f"[Socket] failed to connect to {niriSocket}: {e}")
return False
f = sock.makefile("rwb")
try:
f.write(b'"EventStream"\n')
f.flush()
first = f.readline()
if not first:
# raise RuntimeError("connection closed by server before handshake")
_log("[Socket] connection closed by server before handshake")
return False
try:
resp = json.loads(first.decode())
except Exception:
resp = first.decode().strip()
_log(f"[Socket] handshake response: {resp}")
while True:
line = f.readline()
if not line:
_log("[Socket] socket closed by server")
break
s = line.decode().strip()
if s == "":
continue
try:
obj = json.loads(s)
except Exception as e:
_log(f"[Socket] failed to parse line as JSON: {s}, error: {e}")
continue
keys = list(obj.keys())
if keys:
event_name = keys[0]
payload = obj[event_name]
else:
event_name = "<unknown>"
payload = obj
handler(event_name, payload)
finally:
try:
f.close()
except Exception:
pass
try:
sock.close()
except Exception:
return False
return True
if __name__ == "__main__":
# connectNiri(getNiriSocket(), printEvent)
# exit(0)
desktop = environ.get("XDG_CURRENT_DESKTOP", "")
if desktop == "niri":
_log("[Main] running in Niri")
_log("[Main] starting swww daemons")
if not swwwStartDaemon("background"):
exit(1)
if not swwwStartDaemon("backdrop"):
exit(1)
sleep(1) # give some time to start
_log("[Main] loading initial wallpapers")
# Init wallpaper for backdrop
blurred = getFirstFile(BLURRED_WALLPAPER_DIR)
if blurred:
swwwLoadImg("backdrop", blurred)
# Init wallpaper for background
normal = getFirstFile(NORMAL_WALLPAPER_DIR)
if normal:
swwwLoadImg("background", normal)
# Connect to Niri socket
_log("[Main] connecting to Niri socket")
niri_socket = getNiriSocket()
if not niri_socket:
_log("[Main] NIRI_SOCKET environment variable is not set.")
exit(1)
while True:
try:
if not connectNiri(niri_socket, handleEvent):
_log("[Main] Connection lost or failed.")
except Exception as e:
_log(f"[Main] Exception in connection loop: {e}")
_log("[Main] Retrying in 3 seconds...")
sleep(3)
niri_socket = getNiriSocket() or niri_socket
elif desktop == "Hyprland":
_log("[Main] running in Hyprland")
_log("[Main] starting swww daemon")
if not swwwStartDaemon("background"):
exit(1)
sleep(1) # similarly
_log("[Main] loading initial wallpaper")
normal = getFirstFile(NORMAL_WALLPAPER_DIR)
if normal:
swwwLoadImg("background", normal)
# Wait indefinitely
while True:
sleep(3600)
else:
_log(f"[Main] unsupported desktop environment: {desktop}")
exit(1)
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Description:
# Script to toggle Waybar (start/stop/restart).
# Also manages the lyrics widget state.
#
# Requirements:
# - waybar
# - eww (for lyrics widget)
lyrics_widget_closed=0
function close() {
killall -q waybar
# Also close the lyrics widget if open
if eww active-windows | grep -q "lyrics-single"; then
eww close lyrics-single
lyrics_widget_closed=1
fi
}
function open() {
# the system tray will not work with kded6 started
if killall -q -9 "kded6"; then
while pgrep -u "$USER" -x "kded6" >/dev/null; do
sleep 0.2
done
fi
nohup waybar >/dev/null 2>/dev/null &
# Reopen the lyrics widget if it was previously closed
if [ $lyrics_widget_closed -eq 1 ]; then
eww open lyrics-single
fi
}
if [ "$1" = "restart" ]; then
close
while pgrep -u "$USER" -x "waybar" >/dev/null; do
sleep 0.2
done
open
elif pgrep -u "$USER" -x "waybar" >/dev/null; then
close
elif ! pgrep -u "$USER" -x "waybar" >/dev/null; then
open
else
echo "Usage: $0 [restart]"
exit 1
fi
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Description:
# Create and switch to a new workspace in Hyprland.
# e.g. if current workspaces are 1,2,4, this will create and switch to workspace 5.
#
# Requirements:
# - hyprctl
# get highest workspace ID
max_id=$(hyprctl workspaces | grep '^workspace ID ' | awk '{print $3}' | sort -n | tail -1)
# case not found default to 0
if [ -z "$max_id" ]; then
max_id=0
fi
hyprctl dispatch workspace $((max_id + 1))
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
# Description:
# Show current volume level with notification.
# Made for mako, and is one of mako's official examples.
#
# Requirements:
# - wpctl
# - notify-send
# Get the volume level and convert it to a percentage
volume=$(wpctl get-volume @DEFAULT_AUDIO_SINK@)
volume=$(echo "$volume" | awk '{print $2}')
volume=$(echo "( $volume * 100 ) / 1" | bc)
notify-send -t 1000 -a 'wp-vol' -h int:value:$volume "Volume: ${volume}%"