React UI rewrite; port to new decky plugin framework

This commit is contained in:
NGnius (Graham) 2022-06-10 20:02:05 -04:00
parent 072053dcfc
commit ef678ed122
18 changed files with 713 additions and 760 deletions

42
.gitignore vendored
View file

@ -1 +1,41 @@
*.png
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
*.swp
pids
logs
results
tmp
# Coverage reports
coverage
# API keys and secrets
.env
# Dependency directory
node_modules
bower_components
package-lock.json
# Editors
.idea
*.iml
# OS metadata
.DS_Store
Thumbs.db
# Ignore built ts files
dist/
__pycache__/
/.yalc
yalc.lock

View file

@ -1,6 +1,6 @@
# PowerTools
![plugin_demo](./extras/ui.png)
![plugin_demo](./assets/ui.png)
Steam Deck power tweaks for power users.

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/thumbnail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

BIN
assets/ui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

41
main.py
View file

@ -5,8 +5,8 @@ import asyncio
import pathlib
import subprocess
VERSION = "0.7.0"
HOME_DIR = str(pathlib.Path(os.getcwd()).parent.parent.resolve())
VERSION = "0.7.0-alpha-react"
HOME_DIR = "/home/deck"
DEFAULT_SETTINGS_LOCATION = HOME_DIR + "/.config/powertools/default_settings.json"
LOG_LOCATION = "/tmp/powertools.log"
FANTASTIC_INSTALL_DIR = HOME_DIR + "/homebrew/plugins/Fantastic"
@ -127,10 +127,13 @@ class Plugin:
CPU_COUNT = 8
FAN_SPEEDS = [0, 1000, 2000, 3000, 4000, 5000, 6000]
gpu_power_values = [[-1, -1, -1], [1000000, 15000000, 29000000], [0, 15000000, 30000000]]
auto_fan = True
persistent = True
modified_settings = False
current_gameid = None
ready = False
async def get_version(self) -> str:
return VERSION
@ -139,6 +142,7 @@ class Plugin:
# call from main_view.html with setCPUs(count, smt)
async def set_cpus(self, count, smt=True):
logging.info(f"set_cpus({count}, {smt})")
self.modified_settings = True
cpu_count = len(self.cpus)
self.smt = smt
@ -165,9 +169,11 @@ class Plugin:
for cpu in self.cpus:
if(cpu.status()):
online_count += 1
logging.info(f"get_cpus() -> {online_count}")
return online_count
async def get_smt(self) -> bool:
logging.info(f"get_smt() -> {self.smt}")
return self.smt
async def set_boost(self, enabled: bool) -> bool:
@ -203,6 +209,24 @@ class Plugin:
async def get_gpu_power(self, power_number: int) -> int:
return read_gpu_ppt(power_number)
async def set_gpu_power_index(self, index: int, power_number: int) -> bool:
if index < 3 and index >= 0:
self.modified_settings = True
old_value = read_gpu_ppt(power_number)
if old_value not in self.gpu_power_values[power_number]:
self.gpu_power_values[power_number][1] = old_value
write_gpu_ppt(power_number, self.gpu_power_values[power_number][index])
return True
return False
async def get_gpu_power_index(self, power_number: int) -> int:
value = read_gpu_ppt(power_number)
if value not in self.gpu_power_values[power_number]:
#self.gpu_power_values[power_number][1] = value
return 1
else:
return self.gpu_power_values[power_number].index(value)
# Fan stuff
async def set_fan_tick(self, tick: int):
@ -331,6 +355,9 @@ class Plugin:
# called from main_view::onViewReady
async def on_ready(self):
delta = time.time() - startup_time
if self.ready:
logging.info(f"Front-end init called again {delta}s after startup")
return
logging.info(f"Front-end initialised {delta}s after startup")
# persistence
@ -439,7 +466,15 @@ class Plugin:
self.current_gameid = None
async def get_per_game_profile(self) -> bool:
return self.current_gameid is not None
return self.current_gameid is not None and current_game.has_settings()
async def on_game_start(self, game_id: int, data) -> bool:
pt_server.http_server.set_game(game_id, data)
return True
async def on_game_stop(self, game_id: int) -> bool:
pt_server.http_server.unset_game(game_id)
return True

View file

@ -1,746 +0,0 @@
<html>
<head>
<link rel="stylesheet" href="/steam_resource/css/2.css">
<link rel="stylesheet" href="/steam_resource/css/39.css">
<link rel="stylesheet" href="/steam_resource/css/library.css">
<script src="/static/library.js"></script>
<script>
// Python functions
function getVersion() {
return call_plugin_method("get_version", {});
}
function onViewReady() {
return call_plugin_method("on_ready", {});
}
function setCPUs(value, smt) {
return call_plugin_method("set_cpus", {"count":value, "smt": smt});
}
function getCPUs() {
return call_plugin_method("get_cpus", {});
}
function getSMT() {
return call_plugin_method("get_smt", {});
}
function setCPUBoost(value) {
return call_plugin_method("set_boost", {"enabled": value});
}
function getCPUBoost() {
return call_plugin_method("get_boost", {});
}
function setMaxBoost(index) {
return call_plugin_method("set_max_boost", {"index": index});
}
function getMaxBoost() {
return call_plugin_method("get_max_boost", {});
}
function setGPUPower(value, index) {
return call_plugin_method("set_gpu_power", {"value": value, "power_number": index});
}
function getGPUPower(index) {
return call_plugin_method("get_gpu_power", {"power_number": index});
}
function setFanTick(tick) {
return call_plugin_method("set_fan_tick", {"tick": tick});
}
function getFanTick() {
return call_plugin_method("get_fan_tick", {});
}
function getFantastic() {
return call_plugin_method("fantastic_installed", {});
}
function getChargeNow() {
return call_plugin_method("get_charge_now", {});
}
function getChargeFull() {
return call_plugin_method("get_charge_full", {});
}
function getChargeDesign() {
return call_plugin_method("get_charge_design", {});
}
function setPersistent(value) {
return call_plugin_method("set_persistent", {"enabled": value});
}
function getPersistent() {
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
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,
`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");
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());
}
async function setCPUNotch(index) {
const ROOT_ID = "cpuThreadsNotch";
await setCPUs(index, getToggleState(document.getElementById("smtToggle")));
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";
function setToggleState(toggle, state) {
if (state && !toggle.classList.contains(TOGGLE_ON_CLASS)) {
toggle.classList.add(TOGGLE_ON_CLASS);
}
if (!state && toggle.classList.contains(TOGGLE_ON_CLASS)) {
toggle.classList.remove(TOGGLE_ON_CLASS);
}
}
function getToggleState(toggle) {
return toggle.classList.contains(TOGGLE_ON_CLASS);
}
async function toggleCPUBoost() {
let toggle = document.getElementById("boostToggle");
let isActive = getToggleState(toggle);
await setCPUBoost(!isActive);
setToggleState(toggle, !isActive);
}
async function toggleCPUSMT() {
let toggle = document.getElementById("smtToggle");
let isActive = getToggleState(toggle);
let currentCPUs = await getCPUs();
if (currentCPUs == 4 && !isActive) {
// if all cores are running, enable all the threads as well
await setCPUs(8, !isActive);
} else {
await setCPUs(currentCPUs, !isActive);
}
setToggleState(toggle, !isActive);
selectNotch("cpuThreadsNotch", await getCPUs() - 1, 8);
}
async function setBoostNotch(index) {
const ROOT_ID = "frequencyNotch";
await setMaxBoost(index);
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) {
const ROOT_ID = "fanNotch";
await setFanTick(index);
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() {
let power1_cap = await getGPUPower(1);
let power2_cap = await getGPUPower(2);
if (power1_cap <= 0) {
selectNotch("slowPPTNotch", 0, 3);
document.getElementById("slowPPTAutoDefault").innerText = "Default";
} else if (power1_cap > 15000000) {
selectNotch("slowPPTNotch", 2, 3);
document.getElementById("slowPPTAutoDefault").innerText = "Default";
} else {
selectNotch("slowPPTNotch", 1, 3);
}
if (power2_cap <= 0) {
selectNotch("fastPPTNotch", 0, 3);
document.getElementById("fastPPTAutoDefault").innerText = "Default";
} else if (power2_cap > 15000000) {
selectNotch("fastPPTNotch", 2, 3);
document.getElementById("fastPPTAutoDefault").innerText = "Default";
} else {
selectNotch("fastPPTNotch", 1, 3);
}
}
async function onSetSlowPPTNotch(index) {
const ROOT_ID = "slowPPTNotch";
document.getElementById("slowPPTAutoDefault").innerText = "Default";
if (index == 0) {
await setGPUPower(0, 1);
} else if (index == 1) {
await setGPUPower(15000000, 1);
} else {
await setGPUPower(29000000, 1);
}
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) {
const ROOT_ID = "fastPPTNotch";
document.getElementById("fastPPTAutoDefault").innerText = "Default";
if (index == 0) {
await setGPUPower(0, 2);
} else if (index == 1) {
await setGPUPower(15000000, 2);
} else {
await setGPUPower(30000000, 2);
}
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) {
// WARNING: this yeets any style in div of slider
const ENABLED_CLASS = "gamepadslider_TickActive_1gnUV";
//console.log("Selecting notches up to " + index);
let root = document.getElementById(rootId);
root.style = "--normalized-slider-value:" + index/(elements-1) + ";";
for (let i = 0; i < elements; i++) {
let notch = document.getElementById(rootId + i);
if (notch.classList.contains(ENABLED_CLASS) && i > index) {
notch.classList.remove(ENABLED_CLASS);
} else if (!notch.classList.contains(ENABLED_CLASS) && i <= index) {
notch.classList.add(ENABLED_CLASS);
}
}
}
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() {
//console.log("Updating battery stats");
let batCapacityNow = document.getElementById("batCapacityNow");
let batCapacityFull = document.getElementById("batCapacityFull");
let chargeNow = await getChargeNow();
let chargeFull = await getChargeFull();
let chargeDesign = await getChargeDesign();
batCapacityNow.innerText = (7.7 * chargeNow / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeNow / chargeFull).toFixed(1).toString() + "%)";
batCapacityFull.innerText = (7.7 * chargeFull / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeFull / chargeDesign).toFixed(1).toString() + "%)";
}
async function togglePersist() {
let toggle = document.getElementById("persistToggle");
let isActive = getToggleState(toggle);
await setPersistent(!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;
async function updateVersion() {
let version = await getVersion();
let target = document.getElementById("versionStr");
target.innerText = "v" + version;
if (versionCount >= 9) {
target.innerText += " by NGnius ;) ";
versionCount = 0;
} else {
versionCount += 1;
}
}
</script>
<style type="text/css" media="screen"></style>
</head>
<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 -->
<!-- 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 -->
<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">
<div class="quickaccesscontrols_PanelSection_2C0g0" style="">
<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="toggleCPUSMT()">
<div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">
CPU SMT
</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div id="smtToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable" >
<div class="gamepaddialog_ToggleRail_2JtC3"></div>
<div class="gamepaddialog_ToggleSwitch_3__OD"></div>
</div>
</div>
</div>
<div class="gamepaddialog_FieldDescription_2OJfk">Enables odd-numbered CPUs</div>
</div>
</div>
<!-- CPUs selector -->
<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_FieldLabel_3b0U-">Threads</div>
</div>
<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_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi">
<div class="gamepadslider_SliderHandle_2yVKj"></div>
</div>
</div>
<div class="gamepadslider_SliderNotchContainer_2N-a5 Panel Focusable">
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="cpuThreadsNotch0" class="gamepadslider_SliderNotchTick_Fv1Ht" onclick='setCPUNotch(1)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">1</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="cpuThreadsNotch1" class="gamepadslider_SliderNotchTick_Fv1Ht" onclick='setCPUNotch(2)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">2</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="cpuThreadsNotch2" class="gamepadslider_SliderNotchTick_Fv1Ht" onclick='setCPUNotch(3)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">3</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="cpuThreadsNotch3" class="gamepadslider_SliderNotchTick_Fv1Ht" onclick='setCPUNotch(4)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">4</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="cpuThreadsNotch4" class="gamepadslider_SliderNotchTick_Fv1Ht" onclick='setCPUNotch(5)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">5</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="cpuThreadsNotch5" class="gamepadslider_SliderNotchTick_Fv1Ht" onclick='setCPUNotch(6)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">6</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="cpuThreadsNotch6" class="gamepadslider_SliderNotchTick_Fv1Ht" onclick='setCPUNotch(7)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">7</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="cpuThreadsNotch7" class="gamepadslider_SliderNotchTick_Fv1Ht" onclick='setCPUNotch(8)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">8</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- CPU Boost toggle switch, roughly copied from https://github.com/SteamDeckHomebrew/ExtraSettingsPlugin/blob/main/main_view.html -->
<div class="quickaccesscontrols_PanelSection_2C0g0" style="">
<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="toggleCPUBoost()">
<div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">
CPU Boost
</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div id="boostToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable">
<div class="gamepaddialog_ToggleRail_2JtC3"></div>
<div class="gamepaddialog_ToggleSwitch_3__OD"></div>
</div>
</div>
</div>
<div class="gamepaddialog_FieldDescription_2OJfk">Allows the CPU to go above max frequency</div>
</div>
</div>
<!-- Frequency selector -->
<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_FieldLabel_3b0U-">Max Frequency</div>
</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<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_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi">
<div class="gamepadslider_SliderHandle_2yVKj"></div>
</div>
</div>
<div class="gamepadslider_SliderNotchContainer_2N-a5 Panel Focusable">
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="frequencyNotch0" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='setBoostNotch(0)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1" style="margin-left:2em;">1.7GHz</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="frequencyNotch1" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='setBoostNotch(1)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">2.4GHz</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="frequencyNotch2" class="gamepadslider_SliderNotchTick_Fv1Ht" onclick='setBoostNotch(2)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1" style="margin-right:2em;">2.8GHz</div>
</div>
</div>
</div>
</div>
<div style="font-size:x-small;">
WARNING: This will change the CPU governor.
</div>
</div>
</div>
</div>
<!-- GPU -->
<div class="quickaccesscontrols_PanelSection_2C0g0" style="">
<!-- 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_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">GPU SlowPPT Power</div>
</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<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_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi">
<div class="gamepadslider_SliderHandle_2yVKj"></div>
</div>
</div>
<div class="gamepadslider_SliderNotchContainer_2N-a5 Panel Focusable">
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="slowPPTNotch0" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetSlowPPTNotch(0)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">0</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="slowPPTNotch1" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetSlowPPTNotch(1)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1" id="slowPPTAutoDefault">Auto</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="slowPPTNotch2" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetSlowPPTNotch(2)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">Max</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- FastPPT power limit (number 2) -->
<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_FieldLabel_3b0U-">GPU FastPPT Power</div>
</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<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_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi">
<div class="gamepadslider_SliderHandle_2yVKj"></div>
</div>
</div>
<div class="gamepadslider_SliderNotchContainer_2N-a5 Panel Focusable">
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fastPPTNotch0" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFastPPTNotch(0)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">0</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fastPPTNotch1" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFastPPTNotch(1)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1" id="fastPPTAutoDefault">Auto</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fastPPTNotch2" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFastPPTNotch(2)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">Max</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 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 -->
<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_FieldLabel_3b0U-">Fan RPM</div>
</div>
<div class="gamepaddialog_FieldDescription_2OJfk" style="display:none;">Requires disabling updated fan control</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<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_SliderTrack_Mq25N gamepadslider_SliderHasNotches_2XiAy "></div>
<div class="gamepadslider_SliderHandleContainer_1pQZi">
<div class="gamepadslider_SliderHandle_2yVKj"></div>
</div>
</div>
<div class="gamepadslider_SliderNotchContainer_2N-a5 Panel Focusable">
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fanNotch0" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFanNotch(0)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">0</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fanNotch1" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFanNotch(1)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">1K</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fanNotch2" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFanNotch(2)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">2K</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fanNotch3" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFanNotch(3)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">3K</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fanNotch4" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFanNotch(4)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">4K</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fanNotch5" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFanNotch(5)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">5K</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fanNotch6" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFanNotch(6)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">6K</div>
</div>
<div class="gamepadslider_SliderNotch_3x6ve">
<div id="fanNotch7" class="gamepadslider_SliderNotchTick_Fv1Ht gamepadslider_TickActive_j418S" onclick='onSetFanNotch(7)'></div>
<div class="gamepadslider_SliderNotchLabel_u_sH1">Auto</div>
</div>
</div>
</div>
</div>
<div style="font-size:x-small;">
WARNING: This can cause component overheating.
</div>
</div>
</div>
</div>
<!-- Battery Info -->
<div class="quickaccesscontrols_PanelSection_2C0g0" style="" onclick="updateBatteryStats()" style="margin-bottom:0px;">
<div class="quickaccesscontrols_PanelSectionTitle_2iFf9">
<div class="quickaccesscontrols_Text_1hJkB">Battery</div>
</div>
<div class="Panel Focusable" tabindex="0">
<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 (Charge)</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div class="gamepaddialog_LabelFieldValue_5Mylh" id="batCapacityNow"> :'( (|-_-|) </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-">Max (Design)</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div class="gamepaddialog_LabelFieldValue_5Mylh" id="batCapacityFull"> 9000+ (420%) </div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="quickaccesscontrols_PanelSection_2C0g0" style="">
<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="togglePersist()">
<div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">
Persistent
</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div id="persistToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable">
<div class="gamepaddialog_ToggleRail_2JtC3"></div>
<div class="gamepaddialog_ToggleSwitch_3__OD"></div>
</div>
</div>
</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 class="quickaccesscontrols_PanelSectionRow_2VQ88" onclick="updateVersion()">
<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-">PowerTools</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div class="gamepaddialog_LabelFieldValue_5Mylh" id="versionStr"> v0.42.0 </div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

45
package.json Normal file
View file

@ -0,0 +1,45 @@
{
"name": "PowerTools",
"version": "0.7.0",
"description": "Power tweaks for power users",
"scripts": {
"build": "shx rm -rf dist && rollup -c",
"watch": "rollup -c -w",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/NGnius/PowerTools.git"
},
"keywords": [
"plugin",
"utility",
"power-management",
"steam-deck",
"deck"
],
"author": "NGnius (Graham) <ngniusness@gmail.com>",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/NGnius/PowerTools/issues"
},
"homepage": "https://github.com/NGnius/PowerTools#readme",
"devDependencies": {
"@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.2.1",
"@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-typescript": "^8.3.2",
"@types/react": "16.14.0",
"@types/webpack": "^5.28.0",
"rollup": "^2.70.2",
"rollup-plugin-import-assets": "^1.1.1",
"shx": "^0.3.4",
"tslib": "^2.4.0",
"typescript": "^4.6.4"
},
"dependencies": {
"decky-frontend-lib": "*",
"react-icons": "^4.3.1"
}
}

View file

@ -1,8 +1,6 @@
{
"name": "Power Tools",
"name": "PowerTools",
"author": "NGnius",
"main_view_html": "main_view.html",
"tile_view_html": "",
"flags": ["root", "debug"],
"publish": {
"discord_id": "106537989684887552",

37
rollup.config.js Normal file
View file

@ -0,0 +1,37 @@
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import typescript from '@rollup/plugin-typescript';
import { defineConfig } from 'rollup';
import importAssets from 'rollup-plugin-import-assets';
import { name } from "./plugin.json";
export default defineConfig({
input: './src/index.tsx',
plugins: [
commonjs(),
nodeResolve(),
typescript(),
json(),
replace({
preventAssignment: false,
'process.env.NODE_ENV': JSON.stringify('production'),
}),
importAssets({
publicPath: `http://127.0.0.1:1337/plugins/${name}/`
})
],
context: 'window',
external: ['react', 'react-dom'],
output: {
file: 'dist/index.js',
globals: {
react: 'SP_REACT',
'react-dom': 'SP_REACTDOM',
},
format: 'iife',
exports: 'default',
},
});

View file

@ -7,7 +7,7 @@ import pathlib
from aiohttp import web
import aiohttp
HOME_DIR = str(pathlib.Path(os.getcwd()).parent.parent.resolve())
HOME_DIR = "/home/deck"
SETTINGS_DIR = HOME_DIR + "/.config/powertools"
if not os.path.exists(SETTINGS_DIR):
@ -60,6 +60,15 @@ class Server(web.Application):
def game(self) -> GameInfo:
return self.current_game
def set_game(self, game_id, data):
self.current_game = GameInfo(game_id, data)
def unset_game(self, game_id):
if self.current_game is None:
return
if game_id is None or self.current_game.gameid == game_id:
self.current_game = None
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
@ -97,16 +106,13 @@ class Server(web.Application):
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
self.current_game = None
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
self.current_game = None
return web.Response(status=204, headers={"Access-Control-Allow-Origin": "*"})
async def self_destruct(self, request):

368
src/index.tsx Executable file
View file

@ -0,0 +1,368 @@
import {
//ButtonItem,
definePlugin,
DialogButton,
//Menu,
//MenuItem,
PanelSection,
PanelSectionRow,
//Router,
ServerAPI,
//showContextMenu,
staticClasses,
Slider,
Toggle,
//NotchLabel
gamepadDialogClasses,
joinClassNames,
} from "decky-frontend-lib";
import { VFC, useState } from "react";
import { FaShip } from "react-icons/fa";
import * as python from "./python";
//import logo from "../assets/logo.png";
// interface AddMethodArgs {
// left: number;
// right: number;
// }
var firstTime: boolean = true;
var versionGlobal: string = "0.0.0-jank";
var periodicHook: NodeJS.Timer | null = null;
var lastGame: string = "";
var lifetimeHook: any = null;
var startHook: any = null;
var reload = function(){};
const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
// const [result, setResult] = useState<number | undefined>();
// const onClick = async () => {
// const result = await serverAPI.callPluginMethod<AddMethodArgs, number>(
// "add",
// {
// left: 2,
// right: 2,
// }
// );
// if (result.success) {
// setResult(result.result);
// }
// };
python.setServer(serverAPI);
const [smtGlobal, setSMT] = useState<boolean>(true);
const [cpusGlobal, setCPUs] = useState<number>(8);
const [boostGlobal, setBoost] = useState<boolean>(true);
const [freqGlobal, setFreq] = useState<number>(8);
const [slowPPTGlobal, setSlowPPT] = useState<number>(1);
const [fastPPTGlobal, setFastPPT] = useState<number>(1);
const [chargeNowGlobal, setChargeNow] = useState<number>(40);
const [chargeFullGlobal, setChargeFull] = useState<number>(40);
const [chargeDesignGlobal, setChargeDesign] = useState<number>(40);
const [persistGlobal, setPersist] = useState<boolean>(false);
const [perGameProfileGlobal, setPerGameProfile] = useState<boolean>(false);
const [gameGlobal, setGame] = useState<string>("with your mom");
reload = function () {
python.execute(python.onViewReady());
python.resolve(python.getSMT(), setSMT);
python.resolve(python.getCPUs(), setCPUs);
python.resolve(python.getCPUBoost(), setBoost);
python.resolve(python.getMaxBoost(), setFreq);
python.resolve(python.getGPUPowerI(1), setSlowPPT);
python.resolve(python.getGPUPowerI(2), setFastPPT);
python.resolve(python.getPersistent(), setPersist);
python.resolve(python.getPerGameProfile(), setPerGameProfile);
};
if (firstTime) {
firstTime = false;
reload(); // technically it's just load, not reload ;)
python.resolve(python.getChargeNow(), setChargeNow);
python.resolve(python.getChargeFull(), setChargeFull);
python.resolve(python.getChargeDesign(), setChargeDesign);
python.resolve(python.getCurrentGame(), setGame);
periodicHook = setInterval(function() {
python.resolve(python.getChargeNow(), setChargeNow);
python.resolve(python.getChargeFull(), setChargeFull);
python.resolve(python.getCurrentGame(), (game: string) => {
if (lastGame != game) {
setGame(game);
lastGame = game;
reload();
}
});
}, 1000);
python.resolve(python.getVersion(), (v: string) => {versionGlobal = v;});
//@ts-ignore
lifetimeHook = SteamClient.GameSessions.RegisterForAppLifetimeNotifications((update) => {
if (update.bRunning) {
console.log("AppID " + update.unAppID.toString() + " is now running");
} else {
console.log("AppID " + update.unAppID.toString() + " is no longer running");
python.execute(python.onGameStop(null));
}
});
//@ts-ignore
SteamClient.Apps.RegisterForGameActionStart((actionType, id) => {
//@ts-ignore
let gameInfo: any = appStore.GetAppOverviewByGameID(id);
python.execute(python.onGameStart(id, gameInfo));
});
}
const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard);
return (
<PanelSection>
{/* CPU */}
<div className={staticClasses.PanelSectionTitle}>
CPU
</div>
<PanelSectionRow>
<Toggle
checked={smtGlobal}
label="SMT"
description="Enables odd-numbered CPUs"
onChange={(smt: boolean) => {
console.log("SMT is now " + smt.toString());
python.execute(python.setCPUs(cpusGlobal, smt));
python.resolve(python.getCPUs(), setCPUs);
python.resolve(python.getSMT(), setSMT);
}}
/>
</PanelSectionRow>
<PanelSectionRow>
<Slider
label="Threads"
value={cpusGlobal}
step={1}
max={smtGlobal? 8 : 4}
min={1}
showValue={true}
onChange={(cpus: number) => {
console.log("CPU slider is now " + cpus.toString());
if (cpus != cpusGlobal) {
python.execute(python.setCPUs(cpus, smtGlobal));
python.resolve(python.getCPUs(), setCPUs);
}
}}
/>
</PanelSectionRow>
<PanelSectionRow>
<Toggle
checked={boostGlobal}
label="Boost"
description="Allows the CPU to go above max frequency"
onChange={(boost: boolean) => {
console.log("Boost is now " + boost.toString());
python.execute(python.setCPUBoost(boost));
python.resolve(python.getCPUBoost(), setBoost);
}}
/>
</PanelSectionRow>
<PanelSectionRow>
<Slider
label="Max Frequency"
value={freqGlobal}
max={2}
min={0}
notchCount={3}
notchLabels={[
{notchIndex: 0, label: "1.7GHz"},
{notchIndex: 1, label: "2.4GHz"},
{notchIndex: 2, label: "2.8GHz"},
]}
notchTicksVisible={true}
onChange={(freq: number) => {
console.log("CPU slider is now " + freq.toString());
if (freq != freqGlobal) {
python.execute(python.setMaxBoost(freq));
python.resolve(python.getMaxBoost(), setFreq);
}
}}
/>
</PanelSectionRow>
{/* GPU */}
<div className={staticClasses.PanelSectionTitle}>
GPU
</div>
<PanelSectionRow>
{/* index: 1 */}
<Slider
label="SlowPPT Power"
value={slowPPTGlobal}
max={2}
min={0}
notchCount={3}
notchLabels={[
{notchIndex: 0, label: "Min"},
{notchIndex: 1, label: "Auto"},
{notchIndex: 2, label: "Max"},
]}
notchTicksVisible={true}
onChange={(ppt: number) => {
console.log("SlowPPT is now " + ppt.toString());
if (ppt != slowPPTGlobal) {
python.execute(python.setGPUPowerI(ppt, 1));
python.resolve(python.getGPUPowerI(1), setSlowPPT);
}
}}
/>
</PanelSectionRow>
<PanelSectionRow>
{/* index: 2 */}
<Slider
label="FastPPT Power"
value={fastPPTGlobal}
max={2}
min={0}
notchCount={3}
notchLabels={[
{notchIndex: 0, label: "Min"},
{notchIndex: 1, label: "Auto"},
{notchIndex: 2, label: "Max"},
]}
notchTicksVisible={true}
onChange={(ppt: number) => {
console.log("FastPPT is now " + ppt.toString());
if (ppt != fastPPTGlobal) {
python.execute(python.setGPUPowerI(ppt, 2));
python.resolve(python.getGPUPowerI(2), setFastPPT);
}
}}
/>
</PanelSectionRow>
{/* Battery */}
<div className={staticClasses.PanelSectionTitle}>
Battery
</div>
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>
Now (Charge)
</div>
<div className={gamepadDialogClasses.FieldChildren}>
{(7.7 * chargeNowGlobal / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeNowGlobal / chargeFullGlobal).toFixed(1).toString() + "%)"}
</div>
</div>
</div>
</PanelSectionRow>
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>
Max (Design)
</div>
<div className={gamepadDialogClasses.FieldChildren}>
{(7.7 * chargeFullGlobal / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeFullGlobal / chargeDesignGlobal).toFixed(1).toString() + "%)"}
</div>
</div>
</div>
</PanelSectionRow>
{/* Persistence */}
<PanelSectionRow>
<Toggle
checked={persistGlobal}
label="Persistent"
description="Restores settings after an app or OS restart"
onChange={(persist: boolean) => {
console.log("Persist is now " + persist.toString());
python.execute(python.setPersistent(persist));
python.resolve(python.getPersistent(), setPersist);
}}
/>
</PanelSectionRow>
<PanelSectionRow>
<Toggle
checked={perGameProfileGlobal}
label="Use per-game profile"
onChange={(p: boolean) => {
console.log("Per game profile is now " + p.toString());
python.execute(python.setPerGameProfile(p));
python.resolve(python.getPerGameProfile(), setPerGameProfile);
reload();
}}
/>
</PanelSectionRow>
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>
Now Playing
</div>
<div className={gamepadDialogClasses.FieldChildren}>
{gameGlobal}
</div>
</div>
</div>
</PanelSectionRow>
{/* Version */}
<div className={staticClasses.PanelSectionTitle}>
Debug
</div>
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>
PowerTools
</div>
<div className={gamepadDialogClasses.FieldChildren}>
v{versionGlobal}
</div>
</div>
</div>
</PanelSectionRow>
</PanelSection>
);
};
const DeckyPluginRouterTest: VFC = () => {
return (
<div style={{ marginTop: "50px", color: "white" }}>
Hello World!
<DialogButton onClick={() => {}}>
Go to Store
</DialogButton>
</div>
);
};
export default definePlugin((serverApi: ServerAPI) => {
serverApi.routerHook.addRoute("/decky-plugin-test", DeckyPluginRouterTest, {
exact: true,
});
return {
title: <div className={staticClasses.Title}>PowerTools</div>,
content: <Content serverAPI={serverApi} />,
icon: <FaShip />,
onDismount() {
console.log("PowerTools shutting down");
clearInterval(periodicHook!);
lifetimeHook.unregister();
startHook.unregister();
serverApi.routerHook.removeRoute("/decky-plugin-test");
},
};
});

133
src/python.ts Normal file
View file

@ -0,0 +1,133 @@
import { ServerAPI } from "decky-frontend-lib";
var server: ServerAPI | undefined = undefined;
//import { useEffect } from "react";
export function resolve(promise: Promise<any>, setter: any) {
(async function () {
let data = await promise;
console.log("Got resolved", data);
if (data.success) {
setter(data.result);
} else {
console.log("Resolve failed:", data);
}
})();
}
export function execute(promise: Promise<any>) {
(async function () {
let data = await promise;
console.log("Got executed", data);
})();
}
export function setServer(s: ServerAPI) {
server = s;
}
// Python functions
export function getVersion(): Promise<any> {
return server!.callPluginMethod("get_version", {});
}
export function onViewReady(): Promise<any> {
return server!.callPluginMethod("on_ready", {});
}
export function setCPUs(value: number, smt: boolean): Promise<any> {
return server!.callPluginMethod("set_cpus", {"count":value, "smt": smt});
}
export function getCPUs(): Promise<any> {
return server!.callPluginMethod("get_cpus", {});
}
export function getSMT(): Promise<any> {
return server!.callPluginMethod("get_smt", {});
}
export function setCPUBoost(value: boolean): Promise<any> {
return server!.callPluginMethod("set_boost", {"enabled": value});
}
export function getCPUBoost(): Promise<any> {
return server!.callPluginMethod("get_boost", {});
}
export function setMaxBoost(index: number): Promise<any> {
return server!.callPluginMethod("set_max_boost", {"index": index});
}
export function getMaxBoost(): Promise<any> {
return server!.callPluginMethod("get_max_boost", {});
}
export function setGPUPower(value: number, index: number): Promise<any> {
return server!.callPluginMethod("set_gpu_power", {"value": value, "power_number": index});
}
export function getGPUPower(index: number): Promise<any> {
return server!.callPluginMethod("get_gpu_power", {"power_number": index});
}
export function setGPUPowerI(value: number, index: number): Promise<any> {
return server!.callPluginMethod("set_gpu_power_index", {"index": value, "power_number": index});
}
export function getGPUPowerI(index: number): Promise<any> {
return server!.callPluginMethod("get_gpu_power_index", {"power_number": index});
}
export function setFanTick(tick: number): Promise<any> {
return server!.callPluginMethod("set_fan_tick", {"tick": tick});
}
export function getFanTick(): Promise<any> {
return server!.callPluginMethod("get_fan_tick", {});
}
export function getFantastic(): Promise<any> {
return server!.callPluginMethod("fantastic_installed", {});
}
export function getChargeNow(): Promise<any> {
return server!.callPluginMethod("get_charge_now", {});
}
export function getChargeFull(): Promise<any> {
return server!.callPluginMethod("get_charge_full", {});
}
export function getChargeDesign(): Promise<any> {
return server!.callPluginMethod("get_charge_design", {});
}
export function setPersistent(value: boolean): Promise<any> {
return server!.callPluginMethod("set_persistent", {"enabled": value});
}
export function getPersistent(): Promise<any> {
return server!.callPluginMethod("get_persistent", {});
}
export function setPerGameProfile(value: boolean): Promise<any> {
return server!.callPluginMethod("set_per_game_profile", {"enabled": value});
}
export function getPerGameProfile(): Promise<any> {
return server!.callPluginMethod("get_per_game_profile", {});
}
export function getCurrentGame(): Promise<any> {
return server!.callPluginMethod("get_current_game", {});
}
export function onGameStart(gameId: number, data: any): Promise<any> {
return server!.callPluginMethod("on_game_start", {"game_id": gameId, "data":data});
}
export function onGameStop(gameId: number | null): Promise<any> {
return server!.callPluginMethod("on_game_stop", {"game_id": gameId});
}

14
src/types.d.ts vendored Normal file
View file

@ -0,0 +1,14 @@
declare module "*.svg" {
const content: string;
export default content;
}
declare module "*.png" {
const content: string;
export default content;
}
declare module "*.jpg" {
const content: string;
export default content;
}

View file

23
tsconfig.json Normal file
View file

@ -0,0 +1,23 @@
{
"compilerOptions": {
"outDir": "dist",
"module": "ESNext",
"target": "ES2020",
"jsx": "react",
"jsxFactory": "window.SP_REACT.createElement",
"declaration": false,
"moduleResolution": "node",
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strict": true,
"suppressImplicitAnyIndexErrors": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["node_modules"]
}