Merge branch 'dev'

This commit is contained in:
NGnius (Graham) 2022-05-28 11:16:47 -04:00
commit 6eb4b9e249
4 changed files with 657 additions and 243 deletions

View file

@ -14,7 +14,7 @@ You will need that installed for this plugin to work.
- Set some GPU power parameters (fastPPT & slowPPT) - Set some GPU power parameters (fastPPT & slowPPT)
- Set the fan RPM (unsupported on SteamOS beta) - Set the fan RPM (unsupported on SteamOS beta)
- Display supplementary battery info - Display supplementary battery info
- Keep settings between restarts (stored in `~/.config/powertools.json`) - Keep settings between restarts (stored in `~/.config/powertools/<appid>.json`)
## Cool, but that's too much work ## Cool, but that's too much work
@ -79,6 +79,24 @@ This is how I figured out how the fan stuff works.
I've only scratched the surface of what this code allows, I'm sure it has more useful information. I've only scratched the surface of what this code allows, I'm sure it has more useful information.
https://lkml.org/lkml/2022/2/5/391 https://lkml.org/lkml/2022/2/5/391
### Game launch detection
The biggest limitation right now is it can't detect a game closing -- only opening -- and only after PowerTools is looked at at least once (per SteamOS restart).
From a plugin, this can be accomplished by running some front-end Javascript.
```javascript
await execute_in_tab("SP", false,
`SteamClient.Apps.RegisterForGameActionStart((actionType, data) => {
console.log("start game", appStore.GetAppOverviewByGameID(data));
});`
);
```
In PowerTools, the callback (the part surrounded by `{` and `}`, containing `console.log(...)`) sends a message to a local HTTP server to notify the PowerTools back-end that a game has been launched.
If you go to `http://127.0.0.1:5030` on your Steam Deck with PowerTools >=0.6.0, you can see some info about the last game you launched.
## License ## License
This is licensed under GNU GPLv3. This is licensed under GNU GPLv3.

178
main.py
View file

@ -2,11 +2,14 @@ import time
import os import os
import json import json
import asyncio import asyncio
import pathlib
import subprocess
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 +20,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.debug(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 +130,7 @@ class Plugin:
auto_fan = True auto_fan = True
persistent = True persistent = True
modified_settings = False modified_settings = False
current_gameid = None
async def get_version(self) -> str: async def get_version(self) -> str:
return VERSION return VERSION
@ -198,16 +209,16 @@ class Plugin:
self.modified_settings = True self.modified_settings = True
if tick >= len(self.FAN_SPEEDS): if tick >= len(self.FAN_SPEEDS):
# automatic mode # automatic mode
self.enable_jupiter_fan_control(self)
self.auto_fan = True self.auto_fan = True
write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 0) write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 0)
write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", 4099) # 4099 is default write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", 4099) # 4099 is default
#subprocess.Popen("systemctl start jupiter-fan-control.service", stdout=subprocess.PIPE, shell=True).wait()
else: else:
# manual voltage # manual voltage
self.disable_jupiter_fan_control(self)
self.auto_fan = False self.auto_fan = False
write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 1) write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 1)
write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", self.FAN_SPEEDS[tick]) write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", self.FAN_SPEEDS[tick])
#subprocess.Popen("systemctl stop jupiter-fan-control.service", stdout=subprocess.PIPE, shell=True).wait()
async def get_fan_tick(self) -> int: async def get_fan_tick(self) -> int:
fan_target = read_fan_target() fan_target = read_fan_target()
@ -231,6 +242,27 @@ class Plugin:
async def fantastic_installed(self) -> bool: async def fantastic_installed(self) -> bool:
return os.path.exists(FANTASTIC_INSTALL_DIR) return os.path.exists(FANTASTIC_INSTALL_DIR)
def disable_jupiter_fan_control(self):
active = subprocess.Popen(["systemctl", "is-active", "jupiter-fan-control.service"]).wait() == 0
if active:
logging.info("Stopping jupiter-fan-control.service so it doesn't interfere")
# only disable if currently active
self.jupiter_fan_control_was_disabled = True
stop_p = subprocess.Popen(["systemctl", "stop", "jupiter-fan-control.service"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stop_p.wait()
logging.debug("systemctl stop jupiter-fan-control.service stdout:\n" + stop_p.stdout.read().decode())
logging.debug("systemctl stop jupiter-fan-control.service stderr:\n" + stop_p.stderr.read().decode())
def enable_jupiter_fan_control(self):
if self.jupiter_fan_control_was_disabled:
logging.info("Starting jupiter-fan-control.service so it doesn't interfere")
# only re-enable if I disabled it
self.jupiter_fan_control_was_disabled = False
start_p = subprocess.Popen(["systemctl", "start", "jupiter-fan-control.service"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
start_p.wait()
logging.debug("systemctl start jupiter-fan-control.service stdout:\n" + start_p.stdout.read().decode())
logging.debug("systemctl start jupiter-fan-control.service stderr:\n" + start_p.stderr.read().decode())
# Battery stuff # Battery stuff
async def get_charge_now(self) -> int: async def get_charge_now(self) -> int:
@ -245,55 +277,56 @@ 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): self.jupiter_fan_control_was_disabled = False
settings = read_json(SETTINGS_LOCATION) if os.path.exists(DEFAULT_SETTINGS_LOCATION):
logging.debug(f"Loaded settings from {SETTINGS_LOCATION}: {settings}") settings = read_json(DEFAULT_SETTINGS_LOCATION)
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
self.cpus = [] self.guess_settings(self)
self.modified_settings = True
for cpu_number in range(0, Plugin.CPU_COUNT):
self.cpus.append(CPU(cpu_number))
# If any core has two threads, smt is True
self.smt = self.cpus[1].status()
if(not self.smt):
for cpu_number in range(2, len(self.cpus), 2):
if(self.cpus[cpu_number].status()):
self.smt = True
break
logging.info(f"SMT state is guessed to be {self.smt}")
else: else:
# apply settings # apply settings
logging.debug("Restoring settings from file") logging.debug("Restoring settings from file")
self.persistent = True self.persistent = True
# CPU self.apply_settings(self, settings)
self.cpus = [] # self.modified_settings = False
for cpu_number in range(0, Plugin.CPU_COUNT):
self.cpus.append(CPU(cpu_number, settings=settings["cpu"]["threads"][cpu_number]))
self.smt = settings["cpu"]["smt"]
write_cpu_boost(settings["cpu"]["boost"])
# GPU
write_gpu_ppt(1, settings["gpu"]["slowppt"])
write_gpu_ppt(2, settings["gpu"]["fastppt"])
# Fan
if not (os.path.exists(FANTASTIC_INSTALL_DIR) or settings["fan"]["auto"]):
write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 1)
write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", settings["fan"]["target"])
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:
# persistence
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
if self.persistent:
# per-game profiles
current_game = pt_server.http_server.game()
old_gameid = self.current_gameid
if current_game is not None and current_game.has_settings():
self.current_gameid = current_game.gameid
if old_gameid != self.current_gameid:
logging.info(f"Applying custom settings for {current_game.name()} {current_game.appid()}")
# new game; apply settings
settings = current_game.load_settings()
if settings is not None:
self.apply_settings(self, settings)
else:
self.current_gameid = None
if old_gameid != self.current_gameid:
logging.info("Reapplying default settings; game without custom settings found")
# game without custom settings; apply defaults
settings = read_json(DEFAULT_SETTINGS_LOCATION)
self.apply_settings(self, settings)
logging.debug(f"gameid update: {old_gameid} -> {self.current_gameid}")
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):
@ -342,8 +375,71 @@ 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.debug(f"Saving settings to file: {settings}")
write_json(SETTINGS_LOCATION, settings) current_game = pt_server.http_server.game()
if current_game is not None and self.current_gameid is not None:
save_location = current_game.settings_path()
else:
save_location = DEFAULT_SETTINGS_LOCATION
write_json(save_location, settings)
logging.info(f"Saved settings to {save_location}")
def apply_settings(self, settings: dict):
# CPU
self.cpus = []
for cpu_number in range(0, Plugin.CPU_COUNT):
self.cpus.append(CPU(cpu_number, settings=settings["cpu"]["threads"][cpu_number]))
self.smt = settings["cpu"]["smt"]
write_cpu_boost(settings["cpu"]["boost"])
# GPU
write_gpu_ppt(1, settings["gpu"]["slowppt"])
write_gpu_ppt(2, settings["gpu"]["fastppt"])
# Fan
if not (os.path.exists(FANTASTIC_INSTALL_DIR) or settings["fan"]["auto"]):
self.disable_jupiter_fan_control(self)
write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 1)
write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", settings["fan"]["target"])
elif settings["fan"]["auto"] and not os.path.exists(FANTASTIC_INSTALL_DIR):
self.enable_jupiter_fan_control(self)
def guess_settings(self):
self.cpus = []
for cpu_number in range(0, Plugin.CPU_COUNT):
self.cpus.append(CPU(cpu_number))
# If any core has two threads, smt is True
self.smt = self.cpus[1].status()
if(not self.smt):
for cpu_number in range(2, len(self.cpus), 2):
if(self.cpus[cpu_number].status()):
self.smt = True
break
logging.info(f"SMT state is guessed to be {self.smt}")
# 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()})"
async def set_per_game_profile(self, enabled: bool):
current_game = pt_server.http_server.game()
if enabled and self.persistent and current_game is not None:
self.current_gameid = current_game.gameid
self.modified_settings = True
else:
if not enabled and current_game is not None and current_game.has_settings():
# delete settings; disable settings loading
os.remove(current_game.settings_path())
self.current_gameid = None
async def get_per_game_profile(self) -> bool:
return self.current_gameid is not None

View file

@ -82,11 +82,72 @@
return call_plugin_method("get_persistent", {}); return call_plugin_method("get_persistent", {});
} }
function setPerGameProfile(value) {
return call_plugin_method("set_per_game_profile", {"enabled": value});
}
function getPerGameProfile() {
return call_plugin_method("get_per_game_profile", {});
}
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,
`console.log("Hey PowerTools is over here now too!");
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((_) => {});
}
});*/`
);
await updateCurrentGame();
/*let boostToggle = document.getElementById("boostToggle");
setToggleState(boostToggle, await getCPUBoost());
setToggleState(document.getElementById("smtToggle"), await getSMT());
selectNotch("cpuThreadsNotch", await getCPUs() - 1, 8);
selectNotch("frequencyNotch", await getMaxBoost(), 3);
await onReadyGPU();
let isFantasticInstalled = await getFantastic();
if (isFantasticInstalled) {
// Don't fight with Fantastic
let fanRoot = document.getElementById("fanRoot");
fanRoot.style.visibility = "hidden";
fanRoot.style.height = "0px";
} else {
selectNotch("fanNotch", await getFanTick(), 8);
}
await updateBatteryStats();
setToggleState(document.getElementById("persistToggle"), await getPersistent());
setToggleState(document.getElementById("gameProfileToggle"), await getPerGameProfile());
await updateCurrentGame();*/
// this is unimportant; always do it last
await updateVersion();
window.setInterval(function() {
updateBatteryStats().then(_ => {});
updateCurrentGame().then(_ => {});
}, 1000);
}
async function reloadSettings() {
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());
@ -104,9 +165,7 @@
} }
await updateBatteryStats(); await updateBatteryStats();
setToggleState(document.getElementById("persistToggle"), await getPersistent()); setToggleState(document.getElementById("persistToggle"), await getPersistent());
// this is unimportant; always do it last setToggleState(document.getElementById("gameProfileToggle"), await getPerGameProfile());
await updateVersion();
window.setInterval(function() {updateBatteryStats().then(_ => {})}, 5000);
} }
async function setCPUNotch(index) { async function setCPUNotch(index) {
@ -115,6 +174,12 @@
selectNotch(ROOT_ID, await getCPUs() - 1, 8); selectNotch(ROOT_ID, await getCPUs() - 1, 8);
} }
async function onSlideCPUNotch(e) {
const ROOT_ID = "cpuThreadsNotch";
let closest = closestNotch(e, ROOT_ID, 8);
await setCPUNotch(closest);
}
const TOGGLE_ON_CLASS = "gamepaddialog_On_3ld7T"; const TOGGLE_ON_CLASS = "gamepaddialog_On_3ld7T";
function setToggleState(toggle, state) { function setToggleState(toggle, state) {
@ -159,12 +224,24 @@
selectNotch(ROOT_ID, await getMaxBoost(), 3); selectNotch(ROOT_ID, await getMaxBoost(), 3);
} }
async function onSlideBoostNotch(e) {
const ROOT_ID = "frequencyNotch";
let closest = closestNotch(e, ROOT_ID, 3);
await setBoostNotch(closest);
}
async function onSetFanNotch(index) { async function onSetFanNotch(index) {
const ROOT_ID = "fanNotch"; const ROOT_ID = "fanNotch";
await setFanTick(index); await setFanTick(index);
selectNotch(ROOT_ID, index, 8); selectNotch(ROOT_ID, index, 8);
} }
async function onSlideFanNotch(e) {
const ROOT_ID = "fanNotch";
let closest = closestNotch(e, ROOT_ID, 8);
await onSetFanNotch(closest);
}
async function onReadyGPU() { async function onReadyGPU() {
let power1_cap = await getGPUPower(1); let power1_cap = await getGPUPower(1);
let power2_cap = await getGPUPower(2); let power2_cap = await getGPUPower(2);
@ -202,6 +279,12 @@
selectNotch(ROOT_ID, index, 3); selectNotch(ROOT_ID, index, 3);
} }
async function onSlideSlowPPTNotch(e) {
const ROOT_ID = "slowPPTNotch";
let closest = closestNotch(e, ROOT_ID, 3);
onSetSlowPPTNotch(closest);
}
async function onSetFastPPTNotch(index) { async function onSetFastPPTNotch(index) {
const ROOT_ID = "fastPPTNotch"; const ROOT_ID = "fastPPTNotch";
document.getElementById("fastPPTAutoDefault").innerText = "Default"; document.getElementById("fastPPTAutoDefault").innerText = "Default";
@ -215,6 +298,12 @@
selectNotch(ROOT_ID, index, 3); selectNotch(ROOT_ID, index, 3);
} }
async function onSlideFastPPTNotch(e) {
const ROOT_ID = "fastPPTNotch";
let closest = closestNotch(e, ROOT_ID, 3);
await onSetFastPPTNotch(closest);
}
function selectNotch(rootId, index, elements) { function selectNotch(rootId, index, elements) {
// WARNING: this yeets any style in div of slider // WARNING: this yeets any style in div of slider
const ENABLED_CLASS = "gamepadslider_TickActive_1gnUV"; const ENABLED_CLASS = "gamepadslider_TickActive_1gnUV";
@ -231,6 +320,19 @@
} }
} }
function closestNotch(e, rootId, elements) {
let root = document.getElementById(rootId);
let val = e.x / root.scrollWidth;
let closest_notch = Math.round(val * elements);
if (closest_notch > elements) {
closest_notch = elements;
} else if (closest_notch < 0) {
closest_notch = 0;
}
return closest_notch
//selectNotch(closest_notch);
}
async function updateBatteryStats() { async function updateBatteryStats() {
//console.log("Updating battery stats"); //console.log("Updating battery stats");
let batCapacityNow = document.getElementById("batCapacityNow"); let batCapacityNow = document.getElementById("batCapacityNow");
@ -238,8 +340,8 @@
let chargeNow = await getChargeNow(); let chargeNow = await getChargeNow();
let chargeFull = await getChargeFull(); let chargeFull = await getChargeFull();
let chargeDesign = await getChargeDesign(); let chargeDesign = await getChargeDesign();
batCapacityNow.innerText = (7.7 * chargeNow / 1000000).toFixed(2).toString() + " Wh (" + (100 * chargeNow / chargeFull).toFixed(0).toString() + "%)"; batCapacityNow.innerText = (7.7 * chargeNow / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeNow / chargeFull).toFixed(1).toString() + "%)";
batCapacityFull.innerText = (7.7 * chargeFull / 1000000).toFixed(2).toString() + " Wh (" + (100 * chargeFull / chargeDesign).toFixed(0).toString() + "%)"; batCapacityFull.innerText = (7.7 * chargeFull / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeFull / chargeDesign).toFixed(1).toString() + "%)";
} }
async function togglePersist() { async function togglePersist() {
@ -249,6 +351,26 @@
setToggleState(toggle, !isActive); setToggleState(toggle, !isActive);
} }
async function toggleGameProfile() {
let toggle = document.getElementById("gameProfileToggle");
let isActive = getToggleState(toggle);
await setPerGameProfile(!isActive);
setToggleState(toggle, await getPerGameProfile());
}
let lastGameName = "";
async function updateCurrentGame() {
let gameNow = document.getElementById("gameNow");
let gameNameNow = await getCurrentGame();
if (lastGameName != gameNameNow) {
setToggleState(document.getElementById("gameProfileToggle"), await getPerGameProfile());
await reloadSettings();
}
lastGameName = gameNameNow
gameNow.innerText = gameNameNow;
}
let versionCount = -1; let versionCount = -1;
async function updateVersion() { async function updateVersion() {
let version = await getVersion(); let version = await getVersion();
@ -266,39 +388,44 @@
<style type="text/css" media="screen"></style> <style type="text/css" media="screen"></style>
</head> </head>
<body onload="onReady()" style="/*margin:0px;padding:0px;*/overflow-x:hidden;margin:0px;"> <body onload="onReady()" style="/*margin:0px;padding:0px;*/overflow-x:hidden;margin:0px;">
<!-- Spacer (moves top out of shadow above it) -->
<div class="quickaccessmenu_TabGroupPanel_1QO7b">
<div class="quickaccesscontrols_PanelSection_2C0g0" style="margin-bottom:6px;">
<!--<div class="quickaccesscontrols_PanelSectionRow_2VQ88">
</div>-->
</div>
</div>
<!-- CPU --> <!-- CPU -->
<!-- SMT toggle switch, roughly copied from https://github.com/SteamDeckHomebrew/ExtraSettingsPlugin/blob/main/main_view.html --> <!-- SMT toggle switch, roughly copied from https://github.com/SteamDeckHomebrew/ExtraSettingsPlugin/blob/main/main_view.html -->
<!-- Due to a bug in MangoHud, this has a warning for now --> <!-- Due to a bug in MangoHud, this has a warning for now -->
<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable"> <div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">
<div class="quickaccesscontrols_PanelSection_2C0g0" style="padding:0px 4px;"> <div class="quickaccesscontrols_PanelSection_2C0g0" style="">
<div class="quickaccesscontrols_PanelSectionRow_2VQ88"> <div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_WithDescription_3bMIS gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;"> <div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_WithDescription_3bMIS gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;" onclick="toggleCPUSMT()">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-"> <div class="gamepaddialog_FieldLabel_3b0U-">
CPU SMT CPU SMT
</div> </div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
<div id="smtToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable" onclick="toggleCPUSMT()"> <div id="smtToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable" >
<div class="gamepaddialog_ToggleRail_2JtC3"></div> <div class="gamepaddialog_ToggleRail_2JtC3"></div>
<div class="gamepaddialog_ToggleSwitch_3__OD"></div> <div class="gamepaddialog_ToggleSwitch_3__OD"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="gamepaddialog_FieldDescription_2OJfk">Enables odd-numbered CPUs</div> <div class="gamepaddialog_FieldDescription_2OJfk">Enables odd-numbered CPUs</div>
<div style="font-size:x-small;">
WARNING: Disabling crashes the performance overlay.
</div>
</div> </div>
</div> </div>
<!-- CPUs selector --> <!-- CPUs selector -->
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparator_1lUZx gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable"> <div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">Threads</div> <div class="gamepaddialog_FieldLabel_3b0U-">Threads</div>
</div> </div>
<div id="cpuThreadsNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.5;"> <div id="cpuThreadsNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.5;" onmousemove="onSlideCPUNotch(event)">
<div class="gamepadslider_SliderControl_3o137"> <div class="gamepadslider_SliderControl_3o137">
<div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div> <div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi"> <div class="gamepadslider_SliderHandleContainer_1pQZi">
@ -341,19 +468,20 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- CPU Boost toggle switch, roughly copied from https://github.com/SteamDeckHomebrew/ExtraSettingsPlugin/blob/main/main_view.html --> <!-- CPU Boost toggle switch, roughly copied from https://github.com/SteamDeckHomebrew/ExtraSettingsPlugin/blob/main/main_view.html -->
<div class="quickaccesscontrols_PanelSection_2C0g0" style="padding:0px 4px;"> <div class="quickaccesscontrols_PanelSection_2C0g0" style="">
<div class="quickaccesscontrols_PanelSectionRow_2VQ88"> <div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_WithDescription_3bMIS gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;"> <div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_WithDescription_3bMIS gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;" onclick="toggleCPUBoost()">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-"> <div class="gamepaddialog_FieldLabel_3b0U-">
CPU Boost CPU Boost
</div> </div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
<div id="boostToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable" onclick="toggleCPUBoost()"> <div id="boostToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable">
<div class="gamepaddialog_ToggleRail_2JtC3"></div> <div class="gamepaddialog_ToggleRail_2JtC3"></div>
<div class="gamepaddialog_ToggleSwitch_3__OD"></div> <div class="gamepaddialog_ToggleSwitch_3__OD"></div>
</div> </div>
@ -365,12 +493,13 @@
<!-- Frequency selector --> <!-- Frequency selector -->
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparator_1lUZx gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable"> <div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">Max Frequency</div> <div class="gamepaddialog_FieldLabel_3b0U-">Max Frequency</div>
</div> </div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
<div id="frequencyNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.5;"> <div id="frequencyNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.5;" onmousemove="onSlideBoostNotch(event)">
<div class="gamepadslider_SliderControl_3o137"> <div class="gamepadslider_SliderControl_3o137">
<div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div> <div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi"> <div class="gamepadslider_SliderHandleContainer_1pQZi">
@ -397,20 +526,21 @@
WARNING: This will change the CPU governor. WARNING: This will change the CPU governor.
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- GPU --> <!-- GPU -->
<div class="quickaccesscontrols_PanelSection_2C0g0" style="padding:0px 4px;"> <div class="quickaccesscontrols_PanelSection_2C0g0" style="">
<!-- SlowPPT power limit (number 1) --> <!-- SlowPPT power limit (number 1) -->
<div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable"> <div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">GPU SlowPPT Power</div> <div class="gamepaddialog_FieldLabel_3b0U-">GPU SlowPPT Power</div>
</div> </div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
<div id="slowPPTNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.33;"> <div id="slowPPTNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.33;" onmousemove="onSlideSlowPPTNotch(event)">
<div class="gamepadslider_SliderControl_3o137"> <div class="gamepadslider_SliderControl_3o137">
<div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div> <div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi"> <div class="gamepadslider_SliderHandleContainer_1pQZi">
@ -434,14 +564,16 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- FastPPT power limit (number 2) --> <!-- FastPPT power limit (number 2) -->
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparator_1lUZx gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable"> <div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">GPU FastPPT Power</div> <div class="gamepaddialog_FieldLabel_3b0U-">GPU FastPPT Power</div>
</div> </div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
<div id="fastPPTNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.33;"> <div id="fastPPTNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.33;" onmousemove="onSlideFastPPTNotch(event)">
<div class="gamepadslider_SliderControl_3o137"> <div class="gamepadslider_SliderControl_3o137">
<div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div> <div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi"> <div class="gamepadslider_SliderHandleContainer_1pQZi">
@ -465,17 +597,20 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- Fan RPM selector --> <!-- Fan RPM selector -->
<div class="quickaccesscontrols_PanelSection_2C0g0" style="" id="fanRoot">
<div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<!-- TODO: Make this non-notched slider when PluginLoader PR#41 is merged --> <!-- TODO: Make this non-notched slider when PluginLoader PR#41 is merged -->
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparator_1lUZx gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" id="fanRoot"> <div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_WithChildrenBelow_1u5FT gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_ChildrenWidthFixed_1ugIU gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">Fan RPM</div> <div class="gamepaddialog_FieldLabel_3b0U-">Fan RPM</div>
</div> </div>
<div class="gamepaddialog_FieldDescription_2OJfk" style="display:none;">Requires disabling updated fan control</div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
<div id="fanNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.33;"> <div id="fanNotch" class="gamepadslider_SliderControlAndNotches_1Cccx Focusable" tabindex="0" style="--normalized-slider-value:0.33;" onmousemove="onSlideFanNotch(event)">
<div class="gamepadslider_SliderControl_3o137"> <div class="gamepadslider_SliderControl_3o137">
<div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div> <div class="gamepadslider_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi"> <div class="gamepadslider_SliderHandleContainer_1pQZi">
@ -522,15 +657,17 @@
WARNING: This can cause component overheating. WARNING: This can cause component overheating.
</div> </div>
</div> </div>
</div>
</div>
<!-- Battery Info --> <!-- Battery Info -->
<div class="quickaccesscontrols_PanelSection_2C0g0" style="padding:0px 4px;" onclick="updateBatteryStats()" style="margin-bottom:0px;"> <div class="quickaccesscontrols_PanelSection_2C0g0" style="" onclick="updateBatteryStats()" style="margin-bottom:0px;">
<div class="quickaccesscontrols_PanelSectionTitle_2iFf9"> <div class="quickaccesscontrols_PanelSectionTitle_2iFf9">
<div class="quickaccesscontrols_Text_1hJkB">Battery</div> <div class="quickaccesscontrols_Text_1hJkB">Battery</div>
</div> </div>
<div class="Panel Focusable" tabindex="0"> <div class="Panel Focusable" tabindex="0">
<div class="quickaccesscontrols_PanelSectionRow_2VQ88"> <div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<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_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">Now (Charge)</div> <div class="gamepaddialog_FieldLabel_3b0U-">Now (Charge)</div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
@ -540,7 +677,7 @@
</div> </div>
</div> </div>
<div class="quickaccesscontrols_PanelSectionRow_2VQ88"> <div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<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_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">Max (Design)</div> <div class="gamepaddialog_FieldLabel_3b0U-">Max (Design)</div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
@ -551,26 +688,50 @@
</div> </div>
</div> </div>
</div> </div>
<div class="quickaccesscontrols_PanelSection_2C0g0" style="padding:0px 4px;"> <div class="quickaccesscontrols_PanelSection_2C0g0" style="">
<div class="quickaccesscontrols_PanelSectionRow_2VQ88"> <div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_WithDescription_3bMIS gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;"> <div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_WithDescription_3bMIS gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;" onclick="togglePersist()">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-"> <div class="gamepaddialog_FieldLabel_3b0U-">
Persistent Persistent
</div> </div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
<div id="persistToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable" onclick="togglePersist()"> <div id="persistToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable">
<div class="gamepaddialog_ToggleRail_2JtC3"></div> <div class="gamepaddialog_ToggleRail_2JtC3"></div>
<div class="gamepaddialog_ToggleSwitch_3__OD"></div> <div class="gamepaddialog_ToggleSwitch_3__OD"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="gamepaddialog_FieldDescription_2OJfk">Restores settings after a reboot</div> <div class="gamepaddialog_FieldDescription_2OJfk">Restores settings after an app or OS restart</div>
</div>
</div>
<div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_WithDescription_3bMIS gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;" onclick="toggleGameProfile()">
<div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">
Use per-game profile
</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div id="gameProfileToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable">
<div class="gamepaddialog_ToggleRail_2JtC3"></div>
<div class="gamepaddialog_ToggleSwitch_3__OD"></div>
</div>
</div>
</div>
</div>
</div>
<div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk 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"> the bongos </div>
</div>
</div>
</div> </div>
</div> </div>
<div class="quickaccesscontrols_PanelSection_2C0g0" style="padding:0px 4px;">
<div class="quickaccesscontrols_PanelSectionRow_2VQ88" onclick="updateVersion()"> <div class="quickaccesscontrols_PanelSectionRow_2VQ88" onclick="updateVersion()">
<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_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;">
<div class="gamepaddialog_FieldLabelRow_H9WOq"> <div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">PowerTools</div> <div class="gamepaddialog_FieldLabel_3b0U-">PowerTools</div>
<div class="gamepaddialog_FieldChildren_14_HB"> <div class="gamepaddialog_FieldChildren_14_HB">
@ -581,6 +742,5 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</body> </body>
</html> </html>

140
server.py Normal file
View file

@ -0,0 +1,140 @@
import logging
import json
import os
import pathlib
# import asyncio
from aiohttp import web
import aiohttp
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
http_site = 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.path.sep + str(self.appid()) + ".json"
def load_settings(self) -> dict:
settings_path = self.settings_path()
if os.path.exists(settings_path):
with open(settings_path, mode="r") as f:
return json.load(f)
return None
def has_settings(self) -> bool:
return os.path.exists(self.settings_path())
class Server(web.Application):
def __init__(self, version):
super().__init__()
self.version = version
self.current_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)),
web.get("/self_destruct", lambda req: self.self_destruct(req))
])
logging.debug("Server init complete")
def game(self) -> GameInfo:
return self.current_game
async def index(self, request):
logging.debug("Debug index page accessed")
current_game = None if self.current_game is None else self.current_game.gameid
game_info = None if self.current_game is None else self.current_game.game_info
settings_info = None if self.current_game is None else self.current_game.load_settings()
return web.json_response({
"name": "PowerTools",
"version": self.version,
"latest_game_id": current_game,
"game_info": game_info,
"settings": settings_info
}, 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 self.current_game.has_settings():
self.last_recognised_game = self.current_game
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 self_destruct(self, request):
logging.warning("Geodude self-destructed")
await shutdown()
# unreachable \/ \/ \/
return web.Response(status=204, headers={"Access-Control-Allow-Origin": "*"})
async def start(version):
global http_runner, http_server, http_site
# make sure old server has shutdown
try:
async with aiohttp.ClientSession() as session:
async with session.get('http://127.0.0.1:5030/self_destruct') as response:
await response.text()
except:
pass
http_server = Server(version)
http_runner = web.AppRunner(http_server)
await http_runner.setup()
site = web.TCPSite(http_runner, '127.0.0.1', 5030)
await site.start()
async def shutdown(): # never really called
global http_runner, http_server, http_site
if http_runner is not None:
await http_runner.cleanup()
http_runner = None
http_site.stop()
http_site = None
http_server = None