diff --git a/css_browserhook.py b/css_browserhook.py index e58963e..6286447 100644 --- a/css_browserhook.py +++ b/css_browserhook.py @@ -3,18 +3,6 @@ from css_utils import get_theme_path, Log, Result import css_inject -CSS_TAB_MAPPINGS = { - "SP": ["SP|Steam Big Picture Mode", "~Valve Steam Gamepad/default~"], - "Steam Big Picture Mode": ["SP|Steam Big Picture Mode", "~Valve Steam Gamepad/default~"], - "MainMenu": ["MainMenu.*", "~valve.steam.gamepadui.mainmenu~"], - "MainMenu_.*": ["MainMenu.*", "~valve.steam.gamepadui.mainmenu~"], - "QuickAccess": ["QuickAccess.*", "~valve.steam.gamepadui.quickaccess~"], - "QuickAccess_.*": ["QuickAccess.*", "~valve.steam.gamepadui.quickaccess~"], - "Steam": ["SteamLibraryWindow|Steam"], - "SteamLibraryWindow": ["SteamLibraryWindow|Steam"], - "All": ["SP|Steam Big Picture Mode", "~Valve Steam Gamepad/default~", "MainMenu.*", "~valve.steam.gamepadui.mainmenu~", "QuickAccess.*", "~valve.steam.gamepadui.quickaccess~"] -} - MAX_QUEUE_SIZE = 500 class BrowserTabHook: @@ -27,14 +15,16 @@ def __init__(self, browserHook, sessionId : str, targetInfo : dict): self.hook = browserHook self.pending_add = {} self.pending_remove = [] - asyncio.create_task(self._init()) self.init_done = False + self.html_classes = [] + asyncio.create_task(self._init()) async def _init(self): - res = await self.evaluate_js("(function(){ return {\"title\": document.title} })()") + res = await self.evaluate_js("(function(){ return {\"title\": document.title, \"classes\": Array.from(document.documentElement.classList)} })()") if res != None: self.title = res["title"] + self.html_classes = res["classes"] self.init_done = True Log(f"Connected to tab: {self.title}") @@ -71,17 +61,14 @@ async def has_element(self, element_name): return res if res != None else False def compare(self, tab_name : str) -> bool: - checks = [tab_name] - - if (tab_name in CSS_TAB_MAPPINGS): - checks.extend(CSS_TAB_MAPPINGS[tab_name]) - - for tab_check in checks: - if tab_check.startswith("~") and tab_check.endswith("~") and len(tab_check) > 2: - if tab_check[1:-1] in self.url: - return True - elif re.match(tab_check + "$", self.title): + if tab_name.startswith("~") and tab_name.endswith("~") and len(tab_name) > 2: + if tab_name[1:-1] in self.url: + return True + elif tab_name.startswith("!"): + if tab_name[1:] in self.html_classes: return True + elif re.match(f"^({tab_name})$", self.title): + return True return False diff --git a/css_inject.py b/css_inject.py index da55fe8..03adb63 100644 --- a/css_inject.py +++ b/css_inject.py @@ -102,6 +102,25 @@ async def remove(self) -> Result: self.enabled = False return Result(True) +DEFAULT_MAPPINGS = { + "desktop": ["Steam.*", ".*Supernav"], + "desktopchat": ["!friendsui-container"], + "desktoppopup": ["OverlayBrowser_Browser", "SP Overlay:.*", ".*Menu", "notificationtoasts_.*", "SteamBrowser_Find", "OverlayTab\\d+_Find", "!ModalDialogPopup", "!FullModalOverlay"], + "desktopoverlay": ["desktoppopup"], + "bigpicture": ["~Valve Steam Gamepad/default~"], + "bigpictureoverlay": ["QuickAccess", "MainMenu"], + "store": ["~https://store.steampowered.com~", "~https://steamcommunity.com~"], + "SP": ["bigpicture"], + "Steam Big Picture Mode": ["bigpicture"], + "MainMenu": ["MainMenu.*"], + "MainMenu_.*": ["MainMenu"], + "QuickAccess": ["QuickAccess.*"], + "QuickAccess_.*": ["QuickAccess"], + "Steam": ["desktop"], + "SteamLibraryWindow": ["desktop"], + "All": ["bigpicture", "bigpictureoverlay"] +} + def extend_tabs(tabs : list, theme) -> list: new_tabs = [] @@ -110,7 +129,9 @@ def extend_tabs(tabs : list, theme) -> list: for x in tabs: if x in theme.tab_mappings: - new_tabs.extend(theme.tab_mappings[x]) + new_tabs.extend(extend_tabs(theme.tab_mappings[x], theme)) + elif x in DEFAULT_MAPPINGS: + new_tabs.extend(extend_tabs(DEFAULT_MAPPINGS[x], theme)) else: new_tabs.append(x) diff --git a/css_sfp_compat.py b/css_sfp_compat.py new file mode 100644 index 0000000..9e8319b --- /dev/null +++ b/css_sfp_compat.py @@ -0,0 +1,25 @@ +import os +from css_inject import Inject, to_inject + +SFP_DEFAULT_FILES = { + "libraryroot.custom.css": ["desktop", "desktopoverlay"], + "bigpicture.custom.css": ["bigpicture", "bigpictureoverlay"], + "friends.custom.css": ["desktopchat"], + "webkit.css": ["store"] +} + +def is_folder_sfp_theme(dir : str) -> bool: + for x in SFP_DEFAULT_FILES: + if os.path.exists(os.path.join(dir, x)): + return True + + return False + +def convert_to_css_theme(dir : str, theme) -> None: + theme.name = os.path.basename(dir) + theme.id = theme.name + theme.version = "v1.0" + theme.author = "" + theme.require = 1 + theme.dependencies = [] + theme.injects = [to_inject(x, SFP_DEFAULT_FILES[x], dir, theme) for x in SFP_DEFAULT_FILES if os.path.exists(os.path.join(dir, x))] \ No newline at end of file diff --git a/css_theme.py b/css_theme.py index 4cca253..47b1d7c 100644 --- a/css_theme.py +++ b/css_theme.py @@ -1,11 +1,12 @@ -import os, json, shutil +import os, json, shutil, time from os import path from typing import List from css_inject import Inject, to_injects from css_utils import Result, Log, create_dir, USER from css_themepatch import ThemePatch +from css_sfp_compat import is_folder_sfp_theme, convert_to_css_theme -CSS_LOADER_VER = 7 +CSS_LOADER_VER = 8 class Theme: def __init__(self, themePath : str, json : dict, configPath : str = None): @@ -20,6 +21,8 @@ def __init__(self, themePath : str, json : dict, configPath : str = None): self.enabled = False self.json = json self.priority_mod = 0 + self.created = None + self.modified = path.getmtime(self.configJsonPath) if path.exists(self.configJsonPath) else None try: if (os.path.join(themePath, "PRIORITY")): @@ -29,17 +32,26 @@ def __init__(self, themePath : str, json : dict, configPath : str = None): pass if (json is None): - if not os.path.exists(os.path.join(themePath, "theme.css")): + if os.path.exists(os.path.join(themePath, "theme.css")): + self.name = os.path.basename(themePath) + self.id = self.name + self.version = "v1.0" + self.author = "" + self.require = 1 + self.injects = [Inject(os.path.join(themePath, "theme.css"), [".*"], self)] + self.dependencies = [] + return + elif is_folder_sfp_theme(themePath): + convert_to_css_theme(themePath, self) + return + else: raise Exception("Folder does not look like a theme?") - - self.name = os.path.basename(themePath) - self.id = self.name - self.version = "v1.0" - self.author = "" - self.require = 1 - self.injects = [Inject(os.path.join(themePath, "theme.css"), ["SP", "QuickAccess", "MainMenu"], self)] - self.dependencies = [] - return + + + jsonPath = path.join(self.themePath, "theme.json") + + if path.exists(jsonPath): + self.created = path.getmtime(jsonPath) self.name = json["name"] self.id = json["id"] if ("id" in json) else self.name @@ -98,6 +110,7 @@ async def save(self) -> Result: with open(self.configJsonPath, "w") as fp: json.dump(config, fp) + self.modified = time.time() except Exception as e: return Result(False, str(e)) @@ -165,5 +178,7 @@ def to_dict(self) -> dict: "bundled": self.bundled, "require": self.require, "dependencies": [x for x in self.dependencies], - "flags": self.flags + "flags": self.flags, + "created": self.created, + "modified": self.modified, } \ No newline at end of file diff --git a/css_themepatchcomponent.py b/css_themepatchcomponent.py index 63e6c1a..b7517bb 100644 --- a/css_themepatchcomponent.py +++ b/css_themepatchcomponent.py @@ -1,4 +1,4 @@ -from css_inject import Inject +from css_inject import Inject, to_inject from css_utils import Result, get_theme_path from os.path import join, exists @@ -76,7 +76,7 @@ def __init__(self, themePatch, component : dict): self.css_variable = f"--{self.css_variable}" self.tabs = component["tabs"] - self.inject = Inject("", self.tabs, self.themePatch.theme) + self.inject = to_inject("", self.tabs, "", self.themePatch.theme) self.generate() def check_value_color_picker(self, value : str): diff --git a/main.py b/main.py index 36a0115..8597e00 100644 --- a/main.py +++ b/main.py @@ -31,12 +31,12 @@ def __init__(self, plugin, loop): self.plugin = plugin self.loop = loop self.last = 0 - self.delay = 5 + self.delay = 1 def on_modified(self, event): #Log(f"FS Event: {event}") - if (not event.src_path.endswith(".css")) or event.is_directory: + if (not (event.src_path.endswith(".css") or event.src_path.endswith("theme.json"))) or event.is_directory: #Log("FS Event is not on a CSS file. Ignoring!") return @@ -49,6 +49,36 @@ def on_modified(self, event): class Plugin: async def is_standalone(self) -> bool: return IS_STANDALONE + + async def get_watch_state(self) -> bool: + return self.observer != None + + async def get_server_state(self) -> bool: + return self.server_loaded + + async def enable_server(self) -> dict: + if self.server_loaded: + return Result(False, "Nothing to do!").to_dict() + + start_server(self) + self.server_loaded = True + return Result(True).to_dict() + + async def toggle_watch_state(self, enable : bool = True) -> dict: + if enable and self.observer == None: + Log("Observing themes folder for file changes") + self.observer = Observer() + self.handler = FileChangeHandler(self, asyncio.get_running_loop()) + self.observer.schedule(self.handler, get_theme_path(), recursive=True) + self.observer.start() + return Result(True).to_dict() + elif self.observer != None and not enable: + Log("Stopping observer") + self.observer.stop() + self.observer = None + return Result(True).to_dict() + + return Result(False, "Nothing to do!").to_dict() async def dummy_function(self) -> bool: return True @@ -294,9 +324,9 @@ async def _parse_themes(self, themesDir : str, configDir : str = None): configPath = configDir + "/" + x themeDataPath = themePath + "/theme.json" - if (not path.exists(themeDataPath)) and (not path.exists(os.path.join(themePath, "theme.css"))): + if not os.path.isdir(themePath): continue - + Log(f"Analyzing theme {x}") try: @@ -365,6 +395,8 @@ async def _main(self): return Initialized = True + self.observer = None + self.server_loaded = False await asyncio.sleep(1) @@ -380,18 +412,14 @@ async def _main(self): await self._load_stage_2(self, False) if (store_or_file_config("watch")): - Log("Observing themes folder for file changes") - self.observer = Observer() - self.handler = FileChangeHandler(self, asyncio.get_running_loop()) - self.observer.schedule(self.handler, get_theme_path(), recursive=True) - self.observer.start() + await self.toggle_watch_state(self) else: Log("Not observing themes folder for file changes") Log(f"Initialized css loader. Found {len(self.themes)} themes. Total {len(ALL_INJECTS)} injects, {len([x for x in ALL_INJECTS if x.enabled])} injected") if (ALWAYS_RUN_SERVER or store_or_file_config("server")): - start_server(self) + await self.enable_server(self) await initialize() diff --git a/package.json b/package.json index c3ccf7f..af96ead 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "SDH-CssLoader", - "version": "1.7.0", + "version": "1.7.1", "description": "A css loader", "scripts": { "build": "shx rm -rf dist && rollup -c",