This commit is contained in:
parent
9590a90722
commit
1538f9a862
25 changed files with 726 additions and 482 deletions
89
README.md
89
README.md
|
@ -15,95 +15,6 @@ You will need that installed for this plugin to work.
|
||||||
- Display supplementary battery info
|
- Display supplementary battery info
|
||||||
- Keep settings between restarts (stored in `~/.config/powertools/<appid>.json`)
|
- Keep settings between restarts (stored in `~/.config/powertools/<appid>.json`)
|
||||||
|
|
||||||
## Cool, but that's too much work
|
|
||||||
|
|
||||||
Fair enough.
|
|
||||||
In case you still want some of the functionality, without the nice GUI, here's some equivalent commands.
|
|
||||||
These should all be run as superuser, i.e. run `sudo su` and then run these commands in that.
|
|
||||||
|
|
||||||
### Enable & Disable CPU threads
|
|
||||||
|
|
||||||
Enable: `echo 1 > /sys/devices/system/cpu/cpu{cpu_number}/online` where `{cpu_number}` is a number from 1 to 7 (inclusive).
|
|
||||||
|
|
||||||
Disable: `echo 0 > /sys/devices/system/cpu/cpu{cpu_number}/online` where `{cpu_number}` is a number from 1 to 7 (inclusive).
|
|
||||||
|
|
||||||
NOTE: You cannot enable or disable cpu0, hence why there are only 7 in the range for 8 cpu threads.
|
|
||||||
|
|
||||||
### Enable & Disable CPU boost
|
|
||||||
|
|
||||||
Enable: `echo 1 > /sys/devices/system/cpu/cpufreq/boost` enables boost across all threads.
|
|
||||||
|
|
||||||
Disable: `echo 0 > /sys/devices/system/cpu/cpufreq/boost` disables boost across all threads.
|
|
||||||
|
|
||||||
### Set CPU frequency
|
|
||||||
|
|
||||||
Use `cpupower` (usage: `cpupower --help`).
|
|
||||||
This isn't strictly how PowerTools does it, but it's a multi-step process which can involve changing the CPU governor.
|
|
||||||
All that can be done automatically by `cpupower frequency-set --freq {frequency}` where `{frequency}` is `1.7G`, `2.4G` or `2.8G`.
|
|
||||||
|
|
||||||
### Set GPU Power
|
|
||||||
|
|
||||||
Set Slow Powerplay Table (PPT):`echo {microwatts} > /sys/class/hwmon/hwmon4/power1_cap` where `{microwatts}` is a wattage in millionths of a Watt. This doesn't seem to do a lot.
|
|
||||||
|
|
||||||
Set Fast Powerplay Table (PPT): `echo {microwatts} > /sys/class/hwmon/hwmon4/power2_cap` where `{microwatts}` is a wattage in millionths of a Watt.
|
|
||||||
|
|
||||||
Get the entry limits for those two commands with `cat /sys/class/hwmon/hwmon4/power{number}_cap_max` where `{number}` is `1` (slowPPT) or `2` (fastPPT).
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
Set the fan speed: `echo {rpm} > /sys/class/hwmon/hwmon5/fan1_target` where `{rpm}` is the RPM.
|
|
||||||
|
|
||||||
Read the actual fan RPM: `cat /sys/class/hwmon/hwmon5/fan1_input` gives the fan speed.
|
|
||||||
|
|
||||||
NOTE: There's a bug in the fan controller; if you enable automatic fan control it will forget any previously-set target despite it appearing to be set correctly (i.e. `cat /sys/class/hwmon/hwmon5/fan1_target` will display the correct value).
|
|
||||||
When you disable automatic fan control, you will need to set the fan RPM again.
|
|
||||||
|
|
||||||
### Battery stats
|
|
||||||
|
|
||||||
Get the battery charge right now: `cat /sys/class/hwmon/hwmon2/device/charge_now` gives charge in uAh (uAh * 7.7/1000000 = charge in Wh).
|
|
||||||
|
|
||||||
Get the maximum battery capacity: `cat /sys/class/hwmon/hwmon2/device/charge_full` gives charge in uAh.
|
|
||||||
|
|
||||||
Get the design battery capacity: `cat /sys/class/hwmon/hwmon2/device/charge_full_design` gives charge in uAh.
|
|
||||||
|
|
||||||
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.
|
|
||||||
I've only scratched the surface of what this code allows, I'm sure it has more useful information.
|
|
||||||
https://lkml.org/lkml/2022/2/5/391
|
|
||||||
|
|
||||||
### Game launch detection
|
|
||||||
|
|
||||||
```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
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This is licensed under GNU GPLv3.
|
This is licensed under GNU GPLv3.
|
||||||
|
|
2
main.py
2
main.py
|
@ -13,4 +13,4 @@ class Plugin:
|
||||||
# startup
|
# startup
|
||||||
self.backend_proc = subprocess.Popen([PARENT_DIR + "/bin/backend"])
|
self.backend_proc = subprocess.Popen([PARENT_DIR + "/bin/backend"])
|
||||||
while True:
|
while True:
|
||||||
asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "PowerTools",
|
"name": "PowerTools",
|
||||||
"version": "0.7.0",
|
"version": "1.0.0-alpha",
|
||||||
"description": "Power tweaks for power users",
|
"description": "Power tweaks for power users",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "shx rm -rf dist && rollup -c",
|
"build": "shx rm -rf dist && rollup -c",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "PowerTools",
|
"name": "PowerTools",
|
||||||
"author": "NGnius",
|
"author": "NGnius",
|
||||||
"flags": ["root", "_debug"],
|
"flags": ["root", "debug"],
|
||||||
"publish": {
|
"publish": {
|
||||||
"discord_id": "106537989684887552",
|
"discord_id": "106537989684887552",
|
||||||
"description": "Power tweaks for power users",
|
"description": "Power tweaks for power users",
|
||||||
|
|
4
powertools-rs/Cargo.lock
generated
4
powertools-rs/Cargo.lock
generated
|
@ -1054,8 +1054,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "usdpl-back"
|
name = "usdpl-back"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cbbc0781e83ba990f8239142e33173a2d2548701775f3db66702d1af4fd0319a"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"hex",
|
"hex",
|
||||||
|
@ -1068,8 +1066,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "usdpl-core"
|
name = "usdpl-core"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "862153581fac266458521f49e5906a71c1eee1665cb4c7d71e9586bd34b45394"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm-siv",
|
"aes-gcm-siv",
|
||||||
"base64",
|
"base64",
|
||||||
|
|
|
@ -6,7 +6,7 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
usdpl-back = { version = "0.6.0", features = ["blocking"]}
|
usdpl-back = { version = "0.6.0", features = ["blocking"], path = "../../usdpl-rs/usdpl-back"}
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cargo build --release --target x86_64-unknown-linux-musl
|
#cargo build --release --target x86_64-unknown-linux-musl
|
||||||
mkdir ../bin &> /dev/null
|
cargo build --target x86_64-unknown-linux-musl
|
||||||
cp ./target/release/powertools-rs ../bin/backend
|
#cross build --release
|
||||||
|
|
||||||
|
mkdir -p ../bin
|
||||||
|
#cp ./target/x86_64-unknown-linux-musl/release/powertools-rs ../bin/backend
|
||||||
|
cp ./target/x86_64-unknown-linux-musl/debug/powertools-rs ../bin/backend
|
||||||
|
#cp ./target/release/powertools-rs ../bin/backend
|
||||||
|
|
|
@ -18,10 +18,12 @@ pub fn set_persistent(
|
||||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||||
"Failed to send on save channel",
|
"Failed to send on save channel",
|
||||||
);
|
);
|
||||||
super::utility::map_empty_result(
|
let result = super::utility::map_empty_result(
|
||||||
settings_lock.on_set(),
|
settings_lock.on_set(),
|
||||||
settings_lock.persistent,
|
settings_lock.persistent,
|
||||||
)
|
);
|
||||||
|
log::debug!("Persistent is now {}", settings_lock.persistent);
|
||||||
|
result
|
||||||
} else {
|
} else {
|
||||||
vec!["set_persistent missing parameter".into()]
|
vec!["set_persistent missing parameter".into()]
|
||||||
}
|
}
|
||||||
|
@ -46,9 +48,14 @@ pub fn load_settings(
|
||||||
move |params_in: super::ApiParameterType| {
|
move |params_in: super::ApiParameterType| {
|
||||||
if let Some(Primitive::String(path)) = params_in.get(0) {
|
if let Some(Primitive::String(path)) = params_in.get(0) {
|
||||||
if let Some(Primitive::String(name)) = params_in.get(1) {
|
if let Some(Primitive::String(name)) = params_in.get(1) {
|
||||||
super::utility::map_result(
|
match settings.load_file(path.into(), name.to_owned()) {
|
||||||
settings.load_file(path.into(), name.to_owned())
|
Err(e) => vec![e.msg.into()],
|
||||||
)
|
Ok(success) =>
|
||||||
|
super::utility::map_empty_result(
|
||||||
|
settings.clone().on_set(),
|
||||||
|
success
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
vec!["load_settings missing name parameter".into()]
|
vec!["load_settings missing name parameter".into()]
|
||||||
}
|
}
|
||||||
|
@ -59,6 +66,24 @@ pub fn load_settings(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate load default settings from file web method
|
||||||
|
pub fn load_default_settings(
|
||||||
|
settings: Settings,
|
||||||
|
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||||
|
move |_: super::ApiParameterType| {
|
||||||
|
match settings.load_file(
|
||||||
|
crate::consts::DEFAULT_SETTINGS_FILE.into(),
|
||||||
|
crate::consts::DEFAULT_SETTINGS_NAME.to_owned()
|
||||||
|
) {
|
||||||
|
Err(e) => vec![e.msg.into()],
|
||||||
|
Ok(success) => super::utility::map_empty_result(
|
||||||
|
settings.clone().on_set(),
|
||||||
|
success
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate get current settings name
|
/// Generate get current settings name
|
||||||
pub fn get_name(
|
pub fn get_name(
|
||||||
settings: Arc<Mutex<General>>,
|
settings: Arc<Mutex<General>>,
|
||||||
|
@ -71,3 +96,16 @@ pub fn get_name(
|
||||||
.into()]
|
.into()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate get current settings name
|
||||||
|
pub fn lock_unlock_all(
|
||||||
|
settings: Settings,
|
||||||
|
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||||
|
move |_: super::ApiParameterType| {
|
||||||
|
let _lock = unwrap_lock(settings.general.lock(), "general");
|
||||||
|
let _lock = unwrap_lock(settings.cpus.lock(), "cpus");
|
||||||
|
let _lock = unwrap_lock(settings.gpu.lock(), "gpu");
|
||||||
|
let _lock = unwrap_lock(settings.battery.lock(), "battery");
|
||||||
|
vec![true.into()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,19 +3,27 @@ use usdpl_back::core::serdes::Primitive;
|
||||||
|
|
||||||
use crate::settings::SettingError;
|
use crate::settings::SettingError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn map_result<T: Into<Primitive>>(result: Result<T, SettingError>) -> super::ApiParameterType {
|
pub fn map_result<T: Into<Primitive>>(result: Result<T, SettingError>) -> super::ApiParameterType {
|
||||||
match result {
|
match result {
|
||||||
Ok(val) => vec![val.into()],
|
Ok(val) => vec![val.into()],
|
||||||
Err(e) => vec![e.msg.into()],
|
Err(e) => {
|
||||||
|
log::debug!("Mapping error to primitive: {}", e);
|
||||||
|
vec![e.msg.into()]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn map_empty_result<T: Into<Primitive>>(
|
pub fn map_empty_result<T: Into<Primitive>>(
|
||||||
result: Result<(), SettingError>,
|
result: Result<(), SettingError>,
|
||||||
success: T,
|
success: T,
|
||||||
) -> super::ApiParameterType {
|
) -> super::ApiParameterType {
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => vec![success.into()],
|
Ok(_) => vec![success.into()],
|
||||||
Err(e) => vec![e.msg.into()],
|
Err(e) => {
|
||||||
|
log::debug!("Mapping error to primitive: {}", e);
|
||||||
|
vec![e.msg.into()]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,3 +4,4 @@ pub const PACKAGE_NAME: &'static str = env!("CARGO_PKG_NAME");
|
||||||
pub const PACKAGE_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
pub const PACKAGE_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
pub const DEFAULT_SETTINGS_FILE: &str = "default_settings.json";
|
pub const DEFAULT_SETTINGS_FILE: &str = "default_settings.json";
|
||||||
|
pub const DEFAULT_SETTINGS_NAME: &str = "Default";
|
||||||
|
|
|
@ -9,6 +9,8 @@ mod resume_worker;
|
||||||
mod save_worker;
|
mod save_worker;
|
||||||
mod utility;
|
mod utility;
|
||||||
|
|
||||||
|
use settings::OnSet;
|
||||||
|
|
||||||
use simplelog::{LevelFilter, WriteLogger};
|
use simplelog::{LevelFilter, WriteLogger};
|
||||||
|
|
||||||
use usdpl_back::core::serdes::Primitive;
|
use usdpl_back::core::serdes::Primitive;
|
||||||
|
@ -32,118 +34,124 @@ fn main() -> Result<(), ()> {
|
||||||
log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
|
log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
|
||||||
println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
|
println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
|
||||||
|
|
||||||
let default_settings = persist::SettingsJson::open(settings_dir().join(DEFAULT_SETTINGS_FILE))
|
let mut loaded_settings = persist::SettingsJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE))
|
||||||
.map(|settings| settings::Settings::from_json(settings, DEFAULT_SETTINGS_FILE.into()))
|
.map(|settings| settings::Settings::from_json(settings, DEFAULT_SETTINGS_FILE.into()))
|
||||||
.unwrap_or_else(|_| settings::Settings::system_default(DEFAULT_SETTINGS_FILE.into()));
|
.unwrap_or_else(|_| settings::Settings::system_default(DEFAULT_SETTINGS_FILE.into()));
|
||||||
|
|
||||||
log::debug!("Settings: {:?}", default_settings);
|
log::debug!("Settings: {:?}", loaded_settings);
|
||||||
|
|
||||||
let (_save_handle, save_sender) = save_worker::spawn(default_settings.clone());
|
let (_save_handle, save_sender) = save_worker::spawn(loaded_settings.clone());
|
||||||
let _resume_handle = resume_worker::spawn(default_settings.clone());
|
let _resume_handle = resume_worker::spawn(loaded_settings.clone());
|
||||||
|
|
||||||
|
if let Err(e) = loaded_settings.on_set() {
|
||||||
|
log::error!("Startup Settings.on_set() error: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
Instance::new(PORT)
|
Instance::new(PORT)
|
||||||
.register("hello", |_: Vec<Primitive>| {
|
.register("V_INFO", |_: Vec<Primitive>| {
|
||||||
vec![format!("Hello {}", PACKAGE_NAME).into()]
|
vec![format!("{} v{}", PACKAGE_NAME, PACKAGE_VERSION).into()]
|
||||||
})
|
})
|
||||||
// battery API functions
|
// battery API functions
|
||||||
.register("BATTERY_current_now", api::battery::current_now)
|
.register("BATTERY_current_now", api::battery::current_now)
|
||||||
.register(
|
.register(
|
||||||
"BATTERY_set_charge_rate",
|
"BATTERY_set_charge_rate",
|
||||||
api::battery::set_charge_rate(default_settings.battery.clone(), save_sender.clone()),
|
api::battery::set_charge_rate(loaded_settings.battery.clone(), save_sender.clone()),
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"BATTERY_get_charge_rate",
|
"BATTERY_get_charge_rate",
|
||||||
api::battery::get_charge_rate(default_settings.battery.clone()),
|
api::battery::get_charge_rate(loaded_settings.battery.clone()),
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"BATTERY_unset_charge_rate",
|
"BATTERY_unset_charge_rate",
|
||||||
api::battery::unset_charge_rate(default_settings.battery.clone(), save_sender.clone()),
|
api::battery::unset_charge_rate(loaded_settings.battery.clone(), save_sender.clone()),
|
||||||
)
|
)
|
||||||
// cpu API functions
|
// cpu API functions
|
||||||
.register("CPU_count", api::cpu::max_cpus)
|
.register("CPU_count", api::cpu::max_cpus)
|
||||||
.register(
|
.register(
|
||||||
"CPU_set_online",
|
"CPU_set_online",
|
||||||
api::cpu::set_cpu_online(default_settings.cpus.clone(), save_sender.clone())
|
api::cpu::set_cpu_online(loaded_settings.cpus.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"CPU_get_onlines",
|
"CPU_get_onlines",
|
||||||
api::cpu::get_cpus_online(default_settings.cpus.clone())
|
api::cpu::get_cpus_online(loaded_settings.cpus.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"CPU_set_clock_limits",
|
"CPU_set_clock_limits",
|
||||||
api::cpu::set_clock_limits(default_settings.cpus.clone(), save_sender.clone())
|
api::cpu::set_clock_limits(loaded_settings.cpus.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"CPU_get_clock_limits",
|
"CPU_get_clock_limits",
|
||||||
api::cpu::get_clock_limits(default_settings.cpus.clone())
|
api::cpu::get_clock_limits(loaded_settings.cpus.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"CPU_unset_clock_limits",
|
"CPU_unset_clock_limits",
|
||||||
api::cpu::unset_clock_limits(default_settings.cpus.clone(), save_sender.clone())
|
api::cpu::unset_clock_limits(loaded_settings.cpus.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"CPU_set_governor",
|
"CPU_set_governor",
|
||||||
api::cpu::set_cpu_governor(default_settings.cpus.clone(), save_sender.clone())
|
api::cpu::set_cpu_governor(loaded_settings.cpus.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"CPU_get_governors",
|
"CPU_get_governors",
|
||||||
api::cpu::get_cpu_governors(default_settings.cpus.clone())
|
api::cpu::get_cpu_governors(loaded_settings.cpus.clone())
|
||||||
)
|
)
|
||||||
// gpu API functions
|
// gpu API functions
|
||||||
.register(
|
.register(
|
||||||
"GPU_set_ppt",
|
"GPU_set_ppt",
|
||||||
api::gpu::set_ppt(default_settings.gpu.clone(), save_sender.clone())
|
api::gpu::set_ppt(loaded_settings.gpu.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GPU_get_ppt",
|
"GPU_get_ppt",
|
||||||
api::gpu::get_ppt(default_settings.gpu.clone())
|
api::gpu::get_ppt(loaded_settings.gpu.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GPU_unset_ppt",
|
"GPU_unset_ppt",
|
||||||
api::gpu::unset_ppt(default_settings.gpu.clone(), save_sender.clone())
|
api::gpu::unset_ppt(loaded_settings.gpu.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GPU_set_clock_limits",
|
"GPU_set_clock_limits",
|
||||||
api::gpu::set_clock_limits(default_settings.gpu.clone(), save_sender.clone())
|
api::gpu::set_clock_limits(loaded_settings.gpu.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GPU_get_clock_limits",
|
"GPU_get_clock_limits",
|
||||||
api::gpu::get_clock_limits(default_settings.gpu.clone())
|
api::gpu::get_clock_limits(loaded_settings.gpu.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GPU_unset_clock_limits",
|
"GPU_unset_clock_limits",
|
||||||
api::gpu::unset_clock_limits(default_settings.gpu.clone(), save_sender.clone())
|
api::gpu::unset_clock_limits(loaded_settings.gpu.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GPU_set_slow_memory",
|
"GPU_set_slow_memory",
|
||||||
api::gpu::set_slow_memory(default_settings.gpu.clone(), save_sender.clone())
|
api::gpu::set_slow_memory(loaded_settings.gpu.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GPU_get_slow_memory",
|
"GPU_get_slow_memory",
|
||||||
api::gpu::get_slow_memory(default_settings.gpu.clone())
|
api::gpu::get_slow_memory(loaded_settings.gpu.clone())
|
||||||
)
|
)
|
||||||
// general API functions
|
// general API functions
|
||||||
.register(
|
.register(
|
||||||
"GENERAL_set_persistent",
|
"GENERAL_set_persistent",
|
||||||
api::general::set_persistent(default_settings.general.clone(), save_sender.clone())
|
api::general::set_persistent(loaded_settings.general.clone(), save_sender.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GENERAL_get_persistent",
|
"GENERAL_get_persistent",
|
||||||
api::general::get_persistent(default_settings.general.clone())
|
api::general::get_persistent(loaded_settings.general.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GENERAL_load_settings",
|
"GENERAL_load_settings",
|
||||||
api::general::load_settings(default_settings.clone())
|
api::general::load_settings(loaded_settings.clone())
|
||||||
|
)
|
||||||
|
.register(
|
||||||
|
"GENERAL_load_default_settings",
|
||||||
|
api::general::load_default_settings(loaded_settings.clone())
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
"GENERAL_get_name",
|
"GENERAL_get_name",
|
||||||
api::general::get_name(default_settings.general.clone())
|
api::general::get_name(loaded_settings.general.clone())
|
||||||
|
)
|
||||||
|
.register(
|
||||||
|
"GENERAL_wait_for_unlocks",
|
||||||
|
api::general::lock_unlock_all(loaded_settings.clone())
|
||||||
)
|
)
|
||||||
.run_blocking()
|
.run_blocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings_dir() -> std::path::PathBuf {
|
|
||||||
usdpl_back::api::dirs::home()
|
|
||||||
.unwrap_or_else(|| "/home/deck".into())
|
|
||||||
.join(".config/powertools/")
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl Default for SettingsJson {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
version: 0,
|
version: 0,
|
||||||
name: "default".to_owned(),
|
name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(),
|
||||||
persistent: false,
|
persistent: false,
|
||||||
cpus: Vec::with_capacity(8),
|
cpus: Vec::with_capacity(8),
|
||||||
gpu: GpuJson::default(),
|
gpu: GpuJson::default(),
|
||||||
|
|
|
@ -8,6 +8,7 @@ const ALLOWED_ERROR: f64 = 0.001;
|
||||||
|
|
||||||
pub fn spawn(settings: Settings) -> JoinHandle<()> {
|
pub fn spawn(settings: Settings) -> JoinHandle<()> {
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
|
log::info!("resume_worker starting...");
|
||||||
let duration = Duration::from_millis(5000);
|
let duration = Duration::from_millis(5000);
|
||||||
let mut start = Instant::now();
|
let mut start = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
|
@ -17,11 +18,12 @@ pub fn spawn(settings: Settings) -> JoinHandle<()> {
|
||||||
if old_start.as_secs_f64() > duration.as_secs_f64() * (1.0 + ALLOWED_ERROR) {
|
if old_start.as_secs_f64() > duration.as_secs_f64() * (1.0 + ALLOWED_ERROR) {
|
||||||
// has just resumed from sleep
|
// has just resumed from sleep
|
||||||
unwrap_maybe_fatal(settings.on_resume(), "On resume failure");
|
unwrap_maybe_fatal(settings.on_resume(), "On resume failure");
|
||||||
log::info!(
|
log::debug!(
|
||||||
"OnResume completed after sleeping for {}s",
|
"OnResume completed after sleeping for {}s",
|
||||||
old_start.as_secs_f32()
|
old_start.as_secs_f32()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//log::warn!("resume_worker completed!");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,17 @@ use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
|
||||||
pub fn spawn(settings: Settings) -> (JoinHandle<()>, Sender<()>) {
|
pub fn spawn(settings: Settings) -> (JoinHandle<()>, Sender<()>) {
|
||||||
let (sender, receiver): (Sender<()>, Receiver<()>) = mpsc::channel();
|
let (sender, receiver): (Sender<()>, Receiver<()>) = mpsc::channel();
|
||||||
let worker = thread::spawn(move || {
|
let worker = thread::spawn(move || {
|
||||||
|
log::info!("save_worker starting...");
|
||||||
for _ in receiver.iter() {
|
for _ in receiver.iter() {
|
||||||
let save_path = unwrap_lock(settings.general.lock(), "general").path.clone();
|
log::debug!("save_worker is saving...");
|
||||||
let save_json: SettingsJson = settings.clone().into();
|
let save_path = crate::utility::settings_dir()
|
||||||
|
.join(unwrap_lock(settings.general.lock(), "general").path.clone());
|
||||||
|
let settings_clone = settings.clone();
|
||||||
|
let save_json: SettingsJson = settings_clone.into();
|
||||||
unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings");
|
unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings");
|
||||||
log::debug!("Saved settings to {}", save_path.display());
|
log::debug!("Saved settings to {}", save_path.display());
|
||||||
}
|
}
|
||||||
|
log::warn!("save_worker completed!");
|
||||||
});
|
});
|
||||||
(worker, sender)
|
(worker, sender)
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,16 +101,18 @@ impl Cpu {
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
// set governor
|
// set governor
|
||||||
let governor_path = cpu_governor_path(self.index);
|
if self.index == 0 || self.online {
|
||||||
usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| {
|
let governor_path = cpu_governor_path(self.index);
|
||||||
SettingError {
|
usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| {
|
||||||
msg: format!(
|
SettingError {
|
||||||
"Failed to write `{}` to `{}`: {}",
|
msg: format!(
|
||||||
&self.governor, &governor_path, e
|
"Failed to write `{}` to `{}`: {}",
|
||||||
),
|
&self.governor, &governor_path, e
|
||||||
setting: super::SettingVariant::Cpu,
|
),
|
||||||
}
|
setting: super::SettingVariant::Cpu,
|
||||||
})?;
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +143,7 @@ impl Cpu {
|
||||||
if let Some(dash_index) = data.find('-') {
|
if let Some(dash_index) = data.find('-') {
|
||||||
let data = data.split_off(dash_index + 1);
|
let data = data.split_off(dash_index + 1);
|
||||||
if let Ok(max_cpu) = data.parse::<usize>() {
|
if let Ok(max_cpu) = data.parse::<usize>() {
|
||||||
return Some(max_cpu);
|
return Some(max_cpu + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::warn!("Failed to parse CPU info from kernel, is Tux evil?");
|
log::warn!("Failed to parse CPU info from kernel, is Tux evil?");
|
||||||
|
|
|
@ -40,7 +40,7 @@ impl OnSet for General {
|
||||||
// remove settings file when persistence is turned off, to prevent it from be loaded next time.
|
// remove settings file when persistence is turned off, to prevent it from be loaded next time.
|
||||||
if !self.persistent && self.path.exists() {
|
if !self.persistent && self.path.exists() {
|
||||||
std::fs::remove_file(&self.path).map_err(|e| SettingError {
|
std::fs::remove_file(&self.path).map_err(|e| SettingError {
|
||||||
msg: e.to_string(),
|
msg: format!("Failed to delete `{}`: {}", self.path.display(), e),
|
||||||
setting: SettingVariant::General,
|
setting: SettingVariant::General,
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -67,16 +67,7 @@ impl OnSet for Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unwrap_lock(self.gpu.lock(), "gpu").on_set()?;
|
unwrap_lock(self.gpu.lock(), "gpu").on_set()?;
|
||||||
{
|
unwrap_lock(self.general.lock(), "general").on_set()?;
|
||||||
// general lock scope
|
|
||||||
let gen_lock = unwrap_lock(self.general.lock(), "general");
|
|
||||||
if !gen_lock.persistent && gen_lock.path.exists() {
|
|
||||||
std::fs::remove_file(&gen_lock.path).map_err(|e| SettingError {
|
|
||||||
msg: format!("Failed to delete `{}`: {}", gen_lock.path.display(), e),
|
|
||||||
setting: SettingVariant::General,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +112,7 @@ impl Settings {
|
||||||
general: Arc::new(Mutex::new(General {
|
general: Arc::new(Mutex::new(General {
|
||||||
persistent: false,
|
persistent: false,
|
||||||
path: json_path,
|
path: json_path,
|
||||||
name: "".to_owned(),
|
name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(),
|
||||||
})),
|
})),
|
||||||
cpus: Arc::new(Mutex::new(Cpu::system_default())),
|
cpus: Arc::new(Mutex::new(Cpu::system_default())),
|
||||||
gpu: Arc::new(Mutex::new(Gpu::system_default())),
|
gpu: Arc::new(Mutex::new(Gpu::system_default())),
|
||||||
|
@ -179,17 +170,26 @@ impl OnResume for Settings {
|
||||||
impl Into<SettingsJson> for Settings {
|
impl Into<SettingsJson> for Settings {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into(self) -> SettingsJson {
|
fn into(self) -> SettingsJson {
|
||||||
|
log::debug!("Locking settings to convert into json");
|
||||||
|
let gen_lock = unwrap_lock(self.general.lock(), "general");
|
||||||
|
log::debug!("Got general lock");
|
||||||
|
let cpu_lock = unwrap_lock(self.cpus.lock(), "cpu");
|
||||||
|
log::debug!("Got cpus lock");
|
||||||
|
let gpu_lock = unwrap_lock(self.gpu.lock(), "gpu");
|
||||||
|
log::debug!("Got gpu lock");
|
||||||
|
let batt_lock = unwrap_lock(self.battery.lock(), "battery");
|
||||||
|
log::debug!("Got battery lock");
|
||||||
SettingsJson {
|
SettingsJson {
|
||||||
version: LATEST_VERSION,
|
version: LATEST_VERSION,
|
||||||
name: unwrap_lock(self.general.lock(), "general").name.clone(),
|
name: gen_lock.name.clone(),
|
||||||
persistent: unwrap_lock(self.general.lock(), "general").persistent,
|
persistent: gen_lock.persistent,
|
||||||
cpus: unwrap_lock(self.cpus.lock(), "cpu")
|
cpus: cpu_lock
|
||||||
.clone()
|
.clone()
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.map(|cpu| cpu.into())
|
.map(|cpu| cpu.into())
|
||||||
.collect(),
|
.collect(),
|
||||||
gpu: unwrap_lock(self.gpu.lock(), "gpu").clone().into(),
|
gpu: gpu_lock.clone().into(),
|
||||||
battery: unwrap_lock(self.battery.lock(), "battery").clone().into(),
|
battery: batt_lock.clone().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,3 +23,9 @@ pub fn unwrap_lock<'a, T: Sized>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn settings_dir() -> std::path::PathBuf {
|
||||||
|
usdpl_back::api::dirs::home()
|
||||||
|
.unwrap_or_else(|| "/home/deck".into())
|
||||||
|
.join(".config/powertools/")
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {init_usdpl, target, init_embedded, call_backend} from "usdpl-front";
|
import {init_usdpl, target_usdpl, init_embedded, call_backend} from "usdpl-front";
|
||||||
|
|
||||||
const USDPL_PORT: number = 44443;
|
const USDPL_PORT: number = 44443;
|
||||||
|
|
||||||
|
@ -20,12 +20,16 @@ export async function initBackend() {
|
||||||
// init usdpl
|
// init usdpl
|
||||||
await init_embedded();
|
await init_embedded();
|
||||||
init_usdpl(USDPL_PORT);
|
init_usdpl(USDPL_PORT);
|
||||||
console.log("USDPL started for framework: " + target());
|
console.log("USDPL started for framework: " + target_usdpl());
|
||||||
//setReady(true);
|
//setReady(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// API
|
// API
|
||||||
|
|
||||||
|
export async function getInfo(): Promise<string> {
|
||||||
|
return (await call_backend("V_INFO", []))[0];
|
||||||
|
}
|
||||||
|
|
||||||
// Battery
|
// Battery
|
||||||
|
|
||||||
export async function getBatteryCurrent(): Promise<number> {
|
export async function getBatteryCurrent(): Promise<number> {
|
||||||
|
@ -126,6 +130,14 @@ export async function loadGeneralSettings(path: string, name: string): Promise<b
|
||||||
return (await call_backend("GENERAL_load_settings", [path, name]))[0];
|
return (await call_backend("GENERAL_load_settings", [path, name]))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getGeneralPersistent(): Promise<boolean> {
|
export async function loadGeneralDefaultSettings(): Promise<boolean> {
|
||||||
|
return (await call_backend("GENERAL_load_default_settings", []))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGeneralSettingsName(): Promise<boolean> {
|
||||||
return (await call_backend("GENERAL_get_name", []))[0];
|
return (await call_backend("GENERAL_get_name", []))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function waitForComplete(): Promise<boolean> {
|
||||||
|
return (await call_backend("GENERAL_wait_for_unlocks", []))[0];
|
||||||
|
}
|
||||||
|
|
739
src/index.tsx
739
src/index.tsx
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
//ButtonItem,
|
ButtonItem,
|
||||||
definePlugin,
|
definePlugin,
|
||||||
DialogButton,
|
//DialogButton,
|
||||||
//Menu,
|
//Menu,
|
||||||
//MenuItem,
|
//MenuItem,
|
||||||
PanelSection,
|
PanelSection,
|
||||||
|
@ -19,161 +19,131 @@ import {
|
||||||
import { VFC, useState } from "react";
|
import { VFC, useState } from "react";
|
||||||
import { GiDrill } from "react-icons/gi";
|
import { GiDrill } from "react-icons/gi";
|
||||||
|
|
||||||
import * as python from "./python";
|
//import * as python from "./python";
|
||||||
|
import * as backend from "./backend";
|
||||||
|
import {set_value, get_value, target_usdpl, version_usdpl} from "usdpl-front";
|
||||||
|
|
||||||
//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 periodicHook: NodeJS.Timer | null = null;
|
||||||
var lastGame: string = "";
|
|
||||||
var lifetimeHook: any = null;
|
var lifetimeHook: any = null;
|
||||||
var startHook: any = null;
|
var startHook: any = null;
|
||||||
|
var usdplReady = false;
|
||||||
|
|
||||||
var smt_backup: boolean = true;
|
var smtAllowed = true;
|
||||||
var cpus_backup: number = 8;
|
var smtGlobal = smtAllowed;
|
||||||
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(){};
|
// usdpl persistent store keys
|
||||||
|
|
||||||
const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
|
const BACKEND_INFO = "VINFO";
|
||||||
// const [result, setResult] = useState<number | undefined>();
|
|
||||||
|
|
||||||
// const onClick = async () => {
|
const CURRENT_BATT = "BATTERY_current_now";
|
||||||
// const result = await serverAPI.callPluginMethod<AddMethodArgs, number>(
|
const CHARGE_RATE_BATT = "BATTERY_charge_rate";
|
||||||
// "add",
|
|
||||||
// {
|
|
||||||
// left: 2,
|
|
||||||
// right: 2,
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// if (result.success) {
|
|
||||||
// setResult(result.result);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
python.setServer(serverAPI);
|
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 [smtGlobal, setSMT_internal] = useState<boolean>(smt_backup);
|
const FAST_PPT_GPU = "GPU_fastPPT";
|
||||||
const setSMT = (value: boolean) => {
|
const SLOW_PPT_GPU = "GPU_slowPPT";
|
||||||
smt_backup = value;
|
const CLOCK_MIN_GPU = "GPU_min_clock";
|
||||||
setSMT_internal(value);
|
const CLOCK_MAX_GPU = "GPU_max_clock";
|
||||||
};
|
const SLOW_MEMORY_GPU = "GPU_slow_memory";
|
||||||
|
|
||||||
const [cpusGlobal, setCPUs_internal] = useState<number>(cpus_backup);
|
const PERSISTENT_GEN = "GENERAL_persistent";
|
||||||
const setCPUs = (value: number) => {
|
const NAME_GEN = "GENERAL_name";
|
||||||
cpus_backup = value;
|
|
||||||
setCPUs_internal(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [boostGlobal, setBoost_internal] = useState<boolean>(boost_backup);
|
const reload = function() {
|
||||||
const setBoost = (value: boolean) => {
|
if (!usdplReady) {return;}
|
||||||
boost_backup = value;
|
|
||||||
setBoost_internal(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [freqGlobal, setFreq_internal] = useState<number>(freq_backup);
|
backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) });
|
||||||
const setFreq = (value: number) => {
|
backend.resolve(backend.getBatteryChargeRate(), (rate: number) => { set_value(CHARGE_RATE_BATT, rate) });
|
||||||
freq_backup = value;
|
|
||||||
setFreq_internal(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [slowPPTGlobal, setSlowPPT_internal] = useState<number>(slowPPT_backup);
|
backend.resolve(backend.getCpuCount(), (count: number) => { set_value(TOTAL_CPUS, count)});
|
||||||
const setSlowPPT = (value: number) => {
|
backend.resolve(backend.getCpusOnline(), (statii: boolean[]) => {
|
||||||
slowPPT_backup = value;
|
// TODO: allow for per-core control of online status
|
||||||
setSlowPPT_internal(value);
|
let count = 0;
|
||||||
};
|
for (let i = 0; i < statii.length; i++) {
|
||||||
|
if (statii[i]) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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]) });
|
||||||
|
|
||||||
const [fastPPTGlobal, setFastPPT_internal] = useState<number>(fastPPT_backup);
|
backend.resolve(backend.getGpuPpt(), (ppts: number[]) => {
|
||||||
const setFastPPT = (value: number) => {
|
set_value(FAST_PPT_GPU, ppts[0]);
|
||||||
fastPPT_backup = value;
|
set_value(SLOW_PPT_GPU, ppts[1]);
|
||||||
setFastPPT_internal(value);
|
});
|
||||||
};
|
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) });
|
||||||
|
|
||||||
const [chargeNowGlobal, setChargeNow_internal] = useState<number>(chargeNow_backup);
|
backend.resolve(backend.getGeneralPersistent(), (value: boolean) => { set_value(PERSISTENT_GEN, value) });
|
||||||
const setChargeNow = (value: number) => {
|
backend.resolve(backend.getGeneralSettingsName(), (name: string) => { set_value(NAME_GEN, name) });
|
||||||
chargeNow_backup = value;
|
|
||||||
setChargeNow_internal(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [chargeFullGlobal, setChargeFull_internal] = useState<number>(chargeFull_backup);
|
backend.resolve(backend.getInfo(), (info: string) => { set_value(BACKEND_INFO, info) });
|
||||||
const setChargeFull = (value: number) => {
|
};
|
||||||
chargeFull_backup = value;
|
|
||||||
setChargeFull_internal(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [chargeDesignGlobal, setChargeDesign_internal] = useState<number>(chargeDesign_backup);
|
// init USDPL WASM and connection to back-end
|
||||||
const setChargeDesign = (value: number) => {
|
(async function(){
|
||||||
chargeDesign_backup = value;
|
await backend.initBackend();
|
||||||
setChargeDesign_internal(value);
|
usdplReady = true;
|
||||||
};
|
set_value(NAME_GEN, "Default");
|
||||||
|
reload(); // technically this is only a load
|
||||||
|
|
||||||
const [persistGlobal, setPersist_internal] = useState<boolean>(persistent_backup);
|
// register Steam callbacks
|
||||||
const setPersist = (value: boolean) => {
|
//@ts-ignore
|
||||||
persistent_backup = value;
|
lifetimeHook = SteamClient.GameSessions.RegisterForAppLifetimeNotifications((update) => {
|
||||||
setPersist_internal(value);
|
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);
|
||||||
|
backend.resolve(
|
||||||
|
backend.loadGeneralSettings(gameInfo.appid.toString() + ".json", gameInfo.display_name),
|
||||||
|
(ok: boolean) => {console.debug("Loading settings ok? " + ok)}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const [perGameProfileGlobal, setPerGameProfile_internal] = useState<boolean>(perGameProfile_backup);
|
console.debug("Registered PowerTools callbacks, hello!");
|
||||||
const setPerGameProfile = (value: boolean) => {
|
})();
|
||||||
perGameProfile_backup = value;
|
|
||||||
setPerGameProfile_internal(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [gameGlobal, setGame_internal] = useState<string>(lastGame);
|
const periodicals = function() {
|
||||||
const setGame = (value: string) => {
|
backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) });
|
||||||
lastGame = value;
|
|
||||||
setGame_internal(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [versionGlobal, setVersion_internal] = useState<string>(versionGlobalHolder);
|
backend.resolve(backend.getGeneralPersistent(), (value: boolean) => { set_value(PERSISTENT_GEN, value) });
|
||||||
const setVersion = (value: string) => {
|
backend.resolve(backend.getGeneralSettingsName(), (name: string) => {
|
||||||
versionGlobalHolder = value;
|
const oldValue = get_value(NAME_GEN);
|
||||||
setVersion_internal(value);
|
set_value(NAME_GEN, name);
|
||||||
};
|
if (name != oldValue) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
reload = function () {
|
const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
||||||
python.execute(python.onViewReady());
|
|
||||||
|
|
||||||
python.resolve(python.getSMT(), setSMT);
|
const [_idc, reloadGUI] = useState<any>("/shrug");
|
||||||
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) {
|
if (periodicHook != null) {
|
||||||
clearInterval(periodicHook);
|
clearInterval(periodicHook);
|
||||||
|
@ -181,189 +151,387 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
periodicHook = setInterval(function() {
|
periodicHook = setInterval(function() {
|
||||||
python.resolve(python.getChargeFull(), setChargeFull);
|
periodicals();
|
||||||
python.resolve(python.getChargeNow(), setChargeNow);
|
reloadGUI("periodic" + (new Date()).getTime().toString());
|
||||||
python.resolve(python.getCurrentGame(), (game: string) => {
|
|
||||||
if (lastGame != game) {
|
|
||||||
setGame(game);
|
|
||||||
lastGame = game;
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard);
|
const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard);
|
||||||
|
|
||||||
|
const total_cpus = get_value(TOTAL_CPUS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelSection>
|
<PanelSection>
|
||||||
{/* CPU */}
|
{/* CPU */ /* TODO: set per-core stuff*/}
|
||||||
<div className={staticClasses.PanelSectionTitle}>
|
<div className={staticClasses.PanelSectionTitle}>
|
||||||
CPU
|
CPU
|
||||||
</div>
|
</div>
|
||||||
<PanelSectionRow>
|
{smtAllowed && <PanelSectionRow>
|
||||||
<ToggleField
|
<ToggleField
|
||||||
checked={smtGlobal}
|
checked={smtGlobal}
|
||||||
label="SMT"
|
label="SMT"
|
||||||
description="Enables odd-numbered CPUs"
|
description="Enables odd-numbered CPUs"
|
||||||
onChange={(smt: boolean) => {
|
onChange={(smt: boolean) => {
|
||||||
console.log("SMT is now " + smt.toString());
|
console.debug("SMT is now " + smt.toString());
|
||||||
python.execute(python.setCPUs(cpusGlobal, smt));
|
const cpus = get_value(ONLINE_CPUS);
|
||||||
python.resolve(python.getCPUs(), setCPUs);
|
set_value(ONLINE_CPUS, 0);
|
||||||
python.resolve(python.getSMT(), setSMT);
|
smtGlobal = smt && smtAllowed;
|
||||||
|
for (let i = 0; i < total_cpus; i++) {
|
||||||
|
const online = (smtGlobal? i < cpus : (i % 2 == 0) && (i < cpus * 2))
|
||||||
|
|| (!smtGlobal && cpus == 4);
|
||||||
|
backend.resolve(backend.setCpuOnline(i, online), (value: boolean) => {
|
||||||
|
if (value) {set_value(ONLINE_CPUS, get_value(ONLINE_CPUS) + 1)}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
backend.resolve(backend.waitForComplete(), (_: boolean[]) => {
|
||||||
|
reloadGUI("SMT");
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>}
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
<SliderField
|
<SliderField
|
||||||
label="Threads"
|
label="Threads"
|
||||||
value={cpusGlobal}
|
value={get_value(ONLINE_CPUS)}
|
||||||
step={1}
|
step={1}
|
||||||
max={smtGlobal? 8 : 4}
|
max={smtGlobal? total_cpus : total_cpus/2}
|
||||||
min={1}
|
min={1}
|
||||||
showValue={true}
|
showValue={true}
|
||||||
onChange={(cpus: number) => {
|
onChange={(cpus: number) => {
|
||||||
console.log("CPU slider is now " + cpus.toString());
|
console.debug("CPU slider is now " + cpus.toString());
|
||||||
if (cpus != cpusGlobal) {
|
const onlines = get_value(ONLINE_CPUS);
|
||||||
python.execute(python.setCPUs(cpus, smtGlobal));
|
if (cpus != onlines) {
|
||||||
python.resolve(python.getCPUs(), setCPUs);
|
set_value(ONLINE_CPUS, 0);
|
||||||
|
for (let i = 0; i < total_cpus; i++) {
|
||||||
|
const online = smtGlobal? i < cpus : (i % 2 == 0) && (i < cpus * 2);
|
||||||
|
backend.resolve(backend.setCpuOnline(i, online), (value: boolean) => {
|
||||||
|
if (value) {set_value(ONLINE_CPUS, get_value(ONLINE_CPUS) + 1)}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
backend.resolve(backend.waitForComplete(), (_: boolean[]) => {
|
||||||
|
reloadGUI("CPUs");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
<ToggleField
|
<ToggleField
|
||||||
checked={boostGlobal}
|
checked={get_value(CLOCK_MIN_CPU) != null && get_value(CLOCK_MAX_CPU) != null}
|
||||||
label="Boost"
|
label="Frequency Limits"
|
||||||
description="Allows the CPU to go above max frequency"
|
onChange={(value: boolean) => {
|
||||||
onChange={(boost: boolean) => {
|
if (value) {
|
||||||
console.log("Boost is now " + boost.toString());
|
set_value(CLOCK_MIN_CPU, 1400);
|
||||||
python.execute(python.setCPUBoost(boost));
|
set_value(CLOCK_MAX_CPU, 3500);
|
||||||
python.resolve(python.getCPUBoost(), setBoost);
|
reloadGUI("CPUFreqToggle");
|
||||||
}}
|
} else {
|
||||||
/>
|
set_value(CLOCK_MIN_CPU, null);
|
||||||
</PanelSectionRow>
|
set_value(CLOCK_MAX_CPU, null);
|
||||||
<PanelSectionRow>
|
for (let i = 0; i < total_cpus; i++) {
|
||||||
<SliderField
|
backend.resolve(backend.unsetCpuClockLimits(i), (_idc: any[]) => {});
|
||||||
label="Max Frequency"
|
}
|
||||||
value={freqGlobal}
|
backend.resolve(backend.waitForComplete(), (_: boolean[]) => {
|
||||||
max={2}
|
reloadGUI("CPUUnsetFreq");
|
||||||
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>
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
{get_value(CLOCK_MIN_CPU) != null && <SliderField
|
||||||
|
label="Minimum (MHz)"
|
||||||
|
value={get_value(CLOCK_MIN_CPU)}
|
||||||
|
max={3500}
|
||||||
|
min={1400}
|
||||||
|
step={100}
|
||||||
|
showValue={true}
|
||||||
|
disabled={get_value(CLOCK_MIN_CPU) == null}
|
||||||
|
onChange={(freq: number) => {
|
||||||
|
console.debug("Min freq slider is now " + freq.toString());
|
||||||
|
const freqNow = get_value(CLOCK_MIN_CPU);
|
||||||
|
if (freq != freqNow) {
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
{get_value(CLOCK_MAX_CPU) != null && <SliderField
|
||||||
|
label="Maximum (MHz)"
|
||||||
|
value={get_value(CLOCK_MAX_CPU)}
|
||||||
|
max={3500}
|
||||||
|
min={500}
|
||||||
|
step={100}
|
||||||
|
showValue={true}
|
||||||
|
disabled={get_value(CLOCK_MAX_CPU) == null}
|
||||||
|
onChange={(freq: number) => {
|
||||||
|
console.debug("Max freq slider is now " + freq.toString());
|
||||||
|
const freqNow = get_value(CLOCK_MAX_CPU);
|
||||||
|
if (freq != freqNow) {
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</PanelSectionRow>
|
||||||
|
{/* TODO: CPU governor */}
|
||||||
{/* GPU */}
|
{/* GPU */}
|
||||||
<div className={staticClasses.PanelSectionTitle}>
|
<div className={staticClasses.PanelSectionTitle}>
|
||||||
GPU
|
GPU
|
||||||
</div>
|
</div>
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
{/* index: 1 */}
|
<ToggleField
|
||||||
<SliderField
|
checked={get_value(SLOW_PPT_GPU) != null && get_value(FAST_PPT_GPU) != null}
|
||||||
label="SlowPPT Power"
|
label="PowerPlay Limits"
|
||||||
value={slowPPTGlobal}
|
description="Override APU TDP settings"
|
||||||
max={2}
|
onChange={(value: boolean) => {
|
||||||
min={0}
|
if (value) {
|
||||||
notchCount={3}
|
set_value(SLOW_PPT_GPU, 15000000);
|
||||||
notchLabels={[
|
set_value(FAST_PPT_GPU, 15000000);
|
||||||
{notchIndex: 0, label: "Min"},
|
reloadGUI("GPUPPTToggle");
|
||||||
{notchIndex: 1, label: "Auto"},
|
} else {
|
||||||
{notchIndex: 2, label: "Max"},
|
set_value(SLOW_PPT_GPU, null);
|
||||||
]}
|
set_value(FAST_PPT_GPU, null);
|
||||||
notchTicksVisible={true}
|
backend.resolve(backend.unsetGpuPpt(), (_: any[]) => {
|
||||||
onChange={(ppt: number) => {
|
reloadGUI("GPUUnsetPPT");
|
||||||
console.log("SlowPPT is now " + ppt.toString());
|
});
|
||||||
if (ppt != slowPPTGlobal) {
|
|
||||||
python.execute(python.setGPUPowerI(ppt, 1));
|
|
||||||
python.resolve(python.getGPUPowerI(1), setSlowPPT);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
{/* index: 2 */}
|
{ get_value(SLOW_PPT_GPU) != null && <SliderField
|
||||||
<SliderField
|
label="SlowPPT (uW)"
|
||||||
label="FastPPT Power"
|
value={get_value(SLOW_PPT_GPU)}
|
||||||
value={fastPPTGlobal}
|
max={29000000}
|
||||||
max={2}
|
min={1000000}
|
||||||
min={0}
|
step={1000000}
|
||||||
notchCount={3}
|
showValue={true}
|
||||||
notchLabels={[
|
disabled={get_value(SLOW_PPT_GPU) == null}
|
||||||
{notchIndex: 0, label: "Min"},
|
|
||||||
{notchIndex: 1, label: "Auto"},
|
|
||||||
{notchIndex: 2, label: "Max"},
|
|
||||||
]}
|
|
||||||
notchTicksVisible={true}
|
|
||||||
onChange={(ppt: number) => {
|
onChange={(ppt: number) => {
|
||||||
console.log("FastPPT is now " + ppt.toString());
|
console.debug("SlowPPT is now " + ppt.toString());
|
||||||
if (ppt != fastPPTGlobal) {
|
const pptNow = get_value(SLOW_PPT_GPU);
|
||||||
python.execute(python.setGPUPowerI(ppt, 2));
|
if (ppt != pptNow) {
|
||||||
python.resolve(python.getGPUPowerI(2), setFastPPT);
|
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");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
/>}
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
{get_value(FAST_PPT_GPU) != null && <SliderField
|
||||||
|
label="FastPPT (uW)"
|
||||||
|
value={get_value(FAST_PPT_GPU)}
|
||||||
|
max={29000000}
|
||||||
|
min={1000000}
|
||||||
|
step={1000000}
|
||||||
|
showValue={true}
|
||||||
|
disabled={get_value(FAST_PPT_GPU) == null}
|
||||||
|
onChange={(ppt: number) => {
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
<ToggleField
|
||||||
|
checked={get_value(CLOCK_MIN_GPU) != null && get_value(CLOCK_MAX_GPU) != null}
|
||||||
|
label="Frequency Limits"
|
||||||
|
onChange={(value: boolean) => {
|
||||||
|
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_MIN_GPU, null);
|
||||||
|
backend.resolve(backend.unsetGpuClockLimits(), (_: any[]) => {
|
||||||
|
reloadGUI("GPUUnsetFreq");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
{ get_value(CLOCK_MIN_GPU) != null && <SliderField
|
||||||
|
label="Minimum (MHz)"
|
||||||
|
value={get_value(CLOCK_MIN_GPU)}
|
||||||
|
max={1600}
|
||||||
|
min={200}
|
||||||
|
step={100}
|
||||||
|
showValue={true}
|
||||||
|
disabled={get_value(CLOCK_MIN_GPU) == null}
|
||||||
|
onChange={(val: number) => {
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
{get_value(CLOCK_MAX_GPU) != null && <SliderField
|
||||||
|
label="Maximum (MHz)"
|
||||||
|
value={get_value(CLOCK_MAX_GPU)}
|
||||||
|
max={1600}
|
||||||
|
min={200}
|
||||||
|
step={100}
|
||||||
|
showValue={true}
|
||||||
|
disabled={get_value(CLOCK_MAX_GPU) == null}
|
||||||
|
onChange={(val: number) => {
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
<ToggleField
|
||||||
|
checked={get_value(SLOW_MEMORY_GPU)}
|
||||||
|
label="Downclock Memory"
|
||||||
|
description="Force RAM into low-power mode"
|
||||||
|
onChange={(value: boolean) => {
|
||||||
|
backend.resolve(backend.setGpuSlowMemory(value), (val: boolean) => {
|
||||||
|
set_value(SLOW_MEMORY_GPU, val);
|
||||||
|
reloadGUI("GPUSlowMemory");
|
||||||
|
})
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
{/* Battery */}
|
{/* Battery */}
|
||||||
<div className={staticClasses.PanelSectionTitle}>
|
<div className={staticClasses.PanelSectionTitle}>
|
||||||
Battery
|
Battery
|
||||||
</div>
|
</div>
|
||||||
<PanelSectionRow>
|
{ false && <PanelSectionRow>
|
||||||
<div className={FieldWithSeparator}>
|
<div className={FieldWithSeparator}>
|
||||||
<div className={gamepadDialogClasses.FieldLabelRow}>
|
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||||
<div className={gamepadDialogClasses.FieldLabel}>
|
<div className={gamepadDialogClasses.FieldLabel}>
|
||||||
Now (Charge)
|
Now (Charge)
|
||||||
</div>
|
</div>
|
||||||
<div className={gamepadDialogClasses.FieldChildren}>
|
<div className={gamepadDialogClasses.FieldChildren}>
|
||||||
{(7.7 * chargeNowGlobal / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeNowGlobal / chargeFullGlobal).toFixed(1).toString() + "%)"}
|
{/* TODO: (7.7 * chargeNowGlobal / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeNowGlobal / chargeFullGlobal).toFixed(1).toString() + "%)"*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>}
|
||||||
<PanelSectionRow>
|
{ false && <PanelSectionRow>
|
||||||
<div className={FieldWithSeparator}>
|
<div className={FieldWithSeparator}>
|
||||||
<div className={gamepadDialogClasses.FieldLabelRow}>
|
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||||
<div className={gamepadDialogClasses.FieldLabel}>
|
<div className={gamepadDialogClasses.FieldLabel}>
|
||||||
Max (Design)
|
Max (Design)
|
||||||
</div>
|
</div>
|
||||||
<div className={gamepadDialogClasses.FieldChildren}>
|
<div className={gamepadDialogClasses.FieldChildren}>
|
||||||
{(7.7 * chargeFullGlobal / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeFullGlobal / chargeDesignGlobal).toFixed(1).toString() + "%)"}
|
{/* TODO: (7.7 * chargeFullGlobal / 1000000).toFixed(1).toString() + " Wh (" + (100 * chargeFullGlobal / chargeDesignGlobal).toFixed(1).toString() + "%)"*/}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PanelSectionRow>}
|
||||||
|
<PanelSectionRow>
|
||||||
|
<ToggleField
|
||||||
|
checked={get_value(CHARGE_RATE_BATT) != null}
|
||||||
|
label="Charge Current Limits"
|
||||||
|
description="Control battery charge rate"
|
||||||
|
onChange={(value: boolean) => {
|
||||||
|
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 && <SliderField
|
||||||
|
label="Maximum (mA)"
|
||||||
|
value={get_value(CHARGE_RATE_BATT)}
|
||||||
|
max={2500}
|
||||||
|
min={250}
|
||||||
|
step={50}
|
||||||
|
showValue={true}
|
||||||
|
disabled={get_value(CHARGE_RATE_BATT) == null}
|
||||||
|
onChange={(val: number) => {
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
<div className={FieldWithSeparator}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabel}>
|
||||||
|
Current
|
||||||
|
</div>
|
||||||
|
<div className={gamepadDialogClasses.FieldChildren}>
|
||||||
|
{get_value(CURRENT_BATT)} mA
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
{/* Persistence */}
|
{/* Persistence */}
|
||||||
|
<div className={staticClasses.PanelSectionTitle}>
|
||||||
|
Miscellaneous
|
||||||
|
</div>
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
<ToggleField
|
<ToggleField
|
||||||
checked={persistGlobal}
|
checked={get_value(PERSISTENT_GEN)}
|
||||||
label="Persistent"
|
label="Persistent"
|
||||||
description="Restores settings after an app or OS restart"
|
description="Restores settings after an app or OS restart"
|
||||||
onChange={(persist: boolean) => {
|
onChange={(persist: boolean) => {
|
||||||
console.log("Persist is now " + persist.toString());
|
console.debug("Persist is now " + persist.toString());
|
||||||
python.execute(python.setPersistent(persist));
|
backend.resolve(
|
||||||
python.resolve(python.getPersistent(), setPersist);
|
backend.setGeneralPersistent(persist),
|
||||||
}}
|
(val: boolean) => {set_value(PERSISTENT_GEN, val)}
|
||||||
/>
|
);
|
||||||
</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>
|
||||||
|
@ -374,12 +542,12 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
|
||||||
Now Playing
|
Now Playing
|
||||||
</div>
|
</div>
|
||||||
<div className={gamepadDialogClasses.FieldChildren}>
|
<div className={gamepadDialogClasses.FieldChildren}>
|
||||||
{gameGlobal}
|
{get_value(NAME_GEN)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
{/* Version */}
|
{/* Version Info */}
|
||||||
<div className={staticClasses.PanelSectionTitle}>
|
<div className={staticClasses.PanelSectionTitle}>
|
||||||
Debug
|
Debug
|
||||||
</div>
|
</div>
|
||||||
|
@ -387,68 +555,75 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
|
||||||
<div className={FieldWithSeparator}>
|
<div className={FieldWithSeparator}>
|
||||||
<div className={gamepadDialogClasses.FieldLabelRow}>
|
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||||
<div className={gamepadDialogClasses.FieldLabel}>
|
<div className={gamepadDialogClasses.FieldLabel}>
|
||||||
PowerTools
|
Native
|
||||||
</div>
|
</div>
|
||||||
<div className={gamepadDialogClasses.FieldChildren}>
|
<div className={gamepadDialogClasses.FieldChildren}>
|
||||||
v{versionGlobal}
|
{get_value(BACKEND_INFO)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
<div className={FieldWithSeparator}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabel}>
|
||||||
|
Framework
|
||||||
|
</div>
|
||||||
|
<div className={gamepadDialogClasses.FieldChildren}>
|
||||||
|
{target_usdpl()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
<div className={FieldWithSeparator}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabel}>
|
||||||
|
USDPL
|
||||||
|
</div>
|
||||||
|
<div className={gamepadDialogClasses.FieldChildren}>
|
||||||
|
v{version_usdpl()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
<ButtonItem
|
||||||
|
layout="below"
|
||||||
|
onClick={(_: MouseEvent) => {
|
||||||
|
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
|
||||||
|
</ButtonItem>
|
||||||
|
</PanelSectionRow>
|
||||||
</PanelSection>
|
</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) => {
|
export default definePlugin((serverApi: ServerAPI) => {
|
||||||
serverApi.routerHook.addRoute("/decky-plugin-test", DeckyPluginRouterTest, {
|
|
||||||
exact: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
python.setServer(serverApi);
|
|
||||||
|
|
||||||
//@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 {
|
return {
|
||||||
title: <div className={staticClasses.Title}>PowerTools</div>,
|
title: <div className={staticClasses.Title}>PowerTools</div>,
|
||||||
content: <Content serverAPI={serverApi} />,
|
content: <Content serverAPI={serverApi} />,
|
||||||
icon: <GiDrill />,
|
icon: <GiDrill />,
|
||||||
onDismount() {
|
onDismount() {
|
||||||
console.log("PowerTools shutting down");
|
console.debug("PowerTools shutting down");
|
||||||
clearInterval(periodicHook!);
|
clearInterval(periodicHook!);
|
||||||
periodicHook = null;
|
periodicHook = null;
|
||||||
lifetimeHook!.unregister();
|
lifetimeHook!.unregister();
|
||||||
startHook!.unregister();
|
startHook!.unregister();
|
||||||
serverApi.routerHook.removeRoute("/decky-plugin-test");
|
serverApi.routerHook.removeRoute("/decky-plugin-test");
|
||||||
firstTime = true;
|
console.debug("Unregistered PowerTools callbacks, goodbye.");
|
||||||
lastGame = "";
|
|
||||||
console.log("Unregistered PowerTools callbacks, goodbye.");
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
9
src/usdpl_front/README.md
Normal file
9
src/usdpl_front/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[![Crates.io](https://img.shields.io/crates/v/usdpl-front?style=flat-square)](https://crates.io/crates/usdpl-front)
|
||||||
|
|
||||||
|
# usdpl-front-front
|
||||||
|
|
||||||
|
Front-end library to be called from Javascript.
|
||||||
|
Targets WASM.
|
||||||
|
|
||||||
|
In true Javascript tradition, this part of the library does not support error handling.
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"NGnius (Graham) <ngniusness@gmail.com>"
|
"NGnius (Graham) <ngniusness@gmail.com>"
|
||||||
],
|
],
|
||||||
"description": "Universal Steam Deck Plugin Library front-end designed for WASM",
|
"description": "Universal Steam Deck Plugin Library front-end designed for WASM",
|
||||||
"version": "0.6.0",
|
"version": "0.6.2",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
25
src/usdpl_front/usdpl_front.d.ts
vendored
25
src/usdpl_front/usdpl_front.d.ts
vendored
|
@ -9,7 +9,25 @@ export function init_usdpl(port: number): void;
|
||||||
* Get the targeted plugin framework, or "any" if unknown
|
* Get the targeted plugin framework, or "any" if unknown
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function target(): string;
|
export function target_usdpl(): string;
|
||||||
|
/**
|
||||||
|
* Get the UDSPL front-end version
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function version_usdpl(): string;
|
||||||
|
/**
|
||||||
|
* Get the targeted plugin framework, or "any" if unknown
|
||||||
|
* @param {string} key
|
||||||
|
* @param {any} value
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
export function set_value(key: string, value: any): any;
|
||||||
|
/**
|
||||||
|
* Get the targeted plugin framework, or "any" if unknown
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
export function get_value(key: string): any;
|
||||||
/**
|
/**
|
||||||
* Call a function on the back-end.
|
* Call a function on the back-end.
|
||||||
* Returns null (None) if this fails for any reason.
|
* Returns null (None) if this fails for any reason.
|
||||||
|
@ -24,7 +42,10 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
||||||
export interface InitOutput {
|
export interface InitOutput {
|
||||||
readonly memory: WebAssembly.Memory;
|
readonly memory: WebAssembly.Memory;
|
||||||
readonly init_usdpl: (a: number) => void;
|
readonly init_usdpl: (a: number) => void;
|
||||||
readonly target: (a: number) => void;
|
readonly target_usdpl: (a: number) => void;
|
||||||
|
readonly version_usdpl: (a: number) => void;
|
||||||
|
readonly set_value: (a: number, b: number, c: number) => number;
|
||||||
|
readonly get_value: (a: number, b: number) => number;
|
||||||
readonly call_backend: (a: number, b: number, c: number, d: number) => number;
|
readonly call_backend: (a: number, b: number, c: number, d: number) => number;
|
||||||
readonly __wbindgen_export_0: (a: number) => number;
|
readonly __wbindgen_export_0: (a: number) => number;
|
||||||
readonly __wbindgen_export_1: (a: number, b: number, c: number) => number;
|
readonly __wbindgen_export_1: (a: number, b: number, c: number) => number;
|
||||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
5
src/usdpl_front/usdpl_front_bg.wasm.d.ts
vendored
5
src/usdpl_front/usdpl_front_bg.wasm.d.ts
vendored
|
@ -2,7 +2,10 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export const memory: WebAssembly.Memory;
|
export const memory: WebAssembly.Memory;
|
||||||
export function init_usdpl(a: number): void;
|
export function init_usdpl(a: number): void;
|
||||||
export function target(a: number): void;
|
export function target_usdpl(a: number): void;
|
||||||
|
export function version_usdpl(a: number): void;
|
||||||
|
export function set_value(a: number, b: number, c: number): number;
|
||||||
|
export function get_value(a: number, b: number): number;
|
||||||
export function call_backend(a: number, b: number, c: number, d: number): number;
|
export function call_backend(a: number, b: number, c: number, d: number): number;
|
||||||
export function __wbindgen_export_0(a: number): number;
|
export function __wbindgen_export_0(a: number): number;
|
||||||
export function __wbindgen_export_1(a: number, b: number, c: number): number;
|
export function __wbindgen_export_1(a: number, b: number, c: number): number;
|
||||||
|
|
Loading…
Reference in a new issue