import { ButtonItem, definePlugin, //DialogButton, //Menu, //MenuItem, PanelSection, PanelSectionRow, //Router, ServerAPI, //showContextMenu, staticClasses, SliderField, ToggleField, Dropdown, Field, //DropdownOption, SingleDropdownOption, //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 advancedMode = false; var advancedCpu = 1; type MinMax = { min: number | null; max: number | null; } const governorOptions: SingleDropdownOption[] = [ { data: "conservative", label: conservative, }, { data: "ondemand", label: ondemand, }, { data: "userspace", label: userspace, }, { data: "powersave", label: powersave, }, { data: "performance", label: performance, }, { data: "schedutil", label: schedutil, }, ]; // 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 ONLINE_STATUS_CPUS = "CPUs_status_online"; const SMT_CPU = "CPUs_SMT"; const CLOCK_MIN_CPU = "CPUs_min_clock"; const CLOCK_MAX_CPU = "CPUs_max_clock"; const CLOCK_MIN_MAX_CPU = "CPUs_minmax_clocks"; 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; } function syncPlebClockToAdvanced() { const cpuCount = get_value(TOTAL_CPUS); const minClock = get_value(CLOCK_MIN_CPU); const maxClock = get_value(CLOCK_MAX_CPU); let clockArr = []; for (let i = 0; i < cpuCount; i++) { clockArr.push({ min: minClock, max: maxClock, } as MinMax); } set_value(CLOCK_MIN_MAX_CPU, clockArr); } 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[]) => { set_value(ONLINE_STATUS_CPUS, statii); const count = countCpus(statii); set_value(ONLINE_CPUS, count); set_value(SMT_CPU, statii.length > 3 && statii[0] == statii[1] && statii[2] == statii[3] && smtAllowed); }); backend.resolve(backend.getCpuClockLimits(0), (limits: number[]) => { set_value(CLOCK_MIN_CPU, limits[0]); set_value(CLOCK_MAX_CPU, limits[1]); syncPlebClockToAdvanced(); }); backend.resolve(backend.getCpusGovernor(), (governors: string[]) => { set_value(GOVERNOR_CPU, governors); console.log("POWERTOOLS: Governors from backend", governors); console.log("POWERTOOLS: Governors in dropdown", governorOptions); }); 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); const advancedCpuIndex = advancedCpu - 1; return ( {/* CPU */} CPU { advancedMode = advanced; }} /> {/* CPU plebeian mode */} {!advancedMode && smtAllowed && { console.debug("SMT is now " + smt.toString()); const cpus = get_value(ONLINE_CPUS); const smtNow = smt && smtAllowed; backend.resolve(backend.setCpuSmt(smtNow), (newVal: boolean) => { set_value(SMT_CPU, newVal); }); let onlines: boolean[] = []; for (let i = 0; i < total_cpus; i++) { const online = (smtNow? i < cpus : (i % 2 == 0) && (i < cpus * 2)) || (!smtNow && cpus == 4); onlines.push(online); } backend.resolve(backend.setCpuOnlines(onlines), (statii: boolean[]) => { set_value(ONLINE_STATUS_CPUS, statii); const count = countCpus(statii); set_value(ONLINE_CPUS, count); reloadGUI("SMT"); }); }} /> } {!advancedMode && { console.debug("CPU slider is now " + cpus.toString()); const onlines = get_value(ONLINE_CPUS); if (cpus != onlines) { set_value(ONLINE_CPUS, cpus); const smtNow = get_value(SMT_CPU); let onlines: boolean[] = []; for (let i = 0; i < total_cpus; i++) { const online = smtNow? i < cpus : (i % 2 == 0) && (i < cpus * 2); onlines.push(online); } backend.resolve(backend.setCpuOnlines(onlines), (statii: boolean[]) => { set_value(ONLINE_STATUS_CPUS, statii); const count = countCpus(statii); set_value(ONLINE_CPUS, count); reloadGUI("CPUs"); }); reloadGUI("CPUsImmediate"); } }} /> } {!advancedMode && { if (value) { set_value(CLOCK_MIN_CPU, 1400); set_value(CLOCK_MAX_CPU, 3500); syncPlebClockToAdvanced(); 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"); }); syncPlebClockToAdvanced(); } }} /> } {!advancedMode && {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]); syncPlebClockToAdvanced(); }); } backend.resolve(backend.waitForComplete(), (_: boolean) => { reloadGUI("CPUMinFreq"); }); reloadGUI("CPUMinFreqImmediate"); } }} />} } {!advancedMode && {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]); syncPlebClockToAdvanced(); }); } backend.resolve(backend.waitForComplete(), (_: boolean) => { reloadGUI("CPUMaxFreq"); }); reloadGUI("CPUMaxFreqImmediate"); } }} />} } {/* CPU advanced mode */} {advancedMode && { advancedCpu = cpuNum; }} /> } {advancedMode && { console.debug("CPU " + advancedCpu.toString() + " is now " + status.toString()); if (get_value(SMT_CPU)) { backend.resolve(backend.setCpuSmt(false), (newVal: boolean) => { set_value(SMT_CPU, newVal); }); } backend.resolve(backend.setCpuOnline(advancedCpuIndex, status), (newVal: boolean) => { const onlines = get_value(ONLINE_STATUS_CPUS); onlines[advancedCpuIndex] = newVal; set_value(ONLINE_STATUS_CPUS, onlines); }); }} /> } {advancedMode && { if (value) { const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; clocks[advancedCpuIndex].min = 1400; clocks[advancedCpuIndex].max = 3500; set_value(CLOCK_MIN_MAX_CPU, clocks); reloadGUI("CPUFreqToggle"); } else { const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; clocks[advancedCpuIndex].min = null; clocks[advancedCpuIndex].max = null; set_value(CLOCK_MIN_MAX_CPU, clocks); backend.resolve(backend.unsetCpuClockLimits(advancedCpuIndex), (_idc: any[]) => { reloadGUI("CPUUnsetFreq"); }); } }} /> } {advancedMode && {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null && { console.debug("Min freq slider for " + advancedCpu.toString() + " is now " + freq.toString()); const freqNow = get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex] as MinMax; if (freq != freqNow.min) { backend.resolve(backend.setCpuClockLimits(advancedCpuIndex, freq, freqNow.max!), (limits: number[]) => { const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; clocks[advancedCpuIndex].min = limits[0]; clocks[advancedCpuIndex].max = limits[1]; set_value(CLOCK_MIN_MAX_CPU, clocks); reloadGUI("CPUMinFreq"); }); } }} />} } {advancedMode && {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max != null && { console.debug("Max freq slider for " + advancedCpu.toString() + " is now " + freq.toString()); const freqNow = get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex] as MinMax; if (freq != freqNow.max) { backend.resolve(backend.setCpuClockLimits(advancedCpuIndex, freqNow.min!, freq), (limits: number[]) => { const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; clocks[advancedCpuIndex].min = limits[0]; clocks[advancedCpuIndex].max = limits[1]; set_value(CLOCK_MIN_MAX_CPU, clocks); reloadGUI("CPUMaxFreq"); }); } }} />} } {advancedMode && { console.debug("POWERTOOLS: array item", val); console.debug("POWERTOOLS: looking for data", get_value(GOVERNOR_CPU)[advancedCpuIndex]); return val.data == get_value(GOVERNOR_CPU)[advancedCpuIndex]; })} strDefaultLabel={get_value(GOVERNOR_CPU)[advancedCpuIndex]} onChange={(elem: SingleDropdownOption) => { console.debug("Governor dropdown selected", elem); backend.resolve(backend.setCpuGovernor(advancedCpuIndex, elem.data as string), (gov: string) => { const governors = get_value(GOVERNOR_CPU); governors[advancedCpuIndex] = gov; set_value(GOVERNOR_CPU, governors); reloadGUI("CPUGovernor"); }); }} /> } {/* 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.loadGeneralSystemSettings(), (_) => { reload(); backend.resolve(backend.waitForComplete(), (_) => {reloadGUI("LoadSystemDefaults")}); }); } ); }} > 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."); }, }; });