From cadc3e2ac24f93c6641f9f87ee5d044433c65ce7 Mon Sep 17 00:00:00 2001 From: iruzo <55333544+iruzo@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:49:55 +0300 Subject: [PATCH] perf: improving performance, layout (#215) Co-authored-by: nullishamy --- .github/workflows/build.yml | 51 +- .github/workflows/docker.yml | 27 + .github/workflows/release.yml | 30 +- .gitignore | 3 + .gitmodules | 2 +- build.py | 648 +----------------- docker/Dockerfile | 14 + docker/build.sh | 83 +++ docker/push.sh | 66 ++ ARCHITECTURE.md => docs/ARCHITECTURE.md | 0 CHANGELOG.md => docs/CHANGELOG.md | 0 CONTRIBUTING.md => docs/CONTRIBUTING.md | 26 +- install.py | 10 +- shell.nix | 1 + sources/build/__init__.py | 74 ++ sources/build/args.py | 100 +++ sources/build/context.py | 69 ++ sources/build/logger.py | 9 + sources/build/patches.py | 35 + sources/build/theme.py | 390 +++++++++++ sources/build/utils.py | 23 + colloid => sources/colloid | 0 .../colloid}/alt-tab-background-color.patch | 0 .../patches}/colloid/palette.tera | 0 .../patches}/colloid/plank-dark.patch | 0 .../patches}/colloid/plank-light.patch | 0 .../colloid/sass-palette-frappe.patch | 0 .../patches}/colloid/sass-palette-latte.patch | 0 .../colloid/sass-palette-macchiato.patch | 0 .../patches}/colloid/sass-palette-mocha.patch | 0 .../patches}/colloid/theme-func.patch | 0 {patches => sources/patches}/xfwm4/.gitignore | 0 .../patches}/xfwm4/assets-dark-normal.svg | 0 .../patches}/xfwm4/assets-dark.svg | 0 .../patches}/xfwm4/assets-light-normal.svg | 0 .../patches}/xfwm4/assets-light.svg | 0 .../patches}/xfwm4/generate_assets.py | 19 +- 37 files changed, 1015 insertions(+), 665 deletions(-) create mode 100644 .github/workflows/docker.yml create mode 100644 docker/Dockerfile create mode 100644 docker/build.sh create mode 100644 docker/push.sh rename ARCHITECTURE.md => docs/ARCHITECTURE.md (100%) rename CHANGELOG.md => docs/CHANGELOG.md (100%) rename CONTRIBUTING.md => docs/CONTRIBUTING.md (78%) create mode 100644 sources/build/__init__.py create mode 100644 sources/build/args.py create mode 100644 sources/build/context.py create mode 100644 sources/build/logger.py create mode 100644 sources/build/patches.py create mode 100644 sources/build/theme.py create mode 100644 sources/build/utils.py rename colloid => sources/colloid (100%) rename {patches/colloid/fixes => sources/patches/colloid}/alt-tab-background-color.patch (100%) rename {patches => sources/patches}/colloid/palette.tera (100%) rename {patches => sources/patches}/colloid/plank-dark.patch (100%) rename {patches => sources/patches}/colloid/plank-light.patch (100%) rename {patches => sources/patches}/colloid/sass-palette-frappe.patch (100%) rename {patches => sources/patches}/colloid/sass-palette-latte.patch (100%) rename {patches => sources/patches}/colloid/sass-palette-macchiato.patch (100%) rename {patches => sources/patches}/colloid/sass-palette-mocha.patch (100%) rename {patches => sources/patches}/colloid/theme-func.patch (100%) rename {patches => sources/patches}/xfwm4/.gitignore (100%) rename {patches => sources/patches}/xfwm4/assets-dark-normal.svg (100%) rename {patches => sources/patches}/xfwm4/assets-dark.svg (100%) rename {patches => sources/patches}/xfwm4/assets-light-normal.svg (100%) rename {patches => sources/patches}/xfwm4/assets-light.svg (100%) rename {patches => sources/patches}/xfwm4/generate_assets.py (96%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 50808380..661758c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,33 +3,64 @@ name: "Generate test artifacts" on: pull_request: types: [opened, reopened, synchronize] + +# env: +# TAG: latest + jobs: build: 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: + - name: Checkout uses: actions/checkout@v4 with: submodules: true + - uses: actions/setup-python@v5 with: python-version: "3.11" cache: "pip" + - name: Install dependencies - run: pip install -r requirements.txt - - 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 + pip install -r requirements.txt + - name: Generate themes 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 uses: actions/upload-artifact@v4 with: name: '${{ github.sha }}-artifacts' - path: ./releases/*.zip - + path: ./releases/*.zip \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..60466c1e --- /dev/null +++ b/.github/workflows/docker.yml @@ -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 }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d5504faa..85292b39 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,22 +28,40 @@ jobs: uses: actions/checkout@v4 with: submodules: true + - uses: actions/setup-python@v5 with: python-version: "3.11" cache: "pip" + - name: Install dependencies run: pip install -r requirements.txt + - name: Install colloid specific dependencies run: sudo apt update && sudo apt install -y sassc inkscape optipng + - name: Generate themes run: | - python patches/xfwm4/generate_assets.py - - 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 + 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 + - name: Add zips to release run: gh release upload ${{ needs.release-please.outputs.tag_name }} releases/*.zip env: diff --git a/.gitignore b/.gitignore index cb11597c..dc38c0d7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ lib*/ *.cfg .direnv build/ +*.log +.ruff-cache +.tmp # Releases folder releases diff --git a/.gitmodules b/.gitmodules index 02412092..e92575fa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "colloid"] - path = colloid + path = sources/colloid url = https://github.com/vinceliuice/Colloid-gtk-theme.git diff --git a/build.py b/build.py index 2d37d3d6..3f16be70 100755 --- a/build.py +++ b/build.py @@ -1,636 +1,22 @@ -#!/usr/bin/env python3 -import os, re, shutil, subprocess, argparse, glob, logging, zipfile, sys +import os +import sys +import time +import os -from dataclasses import dataclass -from typing import Any, Literal, List +from sources.build import execute_build +from sources.build.args import parse_args +from sources.build.logger import logger -from catppuccin import PALETTE -from catppuccin.models import Flavor, Color - -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(): +if __name__ == "__main__": + git_root = os.path.dirname(os.path.realpath(__file__)) args = parse_args() - if args.patch: - apply_colloid_patches() - - if args.zip: - output_format = "zip" - else: - output_format = "dir" - - tweaks = Tweaks(tweaks=args.tweaks) - - palette = getattr(PALETTE, args.flavor) - - 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: + start = time.time() + execute_build(git_root, args) + end = time.time() - start -try: - main() -except Exception as e: - logger.error("Something went wrong when building the theme:", exc_info=e) - sys.exit(1) + logger.info("") + logger.info(f"Built in {round(end, 3)}s") + except Exception as e: + logger.error("Something went wrong when building the theme:", exc_info=e) diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..e768f4cc --- /dev/null +++ b/docker/Dockerfile @@ -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"] diff --git a/docker/build.sh b/docker/build.sh new file mode 100644 index 00000000..26261cdd --- /dev/null +++ b/docker/build.sh @@ -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 ] + +Push script for Catppuccin's GTK docker build image + +-v Custom version to build the image (:) + 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 diff --git a/docker/push.sh b/docker/push.sh new file mode 100644 index 00000000..83a72b60 --- /dev/null +++ b/docker/push.sh @@ -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 | -u [your-github-username] | -p [your-github-password]] + +Push script for Catppuccin's GTK docker build image + +-v Custom version to push the image (:) +-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 diff --git a/ARCHITECTURE.md b/docs/ARCHITECTURE.md similarity index 100% rename from ARCHITECTURE.md rename to docs/ARCHITECTURE.md diff --git a/CHANGELOG.md b/docs/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to docs/CHANGELOG.md diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 78% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md index 813da838..018557ce 100644 --- a/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -6,12 +6,24 @@ Information regarding the architecture of the project can be found [in the ARCHI - All the [requirements for building](#building) - `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 > [!TIP] > If you need to change the patches, reset the submodule and rerun the build script. We patch upstream colloid through a series of `.patch` files, applied through `git apply` once when the build begins. -The patches are located in `./patches/colloid/`. +The patches are located in `./patches/colloid/`. Once the build script patches the submodule, it will write a file into `colloid/.patched`, to signal to future invocations that the patches have already been applied. @@ -27,12 +39,12 @@ Now and again, Colloid will have bugs upstream that impacts our theme. With our but we still want to contribute the fixes upstream to benefit all users & forks of Colloid. To avoid stalling unnecessarily, our procedure for the above is as follows: -1) Open a PR to fix the issue, by adding a patch file to our theme, add `upstream:intended` +1) Open a PR to fix the issue, by adding a patch file to our theme, add `upstream:intended` to signal these changes are to be sent to Colloid eventually. 2) Merge the PR & close the issue in our theme pertaining to the issue, once reviewed and approved 3) Open a PR in Colloid with the patch 4) Open a new issue in our theme, with these details: - - The initial issue in our theme + - The initial issue in our theme - The PR in Colloid that fixes the issue there - The PR that fixed the issue in our theme @@ -43,7 +55,7 @@ to signal these changes are to be sent to Colloid eventually. - The tracking issue - The commit that fixed the issue in Colloid 3) Close the tracking issue & merge the PR to remove the patch file - + ### Running test builds We support building and publishing test builds from PRs. When you open PRs, the CI will automatically build with your changes and push an artifact @@ -55,10 +67,10 @@ pass the path into `install.py` under the `--from-artifact` option: python3 install.py mocha blue --dest ./build --from-artifact ~/downloads/7bff2448a81e36bf3b0e03bfbd649bebe6973ec7-artifacts.zip ``` -This will take the target flavor / accent out of the zip, and install it using the regular install process. +This will take the target flavor / accent out of the zip, and install it using the regular install process. It is advised to pass a `--dest` when running in this mode, because the released zips follow the exact same naming scheme as regular builds. -This wil cause conflicts when you install, if you already had that theme installed. Passing a different destination allows you to move the +This wil cause conflicts when you install, if you already had that theme installed. Passing a different destination allows you to move the extracted folders to `~/.local/share/themes` yourself, adding a suffix as appropriate to avoid conflicts. > [!WARNING] @@ -67,4 +79,4 @@ extracted folders to `~/.local/share/themes` yourself, adding a suffix as approp ### Useful resources - GNOME-shell sources: https://gitlab.gnome.org/GNOME/gnome-shell/-/tree/gnome-46/data/theme -- GTK inspector guide: https://developer.gnome.org/documentation/tools/inspector.html \ No newline at end of file +- GTK inspector guide: https://developer.gnome.org/documentation/tools/inspector.html diff --git a/install.py b/install.py index d642cd33..27be5640 100644 --- a/install.py +++ b/install.py @@ -1,11 +1,15 @@ #!/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 pathlib import Path from dataclasses import dataclass from urllib.request import urlopen, Request -from urllib.parse import urlparse logger = logging.getLogger("catppuccin-gtk") logger.setLevel(logging.DEBUG) @@ -160,6 +164,7 @@ def install(ctx: InstallContext): if ctx.link: add_libadwaita_links(ctx) + def install_from_artifact(ctx: InstallContext, artifact_path: Path): # Working from a pull request, special case it 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) logger.info("Links added") + def main(): args = parse_args() diff --git a/shell.nix b/shell.nix index 7305ad6d..7e3c743f 100644 --- a/shell.nix +++ b/shell.nix @@ -9,5 +9,6 @@ pkgs.mkShell { sassc inkscape optipng + ruff ]; } diff --git a/sources/build/__init__.py b/sources/build/__init__.py new file mode 100644 index 00000000..b050cf0a --- /dev/null +++ b/sources/build/__init__.py @@ -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!") diff --git a/sources/build/args.py b/sources/build/args.py new file mode 100644 index 00000000..38a0a128 --- /dev/null +++ b/sources/build/args.py @@ -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() diff --git a/sources/build/context.py b/sources/build/context.py new file mode 100644 index 00000000..3d43c019 --- /dev/null +++ b/sources/build/context.py @@ -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 +) diff --git a/sources/build/logger.py b/sources/build/logger.py new file mode 100644 index 00000000..99e1552e --- /dev/null +++ b/sources/build/logger.py @@ -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) diff --git a/sources/build/patches.py b/sources/build/patches.py new file mode 100644 index 00000000..86237b19 --- /dev/null +++ b/sources/build/patches.py @@ -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.") diff --git a/sources/build/theme.py b/sources/build/theme.py new file mode 100644 index 00000000..2a635afb --- /dev/null +++ b/sources/build/theme.py @@ -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}';", + ), + ) diff --git a/sources/build/utils.py b/sources/build/utils.py new file mode 100644 index 00000000..76d78f41 --- /dev/null +++ b/sources/build/utils.py @@ -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") diff --git a/colloid b/sources/colloid similarity index 100% rename from colloid rename to sources/colloid diff --git a/patches/colloid/fixes/alt-tab-background-color.patch b/sources/patches/colloid/alt-tab-background-color.patch similarity index 100% rename from patches/colloid/fixes/alt-tab-background-color.patch rename to sources/patches/colloid/alt-tab-background-color.patch diff --git a/patches/colloid/palette.tera b/sources/patches/colloid/palette.tera similarity index 100% rename from patches/colloid/palette.tera rename to sources/patches/colloid/palette.tera diff --git a/patches/colloid/plank-dark.patch b/sources/patches/colloid/plank-dark.patch similarity index 100% rename from patches/colloid/plank-dark.patch rename to sources/patches/colloid/plank-dark.patch diff --git a/patches/colloid/plank-light.patch b/sources/patches/colloid/plank-light.patch similarity index 100% rename from patches/colloid/plank-light.patch rename to sources/patches/colloid/plank-light.patch diff --git a/patches/colloid/sass-palette-frappe.patch b/sources/patches/colloid/sass-palette-frappe.patch similarity index 100% rename from patches/colloid/sass-palette-frappe.patch rename to sources/patches/colloid/sass-palette-frappe.patch diff --git a/patches/colloid/sass-palette-latte.patch b/sources/patches/colloid/sass-palette-latte.patch similarity index 100% rename from patches/colloid/sass-palette-latte.patch rename to sources/patches/colloid/sass-palette-latte.patch diff --git a/patches/colloid/sass-palette-macchiato.patch b/sources/patches/colloid/sass-palette-macchiato.patch similarity index 100% rename from patches/colloid/sass-palette-macchiato.patch rename to sources/patches/colloid/sass-palette-macchiato.patch diff --git a/patches/colloid/sass-palette-mocha.patch b/sources/patches/colloid/sass-palette-mocha.patch similarity index 100% rename from patches/colloid/sass-palette-mocha.patch rename to sources/patches/colloid/sass-palette-mocha.patch diff --git a/patches/colloid/theme-func.patch b/sources/patches/colloid/theme-func.patch similarity index 100% rename from patches/colloid/theme-func.patch rename to sources/patches/colloid/theme-func.patch diff --git a/patches/xfwm4/.gitignore b/sources/patches/xfwm4/.gitignore similarity index 100% rename from patches/xfwm4/.gitignore rename to sources/patches/xfwm4/.gitignore diff --git a/patches/xfwm4/assets-dark-normal.svg b/sources/patches/xfwm4/assets-dark-normal.svg similarity index 100% rename from patches/xfwm4/assets-dark-normal.svg rename to sources/patches/xfwm4/assets-dark-normal.svg diff --git a/patches/xfwm4/assets-dark.svg b/sources/patches/xfwm4/assets-dark.svg similarity index 100% rename from patches/xfwm4/assets-dark.svg rename to sources/patches/xfwm4/assets-dark.svg diff --git a/patches/xfwm4/assets-light-normal.svg b/sources/patches/xfwm4/assets-light-normal.svg similarity index 100% rename from patches/xfwm4/assets-light-normal.svg rename to sources/patches/xfwm4/assets-light-normal.svg diff --git a/patches/xfwm4/assets-light.svg b/sources/patches/xfwm4/assets-light.svg similarity index 100% rename from patches/xfwm4/assets-light.svg rename to sources/patches/xfwm4/assets-light.svg diff --git a/patches/xfwm4/generate_assets.py b/sources/patches/xfwm4/generate_assets.py similarity index 96% rename from patches/xfwm4/generate_assets.py rename to sources/patches/xfwm4/generate_assets.py index 82cc89a5..4aed8bf5 100644 --- a/patches/xfwm4/generate_assets.py +++ b/sources/patches/xfwm4/generate_assets.py @@ -2,7 +2,11 @@ from catppuccin import PALETTE 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 THIS_DIR = os.path.dirname(os.path.realpath(__file__)) @@ -83,9 +87,9 @@ def generate_for_flavor(flavor: Flavor): # Setup the palette palette = flavor.colors - close_color=f'#{palette.red.hex}' - max_color=f'#{palette.green.hex}' - min_color=f'#{palette.yellow.hex}' + close_color = f"#{palette.red.hex}" + max_color = f"#{palette.green.hex}" + min_color = f"#{palette.yellow.hex}" # 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 @@ -187,8 +191,9 @@ class RenderState: "-xhdpi": "192", } + 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 src_file = f"{state.input_root}/assets-catppuccin-{flavor.identifier}.svg" @@ -215,9 +220,7 @@ def render_for_screen(state: RenderState, flavor: Flavor, screen: str, ident: st def render_for_flavor(flavor: Flavor, state: RenderState): - print( - f"Starting render tasks for {flavor.identifier}" - ) + print(f"Starting render tasks for {flavor.identifier}") for ident in INDEX: render_for_screen(state=state, flavor=flavor, screen="", ident=ident) render_for_screen(state=state, flavor=flavor, screen="-hdpi", ident=ident)