Skip to content

Commit

Permalink
Merge pull request #79 from suchmememanyskill/dev
Browse files Browse the repository at this point in the history
v1.6.0
  • Loading branch information
suchmememanyskill authored May 5, 2023
2 parents 000f995 + e56c177 commit 9eed174
Show file tree
Hide file tree
Showing 24 changed files with 603 additions and 280 deletions.
43 changes: 41 additions & 2 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on: ["push", "pull_request", "workflow_dispatch"]

jobs:
build:
name: Build SDH-CSSLoader
name: Build SDH-CSSLoader for Decky
runs-on: ubuntu-20.04

steps:
Expand Down Expand Up @@ -35,12 +35,51 @@ jobs:
- name: Upload package artifact
uses: actions/upload-artifact@v3
with:
name: SDH-CSSLoader
name: SDH-CSSLoader-Decky
path: ./build.zip

- name: Send Zip in discord
uses: tsickert/[email protected]
if: ${{ github.event_name == 'workflow_dispatch' }}
with:
webhook-url: ${{ secrets.WEBHOOK_URL }}
content: ${{ github.event.head_commit.message }}
filename: "./build.zip"

build-standalone-win:
name: Build SDH-CSSLoader Standalone for Windows
runs-on: windows-2022

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Set up Python 3.10.2
uses: actions/setup-python@v4
with:
python-version: "3.10.2"

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install pyinstaller==5.5
pip install -r requirements.txt
- name: Get inject.py dependency from decky-loader
run: |
git clone --depth=1 https://github.com/SteamDeckHomebrew/decky-loader
copy ./decky-loader/backend/injector.py injector.py
- name: Build Python Backend
run: pyinstaller --noconfirm --onefile --name "CssLoader-Standalone" ./main.py ./injector.py

- name: Build Python Backend Headless
run: pyinstaller --noconfirm --noconsole --onefile --name "CssLoader-Standalone-Headless" ./main.py ./injector.py

- name: Upload package artifact
uses: actions/upload-artifact@v3
with:
name: SDH-CSSLoader-Win-Standalone
path: |
./dist/CssLoader-Standalone.exe
./dist/CssLoader-Standalone-Headless.exe
7 changes: 4 additions & 3 deletions css_inject.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,15 @@ async def remove(self, tab : Tab = None) -> Result:
return Result(True)

def to_inject(key : str, tabs : list, basePath : str, theme) -> Inject:
inject = Inject(basePath + "/" + key, tabs, theme)
if key.startswith("--"):
value = tabs[0]
tabs = tabs[1:]
if (";" in value or ";" in key):
raise Exception("Multiple css statements are unsupported in a variable")
inject = Inject("", tabs, theme)

inject = Inject("", tabs[1:], theme)
inject.css = f":root {{ {key}: {value}; }}"
else:
inject = Inject(basePath + "/" + key, tabs, theme)

return inject

Expand Down
11 changes: 6 additions & 5 deletions css_remoteinstall.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import asyncio, json, tempfile, os, aiohttp, zipfile
from css_utils import Result, Log, get_theme_path
from css_utils import Result, Log, get_theme_path, store_or_file_config
from css_theme import CSS_LOADER_VER

async def run(command : str) -> str:
Expand Down Expand Up @@ -57,10 +57,11 @@ async def install(id : str, base_url : str, local_themes : list) -> Result:

tempDir.cleanup()

for x in data["dependencies"]:
if x["name"] in local_themes:
continue
if not store_or_file_config("no_deps_install"):
for x in data["dependencies"]:
if x["name"] in local_themes:
continue

await install(x["id"], base_url, local_themes)
await install(x["id"], base_url, local_themes)

return Result(True)
28 changes: 28 additions & 0 deletions css_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import asyncio, aiohttp.web, json
from css_utils import Log, create_cef_flag

PLUGIN_CLASS = None

async def handle(request : aiohttp.web.BaseRequest):
data = await request.json()
request_result = {"res": None, "success": True}

# This is very cool decky code
try:
request_result["res"] = await getattr(PLUGIN_CLASS, data["method"])(PLUGIN_CLASS, **data["args"])
except Exception as e:
request_result["res"] = str(e)
request_result["success"] = False
finally:
return aiohttp.web.Response(text=json.dumps(request_result, ensure_ascii=False), content_type='application/json')

def start_server(plugin):
global PLUGIN_CLASS

PLUGIN_CLASS = plugin
loop = asyncio.get_running_loop()
create_cef_flag()
app = aiohttp.web.Application(loop=loop)
app.router.add_route('POST', '/req', handle)
loop.create_task(aiohttp.web._run_app(app, host="127.0.0.1", port=35821))
Log("Started CSS_Loader server on port 35821")
79 changes: 71 additions & 8 deletions css_tab_mapping.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import os, re, uuid, asyncio, json
from css_utils import get_theme_path, Log, Result
import injector
from utilities import Utilities

pluginManagerUtils = Utilities(None)

def _match_tab(tab, name_mappings: list = [], url_parts : list = []) -> bool:
for x in url_parts:
Expand All @@ -25,6 +22,7 @@ def __init__(self, name_mappings: list = [], url_parts : list = [], extra_keywor
self.keywords = extra_keywords
self.pending_add = {}
self.pending_remove = []
self.primary_instance = None

async def commit_css_transaction(self, retry : int = 3) -> Result:
pending_add = self.pending_add
Expand Down Expand Up @@ -54,6 +52,7 @@ async def commit_css_transaction(self, retry : int = 3) -> Result:
let style = document.createElement('style');
style.id = x.id;
style.classList.add('css-loader-style');
document.head.append(style);
style.textContent = x.css;
}});
Expand All @@ -75,6 +74,27 @@ async def commit_css_transaction(self, retry : int = 3) -> Result:
await asyncio.sleep(0.2)

return Result(False, "Css Commit Retry Count Exceeded")

async def remove_all_css(self, retry : int = 3) -> Result:
js = """
(function() {
document.querySelectorAll('.css-loader-style').forEach(x => x.remove());
})()
"""

self.pending_add = {}
self.pending_remove = []

while (retry > 0):
retry -= 1
res = await self.evaluate_js(js)
if res.success:
return res
else:
Log("Transaction failed! retrying in 0.2 seconds")
await asyncio.sleep(0.2)

return Result(False, "Css Commit Retry Count Exceeded")

def compare(self, name : str) -> bool:
if name in self.tab_names_regex:
Expand All @@ -85,6 +105,9 @@ def compare(self, name : str) -> bool:

if name == self.get_name():
return True

if name in self.tab_url_parts or (name.startswith("~") and name.endswith("~") and name[1:-1] in self.tab_url_parts):
return True

return False

Expand Down Expand Up @@ -123,8 +146,11 @@ async def open(self) -> Result:

return Result(True)

def is_connected(self) -> bool:
return self.tab != None and self.tab.websocket != None and not self.tab.websocket.closed

async def manage_webhook(self) -> Result:
if self.tab == None or self.tab.websocket == None or self.tab.websocket.closed:
if not self.is_connected():
return await self.open()

return Result(True)
Expand Down Expand Up @@ -234,7 +260,12 @@ def get_tab(tab_name : str) -> list:
tabs.append(x)

if len(tabs) <= 0:
tab = Tab([tab_name])

if (tab_name.startswith("~") and tab_name.endswith("~") and len(tab_name) > 2):
tab = Tab(url_parts=[tab_name[1:-1]])
else:
tab = Tab([tab_name])

tabs.append(tab)
CSS_LOADER_TAB_CACHE.append(tab)

Expand All @@ -243,7 +274,7 @@ def get_tab(tab_name : str) -> list:
def get_single_tab(tab_name : str) -> Tab | None:
tabs = get_tab(tab_name)

if (len(tabs) != 1):
if len(tabs) != 1:
return None

return tabs[0]
Expand All @@ -260,6 +291,38 @@ def get_tabs(tab_names : list):
def get_cached_tabs():
return CSS_LOADER_TAB_CACHE

def optimize_tabs() -> bool:
global CSS_LOADER_TAB_CACHE
changed = False
items = [x for x in get_cached_tabs() if x.is_connected() and x.get_name() != None]

for x in items:
for y in items:
if x == y or x.primary_instance != None or y.primary_instance != None:
continue

if x.get_name() == y.get_name():
x.primary_instance = y

for tab_name_regex in x.tab_names_regex:
if tab_name_regex not in y.tab_names_regex:
y.tab_names_regex.append(tab_name_regex)

for tab_url_part in x.tab_url_parts:
if tab_url_part not in y.tab_url_parts:
y.tab_url_parts.append(tab_url_part)

for keyword in x.keywords:
if keyword not in y.keywords:
y.keywords.append(keyword)

CSS_LOADER_TAB_CACHE.remove(x)
changed = True

return changed

async def commit_all():
for x in get_cached_tabs():
await x.commit_css_transaction()
await asyncio.gather(*[x.commit_css_transaction() for x in get_cached_tabs() if x.is_connected()])

async def remove_all():
await asyncio.gather(*[x.remove_all_css() for x in get_cached_tabs() if x.is_connected()])
49 changes: 38 additions & 11 deletions css_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
from logging import getLogger
import os, platform

HOME = os.environ["HOME"]
USER = os.environ["USER"]
DECKY_USER = os.environ["DECKY_USER"]
DECKY_HOME = os.environ["DECKY_HOME"]
HOME = os.getenv("HOME")

if not HOME:
HOME = os.path.expanduser("~")

USER = os.getenv("USER", "user") # USER is just used for config name

DECKY_HOME = os.getenv("DECKY_HOME", os.path.join(HOME, "homebrew"))
DECKY_USER = os.getenv("DECKY_USER")

if not DECKY_USER:
DECKY_USER = os.getlogin()

if not os.path.exists(DECKY_HOME):
os.mkdir(DECKY_HOME)

PLATFORM_WIN = platform.system() == "Windows"

if not PLATFORM_WIN:
Expand All @@ -15,7 +27,6 @@
FLAG_KEEP_DEPENDENCIES = "KEEP_DEPENDENCIES"
FLAG_PRESET = "PRESET"


def Log(text : str):
Logger.info(f"[CSS_Loader] {text}")

Expand Down Expand Up @@ -54,7 +65,7 @@ def get_user_home() -> str:
def get_theme_path() -> str:
return os.path.join(DECKY_HOME, "themes")

async def create_symlink(src : str, dst : str) -> Result:
def create_symlink(src : str, dst : str) -> Result:
try:
if not os.path.exists(dst):
os.symlink(src, dst, True)
Expand All @@ -63,7 +74,7 @@ async def create_symlink(src : str, dst : str) -> Result:

return Result(True)

async def create_steam_symlink() -> Result:
def get_steam_path() -> str:
if PLATFORM_WIN:
try:
import winreg
Expand All @@ -74,11 +85,20 @@ async def create_steam_symlink() -> Result:
raise Exception(f"Expected type {winreg.REG_SZ}, got {type}")

Log(f"Got win steam install path: '{val}'")
return await create_symlink(get_theme_path(), os.path.join(val, "steamui", "themes_custom"))
return val
except Exception as e:
return Result(False, str(e))
return "C:\\Program Files (x86)\\Steam" # Taking a guess here
else:
return await create_symlink(get_theme_path(), f"{get_user_home()}/.local/share/Steam/steamui/themes_custom")
return f"{get_user_home()}/.local/share/Steam"

def create_steam_symlink() -> Result:
return create_symlink(get_theme_path(), os.path.join(get_steam_path(), "steamui", "themes_custom"))

def create_cef_flag() -> Result:
path = os.path.join(get_steam_path(), ".cef-enable-remote-debugging")
if not os.path.exists(path):
with open(path, 'w') as fp:
pass

def store_path() -> str:
return os.path.join(get_theme_path(), "STORE")
Expand Down Expand Up @@ -114,4 +134,11 @@ def store_write(key : str, val : str):
items = store_reads()
items[key] = val.replace('\n', '')
with open(path, 'w') as fp:
fp.write("\n".join([f"{x}:{items[x]}" for x in items]))
fp.write("\n".join([f"{x}:{items[x]}" for x in items]))

def store_or_file_config(key : str) -> bool:
if os.path.exists(os.path.join(get_theme_path(), key.upper())):
return True

read = store_read(key)
return read == "True" or read == "1"
Loading

0 comments on commit 9eed174

Please sign in to comment.