summaryrefslogtreecommitdiff
path: root/src/caelestia/utils/wallpaper.py
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-06-12 21:35:05 +1000
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-06-12 21:35:05 +1000
commitc043a14ca24f70e81b69133350a1174d2e6572fc (patch)
treea938eae5dd8fb468f10d189ec141eeae228e0537 /src/caelestia/utils/wallpaper.py
parentscheme: fix not saving atomically (diff)
downloadcaelestia-cli-c043a14ca24f70e81b69133350a1174d2e6572fc.tar.gz
caelestia-cli-c043a14ca24f70e81b69133350a1174d2e6572fc.tar.bz2
caelestia-cli-c043a14ca24f70e81b69133350a1174d2e6572fc.zip
feat: impl wallpaper
Diffstat (limited to 'src/caelestia/utils/wallpaper.py')
-rw-r--r--src/caelestia/utils/wallpaper.py123
1 files changed, 123 insertions, 0 deletions
diff --git a/src/caelestia/utils/wallpaper.py b/src/caelestia/utils/wallpaper.py
new file mode 100644
index 0000000..c8b3a72
--- /dev/null
+++ b/src/caelestia/utils/wallpaper.py
@@ -0,0 +1,123 @@
+import random
+from argparse import Namespace
+from pathlib import Path
+
+from materialyoucolor.hct import Hct
+from materialyoucolor.utils.color_utils import argb_from_rgb
+from PIL import Image
+
+from caelestia.utils.hypr import message
+from caelestia.utils.material import get_colours_for_image
+from caelestia.utils.paths import (
+ compute_hash,
+ thumbnail_cache_dir,
+ wallpaper_link_path,
+ wallpaper_path_path,
+ wallpaper_thumbnail_path,
+)
+from caelestia.utils.scheme import Scheme, get_scheme
+from caelestia.utils.theme import apply_colours
+
+
+def is_valid_image(path: Path | str) -> bool:
+ path = Path(path)
+ return path.is_file() and path.suffix in [".jpg", ".jpeg", ".png", ".webp", ".tif", ".tiff"]
+
+
+def check_wall(wall: Path, filter_size: tuple[int, int], threshold: float) -> bool:
+ with Image.open(wall) as img:
+ width, height = img.size
+ return width >= filter_size[0] * threshold and height >= filter_size[1] * threshold
+
+
+def get_wallpaper() -> str:
+ return wallpaper_path_path.read_text()
+
+
+def get_wallpapers(args: Namespace) -> list[Path]:
+ dir = Path(args.random)
+ if not dir.is_dir():
+ return []
+
+ walls = [f for f in dir.rglob("*") if is_valid_image(f)]
+
+ if args.no_filter:
+ return walls
+
+ monitors = message("monitors")
+ filter_size = monitors[0]["width"], monitors[0]["height"]
+ for monitor in monitors[1:]:
+ if filter_size[0] > monitor["width"]:
+ filter_size[0] = monitor["width"]
+ if filter_size[1] > monitor["height"]:
+ filter_size[1] = monitor["height"]
+
+ return [f for f in walls if check_wall(f, filter_size, args.threshold)]
+
+
+def get_thumb(wall: Path) -> Path:
+ thumb = (thumbnail_cache_dir / compute_hash(wall)).with_suffix(".jpg")
+
+ if not thumb.exists():
+ with Image.open(wall) as img:
+ img = img.convert("RGB")
+ img.thumbnail((128, 128), Image.NEAREST)
+ thumb.parent.mkdir(parents=True, exist_ok=True)
+ img.save(thumb, "JPEG")
+
+ return thumb
+
+
+def get_smart_mode(wall: Path) -> str:
+ with Image.open(get_thumb(wall)) as img:
+ img.thumbnail((1, 1), Image.LANCZOS)
+ tone = Hct.from_int(argb_from_rgb(*img.getpixel((0, 0)))).tone
+ return "light" if tone > 60 else "dark"
+
+
+def get_colours_for_wall(wall: Path | str, no_smart: bool) -> None:
+ scheme = get_scheme()
+
+ if not no_smart:
+ scheme = Scheme(
+ {
+ "name": scheme.name,
+ "flavour": scheme.flavour,
+ "mode": get_smart_mode(wall),
+ "variant": scheme.variant,
+ "colours": scheme.colours,
+ }
+ )
+
+ return get_colours_for_image(get_thumb(wall), scheme)
+
+
+def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
+ if not is_valid_image(wall):
+ raise ValueError(f'"{wall}" is not a valid image')
+
+ # Update files
+ wallpaper_path_path.parent.mkdir(parents=True, exist_ok=True)
+ wallpaper_path_path.write_text(str(wall))
+ wallpaper_link_path.parent.mkdir(parents=True, exist_ok=True)
+ wallpaper_link_path.unlink(missing_ok=True)
+ wallpaper_link_path.symlink_to(wall)
+
+ # Generate thumbnail or get from cache
+ thumb = get_thumb(wall)
+ wallpaper_thumbnail_path.parent.mkdir(parents=True, exist_ok=True)
+ wallpaper_thumbnail_path.unlink(missing_ok=True)
+ wallpaper_thumbnail_path.symlink_to(thumb)
+
+ scheme = get_scheme()
+
+ # Change mode based on wallpaper colour
+ scheme.mode = get_smart_mode(wall)
+
+ # Update colours
+ scheme.update_colours()
+ apply_colours(scheme.colours, scheme.mode)
+
+
+def set_random(args: Namespace) -> None:
+ set_wallpaper(random.choice(get_wallpapers(args)), args.no_smart)