mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-12 15:50:58 +01:00
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
96 lines
3.2 KiB
Python
96 lines
3.2 KiB
Python
"""Style 4: the time left until tonight's shutdown, as colour and an arc."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import numpy as np
|
|
from PIL import Image, ImageDraw
|
|
|
|
from python_pkg.wallpaper_rotator import _palette
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
from python_pkg.wallpaper_rotator._context import RenderContext
|
|
|
|
_DEFAULT_SHUTDOWN = 23 * 60
|
|
_DEFAULT_MORNING = 5 * 60
|
|
_LAST_MON_WED = 2 # Monday..Wednesday use the early shutdown
|
|
_WARN_MINUTES = 120 # colour moves blue -> red over the last two hours
|
|
_CALM = _palette.mix(_palette.DEEP, _palette.COMMENT, 0.45)
|
|
_HALF = 0.5
|
|
_ALARM = (0x5A, 0x16, 0x22)
|
|
|
|
|
|
def _read_conf(conf: Path) -> dict[str, int]:
|
|
values: dict[str, int] = {}
|
|
try:
|
|
text = conf.read_text(encoding="utf-8")
|
|
except OSError:
|
|
return values
|
|
for line in text.splitlines():
|
|
key, sep, value = line.partition("=")
|
|
if sep and value.strip().isdigit():
|
|
values[key.strip()] = int(value.strip())
|
|
return values
|
|
|
|
|
|
def schedule(conf: Path, weekday: int) -> tuple[int, int]:
|
|
"""Return (morning end, shutdown) in minutes after midnight for a weekday."""
|
|
values = _read_conf(conf)
|
|
key = "MON_WED_MINUTES" if weekday <= _LAST_MON_WED else "THU_SUN_MINUTES"
|
|
return (
|
|
values.get("MORNING_END_MINUTES", _DEFAULT_MORNING),
|
|
values.get(key, _DEFAULT_SHUTDOWN),
|
|
)
|
|
|
|
|
|
def _glow(tone: _palette.Rgb, size: tuple[int, int]) -> Image.Image:
|
|
"""A radial wash from ``tone`` at the centre to DEEP at the corners."""
|
|
width, height = size
|
|
small_w, small_h = max(width // 4, 2), max(height // 4, 2)
|
|
ys, xs = np.indices((small_h, small_w), dtype=np.float64)
|
|
radial = np.hypot(xs / small_w - 0.5, ys / small_h - 0.5) / 0.71
|
|
glow = Image.fromarray(_palette.ramp(radial, (tone, _palette.DEEP)), "RGB")
|
|
return glow.resize(size, Image.Resampling.BICUBIC)
|
|
|
|
|
|
def _draw_arcs(
|
|
image: Image.Image, tone: _palette.Rgb, urgency: float, share: float
|
|
) -> None:
|
|
"""Draw the dim full ring and, over it, the arc of day still left."""
|
|
width, height = image.size
|
|
radius = min(width, height) * 0.32
|
|
box = (
|
|
width / 2 - radius,
|
|
height / 2 - radius,
|
|
width / 2 + radius,
|
|
height / 2 + radius,
|
|
)
|
|
line = max(round(min(width, height) / 180), 1)
|
|
draw = ImageDraw.Draw(image)
|
|
draw.arc(
|
|
box, 0, 360, fill=_palette.mix(_palette.DEEP, _palette.LINE, 0.9), width=line
|
|
)
|
|
if share > 0:
|
|
bright = _palette.mix(
|
|
tone, _palette.CYAN if urgency < _HALF else _palette.RED, 0.5
|
|
)
|
|
draw.arc(box, -90, -90 + 360 * share, fill=bright, width=line)
|
|
|
|
|
|
def render(
|
|
ctx: RenderContext, size: tuple[int, int], _rng: np.random.Generator
|
|
) -> Image.Image:
|
|
"""Tint the screen by urgency; a thin arc shows the share of day left."""
|
|
morning, shutdown = schedule(ctx.shutdown_conf, ctx.now.weekday())
|
|
minute = ctx.now.hour * 60 + ctx.now.minute
|
|
left = max(shutdown - minute, 0)
|
|
urgency = 1.0 - min(left, _WARN_MINUTES) / _WARN_MINUTES
|
|
tone = _palette.mix(_CALM, _ALARM, urgency)
|
|
image = _glow(tone, size)
|
|
share = min(left / max(shutdown - morning, 1), 1.0)
|
|
_draw_arcs(image, tone, urgency, share)
|
|
return image
|