Upgrade to decky 2.0 plugin system
This commit is contained in:
parent
a5b9216be5
commit
d86d72af05
31 changed files with 5673 additions and 6 deletions
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
lib-cov
|
||||||
|
*.seed
|
||||||
|
*.log
|
||||||
|
*.csv
|
||||||
|
*.dat
|
||||||
|
*.out
|
||||||
|
*.pid
|
||||||
|
*.gz
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
pids
|
||||||
|
logs
|
||||||
|
results
|
||||||
|
tmp
|
||||||
|
|
||||||
|
# Coverage reports
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# API keys and secrets
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
node_modules
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# Editors
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# OS metadata
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Ignore built ts files
|
||||||
|
dist/
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
/.yalc
|
||||||
|
yalc.lock
|
||||||
|
|
||||||
|
# ignore Rust compiler files
|
||||||
|
/backend-rs/target
|
|
@ -1,6 +1,6 @@
|
||||||
# Fantastic
|
# Fantastic
|
||||||
|
|
||||||
![plugin_demo](./extras/ui.png)
|
![plugin_demo](./assets/ui.png)
|
||||||
|
|
||||||
Steam Deck fan controls.
|
Steam Deck fan controls.
|
||||||
|
|
||||||
|
|
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
assets/ui.png
Normal file
BIN
assets/ui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 120 KiB |
1107
backend-rs/Cargo.lock
generated
Normal file
1107
backend-rs/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
19
backend-rs/Cargo.toml
Normal file
19
backend-rs/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "fantastic-rs"
|
||||||
|
version = "0.3.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
usdpl-back = { version = "0.5.2", features = ["blocking"]}#, path = "../usdpl/usdpl-back"}
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
# logging
|
||||||
|
log = "0.4"
|
||||||
|
simplelog = "0.12"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = false
|
||||||
|
strip = true
|
||||||
|
lto = true
|
||||||
|
codegen-units = 4
|
6
backend-rs/Cross.toml
Normal file
6
backend-rs/Cross.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[build]
|
||||||
|
#xargo = true
|
||||||
|
default-target = "x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
|
[build.env]
|
||||||
|
volumes = ["/home/ngnius/Documents/git-repos",]
|
4
backend-rs/build.sh
Executable file
4
backend-rs/build.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cross build --release
|
||||||
|
cp ./target/release/fantastic-rs ../backend
|
259
backend-rs/src/api.rs
Normal file
259
backend-rs/src/api.rs
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
use usdpl_back::core::serdes::Primitive;
|
||||||
|
|
||||||
|
use super::control::ControlRuntime;
|
||||||
|
use super::json::GraphPointJson;
|
||||||
|
|
||||||
|
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
pub const NAME: &'static str = env!("CARGO_PKG_NAME");
|
||||||
|
|
||||||
|
pub fn hello(params: Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
if let Some(Primitive::String(name)) = params.get(0) {
|
||||||
|
vec![Primitive::String(format!("Hello {}", name))]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn echo(params: Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
params
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(_: Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
vec![VERSION.into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(_: Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
vec![NAME.into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_fan_rpm(_: Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
if let Some(rpm) = crate::sys::read_fan() {
|
||||||
|
log::debug!("get_fan_rpm() success: {}", rpm);
|
||||||
|
vec![rpm.into()]
|
||||||
|
} else {
|
||||||
|
log::error!("get_fan_rpm failed to read fan speed");
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_temperature(_: Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
if let Some(temperature) = crate::sys::read_thermal_zone(0) {
|
||||||
|
let real_temp = temperature as f64 / 1000.0;
|
||||||
|
log::debug!("get_temperature() success: {}", real_temp);
|
||||||
|
vec![real_temp.into()]
|
||||||
|
} else {
|
||||||
|
log::error!("get_fan_rpm failed to read fan speed");
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_enable_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
let runtime_settings = runtime.settings_clone();
|
||||||
|
let runtime_state = runtime.state_clone();
|
||||||
|
move |params| {
|
||||||
|
if let Some(Primitive::Bool(enabled)) = params.get(0) {
|
||||||
|
let mut settings = match runtime_settings.write() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("set_enable failed to acquire settings write lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if settings.enable != *enabled {
|
||||||
|
settings.enable = *enabled;
|
||||||
|
let mut state = match runtime_state.write() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("set_enable failed to acquire state write lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.dirty = true;
|
||||||
|
log::debug!("set_enable({}) success", enabled);
|
||||||
|
}
|
||||||
|
vec![(*enabled).into()]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_enable_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
let runtime_settings = runtime.settings_clone();
|
||||||
|
move |_| {
|
||||||
|
let lock = match runtime_settings.read() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("get_enable failed to acquire settings read lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
log::debug!("get_enable() success");
|
||||||
|
vec![lock.enable.into()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_interpolate_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
let runtime_settings = runtime.settings_clone();
|
||||||
|
let runtime_state = runtime.state_clone();
|
||||||
|
move |params| {
|
||||||
|
if let Some(Primitive::Bool(enabled)) = params.get(0) {
|
||||||
|
let mut settings = match runtime_settings.write() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("set_enable failed to acquire settings write lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if settings.interpolate != *enabled {
|
||||||
|
settings.interpolate = *enabled;
|
||||||
|
let mut state = match runtime_state.write() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("set_interpolate failed to acquire state write lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.dirty = true;
|
||||||
|
log::debug!("set_interpolate({}) success", enabled);
|
||||||
|
}
|
||||||
|
vec![(*enabled).into()]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_interpolate_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
let runtime_settings = runtime.settings_clone();
|
||||||
|
move |_| {
|
||||||
|
let lock = match runtime_settings.read() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("get_interpolate failed to acquire settings read lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
log::debug!("get_interpolate() success");
|
||||||
|
vec![lock.interpolate.into()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn curve_to_json(curve: &Vec<super::datastructs::GraphPoint>) -> serde_json::Result<String> {
|
||||||
|
let mut curve_points = Vec::<GraphPointJson>::with_capacity(curve.len());
|
||||||
|
for point in curve.iter() {
|
||||||
|
curve_points.push(point.clone().into());
|
||||||
|
}
|
||||||
|
serde_json::to_string(&curve_points)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_curve_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
let runtime_settings = runtime.settings_clone();
|
||||||
|
move |_| {
|
||||||
|
let lock = match runtime_settings.read() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("get_curve failed to acquire settings read lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let json_str = match curve_to_json(&lock.curve) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("get_curve failed to serialize points: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
log::debug!("get_curve() success");
|
||||||
|
vec![Primitive::Json(json_str)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_curve_point_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
let runtime_settings = runtime.settings_clone();
|
||||||
|
let runtime_state = runtime.state_clone();
|
||||||
|
move |params| {
|
||||||
|
if let Some(Primitive::Json(json_str)) = params.get(0) {
|
||||||
|
let mut settings = match runtime_settings.write() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("add_curve_point failed to acquire settings write lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let new_point: GraphPointJson = match serde_json::from_str(&json_str) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("add_curve_point failed deserialize point json: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let version = settings.version;
|
||||||
|
settings.curve.push(super::datastructs::GraphPoint::from_json(new_point, version));
|
||||||
|
settings.sort_curve();
|
||||||
|
let mut state = match runtime_state.write() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("add_curve_point failed to acquire state write lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.dirty = true;
|
||||||
|
let json_str = match curve_to_json(&settings.curve) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("add_curve_point failed to serialize points: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
log::debug!("add_curve_point({}) success", json_str);
|
||||||
|
vec![Primitive::Json(json_str)]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_curve_point_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> {
|
||||||
|
let runtime_settings = runtime.settings_clone();
|
||||||
|
let runtime_state = runtime.state_clone();
|
||||||
|
move |params| {
|
||||||
|
if let Some(Primitive::F64(index)) = params.get(0) {
|
||||||
|
let mut settings = match runtime_settings.write() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("remove_curve_point failed to acquire settings write lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let rounded = index.round();
|
||||||
|
if rounded >= 0.0 && rounded < settings.curve.len() as _ {
|
||||||
|
let index = rounded as usize;
|
||||||
|
settings.curve.swap_remove(index);
|
||||||
|
settings.sort_curve();
|
||||||
|
let mut state = match runtime_state.write() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("remove_curve_point failed to acquire state write lock: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.dirty = true;
|
||||||
|
let json_str = match curve_to_json(&settings.curve) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("remove_curve_point failed to serialize points: {}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
log::debug!("remove_curve_point({}) success", json_str);
|
||||||
|
vec![Primitive::Json(json_str)]
|
||||||
|
} else {
|
||||||
|
log::error!("remove_curve_point received index out of bounds: {} indexing array of length {}", index, settings.curve.len());
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
217
backend-rs/src/control.rs
Normal file
217
backend-rs/src/control.rs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
//! Fan control
|
||||||
|
|
||||||
|
use std::sync::{RwLock, Arc};
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use super::datastructs::{Settings, State, GraphPoint};
|
||||||
|
use super::json::SettingsJson;
|
||||||
|
|
||||||
|
pub struct ControlRuntime {
|
||||||
|
settings: Arc<RwLock<Settings>>,
|
||||||
|
state: Arc<RwLock<State>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlRuntime {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let new_state = State::new();
|
||||||
|
let settings_p = settings_path(&new_state.home);
|
||||||
|
Self {
|
||||||
|
settings: Arc::new(RwLock::new(super::json::SettingsJson::open(settings_p).unwrap_or_default().into())),
|
||||||
|
state: Arc::new(RwLock::new(new_state)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn settings_clone(&self) -> Arc<RwLock<Settings>> {
|
||||||
|
self.settings.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn state_clone(&self) -> Arc<RwLock<State>> {
|
||||||
|
self.state.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&self) -> thread::JoinHandle<()> {
|
||||||
|
let runtime_settings = self.settings_clone();
|
||||||
|
let runtime_state = self.state_clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let sleep_duration = Duration::from_millis(1000);
|
||||||
|
let mut start_time = Instant::now();
|
||||||
|
loop {
|
||||||
|
if Instant::now().duration_since(start_time).as_secs_f64() * 0.95 > sleep_duration.as_secs_f64() {
|
||||||
|
// resumed from sleep; do fan re-init
|
||||||
|
log::debug!("Detected resume from sleep, overriding fan again");
|
||||||
|
{
|
||||||
|
let state = match runtime_state.read() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("runtime failed to acquire state read lock: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let settings = match runtime_settings.read() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("runtime failed to acquire settings read lock: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Self::on_set_enable(&settings, &state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start_time = Instant::now();
|
||||||
|
{ // save to file
|
||||||
|
let state = match runtime_state.read() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("runtime failed to acquire state read lock: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if state.dirty {
|
||||||
|
// save settings to file
|
||||||
|
let settings = match runtime_settings.read() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("runtime failed to acquire settings read lock: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let settings_json: SettingsJson = settings.clone().into();
|
||||||
|
if let Err(e) = settings_json.save(settings_path(&state.home)) {
|
||||||
|
log::error!("SettingsJson.save({}) error: {}", settings_path(&state.home).display(), e);
|
||||||
|
}
|
||||||
|
Self::on_set_enable(&settings, &state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ // fan control
|
||||||
|
let settings = match runtime_settings.read() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("runtime failed to acquire settings read lock: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if settings.enable {
|
||||||
|
Self::do_fan_control(&settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread::sleep(sleep_duration);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_set_enable(settings: &Settings, _state: &State) {
|
||||||
|
// TODO stop/start jupiter fan control (or maybe let the UI handle that?)
|
||||||
|
if let Err(e) = crate::sys::write_fan_recalc(settings.enable) {
|
||||||
|
log::error!("runtime failed to write to fan recalculate file: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_fan_control(settings: &Settings) {
|
||||||
|
/*
|
||||||
|
curve = self.settings["curve"]
|
||||||
|
fan_ratio = 0 # unnecessary in Python, but stupid without
|
||||||
|
if len(curve) == 0:
|
||||||
|
fan_ratio = 1
|
||||||
|
else:
|
||||||
|
index = -1
|
||||||
|
temperature_ratio = (thermal_zone(0) - TEMPERATURE_MINIMUM) / (TEMPERATURE_MAXIMUM - TEMPERATURE_MINIMUM)
|
||||||
|
for i in range(len(curve)-1, -1, -1):
|
||||||
|
if curve[i]["x"] < temperature_ratio:
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
if self.settings["interpolate"]:
|
||||||
|
fan_ratio = self.interpolate_fan(self, index, temperature_ratio)
|
||||||
|
else:
|
||||||
|
fan_ratio = self.step_fan(self, index, temperature_ratio)
|
||||||
|
set_fan_target(int((fan_ratio * FAN_MAXIMUM) + FAN_MINIMUM))
|
||||||
|
*/
|
||||||
|
let fan_ratio: f64 = if let Some(thermal_zone) = crate::sys::read_thermal_zone(0) {
|
||||||
|
let temperature_ratio = (((thermal_zone as f64)/1000.0) - settings.temperature_bounds.min)
|
||||||
|
/ (settings.temperature_bounds.max - settings.temperature_bounds.min);
|
||||||
|
let mut index = None;
|
||||||
|
for i in (0..settings.curve.len()).rev() {
|
||||||
|
if settings.curve[i].x < temperature_ratio {
|
||||||
|
index = Some(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if settings.interpolate {
|
||||||
|
Self::interpolate_fan(settings, index, temperature_ratio)
|
||||||
|
} else {
|
||||||
|
Self::step_fan(settings, index, temperature_ratio)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
let fan_speed: u64 = ((fan_ratio * (settings.fan_bounds.max - settings.fan_bounds.min)) + settings.fan_bounds.min) as _;
|
||||||
|
if let Err(e) = crate::sys::write_fan_target(fan_speed) {
|
||||||
|
log::error!("runtime failed to write to fan target file: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpolate_fan(settings: &Settings, index: Option<usize>, t_ratio: f64) -> f64 {
|
||||||
|
/*
|
||||||
|
curve = self.settings["curve"]
|
||||||
|
upper_point = {"x": 1.0, "y": 0.0}
|
||||||
|
lower_point = {"x": 0.0, "y": 1.0}
|
||||||
|
if index != -1: # guaranteed to not be empty
|
||||||
|
lower_point = curve[index]
|
||||||
|
if index != len(curve) - 1:
|
||||||
|
upper_point = curve[index+1]
|
||||||
|
#logging.debug(f"lower_point: {lower_point}, upper_point: {upper_point}")
|
||||||
|
upper_y = 1-upper_point["y"]
|
||||||
|
lower_y = 1-lower_point["y"]
|
||||||
|
slope_m = (upper_y - lower_y) / (upper_point["x"] - lower_point["x"])
|
||||||
|
y_intercept_b = lower_y - (slope_m * lower_point["x"])
|
||||||
|
logging.debug(f"interpolation: y = {slope_m}x + {y_intercept_b}")
|
||||||
|
return (slope_m * temperature_ratio) + y_intercept_b
|
||||||
|
*/
|
||||||
|
let (upper, lower) = if let Some(i) = index {
|
||||||
|
(if i != settings.curve.len() - 1 {
|
||||||
|
settings.curve[i+1].clone()
|
||||||
|
} else {
|
||||||
|
GraphPoint{x: 1.0, y: 1.0}
|
||||||
|
},
|
||||||
|
settings.curve[i].clone())
|
||||||
|
} else {
|
||||||
|
(if settings.curve.is_empty() {
|
||||||
|
GraphPoint{x: 1.0, y: 1.0}
|
||||||
|
} else {
|
||||||
|
settings.curve[0].clone()
|
||||||
|
},
|
||||||
|
GraphPoint{x: 0.0, y: 0.0})
|
||||||
|
};
|
||||||
|
let slope_m = (upper.y - lower.y) / (upper.x - lower.x);
|
||||||
|
let y_intercept_b = lower.y - (slope_m * lower.x);
|
||||||
|
log::debug!("interpolation: y = {}x + {} (between {:?} and {:?})", slope_m, y_intercept_b, upper, lower);
|
||||||
|
(slope_m * t_ratio) + y_intercept_b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step_fan(settings: &Settings, index: Option<usize>, _t_ratio: f64) -> f64 {
|
||||||
|
/*
|
||||||
|
curve = self.settings["curve"]
|
||||||
|
if index != -1:
|
||||||
|
return 1 - curve[index]["y"]
|
||||||
|
else:
|
||||||
|
if len(curve) == 0:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0.5
|
||||||
|
*/
|
||||||
|
if let Some(index) = index {
|
||||||
|
settings.curve[index].y
|
||||||
|
} else {
|
||||||
|
if settings.curve.is_empty() {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn settings_path<P: AsRef<std::path::Path>>(home: P) -> std::path::PathBuf {
|
||||||
|
home.as_ref().join(".config/fantastic/fantastic.json")
|
||||||
|
}
|
211
backend-rs/src/datastructs.rs
Normal file
211
backend-rs/src/datastructs.rs
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
use std::default::Default;
|
||||||
|
use std::convert::{Into, From};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use super::json::{SettingsJson, GraphPointJson, BoundsJson};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub version: u64,
|
||||||
|
pub enable: bool,
|
||||||
|
pub interpolate: bool,
|
||||||
|
pub curve: Vec<GraphPoint>,
|
||||||
|
pub fan_bounds: Bounds<f64>,
|
||||||
|
pub temperature_bounds: Bounds<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub fn sort_curve(&mut self) {
|
||||||
|
self.curve.sort_by(|a, b| a.x.total_cmp(&b.x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SettingsJson> for Settings {
|
||||||
|
fn from(mut other: SettingsJson) -> Self {
|
||||||
|
match other.version {
|
||||||
|
0 => Self {
|
||||||
|
version: 1,
|
||||||
|
enable: other.enable,
|
||||||
|
interpolate: other.interpolate,
|
||||||
|
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
|
||||||
|
fan_bounds: Bounds {
|
||||||
|
min: 1.0,
|
||||||
|
max: 7000.0,
|
||||||
|
},
|
||||||
|
temperature_bounds: Bounds {
|
||||||
|
min: 0.0,
|
||||||
|
max: 100.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1 => Self {
|
||||||
|
version: 1,
|
||||||
|
enable: other.enable,
|
||||||
|
interpolate: other.interpolate,
|
||||||
|
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
|
||||||
|
fan_bounds: other.fan_bounds.map(|x| Bounds::<f64>::from_json(x, other.version)).unwrap_or(Bounds {
|
||||||
|
min: 1.0,
|
||||||
|
max: 7000.0,
|
||||||
|
}),
|
||||||
|
temperature_bounds: other.temperature_bounds.map(|x| Bounds::<f64>::from_json(x, other.version)).unwrap_or(Bounds {
|
||||||
|
min: 0.0,
|
||||||
|
max: 100.0,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
_ => Self {
|
||||||
|
version: 1,
|
||||||
|
enable: other.enable,
|
||||||
|
interpolate: other.interpolate,
|
||||||
|
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
|
||||||
|
fan_bounds: Bounds {
|
||||||
|
min: 1.0,
|
||||||
|
max: 7000.0,
|
||||||
|
},
|
||||||
|
temperature_bounds: Bounds {
|
||||||
|
min: 0.0,
|
||||||
|
max: 100.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<SettingsJson> for Settings {
|
||||||
|
#[inline]
|
||||||
|
fn into(mut self) -> SettingsJson {
|
||||||
|
SettingsJson {
|
||||||
|
version: self.version,
|
||||||
|
enable: self.enable,
|
||||||
|
interpolate: self.interpolate,
|
||||||
|
curve: self.curve.drain(..).map(|x| x.into()).collect(),
|
||||||
|
fan_bounds: Some(self.fan_bounds.into()),
|
||||||
|
temperature_bounds: Some(self.temperature_bounds.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GraphPoint {
|
||||||
|
pub x: f64,
|
||||||
|
pub y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphPoint {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_json(other: GraphPointJson, version: u64) -> Self {
|
||||||
|
match version {
|
||||||
|
0 => Self {
|
||||||
|
x: other.x,
|
||||||
|
y: 1.0 - other.y, // use bottom left as origin, instead of whacky old way of top left
|
||||||
|
},
|
||||||
|
1 => Self {
|
||||||
|
x: other.x,
|
||||||
|
y: other.y,
|
||||||
|
},
|
||||||
|
_ => Self {
|
||||||
|
x: other.x,
|
||||||
|
y: other.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<GraphPointJson> for GraphPoint {
|
||||||
|
#[inline]
|
||||||
|
fn into(self) -> GraphPointJson {
|
||||||
|
GraphPointJson {
|
||||||
|
x: self.x,
|
||||||
|
y: self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Bounds<T: core::fmt::Debug + Clone> {
|
||||||
|
pub min: T,
|
||||||
|
pub max: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*impl Bounds<usize> {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_json(other: BoundsJson, version: u64) -> Self {
|
||||||
|
match version {
|
||||||
|
0 => Self {
|
||||||
|
min: other.min as _,
|
||||||
|
max: other.max as _,
|
||||||
|
},
|
||||||
|
1 => Self {
|
||||||
|
min: other.min as _,
|
||||||
|
max: other.max as _,
|
||||||
|
},
|
||||||
|
_ => Self {
|
||||||
|
min: other.min as _,
|
||||||
|
max: other.max as _,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
impl Bounds<f64> {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_json(other: BoundsJson, version: u64) -> Self {
|
||||||
|
match version {
|
||||||
|
0 => Self {
|
||||||
|
min: other.min,
|
||||||
|
max: other.max,
|
||||||
|
},
|
||||||
|
1 => Self {
|
||||||
|
min: other.min,
|
||||||
|
max: other.max,
|
||||||
|
},
|
||||||
|
_ => Self {
|
||||||
|
min: other.min,
|
||||||
|
max: other.max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*impl Into<BoundsJson> for Bounds<usize> {
|
||||||
|
#[inline]
|
||||||
|
fn into(self) -> BoundsJson {
|
||||||
|
BoundsJson {
|
||||||
|
min: self.min as _,
|
||||||
|
max: self.max as _,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
impl Into<BoundsJson> for Bounds<f64> {
|
||||||
|
#[inline]
|
||||||
|
fn into(self) -> BoundsJson {
|
||||||
|
BoundsJson {
|
||||||
|
min: self.min,
|
||||||
|
max: self.max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct State {
|
||||||
|
pub home: PathBuf,
|
||||||
|
pub dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let def = Self::default();
|
||||||
|
Self {
|
||||||
|
home: usdpl_back::api::dirs::home().unwrap_or(def.home),
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
home: "/home/deck".into(),
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
backend-rs/src/json.rs
Normal file
78
backend-rs/src/json.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use std::default::Default;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
//use super::datastructs::{Settings, GraphPoint};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SettingsJson {
|
||||||
|
pub version: u64,
|
||||||
|
pub enable: bool,
|
||||||
|
pub interpolate: bool,
|
||||||
|
pub curve: Vec<GraphPointJson>,
|
||||||
|
pub fan_bounds: Option<BoundsJson>,
|
||||||
|
pub temperature_bounds: Option<BoundsJson>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct GraphPointJson {
|
||||||
|
pub x: f64,
|
||||||
|
pub y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct BoundsJson {
|
||||||
|
pub min: f64,
|
||||||
|
pub max: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SettingsJson {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
version: 1,
|
||||||
|
enable: false,
|
||||||
|
interpolate: true,
|
||||||
|
curve: Vec::new(),
|
||||||
|
fan_bounds: Some(BoundsJson {
|
||||||
|
min: 0.0,
|
||||||
|
max: 7000.0,
|
||||||
|
}),
|
||||||
|
temperature_bounds: Some(BoundsJson {
|
||||||
|
min: 0.0,
|
||||||
|
max: 100.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SettingsJson {
|
||||||
|
pub fn save<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), JsonError> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).map_err(JsonError::Io)?;
|
||||||
|
}
|
||||||
|
let mut file = std::fs::File::create(path).map_err(JsonError::Io)?;
|
||||||
|
serde_json::to_writer_pretty(&mut file, &self).map_err(JsonError::Serde)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self, JsonError> {
|
||||||
|
let mut file = std::fs::File::open(path).map_err(JsonError::Io)?;
|
||||||
|
serde_json::from_reader(&mut file).map_err(JsonError::Serde)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum JsonError {
|
||||||
|
Serde(serde_json::Error),
|
||||||
|
Io(std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for JsonError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Serde(e) => (e as &dyn Display).fmt(f),
|
||||||
|
Self::Io(e) => (e as &dyn Display).fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
backend-rs/src/main.rs
Normal file
41
backend-rs/src/main.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
mod api;
|
||||||
|
mod control;
|
||||||
|
mod datastructs;
|
||||||
|
mod json;
|
||||||
|
mod sys;
|
||||||
|
|
||||||
|
use simplelog::{WriteLogger, LevelFilter};
|
||||||
|
|
||||||
|
use usdpl_back::Instance;
|
||||||
|
|
||||||
|
const PORT: u16 = 44444;
|
||||||
|
|
||||||
|
fn main() -> Result<(), ()> {
|
||||||
|
WriteLogger::init(
|
||||||
|
LevelFilter::Debug,
|
||||||
|
Default::default(),
|
||||||
|
std::fs::File::create("/tmp/fantastic.log").unwrap()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
log::info!("Starting back-end ({} v{})", api::NAME, api::VERSION);
|
||||||
|
println!("Starting back-end ({} v{})", api::NAME, api::VERSION);
|
||||||
|
let runtime = control::ControlRuntime::new();
|
||||||
|
runtime.run();
|
||||||
|
Instance::new(PORT)
|
||||||
|
.register("echo", api::echo)
|
||||||
|
.register("hello", api::hello)
|
||||||
|
.register("version", api::version)
|
||||||
|
.register("name", api::name)
|
||||||
|
.register("get_fan_rpm", api::get_fan_rpm)
|
||||||
|
.register("get_temperature", api::get_temperature)
|
||||||
|
.register("set_enable", api::set_enable_gen(&runtime))
|
||||||
|
.register("get_enable", api::get_enable_gen(&runtime))
|
||||||
|
.register("set_interpolate", api::set_interpolate_gen(&runtime))
|
||||||
|
.register("get_interpolate", api::get_interpolate_gen(&runtime))
|
||||||
|
.register("get_curve", api::get_curve_gen(&runtime))
|
||||||
|
.register("add_curve_point", api::add_curve_point_gen(&runtime))
|
||||||
|
.register("remove_curve_point", api::remove_curve_point_gen(&runtime))
|
||||||
|
.run_blocking()
|
||||||
|
//Ok(())
|
||||||
|
//println!("Hello, world!");
|
||||||
|
}
|
17
backend-rs/src/sys.rs
Normal file
17
backend-rs/src/sys.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use usdpl_back::api::files::*;
|
||||||
|
|
||||||
|
pub fn read_fan() -> Option<u64> {
|
||||||
|
read_single("/sys/class/hwmon/hwmon5/fan1_input").ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_thermal_zone(index: u8) -> Option<u64> {
|
||||||
|
read_single(format!("/sys/class/thermal/thermal_zone{}/temp", index)).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_fan_recalc(enabled: bool) -> Result<(), std::io::Error> {
|
||||||
|
write_single("/sys/class/hwmon/hwmon5/recalculate", enabled as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_fan_target(rpm: u64) -> Result<(), std::io::Error> {
|
||||||
|
write_single("/sys/class/hwmon/hwmon5/fan1_target", rpm)
|
||||||
|
}
|
BIN
extras/ui.png
BIN
extras/ui.png
Binary file not shown.
Before Width: | Height: | Size: 87 KiB |
1
fantastic.json
Normal file
1
fantastic.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version": 0, "enable": true, "interpolate": true, "curve": [{"x": 0.2740740740740741, "y": 0.815}, {"x": 0.725925925925926, "y": 0.665}, {"x": 0.9074074074074074, "y": 0.08}]}
|
4
main.py
4
main.py
|
@ -10,7 +10,7 @@ HOME_DIR = str(pathlib.Path(os.getcwd()).parent.parent.resolve())
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
filename = "/tmp/fantastic.log",
|
filename = "/tmp/fantastic.old.log",
|
||||||
format = '%(asctime)s %(levelname)s %(message)s',
|
format = '%(asctime)s %(levelname)s %(message)s',
|
||||||
filemode = 'w',
|
filemode = 'w',
|
||||||
force = True)
|
force = True)
|
||||||
|
@ -35,6 +35,8 @@ DEFAULT_DATA = {
|
||||||
"curve": [], # items are {x: int (distance from left), y: int (distance from top, NOT bottom)}
|
"curve": [], # items are {x: int (distance from left), y: int (distance from top, NOT bottom)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logging.debug(f"CWD: {os.getcwd()}")
|
||||||
|
|
||||||
class Plugin:
|
class Plugin:
|
||||||
settings = None
|
settings = None
|
||||||
is_changed = False
|
is_changed = False
|
||||||
|
|
2582
package-lock.json
generated
Normal file
2582
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
46
package.json
Normal file
46
package.json
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"name": "decky-plugin-template",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A template to quickly create decky plugins from scratch, based on TypeScript and webpack",
|
||||||
|
"scripts": {
|
||||||
|
"build": "shx rm -rf dist && rollup -c",
|
||||||
|
"watch": "rollup -c -w",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/SteamDeckHomebrew/decky-plugin-template.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"decky",
|
||||||
|
"plugin",
|
||||||
|
"plugin-template",
|
||||||
|
"steam-deck",
|
||||||
|
"deck"
|
||||||
|
],
|
||||||
|
"author": "Jonas Dellinger <jonas@dellinger.dev>",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/SteamDeckHomebrew/decky-plugin-template/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/SteamDeckHomebrew/decky-plugin-template#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^21.1.0",
|
||||||
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^13.2.1",
|
||||||
|
"@rollup/plugin-replace": "^4.0.0",
|
||||||
|
"@rollup/plugin-typescript": "^8.3.2",
|
||||||
|
"@types/react": "16.14.0",
|
||||||
|
"@types/webpack": "^5.28.0",
|
||||||
|
"rollup": "^2.70.2",
|
||||||
|
"rollup-plugin-import-assets": "^1.1.1",
|
||||||
|
"shx": "^0.3.4",
|
||||||
|
"tslib": "^2.4.0",
|
||||||
|
"typescript": "^4.6.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"decky-frontend-lib": "*",
|
||||||
|
"react-icons": "^4.3.1",
|
||||||
|
"usdpl-front": "file:./src/usdpl"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Fantastic",
|
"name": "Fantastic_React",
|
||||||
"author": "NGnius",
|
"author": "NGnius",
|
||||||
"main_view_html": "main_view.html",
|
"flags": ["root", "debug"],
|
||||||
"tile_view_html": "",
|
|
||||||
"flags": ["root", "_debug"],
|
|
||||||
"publish": {
|
"publish": {
|
||||||
"discord_id": "106537989684887552",
|
"discord_id": "106537989684887552",
|
||||||
"description": "Fan controls",
|
"description": "Fan controls",
|
||||||
|
|
37
rollup.config.js
Normal file
37
rollup.config.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
|
import json from '@rollup/plugin-json';
|
||||||
|
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||||
|
import replace from '@rollup/plugin-replace';
|
||||||
|
import typescript from '@rollup/plugin-typescript';
|
||||||
|
import { defineConfig } from 'rollup';
|
||||||
|
import importAssets from 'rollup-plugin-import-assets';
|
||||||
|
|
||||||
|
import { name } from "./plugin.json";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
input: './src/index.tsx',
|
||||||
|
plugins: [
|
||||||
|
commonjs(),
|
||||||
|
nodeResolve(),
|
||||||
|
typescript(),
|
||||||
|
json(),
|
||||||
|
replace({
|
||||||
|
preventAssignment: false,
|
||||||
|
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||||
|
}),
|
||||||
|
importAssets({
|
||||||
|
publicPath: `http://127.0.0.1:1337/plugins/${name}/`
|
||||||
|
})
|
||||||
|
],
|
||||||
|
context: 'window',
|
||||||
|
external: ['react', 'react-dom'],
|
||||||
|
output: {
|
||||||
|
file: 'dist/index.js',
|
||||||
|
globals: {
|
||||||
|
react: 'SP_REACT',
|
||||||
|
'react-dom': 'SP_REACTDOM',
|
||||||
|
},
|
||||||
|
format: 'iife',
|
||||||
|
exports: 'default',
|
||||||
|
},
|
||||||
|
});
|
74
src/backend.ts
Normal file
74
src/backend.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import {init_usdpl, target, init_embedded, call_backend} from "usdpl-front";
|
||||||
|
|
||||||
|
const USDPL_PORT: number = 44444;
|
||||||
|
|
||||||
|
// Utility
|
||||||
|
|
||||||
|
export function resolve(promise: Promise<any>, setter: any) {
|
||||||
|
(async function () {
|
||||||
|
let data = await promise;
|
||||||
|
if (data != null) {
|
||||||
|
console.debug("Got resolved", data);
|
||||||
|
setter(data);
|
||||||
|
} else {
|
||||||
|
console.warn("Resolve failed:", data);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function execute(promise: Promise<any[]>) {
|
||||||
|
(async function () {
|
||||||
|
let data = await promise;
|
||||||
|
console.debug("Got executed", data);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initBackend() {
|
||||||
|
// init usdpl
|
||||||
|
await init_embedded();
|
||||||
|
init_usdpl(USDPL_PORT);
|
||||||
|
console.log("USDPL started for framework: " + target());
|
||||||
|
//setReady(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back-end functions
|
||||||
|
|
||||||
|
export async function setEnabled(value: boolean): Promise<boolean> {
|
||||||
|
return (await call_backend("set_enable", [value]))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getEnabled(): Promise<boolean> {
|
||||||
|
return (await call_backend("get_enable", []))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setInterpolate(value: boolean): Promise<boolean> {
|
||||||
|
return (await call_backend("set_interpolate", [value]))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getInterpolate(): Promise<boolean> {
|
||||||
|
return (await call_backend("get_interpolate", []))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVersion(): Promise<string> {
|
||||||
|
return (await call_backend("version", []))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurve(): Promise<{"x": number, "y": number}[]> {
|
||||||
|
return (await call_backend("get_curve", []))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addCurvePoint(point: {"x": number, "y": number}): Promise<{"x": number, "y": number}[]> {
|
||||||
|
return (await call_backend("add_curve_point", [point]))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeCurvePoint(index: number): Promise<{"x": number, "y": number}[]> {
|
||||||
|
return (await call_backend("remove_curve_point", [index]))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFanRpm(): Promise<number> {
|
||||||
|
return (await call_backend("get_fan_rpm", []))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTemperature(): Promise<number> {
|
||||||
|
return (await call_backend("get_temperature", []))[0];
|
||||||
|
}
|
39
src/canvas.tsx
Normal file
39
src/canvas.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// from https://medium.com/@pdx.lucasm/canvas-with-react-js-32e133c05258
|
||||||
|
|
||||||
|
//import React from 'react';
|
||||||
|
import { useRef, useEffect } from 'react';
|
||||||
|
|
||||||
|
export const Canvas = (props: any) => {
|
||||||
|
|
||||||
|
const { draw, options, ...rest } = props;
|
||||||
|
//const { context, ...moreConfig } = options;
|
||||||
|
const canvasRef = useCanvas(draw);
|
||||||
|
|
||||||
|
return <canvas ref={canvasRef} {...rest}/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCanvas = (draw: (ctx: any, count: number) => void) => {
|
||||||
|
|
||||||
|
const canvasRef: any = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
const context = canvas!.getContext('2d');
|
||||||
|
let frameCount = 0;
|
||||||
|
let animationFrameId: number;
|
||||||
|
|
||||||
|
const render = () => {
|
||||||
|
frameCount++;
|
||||||
|
draw(context, frameCount);
|
||||||
|
animationFrameId = window.requestAnimationFrame(render);
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.cancelAnimationFrame(animationFrameId);
|
||||||
|
}
|
||||||
|
}, [draw]);
|
||||||
|
|
||||||
|
return canvasRef;
|
||||||
|
}
|
287
src/index.tsx
Executable file
287
src/index.tsx
Executable file
|
@ -0,0 +1,287 @@
|
||||||
|
import {
|
||||||
|
definePlugin,
|
||||||
|
DialogButton,
|
||||||
|
PanelSection,
|
||||||
|
PanelSectionRow,
|
||||||
|
ServerAPI,
|
||||||
|
ToggleField,
|
||||||
|
staticClasses,
|
||||||
|
gamepadDialogClasses,
|
||||||
|
joinClassNames,
|
||||||
|
} from "decky-frontend-lib";
|
||||||
|
import { VFC, useState } from "react";
|
||||||
|
import { FaFan } from "react-icons/fa";
|
||||||
|
|
||||||
|
import * as backend from "./backend";
|
||||||
|
import {Canvas} from "./canvas";
|
||||||
|
|
||||||
|
const POINT_SIZE = 32;
|
||||||
|
|
||||||
|
var periodicHook: any = null;
|
||||||
|
|
||||||
|
const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
|
||||||
|
// const [result, setResult] = useState<number | undefined>();
|
||||||
|
|
||||||
|
// const onClick = async () => {
|
||||||
|
// const result = await serverAPI.callPluginMethod<AddMethodArgs, number>(
|
||||||
|
// "add",
|
||||||
|
// {
|
||||||
|
// left: 2,
|
||||||
|
// right: 2,
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// if (result.success) {
|
||||||
|
// setResult(result.result);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const [enabledGlobal, setEnableInternal] = useState<boolean>(false);
|
||||||
|
const [interpolGlobal, setInterpol] = useState<boolean>(false);
|
||||||
|
const [serverApiGlobal, setServerApi] = useState<ServerAPI>(serverAPI);
|
||||||
|
const [firstTime, setFirstTime] = useState<boolean>(true);
|
||||||
|
const [usdplReady, setUsdplReady] = useState<boolean>(false);
|
||||||
|
const [curveGlobal, setCurve] = useState<{x: number, y: number}[]>([]);
|
||||||
|
|
||||||
|
const [temperatureGlobal, setTemperature] = useState<number>(-273.15);
|
||||||
|
const [fanRpmGlobal, setFanRpm] = useState<number>(-1337);
|
||||||
|
|
||||||
|
function setEnable(enable: boolean) {
|
||||||
|
setEnableInternal(enable);
|
||||||
|
//@ts-ignore
|
||||||
|
SteamClient.System.SetBetaFanControl(!enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickCanvas(e: any) {
|
||||||
|
//console.log("canvas click", e);
|
||||||
|
const realEvent: any = e.nativeEvent;
|
||||||
|
//console.log("Canvas click @ (" + realEvent.layerX.toString() + ", " + realEvent.layerY.toString() + ")");
|
||||||
|
const target: any = e.currentTarget;
|
||||||
|
//console.log("Target dimensions " + target.width.toString() + "x" + target.height.toString());
|
||||||
|
const clickX = realEvent.layerX;
|
||||||
|
const clickY = realEvent.layerY;
|
||||||
|
for (let i = 0; i < curveGlobal.length; i++) {
|
||||||
|
const curvePoint = curveGlobal[i];
|
||||||
|
const pointX = curvePoint.x * target.width;
|
||||||
|
const pointY = (1 - curvePoint.y) * target.height;
|
||||||
|
if (
|
||||||
|
pointX + POINT_SIZE > clickX
|
||||||
|
&& pointX - POINT_SIZE < clickX
|
||||||
|
&& pointY + POINT_SIZE > clickY
|
||||||
|
&& pointY - POINT_SIZE < clickY
|
||||||
|
) {
|
||||||
|
//console.log("Clicked on point " + i.toString());
|
||||||
|
backend.resolve(backend.removeCurvePoint(i), setCurve);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//console.log("Adding new point");
|
||||||
|
backend.resolve(backend.addCurvePoint({x: clickX / target.width, y: 1 - (clickY / target.height)}), setCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawCanvas(ctx: any, frameCount: number): void {
|
||||||
|
const width: number = ctx.canvas.width;
|
||||||
|
const height: number = ctx.canvas.height;
|
||||||
|
|
||||||
|
ctx.strokeStyle = "#1a9fff";
|
||||||
|
ctx.fillStyle = "#1a9fff";
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.lineJoin = "round";
|
||||||
|
//ctx.beginPath();
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
/*ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // Outer circle
|
||||||
|
ctx.moveTo(110, 75);
|
||||||
|
ctx.arc(75, 75, 35, 0, Math.PI, false); // Mouth (clockwise)
|
||||||
|
ctx.moveTo(65, 65);
|
||||||
|
ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // Left eye
|
||||||
|
ctx.moveTo(95, 65);
|
||||||
|
ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // Right eye*/
|
||||||
|
//ctx.beginPath();
|
||||||
|
//ctx.moveTo(0, height);
|
||||||
|
if (interpolGlobal) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, height);
|
||||||
|
for (let i = 0; i < curveGlobal.length; i++) {
|
||||||
|
const canvasHeight = (1 - curveGlobal[i].y) * height;
|
||||||
|
const canvasWidth = curveGlobal[i].x * width;
|
||||||
|
ctx.lineTo(canvasWidth, canvasHeight);
|
||||||
|
ctx.moveTo(canvasWidth, canvasHeight);
|
||||||
|
ctx.arc(canvasWidth, canvasHeight, 8, 0, Math.PI * 2);
|
||||||
|
ctx.moveTo(canvasWidth, canvasHeight);
|
||||||
|
}
|
||||||
|
ctx.lineTo(width, 0);
|
||||||
|
//ctx.moveTo(width, 0);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fill();
|
||||||
|
} else {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, height);
|
||||||
|
for (let i = 0; i < curveGlobal.length - 1; i++) {
|
||||||
|
const canvasHeight = (1 - curveGlobal[i].y) * height;
|
||||||
|
const canvasWidth = curveGlobal[i].x * width;
|
||||||
|
const canvasHeight2 = (1 - curveGlobal[i+1].y) * height;
|
||||||
|
const canvasWidth2 = curveGlobal[i+1].x * width;
|
||||||
|
//ctx.lineTo(canvasWidth, canvasHeight);
|
||||||
|
ctx.moveTo(canvasWidth, canvasHeight);
|
||||||
|
ctx.arc(canvasWidth, canvasHeight, 8, 0, Math.PI * 2);
|
||||||
|
ctx.moveTo(canvasWidth, canvasHeight);
|
||||||
|
ctx.lineTo(canvasWidth2, canvasHeight);
|
||||||
|
ctx.moveTo(canvasWidth2, canvasHeight);
|
||||||
|
ctx.lineTo(canvasWidth2, canvasHeight2);
|
||||||
|
}
|
||||||
|
if (curveGlobal.length != 0) {
|
||||||
|
const i = curveGlobal.length - 1;
|
||||||
|
const canvasHeight = (1 - curveGlobal[i].y) * height;
|
||||||
|
const canvasWidth = curveGlobal[i].x * width;
|
||||||
|
//ctx.lineTo(width, 0);
|
||||||
|
ctx.moveTo(canvasWidth, canvasHeight);
|
||||||
|
ctx.arc(canvasWidth, canvasHeight, 8, 0, Math.PI * 2);
|
||||||
|
ctx.moveTo(canvasWidth, canvasHeight);
|
||||||
|
ctx.lineTo(width, canvasHeight);
|
||||||
|
//ctx.moveTo(width, canvasHeight);
|
||||||
|
//ctx.lineTo(width, 0);
|
||||||
|
const canvasHeight2 = (1 - curveGlobal[0].y) * height;
|
||||||
|
const canvasWidth2 = curveGlobal[0].x * width;
|
||||||
|
ctx.moveTo(canvasWidth2, canvasHeight2);
|
||||||
|
ctx.lineTo(canvasWidth2, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
//ctx.moveTo(width, 0);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
//console.debug("Drew canvas with " + curveGlobal.length.toString() + " points; " + width.toString() + "x" + height.toString());
|
||||||
|
//ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
//ctx.fillStyle = '#000000';
|
||||||
|
//ctx.beginPath();
|
||||||
|
//ctx.arc(50, 100, 20*Math.sin(frameCount*0.05)**2, 0, 2*Math.PI);
|
||||||
|
//ctx.fill();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstTime) {
|
||||||
|
setFirstTime(false);
|
||||||
|
setServerApi(serverAPI);
|
||||||
|
(async function(){
|
||||||
|
await backend.initBackend();
|
||||||
|
setUsdplReady(true);
|
||||||
|
backend.resolve(backend.getEnabled(), setEnable);
|
||||||
|
backend.resolve(backend.getInterpolate(), setInterpol);
|
||||||
|
backend.resolve(backend.getCurve(), setCurve);
|
||||||
|
backend.resolve(backend.getTemperature(), setTemperature);
|
||||||
|
backend.resolve(backend.getFanRpm(), setFanRpm);
|
||||||
|
})();
|
||||||
|
|
||||||
|
periodicHook = setInterval(function() {
|
||||||
|
backend.resolve(backend.getTemperature(), setTemperature);
|
||||||
|
backend.resolve(backend.getFanRpm(), setFanRpm);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usdplReady) {
|
||||||
|
return (
|
||||||
|
<PanelSection>
|
||||||
|
</PanelSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard);
|
||||||
|
|
||||||
|
// TODO handle clicking on fan curve nodes
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelSection>
|
||||||
|
<PanelSectionRow>
|
||||||
|
<div className={FieldWithSeparator}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabel}>
|
||||||
|
Current Fan Speed
|
||||||
|
</div>
|
||||||
|
<div className={gamepadDialogClasses.FieldChildren}>
|
||||||
|
{fanRpmGlobal.toFixed(0) + " RPM"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
<div className={FieldWithSeparator}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||||
|
<div className={gamepadDialogClasses.FieldLabel}>
|
||||||
|
Current Temperature
|
||||||
|
</div>
|
||||||
|
<div className={gamepadDialogClasses.FieldChildren}>
|
||||||
|
{temperatureGlobal.toFixed(1) + " °C"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PanelSectionRow>
|
||||||
|
<PanelSectionRow>
|
||||||
|
<ToggleField
|
||||||
|
label="Custom Fan Curve"
|
||||||
|
description="Overrides SteamOS fan curve"
|
||||||
|
checked={enabledGlobal}
|
||||||
|
onChange={(value: boolean) => {
|
||||||
|
backend.resolve(backend.setEnabled(value), setEnable);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PanelSectionRow>
|
||||||
|
{ enabledGlobal &&
|
||||||
|
<div className={staticClasses.PanelSectionTitle}>
|
||||||
|
Fan
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{ enabledGlobal &&
|
||||||
|
<PanelSectionRow>
|
||||||
|
<Canvas draw={drawCanvas} width={268} height={200} style={{
|
||||||
|
"width": "268px",
|
||||||
|
"height": "200px",
|
||||||
|
"padding":"0px",
|
||||||
|
"border":"1px solid #1a9fff",
|
||||||
|
//"position":"relative",
|
||||||
|
"background-color":"#1a1f2c",
|
||||||
|
"border-radius":"4px",
|
||||||
|
//"margin":"auto",
|
||||||
|
}} onClick={(e: any) => onClickCanvas(e)}/>
|
||||||
|
</PanelSectionRow>
|
||||||
|
}
|
||||||
|
{ enabledGlobal &&
|
||||||
|
<PanelSectionRow>
|
||||||
|
<ToggleField
|
||||||
|
label="Linear Interpolation"
|
||||||
|
description="Pretends a straight line connects points"
|
||||||
|
checked={interpolGlobal}
|
||||||
|
onChange={(value: boolean) => {
|
||||||
|
backend.resolve(backend.setInterpolate(value), setInterpol);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PanelSectionRow>
|
||||||
|
}
|
||||||
|
</PanelSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeckyPluginRouterTest: VFC = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: "50px", color: "white" }}>
|
||||||
|
Hello World!
|
||||||
|
<DialogButton onClick={() => {}}>
|
||||||
|
Go to Store
|
||||||
|
</DialogButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default definePlugin((serverApi: ServerAPI) => {
|
||||||
|
serverApi.routerHook.addRoute("/decky-plugin-test", DeckyPluginRouterTest, {
|
||||||
|
exact: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: <div className={staticClasses.Title}>Fantastic</div>,
|
||||||
|
content: <Content serverAPI={serverApi} />,
|
||||||
|
icon: <FaFan />,
|
||||||
|
onDismount() {
|
||||||
|
clearInterval(periodicHook!);
|
||||||
|
serverApi.routerHook.removeRoute("/decky-plugin-test");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
14
src/types.d.ts
vendored
Normal file
14
src/types.d.ts
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
declare module "*.svg" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.png" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.jpg" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
21
src/usdpl/package.json
Normal file
21
src/usdpl/package.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "usdpl-front",
|
||||||
|
"collaborators": [
|
||||||
|
"NGnius (Graham) <ngniusness@gmail.com>"
|
||||||
|
],
|
||||||
|
"description": "Universal Steam Deck Plugin Library front-end designed for WASM",
|
||||||
|
"version": "0.4.0",
|
||||||
|
"license": "GPL-3.0-only",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/NGnius/usdpl-rs"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"usdpl_front_bg.wasm",
|
||||||
|
"usdpl_front.js",
|
||||||
|
"usdpl_front.d.ts"
|
||||||
|
],
|
||||||
|
"module": "usdpl_front.js",
|
||||||
|
"types": "usdpl_front.d.ts",
|
||||||
|
"sideEffects": false
|
||||||
|
}
|
51
src/usdpl/usdpl_front.d.ts
vendored
Normal file
51
src/usdpl/usdpl_front.d.ts
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Initialize the front-end library
|
||||||
|
* @param {number} port
|
||||||
|
*/
|
||||||
|
export function init_usdpl(port: number): void;
|
||||||
|
/**
|
||||||
|
* Get the targeted plugin framework, or "any" if unknown
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function target(): string;
|
||||||
|
/**
|
||||||
|
* Call a function on the back-end.
|
||||||
|
* Returns null (None) if this fails for any reason.
|
||||||
|
* @param {string} name
|
||||||
|
* @param {any[]} parameters
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
export function call_backend(name: string, parameters: any[]): Promise<any>;
|
||||||
|
|
||||||
|
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||||
|
|
||||||
|
export interface InitOutput {
|
||||||
|
readonly memory: WebAssembly.Memory;
|
||||||
|
readonly init_usdpl: (a: number) => void;
|
||||||
|
readonly target: (a: number) => void;
|
||||||
|
readonly call_backend: (a: number, b: number, c: number, d: number) => number;
|
||||||
|
readonly __wbindgen_malloc: (a: number) => number;
|
||||||
|
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
|
||||||
|
readonly __wbindgen_export_2: WebAssembly.Table;
|
||||||
|
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2bd47cee569ae4c6: (a: number, b: number, c: number) => void;
|
||||||
|
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||||
|
readonly __wbindgen_free: (a: number, b: number) => void;
|
||||||
|
readonly __wbindgen_exn_store: (a: number) => void;
|
||||||
|
readonly wasm_bindgen__convert__closures__invoke2_mut__hfe1195d34914cc54: (a: number, b: number, c: number, d: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||||
|
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||||
|
*
|
||||||
|
* @param {InitInput | Promise<InitInput>} module_or_path
|
||||||
|
*
|
||||||
|
* @returns {Promise<InitOutput>}
|
||||||
|
*/
|
||||||
|
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||||
|
|
||||||
|
|
||||||
|
// USDPL customization
|
||||||
|
export function init_embedded();
|
476
src/usdpl/usdpl_front.js
Normal file
476
src/usdpl/usdpl_front.js
Normal file
File diff suppressed because one or more lines are too long
BIN
src/usdpl/usdpl_front_bg.wasm
Normal file
BIN
src/usdpl/usdpl_front_bg.wasm
Normal file
Binary file not shown.
14
src/usdpl/usdpl_front_bg.wasm.d.ts
vendored
Normal file
14
src/usdpl/usdpl_front_bg.wasm.d.ts
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export const memory: WebAssembly.Memory;
|
||||||
|
export function init_usdpl(a: number): void;
|
||||||
|
export function target(a: number): void;
|
||||||
|
export function call_backend(a: number, b: number, c: number, d: number): number;
|
||||||
|
export function __wbindgen_malloc(a: number): number;
|
||||||
|
export function __wbindgen_realloc(a: number, b: number, c: number): number;
|
||||||
|
export const __wbindgen_export_2: WebAssembly.Table;
|
||||||
|
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2bd47cee569ae4c6(a: number, b: number, c: number): void;
|
||||||
|
export function __wbindgen_add_to_stack_pointer(a: number): number;
|
||||||
|
export function __wbindgen_free(a: number, b: number): void;
|
||||||
|
export function __wbindgen_exn_store(a: number): void;
|
||||||
|
export function wasm_bindgen__convert__closures__invoke2_mut__hfe1195d34914cc54(a: number, b: number, c: number, d: number): void;
|
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ES2020",
|
||||||
|
"jsx": "react",
|
||||||
|
"jsxFactory": "window.SP_REACT.createElement",
|
||||||
|
"declaration": false,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strict": true,
|
||||||
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
Loading…
Reference in a new issue