perf: improving performance, layout (#215)

Co-authored-by: nullishamy <git@amyerskine.me>
This commit is contained in:
iruzo
2024-06-01 21:49:55 +03:00
committed by GitHub
parent 54633c0f72
commit cadc3e2ac2
37 changed files with 1015 additions and 665 deletions

View File

@@ -3,33 +3,64 @@ name: "Generate test artifacts"
on: on:
pull_request: pull_request:
types: [opened, reopened, synchronize] types: [opened, reopened, synchronize]
# env:
# TAG: latest
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# container:
# image: ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ env.TAG }}
#
# # is this really necessary?
# credentials:
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: "3.11" python-version: "3.11"
cache: "pip" cache: "pip"
- name: Install dependencies - name: Install dependencies
run: pip install -r requirements.txt run: |
- name: Install colloid specific dependencies sudo apt update && sudo apt install -y sassc inkscape optipng
run: sudo apt update && sudo apt install -y sassc inkscape optipng pip install -r requirements.txt
- name: Generate themes - name: Generate themes
run: | run: |
python patches/xfwm4/generate_assets.py set -eu pipefile
python sources/patches/xfwm4/generate_assets.py
python ./build.py mocha --all-accents --zip -d $PWD/releases > mocha.log 2>&1 &
python ./build.py macchiato --all-accents --zip -d $PWD/releases > macchiato.log 2>&1 &
python ./build.py frappe --all-accents --zip -d $PWD/releases > frappe.log 2>&1 &
python ./build.py latte --all-accents --zip -d $PWD/releases > latte.log 2>&1 &
declare -i err=0 werr=0
while wait -fn || werr=$?; ((werr != 127)); do
err=$werr
if [[ $err -ne 0 ]]; then
echo "Build failure, abort"
cat *.log
exit 1
fi
done
cat *.log
python ./build.py mocha --all-accents --zip -d $PWD/releases &&
python ./build.py macchiato --all-accents --zip -d $PWD/releases &&
python ./build.py frappe --all-accents --zip -d $PWD/releases &&
python ./build.py latte --all-accents --zip -d $PWD/releases
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: '${{ github.sha }}-artifacts' name: '${{ github.sha }}-artifacts'
path: ./releases/*.zip path: ./releases/*.zip

27
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: "Publish containers for build"
on:
workflow_dispatch:
pull_request:
# types: [opened, reopened, synchronize]
types: [synchronize]
permissions: write-all
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Generate and push image to ghcr
run: |
# build docker image for the ci
sh docker/build.sh -v no
# push the image to the registry
sh docker/push.sh -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -28,22 +28,40 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: "3.11" python-version: "3.11"
cache: "pip" cache: "pip"
- name: Install dependencies - name: Install dependencies
run: pip install -r requirements.txt run: pip install -r requirements.txt
- name: Install colloid specific dependencies - name: Install colloid specific dependencies
run: sudo apt update && sudo apt install -y sassc inkscape optipng run: sudo apt update && sudo apt install -y sassc inkscape optipng
- name: Generate themes - name: Generate themes
run: | run: |
python patches/xfwm4/generate_assets.py set -eu pipefile
python sources/patches/xfwm4/generate_assets.py
python ./build.py mocha --all-accents --zip -d $PWD/releases > mocha.log 2>&1 &
python ./build.py macchiato --all-accents --zip -d $PWD/releases > macchiato.log 2>&1 &
python ./build.py frappe --all-accents --zip -d $PWD/releases > frappe.log 2>&1 &
python ./build.py latte --all-accents --zip -d $PWD/releases > latte.log 2>&1 &
declare -i err=0 werr=0
while wait -fn || werr=$?; ((werr != 127)); do
err=$werr
if [[ $err -ne 0 ]]; then
echo "Build failure, abort"
cat *.log
exit 1
fi
done
cat *.log
python ./build.py mocha --all-accents --zip -d $PWD/releases &&
python ./build.py macchiato --all-accents --zip -d $PWD/releases &&
python ./build.py frappe --all-accents --zip -d $PWD/releases &&
python ./build.py latte --all-accents --zip -d $PWD/releases
- name: Add zips to release - name: Add zips to release
run: gh release upload ${{ needs.release-please.outputs.tag_name }} releases/*.zip run: gh release upload ${{ needs.release-please.outputs.tag_name }} releases/*.zip
env: env:

3
.gitignore vendored
View File

@@ -8,6 +8,9 @@ lib*/
*.cfg *.cfg
.direnv .direnv
build/ build/
*.log
.ruff-cache
.tmp
# Releases folder # Releases folder
releases releases

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "colloid"] [submodule "colloid"]
path = colloid path = sources/colloid
url = https://github.com/vinceliuice/Colloid-gtk-theme.git url = https://github.com/vinceliuice/Colloid-gtk-theme.git

648
build.py
View File

@@ -1,636 +1,22 @@
#!/usr/bin/env python3 import os
import os, re, shutil, subprocess, argparse, glob, logging, zipfile, sys import sys
import time
import os
from dataclasses import dataclass from sources.build import execute_build
from typing import Any, Literal, List from sources.build.args import parse_args
from sources.build.logger import logger
from catppuccin import PALETTE if __name__ == "__main__":
from catppuccin.models import Flavor, Color git_root = os.path.dirname(os.path.realpath(__file__))
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
SRC_DIR = f"{THIS_DIR}/colloid/src"
SASSC_OPT = ["-M", "-t", "expanded"]
logger = logging.getLogger("catppuccin-gtk")
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
formatter = logging.Formatter("[%(name)s] [%(levelname)s] - %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)
@dataclass
class Tweaks:
tweaks: List[str]
def has(self, tweak: str) -> bool:
return tweak in self.tweaks
def id(self) -> str:
return ",".join(self.tweaks)
@dataclass
class Suffix:
true_value: str
test: Any # callback function
false_value: str = ""
IS_DARK = Suffix(true_value="-Dark", test=lambda ctx: ctx.flavor.dark)
IS_LIGHT = Suffix(true_value="-Light", test=lambda ctx: not ctx.flavor.dark)
IS_WINDOW_NORMAL = Suffix(true_value="-Normal", test=lambda ctx: ctx.tweaks.has('normal'))
DARK_LIGHT = Suffix(
true_value="-Dark", false_value="-Light", test=lambda ctx: ctx.flavor.dark
)
@dataclass
class BuildContext:
build_root: str
output_format: Literal["zip"] | Literal["dir"]
theme_name: str
flavor: Flavor
accent: Color
size: Literal["standard"] | Literal["compact"]
tweaks: Tweaks
def output_dir(self) -> str:
suffix = "light"
if self.flavor.dark:
suffix = "dark"
return f"{self.build_root}/{self.build_id()}-{suffix}"
def build_id(self) -> str:
return f"{self.theme_name}-{self.flavor.identifier}-{self.accent.identifier}-{self.size}+{self.tweaks.id() or 'default'}"
def apply_suffix(self, suffix: Suffix) -> str:
if suffix.test(self):
return suffix.true_value
else:
return suffix.false_value
def build(ctx: BuildContext):
output_dir = ctx.output_dir()
logger.info(f"Building into '{output_dir}'...")
apply_tweaks(ctx)
os.makedirs(output_dir, exist_ok=True)
with open(f"{output_dir}/index.theme", "w") as file:
file.write("[Desktop Entry]\n")
file.write("Type=X-GNOME-Metatheme\n")
file.write(f"Name={ctx.build_id()}\n")
file.write("Comment=An Flat Gtk+ theme based on Elegant Design\n")
file.write("Encoding=UTF-8\n")
file.write("\n")
file.write("[X-GNOME-Metatheme]\n")
file.write(f"GtkTheme={ctx.build_id()}\n")
file.write(f"MetacityTheme={ctx.build_id()}\n")
file.write(f"IconTheme=Tela-circle{ctx.apply_suffix(IS_DARK)}\n")
file.write(f"CursorTheme={ctx.flavor.name}-cursors\n")
file.write("ButtonLayout=close,minimize,maximize:menu\n")
os.makedirs(f"{output_dir}/gnome-shell", exist_ok=True)
shutil.copyfile(
f"{SRC_DIR}/main/gnome-shell/pad-osd.css",
f"{output_dir}/gnome-shell/pad-osd.css",
)
subprocess.check_call(
[
"sassc",
*SASSC_OPT,
f"{SRC_DIR}/main/gnome-shell/gnome-shell{ctx.apply_suffix(DARK_LIGHT)}.scss",
f"{output_dir}/gnome-shell/gnome-shell.css",
]
)
os.makedirs(f"{output_dir}/gtk-3.0", exist_ok=True)
subprocess.check_call(
[
"sassc",
*SASSC_OPT,
f"{SRC_DIR}/main/gtk-3.0/gtk{ctx.apply_suffix(DARK_LIGHT)}.scss",
f"{output_dir}/gtk-3.0/gtk.css",
]
)
subprocess.check_call(
[
"sassc",
*SASSC_OPT,
# NOTE: This uses 'Dark' for the source, but 'dark' for the destination. This is intentional. Do !!NOT!! change it without consultation
f"{SRC_DIR}/main/gtk-3.0/gtk-Dark.scss",
f"{output_dir}/gtk-3.0/gtk-dark.css",
]
)
os.makedirs(f"{output_dir}/gtk-4.0", exist_ok=True)
subprocess.check_call(
[
"sassc",
*SASSC_OPT,
f"{SRC_DIR}/main/gtk-4.0/gtk{ctx.apply_suffix(DARK_LIGHT)}.scss",
f"{output_dir}/gtk-4.0/gtk.css",
]
)
subprocess.check_call(
[
"sassc",
*SASSC_OPT,
# NOTE: This uses 'Dark' for the source, but 'dark' for the destination. This is intentional. Do !!NOT!! change it without consultation
f"{SRC_DIR}/main/gtk-4.0/gtk-Dark.scss",
f"{output_dir}/gtk-4.0/gtk-dark.css",
]
)
os.makedirs(f"{output_dir}/cinnamon", exist_ok=True)
subprocess.check_call(
[
"sassc",
*SASSC_OPT,
f"{SRC_DIR}/main/cinnamon/cinnamon{ctx.apply_suffix(DARK_LIGHT)}.scss",
f"{output_dir}/cinnamon/cinnamon.css",
]
)
os.makedirs(f"{output_dir}/metacity-1", exist_ok=True)
shutil.copyfile(
f"{SRC_DIR}/main/metacity-1/metacity-theme-3{ctx.apply_suffix(IS_WINDOW_NORMAL)}.xml",
f"{output_dir}/metacity-1/metacity-theme-3.xml",
)
# FIXME: Symlinks aren't working as intended
# FIXME: Do we need them?
# os.symlink(
# f"{output_dir}/metacity-1/metacity-theme-3.xml",
# f"{output_dir}/metacity-1/metacity-theme-2.xml",
# )
# os.symlink(
# f"{output_dir}/metacity-1/metacity-theme-3.xml",
# f"{output_dir}/metacity-1/metacity-theme-1.xml",
# )
os.makedirs(f"{output_dir}/xfwm4", exist_ok=True)
shutil.copyfile(
f"{SRC_DIR}/main/xfwm4/themerc{ctx.apply_suffix(IS_LIGHT)}",
f"{output_dir}/xfwm4/themerc",
)
os.makedirs(f"{output_dir}-hdpi/xfwm4", exist_ok=True)
shutil.copyfile(
f"{SRC_DIR}/main/xfwm4/themerc{ctx.apply_suffix(IS_LIGHT)}",
f"{output_dir}-hdpi/xfwm4/themerc",
)
subst_text(f"{output_dir}-hdpi/xfwm4/themerc", "button_offset=6", "button_offset=9")
os.makedirs(f"{output_dir}-xhdpi/xfwm4", exist_ok=True)
shutil.copyfile(
f"{SRC_DIR}/main/xfwm4/themerc{ctx.apply_suffix(IS_LIGHT)}",
f"{output_dir}-xhdpi/xfwm4/themerc",
)
subst_text(
f"{output_dir}-xhdpi/xfwm4/themerc", "button_offset=6", "button_offset=12"
)
if not ctx.flavor.dark:
shutil.copytree(
f"{SRC_DIR}/main/plank/theme-Light-Catppuccin/", f"{output_dir}/plank", dirs_exist_ok=True
)
else:
shutil.copytree(
f"{SRC_DIR}/main/plank/theme-Dark-Catppuccin/", f"{output_dir}/plank", dirs_exist_ok=True
)
def init_tweaks_temp():
shutil.copyfile(f"{SRC_DIR}/sass/_tweaks.scss", f"{SRC_DIR}/sass/_tweaks-temp.scss")
def subst_text(path, _from, to):
with open(path, "r+") as f:
content = f.read()
f.seek(0)
f.truncate()
f.write(re.sub(_from, to, content))
GS_VERSION = "46-0"
def gnome_shell_version():
shutil.copyfile(
f"{SRC_DIR}/sass/gnome-shell/_common.scss",
f"{SRC_DIR}/sass/gnome-shell/_common-temp.scss",
)
subst_text(
f"{SRC_DIR}/sass/gnome-shell/_common-temp.scss",
"@import 'widgets-40-0';",
f"@import 'widgets-{GS_VERSION}';",
)
if GS_VERSION == "3-28":
subst_text(
f"{SRC_DIR}/sass/gnome-shell/_common-temp.scss",
"@import 'extensions-40-0';",
f"@import 'extensions-{GS_VERSION}';",
)
def write_tweak(key, default, value):
subst_text(
f"{SRC_DIR}/sass/_tweaks-temp.scss", f"\\${key}: {default}", f"${key}: {value}"
)
def apply_tweaks(ctx: BuildContext):
write_tweak("theme", "'default'", f"'{ctx.accent.identifier}'")
if ctx.size == "compact":
write_tweak("compact", "'false'", "'true'")
subst_text(
f"{SRC_DIR}/sass/_tweaks-temp.scss",
"@import 'color-palette-default';",
f"@import 'color-palette-catppuccin-{ctx.flavor.identifier}';",
)
write_tweak("colorscheme", "'default'", "'catppuccin'")
if ctx.tweaks.has("black"):
write_tweak("blackness", "'false'", "'true'")
if ctx.tweaks.has("rimless"):
write_tweak("rimless", "'false'", "'true'")
if ctx.tweaks.has("normal"):
write_tweak("window_button", "'mac'", "'normal'")
if ctx.tweaks.has("float"):
write_tweak("float", "'false'", "'true'")
def make_assets(ctx: BuildContext):
output_dir = ctx.output_dir()
os.makedirs(f"{output_dir}/cinnamon/assets", exist_ok=True)
for file in glob.glob(f"{SRC_DIR}/assets/cinnamon/theme/*.svg"):
shutil.copy(file, f"{output_dir}/cinnamon/assets")
shutil.copy(
f"{SRC_DIR}/assets/cinnamon/thumbnail{ctx.apply_suffix(DARK_LIGHT)}.svg",
f"{output_dir}/cinnamon/thumbnail.png",
)
os.makedirs(f"{output_dir}/gnome-shell/assets", exist_ok=True)
for file in glob.glob(f"{SRC_DIR}/assets/gnome-shell/theme/*.svg"):
shutil.copy(file, f"{output_dir}/gnome-shell/assets")
shutil.copytree(
f"{SRC_DIR}/assets/gtk/assets",
f"{output_dir}/gtk-3.0/assets",
dirs_exist_ok=True,
)
shutil.copytree(
f"{SRC_DIR}/assets/gtk/assets",
f"{output_dir}/gtk-4.0/assets",
dirs_exist_ok=True,
)
shutil.copyfile(
f"{SRC_DIR}/assets/gtk/thumbnail{ctx.apply_suffix(IS_DARK)}.svg",
f"{output_dir}/gtk-3.0/thumbnail.png",
)
shutil.copyfile(
f"{SRC_DIR}/assets/gtk/thumbnail{ctx.apply_suffix(IS_DARK)}.svg",
f"{output_dir}/gtk-4.0/thumbnail.png",
)
theme_color = ctx.accent.hex
palette = ctx.flavor.colors
background = palette.base.hex
background_alt = palette.mantle.hex
titlebar = palette.overlay0.hex
for file in glob.glob(f"{output_dir}/cinnamon/assets/*.svg"):
subst_text(file, "#5b9bf8", theme_color)
subst_text(file, "#3c84f7", theme_color)
for file in glob.glob(f"{output_dir}/gnome-shell/assets/*.svg"):
subst_text(file, "#5b9bf8", theme_color)
subst_text(file, "#3c84f7", theme_color)
for file in glob.glob(f"{output_dir}/gtk-3.0/assets/*.svg"):
subst_text(file, "#5b9bf8", theme_color)
subst_text(file, "#3c84f7", theme_color)
subst_text(file, "#ffffff", background)
subst_text(file, "#2c2c2c", background)
subst_text(file, "#3c3c3c", background_alt)
for file in glob.glob(f"{output_dir}/gtk-4.0/assets/*.svg"):
subst_text(file, "#5b9bf8", theme_color)
subst_text(file, "#3c84f7", theme_color)
subst_text(file, "#ffffff", background)
subst_text(file, "#2c2c2c", background)
subst_text(file, "#3c3c3c", background_alt)
if ctx.flavor.dark:
subst_text(f"{output_dir}/cinnamon/thumbnail.png", "#2c2c2c", background)
subst_text(f"{output_dir}/cinnamon/thumbnail.png", "#5b9bf8", theme_color)
subst_text(f"{output_dir}/gtk-3.0/thumbnail.png", "#2c2c2c", background)
subst_text(f"{output_dir}/gtk-4.0/thumbnail.png", "#2c2c2c", background)
subst_text(f"{output_dir}/gtk-3.0/thumbnail.png", "#5b9bf8", theme_color)
subst_text(f"{output_dir}/gtk-4.0/thumbnail.png", "#5b9bf8", theme_color)
else:
subst_text(f"{output_dir}/cinnamon/thumbnail.png", "#ffffff", background)
subst_text(f"{output_dir}/cinnamon/thumbnail.png", "#f2f2f2", titlebar)
subst_text(f"{output_dir}/cinnamon/thumbnail.png", "#3c84f7", theme_color)
subst_text(f"{output_dir}/gtk-3.0/thumbnail.png", "#f2f2f2", titlebar)
subst_text(f"{output_dir}/gtk-3.0/thumbnail.png", "#3c84f7", theme_color)
subst_text(f"{output_dir}/gtk-4.0/thumbnail.png", "#f2f2f2", titlebar)
subst_text(f"{output_dir}/gtk-4.0/thumbnail.png", "#3c84f7", theme_color)
for file in glob.glob(f"{SRC_DIR}/assets/cinnamon/common-assets/*.svg"):
shutil.copy(file, f"{output_dir}/cinnamon/assets")
for file in glob.glob(f"{SRC_DIR}/assets/cinnamon/assets{ctx.apply_suffix(IS_DARK)}/*.svg"):
shutil.copy(file, f"{output_dir}/cinnamon/assets")
for file in glob.glob(f"{SRC_DIR}/assets/gnome-shell/common-assets/*.svg"):
shutil.copy(file, f"{output_dir}/gnome-shell/assets")
for file in glob.glob(f"{SRC_DIR}/assets/gnome-shell/assets{ctx.apply_suffix(IS_DARK)}/*.svg"):
shutil.copy(file, f"{output_dir}/gnome-shell/assets")
for file in glob.glob(f"{SRC_DIR}/assets/gtk/symbolics/*.svg"):
shutil.copy(file, f"{output_dir}/gtk-3.0/assets")
shutil.copy(file, f"{output_dir}/gtk-4.0/assets")
for file in glob.glob(f"{SRC_DIR}/assets/metacity-1/assets{ctx.apply_suffix(IS_WINDOW_NORMAL)}/*.svg"):
shutil.copy(file, f"{output_dir}/metacity-1/assets")
shutil.copy(
f"{SRC_DIR}/assets/metacity-1/thumbnail{ctx.apply_suffix(IS_DARK)}.png",
f"{output_dir}/metacity-1/thumbnail.png",
)
xfwm4_assets_root = f"{THIS_DIR}/patches/xfwm4/generated/assets-catppuccin-{ctx.flavor.identifier}"
for file in glob.glob(xfwm4_assets_root + '/*'):
shutil.copy(file, f"{output_dir}/xfwm4")
xfwm4_assets = xfwm4_assets_root + "-hdpi/*"
for file in glob.glob(xfwm4_assets):
shutil.copy(file, f"{output_dir}-hdpi/xfwm4")
xfwm4_assets = xfwm4_assets_root + "-xhdpi/*"
for file in glob.glob(xfwm4_assets):
shutil.copy(file, f"{output_dir}-xhdpi/xfwm4")
def zip_dir(path, zip_file):
# Ref: https://stackoverflow.com/questions/46229764/python-zip-multiple-directories-into-one-zip-file
for root, _, files in os.walk(path):
for file in files:
zip_file.write(
os.path.join(root, file),
os.path.relpath(os.path.join(root, file), os.path.join(path, "..")),
)
def zip_artifacts(dir_list, zip_name, remove=True):
with zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED) as zipf:
for dir in dir_list:
zip_dir(dir, zipf)
if remove:
for dir in dir_list:
shutil.rmtree(dir)
def build_theme(ctx: BuildContext):
build_info = f"""Build info:
build_root: {ctx.build_root}
theme_name: {ctx.theme_name}
flavor: {ctx.flavor.identifier}
accent: {ctx.accent.identifier}
size: {ctx.size}
tweaks: {ctx.tweaks}"""
logger.info(build_info)
build(ctx)
logger.info(f"Main build complete")
logger.info("Bundling assets...")
make_assets(ctx)
logger.info("Asset bundling done")
if ctx.output_format == "zip":
zip_artifacts(
[
ctx.output_dir(),
f"{ctx.output_dir()}-hdpi",
f"{ctx.output_dir()}-xhdpi",
],
f"{ctx.build_root}/{ctx.build_id()}.zip",
True,
)
"""
if (command -v xfce4-popup-whiskermenu &> /dev/null) && $(sed -i "s|.*menu-opacity=.*|menu-opacity=95|" "$HOME/.config/xfce4/panel/whiskermenu"*".rc" &> /dev/null); then
sed -i "s|.*menu-opacity=.*|menu-opacity=95|" "$HOME/.config/xfce4/panel/whiskermenu"*".rc"
fi
if (pgrep xfce4-session &> /dev/null); then
xfce4-panel -r
fi
"""
def apply_colloid_patches():
if os.path.isfile("colloid/.patched"):
logger.info(
'Patches seem to be applied, remove "colloid/.patched" to force application (this may fail)'
)
return
logger.info("Applying patches...")
# Change into colloid
for patch in [
"plank-dark.patch",
"plank-light.patch",
"theme-func.patch",
"sass-palette-frappe.patch",
"sass-palette-mocha.patch",
"sass-palette-latte.patch",
"sass-palette-macchiato.patch",
"fixes/alt-tab-background-color.patch"
]:
path = f"./patches/colloid/{patch}"
logger.info(f"Applying patch '{patch}', located at '{path}'")
subprocess.check_call(["git", "apply", path, "--directory", f"colloid"])
with open("colloid/.patched", "w") as f:
f.write("true")
logger.info("Patching finished.")
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"flavor",
type=str,
choices=["mocha", "frappe", "macchiato", "latte"],
help="Flavor of the theme to apply.",
)
parser.add_argument(
"--name",
"-n",
type=str,
default="catppuccin",
dest="name",
help="Name of the theme to apply.",
)
parser.add_argument(
"--dest",
"-d",
type=str,
required=True,
dest="dest",
help="Destination of the files.",
)
parser.add_argument(
"--accent",
"-a",
type=str,
default="mauve",
nargs='+',
dest="accents",
choices=[
"rosewater",
"flamingo",
"pink",
"mauve",
"red",
"maroon",
"peach",
"yellow",
"green",
"teal",
"sky",
"sapphire",
"blue",
"lavender",
],
help="Accent of the theme.",
)
parser.add_argument(
"--all-accents",
help="Whether to build all accents",
dest="all_accents",
action="store_true",
)
parser.add_argument(
"--size",
"-s",
type=str,
default="standard",
dest="size",
choices=["standard", "compact"],
help="Size variant of the theme.",
)
parser.add_argument(
"--tweaks",
type=str,
default=[],
nargs="+",
dest="tweaks",
choices=["black", "rimless", "normal", "float"],
help="Tweaks to apply to the build.",
)
parser.add_argument(
"--zip",
help="Whether to bundle the theme into a zip",
type=bool,
default=False,
action=argparse.BooleanOptionalAction,
)
parser.add_argument(
"--patch",
help="Whether to patch the colloid submodule",
type=bool,
default=True,
action=argparse.BooleanOptionalAction,
)
return parser.parse_args()
def main():
args = parse_args() args = parse_args()
if args.patch:
apply_colloid_patches()
if args.zip: try:
output_format = "zip" start = time.time()
else: execute_build(git_root, args)
output_format = "dir" end = time.time() - start
tweaks = Tweaks(tweaks=args.tweaks) logger.info("")
logger.info(f"Built in {round(end, 3)}s")
palette = getattr(PALETTE, args.flavor) except Exception as e:
logger.error("Something went wrong when building the theme:", exc_info=e)
accents = args.accents
if args.all_accents:
accents = [
"rosewater",
"flamingo",
"pink",
"mauve",
"red",
"maroon",
"peach",
"yellow",
"green",
"teal",
"sky",
"sapphire",
"blue",
"lavender",
]
for accent in accents:
accent = getattr(palette.colors, accent)
tweaks = Tweaks(tweaks=args.tweaks)
if args.zip:
output_format = "zip"
else:
output_format = "dir"
ctx = BuildContext(
build_root=args.dest,
theme_name=args.name,
flavor=palette,
accent=accent,
size=args.size,
tweaks=tweaks,
output_format=output_format,
)
logger.info("Building temp tweaks file")
init_tweaks_temp()
logger.info("Inserting gnome-shell imports")
gnome_shell_version()
logger.info("Building main theme")
build_theme(ctx)
logger.info(f"Completed {palette.identifier} with {accent.identifier}")
logger.info("Done!")
try:
main()
except Exception as e:
logger.error("Something went wrong when building the theme:", exc_info=e)
sys.exit(1)

14
docker/Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
# This dockerfile generates a container with the needed dependencies to build the theme
FROM python:alpine
RUN apk add sassc inkscape optipng
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt && rm requirements.txt
# The reason for this is to allow the GH Actions Workflow execute commands within the container
CMD ["sleep", "infinity"]

83
docker/build.sh Normal file
View File

@@ -0,0 +1,83 @@
#!/bin/sh
# Default value for custom_version
VERSION="no"
while getopts v:h opt 2>/dev/null; do
case "$opt" in
v)
VERSION=$OPTARG
;;
h)
echo "\
Usage: $0 [-v <version>]
Push script for Catppuccin's GTK docker build image
-v Custom version to build the image (<your-image-name>:<version>)
If you only want to generate the image with tag 'latest' use '-v no'
-h Print this help text" >&2
exit 0
;;
?)
echo "Usage: $0 [-h]" >&2
exit 1
;;
esac
done
if [ $# -eq 0 ]
then
echo "Usage: $0 [-h]"
exit 1
fi
# Resolve the absolute path of the script without readlink
SCRIPT_PATH="$0"
# Check if SCRIPT_PATH is a symbolic link
while [ -h "$SCRIPT_PATH" ]; do
LS=$(ls -ld "$SCRIPT_PATH")
LINK=$(expr "$LS" : '.*-> \(.*\)$')
if expr "$LINK" : '/.*' > /dev/null; then
SCRIPT_PATH="$LINK"
else
SCRIPT_PATH=$(dirname "$SCRIPT_PATH")/"$LINK"
fi
done
# Ensure we have an absolute path
case "$SCRIPT_PATH" in
/*) ;;
*) SCRIPT_PATH="$(pwd)/$SCRIPT_PATH" ;;
esac
# Path to script' dir
SCRIPT_DIR=$(cd "$(dirname "$SCRIPT_PATH")" && pwd)
# Path to the Dockerfile
DOCKERFILE_PATH="$SCRIPT_DIR/Dockerfile"
# Docker variables
IMAGE_NAME="ghcr.io/catppuccin/gtk"
# Detect podman
if command -v podman > /dev/null 2>&1; then
CONTAINER_TOOL="podman"
elif command -v docker > /dev/null 2>&1; then
CONTAINER_TOOL="docker"
else
echo "Error: Neither podman nor docker is installed."
exit 1
fi
# Clean previous generated images
$CONTAINER_TOOL image rm "$IMAGE_NAME:latest" 2> /dev/null
$CONTAINER_TOOL image rm "$IMAGE_NAME:$VERSION" 2> /dev/null
# Build the Docker image with latest tag
$CONTAINER_TOOL build -t "$IMAGE_NAME:latest" -f "$DOCKERFILE_PATH" "$SCRIPT_DIR/.."
# Execute docker tag command if VERSION is not "no"
if [ "$VERSION" != "no" ]; then
$CONTAINER_TOOL tag "$IMAGE_NAME:latest" "$IMAGE_NAME:$VERSION"
fi

66
docker/push.sh Normal file
View File

@@ -0,0 +1,66 @@
#!/bin/sh
# Default value for parameters
VERSION="no"
USERNAME="no"
PASSWORD="no"
while getopts u:p:v:h opt 2>/dev/null; do
case "$opt" in
v)
VERSION=$OPTARG
;;
u)
USERNAME=$OPTARG
;;
p)
PASSWORD=$OPTARG
;;
h)
echo "\
Usage: $0 [-v <version> | -u [your-github-username] | -p [your-github-password]]
Push script for Catppuccin's GTK docker build image
-v Custom version to push the image (<your-image-name>:<version>)
-u Your GitHub username that will be used to log into GHCR
-p Your GitHub password that will be used to log into GHCR
-h Print this help text" >&2
exit 0
;;
?)
echo "Usage: $0 [-h]" >&2
exit 1
;;
esac
done
if [ $# -eq 0 ]
then
echo "Usage: $0 [-h]"
exit 1
fi
# Detect podman
if command -v podman > /dev/null 2>&1; then
CONTAINER_TOOL="podman"
elif command -v docker > /dev/null 2>&1; then
CONTAINER_TOOL="docker"
else
echo "Error: Neither podman nor docker is installed."
exit 1
fi
# Docker variables
IMAGE_NAME="ghcr.io/catppuccin/gtk"
# Log into ghcr
$CONTAINER_TOOL login ghcr.io -u $USERNAME --password $PASSWORD
# Push docker image with latest tag
$CONTAINER_TOOL push "$IMAGE_NAME:latest"
# Execute docker push for specific version if VERSION is not "no"
if [ "$VERSION" != "no" ]; then
$CONTAINER_TOOL push "$IMAGE_NAME:$VERSION"
fi

View File

@@ -6,6 +6,18 @@ Information regarding the architecture of the project can be found [in the ARCHI
- All the [requirements for building](#building) - All the [requirements for building](#building)
- `whiskers`, optionally, from [catppuccin/toolbox](https://github.com/catppuccin/toolbox/tree/main/whiskers#installation) - `whiskers`, optionally, from [catppuccin/toolbox](https://github.com/catppuccin/toolbox/tree/main/whiskers#installation)
### Project structure
`sources/` contains all of the source files needed for the project to build, including the Colloid submodule.
It also contains our patches for Colloid, alongside the core build system implemented by us to replace the one from Colloid.
`build.py` is the entrypoint to the build system, placed at the root for convenience. The plumbing this utilizes is in
`sources/build`.
`install.py` is our officially supported install script, which will automate the process of pulling the release, extracting it,
and optionally adding symlinks for GTK-4.0 support. This script intentionally has no dependencies other than Python 3 itself.
This keeps the end user experience simple and reproducible. Do not add external dependencies to this script.
### Patching colloid ### Patching colloid
> [!TIP] > [!TIP]
> If you need to change the patches, reset the submodule and rerun the build script. > If you need to change the patches, reset the submodule and rerun the build script.

View File

@@ -1,11 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os, zipfile, argparse, logging, io import os
import zipfile
import argparse
import logging
import io
from typing import Optional from typing import Optional
from pathlib import Path from pathlib import Path
from dataclasses import dataclass from dataclasses import dataclass
from urllib.request import urlopen, Request from urllib.request import urlopen, Request
from urllib.parse import urlparse
logger = logging.getLogger("catppuccin-gtk") logger = logging.getLogger("catppuccin-gtk")
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@@ -160,6 +164,7 @@ def install(ctx: InstallContext):
if ctx.link: if ctx.link:
add_libadwaita_links(ctx) add_libadwaita_links(ctx)
def install_from_artifact(ctx: InstallContext, artifact_path: Path): def install_from_artifact(ctx: InstallContext, artifact_path: Path):
# Working from a pull request, special case it # Working from a pull request, special case it
logger.info(f"Extracting artifact from '{artifact_path}'") logger.info(f"Extracting artifact from '{artifact_path}'")
@@ -189,6 +194,7 @@ def install_from_artifact(ctx: InstallContext, artifact_path: Path):
add_libadwaita_links(ctx, True) add_libadwaita_links(ctx, True)
logger.info("Links added") logger.info("Links added")
def main(): def main():
args = parse_args() args = parse_args()

View File

@@ -9,5 +9,6 @@ pkgs.mkShell {
sassc sassc
inkscape inkscape
optipng optipng
ruff
]; ];
} }

74
sources/build/__init__.py Normal file
View File

@@ -0,0 +1,74 @@
from argparse import Namespace
import shutil
from .patches import apply_colloid_patches
from .theme import build_with_context, gnome_shell_version
from .utils import init_tweaks_temp
from .context import Tweaks, BuildContext
from .logger import logger
from catppuccin import PALETTE
def execute_build(git_root: str, args: Namespace):
colloid_dir = f"{git_root}/sources/colloid"
colloid_tmp_dir = f"{git_root}/.tmp/colloid-tmp-{args.flavor}"
shutil.copytree(colloid_dir, colloid_tmp_dir)
src_dir = colloid_tmp_dir + "/src"
tweaks = Tweaks(tweaks=args.tweaks)
palette = getattr(PALETTE, args.flavor)
output_format = "zip" if args.zip else "dir"
if args.patch:
patch_dir = git_root + "/sources/patches/colloid/"
apply_colloid_patches(colloid_tmp_dir, patch_dir)
accents = args.accents
if args.all_accents:
accents = [
"rosewater",
"flamingo",
"pink",
"mauve",
"red",
"maroon",
"peach",
"yellow",
"green",
"teal",
"sky",
"sapphire",
"blue",
"lavender",
]
for accent in accents:
accent = getattr(palette.colors, accent)
ctx = BuildContext(
output_root=args.dest,
colloid_src_dir=src_dir,
git_root=git_root,
theme_name=args.name,
flavor=palette,
accent=accent,
size=args.size,
tweaks=tweaks,
output_format=output_format,
)
logger.info("Building temp tweaks file")
init_tweaks_temp(src_dir)
logger.info("Inserting gnome-shell imports")
gnome_shell_version(src_dir)
logger.info("Building main theme")
build_with_context(ctx)
logger.info(f"Completed {palette.identifier} with {accent.identifier}")
print()
shutil.rmtree(colloid_tmp_dir)
logger.info("Done!")

100
sources/build/args.py Normal file
View File

@@ -0,0 +1,100 @@
import argparse
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"flavor",
type=str,
choices=["mocha", "frappe", "macchiato", "latte"],
help="Flavor of the theme to apply.",
)
parser.add_argument(
"--name",
"-n",
type=str,
default="catppuccin",
dest="name",
help="Name of the theme to apply.",
)
parser.add_argument(
"--dest",
"-d",
type=str,
required=True,
dest="dest",
help="Destination of the files.",
)
parser.add_argument(
"--accent",
"-a",
type=str,
default="mauve",
nargs="+",
dest="accents",
choices=[
"rosewater",
"flamingo",
"pink",
"mauve",
"red",
"maroon",
"peach",
"yellow",
"green",
"teal",
"sky",
"sapphire",
"blue",
"lavender",
],
help="Accent of the theme.",
)
parser.add_argument(
"--all-accents",
help="Whether to build all accents",
dest="all_accents",
action="store_true",
)
parser.add_argument(
"--size",
"-s",
type=str,
default="standard",
dest="size",
choices=["standard", "compact"],
help="Size variant of the theme.",
)
parser.add_argument(
"--tweaks",
type=str,
default=[],
nargs="+",
dest="tweaks",
choices=["black", "rimless", "normal", "float"],
help="Tweaks to apply to the build.",
)
parser.add_argument(
"--zip",
help="Whether to bundle the theme into a zip",
type=bool,
default=False,
action=argparse.BooleanOptionalAction,
)
parser.add_argument(
"--patch",
help="Whether to patch the colloid submodule",
type=bool,
default=True,
action=argparse.BooleanOptionalAction,
)
return parser.parse_args()

69
sources/build/context.py Normal file
View File

@@ -0,0 +1,69 @@
from dataclasses import dataclass
from typing import Any, Literal, List
from catppuccin.models import Flavor, Color
from .utils import find_and_replace, Subsitution
@dataclass
class Tweaks:
tweaks: List[str]
def has(self, tweak: str) -> bool:
return tweak in self.tweaks
def id(self) -> str:
return ",".join(self.tweaks)
@dataclass
class Suffix:
true_value: str
test: Any
false_value: str = ""
@dataclass
class BuildContext:
# The src dir of the Colloid copy to operate on
colloid_src_dir: str
# The root of the project
git_root: str
# The root of the output dir (as specified by --dest if given)
output_root: str
output_format: Literal["zip"] | Literal["dir"]
theme_name: str
flavor: Flavor
accent: Color
size: Literal["standard"] | Literal["compact"]
tweaks: Tweaks
def output_dir(self) -> str:
return f"{self.output_root}/{self.build_id()}"
def build_id(self) -> str:
return f"{self.theme_name}-{self.flavor.identifier}-{self.accent.identifier}-{self.size}+{self.tweaks.id() or 'default'}"
def apply_suffix(self, suffix: Suffix) -> str:
if suffix.test(self):
return suffix.true_value
else:
return suffix.false_value
def apply_tweak(self, key, default, value):
find_and_replace(
f"{self.colloid_src_dir}/sass/_tweaks-temp.scss",
Subsitution(find=f"\\${key}: {default}", replace=f"${key}: {value}"),
)
IS_DARK = Suffix(true_value="-Dark", test=lambda ctx: ctx.flavor.dark)
IS_LIGHT = Suffix(true_value="-Light", test=lambda ctx: not ctx.flavor.dark)
IS_WINDOW_NORMAL = Suffix(
true_value="-Normal", test=lambda ctx: ctx.tweaks.has("normal")
)
DARK_LIGHT = Suffix(
true_value="-Dark", false_value="-Light", test=lambda ctx: ctx.flavor.dark
)

9
sources/build/logger.py Normal file
View File

@@ -0,0 +1,9 @@
import logging
logger = logging.getLogger("catppuccin-gtk")
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
formatter = logging.Formatter("[%(name)s] [%(levelname)s] - %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)

35
sources/build/patches.py Normal file
View File

@@ -0,0 +1,35 @@
import os
import subprocess
from pathlib import Path
from .logger import logger
def apply_colloid_patches(colloid_dir, patch_dir):
colloid_dir = Path(colloid_dir).relative_to(os.getcwd())
if os.path.isfile(colloid_dir / ".patched"):
logger.info(
f'Patches seem to be applied, remove "{colloid_dir}/.patched" to force application (this may fail)'
)
return
logger.info("Applying patches...")
# Change into colloid
for patch in [
"plank-dark.patch",
"plank-light.patch",
"sass-palette-frappe.patch",
"sass-palette-mocha.patch",
"sass-palette-latte.patch",
"sass-palette-macchiato.patch",
"theme-func.patch",
]:
path = (Path(patch_dir) / patch).relative_to(os.getcwd())
logger.info(f"Applying patch '{patch}', located at '{path}'")
subprocess.check_call(
["git", "apply", str(path), "--directory", str(colloid_dir)]
)
with open(colloid_dir / ".patched", "w") as f:
f.write("true")
logger.info("Patching finished.")

390
sources/build/theme.py Normal file
View File

@@ -0,0 +1,390 @@
import os
import shutil
import subprocess
import glob
import zipfile
from .logger import logger
from .utils import find_and_replace, Subsitution
from .context import BuildContext, IS_DARK, IS_LIGHT, IS_WINDOW_NORMAL, DARK_LIGHT
def apply_tweaks(ctx: BuildContext):
ctx.apply_tweak("theme", "'default'", f"'{ctx.accent.identifier}'")
if ctx.size == "compact":
ctx.apply_tweak("compact", "'false'", "'true'")
find_and_replace(
f"{ctx.colloid_src_dir}/sass/_tweaks-temp.scss",
Subsitution(
find="@import 'color-palette-default';",
replace=f"@import 'color-palette-catppuccin-{ctx.flavor.identifier}';",
),
)
ctx.apply_tweak("colorscheme", "'default'", "'catppuccin'")
if ctx.tweaks.has("black"):
ctx.apply_tweak("blackness", "'false'", "'true'")
if ctx.tweaks.has("rimless"):
ctx.apply_tweak("rimless", "'false'", "'true'")
if ctx.tweaks.has("normal"):
ctx.apply_tweak("window_button", "'mac'", "'normal'")
if ctx.tweaks.has("float"):
ctx.apply_tweak("float", "'false'", "'true'")
SASSC_OPT = ["-M", "-t", "expanded"]
def compile_sass(src: str, dest: str) -> subprocess.Popen:
return subprocess.Popen(["sassc", *SASSC_OPT, src, dest])
def execute_build(ctx: BuildContext):
src_dir = ctx.colloid_src_dir
output_dir = ctx.output_dir()
logger.info(f"Building into '{output_dir}'...")
apply_tweaks(ctx)
os.makedirs(output_dir, exist_ok=True)
with open(f"{output_dir}/index.theme", "w") as file:
file.write("[Desktop Entry]\n")
file.write("Type=X-GNOME-Metatheme\n")
file.write(f"Name={ctx.build_id()}\n")
file.write("Comment=An Flat Gtk+ theme based on Elegant Design\n")
file.write("Encoding=UTF-8\n")
file.write("\n")
file.write("[X-GNOME-Metatheme]\n")
file.write(f"GtkTheme={ctx.build_id()}\n")
file.write(f"MetacityTheme={ctx.build_id()}\n")
file.write(f"IconTheme=Tela-circle{ctx.apply_suffix(IS_DARK)}\n")
file.write(f"CursorTheme={ctx.flavor.name}-cursors\n")
file.write("ButtonLayout=close,minimize,maximize:menu\n")
sassc_tasks = []
os.makedirs(f"{output_dir}/gnome-shell", exist_ok=True)
shutil.copyfile(
f"{src_dir}/main/gnome-shell/pad-osd.css",
f"{output_dir}/gnome-shell/pad-osd.css",
)
sassc_tasks.append(
compile_sass(
f"{src_dir}/main/gnome-shell/gnome-shell{ctx.apply_suffix(DARK_LIGHT)}.scss",
f"{output_dir}/gnome-shell/gnome-shell.css",
)
)
os.makedirs(f"{output_dir}/gtk-3.0", exist_ok=True)
sassc_tasks.append(
compile_sass(
f"{src_dir}/main/gtk-3.0/gtk{ctx.apply_suffix(DARK_LIGHT)}.scss",
f"{output_dir}/gtk-3.0/gtk.css",
)
)
sassc_tasks.append(
compile_sass(
f"{src_dir}/main/gtk-3.0/gtk-Dark.scss",
f"{output_dir}/gtk-3.0/gtk-dark.css",
)
)
os.makedirs(f"{output_dir}/gtk-4.0", exist_ok=True)
sassc_tasks.append(
compile_sass(
f"{src_dir}/main/gtk-4.0/gtk{ctx.apply_suffix(DARK_LIGHT)}.scss",
f"{output_dir}/gtk-4.0/gtk.css",
)
)
sassc_tasks.append(
compile_sass(
f"{src_dir}/main/gtk-4.0/gtk-Dark.scss",
f"{output_dir}/gtk-4.0/gtk-dark.css",
)
)
os.makedirs(f"{output_dir}/cinnamon", exist_ok=True)
sassc_tasks.append(
compile_sass(
f"{src_dir}/main/cinnamon/cinnamon{ctx.apply_suffix(DARK_LIGHT)}.scss",
f"{output_dir}/cinnamon/cinnamon.css",
)
)
for task in sassc_tasks:
task.wait()
os.makedirs(f"{output_dir}/metacity-1", exist_ok=True)
shutil.copyfile(
f"{src_dir}/main/metacity-1/metacity-theme-3{ctx.apply_suffix(IS_WINDOW_NORMAL)}.xml",
f"{output_dir}/metacity-1/metacity-theme-3.xml",
)
os.makedirs(f"{output_dir}/xfwm4", exist_ok=True)
shutil.copyfile(
f"{src_dir}/main/xfwm4/themerc{ctx.apply_suffix(IS_LIGHT)}",
f"{output_dir}/xfwm4/themerc",
)
os.makedirs(f"{output_dir}-hdpi/xfwm4", exist_ok=True)
shutil.copyfile(
f"{src_dir}/main/xfwm4/themerc{ctx.apply_suffix(IS_LIGHT)}",
f"{output_dir}-hdpi/xfwm4/themerc",
)
find_and_replace(
f"{output_dir}-hdpi/xfwm4/themerc",
Subsitution(find="button_offset=6", replace="button_offset=9"),
)
os.makedirs(f"{output_dir}-xhdpi/xfwm4", exist_ok=True)
shutil.copyfile(
f"{src_dir}/main/xfwm4/themerc{ctx.apply_suffix(IS_LIGHT)}",
f"{output_dir}-xhdpi/xfwm4/themerc",
)
find_and_replace(
f"{output_dir}-xhdpi/xfwm4/themerc",
Subsitution(find="button_offset=6", replace="button_offset=12"),
)
if not ctx.flavor.dark:
shutil.copytree(
f"{src_dir}/main/plank/theme-Light-Catppuccin/",
f"{output_dir}/plank",
dirs_exist_ok=True,
)
else:
shutil.copytree(
f"{src_dir}/main/plank/theme-Dark-Catppuccin/",
f"{output_dir}/plank",
dirs_exist_ok=True,
)
def make_assets(ctx: BuildContext):
output_dir = ctx.output_dir()
src_dir = ctx.colloid_src_dir
os.makedirs(f"{output_dir}/cinnamon/assets", exist_ok=True)
for file in glob.glob(f"{src_dir}/assets/cinnamon/theme/*.svg"):
shutil.copy(file, f"{output_dir}/cinnamon/assets")
shutil.copy(
f"{src_dir}/assets/cinnamon/thumbnail{ctx.apply_suffix(DARK_LIGHT)}.svg",
f"{output_dir}/cinnamon/thumbnail.png",
)
os.makedirs(f"{output_dir}/gnome-shell/assets", exist_ok=True)
for file in glob.glob(f"{src_dir}/assets/gnome-shell/theme/*.svg"):
shutil.copy(file, f"{output_dir}/gnome-shell/assets")
shutil.copytree(
f"{src_dir}/assets/gtk/assets",
f"{output_dir}/gtk-3.0/assets",
dirs_exist_ok=True,
)
shutil.copytree(
f"{src_dir}/assets/gtk/assets",
f"{output_dir}/gtk-4.0/assets",
dirs_exist_ok=True,
)
shutil.copyfile(
f"{src_dir}/assets/gtk/thumbnail{ctx.apply_suffix(IS_DARK)}.svg",
f"{output_dir}/gtk-3.0/thumbnail.png",
)
shutil.copyfile(
f"{src_dir}/assets/gtk/thumbnail{ctx.apply_suffix(IS_DARK)}.svg",
f"{output_dir}/gtk-4.0/thumbnail.png",
)
theme_color = ctx.accent.hex
palette = ctx.flavor.colors
background = palette.base.hex
background_alt = palette.mantle.hex
titlebar = palette.overlay0.hex
for file in glob.glob(f"{output_dir}/cinnamon/assets/*.svg"):
find_and_replace(
file,
Subsitution(find="#5b9bf8", replace=theme_color),
Subsitution(find="#3c84f7", replace=theme_color),
)
for file in glob.glob(f"{output_dir}/gnome-shell/assets/*.svg"):
find_and_replace(
file,
Subsitution(find="#5b9bf8", replace=theme_color),
Subsitution(find="#3c84f7", replace=theme_color),
)
for file in glob.glob(f"{output_dir}/gtk-3.0/assets/*.svg"):
find_and_replace(
file,
Subsitution(find="#5b9bf8", replace=theme_color),
Subsitution(find="#3c84f7", replace=theme_color),
Subsitution(find="#ffffff", replace=background),
Subsitution(find="#2c2c2c", replace=background),
Subsitution(find="#3c3c3c", replace=background_alt),
)
for file in glob.glob(f"{output_dir}/gtk-4.0/assets/*.svg"):
find_and_replace(
file,
Subsitution(find="#5b9bf8", replace=theme_color),
Subsitution(find="#3c84f7", replace=theme_color),
Subsitution(find="#ffffff", replace=background),
Subsitution(find="#2c2c2c", replace=background),
Subsitution(find="#3c3c3c", replace=background_alt),
)
if ctx.flavor.dark:
find_and_replace(
f"{output_dir}/cinnamon/thumbnail.png",
Subsitution(find="#2c2c2c", replace=background),
Subsitution(find="#5b9bf8", replace=theme_color),
)
find_and_replace(
f"{output_dir}/gtk-3.0/thumbnail.png",
Subsitution(find="#5b9bf8", replace=theme_color),
Subsitution(find="#2c2c2c", replace=background),
)
find_and_replace(
f"{output_dir}/gtk-4.0/thumbnail.png",
Subsitution(find="#5b9bf8", replace=theme_color),
Subsitution(find="#2c2c2c", replace=background),
)
else:
find_and_replace(
f"{output_dir}/cinnamon/thumbnail.png",
Subsitution(find="#ffffff", replace=background),
Subsitution(find="#f2f2f2", replace=titlebar),
Subsitution(find="#3c84f7", replace=theme_color),
)
find_and_replace(
f"{output_dir}/gtk-3.0/thumbnail.png",
Subsitution(find="#f2f2f2", replace=titlebar),
Subsitution(find="#3c84f7", replace=theme_color),
)
find_and_replace(
f"{output_dir}/gtk-4.0/thumbnail.png",
Subsitution(find="#f2f2f2", replace=titlebar),
Subsitution(find="#3c84f7", replace=theme_color),
)
for file in glob.glob(f"{src_dir}/assets/cinnamon/common-assets/*.svg"):
shutil.copy(file, f"{output_dir}/cinnamon/assets")
for file in glob.glob(
f"{src_dir}/assets/cinnamon/assets{ctx.apply_suffix(IS_DARK)}/*.svg"
):
shutil.copy(file, f"{output_dir}/cinnamon/assets")
for file in glob.glob(f"{src_dir}/assets/gnome-shell/common-assets/*.svg"):
shutil.copy(file, f"{output_dir}/gnome-shell/assets")
for file in glob.glob(
f"{src_dir}/assets/gnome-shell/assets{ctx.apply_suffix(IS_DARK)}/*.svg"
):
shutil.copy(file, f"{output_dir}/gnome-shell/assets")
for file in glob.glob(f"{src_dir}/assets/gtk/symbolics/*.svg"):
shutil.copy(file, f"{output_dir}/gtk-3.0/assets")
shutil.copy(file, f"{output_dir}/gtk-4.0/assets")
for file in glob.glob(
f"{src_dir}/assets/metacity-1/assets{ctx.apply_suffix(IS_WINDOW_NORMAL)}/*.svg"
):
shutil.copy(file, f"{output_dir}/metacity-1/assets")
shutil.copy(
f"{src_dir}/assets/metacity-1/thumbnail{ctx.apply_suffix(IS_DARK)}.png",
f"{output_dir}/metacity-1/thumbnail.png",
)
xfwm4_assets = f"{ctx.git_root}/patches/xfwm4/generated/assets-catppuccin-{ctx.flavor.identifier}"
for file in glob.glob(xfwm4_assets + "/*"):
shutil.copy(file, f"{output_dir}/xfwm4")
xfwm4_assets = xfwm4_assets + "-hdpi/*"
for file in glob.glob(xfwm4_assets):
shutil.copy(file, f"{output_dir}-hdpi/xfwm4")
xfwm4_assets = xfwm4_assets + "-xhdpi/*"
for file in glob.glob(xfwm4_assets):
shutil.copy(file, f"{output_dir}-xhdpi/xfwm4")
def zip_dir(path, zip_file):
for root, _, files in os.walk(path):
for file in files:
zip_file.write(
os.path.join(root, file),
os.path.relpath(os.path.join(root, file), os.path.join(path, "..")),
)
def zip_artifacts(dir_list, zip_name, remove=True):
with zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED) as zipf:
for dir in dir_list:
zip_dir(dir, zipf)
if remove:
for dir in dir_list:
shutil.rmtree(dir)
def build_with_context(ctx: BuildContext):
build_info = f"""Build info:
build_root: {ctx.output_root}
src_root: {ctx.colloid_src_dir}
theme_name: {ctx.theme_name}
flavor: {ctx.flavor.identifier}
accent: {ctx.accent.identifier}
size: {ctx.size}
tweaks: {ctx.tweaks}"""
logger.info(build_info)
execute_build(ctx)
logger.info("Main build complete")
logger.info("Bundling assets...")
make_assets(ctx)
logger.info("Asset bundling done")
if ctx.output_format == "zip":
zip_artifacts(
[
ctx.output_dir(),
f"{ctx.output_dir()}-hdpi",
f"{ctx.output_dir()}-xhdpi",
],
f"{ctx.output_root}/{ctx.build_id()}.zip",
True,
)
def gnome_shell_version(src_dir):
# Hardcoded here, Colloid checks for this on end user machines
# but we cannot do that. Old build system would've resulted in this too.
gs_version = "46-0"
shutil.copyfile(
f"{src_dir}/sass/gnome-shell/_common.scss",
f"{src_dir}/sass/gnome-shell/_common-temp.scss",
)
find_and_replace(
f"{src_dir}/sass/gnome-shell/_common-temp.scss",
Subsitution(
find="@import 'widgets-40-0';",
replace=f"@import 'widgets-{gs_version}';",
),
)

23
sources/build/utils.py Normal file
View File

@@ -0,0 +1,23 @@
import re
import shutil
from dataclasses import dataclass
@dataclass
class Subsitution:
find: str
replace: str
def find_and_replace(path: str, *subs: Subsitution):
with open(path, "r+") as f:
content = f.read()
f.seek(0)
f.truncate()
for sub in subs:
content = re.sub(sub.find, sub.replace, content)
f.write(content)
def init_tweaks_temp(src_dir):
shutil.copyfile(f"{src_dir}/sass/_tweaks.scss", f"{src_dir}/sass/_tweaks-temp.scss")

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -2,7 +2,11 @@ from typing import List
from catppuccin import PALETTE from catppuccin import PALETTE
from catppuccin.models import Flavor from catppuccin.models import Flavor
import re, os, shutil, subprocess, time import re
import os
import shutil
import subprocess
import time
from dataclasses import dataclass from dataclasses import dataclass
THIS_DIR = os.path.dirname(os.path.realpath(__file__)) THIS_DIR = os.path.dirname(os.path.realpath(__file__))
@@ -83,9 +87,9 @@ def generate_for_flavor(flavor: Flavor):
# Setup the palette # Setup the palette
palette = flavor.colors palette = flavor.colors
close_color=f'#{palette.red.hex}' close_color = f"#{palette.red.hex}"
max_color=f'#{palette.green.hex}' max_color = f"#{palette.green.hex}"
min_color=f'#{palette.yellow.hex}' min_color = f"#{palette.yellow.hex}"
# We expand the source assets into the 4 flavors, but need to map between # We expand the source assets into the 4 flavors, but need to map between
# Their definition of dark and ours. This means that latte will get the -light assets # Their definition of dark and ours. This means that latte will get the -light assets
@@ -187,6 +191,7 @@ screen_to_dpi = {
"-xhdpi": "192", "-xhdpi": "192",
} }
def render_for_screen(state: RenderState, flavor: Flavor, screen: str, ident: str): def render_for_screen(state: RenderState, flavor: Flavor, screen: str, ident: str):
# NOTE: We do not generate for the -normal variant currently, that would just be # NOTE: We do not generate for the -normal variant currently, that would just be
# a stupid amount of compute and time for little benefit # a stupid amount of compute and time for little benefit
@@ -215,9 +220,7 @@ def render_for_screen(state: RenderState, flavor: Flavor, screen: str, ident: st
def render_for_flavor(flavor: Flavor, state: RenderState): def render_for_flavor(flavor: Flavor, state: RenderState):
print( print(f"Starting render tasks for {flavor.identifier}")
f"Starting render tasks for {flavor.identifier}"
)
for ident in INDEX: for ident in INDEX:
render_for_screen(state=state, flavor=flavor, screen="", ident=ident) render_for_screen(state=state, flavor=flavor, screen="", ident=ident)
render_for_screen(state=state, flavor=flavor, screen="-hdpi", ident=ident) render_for_screen(state=state, flavor=flavor, screen="-hdpi", ident=ident)