forked from NG-SD-Plugins/PowerTools
Merge branch 'dev'
This commit is contained in:
commit
1182ab11df
18 changed files with 852 additions and 869 deletions
42
.gitignore
vendored
42
.gitignore
vendored
|
@ -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
|
||||
|
|
41
README.md
41
README.md
|
@ -1,10 +1,10 @@
|
|||
# PowerTools
|
||||
|
||||
![plugin_demo](./extras/ui.png)
|
||||
![plugin_demo](./assets/ui.png)
|
||||
|
||||
Steam Deck power tweaks for power users.
|
||||
|
||||
This is generated from the template plugin for the [SteamOS Plugin Loader](https://github.com/SteamDeckHomebrew/PluginLoader).
|
||||
This is generated from the template plugin for the [Decky Plugin Loader](https://github.com/SteamDeckHomebrew/decky-loader).
|
||||
You will need that installed for this plugin to work.
|
||||
|
||||
## What does it do?
|
||||
|
@ -12,7 +12,6 @@ You will need that installed for this plugin to work.
|
|||
- Enable & disable CPU threads & SMT
|
||||
- Set CPU max frequency and toggle boost
|
||||
- Set some GPU power parameters (fastPPT & slowPPT)
|
||||
- Set the fan RPM (unsupported on SteamOS beta)
|
||||
- Display supplementary battery info
|
||||
- Keep settings between restarts (stored in `~/.config/powertools/<appid>.json`)
|
||||
|
||||
|
@ -52,6 +51,8 @@ Get the entry limits for those two commands with `cat /sys/class/hwmon/hwmon4/po
|
|||
|
||||
### Set Fan speed
|
||||
|
||||
NOTE: PowerTools no longer supports this, since [Fantastic](https://github.com/NGnius/Fantastic) does it much better.
|
||||
|
||||
Enable automatic control: `echo 0 > /sys/class/hwmon/hwmon5/recalculate` enables automatic fan control.
|
||||
|
||||
Disable automatic control: `echo 1 > /sys/class/hwmon/hwmon5/recalculate` disables automatic (temperature-based) fan control and starts using the set fan target instead.
|
||||
|
@ -73,6 +74,8 @@ Get the design battery capacity: `cat /sys/class/hwmon/hwmon2/device/charge_full
|
|||
|
||||
Get whether the deck is plugged in: `cat /sys/class/hwmon/hwmon5/curr1_input` gives the charger current in mA.
|
||||
|
||||
NOTE: 7.7 is the voltage of the battery -- it's not just a magic number.
|
||||
|
||||
### Steam Deck kernel patches
|
||||
|
||||
This is how I figured out how the fan stuff works.
|
||||
|
@ -81,22 +84,26 @@ 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));
|
||||
});`
|
||||
);
|
||||
```typescript
|
||||
//@ts-ignore
|
||||
let 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");
|
||||
// game exit code here
|
||||
// NOTE: custom games always have 0 as AppID, so AppID is bad to use as ID
|
||||
}
|
||||
});
|
||||
//@ts-ignore
|
||||
let startHook = SteamClient.Apps.RegisterForGameActionStart((actionType, id) => {
|
||||
//@ts-ignore
|
||||
let gameInfo: any = appStore.GetAppOverviewByGameID(id);
|
||||
// game start code here
|
||||
// NOTE: GameID (variable: id) is always unique, even for custom games, so it's better to use than AppID
|
||||
});
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
This is licensed under GNU GPLv3.
|
||||
|
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
assets/thumbnail.png
Normal file
BIN
assets/thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 290 KiB |
BIN
assets/ui.png
Normal file
BIN
assets/ui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
BIN
extras/ui.png
BIN
extras/ui.png
Binary file not shown.
Before Width: | Height: | Size: 202 KiB |
89
main.py
89
main.py
|
@ -5,8 +5,8 @@ import asyncio
|
|||
import pathlib
|
||||
import subprocess
|
||||
|
||||
VERSION = "0.6.0"
|
||||
HOME_DIR = str(pathlib.Path(os.getcwd()).parent.parent.resolve())
|
||||
VERSION = "0.7.0"
|
||||
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,14 @@ 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
|
||||
old_gameid = None
|
||||
ready = False
|
||||
|
||||
async def get_version(self) -> str:
|
||||
return VERSION
|
||||
|
@ -139,6 +143,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 +170,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 +210,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):
|
||||
|
@ -304,26 +329,7 @@ class Plugin:
|
|||
if self.modified_settings and self.persistent:
|
||||
self.save_settings(self)
|
||||
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}")
|
||||
#self.reload_current_settings(self)
|
||||
|
||||
await asyncio.sleep(1)
|
||||
await pt_server.shutdown()
|
||||
|
@ -331,6 +337,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
|
||||
|
@ -373,6 +382,29 @@ class Plugin:
|
|||
settings["auto"] = self.auto_fan
|
||||
return settings
|
||||
|
||||
def reload_current_settings(self):
|
||||
logging.debug(f"gameid update: {self.old_gameid} -> {self.current_gameid}")
|
||||
if self.persistent:
|
||||
# per-game profiles
|
||||
current_game = pt_server.http_server.game()
|
||||
self.old_gameid = self.current_gameid
|
||||
if current_game is not None and current_game.has_settings():
|
||||
self.current_gameid = current_game.gameid
|
||||
if self.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 self.old_gameid != None:
|
||||
logging.info("Reapplying default settings; game without custom settings found")
|
||||
self.old_gameid = None
|
||||
# game without custom settings; apply defaults
|
||||
settings = read_json(DEFAULT_SETTINGS_LOCATION)
|
||||
self.apply_settings(self, settings)
|
||||
|
||||
def save_settings(self):
|
||||
settings = self.current_settings(self)
|
||||
logging.debug(f"Saving settings to file: {settings}")
|
||||
|
@ -439,7 +471,18 @@ class Plugin:
|
|||
self.current_gameid = None
|
||||
|
||||
async def get_per_game_profile(self) -> bool:
|
||||
return self.current_gameid is not None
|
||||
current_game = pt_server.http_server.game()
|
||||
return current_game 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)
|
||||
self.reload_current_settings(self)
|
||||
return True
|
||||
|
||||
async def on_game_stop(self, game_id: int) -> bool:
|
||||
pt_server.http_server.unset_game(game_id)
|
||||
self.reload_current_settings(self)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
|
746
main_view.html
746
main_view.html
|
@ -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
45
package.json
Normal 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.4.0"
|
||||
}
|
||||
}
|
|
@ -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
37
rollup.config.js
Normal 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',
|
||||
},
|
||||
});
|
89
server.py
89
server.py
|
@ -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):
|
||||
|
@ -42,99 +42,30 @@ class GameInfo:
|
|||
return os.path.exists(self.settings_path())
|
||||
|
||||
|
||||
class Server(web.Application):
|
||||
class Server:
|
||||
|
||||
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)
|
||||
def set_game(self, game_id, data):
|
||||
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": "*"})
|
||||
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 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
|
||||
global http_server
|
||||
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
|
||||
global http_server
|
||||
http_server = None
|
||||
|
|
452
src/index.tsx
Executable file
452
src/index.tsx
Executable file
|
@ -0,0 +1,452 @@
|
|||
import {
|
||||
//ButtonItem,
|
||||
definePlugin,
|
||||
DialogButton,
|
||||
//Menu,
|
||||
//MenuItem,
|
||||
PanelSection,
|
||||
PanelSectionRow,
|
||||
//Router,
|
||||
ServerAPI,
|
||||
//showContextMenu,
|
||||
staticClasses,
|
||||
SliderField,
|
||||
ToggleField,
|
||||
//NotchLabel
|
||||
gamepadDialogClasses,
|
||||
joinClassNames,
|
||||
} from "decky-frontend-lib";
|
||||
import { VFC, useState } from "react";
|
||||
import { GiDrill } from "react-icons/gi";
|
||||
|
||||
import * as python from "./python";
|
||||
|
||||
//import logo from "../assets/logo.png";
|
||||
|
||||
// interface AddMethodArgs {
|
||||
// left: number;
|
||||
// right: number;
|
||||
// }
|
||||
|
||||
var firstTime: boolean = true;
|
||||
var versionGlobalHolder: string = "0.0.0-jank";
|
||||
var periodicHook: NodeJS.Timer | null = null;
|
||||
var lastGame: string = "";
|
||||
var lifetimeHook: any = null;
|
||||
var startHook: any = null;
|
||||
|
||||
var smt_backup: boolean = true;
|
||||
var cpus_backup: number = 8;
|
||||
var boost_backup: boolean = true;
|
||||
var freq_backup: number = 8;
|
||||
var slowPPT_backup: number = 1;
|
||||
var fastPPT_backup: number = 1;
|
||||
var chargeNow_backup: number = 5200000;
|
||||
var chargeFull_backup: number = 5200000;
|
||||
var chargeDesign_backup: number = 5200000;
|
||||
var persistent_backup: boolean = false;
|
||||
var perGameProfile_backup: boolean = false;
|
||||
|
||||
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_internal] = useState<boolean>(smt_backup);
|
||||
const setSMT = (value: boolean) => {
|
||||
smt_backup = value;
|
||||
setSMT_internal(value);
|
||||
};
|
||||
|
||||
const [cpusGlobal, setCPUs_internal] = useState<number>(cpus_backup);
|
||||
const setCPUs = (value: number) => {
|
||||
cpus_backup = value;
|
||||
setCPUs_internal(value);
|
||||
};
|
||||
|
||||
const [boostGlobal, setBoost_internal] = useState<boolean>(boost_backup);
|
||||
const setBoost = (value: boolean) => {
|
||||
boost_backup = value;
|
||||
setBoost_internal(value);
|
||||
};
|
||||
|
||||
const [freqGlobal, setFreq_internal] = useState<number>(freq_backup);
|
||||
const setFreq = (value: number) => {
|
||||
freq_backup = value;
|
||||
setFreq_internal(value);
|
||||
};
|
||||
|
||||
const [slowPPTGlobal, setSlowPPT_internal] = useState<number>(slowPPT_backup);
|
||||
const setSlowPPT = (value: number) => {
|
||||
slowPPT_backup = value;
|
||||
setSlowPPT_internal(value);
|
||||
};
|
||||
|
||||
const [fastPPTGlobal, setFastPPT_internal] = useState<number>(fastPPT_backup);
|
||||
const setFastPPT = (value: number) => {
|
||||
fastPPT_backup = value;
|
||||
setFastPPT_internal(value);
|
||||
};
|
||||
|
||||
const [chargeNowGlobal, setChargeNow_internal] = useState<number>(chargeNow_backup);
|
||||
const setChargeNow = (value: number) => {
|
||||
chargeNow_backup = value;
|
||||
setChargeNow_internal(value);
|
||||
};
|
||||
|
||||
const [chargeFullGlobal, setChargeFull_internal] = useState<number>(chargeFull_backup);
|
||||
const setChargeFull = (value: number) => {
|
||||
chargeFull_backup = value;
|
||||
setChargeFull_internal(value);
|
||||
};
|
||||
|
||||
const [chargeDesignGlobal, setChargeDesign_internal] = useState<number>(chargeDesign_backup);
|
||||
const setChargeDesign = (value: number) => {
|
||||
chargeDesign_backup = value;
|
||||
setChargeDesign_internal(value);
|
||||
};
|
||||
|
||||
const [persistGlobal, setPersist_internal] = useState<boolean>(persistent_backup);
|
||||
const setPersist = (value: boolean) => {
|
||||
persistent_backup = value;
|
||||
setPersist_internal(value);
|
||||
};
|
||||
|
||||
const [perGameProfileGlobal, setPerGameProfile_internal] = useState<boolean>(perGameProfile_backup);
|
||||
const setPerGameProfile = (value: boolean) => {
|
||||
perGameProfile_backup = value;
|
||||
setPerGameProfile_internal(value);
|
||||
};
|
||||
|
||||
const [gameGlobal, setGame_internal] = useState<string>(lastGame);
|
||||
const setGame = (value: string) => {
|
||||
lastGame = value;
|
||||
setGame_internal(value);
|
||||
};
|
||||
|
||||
const [versionGlobal, setVersion_internal] = useState<string>(versionGlobalHolder);
|
||||
const setVersion = (value: string) => {
|
||||
versionGlobalHolder = value;
|
||||
setVersion_internal(value);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
python.resolve(python.getVersion(), setVersion);
|
||||
}
|
||||
|
||||
if (periodicHook != null) {
|
||||
clearInterval(periodicHook);
|
||||
periodicHook = null;
|
||||
}
|
||||
|
||||
periodicHook = setInterval(function() {
|
||||
python.resolve(python.getChargeFull(), setChargeFull);
|
||||
python.resolve(python.getChargeNow(), setChargeNow);
|
||||
python.resolve(python.getCurrentGame(), (game: string) => {
|
||||
if (lastGame != game) {
|
||||
setGame(game);
|
||||
lastGame = game;
|
||||
reload();
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard);
|
||||
|
||||
return (
|
||||
<PanelSection>
|
||||
{/* CPU */}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
CPU
|
||||
</div>
|
||||
<PanelSectionRow>
|
||||
<ToggleField
|
||||
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>
|
||||
<SliderField
|
||||
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>
|
||||
<ToggleField
|
||||
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>
|
||||
<SliderField
|
||||
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 */}
|
||||
<SliderField
|
||||
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 */}
|
||||
<SliderField
|
||||
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>
|
||||
<ToggleField
|
||||
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>
|
||||
<ToggleField
|
||||
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,
|
||||
});
|
||||
|
||||
//@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
|
||||
startHook = SteamClient.Apps.RegisterForGameActionStart((actionType, id) => {
|
||||
//@ts-ignore
|
||||
let gameInfo: any = appStore.GetAppOverviewByGameID(id);
|
||||
python.execute(python.onGameStart(id, gameInfo));
|
||||
});
|
||||
|
||||
console.log("Registered PowerTools callbacks, hello!");
|
||||
|
||||
return {
|
||||
title: <div className={staticClasses.Title}>PowerTools</div>,
|
||||
content: <Content serverAPI={serverApi} />,
|
||||
icon: <GiDrill />,
|
||||
onDismount() {
|
||||
console.log("PowerTools shutting down");
|
||||
clearInterval(periodicHook!);
|
||||
periodicHook = null;
|
||||
lifetimeHook!.unregister();
|
||||
startHook!.unregister();
|
||||
serverApi.routerHook.removeRoute("/decky-plugin-test");
|
||||
firstTime = true;
|
||||
lastGame = "";
|
||||
console.log("Unregistered PowerTools callbacks, goodbye.");
|
||||
},
|
||||
};
|
||||
});
|
139
src/python.ts
Normal file
139
src/python.ts
Normal file
|
@ -0,0 +1,139 @@
|
|||
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;
|
||||
if (data.success) {
|
||||
console.debug("Got resolved", data, "promise", promise);
|
||||
setter(data.result);
|
||||
} else {
|
||||
console.warn("Resolve failed:", data, "promise", promise);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
export function execute(promise: Promise<any>) {
|
||||
(async function () {
|
||||
let data = await promise;
|
||||
if (data.success) {
|
||||
console.debug("Got executed", data, "promise", promise);
|
||||
} else {
|
||||
console.warn("Execute failed:", data, "promise", promise);
|
||||
}
|
||||
|
||||
})();
|
||||
}
|
||||
|
||||
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> {
|
||||
const data2 = {appid: data.appid, display_name: data.display_name, gameid: gameId}; // Issue #17
|
||||
return server!.callPluginMethod("on_game_start", {"game_id": gameId, "data":data2});
|
||||
}
|
||||
|
||||
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
14
src/types.d.ts
vendored
Normal 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;
|
||||
}
|
23
tsconfig.json
Normal file
23
tsconfig.json
Normal 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"]
|
||||
}
|
Loading…
Reference in a new issue