Files
Marble-shell-theme/scripts/gdm.py
Vladyslav Hroshev 31e1a3deb9 TESTS TESTS ADDED MORE TEST TESTED GRESOURCES TESTS TESTS I LOVE TESTS SO MUCH
- Fully covered gresource orchestrator and it helpers with tests
- Use abstract command runner INSTEAD OF SUBPROCESS in gresources
- Replaced some subprocesses by builtin functions
2025-04-06 21:56:27 +03:00

152 lines
6.6 KiB
Python

import os
from typing import Optional
from .install.colors_definer import ColorsDefiner
from .theme import Theme
from .utils import remove_properties, remove_keywords
from . import config
from .utils.alternatives_updater import AlternativesUpdater
from scripts.utils.logger.console import Console, Color, Format
from .utils.command_runner.subprocess_command_runner import SubprocessCommandRunner
from .utils.files_labeler import FilesLabeler
from .utils.gresource import GresourceBackupNotFoundError
from .utils.gresource.gresource import Gresource
class GlobalTheme:
"""Class to install global theme for GDM"""
def __init__(self,
colors_json: ColorsDefiner, theme_folder: str,
destination_folder: str, destination_file: str, temp_folder: str,
mode: Optional[str] = None, is_filled = False
):
"""
:param colors_json: location of a JSON file with color values
:param theme_folder: raw theme location
:param destination_folder: folder where themes will be installed
:param temp_folder: folder where files will be collected
:param mode: theme mode (light or dark)
:param is_filled: if True, the theme will be filled
"""
self.colors_json = colors_json
self.theme_folder = theme_folder
self.destination_folder = destination_folder
self.destination_file = destination_file
self.temp_folder = temp_folder
self.is_filled = is_filled
self.mode = mode
self.themes: list[ThemePrepare] = []
self.__is_installed_trigger = "\n/* Marble theme */\n"
self.__gresource_file = os.path.join(self.destination_folder, self.destination_file)
self.__gresource_temp_folder = os.path.join(self.temp_folder, config.extracted_gdm_folder)
self.__gresource = Gresource(self.destination_file, self.__gresource_temp_folder, self.destination_folder,
logger_factory=Console(), runner=SubprocessCommandRunner())
def prepare(self):
if self.__is_installed():
Console.Line().info("Theme is installed. Reinstalling...")
self.__gresource.use_backup_gresource()
self.__gresource.extract()
self.__find_themes()
def __is_installed(self) -> bool:
if not hasattr(self, '__is_installed_cached'):
with open(self.__gresource_file, "rb") as f:
self.__is_installed_cached = self.__is_installed_trigger.encode() in f.read()
return self.__is_installed_cached
def __find_themes(self):
extracted_theme_files = os.listdir(self.__gresource_temp_folder)
allowed_modes = ("dark", "light")
allowed_css = ("gnome-shell-dark", "gnome-shell-light", "gnome-shell")
for style_name in allowed_css:
style_file = style_name + ".css"
if style_file in extracted_theme_files:
last_mode = style_name.split("-")[-1]
mode = last_mode if last_mode in allowed_modes else None
self.__append_theme(style_name, mode=mode or self.mode, label=mode)
def __append_theme(self, theme_type: str, mode = None, label: Optional[str] = None):
"""Helper to create theme objects"""
theme = Theme(theme_type, self.colors_json, self.theme_folder,
self.__gresource_temp_folder, self.temp_folder,
mode=mode, is_filled=self.is_filled)
theme.prepare()
theme_file = os.path.join(self.__gresource_temp_folder, f"{theme_type}.css")
self.themes.append(ThemePrepare(theme=theme, theme_file=theme_file, label=label))
def install(self, hue, sat=None):
"""Install theme globally"""
if os.geteuid() != 0:
raise Exception("Root privileges required to install GDM theme")
self.__generate_themes(hue, 'Marble', sat)
self.__gresource.compile()
if not self.__is_installed():
self.__gresource.backup()
self.__gresource.move()
self.__update_alternatives()
def __generate_themes(self, hue: int, color: str, sat: Optional[int] = None):
"""Generate theme files for gnome-shell-theme.gresource.xml"""
for theme_prepare in self.themes:
if theme_prepare.label is not None:
temp_folder = theme_prepare.theme.temp_folder
main_styles = theme_prepare.theme.main_styles
FilesLabeler(temp_folder, main_styles).append_label(theme_prepare.label)
remove_keywords(theme_prepare.theme_file, "!important")
remove_properties(theme_prepare.theme_file, "background-color", "color", "box-shadow", "border-radius")
self.__add_gnome_styles(theme_prepare.theme)
theme_prepare.theme.install(hue, color, sat, destination=self.__gresource_temp_folder)
def __add_gnome_styles(self, theme: Theme):
"""Add gnome styles to the start of the file"""
with open(f"{theme.destination_folder}/{theme.theme_type}.css", 'r') as gnome_theme:
gnome_styles = gnome_theme.read() + self.__is_installed_trigger
theme.add_to_start(gnome_styles)
def __update_alternatives(self):
link = os.path.join(self.destination_folder, config.ubuntu_gresource_link)
name = config.ubuntu_gresource_link
path = os.path.join(self.destination_folder, self.destination_file)
AlternativesUpdater.install_and_set(link, name, path)
def remove(self):
if self.__is_installed():
removing_line = Console.Line()
removing_line.update("Theme is installed. Removing...")
try:
self.__gresource.restore()
removing_line.success("Global theme removed successfully. Restart GDM to apply changes.")
except GresourceBackupNotFoundError:
formatted_shell = Console.format("gnome-shell", color=Color.BLUE, format_type=Format.BOLD)
removing_line.error(f"Backup file not found. Try reinstalling {formatted_shell} package.")
else:
Console.Line().error("Theme is not installed. Nothing to remove.")
Console.Line().warn("If theme is still installed globally, try reinstalling gnome-shell package.")
def __remove_alternatives(self):
name = config.ubuntu_gresource_link
path = os.path.join(self.destination_folder, self.destination_file)
AlternativesUpdater.remove(name, path)
class ThemePrepare:
"""Theme data class prepared for installation"""
def __init__(self, theme: Theme, theme_file, label: Optional[str] = None):
self.theme = theme
self.theme_file = theme_file
self.label = label