feat: impl wallpaper

This commit is contained in:
2 * r + 2 * t
2025-06-12 21:35:05 +10:00
parent a53a2568ec
commit c043a14ca2
5 changed files with 154 additions and 12 deletions
+9 -3
View File
@@ -1,7 +1,9 @@
import argparse import argparse
from caelestia.subcommands import clipboard, emoji, pip, record, scheme, screenshot, shell, toggle, wallpaper, wsaction 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.scheme import get_scheme_names, scheme_variants
from caelestia.utils.wallpaper import get_wallpaper
def parse_args() -> (argparse.ArgumentParser, argparse.Namespace): def parse_args() -> (argparse.ArgumentParser, argparse.Namespace):
@@ -81,14 +83,18 @@ def parse_args() -> (argparse.ArgumentParser, argparse.Namespace):
# Create parser for wallpaper opts # Create parser for wallpaper opts
wallpaper_parser = command_parser.add_parser("wallpaper", help="manage the wallpaper") wallpaper_parser = command_parser.add_parser("wallpaper", help="manage the wallpaper")
wallpaper_parser.set_defaults(cls=wallpaper.Command) 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(
wallpaper_parser.add_argument("-r", "--random", action="store_true", help="switch to a random wallpaper") "-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("-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("-n", "--no-filter", action="store_true", help="do not filter by size")
wallpaper_parser.add_argument( wallpaper_parser.add_argument(
"-t", "-t",
"--threshold", "--threshold",
default=80, default=0.8,
help="the minimum percentage of the largest monitor size the image must be greater than to be selected", help="the minimum percentage of the largest monitor size the image must be greater than to be selected",
) )
wallpaper_parser.add_argument( wallpaper_parser.add_argument(
+11 -1
View File
@@ -1,5 +1,8 @@
import json
from argparse import Namespace from argparse import Namespace
from caelestia.utils.wallpaper import get_colours_for_wall, get_wallpaper, set_random, set_wallpaper
class Command: class Command:
args: Namespace args: Namespace
@@ -8,4 +11,11 @@ class Command:
self.args = args self.args = args
def run(self) -> None: 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())
+5 -2
View File
@@ -22,11 +22,14 @@ scheme_path = c_state_dir / "scheme.json"
scheme_data_dir = cli_data_dir / "schemes" scheme_data_dir = cli_data_dir / "schemes"
scheme_cache_dir = c_cache_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" 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() sha = hashlib.sha256()
with open(path, "rb") as f: with open(path, "rb") as f:
+6 -6
View File
@@ -59,8 +59,7 @@ class Scheme:
self._flavour = flavour self._flavour = flavour
self._check_mode() self._check_mode()
self._update_colours() self.update_colours()
self.save()
@property @property
def mode(self) -> str: def mode(self) -> str:
@@ -75,8 +74,7 @@ class Scheme:
raise ValueError(f'Invalid scheme mode: "{mode}". Valid modes: {get_scheme_modes()}') raise ValueError(f'Invalid scheme mode: "{mode}". Valid modes: {get_scheme_modes()}')
self._mode = mode self._mode = mode
self._update_colours() self.update_colours()
self.save()
@property @property
def variant(self) -> str: def variant(self) -> str:
@@ -88,8 +86,7 @@ class Scheme:
return return
self._variant = variant self._variant = variant
self._update_colours() self.update_colours()
self.save()
@property @property
def colours(self) -> dict[str, str]: def colours(self) -> dict[str, str]:
@@ -115,6 +112,9 @@ class Scheme:
self._name = random.choice(get_scheme_names()) self._name = random.choice(get_scheme_names())
self._flavour = random.choice(get_scheme_flavours()) self._flavour = random.choice(get_scheme_flavours())
self._mode = random.choice(get_scheme_modes()) self._mode = random.choice(get_scheme_modes())
self.update_colours()
def update_colours(self) -> None:
self._update_colours() self._update_colours()
self.save() self.save()
+123
View File
@@ -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)