Skip to content

Commit

Permalink
Merge pull request #115 from DeckThemes/dev
Browse files Browse the repository at this point in the history
v2.1.1
  • Loading branch information
suchmememanyskill authored Apr 12, 2024
2 parents 2e5920b + d64e798 commit 05068b6
Show file tree
Hide file tree
Showing 14 changed files with 259 additions and 37 deletions.
4 changes: 2 additions & 2 deletions css_browserhook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os, re, uuid, asyncio, json, aiohttp, time
from typing import List
from css_utils import get_theme_path, Log, Result
from css_utils import get_theme_path, Log, Result, PLATFORM_WIN
import css_inject

MAX_QUEUE_SIZE = 500
Expand Down Expand Up @@ -414,7 +414,7 @@ async def health_check(self):
while True:
await asyncio.sleep(3)
try:
async with aiohttp.ClientSession() as web:
async with aiohttp.ClientSession(trust_env=PLATFORM_WIN) as web:
res = await web.get(f"http://127.0.0.1:8080/json/version", timeout=3)

if (res.status != 200):
Expand Down
10 changes: 9 additions & 1 deletion css_inject.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ async def load(self) -> Result:
if split_css[x].startswith(".") and split_css[x][1:] in CLASS_MAPPINGS:
split_css[x] = "." + CLASS_MAPPINGS[split_css[x][1:]]

self.css = ("".join(split_css)).replace("\\", "\\\\").replace("`", "\\`")

split_css = re.split(r"(\[class[*^|~]=\"[_a-zA-Z0-9-]*\"\])", self.css)

for x in range(len(split_css)):
if split_css[x].startswith("[class") and split_css[x].endswith("\"]") and split_css[x][9:-2] in CLASS_MAPPINGS:
split_css[x] = split_css[x][0:9] + CLASS_MAPPINGS[split_css[x][9:-2]] + split_css[x][-2:]

self.css = ("".join(split_css)).replace("\\", "\\\\").replace("`", "\\`")
Log(f"Loaded css at {self.cssPath}")

Expand Down Expand Up @@ -142,7 +150,7 @@ async def remove(self) -> Result:
"desktoppopup": ["OverlayBrowser_Browser", "SP Overlay:.*", "notificationtoasts_.*", "SteamBrowser_Find", "OverlayTab\\d+_Find", "!ModalDialogPopup", "!FullModalOverlay"],
"desktopoverlay": ["desktoppopup"],
"desktopcontextmenu": [".*Menu", ".*Supernav"],
"bigpicture": ["~Valve Steam Gamepad/default~", "~Valve%20Steam%20Gamepad/default~"],
"bigpicture": ["~Valve Steam Gamepad/default~", "~Valve%20Steam%20Gamepad~"],
"bigpictureoverlay": ["QuickAccess", "MainMenu"],
"store": ["~https://store.steampowered.com~", "~https://steamcommunity.com~"],

Expand Down
14 changes: 14 additions & 0 deletions css_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ def get_steam_path() -> str:
else:
return f"{get_user_home()}/.steam/steam"

def is_steam_beta_active() -> bool:
beta_path = os.path.join(get_steam_path(), "package", "beta")
if not os.path.exists(beta_path):
return False

with open(beta_path, 'r') as fp:
content = fp.read().strip()

stable_branches = [
"steamdeck_stable",
]

return content not in stable_branches

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

Expand Down
27 changes: 23 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

sys.path.append(os.path.dirname(__file__))

from css_utils import Log, create_steam_symlink, Result, get_theme_path, store_read as util_store_read, store_write as util_store_write, store_or_file_config
from css_utils import Log, create_steam_symlink, Result, get_theme_path, store_read as util_store_read, store_write as util_store_write, store_or_file_config, is_steam_beta_active
from css_inject import ALL_INJECTS, initialize_class_mappings
from css_theme import CSS_LOADER_VER
from css_remoteinstall import install
Expand Down Expand Up @@ -34,7 +34,13 @@ async def fetch_class_mappings(css_translations_path : str, loader : Loader):
return

setting = util_store_read("beta_translations")
css_translations_url = "https://api.deckthemes.com/beta.json" if (setting == "1" or setting == "true") else "https://api.deckthemes.com/stable.json"

if ((len(setting.strip()) <= 0 or setting == "-1" or setting == "auto") and is_steam_beta_active()) or (setting == "1" or setting == "true"):
css_translations_url = "https://api.deckthemes.com/beta.json"
else:
css_translations_url = "https://api.deckthemes.com/stable.json"

Log(f"Fetching CSS mappings from {css_translations_url}")

try:
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False), timeout=aiohttp.ClientTimeout(total=2)) as session:
Expand Down Expand Up @@ -180,6 +186,20 @@ async def get_last_load_errors(self):
async def upload_theme(self, name : str, base_url : str, bearer_token : str) -> dict:
return (await self.loader.upload_theme(name, base_url, bearer_token)).to_dict()

async def fetch_class_mappings(self):
await self._fetch_class_mappings(self)
return Result(True).to_dict()

async def _fetch_class_mappings(self, run_in_bg : bool = False):
global SUCCESSFUL_FETCH_THIS_RUN

SUCCESSFUL_FETCH_THIS_RUN = False
css_translations_path = os.path.join(get_theme_path(), "css_translations.json")
if run_in_bg:
asyncio.get_event_loop().create_task(every(60, fetch_class_mappings, css_translations_path, self.loader))
else:
await fetch_class_mappings(css_translations_path, self.loader)

async def _main(self):
global Initialized
if Initialized:
Expand Down Expand Up @@ -208,8 +228,7 @@ async def _main(self):
if (ALWAYS_RUN_SERVER or store_or_file_config("server")):
await self.enable_server(self)

css_translations_path = os.path.join(get_theme_path(), "css_translations.json")
asyncio.get_event_loop().create_task(every(60, fetch_class_mappings, css_translations_path, self.loader))
await self._fetch_class_mappings(self, True)
await initialize()

if __name__ == '__main__':
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "SDH-CssLoader",
"version": "2.1.0",
"version": "2.1.1",
"description": "A css loader",
"scripts": {
"build": "shx rm -rf dist && rollup -c",
Expand Down Expand Up @@ -43,7 +43,7 @@
},
"dependencies": {
"color": "^4.2.3",
"decky-frontend-lib": "^3.24.3",
"decky-frontend-lib": "^3.25.0",
"lodash": "^4.17.21",
"react-icons": "^4.12.0"
},
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions src/backend/pythonMethods/pluginSettingsMethods.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { toast } from "../../python";
import { storeRead, toast } from "../../python";
import { server, globalState } from "../pythonRoot";
import { booleanStoreRead } from "./storeUtils";
import { booleanStoreRead, stringStoreRead } from "./storeUtils";

export function enableServer() {
return server!.callPluginMethod("enable_server", {});
Expand All @@ -25,7 +25,7 @@ export async function getWatchState() {
}

export async function getBetaTranslationsState() {
return booleanStoreRead("beta_translations");
return stringStoreRead("beta_translations");
}

export function toggleWatchState(bool: boolean, onlyThisSession: boolean = false) {
Expand All @@ -50,3 +50,7 @@ export function getHiddenMotd() {
key: "hiddenMotd",
});
}

export function fetchClassMappings() {
return server!.callPluginMethod<{}>("fetch_class_mappings", {});
}
20 changes: 20 additions & 0 deletions src/backend/pythonMethods/storeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,27 @@ export async function booleanStoreWrite(key: string, value: boolean) {
key,
val: value ? "1" : "0",
});
if (!deckyRes.success) {
toast(`Error setting ${key}`, deckyRes.result);
}
}

export async function stringStoreRead(key: string) {
const deckyRes = await server!.callPluginMethod<{ key: string }, string>("store_read", {
key,
});
if (!deckyRes.success) {
toast(`Error fetching ${key}`, deckyRes.result);
return "";
}
return deckyRes.result;
}
export async function stringStoreWrite(key: string, value: string) {
const deckyRes = await server!.callPluginMethod<{ key: string; val: string }>("store_write", {
key,
val: value,
});
if (!deckyRes.success) {
toast(`Error setting ${key}`, deckyRes.result);
}
}
47 changes: 47 additions & 0 deletions src/deckyPatches/ClassHashMap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { classMap } from "decky-frontend-lib";

export var classHashMap = new Map<string, string>();

export function initializeClassHashMap() {
const withoutLocalizationClasses = classMap.filter((module) => Object.keys(module).length < 1000);

const allClasses = withoutLocalizationClasses
.map((module) => {
let filteredModule = {};
Object.entries(module).forEach(([propertyName, value]) => {
// Filter out things that start with a number (eg: Breakpoints like 800px)
// I have confirmed the new classes don't start with numbers
if (isNaN(Number(value.charAt(0)))) {
filteredModule[propertyName] = value;
}
});
return filteredModule;
})
.filter((module) => {
// Some modules will be empty after the filtering, remove those
return Object.keys(module).length > 0;
});

const mappings = allClasses.reduce((acc, cur) => {
Object.entries(cur).forEach(([property, value]) => {
if (acc[property]) {
acc[property].push(value);
} else {
acc[property] = [value];
}
});
return acc;
}, {});

const hashMapNoDupes = Object.entries<string[]>(mappings).reduce<Map<string, string>>(
(acc, entry) => {
if (entry[1].length === 1) {
acc.set(entry[1][0], entry[0]);
}
return acc;
},
new Map()
);

classHashMap = hashMapNoDupes;
}
21 changes: 21 additions & 0 deletions src/deckyPatches/SteamTabElementsFinder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getGamepadNavigationTrees } from "decky-frontend-lib";

export function getElementFromNavID(navID: string) {
const all = getGamepadNavigationTrees();
if (!all) return null;
const tree = all?.find((e: any) => e.m_ID == navID);
if (!tree) return null;
return tree.Root.Element;
}
export function getSP() {
return getElementFromNavID("root_1_");
}
export function getQAM() {
return getElementFromNavID("QuickAccess-NA");
}
export function getMainMenu() {
return getElementFromNavID("MainNavMenuContainer");
}
export function getRootElements() {
return [getSP(), getQAM(), getMainMenu()].filter((e) => e);
}
63 changes: 63 additions & 0 deletions src/deckyPatches/UnminifyMode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { classHashMap, initializeClassHashMap } from "./ClassHashMap";
import { getRootElements } from "./SteamTabElementsFinder";

export function unminifyElement(element: Element) {
if (element.classList.length === 0) return;

const classList = Array.from(element.classList);
const unminifiedClassList = classList.map((c) => classHashMap.get(c) || c);
element.setAttribute("unminified-class", unminifiedClassList.join(" "));
}

export function recursivelyUnminifyElement(element: Element) {
unminifyElement(element);
Array.from(element.children).forEach(recursivelyUnminifyElement);
}

export function initialUnminification(rootElement: any) {
const allElements = rootElement.ownerDocument.all as HTMLAllCollection;
Array.from(allElements).forEach(unminifyElement);
}

var mutationObservers: MutationObserver[] = [];

export function disconnectMutationObservers() {
mutationObservers.forEach((observer) => observer.disconnect());
mutationObservers = [];
}

export function mutationObserverCallback(mutations: MutationRecord[]) {
mutations.forEach((mutation) => {
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach((node) => {
recursivelyUnminifyElement(node as Element);
});
}
if (mutation.type === "attributes" && mutation.attributeName === "class") {
unminifyElement(mutation.target as HTMLElement);
}
});
}

export function setUpMutationObserver(rootElement: any) {
const mutationObserver = new MutationObserver(mutationObserverCallback);
mutationObserver.observe(rootElement.ownerDocument.documentElement, {
attributes: true,
attributeFilter: ["class"],
childList: true,
subtree: true,
});
mutationObservers.push(mutationObserver);
}

export function enableUnminifyMode() {
if (mutationObservers.length > 0) disconnectMutationObservers();
initializeClassHashMap();
const roots = getRootElements();
roots.forEach(initialUnminification);
roots.forEach(setUpMutationObserver);
}

export function disableUnminifyMode() {
disconnectMutationObservers();
}
8 changes: 3 additions & 5 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import {
PanelSection,
PanelSectionRow,
ServerAPI,
DialogButton,
Focusable,
Navigation,
} from "decky-frontend-lib";
import { useEffect, useState, FC } from "react";
import { useEffect, useState } from "react";
import * as python from "./python";
import * as api from "./api";
import { RiPaintFill } from "react-icons/ri";
Expand All @@ -21,8 +18,8 @@ import { Flags, Theme } from "./ThemeTypes";
import { dummyFunction, getInstalledThemes, reloadBackend } from "./python";
import { bulkThemeUpdateCheck } from "./logic/bulkThemeUpdateCheck";
import { disableNavPatch, enableNavPatch } from "./deckyPatches/NavPatch";
import { FaCog, FaStore } from "react-icons/fa";
import { SettingsPageRouter } from "./pages/settings/SettingsPageRouter";
import { disableUnminifyMode } from "./deckyPatches/UnminifyMode";

function Content() {
const { localThemeList, setGlobalState } = useCssLoaderState();
Expand Down Expand Up @@ -205,6 +202,7 @@ export default definePlugin((serverApi: ServerAPI) => {
onDismount: () => {
const { updateCheckTimeout } = state.getPublicState();
if (updateCheckTimeout) clearTimeout(updateCheckTimeout);
disableUnminifyMode();
disableNavPatch();
},
};
Expand Down
Loading

0 comments on commit 05068b6

Please sign in to comment.