Implement basic current game detection

This commit is contained in:
NGnius (Graham) 2022-05-22 17:47:33 -04:00
parent 2cbf3ec2c6
commit 9ec9a0cf40
3 changed files with 198 additions and 10 deletions

41
main.py
View file

@ -2,11 +2,13 @@ import time
import os import os
import json import json
import asyncio import asyncio
import pathlib
VERSION = "0.5.0" VERSION = "0.6.0"
SETTINGS_LOCATION = "~/.config/powertools.json" 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" LOG_LOCATION = "/tmp/powertools.log"
FANTASTIC_INSTALL_DIR = "~/homebrew/plugins/Fantastic" FANTASTIC_INSTALL_DIR = HOME_DIR + "/homebrew/plugins/Fantastic"
import logging import logging
@ -17,8 +19,15 @@ logging.basicConfig(
force = True) force = True)
logger = logging.getLogger() 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"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() startup_time = time.time()
class CPU: class CPU:
@ -120,6 +129,8 @@ class Plugin:
auto_fan = True auto_fan = True
persistent = True persistent = True
modified_settings = False modified_settings = False
current_game = None # None means main menu
last_recognised_game = None
async def get_version(self) -> str: async def get_version(self) -> str:
return VERSION return VERSION
@ -245,12 +256,12 @@ class Plugin:
# Asyncio-compatible long-running code, executed in a task when the plugin is loaded # Asyncio-compatible long-running code, executed in a task when the plugin is loaded
async def _main(self): async def _main(self):
# startup: load & apply settings # startup: load & apply settings
if os.path.exists(SETTINGS_LOCATION): if os.path.exists(DEFAULT_SETTINGS_LOCATION):
settings = read_json(SETTINGS_LOCATION) settings = read_json(DEFAULT_SETTINGS_LOCATION)
logging.debug(f"Loaded settings from {SETTINGS_LOCATION}: {settings}") logging.debug(f"Loaded settings from {DEFAULT_SETTINGS_LOCATION}: {settings}")
else: else:
settings = None 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: if settings is None or settings["persistent"] == False:
logging.debug("Ignoring settings from file") logging.debug("Ignoring settings from file")
self.persistent = False self.persistent = False
@ -288,12 +299,15 @@ class Plugin:
write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", settings["fan"]["target"]) write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", settings["fan"]["target"])
self.dirty = False self.dirty = False
logging.info("Handled saved settings, back-end startup complete") logging.info("Handled saved settings, back-end startup complete")
# server setup
await pt_server.start(VERSION)
# work loop # work loop
while True: while True:
if self.modified_settings and self.persistent: if self.modified_settings and self.persistent:
self.save_settings(self) self.save_settings(self)
self.modified_settings = False self.modified_settings = False
await asyncio.sleep(1) await asyncio.sleep(1)
await pt_server.shutdown()
# called from main_view::onViewReady # called from main_view::onViewReady
async def on_ready(self): async def on_ready(self):
@ -343,7 +357,16 @@ class Plugin:
def save_settings(self): def save_settings(self):
settings = self.current_settings(self) settings = self.current_settings(self)
logging.info(f"Saving settings to file: {settings}") 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()})"

View file

@ -81,12 +81,35 @@
function getPersistent() { function getPersistent() {
return call_plugin_method("get_persistent", {}); return call_plugin_method("get_persistent", {});
} }
function getCurrentGame() {
return call_plugin_method("get_current_game", {});
}
// other logic // other logic
async function onReady() { async function onReady() {
await onViewReady(); 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"); let boostToggle = document.getElementById("boostToggle");
setToggleState(boostToggle, await getCPUBoost()); setToggleState(boostToggle, await getCPUBoost());
setToggleState(document.getElementById("smtToggle"), await getSMT()); setToggleState(document.getElementById("smtToggle"), await getSMT());
@ -106,7 +129,10 @@
setToggleState(document.getElementById("persistToggle"), await getPersistent()); setToggleState(document.getElementById("persistToggle"), await getPersistent());
// this is unimportant; always do it last // this is unimportant; always do it last
await updateVersion(); await updateVersion();
window.setInterval(function() {updateBatteryStats().then(_ => {})}, 5000); window.setInterval(function() {
updateBatteryStats().then(_ => {});
updateCurrentGame().then(_ => {});
}, 5000);
} }
async function setCPUNotch(index) { async function setCPUNotch(index) {
@ -249,6 +275,12 @@
setToggleState(toggle, !isActive); setToggleState(toggle, !isActive);
} }
async function updateCurrentGame() {
let gameNow = document.getElementById("gameNow");
let gameNameNow = await getCurrentGame();
gameNow.innerText = gameNameNow;
}
let versionCount = -1; let versionCount = -1;
async function updateVersion() { async function updateVersion() {
let version = await getVersion(); let version = await getVersion();
@ -566,6 +598,15 @@
</div> </div>
</div> </div>
<div class="gamepaddialog_FieldDescription_2OJfk">Restores settings after a reboot</div> <div class="gamepaddialog_FieldDescription_2OJfk">Restores settings after a reboot</div>
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparator_1lUZx gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;">
<div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">Now Playing</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div class="gamepaddialog_LabelFieldValue_5Mylh" id="gameNow"> Littlewood (894940) </div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="quickaccesscontrols_PanelSection_2C0g0" style="padding:0px 4px;"> <div class="quickaccesscontrols_PanelSection_2C0g0" style="padding:0px 4px;">

124
server.py Normal file
View file

@ -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