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 * as backend from "./backend"; import {set_value, get_value, target_usdpl, version_usdpl} from "usdpl-front"; var periodicHook: NodeJS.Timer | null = null; var lifetimeHook: any = null; var startHook: any = null; var usdplReady = false; var smtAllowed = true; var smtGlobal = smtAllowed; // usdpl persistent store keys const BACKEND_INFO = "VINFO"; const CURRENT_BATT = "BATTERY_current_now"; const CHARGE_RATE_BATT = "BATTERY_charge_rate"; const CHARGE_NOW_BATT = "BATTERY_charge_now"; const CHARGE_FULL_BATT = "BATTERY_charge_full"; const CHARGE_DESIGN_BATT = "BATTERY_charge_design" const TOTAL_CPUS = "CPUs_total"; const ONLINE_CPUS = "CPUs_online"; const CLOCK_MIN_CPU = "CPUs_min_clock"; const CLOCK_MAX_CPU = "CPUs_max_clock"; const GOVERNOR_CPU = "CPUs_governor"; const FAST_PPT_GPU = "GPU_fastPPT"; const SLOW_PPT_GPU = "GPU_slowPPT"; const CLOCK_MIN_GPU = "GPU_min_clock"; const CLOCK_MAX_GPU = "GPU_max_clock"; const SLOW_MEMORY_GPU = "GPU_slow_memory"; const PERSISTENT_GEN = "GENERAL_persistent"; const NAME_GEN = "GENERAL_name"; function countCpus(statii: boolean[]): number { let count = 0; for (let i = 0; i < statii.length; i++) { if (statii[i]) { count += 1; } } return count; } const reload = function() { if (!usdplReady) {return;} backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) }); backend.resolve(backend.getBatteryChargeRate(), (rate: number) => { set_value(CHARGE_RATE_BATT, rate) }); backend.resolve(backend.getBatteryChargeNow(), (rate: number) => { set_value(CHARGE_NOW_BATT, rate) }); backend.resolve(backend.getBatteryChargeFull(), (rate: number) => { set_value(CHARGE_FULL_BATT, rate) }); backend.resolve(backend.getBatteryChargeDesign(), (rate: number) => { set_value(CHARGE_DESIGN_BATT, rate) }); backend.resolve(backend.getCpuCount(), (count: number) => { set_value(TOTAL_CPUS, count)}); backend.resolve(backend.getCpusOnline(), (statii: boolean[]) => { // TODO: allow for per-core control of online status const count = countCpus(statii); set_value(ONLINE_CPUS, count); smtGlobal = statii.length > 3 && statii[0] == statii[1] && statii[2] == statii[3] && smtAllowed; }); // TODO: allow for per-core control of clock limits backend.resolve(backend.getCpuClockLimits(0), (limits: number[]) => { set_value(CLOCK_MIN_CPU, limits[0]); set_value(CLOCK_MAX_CPU, limits[1]); }); // TODO: allow for control of governor backend.resolve(backend.getCpusGovernor(), (governors: string[]) => { set_value(GOVERNOR_CPU, governors[0]) }); backend.resolve(backend.getGpuPpt(), (ppts: number[]) => { set_value(FAST_PPT_GPU, ppts[0]); set_value(SLOW_PPT_GPU, ppts[1]); }); backend.resolve(backend.getGpuClockLimits(), (limits: number[]) => { set_value(CLOCK_MIN_GPU, limits[0]); set_value(CLOCK_MAX_GPU, limits[1]); }); backend.resolve(backend.getGpuSlowMemory(), (status: boolean) => { set_value(SLOW_MEMORY_GPU, status) }); backend.resolve(backend.getGeneralPersistent(), (value: boolean) => { set_value(PERSISTENT_GEN, value) }); backend.resolve(backend.getGeneralSettingsName(), (name: string) => { set_value(NAME_GEN, name) }); backend.resolve(backend.getInfo(), (info: string) => { set_value(BACKEND_INFO, info) }); }; // init USDPL WASM and connection to back-end (async function(){ await backend.initBackend(); usdplReady = true; set_value(NAME_GEN, "Default"); reload(); // technically this is only a load // register Steam callbacks //@ts-ignore lifetimeHook = SteamClient.GameSessions.RegisterForAppLifetimeNotifications((update) => { if (update.bRunning) { //console.debug("AppID " + update.unAppID.toString() + " is now running"); } else { //console.debug("AppID " + update.unAppID.toString() + " is no longer running"); backend.resolve( backend.loadGeneralDefaultSettings(), (ok: boolean) => {console.debug("Loading default settings ok? " + ok)} ); } }); //@ts-ignore startHook = SteamClient.Apps.RegisterForGameActionStart((actionType, id) => { //@ts-ignore let gameInfo: any = appStore.GetAppOverviewByGameID(id); // don't use gameInfo.appid, haha backend.resolve( backend.loadGeneralSettings(id.toString() + ".json", gameInfo.display_name), (ok: boolean) => {console.debug("Loading settings ok? " + ok)} ); }); console.debug("Registered PowerTools callbacks, hello!"); })(); const periodicals = function() { backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) }); backend.resolve(backend.getBatteryChargeNow(), (rate: number) => { set_value(CHARGE_NOW_BATT, rate) }); backend.resolve(backend.getBatteryChargeFull(), (rate: number) => { set_value(CHARGE_FULL_BATT, rate) }); backend.resolve(backend.getGeneralPersistent(), (value: boolean) => { set_value(PERSISTENT_GEN, value) }); backend.resolve(backend.getGeneralSettingsName(), (name: string) => { const oldValue = get_value(NAME_GEN); set_value(NAME_GEN, name); if (name != oldValue) { reload(); } }); }; const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { const [_idc, reloadGUI] = useState("/shrug"); if (periodicHook != null) { clearInterval(periodicHook); periodicHook = null; } periodicHook = setInterval(function() { periodicals(); reloadGUI("periodic" + (new Date()).getTime().toString()); }, 1000); const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard); const total_cpus = get_value(TOTAL_CPUS); return ( {/* CPU */ /* TODO: set per-core stuff*/}
CPU
{smtAllowed && { console.debug("SMT is now " + smt.toString()); const cpus = get_value(ONLINE_CPUS); smtGlobal = smt && smtAllowed; // TODO: move SMT setting logic back to back-end let onlines: boolean[] = []; for (let i = 0; i < total_cpus; i++) { const online = (smtGlobal? i < cpus : (i % 2 == 0) && (i < cpus * 2)) || (!smtGlobal && cpus == 4); onlines.push(online); } backend.resolve(backend.setCpuOnlines(onlines), (statii: boolean[]) => { // TODO: allow for per-core control of online status const count = countCpus(statii); set_value(ONLINE_CPUS, count); reloadGUI("SMT"); }); }} /> } { console.debug("CPU slider is now " + cpus.toString()); const onlines = get_value(ONLINE_CPUS); if (cpus != onlines) { set_value(ONLINE_CPUS, cpus); let onlines: boolean[] = []; for (let i = 0; i < total_cpus; i++) { const online = smtGlobal? i < cpus : (i % 2 == 0) && (i < cpus * 2); onlines.push(online); } backend.resolve(backend.setCpuOnlines(onlines), (statii: boolean[]) => { // TODO: allow for per-core control of online status const count = countCpus(statii); set_value(ONLINE_CPUS, count); reloadGUI("CPUs"); }); reloadGUI("CPUsImmediate"); } }} /> { if (value) { set_value(CLOCK_MIN_CPU, 1400); set_value(CLOCK_MAX_CPU, 3500); reloadGUI("CPUFreqToggle"); } else { set_value(CLOCK_MIN_CPU, null); set_value(CLOCK_MAX_CPU, null); for (let i = 0; i < total_cpus; i++) { backend.resolve(backend.unsetCpuClockLimits(i), (_idc: any[]) => {}); } backend.resolve(backend.waitForComplete(), (_: boolean[]) => { reloadGUI("CPUUnsetFreq"); }); } }} /> {get_value(CLOCK_MIN_CPU) != null && { console.debug("Min freq slider is now " + freq.toString()); const freqNow = get_value(CLOCK_MIN_CPU); if (freq != freqNow) { set_value(CLOCK_MIN_CPU, freq); for (let i = 0; i < total_cpus; i++) { backend.resolve(backend.setCpuClockLimits(i, freq, get_value(CLOCK_MAX_CPU)), (limits: number[]) => { set_value(CLOCK_MIN_CPU, limits[0]); set_value(CLOCK_MAX_CPU, limits[1]); }); } backend.resolve(backend.waitForComplete(), (_: boolean[]) => { reloadGUI("CPUMinFreq"); }); reloadGUI("CPUMinFreqImmediate"); } }} />} {get_value(CLOCK_MAX_CPU) != null && { console.debug("Max freq slider is now " + freq.toString()); const freqNow = get_value(CLOCK_MAX_CPU); if (freq != freqNow) { set_value(CLOCK_MAX_CPU, freq); for (let i = 0; i < total_cpus; i++) { backend.resolve(backend.setCpuClockLimits(i, get_value(CLOCK_MIN_CPU), freq), (limits: number[]) => { set_value(CLOCK_MIN_CPU, limits[0]); set_value(CLOCK_MAX_CPU, limits[1]); }); } backend.resolve(backend.waitForComplete(), (_: boolean[]) => { reloadGUI("CPUMaxFreq"); }); reloadGUI("CPUMaxFreqImmediate"); } }} />} {/* TODO: CPU governor */} {/* GPU */}
GPU
{ if (value) { set_value(SLOW_PPT_GPU, 15000000); set_value(FAST_PPT_GPU, 15000000); reloadGUI("GPUPPTToggle"); } else { set_value(SLOW_PPT_GPU, null); set_value(FAST_PPT_GPU, null); backend.resolve(backend.unsetGpuPpt(), (_: any[]) => { reloadGUI("GPUUnsetPPT"); }); } }} /> { get_value(SLOW_PPT_GPU) != null && { console.debug("SlowPPT is now " + ppt.toString()); const pptNow = get_value(SLOW_PPT_GPU); if (ppt != pptNow) { backend.resolve(backend.setGpuPpt(get_value(FAST_PPT_GPU), ppt), (limits: number[]) => { set_value(FAST_PPT_GPU, limits[0]); set_value(SLOW_PPT_GPU, limits[1]); reloadGUI("GPUSlowPPT"); }); } }} />} {get_value(FAST_PPT_GPU) != null && { console.debug("FastPPT is now " + ppt.toString()); const pptNow = get_value(FAST_PPT_GPU); if (ppt != pptNow) { backend.resolve(backend.setGpuPpt(get_value(SLOW_PPT_GPU), ppt), (limits: number[]) => { set_value(FAST_PPT_GPU, limits[0]); set_value(SLOW_PPT_GPU, limits[1]); reloadGUI("GPUFastPPT"); }); } }} />} { if (value) { set_value(CLOCK_MIN_GPU, 200); set_value(CLOCK_MAX_GPU, 1600); reloadGUI("GPUFreqToggle"); } else { set_value(CLOCK_MIN_GPU, null); set_value(CLOCK_MAX_GPU, null); backend.resolve(backend.unsetGpuClockLimits(), (_: any[]) => { reloadGUI("GPUUnsetFreq"); }); } }} /> { get_value(CLOCK_MIN_GPU) != null && { console.debug("GPU Clock Min is now " + val.toString()); const valNow = get_value(CLOCK_MIN_GPU); if (val != valNow) { backend.resolve(backend.setGpuClockLimits(val, get_value(CLOCK_MAX_GPU)), (limits: number[]) => { set_value(CLOCK_MIN_GPU, limits[0]); set_value(CLOCK_MAX_GPU, limits[1]); reloadGUI("GPUMinClock"); }); } }} />} {get_value(CLOCK_MAX_GPU) != null && { console.debug("GPU Clock Max is now " + val.toString()); const valNow = get_value(CLOCK_MAX_GPU); if (val != valNow) { backend.resolve(backend.setGpuClockLimits(get_value(CLOCK_MIN_GPU), val), (limits: number[]) => { set_value(CLOCK_MIN_GPU, limits[0]); set_value(CLOCK_MAX_GPU, limits[1]); reloadGUI("GPUMaxClock"); }); } }} />} { backend.resolve(backend.setGpuSlowMemory(value), (val: boolean) => { set_value(SLOW_MEMORY_GPU, val); reloadGUI("GPUSlowMemory"); }) }} /> {/* Battery */}
Battery
Now (Charge)
{get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%)
Max (Design)
{get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%)
{ if (value) { set_value(CHARGE_RATE_BATT, 2500); reloadGUI("BATTChargeRateToggle"); } else { set_value(CHARGE_RATE_BATT, null); backend.resolve(backend.unsetBatteryChargeRate(), (_: any[]) => { reloadGUI("BATTUnsetChargeRate"); }); } }} /> { get_value(CHARGE_RATE_BATT) != null && { console.debug("Charge rate is now " + val.toString()); const rateNow = get_value(CHARGE_RATE_BATT); if (val != rateNow) { backend.resolve(backend.setBatteryChargeRate(val), (rate: number) => { set_value(CHARGE_RATE_BATT, rate); reloadGUI("BATTChargeRate"); }); } }} />}
Current
{get_value(CURRENT_BATT)} mA
{/* Persistence */}
Miscellaneous
{ console.debug("Persist is now " + persist.toString()); backend.resolve( backend.setGeneralPersistent(persist), (val: boolean) => {set_value(PERSISTENT_GEN, val)} ); }} />
Profile
{get_value(NAME_GEN)}
{/* Version Info */}
Debug
Native
{get_value(BACKEND_INFO)}
Framework
{target_usdpl()}
USDPL
v{version_usdpl()}
{ console.debug("Loading default PowerTools settings"); backend.resolve( backend.setGeneralPersistent(false), (val: boolean) => { set_value(PERSISTENT_GEN, val); backend.resolve(backend.loadGeneralDefaultSettings(), (_: any[]) => { reload(); backend.resolve(backend.waitForComplete(), (_: any[]) => {reloadGUI("LoadDefaults")}); }); } ); }} > Defaults
); }; export default definePlugin((serverApi: ServerAPI) => { return { title:
PowerTools
, content: , icon: , onDismount() { console.debug("PowerTools shutting down"); clearInterval(periodicHook!); periodicHook = null; lifetimeHook!.unregister(); startHook!.unregister(); serverApi.routerHook.removeRoute("/decky-plugin-test"); console.debug("Unregistered PowerTools callbacks, goodbye."); }, }; });