diff --git a/main.py b/main.py index 07f5598..e7819f5 100644 --- a/main.py +++ b/main.py @@ -2,11 +2,13 @@ import time import os import json import asyncio +import pathlib -VERSION = "0.5.0" -SETTINGS_LOCATION = "~/.config/powertools.json" +VERSION = "0.6.0" +HOME_DIR = str(pathlib.Path(os.getcwd()).parent.parent.resolve()) +DEFAULT_SETTINGS_LOCATION = HOME_DIR + "/.config/powertools/default_settings.json" LOG_LOCATION = "/tmp/powertools.log" -FANTASTIC_INSTALL_DIR = "~/homebrew/plugins/Fantastic" +FANTASTIC_INSTALL_DIR = HOME_DIR + "/homebrew/plugins/Fantastic" import logging @@ -17,8 +19,15 @@ logging.basicConfig( force = True) logger = logging.getLogger() -logger.setLevel(logging.INFO) +logger.setLevel(logging.DEBUG) logging.info(f"PowerTools v{VERSION} https://github.com/NGnius/PowerTools") +logging.info(f"CWD: {os.getcwd()} HOME:{HOME_DIR}") + +import sys +#import pathlib +sys.path.append(str(pathlib.Path(__file__).parent.resolve())) +import server as pt_server + startup_time = time.time() class CPU: @@ -120,6 +129,8 @@ class Plugin: auto_fan = True persistent = True modified_settings = False + current_game = None # None means main menu + last_recognised_game = None async def get_version(self) -> str: return VERSION @@ -245,12 +256,12 @@ class Plugin: # Asyncio-compatible long-running code, executed in a task when the plugin is loaded async def _main(self): # startup: load & apply settings - if os.path.exists(SETTINGS_LOCATION): - settings = read_json(SETTINGS_LOCATION) - logging.debug(f"Loaded settings from {SETTINGS_LOCATION}: {settings}") + if os.path.exists(DEFAULT_SETTINGS_LOCATION): + settings = read_json(DEFAULT_SETTINGS_LOCATION) + logging.debug(f"Loaded settings from {DEFAULT_SETTINGS_LOCATION}: {settings}") else: settings = None - logging.debug(f"Settings {SETTINGS_LOCATION} does not exist, skipped") + logging.debug(f"Settings {DEFAULT_SETTINGS_LOCATION} does not exist, skipped") if settings is None or settings["persistent"] == False: logging.debug("Ignoring settings from file") self.persistent = False @@ -288,12 +299,15 @@ class Plugin: write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", settings["fan"]["target"]) self.dirty = False logging.info("Handled saved settings, back-end startup complete") + # server setup + await pt_server.start(VERSION) # work loop while True: if self.modified_settings and self.persistent: self.save_settings(self) self.modified_settings = False await asyncio.sleep(1) + await pt_server.shutdown() # called from main_view::onViewReady async def on_ready(self): @@ -343,7 +357,16 @@ class Plugin: def save_settings(self): settings = self.current_settings(self) logging.info(f"Saving settings to file: {settings}") - write_json(SETTINGS_LOCATION, settings) + write_json(DEFAULT_SETTINGS_LOCATION, settings) + + # per-game profiles + + async def get_current_game(self) -> str: + current_game = pt_server.http_server.game() + if current_game is None: + return "Menu (default)" + else: + return f"{current_game.name()} ({current_game.appid()})" diff --git a/main_view.html b/main_view.html index 899076d..c9de4ba 100644 --- a/main_view.html +++ b/main_view.html @@ -81,12 +81,35 @@ function getPersistent() { return call_plugin_method("get_persistent", {}); } + + function getCurrentGame() { + return call_plugin_method("get_current_game", {}); + } // other logic async function onReady() { await onViewReady(); + // detect game starts and exits + console.log("Injecting game detection code into main window (SP)"); + await execute_in_tab("SP", false, + `SteamClient.Apps.RegisterForGameActionStart((actionType, data) => { + console.log("start game", appStore.GetAppOverviewByGameID(data)); + fetch("http://127.0.0.1:5030/on_game_start/" + data.toString(), {method: "POST", body: JSON.stringify(appStore.GetAppOverviewByGameID(data))}).then((_) => {}); + }); + // this seems to not run when I thought (runs right after ^^^, not when game exits) + /*SteamClient.Apps.RegisterForGameActionEnd((actionType, data) => { + if (data != null && data != undefined) { + console.log("stop game", appStore.GetAppOverviewByGameID(data)); + fetch("http://127.0.0.1:5030/on_game_exit/" + data.toString(), {method: "POST", body: JSON.stringify(appStore.GetAppOverviewByGameID(data))}).then((_) => {}); + } else { + console.log("stop game null"); + fetch("http://127.0.0.1:5030/on_game_exit_null", {method: "POST", body:data}).then((_) => {}); + } + });*/` + ); + let boostToggle = document.getElementById("boostToggle"); setToggleState(boostToggle, await getCPUBoost()); setToggleState(document.getElementById("smtToggle"), await getSMT()); @@ -106,7 +129,10 @@ setToggleState(document.getElementById("persistToggle"), await getPersistent()); // this is unimportant; always do it last await updateVersion(); - window.setInterval(function() {updateBatteryStats().then(_ => {})}, 5000); + window.setInterval(function() { + updateBatteryStats().then(_ => {}); + updateCurrentGame().then(_ => {}); + }, 5000); } async function setCPUNotch(index) { @@ -249,6 +275,12 @@ setToggleState(toggle, !isActive); } + async function updateCurrentGame() { + let gameNow = document.getElementById("gameNow"); + let gameNameNow = await getCurrentGame(); + gameNow.innerText = gameNameNow; + } + let versionCount = -1; async function updateVersion() { let version = await getVersion(); @@ -566,6 +598,15 @@
Restores settings after a reboot
+
+
+
Now Playing
+
+
Littlewood (894940)
+
+
+
+
diff --git a/server.py b/server.py new file mode 100644 index 0000000..28cfc7f --- /dev/null +++ b/server.py @@ -0,0 +1,124 @@ +import logging +import json +import os +import pathlib + +import asyncio +from aiohttp import web + +HOME_DIR = str(pathlib.Path(os.getcwd()).parent.parent.resolve()) +SETTINGS_DIR = HOME_DIR + "/.config/powertools" + +if not os.path.exists(SETTINGS_DIR): + os.mkdir(SETTINGS_DIR) + +http_runner = None +http_server = None + +class GameInfo: + def __init__(self, gameid: int, game_info: dict): + self.gameid = gameid + self.game_info = game_info + + def appid(self): + return self.game_info["appid"] + + def name(self): + return self.game_info["display_name"] + + def settings_path(self) -> str: + return SETTINGS_DIR + os.pathsep + str(self.appid()) + ".json" + + def load_settings(self) -> dict: + settings_path = self.settings_path() + if os.exists(settings_path): + with open(settings_path, mode="r") as f: + return json.load(f) + return None + + def has_settings(self) -> bool: + return os.exists(self.settings_path()) + + +class Server(web.Application): + + def __init__(self, version): + super().__init__() + self.version = version + self.current_game = None + self.last_recognised_game = None + self.add_routes([ + web.get("/", lambda req: self.index(req)), + web.post("/on_game_start/{game_id}", lambda req: self.on_game_start(req)), + web.post("/on_game_exit/{game_id}", lambda req: self.on_game_exit(req)), + web.post("/on_game_exit_null", lambda req: self.on_game_exit_null(req)) + ]) + logging.debug("Server init complete") + + def game(self) -> GameInfo: + return self.current_game + + def recognised_game(self) -> GameInfo: + return self.last_recognised_game + + async def index(self, request): + logging.debug("Debug index page accessed") + return web.json_response({ + "name": "PowerTools", + "version": self.version, + "latest_game_id": self.current_game, + "latest_recognised_game_id": self.last_recognised_game, + }, headers={"Access-Control-Allow-Origin": "*"}) + + async def on_game_start(self, request): + game_id = request.match_info["game_id"] + data = await request.text() + logging.debug(f"on_game_start {game_id} body:\n{data}") + try: + game_id = int(game_id) + data = json.loads(data) + except: + return web.Response(text="WTF", status=400) + self.current_game = GameInfo(game_id, data) + if True: # TODO check for game_id in existing profiles + self.last_recognised_game = self.current_game # only set this when profile exists + # TODO apply profile + return web.Response(status=204, headers={"Access-Control-Allow-Origin": "*"}) + + async def on_game_exit(self, request): + # ignored for now + game_id = request.match_info["game_id"] + data = await request.text() + logging.debug(f"on_game_exit {game_id}") + try: + game_id = int(game_id) + except ValueError: + return web.Response(text="WTF", status=400) + if self.current_game.gameid == game_id: + pass + #self.current_game = None + # TODO change settings to default + return web.Response(status=204, headers={"Access-Control-Allow-Origin": "*"}) + + async def on_game_exit_null(self, request): + # ignored for now + logging.info(f"on_game_exit_null") + #self.current_game = None + # TODO change settings to default + return web.Response(status=204, headers={"Access-Control-Allow-Origin": "*"}) + +async def start(version): + global http_runner, http_server + loop = asyncio.get_event_loop() + http_server = Server(version) + http_runner = web.AppRunner(http_server) + await http_runner.setup() + site = web.TCPSite(http_runner, '0.0.0.0', 5030) + await site.start() + +async def shutdown(): # never really called + global http_runner, http_server + if http_runner is not None: + await http_runner.cleanup() + http_runner = None + http_server = None