forked from Shinonome/caelestia-cli
refactor: enforce stricter type hints (#91)
LSP was screaming at me so I decided to just address it to get it off my screen. + Fixed the type hints := Modified and added type hints for certain functions and variables in most of the files in the utils/ folder (and some in the subcommands/ folder) for clarity and so pyright's type checker wouldn't cry. :+ To resolve certain type issues, I had to add a bit more tiny additional code such as, additional checks if a variable is None, a tiny class in utils/material/generator.py to resolve the constructor usage mismatch between what the DynamicScheme accepts and what the code actually passes, and etc. - Renamed certain functions and variables for clarity and also for some to not collide with pre-existing definitions from well-known library imports. + PIL has reorganized their code a bit, so the code is made to reflect their new definitions. = Reorganized the single import statement for "colourfulness" in utils/wallpaper.py to be close to the top. (I think that's it) Side Effects?: Everything should work the same as no logic change was done whatsover (unless we consider the added if statements for type checking as a logic change). I've tested it, everything seems to be in urdir.
This commit is contained in:
@@ -6,7 +6,7 @@ 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() -> tuple[argparse.ArgumentParser, argparse.Namespace]:
|
||||
parser = argparse.ArgumentParser(prog="caelestia", description="Main control script for the Caelestia dotfiles")
|
||||
parser.add_argument("-v", "--version", action="store_true", help="print the current version")
|
||||
|
||||
|
||||
@@ -26,8 +26,11 @@ class Command:
|
||||
else:
|
||||
sc_data = subprocess.check_output(["grim", "-l", "0", "-g", self.args.region.strip(), "-"])
|
||||
swappy = subprocess.Popen(["swappy", "-f", "-"], stdin=subprocess.PIPE, start_new_session=True)
|
||||
swappy.stdin.write(sc_data)
|
||||
swappy.stdin.close()
|
||||
|
||||
# Ensure stdin is not None for the type checker
|
||||
if swappy.stdin:
|
||||
swappy.stdin.write(sc_data)
|
||||
swappy.stdin.close()
|
||||
|
||||
def fullscreen(self) -> None:
|
||||
sc_data = subprocess.check_output(["grim", "-"])
|
||||
|
||||
@@ -33,11 +33,14 @@ class Command:
|
||||
subprocess.run(args)
|
||||
else:
|
||||
shell = subprocess.Popen(args, stdout=subprocess.PIPE, universal_newlines=True)
|
||||
for line in shell.stdout:
|
||||
if self.filter_log(line):
|
||||
print(line, end="")
|
||||
|
||||
def shell(self, *args: list[str]) -> str:
|
||||
# Ensure stdout is not None for the type checker
|
||||
if shell.stdout:
|
||||
for line in shell.stdout:
|
||||
if self.filter_log(line):
|
||||
print(line, end="")
|
||||
|
||||
def shell(self, *args: str) -> str:
|
||||
return subprocess.check_output(["qs", "-c", "caelestia", *args], text=True)
|
||||
|
||||
def filter_log(self, line: str) -> bool:
|
||||
|
||||
@@ -3,6 +3,7 @@ import shlex
|
||||
import shutil
|
||||
from argparse import Namespace
|
||||
from collections import ChainMap
|
||||
from typing import Any, Callable, cast
|
||||
|
||||
from caelestia.utils import hypr
|
||||
from caelestia.utils.paths import user_config_path
|
||||
@@ -52,8 +53,8 @@ class DeepChainMap(ChainMap):
|
||||
|
||||
class Command:
|
||||
args: Namespace
|
||||
cfg: dict[str, dict[str, dict[str, any]]] | DeepChainMap
|
||||
clients: list[dict[str, any]] = None
|
||||
cfg: dict[str, dict[str, dict[str, Any]]] | DeepChainMap
|
||||
clients: list[dict[str, Any]] | None = None
|
||||
|
||||
def __init__(self, args: Namespace) -> None:
|
||||
self.args = args
|
||||
@@ -120,27 +121,27 @@ class Command:
|
||||
if not spawned:
|
||||
hypr.dispatch("togglespecialworkspace", self.args.workspace)
|
||||
|
||||
def get_clients(self) -> list[dict[str, any]]:
|
||||
def get_clients(self) -> list[dict[str, Any]]:
|
||||
if self.clients is None:
|
||||
self.clients = hypr.message("clients")
|
||||
|
||||
self.clients = cast(list[dict[str, Any]], hypr.message("clients"))
|
||||
return self.clients
|
||||
|
||||
def move_client(self, selector: callable, workspace: str) -> None:
|
||||
def move_client(self, selector: Callable, workspace: str) -> None:
|
||||
for client in self.get_clients():
|
||||
if selector(client) and client["workspace"]["name"] != f"special:{workspace}":
|
||||
hypr.dispatch("movetoworkspacesilent", f"special:{workspace},address:{client['address']}")
|
||||
|
||||
def spawn_client(self, selector: callable, spawn: list[str]) -> bool:
|
||||
def spawn_client(self, selector: Callable, spawn: list[str]) -> bool:
|
||||
if (spawn[0].endswith(".desktop") or shutil.which(spawn[0])) and not any(
|
||||
selector(client) for client in self.get_clients()
|
||||
):
|
||||
hypr.dispatch("exec", f"[workspace special:{self.args.workspace}] app2unit -- {shlex.join(spawn)}")
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def handle_client_config(self, client: dict[str, any]) -> bool:
|
||||
def selector(c: dict[str, any]) -> bool:
|
||||
def handle_client_config(self, client: dict[str, Any]) -> bool:
|
||||
def selector(c: dict[str, Any]) -> bool:
|
||||
# Each match is or, inside matches is and
|
||||
for match in client["match"]:
|
||||
if is_subset(c, match):
|
||||
@@ -156,5 +157,8 @@ class Command:
|
||||
return spawned
|
||||
|
||||
def specialws(self) -> None:
|
||||
special = next(m for m in hypr.message("monitors") if m["focused"])["specialWorkspace"]["name"]
|
||||
hypr.dispatch("togglespecialworkspace", special[8:] or "special")
|
||||
monitors = cast(list[dict[str, Any]], hypr.message("monitors"))
|
||||
target = next((m for m in monitors if m.get("focused")), None)
|
||||
if target:
|
||||
special = target.get("specialWorkspace", {}).get("name", "")[8:] or "special"
|
||||
hypr.dispatch("togglespecialworkspace", special)
|
||||
|
||||
@@ -11,8 +11,7 @@ def stddev(values: list[float], mean_val: float) -> float:
|
||||
return math.sqrt(sum((x - mean_val) ** 2 for x in values) / len(values)) if values else 0
|
||||
|
||||
|
||||
def calc_colourfulness(image: Image) -> float:
|
||||
width, height = image.size
|
||||
def calc_colourfulness(image: Image.Image) -> float:
|
||||
pixels = list(image.getdata()) # List of (R, G, B) tuples
|
||||
|
||||
rg_diffs = []
|
||||
@@ -32,7 +31,7 @@ def calc_colourfulness(image: Image) -> float:
|
||||
return math.sqrt(std_rg**2 + std_yb**2) + 0.3 * math.sqrt(mean_rg**2 + mean_yb**2)
|
||||
|
||||
|
||||
def get_variant(image: Image) -> str:
|
||||
def get_variant(image: Image.Image) -> str:
|
||||
colourfulness = calc_colourfulness(image)
|
||||
|
||||
if colourfulness < 10:
|
||||
|
||||
+14
-10
@@ -1,17 +1,18 @@
|
||||
import json as j
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from typing import Any
|
||||
|
||||
socket_base = f"{os.getenv('XDG_RUNTIME_DIR')}/hypr/{os.getenv('HYPRLAND_INSTANCE_SIGNATURE')}"
|
||||
socket_path = f"{socket_base}/.socket.sock"
|
||||
socket2_path = f"{socket_base}/.socket2.sock"
|
||||
|
||||
|
||||
def message(msg: str, json: bool = True) -> str | dict[str, any]:
|
||||
def message(msg: str, is_json: bool = True) -> str | dict[str, Any]:
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
||||
sock.connect(socket_path)
|
||||
|
||||
if json:
|
||||
if is_json:
|
||||
msg = f"j/{msg}"
|
||||
sock.send(msg.encode())
|
||||
|
||||
@@ -22,14 +23,17 @@ def message(msg: str, json: bool = True) -> str | dict[str, any]:
|
||||
break
|
||||
resp += new_resp.decode()
|
||||
|
||||
return j.loads(resp) if json else resp
|
||||
return json.loads(resp) if is_json else resp
|
||||
|
||||
|
||||
def dispatch(dispatcher: str, *args: list[any]) -> bool:
|
||||
return message(f"dispatch {dispatcher} {' '.join(map(str, args))}".rstrip(), json=False) == "ok"
|
||||
def dispatch(dispatcher: str, *args: str) -> bool:
|
||||
return message(f"dispatch {dispatcher} {' '.join(map(str, args))}".rstrip(), is_json=False) == "ok"
|
||||
|
||||
|
||||
def batch(*msgs: list[str], json: bool = False) -> str | dict[str, any]:
|
||||
if json:
|
||||
msgs = (f"j/{m.strip()}" for m in msgs)
|
||||
return message(f"[[BATCH]]{';'.join(msgs)}", json=False)
|
||||
def batch(*msgs: str, is_json: bool = False) -> str | dict[str, Any]:
|
||||
formatted_msgs = msgs
|
||||
|
||||
if is_json:
|
||||
formatted_msgs = [f"j/{m.strip()}" for m in msgs]
|
||||
|
||||
return message(f"[[BATCH]]{';'.join(formatted_msgs)}", is_json=False)
|
||||
|
||||
@@ -11,6 +11,14 @@ from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow
|
||||
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
|
||||
from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant
|
||||
from materialyoucolor.utils.math_utils import difference_degrees, rotation_direction, sanitize_degrees_double
|
||||
from typing import Protocol, Any
|
||||
|
||||
|
||||
# The base DynamicScheme class requires a 'variant' argument, but the specific
|
||||
# subclasses in get_scheme() handle that internally. This Protocol tells the type
|
||||
# checker to expect our specific 3-argument setup instead of the base class signature.
|
||||
class SchemeConstructor(Protocol):
|
||||
def __call__(self, source_color_hct: Any, is_dark: bool, contrast_level: float) -> DynamicScheme: ...
|
||||
|
||||
try:
|
||||
from materialyoucolor.dynamiccolor.dynamic_scheme import DynamicScheme
|
||||
@@ -147,7 +155,7 @@ def darken(colour: Hct, amount: float) -> Hct:
|
||||
return Hct.from_hct(colour.hue, colour.chroma - diff / 5, colour.tone - diff)
|
||||
|
||||
|
||||
def get_scheme(scheme: str) -> DynamicScheme:
|
||||
def get_scheme(scheme: str) -> SchemeConstructor:
|
||||
if scheme == "content":
|
||||
return SchemeContent
|
||||
if scheme == "expressive":
|
||||
@@ -168,12 +176,12 @@ def get_scheme(scheme: str) -> DynamicScheme:
|
||||
|
||||
|
||||
def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
light = scheme.mode == "light"
|
||||
is_light = scheme.mode == "light"
|
||||
|
||||
colours = {}
|
||||
|
||||
# Material colours
|
||||
primary_scheme = get_scheme(scheme.variant)(primary, not light, 0)
|
||||
primary_scheme = get_scheme(scheme.variant)(source_color_hct=primary, is_dark=not is_light, contrast_level=0.0)
|
||||
if hasattr(MaterialDynamicColors, "all_colors"): # materialyoucolor-python >= 3.0.0
|
||||
dyn_colours = MaterialDynamicColors()
|
||||
for colour in dyn_colours.all_colors:
|
||||
@@ -191,28 +199,28 @@ def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
colours["neutral_variant_paletteKeyColor"] = colours["neutralVariantPaletteKeyColor"]
|
||||
|
||||
# Harmonize terminal colours
|
||||
for i, hct in enumerate(light_gruvbox if light else dark_gruvbox):
|
||||
for i, hct in enumerate(light_gruvbox if is_light else dark_gruvbox):
|
||||
if scheme.variant == "monochrome":
|
||||
colours[f"term{i}"] = grayscale(hct, light)
|
||||
colours[f"term{i}"] = grayscale(hct, is_light)
|
||||
else:
|
||||
colours[f"term{i}"] = harmonize(
|
||||
hct, colours["primary_paletteKeyColor"], (0.35 if i < 8 else 0.2) * (-1 if light else 1)
|
||||
hct, colours["primary_paletteKeyColor"], (0.35 if i < 8 else 0.2) * (-1 if is_light else 1)
|
||||
)
|
||||
|
||||
# Harmonize named colours
|
||||
for i, hct in enumerate(light_catppuccin if light else dark_catppuccin):
|
||||
for i, hct in enumerate(light_catppuccin if is_light else dark_catppuccin):
|
||||
if scheme.variant == "monochrome":
|
||||
colours[colour_names[i]] = grayscale(hct, light)
|
||||
colours[colour_names[i]] = grayscale(hct, is_light)
|
||||
else:
|
||||
colours[colour_names[i]] = harmonize(hct, colours["primary_paletteKeyColor"], (-0.2 if light else 0.05))
|
||||
colours[colour_names[i]] = harmonize(hct, colours["primary_paletteKeyColor"], (-0.2 if is_light else 0.05))
|
||||
|
||||
# KColours
|
||||
for colour in kcolours:
|
||||
colours[colour["name"]] = harmonize(colour["hct"], colours["primary"], 0.1)
|
||||
colours[f"{colour['name']}Selection"] = harmonize(colour["hct"], colours["onPrimaryFixedVariant"], 0.1)
|
||||
if scheme.variant == "monochrome":
|
||||
colours[colour["name"]] = grayscale(colours[colour["name"]], light)
|
||||
colours[f"{colour['name']}Selection"] = grayscale(colours[f"{colour['name']}Selection"], light)
|
||||
colours[colour["name"]] = grayscale(colours[colour["name"]], is_light)
|
||||
colours[f"{colour['name']}Selection"] = grayscale(colours[f"{colour['name']}Selection"], is_light)
|
||||
|
||||
if scheme.variant == "neutral":
|
||||
for name, hct in colours.items():
|
||||
@@ -221,8 +229,8 @@ def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
# Darken surfaces for hard flavour
|
||||
if scheme.flavour == "hard":
|
||||
for colour in "background", *(k for k in colours.keys() if k.startswith("surface")):
|
||||
colours[colour] = lighten(colours[colour], 0.4) if light else darken(colours[colour], 0.8)
|
||||
colours["term0"] = lighten(colours["term0"], 0.4) if light else darken(colours["term0"], 0.9)
|
||||
colours[colour] = lighten(colours[colour], 0.4) if is_light else darken(colours[colour], 0.8)
|
||||
colours["term0"] = lighten(colours["term0"], 0.4) if is_light else darken(colours["term0"], 0.9)
|
||||
|
||||
# FIXME: deprecated stuff
|
||||
colours["text"] = colours["onBackground"]
|
||||
@@ -241,13 +249,13 @@ def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
# More darkening if hard flavour
|
||||
if scheme.flavour == "hard":
|
||||
for colour in "base", "mantle", "crust":
|
||||
colours[colour] = lighten(colours[colour], 0.4) if light else darken(colours[colour], 0.9)
|
||||
colours[colour] = lighten(colours[colour], 0.4) if is_light else darken(colours[colour], 0.9)
|
||||
for i in range(3):
|
||||
colours[f"overlay{i}"] = (
|
||||
lighten(colours[f"overlay{i}"], 0.4) if light else darken(colours[f"overlay{i}"], 0.8)
|
||||
lighten(colours[f"overlay{i}"], 0.4) if is_light else darken(colours[f"overlay{i}"], 0.8)
|
||||
)
|
||||
colours[f"surface{i}"] = (
|
||||
lighten(colours[f"surface{i}"], 0.4) if light else darken(colours[f"surface{i}"], 0.8)
|
||||
lighten(colours[f"surface{i}"], 0.4) if is_light else darken(colours[f"surface{i}"], 0.8)
|
||||
)
|
||||
|
||||
# For debugging
|
||||
@@ -256,7 +264,7 @@ def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
colours = {k: hex(v.to_int())[4:] for k, v in colours.items()}
|
||||
|
||||
# Extended material
|
||||
if light:
|
||||
if is_light:
|
||||
colours["success"] = "4F6354"
|
||||
colours["onSuccess"] = "FFFFFF"
|
||||
colours["successContainer"] = "D1E8D5"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import subprocess
|
||||
|
||||
|
||||
def notify(*args: list[str]) -> str:
|
||||
def notify(*args: str) -> str:
|
||||
return subprocess.check_output(["notify-send", "-a", "caelestia-cli", *args], text=True).strip()
|
||||
|
||||
|
||||
|
||||
@@ -4,41 +4,42 @@ import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
config_dir = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
|
||||
data_dir = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local/share"))
|
||||
state_dir = Path(os.getenv("XDG_STATE_HOME", Path.home() / ".local/state"))
|
||||
cache_dir = Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache"))
|
||||
pictures_dir = Path(os.getenv("XDG_PICTURES_DIR", Path.home() / "Pictures"))
|
||||
videos_dir = Path(os.getenv("XDG_VIDEOS_DIR", Path.home() / "Videos"))
|
||||
config_dir: Path = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
|
||||
data_dir: Path = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local/share"))
|
||||
state_dir: Path = Path(os.getenv("XDG_STATE_HOME", Path.home() / ".local/state"))
|
||||
cache_dir: Path = Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache"))
|
||||
pictures_dir: Path = Path(os.getenv("XDG_PICTURES_DIR", Path.home() / "Pictures"))
|
||||
videos_dir: Path = Path(os.getenv("XDG_VIDEOS_DIR", Path.home() / "Videos"))
|
||||
|
||||
c_config_dir = config_dir / "caelestia"
|
||||
c_data_dir = data_dir / "caelestia"
|
||||
c_state_dir = state_dir / "caelestia"
|
||||
c_cache_dir = cache_dir / "caelestia"
|
||||
c_config_dir: Path = config_dir / "caelestia"
|
||||
c_data_dir: Path = data_dir / "caelestia"
|
||||
c_state_dir: Path = state_dir / "caelestia"
|
||||
c_cache_dir: Path = cache_dir / "caelestia"
|
||||
|
||||
user_config_path = c_config_dir / "cli.json"
|
||||
cli_data_dir = Path(__file__).parent.parent / "data"
|
||||
templates_dir = cli_data_dir / "templates"
|
||||
user_templates_dir = c_config_dir / "templates"
|
||||
theme_dir = c_state_dir / "theme"
|
||||
user_config_path: Path = c_config_dir / "cli.json"
|
||||
cli_data_dir: Path = Path(__file__).parent.parent / "data"
|
||||
templates_dir: Path = cli_data_dir / "templates"
|
||||
user_templates_dir: Path = c_config_dir / "templates"
|
||||
theme_dir: Path = c_state_dir / "theme"
|
||||
|
||||
scheme_path = c_state_dir / "scheme.json"
|
||||
scheme_data_dir = cli_data_dir / "schemes"
|
||||
scheme_cache_dir = c_cache_dir / "schemes"
|
||||
scheme_path: Path = c_state_dir / "scheme.json"
|
||||
scheme_data_dir: Path = cli_data_dir / "schemes"
|
||||
scheme_cache_dir: Path = c_cache_dir / "schemes"
|
||||
|
||||
wallpapers_dir = os.getenv("CAELESTIA_WALLPAPERS_DIR", pictures_dir / "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"
|
||||
wallpapers_cache_dir = c_cache_dir / "wallpapers"
|
||||
wallpapers_dir: Path = Path(os.getenv("CAELESTIA_WALLPAPERS_DIR", pictures_dir / "Wallpapers"))
|
||||
wallpaper_path_path: Path = c_state_dir / "wallpaper/path.txt"
|
||||
wallpaper_link_path: Path = c_state_dir / "wallpaper/current"
|
||||
wallpaper_thumbnail_path: Path = c_state_dir / "wallpaper/thumbnail.jpg"
|
||||
wallpapers_cache_dir: Path = c_cache_dir / "wallpapers"
|
||||
|
||||
screenshots_dir = os.getenv("CAELESTIA_SCREENSHOTS_DIR", pictures_dir / "Screenshots")
|
||||
screenshots_cache_dir = c_cache_dir / "screenshots"
|
||||
screenshots_dir: Path = Path(os.getenv("CAELESTIA_SCREENSHOTS_DIR", pictures_dir / "Screenshots"))
|
||||
screenshots_cache_dir: Path = c_cache_dir / "screenshots"
|
||||
|
||||
recordings_dir = os.getenv("CAELESTIA_RECORDINGS_DIR", videos_dir / "Recordings")
|
||||
recording_path = c_state_dir / "record/recording.mp4"
|
||||
recording_notif_path = c_state_dir / "record/notifid.txt"
|
||||
recordings_dir: Path = Path(os.getenv("CAELESTIA_RECORDINGS_DIR", videos_dir / "Recordings"))
|
||||
recording_path: Path = c_state_dir / "record/recording.mp4"
|
||||
recording_notif_path: Path = c_state_dir / "record/notifid.txt"
|
||||
|
||||
|
||||
def compute_hash(path: Path | str) -> str:
|
||||
@@ -51,7 +52,7 @@ def compute_hash(path: Path | str) -> str:
|
||||
return sha.hexdigest()
|
||||
|
||||
|
||||
def atomic_dump(path: Path, content: dict[str, any]) -> None:
|
||||
def atomic_dump(path: Path, content: dict[str, Any]) -> None:
|
||||
with tempfile.NamedTemporaryFile("w") as f:
|
||||
json.dump(content, f)
|
||||
f.flush()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import random
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from caelestia.utils.notify import notify
|
||||
from caelestia.utils.paths import atomic_dump, scheme_data_dir, scheme_path
|
||||
@@ -14,19 +15,19 @@ class Scheme:
|
||||
_colours: dict[str, str]
|
||||
notify: bool
|
||||
|
||||
def __init__(self, json: dict[str, any] | None) -> None:
|
||||
if json is None:
|
||||
def __init__(self, scheme_json: dict[str, Any] | None) -> None:
|
||||
if scheme_json is None:
|
||||
self._name = "catppuccin"
|
||||
self._flavour = "mocha"
|
||||
self._mode = "dark"
|
||||
self._variant = "tonalspot"
|
||||
self._colours = read_colours_from_file(self.get_colours_path())
|
||||
else:
|
||||
self._name = json["name"]
|
||||
self._flavour = json["flavour"]
|
||||
self._mode = json["mode"]
|
||||
self._variant = json["variant"]
|
||||
self._colours = json["colours"]
|
||||
self._name = scheme_json["name"]
|
||||
self._flavour = scheme_json["flavour"]
|
||||
self._mode = scheme_json["mode"]
|
||||
self._variant = scheme_json["variant"]
|
||||
self._colours = scheme_json["colours"]
|
||||
self.notify = False
|
||||
|
||||
@property
|
||||
@@ -196,7 +197,7 @@ scheme_variants = [
|
||||
"content",
|
||||
]
|
||||
|
||||
scheme: Scheme = None
|
||||
scheme: Scheme | None = None
|
||||
|
||||
|
||||
def read_colours_from_file(path: Path) -> dict[str, str]:
|
||||
@@ -225,7 +226,7 @@ def get_scheme_names() -> list[str]:
|
||||
return [*(f.name for f in scheme_data_dir.iterdir() if f.is_dir()), "dynamic"]
|
||||
|
||||
|
||||
def get_scheme_flavours(name: str = None) -> list[str]:
|
||||
def get_scheme_flavours(name: str | None = None) -> list[str]:
|
||||
if name is None:
|
||||
name = get_scheme().name
|
||||
|
||||
@@ -234,11 +235,11 @@ def get_scheme_flavours(name: str = None) -> list[str]:
|
||||
)
|
||||
|
||||
|
||||
def get_scheme_modes(name: str = None, flavour: str = None) -> list[str]:
|
||||
if name is None:
|
||||
def get_scheme_modes(name: str | None = None, flavour: str | None = None) -> list[str]:
|
||||
if name is None or flavour is None:
|
||||
scheme = get_scheme()
|
||||
name = scheme.name
|
||||
flavour = scheme.flavour
|
||||
name = name or scheme.name
|
||||
flavour = flavour or scheme.flavour
|
||||
|
||||
if name == "dynamic":
|
||||
return ["light", "dark"]
|
||||
|
||||
@@ -4,6 +4,8 @@ import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
import fcntl
|
||||
from pathlib import Path
|
||||
|
||||
from caelestia.utils.colour import get_dynamic_colours
|
||||
@@ -34,10 +36,10 @@ def gen_scss(colours: dict[str, str]) -> str:
|
||||
|
||||
|
||||
def gen_replace(colours: dict[str, str], template: Path, hash: bool = False) -> str:
|
||||
template = template.read_text()
|
||||
new_template = template.read_text()
|
||||
for name, colour in colours.items():
|
||||
template = template.replace(f"{{{{ ${name} }}}}", f"#{colour}" if hash else colour)
|
||||
return template
|
||||
new_template = new_template.replace(f"{{{{ ${name} }}}}", f"#{colour}" if hash else colour)
|
||||
return new_template
|
||||
|
||||
|
||||
def gen_replace_dynamic(colours: dict[str, str], template: Path, mode: str) -> str:
|
||||
@@ -65,7 +67,7 @@ def gen_replace_dynamic(colours: dict[str, str], template: Path, mode: str) -> s
|
||||
return template_filled
|
||||
|
||||
|
||||
def c2s(c: str, *i: list[int]) -> str:
|
||||
def hex_to_ansi(c: str, *i: int) -> str:
|
||||
"""Hex to ANSI sequence (e.g. ffffff, 11 -> \x1b]11;rgb:ff/ff/ff\x1b\\)"""
|
||||
return f"\x1b]{';'.join(map(str, i))};rgb:{c[0:2]}/{c[2:4]}/{c[4:6]}\x1b\\"
|
||||
|
||||
@@ -82,29 +84,29 @@ def gen_sequences(colours: dict[str, str]) -> str:
|
||||
16+: 256 colours
|
||||
"""
|
||||
return (
|
||||
c2s(colours["onSurface"], 10)
|
||||
+ c2s(colours["surface"], 11)
|
||||
+ c2s(colours["secondary"], 12)
|
||||
+ c2s(colours["secondary"], 17)
|
||||
+ c2s(colours["term0"], 4, 0)
|
||||
+ c2s(colours["term1"], 4, 1)
|
||||
+ c2s(colours["term2"], 4, 2)
|
||||
+ c2s(colours["term3"], 4, 3)
|
||||
+ c2s(colours["term4"], 4, 4)
|
||||
+ c2s(colours["term5"], 4, 5)
|
||||
+ c2s(colours["term6"], 4, 6)
|
||||
+ c2s(colours["term7"], 4, 7)
|
||||
+ c2s(colours["term8"], 4, 8)
|
||||
+ c2s(colours["term9"], 4, 9)
|
||||
+ c2s(colours["term10"], 4, 10)
|
||||
+ c2s(colours["term11"], 4, 11)
|
||||
+ c2s(colours["term12"], 4, 12)
|
||||
+ c2s(colours["term13"], 4, 13)
|
||||
+ c2s(colours["term14"], 4, 14)
|
||||
+ c2s(colours["term15"], 4, 15)
|
||||
+ c2s(colours["primary"], 4, 16)
|
||||
+ c2s(colours["secondary"], 4, 17)
|
||||
+ c2s(colours["tertiary"], 4, 18)
|
||||
hex_to_ansi(colours["onSurface"], 10)
|
||||
+ hex_to_ansi(colours["surface"], 11)
|
||||
+ hex_to_ansi(colours["secondary"], 12)
|
||||
+ hex_to_ansi(colours["secondary"], 17)
|
||||
+ hex_to_ansi(colours["term0"], 4, 0)
|
||||
+ hex_to_ansi(colours["term1"], 4, 1)
|
||||
+ hex_to_ansi(colours["term2"], 4, 2)
|
||||
+ hex_to_ansi(colours["term3"], 4, 3)
|
||||
+ hex_to_ansi(colours["term4"], 4, 4)
|
||||
+ hex_to_ansi(colours["term5"], 4, 5)
|
||||
+ hex_to_ansi(colours["term6"], 4, 6)
|
||||
+ hex_to_ansi(colours["term7"], 4, 7)
|
||||
+ hex_to_ansi(colours["term8"], 4, 8)
|
||||
+ hex_to_ansi(colours["term9"], 4, 9)
|
||||
+ hex_to_ansi(colours["term10"], 4, 10)
|
||||
+ hex_to_ansi(colours["term11"], 4, 11)
|
||||
+ hex_to_ansi(colours["term12"], 4, 12)
|
||||
+ hex_to_ansi(colours["term13"], 4, 13)
|
||||
+ hex_to_ansi(colours["term14"], 4, 14)
|
||||
+ hex_to_ansi(colours["term15"], 4, 15)
|
||||
+ hex_to_ansi(colours["primary"], 4, 16)
|
||||
+ hex_to_ansi(colours["secondary"], 4, 17)
|
||||
+ hex_to_ansi(colours["tertiary"], 4, 18)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ import json
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
|
||||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from materialyoucolor.hct import Hct
|
||||
from materialyoucolor.utils.color_utils import argb_from_rgb
|
||||
@@ -11,6 +13,7 @@ from PIL import Image
|
||||
|
||||
from caelestia.utils.hypr import message
|
||||
from caelestia.utils.material import get_colours_for_image
|
||||
from caelestia.utils.colourfulness import get_variant
|
||||
from caelestia.utils.paths import (
|
||||
compute_hash,
|
||||
user_config_path,
|
||||
@@ -33,7 +36,7 @@ def check_wall(wall: Path, filter_size: tuple[int, int], threshold: float) -> bo
|
||||
return width >= filter_size[0] * threshold and height >= filter_size[1] * threshold
|
||||
|
||||
|
||||
def get_wallpaper() -> str:
|
||||
def get_wallpaper() -> str | None:
|
||||
try:
|
||||
return wallpaper_path_path.read_text()
|
||||
except IOError:
|
||||
@@ -41,16 +44,16 @@ def get_wallpaper() -> str:
|
||||
|
||||
|
||||
def get_wallpapers(args: Namespace) -> list[Path]:
|
||||
dir = Path(args.random)
|
||||
if not dir.is_dir():
|
||||
directory = Path(args.random)
|
||||
if not directory.is_dir():
|
||||
return []
|
||||
|
||||
walls = [f for f in dir.rglob("*") if is_valid_image(f)]
|
||||
walls = [f for f in directory.rglob("*") if is_valid_image(f)]
|
||||
|
||||
if args.no_filter:
|
||||
return walls
|
||||
|
||||
monitors = message("monitors")
|
||||
monitors = cast(list[dict[str, int]], message("monitors"))
|
||||
filter_size = min(m["width"] for m in monitors), min(m["height"] for m in monitors)
|
||||
|
||||
return [f for f in walls if check_wall(f, filter_size, args.threshold)]
|
||||
@@ -62,14 +65,14 @@ def get_thumb(wall: Path, cache: Path) -> Path:
|
||||
if not thumb.exists():
|
||||
with Image.open(wall) as img:
|
||||
img = img.convert("RGB")
|
||||
img.thumbnail((128, 128), Image.NEAREST)
|
||||
img.thumbnail((128, 128), Image.Resampling.NEAREST)
|
||||
thumb.parent.mkdir(parents=True, exist_ok=True)
|
||||
img.save(thumb, "JPEG")
|
||||
|
||||
return thumb
|
||||
|
||||
|
||||
def get_smart_opts(wall: Path, cache: Path) -> str:
|
||||
def get_smart_opts(wall: Path, cache: Path) -> dict:
|
||||
opts_cache = cache / "smart.json"
|
||||
|
||||
try:
|
||||
@@ -77,15 +80,16 @@ def get_smart_opts(wall: Path, cache: Path) -> str:
|
||||
except (IOError, json.JSONDecodeError):
|
||||
pass
|
||||
|
||||
from caelestia.utils.colourfulness import get_variant
|
||||
|
||||
opts = {}
|
||||
|
||||
with Image.open(get_thumb(wall, cache)) as img:
|
||||
opts["variant"] = get_variant(img)
|
||||
img.thumbnail((1, 1), Image.Resampling.LANCZOS)
|
||||
|
||||
# Cast the pixel to a tuple of 3 integers to safely unpack it
|
||||
pixel = cast(tuple[int, int, int], img.getpixel((0, 0)))
|
||||
hct = Hct.from_int(argb_from_rgb(*pixel))
|
||||
|
||||
img.thumbnail((1, 1), Image.LANCZOS)
|
||||
hct = Hct.from_int(argb_from_rgb(*img.getpixel((0, 0))))
|
||||
opts["mode"] = "light" if hct.tone > 60 else "dark"
|
||||
|
||||
opts_cache.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -144,7 +148,7 @@ def convert_gif(wall: Path) -> Path:
|
||||
return output_path
|
||||
|
||||
|
||||
def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
|
||||
def set_wallpaper(wall: Path, no_smart: bool) -> None:
|
||||
# Make path absolute
|
||||
wall = Path(wall).resolve()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user