diff --git a/README.md b/README.md index ac1603e..a76224a 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,9 @@ Icon theme: https://github.com/vinceliuice/Colloid-icon-theme > [!TIP] > If you want to install only one color, use the `--red`, `--yellow`, `--green`, `--blue`, `--purple`, `--gray` option. +See the [installation tweaks](#-installation-tweaks) section for more information. + +If you want to remove the theme, see the [uninstallation](#-uninstallation--reinstallation) section. ## 🖥️ GDM theme @@ -205,3 +208,37 @@ Icon theme: https://github.com/vinceliuice/Colloid-icon-theme | --red --green --sat=70 | red, green accent colors, 70% of the stock saturation | | --hue=200 --name=grayblue --sat=50 --mode=dark | custom grayblue accent color, 50% of the stock saturation, dark mode | | --gdm --blue --gdm-image /path/to/image.jpg --gdm-blur=40 | Install GDM theming in blue color with own GDM background image and blur | + + +## 🗑️ Uninstallation / Reinstallation + +- To remove the theme, run the program with the `--remove` option: + ```shell + python install.py -ra + ``` +- To reinstall the theme, run the program with the `--reinstall` option: + ```shell + python install.py -ri -a # (other installation options) + ``` + +> [!TIP] +> If you want to remove the GDM theme, use the `--gdm` option with the `--remove` option. + + +The program allows you to specify the color of the theme and the mode to remove. +For example, to remove purple and green themes in light mode, use the command: +```shell +python install.py -r --purple --green --mode light +``` + +Also, you can use the command above to reinstall purple and green themes in light mode with the `--reinstall` option. +```shell +python install.py -ri --purple --green --mode light +``` + +#### Options + +| Option | Description | +|------------------|-----------------------| +| -r, --remove | Remove the theme | +| -ri, --reinstall | Reinstall the theme | \ No newline at end of file diff --git a/install.py b/install.py index f5f044a..50b826f 100644 --- a/install.py +++ b/install.py @@ -30,7 +30,7 @@ from scripts.theme import Theme from scripts.gdm import GlobalTheme -def parse_args(colors): +def parse_args(colors) -> argparse.Namespace: """ Parse command-line arguments :return: parsed arguments @@ -51,7 +51,8 @@ def parse_args(colors): ''')) # Default arguments - parser.add_argument('-r', '--remove', action='store_true', help='remove all "Marble" themes') + parser.add_argument('-r', '--remove', action='store_true', help='remove Marble themes') + parser.add_argument('-ri', '--reinstall', action='store_true', help='reinstall Marble themes') default_args = parser.add_argument_group('Install default theme') default_args.add_argument('-a', '--all', action='store_true', help='all available accent colors') @@ -70,7 +71,7 @@ def parse_args(colors): color_tweaks = parser.add_argument_group('Optional theme tweaks') color_tweaks.add_argument('--mode', choices=['light', 'dark'], help='select a specific theme mode to install') color_tweaks.add_argument('--sat', type=int, choices=range(0, 251), - help='custom color saturation (<100%% - reduce, >100%% - increase)', metavar='(0 - 250)%') + help='custom color saturation (<100%% - reduce, >100%% - increase)', metavar='(0 - 250)') gdm_theming = parser.add_argument_group('GDM theming') gdm_theming.add_argument('--gdm', action='store_true', help='install GDM theme. \ @@ -183,8 +184,10 @@ def local_theme(args, colors): :param colors: colors from colors.json """ - if args.remove: - remove_files() + if args.remove or args.reinstall: + remove_files(args, colors["colors"]) + if not args.reinstall: + return gnome_shell_theme = Theme("gnome-shell", colors, f"{config.raw_theme_folder}/{config.gnome_folder}", config.themes_folder, config.temp_folder, @@ -204,7 +207,9 @@ def main(): else: local_theme(args, colors) - apply_gnome_theme() + + if args.remove == args.reinstall: + apply_gnome_theme() if __name__ == "__main__": diff --git a/scripts/utils/remove_files.py b/scripts/utils/remove_files.py index d32cbd3..b3fd73b 100644 --- a/scripts/utils/remove_files.py +++ b/scripts/utils/remove_files.py @@ -1,48 +1,122 @@ -# TODO: Less verbose output for the user and simplify the code +# TODO: Add ability to delete custom colors +# TODO: Create an interface where the user can select which themes to delete +# TODO: Add a flag to skip the confirmation prompt + +import argparse +import shutil +from collections import defaultdict from .. import config import os -def remove_files(): - """ - Delete already installed Marble theme - """ +def remove_files(args: argparse.Namespace, colors: dict[str, str]): + """Delete already installed Marble theme""" + themes = detect_themes(config.themes_folder) - paths = (config.themes_folder, "~/.local/share/themes") + filtered_themes = themes - print("💡 You do not need to delete files if you want to update theme.\n") + if args.all: + filtered_themes = themes + if not args.all: + args_dict = vars(args) + arguments = [color for color in colors.keys() if args_dict.get(color)] + filtered_themes = themes.filter(arguments) - confirmation = input(f"Do you want to delete all \"Marble\" folders in {' and in '.join(paths)}? (y/N) ").lower() + if not filtered_themes: + print("No matching themes found.") + return - if confirmation == "y": - for path in paths: + colors = [color for (color, modes) in filtered_themes] + print(f"The following themes will be deleted: {', '.join(colors)}.") + if args.mode: + print(f"Theme modes to be deleted: {args.mode}.") - # Check if the path exists - if os.path.exists(os.path.expanduser(path)): + if input(f"Proceed? (y/N) ").lower() == "y": + filtered_themes.remove(args.mode) + print("Themes deleted successfully.") + else: + print("Operation cancelled.") - # Get the list of folders in the path - folders = os.listdir(os.path.expanduser(path)) - # toggle if folder has no marble theme - found_folder = False +def detect_themes(path: str) -> 'Themes': + """Detect themes in a given path""" + abs_path = os.path.expanduser(path) + themes = Themes() - for folder in folders: - if folder.startswith("Marble"): - folder_path = os.path.join(os.path.expanduser(path), folder) - print(f"Deleting folder {folder_path}...", end='') + if not os.path.exists(abs_path): + return themes - try: - os.system(f"rm -r {folder_path}") + folders = os.listdir(abs_path) - except Exception as e: - print(f"Error deleting folder {folder_path}: {e}") + for folder in folders: + parsed = parse_folder(folder) + if parsed: + (color, mode) = parsed + theme_mode = ThemeMode(mode=mode, path=os.path.join(abs_path, folder)) + themes.add_theme(color, theme_mode) - else: - found_folder = True - print("Done.") + return themes - if not found_folder: - print(f"No folders starting with \"Marble\" found in {path}.") - else: - print(f"The path {path} does not exist.") \ No newline at end of file +def parse_folder(folder: str) -> tuple[str, str] | None: + """Parse a folder name into color and mode""" + folder_arr = folder.split("-") + + if len(folder_arr) < 2 or folder_arr[0] != "Marble": + return None + + color = "-".join(folder_arr[1:-1]) + mode = folder_arr[-1] + + return color, mode + + +class ThemeMode: + """Concrete theme with mode and path""" + mode: str + path: str + + def __init__(self, mode: str, path: str): + self.mode = mode + self.path = path + + def remove(self): + try: + shutil.rmtree(self.path) + except Exception as e: + print(f"Error deleting {self.path}: {e}") + + +class Themes: + """Collection of themes grouped by color""" + def __init__(self): + self.by_color: dict[str, list[ThemeMode]] = defaultdict(list) # color: list[ThemeMode] + + def add_theme(self, color: str, theme_mode: ThemeMode): + self.by_color[color].append(theme_mode) + + def filter(self, colors: list[str]): + """ + Filter themes by colors. + Returns a new Themes object. + """ + filtered = Themes() + + for color in colors: + if color in self.by_color: + filtered.by_color[color] = self.by_color[color].copy() + + return filtered + + def remove(self, mode: str | None = None): + for modes in self.by_color.values(): + for theme_mode in modes: + if mode is None or theme_mode.mode == mode: + theme_mode.remove() + + def __bool__(self): + return bool(self.by_color) + + def __iter__(self): + for color, modes in self.by_color.items(): + yield color, modes diff --git a/tweaks/gdm/tweak.py b/tweaks/gdm/tweak.py index 79450d5..0831610 100755 --- a/tweaks/gdm/tweak.py +++ b/tweaks/gdm/tweak.py @@ -9,7 +9,7 @@ def define_arguments(parser: ArgumentParser): gdm_args = parser.add_argument_group("GDM tweaks") gdm_args.add_argument("--gdm-image", type=str, nargs="?", help="Set GDM background image") gdm_args.add_argument("--gdm-blur", type=int, nargs="?", help="Blur GDM background image (px)") - gdm_args.add_argument("--gdm-darken", type=int, choices=range(0, 100), help="Darken GDM background image (%)", metavar="(0 - 100)") + gdm_args.add_argument("--gdm-darken", type=int, choices=range(0, 100), help="Darken GDM background image (%%)", metavar="(0 - 100)") gdm_args.add_argument("--gdm-lighten", type=int, choices=range(0, 100), help="Lighten GDM background image", metavar="(0 - 100)")