Merge branch 'dev'

This commit is contained in:
NGnius (Graham) 2023-02-03 17:40:03 -05:00
commit 1ca1148a4c
109 changed files with 9502 additions and 1994 deletions

5
.gitignore vendored
View file

@ -44,3 +44,8 @@ yalc.lock
/backend/target
/bin
/backend/out
/**/target
# packaging
/PowerTools
**.zip

87
Makefile Normal file
View 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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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" ]

View file

@ -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
View 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"

View 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"

View 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())
}
}
}

View 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 */
}

View 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()
}
}

View 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>,
}

View 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,
}

View 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>,
}

View 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),
}

View 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;

View 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,
}

View file

@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Target {
SteamDeck,
SteamDeckAdvance,
Generic,
Unknown,
}

View file

@ -0,0 +1 @@
pub mod json;

1026
backend/limits_srv/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View 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"] }

View 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"
}

View 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;
}

View 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,
}

View 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)
}
}

View file

@ -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()]
}
}

View file

@ -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) = &params_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
}

View file

@ -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),
}
}

View file

@ -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
View 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)
}
}

View file

@ -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::*;

View file

@ -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
View 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!");
})
}

View file

@ -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";

View file

@ -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()
}

View file

@ -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,
}
}
}

View file

@ -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>>,

View 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,
}

View file

@ -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,

View file

@ -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>,

View file

@ -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;

View file

@ -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()

View file

@ -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(),
}
}
}

View file

@ -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
)
}

View 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))
}
}
}

View 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)
}
}

View file

@ -0,0 +1,5 @@
mod auto_detect;
pub mod limits_worker;
mod utility;
pub use auto_detect::{auto_detect_provider, auto_detect0};

View file

@ -0,0 +1,3 @@
pub fn limits_path() -> std::path::PathBuf {
crate::utility::settings_dir().join(crate::consts::LIMITS_FILE)
}

View 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???")
}
}

View file

@ -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()),
}
}
}
}*/

View 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
}
}

View 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
)
}

View 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
}
}

View 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;

View 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;
}

View 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()
}
}

View 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
}
}

View file

@ -0,0 +1,5 @@
mod cpu;
mod gpu;
pub use cpu::{Cpu, Cpus};
pub use gpu::Gpu;

View file

@ -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 {

View file

@ -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 {

View 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()
}
}

View 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
)
}

View file

@ -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()
}
}

View 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;

View 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)
}

View 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,
}

View file

@ -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
}
}

View 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
}
}

View 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
)
}

View 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
}
}

View file

@ -0,0 +1,7 @@
mod battery;
mod cpu;
mod gpu;
pub use battery::Battery;
pub use cpu::{Cpu, Cpus};
pub use gpu::Gpu;

View 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
}

View 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,
}
}
}

View file

@ -0,0 +1,3 @@
mod gpu;
pub use gpu::Gpu;

View file

@ -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;

View file

@ -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,
}
}
}

View file

@ -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,
}
}
}

View file

@ -0,0 +1,7 @@
mod battery;
mod cpu;
mod gpu;
pub use battery::Battery;
pub use cpu::Cpu;
pub use gpu::Gpu;

View file

@ -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/")
}

View file

@ -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"
}
}

View file

@ -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
View 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
}
}

View file

@ -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
View 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
View 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
View 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
View 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
View 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";

View file

@ -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.");
},
};
});

View file

@ -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});
}

View file

@ -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",

View file

@ -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.

View file

@ -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
View 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

Binary file not shown.

234
translations/es-ES.po Normal file
View 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

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more