summaryrefslogtreecommitdiff
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
parentscheme: fix not saving atomically (diff)
downloadcaelestia-cli-c043a14ca24f70e81b69133350a1174d2e6572fc.tar.gz
caelestia-cli-c043a14ca24f70e81b69133350a1174d2e6572fc.tar.bz2
caelestia-cli-c043a14ca24f70e81b69133350a1174d2e6572fc.zip
feat: impl wallpaper
-rw-r--r--src/caelestia/parser.py12
-rw-r--r--src/caelestia/subcommands/wallpaper.py12
-rw-r--r--src/caelestia/utils/paths.py7
-rw-r--r--src/caelestia/utils/scheme.py12
-rw-r--r--src/caelestia/utils/wallpaper.py123
5 files changed, 154 insertions, 12 deletions
diff --git a/src/caelestia/parser.py b/src/caelestia/parser.py
index 3f6f506..f8c7bac 100644
--- a/src/caelestia/parser.py
+++ b/src/caelestia/parser.py
@@ -1,7 +1,9 @@
import argparse
from caelestia.subcommands import clipboard, emoji, pip, record, scheme, screenshot, shell, toggle, wallpaper, wsaction
+from caelestia.utils.paths import wallpapers_dir
from caelestia.utils.scheme import get_scheme_names, scheme_variants
+from caelestia.utils.wallpaper import get_wallpaper
def parse_args() -> (argparse.ArgumentParser, argparse.Namespace):
@@ -81,14 +83,18 @@ def parse_args() -> (argparse.ArgumentParser, argparse.Namespace):
# Create parser for wallpaper opts
wallpaper_parser = command_parser.add_parser("wallpaper", help="manage the wallpaper")
wallpaper_parser.set_defaults(cls=wallpaper.Command)
- wallpaper_parser.add_argument("-g", "--get", action="store_true", help="print the current wallpaper")
- wallpaper_parser.add_argument("-r", "--random", action="store_true", help="switch to a random wallpaper")
+ wallpaper_parser.add_argument(
+ "-p", "--print", nargs="?", const=get_wallpaper(), metavar="PATH", help="print the scheme for a wallpaper"
+ )
+ wallpaper_parser.add_argument(
+ "-r", "--random", nargs="?", const=wallpapers_dir, metavar="DIR", help="switch to a random wallpaper"
+ )
wallpaper_parser.add_argument("-f", "--file", help="the path to the wallpaper to switch to")
wallpaper_parser.add_argument("-n", "--no-filter", action="store_true", help="do not filter by size")
wallpaper_parser.add_argument(
"-t",
"--threshold",
- default=80,
+ default=0.8,
help="the minimum percentage of the largest monitor size the image must be greater than to be selected",
)
wallpaper_parser.add_argument(
diff --git a/src/caelestia/subcommands/wallpaper.py b/src/caelestia/subcommands/wallpaper.py
index 37f9a2b..1440484 100644
--- a/src/caelestia/subcommands/wallpaper.py
+++ b/src/caelestia/subcommands/wallpaper.py
@@ -1,5 +1,8 @@
+import json
from argparse import Namespace
+from caelestia.utils.wallpaper import get_colours_for_wall, get_wallpaper, set_random, set_wallpaper
+
class Command:
args: Namespace
@@ -8,4 +11,11 @@ class Command:
self.args = args
def run(self) -> None:
- pass
+ if self.args.print:
+ print(json.dumps(get_colours_for_wall(self.args.print, self.args.no_smart)))
+ elif self.args.file:
+ set_wallpaper(self.args.file, self.args.no_smart)
+ elif self.args.random:
+ set_random(self.args)
+ else:
+ print(get_wallpaper())
diff --git a/src/caelestia/utils/paths.py b/src/caelestia/utils/paths.py
index 3b5a7a6..f81b996 100644
--- a/src/caelestia/utils/paths.py
+++ b/src/caelestia/utils/paths.py
@@ -22,11 +22,14 @@ scheme_path = c_state_dir / "scheme.json"
scheme_data_dir = cli_data_dir / "schemes"
scheme_cache_dir = c_cache_dir / "schemes"
-last_wallpaper_path = c_state_dir / "wallpaper/last.txt"
+wallpapers_dir = Path.home() / "Pictures/Wallpapers"
+wallpaper_path_path = c_state_dir / "wallpaper/path.txt"
+wallpaper_link_path = c_state_dir / "wallpaper/current"
wallpaper_thumbnail_path = c_state_dir / "wallpaper/thumbnail.jpg"
+thumbnail_cache_dir = c_cache_dir / "thumbnails"
-def compute_hash(path: str) -> str:
+def compute_hash(path: Path | str) -> str:
sha = hashlib.sha256()
with open(path, "rb") as f:
diff --git a/src/caelestia/utils/scheme.py b/src/caelestia/utils/scheme.py
index 9027589..0d6cfb5 100644
--- a/src/caelestia/utils/scheme.py
+++ b/src/caelestia/utils/scheme.py
@@ -59,8 +59,7 @@ class Scheme:
self._flavour = flavour
self._check_mode()
- self._update_colours()
- self.save()
+ self.update_colours()
@property
def mode(self) -> str:
@@ -75,8 +74,7 @@ class Scheme:
raise ValueError(f'Invalid scheme mode: "{mode}". Valid modes: {get_scheme_modes()}')
self._mode = mode
- self._update_colours()
- self.save()
+ self.update_colours()
@property
def variant(self) -> str:
@@ -88,8 +86,7 @@ class Scheme:
return
self._variant = variant
- self._update_colours()
- self.save()
+ self.update_colours()
@property
def colours(self) -> dict[str, str]:
@@ -115,6 +112,9 @@ class Scheme:
self._name = random.choice(get_scheme_names())
self._flavour = random.choice(get_scheme_flavours())
self._mode = random.choice(get_scheme_modes())
+ self.update_colours()
+
+ def update_colours(self) -> None:
self._update_colours()
self.save()
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)