diff --git a/safeeyes/__main__.py b/safeeyes/__main__.py index 929e1ede..e4e0d9f1 100755 --- a/safeeyes/__main__.py +++ b/safeeyes/__main__.py @@ -130,7 +130,7 @@ def main(): utility.initialize_logging(args.debug) utility.initialize_platform() config = Config() - utility.create_user_stylesheet_if_missing() + utility.cleanup_old_user_stylesheet() if __running(): logging.info("Safe Eyes is already running") diff --git a/safeeyes/config/locale/safeeyes.pot b/safeeyes/config/locale/safeeyes.pot index d9fe78c7..0747768a 100644 --- a/safeeyes/config/locale/safeeyes.pot +++ b/safeeyes/config/locale/safeeyes.pot @@ -557,3 +557,7 @@ msgstr "" msgid "License:" msgstr "" + +#, python-format +msgid "Old stylesheet found at '%(old)s', ignoring. For custom styles, create a new stylesheet in '%(new)s' instead." +msgstr "" diff --git a/safeeyes/model.py b/safeeyes/model.py index 2fc5f0e3..d397ffb1 100644 --- a/safeeyes/model.py +++ b/safeeyes/model.py @@ -327,8 +327,6 @@ def __init__(self, init=True): self.__user_config, self.__system_config ) self.__user_config = self.__system_config - # Update the style sheet - utility.replace_style_sheet() utility.merge_plugins(self.__user_config) self.save() diff --git a/safeeyes/safeeyes.py b/safeeyes/safeeyes.py index a43fcb3e..d7f743ef 100644 --- a/safeeyes/safeeyes.py +++ b/safeeyes/safeeyes.py @@ -104,12 +104,11 @@ def do_startup(self): else: self.context["session"] = {"plugin": {}} + # Initialize the theme + self._initialize_styles() + self.break_screen = BreakScreen( - self, - self.context, - self.on_skipped, - self.on_postponed, - utility.STYLE_SHEET_PATH, + self, self.context, self.on_skipped, self.on_postponed ) self.break_screen.initialize(self.config) self.plugins_manager = PluginManager() @@ -166,6 +165,16 @@ def do_activate(self): elif self.cli_args.take_break: self.take_break() + def _initialize_styles(self): + utility.load_css_file( + utility.SYSTEM_STYLE_SHEET_PATH, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + ) + utility.load_css_file( + utility.CUSTOM_STYLE_SHEET_PATH, + Gtk.STYLE_PROVIDER_PRIORITY_USER, + required=False, + ) + def _retry_errored_plugins(self): if not self.plugins_manager.needs_retry(): return diff --git a/safeeyes/ui/break_screen.py b/safeeyes/ui/break_screen.py index c385d7f0..3ff43469 100644 --- a/safeeyes/ui/break_screen.py +++ b/safeeyes/ui/break_screen.py @@ -43,9 +43,7 @@ class BreakScreen: interface. """ - def __init__( - self, application, context, on_skipped, on_postponed, style_sheet_path - ): + def __init__(self, application, context, on_skipped, on_postponed): self.application = application self.context = context self.count_labels = [] @@ -64,15 +62,6 @@ def __init__( if not self.context["is_wayland"]: self.x11_display = Display() - # Initialize the theme - css_provider = Gtk.CssProvider() - css_provider.load_from_path(style_sheet_path) - - display = Gdk.Display.get_default() - Gtk.StyleContext.add_provider_for_display( - display, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION - ) - def initialize(self, config): """Initialize the internal properties from configuration.""" logging.info("Initialize the break screen") diff --git a/safeeyes/utility.py b/safeeyes/utility.py index 7bc8532d..b6f0fe0b 100644 --- a/safeeyes/utility.py +++ b/safeeyes/utility.py @@ -19,6 +19,7 @@ """This module contains utility functions for Safe Eyes and its plugins.""" import errno +import hashlib import inspect import importlib import json @@ -38,13 +39,14 @@ import gi gi.require_version("Gtk", "4.0") +gi.require_version("Gdk", "4.0") + +from gi.repository import Gdk from gi.repository import Gtk from gi.repository import GLib from gi.repository import GdkPixbuf from packaging.version import parse -gi.require_version("Gdk", "4.0") - BIN_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) HOME_DIRECTORY = os.environ.get("HOME") or os.path.expanduser("~") CONFIG_DIRECTORY = os.path.join( @@ -55,7 +57,10 @@ CONFIG_FILE_PATH = os.path.join(CONFIG_DIRECTORY, "safeeyes.json") CONFIG_RESOURCE = os.path.join(CONFIG_DIRECTORY, "resource") SESSION_FILE_PATH = os.path.join(CONFIG_DIRECTORY, "session.json") -STYLE_SHEET_PATH = os.path.join(STYLE_SHEET_DIRECTORY, "safeeyes_style.css") +OLD_STYLE_SHEET_PATH = os.path.join(STYLE_SHEET_DIRECTORY, "safeeyes_style.css") +CUSTOM_STYLE_SHEET_PATH = os.path.join( + STYLE_SHEET_DIRECTORY, "safeeyes_custom_style.css" +) SYSTEM_CONFIG_FILE_PATH = os.path.join(BIN_DIRECTORY, "config/safeeyes.json") SYSTEM_STYLE_SHEET_PATH = os.path.join(BIN_DIRECTORY, "config/style/safeeyes_style.css") LOG_FILE_PATH = os.path.join(HOME_DIRECTORY, "safeeyes.log") @@ -372,8 +377,32 @@ def merge_configs(new_config, old_config): return new_config +def sha256sum(filename): + """Get the sha256 hash of the given file.""" + h = hashlib.sha256() + b = bytearray(128 * 1024) + mv = memoryview(b) + with open(filename, "rb", buffering=0) as f: + for n in iter(lambda: f.readinto(mv), 0): + h.update(mv[:n]) + return h.hexdigest() + + +def load_css_file(style_sheet_path, priority, required=True): + if not os.path.isfile(style_sheet_path): + if required: + logging.warning("Failed loading required stylesheet") + return + + css_provider = Gtk.CssProvider() + css_provider.load_from_path(style_sheet_path) + + display = Gdk.Display.get_default() + Gtk.StyleContext.add_provider_for_display(display, css_provider, priority) + + def initialize_safeeyes(): - """Create the config file and style sheet in XDG_CONFIG_HOME(or + """Create the config file in XDG_CONFIG_HOME(or ~/.config)/safeeyes directory. """ logging.info("Copy the config files to XDG_CONFIG_HOME(or ~/.config)/safeeyes") @@ -388,8 +417,6 @@ def initialize_safeeyes(): shutil.copy2(SYSTEM_CONFIG_FILE_PATH, CONFIG_FILE_PATH) os.chmod(CONFIG_FILE_PATH, 0o666) - create_user_stylesheet_if_missing() - # initialize_safeeyes gets called when the configuration file is not present, which # happens just after installation or manual deletion of # .config/safeeyes/safeeyes.json file. In these cases, we want to force the creation @@ -397,15 +424,38 @@ def initialize_safeeyes(): create_startup_entry(force=True) -def create_user_stylesheet_if_missing(): +def cleanup_old_user_stylesheet(): # Create the XDG_CONFIG_HOME(or ~/.config)/safeeyes/style directory if not os.path.isdir(STYLE_SHEET_DIRECTORY): mkdir(STYLE_SHEET_DIRECTORY) - # Copy the new style sheet - if not os.path.isfile(STYLE_SHEET_PATH): - shutil.copy2(SYSTEM_STYLE_SHEET_PATH, STYLE_SHEET_PATH) - os.chmod(STYLE_SHEET_PATH, 0o666) + # Delete the old stylesheet, unless it has customizations + if os.path.isfile(OLD_STYLE_SHEET_PATH): + hash = sha256sum(OLD_STYLE_SHEET_PATH) + old_default_versions = [ + # 2.2.3 + "fdc2a305613ae4eeb269650452789d35df3df5bdf1c56eb576cd5ebac70a6f09", + # 2.1.0 - 2.2.2 + "fbde048fc234db757461971a7542df43a467869035ca3d05ff9b236ca250e4c5", + # 2.0.9 + "70ca55c12d83ad7a6a4e1c5e7758a38617a43f5d32f28709ede75426d3186713", + # 2.0.7 - 2.0.8 + "7a15f985e0da6d92c8a62d49ce151781e6d423f87e66d205cc1dc4536e369e19", + # 2.0.6 and earlier + "f26621a883e323ca7685a4adba25027e70daa471e0db4a21c261e6c15caaa5ee", + ] + if hash in old_default_versions: + logging.info("Deleting old stylesheet containing default content") + delete(OLD_STYLE_SHEET_PATH) + else: + # Stylesheet was likely customized, don't delete but warn + logging.warning( + _( + "Old stylesheet found at '%(old)s', ignoring. For custom styles, " + "create a new stylesheet in '%(new)s' instead.", + ) + % {"old": OLD_STYLE_SHEET_PATH, "new": CUSTOM_STYLE_SHEET_PATH} + ) def create_startup_entry(force=False): @@ -517,26 +567,16 @@ def initialize_platform(): def reset_config(): # Remove the ~/.config/safeeyes/safeeyes.json and safeeyes_style.css delete(CONFIG_FILE_PATH) - delete(STYLE_SHEET_PATH) # Copy the safeeyes.json and safeeyes_style.css shutil.copy2(SYSTEM_CONFIG_FILE_PATH, CONFIG_FILE_PATH) - shutil.copy2(SYSTEM_STYLE_SHEET_PATH, STYLE_SHEET_PATH) # Add write permission (e.g. if original file was stored in /nix/store) os.chmod(CONFIG_FILE_PATH, 0o666) - os.chmod(STYLE_SHEET_PATH, 0o666) create_startup_entry() -def replace_style_sheet(): - """Replace the user style sheet by system style sheet.""" - delete(STYLE_SHEET_PATH) - shutil.copy2(SYSTEM_STYLE_SHEET_PATH, STYLE_SHEET_PATH) - os.chmod(STYLE_SHEET_PATH, 0o666) - - def initialize_logging(debug): """Initialize the logging framework using the Safe Eyes specific configurations.