testsAndMisc/python_pkg/wallpaper_rotator/_paint.py
Krzysztof kuhy Rudnicki d502580e3f Dark wallpaper rotator: new style every 3 h
Six text-free styles (gradient, flow/Voronoi, habit heatmap, shutdown
clock, Earth day/night, plasma) replace the white i3 root. A 15-minute
systemd user timer picks a new random style per monitor every 3 hours,
unlike its previous one and the other monitor's; live styles refresh in
between. Also syncs i3/config with the live config and prunes the
oldest evidence artifact for the 60-file cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWX51FMD58yKUC1eYWf
2026-11-11 16:31:06 +01:00

60 lines
1.8 KiB
Python

"""Compose per-monitor images into one root image and hand it to feh."""
from __future__ import annotations
import subprocess
from typing import TYPE_CHECKING, cast
from PIL import Image
from python_pkg.wallpaper_rotator import _palette
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
from pathlib import Path
Rect = tuple[int, int, int, int]
def layout(outputs: Sequence[Mapping[str, object]]) -> dict[str, Rect]:
"""Map each active output from ``i3-msg -t get_outputs`` to (x, y, w, h)."""
rects: dict[str, Rect] = {}
for output in outputs:
if not output.get("active"):
continue
rect = cast("Mapping[str, int]", output["rect"])
rects[str(output["name"])] = (
rect["x"],
rect["y"],
rect["width"],
rect["height"],
)
return rects
def compose(
rects: Mapping[str, Rect], images: Mapping[str, Image.Image]
) -> Image.Image:
"""Paste each output's image at its position on one root-sized canvas."""
width = max((x + w for x, _, w, _ in rects.values()), default=1)
height = max((y + h for _, y, _, h in rects.values()), default=1)
root = Image.new("RGB", (width, height), _palette.DEEP)
for name, (x, y, _, _) in rects.items():
if name in images:
root.paste(images[name], (x, y))
return root
def apply(root: Image.Image, target: Path) -> None:
"""Write the root image and set it as the X background with feh.
One image spanning the whole root avoids relying on feh's Xinerama
screen order matching i3's output names.
"""
target.parent.mkdir(parents=True, exist_ok=True)
root.save(target, compress_level=1)
subprocess.run(
["/usr/bin/feh", "--no-fehbg", "--no-xinerama", "--bg-tile", str(target)],
check=True,
)