forked from NG-SD-Plugins/PowerTools
Merge branch 'dev'
This commit is contained in:
commit
1ca1148a4c
109 changed files with 9502 additions and 1994 deletions
.gitignoreMakefile
backend
Cargo.lockCargo.tomlDockerfilebuild-docker.sh
package.jsonpnpm-lock.yamlpt_oc.jsonlimits_core
limits_srv
src
api
api_worker.rsconsts.rsmain.rspersist
resume_worker.rssettings
battery.rscpu.rs
detect
driver.rsgeneral.rsgeneric
generic_amd
min_max.rsmod.rssteam_deck
traits.rsunknown
util.rsstate
utility.rssrc
translations
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -44,3 +44,8 @@ yalc.lock
|
|||
/backend/target
|
||||
/bin
|
||||
/backend/out
|
||||
/**/target
|
||||
|
||||
# packaging
|
||||
/PowerTools
|
||||
**.zip
|
||||
|
|
87
Makefile
Normal file
87
Makefile
Normal file
|
@ -0,0 +1,87 @@
|
|||
# Configuration settings
|
||||
PLUGIN_NAME ?= $(shell basename $(PWD))
|
||||
PLUGIN_VERSION ?= 0.3.0
|
||||
|
||||
# Source files
|
||||
TS_FILES := $(shell find src -name *.ts)
|
||||
TSX_FILES := $(shell find src -name *.tsx)
|
||||
SRC_FILES := $(TS_FILES) $(TSX_FILES) plugin.json
|
||||
|
||||
TAR_FILES := bin dist main.py package.json plugin.json
|
||||
|
||||
# plugin dir
|
||||
DATA_PATH ?= homebrew
|
||||
|
||||
# SSH Configuration
|
||||
SSH_USER ?= gamer
|
||||
SSH_HOST ?= 192.168.0.246
|
||||
SSH_MOUNT_PATH ?= /tmp/remote
|
||||
SSH_DATA_PATH ?= /home/$(SSH_USER)/$(DATA_PATH)
|
||||
|
||||
# Default target is to build and restart crankshaft
|
||||
.PHONY: default
|
||||
default: build restart
|
||||
|
||||
.PHONY: build
|
||||
build: build ## Builds the project
|
||||
cd backend && ./build.sh && cd ..
|
||||
|
||||
dist: $(SRC_FILES) node_modules
|
||||
npm run build
|
||||
|
||||
.PHONY: watch
|
||||
watch: ## Build and watch for source code changes
|
||||
npm run build-watch
|
||||
|
||||
package-lock.json: package.json
|
||||
npm i
|
||||
|
||||
node_modules: node_modules/installed ## Install dependencies
|
||||
node_modules/installed: package-lock.json
|
||||
npm ci
|
||||
touch $@
|
||||
|
||||
.PHONY: restart
|
||||
restart: ## Restart crankshaft
|
||||
ssh $(SSH_USER)@$(SSH_HOST) sudo systemctl restart plugin_loader -S
|
||||
|
||||
.PHONY: debug
|
||||
debug: ## Show Makefile variables
|
||||
@echo "Source Files: $(SRC_FILES)"
|
||||
|
||||
.PHONY: cef-debug
|
||||
cef-debug: ## Open Chrome CEF debugging. Add a network target: localhost:8080
|
||||
chromium "chrome://inspect/#devices"
|
||||
|
||||
.PHONY: tunnel
|
||||
tunnel: ## Create an SSH tunnel to remote Steam Client (accessible on localhost:4040)
|
||||
ssh $(SSH_USER)@$(SSH_HOST) -N -f -L 4040:localhost:8080
|
||||
|
||||
$(SSH_MOUNT_PATH)/.mounted:
|
||||
mkdir -p $(SSH_MOUNT_PATH)
|
||||
sshfs -o default_permissions $(SSH_USER)@$(SSH_HOST):$(SSH_DATA_PATH) $(SSH_MOUNT_PATH)
|
||||
touch $(SSH_MOUNT_PATH)/.mounted
|
||||
$(MAKE) tunnel
|
||||
|
||||
# Cleans and transfers the project
|
||||
$(SSH_MOUNT_PATH)/plugins/$(PLUGIN_NAME): $(SRC_FILES)
|
||||
rsync -avh $(PWD)/ $(SSH_MOUNT_PATH)/plugins/$(PLUGIN_NAME) --delete
|
||||
|
||||
.PHONY: remote-restart
|
||||
remote-restart: ## Restart remote crankshaft
|
||||
ssh $(SSH_USER)@$(SSH_HOST) sudo systemctl restart plugin_loader
|
||||
|
||||
.PHONY: mount
|
||||
mount: $(SSH_MOUNT_PATH)/.mounted
|
||||
|
||||
.PHONY: remote-update
|
||||
remote-update: $(SSH_MOUNT_PATH)/plugins/$(PLUGIN_NAME)
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Clean all build artifacts
|
||||
rm -rf build dist bin
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show this help message
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
727
backend/Cargo.lock
generated
727
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,27 +1,55 @@
|
|||
[package]
|
||||
name = "powertools-rs"
|
||||
version = "1.0.5+1"
|
||||
version = "1.1.0"
|
||||
edition = "2021"
|
||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||
description = "Backend (superuser) functionality for PowerTools"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/NGnius/PowerTools"
|
||||
keywords = ["utility", "power-management", "root", "decky"]
|
||||
readme = "../README.md"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
usdpl-back = { version = "0.6.0", features = ["blocking"]}
|
||||
usdpl-back = { version = "0.9.1", features = ["blocking"] }#, path = "../../usdpl-rs/usdpl-back"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
# async
|
||||
tokio = { version = "*", features = ["time"] }
|
||||
async-trait = { version = "0.1" }
|
||||
|
||||
# logging
|
||||
log = "0.4"
|
||||
simplelog = "0.12"
|
||||
|
||||
# limits & driver functionality
|
||||
limits_core = { version = "0.1.0", path = "./limits_core" }
|
||||
regex = "1"
|
||||
ryzenadj-rs = { version = "0.1" }
|
||||
# ureq's tls feature does not like musl targets
|
||||
ureq = { version = "2.5", features = ["json", "gzip", "brotli", "charset"], default-features = false, optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["online"]
|
||||
decky = ["usdpl-back/decky"]
|
||||
crankshaft = ["usdpl-back/crankshaft"]
|
||||
encrypt = ["usdpl-back/encrypt"]
|
||||
online = ["ureq"]
|
||||
|
||||
[profile.release]
|
||||
debug = false
|
||||
strip = true
|
||||
lto = true
|
||||
codegen-units = 4
|
||||
codegen-units = 1
|
||||
|
||||
[profile.docker]
|
||||
inherits = "release"
|
||||
debug = false
|
||||
strip = true
|
||||
lto = "thin"
|
||||
codegen-units = 16
|
||||
opt-level = 2
|
||||
debug-assertions = false
|
||||
overflow-checks = false
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
FROM ghcr.io/steamdeckhomebrew/holo-toolchain-rust:latest
|
||||
|
||||
ENTRYPOINT [ "/backend/entrypoint.sh" ]
|
||||
RUN pacman -S --noconfirm cmake make
|
||||
|
||||
ENTRYPOINT [ "/backend/entrypoint.sh" ]
|
||||
|
|
|
@ -6,7 +6,7 @@ rustc --version
|
|||
cargo --version
|
||||
|
||||
echo "--- Building plugin backend ---"
|
||||
cargo build --release
|
||||
cargo build --profile docker
|
||||
mkdir -p out
|
||||
cp target/release/powertools-rs out/backend
|
||||
|
||||
|
|
89
backend/limits_core/Cargo.lock
generated
Normal file
89
backend/limits_core/Cargo.lock
generated
Normal file
|
@ -0,0 +1,89 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "limits_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
10
backend/limits_core/Cargo.toml
Normal file
10
backend/limits_core/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "limits_core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
66
backend/limits_core/src/json/base.rs
Normal file
66
backend/limits_core/src/json/base.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use std::default::Default;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Base JSON limits information
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Base {
|
||||
/// System-specific configurations
|
||||
pub configs: Vec<super::Config>,
|
||||
/// URL from which to grab the next update
|
||||
pub refresh: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Base {
|
||||
fn default() -> Self {
|
||||
Base {
|
||||
configs: vec![
|
||||
super::Config {
|
||||
name: "Steam Deck Custom".to_owned(),
|
||||
conditions: super::Conditions {
|
||||
dmi: None,
|
||||
cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()),
|
||||
os: None,
|
||||
command: None,
|
||||
file_exists: Some("./pt_oc.json".into()),
|
||||
},
|
||||
limits: vec![
|
||||
super::Limits::Cpu(super::CpuLimit::SteamDeckAdvance),
|
||||
super::Limits::Gpu(super::GpuLimit::SteamDeckAdvance),
|
||||
super::Limits::Battery(super::BatteryLimit::SteamDeckAdvance),
|
||||
]
|
||||
},
|
||||
super::Config {
|
||||
name: "Steam Deck".to_owned(),
|
||||
conditions: super::Conditions {
|
||||
dmi: None,
|
||||
cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()),
|
||||
os: None,
|
||||
command: None,
|
||||
file_exists: None,
|
||||
},
|
||||
limits: vec![
|
||||
super::Limits::Cpu(super::CpuLimit::SteamDeck),
|
||||
super::Limits::Gpu(super::GpuLimit::SteamDeck),
|
||||
super::Limits::Battery(super::BatteryLimit::SteamDeck),
|
||||
]
|
||||
},
|
||||
super::Config {
|
||||
name: "Fallback".to_owned(),
|
||||
conditions: super::Conditions {
|
||||
dmi: None,
|
||||
cpuinfo: None,
|
||||
os: None,
|
||||
command: None,
|
||||
file_exists: None,
|
||||
},
|
||||
limits: vec![
|
||||
super::Limits::Cpu(super::CpuLimit::Unknown),
|
||||
super::Limits::Gpu(super::GpuLimit::Unknown),
|
||||
super::Limits::Battery(super::BatteryLimit::Unknown),
|
||||
]
|
||||
}
|
||||
],
|
||||
refresh: Some("http://limits.ngni.us:45000/powertools/v1".to_owned())
|
||||
}
|
||||
}
|
||||
}
|
15
backend/limits_core/src/json/battery_limit.rs
Normal file
15
backend/limits_core/src/json/battery_limit.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "target")]
|
||||
pub enum BatteryLimit {
|
||||
SteamDeck,
|
||||
SteamDeckAdvance,
|
||||
Generic(GenericBatteryLimit),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GenericBatteryLimit {
|
||||
/* TODO */
|
||||
}
|
26
backend/limits_core/src/json/conditions.rs
Normal file
26
backend/limits_core/src/json/conditions.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Conditions under which a config applies (ANDed together)
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Conditions {
|
||||
/// Regex pattern for dmidecode output
|
||||
pub dmi: Option<String>,
|
||||
/// Regex pattern for /proc/cpuinfo reading
|
||||
pub cpuinfo: Option<String>,
|
||||
/// Regex pattern for /etc/os-release reading
|
||||
pub os: Option<String>,
|
||||
/// Custom command to run, where an exit code of 0 means a successful match
|
||||
pub command: Option<String>,
|
||||
/// Check if file exists
|
||||
pub file_exists: Option<String>,
|
||||
}
|
||||
|
||||
impl Conditions {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.dmi.is_none()
|
||||
&& self.cpuinfo.is_none()
|
||||
&& self.os.is_none()
|
||||
&& self.command.is_none()
|
||||
&& self.file_exists.is_none()
|
||||
}
|
||||
}
|
8
backend/limits_core/src/json/config.rs
Normal file
8
backend/limits_core/src/json/config.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub name: String,
|
||||
pub conditions: super::Conditions,
|
||||
pub limits: Vec<super::Limits>,
|
||||
}
|
20
backend/limits_core/src/json/cpu_limit.rs
Normal file
20
backend/limits_core/src/json/cpu_limit.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::RangeLimit;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "target")]
|
||||
pub enum CpuLimit {
|
||||
SteamDeck,
|
||||
SteamDeckAdvance,
|
||||
Generic(GenericCpuLimit),
|
||||
GenericAMD(GenericCpuLimit),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GenericCpuLimit {
|
||||
pub clock_min: Option<RangeLimit<u64>>,
|
||||
pub clock_max: Option<RangeLimit<u64>>,
|
||||
pub clock_step: u64,
|
||||
}
|
25
backend/limits_core/src/json/gpu_limit.rs
Normal file
25
backend/limits_core/src/json/gpu_limit.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use super::RangeLimit;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "target")]
|
||||
pub enum GpuLimit {
|
||||
SteamDeck,
|
||||
SteamDeckAdvance,
|
||||
Generic(GenericGpuLimit),
|
||||
GenericAMD(GenericGpuLimit),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GenericGpuLimit {
|
||||
pub fast_ppt: Option<RangeLimit<u64>>,
|
||||
pub slow_ppt: Option<RangeLimit<u64>>,
|
||||
pub ppt_step: Option<u64>,
|
||||
pub tdp: Option<RangeLimit<u64>>,
|
||||
pub tdp_boost: Option<RangeLimit<u64>>,
|
||||
pub tdp_step: Option<u64>,
|
||||
pub clock_min: Option<RangeLimit<u64>>,
|
||||
pub clock_max: Option<RangeLimit<u64>>,
|
||||
pub clock_step: Option<u64>,
|
||||
}
|
9
backend/limits_core/src/json/limits.rs
Normal file
9
backend/limits_core/src/json/limits.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "limits")]
|
||||
pub enum Limits {
|
||||
Cpu(super::CpuLimit),
|
||||
Gpu(super::GpuLimit),
|
||||
Battery(super::BatteryLimit),
|
||||
}
|
19
backend/limits_core/src/json/mod.rs
Normal file
19
backend/limits_core/src/json/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
mod base;
|
||||
mod battery_limit;
|
||||
mod conditions;
|
||||
mod config;
|
||||
mod cpu_limit;
|
||||
mod gpu_limit;
|
||||
mod limits;
|
||||
mod range;
|
||||
mod target;
|
||||
|
||||
pub use base::Base;
|
||||
pub use battery_limit::{BatteryLimit, GenericBatteryLimit};
|
||||
pub use conditions::Conditions;
|
||||
pub use cpu_limit::{CpuLimit, GenericCpuLimit};
|
||||
pub use gpu_limit::{GpuLimit, GenericGpuLimit};
|
||||
pub use config::Config;
|
||||
pub use limits::Limits;
|
||||
pub use range::RangeLimit;
|
||||
pub use target::Target;
|
8
backend/limits_core/src/json/range.rs
Normal file
8
backend/limits_core/src/json/range.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Base JSON limits information
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RangeLimit<T> {
|
||||
pub min: T,
|
||||
pub max: T,
|
||||
}
|
9
backend/limits_core/src/json/target.rs
Normal file
9
backend/limits_core/src/json/target.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum Target {
|
||||
SteamDeck,
|
||||
SteamDeckAdvance,
|
||||
Generic,
|
||||
Unknown,
|
||||
}
|
1
backend/limits_core/src/lib.rs
Normal file
1
backend/limits_core/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod json;
|
1026
backend/limits_srv/Cargo.lock
generated
Normal file
1026
backend/limits_srv/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
backend/limits_srv/Cargo.toml
Normal file
12
backend/limits_srv/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "limits_srv"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
limits_core = { version = "0.1.0", path = "../limits_core" }
|
||||
serde_json = "1.0"
|
||||
warp = { version = "0.3" }
|
||||
tokio = { version = "1.22", features = ["macros", "rt", "rt-multi-thread"] }
|
64
backend/limits_srv/pt_limits.json
Normal file
64
backend/limits_srv/pt_limits.json
Normal file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"configs": [
|
||||
{
|
||||
"name": "Steam Deck Custom",
|
||||
"conditions": {
|
||||
"cpuinfo": "model name\t: AMD Custom APU 0405\n",
|
||||
"file_exists": "./pt_oc.json"
|
||||
},
|
||||
"limits": [
|
||||
{
|
||||
"limits": "Cpu",
|
||||
"target": "SteamDeck"
|
||||
},
|
||||
{
|
||||
"limits": "Gpu",
|
||||
"target": "SteamDeck"
|
||||
},
|
||||
{
|
||||
"limits": "Battery",
|
||||
"target": "SteamDeck"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Steam Deck",
|
||||
"conditions": {
|
||||
"cpuinfo": "model name\t: AMD Custom APU 0405\n"
|
||||
},
|
||||
"limits": [
|
||||
{
|
||||
"limits": "Cpu",
|
||||
"target": "SteamDeck"
|
||||
},
|
||||
{
|
||||
"limits": "Gpu",
|
||||
"target": "SteamDeck"
|
||||
},
|
||||
{
|
||||
"limits": "Battery",
|
||||
"target": "SteamDeck"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Fallback",
|
||||
"conditions": {},
|
||||
"limits": [
|
||||
{
|
||||
"limits": "Cpu",
|
||||
"target": "Unknown"
|
||||
},
|
||||
{
|
||||
"limits": "Gpu",
|
||||
"target": "Unknown"
|
||||
},
|
||||
{
|
||||
"limits": "Battery",
|
||||
"target": "Unknown"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"refresh": "http://limits.ngni.us:45000/powertools/v1"
|
||||
}
|
53
backend/limits_srv/src/main.rs
Normal file
53
backend/limits_srv/src/main.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use std::sync::atomic::{Ordering, AtomicU64};
|
||||
use std::sync::{RwLock, Arc};
|
||||
|
||||
use warp::Filter;
|
||||
|
||||
use limits_core::json::Base;
|
||||
|
||||
static VISIT_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
fn get_limits(base: Base) -> impl warp::Reply {
|
||||
VISIT_COUNT.fetch_add(1, Ordering::AcqRel);
|
||||
println!("Limits got");
|
||||
warp::reply::json(&base)
|
||||
}
|
||||
|
||||
fn get_visits() -> impl warp::Reply {
|
||||
let count = VISIT_COUNT.load(Ordering::Relaxed);
|
||||
println!("Count got");
|
||||
warp::reply::json(&count)
|
||||
}
|
||||
|
||||
fn routes(base: Arc<RwLock<Base>>) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::get().and(
|
||||
warp::path!("powertools" / "v1")
|
||||
.map(move || {
|
||||
let base = base.read().expect("Failed to acquire base limits read lock").clone();
|
||||
get_limits(base)
|
||||
})
|
||||
.or(
|
||||
warp::path!("powertools" / "count")
|
||||
.map(get_visits)
|
||||
)
|
||||
).recover(recovery)
|
||||
}
|
||||
|
||||
pub async fn recovery(reject: warp::Rejection) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
if reject.is_not_found() {
|
||||
Ok(warp::hyper::StatusCode::NOT_FOUND)
|
||||
} else {
|
||||
Err(reject)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let file = std::fs::File::open("./pt_limits.json").expect("Failed to read limits file");
|
||||
let limits: Base = serde_json::from_reader(file).expect("Failed to parse limits file");
|
||||
assert!(limits.refresh.is_some(), "`refresh` cannot be null, since it will brick future refreshes");
|
||||
|
||||
warp::serve(routes(Arc::new(RwLock::new(limits))))
|
||||
.run(([0, 0, 0, 0], 8080))
|
||||
.await;
|
||||
}
|
63
backend/src/api/api_types.rs
Normal file
63
backend/src/api/api_types.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct RangeLimit<T> {
|
||||
pub min: T,
|
||||
pub max: T,
|
||||
}
|
||||
|
||||
impl<T> From<limits_core::json::RangeLimit<T>> for RangeLimit<T> {
|
||||
#[inline]
|
||||
fn from(other: limits_core::json::RangeLimit<T>) -> Self {
|
||||
RangeLimit { min: other.min, max: other.max }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SettingsLimits {
|
||||
pub battery: BatteryLimits,
|
||||
pub cpu: CpusLimits,
|
||||
pub gpu: GpuLimits,
|
||||
pub general: GeneralLimits,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct BatteryLimits {
|
||||
pub charge_current: Option<RangeLimit<u64>>,
|
||||
pub charge_current_step: u64,
|
||||
pub charge_modes: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CpusLimits {
|
||||
pub cpus: Vec<CpuLimits>,
|
||||
pub count: usize,
|
||||
pub smt_capable: bool,
|
||||
pub governors: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CpuLimits {
|
||||
pub clock_min_limits: Option<RangeLimit<u64>>,
|
||||
pub clock_max_limits: Option<RangeLimit<u64>>,
|
||||
pub clock_step: u64,
|
||||
pub governors: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GeneralLimits {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GpuLimits {
|
||||
pub fast_ppt_limits: Option<RangeLimit<u64>>,
|
||||
pub slow_ppt_limits: Option<RangeLimit<u64>>,
|
||||
pub ppt_step: u64,
|
||||
pub tdp_limits: Option<RangeLimit<u64>>,
|
||||
pub tdp_boost_limits: Option<RangeLimit<u64>>,
|
||||
pub tdp_step: u64,
|
||||
pub clock_min_limits: Option<RangeLimit<u64>>,
|
||||
pub clock_max_limits: Option<RangeLimit<u64>>,
|
||||
pub clock_step: u64,
|
||||
pub memory_control_capable: bool,
|
||||
}
|
70
backend/src/api/async_utils.rs
Normal file
70
backend/src/api/async_utils.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
//use usdpl_back::core::serdes::Primitive;
|
||||
use usdpl_back::AsyncCallable;
|
||||
|
||||
pub struct AsyncIsh<In: Send + 'static,
|
||||
Out: Send + 'static,
|
||||
TS: (Fn(super::ApiParameterType) -> Result<In, String>) + Send + Sync,
|
||||
Gen: (Fn() -> SG) + Send + Sync,
|
||||
SG: (Fn(In) -> Out) + Send + Sync + 'static,
|
||||
TG: (Fn(Out) -> super::ApiParameterType) + Send + Sync> {
|
||||
pub trans_setter: TS, // assumed to be pretty fast
|
||||
pub set_get: Gen, // probably has locks (i.e. slow)
|
||||
pub trans_getter: TG, // assumed to be pretty fast
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <In: Send + 'static,
|
||||
Out: Send + 'static,
|
||||
TS: (Fn(super::ApiParameterType) -> Result<In, String>) + Send + Sync,
|
||||
Gen: (Fn() -> SG) + Send + Sync,
|
||||
SG: (Fn(In) -> Out) + Send + Sync + 'static,
|
||||
TG: (Fn(Out) -> super::ApiParameterType) + Send + Sync>
|
||||
AsyncCallable for AsyncIsh<In, Out, TS, Gen, SG, TG> {
|
||||
async fn call(&self, params: super::ApiParameterType) -> super::ApiParameterType {
|
||||
let t_to_set = match (self.trans_setter)(params) {
|
||||
Ok(t) => t,
|
||||
Err(e) => return vec![e.into()]
|
||||
};
|
||||
let setter = (self.set_get)();
|
||||
let t_got = match tokio::task::spawn_blocking(move || setter(t_to_set)).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => return vec![e.to_string().into()],
|
||||
};
|
||||
(self.trans_getter)(t_got)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsyncIshGetter<T: Send + 'static,
|
||||
Gen: (Fn() -> G) + Send + Sync,
|
||||
G: (Fn() -> T) + Send + Sync + 'static,
|
||||
TG: (Fn(T) -> super::ApiParameterType) + Send + Sync> {
|
||||
pub set_get: Gen, // probably has locks (i.e. slow)
|
||||
pub trans_getter: TG, // assumed to be pretty fast
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <T: Send + 'static,
|
||||
Gen: (Fn() -> G) + Send + Sync,
|
||||
G: (Fn() -> T) + Send + Sync + 'static,
|
||||
TG: (Fn(T) -> super::ApiParameterType) + Send + Sync>
|
||||
AsyncCallable for AsyncIshGetter<T, Gen, G, TG> {
|
||||
async fn call(&self, _params: super::ApiParameterType) -> super::ApiParameterType {
|
||||
let getter = (self.set_get)();
|
||||
let t_got = match tokio::task::spawn_blocking(move || getter()).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => return vec![e.to_string().into()],
|
||||
};
|
||||
(self.trans_getter)(t_got)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Blocking<F: (Fn(super::ApiParameterType) -> super::ApiParameterType) + Send + Sync> {
|
||||
pub func: F,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <F: (Fn(super::ApiParameterType) -> super::ApiParameterType) + Send + Sync> AsyncCallable for Blocking<F> {
|
||||
async fn call(&self, params: super::ApiParameterType) -> super::ApiParameterType {
|
||||
(self.func)(params)
|
||||
}
|
||||
}
|
|
@ -1,47 +1,117 @@
|
|||
use std::sync::{mpsc::Sender, Arc, Mutex};
|
||||
use std::sync::mpsc::{Sender, self};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use usdpl_back::core::serdes::Primitive;
|
||||
use usdpl_back::AsyncCallable;
|
||||
|
||||
use crate::settings::{Battery, OnSet};
|
||||
use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
|
||||
use super::handler::{ApiMessage, BatteryMessage};
|
||||
|
||||
/// Current current (ha!) web method
|
||||
pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType {
|
||||
super::utility::map_result(crate::settings::Battery::read_current_now())
|
||||
pub fn current_now(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |val: Option<f64>| tx.send(val).expect("current_now callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadCurrentNow(Box::new(callback)))).expect("current_now send failed");
|
||||
rx.recv().expect("current_now callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |result| {
|
||||
super::utility::map_optional_result(Ok(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Current current (ha!) web method
|
||||
/*pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType {
|
||||
super::utility::map_optional_result(crate::settings::driver::read_current_now())
|
||||
}*/
|
||||
|
||||
/// Charge now web method
|
||||
pub fn charge_now(_: super::ApiParameterType) -> super::ApiParameterType {
|
||||
super::utility::map_result(crate::settings::Battery::read_charge_now())
|
||||
pub fn charge_now(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |val: Option<f64>| tx.send(val).expect("charge_now callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadChargeNow(Box::new(callback)))).expect("charge_now send failed");
|
||||
rx.recv().expect("charge_now callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |result| {
|
||||
super::utility::map_optional_result(Ok(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Charge full web method
|
||||
pub fn charge_full(_: super::ApiParameterType) -> super::ApiParameterType {
|
||||
super::utility::map_result(crate::settings::Battery::read_charge_full())
|
||||
pub fn charge_full(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |val: Option<f64>| tx.send(val).expect("charge_full callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadChargeFull(Box::new(callback)))).expect("charge_full send failed");
|
||||
rx.recv().expect("charge_full callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |result| {
|
||||
super::utility::map_optional_result(Ok(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Charge design web method
|
||||
pub fn charge_design(_: super::ApiParameterType) -> super::ApiParameterType {
|
||||
super::utility::map_result(crate::settings::Battery::read_charge_design())
|
||||
pub fn charge_design(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |val: Option<f64>| tx.send(val).expect("charge_design callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadChargeDesign(Box::new(callback)))).expect("charge_design send failed");
|
||||
rx.recv().expect("charge_design callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |result| {
|
||||
super::utility::map_optional_result(Ok(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate set battery charge rate web method
|
||||
pub fn set_charge_rate(
|
||||
settings: Arc<Mutex<Battery>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |rate: f64|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Battery(BatteryMessage::SetChargeRate(Some(rate as u64))))
|
||||
.expect("set_charge_rate send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::F64(new_val)) = params_in.get(0) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "battery");
|
||||
settings_lock.charge_rate = Some(*new_val as _);
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
super::utility::map_empty_result(
|
||||
settings_lock.on_set(),
|
||||
settings_lock.charge_rate.unwrap(),
|
||||
)
|
||||
if let Some(&Primitive::F64(new_val)) = params_in.get(0) {
|
||||
setter(new_val);
|
||||
vec![(new_val).into()]
|
||||
} else {
|
||||
vec!["set_charge_rate missing parameter".into()]
|
||||
}
|
||||
|
@ -50,30 +120,76 @@ pub fn set_charge_rate(
|
|||
|
||||
/// Generate get battery charge rate web method
|
||||
pub fn get_charge_rate(
|
||||
settings: Arc<Mutex<Battery>>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |rate: Option<u64>| tx.send(rate).expect("get_charge_rate callback send failed");
|
||||
sender.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::GetChargeRate(Box::new(callback)))).expect("get_charge_rate send failed");
|
||||
rx.recv().expect("get_charge_rate callback recv failed")
|
||||
};
|
||||
move |_: super::ApiParameterType| {
|
||||
let settings_lock = unwrap_lock(settings.lock(), "battery");
|
||||
vec![settings_lock
|
||||
.charge_rate
|
||||
.map(|x| x.into())
|
||||
.unwrap_or(Primitive::Empty)]
|
||||
vec![getter().map(|x| x.into()).unwrap_or(Primitive::Empty)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate unset battery charge rate web method
|
||||
pub fn unset_charge_rate(
|
||||
settings: Arc<Mutex<Battery>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
move |_: super::ApiParameterType| {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "battery");
|
||||
settings_lock.charge_rate = None;
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
super::utility::map_empty_result(settings_lock.on_set(), true)
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move || sender.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::SetChargeRate(None))).expect("unset_charge_rate send failed");
|
||||
move |_params_in: super::ApiParameterType| {
|
||||
setter();
|
||||
vec![true.into()]
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate set battery charge mode web method
|
||||
pub fn set_charge_mode(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |mode: String|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Battery(BatteryMessage::SetChargeMode(Some(mode))))
|
||||
.expect("set_charge_mode send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::String(new_val)) = params_in.get(0) {
|
||||
setter(new_val.to_owned());
|
||||
vec![new_val.to_owned().into()]
|
||||
} else {
|
||||
vec!["set_charge_rate missing parameter".into()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate get battery charge mode web method
|
||||
pub fn get_charge_mode(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |mode: Option<String>| tx.send(mode).expect("get_charge_mode callback send failed");
|
||||
sender.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::GetChargeMode(Box::new(callback)))).expect("get_charge_mode send failed");
|
||||
rx.recv().expect("get_charge_mode callback recv failed")
|
||||
};
|
||||
move |_: super::ApiParameterType| {
|
||||
vec![getter().map(|x| x.into()).unwrap_or(Primitive::Empty)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate unset battery charge mode web method
|
||||
pub fn unset_charge_mode(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move || sender.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::SetChargeMode(None))).expect("unset_charge_mode send failed");
|
||||
move |_params_in: super::ApiParameterType| {
|
||||
setter();
|
||||
vec![true.into()]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use std::sync::{mpsc::Sender, Arc, Mutex};
|
||||
use std::sync::mpsc::{Sender, self};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use usdpl_back::core::serdes::Primitive;
|
||||
use usdpl_back::AsyncCallable;
|
||||
|
||||
use crate::settings::{Cpu, OnSet, SettingError, SettingVariant, MinMax};
|
||||
use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
|
||||
use crate::settings::{SettingError, SettingVariant, MinMax};
|
||||
//use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
|
||||
use super::handler::{ApiMessage, CpuMessage};
|
||||
|
||||
/// Available CPUs web method
|
||||
pub fn max_cpus(_: super::ApiParameterType) -> super::ApiParameterType {
|
||||
super::utility::map_result(
|
||||
Cpu::cpu_count()
|
||||
crate::settings::steam_deck::Cpus::cpu_count()
|
||||
.map(|x| x as u64)
|
||||
.ok_or_else(
|
||||
|| SettingError {
|
||||
|
@ -20,29 +23,21 @@ pub fn max_cpus(_: super::ApiParameterType) -> super::ApiParameterType {
|
|||
|
||||
/// Generate set CPU online web method
|
||||
pub fn set_cpu_online(
|
||||
settings: Arc<Mutex<Vec<Cpu>>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |index: usize, value: bool|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Cpu(CpuMessage::SetCpuOnline(index, value))).expect("set_cpu_online send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::F64(index)) = params_in.get(0) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
|
||||
if let Some(Primitive::Bool(online)) = params_in.get(1) {
|
||||
cpu.online = *online;
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
super::utility::map_empty_result(
|
||||
cpu.on_set(),
|
||||
cpu.online,
|
||||
)
|
||||
} else {
|
||||
vec!["set_cpu_online missing parameter 1".into()]
|
||||
}
|
||||
if let Some(&Primitive::F64(index)) = params_in.get(0) {
|
||||
//let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
if let Some(&Primitive::Bool(online)) = params_in.get(1) {
|
||||
setter(index as usize, online);
|
||||
vec![online.into()]
|
||||
} else {
|
||||
vec!["set_cpu_online cpu index out of bounds".into()]
|
||||
vec!["set_cpu_online missing parameter 1".into()]
|
||||
}
|
||||
} else {
|
||||
vec!["set_cpu_online missing parameter 0".into()]
|
||||
|
@ -51,81 +46,157 @@ pub fn set_cpu_online(
|
|||
}
|
||||
|
||||
pub fn set_cpus_online(
|
||||
settings: Arc<Mutex<Vec<Cpu>>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |values: Vec<bool>|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Cpu(CpuMessage::SetCpusOnline(values))).expect("set_cpus_online send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
let mut result = Vec::with_capacity(params_in.len());
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
let mut values = Vec::with_capacity(params_in.len());
|
||||
for i in 0..params_in.len() {
|
||||
if let Primitive::Bool(online) = params_in[i] {
|
||||
if let Some(cpu) = settings_lock.get_mut(i) {
|
||||
cpu.online = online;
|
||||
match cpu.on_set() {
|
||||
Ok(_) => result.push(cpu.online.into()),
|
||||
Err(e) => result.push(e.msg.into())
|
||||
}
|
||||
}
|
||||
values.push(online);
|
||||
result.push(online.into());
|
||||
} else {
|
||||
values.push(true);
|
||||
result.push(format!("Invalid parameter {}", i).into())
|
||||
}
|
||||
}
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
setter(values);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cpus_online(
|
||||
settings: Arc<Mutex<Vec<Cpu>>>,
|
||||
/*pub fn get_cpus_online(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |values: Vec<bool>| tx.send(values).expect("get_cpus_online callback send failed");
|
||||
sender.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetCpusOnline(Box::new(callback)))).expect("get_cpus_online send failed");
|
||||
rx.recv().expect("get_cpus_online callback recv failed")
|
||||
};
|
||||
move |_: super::ApiParameterType| {
|
||||
let settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
let mut output = Vec::with_capacity(settings_lock.len());
|
||||
for cpu in settings_lock.as_slice() {
|
||||
output.push(cpu.online.into());
|
||||
let result = getter();
|
||||
let mut output = Vec::with_capacity(result.len());
|
||||
for &status in result.as_slice() {
|
||||
output.push(status.into());
|
||||
}
|
||||
output
|
||||
}
|
||||
}*/
|
||||
|
||||
pub fn set_smt(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move |smt: bool| {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |values: Vec<bool>| tx.send(values).expect("set_smt callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::SetSmt(smt, Box::new(callback)))).expect("set_smt send failed");
|
||||
rx.recv().expect("set_smt callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIsh {
|
||||
trans_setter: |params| {
|
||||
if let Some(&Primitive::Bool(smt_value)) = params.get(0) {
|
||||
Ok(smt_value)
|
||||
} else {
|
||||
Err("set_smt missing/invalid parameter 0".to_owned())
|
||||
}
|
||||
},
|
||||
set_get: getter,
|
||||
trans_getter: |result| {
|
||||
let mut output = Vec::with_capacity(result.len());
|
||||
for &status in result.as_slice() {
|
||||
output.push(status.into());
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_smt(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |value: bool| tx.send(value).expect("get_smt callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetSmt(Box::new(callback)))).expect("get_smt send failed");
|
||||
rx.recv().expect("get_smt callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |result| {
|
||||
vec![result.into()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cpus_online(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |values: Vec<bool>| tx.send(values).expect("get_cpus_online callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetCpusOnline(Box::new(callback)))).expect("get_cpus_online send failed");
|
||||
rx.recv().expect("get_cpus_online callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |result| {
|
||||
let mut output = Vec::with_capacity(result.len());
|
||||
for &status in result.as_slice() {
|
||||
output.push(status.into());
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_clock_limits(
|
||||
settings: Arc<Mutex<Vec<Cpu>>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |index: usize, value: MinMax<u64>|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Cpu(CpuMessage::SetClockLimits(index, Some(value)))).expect("set_clock_limits send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::F64(index)) = params_in.get(0) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
|
||||
if let Some(Primitive::F64(min)) = params_in.get(1) {
|
||||
if let Some(Primitive::F64(max)) = params_in.get(2) {
|
||||
cpu.clock_limits = Some(MinMax {
|
||||
min: *min as _,
|
||||
max: *max as _,
|
||||
});
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
match cpu.on_set() {
|
||||
Ok(_) => vec![
|
||||
cpu.clock_limits.as_ref().unwrap().min.into(),
|
||||
cpu.clock_limits.as_ref().unwrap().max.into(),
|
||||
],
|
||||
Err(e) => vec![e.msg.into()]
|
||||
}
|
||||
if let Some(&Primitive::F64(index)) = params_in.get(0) {
|
||||
if let Some(&Primitive::F64(min)) = params_in.get(1) {
|
||||
if let Some(&Primitive::F64(max)) = params_in.get(2) {
|
||||
let safe_max = if max < min {
|
||||
min
|
||||
} else {
|
||||
vec!["set_clock_limits missing parameter 2".into()]
|
||||
}
|
||||
max
|
||||
};
|
||||
let safe_min = if min > max {
|
||||
max
|
||||
} else {
|
||||
min
|
||||
};
|
||||
setter(index as usize, MinMax {min: safe_min as u64, max: safe_max as u64});
|
||||
vec![safe_min.into(), safe_max.into()]
|
||||
} else {
|
||||
vec!["set_clock_limits missing parameter 1".into()]
|
||||
vec!["set_clock_limits missing parameter 2".into()]
|
||||
}
|
||||
} else {
|
||||
vec!["set_clock_limits cpu index out of bounds".into()]
|
||||
vec!["set_clock_limits missing parameter 1".into()]
|
||||
}
|
||||
} else {
|
||||
vec!["set_clock_limits missing parameter 0".into()]
|
||||
|
@ -134,19 +205,21 @@ pub fn set_clock_limits(
|
|||
}
|
||||
|
||||
pub fn get_clock_limits(
|
||||
settings: Arc<Mutex<Vec<Cpu>>>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let getter = move |index: usize| {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |values: Option<MinMax<u64>>| tx.send(values).expect("get_clock_limits callback send failed");
|
||||
sender.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetClockLimits(index, Box::new(callback)))).expect("get_clock_limits send failed");
|
||||
rx.recv().expect("get_clock_limits callback recv failed")
|
||||
};
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::F64(index)) = params_in.get(0) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
|
||||
if let Some(min_max) = &cpu.clock_limits {
|
||||
vec![min_max.min.into(), min_max.max.into()]
|
||||
} else {
|
||||
vec![Primitive::Empty, Primitive::Empty]
|
||||
}
|
||||
if let Some(&Primitive::F64(index)) = params_in.get(0) {
|
||||
if let Some(min_max) = getter(index as usize) {
|
||||
vec![min_max.min.into(), min_max.max.into()]
|
||||
} else {
|
||||
vec!["get_clock_limits cpu index out of bounds".into()]
|
||||
vec![Primitive::Empty, Primitive::Empty]
|
||||
}
|
||||
} else {
|
||||
vec!["get_clock_limits missing parameter 0".into()]
|
||||
|
@ -155,23 +228,17 @@ pub fn get_clock_limits(
|
|||
}
|
||||
|
||||
pub fn unset_clock_limits(
|
||||
settings: Arc<Mutex<Vec<Cpu>>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |index: usize|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Cpu(CpuMessage::SetClockLimits(index, None))).expect("unset_clock_limits send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::F64(index)) = params_in.get(0) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
|
||||
cpu.clock_limits = None;
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
super::utility::map_empty_result(cpu.on_set(), true)
|
||||
} else {
|
||||
vec!["get_clock_limits cpu index out of bounds".into()]
|
||||
}
|
||||
if let Some(&Primitive::F64(index)) = params_in.get(0) {
|
||||
setter(index as usize);
|
||||
vec![true.into()]
|
||||
} else {
|
||||
vec!["get_clock_limits missing parameter 0".into()]
|
||||
}
|
||||
|
@ -179,29 +246,20 @@ pub fn unset_clock_limits(
|
|||
}
|
||||
|
||||
pub fn set_cpu_governor(
|
||||
settings: Arc<Mutex<Vec<Cpu>>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |index: usize, governor: String|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Cpu(CpuMessage::SetCpuGovernor(index, governor))).expect("set_cpu_governor send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::F64(index)) = params_in.get(0) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
|
||||
if let Some(Primitive::String(governor)) = params_in.get(1) {
|
||||
cpu.governor = governor.to_owned();
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
super::utility::map_empty_result(
|
||||
cpu.on_set(),
|
||||
&cpu.governor as &str,
|
||||
)
|
||||
} else {
|
||||
vec!["set_cpu_governor missing parameter 1".into()]
|
||||
}
|
||||
if let Some(&Primitive::F64(index)) = params_in.get(0) {
|
||||
if let Some(Primitive::String(governor)) = params_in.get(1) {
|
||||
setter(index as usize, governor.to_owned());
|
||||
vec![(governor as &str).into()]
|
||||
} else {
|
||||
vec!["set_cpu_governor cpu index out of bounds".into()]
|
||||
vec!["set_cpu_governor missing parameter 1".into()]
|
||||
}
|
||||
} else {
|
||||
vec!["set_cpu_governor missing parameter 0".into()]
|
||||
|
@ -209,14 +267,46 @@ pub fn set_cpu_governor(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_cpu_governors(
|
||||
settings: Arc<Mutex<Vec<Cpu>>>,
|
||||
pub fn set_cpus_governors(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |governors: Vec<String>|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Cpu(CpuMessage::SetCpusGovernor(governors))).expect("set_cpus_governor send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
let mut result = Vec::with_capacity(params_in.len());
|
||||
let mut values = Vec::with_capacity(params_in.len());
|
||||
for i in 0..params_in.len() {
|
||||
if let Primitive::String(gov) = ¶ms_in[i] {
|
||||
values.push(gov.to_owned());
|
||||
result.push((gov as &str).into());
|
||||
} else {
|
||||
//values.push(true);
|
||||
result.push(format!("Invalid parameter {}", i).into())
|
||||
}
|
||||
}
|
||||
setter(values);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cpu_governors(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |values: Vec<String>| tx.send(values).expect("get_cpu_governors callback send failed");
|
||||
sender.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetCpusGovernor(Box::new(callback)))).expect("get_cpu_governors send failed");
|
||||
rx.recv().expect("get_cpu_governors callback recv failed")
|
||||
};
|
||||
move |_: super::ApiParameterType| {
|
||||
let settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
let mut output = Vec::with_capacity(settings_lock.len());
|
||||
for cpu in settings_lock.as_slice() {
|
||||
output.push(cpu.governor.clone().into());
|
||||
let result = getter();
|
||||
let mut output = Vec::with_capacity(result.len());
|
||||
for cpu in result.as_slice() {
|
||||
output.push(cpu.clone().into());
|
||||
}
|
||||
output
|
||||
}
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
use std::sync::{mpsc::Sender, Arc, Mutex};
|
||||
use std::sync::mpsc::{Sender, self};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use usdpl_back::core::serdes::Primitive;
|
||||
use usdpl_back::AsyncCallable;
|
||||
|
||||
use crate::settings::{General, Settings, OnSet};
|
||||
use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
|
||||
//use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
|
||||
use super::handler::{ApiMessage, GeneralMessage};
|
||||
|
||||
/// Generate set persistent web method
|
||||
pub fn set_persistent(
|
||||
settings: Arc<Mutex<General>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |pers: bool|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::General(GeneralMessage::SetPersistent(pers))).expect("set_persistent send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::Bool(new_val)) = params_in.get(0) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "general");
|
||||
settings_lock.persistent = *new_val;
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
let result = super::utility::map_empty_result(
|
||||
settings_lock.on_set(),
|
||||
settings_lock.persistent,
|
||||
);
|
||||
log::debug!("Persistent is now {}", settings_lock.persistent);
|
||||
result
|
||||
if let Some(&Primitive::Bool(new_val)) = params_in.get(0) {
|
||||
setter(new_val);
|
||||
//log::debug!("Persistent is now {}", settings_lock.persistent);
|
||||
vec![new_val.into()]
|
||||
} else {
|
||||
vec!["set_persistent missing parameter".into()]
|
||||
}
|
||||
|
@ -32,30 +28,34 @@ pub fn set_persistent(
|
|||
|
||||
/// Generate get persistent save mode web method
|
||||
pub fn get_persistent(
|
||||
settings: Arc<Mutex<General>>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |value: bool| tx.send(value).expect("get_persistent callback send failed");
|
||||
sender.lock().unwrap().send(ApiMessage::General(GeneralMessage::GetPersistent(Box::new(callback)))).expect("get_persistent send failed");
|
||||
rx.recv().expect("get_persistent callback recv failed")
|
||||
};
|
||||
move |_: super::ApiParameterType| {
|
||||
let settings_lock = unwrap_lock(settings.lock(), "general");
|
||||
vec![settings_lock
|
||||
.persistent.into()]
|
||||
vec![getter().into()]
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate load app settings from file web method
|
||||
pub fn load_settings(
|
||||
settings: Settings,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |path: String, name: String|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::LoadSettings(path, name)).expect("load_settings send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::String(path)) = params_in.get(0) {
|
||||
if let Some(Primitive::String(name)) = params_in.get(1) {
|
||||
match settings.load_file(path.into(), name.to_owned(), false) {
|
||||
Err(e) => vec![e.msg.into()],
|
||||
Ok(success) =>
|
||||
super::utility::map_empty_result(
|
||||
settings.clone().on_set(),
|
||||
success
|
||||
)
|
||||
}
|
||||
setter(path.to_owned(), name.to_owned());
|
||||
vec![true.into()]
|
||||
} else {
|
||||
vec!["load_settings missing name parameter".into()]
|
||||
}
|
||||
|
@ -68,10 +68,17 @@ pub fn load_settings(
|
|||
|
||||
/// Generate load default settings from file web method
|
||||
pub fn load_default_settings(
|
||||
settings: Settings,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move ||
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::LoadMainSettings).expect("load_default_settings send failed");
|
||||
move |_: super::ApiParameterType| {
|
||||
match settings.load_file(
|
||||
setter();
|
||||
vec![true.into()]
|
||||
/*match settings.load_file(
|
||||
crate::consts::DEFAULT_SETTINGS_FILE.into(),
|
||||
crate::consts::DEFAULT_SETTINGS_NAME.to_owned(),
|
||||
true
|
||||
|
@ -81,32 +88,163 @@ pub fn load_default_settings(
|
|||
settings.clone().on_set(),
|
||||
success
|
||||
)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate load system default settings from file web method
|
||||
pub fn load_system_settings(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move ||
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::LoadSystemSettings).expect("load_default_settings send failed");
|
||||
move |_: super::ApiParameterType| {
|
||||
setter();
|
||||
vec![true.into()]
|
||||
/*match settings.load_file(
|
||||
crate::consts::DEFAULT_SETTINGS_FILE.into(),
|
||||
crate::consts::DEFAULT_SETTINGS_NAME.to_owned(),
|
||||
true
|
||||
) {
|
||||
Err(e) => vec![e.msg.into()],
|
||||
Ok(success) => super::utility::map_empty_result(
|
||||
settings.clone().on_set(),
|
||||
success
|
||||
)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate get current settings name
|
||||
pub fn get_name(
|
||||
settings: Arc<Mutex<General>>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
move |_: super::ApiParameterType| {
|
||||
let settings_lock = unwrap_lock(settings.lock(), "general");
|
||||
vec![settings_lock
|
||||
.name
|
||||
.clone()
|
||||
.into()]
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |name: String| tx.send(name).expect("get_name callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::General(GeneralMessage::GetCurrentProfileName(Box::new(callback)))).expect("get_name send failed");
|
||||
rx.recv().expect("get_name callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |result| {
|
||||
vec![result.into()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate wait for all locks to be available web method
|
||||
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()]
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |x| tx.send(x).expect("lock_unlock_all callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::WaitForEmptyQueue(Box::new(callback))).expect("lock_unlock_all send failed");
|
||||
rx.recv().expect("lock_unlock_all callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |_| {
|
||||
vec![true.into()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate get limits web method
|
||||
pub fn get_limits(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |value: super::SettingsLimits| tx.send(value).expect("get_limits callback send failed");
|
||||
sender.lock().unwrap().send(ApiMessage::GetLimits(Box::new(callback))).expect("get_limits send failed");
|
||||
rx.recv().expect("get_limits callback recv failed")
|
||||
};
|
||||
move |_: super::ApiParameterType| {
|
||||
vec![Primitive::Json(serde_json::to_string(&getter()).unwrap())]
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate get current driver name
|
||||
pub fn get_provider(
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move |provider_name: String| {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |name: crate::persist::DriverJson| tx.send(name).expect("get_provider callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::GetProvider(provider_name, Box::new(callback))).expect("get_provider send failed");
|
||||
rx.recv().expect("get_provider callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIsh {
|
||||
trans_setter: |mut params| {
|
||||
if let Some(Primitive::String(name)) = params.pop() {
|
||||
Ok(name.to_owned())
|
||||
} else {
|
||||
Err(format!("Invalid/missing single param in get_provider"))
|
||||
}
|
||||
},
|
||||
set_get: getter,
|
||||
trans_getter: |result| {
|
||||
vec![format!("{:?}", result).into()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gunter(_: super::ApiParameterType) -> super::ApiParameterType {
|
||||
std::thread::spawn(|| {
|
||||
log::info!("Zhu Li, do the thing!");
|
||||
crate::settings::driver::maybe_do_button();
|
||||
log::info!("Thing done.")
|
||||
});
|
||||
vec![true.into()]
|
||||
}
|
||||
|
||||
/// API web method to send log messages to the back-end log, callable from the front-end
|
||||
pub fn log_it() -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
move |params| {
|
||||
if let Some(Primitive::F64(level)) = params.get(0) {
|
||||
if let Some(Primitive::String(msg)) = params.get(1) {
|
||||
log_msg_by_level(*level as u8, msg);
|
||||
vec![true.into()]
|
||||
} else if let Some(Primitive::Json(msg)) = params.get(1) {
|
||||
log_msg_by_level(*level as u8, msg);
|
||||
vec![true.into()]
|
||||
} else {
|
||||
log::warn!("Got log_it call with wrong/missing 2nd parameter");
|
||||
vec![false.into()]
|
||||
}
|
||||
} else {
|
||||
log::warn!("Got log_it call with wrong/missing 1st parameter");
|
||||
vec![false.into()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn log_msg_by_level(level: u8, msg: &str) {
|
||||
match level {
|
||||
1 => log::trace!("FRONT-END: {}", msg),
|
||||
2 => log::debug!("FRONT-END: {}", msg),
|
||||
3 => log::info!("FRONT-END: {}", msg),
|
||||
4 => log::warn!("FRONT-END: {}", msg),
|
||||
5 => log::error!("FRONT-END: {}", msg),
|
||||
_ => log::trace!("FRONT-END: {}", msg),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
use std::sync::{mpsc::Sender, Arc, Mutex};
|
||||
use std::sync::mpsc::{Sender, self};
|
||||
use std::sync::{Mutex, Arc};
|
||||
use usdpl_back::core::serdes::Primitive;
|
||||
use usdpl_back::AsyncCallable;
|
||||
|
||||
use crate::settings::{Gpu, OnSet, MinMax};
|
||||
use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
|
||||
use crate::settings::MinMax;
|
||||
//use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
|
||||
use super::handler::{ApiMessage, GpuMessage};
|
||||
|
||||
pub fn set_ppt(
|
||||
settings: Arc<Mutex<Gpu>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |fast: u64, slow: u64|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Gpu(GpuMessage::SetPpt(Some(fast), Some(slow)))).expect("set_ppt send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::F64(fast_ppt)) = params_in.get(0) {
|
||||
if let Some(Primitive::F64(slow_ppt)) = params_in.get(1) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
|
||||
settings_lock.fast_ppt = Some(*fast_ppt as u64);
|
||||
settings_lock.slow_ppt = Some(*slow_ppt as u64);
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
match settings_lock.on_set() {
|
||||
Ok(_) => vec![
|
||||
settings_lock.fast_ppt.unwrap().into(),
|
||||
settings_lock.slow_ppt.unwrap().into()
|
||||
],
|
||||
Err(e) => vec![e.msg.into()],
|
||||
}
|
||||
if let Some(&Primitive::F64(fast_ppt)) = params_in.get(0) {
|
||||
if let Some(&Primitive::F64(slow_ppt)) = params_in.get(1) {
|
||||
setter(fast_ppt as u64, slow_ppt as u64);
|
||||
vec![(fast_ppt as u64).into(), (slow_ppt as u64).into()]
|
||||
} else {
|
||||
vec!["set_ppt missing parameter 1".into()]
|
||||
}
|
||||
|
@ -36,60 +30,69 @@ pub fn set_ppt(
|
|||
}
|
||||
|
||||
pub fn get_ppt(
|
||||
settings: Arc<Mutex<Gpu>>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
move |_: super::ApiParameterType| {
|
||||
let settings_lock = unwrap_lock(settings.lock(), "gpu");
|
||||
let fast_ppt = settings_lock.fast_ppt.map(|x| x.into()).unwrap_or(Primitive::Empty);
|
||||
let slow_ppt = settings_lock.slow_ppt.map(|x| x.into()).unwrap_or(Primitive::Empty);
|
||||
vec![fast_ppt, slow_ppt]
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |ppt: (Option<u64>, Option<u64>)| tx.send(ppt).expect("get_ppt callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Gpu(GpuMessage::GetPpt(Box::new(callback)))).expect("get_ppt send failed");
|
||||
rx.recv().expect("get_ppt callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |(fast, slow): (Option<u64>, Option<u64>)| {
|
||||
vec![
|
||||
fast.map(|x| x.into()).unwrap_or(Primitive::Empty),
|
||||
slow.map(|x| x.into()).unwrap_or(Primitive::Empty),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unset_ppt(
|
||||
settings: Arc<Mutex<Gpu>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move ||
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Gpu(GpuMessage::SetPpt(None, None))).expect("set_ppt send failed");
|
||||
move |_: super::ApiParameterType| {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
|
||||
settings_lock.fast_ppt = None;
|
||||
settings_lock.slow_ppt = None;
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
super::utility::map_empty_result(
|
||||
settings_lock.on_set(),
|
||||
Primitive::Empty,
|
||||
)
|
||||
setter();
|
||||
vec![true.into()]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_clock_limits(
|
||||
settings: Arc<Mutex<Gpu>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |value: MinMax<u64>|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Gpu(GpuMessage::SetClockLimits(Some(value)))).expect("set_clock_limits send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::F64(min)) = params_in.get(0) {
|
||||
if let Some(Primitive::F64(max)) = params_in.get(1) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
|
||||
settings_lock.clock_limits = Some(MinMax {
|
||||
min: *min as _,
|
||||
max: *max as _,
|
||||
if let Some(&Primitive::F64(min)) = params_in.get(0) {
|
||||
if let Some(&Primitive::F64(max)) = params_in.get(1) {
|
||||
let safe_max = if max < min {
|
||||
min
|
||||
} else {
|
||||
max
|
||||
};
|
||||
let safe_min = if min > max {
|
||||
max
|
||||
} else {
|
||||
min
|
||||
};
|
||||
setter(MinMax {
|
||||
min: safe_min as _,
|
||||
max: safe_max as _,
|
||||
});
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
match settings_lock.on_set() {
|
||||
Ok(_) => vec![
|
||||
settings_lock.clock_limits.as_ref().unwrap().min.into(),
|
||||
settings_lock.clock_limits.as_ref().unwrap().max.into(),
|
||||
],
|
||||
Err(e) => vec![e.msg.into()]
|
||||
}
|
||||
vec![(safe_min as u64).into(), (safe_max as u64).into()]
|
||||
} else {
|
||||
vec!["set_clock_limits missing parameter 1".into()]
|
||||
}
|
||||
|
@ -100,51 +103,54 @@ pub fn set_clock_limits(
|
|||
}
|
||||
|
||||
pub fn get_clock_limits(
|
||||
settings: Arc<Mutex<Gpu>>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
move |_: super::ApiParameterType| {
|
||||
let settings_lock = unwrap_lock(settings.lock(), "gpu");
|
||||
if let Some(min_max) = &settings_lock.clock_limits {
|
||||
vec![min_max.min.into(), min_max.max.into()]
|
||||
} else {
|
||||
vec![Primitive::Empty, Primitive::Empty]
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move|| {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |clocks: Option<MinMax<u64>>| tx.send(clocks).expect("get_clock_limits callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Gpu(GpuMessage::GetClockLimits(Box::new(callback)))).expect("get_clock_limits send failed");
|
||||
rx.recv().expect("get_clock_limits callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |clocks: Option<MinMax<u64>>| {
|
||||
clocks.map(|x| vec![
|
||||
x.min.into(), x.max.into()
|
||||
]).unwrap_or_else(|| vec![Primitive::Empty, Primitive::Empty])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unset_clock_limits(
|
||||
settings: Arc<Mutex<Gpu>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move ||
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Gpu(GpuMessage::SetClockLimits(None))).expect("unset_clock_limits send failed");
|
||||
move |_: super::ApiParameterType| {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
|
||||
settings_lock.clock_limits = None;
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
super::utility::map_empty_result(settings_lock.on_set(), true)
|
||||
setter();
|
||||
vec![true.into()]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_slow_memory(
|
||||
settings: Arc<Mutex<Gpu>>,
|
||||
saver: Sender<()>,
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
|
||||
let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety
|
||||
let setter = move |value: bool|
|
||||
sender.lock()
|
||||
.unwrap()
|
||||
.send(ApiMessage::Gpu(GpuMessage::SetSlowMemory(value))).expect("unset_clock_limits send failed");
|
||||
move |params_in: super::ApiParameterType| {
|
||||
if let Some(Primitive::Bool(memory_is_slow)) = params_in.get(0) {
|
||||
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
|
||||
settings_lock.slow_memory = *memory_is_slow;
|
||||
unwrap_maybe_fatal(
|
||||
unwrap_lock(saver.lock(), "save channel").send(()),
|
||||
"Failed to send on save channel",
|
||||
);
|
||||
super::utility::map_empty_result(
|
||||
settings_lock.on_set(),
|
||||
settings_lock.slow_memory,
|
||||
)
|
||||
if let Some(&Primitive::Bool(memory_is_slow)) = params_in.get(0) {
|
||||
setter(memory_is_slow);
|
||||
vec![memory_is_slow.into()]
|
||||
} else {
|
||||
vec!["set_slow_memory missing parameter 0".into()]
|
||||
}
|
||||
|
@ -152,10 +158,22 @@ pub fn set_slow_memory(
|
|||
}
|
||||
|
||||
pub fn get_slow_memory(
|
||||
settings: Arc<Mutex<Gpu>>,
|
||||
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
|
||||
move |_: super::ApiParameterType| {
|
||||
let settings_lock = unwrap_lock(settings.lock(), "cpu");
|
||||
vec![settings_lock.slow_memory.into()]
|
||||
sender: Sender<ApiMessage>,
|
||||
) -> impl AsyncCallable {
|
||||
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||
let getter = move || {
|
||||
let sender2 = sender.clone();
|
||||
move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let callback = move |value: bool| tx.send(value).expect("get_slow_memory callback send failed");
|
||||
sender2.lock().unwrap().send(ApiMessage::Gpu(GpuMessage::GetSlowMemory(Box::new(callback)))).expect("get_slow_memory send failed");
|
||||
rx.recv().expect("get_slow_memory callback recv failed")
|
||||
}
|
||||
};
|
||||
super::async_utils::AsyncIshGetter {
|
||||
set_get: getter,
|
||||
trans_getter: |value: bool| {
|
||||
vec![value.into()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
330
backend/src/api/handler.rs
Normal file
330
backend/src/api/handler.rs
Normal file
|
@ -0,0 +1,330 @@
|
|||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
|
||||
use crate::settings::{Settings, TCpus, TGpu, TBattery, TGeneral, OnSet, OnResume, MinMax};
|
||||
use crate::persist::SettingsJson;
|
||||
use crate::utility::unwrap_maybe_fatal;
|
||||
|
||||
type Callback<T> = Box<dyn FnOnce(T) + Send>;
|
||||
|
||||
pub enum ApiMessage {
|
||||
Battery(BatteryMessage),
|
||||
Cpu(CpuMessage),
|
||||
Gpu(GpuMessage),
|
||||
General(GeneralMessage),
|
||||
OnResume,
|
||||
WaitForEmptyQueue(Callback<()>),
|
||||
LoadSettings(String, String), // (path, name)
|
||||
LoadMainSettings,
|
||||
LoadSystemSettings,
|
||||
GetLimits(Callback<super::SettingsLimits>),
|
||||
GetProvider(String, Callback<crate::persist::DriverJson>),
|
||||
}
|
||||
|
||||
pub enum BatteryMessage {
|
||||
SetChargeRate(Option<u64>),
|
||||
GetChargeRate(Callback<Option<u64>>),
|
||||
SetChargeMode(Option<String>),
|
||||
GetChargeMode(Callback<Option<String>>),
|
||||
ReadChargeFull(Callback<Option<f64>>),
|
||||
ReadChargeNow(Callback<Option<f64>>),
|
||||
ReadChargeDesign(Callback<Option<f64>>),
|
||||
ReadCurrentNow(Callback<Option<f64>>),
|
||||
}
|
||||
|
||||
impl BatteryMessage {
|
||||
fn process(self, settings: &mut dyn TBattery) -> bool {
|
||||
let dirty = self.is_modify();
|
||||
match self {
|
||||
Self::SetChargeRate(rate) => settings.charge_rate(rate),
|
||||
Self::GetChargeRate(cb) => cb(settings.get_charge_rate()),
|
||||
Self::SetChargeMode(mode) => settings.charge_mode(mode),
|
||||
Self::GetChargeMode(cb) => cb(settings.get_charge_mode()),
|
||||
Self::ReadChargeFull(cb) => cb(settings.read_charge_full()),
|
||||
Self::ReadChargeNow(cb) => cb(settings.read_charge_now()),
|
||||
Self::ReadChargeDesign(cb) => cb(settings.read_charge_design()),
|
||||
Self::ReadCurrentNow(cb) => cb(settings.read_current_now()),
|
||||
}
|
||||
dirty
|
||||
}
|
||||
|
||||
/// Message instructs the driver to modify settings
|
||||
fn is_modify(&self) -> bool {
|
||||
matches!(self, Self::SetChargeRate(_) | Self::SetChargeMode(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum CpuMessage {
|
||||
SetCpuOnline(usize, bool),
|
||||
SetCpusOnline(Vec<bool>),
|
||||
SetSmt(bool, Callback<Vec<bool>>),
|
||||
GetSmt(Callback<bool>),
|
||||
GetCpusOnline(Callback<Vec<bool>>),
|
||||
SetClockLimits(usize, Option<MinMax<u64>>),
|
||||
GetClockLimits(usize, Callback<Option<MinMax<u64>>>),
|
||||
SetCpuGovernor(usize, String),
|
||||
SetCpusGovernor(Vec<String>),
|
||||
GetCpusGovernor(Callback<Vec<String>>),
|
||||
}
|
||||
|
||||
impl CpuMessage {
|
||||
fn process(self, settings: &mut dyn TCpus) -> bool {
|
||||
let dirty = self.is_modify();
|
||||
// NOTE: "cpu" refers to the Linux kernel definition of a CPU, which is actually a hardware thread
|
||||
// not to be confused with a CPU chip, which usually has multiple hardware threads (cpu cores/threads) in the chip
|
||||
match self {
|
||||
Self::SetCpuOnline(index, status) => {settings.cpus().get_mut(index).map(|c| *c.online() = status);},
|
||||
Self::SetCpusOnline(cpus) => {
|
||||
for i in 0..cpus.len() {
|
||||
settings.cpus().get_mut(i).map(|c| *c.online() = cpus[i]);
|
||||
}
|
||||
},
|
||||
Self::SetSmt(status, cb) => {
|
||||
if *settings.smt() == status {
|
||||
// already set, do nothing
|
||||
} else if status {
|
||||
// set SMT on
|
||||
*settings.smt() = true;
|
||||
let mut should_be_online = false;
|
||||
let cpu_count = settings.len();
|
||||
for i in (0..cpu_count).rev() {
|
||||
if *settings.cpus()[i].online() && !should_be_online {
|
||||
should_be_online = true;
|
||||
// enable the odd-numbered thread right before
|
||||
// for 1c:2t configs (i.e. anything with SMT2), the highest cpu core is always odd
|
||||
// (e.g. 4c8t has CPUs 0-7, inclusive)
|
||||
// this enables the """fake""" (i.e. odd) cpu which is disabled when SMT is set off
|
||||
if i % 2 == 0 && i+1 != cpu_count {
|
||||
*(settings.cpus()[i+1].online()) = true;
|
||||
}
|
||||
} else {
|
||||
*settings.cpus()[i].online() = should_be_online;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// set SMT off
|
||||
*settings.smt() = false;
|
||||
for i in 0..settings.len() {
|
||||
// this disables the """fake""" (odd) cpu for appearances' sake
|
||||
// the kernel will automatically disable that same cpu when SMT is changed
|
||||
*settings.cpus()[i].online() = *settings.cpus()[i].online() && (status || i % 2 == 0);
|
||||
}
|
||||
}
|
||||
let mut result = Vec::with_capacity(settings.len());
|
||||
for i in 0..settings.len() {
|
||||
result.push(*settings.cpus()[i].online());
|
||||
}
|
||||
cb(result);
|
||||
},
|
||||
Self::GetSmt(cb) => {
|
||||
cb(*settings.smt());
|
||||
},
|
||||
Self::GetCpusOnline(cb) => {
|
||||
let mut result = Vec::with_capacity(settings.len());
|
||||
for cpu in settings.cpus() {
|
||||
result.push(*cpu.online());
|
||||
}
|
||||
cb(result);
|
||||
},
|
||||
Self::SetClockLimits(index, clocks) => {settings.cpus().get_mut(index).map(|c| c.clock_limits(clocks));},
|
||||
Self::GetClockLimits(index, cb) => {settings.cpus().get(index).map(|c| cb(c.get_clock_limits().map(|x| x.to_owned())));},
|
||||
Self::SetCpuGovernor(index, gov) => {settings.cpus().get_mut(index).map(|c| c.governor(gov));},
|
||||
Self::SetCpusGovernor(govs) => {
|
||||
for i in 0..govs.len() {
|
||||
settings.cpus().get_mut(i).map(|c| c.governor(govs[i].clone()));
|
||||
}
|
||||
},
|
||||
Self::GetCpusGovernor(cb) => {
|
||||
let mut result = Vec::with_capacity(settings.len());
|
||||
for cpu in settings.cpus() {
|
||||
result.push(cpu.get_governor().to_owned());
|
||||
}
|
||||
cb(result);
|
||||
}
|
||||
}
|
||||
dirty
|
||||
}
|
||||
|
||||
/// Message instructs the driver to modify settings
|
||||
fn is_modify(&self) -> bool {
|
||||
matches!(self,
|
||||
Self::SetCpuOnline(_, _)
|
||||
| Self::SetCpusOnline(_)
|
||||
| Self::SetSmt(_, _)
|
||||
| Self::SetClockLimits(_, _)
|
||||
| Self::SetCpuGovernor(_, _)
|
||||
| Self::SetCpusGovernor(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum GpuMessage {
|
||||
SetPpt(Option<u64>, Option<u64>), // (fast, slow)
|
||||
GetPpt(Callback<(Option<u64>, Option<u64>)>),
|
||||
SetClockLimits(Option<MinMax<u64>>),
|
||||
GetClockLimits(Callback<Option<MinMax<u64>>>),
|
||||
SetSlowMemory(bool),
|
||||
GetSlowMemory(Callback<bool>),
|
||||
}
|
||||
|
||||
impl GpuMessage {
|
||||
fn process(self, settings: &mut dyn TGpu) -> bool {
|
||||
let dirty = self.is_modify();
|
||||
match self {
|
||||
Self::SetPpt(fast, slow) => settings.ppt(fast, slow),
|
||||
Self::GetPpt(cb) => cb(settings.get_ppt()),
|
||||
Self::SetClockLimits(clocks) => settings.clock_limits(clocks),
|
||||
Self::GetClockLimits(cb) => cb(settings.get_clock_limits().map(|x| x.to_owned())),
|
||||
Self::SetSlowMemory(val) => *settings.slow_memory() = val,
|
||||
Self::GetSlowMemory(cb) => cb(*settings.slow_memory()),
|
||||
}
|
||||
dirty
|
||||
}
|
||||
|
||||
fn is_modify(&self) -> bool {
|
||||
matches!(self,
|
||||
Self::SetPpt(_, _)
|
||||
| Self::SetClockLimits(_)
|
||||
| Self::SetSlowMemory(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum GeneralMessage {
|
||||
SetPersistent(bool),
|
||||
GetPersistent(Callback<bool>),
|
||||
GetCurrentProfileName(Callback<String>),
|
||||
}
|
||||
|
||||
impl GeneralMessage {
|
||||
fn process(self, settings: &mut dyn TGeneral) -> bool {
|
||||
let dirty = self.is_modify();
|
||||
match self {
|
||||
Self::SetPersistent(val) => *settings.persistent() = val,
|
||||
Self::GetPersistent(cb) => cb(*settings.persistent()),
|
||||
Self::GetCurrentProfileName(cb) => cb(settings.get_name().to_owned()),
|
||||
}
|
||||
dirty
|
||||
}
|
||||
|
||||
fn is_modify(&self) -> bool {
|
||||
matches!(self, Self::SetPersistent(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ApiMessageHandler {
|
||||
intake: Receiver<ApiMessage>,
|
||||
on_empty: Vec<Callback<()>>,
|
||||
}
|
||||
|
||||
impl ApiMessageHandler {
|
||||
pub fn process_forever(&mut self, settings: &mut Settings) {
|
||||
let mut dirty_echo = true; // set everything twice, to make sure PowerTools wins on race conditions
|
||||
while let Ok(msg) = self.intake.recv() {
|
||||
let mut dirty = self.process(settings, msg);
|
||||
while let Ok(msg) = self.intake.try_recv() {
|
||||
dirty |= self.process(settings, msg);
|
||||
}
|
||||
if dirty || dirty_echo {
|
||||
dirty_echo = dirty; // echo only once
|
||||
// run on_set
|
||||
if let Err(e) = settings.on_set() {
|
||||
log::error!("Settings on_set() err: {}", e);
|
||||
}
|
||||
// do callbacks
|
||||
for func in self.on_empty.drain(..) {
|
||||
func(());
|
||||
}
|
||||
// save
|
||||
log::debug!("api_worker is saving...");
|
||||
let is_persistent = *settings.general.persistent();
|
||||
let save_path = crate::utility::settings_dir()
|
||||
.join(settings.general.get_path().clone());
|
||||
if is_persistent {
|
||||
let settings_clone = settings.json();
|
||||
let save_json: SettingsJson = settings_clone.into();
|
||||
unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings");
|
||||
log::debug!("Saved settings to {}", save_path.display());
|
||||
} else {
|
||||
if save_path.exists() {
|
||||
if let Err(e) = std::fs::remove_file(&save_path) {
|
||||
log::warn!("Failed to delete persistent settings file {}: {}", save_path.display(), e);
|
||||
} else {
|
||||
log::debug!("Deleted persistent settings file {}", save_path.display());
|
||||
}
|
||||
} else {
|
||||
log::debug!("Ignored save request for non-persistent settings");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!("Skipping callbacks for non-modify handled message(s)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&mut self, settings: &mut Settings, message: ApiMessage) -> bool {
|
||||
match message {
|
||||
ApiMessage::Battery(x) => x.process(settings.battery.as_mut()),
|
||||
ApiMessage::Cpu(x) => x.process(settings.cpus.as_mut()),
|
||||
ApiMessage::Gpu(x) => x.process(settings.gpu.as_mut()),
|
||||
ApiMessage::General(x) => x.process(settings.general.as_mut()),
|
||||
ApiMessage::OnResume => {
|
||||
if let Err(e) = settings.on_resume() {
|
||||
log::error!("Settings on_resume() err: {}", e);
|
||||
}
|
||||
false
|
||||
}
|
||||
ApiMessage::WaitForEmptyQueue(callback) => {
|
||||
self.on_empty.push(callback);
|
||||
false
|
||||
},
|
||||
ApiMessage::LoadSettings(path, name) => {
|
||||
match settings.load_file(path.into(), name, false) {
|
||||
Ok(success) => log::info!("Loaded settings file? {}", success),
|
||||
Err(e) => log::warn!("Load file err: {}", e),
|
||||
}
|
||||
true
|
||||
}
|
||||
ApiMessage::LoadMainSettings => {
|
||||
match settings.load_file(
|
||||
crate::consts::DEFAULT_SETTINGS_FILE.into(),
|
||||
crate::consts::DEFAULT_SETTINGS_NAME.to_owned(),
|
||||
true
|
||||
) {
|
||||
Ok(success) => log::info!("Loaded main settings file? {}", success),
|
||||
Err(e) => log::warn!("Load file err: {}", e),
|
||||
}
|
||||
true
|
||||
}
|
||||
ApiMessage::LoadSystemSettings => {
|
||||
settings.load_system_default();
|
||||
true
|
||||
},
|
||||
ApiMessage::GetLimits(cb) => {
|
||||
cb(super::SettingsLimits {
|
||||
battery: settings.battery.limits(),
|
||||
cpu: settings.cpus.limits(),
|
||||
gpu: settings.gpu.limits(),
|
||||
general: settings.general.limits(),
|
||||
});
|
||||
false
|
||||
},
|
||||
ApiMessage::GetProvider(name, cb) => {
|
||||
cb(match &name as &str {
|
||||
"battery" => settings.battery.provider(),
|
||||
"cpu" | "cpus" => settings.cpus.provider(),
|
||||
"gpu" => settings.gpu.provider(),
|
||||
_ => settings.general.provider(),
|
||||
});
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> (Self, Sender<ApiMessage>) {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
(Self {
|
||||
intake: rx,
|
||||
on_empty: Vec::with_capacity(4),
|
||||
}, tx)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
mod api_types;
|
||||
pub mod battery;
|
||||
pub mod cpu;
|
||||
pub mod general;
|
||||
pub mod gpu;
|
||||
pub mod handler;
|
||||
mod async_utils;
|
||||
mod utility;
|
||||
|
||||
pub(super) type ApiParameterType = Vec<usdpl_back::core::serdes::Primitive>;
|
||||
|
||||
pub use api_types::*;
|
||||
|
|
|
@ -15,6 +15,20 @@ pub fn map_result<T: Into<Primitive>>(result: Result<T, SettingError>) -> super:
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_optional_result<T: Into<Primitive>>(result: Result<Option<T>, SettingError>) -> super::ApiParameterType {
|
||||
match result {
|
||||
Ok(val) => match val {
|
||||
Some(val) => vec![val.into()],
|
||||
None => vec![Primitive::Empty],
|
||||
},
|
||||
Err(e) => {
|
||||
log::debug!("Mapping error to primitive: {}", e);
|
||||
vec![e.msg.into()]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/*#[inline]
|
||||
pub fn map_empty_result<T: Into<Primitive>>(
|
||||
result: Result<(), SettingError>,
|
||||
success: T,
|
||||
|
@ -26,4 +40,4 @@ pub fn map_empty_result<T: Into<Primitive>>(
|
|||
vec![e.msg.into()]
|
||||
},
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
|
13
backend/src/api_worker.rs
Normal file
13
backend/src/api_worker.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use std::thread::{self, JoinHandle};
|
||||
|
||||
use crate::settings::Settings;
|
||||
//use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
|
||||
use crate::api::handler::ApiMessageHandler;
|
||||
|
||||
pub fn spawn(mut settings: Settings, mut handler: ApiMessageHandler) -> JoinHandle<()> {
|
||||
thread::spawn(move || {
|
||||
log::info!("api_worker starting...");
|
||||
handler.process_forever(&mut settings);
|
||||
log::warn!("api_worker completed!");
|
||||
})
|
||||
}
|
|
@ -4,4 +4,6 @@ pub const PACKAGE_NAME: &'static str = env!("CARGO_PKG_NAME");
|
|||
pub const PACKAGE_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub const DEFAULT_SETTINGS_FILE: &str = "default_settings.json";
|
||||
pub const DEFAULT_SETTINGS_NAME: &str = "Default";
|
||||
pub const DEFAULT_SETTINGS_NAME: &str = "Main";
|
||||
|
||||
pub const LIMITS_FILE: &str = "limits_cache.json";
|
||||
|
|
|
@ -6,7 +6,8 @@ mod state;
|
|||
mod consts;
|
||||
use consts::*;
|
||||
mod resume_worker;
|
||||
mod save_worker;
|
||||
//mod save_worker;
|
||||
mod api_worker;
|
||||
mod utility;
|
||||
|
||||
use settings::OnSet;
|
||||
|
@ -17,14 +18,21 @@ use usdpl_back::core::serdes::Primitive;
|
|||
use usdpl_back::Instance;
|
||||
|
||||
fn main() -> Result<(), ()> {
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let log_filepath = format!("/home/deck/{}.log", PACKAGE_NAME);
|
||||
let log_filepath = usdpl_back::api::dirs::home()
|
||||
.unwrap_or_else(|| "/tmp/".into())
|
||||
.join(PACKAGE_NAME.to_owned()+".log");
|
||||
#[cfg(not(debug_assertions))]
|
||||
let log_filepath = format!("/tmp/{}.log", PACKAGE_NAME);
|
||||
let log_filepath = std::path::Path::new("/tmp").join(format!("{}.log", PACKAGE_NAME));
|
||||
#[cfg(debug_assertions)]
|
||||
let old_log_filepath = usdpl_back::api::dirs::home()
|
||||
.unwrap_or_else(|| "/tmp/".into())
|
||||
.join(PACKAGE_NAME.to_owned()+".log.old");
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if std::path::Path::new(&log_filepath).exists() {
|
||||
std::fs::copy(&log_filepath, format!("/home/deck/{}.log.old", PACKAGE_NAME)).unwrap();
|
||||
std::fs::copy(&log_filepath, &old_log_filepath).expect("Unable to increment logs. Do you have write permissions?");
|
||||
}
|
||||
}
|
||||
WriteLogger::init(
|
||||
|
@ -40,8 +48,15 @@ fn main() -> Result<(), ()> {
|
|||
std::fs::File::create(&log_filepath).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
log::debug!("Logging to: {:?}.", log_filepath);
|
||||
println!("Logging to: {:?}", log_filepath);
|
||||
log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
|
||||
println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
|
||||
log::info!("Current dir `{}`", std::env::current_dir().unwrap().display());
|
||||
println!("Current dir `{}`", std::env::current_dir().unwrap().display());
|
||||
|
||||
let _limits_handle = crate::settings::limits_worker_spawn();
|
||||
log::info!("Detected device automatically, starting with driver: {:?} (This can be overriden)", crate::settings::auto_detect_provider());
|
||||
|
||||
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()))
|
||||
|
@ -49,125 +64,171 @@ fn main() -> Result<(), ()> {
|
|||
|
||||
log::debug!("Settings: {:?}", loaded_settings);
|
||||
|
||||
let (_save_handle, save_sender) = save_worker::spawn(loaded_settings.clone());
|
||||
let _resume_handle = resume_worker::spawn(loaded_settings.clone());
|
||||
let (api_handler, api_sender) = crate::api::handler::ApiMessageHandler::new();
|
||||
|
||||
if let Err(e) = loaded_settings.on_set() {
|
||||
log::error!("Startup Settings.on_set() error: {}", e);
|
||||
}
|
||||
//let (_save_handle, save_sender) = save_worker::spawn(loaded_settings.clone());
|
||||
let _resume_handle = resume_worker::spawn(api_sender.clone());
|
||||
|
||||
Instance::new(PORT)
|
||||
let instance = Instance::new(PORT)
|
||||
.register("V_INFO", |_: Vec<Primitive>| {
|
||||
vec![format!("{} v{}", PACKAGE_NAME, PACKAGE_VERSION).into()]
|
||||
})
|
||||
.register("LOG", api::general::log_it())
|
||||
// battery API functions
|
||||
.register("BATTERY_current_now", api::battery::current_now)
|
||||
.register("BATTERY_charge_now", api::battery::charge_now)
|
||||
.register("BATTERY_charge_full", api::battery::charge_full)
|
||||
.register("BATTERY_charge_design", api::battery::charge_design)
|
||||
.register_async("BATTERY_current_now", api::battery::current_now(api_sender.clone()))
|
||||
.register_async("BATTERY_charge_now", api::battery::charge_now(api_sender.clone()))
|
||||
.register_async("BATTERY_charge_full", api::battery::charge_full(api_sender.clone()))
|
||||
.register_async("BATTERY_charge_design", api::battery::charge_design(api_sender.clone()))
|
||||
.register(
|
||||
"BATTERY_set_charge_rate",
|
||||
api::battery::set_charge_rate(loaded_settings.battery.clone(), save_sender.clone()),
|
||||
api::battery::set_charge_rate(api_sender.clone()),
|
||||
)
|
||||
.register(
|
||||
"BATTERY_get_charge_rate",
|
||||
api::battery::get_charge_rate(loaded_settings.battery.clone()),
|
||||
api::battery::get_charge_rate(api_sender.clone()),
|
||||
)
|
||||
.register(
|
||||
"BATTERY_unset_charge_rate",
|
||||
api::battery::unset_charge_rate(loaded_settings.battery.clone(), save_sender.clone()),
|
||||
api::battery::unset_charge_rate(api_sender.clone()),
|
||||
)
|
||||
.register(
|
||||
"BATTERY_set_charge_mode",
|
||||
api::battery::set_charge_mode(api_sender.clone()),
|
||||
)
|
||||
.register(
|
||||
"BATTERY_get_charge_mode",
|
||||
api::battery::get_charge_mode(api_sender.clone()),
|
||||
)
|
||||
.register(
|
||||
"BATTERY_unset_charge_mode",
|
||||
api::battery::unset_charge_mode(api_sender.clone()),
|
||||
)
|
||||
// cpu API functions
|
||||
.register("CPU_count", api::cpu::max_cpus)
|
||||
.register(
|
||||
"CPU_set_online",
|
||||
api::cpu::set_cpu_online(loaded_settings.cpus.clone(), save_sender.clone())
|
||||
api::cpu::set_cpu_online(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"CPU_set_onlines",
|
||||
api::cpu::set_cpus_online(loaded_settings.cpus.clone(), save_sender.clone())
|
||||
api::cpu::set_cpus_online(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
.register_async(
|
||||
"CPU_get_onlines",
|
||||
api::cpu::get_cpus_online(loaded_settings.cpus.clone())
|
||||
api::cpu::get_cpus_online(api_sender.clone())
|
||||
)
|
||||
.register_async(
|
||||
"CPU_set_smt",
|
||||
api::cpu::set_smt(api_sender.clone())
|
||||
)
|
||||
.register_async(
|
||||
"CPU_get_smt",
|
||||
api::cpu::get_smt(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"CPU_set_clock_limits",
|
||||
api::cpu::set_clock_limits(loaded_settings.cpus.clone(), save_sender.clone())
|
||||
api::cpu::set_clock_limits(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"CPU_get_clock_limits",
|
||||
api::cpu::get_clock_limits(loaded_settings.cpus.clone())
|
||||
api::cpu::get_clock_limits(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"CPU_unset_clock_limits",
|
||||
api::cpu::unset_clock_limits(loaded_settings.cpus.clone(), save_sender.clone())
|
||||
api::cpu::unset_clock_limits(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"CPU_set_governor",
|
||||
api::cpu::set_cpu_governor(loaded_settings.cpus.clone(), save_sender.clone())
|
||||
api::cpu::set_cpu_governor(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"CPU_set_governors",
|
||||
api::cpu::set_cpus_governors(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"CPU_get_governors",
|
||||
api::cpu::get_cpu_governors(loaded_settings.cpus.clone())
|
||||
api::cpu::get_cpu_governors(api_sender.clone())
|
||||
)
|
||||
// gpu API functions
|
||||
.register(
|
||||
"GPU_set_ppt",
|
||||
api::gpu::set_ppt(loaded_settings.gpu.clone(), save_sender.clone())
|
||||
api::gpu::set_ppt(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
.register_async(
|
||||
"GPU_get_ppt",
|
||||
api::gpu::get_ppt(loaded_settings.gpu.clone())
|
||||
api::gpu::get_ppt(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"GPU_unset_ppt",
|
||||
api::gpu::unset_ppt(loaded_settings.gpu.clone(), save_sender.clone())
|
||||
api::gpu::unset_ppt(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"GPU_set_clock_limits",
|
||||
api::gpu::set_clock_limits(loaded_settings.gpu.clone(), save_sender.clone())
|
||||
api::gpu::set_clock_limits(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
.register_async(
|
||||
"GPU_get_clock_limits",
|
||||
api::gpu::get_clock_limits(loaded_settings.gpu.clone())
|
||||
api::gpu::get_clock_limits(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"GPU_unset_clock_limits",
|
||||
api::gpu::unset_clock_limits(loaded_settings.gpu.clone(), save_sender.clone())
|
||||
api::gpu::unset_clock_limits(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"GPU_set_slow_memory",
|
||||
api::gpu::set_slow_memory(loaded_settings.gpu.clone(), save_sender.clone())
|
||||
api::gpu::set_slow_memory(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
.register_async(
|
||||
"GPU_get_slow_memory",
|
||||
api::gpu::get_slow_memory(loaded_settings.gpu.clone())
|
||||
api::gpu::get_slow_memory(api_sender.clone())
|
||||
)
|
||||
// general API functions
|
||||
.register(
|
||||
"GENERAL_set_persistent",
|
||||
api::general::set_persistent(loaded_settings.general.clone(), save_sender.clone())
|
||||
api::general::set_persistent(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"GENERAL_get_persistent",
|
||||
api::general::get_persistent(loaded_settings.general.clone())
|
||||
api::general::get_persistent(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"GENERAL_load_settings",
|
||||
api::general::load_settings(loaded_settings.clone())
|
||||
api::general::load_settings(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"GENERAL_load_default_settings",
|
||||
api::general::load_default_settings(loaded_settings.clone())
|
||||
api::general::load_default_settings(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
"GENERAL_load_system_settings",
|
||||
api::general::load_system_settings(api_sender.clone())
|
||||
)
|
||||
.register_async(
|
||||
"GENERAL_get_name",
|
||||
api::general::get_name(loaded_settings.general.clone())
|
||||
api::general::get_name(api_sender.clone())
|
||||
)
|
||||
.register(
|
||||
.register_async(
|
||||
"GENERAL_wait_for_unlocks",
|
||||
api::general::lock_unlock_all(loaded_settings.clone())
|
||||
api::general::lock_unlock_all(api_sender.clone())
|
||||
)
|
||||
.register_blocking(
|
||||
"GENERAL_get_limits",
|
||||
api::general::get_limits(api_sender.clone())
|
||||
)
|
||||
.register_async(
|
||||
"GENERAL_get_provider",
|
||||
api::general::get_provider(api_sender.clone())
|
||||
)
|
||||
.register("GENERAL_idk", api::general::gunter);
|
||||
|
||||
if let Err(e) = loaded_settings.on_set() {
|
||||
log::error!("Startup Settings.on_set() error: {}", e);
|
||||
} else {
|
||||
log::info!("Startup Settings.on_set() success");
|
||||
}
|
||||
|
||||
api_worker::spawn(loaded_settings, api_handler);
|
||||
|
||||
instance
|
||||
.run_blocking()
|
||||
}
|
||||
|
|
|
@ -3,13 +3,17 @@ use std::default::Default;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct BatteryJson {
|
||||
pub charge_rate: Option<u64>,
|
||||
pub charge_mode: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for BatteryJson {
|
||||
fn default() -> Self {
|
||||
Self { charge_rate: None }
|
||||
Self {
|
||||
charge_rate: None,
|
||||
charge_mode: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use super::MinMaxJson;
|
|||
|
||||
//const SCALING_FREQUENCIES: &[u64] = &[1700000, 2400000, 2800000];
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct CpuJson {
|
||||
pub online: bool,
|
||||
pub clock_limits: Option<MinMaxJson<u64>>,
|
||||
|
|
20
backend/src/persist/driver.rs
Normal file
20
backend/src/persist/driver.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
//use std::default::Default;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
|
||||
pub enum DriverJson {
|
||||
#[serde(rename = "steam-deck", alias = "gabe-boy")]
|
||||
SteamDeck,
|
||||
#[serde(rename = "steam-deck-oc", alias = "gabe-boy-advance")]
|
||||
SteamDeckAdvance,
|
||||
#[serde(rename = "generic")]
|
||||
Generic,
|
||||
#[serde(rename = "generic-amd")]
|
||||
GenericAMD,
|
||||
#[serde(rename = "unknown")]
|
||||
Unknown,
|
||||
#[default]
|
||||
#[serde(rename = "auto")]
|
||||
AutoDetect,
|
||||
}
|
|
@ -3,7 +3,7 @@ use std::default::Default;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::JsonError;
|
||||
use super::{BatteryJson, CpuJson, GpuJson};
|
||||
use super::{BatteryJson, CpuJson, GpuJson, DriverJson};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SettingsJson {
|
||||
|
@ -13,6 +13,7 @@ pub struct SettingsJson {
|
|||
pub cpus: Vec<CpuJson>,
|
||||
pub gpu: GpuJson,
|
||||
pub battery: BatteryJson,
|
||||
pub provider: Option<DriverJson>,
|
||||
}
|
||||
|
||||
impl Default for SettingsJson {
|
||||
|
@ -24,6 +25,7 @@ impl Default for SettingsJson {
|
|||
cpus: Vec::with_capacity(8),
|
||||
gpu: GpuJson::default(),
|
||||
battery: BatteryJson::default(),
|
||||
provider: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +56,7 @@ impl SettingsJson {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct MinMaxJson<T> {
|
||||
pub max: T,
|
||||
pub min: T,
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::default::Default;
|
|||
use super::MinMaxJson;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct GpuJson {
|
||||
pub fast_ppt: Option<u64>,
|
||||
pub slow_ppt: Option<u64>,
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
mod battery;
|
||||
mod cpu;
|
||||
mod driver;
|
||||
mod error;
|
||||
mod general;
|
||||
mod gpu;
|
||||
|
||||
pub use battery::BatteryJson;
|
||||
pub use cpu::CpuJson;
|
||||
pub use driver::DriverJson;
|
||||
pub use general::{MinMaxJson, SettingsJson};
|
||||
pub use gpu::GpuJson;
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use std::thread::{self, JoinHandle};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use crate::settings::{OnResume, Settings};
|
||||
use crate::utility::unwrap_maybe_fatal;
|
||||
use crate::api::handler::ApiMessage;
|
||||
//use crate::utility::unwrap_maybe_fatal;
|
||||
|
||||
const ALLOWED_ERROR: f64 = 100.0; // period of 10ms with 100x means sleep has to be >= 1s to be detected
|
||||
|
||||
pub fn spawn(settings: Settings) -> JoinHandle<()> {
|
||||
pub fn spawn(sender: Sender<ApiMessage>) -> JoinHandle<()> {
|
||||
thread::spawn(move || {
|
||||
log::info!("resume_worker starting...");
|
||||
let duration = Duration::from_millis(10); // very low so it detects before Steam client does
|
||||
|
@ -18,7 +19,7 @@ pub fn spawn(settings: Settings) -> JoinHandle<()> {
|
|||
if old_start.as_secs_f64() > duration.as_secs_f64() * (1.0 + ALLOWED_ERROR) {
|
||||
// has just resumed from sleep
|
||||
log::info!("Resume detected");
|
||||
unwrap_maybe_fatal(settings.on_resume(), "On resume failure");
|
||||
sender.send(ApiMessage::OnResume).expect("resume_worker send failed");
|
||||
log::debug!(
|
||||
"OnResume completed after sleeping for {}s",
|
||||
old_start.as_secs_f32()
|
||||
|
|
|
@ -1,188 +0,0 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use super::{OnResume, OnSet, SettingError, SettingsRange};
|
||||
use crate::persist::BatteryJson;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Battery {
|
||||
pub charge_rate: Option<u64>,
|
||||
state: crate::state::Battery,
|
||||
}
|
||||
|
||||
const BATTERY_VOLTAGE: f64 = 7.7;
|
||||
|
||||
const BATTERY_CHARGE_RATE_PATH: &str = "/sys/class/hwmon/hwmon5/maximum_battery_charge_rate"; // write-only
|
||||
const BATTERY_CURRENT_NOW_PATH: &str = "/sys/class/power_supply/BAT1/current_now"; // read-only
|
||||
const BATTERY_CHARGE_NOW_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_now"; // read-only
|
||||
const BATTERY_CHARGE_FULL_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full"; // read-only
|
||||
const BATTERY_CHARGE_DESIGN_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full_design"; // read-only
|
||||
|
||||
impl Battery {
|
||||
#[inline]
|
||||
pub fn from_json(other: BatteryJson, version: u64) -> Self {
|
||||
match version {
|
||||
0 => Self {
|
||||
charge_rate: other.charge_rate,
|
||||
state: crate::state::Battery::default(),
|
||||
},
|
||||
_ => Self {
|
||||
charge_rate: other.charge_rate,
|
||||
state: crate::state::Battery::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_all(&mut self) -> Result<(), SettingError> {
|
||||
if let Some(charge_rate) = self.charge_rate {
|
||||
self.state.charge_rate_set = true;
|
||||
usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
},
|
||||
)
|
||||
} else if self.state.charge_rate_set {
|
||||
self.state.charge_rate_set = false;
|
||||
usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, Self::max().charge_rate.unwrap()).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn clamp_all(&mut self) {
|
||||
let min = Self::min();
|
||||
let max = Self::max();
|
||||
if let Some(charge_rate) = &mut self.charge_rate {
|
||||
*charge_rate = (*charge_rate).clamp(min.charge_rate.unwrap(), max.charge_rate.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_current_now() -> Result<u64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) {
|
||||
Err((Some(e), None)) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
}),
|
||||
Err((None, Some(e))) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
}),
|
||||
Err(_) => panic!(
|
||||
"Invalid error while reading from `{}`",
|
||||
BATTERY_CURRENT_NOW_PATH
|
||||
),
|
||||
// this value is in uA, while it's set in mA
|
||||
// so convert this to mA for consistency
|
||||
Ok(val) => Ok(val / 1000),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_charge_now() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) {
|
||||
Err((Some(e), None)) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
}),
|
||||
Err((None, Some(e))) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
}),
|
||||
Err(_) => panic!(
|
||||
"Invalid error while reading from `{}`",
|
||||
BATTERY_CHARGE_NOW_PATH
|
||||
),
|
||||
// convert to Wh
|
||||
Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_charge_full() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) {
|
||||
Err((Some(e), None)) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
}),
|
||||
Err((None, Some(e))) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
}),
|
||||
Err(_) => panic!(
|
||||
"Invalid error while reading from `{}`",
|
||||
BATTERY_CHARGE_NOW_PATH
|
||||
),
|
||||
// convert to Wh
|
||||
Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_charge_design() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) {
|
||||
Err((Some(e), None)) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
}),
|
||||
Err((None, Some(e))) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e),
|
||||
setting: super::SettingVariant::Battery,
|
||||
}),
|
||||
Err(_) => panic!(
|
||||
"Invalid error while reading from `{}`",
|
||||
BATTERY_CHARGE_NOW_PATH
|
||||
),
|
||||
// convert to Wh
|
||||
Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_default() -> Self {
|
||||
Self {
|
||||
charge_rate: None,
|
||||
state: crate::state::Battery::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<BatteryJson> for Battery {
|
||||
#[inline]
|
||||
fn into(self) -> BatteryJson {
|
||||
BatteryJson {
|
||||
charge_rate: self.charge_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Battery {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
self.clamp_all();
|
||||
self.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Battery {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
self.clone().set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl SettingsRange for Battery {
|
||||
#[inline]
|
||||
fn max() -> Self {
|
||||
Self {
|
||||
charge_rate: Some(2500),
|
||||
state: crate::state::Battery::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn min() -> Self {
|
||||
Self {
|
||||
charge_rate: Some(250),
|
||||
state: crate::state::Battery::default(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,256 +0,0 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use super::MinMax;
|
||||
use super::{OnResume, OnSet, SettingError, SettingsRange};
|
||||
use crate::persist::CpuJson;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cpu {
|
||||
pub online: bool,
|
||||
pub clock_limits: Option<MinMax<u64>>,
|
||||
pub governor: String,
|
||||
index: usize,
|
||||
state: crate::state::Cpu,
|
||||
}
|
||||
|
||||
const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage";
|
||||
const CPU_FORCE_LIMITS_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level";
|
||||
|
||||
const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present";
|
||||
|
||||
impl Cpu {
|
||||
#[inline]
|
||||
pub fn from_json(other: CpuJson, version: u64, i: usize) -> Self {
|
||||
match version {
|
||||
0 => Self {
|
||||
online: other.online,
|
||||
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
|
||||
governor: other.governor,
|
||||
index: i,
|
||||
state: crate::state::Cpu::default(),
|
||||
},
|
||||
_ => Self {
|
||||
online: other.online,
|
||||
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
|
||||
governor: other.governor,
|
||||
index: i,
|
||||
state: crate::state::Cpu::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_all(&mut self) -> Result<(), SettingError> {
|
||||
// set cpu online/offline
|
||||
if self.index != 0 { // cpu0 cannot be disabled
|
||||
let online_path = cpu_online_path(self.index);
|
||||
usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", &online_path, e),
|
||||
setting: super::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
// set clock limits
|
||||
log::debug!("Setting {} to manual", CPU_FORCE_LIMITS_PATH);
|
||||
let mode: String = usdpl_back::api::files::read_single(CPU_FORCE_LIMITS_PATH.to_owned()).unwrap();
|
||||
if mode != "manual" {
|
||||
// set manual control
|
||||
usdpl_back::api::files::write_single(CPU_FORCE_LIMITS_PATH, "manual").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `manual` to `{}`: {}",
|
||||
CPU_FORCE_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
if let Some(clock_limits) = &self.clock_limits {
|
||||
log::debug!("Setting CPU {} (min, max) clockspeed to ({}, {})", self.index, clock_limits.min, clock_limits.max);
|
||||
self.state.clock_limits_set = true;
|
||||
// max clock
|
||||
let payload_max = format!("p {} 1 {}\n", self.index / 2, clock_limits.max);
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_max, CPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Cpu,
|
||||
},
|
||||
)?;
|
||||
// min clock
|
||||
let payload_min = format!("p {} 0 {}\n", self.index / 2, clock_limits.min);
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_min, CPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Cpu,
|
||||
},
|
||||
)?;
|
||||
} else if self.state.clock_limits_set || self.state.is_resuming {
|
||||
self.state.clock_limits_set = false;
|
||||
// disable manual clock limits
|
||||
log::debug!("Setting CPU {} to default clockspeed", self.index);
|
||||
// max clock
|
||||
let payload_max = format!("p {} 1 {}\n", self.index / 2, Self::max().clock_limits.unwrap().max);
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_max, CPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Cpu,
|
||||
},
|
||||
)?;
|
||||
// min clock
|
||||
let payload_min = format!("p {} 0 {}\n", self.index / 2, Self::min().clock_limits.unwrap().min);
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_min, CPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Cpu,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
// commit changes
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e),
|
||||
setting: super::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
|
||||
// set governor
|
||||
if self.index == 0 || self.online {
|
||||
let governor_path = cpu_governor_path(self.index);
|
||||
usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&self.governor, &governor_path, e
|
||||
),
|
||||
setting: super::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clamp_all(&mut self) {
|
||||
let min = Self::min();
|
||||
let max = Self::max();
|
||||
if let Some(clock_limits) = &mut self.clock_limits {
|
||||
let max_boost = max.clock_limits.as_ref().unwrap();
|
||||
let min_boost = min.clock_limits.as_ref().unwrap();
|
||||
clock_limits.min = clock_limits.min.clamp(min_boost.min, max_boost.min);
|
||||
clock_limits.max = clock_limits.max.clamp(min_boost.max, max_boost.max);
|
||||
}
|
||||
}
|
||||
|
||||
fn from_sys(index: usize) -> Self {
|
||||
Self {
|
||||
online: usdpl_back::api::files::read_single(cpu_online_path(index)).unwrap_or(1u8) != 0,
|
||||
clock_limits: None,
|
||||
governor: usdpl_back::api::files::read_single(cpu_governor_path(index))
|
||||
.unwrap_or("schedutil".to_owned()),
|
||||
index: index,
|
||||
state: crate::state::Cpu::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cpu_count() -> Option<usize> {
|
||||
let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH)
|
||||
.unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */);
|
||||
if let Some(dash_index) = data.find('-') {
|
||||
let data = data.split_off(dash_index + 1);
|
||||
if let Ok(max_cpu) = data.parse::<usize>() {
|
||||
return Some(max_cpu + 1);
|
||||
}
|
||||
}
|
||||
log::warn!("Failed to parse CPU info from kernel, is Tux evil?");
|
||||
None
|
||||
}
|
||||
|
||||
pub fn system_default() -> Vec<Self> {
|
||||
if let Some(max_cpu) = Self::cpu_count() {
|
||||
let mut cpus = Vec::with_capacity(max_cpu);
|
||||
for i in 0..max_cpu {
|
||||
cpus.push(Self::from_sys(i));
|
||||
}
|
||||
cpus
|
||||
} else {
|
||||
Vec::with_capacity(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<CpuJson> for Cpu {
|
||||
#[inline]
|
||||
fn into(self) -> CpuJson {
|
||||
CpuJson {
|
||||
online: self.online,
|
||||
clock_limits: self.clock_limits.map(|x| x.into()),
|
||||
governor: self.governor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Cpu {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
self.clamp_all();
|
||||
self.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Cpu {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
let mut copy = self.clone();
|
||||
copy.state.is_resuming = true;
|
||||
copy.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl SettingsRange for Cpu {
|
||||
#[inline]
|
||||
fn max() -> Self {
|
||||
Self {
|
||||
online: true,
|
||||
clock_limits: Some(MinMax {
|
||||
max: 3500,
|
||||
min: 3500,
|
||||
}),
|
||||
governor: "schedutil".to_owned(),
|
||||
index: usize::MAX,
|
||||
state: crate::state::Cpu::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn min() -> Self {
|
||||
Self {
|
||||
online: false,
|
||||
clock_limits: Some(MinMax { max: 500, min: 1400 }),
|
||||
governor: "schedutil".to_owned(),
|
||||
index: usize::MIN,
|
||||
state: crate::state::Cpu::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpu_online_path(index: usize) -> String {
|
||||
format!("/sys/devices/system/cpu/cpu{}/online", index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpu_governor_path(index: usize) -> String {
|
||||
format!(
|
||||
"/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor",
|
||||
index
|
||||
)
|
||||
}
|
204
backend/src/settings/detect/auto_detect.rs
Normal file
204
backend/src/settings/detect/auto_detect.rs
Normal file
|
@ -0,0 +1,204 @@
|
|||
use std::fs::File;
|
||||
|
||||
use regex::RegexBuilder;
|
||||
|
||||
use limits_core::json::{Limits, BatteryLimit, CpuLimit, GpuLimit};
|
||||
|
||||
use crate::persist::{DriverJson, SettingsJson};
|
||||
use crate::settings::{TGeneral, TCpus, TGpu, TBattery, Driver, General};
|
||||
|
||||
fn get_limits() -> limits_core::json::Base {
|
||||
let limits_path = super::utility::limits_path();
|
||||
match File::open(&limits_path) {
|
||||
Ok(f) => {
|
||||
match serde_json::from_reader(f) {
|
||||
Ok(lim) => lim,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to parse limits file `{}`, cannot use for auto_detect: {}", limits_path.display(), e);
|
||||
limits_core::json::Base::default()
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to open limits file `{}` (trying force refresh...): {}", limits_path.display(), e);
|
||||
super::limits_worker::get_limits_blocking()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn auto_detect_provider() -> DriverJson {
|
||||
let provider = auto_detect0(None, crate::utility::settings_dir().join("autodetect.json"))
|
||||
.battery
|
||||
.provider();
|
||||
//log::info!("Detected device automatically, compatible driver: {:?}", provider);
|
||||
provider
|
||||
}
|
||||
|
||||
/// Device detection logic
|
||||
pub fn auto_detect0(settings_opt: Option<SettingsJson>, json_path: std::path::PathBuf) -> Driver {
|
||||
let mut builder = DriverBuilder::new(json_path);
|
||||
|
||||
let cpu_info: String = usdpl_back::api::files::read_single("/proc/cpuinfo").unwrap_or_default();
|
||||
log::debug!("Read from /proc/cpuinfo:\n{}", cpu_info);
|
||||
let os_info: String = usdpl_back::api::files::read_single("/etc/os-release").unwrap_or_default();
|
||||
log::debug!("Read from /etc/os-release:\n{}", os_info);
|
||||
let dmi_info: String = std::process::Command::new("dmidecode").output().map(|out| String::from_utf8_lossy(&out.stdout).into_owned()).unwrap_or_default();
|
||||
log::debug!("Read dmidecode:\n{}", dmi_info);
|
||||
|
||||
let limits = get_limits();
|
||||
|
||||
// build driver based on limits conditions
|
||||
for conf in limits.configs {
|
||||
let conditions = conf.conditions;
|
||||
let mut matches = true;
|
||||
if conditions.is_empty() {
|
||||
matches = !builder.is_complete();
|
||||
} else {
|
||||
if let Some(dmi) = &conditions.dmi {
|
||||
let pattern = RegexBuilder::new(dmi)
|
||||
.multi_line(true)
|
||||
.build()
|
||||
.expect("Invalid DMI regex");
|
||||
matches &=pattern.is_match(&dmi_info);
|
||||
}
|
||||
if let Some(cpuinfo) = &conditions.cpuinfo {
|
||||
let pattern = RegexBuilder::new(cpuinfo)
|
||||
.multi_line(true)
|
||||
.build()
|
||||
.expect("Invalid CPU regex");
|
||||
matches &=pattern.is_match(&cpu_info);
|
||||
}
|
||||
if let Some(os) = &conditions.os {
|
||||
let pattern = RegexBuilder::new(os)
|
||||
.multi_line(true)
|
||||
.build()
|
||||
.expect("Invalid OS regex");
|
||||
matches &=pattern.is_match(&os_info);
|
||||
}
|
||||
if let Some(cmd) = &conditions.command {
|
||||
match std::process::Command::new("bash")
|
||||
.args(["-c", cmd])
|
||||
.status() {
|
||||
Ok(status) => matches &= status.code().map(|c| c == 0).unwrap_or(false),
|
||||
Err(e) => log::warn!("Ignoring bash limits error: {}", e),
|
||||
}
|
||||
}
|
||||
if let Some(file_exists) = &conditions.file_exists {
|
||||
let exists = std::path::Path::new(file_exists).exists();
|
||||
matches &= exists;
|
||||
}
|
||||
}
|
||||
if matches {
|
||||
if let Some(settings) = &settings_opt {
|
||||
*builder.general.persistent() = true;
|
||||
for limit in conf.limits {
|
||||
match limit {
|
||||
Limits::Cpu(cpus) => {
|
||||
let cpu_driver: Box<dyn TCpus> = match cpus {
|
||||
CpuLimit::SteamDeck => Box::new(crate::settings::steam_deck::Cpus::from_json(settings.cpus.clone(), settings.version)),
|
||||
CpuLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck::Cpus::from_json(settings.cpus.clone(), settings.version)),
|
||||
CpuLimit::Generic(x) => Box::new(crate::settings::generic::Cpus::<crate::settings::generic::Cpu>::from_json_and_limits(settings.cpus.clone(), settings.version, x)),
|
||||
CpuLimit::GenericAMD(x) => Box::new(crate::settings::generic_amd::Cpus::from_json_and_limits(settings.cpus.clone(), settings.version, x)),
|
||||
CpuLimit::Unknown => Box::new(crate::settings::unknown::Cpus::from_json(settings.cpus.clone(), settings.version)),
|
||||
};
|
||||
builder.cpus = Some(cpu_driver);
|
||||
},
|
||||
Limits::Gpu(gpu) => {
|
||||
let driver: Box<dyn TGpu> = match gpu {
|
||||
GpuLimit::SteamDeck => Box::new(crate::settings::steam_deck::Gpu::from_json(settings.gpu.clone(), settings.version)),
|
||||
GpuLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck::Gpu::from_json(settings.gpu.clone(), settings.version)),
|
||||
GpuLimit::Generic(x) => Box::new(crate::settings::generic::Gpu::from_json_and_limits(settings.gpu.clone(), settings.version, x)),
|
||||
GpuLimit::GenericAMD(x) => Box::new(crate::settings::generic_amd::Gpu::from_json_and_limits(settings.gpu.clone(), settings.version, x)),
|
||||
GpuLimit::Unknown => Box::new(crate::settings::unknown::Gpu::from_json(settings.gpu.clone(), settings.version)),
|
||||
};
|
||||
builder.gpu = Some(driver);
|
||||
},
|
||||
Limits::Battery(batt) => {
|
||||
let driver: Box<dyn TBattery> = match batt {
|
||||
BatteryLimit::SteamDeck => Box::new(crate::settings::steam_deck::Battery::from_json(settings.battery.clone(), settings.version)),
|
||||
BatteryLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck::Battery::from_json(settings.battery.clone(), settings.version)),
|
||||
BatteryLimit::Generic(x) => Box::new(crate::settings::generic::Battery::from_json_and_limits(settings.battery.clone(), settings.version, x)),
|
||||
BatteryLimit::Unknown => Box::new(crate::settings::unknown::Battery),
|
||||
};
|
||||
builder.battery = Some(driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for limit in conf.limits {
|
||||
match limit {
|
||||
Limits::Cpu(cpus) => {
|
||||
let cpu_driver: Box<dyn TCpus> = match cpus {
|
||||
CpuLimit::SteamDeck => Box::new(crate::settings::steam_deck::Cpus::system_default()),
|
||||
CpuLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck::Cpus::system_default()),
|
||||
CpuLimit::Generic(x) => Box::new(crate::settings::generic::Cpus::<crate::settings::generic::Cpu>::from_limits(x)),
|
||||
CpuLimit::GenericAMD(x) => Box::new(crate::settings::generic_amd::Cpus::from_limits(x)),
|
||||
CpuLimit::Unknown => Box::new(crate::settings::unknown::Cpus::system_default()),
|
||||
};
|
||||
builder.cpus = Some(cpu_driver);
|
||||
},
|
||||
Limits::Gpu(gpu) => {
|
||||
let driver: Box<dyn TGpu> = match gpu {
|
||||
GpuLimit::SteamDeck => Box::new(crate::settings::steam_deck::Gpu::system_default()),
|
||||
GpuLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck::Gpu::system_default()),
|
||||
GpuLimit::Generic(x) => Box::new(crate::settings::generic::Gpu::from_limits(x)),
|
||||
GpuLimit::GenericAMD(x) => Box::new(crate::settings::generic_amd::Gpu::from_limits(x)),
|
||||
GpuLimit::Unknown => Box::new(crate::settings::unknown::Gpu::system_default()),
|
||||
};
|
||||
builder.gpu = Some(driver);
|
||||
},
|
||||
Limits::Battery(batt) => {
|
||||
let driver: Box<dyn TBattery> = match batt {
|
||||
BatteryLimit::SteamDeck => Box::new(crate::settings::steam_deck::Battery::system_default()),
|
||||
BatteryLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck::Battery::system_default()),
|
||||
BatteryLimit::Generic(x) => Box::new(crate::settings::generic::Battery::from_limits(x)),
|
||||
BatteryLimit::Unknown => Box::new(crate::settings::unknown::Battery),
|
||||
};
|
||||
builder.battery = Some(driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
builder.build()
|
||||
}
|
||||
|
||||
struct DriverBuilder {
|
||||
general: Box<dyn TGeneral>,
|
||||
cpus: Option<Box<dyn TCpus>>,
|
||||
gpu: Option<Box<dyn TGpu>>,
|
||||
battery: Option<Box<dyn TBattery>>,
|
||||
}
|
||||
|
||||
impl DriverBuilder {
|
||||
fn new(json_path: std::path::PathBuf) -> Self {
|
||||
Self {
|
||||
general: Box::new(General {
|
||||
persistent: false,
|
||||
path: json_path,
|
||||
name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(),
|
||||
driver: DriverJson::AutoDetect,
|
||||
}),
|
||||
cpus: None,
|
||||
gpu: None,
|
||||
battery: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete(&self) -> bool {
|
||||
self.cpus.is_some() && self.gpu.is_some() && self.battery.is_some()
|
||||
}
|
||||
|
||||
fn build(self) -> Driver {
|
||||
Driver {
|
||||
general: self.general,
|
||||
cpus: self.cpus.unwrap_or_else(|| Box::new(crate::settings::unknown::Cpus::system_default())),
|
||||
gpu: self.gpu.unwrap_or_else(|| Box::new(crate::settings::unknown::Gpu::system_default())),
|
||||
battery: self.battery.unwrap_or_else(|| Box::new(crate::settings::unknown::Battery))
|
||||
}
|
||||
}
|
||||
}
|
119
backend/src/settings/detect/limits_worker.rs
Normal file
119
backend/src/settings/detect/limits_worker.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use std::thread::{self, JoinHandle};
|
||||
#[cfg(feature = "online")]
|
||||
use std::time::Duration;
|
||||
|
||||
use limits_core::json::Base;
|
||||
|
||||
#[cfg(feature = "online")]
|
||||
pub fn spawn() -> JoinHandle<()> {
|
||||
thread::spawn(move || {
|
||||
log::info!("limits_worker starting...");
|
||||
let sleep_dur = Duration::from_secs(60*60*24); // 1 day
|
||||
let limits_path = super::utility::limits_path();
|
||||
loop {
|
||||
thread::sleep(sleep_dur);
|
||||
if (limits_path.exists() && limits_path.is_file()) || !limits_path.exists() {
|
||||
// try to load limits from file, fallback to built-in default
|
||||
let base = match std::fs::File::open(&limits_path) {
|
||||
Ok(f) => {
|
||||
match serde_json::from_reader(f) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
log::error!("Cannot parse {}: {}", limits_path.display(), e);
|
||||
Base::default()
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Cannot open {}: {}", limits_path.display(), e);
|
||||
Base::default()
|
||||
}
|
||||
};
|
||||
if let Some(refresh) = &base.refresh {
|
||||
// try to retrieve newer version
|
||||
match ureq::get(refresh)
|
||||
.call() {
|
||||
Ok(response) => {
|
||||
let json_res: std::io::Result<Base> = response.into_json();
|
||||
match json_res {
|
||||
Ok(new_base) => {
|
||||
save_base(&new_base, &limits_path);
|
||||
},
|
||||
Err(e) => log::error!("Cannot parse response from `{}`: {}", refresh, e),
|
||||
}
|
||||
},
|
||||
Err(e) => log::warn!("Cannot download limits from `{}`: {}", refresh, e),
|
||||
}
|
||||
} else {
|
||||
log::info!("limits_worker refresh is empty, terminating...");
|
||||
break;
|
||||
}
|
||||
} else if !limits_path.is_file() {
|
||||
log::error!("Path for storing limits is not a file!");
|
||||
}
|
||||
}
|
||||
log::warn!("limits_worker completed!");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "online"))]
|
||||
pub fn spawn() -> JoinHandle<()> {
|
||||
thread::spawn(move || {
|
||||
log::info!("limits_worker disabled...");
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_limits_blocking() -> Base {
|
||||
let limits_path = super::utility::limits_path();
|
||||
if limits_path.is_file() {
|
||||
match std::fs::File::open(&limits_path) {
|
||||
Ok(f) => {
|
||||
match serde_json::from_reader(f) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
log::error!("Cannot parse {}: {}", limits_path.display(), e);
|
||||
Base::default()
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Cannot open {}: {}", limits_path.display(), e);
|
||||
Base::default()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#[cfg(feature = "online")]
|
||||
{
|
||||
let refresh = Base::default().refresh.unwrap();
|
||||
match ureq::get(&refresh) // try to retrieve newer version
|
||||
.call() {
|
||||
Ok(response) => {
|
||||
let json_res: std::io::Result<Base> = response.into_json();
|
||||
match json_res {
|
||||
Ok(new_base) => {
|
||||
save_base(&new_base, &limits_path);
|
||||
return new_base;
|
||||
},
|
||||
Err(e) => log::error!("Cannot parse response from `{}`: {}", refresh, e)
|
||||
}
|
||||
},
|
||||
Err(e) => log::warn!("Cannot download limits from `{}`: {}", refresh, e),
|
||||
}
|
||||
}
|
||||
Base::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "online")]
|
||||
fn save_base(new_base: &Base, path: impl AsRef<std::path::Path>) {
|
||||
let limits_path = path.as_ref();
|
||||
match std::fs::File::create(&limits_path) {
|
||||
Ok(f) => {
|
||||
match serde_json::to_writer_pretty(f, &new_base) {
|
||||
Ok(_) => log::info!("Successfully saved new limits to {}", limits_path.display()),
|
||||
Err(e) => log::error!("Failed to save limits json to file `{}`: {}", limits_path.display(), e),
|
||||
}
|
||||
},
|
||||
Err(e) => log::error!("Cannot create {}: {}", limits_path.display(), e)
|
||||
}
|
||||
}
|
5
backend/src/settings/detect/mod.rs
Normal file
5
backend/src/settings/detect/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod auto_detect;
|
||||
pub mod limits_worker;
|
||||
mod utility;
|
||||
|
||||
pub use auto_detect::{auto_detect_provider, auto_detect0};
|
3
backend/src/settings/detect/utility.rs
Normal file
3
backend/src/settings/detect/utility.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub fn limits_path() -> std::path::PathBuf {
|
||||
crate::utility::settings_dir().join(crate::consts::LIMITS_FILE)
|
||||
}
|
80
backend/src/settings/driver.rs
Normal file
80
backend/src/settings/driver.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use crate::persist::{DriverJson, SettingsJson};
|
||||
use super::{TGeneral, TCpus, TGpu, TBattery, SettingError, General, auto_detect0};
|
||||
|
||||
pub struct Driver {
|
||||
pub general: Box<dyn TGeneral>,
|
||||
pub cpus: Box<dyn TCpus>,
|
||||
pub gpu: Box<dyn TGpu>,
|
||||
pub battery: Box<dyn TBattery>,
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
pub fn init(settings: SettingsJson, json_path: std::path::PathBuf) -> Result<Self, SettingError> {
|
||||
Ok(match settings.version {
|
||||
0 => Self::version0(settings, json_path)?,
|
||||
_ => Self {
|
||||
general: Box::new(General {
|
||||
persistent: settings.persistent,
|
||||
path: json_path,
|
||||
name: settings.name,
|
||||
driver: DriverJson::SteamDeck,
|
||||
}),
|
||||
cpus: Box::new(super::steam_deck::Cpus::from_json(settings.cpus, settings.version)),
|
||||
gpu: Box::new(super::steam_deck::Gpu::from_json(settings.gpu, settings.version)),
|
||||
battery: Box::new(super::steam_deck::Battery::from_json(settings.battery, settings.version)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn version0(settings: SettingsJson, json_path: std::path::PathBuf) -> Result<Self, SettingError> {
|
||||
if let Some(provider) = &settings.provider {
|
||||
match provider {
|
||||
DriverJson::SteamDeck => Ok(Self {
|
||||
general: Box::new(General {
|
||||
persistent: settings.persistent,
|
||||
path: json_path,
|
||||
name: settings.name,
|
||||
driver: DriverJson::SteamDeck,
|
||||
}),
|
||||
cpus: Box::new(super::steam_deck::Cpus::from_json(settings.cpus, settings.version)),
|
||||
gpu: Box::new(super::steam_deck::Gpu::from_json(settings.gpu, settings.version)),
|
||||
battery: Box::new(super::steam_deck::Battery::from_json(settings.battery, settings.version)),
|
||||
}),
|
||||
// There's nothing special about SteamDeckAdvance, it just appears different
|
||||
DriverJson::SteamDeckAdvance => Ok(Self {
|
||||
general: Box::new(General {
|
||||
persistent: settings.persistent,
|
||||
path: json_path,
|
||||
name: settings.name,
|
||||
driver: DriverJson::SteamDeckAdvance,
|
||||
}),
|
||||
cpus: Box::new(super::steam_deck::Cpus::from_json(settings.cpus, settings.version)),
|
||||
gpu: Box::new(super::steam_deck::Gpu::from_json(settings.gpu, settings.version)),
|
||||
battery: Box::new(super::steam_deck::Battery::from_json(settings.battery, settings.version)),
|
||||
}),
|
||||
DriverJson::Generic | DriverJson::GenericAMD => Ok(super::detect::auto_detect0(Some(settings), json_path)),
|
||||
DriverJson::Unknown => Ok(super::detect::auto_detect0(Some(settings), json_path)),
|
||||
DriverJson::AutoDetect => Ok(super::detect::auto_detect0(Some(settings), json_path)),
|
||||
}
|
||||
} else {
|
||||
Ok(super::detect::auto_detect0(Some(settings), json_path))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_default(json_path: std::path::PathBuf) -> Self {
|
||||
auto_detect0(None, json_path)
|
||||
}
|
||||
}
|
||||
|
||||
// sshhhh, this function isn't here ;)
|
||||
#[inline]
|
||||
pub fn maybe_do_button() {
|
||||
match super::auto_detect_provider() {
|
||||
DriverJson::SteamDeck | DriverJson::SteamDeckAdvance => {
|
||||
crate::settings::steam_deck::flash_led();
|
||||
},
|
||||
DriverJson::Generic | DriverJson::GenericAMD => log::warn!("You need to come up with something fun on generic"),
|
||||
DriverJson::Unknown => log::warn!("Can't do button activities on unknown platform"),
|
||||
DriverJson::AutoDetect => log::warn!("WTF, why is auto_detect detecting AutoDetect???")
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use std::convert::Into;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
//use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::{Battery, Cpu, Gpu};
|
||||
//use super::{Battery, Cpus, Gpu};
|
||||
use super::{OnResume, OnSet, SettingError};
|
||||
use crate::persist::{CpuJson, SettingsJson};
|
||||
use crate::utility::unwrap_lock;
|
||||
use super::{TGeneral, TGpu, TCpus, TBattery};
|
||||
use crate::persist::SettingsJson;
|
||||
//use crate::utility::unwrap_lock;
|
||||
|
||||
const LATEST_VERSION: u64 = 0;
|
||||
|
||||
|
@ -33,6 +33,7 @@ pub struct General {
|
|||
pub persistent: bool,
|
||||
pub path: PathBuf,
|
||||
pub name: String,
|
||||
pub driver: crate::persist::DriverJson,
|
||||
}
|
||||
|
||||
impl OnSet for General {
|
||||
|
@ -41,26 +42,60 @@ impl OnSet for General {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
impl OnResume for General {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TGeneral for General {
|
||||
fn limits(&self) -> crate::api::GeneralLimits {
|
||||
crate::api::GeneralLimits { }
|
||||
}
|
||||
|
||||
fn get_persistent(&self) -> bool {
|
||||
self.persistent
|
||||
}
|
||||
|
||||
fn persistent(&mut self) -> &'_ mut bool {
|
||||
&mut self.persistent
|
||||
}
|
||||
|
||||
fn get_path(&self) -> &'_ std::path::Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn path(&mut self, path: std::path::PathBuf) {
|
||||
self.path = path;
|
||||
}
|
||||
|
||||
fn get_name(&self) -> &'_ str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
self.driver.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Settings {
|
||||
pub general: Arc<Mutex<General>>,
|
||||
pub cpus: Arc<Mutex<Vec<Cpu>>>,
|
||||
pub gpu: Arc<Mutex<Gpu>>,
|
||||
pub battery: Arc<Mutex<Battery>>,
|
||||
pub general: Box<dyn TGeneral>,
|
||||
pub cpus: Box<dyn TCpus>,
|
||||
pub gpu: Box<dyn TGpu>,
|
||||
pub battery: Box<dyn TBattery>,
|
||||
}
|
||||
|
||||
impl OnSet for Settings {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
unwrap_lock(self.battery.lock(), "battery").on_set()?;
|
||||
{
|
||||
// cpu lock scope
|
||||
let mut cpu_lock = unwrap_lock(self.cpus.lock(), "cpu");
|
||||
for cpu in cpu_lock.iter_mut() {
|
||||
cpu.on_set()?;
|
||||
}
|
||||
}
|
||||
unwrap_lock(self.gpu.lock(), "gpu").on_set()?;
|
||||
unwrap_lock(self.general.lock(), "general").on_set()?;
|
||||
self.battery.on_set()?;
|
||||
self.cpus.on_set()?;
|
||||
self.gpu.on_set()?;
|
||||
self.general.on_set()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -68,84 +103,42 @@ impl OnSet for Settings {
|
|||
impl Settings {
|
||||
#[inline]
|
||||
pub fn from_json(other: SettingsJson, json_path: PathBuf) -> Self {
|
||||
match other.version {
|
||||
0 => Self {
|
||||
general: Arc::new(Mutex::new(General {
|
||||
persistent: other.persistent,
|
||||
path: json_path,
|
||||
name: other.name,
|
||||
})),
|
||||
cpus: Arc::new(Mutex::new(Self::convert_cpus(other.cpus, other.version))),
|
||||
gpu: Arc::new(Mutex::new(Gpu::from_json(other.gpu, other.version))),
|
||||
battery: Arc::new(Mutex::new(Battery::from_json(other.battery, other.version))),
|
||||
},
|
||||
_ => Self {
|
||||
general: Arc::new(Mutex::new(General {
|
||||
persistent: other.persistent,
|
||||
path: json_path,
|
||||
name: other.name,
|
||||
})),
|
||||
cpus: Arc::new(Mutex::new(Self::convert_cpus(other.cpus, other.version))),
|
||||
gpu: Arc::new(Mutex::new(Gpu::from_json(other.gpu, other.version))),
|
||||
battery: Arc::new(Mutex::new(Battery::from_json(other.battery, other.version))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_cpus(mut cpus: Vec<CpuJson>, version: u64) -> Vec<Cpu> {
|
||||
let mut result = Vec::with_capacity(cpus.len());
|
||||
let max_cpus = Cpu::cpu_count();
|
||||
for (i, cpu) in cpus.drain(..).enumerate() {
|
||||
// prevent having more CPUs than available
|
||||
if let Some(max_cpus) = max_cpus {
|
||||
if i == max_cpus {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.push(Cpu::from_json(cpu, version, i));
|
||||
}
|
||||
if let Some(max_cpus) = max_cpus {
|
||||
if result.len() != max_cpus {
|
||||
let mut sys_cpus = Cpu::system_default();
|
||||
for i in result.len()..sys_cpus.len() {
|
||||
result.push(sys_cpus.remove(i));
|
||||
match super::Driver::init(other, json_path.clone()) {
|
||||
Ok(x) => {
|
||||
log::info!("Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", x.general.provider(), x.cpus.provider(), x.gpu.provider(), x.battery.provider());
|
||||
Self {
|
||||
general: x.general,
|
||||
cpus: x.cpus,
|
||||
gpu: x.gpu,
|
||||
battery: x.battery,
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Driver init error: {}", e);
|
||||
Self::system_default(json_path)
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn system_default(json_path: PathBuf) -> Self {
|
||||
let driver = super::Driver::system_default(json_path);
|
||||
Self {
|
||||
general: Arc::new(Mutex::new(General {
|
||||
persistent: false,
|
||||
path: json_path,
|
||||
name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(),
|
||||
})),
|
||||
cpus: Arc::new(Mutex::new(Cpu::system_default())),
|
||||
gpu: Arc::new(Mutex::new(Gpu::system_default())),
|
||||
battery: Arc::new(Mutex::new(Battery::system_default())),
|
||||
general: driver.general,
|
||||
cpus: driver.cpus,
|
||||
gpu: driver.gpu,
|
||||
battery: driver.battery,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_system_default(&self) {
|
||||
{
|
||||
let mut cpu_lock = unwrap_lock(self.cpus.lock(), "cpu");
|
||||
*cpu_lock = Cpu::system_default();
|
||||
}
|
||||
{
|
||||
let mut gpu_lock = unwrap_lock(self.gpu.lock(), "gpu");
|
||||
*gpu_lock = Gpu::system_default();
|
||||
}
|
||||
{
|
||||
let mut battery_lock = unwrap_lock(self.battery.lock(), "battery");
|
||||
*battery_lock = Battery::system_default();
|
||||
}
|
||||
pub fn load_system_default(&mut self) {
|
||||
let driver = super::Driver::system_default(self.general.get_path().to_owned());
|
||||
self.cpus = driver.cpus;
|
||||
self.gpu = driver.gpu;
|
||||
self.battery = driver.battery;
|
||||
}
|
||||
|
||||
pub fn load_file(&self, filename: PathBuf, name: String, system_defaults: bool) -> Result<bool, SettingError> {
|
||||
|
||||
pub fn load_file(&mut self, filename: PathBuf, name: String, system_defaults: bool) -> Result<bool, SettingError> {
|
||||
let json_path = crate::utility::settings_dir().join(filename);
|
||||
let mut general_lock = unwrap_lock(self.general.lock(), "general");
|
||||
if json_path.exists() {
|
||||
let settings_json = SettingsJson::open(&json_path).map_err(|e| SettingError {
|
||||
msg: e.to_string(),
|
||||
|
@ -153,80 +146,106 @@ impl Settings {
|
|||
})?;
|
||||
if !settings_json.persistent {
|
||||
log::warn!("Loaded persistent config `{}` ({}) with persistent=false", &settings_json.name, json_path.display());
|
||||
general_lock.persistent = false;
|
||||
general_lock.name = name;
|
||||
*self.general.persistent() = false;
|
||||
self.general.name(name);
|
||||
} else {
|
||||
let new_cpus = Self::convert_cpus(settings_json.cpus, settings_json.version);
|
||||
let new_gpu = Gpu::from_json(settings_json.gpu, settings_json.version);
|
||||
let new_battery = Battery::from_json(settings_json.battery, settings_json.version);
|
||||
{
|
||||
let mut cpu_lock = unwrap_lock(self.cpus.lock(), "cpu");
|
||||
*cpu_lock = new_cpus;
|
||||
}
|
||||
{
|
||||
let mut gpu_lock = unwrap_lock(self.gpu.lock(), "gpu");
|
||||
*gpu_lock = new_gpu;
|
||||
}
|
||||
{
|
||||
let mut battery_lock = unwrap_lock(self.battery.lock(), "battery");
|
||||
*battery_lock = new_battery;
|
||||
}
|
||||
general_lock.persistent = true;
|
||||
general_lock.name = settings_json.name;
|
||||
match super::Driver::init(settings_json, json_path.clone()) {
|
||||
Ok(x) => {
|
||||
log::info!("Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", x.general.provider(), x.cpus.provider(), x.gpu.provider(), x.battery.provider());
|
||||
self.general = x.general;
|
||||
self.cpus = x.cpus;
|
||||
self.gpu = x.gpu;
|
||||
self.battery = x.battery;
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Driver init error: {}", e);
|
||||
self.general.name(name);
|
||||
*self.general.persistent() = false;
|
||||
self.general.path(json_path);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if system_defaults {
|
||||
self.load_system_default();
|
||||
}
|
||||
general_lock.persistent = false;
|
||||
general_lock.name = name;
|
||||
*self.general.persistent() = false;
|
||||
self.general.name(name);
|
||||
}
|
||||
self.general.path(json_path);
|
||||
Ok(*self.general.persistent())
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn load_file(&mut self, filename: PathBuf, name: String, system_defaults: bool) -> Result<bool, SettingError> {
|
||||
let json_path = crate::utility::settings_dir().join(filename);
|
||||
//let mut general_lock = unwrap_lock(self.general.lock(), "general");
|
||||
if json_path.exists() {
|
||||
let settings_json = SettingsJson::open(&json_path).map_err(|e| SettingError {
|
||||
msg: e.to_string(),
|
||||
setting: SettingVariant::General,
|
||||
})?;
|
||||
if !settings_json.persistent {
|
||||
log::warn!("Loaded persistent config `{}` ({}) with persistent=false", &settings_json.name, json_path.display());
|
||||
*self.general.persistent() = false;
|
||||
self.general.name(name);
|
||||
} else {
|
||||
self.cpus = Box::new(super::steam_deck::Cpus::from_json(settings_json.cpus, settings_json.version));
|
||||
self.gpu = Box::new(super::steam_deck::Gpu::from_json(settings_json.gpu, settings_json.version));
|
||||
self.battery = Box::new(super::steam_deck::Battery::from_json(settings_json.battery, settings_json.version));
|
||||
*self.general.persistent() = true;
|
||||
self.general.name(settings_json.name);
|
||||
}
|
||||
} else {
|
||||
if system_defaults {
|
||||
self.load_system_default();
|
||||
}
|
||||
*self.general.persistent() = false;
|
||||
self.general.name(name);
|
||||
}
|
||||
self.general.path(json_path);
|
||||
Ok(*self.general.persistent())
|
||||
}*/
|
||||
|
||||
pub fn json(&self) -> SettingsJson {
|
||||
SettingsJson {
|
||||
version: LATEST_VERSION,
|
||||
name: self.general.get_name().to_owned(),
|
||||
persistent: self.general.get_persistent(),
|
||||
cpus: self.cpus.json(),
|
||||
gpu: self.gpu.json(),
|
||||
battery: self.battery.json(),
|
||||
provider: Some(self.general.provider()),
|
||||
}
|
||||
general_lock.path = json_path;
|
||||
Ok(general_lock.persistent)
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Settings {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
log::debug!("Locking settings for on_resume");
|
||||
unwrap_lock(self.battery.lock(), "battery").on_resume()?;
|
||||
log::debug!("Got battery lock");
|
||||
{
|
||||
let cpu_lock = unwrap_lock(self.cpus.lock(), "cpu");
|
||||
log::debug!("Got cpus lock");
|
||||
for cpu in cpu_lock.iter() {
|
||||
cpu.on_resume()?;
|
||||
}
|
||||
}
|
||||
unwrap_lock(self.gpu.lock(), "gpu").on_resume()?;
|
||||
log::debug!("Got gpu lock");
|
||||
log::debug!("Applying settings for on_resume");
|
||||
self.battery.on_resume()?;
|
||||
log::debug!("Resumed battery");
|
||||
self.cpus.on_resume()?;
|
||||
log::debug!("Resumed CPUs");
|
||||
self.gpu.on_resume()?;
|
||||
log::debug!("Resumed GPU");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<SettingsJson> for Settings {
|
||||
/*impl Into<SettingsJson> for Settings {
|
||||
#[inline]
|
||||
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");
|
||||
log::debug!("Converting into json");
|
||||
SettingsJson {
|
||||
version: LATEST_VERSION,
|
||||
name: gen_lock.name.clone(),
|
||||
persistent: gen_lock.persistent,
|
||||
cpus: cpu_lock
|
||||
.clone()
|
||||
.drain(..)
|
||||
.map(|cpu| cpu.into())
|
||||
.collect(),
|
||||
gpu: gpu_lock.clone().into(),
|
||||
battery: batt_lock.clone().into(),
|
||||
name: self.general.get_name().to_owned(),
|
||||
persistent: self.general.get_persistent(),
|
||||
cpus: self.cpus.json(),
|
||||
gpu: self.gpu.json(),
|
||||
battery: self.battery.json(),
|
||||
provider: Some(self.general.provider()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
|
130
backend/src/settings/generic/battery.rs
Normal file
130
backend/src/settings/generic/battery.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use limits_core::json::GenericBatteryLimit;
|
||||
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::TBattery;
|
||||
use crate::persist::BatteryJson;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Battery {
|
||||
#[allow(dead_code)]
|
||||
limits: GenericBatteryLimit,
|
||||
}
|
||||
|
||||
impl Into<BatteryJson> for Battery {
|
||||
#[inline]
|
||||
fn into(self) -> BatteryJson {
|
||||
BatteryJson {
|
||||
charge_rate: None,
|
||||
charge_mode: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Battery {
|
||||
fn read_f64<P: AsRef<std::path::Path>>(path: P) -> Result<f64, SettingError> {
|
||||
let path = path.as_ref();
|
||||
match usdpl_back::api::files::read_single::<_, f64, _>(path) {
|
||||
Err(e) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", path.display(), e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
}),
|
||||
// this value is in uA, while it's set in mA
|
||||
// so convert this to mA for consistency
|
||||
Ok(val) => Ok(val / 1000.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_limits(limits: limits_core::json::GenericBatteryLimit) -> Self {
|
||||
// TODO
|
||||
Self {
|
||||
limits
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json_and_limits(_other: BatteryJson, _version: u64, limits: limits_core::json::GenericBatteryLimit) -> Self {
|
||||
// TODO
|
||||
Self {
|
||||
limits
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Battery {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Battery {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TBattery for Battery {
|
||||
fn limits(&self) -> crate::api::BatteryLimits {
|
||||
crate::api::BatteryLimits {
|
||||
charge_current: None,
|
||||
charge_current_step: 50,
|
||||
charge_modes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn json(&self) -> crate::persist::BatteryJson {
|
||||
self.clone().into()
|
||||
}
|
||||
|
||||
fn charge_rate(&mut self, _rate: Option<u64>) {
|
||||
}
|
||||
|
||||
fn get_charge_rate(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn charge_mode(&mut self, _rate: Option<String>) {
|
||||
}
|
||||
|
||||
fn get_charge_mode(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn read_charge_full(&self) -> Option<f64> {
|
||||
match Self::read_f64("/sys/class/power_supply/BAT0/energy_full") {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::warn!("read_charge_full err: {}", e.msg);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_charge_now(&self) -> Option<f64> {
|
||||
match Self::read_f64("/sys/class/power_supply/BAT0/energy_now") {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::warn!("read_charge_now err: {}", e.msg);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_charge_design(&self) -> Option<f64> {
|
||||
match Self::read_f64("/sys/class/power_supply/BAT0/energy_design") {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::warn!("read_charge_design err: {}", e.msg);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_current_now(&self) -> Option<f64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::Generic
|
||||
}
|
||||
}
|
364
backend/src/settings/generic/cpu.rs
Normal file
364
backend/src/settings/generic/cpu.rs
Normal file
|
@ -0,0 +1,364 @@
|
|||
use std::convert::{Into, AsMut, AsRef};
|
||||
|
||||
use limits_core::json::GenericCpuLimit;
|
||||
|
||||
use crate::settings::{MinMax, min_max_from_json};
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::{TCpus, TCpu};
|
||||
use crate::persist::CpuJson;
|
||||
use super::FromGenericCpuInfo;
|
||||
|
||||
const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present";
|
||||
const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cpus<C: AsMut<Cpu> + AsRef<Cpu> + TCpu> {
|
||||
pub cpus: Vec<C>,
|
||||
pub smt: bool,
|
||||
pub smt_capable: bool,
|
||||
}
|
||||
|
||||
impl<C: AsMut<Cpu> + AsRef<Cpu> + TCpu + OnSet> OnSet for Cpus<C> {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
if self.smt_capable {
|
||||
// toggle SMT
|
||||
if self.smt {
|
||||
usdpl_back::api::files::write_single(CPU_SMT_PATH, "on").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `on` to `{}`: {}",
|
||||
CPU_SMT_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
} else {
|
||||
usdpl_back::api::files::write_single(CPU_SMT_PATH, "off").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `off` to `{}`: {}",
|
||||
CPU_SMT_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
}
|
||||
for (i, cpu) in self.cpus.as_mut_slice().iter_mut().enumerate() {
|
||||
cpu.as_mut().state.do_set_online = self.smt || i % 2 == 0 || !self.smt_capable;
|
||||
cpu.on_set()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsMut<Cpu> + AsRef<Cpu> + TCpu + OnResume> OnResume for Cpus<C> {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
for cpu in &self.cpus {
|
||||
cpu.on_resume()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsMut<Cpu> + AsRef<Cpu> + TCpu + FromGenericCpuInfo> Cpus<C> {
|
||||
pub fn cpu_count() -> Option<usize> {
|
||||
let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH)
|
||||
.unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */);
|
||||
if let Some(dash_index) = data.find('-') {
|
||||
let data = data.split_off(dash_index + 1);
|
||||
if let Ok(max_cpu) = data.parse::<usize>() {
|
||||
return Some(max_cpu + 1);
|
||||
}
|
||||
}
|
||||
log::warn!("Failed to parse CPU info from kernel, is Tux evil?");
|
||||
None
|
||||
}
|
||||
|
||||
fn system_smt_capabilities() -> (bool, bool) {
|
||||
match usdpl_back::api::files::read_single::<_, String, _>(CPU_SMT_PATH) {
|
||||
Ok(val) => (val.trim().to_lowercase() == "on", true),
|
||||
Err(_) => (false, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_limits(limits: limits_core::json::GenericCpuLimit) -> Self {
|
||||
let cpu_count = Self::cpu_count().unwrap_or(8);
|
||||
let (_, can_smt) = Self::system_smt_capabilities();
|
||||
let mut new_cpus = Vec::with_capacity(cpu_count);
|
||||
for i in 0..cpu_count {
|
||||
let new_cpu = C::from_limits(i, limits.clone());
|
||||
new_cpus.push(new_cpu);
|
||||
}
|
||||
Self {
|
||||
cpus: new_cpus,
|
||||
smt: true,
|
||||
smt_capable: can_smt,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json_and_limits(mut other: Vec<CpuJson>, version: u64, limits: limits_core::json::GenericCpuLimit) -> Self {
|
||||
let (_, can_smt) = Self::system_smt_capabilities();
|
||||
let mut result = Vec::with_capacity(other.len());
|
||||
let max_cpus = Self::cpu_count();
|
||||
let smt_guess = crate::settings::util::guess_smt(&other) && can_smt;
|
||||
for (i, cpu) in other.drain(..).enumerate() {
|
||||
// prevent having more CPUs than available
|
||||
if let Some(max_cpus) = max_cpus {
|
||||
if i == max_cpus {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let new_cpu = C::from_json_and_limits(cpu, version, i, limits.clone());
|
||||
result.push(new_cpu);
|
||||
}
|
||||
if let Some(max_cpus) = max_cpus {
|
||||
if result.len() != max_cpus {
|
||||
let mut sys_cpus = Cpus::from_limits(limits.clone());
|
||||
for i in result.len()..sys_cpus.cpus.len() {
|
||||
result.push(sys_cpus.cpus.remove(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
Self {
|
||||
cpus: result,
|
||||
smt: smt_guess,
|
||||
smt_capable: can_smt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsMut<Cpu> + AsRef<Cpu> + TCpu + OnResume + OnSet> TCpus for Cpus<C> {
|
||||
fn limits(&self) -> crate::api::CpusLimits {
|
||||
crate::api::CpusLimits {
|
||||
cpus: self.cpus.iter().map(|x| x.as_ref().limits()).collect(),
|
||||
count: self.cpus.len(),
|
||||
smt_capable: self.smt_capable,
|
||||
governors: Vec::with_capacity(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn json(&self) -> Vec<crate::persist::CpuJson> {
|
||||
self.cpus.iter().map(|x| x.as_ref().to_owned().into()).collect()
|
||||
}
|
||||
|
||||
fn cpus(&mut self) -> Vec<&mut dyn TCpu> {
|
||||
self.cpus.iter_mut().map(|x| x as &mut dyn TCpu).collect()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.cpus.len()
|
||||
}
|
||||
|
||||
fn smt(&mut self) -> &'_ mut bool {
|
||||
&mut self.smt
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::Generic
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cpu {
|
||||
pub online: bool,
|
||||
pub governor: String,
|
||||
pub clock_limits: Option<MinMax<u64>>,
|
||||
limits: GenericCpuLimit,
|
||||
index: usize,
|
||||
state: crate::state::steam_deck::Cpu,
|
||||
}
|
||||
|
||||
/*impl Cpu {
|
||||
#[inline]
|
||||
pub fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
}*/
|
||||
|
||||
impl AsRef<Cpu> for Cpu {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &Cpu {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<Cpu> for Cpu {
|
||||
#[inline]
|
||||
fn as_mut(&mut self) -> &mut Cpu {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl FromGenericCpuInfo for Cpu {
|
||||
#[inline]
|
||||
fn from_limits(cpu_index: usize, limits: GenericCpuLimit) -> Self {
|
||||
Self {
|
||||
online: true,
|
||||
governor: "schedutil".to_owned(),
|
||||
clock_limits: None,
|
||||
limits,
|
||||
index: cpu_index,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_json_and_limits(other: CpuJson, version: u64, i: usize, limits: GenericCpuLimit) -> Self {
|
||||
let clock_lims = if limits.clock_min.is_some() && limits.clock_max.is_some() {
|
||||
other.clock_limits.map(|x| min_max_from_json(x, version))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match version {
|
||||
0 => Self {
|
||||
online: other.online,
|
||||
governor: other.governor,
|
||||
clock_limits: clock_lims,
|
||||
limits,
|
||||
index: i,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
},
|
||||
_ => Self {
|
||||
online: other.online,
|
||||
governor: other.governor,
|
||||
clock_limits: clock_lims,
|
||||
limits,
|
||||
index: i,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
fn set_all(&mut self) -> Result<(), SettingError> {
|
||||
// set cpu online/offline
|
||||
if self.index != 0 && self.state.do_set_online { // cpu0 cannot be disabled
|
||||
let online_path = cpu_online_path(self.index);
|
||||
usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", &online_path, e),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
// set governor
|
||||
if self.index == 0 || self.online {
|
||||
let governor_path = cpu_governor_path(self.index);
|
||||
usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&self.governor, &governor_path, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*fn from_sys(cpu_index: usize) -> Self {
|
||||
Self {
|
||||
online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0,
|
||||
governor: usdpl_back::api::files::read_single(cpu_governor_path(cpu_index))
|
||||
.unwrap_or("schedutil".to_owned()),
|
||||
index: cpu_index,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
}
|
||||
}*/
|
||||
|
||||
fn governors(&self) -> Vec<String> {
|
||||
// NOTE: this eats errors
|
||||
let gov_str: String = match usdpl_back::api::files::read_single(cpu_available_governors_path(self.index)) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::warn!("Error getting available CPU governors: {}", e);
|
||||
return vec![];
|
||||
},
|
||||
};
|
||||
gov_str.split(' ').map(|s| s.to_owned()).collect()
|
||||
}
|
||||
|
||||
fn limits(&self) -> crate::api::CpuLimits {
|
||||
crate::api::CpuLimits {
|
||||
clock_min_limits: self.limits.clock_min.clone().map(|x| x.into()),
|
||||
clock_max_limits: self.limits.clock_max.clone().map(|x| x.into()),
|
||||
clock_step: self.limits.clock_step,
|
||||
governors: self.governors(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<CpuJson> for Cpu {
|
||||
#[inline]
|
||||
fn into(self) -> CpuJson {
|
||||
CpuJson {
|
||||
online: self.online,
|
||||
clock_limits: self.clock_limits.map(|x| x.into()),
|
||||
governor: self.governor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Cpu {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
//self.clamp_all();
|
||||
self.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Cpu {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
let mut copy = self.clone();
|
||||
copy.state.is_resuming = true;
|
||||
copy.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl TCpu for Cpu {
|
||||
fn online(&mut self) -> &mut bool {
|
||||
&mut self.online
|
||||
}
|
||||
|
||||
fn governor(&mut self, governor: String) {
|
||||
self.governor = governor;
|
||||
}
|
||||
|
||||
fn get_governor(&self) -> &'_ str {
|
||||
&self.governor
|
||||
}
|
||||
|
||||
fn clock_limits(&mut self, limits: Option<MinMax<u64>>) {
|
||||
if self.limits.clock_min.is_some() && self.limits.clock_max.is_some() {
|
||||
self.clock_limits = limits;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
|
||||
self.clock_limits.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpu_online_path(index: usize) -> String {
|
||||
format!("/sys/devices/system/cpu/cpu{}/online", index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpu_governor_path(index: usize) -> String {
|
||||
format!(
|
||||
"/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor",
|
||||
index
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpu_available_governors_path(index: usize) -> String {
|
||||
format!(
|
||||
"/sys/devices/system/cpu/cpu{}/cpufreq/scaling_available_governors",
|
||||
index
|
||||
)
|
||||
}
|
139
backend/src/settings/generic/gpu.rs
Normal file
139
backend/src/settings/generic/gpu.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use limits_core::json::GenericGpuLimit;
|
||||
|
||||
use crate::settings::{MinMax, min_max_from_json};
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::TGpu;
|
||||
use crate::persist::GpuJson;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Gpu {
|
||||
pub slow_memory: bool,
|
||||
pub fast_ppt: Option<u64>,
|
||||
pub slow_ppt: Option<u64>,
|
||||
pub clock_limits: Option<MinMax<u64>>,
|
||||
limits: GenericGpuLimit,
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
/*#[inline]
|
||||
pub fn from_json(_other: GpuJson, _version: u64) -> Self {
|
||||
Self {
|
||||
slow_memory: false,
|
||||
}
|
||||
}*/
|
||||
|
||||
/*pub fn system_default() -> Self {
|
||||
Self {
|
||||
slow_memory: false,
|
||||
}
|
||||
}*/
|
||||
|
||||
pub fn from_limits(limits: limits_core::json::GenericGpuLimit) -> Self {
|
||||
Self {
|
||||
slow_memory: false,
|
||||
fast_ppt: None,
|
||||
slow_ppt: None,
|
||||
clock_limits: None,
|
||||
limits,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json_and_limits(other: GpuJson, version: u64, limits: limits_core::json::GenericGpuLimit) -> Self {
|
||||
let clock_lims = if limits.clock_min.is_some() && limits.clock_max.is_some() {
|
||||
other.clock_limits.map(|x| min_max_from_json(x, version))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self {
|
||||
slow_memory: false,
|
||||
fast_ppt: if limits.fast_ppt.is_some() {other.fast_ppt} else {None},
|
||||
slow_ppt: if limits.slow_ppt.is_some() {other.slow_ppt} else {None},
|
||||
clock_limits: clock_lims,
|
||||
limits,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<GpuJson> for Gpu {
|
||||
#[inline]
|
||||
fn into(self) -> GpuJson {
|
||||
GpuJson {
|
||||
fast_ppt: self.fast_ppt,
|
||||
slow_ppt: self.slow_ppt,
|
||||
clock_limits: self.clock_limits.map(|x| x.into()),
|
||||
slow_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Gpu {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Gpu {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TGpu for Gpu {
|
||||
fn limits(&self) -> crate::api::GpuLimits {
|
||||
crate::api::GpuLimits {
|
||||
fast_ppt_limits: self.limits.fast_ppt.clone().map(|x| x.into()),
|
||||
slow_ppt_limits: self.limits.slow_ppt.clone().map(|x| x.into()),
|
||||
ppt_step: self.limits.ppt_step.unwrap_or(1_000_000),
|
||||
tdp_limits: self.limits.tdp.clone().map(|x| x.into()),
|
||||
tdp_boost_limits: self.limits.tdp_boost.clone().map(|x| x.into()),
|
||||
tdp_step: self.limits.tdp_step.unwrap_or(42),
|
||||
clock_min_limits: self.limits.clock_min.clone().map(|x| x.into()),
|
||||
clock_max_limits: self.limits.clock_max.clone().map(|x| x.into()),
|
||||
clock_step: self.limits.clock_step.unwrap_or(100),
|
||||
memory_control_capable: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn json(&self) -> crate::persist::GpuJson {
|
||||
self.clone().into()
|
||||
}
|
||||
|
||||
fn ppt(&mut self, fast: Option<u64>, slow: Option<u64>) {
|
||||
if let Some(fast_lims) = &self.limits.fast_ppt {
|
||||
self.fast_ppt = fast.map(|x| x.clamp(fast_lims.min, fast_lims.max));
|
||||
}
|
||||
if let Some(slow_lims) = &self.limits.slow_ppt {
|
||||
self.slow_ppt = slow.map(|x| x.clamp(slow_lims.min, slow_lims.max));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ppt(&self) -> (Option<u64>, Option<u64>) {
|
||||
(self.fast_ppt, self.slow_ppt)
|
||||
}
|
||||
|
||||
fn clock_limits(&mut self, limits: Option<MinMax<u64>>) {
|
||||
if let Some(clock_min) = &self.limits.clock_min {
|
||||
if let Some(clock_max) = &self.limits.clock_max {
|
||||
self.clock_limits = limits.map(|mut x| {
|
||||
x.min = x.min.clamp(clock_min.min, clock_min.max);
|
||||
x.max = x.max.clamp(clock_max.max, clock_max.max);
|
||||
x
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
|
||||
self.clock_limits.as_ref()
|
||||
}
|
||||
|
||||
fn slow_memory(&mut self) -> &mut bool {
|
||||
&mut self.slow_memory
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::Generic
|
||||
}
|
||||
}
|
9
backend/src/settings/generic/mod.rs
Normal file
9
backend/src/settings/generic/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
mod battery;
|
||||
mod cpu;
|
||||
mod gpu;
|
||||
mod traits;
|
||||
|
||||
pub use battery::Battery;
|
||||
pub use cpu::{Cpu, Cpus};
|
||||
pub use gpu::Gpu;
|
||||
pub use traits::FromGenericCpuInfo;
|
8
backend/src/settings/generic/traits.rs
Normal file
8
backend/src/settings/generic/traits.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use limits_core::json::GenericCpuLimit;
|
||||
use crate::persist::CpuJson;
|
||||
|
||||
pub trait FromGenericCpuInfo {
|
||||
fn from_limits(cpu_index: usize, limits: GenericCpuLimit) -> Self;
|
||||
|
||||
fn from_json_and_limits(other: CpuJson, version: u64, cpu_index: usize, limits: GenericCpuLimit) -> Self;
|
||||
}
|
134
backend/src/settings/generic_amd/cpu.rs
Normal file
134
backend/src/settings/generic_amd/cpu.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use crate::persist::CpuJson;
|
||||
use crate::settings::MinMax;
|
||||
use crate::settings::generic::{Cpu as GenericCpu, Cpus as GenericCpus, FromGenericCpuInfo};
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::{TCpus, TCpu};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Cpus {
|
||||
generic: GenericCpus<Cpu>,
|
||||
}
|
||||
|
||||
impl Cpus {
|
||||
pub fn from_limits(limits: limits_core::json::GenericCpuLimit) -> Self {
|
||||
Self {
|
||||
generic: GenericCpus::from_limits(limits),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json_and_limits(other: Vec<CpuJson>, version: u64, limits: limits_core::json::GenericCpuLimit) -> Self {
|
||||
Self {
|
||||
generic: GenericCpus::from_json_and_limits(other, version, limits),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Cpus {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
self.generic.on_resume()
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Cpus {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
self.generic.on_set()
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
impl TCpus for Cpus {
|
||||
fn limits(&self) -> crate::api::CpusLimits {
|
||||
self.generic.limits()
|
||||
}
|
||||
|
||||
fn json(&self) -> Vec<crate::persist::CpuJson> {
|
||||
self.generic.json() // TODO
|
||||
}
|
||||
|
||||
fn cpus(&mut self) -> Vec<&mut dyn TCpu> {
|
||||
self.generic.cpus() // TODO
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.generic.len() // TODO
|
||||
}
|
||||
|
||||
fn smt(&mut self) -> &'_ mut bool {
|
||||
self.generic.smt()
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::GenericAMD
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Cpu {
|
||||
generic: GenericCpu,
|
||||
}
|
||||
|
||||
impl FromGenericCpuInfo for Cpu {
|
||||
fn from_limits(cpu_index: usize, limits: limits_core::json::GenericCpuLimit) -> Self {
|
||||
let gen = GenericCpu::from_limits(cpu_index, limits.clone());
|
||||
Self {
|
||||
generic: gen,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_json_and_limits(other: CpuJson, version: u64, cpu_index: usize, limits: limits_core::json::GenericCpuLimit) -> Self {
|
||||
let gen = GenericCpu::from_json_and_limits(other, version, cpu_index, limits);
|
||||
Self {
|
||||
generic: gen,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<GenericCpu> for Cpu {
|
||||
fn as_ref(&self) -> &GenericCpu {
|
||||
&self.generic
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<GenericCpu> for Cpu {
|
||||
fn as_mut(&mut self) -> &mut GenericCpu {
|
||||
&mut self.generic
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Cpu {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
self.generic.on_resume()
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Cpu {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
self.generic.on_set()
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
impl TCpu for Cpu {
|
||||
fn online(&mut self) -> &mut bool {
|
||||
self.generic.online()
|
||||
}
|
||||
|
||||
fn governor(&mut self, governor: String) {
|
||||
self.generic.governor(governor)
|
||||
}
|
||||
|
||||
fn get_governor(&self) -> &'_ str {
|
||||
self.generic.get_governor()
|
||||
}
|
||||
|
||||
fn clock_limits(&mut self, _limits: Option<MinMax<u64>>) {
|
||||
//self.generic.clock_limits(limits)
|
||||
// TODO: support this
|
||||
}
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
|
||||
self.generic.get_clock_limits()
|
||||
}
|
||||
}
|
215
backend/src/settings/generic_amd/gpu.rs
Normal file
215
backend/src/settings/generic_amd/gpu.rs
Normal file
|
@ -0,0 +1,215 @@
|
|||
use std::sync::Mutex;
|
||||
use ryzenadj_rs::RyzenAccess;
|
||||
|
||||
use crate::persist::GpuJson;
|
||||
use crate::settings::MinMax;
|
||||
use crate::settings::generic::Gpu as GenericGpu;
|
||||
use crate::settings::{OnResume, OnSet, SettingError, SettingVariant};
|
||||
use crate::settings::TGpu;
|
||||
|
||||
fn ryzen_adj_or_log() -> Option<Mutex<RyzenAccess>> {
|
||||
match RyzenAccess::new() {
|
||||
Ok(x) => Some(Mutex::new(x)),
|
||||
Err(e) => {
|
||||
log::error!("RyzenAdj init error: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Gpu {
|
||||
generic: GenericGpu,
|
||||
implementor: Option<Mutex<RyzenAccess>>,
|
||||
state: crate::state::generic::Gpu, // NOTE this is re-used for simplicity
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
pub fn from_limits(limits: limits_core::json::GenericGpuLimit) -> Self {
|
||||
Self {
|
||||
generic: GenericGpu::from_limits(limits),
|
||||
implementor: ryzen_adj_or_log(),
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json_and_limits(other: GpuJson, version: u64, limits: limits_core::json::GenericGpuLimit) -> Self {
|
||||
Self {
|
||||
generic: GenericGpu::from_json_and_limits(other, version, limits),
|
||||
implementor: ryzen_adj_or_log(),
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_all(&mut self) -> Result<(), SettingError> {
|
||||
let mutex = match &self.implementor {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(SettingError {
|
||||
msg: "RyzenAdj unavailable".to_owned(),
|
||||
setting: SettingVariant::Gpu,
|
||||
});
|
||||
}
|
||||
};
|
||||
let lock = match mutex.lock() {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return Err(SettingError {
|
||||
msg: format!("RyzenAdj lock acquire failed: {}", e),
|
||||
setting: SettingVariant::Gpu,
|
||||
});
|
||||
}
|
||||
};
|
||||
if let Some(fast_ppt) = &self.generic.fast_ppt {
|
||||
if self.state.old_fast_ppt.is_none() {
|
||||
self.state.old_fast_ppt = Some(lock.get_fast_value() as _);
|
||||
}
|
||||
lock.set_fast_limit(*fast_ppt as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_fast_limit({}) err: {}", *fast_ppt, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
} else if let Some(fast_ppt) = &self.state.old_fast_ppt {
|
||||
lock.set_fast_limit(*fast_ppt as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_fast_limit({}) err: {}", *fast_ppt, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
self.state.old_fast_ppt = None;
|
||||
}
|
||||
if let Some(slow_ppt) = &self.generic.slow_ppt {
|
||||
if self.state.old_slow_ppt.is_none() {
|
||||
self.state.old_slow_ppt = Some(lock.get_slow_value() as _);
|
||||
}
|
||||
lock.set_slow_limit(*slow_ppt as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_slow_limit({}) err: {}", *slow_ppt, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
} else if let Some(slow_ppt) = &self.state.old_slow_ppt {
|
||||
lock.set_slow_limit(*slow_ppt as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_slow_limit({}) err: {}", *slow_ppt, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
self.state.old_slow_ppt = None;
|
||||
}
|
||||
if let Some(clock_limits) = &self.generic.clock_limits {
|
||||
self.state.clock_limits_set = true;
|
||||
lock.set_max_gfxclk_freq(clock_limits.max as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_max_gfxclk_freq({}) err: {}", clock_limits.max, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
lock.set_min_gfxclk_freq(clock_limits.min as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_min_gfxclk_freq({}) err: {}", clock_limits.min, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
} else if self.state.clock_limits_set {
|
||||
self.state.clock_limits_set = false;
|
||||
let limits = self.generic.limits();
|
||||
if let Some(min_limits) = limits.clock_min_limits {
|
||||
if let Some(max_limits) = limits.clock_max_limits {
|
||||
lock.set_max_gfxclk_freq(max_limits.max as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_max_gfxclk_freq({}) err: {}", max_limits.max, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
lock.set_min_gfxclk_freq(min_limits.min as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_min_gfxclk_freq({}) err: {}", min_limits.min, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resume_all(&self) -> Result<(), SettingError> {
|
||||
// like set_all() but without updating state
|
||||
// -- assumption: state is already up to date
|
||||
let mutex = match &self.implementor {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(SettingError {
|
||||
msg: "RyzenAdj unavailable".to_owned(),
|
||||
setting: SettingVariant::Gpu,
|
||||
});
|
||||
}
|
||||
};
|
||||
let lock = match mutex.lock() {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return Err(SettingError {
|
||||
msg: format!("RyzenAdj lock acquire failed: {}", e),
|
||||
setting: SettingVariant::Gpu,
|
||||
});
|
||||
}
|
||||
};
|
||||
if let Some(fast_ppt) = &self.generic.fast_ppt {
|
||||
lock.set_fast_limit(*fast_ppt as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_fast_limit({}) err: {}", *fast_ppt, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
}
|
||||
if let Some(slow_ppt) = &self.generic.slow_ppt {
|
||||
lock.set_slow_limit(*slow_ppt as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_slow_limit({}) err: {}", *slow_ppt, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
}
|
||||
if let Some(clock_limits) = &self.generic.clock_limits {
|
||||
lock.set_max_gfxclk_freq(clock_limits.max as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_max_gfxclk_freq({}) err: {}", clock_limits.max, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
lock.set_min_gfxclk_freq(clock_limits.min as _).map_err(|e| SettingError {
|
||||
msg: format!("RyzenAdj set_min_gfxclk_freq({}) err: {}", clock_limits.min, e),
|
||||
setting: SettingVariant::Gpu,
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Gpu {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
self.generic.on_resume()?;
|
||||
self.resume_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Gpu {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
self.generic.on_set()?;
|
||||
self.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl TGpu for Gpu {
|
||||
fn limits(&self) -> crate::api::GpuLimits {
|
||||
self.generic.limits()
|
||||
}
|
||||
|
||||
fn json(&self) -> crate::persist::GpuJson {
|
||||
self.generic.json()
|
||||
}
|
||||
|
||||
fn ppt(&mut self, fast: Option<u64>, slow: Option<u64>) {
|
||||
self.generic.ppt(fast, slow)
|
||||
}
|
||||
|
||||
fn get_ppt(&self) -> (Option<u64>, Option<u64>) {
|
||||
self.generic.get_ppt()
|
||||
}
|
||||
|
||||
fn clock_limits(&mut self, limits: Option<MinMax<u64>>) {
|
||||
self.generic.clock_limits(limits)
|
||||
}
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
|
||||
self.generic.get_clock_limits()
|
||||
}
|
||||
|
||||
fn slow_memory(&mut self) -> &mut bool {
|
||||
self.generic.slow_memory()
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::GenericAMD
|
||||
}
|
||||
}
|
5
backend/src/settings/generic_amd/mod.rs
Normal file
5
backend/src/settings/generic_amd/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod cpu;
|
||||
mod gpu;
|
||||
|
||||
pub use cpu::{Cpu, Cpus};
|
||||
pub use gpu::Gpu;
|
|
@ -1,24 +1,19 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use limits_core::json::RangeLimit;
|
||||
|
||||
use crate::persist::MinMaxJson;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MinMax<T> {
|
||||
pub max: T,
|
||||
pub min: T,
|
||||
}
|
||||
pub type MinMax<T> = RangeLimit<T>;
|
||||
|
||||
impl<T> MinMax<T> {
|
||||
#[inline]
|
||||
pub fn from_json<X: Into<T>>(other: MinMaxJson<X>, _version: u64) -> Self {
|
||||
Self {
|
||||
max: other.max.into(),
|
||||
min: other.min.into(),
|
||||
}
|
||||
pub fn min_max_from_json<T, X: Into<T>>(other: MinMaxJson<X>, _version: u64) -> MinMax<T> {
|
||||
MinMax {
|
||||
max: other.max.into(),
|
||||
min: other.min.into(),
|
||||
}
|
||||
}
|
||||
|
||||
impl<X: Into<Y>, Y> Into<MinMaxJson<Y>> for MinMax<X> {
|
||||
impl<X: Into<Y>, Y> Into<MinMaxJson<Y>> for RangeLimit<X> {
|
||||
#[inline]
|
||||
fn into(self) -> MinMaxJson<Y> {
|
||||
MinMaxJson {
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
mod battery;
|
||||
mod cpu;
|
||||
mod detect;
|
||||
pub mod driver;
|
||||
mod error;
|
||||
mod general;
|
||||
mod gpu;
|
||||
mod min_max;
|
||||
mod traits;
|
||||
mod util;
|
||||
|
||||
pub use battery::Battery;
|
||||
pub use cpu::Cpu;
|
||||
pub mod generic;
|
||||
pub mod generic_amd;
|
||||
pub mod steam_deck;
|
||||
pub mod unknown;
|
||||
|
||||
pub use detect::{auto_detect0, auto_detect_provider, limits_worker::spawn as limits_worker_spawn};
|
||||
pub use driver::Driver;
|
||||
pub use general::{SettingVariant, Settings, General};
|
||||
pub use gpu::Gpu;
|
||||
pub use min_max::MinMax;
|
||||
pub use min_max::{MinMax, min_max_from_json};
|
||||
|
||||
pub use error::SettingError;
|
||||
pub use traits::{OnResume, OnSet, SettingsRange};
|
||||
pub use traits::{OnResume, OnSet, SettingsRange, TGeneral, TGpu, TCpus, TBattery, TCpu};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
271
backend/src/settings/steam_deck/battery.rs
Normal file
271
backend/src/settings/steam_deck/battery.rs
Normal file
|
@ -0,0 +1,271 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use crate::api::RangeLimit;
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::TBattery;
|
||||
use crate::persist::BatteryJson;
|
||||
use super::util::ChargeMode;
|
||||
use super::oc_limits::{BatteryLimits, OverclockLimits};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Battery {
|
||||
pub charge_rate: Option<u64>,
|
||||
pub charge_mode: Option<ChargeMode>,
|
||||
limits: BatteryLimits,
|
||||
state: crate::state::steam_deck::Battery,
|
||||
driver_mode: crate::persist::DriverJson,
|
||||
}
|
||||
|
||||
const BATTERY_VOLTAGE: f64 = 7.7;
|
||||
|
||||
const BATTERY_CHARGE_RATE_PATH: &str = "/sys/class/hwmon/hwmon5/maximum_battery_charge_rate"; // write-only
|
||||
const BATTERY_CURRENT_NOW_PATH: &str = "/sys/class/power_supply/BAT1/current_now"; // read-only
|
||||
const BATTERY_CHARGE_NOW_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_now"; // read-only
|
||||
const BATTERY_CHARGE_FULL_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full"; // read-only
|
||||
const BATTERY_CHARGE_DESIGN_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full_design"; // read-only
|
||||
|
||||
impl Battery {
|
||||
#[inline]
|
||||
pub fn from_json(other: BatteryJson, version: u64) -> Self {
|
||||
let (oc_limits, is_default) = OverclockLimits::load_or_default();
|
||||
let oc_limits = oc_limits.battery;
|
||||
let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance };
|
||||
match version {
|
||||
0 => Self {
|
||||
charge_rate: other.charge_rate,
|
||||
charge_mode: other.charge_mode.map(|x| Self::str_to_charge_mode(&x)).flatten(),
|
||||
limits: oc_limits,
|
||||
state: crate::state::steam_deck::Battery::default(),
|
||||
driver_mode: driver,
|
||||
},
|
||||
_ => Self {
|
||||
charge_rate: other.charge_rate,
|
||||
charge_mode: other.charge_mode.map(|x| Self::str_to_charge_mode(&x)).flatten(),
|
||||
limits: oc_limits,
|
||||
state: crate::state::steam_deck::Battery::default(),
|
||||
driver_mode: driver,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn charge_mode_to_str(mode: ChargeMode) -> String {
|
||||
match mode {
|
||||
ChargeMode::Normal => "normal",
|
||||
ChargeMode::Idle => "idle",
|
||||
ChargeMode::Discharge => "discharge",
|
||||
}.to_owned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn str_to_charge_mode(s: &str) -> Option<ChargeMode> {
|
||||
match s {
|
||||
"normal" => Some(ChargeMode::Normal),
|
||||
"idle" => Some(ChargeMode::Idle),
|
||||
"discharge" | "disacharge" => Some(ChargeMode::Discharge),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_all(&mut self) -> Result<(), SettingError> {
|
||||
if let Some(charge_rate) = self.charge_rate {
|
||||
self.state.charge_rate_set = true;
|
||||
usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
},
|
||||
)?;
|
||||
} else if self.state.charge_rate_set {
|
||||
self.state.charge_rate_set = false;
|
||||
usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, self.limits.charge_rate.max).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
if let Some(charge_mode) = self.charge_mode {
|
||||
self.state.charge_mode_set = true;
|
||||
super::util::set(super::util::Setting::ChargeMode, charge_mode as _).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!("Failed to set charge mode: {}", e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
},
|
||||
)?;
|
||||
} else if self.state.charge_mode_set {
|
||||
self.state.charge_mode_set = false;
|
||||
super::util::set(super::util::Setting::ChargeMode, ChargeMode::Normal as _).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!("Failed to set charge mode: {}", e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clamp_all(&mut self) {
|
||||
if let Some(charge_rate) = &mut self.charge_rate {
|
||||
*charge_rate = (*charge_rate).clamp(self.limits.charge_rate.min, self.limits.charge_rate.max);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_current_now() -> Result<u64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) {
|
||||
Err(e) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
}),
|
||||
// this value is in uA, while it's set in mA
|
||||
// so convert this to mA for consistency
|
||||
Ok(val) => Ok(val / 1000),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_charge_now() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) {
|
||||
Err(e) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
}),
|
||||
// convert to Wh
|
||||
Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_charge_full() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) {
|
||||
Err(e) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
}),
|
||||
// convert to Wh
|
||||
Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_charge_design() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) {
|
||||
Err(e) => Err(SettingError {
|
||||
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
}),
|
||||
// convert to Wh
|
||||
Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_default() -> Self {
|
||||
let (oc_limits, is_default) = OverclockLimits::load_or_default();
|
||||
let oc_limits = oc_limits.battery;
|
||||
let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance };
|
||||
Self {
|
||||
charge_rate: None,
|
||||
charge_mode: None,
|
||||
limits: oc_limits,
|
||||
state: crate::state::steam_deck::Battery::default(),
|
||||
driver_mode: driver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<BatteryJson> for Battery {
|
||||
#[inline]
|
||||
fn into(self) -> BatteryJson {
|
||||
BatteryJson {
|
||||
charge_rate: self.charge_rate,
|
||||
charge_mode: self.charge_mode.map(Self::charge_mode_to_str),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Battery {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
self.clamp_all();
|
||||
self.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Battery {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
self.clone().set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl TBattery for Battery {
|
||||
fn limits(&self) -> crate::api::BatteryLimits {
|
||||
crate::api::BatteryLimits {
|
||||
charge_current: Some(RangeLimit{
|
||||
min: self.limits.charge_rate.min,
|
||||
max: self.limits.charge_rate.max
|
||||
}),
|
||||
charge_current_step: 50,
|
||||
charge_modes: vec!["normal".to_owned(), "discharge".to_owned(), "idle".to_owned()],
|
||||
}
|
||||
}
|
||||
|
||||
fn json(&self) -> crate::persist::BatteryJson {
|
||||
self.clone().into()
|
||||
}
|
||||
|
||||
fn charge_rate(&mut self, rate: Option<u64>) {
|
||||
self.charge_rate = rate;
|
||||
}
|
||||
|
||||
fn get_charge_rate(&self) -> Option<u64> {
|
||||
self.charge_rate
|
||||
}
|
||||
|
||||
fn charge_mode(&mut self, mode: Option<String>) {
|
||||
self.charge_mode = mode.map(|s| Self::str_to_charge_mode(&s)).flatten()
|
||||
}
|
||||
|
||||
fn get_charge_mode(&self) -> Option<String> {
|
||||
self.charge_mode.map(Self::charge_mode_to_str)
|
||||
}
|
||||
|
||||
fn read_charge_full(&self) -> Option<f64> {
|
||||
match Self::read_charge_full() {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::warn!("read_charge_full err: {}", e.msg);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_charge_now(&self) -> Option<f64> {
|
||||
match Self::read_charge_now() {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::warn!("read_charge_now err: {}", e.msg);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_charge_design(&self) -> Option<f64> {
|
||||
match Self::read_charge_design() {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::warn!("read_charge_design err: {}", e.msg);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_current_now(&self) -> Option<f64> {
|
||||
match Self::read_current_now() {
|
||||
Ok(x) => Some(x as f64),
|
||||
Err(e) => {
|
||||
log::warn!("read_current_now err: {}", e.msg);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
self.driver_mode.clone()
|
||||
}
|
||||
}
|
452
backend/src/settings/steam_deck/cpu.rs
Normal file
452
backend/src/settings/steam_deck/cpu.rs
Normal file
|
@ -0,0 +1,452 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use crate::api::RangeLimit;
|
||||
use crate::settings::{MinMax, min_max_from_json};
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::{TCpus, TCpu};
|
||||
use crate::persist::CpuJson;
|
||||
use super::oc_limits::{OverclockLimits, CpusLimits, CpuLimits};
|
||||
|
||||
const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present";
|
||||
const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cpus {
|
||||
pub cpus: Vec<Cpu>,
|
||||
pub smt: bool,
|
||||
pub smt_capable: bool,
|
||||
pub(super) limits: CpusLimits,
|
||||
driver_mode: crate::persist::DriverJson,
|
||||
}
|
||||
|
||||
impl OnSet for Cpus {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
if self.smt_capable {
|
||||
// toggle SMT
|
||||
if self.smt {
|
||||
usdpl_back::api::files::write_single(CPU_SMT_PATH, "on").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `on` to `{}`: {}",
|
||||
CPU_SMT_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
} else {
|
||||
usdpl_back::api::files::write_single(CPU_SMT_PATH, "off").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `off` to `{}`: {}",
|
||||
CPU_SMT_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
}
|
||||
for (i, cpu) in self.cpus.as_mut_slice().iter_mut().enumerate() {
|
||||
cpu.state.do_set_online = self.smt || i % 2 == 0;
|
||||
cpu.on_set()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Cpus {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
for cpu in &self.cpus {
|
||||
cpu.on_resume()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Cpus {
|
||||
pub fn cpu_count() -> Option<usize> {
|
||||
let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH)
|
||||
.unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */);
|
||||
if let Some(dash_index) = data.find('-') {
|
||||
let data = data.split_off(dash_index + 1);
|
||||
if let Ok(max_cpu) = data.parse::<usize>() {
|
||||
return Some(max_cpu + 1);
|
||||
}
|
||||
}
|
||||
log::warn!("Failed to parse CPU info from kernel, is Tux evil?");
|
||||
None
|
||||
}
|
||||
|
||||
fn system_smt_capabilities() -> (bool, bool) {
|
||||
match usdpl_back::api::files::read_single::<_, String, _>(CPU_SMT_PATH) {
|
||||
Ok(val) => (val.trim().to_lowercase() == "on", true),
|
||||
Err(_) => (false, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_default() -> Self {
|
||||
let (oc_limits, is_default) = OverclockLimits::load_or_default();
|
||||
let oc_limits = oc_limits.cpus;
|
||||
let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance };
|
||||
if let Some(max_cpu) = Self::cpu_count() {
|
||||
let mut sys_cpus = Vec::with_capacity(max_cpu);
|
||||
for i in 0..max_cpu {
|
||||
sys_cpus.push(Cpu::system_default(i, oc_limits.cpus.get(i).map(|x| x.to_owned()).unwrap_or_default()));
|
||||
}
|
||||
let (_, can_smt) = Self::system_smt_capabilities();
|
||||
Self {
|
||||
cpus: sys_cpus,
|
||||
smt: true,
|
||||
smt_capable: can_smt,
|
||||
limits: oc_limits,
|
||||
driver_mode: driver,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
cpus: vec![],
|
||||
smt: false,
|
||||
smt_capable: false,
|
||||
limits: oc_limits,
|
||||
driver_mode: driver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_json(mut other: Vec<CpuJson>, version: u64) -> Self {
|
||||
let (oc_limits, is_default) = OverclockLimits::load_or_default();
|
||||
let oc_limits = oc_limits.cpus;
|
||||
let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance };
|
||||
let (_, can_smt) = Self::system_smt_capabilities();
|
||||
let mut result = Vec::with_capacity(other.len());
|
||||
let max_cpus = Self::cpu_count();
|
||||
let smt_guess = crate::settings::util::guess_smt(&other) && can_smt;
|
||||
for (i, cpu) in other.drain(..).enumerate() {
|
||||
// prevent having more CPUs than available
|
||||
if let Some(max_cpus) = max_cpus {
|
||||
if i == max_cpus {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let new_cpu = Cpu::from_json(cpu, version, i, oc_limits.cpus.get(i).map(|x| x.to_owned()).unwrap_or_default());
|
||||
result.push(new_cpu);
|
||||
}
|
||||
if let Some(max_cpus) = max_cpus {
|
||||
if result.len() != max_cpus {
|
||||
let mut sys_cpus = Cpus::system_default();
|
||||
for i in result.len()..sys_cpus.cpus.len() {
|
||||
result.push(sys_cpus.cpus.remove(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
Self {
|
||||
cpus: result,
|
||||
smt: smt_guess,
|
||||
smt_capable: can_smt,
|
||||
limits: oc_limits,
|
||||
driver_mode: driver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TCpus for Cpus {
|
||||
fn limits(&self) -> crate::api::CpusLimits {
|
||||
crate::api::CpusLimits {
|
||||
cpus: self.cpus.iter().map(|x| x.limits()).collect(),
|
||||
count: self.cpus.len(),
|
||||
smt_capable: self.smt_capable,
|
||||
governors: if self.limits.global_governors {
|
||||
self.cpus.iter()
|
||||
.next()
|
||||
.map(|x| x.governors())
|
||||
.unwrap_or_else(|| Vec::with_capacity(0))
|
||||
} else { Vec::with_capacity(0) },
|
||||
}
|
||||
}
|
||||
|
||||
fn json(&self) -> Vec<crate::persist::CpuJson> {
|
||||
self.cpus.iter().map(|x| x.to_owned().into()).collect()
|
||||
}
|
||||
|
||||
fn cpus(&mut self) -> Vec<&mut dyn TCpu> {
|
||||
self.cpus.iter_mut().map(|x| x as &mut dyn TCpu).collect()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.cpus.len()
|
||||
}
|
||||
|
||||
fn smt(&mut self) -> &'_ mut bool {
|
||||
&mut self.smt
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
self.driver_mode.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cpu {
|
||||
pub online: bool,
|
||||
pub clock_limits: Option<MinMax<u64>>,
|
||||
pub governor: String,
|
||||
limits: CpuLimits,
|
||||
index: usize,
|
||||
state: crate::state::steam_deck::Cpu,
|
||||
}
|
||||
|
||||
const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage";
|
||||
const CPU_FORCE_LIMITS_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level";
|
||||
|
||||
impl Cpu {
|
||||
#[inline]
|
||||
fn from_json(other: CpuJson, version: u64, i: usize, oc_limits: CpuLimits) -> Self {
|
||||
match version {
|
||||
0 => Self {
|
||||
online: other.online,
|
||||
clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)),
|
||||
governor: other.governor,
|
||||
limits: oc_limits,
|
||||
index: i,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
},
|
||||
_ => Self {
|
||||
online: other.online,
|
||||
clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)),
|
||||
governor: other.governor,
|
||||
limits: oc_limits,
|
||||
index: i,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_all(&mut self) -> Result<(), SettingError> {
|
||||
// set cpu online/offline
|
||||
if self.index != 0 && self.state.do_set_online { // cpu0 cannot be disabled
|
||||
let online_path = cpu_online_path(self.index);
|
||||
usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", &online_path, e),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
// set clock limits
|
||||
log::debug!("Setting {} to manual", CPU_FORCE_LIMITS_PATH);
|
||||
let mode: String = usdpl_back::api::files::read_single(CPU_FORCE_LIMITS_PATH.to_owned()).unwrap();
|
||||
if mode != "manual" {
|
||||
// set manual control
|
||||
usdpl_back::api::files::write_single(CPU_FORCE_LIMITS_PATH, "manual").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `manual` to `{}`: {}",
|
||||
CPU_FORCE_LIMITS_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
if let Some(clock_limits) = &self.clock_limits {
|
||||
log::debug!("Setting CPU {} (min, max) clockspeed to ({}, {})", self.index, clock_limits.min, clock_limits.max);
|
||||
self.state.clock_limits_set = true;
|
||||
// max clock
|
||||
let payload_max = format!("p {} 1 {}\n", self.index / 2, clock_limits.max);
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_max, CPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
},
|
||||
)?;
|
||||
// min clock
|
||||
let valid_min = if clock_limits.min < self.limits.clock_min.min {self.limits.clock_min.min} else {clock_limits.min};
|
||||
let payload_min = format!("p {} 0 {}\n", self.index / 2, valid_min);
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_min, CPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
},
|
||||
)?;
|
||||
} else if self.state.clock_limits_set || self.state.is_resuming {
|
||||
self.state.clock_limits_set = false;
|
||||
// disable manual clock limits
|
||||
log::debug!("Setting CPU {} to default clockspeed", self.index);
|
||||
// max clock
|
||||
let payload_max = format!("p {} 1 {}\n", self.index / 2, self.limits.clock_max.max);
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_max, CPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
},
|
||||
)?;
|
||||
// min clock
|
||||
let payload_min = format!("p {} 0 {}\n", self.index / 2, self.limits.clock_min.min);
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_min, CPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
// commit changes
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
|
||||
// set governor
|
||||
if self.index == 0 || self.online {
|
||||
let governor_path = cpu_governor_path(self.index);
|
||||
usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&self.governor, &governor_path, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clamp_all(&mut self) {
|
||||
if let Some(clock_limits) = &mut self.clock_limits {
|
||||
clock_limits.min = clock_limits.min.clamp(self.limits.clock_min.min, self.limits.clock_min.max);
|
||||
clock_limits.max = clock_limits.max.clamp(self.limits.clock_max.min, self.limits.clock_max.max);
|
||||
}
|
||||
}
|
||||
|
||||
/*fn from_sys(cpu_index: usize, oc_limits: CpuLimits) -> Self {
|
||||
Self {
|
||||
online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0,
|
||||
clock_limits: None,
|
||||
governor: usdpl_back::api::files::read_single(cpu_governor_path(cpu_index))
|
||||
.unwrap_or("schedutil".to_owned()),
|
||||
limits: oc_limits,
|
||||
index: cpu_index,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
}
|
||||
}*/
|
||||
|
||||
fn system_default(cpu_index: usize, oc_limits: CpuLimits) -> Self {
|
||||
Self {
|
||||
online: true,
|
||||
clock_limits: None,
|
||||
governor: "schedutil".to_owned(),
|
||||
limits: oc_limits,
|
||||
index: cpu_index,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn limits(&self) -> crate::api::CpuLimits {
|
||||
crate::api::CpuLimits {
|
||||
clock_min_limits: Some(RangeLimit {
|
||||
min: self.limits.clock_max.min, // allows min to be set by max (it's weird, blame the kernel)
|
||||
max: self.limits.clock_min.max
|
||||
}),
|
||||
clock_max_limits: Some(RangeLimit {
|
||||
min: self.limits.clock_max.min,
|
||||
max: self.limits.clock_max.max
|
||||
}),
|
||||
clock_step: self.limits.clock_step,
|
||||
governors: self.governors(),
|
||||
}
|
||||
}
|
||||
|
||||
fn governors(&self) -> Vec<String> {
|
||||
// NOTE: this eats errors
|
||||
let gov_str: String = match usdpl_back::api::files::read_single(cpu_available_governors_path(self.index)) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::warn!("Error getting available CPU governors: {}", e);
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
gov_str.split(' ').map(|s| s.to_owned()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<CpuJson> for Cpu {
|
||||
#[inline]
|
||||
fn into(self) -> CpuJson {
|
||||
CpuJson {
|
||||
online: self.online,
|
||||
clock_limits: self.clock_limits.map(|x| x.into()),
|
||||
governor: self.governor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Cpu {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
self.clamp_all();
|
||||
self.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Cpu {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
let mut copy = self.clone();
|
||||
copy.state.is_resuming = true;
|
||||
copy.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl TCpu for Cpu {
|
||||
fn online(&mut self) -> &mut bool {
|
||||
&mut self.online
|
||||
}
|
||||
|
||||
fn governor(&mut self, governor: String) {
|
||||
self.governor = governor;
|
||||
}
|
||||
|
||||
fn get_governor(&self) -> &'_ str {
|
||||
&self.governor
|
||||
}
|
||||
|
||||
fn clock_limits(&mut self, limits: Option<MinMax<u64>>) {
|
||||
self.clock_limits = limits;
|
||||
}
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
|
||||
self.clock_limits.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpu_online_path(index: usize) -> String {
|
||||
format!("/sys/devices/system/cpu/cpu{}/online", index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpu_governor_path(index: usize) -> String {
|
||||
format!(
|
||||
"/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor",
|
||||
index
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
fn cpu_available_governors_path(index: usize) -> String {
|
||||
format!(
|
||||
"/sys/devices/system/cpu/cpu{}/cpufreq/scaling_available_governors",
|
||||
index
|
||||
)
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use super::MinMax;
|
||||
use super::{OnResume, OnSet, SettingError, SettingsRange};
|
||||
use crate::api::RangeLimit;
|
||||
use crate::settings::{MinMax, min_max_from_json};
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::TGpu;
|
||||
use crate::persist::GpuJson;
|
||||
use super::oc_limits::{OverclockLimits, GpuLimits};
|
||||
|
||||
const SLOW_PPT: u8 = 1;
|
||||
const FAST_PPT: u8 = 2;
|
||||
|
@ -13,7 +16,9 @@ pub struct Gpu {
|
|||
pub slow_ppt: Option<u64>,
|
||||
pub clock_limits: Option<MinMax<u64>>,
|
||||
pub slow_memory: bool,
|
||||
state: crate::state::Gpu,
|
||||
limits: GpuLimits,
|
||||
state: crate::state::steam_deck::Gpu,
|
||||
driver_mode: crate::persist::DriverJson,
|
||||
}
|
||||
|
||||
// same as CPU
|
||||
|
@ -24,20 +29,26 @@ const GPU_MEMORY_DOWNCLOCK_PATH: &str = "/sys/class/drm/card0/device/pp_dpm_fclk
|
|||
impl Gpu {
|
||||
#[inline]
|
||||
pub fn from_json(other: GpuJson, version: u64) -> Self {
|
||||
let (oc_limits, is_default) = OverclockLimits::load_or_default();
|
||||
let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance };
|
||||
match version {
|
||||
0 => Self {
|
||||
fast_ppt: other.fast_ppt,
|
||||
slow_ppt: other.slow_ppt,
|
||||
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
|
||||
clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)),
|
||||
slow_memory: other.slow_memory,
|
||||
state: crate::state::Gpu::default(),
|
||||
limits: oc_limits.gpu,
|
||||
state: crate::state::steam_deck::Gpu::default(),
|
||||
driver_mode: driver,
|
||||
},
|
||||
_ => Self {
|
||||
fast_ppt: other.fast_ppt,
|
||||
slow_ppt: other.slow_ppt,
|
||||
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
|
||||
clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)),
|
||||
slow_memory: other.slow_memory,
|
||||
state: crate::state::Gpu::default(),
|
||||
limits: oc_limits.gpu,
|
||||
state: crate::state::steam_deck::Gpu::default(),
|
||||
driver_mode: driver,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +63,7 @@ impl Gpu {
|
|||
"Failed to write `{}` to `{}`: {}",
|
||||
fast_ppt, &fast_ppt_path, e
|
||||
),
|
||||
setting: super::SettingVariant::Gpu,
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
@ -65,7 +76,7 @@ impl Gpu {
|
|||
"Failed to write `{}` to `{}`: {}",
|
||||
slow_ppt, &slow_ppt_path, e
|
||||
),
|
||||
setting: super::SettingVariant::Gpu,
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
@ -79,7 +90,7 @@ impl Gpu {
|
|||
"Failed to write `manual` to `{}`: {}",
|
||||
GPU_FORCE_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Gpu,
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
@ -87,7 +98,7 @@ impl Gpu {
|
|||
usdpl_back::api::files::write_single(GPU_MEMORY_DOWNCLOCK_PATH, self.slow_memory as u8)
|
||||
.map_err(|e| SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", GPU_MEMORY_DOWNCLOCK_PATH, e),
|
||||
setting: super::SettingVariant::Gpu,
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
})?;
|
||||
if let Some(clock_limits) = &self.clock_limits {
|
||||
// set clock limits
|
||||
|
@ -100,7 +111,7 @@ impl Gpu {
|
|||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_max, GPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Gpu,
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
},
|
||||
)?;
|
||||
// min clock
|
||||
|
@ -111,32 +122,32 @@ impl Gpu {
|
|||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_min, GPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Gpu,
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
},
|
||||
)?;
|
||||
} else if self.state.clock_limits_set || self.state.is_resuming {
|
||||
self.state.clock_limits_set = false;
|
||||
// disable manual clock limits
|
||||
// max clock
|
||||
let payload_max = format!("s 1 {}\n", Self::max().clock_limits.unwrap().max);
|
||||
let payload_max = format!("s 1 {}\n", self.limits.clock_max.max);
|
||||
usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, &payload_max).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_max, GPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Gpu,
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
},
|
||||
)?;
|
||||
// min clock
|
||||
let payload_min = format!("s 0 {}\n", Self::min().clock_limits.unwrap().min);
|
||||
let payload_min = format!("s 0 {}\n", self.limits.clock_min.min);
|
||||
usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, &payload_min).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload_min, GPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
setting: super::SettingVariant::Gpu,
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
@ -144,7 +155,7 @@ impl Gpu {
|
|||
usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!("Failed to write `c` to `{}`: {}", GPU_CLOCK_LIMITS_PATH, e),
|
||||
setting: super::SettingVariant::Gpu,
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
}
|
||||
})?;
|
||||
|
||||
|
@ -152,35 +163,34 @@ impl Gpu {
|
|||
}
|
||||
|
||||
fn clamp_all(&mut self) {
|
||||
let min = Self::min();
|
||||
let max = Self::max();
|
||||
if let Some(fast_ppt) = &mut self.fast_ppt {
|
||||
*fast_ppt = (*fast_ppt).clamp(
|
||||
*min.fast_ppt.as_ref().unwrap(),
|
||||
*max.fast_ppt.as_ref().unwrap(),
|
||||
self.limits.fast_ppt.min,
|
||||
self.limits.fast_ppt.max,
|
||||
);
|
||||
}
|
||||
if let Some(slow_ppt) = &mut self.slow_ppt {
|
||||
*slow_ppt = (*slow_ppt).clamp(
|
||||
*min.slow_ppt.as_ref().unwrap(),
|
||||
*max.slow_ppt.as_ref().unwrap(),
|
||||
self.limits.slow_ppt.min,
|
||||
self.limits.slow_ppt.max,
|
||||
);
|
||||
}
|
||||
if let Some(clock_limits) = &mut self.clock_limits {
|
||||
let max_boost = max.clock_limits.as_ref().unwrap();
|
||||
let min_boost = min.clock_limits.as_ref().unwrap();
|
||||
clock_limits.min = clock_limits.min.clamp(min_boost.min, max_boost.min);
|
||||
clock_limits.max = clock_limits.max.clamp(min_boost.max, max_boost.max);
|
||||
clock_limits.min = clock_limits.min.clamp(self.limits.clock_min.min, self.limits.clock_min.max);
|
||||
clock_limits.max = clock_limits.max.clamp(self.limits.clock_max.min, self.limits.clock_max.max);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_default() -> Self {
|
||||
let (oc_limits, is_default) = OverclockLimits::load_or_default();
|
||||
Self {
|
||||
fast_ppt: None,
|
||||
slow_ppt: None,
|
||||
clock_limits: None,
|
||||
slow_memory: false,
|
||||
state: crate::state::Gpu::default(),
|
||||
limits: oc_limits.gpu,
|
||||
state: crate::state::steam_deck::Gpu::default(),
|
||||
driver_mode: if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -212,30 +222,61 @@ impl OnResume for Gpu {
|
|||
}
|
||||
}
|
||||
|
||||
impl SettingsRange for Gpu {
|
||||
#[inline]
|
||||
fn max() -> Self {
|
||||
Self {
|
||||
fast_ppt: Some(30000000),
|
||||
slow_ppt: Some(29000000),
|
||||
clock_limits: Some(MinMax {
|
||||
min: 1600,
|
||||
max: 1600,
|
||||
impl TGpu for Gpu {
|
||||
fn limits(&self) -> crate::api::GpuLimits {
|
||||
crate::api::GpuLimits {
|
||||
fast_ppt_limits: Some(RangeLimit {
|
||||
min: self.limits.fast_ppt.min / self.limits.ppt_divisor,
|
||||
max: self.limits.fast_ppt.max / self.limits.ppt_divisor,
|
||||
}),
|
||||
slow_memory: false,
|
||||
state: crate::state::Gpu::default(),
|
||||
slow_ppt_limits: Some(RangeLimit {
|
||||
min: self.limits.slow_ppt.min / self.limits.ppt_divisor,
|
||||
max: self.limits.slow_ppt.max / self.limits.ppt_divisor,
|
||||
}),
|
||||
ppt_step: self.limits.ppt_step,
|
||||
tdp_limits: None,
|
||||
tdp_boost_limits: None,
|
||||
tdp_step: 42,
|
||||
clock_min_limits: Some(RangeLimit {
|
||||
min: self.limits.clock_min.min,
|
||||
max: self.limits.clock_min.max,
|
||||
}),
|
||||
clock_max_limits: Some(RangeLimit {
|
||||
min: self.limits.clock_max.min,
|
||||
max: self.limits.clock_max.max,
|
||||
}),
|
||||
clock_step: self.limits.clock_step,
|
||||
memory_control_capable: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn min() -> Self {
|
||||
Self {
|
||||
fast_ppt: Some(0),
|
||||
slow_ppt: Some(1000000),
|
||||
clock_limits: Some(MinMax { min: 200, max: 200 }),
|
||||
slow_memory: true,
|
||||
state: crate::state::Gpu::default(),
|
||||
}
|
||||
fn json(&self) -> crate::persist::GpuJson {
|
||||
self.clone().into()
|
||||
}
|
||||
|
||||
fn ppt(&mut self, fast: Option<u64>, slow: Option<u64>) {
|
||||
self.fast_ppt = fast.map(|x| x * self.limits.ppt_divisor);
|
||||
self.slow_ppt = slow.map(|x| x * self.limits.ppt_divisor);
|
||||
}
|
||||
|
||||
fn get_ppt(&self) -> (Option<u64>, Option<u64>) {
|
||||
(self.fast_ppt.map(|x| x / self.limits.ppt_divisor), self.slow_ppt.map(|x| x / self.limits.ppt_divisor))
|
||||
}
|
||||
|
||||
fn clock_limits(&mut self, limits: Option<MinMax<u64>>) {
|
||||
self.clock_limits = limits;
|
||||
}
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
|
||||
self.clock_limits.as_ref()
|
||||
}
|
||||
|
||||
fn slow_memory(&mut self) -> &mut bool {
|
||||
&mut self.slow_memory
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
self.driver_mode.clone()
|
||||
}
|
||||
}
|
||||
|
11
backend/src/settings/steam_deck/mod.rs
Normal file
11
backend/src/settings/steam_deck/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
mod battery;
|
||||
mod cpu;
|
||||
mod gpu;
|
||||
mod oc_limits;
|
||||
mod util;
|
||||
|
||||
pub use battery::Battery;
|
||||
pub use cpu::{Cpu, Cpus};
|
||||
pub use gpu::Gpu;
|
||||
|
||||
pub use util::flash_led;
|
125
backend/src/settings/steam_deck/oc_limits.rs
Normal file
125
backend/src/settings/steam_deck/oc_limits.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use crate::settings::MinMax;
|
||||
|
||||
const OC_LIMITS_FILEPATH: &str = "pt_oc.json";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(super) struct OverclockLimits {
|
||||
pub battery: BatteryLimits,
|
||||
pub cpus: CpusLimits,
|
||||
pub gpu: GpuLimits,
|
||||
}
|
||||
|
||||
impl Default for OverclockLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
battery: BatteryLimits::default(),
|
||||
cpus: CpusLimits::default(),
|
||||
gpu: GpuLimits::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OverclockLimits {
|
||||
/// (Self, is_default)
|
||||
pub fn load_or_default() -> (Self, bool) {
|
||||
let path = oc_limits_filepath();
|
||||
if path.exists() {
|
||||
log::info!("Steam Deck limits file {} found", path.display());
|
||||
let mut file = match std::fs::File::open(&path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
log::warn!("Steam Deck limits file {} err: {} (using default fallback)", path.display(), e);
|
||||
return (Self::default(), true);
|
||||
},
|
||||
};
|
||||
match serde_json::from_reader(&mut file) {
|
||||
Ok(result) => {
|
||||
log::debug!("Steam Deck limits file {} successfully loaded", path.display());
|
||||
(result, false)
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Steam Deck limits file {} json err: {} (using default fallback)", path.display(), e);
|
||||
(Self::default(), true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::info!("Steam Deck limits file {} not found (using default fallback)", path.display());
|
||||
(Self::default(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub(super) struct BatteryLimits {
|
||||
pub charge_rate: MinMax<u64>,
|
||||
}
|
||||
|
||||
impl Default for BatteryLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
charge_rate: MinMax { min: 250, max: 2500 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub(super) struct CpusLimits {
|
||||
pub cpus: Vec<CpuLimits>,
|
||||
pub global_governors: bool,
|
||||
}
|
||||
|
||||
impl Default for CpusLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cpus: [(); 8].iter().map(|_| CpuLimits::default()).collect(),
|
||||
global_governors: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub(super) struct CpuLimits {
|
||||
pub clock_min: MinMax<u64>,
|
||||
pub clock_max: MinMax<u64>,
|
||||
pub clock_step: u64,
|
||||
}
|
||||
|
||||
impl Default for CpuLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
clock_min: MinMax { min: 1400, max: 3500 },
|
||||
clock_max: MinMax { min: 400, max: 3500 },
|
||||
clock_step: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub(super) struct GpuLimits {
|
||||
pub fast_ppt: MinMax<u64>,
|
||||
pub slow_ppt: MinMax<u64>,
|
||||
pub ppt_divisor: u64,
|
||||
pub ppt_step: u64,
|
||||
pub clock_min: MinMax<u64>,
|
||||
pub clock_max: MinMax<u64>,
|
||||
pub clock_step: u64,
|
||||
}
|
||||
|
||||
impl Default for GpuLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fast_ppt: MinMax { min: 1000000, max: 30_000_000 },
|
||||
slow_ppt: MinMax { min: 1000000, max: 29_000_000 },
|
||||
ppt_divisor: 1_000_000,
|
||||
ppt_step: 1,
|
||||
clock_min: MinMax { min: 200, max: 1600 },
|
||||
clock_max: MinMax { min: 200, max: 1600 },
|
||||
clock_step: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn oc_limits_filepath() -> std::path::PathBuf {
|
||||
crate::utility::settings_dir().join(OC_LIMITS_FILEPATH)
|
||||
}
|
126
backend/src/settings/steam_deck/util.rs
Normal file
126
backend/src/settings/steam_deck/util.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
//! Rough Rust port of some BatCtrl functionality
|
||||
//! Original: /usr/share/jupiter_controller_fw_updater/RA_bootloader_updater/linux_host_tools/BatCtrl
|
||||
//! I do not have access to the source code, so this is my own interpretation of what it does.
|
||||
//!
|
||||
//! But also Quanta is based in a place with some questionable copyright practices, so...
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Error, Seek, SeekFrom, Read, Write};
|
||||
|
||||
#[inline]
|
||||
fn write2(p0: u8, p1: u8) -> Result<usize, Error> {
|
||||
write_to(0x6c, 0x81)?;
|
||||
wait_ready_for_write()?;
|
||||
let count0 = write_to(0x68, p0)?;
|
||||
wait_ready_for_write()?;
|
||||
let count1 = write_to(0x68, p1)?;
|
||||
Ok(count0 + count1)
|
||||
}
|
||||
|
||||
fn write_read(p0: u8) -> Result<u8, Error> {
|
||||
write_to(0x6c, 0x81)?;
|
||||
wait_ready_for_write()?;
|
||||
write_to(0x68, p0)?;
|
||||
wait_ready_for_read()?;
|
||||
read_from(0x68)
|
||||
}
|
||||
|
||||
fn write_to(location: u64, value: u8) -> Result<usize, Error> {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open("/dev/port")?;
|
||||
file.seek(SeekFrom::Start(location))?;
|
||||
file.write(&[value])
|
||||
}
|
||||
|
||||
fn read_from(location: u64) -> Result<u8, Error> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open("/dev/port")?;
|
||||
file.seek(SeekFrom::Start(location))?;
|
||||
let mut buffer = [0];
|
||||
file.read(&mut buffer)?;
|
||||
Ok(buffer[0])
|
||||
}
|
||||
|
||||
fn wait_ready_for_write() -> Result<(), Error> {
|
||||
let mut count = 0;
|
||||
while count < 0x1ffff && (read_from(0x6c)? & 2) != 0 {
|
||||
count += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_ready_for_read() -> Result<(), Error> {
|
||||
let mut count = 0;
|
||||
while count < 0x1ffff && (read_from(0x6c)? & 1) == 0 {
|
||||
count += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_led(red_unused: bool, green_aka_white: bool, blue_unused: bool) -> Result<usize, Error> {
|
||||
let payload: u8 = 0x80 | (red_unused as u8 & 1) | ((green_aka_white as u8 & 1) << 1) | ((blue_unused as u8 & 1) << 2);
|
||||
//log::info!("Payload: {:b}", payload);
|
||||
write2(Setting::LEDStatus as _, payload)
|
||||
}
|
||||
|
||||
const THINGS: &[u8] = &[
|
||||
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
const TIME_UNIT: std::time::Duration = std::time::Duration::from_millis(200);
|
||||
|
||||
pub fn flash_led() {
|
||||
let old_led_state = write_read(Setting::LEDStatus as _).map_err(|e| log::error!("Failed to read LED status: {}", e));
|
||||
for &code in THINGS {
|
||||
let on = code != 0;
|
||||
if let Err(e) = set_led(on, on, false) {
|
||||
log::error!("Thing err: {}", e);
|
||||
}
|
||||
std::thread::sleep(TIME_UNIT);
|
||||
}
|
||||
if let Ok(old_led_state) = old_led_state {
|
||||
log::debug!("Restoring LED state to {:#02b}", old_led_state);
|
||||
write2(Setting::LEDStatus as _, old_led_state).map_err(|e| log::error!("Failed to restore LED status: {}", e)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(setting: Setting, mode: u8) -> Result<usize, Error> {
|
||||
write2(setting as u8, mode)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum Setting {
|
||||
CycleCount = 0x32,
|
||||
ControlBoard = 0x6C,
|
||||
Charge = 0xA6,
|
||||
ChargeMode = 0x76,
|
||||
LEDStatus = 199,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum ControlBoard {
|
||||
Enable = 0xAA,
|
||||
Disable = 0xAB,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum ChargeMode {
|
||||
Normal = 0,
|
||||
Discharge = 0x42,
|
||||
Idle = 0x45,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum Charge {
|
||||
Enable = 0,
|
||||
Disable = 4,
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
use std::fmt::Debug;
|
||||
use super::SettingError;
|
||||
use super::MinMax;
|
||||
|
||||
pub trait OnSet {
|
||||
fn on_set(&mut self) -> Result<(), SettingError>;
|
||||
|
@ -12,3 +14,95 @@ pub trait SettingsRange {
|
|||
fn max() -> Self;
|
||||
fn min() -> Self;
|
||||
}
|
||||
|
||||
pub trait TGpu: OnResume + OnSet + Debug + Send {
|
||||
fn limits(&self) -> crate::api::GpuLimits;
|
||||
|
||||
fn json(&self) -> crate::persist::GpuJson;
|
||||
|
||||
fn ppt(&mut self, fast: Option<u64>, slow: Option<u64>);
|
||||
|
||||
fn get_ppt(&self) -> (Option<u64>, Option<u64>);
|
||||
|
||||
fn clock_limits(&mut self, limits: Option<MinMax<u64>>);
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>>;
|
||||
|
||||
fn slow_memory(&mut self) -> &mut bool;
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::AutoDetect
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TCpus: OnResume + OnSet + Debug + Send {
|
||||
fn limits(&self) -> crate::api::CpusLimits;
|
||||
|
||||
fn json(&self) -> Vec<crate::persist::CpuJson>;
|
||||
|
||||
fn cpus(&mut self) -> Vec<&mut dyn TCpu>;
|
||||
|
||||
fn len(&self) -> usize;
|
||||
|
||||
fn smt(&mut self) -> &'_ mut bool;
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::AutoDetect
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TCpu: Debug + Send {
|
||||
fn online(&mut self) -> &mut bool;
|
||||
|
||||
fn governor(&mut self, governor: String);
|
||||
|
||||
fn get_governor(&self) -> &'_ str;
|
||||
|
||||
fn clock_limits(&mut self, limits: Option<MinMax<u64>>);
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>>;
|
||||
}
|
||||
|
||||
pub trait TGeneral: OnResume + OnSet + Debug + Send {
|
||||
fn limits(&self) -> crate::api::GeneralLimits;
|
||||
|
||||
fn get_persistent(&self) -> bool;
|
||||
|
||||
fn persistent(&mut self) -> &'_ mut bool;
|
||||
|
||||
fn get_path(&self) -> &'_ std::path::Path;
|
||||
|
||||
fn path(&mut self, path: std::path::PathBuf);
|
||||
|
||||
fn get_name(&self) -> &'_ str;
|
||||
|
||||
fn name(&mut self, name: String);
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson;
|
||||
}
|
||||
|
||||
pub trait TBattery: OnResume + OnSet + Debug + Send {
|
||||
fn limits(&self) -> crate::api::BatteryLimits;
|
||||
|
||||
fn json(&self) -> crate::persist::BatteryJson;
|
||||
|
||||
fn charge_rate(&mut self, rate: Option<u64>);
|
||||
|
||||
fn get_charge_rate(&self) -> Option<u64>;
|
||||
|
||||
fn charge_mode(&mut self, mode: Option<String>);
|
||||
|
||||
fn get_charge_mode(&self) -> Option<String>;
|
||||
|
||||
fn read_charge_full(&self) -> Option<f64>;
|
||||
|
||||
fn read_charge_now(&self) -> Option<f64>;
|
||||
|
||||
fn read_charge_design(&self) -> Option<f64>;
|
||||
|
||||
fn read_current_now(&self) -> Option<f64>;
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::AutoDetect
|
||||
}
|
||||
}
|
||||
|
|
70
backend/src/settings/unknown/battery.rs
Normal file
70
backend/src/settings/unknown/battery.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::TBattery;
|
||||
use crate::persist::BatteryJson;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Battery;
|
||||
|
||||
impl Into<BatteryJson> for Battery {
|
||||
#[inline]
|
||||
fn into(self) -> BatteryJson {
|
||||
BatteryJson {
|
||||
charge_rate: None,
|
||||
charge_mode: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Battery {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Battery {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TBattery for Battery {
|
||||
fn limits(&self) -> crate::api::BatteryLimits {
|
||||
crate::api::BatteryLimits {
|
||||
charge_current: None,
|
||||
charge_current_step: 50,
|
||||
charge_modes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn json(&self) -> crate::persist::BatteryJson {
|
||||
self.clone().into()
|
||||
}
|
||||
|
||||
fn charge_rate(&mut self, _rate: Option<u64>) {
|
||||
}
|
||||
|
||||
fn get_charge_rate(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn charge_mode(&mut self, _rate: Option<String>) {
|
||||
}
|
||||
|
||||
fn get_charge_mode(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn read_charge_full(&self) -> Option<f64> { None }
|
||||
|
||||
fn read_charge_now(&self) -> Option<f64> { None }
|
||||
|
||||
fn read_charge_design(&self) -> Option<f64> { None }
|
||||
|
||||
fn read_current_now(&self) -> Option<f64> { None }
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::Unknown
|
||||
}
|
||||
}
|
300
backend/src/settings/unknown/cpu.rs
Normal file
300
backend/src/settings/unknown/cpu.rs
Normal file
|
@ -0,0 +1,300 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use crate::settings::MinMax;
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::{TCpus, TCpu};
|
||||
use crate::persist::CpuJson;
|
||||
|
||||
const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present";
|
||||
const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cpus {
|
||||
pub cpus: Vec<Cpu>,
|
||||
pub smt: bool,
|
||||
pub smt_capable: bool,
|
||||
}
|
||||
|
||||
impl OnSet for Cpus {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
if self.smt_capable {
|
||||
// toggle SMT
|
||||
if self.smt {
|
||||
usdpl_back::api::files::write_single(CPU_SMT_PATH, "on").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `on` to `{}`: {}",
|
||||
CPU_SMT_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
} else {
|
||||
usdpl_back::api::files::write_single(CPU_SMT_PATH, "off").map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `off` to `{}`: {}",
|
||||
CPU_SMT_PATH, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
}
|
||||
for (i, cpu) in self.cpus.as_mut_slice().iter_mut().enumerate() {
|
||||
cpu.state.do_set_online = self.smt || i % 2 == 0;
|
||||
cpu.on_set()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Cpus {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
for cpu in &self.cpus {
|
||||
cpu.on_resume()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Cpus {
|
||||
pub fn cpu_count() -> Option<usize> {
|
||||
let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH)
|
||||
.unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */);
|
||||
if let Some(dash_index) = data.find('-') {
|
||||
let data = data.split_off(dash_index + 1);
|
||||
if let Ok(max_cpu) = data.parse::<usize>() {
|
||||
return Some(max_cpu + 1);
|
||||
}
|
||||
}
|
||||
log::warn!("Failed to parse CPU info from kernel, is Tux evil?");
|
||||
None
|
||||
}
|
||||
|
||||
fn system_smt_capabilities() -> (bool, bool) {
|
||||
match usdpl_back::api::files::read_single::<_, String, _>(CPU_SMT_PATH) {
|
||||
Ok(val) => (val.trim().to_lowercase() == "on", true),
|
||||
Err(_) => (false, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_default() -> Self {
|
||||
if let Some(max_cpu) = Self::cpu_count() {
|
||||
let mut sys_cpus = Vec::with_capacity(max_cpu);
|
||||
for i in 0..max_cpu {
|
||||
sys_cpus.push(Cpu::from_sys(i));
|
||||
}
|
||||
let (smt_status, can_smt) = Self::system_smt_capabilities();
|
||||
Self {
|
||||
cpus: sys_cpus,
|
||||
smt: smt_status,
|
||||
smt_capable: can_smt,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
cpus: vec![],
|
||||
smt: false,
|
||||
smt_capable: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_json(mut other: Vec<CpuJson>, version: u64) -> Self {
|
||||
let (_, can_smt) = Self::system_smt_capabilities();
|
||||
let mut result = Vec::with_capacity(other.len());
|
||||
let max_cpus = Self::cpu_count();
|
||||
let smt_guess = crate::settings::util::guess_smt(&other) && can_smt;
|
||||
for (i, cpu) in other.drain(..).enumerate() {
|
||||
// prevent having more CPUs than available
|
||||
if let Some(max_cpus) = max_cpus {
|
||||
if i == max_cpus {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let new_cpu = Cpu::from_json(cpu, version, i);
|
||||
result.push(new_cpu);
|
||||
}
|
||||
if let Some(max_cpus) = max_cpus {
|
||||
if result.len() != max_cpus {
|
||||
let mut sys_cpus = Cpus::system_default();
|
||||
for i in result.len()..sys_cpus.cpus.len() {
|
||||
result.push(sys_cpus.cpus.remove(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
Self {
|
||||
cpus: result,
|
||||
smt: smt_guess,
|
||||
smt_capable: can_smt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TCpus for Cpus {
|
||||
fn limits(&self) -> crate::api::CpusLimits {
|
||||
crate::api::CpusLimits {
|
||||
cpus: self.cpus.iter().map(|x| x.limits()).collect(),
|
||||
count: self.cpus.len(),
|
||||
smt_capable: self.smt_capable,
|
||||
governors: Vec::with_capacity(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn json(&self) -> Vec<crate::persist::CpuJson> {
|
||||
self.cpus.iter().map(|x| x.to_owned().into()).collect()
|
||||
}
|
||||
|
||||
fn cpus(&mut self) -> Vec<&mut dyn TCpu> {
|
||||
self.cpus.iter_mut().map(|x| x as &mut dyn TCpu).collect()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.cpus.len()
|
||||
}
|
||||
|
||||
fn smt(&mut self) -> &'_ mut bool {
|
||||
&mut self.smt
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cpu {
|
||||
pub online: bool,
|
||||
pub governor: String,
|
||||
index: usize,
|
||||
state: crate::state::steam_deck::Cpu,
|
||||
}
|
||||
|
||||
|
||||
impl Cpu {
|
||||
#[inline]
|
||||
pub fn from_json(other: CpuJson, version: u64, i: usize) -> Self {
|
||||
match version {
|
||||
0 => Self {
|
||||
online: other.online,
|
||||
governor: other.governor,
|
||||
index: i,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
},
|
||||
_ => Self {
|
||||
online: other.online,
|
||||
governor: other.governor,
|
||||
index: i,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_all(&mut self) -> Result<(), SettingError> {
|
||||
// set cpu online/offline
|
||||
if self.index != 0 && self.state.do_set_online { // cpu0 cannot be disabled
|
||||
let online_path = cpu_online_path(self.index);
|
||||
usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", &online_path, e),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
// set governor
|
||||
if self.index == 0 || self.online {
|
||||
let governor_path = cpu_governor_path(self.index);
|
||||
usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| {
|
||||
SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&self.governor, &governor_path, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn from_sys(cpu_index: usize) -> Self {
|
||||
Self {
|
||||
online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0,
|
||||
governor: usdpl_back::api::files::read_single(cpu_governor_path(cpu_index))
|
||||
.unwrap_or("schedutil".to_owned()),
|
||||
index: cpu_index,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn limits(&self) -> crate::api::CpuLimits {
|
||||
crate::api::CpuLimits {
|
||||
clock_min_limits: None,
|
||||
clock_max_limits: None,
|
||||
clock_step: 100,
|
||||
governors: vec![], // TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<CpuJson> for Cpu {
|
||||
#[inline]
|
||||
fn into(self) -> CpuJson {
|
||||
CpuJson {
|
||||
online: self.online,
|
||||
clock_limits: None,
|
||||
governor: self.governor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Cpu {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
//self.clamp_all();
|
||||
self.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Cpu {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
let mut copy = self.clone();
|
||||
copy.state.is_resuming = true;
|
||||
copy.set_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl TCpu for Cpu {
|
||||
fn online(&mut self) -> &mut bool {
|
||||
&mut self.online
|
||||
}
|
||||
|
||||
fn governor(&mut self, governor: String) {
|
||||
self.governor = governor;
|
||||
}
|
||||
|
||||
fn get_governor(&self) -> &'_ str {
|
||||
&self.governor
|
||||
}
|
||||
|
||||
fn clock_limits(&mut self, _limits: Option<MinMax<u64>>) {
|
||||
}
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpu_online_path(index: usize) -> String {
|
||||
format!("/sys/devices/system/cpu/cpu{}/online", index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpu_governor_path(index: usize) -> String {
|
||||
format!(
|
||||
"/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor",
|
||||
index
|
||||
)
|
||||
}
|
93
backend/src/settings/unknown/gpu.rs
Normal file
93
backend/src/settings/unknown/gpu.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use crate::settings::MinMax;
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
use crate::settings::TGpu;
|
||||
use crate::persist::GpuJson;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Gpu {
|
||||
slow_memory: bool, // ignored
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
#[inline]
|
||||
pub fn from_json(_other: GpuJson, _version: u64) -> Self {
|
||||
Self {
|
||||
slow_memory: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_default() -> Self {
|
||||
Self {
|
||||
slow_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<GpuJson> for Gpu {
|
||||
#[inline]
|
||||
fn into(self) -> GpuJson {
|
||||
GpuJson {
|
||||
fast_ppt: None,
|
||||
slow_ppt: None,
|
||||
clock_limits: None,
|
||||
slow_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnSet for Gpu {
|
||||
fn on_set(&mut self) -> Result<(), SettingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OnResume for Gpu {
|
||||
fn on_resume(&self) -> Result<(), SettingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TGpu for Gpu {
|
||||
fn limits(&self) -> crate::api::GpuLimits {
|
||||
crate::api::GpuLimits {
|
||||
fast_ppt_limits: None,
|
||||
slow_ppt_limits: None,
|
||||
ppt_step: 1_000_000,
|
||||
tdp_limits: None,
|
||||
tdp_boost_limits: None,
|
||||
tdp_step: 42,
|
||||
clock_min_limits: None,
|
||||
clock_max_limits: None,
|
||||
clock_step: 100,
|
||||
memory_control_capable: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn json(&self) -> crate::persist::GpuJson {
|
||||
self.clone().into()
|
||||
}
|
||||
|
||||
fn ppt(&mut self, _fast: Option<u64>, _slow: Option<u64>) {
|
||||
}
|
||||
|
||||
fn get_ppt(&self) -> (Option<u64>, Option<u64>) {
|
||||
(None, None)
|
||||
}
|
||||
|
||||
fn clock_limits(&mut self, _limits: Option<MinMax<u64>>) {
|
||||
}
|
||||
|
||||
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn slow_memory(&mut self) -> &mut bool {
|
||||
&mut self.slow_memory
|
||||
}
|
||||
|
||||
fn provider(&self) -> crate::persist::DriverJson {
|
||||
crate::persist::DriverJson::Unknown
|
||||
}
|
||||
}
|
7
backend/src/settings/unknown/mod.rs
Normal file
7
backend/src/settings/unknown/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod battery;
|
||||
mod cpu;
|
||||
mod gpu;
|
||||
|
||||
pub use battery::Battery;
|
||||
pub use cpu::{Cpu, Cpus};
|
||||
pub use gpu::Gpu;
|
7
backend/src/settings/util.rs
Normal file
7
backend/src/settings/util.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
pub fn guess_smt(cpus: &Vec<crate::persist::CpuJson>) -> bool {
|
||||
let mut guess = true;
|
||||
for i in (0..cpus.len()).step_by(2) {
|
||||
guess &= cpus[i].online == cpus[i+1].online;
|
||||
}
|
||||
guess
|
||||
}
|
16
backend/src/state/generic/gpu.rs
Normal file
16
backend/src/state/generic/gpu.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Gpu {
|
||||
pub clock_limits_set: bool,
|
||||
pub old_fast_ppt: Option<u64>,
|
||||
pub old_slow_ppt: Option<u64>,
|
||||
}
|
||||
|
||||
impl std::default::Default for Gpu {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
clock_limits_set: false,
|
||||
old_fast_ppt: None,
|
||||
old_slow_ppt: None,
|
||||
}
|
||||
}
|
||||
}
|
3
backend/src/state/generic/mod.rs
Normal file
3
backend/src/state/generic/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod gpu;
|
||||
|
||||
pub use gpu::Gpu;
|
|
@ -1,11 +1,8 @@
|
|||
mod battery;
|
||||
mod cpu;
|
||||
mod error;
|
||||
mod gpu;
|
||||
mod traits;
|
||||
|
||||
pub use battery::Battery;
|
||||
pub use cpu::Cpu;
|
||||
pub mod generic;
|
||||
pub mod steam_deck;
|
||||
|
||||
pub use error::StateError;
|
||||
pub use gpu::Gpu;
|
||||
pub use traits::OnPoll;
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Battery {
|
||||
pub charge_rate_set: bool,
|
||||
pub charge_mode_set: bool,
|
||||
}
|
||||
|
||||
impl std::default::Default for Battery {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
charge_rate_set: false,
|
||||
charge_mode_set: false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
pub struct Cpu {
|
||||
pub clock_limits_set: bool,
|
||||
pub is_resuming: bool,
|
||||
pub do_set_online: bool,
|
||||
}
|
||||
|
||||
impl std::default::Default for Cpu {
|
||||
|
@ -9,6 +10,7 @@ impl std::default::Default for Cpu {
|
|||
Self {
|
||||
clock_limits_set: false,
|
||||
is_resuming: false,
|
||||
do_set_online: true,
|
||||
}
|
||||
}
|
||||
}
|
7
backend/src/state/steam_deck/mod.rs
Normal file
7
backend/src/state/steam_deck/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod battery;
|
||||
mod cpu;
|
||||
mod gpu;
|
||||
|
||||
pub use battery::Battery;
|
||||
pub use cpu::Cpu;
|
||||
pub use gpu::Gpu;
|
|
@ -1,5 +1,5 @@
|
|||
use std::fmt::Display;
|
||||
use std::sync::{LockResult, MutexGuard};
|
||||
//use std::sync::{LockResult, MutexGuard};
|
||||
|
||||
pub fn unwrap_maybe_fatal<T: Sized, E: Display>(result: Result<T, E>, message: &str) -> T {
|
||||
match result {
|
||||
|
@ -11,7 +11,7 @@ pub fn unwrap_maybe_fatal<T: Sized, E: Display>(result: Result<T, E>, message: &
|
|||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_lock<'a, T: Sized>(
|
||||
/*pub fn unwrap_lock<'a, T: Sized>(
|
||||
result: LockResult<MutexGuard<'a, T>>,
|
||||
lock_name: &str,
|
||||
) -> MutexGuard<'a, T> {
|
||||
|
@ -22,10 +22,10 @@ pub fn unwrap_lock<'a, T: Sized>(
|
|||
panic!("Failed to acquire {} lock: {}", lock_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
pub fn settings_dir() -> std::path::PathBuf {
|
||||
usdpl_back::api::dirs::home()
|
||||
.unwrap_or_else(|| "/home/deck".into())
|
||||
.unwrap_or_else(|| "/tmp/".into())
|
||||
.join(".config/powertools/")
|
||||
}
|
||||
|
|
18
package.json
18
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "PowerTools",
|
||||
"version": "1.0.6",
|
||||
"version": "1.1.0",
|
||||
"description": "Power tweaks for power users",
|
||||
"scripts": {
|
||||
"build": "shx rm -rf dist && rollup -c",
|
||||
|
@ -27,20 +27,20 @@
|
|||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^21.1.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.2.1",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.2",
|
||||
"@rollup/plugin-typescript": "^8.5.0",
|
||||
"@types/react": "16.14.0",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"rollup": "^2.70.2",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-import-assets": "^1.1.1",
|
||||
"shx": "^0.3.4",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.4"
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"decky-frontend-lib": "3.*",
|
||||
"react-icons": "^4.4.0",
|
||||
"usdpl-front": "file:./src/usdpl_front"
|
||||
"decky-frontend-lib": "~3.18.10",
|
||||
"react-icons": "^4.7.1",
|
||||
"usdpl-front": "file:src/usdpl_front"
|
||||
}
|
||||
}
|
||||
|
|
124
pnpm-lock.yaml
124
pnpm-lock.yaml
|
@ -3,22 +3,22 @@ lockfileVersion: 5.4
|
|||
specifiers:
|
||||
'@rollup/plugin-commonjs': ^21.1.0
|
||||
'@rollup/plugin-json': ^4.1.0
|
||||
'@rollup/plugin-node-resolve': ^13.2.1
|
||||
'@rollup/plugin-node-resolve': ^13.3.0
|
||||
'@rollup/plugin-replace': ^4.0.0
|
||||
'@rollup/plugin-typescript': ^8.3.2
|
||||
'@rollup/plugin-typescript': ^8.5.0
|
||||
'@types/react': 16.14.0
|
||||
'@types/webpack': ^5.28.0
|
||||
decky-frontend-lib: 3.*
|
||||
react-icons: ^4.4.0
|
||||
rollup: ^2.70.2
|
||||
decky-frontend-lib: ~3.18.10
|
||||
react-icons: ^4.7.1
|
||||
rollup: ^2.79.1
|
||||
rollup-plugin-import-assets: ^1.1.1
|
||||
shx: ^0.3.4
|
||||
tslib: ^2.4.0
|
||||
typescript: ^4.6.4
|
||||
usdpl-front: file:./src/usdpl_front
|
||||
tslib: ^2.4.1
|
||||
typescript: ^4.9.4
|
||||
usdpl-front: file:src/usdpl_front
|
||||
|
||||
dependencies:
|
||||
decky-frontend-lib: 3.18.5
|
||||
decky-frontend-lib: 3.18.10
|
||||
react-icons: 4.7.1
|
||||
usdpl-front: file:src/usdpl_front
|
||||
|
||||
|
@ -27,14 +27,14 @@ devDependencies:
|
|||
'@rollup/plugin-json': 4.1.0_rollup@2.79.1
|
||||
'@rollup/plugin-node-resolve': 13.3.0_rollup@2.79.1
|
||||
'@rollup/plugin-replace': 4.0.0_rollup@2.79.1
|
||||
'@rollup/plugin-typescript': 8.5.0_sbiskyiysxhldmns7rmnvoiszu
|
||||
'@rollup/plugin-typescript': 8.5.0_bhcmvni67fkldpaxrtldxbogce
|
||||
'@types/react': 16.14.0
|
||||
'@types/webpack': 5.28.0
|
||||
rollup: 2.79.1
|
||||
rollup-plugin-import-assets: 1.1.1_rollup@2.79.1
|
||||
shx: 0.3.4
|
||||
tslib: 2.4.1
|
||||
typescript: 4.9.4
|
||||
tslib: 2.5.0
|
||||
typescript: 4.9.5
|
||||
|
||||
packages:
|
||||
|
||||
|
@ -108,8 +108,8 @@ packages:
|
|||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0_rollup@2.79.1
|
||||
'@types/resolve': 1.17.1
|
||||
deepmerge: 4.2.2
|
||||
is-builtin-module: 3.2.0
|
||||
deepmerge: 4.3.0
|
||||
is-builtin-module: 3.2.1
|
||||
is-module: 1.0.0
|
||||
resolve: 1.22.1
|
||||
rollup: 2.79.1
|
||||
|
@ -125,7 +125,7 @@ packages:
|
|||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-typescript/8.5.0_sbiskyiysxhldmns7rmnvoiszu:
|
||||
/@rollup/plugin-typescript/8.5.0_bhcmvni67fkldpaxrtldxbogce:
|
||||
resolution: {integrity: sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
peerDependencies:
|
||||
|
@ -139,8 +139,8 @@ packages:
|
|||
'@rollup/pluginutils': 3.1.0_rollup@2.79.1
|
||||
resolve: 1.22.1
|
||||
rollup: 2.79.1
|
||||
tslib: 2.4.1
|
||||
typescript: 4.9.4
|
||||
tslib: 2.5.0
|
||||
typescript: 4.9.5
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils/3.1.0_rollup@2.79.1:
|
||||
|
@ -158,12 +158,12 @@ packages:
|
|||
/@types/eslint-scope/3.7.4:
|
||||
resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
|
||||
dependencies:
|
||||
'@types/eslint': 8.4.10
|
||||
'@types/eslint': 8.21.0
|
||||
'@types/estree': 0.0.51
|
||||
dev: true
|
||||
|
||||
/@types/eslint/8.4.10:
|
||||
resolution: {integrity: sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==}
|
||||
/@types/eslint/8.21.0:
|
||||
resolution: {integrity: sha512-35EhHNOXgxnUgh4XCJsGhE7zdlDhYDN/aMG6UbkByCFFNgQ7b3U+uVoqBpicFydR8JEfgdjCF7SJ7MiJfzuiTA==}
|
||||
dependencies:
|
||||
'@types/estree': 0.0.51
|
||||
'@types/json-schema': 7.0.11
|
||||
|
@ -333,16 +333,16 @@ packages:
|
|||
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
|
||||
dev: true
|
||||
|
||||
/acorn-import-assertions/1.8.0_acorn@8.8.1:
|
||||
/acorn-import-assertions/1.8.0_acorn@8.8.2:
|
||||
resolution: {integrity: sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==}
|
||||
peerDependencies:
|
||||
acorn: ^8
|
||||
dependencies:
|
||||
acorn: 8.8.1
|
||||
acorn: 8.8.2
|
||||
dev: true
|
||||
|
||||
/acorn/8.8.1:
|
||||
resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
|
||||
/acorn/8.8.2:
|
||||
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
@ -375,15 +375,15 @@ packages:
|
|||
concat-map: 0.0.1
|
||||
dev: true
|
||||
|
||||
/browserslist/4.21.4:
|
||||
resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==}
|
||||
/browserslist/4.21.5:
|
||||
resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001442
|
||||
electron-to-chromium: 1.4.284
|
||||
node-releases: 2.0.8
|
||||
update-browserslist-db: 1.0.10_browserslist@4.21.4
|
||||
caniuse-lite: 1.0.30001450
|
||||
electron-to-chromium: 1.4.286
|
||||
node-releases: 2.0.9
|
||||
update-browserslist-db: 1.0.10_browserslist@4.21.5
|
||||
dev: true
|
||||
|
||||
/buffer-from/1.1.2:
|
||||
|
@ -395,8 +395,8 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite/1.0.30001442:
|
||||
resolution: {integrity: sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==}
|
||||
/caniuse-lite/1.0.30001450:
|
||||
resolution: {integrity: sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew==}
|
||||
dev: true
|
||||
|
||||
/chrome-trace-event/1.0.3:
|
||||
|
@ -420,17 +420,17 @@ packages:
|
|||
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
|
||||
dev: true
|
||||
|
||||
/decky-frontend-lib/3.18.5:
|
||||
resolution: {integrity: sha512-CTIJs61La17spws5IzAbLbZ/Bqe+gYgnO6xOrolK1QZh7ZbZeoQ67dtnI0zqRMMC10J8H7jPdqmQnwGN10/bzw==}
|
||||
/decky-frontend-lib/3.18.10:
|
||||
resolution: {integrity: sha512-2mgbA3sSkuwQR/FnmhXVrcW6LyTS95IuL6muJAmQCruhBvXapDtjk1TcgxqMZxFZwGD1IPnemPYxHZll6IgnZw==}
|
||||
dev: false
|
||||
|
||||
/deepmerge/4.2.2:
|
||||
resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
|
||||
/deepmerge/4.3.0:
|
||||
resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium/1.4.284:
|
||||
resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
|
||||
/electron-to-chromium/1.4.286:
|
||||
resolution: {integrity: sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ==}
|
||||
dev: true
|
||||
|
||||
/enhanced-resolve/5.12.0:
|
||||
|
@ -563,8 +563,8 @@ packages:
|
|||
engines: {node: '>= 0.10'}
|
||||
dev: true
|
||||
|
||||
/is-builtin-module/3.2.0:
|
||||
resolution: {integrity: sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==}
|
||||
/is-builtin-module/3.2.1:
|
||||
resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
builtin-modules: 3.3.0
|
||||
|
@ -644,8 +644,8 @@ packages:
|
|||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||
dev: true
|
||||
|
||||
/node-releases/2.0.8:
|
||||
resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==}
|
||||
/node-releases/2.0.9:
|
||||
resolution: {integrity: sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA==}
|
||||
dev: true
|
||||
|
||||
/once/1.4.0:
|
||||
|
@ -672,8 +672,8 @@ packages:
|
|||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/punycode/2.2.0:
|
||||
resolution: {integrity: sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==}
|
||||
/punycode/2.3.0:
|
||||
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
|
@ -742,8 +742,8 @@ packages:
|
|||
ajv-keywords: 3.5.2_ajv@6.12.6
|
||||
dev: true
|
||||
|
||||
/serialize-javascript/6.0.0:
|
||||
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
|
||||
/serialize-javascript/6.0.1:
|
||||
resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==}
|
||||
dependencies:
|
||||
randombytes: 2.1.0
|
||||
dev: true
|
||||
|
@ -820,39 +820,39 @@ packages:
|
|||
'@jridgewell/trace-mapping': 0.3.17
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 3.1.1
|
||||
serialize-javascript: 6.0.0
|
||||
terser: 5.16.1
|
||||
serialize-javascript: 6.0.1
|
||||
terser: 5.16.3
|
||||
webpack: 5.75.0
|
||||
dev: true
|
||||
|
||||
/terser/5.16.1:
|
||||
resolution: {integrity: sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==}
|
||||
/terser/5.16.3:
|
||||
resolution: {integrity: sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.2
|
||||
acorn: 8.8.1
|
||||
acorn: 8.8.2
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
dev: true
|
||||
|
||||
/tslib/2.4.1:
|
||||
resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
|
||||
/tslib/2.5.0:
|
||||
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
|
||||
dev: true
|
||||
|
||||
/typescript/4.9.4:
|
||||
resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==}
|
||||
/typescript/4.9.5:
|
||||
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/update-browserslist-db/1.0.10_browserslist@4.21.4:
|
||||
/update-browserslist-db/1.0.10_browserslist@4.21.5:
|
||||
resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
dependencies:
|
||||
browserslist: 4.21.4
|
||||
browserslist: 4.21.5
|
||||
escalade: 3.1.1
|
||||
picocolors: 1.0.0
|
||||
dev: true
|
||||
|
@ -860,7 +860,7 @@ packages:
|
|||
/uri-js/4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
dependencies:
|
||||
punycode: 2.2.0
|
||||
punycode: 2.3.0
|
||||
dev: true
|
||||
|
||||
/url-join/4.0.1:
|
||||
|
@ -895,9 +895,9 @@ packages:
|
|||
'@webassemblyjs/ast': 1.11.1
|
||||
'@webassemblyjs/wasm-edit': 1.11.1
|
||||
'@webassemblyjs/wasm-parser': 1.11.1
|
||||
acorn: 8.8.1
|
||||
acorn-import-assertions: 1.8.0_acorn@8.8.1
|
||||
browserslist: 4.21.4
|
||||
acorn: 8.8.2
|
||||
acorn-import-assertions: 1.8.0_acorn@8.8.2
|
||||
browserslist: 4.21.5
|
||||
chrome-trace-event: 1.0.3
|
||||
enhanced-resolve: 5.12.0
|
||||
es-module-lexer: 0.9.3
|
||||
|
@ -927,5 +927,5 @@ packages:
|
|||
file:src/usdpl_front:
|
||||
resolution: {directory: src/usdpl_front, type: directory}
|
||||
name: usdpl-front
|
||||
version: 0.6.2
|
||||
version: 0.9.1
|
||||
dev: false
|
||||
|
|
59
pt_oc.json
Normal file
59
pt_oc.json
Normal file
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"battery": {
|
||||
"charge_rate": {"min": 250, "max": 2500}
|
||||
},
|
||||
"cpus": {
|
||||
"cpus": [
|
||||
{
|
||||
"clock_min": {"min": 1400, "max": 3500},
|
||||
"clock_max": {"min": 500, "max": 3500},
|
||||
"clock_step": 100
|
||||
},
|
||||
{
|
||||
"clock_min": {"min": 1400, "max": 3500},
|
||||
"clock_max": {"min": 500, "max": 3500},
|
||||
"clock_step": 100
|
||||
},
|
||||
{
|
||||
"clock_min": {"min": 1400, "max": 3500},
|
||||
"clock_max": {"min": 500, "max": 3500},
|
||||
"clock_step": 100
|
||||
},
|
||||
{
|
||||
"clock_min": {"min": 1400, "max": 3500},
|
||||
"clock_max": {"min": 500, "max": 3500},
|
||||
"clock_step": 100
|
||||
},
|
||||
{
|
||||
"clock_min": {"min": 1400, "max": 3500},
|
||||
"clock_max": {"min": 500, "max": 3500},
|
||||
"clock_step": 100
|
||||
},
|
||||
{
|
||||
"clock_min": {"min": 1400, "max": 3500},
|
||||
"clock_max": {"min": 500, "max": 3500},
|
||||
"clock_step": 100
|
||||
},
|
||||
{
|
||||
"clock_min": {"min": 1400, "max": 3500},
|
||||
"clock_max": {"min": 500, "max": 3500},
|
||||
"clock_step": 100
|
||||
},
|
||||
{
|
||||
"clock_min": {"min": 1400, "max": 3500},
|
||||
"clock_max": {"min": 500, "max": 3500},
|
||||
"clock_step": 100
|
||||
}
|
||||
],
|
||||
"global_governors": false
|
||||
},
|
||||
"gpu": {
|
||||
"fast_ppt": {"min": 1000000, "max": 30000000},
|
||||
"slow_ppt": {"min": 1000000, "max": 29000000},
|
||||
"ppt_divisor": 1000000,
|
||||
"ppt_step": 1,
|
||||
"clock_min": {"min": 200, "max": 1600},
|
||||
"clock_max": {"min": 200, "max": 1600},
|
||||
"clock_step": 100
|
||||
}
|
||||
}
|
125
src/backend.ts
125
src/backend.ts
|
@ -1,29 +1,92 @@
|
|||
import {init_usdpl, target_usdpl, init_embedded, call_backend} from "usdpl-front";
|
||||
import {init_usdpl, target_usdpl, init_embedded, call_backend, init_tr} from "usdpl-front";
|
||||
|
||||
const USDPL_PORT: number = 44443;
|
||||
|
||||
// Utility
|
||||
|
||||
export function resolve(promise: Promise<any>, setter: any) {
|
||||
export function resolve<T>(promise: Promise<T>, setter: (t: T) => void) {
|
||||
(async function () {
|
||||
let data = await promise;
|
||||
if (data != null) {
|
||||
console.debug("Got resolved", data);
|
||||
setter(data);
|
||||
} else {
|
||||
console.warn("Resolve failed:", data);
|
||||
console.warn("Resolve failed:", data, promise);
|
||||
log(LogLevel.Warn, "A resolve failed");
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
export function resolve_nullable<T>(promise: Promise<T | null>, setter: (t: T | null) => void) {
|
||||
(async function () {
|
||||
let data = await promise;
|
||||
console.debug("Got resolved", data);
|
||||
setter(data);
|
||||
})();
|
||||
}
|
||||
|
||||
export async function initBackend() {
|
||||
// init usdpl
|
||||
await init_embedded();
|
||||
init_usdpl(USDPL_PORT);
|
||||
console.log("USDPL started for framework: " + target_usdpl());
|
||||
const user_locale =
|
||||
navigator.languages && navigator.languages.length
|
||||
? navigator.languages[0]
|
||||
: navigator.language;
|
||||
console.log("POWERTOOLS: locale", user_locale);
|
||||
let mo_path = "../plugins/PowerTools/translations/" + user_locale.toString() + ".mo";
|
||||
await init_tr(mo_path);
|
||||
//await init_tr("../plugins/PowerTools/translations/test.mo");
|
||||
//setReady(true);
|
||||
}
|
||||
|
||||
// API limit types
|
||||
|
||||
export type RangeLimit = {
|
||||
min: number;
|
||||
max: number;
|
||||
};
|
||||
|
||||
export type SettingsLimits = {
|
||||
battery: BatteryLimits;
|
||||
cpu: CpusLimits;
|
||||
gpu: GpuLimits;
|
||||
general: GeneralLimits;
|
||||
};
|
||||
|
||||
export type BatteryLimits = {
|
||||
charge_current: RangeLimit | null;
|
||||
charge_current_step: number;
|
||||
charge_modes: string[];
|
||||
};
|
||||
|
||||
export type CpuLimits = {
|
||||
clock_min_limits: RangeLimit | null;
|
||||
clock_max_limits: RangeLimit | null;
|
||||
clock_step: number;
|
||||
governors: string[];
|
||||
};
|
||||
|
||||
export type CpusLimits = {
|
||||
cpus: CpuLimits[];
|
||||
count: number;
|
||||
smt_capable: boolean;
|
||||
governors: string[];
|
||||
};
|
||||
|
||||
export type GeneralLimits = {};
|
||||
|
||||
export type GpuLimits = {
|
||||
fast_ppt_limits: RangeLimit | null;
|
||||
slow_ppt_limits: RangeLimit | null;
|
||||
ppt_step: number;
|
||||
clock_min_limits: RangeLimit | null;
|
||||
clock_max_limits: RangeLimit | null;
|
||||
clock_step: number;
|
||||
memory_control_capable: boolean;
|
||||
};
|
||||
|
||||
// API
|
||||
|
||||
export async function getInfo(): Promise<string> {
|
||||
|
@ -48,7 +111,7 @@ export async function getBatteryChargeDesign(): Promise<number> {
|
|||
return (await call_backend("BATTERY_charge_design", []))[0];
|
||||
}
|
||||
|
||||
export async function getBatteryChargeRate(): Promise<number> {
|
||||
export async function getBatteryChargeRate(): Promise<number | null> {
|
||||
return (await call_backend("BATTERY_get_charge_rate", []))[0];
|
||||
}
|
||||
|
||||
|
@ -60,12 +123,32 @@ export async function unsetBatteryChargeRate(): Promise<any[]> {
|
|||
return await call_backend("BATTERY_unset_charge_rate", []);
|
||||
}
|
||||
|
||||
export async function getBatteryChargeMode(): Promise<string | null> {
|
||||
return (await call_backend("BATTERY_get_charge_mode", []))[0];
|
||||
}
|
||||
|
||||
export async function setBatteryChargeMode(val: string): Promise<string> {
|
||||
return (await call_backend("BATTERY_set_charge_mode", [val]))[0];
|
||||
}
|
||||
|
||||
export async function unsetBatteryChargeMode(): Promise<any[]> {
|
||||
return await call_backend("BATTERY_unset_charge_mode", []);
|
||||
}
|
||||
|
||||
// CPU
|
||||
|
||||
export async function getCpuCount(): Promise<number> {
|
||||
return (await call_backend("CPU_count", []))[0];
|
||||
export async function setCpuSmt(status: boolean): Promise<boolean[]> {
|
||||
return await call_backend("CPU_set_smt", [status]);
|
||||
}
|
||||
|
||||
export async function getCpuSmt(): Promise<boolean> {
|
||||
return await call_backend("CPU_get_smt", []);
|
||||
}
|
||||
|
||||
/*export async function getCpuCount(): Promise<number> {
|
||||
return (await call_backend("CPU_count", []))[0];
|
||||
}*/
|
||||
|
||||
export async function setCpuOnline(index: number, online: boolean): Promise<boolean> {
|
||||
return (await call_backend("CPU_set_online", [index, online]))[0];
|
||||
}
|
||||
|
@ -150,10 +233,38 @@ export async function loadGeneralDefaultSettings(): Promise<boolean> {
|
|||
return (await call_backend("GENERAL_load_default_settings", []))[0];
|
||||
}
|
||||
|
||||
export async function getGeneralSettingsName(): Promise<boolean> {
|
||||
export async function loadGeneralSystemSettings(): Promise<boolean> {
|
||||
return (await call_backend("GENERAL_load_system_settings", []))[0];
|
||||
}
|
||||
|
||||
export async function getGeneralSettingsName(): Promise<string> {
|
||||
return (await call_backend("GENERAL_get_name", []))[0];
|
||||
}
|
||||
|
||||
export async function waitForComplete(): Promise<boolean> {
|
||||
return (await call_backend("GENERAL_wait_for_unlocks", []))[0];
|
||||
}
|
||||
|
||||
export async function getLimits(): Promise<SettingsLimits> {
|
||||
return (await call_backend("GENERAL_get_limits", []))[0];
|
||||
}
|
||||
|
||||
export async function getDriverProviderName(name: string): Promise<string> {
|
||||
return (await call_backend("GENERAL_get_provider", [name]))[0];
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
Trace = 1,
|
||||
Debug = 2,
|
||||
Info = 3,
|
||||
Warn = 4,
|
||||
Error = 5,
|
||||
}
|
||||
|
||||
export async function log(level: LogLevel, msg: string): Promise<boolean> {
|
||||
return (await call_backend("LOG", [level, msg]))[0];
|
||||
}
|
||||
|
||||
export async function idk(): Promise<boolean> {
|
||||
return (await call_backend("GENERAL_idk", []))[0];
|
||||
}
|
||||
|
|
139
src/components/battery.tsx
Normal file
139
src/components/battery.tsx
Normal file
|
@ -0,0 +1,139 @@
|
|||
import { Fragment } from "react";
|
||||
import {Component} from "react";
|
||||
import {
|
||||
ToggleField,
|
||||
SliderField,
|
||||
Field,
|
||||
SingleDropdownOption,
|
||||
Dropdown,
|
||||
PanelSectionRow,
|
||||
staticClasses,
|
||||
} from "decky-frontend-lib";
|
||||
import * as backend from "../backend";
|
||||
import { tr } from "usdpl-front";
|
||||
import {
|
||||
LIMITS_INFO,
|
||||
CHARGE_DESIGN_BATT,
|
||||
CHARGE_FULL_BATT,
|
||||
CHARGE_NOW_BATT,
|
||||
CHARGE_RATE_BATT,
|
||||
CHARGE_MODE_BATT,
|
||||
CURRENT_BATT,
|
||||
} from "../consts";
|
||||
import { set_value, get_value} from "usdpl-front";
|
||||
|
||||
export class Battery extends Component<{}> {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
reloadThingy: "/shrug",
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const reloadGUI = (x: string) => this.setState({reloadThingy: x});
|
||||
const chargeModeOptions: SingleDropdownOption[] = (get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_modes.map((elem) => {return {
|
||||
data: elem,
|
||||
label: <span>{elem}</span>,
|
||||
};});
|
||||
return (<Fragment>
|
||||
{/* Battery */}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
{tr("Battery")}
|
||||
</div>
|
||||
{get_value(CHARGE_NOW_BATT) != null && get_value(CHARGE_FULL_BATT) != null && <PanelSectionRow>
|
||||
<Field
|
||||
label={tr("Now (Charge)")}>
|
||||
{get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%)
|
||||
</Field>
|
||||
</PanelSectionRow>}
|
||||
{get_value(CHARGE_FULL_BATT) != null && get_value(CHARGE_DESIGN_BATT) != null && <PanelSectionRow>
|
||||
<Field
|
||||
label={tr("Max (Design)")}>
|
||||
{get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%)
|
||||
</Field>
|
||||
</PanelSectionRow>}
|
||||
{(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current != null && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(CHARGE_RATE_BATT) != null}
|
||||
label={tr("Charge Current Limits")}
|
||||
description={tr("Control battery charge rate when awake")}
|
||||
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={tr("Maximum (mA)")}
|
||||
value={get_value(CHARGE_RATE_BATT)}
|
||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current!.max}
|
||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current!.min}
|
||||
step={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current_step}
|
||||
showValue={true}
|
||||
disabled={get_value(CHARGE_RATE_BATT) == null}
|
||||
onChange={(val: number) => {
|
||||
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");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
</PanelSectionRow>}
|
||||
{chargeModeOptions.length != 0 && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(CHARGE_MODE_BATT) != null}
|
||||
label={tr("Charge Mode")}
|
||||
description={tr("Force battery charge mode")}
|
||||
onChange={(value: boolean) => {
|
||||
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 && <Field
|
||||
label={tr("Mode")}
|
||||
>
|
||||
<Dropdown
|
||||
menuLabel={tr("Charge Mode")}
|
||||
rgOptions={chargeModeOptions}
|
||||
selectedOption={chargeModeOptions.find((val: SingleDropdownOption, _index, _arr) => {
|
||||
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");
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Field>}
|
||||
</PanelSectionRow>}
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
label={tr("Current")}>
|
||||
{get_value(CURRENT_BATT)} mA
|
||||
</Field>
|
||||
</PanelSectionRow>
|
||||
</Fragment>);
|
||||
}
|
||||
}
|
423
src/components/cpus.tsx
Normal file
423
src/components/cpus.tsx
Normal file
|
@ -0,0 +1,423 @@
|
|||
import { Fragment } from "react";
|
||||
import { Component } from "react";
|
||||
import {
|
||||
ToggleField,
|
||||
SliderField,
|
||||
Field,
|
||||
SingleDropdownOption,
|
||||
Dropdown,
|
||||
PanelSectionRow,
|
||||
staticClasses,
|
||||
} from "decky-frontend-lib";
|
||||
import * as backend from "../backend";
|
||||
import { tr } from "usdpl-front";
|
||||
import {
|
||||
LIMITS_INFO,
|
||||
SMT_CPU,
|
||||
CLOCK_MAX_CPU,
|
||||
CLOCK_MIN_CPU,
|
||||
CLOCK_MIN_MAX_CPU,
|
||||
ONLINE_CPUS,
|
||||
ONLINE_STATUS_CPUS,
|
||||
GOVERNOR_CPU,
|
||||
} from "../consts";
|
||||
import { set_value, get_value } from "usdpl-front";
|
||||
|
||||
interface CpuState {
|
||||
reloadThingy: string;
|
||||
}
|
||||
|
||||
let advancedMode = false;
|
||||
let advancedCpu = 1;
|
||||
|
||||
export class Cpus extends Component<{}, CpuState> {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
reloadThingy: "/shrug",
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const reloadGUI = (x: string) => this.setState((_state) => {
|
||||
return {
|
||||
reloadThingy: x,
|
||||
};
|
||||
});
|
||||
|
||||
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: <span>{elem}</span>,
|
||||
};});
|
||||
|
||||
const governorGlobalOptions: SingleDropdownOption[] = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.governors.map((elem) => {return {
|
||||
data: elem,
|
||||
label: <span>{elem}</span>,
|
||||
};});
|
||||
|
||||
return (<Fragment>
|
||||
{/* CPU */}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
{tr("CPU")}
|
||||
</div>
|
||||
<PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={advancedMode}
|
||||
label={tr("Advanced")}
|
||||
description={tr("Enables per-thread configuration")}
|
||||
onChange={(advanced: boolean) => {
|
||||
advancedMode = advanced;
|
||||
this.setState((state) => {
|
||||
return {
|
||||
reloadThingy: state.reloadThingy,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>
|
||||
{/* CPU plebeian mode */}
|
||||
{!advancedMode && smtAllowed && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(SMT_CPU)}
|
||||
label={tr("SMT")}
|
||||
description={tr("Enables odd-numbered CPUs")}
|
||||
onChange={(smt: boolean) => {
|
||||
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");
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>}
|
||||
{!advancedMode && <PanelSectionRow>
|
||||
<SliderField
|
||||
label={tr("Threads")}
|
||||
value={get_value(ONLINE_CPUS)}
|
||||
step={1}
|
||||
max={(get_value(SMT_CPU) || !smtAllowed) ? total_cpus : total_cpus/2}
|
||||
min={1}
|
||||
showValue={true}
|
||||
onChange={(cpus: number) => {
|
||||
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");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>}
|
||||
{!advancedMode && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(CLOCK_MIN_CPU) != null || get_value(CLOCK_MAX_CPU) != null}
|
||||
label={tr("Frequency Limits")}
|
||||
description={tr("Set bounds on clock speed")}
|
||||
onChange={(value: boolean) => {
|
||||
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();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>}
|
||||
{!advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits != null && <PanelSectionRow>
|
||||
{get_value(CLOCK_MIN_CPU) != null && <SliderField
|
||||
label={tr("Minimum (MHz)")}
|
||||
value={get_value(CLOCK_MIN_CPU)}
|
||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits!.max}
|
||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits!.min}
|
||||
step={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_step}
|
||||
showValue={true}
|
||||
disabled={get_value(CLOCK_MIN_CPU) == null}
|
||||
onChange={(freq: number) => {
|
||||
backend.log(backend.LogLevel.Debug, "Min freq slider is now " + freq.toString());
|
||||
const freqNow = get_value(CLOCK_MIN_CPU);
|
||||
const maxNow = get_value(CLOCK_MAX_CPU);
|
||||
if (freq != freqNow && ((maxNow != null && freq <= maxNow) || maxNow == null)) {
|
||||
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");
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
</PanelSectionRow>}
|
||||
{!advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits != null && <PanelSectionRow>
|
||||
{get_value(CLOCK_MAX_CPU) != null && <SliderField
|
||||
label={tr("Maximum (MHz)")}
|
||||
value={get_value(CLOCK_MAX_CPU)}
|
||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits!.max}
|
||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits!.min}
|
||||
step={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_step}
|
||||
showValue={true}
|
||||
disabled={get_value(CLOCK_MAX_CPU) == null}
|
||||
onChange={(freq: number) => {
|
||||
backend.log(backend.LogLevel.Debug, "Max freq slider is now " + freq.toString());
|
||||
const freqNow = get_value(CLOCK_MAX_CPU);
|
||||
const minNow = get_value(CLOCK_MIN_CPU);
|
||||
if (freq != freqNow && ((minNow != null && freq >= minNow) || minNow == null)) {
|
||||
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");
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
</PanelSectionRow>}
|
||||
{!advancedMode && governorGlobalOptions.length != 0 && <PanelSectionRow>
|
||||
<Field
|
||||
label={tr("Governor")}
|
||||
>
|
||||
<Dropdown
|
||||
menuLabel={tr("Governor")}
|
||||
rgOptions={governorGlobalOptions}
|
||||
selectedOption={governorGlobalOptions.find((val: SingleDropdownOption, _index, _arr) => {
|
||||
backend.log(backend.LogLevel.Debug, "POWERTOOLS: array item " + val.toString());
|
||||
backend.log(backend.LogLevel.Debug, "POWERTOOLS: looking for data " + get_value(GOVERNOR_CPU)[0].toString());
|
||||
return val.data == get_value(GOVERNOR_CPU)[0];
|
||||
})}
|
||||
strDefaultLabel={get_value(GOVERNOR_CPU)[0]}
|
||||
onChange={(elem: SingleDropdownOption) => {
|
||||
backend.log(backend.LogLevel.Debug, "Governor global dropdown selected " + elem.data.toString());
|
||||
const governors = get_value(GOVERNOR_CPU);
|
||||
for (let i = 0; i < total_cpus; i++) {
|
||||
governors[i] = elem.data as string;
|
||||
backend.resolve(backend.setCpuGovernor(i, elem.data as string), (_: string) => {});
|
||||
}
|
||||
set_value(GOVERNOR_CPU, governors);
|
||||
reloadGUI("CPUGlobalGovernor");
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</PanelSectionRow>}
|
||||
{/* CPU advanced mode */}
|
||||
{advancedMode && <PanelSectionRow>
|
||||
<SliderField
|
||||
label={tr("Selected CPU")}
|
||||
value={advancedCpu}
|
||||
step={1}
|
||||
max={total_cpus}
|
||||
min={1}
|
||||
showValue={true}
|
||||
onChange={(cpuNum: number) => {
|
||||
advancedCpu = cpuNum;
|
||||
this.setState((state) => {
|
||||
return {
|
||||
reloadThingy: state.reloadThingy,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>}
|
||||
{advancedMode && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(ONLINE_STATUS_CPUS)[advancedCpuIndex]}
|
||||
label={tr("Online")}
|
||||
description={tr("Allow the CPU thread to do work")}
|
||||
onChange={(status: boolean) => {
|
||||
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);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>}
|
||||
{advancedMode && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null || get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max != null}
|
||||
label={tr("Frequency Limits")}
|
||||
description={tr("Set bounds on clock speed")}
|
||||
onChange={(value: boolean) => {
|
||||
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");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>}
|
||||
{advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits != null && <PanelSectionRow>
|
||||
{get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null && <SliderField
|
||||
label={tr("Minimum (MHz)")}
|
||||
value={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min}
|
||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits!.max}
|
||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits!.min}
|
||||
step={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_step}
|
||||
showValue={true}
|
||||
disabled={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min == null}
|
||||
onChange={(freq: number) => {
|
||||
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 && ((freqNow.max != null && freq <= freqNow.max) || freqNow.max == null)) {
|
||||
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");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
</PanelSectionRow>}
|
||||
{advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits != null && <PanelSectionRow>
|
||||
{get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max != null && <SliderField
|
||||
label={tr("Maximum (MHz)")}
|
||||
value={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max}
|
||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits!.max}
|
||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits!.min}
|
||||
step={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_step}
|
||||
showValue={true}
|
||||
disabled={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max == null}
|
||||
onChange={(freq: number) => {
|
||||
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 && ((freqNow.min != null && freq >= freqNow.min) || freqNow.min == null)) {
|
||||
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");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
</PanelSectionRow>}
|
||||
{advancedMode && governorOptions.length != 0 && <PanelSectionRow>
|
||||
<Field
|
||||
label={tr("Governor")}
|
||||
>
|
||||
<Dropdown
|
||||
menuLabel={tr("Governor")}
|
||||
rgOptions={governorOptions}
|
||||
selectedOption={governorOptions.find((val: SingleDropdownOption, _index, _arr) => {
|
||||
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");
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</PanelSectionRow>}
|
||||
</Fragment>);
|
||||
}
|
||||
}
|
||||
|
||||
function countCpus(statii: boolean[]): number {
|
||||
let count = 0;
|
||||
for (let i = 0; i < statii.length; i++) {
|
||||
if (statii[i]) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
type MinMax = {
|
||||
min: number | null;
|
||||
max: number | null;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
84
src/components/debug.tsx
Normal file
84
src/components/debug.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { Fragment } from "react";
|
||||
import {Component} from "react";
|
||||
import {
|
||||
ButtonItem,
|
||||
Field,
|
||||
PanelSectionRow,
|
||||
staticClasses,
|
||||
Router,
|
||||
} from "decky-frontend-lib";
|
||||
import * as backend from "../backend";
|
||||
import { tr } from "usdpl-front";
|
||||
import {
|
||||
BACKEND_INFO,
|
||||
DRIVER_INFO,
|
||||
} from "../consts";
|
||||
import { get_value, target_usdpl, version_usdpl} from "usdpl-front";
|
||||
|
||||
let eggCount = 0;
|
||||
|
||||
export class Debug extends Component<{}> {
|
||||
render() {
|
||||
return buildDebug();
|
||||
}
|
||||
}
|
||||
|
||||
function buildDebug() {
|
||||
return (<Fragment>{/* Version Info */}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
{eggCount % 10 == 9 ? "Ha! Nerd" : tr("Debug")}
|
||||
</div>
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
label={eggCount % 10 == 9 ? "PowerTools" : tr("Native")}
|
||||
onClick={()=> {
|
||||
if (eggCount % 10 == 9) {
|
||||
// you know you're bored and/or conceited when you spend time adding an easter egg
|
||||
// that just sends people to your own project's repo
|
||||
Router.NavigateToExternalWeb("https://github.com/NGnius/PowerTools");
|
||||
}
|
||||
eggCount++;
|
||||
}}>
|
||||
{eggCount % 10 == 9 ? "by NGnius" : get_value(BACKEND_INFO)}
|
||||
</Field>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
label={tr("Framework")}
|
||||
onClick={()=> eggCount++}>
|
||||
{eggCount % 10 == 9 ? "<3 <3 <3" : target_usdpl()}
|
||||
</Field>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
label={tr("Driver")}
|
||||
onClick={()=> eggCount++}>
|
||||
{eggCount % 10 == 9 ? "Tracy Chapman" : get_value(DRIVER_INFO)}
|
||||
</Field>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
label="USDPL"
|
||||
onClick={()=> {
|
||||
if (eggCount % 10 == 9) {
|
||||
// you know you're bored and/or conceited when you spend time adding an easter egg
|
||||
// that just sends people to your own project's repo
|
||||
Router.NavigateToExternalWeb("https://github.com/NGnius/usdpl-rs");
|
||||
}
|
||||
eggCount++;
|
||||
}}>
|
||||
v{version_usdpl()}
|
||||
</Field>
|
||||
</PanelSectionRow>
|
||||
{eggCount % 10 == 9 && <PanelSectionRow>
|
||||
<ButtonItem
|
||||
layout="below"
|
||||
onClick={(_: MouseEvent) => {
|
||||
backend.idk();
|
||||
}}
|
||||
>
|
||||
???
|
||||
</ButtonItem>
|
||||
</PanelSectionRow>}
|
||||
</Fragment>);
|
||||
}
|
198
src/components/gpu.tsx
Normal file
198
src/components/gpu.tsx
Normal file
|
@ -0,0 +1,198 @@
|
|||
import { Fragment } from "react";
|
||||
import {Component} from "react";
|
||||
import {
|
||||
ToggleField,
|
||||
SliderField,
|
||||
PanelSectionRow,
|
||||
staticClasses,
|
||||
} from "decky-frontend-lib";
|
||||
import * as backend from "../backend";
|
||||
import { tr } from "usdpl-front";
|
||||
import {
|
||||
LIMITS_INFO,
|
||||
SLOW_PPT_GPU,
|
||||
FAST_PPT_GPU,
|
||||
CLOCK_MIN_GPU,
|
||||
CLOCK_MAX_GPU,
|
||||
SLOW_MEMORY_GPU,
|
||||
} from "../consts";
|
||||
import { set_value, get_value} from "usdpl-front";
|
||||
|
||||
export class Gpu extends Component<{}> {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
reloadThingy: "/shrug",
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const reloadGUI = (x: string) => this.setState({reloadThingy: x});
|
||||
return (<Fragment>
|
||||
{/* GPU */}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
{tr("GPU")}
|
||||
</div>
|
||||
{ ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits != null ||(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits != null) && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(SLOW_PPT_GPU) != null || get_value(FAST_PPT_GPU) != null}
|
||||
label={tr("PowerPlay Limits")}
|
||||
description={tr("Override APU TDP settings")}
|
||||
onChange={(value: boolean) => {
|
||||
if (value) {
|
||||
if ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits != null) {
|
||||
set_value(SLOW_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits!.max);
|
||||
}
|
||||
|
||||
if ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits != null) {
|
||||
set_value(FAST_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits!.max);
|
||||
}
|
||||
reloadGUI("GPUPPTToggle");
|
||||
} else {
|
||||
set_value(SLOW_PPT_GPU, null);
|
||||
set_value(FAST_PPT_GPU, null);
|
||||
backend.resolve(backend.unsetGpuPpt(), (_: any[]) => {
|
||||
reloadGUI("GPUUnsetPPT");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>}
|
||||
<PanelSectionRow>
|
||||
{ get_value(SLOW_PPT_GPU) != null && <SliderField
|
||||
label={tr("SlowPPT (W)")}
|
||||
value={get_value(SLOW_PPT_GPU)}
|
||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits!.max}
|
||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits!.min}
|
||||
step={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.ppt_step}
|
||||
showValue={true}
|
||||
disabled={get_value(SLOW_PPT_GPU) == null}
|
||||
onChange={(ppt: number) => {
|
||||
backend.log(backend.LogLevel.Debug, "SlowPPT is now " + ppt.toString());
|
||||
const pptNow = get_value(SLOW_PPT_GPU);
|
||||
const realPpt = ppt;
|
||||
if (realPpt != pptNow) {
|
||||
backend.resolve(backend.setGpuPpt(get_value(FAST_PPT_GPU), realPpt),
|
||||
(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={tr("FastPPT (W)")}
|
||||
value={get_value(FAST_PPT_GPU)}
|
||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits!.max}
|
||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits!.min}
|
||||
step={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.ppt_step}
|
||||
showValue={true}
|
||||
disabled={get_value(FAST_PPT_GPU) == null}
|
||||
onChange={(ppt: number) => {
|
||||
backend.log(backend.LogLevel.Debug, "FastPPT is now " + ppt.toString());
|
||||
const pptNow = get_value(FAST_PPT_GPU);
|
||||
const realPpt = ppt;
|
||||
if (realPpt != pptNow) {
|
||||
backend.resolve(backend.setGpuPpt(realPpt, get_value(SLOW_PPT_GPU)),
|
||||
(limits: number[]) => {
|
||||
set_value(FAST_PPT_GPU, limits[0]);
|
||||
set_value(SLOW_PPT_GPU, limits[1]);
|
||||
reloadGUI("GPUFastPPT");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
</PanelSectionRow>
|
||||
{((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits != null || (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits != null) && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(CLOCK_MIN_GPU) != null || get_value(CLOCK_MAX_GPU) != null}
|
||||
label={tr("Frequency Limits")}
|
||||
description={tr("Set bounds on clock speed")}
|
||||
onChange={(value: boolean) => {
|
||||
if (value) {
|
||||
let clock_min_limits = (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits;
|
||||
let clock_max_limits = (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits;
|
||||
if (clock_min_limits != null) {
|
||||
set_value(CLOCK_MIN_GPU, clock_min_limits.min);
|
||||
}
|
||||
if (clock_max_limits != null) {
|
||||
set_value(CLOCK_MAX_GPU, clock_max_limits.max);
|
||||
}
|
||||
reloadGUI("GPUFreqToggle");
|
||||
} else {
|
||||
set_value(CLOCK_MIN_GPU, null);
|
||||
set_value(CLOCK_MAX_GPU, null);
|
||||
backend.resolve(backend.unsetGpuClockLimits(), (_: any[]) => {
|
||||
reloadGUI("GPUUnsetFreq");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>}
|
||||
<PanelSectionRow>
|
||||
{ get_value(CLOCK_MIN_GPU) != null && <SliderField
|
||||
label={tr("Minimum (MHz)")}
|
||||
value={get_value(CLOCK_MIN_GPU)}
|
||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits!.max}
|
||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits!.min}
|
||||
step={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_step}
|
||||
showValue={true}
|
||||
disabled={get_value(CLOCK_MIN_GPU) == null}
|
||||
onChange={(val: number) => {
|
||||
backend.log(backend.LogLevel.Debug, "GPU Clock Min is now " + val.toString());
|
||||
const valNow = get_value(CLOCK_MIN_GPU);
|
||||
const maxNow = get_value(CLOCK_MAX_GPU);
|
||||
if (val != valNow && ((maxNow != null && val <= maxNow) || maxNow == null)) {
|
||||
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={tr("Maximum (MHz)")}
|
||||
value={get_value(CLOCK_MAX_GPU)}
|
||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits!.max}
|
||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits!.min}
|
||||
step={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_step}
|
||||
showValue={true}
|
||||
disabled={get_value(CLOCK_MAX_GPU) == null}
|
||||
onChange={(val: number) => {
|
||||
backend.log(backend.LogLevel.Debug, "GPU Clock Max is now " + val.toString());
|
||||
const valNow = get_value(CLOCK_MAX_GPU);
|
||||
const minNow = get_value(CLOCK_MIN_GPU);
|
||||
if (val != valNow && ((minNow != null && val >= minNow) || minNow == null)) {
|
||||
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>
|
||||
{(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.memory_control_capable && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(SLOW_MEMORY_GPU)}
|
||||
label={tr("Downclock Memory")}
|
||||
description={tr("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>}
|
||||
</Fragment>);
|
||||
}
|
||||
}
|
29
src/consts.ts
Normal file
29
src/consts.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
export const BACKEND_INFO = "VINFO";
|
||||
export const DRIVER_INFO = "GENERAL_provider";
|
||||
|
||||
export const LIMITS_INFO = "LIMITS_all";
|
||||
|
||||
export const CURRENT_BATT = "BATTERY_current_now";
|
||||
export const CHARGE_RATE_BATT = "BATTERY_charge_rate";
|
||||
export const CHARGE_MODE_BATT = "BATTERY_charge_mode";
|
||||
export const CHARGE_NOW_BATT = "BATTERY_charge_now";
|
||||
export const CHARGE_FULL_BATT = "BATTERY_charge_full";
|
||||
export const CHARGE_DESIGN_BATT = "BATTERY_charge_design";
|
||||
|
||||
//export const TOTAL_CPUS = "CPUs_total";
|
||||
export const ONLINE_CPUS = "CPUs_online";
|
||||
export const ONLINE_STATUS_CPUS = "CPUs_status_online";
|
||||
export const SMT_CPU = "CPUs_SMT";
|
||||
export const CLOCK_MIN_CPU = "CPUs_min_clock";
|
||||
export const CLOCK_MAX_CPU = "CPUs_max_clock";
|
||||
export const CLOCK_MIN_MAX_CPU = "CPUs_minmax_clocks";
|
||||
export const GOVERNOR_CPU = "CPUs_governor";
|
||||
|
||||
export const FAST_PPT_GPU = "GPU_fastPPT";
|
||||
export const SLOW_PPT_GPU = "GPU_slowPPT";
|
||||
export const CLOCK_MIN_GPU = "GPU_min_clock";
|
||||
export const CLOCK_MAX_GPU = "GPU_max_clock";
|
||||
export const SLOW_MEMORY_GPU = "GPU_slow_memory";
|
||||
|
||||
export const PERSISTENT_GEN = "GENERAL_persistent";
|
||||
export const NAME_GEN = "GENERAL_name";
|
584
src/index.tsx
584
src/index.tsx
|
@ -6,55 +6,70 @@ import {
|
|||
//MenuItem,
|
||||
PanelSection,
|
||||
PanelSectionRow,
|
||||
//Router,
|
||||
ServerAPI,
|
||||
//showContextMenu,
|
||||
staticClasses,
|
||||
SliderField,
|
||||
//SliderField,
|
||||
ToggleField,
|
||||
//Dropdown,
|
||||
Field,
|
||||
//DropdownOption,
|
||||
//SingleDropdownOption,
|
||||
//NotchLabel
|
||||
gamepadDialogClasses,
|
||||
joinClassNames,
|
||||
//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";
|
||||
import { tr } from "usdpl-front";
|
||||
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";
|
||||
import { Battery } from "./components/battery";
|
||||
import { Cpus } from "./components/cpus";
|
||||
|
||||
var periodicHook: NodeJS.Timer | null = null;
|
||||
var lifetimeHook: any = null;
|
||||
var startHook: any = null;
|
||||
var usdplReady = false;
|
||||
|
||||
var smtAllowed = true;
|
||||
var smtGlobal = smtAllowed;
|
||||
|
||||
// usdpl persistent store keys
|
||||
|
||||
const BACKEND_INFO = "VINFO";
|
||||
|
||||
const CURRENT_BATT = "BATTERY_current_now";
|
||||
const CHARGE_RATE_BATT = "BATTERY_charge_rate";
|
||||
const CHARGE_NOW_BATT = "BATTERY_charge_now";
|
||||
const CHARGE_FULL_BATT = "BATTERY_charge_full";
|
||||
const CHARGE_DESIGN_BATT = "BATTERY_charge_design"
|
||||
|
||||
const TOTAL_CPUS = "CPUs_total";
|
||||
const ONLINE_CPUS = "CPUs_online";
|
||||
const CLOCK_MIN_CPU = "CPUs_min_clock";
|
||||
const CLOCK_MAX_CPU = "CPUs_max_clock";
|
||||
const GOVERNOR_CPU = "CPUs_governor";
|
||||
|
||||
const FAST_PPT_GPU = "GPU_fastPPT";
|
||||
const SLOW_PPT_GPU = "GPU_slowPPT";
|
||||
const CLOCK_MIN_GPU = "GPU_min_clock";
|
||||
const CLOCK_MAX_GPU = "GPU_max_clock";
|
||||
const SLOW_MEMORY_GPU = "GPU_slow_memory";
|
||||
|
||||
const PERSISTENT_GEN = "GENERAL_persistent";
|
||||
const NAME_GEN = "GENERAL_name";
|
||||
type MinMax = {
|
||||
min: number | null;
|
||||
max: number | null;
|
||||
}
|
||||
|
||||
function countCpus(statii: boolean[]): number {
|
||||
let count = 0;
|
||||
|
@ -66,29 +81,54 @@ function countCpus(statii: boolean[]): number {
|
|||
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_nullable(backend.getBatteryChargeRate(), (rate: number | null) => { set_value(CHARGE_RATE_BATT, rate) });
|
||||
backend.resolve_nullable(backend.getBatteryChargeMode(), (mode: string | null) => { 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.getCpuCount(), (count: number) => { set_value(TOTAL_CPUS, count)});
|
||||
backend.resolve(backend.getCpusOnline(), (statii: boolean[]) => {
|
||||
// TODO: allow for per-core control of online status
|
||||
set_value(ONLINE_STATUS_CPUS, statii);
|
||||
const count = countCpus(statii);
|
||||
set_value(ONLINE_CPUS, count);
|
||||
smtGlobal = statii.length > 3 && statii[0] == statii[1] && statii[2] == statii[3] && smtAllowed;
|
||||
set_value(SMT_CPU, statii.length > 3 && statii[0] == statii[1] && statii[2] == statii[3]);
|
||||
});
|
||||
backend.resolve(backend.getCpuSmt(), (smt: boolean) => {
|
||||
set_value(SMT_CPU, smt);
|
||||
});
|
||||
// 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]);
|
||||
syncPlebClockToAdvanced();
|
||||
});
|
||||
backend.resolve(backend.getCpusGovernor(), (governors: string[]) => {
|
||||
set_value(GOVERNOR_CPU, governors);
|
||||
backend.log(backend.LogLevel.Info, "POWERTOOLS: Governors from backend " + governors.toString());
|
||||
});
|
||||
// TODO: allow for control of governor
|
||||
backend.resolve(backend.getCpusGovernor(), (governors: string[]) => { set_value(GOVERNOR_CPU, governors[0]) });
|
||||
|
||||
backend.resolve(backend.getGpuPpt(), (ppts: number[]) => {
|
||||
set_value(FAST_PPT_GPU, ppts[0]);
|
||||
|
@ -104,6 +144,7 @@ const reload = function() {
|
|||
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
|
||||
|
@ -117,12 +158,12 @@ const reload = function() {
|
|||
//@ts-ignore
|
||||
lifetimeHook = SteamClient.GameSessions.RegisterForAppLifetimeNotifications((update) => {
|
||||
if (update.bRunning) {
|
||||
//console.debug("AppID " + update.unAppID.toString() + " is now running");
|
||||
//backend.log(backend.LogLevel.Debug, "AppID " + update.unAppID.toString() + " is now running");
|
||||
} else {
|
||||
//console.debug("AppID " + update.unAppID.toString() + " is no longer running");
|
||||
//backend.log(backend.LogLevel.Debug, "AppID " + update.unAppID.toString() + " is no longer running");
|
||||
backend.resolve(
|
||||
backend.loadGeneralDefaultSettings(),
|
||||
(ok: boolean) => {console.debug("Loading default settings ok? " + ok)}
|
||||
(ok: boolean) => {backend.log(backend.LogLevel.Debug, "Loading default settings ok? " + ok)}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -133,11 +174,11 @@ const reload = function() {
|
|||
// don't use gameInfo.appid, haha
|
||||
backend.resolve(
|
||||
backend.loadGeneralSettings(id.toString() + ".json", gameInfo.display_name),
|
||||
(ok: boolean) => {console.debug("Loading settings ok? " + ok)}
|
||||
(ok: boolean) => {backend.log(backend.LogLevel.Debug, "Loading settings ok? " + ok)}
|
||||
);
|
||||
});
|
||||
|
||||
console.debug("Registered PowerTools callbacks, hello!");
|
||||
backend.log(backend.LogLevel.Debug, "Registered PowerTools callbacks, hello!");
|
||||
})();
|
||||
|
||||
const periodicals = function() {
|
||||
|
@ -169,390 +210,26 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
|||
reloadGUI("periodic" + (new Date()).getTime().toString());
|
||||
}, 1000);
|
||||
|
||||
const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard);
|
||||
|
||||
const total_cpus = get_value(TOTAL_CPUS);
|
||||
|
||||
return (
|
||||
<PanelSection>
|
||||
{/* CPU */ /* TODO: set per-core stuff*/}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
CPU
|
||||
</div>
|
||||
{smtAllowed && <PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={smtGlobal}
|
||||
label="SMT"
|
||||
description="Enables odd-numbered CPUs"
|
||||
onChange={(smt: boolean) => {
|
||||
console.debug("SMT is now " + smt.toString());
|
||||
const cpus = get_value(ONLINE_CPUS);
|
||||
smtGlobal = smt && smtAllowed;
|
||||
// TODO: move SMT setting logic back to back-end
|
||||
let onlines: boolean[] = [];
|
||||
for (let i = 0; i < total_cpus; i++) {
|
||||
const online = (smtGlobal? i < cpus : (i % 2 == 0) && (i < cpus * 2))
|
||||
|| (!smtGlobal && cpus == 4);
|
||||
onlines.push(online);
|
||||
}
|
||||
backend.resolve(backend.setCpuOnlines(onlines), (statii: boolean[]) => {
|
||||
// TODO: allow for per-core control of online status
|
||||
const count = countCpus(statii);
|
||||
set_value(ONLINE_CPUS, count);
|
||||
reloadGUI("SMT");
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>}
|
||||
<PanelSectionRow>
|
||||
<SliderField
|
||||
label="Threads"
|
||||
value={get_value(ONLINE_CPUS)}
|
||||
step={1}
|
||||
max={smtGlobal? total_cpus : total_cpus/2}
|
||||
min={1}
|
||||
showValue={true}
|
||||
onChange={(cpus: number) => {
|
||||
console.debug("CPU slider is now " + cpus.toString());
|
||||
const onlines = get_value(ONLINE_CPUS);
|
||||
if (cpus != onlines) {
|
||||
set_value(ONLINE_CPUS, cpus);
|
||||
let onlines: boolean[] = [];
|
||||
for (let i = 0; i < total_cpus; i++) {
|
||||
const online = smtGlobal? i < cpus : (i % 2 == 0) && (i < cpus * 2);
|
||||
onlines.push(online);
|
||||
}
|
||||
backend.resolve(backend.setCpuOnlines(onlines), (statii: boolean[]) => {
|
||||
// TODO: allow for per-core control of online status
|
||||
const count = countCpus(statii);
|
||||
set_value(ONLINE_CPUS, count);
|
||||
reloadGUI("CPUs");
|
||||
});
|
||||
reloadGUI("CPUsImmediate");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(CLOCK_MIN_CPU) != null && get_value(CLOCK_MAX_CPU) != null}
|
||||
label="Frequency Limits"
|
||||
description="Set bounds on clock speed"
|
||||
onChange={(value: boolean) => {
|
||||
if (value) {
|
||||
set_value(CLOCK_MIN_CPU, 1400);
|
||||
set_value(CLOCK_MAX_CPU, 3500);
|
||||
reloadGUI("CPUFreqToggle");
|
||||
} else {
|
||||
set_value(CLOCK_MIN_CPU, null);
|
||||
set_value(CLOCK_MAX_CPU, null);
|
||||
for (let i = 0; i < total_cpus; i++) {
|
||||
backend.resolve(backend.unsetCpuClockLimits(i), (_idc: any[]) => {});
|
||||
}
|
||||
backend.resolve(backend.waitForComplete(), (_: boolean[]) => {
|
||||
reloadGUI("CPUUnsetFreq");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</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) {
|
||||
set_value(CLOCK_MIN_CPU, freq);
|
||||
for (let i = 0; i < total_cpus; i++) {
|
||||
backend.resolve(backend.setCpuClockLimits(i, freq, get_value(CLOCK_MAX_CPU)),
|
||||
(limits: number[]) => {
|
||||
set_value(CLOCK_MIN_CPU, limits[0]);
|
||||
set_value(CLOCK_MAX_CPU, limits[1]);
|
||||
});
|
||||
}
|
||||
backend.resolve(backend.waitForComplete(), (_: boolean[]) => {
|
||||
reloadGUI("CPUMinFreq");
|
||||
});
|
||||
reloadGUI("CPUMinFreqImmediate");
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
</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) {
|
||||
set_value(CLOCK_MAX_CPU, freq);
|
||||
for (let i = 0; i < total_cpus; i++) {
|
||||
backend.resolve(backend.setCpuClockLimits(i, get_value(CLOCK_MIN_CPU), freq),
|
||||
(limits: number[]) => {
|
||||
set_value(CLOCK_MIN_CPU, limits[0]);
|
||||
set_value(CLOCK_MAX_CPU, limits[1]);
|
||||
});
|
||||
}
|
||||
backend.resolve(backend.waitForComplete(), (_: boolean[]) => {
|
||||
reloadGUI("CPUMaxFreq");
|
||||
});
|
||||
reloadGUI("CPUMaxFreqImmediate");
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
</PanelSectionRow>
|
||||
{/* TODO: CPU governor */}
|
||||
{/* GPU */}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
GPU
|
||||
</div>
|
||||
<PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(SLOW_PPT_GPU) != null && get_value(FAST_PPT_GPU) != null}
|
||||
label="PowerPlay Limits"
|
||||
description="Override APU TDP settings"
|
||||
onChange={(value: boolean) => {
|
||||
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");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
{ get_value(SLOW_PPT_GPU) != null && <SliderField
|
||||
label="SlowPPT (uW)"
|
||||
value={get_value(SLOW_PPT_GPU)}
|
||||
max={29000000}
|
||||
min={1000000}
|
||||
step={1000000}
|
||||
showValue={true}
|
||||
disabled={get_value(SLOW_PPT_GPU) == null}
|
||||
onChange={(ppt: number) => {
|
||||
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");
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
</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"
|
||||
description="Override bounds on gpu clock"
|
||||
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_MAX_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>
|
||||
{/* Battery */}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
Battery
|
||||
</div>
|
||||
<PanelSectionRow>
|
||||
<div className={FieldWithSeparator}>
|
||||
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||
<div className={gamepadDialogClasses.FieldLabel}>
|
||||
Now (Charge)
|
||||
</div>
|
||||
<div className={gamepadDialogClasses.FieldChildren}>
|
||||
{get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
<div className={FieldWithSeparator}>
|
||||
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||
<div className={gamepadDialogClasses.FieldLabel}>
|
||||
Max (Design)
|
||||
</div>
|
||||
<div className={gamepadDialogClasses.FieldChildren}>
|
||||
{get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(CHARGE_RATE_BATT) != null}
|
||||
label="Charge Current Limits"
|
||||
description="Control battery charge rate when awake"
|
||||
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>
|
||||
</PanelSectionRow>
|
||||
<Cpus />
|
||||
|
||||
<Gpu />
|
||||
|
||||
<Battery />
|
||||
|
||||
|
||||
{/* Persistence */}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
Miscellaneous
|
||||
{tr("Miscellaneous")}
|
||||
</div>
|
||||
<PanelSectionRow>
|
||||
<ToggleField
|
||||
checked={get_value(PERSISTENT_GEN)}
|
||||
label="Persistent"
|
||||
description="Restores settings after an app or OS restart"
|
||||
label={tr("Persistent Profile")}
|
||||
description={tr("Save profile and load it next time")}
|
||||
onChange={(persist: boolean) => {
|
||||
console.debug("Persist is now " + persist.toString());
|
||||
backend.log(backend.LogLevel.Debug, "Persist is now " + persist.toString());
|
||||
backend.resolve(
|
||||
backend.setGeneralPersistent(persist),
|
||||
(val: boolean) => {set_value(PERSISTENT_GEN, val)}
|
||||
|
@ -561,75 +238,32 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
|||
/>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
<div className={FieldWithSeparator}>
|
||||
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||
<div className={gamepadDialogClasses.FieldLabel}>
|
||||
Profile
|
||||
</div>
|
||||
<div className={gamepadDialogClasses.FieldChildren}>
|
||||
{get_value(NAME_GEN)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PanelSectionRow>
|
||||
{/* Version Info */}
|
||||
<div className={staticClasses.PanelSectionTitle}>
|
||||
Debug
|
||||
</div>
|
||||
<PanelSectionRow>
|
||||
<div className={FieldWithSeparator}>
|
||||
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||
<div className={gamepadDialogClasses.FieldLabel}>
|
||||
Native
|
||||
</div>
|
||||
<div className={gamepadDialogClasses.FieldChildren}>
|
||||
{get_value(BACKEND_INFO)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
<Field
|
||||
label={tr("Profile")}>
|
||||
{get_value(NAME_GEN)}
|
||||
</Field>
|
||||
</PanelSectionRow>
|
||||
|
||||
<Debug/>
|
||||
|
||||
<PanelSectionRow>
|
||||
<ButtonItem
|
||||
layout="below"
|
||||
onClick={(_: MouseEvent) => {
|
||||
console.debug("Loading default PowerTools settings");
|
||||
backend.log(backend.LogLevel.Debug, "Loading default PowerTools settings");
|
||||
backend.resolve(
|
||||
backend.setGeneralPersistent(false),
|
||||
(val: boolean) => {
|
||||
set_value(PERSISTENT_GEN, val);
|
||||
backend.resolve(backend.loadGeneralDefaultSettings(), (_: any[]) => {
|
||||
backend.resolve(backend.loadGeneralSystemSettings(), (_) => {
|
||||
reload();
|
||||
backend.resolve(backend.waitForComplete(), (_: any[]) => {reloadGUI("LoadDefaults")});
|
||||
backend.resolve(backend.waitForComplete(), (_) => {reloadGUI("LoadSystemDefaults")});
|
||||
});
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
Defaults
|
||||
{tr("Defaults")}
|
||||
</ButtonItem>
|
||||
</PanelSectionRow>
|
||||
</PanelSection>
|
||||
|
@ -642,13 +276,13 @@ export default definePlugin((serverApi: ServerAPI) => {
|
|||
content: <Content serverAPI={serverApi} />,
|
||||
icon: <GiDrill />,
|
||||
onDismount() {
|
||||
console.debug("PowerTools shutting down");
|
||||
backend.log(backend.LogLevel.Debug, "PowerTools shutting down");
|
||||
clearInterval(periodicHook!);
|
||||
periodicHook = null;
|
||||
lifetimeHook!.unregister();
|
||||
startHook!.unregister();
|
||||
serverApi.routerHook.removeRoute("/decky-plugin-test");
|
||||
console.debug("Unregistered PowerTools callbacks, goodbye.");
|
||||
backend.log(backend.LogLevel.Debug, "Unregistered PowerTools callbacks, so long and thanks for all the fish.");
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
139
src/python.ts
139
src/python.ts
|
@ -1,139 +0,0 @@
|
|||
import { ServerAPI } from "decky-frontend-lib";
|
||||
|
||||
var server: ServerAPI | undefined = undefined;
|
||||
|
||||
//import { useEffect } from "react";
|
||||
|
||||
export function resolve(promise: Promise<any>, setter: any) {
|
||||
(async function () {
|
||||
let data = await promise;
|
||||
if (data.success) {
|
||||
console.debug("Got resolved", data, "promise", promise);
|
||||
setter(data.result);
|
||||
} else {
|
||||
console.warn("Resolve failed:", data, "promise", promise);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
export function execute(promise: Promise<any>) {
|
||||
(async function () {
|
||||
let data = await promise;
|
||||
if (data.success) {
|
||||
console.debug("Got executed", data, "promise", promise);
|
||||
} else {
|
||||
console.warn("Execute failed:", data, "promise", promise);
|
||||
}
|
||||
|
||||
})();
|
||||
}
|
||||
|
||||
export function setServer(s: ServerAPI) {
|
||||
server = s;
|
||||
}
|
||||
|
||||
// Python functions
|
||||
export function getVersion(): Promise<any> {
|
||||
return server!.callPluginMethod("get_version", {});
|
||||
}
|
||||
|
||||
export function onViewReady(): Promise<any> {
|
||||
return server!.callPluginMethod("on_ready", {});
|
||||
}
|
||||
|
||||
export function setCPUs(value: number, smt: boolean): Promise<any> {
|
||||
return server!.callPluginMethod("set_cpus", {"count":value, "smt": smt});
|
||||
}
|
||||
|
||||
export function getCPUs(): Promise<any> {
|
||||
return server!.callPluginMethod("get_cpus", {});
|
||||
}
|
||||
|
||||
export function getSMT(): Promise<any> {
|
||||
return server!.callPluginMethod("get_smt", {});
|
||||
}
|
||||
|
||||
export function setCPUBoost(value: boolean): Promise<any> {
|
||||
return server!.callPluginMethod("set_boost", {"enabled": value});
|
||||
}
|
||||
|
||||
export function getCPUBoost(): Promise<any> {
|
||||
return server!.callPluginMethod("get_boost", {});
|
||||
}
|
||||
|
||||
export function setMaxBoost(index: number): Promise<any> {
|
||||
return server!.callPluginMethod("set_max_boost", {"index": index});
|
||||
}
|
||||
|
||||
export function getMaxBoost(): Promise<any> {
|
||||
return server!.callPluginMethod("get_max_boost", {});
|
||||
}
|
||||
|
||||
export function setGPUPower(value: number, index: number): Promise<any> {
|
||||
return server!.callPluginMethod("set_gpu_power", {"value": value, "power_number": index});
|
||||
}
|
||||
|
||||
export function getGPUPower(index: number): Promise<any> {
|
||||
return server!.callPluginMethod("get_gpu_power", {"power_number": index});
|
||||
}
|
||||
|
||||
export function setGPUPowerI(value: number, index: number): Promise<any> {
|
||||
return server!.callPluginMethod("set_gpu_power_index", {"index": value, "power_number": index});
|
||||
}
|
||||
|
||||
export function getGPUPowerI(index: number): Promise<any> {
|
||||
return server!.callPluginMethod("get_gpu_power_index", {"power_number": index});
|
||||
}
|
||||
|
||||
export function setFanTick(tick: number): Promise<any> {
|
||||
return server!.callPluginMethod("set_fan_tick", {"tick": tick});
|
||||
}
|
||||
|
||||
export function getFanTick(): Promise<any> {
|
||||
return server!.callPluginMethod("get_fan_tick", {});
|
||||
}
|
||||
|
||||
export function getFantastic(): Promise<any> {
|
||||
return server!.callPluginMethod("fantastic_installed", {});
|
||||
}
|
||||
|
||||
export function getChargeNow(): Promise<any> {
|
||||
return server!.callPluginMethod("get_charge_now", {});
|
||||
}
|
||||
|
||||
export function getChargeFull(): Promise<any> {
|
||||
return server!.callPluginMethod("get_charge_full", {});
|
||||
}
|
||||
|
||||
export function getChargeDesign(): Promise<any> {
|
||||
return server!.callPluginMethod("get_charge_design", {});
|
||||
}
|
||||
|
||||
export function setPersistent(value: boolean): Promise<any> {
|
||||
return server!.callPluginMethod("set_persistent", {"enabled": value});
|
||||
}
|
||||
|
||||
export function getPersistent(): Promise<any> {
|
||||
return server!.callPluginMethod("get_persistent", {});
|
||||
}
|
||||
|
||||
export function setPerGameProfile(value: boolean): Promise<any> {
|
||||
return server!.callPluginMethod("set_per_game_profile", {"enabled": value});
|
||||
}
|
||||
|
||||
export function getPerGameProfile(): Promise<any> {
|
||||
return server!.callPluginMethod("get_per_game_profile", {});
|
||||
}
|
||||
|
||||
export function getCurrentGame(): Promise<any> {
|
||||
return server!.callPluginMethod("get_current_game", {});
|
||||
}
|
||||
|
||||
export function onGameStart(gameId: number, data: any): Promise<any> {
|
||||
const data2 = {appid: data.appid, display_name: data.display_name, gameid: gameId}; // Issue #17
|
||||
return server!.callPluginMethod("on_game_start", {"game_id": gameId, "data":data2});
|
||||
}
|
||||
|
||||
export function onGameStop(gameId: number | null): Promise<any> {
|
||||
return server!.callPluginMethod("on_game_stop", {"game_id": gameId});
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
"NGnius (Graham) <ngniusness@gmail.com>"
|
||||
],
|
||||
"description": "Universal Steam Deck Plugin Library front-end designed for WASM",
|
||||
"version": "0.6.2",
|
||||
"version": "0.9.1",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
30
src/usdpl_front/usdpl_front.d.ts
vendored
30
src/usdpl_front/usdpl_front.d.ts
vendored
|
@ -36,6 +36,25 @@ export function get_value(key: string): any;
|
|||
* @returns {Promise<any>}
|
||||
*/
|
||||
export function call_backend(name: string, parameters: any[]): Promise<any>;
|
||||
/**
|
||||
* Initialize translation strings for the front-end
|
||||
* @param {string} locale
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function init_tr(locale: string): Promise<void>;
|
||||
/**
|
||||
* Translate a phrase, equivalent to tr_n(msg_id, 0)
|
||||
* @param {string} msg_id
|
||||
* @returns {string}
|
||||
*/
|
||||
export function tr(msg_id: string): string;
|
||||
/**
|
||||
* Translate a phrase, retrieving the plural form for `n` items
|
||||
* @param {string} msg_id
|
||||
* @param {number} n
|
||||
* @returns {string}
|
||||
*/
|
||||
export function tr_n(msg_id: string, n: number): string;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
|
@ -47,6 +66,9 @@ export interface InitOutput {
|
|||
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 init_tr: (a: number, b: number) => number;
|
||||
readonly tr: (a: number, b: number, c: number) => void;
|
||||
readonly tr_n: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly __wbindgen_export_0: (a: number) => number;
|
||||
readonly __wbindgen_export_1: (a: number, b: number, c: number) => number;
|
||||
readonly __wbindgen_export_2: WebAssembly.Table;
|
||||
|
@ -57,14 +79,16 @@ export interface InitOutput {
|
|||
readonly __wbindgen_export_6: (a: number, b: number, c: number, d: number) => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
/**
|
||||
* Synchronously compiles the given `bytes` and instantiates the WebAssembly module.
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {BufferSource} bytes
|
||||
* @param {SyncInitInput} module
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(bytes: BufferSource): InitOutput;
|
||||
export function initSync(module: SyncInitInput): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
3
src/usdpl_front/usdpl_front_bg.wasm.d.ts
vendored
3
src/usdpl_front/usdpl_front_bg.wasm.d.ts
vendored
|
@ -7,6 +7,9 @@ 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 init_tr(a: number, b: number): number;
|
||||
export function tr(a: number, b: number, c: number): void;
|
||||
export function tr_n(a: number, b: number, c: number, d: number): void;
|
||||
export function __wbindgen_export_0(a: number): number;
|
||||
export function __wbindgen_export_1(a: number, b: number, c: number): number;
|
||||
export const __wbindgen_export_2: WebAssembly.Table;
|
||||
|
|
13
translations/build.py
Executable file
13
translations/build.py
Executable file
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
if __name__ == "__main__":
|
||||
for item in os.listdir("."):
|
||||
if item[-3:] == ".po":
|
||||
print("Generating binary translation file from", item)
|
||||
subprocess.run(["msgfmt", "-c", "-o", item[:-2]+"mo", item])
|
||||
else:
|
||||
print("Ignoring", item)
|
||||
|
BIN
translations/es-ES.mo
Normal file
BIN
translations/es-ES.mo
Normal file
Binary file not shown.
234
translations/es-ES.po
Normal file
234
translations/es-ES.po
Normal file
|
@ -0,0 +1,234 @@
|
|||
# TEMPLATE TITLE.
|
||||
# Copyright (C) 2023 NGnius
|
||||
# This file is distributed under the same license as the PowerTools package.
|
||||
# NGnius (Graham) <ngniusness@gmail.com>, 2023.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: v1.1\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/NGnius/PowerTools/issues\n"
|
||||
"POT-Creation-Date: 2023-01-09 19:52-0500\n"
|
||||
"PO-Revision-Date: 2023-01-18 19:52-0500\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: es-ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
# -- index.tsx --
|
||||
|
||||
#: index.tsx:226
|
||||
# (Section title)
|
||||
msgid "Miscellaneous"
|
||||
msgstr "Misceláneo"
|
||||
|
||||
#: index.tsx:226
|
||||
# (Profile persistence toggle)
|
||||
msgid "Persistent Profile"
|
||||
msgstr "Persistente"
|
||||
|
||||
#: index.tsx:227
|
||||
# (Profile persistence toggle description)
|
||||
msgid "Save profile and load it next time"
|
||||
msgstr "Guardar perfil y cargarlo la próxima vez"
|
||||
|
||||
#: index.tsx:239
|
||||
# (Profile display)
|
||||
msgid "Profile"
|
||||
msgstr "Perfil"
|
||||
|
||||
#: index.tsx:266
|
||||
# (Button to reset everything to system defaults)
|
||||
msgid "Defaults"
|
||||
msgstr "Valores predeterminados"
|
||||
|
||||
# -- components/battery.tsx --
|
||||
|
||||
#: components/battery.tsx:42
|
||||
# (Battery section title)
|
||||
msgid "Battery"
|
||||
msgstr "Batería"
|
||||
|
||||
#: components/battery.tsx:46
|
||||
# (Charge of battery at this moment, with percentage of expected full charge in brackets)
|
||||
msgid "Now (Charge)"
|
||||
msgstr "Ahora (Charge)"
|
||||
|
||||
#: components/battery.tsx:52
|
||||
# (Maximum capacity of battery, with percentage of design capacity in brackets)
|
||||
msgid "Max (Design)"
|
||||
msgstr "Max (Design)"
|
||||
|
||||
#: components/battery.tsx:59
|
||||
# (Charge current limit override toggle)
|
||||
msgid "Charge Current Limits"
|
||||
msgstr "Limite de carga"
|
||||
|
||||
#: components/battery.tsx:60
|
||||
# (Charge current limit override toggle description)
|
||||
msgid "Control battery charge rate when awake"
|
||||
msgstr "Control de carga de la batería mientras está encendido"
|
||||
|
||||
#: components/battery.tsx:74
|
||||
# (Battery maximum input current with unit)
|
||||
msgid "Maximum (mA)"
|
||||
msgstr "Máximo (mA)"
|
||||
|
||||
#: components/battery.tsx:97,115
|
||||
# (Battery charge mode override toggle)
|
||||
msgid "Charge Mode"
|
||||
msgstr "Modo carga"
|
||||
|
||||
#: components/battery.tsx:98
|
||||
# (Battery charge mode override toggle description)
|
||||
msgid "Force battery charge mode"
|
||||
msgstr "Forzar modo carga de la batería"
|
||||
|
||||
#: components/battery.tsx:112
|
||||
# (Battery charge mode dropdown)
|
||||
msgid "Mode"
|
||||
msgstr "Modo"
|
||||
|
||||
#: components/battery.tsx:133
|
||||
# (Battery current display)
|
||||
msgid "Current"
|
||||
msgstr "Corriente"
|
||||
|
||||
# -- components/cpus.tsx --
|
||||
|
||||
#: components/cpus.tsx:64
|
||||
# (CPU section title)
|
||||
msgid "CPU"
|
||||
msgstr "CPU"
|
||||
|
||||
#: components/cpus.tsx:70
|
||||
# (CPU advanced mode toggle)
|
||||
msgid "Advanced"
|
||||
msgstr "Avanzado"
|
||||
|
||||
#: components/cpus.tsx:71
|
||||
# (CPU advanced mode toggle description)
|
||||
msgid "Enables per-thread configuration"
|
||||
msgstr "Habilita la configuración por subprocesos"
|
||||
|
||||
#: components/cpus.tsx:88
|
||||
# (CPU Simultaneous MultiThreading toggle)
|
||||
msgid "SMT"
|
||||
msgstr "SMT"
|
||||
|
||||
#: components/cpus.tsx:89
|
||||
# (CPU SMT toggle description)
|
||||
msgid "Enables odd-numbered CPUs"
|
||||
msgstr "Habilita CPUs impares"
|
||||
|
||||
#: components/cpus.tsx:106
|
||||
# (CPU thread count slider)
|
||||
msgid "Threads"
|
||||
msgstr "Subprocesos"
|
||||
|
||||
#: components/cpus.tsx:137
|
||||
#: components/gpu.tsx:112
|
||||
# (Clock speed override toggle)
|
||||
msgid "Frequency Limits"
|
||||
msgstr "Límites de frecuencia"
|
||||
|
||||
#: components/cpus.tsx:138
|
||||
#: components/gpu.tsx:113
|
||||
# (Clock speed override toggle description)
|
||||
msgid "Set bounds on clock speed"
|
||||
msgstr "Establecer límites en la velocidad de reloj"
|
||||
|
||||
#: components/cpus.tsx:165
|
||||
#: components/gpu.tsx:137
|
||||
# (Minimum clock speed with unit)
|
||||
msgid "Minimum (MHz)"
|
||||
msgstr "Mínimo (MHz)"
|
||||
|
||||
#: components/cpus.tsx:195
|
||||
#: components/gpu.tsx:160
|
||||
# (Maximum clock speed with unit)
|
||||
msgid "Maximum (MHz)"
|
||||
msgstr "Máximo (MHz)"
|
||||
|
||||
# advanced mode
|
||||
|
||||
#: components/cpus.tsx:226
|
||||
# (CPU selection slider)
|
||||
msgid "Selected CPU"
|
||||
msgstr "CPU seleccionada"
|
||||
|
||||
#: components/cpus.tsx:246
|
||||
# (CPU Online toggle)
|
||||
msgid "Online"
|
||||
msgstr "Online"
|
||||
|
||||
#: components/cpus.tsx:247
|
||||
# (CPU Online description)
|
||||
msgid "Allow the CPU thread to do work"
|
||||
msgstr "Permite que el subproceso de la CPU funcione"
|
||||
|
||||
#: components/cpus.tsx:342
|
||||
# (CPU scheduling governor dropdown -- governor names are not translated)
|
||||
msgid "Governor"
|
||||
msgstr "Gobernador"
|
||||
|
||||
# -- components/debug.tsx --
|
||||
|
||||
#: components/debug.tsx:29
|
||||
# (Debug section title)
|
||||
msgid "Debug"
|
||||
msgstr "Depurar"
|
||||
|
||||
#: components/debug.tsx:33
|
||||
# (Version display for native back-end of PowerTools)
|
||||
msgid "Native"
|
||||
msgstr "Nativo"
|
||||
|
||||
#: components/debug.tsx:47
|
||||
# (Mode display for framework of USDPL API)
|
||||
msgid "Framework"
|
||||
msgstr "Framework"
|
||||
|
||||
#: components/debug.tsx:54
|
||||
# (Display for software implementation in PowerTools which applies settings)
|
||||
msgid "Driver"
|
||||
msgstr "Controlador"
|
||||
|
||||
# -- components/gpu.tsx --
|
||||
|
||||
#: components/gpu.tsx:34
|
||||
# (GPU section title)
|
||||
msgid "GPU"
|
||||
msgstr "GPU"
|
||||
|
||||
#: components/gpu.tsx:39
|
||||
# (PPT Limits override toggle)
|
||||
msgid "PowerPlay Limits"
|
||||
msgstr "Límites de PowerPlay"
|
||||
|
||||
#: components/gpu.tsx:40
|
||||
# (PPT Limits override toggle description)
|
||||
msgid "Override APU TDP settings"
|
||||
msgstr "Anular la configuración del APU TDP"
|
||||
|
||||
#: components/gpu.tsx:63
|
||||
# (SlowPPT slider with unit)
|
||||
msgid "SlowPPT (W)"
|
||||
msgstr "SlowPPT (W)"
|
||||
|
||||
#: components/gpu.tsx:87
|
||||
# (FastPPT slider with unit)
|
||||
msgid "FastPPT (W)"
|
||||
msgstr "FastPPT (W)"
|
||||
|
||||
#: components/gpu.tsx:112
|
||||
# (Reduce memory clock speed toggle)
|
||||
msgid "Downclock Memory"
|
||||
msgstr "Memoria descendente"
|
||||
|
||||
#: components/gpu.tsx:112
|
||||
# (Reduce memory clock speed toggle description)
|
||||
msgid "Force RAM into low-power mode"
|
||||
msgstr "Forzar RAM a modo ahorro de energía"
|
BIN
translations/fr-CA.mo
Normal file
BIN
translations/fr-CA.mo
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue