import { ButtonItem, definePlugin, //DialogButton, //Menu, //MenuItem, PanelSection, PanelSectionRow, 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 { BACKEND_INFO, DRIVER_INFO, LIMITS_INFO, CURRENT_BATT, CHARGE_RATE_BATT, CHARGE_MODE_BATT, CHARGE_NOW_BATT, CHARGE_FULL_BATT, CHARGE_DESIGN_BATT, ONLINE_CPUS, ONLINE_STATUS_CPUS, SMT_CPU, CLOCK_MIN_CPU, CLOCK_MAX_CPU, CLOCK_MIN_MAX_CPU, GOVERNOR_CPU, FAST_PPT_GPU, SLOW_PPT_GPU, CLOCK_MIN_GPU, CLOCK_MAX_GPU, SLOW_MEMORY_GPU, PERSISTENT_GEN, NAME_GEN, } from "./consts"; import {set_value, get_value} from "usdpl-front"; import {Debug} from "./components/debug"; import {Gpu} from "./components/gpu"; var periodicHook: NodeJS.Timer | null = null; var lifetimeHook: any = null; var startHook: any = null; var usdplReady = false; var eggCount = 0; //var smtAllowed = true; var advancedMode = false; var advancedCpu = 1; type MinMax = { min: number | null; max: number | null; } // usdpl persistent store keys 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(LIMITS_INFO) as backend.SettingsLimits).cpu.count; 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.getLimits(), (limits) => { set_value(LIMITS_INFO, limits); console.debug("POWERTOOLS: got limits ", limits); }); 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.getBatteryChargeMode(), (mode: string) => { set_value(CHARGE_MODE_BATT, mode) }); 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]); }); 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); backend.log(backend.LogLevel.Info, "POWERTOOLS: Governors from backend " + governors.toString()); }); 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) }); backend.resolve(backend.getDriverProviderName("gpu"), (driver: string) => { set_value(DRIVER_INFO, driver) }); }; // 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) { //backend.log(backend.LogLevel.Debug, "AppID " + update.unAppID.toString() + " is now running"); } else { //backend.log(backend.LogLevel.Debug, "AppID " + update.unAppID.toString() + " is no longer running"); backend.resolve( backend.loadGeneralDefaultSettings(), (ok: boolean) => {backend.log(backend.LogLevel.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) => {backend.log(backend.LogLevel.Debug, "Loading settings ok? " + ok)} ); }); backend.log(backend.LogLevel.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(LIMITS_INFO) as backend.SettingsLimits | null)?.cpu.count ?? 8; const advancedCpuIndex = advancedCpu - 1; const smtAllowed = (get_value(LIMITS_INFO) as backend.SettingsLimits | null)?.cpu.smt_capable ?? true; const governorOptions: SingleDropdownOption[] = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].governors.map((elem) => {return { data: elem, label: {elem}, };}); const chargeModeOptions: SingleDropdownOption[] = (get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_modes.map((elem) => {return { data: elem, label: {elem}, };}); return ( {/* CPU */}
CPU
{ advancedMode = advanced; }} /> {/* CPU plebeian mode */} {!advancedMode && smtAllowed && { backend.log(backend.LogLevel.Debug, "SMT is now " + smt.toString()); //const cpus = get_value(ONLINE_CPUS); const smtNow = smt && smtAllowed; backend.resolve(backend.setCpuSmt(smtNow), (statii: boolean[]) => { set_value(SMT_CPU, smtNow); set_value(ONLINE_STATUS_CPUS, statii); const count = countCpus(statii); set_value(ONLINE_CPUS, count); reloadGUI("SMT"); }); }} /> } {!advancedMode && { backend.log(backend.LogLevel.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) { if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits != null) { set_value(CLOCK_MIN_CPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits!.min); } if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits != null) { set_value(CLOCK_MAX_CPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits!.max); } 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(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits != null && {get_value(CLOCK_MIN_CPU) != null && { backend.log(backend.LogLevel.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(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits != null && {get_value(CLOCK_MAX_CPU) != null && { backend.log(backend.LogLevel.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 && { backend.log(backend.LogLevel.Debug, "CPU " + advancedCpu.toString() + " is now " + status.toString()); if (!get_value(SMT_CPU)) { backend.resolve(backend.setCpuSmt(true), (_newVal: boolean[]) => { set_value(SMT_CPU, true); }); } 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[]; if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits != null) { clocks[advancedCpuIndex].min = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits!.min; } if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits != null) { clocks[advancedCpuIndex].max = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits!.max; } 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(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits != null && {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null && { backend.log(backend.LogLevel.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(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits != null && {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max != null && { backend.log(backend.LogLevel.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 && governorOptions.length != 0 && { backend.log(backend.LogLevel.Debug, "POWERTOOLS: array item " + val.toString()); backend.log(backend.LogLevel.Debug, "POWERTOOLS: looking for data " + get_value(GOVERNOR_CPU)[advancedCpuIndex].toString()); return val.data == get_value(GOVERNOR_CPU)[advancedCpuIndex]; })} strDefaultLabel={get_value(GOVERNOR_CPU)[advancedCpuIndex]} onChange={(elem: SingleDropdownOption) => { backend.log(backend.LogLevel.Debug, "Governor dropdown selected " + elem.data.toString()); 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"); }); }} /> } {/* Battery */}
Battery
{get_value(CHARGE_NOW_BATT) != null && get_value(CHARGE_FULL_BATT) != null && eggCount++} focusable={false}> {get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%) } {get_value(CHARGE_FULL_BATT) != null && get_value(CHARGE_DESIGN_BATT) != null && eggCount++} focusable={false}> {get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%) } {(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current != null && { 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 && { backend.log(backend.LogLevel.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"); }); } }} />} } {chargeModeOptions.length != 0 && { if (value) { set_value(CHARGE_MODE_BATT, chargeModeOptions[0].data as string); reloadGUI("BATTChargeModeToggle"); } else { set_value(CHARGE_MODE_BATT, null); backend.resolve(backend.unsetBatteryChargeMode(), (_: any[]) => { reloadGUI("BATTUnsetChargeMode"); }); } }} /> {get_value(CHARGE_MODE_BATT) != null && { return val.data == get_value(CHARGE_MODE_BATT); })} strDefaultLabel={get_value(CHARGE_MODE_BATT)} onChange={(elem: SingleDropdownOption) => { backend.log(backend.LogLevel.Debug, "Charge mode dropdown selected " + elem.data.toString()); backend.resolve(backend.setBatteryChargeMode(elem.data as string), (mode: string) => { set_value(CHARGE_MODE_BATT, mode); reloadGUI("BATTChargeMode"); }); }} /> } } eggCount++} focusable={false}> {get_value(CURRENT_BATT)} mA {/* Persistence */}
Miscellaneous
{ backend.log(backend.LogLevel.Debug, "Persist is now " + persist.toString()); backend.resolve( backend.setGeneralPersistent(persist), (val: boolean) => {set_value(PERSISTENT_GEN, val)} ); }} /> eggCount++} focusable={false}> {get_value(NAME_GEN)} { backend.log(backend.LogLevel.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() { backend.log(backend.LogLevel.Debug, "PowerTools shutting down"); clearInterval(periodicHook!); periodicHook = null; lifetimeHook!.unregister(); startHook!.unregister(); serverApi.routerHook.removeRoute("/decky-plugin-test"); backend.log(backend.LogLevel.Debug, "Unregistered PowerTools callbacks, goodbye."); }, }; });