mirror of
https://github.com/imarkoff/Marble-shell-theme.git
synced 2025-09-24 20:26:35 -07:00
Formatted logs
This commit is contained in:
@@ -19,3 +19,5 @@ extracted_gdm_folder = "theme"
|
|||||||
gnome_shell_css = f"{temp_gnome_folder}/gnome-shell.css"
|
gnome_shell_css = f"{temp_gnome_folder}/gnome-shell.css"
|
||||||
tweak_file = f"./{tweaks_folder}/*/tweak.py"
|
tweak_file = f"./{tweaks_folder}/*/tweak.py"
|
||||||
colors_json = "colors.json"
|
colors_json = "colors.json"
|
||||||
|
|
||||||
|
user_themes_extension = "/org/gnome/shell/extensions/user-theme/name"
|
||||||
|
@@ -4,6 +4,7 @@ import subprocess
|
|||||||
from .theme import Theme
|
from .theme import Theme
|
||||||
from .utils import remove_properties, remove_keywords, gnome
|
from .utils import remove_properties, remove_keywords, gnome
|
||||||
from . import config
|
from . import config
|
||||||
|
from .utils.console import Console, Color, Format
|
||||||
from .utils.files_labeler import FilesLabeler
|
from .utils.files_labeler import FilesLabeler
|
||||||
|
|
||||||
|
|
||||||
@@ -69,7 +70,8 @@ class GlobalTheme:
|
|||||||
"""
|
"""
|
||||||
Extract gresource files to temp folder
|
Extract gresource files to temp folder
|
||||||
"""
|
"""
|
||||||
print("Extracting gresource files...")
|
extract_line = Console.Line()
|
||||||
|
extract_line.update("Extracting gresource files...")
|
||||||
|
|
||||||
resources = subprocess.getoutput(f"gresource list {self.gst}").split("\n")
|
resources = subprocess.getoutput(f"gresource list {self.gst}").split("\n")
|
||||||
prefix = "/org/gnome/shell/"
|
prefix = "/org/gnome/shell/"
|
||||||
@@ -84,6 +86,8 @@ class GlobalTheme:
|
|||||||
with open(output_path, 'wb') as f:
|
with open(output_path, 'wb') as f:
|
||||||
subprocess.run(["gresource", "extract", self.gst, resource], stdout=f, check=True)
|
subprocess.run(["gresource", "extract", self.gst, resource], stdout=f, check=True)
|
||||||
|
|
||||||
|
extract_line.success("Extracted gresource files.")
|
||||||
|
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
if "gresource" in str(e):
|
if "gresource" in str(e):
|
||||||
print("Error: 'gresource' command not found.")
|
print("Error: 'gresource' command not found.")
|
||||||
@@ -127,22 +131,16 @@ class GlobalTheme:
|
|||||||
|
|
||||||
|
|
||||||
def __backup(self):
|
def __backup(self):
|
||||||
"""
|
|
||||||
Backup installed theme
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.__is_installed():
|
if self.__is_installed():
|
||||||
return
|
return
|
||||||
|
|
||||||
# backup installed theme
|
backup_line = Console.Line()
|
||||||
print("Backing up default theme...")
|
|
||||||
|
backup_line.update("Backing up default theme...")
|
||||||
subprocess.run(["cp", "-aT", self.gst, f"{self.gst}.backup"], cwd=self.destination_folder, check=True)
|
subprocess.run(["cp", "-aT", self.gst, f"{self.gst}.backup"], cwd=self.destination_folder, check=True)
|
||||||
|
backup_line.success("Backed up default theme.")
|
||||||
|
|
||||||
def __generate_gresource_xml(self):
|
def __generate_gresource_xml(self):
|
||||||
"""
|
|
||||||
Generates.gresource.xml
|
|
||||||
"""
|
|
||||||
|
|
||||||
# list of files to add to gnome-shell-theme.gresource.xml
|
# list of files to add to gnome-shell-theme.gresource.xml
|
||||||
files = [f"<file>{file}</file>" for file in os.listdir(self.extracted_theme)]
|
files = [f"<file>{file}</file>" for file in os.listdir(self.extracted_theme)]
|
||||||
nl = "\n" # fstring doesn't support newline character
|
nl = "\n" # fstring doesn't support newline character
|
||||||
@@ -197,21 +195,23 @@ class GlobalTheme:
|
|||||||
gresource_xml.write(generated_xml)
|
gresource_xml.write(generated_xml)
|
||||||
|
|
||||||
# compile gnome-shell-theme.gresource.xml
|
# compile gnome-shell-theme.gresource.xml
|
||||||
print("Compiling theme...")
|
compile_line = Console.Line()
|
||||||
|
compile_line.update("Compiling gnome-shell theme...")
|
||||||
subprocess.run(["glib-compile-resources" , f"{self.destination_file}.xml"],
|
subprocess.run(["glib-compile-resources" , f"{self.destination_file}.xml"],
|
||||||
cwd=self.extracted_theme, check=True)
|
cwd=self.extracted_theme, check=True)
|
||||||
|
compile_line.success("Theme compiled.")
|
||||||
|
|
||||||
# backup installed theme
|
# backup installed theme
|
||||||
self.__backup()
|
self.__backup()
|
||||||
|
|
||||||
# install theme
|
# install theme
|
||||||
print("Installing theme...")
|
install_line = Console.Line()
|
||||||
|
install_line.update("Moving compiled theme to system folder...")
|
||||||
subprocess.run(["sudo", "cp", "-f",
|
subprocess.run(["sudo", "cp", "-f",
|
||||||
f"{self.extracted_theme}/{self.destination_file}",
|
f"{self.extracted_theme}/{self.destination_file}",
|
||||||
f"{self.destination_folder}/{self.destination_file}"],
|
f"{self.destination_folder}/{self.destination_file}"],
|
||||||
check=True)
|
check=True)
|
||||||
|
install_line.success("Theme moved to system folder.")
|
||||||
print("Theme installed successfully.")
|
|
||||||
|
|
||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
@@ -221,16 +221,19 @@ class GlobalTheme:
|
|||||||
|
|
||||||
# use backup file if theme is installed
|
# use backup file if theme is installed
|
||||||
if self.__is_installed():
|
if self.__is_installed():
|
||||||
print("Theme is installed. Removing...")
|
removing_line = Console.Line()
|
||||||
|
removing_line.update("Theme is installed. Removing...")
|
||||||
backup_path = os.path.join(self.destination_folder, self.backup_file)
|
backup_path = os.path.join(self.destination_folder, self.backup_file)
|
||||||
dest_path = os.path.join(self.destination_folder, self.destination_file)
|
dest_path = os.path.join(self.destination_folder, self.destination_file)
|
||||||
|
|
||||||
if os.path.isfile(backup_path):
|
if os.path.isfile(backup_path):
|
||||||
subprocess.run(["sudo", "mv", backup_path, dest_path], check=True)
|
subprocess.run(["sudo", "mv", backup_path, dest_path], check=True)
|
||||||
|
removing_line.success("Global theme removed successfully. Restart GDM to apply changes.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Backup file not found. Try reinstalling gnome-shell package.")
|
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:
|
else:
|
||||||
print("Theme is not installed. Nothing to remove.")
|
Console.Line().error("Theme is not installed. Nothing to remove.")
|
||||||
print("If theme is still installed globally, try reinstalling gnome-shell package.")
|
Console.Line().update("If theme is still installed globally, try reinstalling gnome-shell package.", icon="⚠️")
|
||||||
|
@@ -3,6 +3,7 @@ import os
|
|||||||
from scripts import config
|
from scripts import config
|
||||||
from scripts.gdm import GlobalTheme
|
from scripts.gdm import GlobalTheme
|
||||||
from scripts.install.theme_installer import ThemeInstaller
|
from scripts.install.theme_installer import ThemeInstaller
|
||||||
|
from scripts.utils.console import Console, Color, Format
|
||||||
|
|
||||||
|
|
||||||
class GlobalThemeInstaller(ThemeInstaller):
|
class GlobalThemeInstaller(ThemeInstaller):
|
||||||
@@ -27,6 +28,12 @@ class GlobalThemeInstaller(ThemeInstaller):
|
|||||||
self._apply_tweaks(theme.theme)
|
self._apply_tweaks(theme.theme)
|
||||||
|
|
||||||
def _after_install(self):
|
def _after_install(self):
|
||||||
print("\nGDM theme installed successfully.")
|
print()
|
||||||
print("You need to restart gdm.service to apply changes.")
|
Console.Line().update(
|
||||||
print("Run \"systemctl restart gdm.service\" to restart GDM.")
|
Console.format("GDM theme installed successfully.", color=Color.GREEN, format_type=Format.BOLD),
|
||||||
|
icon="🥳"
|
||||||
|
)
|
||||||
|
Console.Line().update("You need to restart GDM to apply changes.", icon="ℹ️ ")
|
||||||
|
|
||||||
|
formatted_command = Console.format("systemctl restart gdm.service", color=Color.YELLOW, format_type=Format.BOLD)
|
||||||
|
Console.Line().update(f"Run {formatted_command} to restart GDM.", icon="🔄")
|
@@ -4,6 +4,7 @@ from scripts import config
|
|||||||
from scripts.install.theme_installer import ThemeInstaller
|
from scripts.install.theme_installer import ThemeInstaller
|
||||||
from scripts.theme import Theme
|
from scripts.theme import Theme
|
||||||
from scripts.utils import remove_files
|
from scripts.utils import remove_files
|
||||||
|
from scripts.utils.console import Console, Color, Format
|
||||||
|
|
||||||
|
|
||||||
class LocalThemeInstaller(ThemeInstaller):
|
class LocalThemeInstaller(ThemeInstaller):
|
||||||
@@ -26,4 +27,6 @@ class LocalThemeInstaller(ThemeInstaller):
|
|||||||
self._apply_tweaks(self.theme)
|
self._apply_tweaks(self.theme)
|
||||||
|
|
||||||
def _after_install(self):
|
def _after_install(self):
|
||||||
print("\nTheme installed successfully.")
|
print()
|
||||||
|
formatted_output = Console.format("Theme installed successfully.", color=Color.GREEN, format_type=Format.BOLD)
|
||||||
|
Console.Line().update(formatted_output, icon="🥳")
|
@@ -11,9 +11,9 @@ class Console:
|
|||||||
_next_line = 0
|
_next_line = 0
|
||||||
|
|
||||||
class Line:
|
class Line:
|
||||||
def __init__(self, name):
|
def __init__(self, name: Optional[str]=None):
|
||||||
"""Initialize a new managed line"""
|
"""Initialize a new managed line"""
|
||||||
self.name = name
|
self.name = name or f"line_{Console._next_line}"
|
||||||
self._reserve_line()
|
self._reserve_line()
|
||||||
|
|
||||||
def update(self, message, icon="⏳"):
|
def update(self, message, icon="⏳"):
|
||||||
@@ -25,6 +25,9 @@ class Console:
|
|||||||
if lines_up > 0:
|
if lines_up > 0:
|
||||||
sys.stdout.write(f"\033[{lines_up}F")
|
sys.stdout.write(f"\033[{lines_up}F")
|
||||||
# Clear line and write status
|
# Clear line and write status
|
||||||
|
if icon.strip() == "":
|
||||||
|
sys.stdout.write(f"\r\033[K{message}")
|
||||||
|
else:
|
||||||
sys.stdout.write(f"\r\033[K{icon} {message}")
|
sys.stdout.write(f"\r\033[K{icon} {message}")
|
||||||
# Move the cursor back down
|
# Move the cursor back down
|
||||||
if lines_up > 0:
|
if lines_up > 0:
|
||||||
@@ -78,8 +81,8 @@ class Color(Enum):
|
|||||||
GRAY = '\033[90m'
|
GRAY = '\033[90m'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, color: str) -> Optional['Color']:
|
def get(cls, color: str, default: Optional['Color']=None) -> Optional['Color']:
|
||||||
return getattr(cls, color.upper(), None)
|
return getattr(cls, color.upper(), default)
|
||||||
|
|
||||||
|
|
||||||
class Format(Enum):
|
class Format(Enum):
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
from scripts import config
|
||||||
|
from scripts.utils.console import Console, Format, Color
|
||||||
|
from scripts.utils.parse_folder import parse_folder
|
||||||
|
|
||||||
|
|
||||||
def gnome_version() -> str | None:
|
def gnome_version() -> str | None:
|
||||||
"""
|
"""
|
||||||
Get gnome-shell version
|
Get gnome-shell version
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(['gnome-shell', '--version'], text=True).strip()
|
output = subprocess.check_output(['gnome-shell', '--version'], text=True).strip()
|
||||||
return output.split(' ')[2]
|
return output.split(' ')[2]
|
||||||
@@ -13,21 +18,40 @@ def gnome_version() -> str | None:
|
|||||||
|
|
||||||
def apply_gnome_theme(theme=None) -> bool:
|
def apply_gnome_theme(theme=None) -> bool:
|
||||||
"""
|
"""
|
||||||
Apply gnome-shell theme
|
Applies the theme in user theme extension if it is Marble and extension installed.
|
||||||
:param theme: theme name
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if theme is None:
|
if theme is None:
|
||||||
current_theme = subprocess.check_output(['dconf', 'read', '/org/gnome/shell/extensions/user-theme/name'], text=True).strip().strip("'")
|
theme = get_current_theme()
|
||||||
if current_theme.startswith("Marble"):
|
|
||||||
theme = current_theme
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
subprocess.run(['dconf', 'reset', '/org/gnome/shell/extensions/user-theme/name'], check=True)
|
line = Console.Line("apply_gnome_theme")
|
||||||
subprocess.run(['dconf', 'write', '/org/gnome/shell/extensions/user-theme/name', f"'{theme}'"], check=True)
|
(color, _) = parse_folder(theme)
|
||||||
print(f"Theme '{theme}' applied.")
|
formatted_theme = Console.format(theme, color=Color.get(color, Color.GRAY), format_type=Format.BOLD)
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
|
line.update(f"Applying {formatted_theme} theme...")
|
||||||
|
time.sleep(0.1) # applying the theme may freeze, so we need to wait a bit
|
||||||
|
apply_user_theme(theme)
|
||||||
|
line.success(f"Theme {formatted_theme} applied.")
|
||||||
|
except Exception:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_theme() -> str:
|
||||||
|
"""
|
||||||
|
Throws an error if theme is not Marble.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
output = subprocess.check_output(['dconf', 'read', config.user_themes_extension], text=True)
|
||||||
|
output = output.strip().strip("'")
|
||||||
|
|
||||||
|
if not output.startswith("Marble"):
|
||||||
|
raise Exception(f"Theme {output} doesn't appear to be a Marble theme")
|
||||||
|
return output
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
raise Exception("User theme extension not found.")
|
||||||
|
|
||||||
|
|
||||||
|
def apply_user_theme(theme_name: str):
|
||||||
|
subprocess.run(['dconf', 'reset', config.user_themes_extension], check=True)
|
||||||
|
subprocess.run(['dconf', 'write', config.user_themes_extension, f"'{theme_name}'"], check=True)
|
11
scripts/utils/parse_folder.py
Normal file
11
scripts/utils/parse_folder.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
@@ -7,10 +7,12 @@ import shutil
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from .console import Console, Color, Format
|
||||||
|
from .parse_folder import parse_folder
|
||||||
from .. import config
|
from .. import config
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def remove_files(args: argparse.Namespace, colors: dict[str, Any]):
|
def remove_files(args: argparse.Namespace, formatted_colors: dict[str, Any]):
|
||||||
"""Delete already installed Marble theme"""
|
"""Delete already installed Marble theme"""
|
||||||
themes = detect_themes(config.themes_folder)
|
themes = detect_themes(config.themes_folder)
|
||||||
|
|
||||||
@@ -20,23 +22,26 @@ def remove_files(args: argparse.Namespace, colors: dict[str, Any]):
|
|||||||
filtered_themes = themes
|
filtered_themes = themes
|
||||||
if not args.all:
|
if not args.all:
|
||||||
args_dict = vars(args)
|
args_dict = vars(args)
|
||||||
arguments = [color for color in colors.keys() if args_dict.get(color)]
|
arguments = [color for color in formatted_colors.keys() if args_dict.get(color)]
|
||||||
filtered_themes = themes.filter(arguments)
|
filtered_themes = themes.filter(arguments)
|
||||||
|
|
||||||
if not filtered_themes:
|
if not filtered_themes:
|
||||||
print("No matching themes found.")
|
Console.Line().error("No matching themes found.")
|
||||||
return
|
return
|
||||||
|
|
||||||
colors = [color for (color, modes) in filtered_themes]
|
formatted_colors = [
|
||||||
print(f"The following themes will be deleted: {', '.join(colors)}.")
|
Console.format(color, color=Color.get(color), format_type=Format.BOLD)
|
||||||
|
for (color, modes) in filtered_themes
|
||||||
|
]
|
||||||
|
Console.Line().warn(f"The following themes will be deleted: {', '.join(formatted_colors)}.")
|
||||||
if args.mode:
|
if args.mode:
|
||||||
print(f"Theme modes to be deleted: {args.mode}.")
|
Console.Line().warn(f"Theme modes to be deleted: {args.mode}.")
|
||||||
|
|
||||||
if input(f"Proceed? (y/N) ").lower() == "y":
|
if proceed_input().lower() == "y":
|
||||||
filtered_themes.remove(args.mode)
|
filtered_themes.remove(args.mode)
|
||||||
print("Themes deleted successfully.")
|
Console.Line().success("Themes deleted successfully.")
|
||||||
else:
|
else:
|
||||||
print("Operation cancelled.")
|
Console.Line().error("Operation cancelled.")
|
||||||
|
|
||||||
|
|
||||||
def detect_themes(path: str) -> 'Themes':
|
def detect_themes(path: str) -> 'Themes':
|
||||||
@@ -59,41 +64,12 @@ def detect_themes(path: str) -> 'Themes':
|
|||||||
return themes
|
return themes
|
||||||
|
|
||||||
|
|
||||||
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:
|
class Themes:
|
||||||
"""Collection of themes grouped by color"""
|
"""Collection of themes grouped by color"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.by_color: dict[str, list[ThemeMode]] = defaultdict(list) # color: list[ThemeMode]
|
self.by_color: dict[str, list[ThemeMode]] = defaultdict(list) # color: list[ThemeMode]
|
||||||
|
|
||||||
def add_theme(self, color: str, theme_mode: ThemeMode):
|
def add_theme(self, color: str, theme_mode: 'ThemeMode'):
|
||||||
self.by_color[color].append(theme_mode)
|
self.by_color[color].append(theme_mode)
|
||||||
|
|
||||||
def filter(self, colors: list[str]):
|
def filter(self, colors: list[str]):
|
||||||
@@ -121,3 +97,26 @@ class Themes:
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for color, modes in self.by_color.items():
|
for color, modes in self.by_color.items():
|
||||||
yield color, modes
|
yield color, modes
|
||||||
|
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
|
||||||
|
def proceed_input():
|
||||||
|
formatted_agree = Console.format("y", color=Color.GREEN, format_type=Format.BOLD)
|
||||||
|
formatted_disagree = Console.format("N", color=Color.RED, format_type=Format.BOLD)
|
||||||
|
formatted_proceed = Console.format("Proceed?", format_type=Format.BOLD)
|
||||||
|
return input(f"{formatted_proceed} ({formatted_agree}/{formatted_disagree}) ")
|
Reference in New Issue
Block a user