Compare commits
45 commits
Author | SHA1 | Date | |
---|---|---|---|
924f1aaa51 | |||
364d9650f2 | |||
dd0c3998f0 | |||
a0f565895b | |||
32d09219da | |||
b637f4f5f9 | |||
8c9a7b7c68 | |||
f620dc1854 | |||
6c8b335417 | |||
dd6672f9ba | |||
f4a3f06aae | |||
255958f96b | |||
2bec0087a0 | |||
9972ac7854 | |||
438d91f639 | |||
1c6a889006 | |||
3d744f31aa | |||
c88402e580 | |||
b52441f1ad | |||
95dab80171 | |||
dc68a1f1fb | |||
b6e70c2c79 | |||
b3de3fcd7e | |||
2286e0f43a | |||
2fc52c246f | |||
6462939dad | |||
603da6e564 | |||
9408c16461 | |||
7cc91b2c0b | |||
4066f4e8ce | |||
dd4727770a | |||
11608e8a29 | |||
94ead16ac5 | |||
616f73d08c | |||
2ef35ba832 | |||
8e6d328710 | |||
8efb5c9907 | |||
d39037f3e8 | |||
506d895975 | |||
d86d72af05 | |||
a5b9216be5 | |||
4458092c3d | |||
10c66f00c0 | |||
a60358e494 | |||
d7b63fe64f |
50 changed files with 5939 additions and 494 deletions
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
github: NGnius
|
||||
liberapay: NGnius
|
52
.gitignore
vendored
Normal file
52
.gitignore
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
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
|
||||
package-lock.json
|
||||
|
||||
# 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
|
||||
backend
|
||||
/bin/backend
|
||||
/src/rust/target
|
||||
|
||||
# packaged teasers
|
||||
*.zip
|
||||
*.tar.gz
|
||||
Fantastic/
|
12
README.md
12
README.md
|
@ -1,9 +1,17 @@
|
|||
# Fantastic
|
||||
|
||||
![plugin_demo](./assets/ui.png)
|
||||
|
||||
Steam Deck fan controls.
|
||||
|
||||
This is generated from the template plugin for the [SteamOS Plugin Loader](https://github.com/SteamDeckHomebrew/PluginLoader).
|
||||
You will need that installed for this plugin to work.
|
||||
This plugin is a bit special because it doesn't use the standard Python back-end.
|
||||
Instead, it uses [USDPL](https://github.com/NGnius/usdpl-rs) written in Rust.
|
||||
This means Fantastic needs a compiled binary to work correctly, which is not included in this repository.
|
||||
If you run `./build.sh` from the `backend-rs` directory on a Linux system with `cargo` installed, the binary will be built for you.
|
||||
|
||||
This is generated from the template plugin for the [SteamOS Plugin Loader](https://github.com/SteamDeckHomebrew/decky-plugin-template).
|
||||
|
||||
Once Plugin Loader is installed, you can get this plugin from the [plugin store](https://plugins.deckbrew.xyz/) (that shopping basket at the top of the plugin menu).
|
||||
|
||||
## License
|
||||
|
||||
|
|
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
assets/thumbnail.jpg
Normal file
BIN
assets/thumbnail.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
assets/thumbnail.png
Normal file
BIN
assets/thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 223 KiB |
BIN
assets/thumbnail.xcf
Normal file
BIN
assets/thumbnail.xcf
Normal file
Binary file not shown.
BIN
assets/ui.png
Normal file
BIN
assets/ui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 120 KiB |
1766
backend-rs/Cargo.lock
generated
Normal file
1766
backend-rs/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
36
backend-rs/Cargo.toml
Normal file
36
backend-rs/Cargo.toml
Normal file
|
@ -0,0 +1,36 @@
|
|||
[package]
|
||||
name = "fantastic-rs"
|
||||
version = "0.6.0-alpha1"
|
||||
edition = "2021"
|
||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||
description = "Backend (superuser) functionality for Fantastic"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://git.ngni.us/NG-SD-Plugins/Fantastic"
|
||||
keywords = ["utility", "fan-control", "root", "decky"]
|
||||
readme = "../README.md"
|
||||
|
||||
|
||||
[dependencies]
|
||||
usdpl-back = { version = "1.0", features = ["blocking"], path = "../../usdpl-rs/usdpl-back"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
nrpc = { version = "1.0", path = "../../nRPC/nrpc", features = ["async-trait"] }
|
||||
prost = "0.11"
|
||||
tokio = { version = "1", features = ["sync", "rt"] }
|
||||
|
||||
sysfuss = { version = "0.4", features = ["derive"], path = "../../sysfs-nav" }
|
||||
sysinfo = "0.31"
|
||||
|
||||
# logging
|
||||
log = "0.4"
|
||||
simplelog = "0.12"
|
||||
|
||||
[build-dependencies]
|
||||
usdpl-build = { version = "1.0", path = "../../usdpl-rs/usdpl-build" }
|
||||
|
||||
[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",]
|
9
backend-rs/build.rs
Normal file
9
backend-rs/build.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
fn main() {
|
||||
//println!("CWD: {}", std::env::current_dir().unwrap().display());
|
||||
usdpl_build::back::build([
|
||||
format!("{}/protos/fantastic.proto", std::env::current_dir().unwrap().display())
|
||||
].into_iter(),
|
||||
[
|
||||
format!("{}/protos/", std::env::current_dir().unwrap().display())
|
||||
].into_iter())
|
||||
}
|
13
backend-rs/build.sh
Executable file
13
backend-rs/build.sh
Executable file
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
#cargo build --release --target x86_64-unknown-linux-musl
|
||||
#cargo build --target x86_64-unknown-linux-musl
|
||||
#cross build --release
|
||||
cargo build
|
||||
|
||||
mkdir -p ../bin
|
||||
#cp --preserve=mode ./target/x86_64-unknown-linux-musl/release/fantastic-rs ../bin/backend
|
||||
#cp --preserve=mode ./target/x86_64-unknown-linux-musl/debug/fantastic-rs ../bin/backend
|
||||
#cp --preserve=mode ./target/release/fantastic-rs ../bin/backend
|
||||
cp --preserve=mode ./target/debug/fantastic-rs ../bin/backend
|
||||
|
136
backend-rs/protos/fantastic.proto
Normal file
136
backend-rs/protos/fantastic.proto
Normal file
|
@ -0,0 +1,136 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package fantastic;
|
||||
|
||||
// The most amazing fan service
|
||||
service Fan {
|
||||
// Send back the exact same message as received
|
||||
rpc echo (EchoMessage) returns (EchoMessage);
|
||||
|
||||
// Hello world
|
||||
rpc hello (NameMessage) returns (HelloResponse);
|
||||
|
||||
// Fantastic version info
|
||||
rpc version (Empty) returns (VersionMessage);
|
||||
|
||||
// Fantastic version number string
|
||||
rpc version_str (Empty) returns (VersionDisplayMessage);
|
||||
|
||||
// Rust name (fantastic)
|
||||
rpc name (Empty) returns (NameMessage);
|
||||
|
||||
// Get fan speed
|
||||
rpc get_fan_rpm (Empty) returns (stream RpmMessage);
|
||||
|
||||
// Get system temperature
|
||||
rpc get_temperature (Empty) returns (stream TemperatureMessage);
|
||||
|
||||
// Set custom fan control enabled
|
||||
rpc set_enable (EnablementMessage) returns (EnablementMessage);
|
||||
|
||||
// Get custon fan control status
|
||||
rpc get_enable (Empty) returns (EnablementMessage);
|
||||
|
||||
// Set fan control interpolation
|
||||
rpc set_interpolate (EnablementMessage) returns (EnablementMessage);
|
||||
|
||||
// Get fan control interpolation
|
||||
rpc get_interpolate (Empty) returns (EnablementMessage);
|
||||
|
||||
// Get fan control curve
|
||||
rpc get_curve_x (Empty) returns (CurveMessageX);
|
||||
|
||||
// Get fan control curve
|
||||
rpc get_curve_y (Empty) returns (CurveMessageY);
|
||||
|
||||
// Add a new point to the fan curve
|
||||
rpc add_curve_point (GraphPoint) returns (Empty);
|
||||
|
||||
// Remove a point from the fan curve
|
||||
rpc remove_curve_point (IndexMessage) returns (Empty);
|
||||
|
||||
/*
|
||||
.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))
|
||||
*/
|
||||
}
|
||||
|
||||
// The request and response message for Echo
|
||||
message EchoMessage {
|
||||
string msg = 1;
|
||||
}
|
||||
|
||||
message NameMessage {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloResponse {
|
||||
string phrase = 1;
|
||||
}
|
||||
|
||||
message Empty {
|
||||
bool ok = 1;
|
||||
}
|
||||
|
||||
message VersionMessage {
|
||||
int32 major = 1;
|
||||
int32 minor = 2;
|
||||
int32 patch = 3;
|
||||
//string display = 4;
|
||||
}
|
||||
|
||||
message VersionDisplayMessage {
|
||||
string display = 1;
|
||||
}
|
||||
|
||||
message VersionStr {
|
||||
string version_str = 1;
|
||||
}
|
||||
|
||||
message RpmMessage {
|
||||
uint32 rpm = 1;
|
||||
}
|
||||
|
||||
message TemperatureMessage {
|
||||
double temperature = 1;
|
||||
}
|
||||
|
||||
message EnablementMessage {
|
||||
bool is_enabled = 1;
|
||||
}
|
||||
|
||||
message GraphPoint {
|
||||
double x = 1;
|
||||
double y = 2;
|
||||
}
|
||||
|
||||
/*message CurveMessage {
|
||||
//repeated GraphPoint points = 1;
|
||||
repeated double x = 1;
|
||||
repeated double y = 2;
|
||||
}*/
|
||||
|
||||
message CurveMessageX {
|
||||
//repeated GraphPoint points = 1;
|
||||
repeated double x = 1;
|
||||
}
|
||||
|
||||
message CurveMessageY {
|
||||
//repeated GraphPoint points = 1;
|
||||
repeated double y = 2;
|
||||
}
|
||||
|
||||
message IndexMessage {
|
||||
uint32 index = 1;
|
||||
}
|
11
backend-rs/src/adapters/fans/dev_mode.rs
Normal file
11
backend-rs/src/adapters/fans/dev_mode.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
pub struct DevModeFan;
|
||||
|
||||
impl super::Adapter for DevModeFan {
|
||||
fn on_enable_toggled(&self, settings: &crate::datastructs::Settings) {
|
||||
log::info!("on_enable_toggled invoked with settings {:?}", settings);
|
||||
}
|
||||
|
||||
fn control_fan(&self, settings: &crate::datastructs::Settings, sensor: &crate::adapters::traits::SensorReading) {
|
||||
log::info!("control_fan invoked with settings {:?} and sensor {:?}", settings, sensor);
|
||||
}
|
||||
}
|
10
backend-rs/src/adapters/fans/mod.rs
Normal file
10
backend-rs/src/adapters/fans/mod.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
mod dev_mode;
|
||||
pub use dev_mode::DevModeFan;
|
||||
|
||||
mod steam_deck;
|
||||
pub use steam_deck::SteamDeckFan;
|
||||
|
||||
mod rog_ally;
|
||||
pub use rog_ally::RogAllyFan;
|
||||
|
||||
pub(self) use super::FanAdapter as Adapter;
|
159
backend-rs/src/adapters/fans/rog_ally/adapter.rs
Normal file
159
backend-rs/src/adapters/fans/rog_ally/adapter.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use sysfuss::{BasicEntityPath, HwMonAttribute, HwMonAttributeItem, HwMonAttributeType, HwMonPath, SysAttributeExt};
|
||||
use sysfuss::{SysPath, SysEntityAttributesExt};
|
||||
|
||||
use crate::datastructs::{Settings, GraphPoint};
|
||||
|
||||
const ASUS_CUSTOM_FAN_CURVE_HWMON_NAME: &str = "asus_custom_fan_curve";
|
||||
const ASUS_REGULAR_HWMON_NAME: &str = "asus";
|
||||
|
||||
pub struct RogAllyFan {
|
||||
hwmon_curve: HwMonPath,
|
||||
hwmon_asus: HwMonPath,
|
||||
}
|
||||
|
||||
impl RogAllyFan {
|
||||
pub fn maybe_find() -> Option<Self> {
|
||||
let syspath = SysPath::path(crate::sys::SYSFS_ROOT);
|
||||
match syspath.hwmon_by_name(ASUS_CUSTOM_FAN_CURVE_HWMON_NAME)
|
||||
{
|
||||
Err(e) => {
|
||||
log::error!("sysfs hwmon iter error while finding Asus ROG Ally fan curve: {}", e);
|
||||
None
|
||||
},
|
||||
Ok(hwmon_curve) => {
|
||||
match syspath.hwmon_by_name(ASUS_REGULAR_HWMON_NAME) {
|
||||
Err(e) => {
|
||||
log::error!("sysfs hwmon iter error while finding Asus fan: {}", e);
|
||||
None
|
||||
},
|
||||
Ok(hwmon_asus) => {
|
||||
Some(Self {
|
||||
hwmon_curve,
|
||||
hwmon_asus,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns true when the point probably doesn't exist at all
|
||||
// (instead of it just running out of pwm# entries)
|
||||
fn set_gp(gp_i: u64, gp: &GraphPoint, basic_curve: &BasicEntityPath) -> bool {
|
||||
for pwm_i in 1..u64::MAX {
|
||||
let temp_attr = pwm_point_temp(pwm_i, gp_i);
|
||||
let pwm_attr = pwm_point_pwm(pwm_i, gp_i);
|
||||
if !(temp_attr.exists(basic_curve) && pwm_attr.exists(basic_curve)) {
|
||||
return pwm_i == 1;
|
||||
}
|
||||
let pwm_val = (gp.y * 255.0).round() as u8;
|
||||
if let Err(e) = basic_curve.set(&pwm_attr as &str, pwm_val) {
|
||||
let attr_path = pwm_attr.path(basic_curve);
|
||||
log::error!("failed to write to {}: {}", attr_path.display(), e);
|
||||
}
|
||||
let temp_val = (gp.x * 100.0).round() as u8;
|
||||
if let Err(e) = basic_curve.set(&temp_attr as &str, temp_val) {
|
||||
let attr_path = temp_attr.path(basic_curve);
|
||||
log::error!("failed to write to {}: {}", attr_path.display(), e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl super::super::Adapter for RogAllyFan {
|
||||
fn on_enable_toggled(&self, settings: &Settings) {
|
||||
let value_to_set = settings.enable as u8;
|
||||
for i in 1..u64::MAX {
|
||||
let attr = pwm_enable_attr_name(i);
|
||||
if self.hwmon_curve.exists(&attr) {
|
||||
if let Err(e) = self.hwmon_curve.set(attr, value_to_set) {
|
||||
log::error!("failed to write to {}: {}", self.hwmon_curve.path_to(attr).display(), e);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn control_fan(&self, settings: &Settings, _sensor: &crate::adapters::traits::SensorReading) {
|
||||
let basic_curve = BasicEntityPath::new(self.hwmon_curve.as_ref());
|
||||
if settings.curve.is_empty() {
|
||||
for i in 1..u64::MAX {
|
||||
let out_of_points = if i == 1 {
|
||||
Self::set_gp(i as _, &GraphPoint { x: 0.0, y: 0.0 }, &basic_curve)
|
||||
} else {
|
||||
Self::set_gp(i as _, &GraphPoint { x: 1.0, y: 1.0 }, &basic_curve)
|
||||
};
|
||||
if out_of_points { break; }
|
||||
}
|
||||
}
|
||||
for (i, gp) in settings.curve.iter().enumerate() {
|
||||
Self::set_gp(i as _, gp, &basic_curve);
|
||||
}
|
||||
// set remaining pwm points to highest in curve
|
||||
for i in settings.curve.len()..usize::MAX {
|
||||
if Self::set_gp(i as _, &GraphPoint { x: 1.0, y: 1.0 }, &basic_curve) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sensor<'a: 'b, 'b>(&'a self) -> Option<&'b dyn crate::adapters::SensorAdapter> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl super::super::super::SensorAdapter for RogAllyFan {
|
||||
fn read(&self) -> Result<crate::adapters::SensorReading, std::io::Error> {
|
||||
let mut readings = Vec::new();
|
||||
let mut last_error = None;
|
||||
for i in 1..u64::MAX {
|
||||
let attr = fan_reading_attr_name(i);
|
||||
let reading = match self.hwmon_asus.attribute::<u64>(attr) {
|
||||
Ok(reading) => reading,
|
||||
Err(sysfuss::EitherErr2::First(e1)) => {
|
||||
log::debug!("failed to read attribute {}: {} (ok; breaking loop...)", self.hwmon_asus.path_to(attr).display(), e1);
|
||||
last_error = Some(e1);
|
||||
break;
|
||||
},
|
||||
Err(sysfuss::EitherErr2::Second(e2)) => {
|
||||
log::debug!("failed to read attribute {}: {} (ok; breaking loop...)", self.hwmon_asus.path_to(attr).display(), e2);
|
||||
last_error = Some(std::io::Error::other(e2));
|
||||
break;
|
||||
},
|
||||
};
|
||||
readings.push(reading);
|
||||
}
|
||||
let mut reading_sum = 0;
|
||||
for reading in readings.iter() {
|
||||
reading_sum += *reading;
|
||||
}
|
||||
if readings.is_empty() {
|
||||
Err(last_error.unwrap())
|
||||
} else {
|
||||
Ok(crate::adapters::SensorReading {
|
||||
value: (reading_sum / (readings.len() as u64)) as f64,
|
||||
meaning: crate::adapters::SensorType::Fan,
|
||||
name: "ROG Ally Fan",
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn pwm_enable_attr_name(index: u64) -> HwMonAttribute {
|
||||
HwMonAttribute::new(HwMonAttributeType::Pwm, index, HwMonAttributeItem::Enable)
|
||||
}
|
||||
|
||||
fn fan_reading_attr_name(index: u64) -> HwMonAttribute {
|
||||
HwMonAttribute::new(HwMonAttributeType::Fan, index, HwMonAttributeItem::Input)
|
||||
}
|
||||
|
||||
fn pwm_point_temp(index: u64, point_index: u64) -> String {
|
||||
format!("pwm{}_auto_point{}_temp", index, point_index)
|
||||
}
|
||||
|
||||
fn pwm_point_pwm(index: u64, point_index: u64) -> String {
|
||||
format!("pwm{}_auto_point{}_pwm", index, point_index)
|
||||
}
|
2
backend-rs/src/adapters/fans/rog_ally/mod.rs
Normal file
2
backend-rs/src/adapters/fans/rog_ally/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod adapter;
|
||||
pub use adapter::RogAllyFan;
|
297
backend-rs/src/adapters/fans/steam_deck/adapter.rs
Normal file
297
backend-rs/src/adapters/fans/steam_deck/adapter.rs
Normal file
|
@ -0,0 +1,297 @@
|
|||
use sysfuss::{HwMonPath, HwMonAttribute, HwMonAttributeType, HwMonAttributeItem, BasicEntityPath};
|
||||
use sysfuss::{SysPath, capability::attributes, SysEntityAttributesExt};
|
||||
|
||||
use crate::datastructs::{Settings, GraphPoint};
|
||||
|
||||
const VALVE_FAN_SERVICE: &str = "jupiter-fan-control.service";
|
||||
|
||||
pub struct SteamDeckFan {
|
||||
hwmon: HwMonPath,
|
||||
}
|
||||
|
||||
impl SteamDeckFan {
|
||||
pub fn maybe_find() -> Option<Self> {
|
||||
find_hwmon(crate::sys::SYSFS_ROOT).map(|hwmon| Self { hwmon })
|
||||
}
|
||||
|
||||
pub fn maybe_find_thermal_zone() -> Option<BasicEntityPath> {
|
||||
find_thermal_zone(crate::sys::SYSFS_ROOT)
|
||||
}
|
||||
}
|
||||
|
||||
impl super::super::Adapter for SteamDeckFan {
|
||||
fn on_enable_toggled(&self, settings: &Settings) {
|
||||
on_set_enable(settings, &self.hwmon)
|
||||
}
|
||||
|
||||
fn control_fan(&self, settings: &Settings, sensor: &crate::adapters::traits::SensorReading) {
|
||||
enforce_jupiter_status(true);
|
||||
do_fan_control(settings, &self.hwmon, sensor.value)
|
||||
}
|
||||
|
||||
fn sensor<'a: 'b, 'b>(&'a self) -> Option<&'b dyn crate::adapters::SensorAdapter> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl super::super::super::SensorAdapter for SteamDeckFan {
|
||||
fn read(&self) -> Result<crate::adapters::SensorReading, std::io::Error> {
|
||||
Ok(crate::adapters::SensorReading {
|
||||
value: read_fan(&self.hwmon)? as f64,
|
||||
meaning: crate::adapters::SensorType::Fan,
|
||||
name: "Steam Deck Fan",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const RECALCULATE_ATTR: HwMonAttribute = HwMonAttribute::custom("recalculate");
|
||||
const FAN1_INPUT_ATTR: HwMonAttribute = HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Input);
|
||||
const FAN1_LABEL_ATTR: HwMonAttribute = HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Label);
|
||||
const FAN1_TARGET_ATTR: HwMonAttribute = HwMonAttribute::custom("fan1_target");
|
||||
|
||||
const HWMON_NEEDS: [HwMonAttribute; 3] = [
|
||||
//RECALCULATE_ATTR,
|
||||
FAN1_INPUT_ATTR,
|
||||
FAN1_TARGET_ATTR,
|
||||
FAN1_LABEL_ATTR,
|
||||
];
|
||||
|
||||
fn find_hwmon<P: AsRef<std::path::Path>>(path: P) -> Option<HwMonPath> {
|
||||
let syspath = SysPath::path(path);
|
||||
match syspath.hwmon(attributes(HWMON_NEEDS.into_iter()))
|
||||
{
|
||||
Err(e) => {
|
||||
log::error!("sysfs hwmon iter error while finding Steam Deck fan: {}", e);
|
||||
None
|
||||
},
|
||||
Ok(mut iter) => {
|
||||
if let Some(entity) = iter.next() {
|
||||
log::info!("Found Steam Deck fan hwmon {}", entity.as_ref().display());
|
||||
Some(entity)
|
||||
} else {
|
||||
log::error!("sysfs hwmon iter empty while finding Steam Deck fan: [no capable results]");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_thermal_zone<P: AsRef<std::path::Path>>(path: P) -> Option<BasicEntityPath> {
|
||||
let syspath = SysPath::path(path);
|
||||
match syspath.class("thermal",
|
||||
|ent: &BasicEntityPath| ent.exists(&"temp".to_owned()) && ent.exists(&"type".to_owned()) && ent.attribute("type".to_owned()).map(|val: String| val.to_lowercase() != "processor").unwrap_or(false))
|
||||
{
|
||||
Err(e) => {
|
||||
log::error!("sysfs thermal class iter error while finding Steam Deck thermal zone: {}", e);
|
||||
None
|
||||
},
|
||||
Ok(mut iter) => {
|
||||
if let Some(entity) = iter.next() {
|
||||
log::info!("Found thermal zone {}", entity.as_ref().display());
|
||||
Some(entity)
|
||||
} else {
|
||||
log::error!("sysfs thermal class iter empty while finding Steam Deck thermal zone: [no capable results]");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_set_enable(settings: &Settings, hwmon: &HwMonPath) {
|
||||
// stop/start jupiter fan control (since the client-side way of doing this was removed :( )
|
||||
enforce_jupiter_status(settings.enable);
|
||||
if let Err(e) = write_fan_recalc(hwmon, settings.enable) {
|
||||
log::error!("runtime failed to write to fan recalculate file: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn enforce_jupiter_status(enabled: bool) {
|
||||
// enabled refers to whether this plugin's functionality is enabled,
|
||||
// not the jupiter fan control service
|
||||
let service_status = detect_jupiter_fan_service();
|
||||
log::debug!("fan control service is enabled? {}", service_status);
|
||||
if enabled == service_status {
|
||||
// do not run Valve's fan service along with Fantastic, since they fight
|
||||
if enabled {
|
||||
stop_fan_service();
|
||||
} else {
|
||||
start_fan_service();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_jupiter_fan_service() -> bool {
|
||||
match std::process::Command::new("systemctl")
|
||||
.args(["is-active", VALVE_FAN_SERVICE])
|
||||
.output() {
|
||||
Ok(cmd) => String::from_utf8_lossy(&cmd.stdout).trim() == "active",
|
||||
Err(e) => {
|
||||
log::error!("`systemctl is-active {}` err: {}", VALVE_FAN_SERVICE, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_fan_service() {
|
||||
match std::process::Command::new("systemctl")
|
||||
.args(["start", VALVE_FAN_SERVICE])
|
||||
.output() {
|
||||
Err(e) => log::error!("`systemctl start {}` err: {}", VALVE_FAN_SERVICE, e),
|
||||
Ok(out) => log::debug!("started `{}`:\nstdout:{}\nstderr:{}", VALVE_FAN_SERVICE, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr)),
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_fan_service() {
|
||||
match std::process::Command::new("systemctl")
|
||||
.args(["stop", VALVE_FAN_SERVICE])
|
||||
.output() {
|
||||
Err(e) => log::error!("`systemctl stop {}` err: {}", VALVE_FAN_SERVICE, e),
|
||||
Ok(out) => log::debug!("stopped `{}`:\nstdout:{}\nstderr:{}", VALVE_FAN_SERVICE, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr)),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_fan_recalc(hwmon: &HwMonPath, enabled: bool) -> Result<(), std::io::Error> {
|
||||
hwmon.set(RECALCULATE_ATTR, enabled as u8)
|
||||
//write_single(format!("/sys/class/hwmon/hwmon{}/recalculate", HWMON_INDEX), enabled as u8)
|
||||
}
|
||||
|
||||
fn write_fan_target(hwmon: &HwMonPath, rpm: u64) -> Result<(), std::io::Error> {
|
||||
hwmon.set(FAN1_TARGET_ATTR, rpm)
|
||||
//write_single(format!("/sys/class/hwmon/hwmon{}/fan1_target", HWMON_INDEX), rpm)
|
||||
}
|
||||
|
||||
fn read_fan(hwmon: &HwMonPath) -> std::io::Result<u64> {
|
||||
match hwmon.attribute(FAN1_INPUT_ATTR){
|
||||
Ok(x) => Ok(x),
|
||||
Err(sysfuss::EitherErr2::First(e)) => {
|
||||
log::error!("Failed Steam Deck read_fan(): {}", e);
|
||||
Err(e)
|
||||
},
|
||||
Err(sysfuss::EitherErr2::Second(e)) => {
|
||||
log::error!("Failed Steam Deck read_fan(): {}", e);
|
||||
Err(std::io::Error::other(e))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn fan_range() -> std::ops::Range<f64> {
|
||||
let sys = sysinfo::System::new_with_specifics(
|
||||
sysinfo::RefreshKind::new().with_cpu(sysinfo::CpuRefreshKind::everything()),
|
||||
);
|
||||
if sys.cpus().is_empty() {
|
||||
1.0..7123.0
|
||||
} else {
|
||||
let cpu_name = sys.cpus()[0].brand();
|
||||
if cpu_name.contains("AMD Custom APU 0405") {
|
||||
// LCD
|
||||
1.0..7000.0
|
||||
} else if cpu_name.contains("AMD Custom APU 0932") {
|
||||
// OLED
|
||||
1.0..9000.0
|
||||
} else {
|
||||
// Hopefully never reached
|
||||
1.0..7001.0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn do_fan_control(settings: &Settings, hwmon: &HwMonPath, thermal_zone: f64) {
|
||||
/*
|
||||
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_bounds = fan_range();
|
||||
let temperature_ratio = ((thermal_zone as f64)/1000.0) / 100.0 /* temperature range in C */;
|
||||
let mut index = None;
|
||||
for i in (0..settings.curve.len()).rev() {
|
||||
if settings.curve[i].x < temperature_ratio {
|
||||
index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let fan_ratio = if settings.interpolate {
|
||||
interpolate_fan(settings, index, temperature_ratio)
|
||||
} else {
|
||||
step_fan(settings, index, temperature_ratio)
|
||||
};
|
||||
let fan_speed: u64 = ((fan_ratio * (fan_bounds.end - fan_bounds.start)) + fan_bounds.start) as _;
|
||||
if let Err(e) = write_fan_target(hwmon, fan_speed) {
|
||||
log::error!("Failed to write to Steam Deck 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
|
||||
*/
|
||||
// step fan, what are you doing?
|
||||
if let Some(index) = index {
|
||||
settings.curve[index].y
|
||||
} else {
|
||||
if settings.curve.is_empty() {
|
||||
1.0
|
||||
} else {
|
||||
0.5
|
||||
}
|
||||
}
|
||||
}
|
2
backend-rs/src/adapters/fans/steam_deck/mod.rs
Normal file
2
backend-rs/src/adapters/fans/steam_deck/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod adapter;
|
||||
pub use adapter::SteamDeckFan;
|
5
backend-rs/src/adapters/mod.rs
Normal file
5
backend-rs/src/adapters/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod traits;
|
||||
pub use traits::{FanAdapter, SensorAdapter, SensorReading, SensorType};
|
||||
|
||||
pub mod fans;
|
||||
pub mod sensors;
|
12
backend-rs/src/adapters/sensors/dev_mode.rs
Normal file
12
backend-rs/src/adapters/sensors/dev_mode.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
pub struct DevModeSensor;
|
||||
|
||||
impl super::Adapter for DevModeSensor {
|
||||
fn read(&self) -> Result<super::Reading, std::io::Error> {
|
||||
log::info!("read invoked");
|
||||
return Ok(super::Reading {
|
||||
value: 42_000.0,
|
||||
meaning: super::super::SensorType::Temperature,
|
||||
name: "DevModeSensor",
|
||||
})
|
||||
}
|
||||
}
|
8
backend-rs/src/adapters/sensors/mod.rs
Normal file
8
backend-rs/src/adapters/sensors/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
mod dev_mode;
|
||||
pub use dev_mode::DevModeSensor;
|
||||
|
||||
mod thermal_zone;
|
||||
pub use thermal_zone::ThermalZoneSensor;
|
||||
|
||||
pub(self) use super::SensorAdapter as Adapter;
|
||||
pub(self) use super::SensorReading as Reading;
|
44
backend-rs/src/adapters/sensors/thermal_zone.rs
Normal file
44
backend-rs/src/adapters/sensors/thermal_zone.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use sysfuss::BasicEntityPath;
|
||||
use sysfuss::SysEntityAttributesExt;
|
||||
|
||||
pub struct ThermalZoneSensor {
|
||||
zone: BasicEntityPath,
|
||||
}
|
||||
|
||||
impl ThermalZoneSensor {
|
||||
pub fn new<P: AsRef<std::path::Path>>(zone_path: P) -> Self {
|
||||
Self { zone: BasicEntityPath::new(zone_path) }
|
||||
}
|
||||
|
||||
pub fn new_if_exists<P: AsRef<std::path::Path>>(zone_path: P) -> Option<Self> {
|
||||
if zone_path.as_ref().exists() {
|
||||
Some(Self { zone: BasicEntityPath::new(zone_path) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Adapter for ThermalZoneSensor {
|
||||
fn read(&self) -> Result<super::Reading, std::io::Error> {
|
||||
Ok(super::Reading {
|
||||
value: read_thermal_zone(&self.zone)? as f64,
|
||||
meaning: super::super::SensorType::Temperature,
|
||||
name: "thermal_zone",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn read_thermal_zone(entity: &BasicEntityPath) -> std::io::Result<u64> {
|
||||
match entity.attribute("temp".to_owned()) {
|
||||
Ok(x) => Ok(x),
|
||||
Err(sysfuss::EitherErr2::First(e)) => {
|
||||
log::error!("Failed read_thermal_zone(): {}", e);
|
||||
Err(e)
|
||||
},
|
||||
Err(sysfuss::EitherErr2::Second(e)) => {
|
||||
log::error!("Failed read_thermal_zone(): {}", e);
|
||||
Err(std::io::Error::other(e))
|
||||
},
|
||||
}
|
||||
}
|
30
backend-rs/src/adapters/traits.rs
Normal file
30
backend-rs/src/adapters/traits.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use crate::datastructs::Settings;
|
||||
|
||||
pub trait FanAdapter: Send + Sync {
|
||||
/// Handle fan enable UI toggle
|
||||
fn on_enable_toggled(&self, settings: &Settings);
|
||||
/// Apply fan settings to fan (probably through udev/sysfs)
|
||||
fn control_fan(&self, settings: &Settings, sensor: &SensorReading);
|
||||
/// Get fan speed sensor
|
||||
fn sensor<'a: 'b, 'b>(&'a self) -> Option<&'b dyn SensorAdapter> { None }
|
||||
}
|
||||
|
||||
pub trait SensorAdapter: Send + Sync {
|
||||
/// Read sensor value
|
||||
fn read(&self) -> Result<SensorReading, std::io::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SensorReading {
|
||||
pub value: f64,
|
||||
pub meaning: SensorType,
|
||||
pub name: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SensorType {
|
||||
Temperature, // milli-degrees Celcius
|
||||
Fan, // revolutions per minute (RPM)
|
||||
#[allow(dead_code)]
|
||||
Unknown,
|
||||
}
|
238
backend-rs/src/api.rs
Normal file
238
backend-rs/src/api.rs
Normal file
|
@ -0,0 +1,238 @@
|
|||
use crate::services::fantastic::*;
|
||||
|
||||
use usdpl_back::nrpc::_helpers::futures::{StreamExt, FutureExt};
|
||||
|
||||
use super::control::ControlRuntime;
|
||||
|
||||
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
pub const NAME: &'static str = env!("CARGO_PKG_NAME");
|
||||
|
||||
const FAN_READ_PERIOD: std::time::Duration = std::time::Duration::from_millis(1000);
|
||||
const TEMPERATURE_READ_PERIOD: std::time::Duration = std::time::Duration::from_millis(2000);
|
||||
|
||||
pub struct FanService {
|
||||
ctrl: ControlRuntime,
|
||||
}
|
||||
|
||||
impl FanService {
|
||||
pub fn new(runtime: ControlRuntime) -> Self {
|
||||
runtime.run();
|
||||
Self {
|
||||
ctrl: runtime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn once_true() -> impl std::iter::Iterator<Item = bool> {
|
||||
// iters over [true, false, false, ...]
|
||||
std::iter::once(true).chain(std::iter::repeat(false))
|
||||
}
|
||||
|
||||
#[::nrpc::_helpers::async_trait::async_trait]
|
||||
impl<'a> IFan<'a> for FanService {
|
||||
async fn echo(
|
||||
&mut self,
|
||||
input: EchoMessage,
|
||||
) -> Result<EchoMessage, Box<dyn std::error::Error + Send>> {
|
||||
Ok(input)
|
||||
}
|
||||
async fn hello(
|
||||
&mut self,
|
||||
input: NameMessage,
|
||||
) -> Result<HelloResponse, Box<dyn std::error::Error + Send>> {
|
||||
Ok(HelloResponse {
|
||||
phrase: format!("Hello {}", input.name)
|
||||
})
|
||||
}
|
||||
async fn version(
|
||||
&mut self,
|
||||
_input: Empty,
|
||||
) -> Result<VersionMessage, Box<dyn std::error::Error + Send>> {
|
||||
Ok(
|
||||
VersionMessage {
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
//display: VERSION.to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
async fn version_str(
|
||||
&mut self,
|
||||
_input: Empty,
|
||||
) -> Result<VersionDisplayMessage, Box<dyn std::error::Error + Send>> {
|
||||
Ok(
|
||||
VersionDisplayMessage {
|
||||
display: VERSION.to_owned(),
|
||||
}
|
||||
)
|
||||
}
|
||||
async fn name(
|
||||
&mut self,
|
||||
_input: Empty,
|
||||
) -> Result<NameMessage, Box<dyn std::error::Error + Send>> {
|
||||
Ok(
|
||||
NameMessage {
|
||||
name: NAME.to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
async fn get_fan_rpm<'b: 'a>(
|
||||
&mut self,
|
||||
_input: Empty,
|
||||
) -> Result<
|
||||
usdpl_back::nrpc::ServiceServerStream<'b, RpmMessage>,
|
||||
Box<dyn std::error::Error + Send>,
|
||||
> {
|
||||
let fan_clone = self.ctrl.fan_clone();
|
||||
if fan_clone.sensor().is_some() {
|
||||
let stream = usdpl_back::nrpc::_helpers::futures::stream::iter(once_true()).then(move |is_first| {
|
||||
let fan_clone2 = fan_clone.clone();
|
||||
tokio::task::spawn_blocking(
|
||||
/* tokio::time::sleep(..) is not Unpin (but this is)... *grumble grumble* */
|
||||
move || if !is_first { std::thread::sleep(FAN_READ_PERIOD); })
|
||||
.map(move |_| {
|
||||
if let Some(fan_sensor) = fan_clone2.sensor() {
|
||||
match fan_sensor.read() {
|
||||
Ok(reading) => {
|
||||
log::debug!("get_fan_rpm() success: {}", reading.value);
|
||||
Ok(RpmMessage { rpm: reading.value as u32 })
|
||||
}
|
||||
Err(e) => {
|
||||
Err(usdpl_back::nrpc::ServiceError::Method(Box::<dyn std::error::Error + Send + Sync>::from(format!("Failed to read fan speed: {}", e))))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(usdpl_back::nrpc::ServiceError::Method(Box::<dyn std::error::Error + Send + Sync>::from("Failed to get fan speed sensor")))
|
||||
}
|
||||
})
|
||||
});
|
||||
Ok(Box::new(stream))
|
||||
} else {
|
||||
Ok(Box::new(usdpl_back::nrpc::_helpers::futures::stream::empty()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_temperature<'b: 'a>(
|
||||
&mut self,
|
||||
_input: Empty,
|
||||
) -> Result<
|
||||
usdpl_back::nrpc::ServiceServerStream<'b, TemperatureMessage>,
|
||||
Box<dyn std::error::Error + Send>,
|
||||
> {
|
||||
let sensor_clone = self.ctrl.sensor_clone();
|
||||
let stream = usdpl_back::nrpc::_helpers::futures::stream::iter(once_true()).then(move |is_first| {
|
||||
let sensor_clone2 = sensor_clone.clone();
|
||||
tokio::task::spawn_blocking(
|
||||
/* tokio::time::sleep(..) is not Unpin (but this is)... *grumble grumble* */
|
||||
move || if !is_first { std::thread::sleep(TEMPERATURE_READ_PERIOD); })
|
||||
.map(move |_| {
|
||||
match sensor_clone2.read() {
|
||||
Ok(reading) => {
|
||||
let real_temp = reading.value as f64 / 1000.0;
|
||||
log::debug!("get_temperature() success: {}", real_temp);
|
||||
Ok(TemperatureMessage { temperature: real_temp })
|
||||
},
|
||||
Err(e) => {
|
||||
Err(usdpl_back::nrpc::ServiceError::Method(Box::<dyn std::error::Error + Send + Sync>::from(format!("get_temperature failed to read sensor: {}", e))))
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
Ok(Box::new(stream))
|
||||
}
|
||||
|
||||
async fn set_enable(
|
||||
&mut self,
|
||||
input: EnablementMessage,
|
||||
) -> Result<EnablementMessage, Box<dyn std::error::Error + Send>>{
|
||||
let mut settings = self.ctrl.settings().write().await;
|
||||
if settings.enable != input.is_enabled {
|
||||
let mut state = self.ctrl.state().write().await;
|
||||
settings.enable = input.is_enabled;
|
||||
state.dirty = true;
|
||||
}
|
||||
log::debug!("set_enable({}) success", input.is_enabled);
|
||||
Ok(input)
|
||||
}
|
||||
async fn get_enable(
|
||||
&mut self,
|
||||
_input: Empty,
|
||||
) -> Result<EnablementMessage, Box<dyn std::error::Error + Send>>{
|
||||
let is_enabled = self.ctrl.settings().read().await.enable;
|
||||
log::debug!("get_enable() success");
|
||||
Ok(EnablementMessage { is_enabled })
|
||||
}
|
||||
async fn set_interpolate(
|
||||
&mut self,
|
||||
input: EnablementMessage,
|
||||
) -> Result<EnablementMessage, Box<dyn std::error::Error + Send>>{
|
||||
let mut settings = self.ctrl.settings().write().await;
|
||||
if settings.interpolate != input.is_enabled {
|
||||
let mut state = self.ctrl.state().write().await;
|
||||
settings.interpolate = input.is_enabled;
|
||||
state.dirty = true;
|
||||
}
|
||||
log::debug!("set_interpolate({}) success", input.is_enabled);
|
||||
Ok(input)
|
||||
}
|
||||
async fn get_interpolate(
|
||||
&mut self,
|
||||
_input: Empty,
|
||||
) -> Result<EnablementMessage, Box<dyn std::error::Error + Send>>{
|
||||
let is_enabled = self.ctrl.settings().read().await.interpolate;
|
||||
log::debug!("get_interpolate() success");
|
||||
Ok(EnablementMessage { is_enabled })
|
||||
}
|
||||
async fn get_curve_x(
|
||||
&mut self,
|
||||
_input: Empty,
|
||||
) -> Result<CurveMessageX, Box<dyn std::error::Error + Send>>{
|
||||
let settings = self.ctrl.settings().read().await;
|
||||
let x = settings.curve.iter().map(|p| p.x).collect();
|
||||
log::debug!("get_curve_x() success");
|
||||
Ok(CurveMessageX { x })
|
||||
}
|
||||
async fn get_curve_y(
|
||||
&mut self,
|
||||
_input: Empty,
|
||||
) -> Result<CurveMessageY, Box<dyn std::error::Error + Send>>{
|
||||
let settings = self.ctrl.settings().read().await;
|
||||
let y = settings.curve.iter().map(|p| p.y).collect();
|
||||
log::debug!("get_curve_x() success");
|
||||
Ok(CurveMessageY { y })
|
||||
}
|
||||
async fn add_curve_point(
|
||||
&mut self,
|
||||
point: GraphPoint,
|
||||
) -> Result<Empty, Box<dyn std::error::Error + Send>>{
|
||||
let mut settings = self.ctrl.settings().write().await;
|
||||
settings.curve.push(super::datastructs::GraphPoint {
|
||||
x: point.x,
|
||||
y: point.y
|
||||
});
|
||||
settings.sort_curve();
|
||||
let mut state = self.ctrl.state().write().await;
|
||||
state.dirty = true;
|
||||
log::debug!("add_curve_point(point: {:?}) success", point);
|
||||
Ok(Empty { ok: true })
|
||||
}
|
||||
async fn remove_curve_point(
|
||||
&mut self,
|
||||
input: IndexMessage,
|
||||
) -> Result<Empty, Box<dyn std::error::Error + Send>>{
|
||||
let mut settings = self.ctrl.settings().write().await;
|
||||
let i = input.index as usize;
|
||||
if settings.curve.len() > i {
|
||||
settings.curve.swap_remove(i);
|
||||
settings.sort_curve();
|
||||
let mut state = self.ctrl.state().write().await;
|
||||
state.dirty = true;
|
||||
log::debug!("remove_curve_point(point: {}) success", input.index);
|
||||
Ok(Empty { ok: true })
|
||||
} else {
|
||||
log::debug!("remove_curve_point(index: {}) failed", input.index);
|
||||
Ok(Empty { ok: false })
|
||||
}
|
||||
}
|
||||
}
|
115
backend-rs/src/control.rs
Normal file
115
backend-rs/src/control.rs
Normal file
|
@ -0,0 +1,115 @@
|
|||
//! Fan control
|
||||
|
||||
use std::sync::Arc;
|
||||
//use std::collections::HashMap;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::datastructs::{Settings, State};
|
||||
use super::json::SettingsJson;
|
||||
|
||||
pub struct ControlRuntime {
|
||||
settings: Arc<RwLock<Settings>>,
|
||||
state: Arc<RwLock<State>>,
|
||||
fan_adapter: Arc<Box<dyn crate::adapters::FanAdapter + 'static>>,
|
||||
sensor_adapter: Arc<Box<dyn crate::adapters::SensorAdapter + 'static>>,
|
||||
}
|
||||
|
||||
impl ControlRuntime {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<F: crate::adapters::FanAdapter + 'static, S: crate::adapters::SensorAdapter + 'static>(fan: F, sensor: S) -> Self {
|
||||
Self::new_boxed(Box::new(fan), Box::new(sensor))
|
||||
}
|
||||
|
||||
pub(crate) fn new_boxed(fan: Box<dyn crate::adapters::FanAdapter + 'static>, sensor: Box<dyn crate::adapters::SensorAdapter + 'static>) -> 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)),
|
||||
fan_adapter: Arc::new(fan),
|
||||
sensor_adapter: Arc::new(sensor),
|
||||
}
|
||||
}
|
||||
|
||||
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(crate) fn settings(&self) -> &'_ RwLock<Settings> {
|
||||
&self.settings
|
||||
}
|
||||
|
||||
pub(crate) fn state(&self) -> &'_ RwLock<State> {
|
||||
&self.state
|
||||
}
|
||||
|
||||
pub(crate) fn sensor_clone(&self) -> Arc<Box<dyn crate::adapters::SensorAdapter>> {
|
||||
self.sensor_adapter.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn fan_clone(&self) -> Arc<Box<dyn crate::adapters::FanAdapter>> {
|
||||
self.fan_adapter.clone()
|
||||
}
|
||||
|
||||
pub fn run(&self) -> thread::JoinHandle<()> {
|
||||
let runtime_settings = self.settings_clone();
|
||||
let runtime_state = self.state_clone();
|
||||
let runtime_fan = self.fan_adapter.clone();
|
||||
let runtime_sensor = self.sensor_adapter.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 settings = runtime_settings.blocking_read();
|
||||
if settings.enable {
|
||||
runtime_fan.on_enable_toggled(&settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
start_time = Instant::now();
|
||||
{ // save to file
|
||||
let state = runtime_state.blocking_read();
|
||||
if state.dirty {
|
||||
// save settings to file
|
||||
let settings = runtime_settings.blocking_read();
|
||||
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);
|
||||
}
|
||||
runtime_fan.on_enable_toggled(&settings);
|
||||
drop(state);
|
||||
let mut state = runtime_state.blocking_write();
|
||||
state.dirty = false;
|
||||
}
|
||||
}
|
||||
{ // fan control
|
||||
let settings = runtime_settings.blocking_read();
|
||||
if settings.enable {
|
||||
match runtime_sensor.read() {
|
||||
Err(e) => log::error!("Failed to read sensor for control_fan input: {}", e),
|
||||
Ok(reading) => {
|
||||
runtime_fan.control_fan(&settings, &reading);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thread::sleep(sleep_duration);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_path<P: AsRef<std::path::Path>>(home: P) -> std::path::PathBuf {
|
||||
home.as_ref().join(".config/fantastic/fantastic.json")
|
||||
}
|
119
backend-rs/src/datastructs.rs
Normal file
119
backend-rs/src/datastructs.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use std::default::Default;
|
||||
use std::convert::{Into, From};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::json::{SettingsJson, GraphPointJson};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Settings {
|
||||
pub version: u64,
|
||||
pub enable: bool,
|
||||
pub interpolate: bool,
|
||||
pub curve: Vec<GraphPoint>,
|
||||
}
|
||||
|
||||
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 {
|
||||
let mut result = 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(),
|
||||
},
|
||||
1 => Self {
|
||||
version: 1,
|
||||
enable: other.enable,
|
||||
interpolate: other.interpolate,
|
||||
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
|
||||
},
|
||||
_ => Self {
|
||||
version: 1,
|
||||
enable: other.enable,
|
||||
interpolate: other.interpolate,
|
||||
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
|
||||
}
|
||||
};
|
||||
result.sort_curve();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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)]
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
62
backend-rs/src/json.rs
Normal file
62
backend-rs/src/json.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
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>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GraphPointJson {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
impl Default for SettingsJson {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: 1,
|
||||
enable: false,
|
||||
interpolate: true,
|
||||
curve: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
69
backend-rs/src/main.rs
Normal file
69
backend-rs/src/main.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
mod api;
|
||||
mod adapters;
|
||||
mod control;
|
||||
mod datastructs;
|
||||
mod json;
|
||||
mod sys;
|
||||
|
||||
use simplelog::{WriteLogger, LevelFilter};
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[allow(dead_code)]
|
||||
pub mod services {
|
||||
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
|
||||
}
|
||||
|
||||
use services::fantastic::FanServer;
|
||||
|
||||
const PORT: u16 = 44444;
|
||||
|
||||
fn main() -> Result<(), ()> {
|
||||
WriteLogger::init(
|
||||
#[cfg(debug_assertions)]{LevelFilter::Debug},
|
||||
#[cfg(not(debug_assertions))]{LevelFilter::Info},
|
||||
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);
|
||||
usdpl_back::Server::new(PORT)
|
||||
.register(FanServer::new(
|
||||
api::FanService::new(control::ControlRuntime::new_boxed(
|
||||
adapters::fans::SteamDeckFan::maybe_find()
|
||||
.map(|f| Box::new(f) as Box<dyn adapters::FanAdapter>)
|
||||
.or_else(
|
||||
|| adapters::fans::RogAllyFan::maybe_find()
|
||||
.map(|f| Box::new(f) as Box<dyn adapters::FanAdapter>)
|
||||
)
|
||||
.unwrap_or_else(|| Box::new(adapters::fans::DevModeFan)),
|
||||
adapters::fans::SteamDeckFan::maybe_find_thermal_zone()
|
||||
.map(|t| Box::new(adapters::sensors::ThermalZoneSensor::new(t)) as Box<dyn adapters::SensorAdapter>)
|
||||
.or_else(
|
||||
|| adapters::sensors::ThermalZoneSensor::new_if_exists("/sys/thermal/thermal_zone0")
|
||||
.map(|t| Box::new(t) as Box<dyn adapters::SensorAdapter>)
|
||||
)
|
||||
.unwrap_or_else(|| Box::new(adapters::sensors::DevModeSensor))
|
||||
))
|
||||
))
|
||||
.run_blocking()
|
||||
.unwrap();
|
||||
Ok(())
|
||||
/*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!");
|
||||
}
|
1
backend-rs/src/sys.rs
Normal file
1
backend-rs/src/sys.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub const SYSFS_ROOT: &str = "/";
|
23
fantastic.json
Normal file
23
fantastic.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"version": 1,
|
||||
"enable": true,
|
||||
"interpolate": true,
|
||||
"curve": [
|
||||
{
|
||||
"x": 0.5074626865671642,
|
||||
"y": 0.225
|
||||
},
|
||||
{
|
||||
"x": 0.6902985074626866,
|
||||
"y": 0.795
|
||||
}
|
||||
],
|
||||
"fan_bounds": {
|
||||
"min": 1.0,
|
||||
"max": 7000.0
|
||||
},
|
||||
"temperature_bounds": {
|
||||
"min": 0.0,
|
||||
"max": 100.0
|
||||
}
|
||||
}
|
229
main.py
229
main.py
|
@ -1,223 +1,42 @@
|
|||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
HOME_DIR = str(pathlib.Path(os.getcwd()).parent.parent.resolve())
|
||||
PARENT_DIR = str(pathlib.Path(__file__).parent.resolve())
|
||||
|
||||
LOG_LOCATION = "/tmp/fantastic.py.log"
|
||||
|
||||
import logging
|
||||
|
||||
logging.basicConfig(
|
||||
filename = "/home/deck/.fantastic.log",
|
||||
filename = LOG_LOCATION,
|
||||
format = '%(asctime)s %(levelname)s %(message)s',
|
||||
filemode = 'w',
|
||||
force = True)
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
FAN_MINIMUM = 0
|
||||
FAN_MAXIMUM = 7000 # max is more around 7100
|
||||
|
||||
TEMPERATURE_MINIMUM = 0.0
|
||||
TEMPERATURE_MAXIMUM = 100.0
|
||||
|
||||
DATA_SAVE_FILE = "fantastic.json"
|
||||
DATA_SAVE_FOLDER = "/home/deck/.config/fantastic/"
|
||||
DATA_SAVE_PATH = DATA_SAVE_FOLDER + DATA_SAVE_FILE
|
||||
|
||||
DEFAULT_DATA = {
|
||||
"version": 0,
|
||||
"enable": False,
|
||||
"interpolate": True,
|
||||
"curve": [], # items are {x: int (distance from left), y: int (distance from top, NOT bottom)}
|
||||
}
|
||||
logging.info(f"Fantastic main.py https://github.com/NGnius/Fantastic")
|
||||
|
||||
class Plugin:
|
||||
settings = None
|
||||
is_changed = False
|
||||
|
||||
plot_width = 1;
|
||||
plot_height = 1;
|
||||
|
||||
period_s = 1.0;
|
||||
|
||||
async def set_curve(self, curve):
|
||||
await self.wait_for_ready(self)
|
||||
self.settings["curve"] = curve
|
||||
self.is_changed = True
|
||||
|
||||
async def get_curve(self):
|
||||
await self.wait_for_ready(self)
|
||||
return self.settings["curve"]
|
||||
|
||||
async def get_curve_point(self, index):
|
||||
await self.wait_for_ready(self)
|
||||
return self.settings["curve"][index]
|
||||
|
||||
async def set_curve_point(self, index, point):
|
||||
await self.wait_for_ready(self)
|
||||
self.settings["curve"][index] = point
|
||||
self.is_changed = True
|
||||
|
||||
async def add_curve_point(self, point):
|
||||
await self.wait_for_ready(self)
|
||||
self.settings["curve"].append(point)
|
||||
self.settings["curve"].sort(key=lambda p: p["x"])
|
||||
self.is_changed = True
|
||||
x = point["x"]
|
||||
y = point["y"]
|
||||
logger.debug(f"Added point (Temp:{100*x},PWM%:{100*y}) ~= ({x*self.plot_width},{y*self.plot_height})")
|
||||
|
||||
async def remove_curve_point(self, index):
|
||||
await self.wait_for_ready(self)
|
||||
del(self.settings["curve"][index])
|
||||
self.is_changed = True
|
||||
|
||||
async def set_enable(self, enable: bool):
|
||||
await self.wait_for_ready(self)
|
||||
self.settings["enable"] = enable
|
||||
on_set_enable(enable)
|
||||
self.is_changed = True
|
||||
|
||||
async def get_enable(self) -> bool:
|
||||
await self.wait_for_ready(self)
|
||||
return self.settings["enable"]
|
||||
|
||||
async def set_interpol(self, interpolate: bool):
|
||||
await self.wait_for_ready(self)
|
||||
self.settings["interpolate"] = interpolate
|
||||
self.is_changed = True
|
||||
|
||||
async def get_interpol(self) -> bool:
|
||||
await self.wait_for_ready(self)
|
||||
return self.settings["interpolate"]
|
||||
|
||||
async def set_plot_size(self, x, y):
|
||||
logging.debug(f"Set plot size to ({x},{y})")
|
||||
self.plot_width = x
|
||||
self.plot_height = y
|
||||
|
||||
async def get_fan_rpm(self) -> int:
|
||||
return get_fan_input()
|
||||
|
||||
async def get_temperature(self) -> int:
|
||||
return int(thermal_zone(0))
|
||||
|
||||
async def set_poll_period(self, period):
|
||||
self.period_s = period
|
||||
|
||||
def save(self):
|
||||
if not os.path.exists(DATA_SAVE_FOLDER):
|
||||
os.mkdir(DATA_SAVE_FOLDER)
|
||||
with open(DATA_SAVE_PATH, "w") as data_file :
|
||||
json.dump(self.settings, data_file)
|
||||
|
||||
async def wait_for_ready(self):
|
||||
while self.settings is None:
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
def do_fan_control(self):
|
||||
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))
|
||||
|
||||
|
||||
def interpolate_fan(self, index, temperature_ratio):
|
||||
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
|
||||
|
||||
def step_fan(self, index, temperature_ratio):
|
||||
curve = self.settings["curve"]
|
||||
if index != -1:
|
||||
return 1 - curve[index]["y"]
|
||||
else:
|
||||
if len(curve) == 0:
|
||||
return 1
|
||||
else:
|
||||
return 0.5
|
||||
|
||||
backend_proc = None
|
||||
# Asyncio-compatible long-running code, executed in a task when the plugin is loaded
|
||||
async def _main(self):
|
||||
# startup
|
||||
if os.path.exists(DATA_SAVE_PATH):
|
||||
with open(DATA_SAVE_PATH, "r") as data_file:
|
||||
self.settings = json.load(data_file)
|
||||
else:
|
||||
self.settings = dict(DEFAULT_DATA)
|
||||
try:
|
||||
self.settings["version"]
|
||||
except:
|
||||
self.settings = dict(DEFAULT_DATA)
|
||||
while self.settings["version"] != DEFAULT_DATA["version"]:
|
||||
# TODO specific upgrade functionality
|
||||
self.settings["version"] = DEFAULT_DATA["version"]
|
||||
self.settings["enable"] = DEFAULT_DATA["enable"]
|
||||
self.settings["interpolate"] = DEFAULT_DATA["interpolate"]
|
||||
self.settings["curve"] = DEFAULT_DATA["curve"]
|
||||
self.is_changed = True
|
||||
on_set_enable(self.settings["enable"])
|
||||
# work loop
|
||||
self.backend_proc = subprocess.Popen(
|
||||
[PARENT_DIR + "/bin/backend"],
|
||||
env = dict(os.environ))
|
||||
while True:
|
||||
if self.is_changed:
|
||||
self.save(self)
|
||||
self.is_changed = False
|
||||
if self.settings["enable"]:
|
||||
# custom fan curve is enabled
|
||||
self.do_fan_control(self)
|
||||
await asyncio.sleep(self.period_s)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
def thermal_zone(index: int) -> float:
|
||||
with open(f"/sys/class/thermal/thermal_zone{index}/temp", "r") as f:
|
||||
result = float(f.read().strip()) / 1000.0
|
||||
logging.debug(f"Got {result}'C from thermal_zone{index}")
|
||||
return result
|
||||
|
||||
def set_fan_target(rpm: int):
|
||||
logging.debug(f"Setting fan1_target to {rpm}")
|
||||
with open("/sys/class/hwmon/hwmon5/fan1_target", "w") as f:
|
||||
f.write(str(rpm))
|
||||
|
||||
def get_fan_input() -> int:
|
||||
with open("/sys/class/hwmon/hwmon5/fan1_input", "r") as f:
|
||||
rpm = int(f.read().strip())
|
||||
#logging.debug(f"Got {rpm} from fan1_input") # this is too spammy; runs every 0.5s
|
||||
return rpm
|
||||
|
||||
def on_enable():
|
||||
with open("/sys/class/hwmon/hwmon5/recalculate", "w") as f:
|
||||
f.write("1")
|
||||
# TODO stop system fan control
|
||||
|
||||
def on_disable():
|
||||
with open("/sys/class/hwmon/hwmon5/recalculate", "w") as f:
|
||||
f.write("0")
|
||||
# TODO restart system fan control
|
||||
|
||||
def on_set_enable(enable):
|
||||
if enable:
|
||||
on_enable()
|
||||
else:
|
||||
on_disable()
|
||||
async def _unload(self):
|
||||
# shutdown
|
||||
if self.backend_proc is not None:
|
||||
self.backend_proc.terminate()
|
||||
try:
|
||||
self.backend_proc.wait(timeout=5) # 5 seconds timeout
|
||||
except subprocess.TimeoutExpired:
|
||||
self.backend_proc.kill()
|
||||
self.backend_proc = None
|
||||
|
|
283
main_view.html
283
main_view.html
|
@ -1,283 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/steam_resource/css/2.css">
|
||||
<link rel="stylesheet" href="/steam_resource/css/39.css">
|
||||
<link rel="stylesheet" href="/steam_resource/css/library.css">
|
||||
<script src="/static/library.js"></script>
|
||||
<script>
|
||||
const PLOT_HEIGHT = 200;
|
||||
const PLOT_WIDTH = 270;
|
||||
const OFFSET_X = 0;
|
||||
const OFFSET_Y = 0;
|
||||
// state
|
||||
let curve = [];
|
||||
let plotClickIsHandled = false;
|
||||
|
||||
// back-end
|
||||
|
||||
function setCurve(curve) {
|
||||
return call_plugin_method("set_curve", {"curve": curve});
|
||||
}
|
||||
|
||||
function getCurve() {
|
||||
return call_plugin_method("get_curve", {});
|
||||
}
|
||||
|
||||
function getCurvePoint(index) {
|
||||
return call_plugin_method("get_curve_point", {"index": index});
|
||||
}
|
||||
|
||||
function setCurvePoint(index, point) {
|
||||
return call_plugin_method("set_curve_point", {"index": index, "point": point});
|
||||
}
|
||||
|
||||
function addCurvePoint(point) {
|
||||
return call_plugin_method("add_curve_point", {"point": point});
|
||||
}
|
||||
|
||||
function removeCurvePoint(index) {
|
||||
return call_plugin_method("remove_curve_point", {"index": index});
|
||||
}
|
||||
|
||||
function setEnable(enable) {
|
||||
return call_plugin_method("set_enable", {"enable": enable});
|
||||
}
|
||||
|
||||
function getEnable() {
|
||||
return call_plugin_method("get_enable", {});
|
||||
}
|
||||
|
||||
function setInterpol(enable) {
|
||||
return call_plugin_method("set_interpol", {"interpolate": enable});
|
||||
}
|
||||
|
||||
function getInterpol() {
|
||||
return call_plugin_method("get_interpol", {});
|
||||
}
|
||||
|
||||
function getFanRpm() {
|
||||
return call_plugin_method("get_fan_rpm", {});
|
||||
}
|
||||
|
||||
function getTemperature() {
|
||||
return call_plugin_method("get_temperature", {});
|
||||
}
|
||||
|
||||
function setPlotSize(x, y) {
|
||||
return call_plugin_method("set_plot_size", {"x": x, "y": y});
|
||||
}
|
||||
|
||||
// events
|
||||
|
||||
async function onload_body() {
|
||||
let hiderDiv = document.getElementById("hiderDiv");
|
||||
let graphDiv = document.getElementById("graphDiv");
|
||||
await setPlotSize(PLOT_WIDTH, PLOT_HEIGHT);
|
||||
const state_controlToggle = await getEnable(); // retrieve from back-end
|
||||
setToggleState(document.getElementById("controlToggle"), state_controlToggle);
|
||||
const state_interpolToggle = await getInterpol(); // retrieve from back-end
|
||||
setToggleState(document.getElementById("interpolToggle"), state_interpolToggle);
|
||||
showHideElement(hiderDiv, state_controlToggle);
|
||||
if (state_controlToggle) {
|
||||
curve = await getCurve();
|
||||
buildCurvePlot(curve);
|
||||
}
|
||||
window.setInterval(pollStats, 500);
|
||||
console.log("Loaded");
|
||||
}
|
||||
|
||||
async function onclick_graphDiv(e) {
|
||||
console.log("Click @ (" + e.layerX.toString() + ", " + e.layerY.toString() + ")");
|
||||
if (plotClickIsHandled) {
|
||||
plotClickIsHandled = false;
|
||||
} else {
|
||||
await addCurvePoint({"x": (e.layerX - OFFSET_X) / PLOT_WIDTH, "y": (e.layerY - OFFSET_Y) / PLOT_HEIGHT});
|
||||
curve = await getCurve();
|
||||
buildCurvePlot(curve);
|
||||
}
|
||||
}
|
||||
|
||||
async function onclick_controlToggle() {
|
||||
console.log("Click @ controlToggle");
|
||||
let hiderDiv = document.getElementById("hiderDiv");
|
||||
let graphDiv = document.getElementById("graphDiv");
|
||||
let controlToggle = document.getElementById("controlToggle");
|
||||
const state_controlToggle = getToggleState(controlToggle);
|
||||
await setEnable(!state_controlToggle); // notify back-end
|
||||
setToggleState(controlToggle, !state_controlToggle);
|
||||
if (!state_controlToggle) {
|
||||
curve = await getCurve();
|
||||
buildCurvePlot(curve);
|
||||
}
|
||||
showHideElement(hiderDiv, !state_controlToggle);
|
||||
}
|
||||
|
||||
async function onclick_interpolToggle() {
|
||||
console.log("Click @ interpolToggle");
|
||||
let interpolToggle = document.getElementById("interpolToggle");
|
||||
const state_interpolToggle = getToggleState(interpolToggle);
|
||||
await setInterpol(!state_interpolToggle); // notify back-end
|
||||
setToggleState(interpolToggle, !state_interpolToggle);
|
||||
}
|
||||
|
||||
async function onclick_plotPoint(e, index) {
|
||||
console.log("Click @ plotPoint " + index.toString());
|
||||
plotClickIsHandled = true; // this must be before the first async call (janky!)
|
||||
await removeCurvePoint(index);
|
||||
curve = await getCurve();
|
||||
buildCurvePlot(curve);
|
||||
//e.stopPropogation();
|
||||
}
|
||||
|
||||
// common
|
||||
|
||||
function buildCurvePlot(curve_points) {
|
||||
// TODO
|
||||
let graphDiv = document.getElementById("graphDiv");
|
||||
let newStr = "<span style=\"font-size:x-small;position:absolute;left:1px;top:-1px;\">100%</span><span style=\"font-size:x-small;position:absolute;left:1px;bottom:-1px;\">0</span><span style=\"font-size:x-small;position:absolute;left:-2px;bottom:50%;writing-mode:vertical-lr;text-orientation:mixed;\">Fan</span><span style=\"font-size:x-small;position:absolute;right:1px;bottom:-1px;\">100</span><span style=\"font-size:x-small;position:absolute;left:35%;bottom:-1px;\">Temperature (°C)</span>";
|
||||
for (let i = 0; i < curve_points.length; i++) {
|
||||
const point = curve_points[i];
|
||||
newStr += "<span style=\"position:absolute;"
|
||||
newStr += "top:" + Math.round(point["y"]*PLOT_HEIGHT + OFFSET_Y + 1).toString() + "px;left:" + Math.round(point["x"]*PLOT_WIDTH + OFFSET_X + 1).toString() + "px;";
|
||||
newStr += "width:8px;height:8px;background-color:#1a9fff;border-radius:4px\" id=\"plotPoint";
|
||||
newStr += i.toString() + "\" onclick=\"onclick_plotPoint(event," + i.toString() + ")\"></span>";
|
||||
}
|
||||
graphDiv.innerHTML = newStr;
|
||||
}
|
||||
|
||||
function pollStats() {
|
||||
/*getFanRpm().then(speed => {
|
||||
let fanNow = document.getElementById("fanNow");
|
||||
fanNow.innerText = speed.toString() + " RPM";
|
||||
});
|
||||
sleep(1).then(_ => {});
|
||||
getTemperature().then(temp => {
|
||||
let tempNow = document.getElementById("tempNow");
|
||||
tempNow.innerText = temp.toString() + " °C";
|
||||
});
|
||||
sleep(1).then(_ => {});*/
|
||||
pollStatsAsync().then(_ => {});
|
||||
}
|
||||
|
||||
async function pollStatsAsync() {
|
||||
let fanNow = document.getElementById("fanNow");
|
||||
let tempNow = document.getElementById("tempNow");
|
||||
|
||||
const speed = await getFanRpm();
|
||||
const temp = await getTemperature();
|
||||
|
||||
fanNow.innerText = speed.toString() + " RPM";
|
||||
tempNow.innerText = temp.toString() + " °C";
|
||||
}
|
||||
|
||||
function showHideElement(elem, visible) {
|
||||
if (visible) {
|
||||
elem.style.visibility = "visible";
|
||||
elem.style.height = "auto";
|
||||
} else {
|
||||
elem.style.visibility = "hidden";
|
||||
elem.style.height = "0px";
|
||||
}
|
||||
}
|
||||
|
||||
function setToggleState(toggle, state) {
|
||||
const ENABLED_CLASS = "gamepaddialog_On_yLrDA";
|
||||
if (state && !toggle.classList.contains(ENABLED_CLASS)) {
|
||||
toggle.classList.add(ENABLED_CLASS);
|
||||
}
|
||||
|
||||
if (!state && toggle.classList.contains(ENABLED_CLASS)) {
|
||||
toggle.classList.remove(ENABLED_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
function getToggleState(toggle) {
|
||||
return toggle.classList.contains("gamepaddialog_On_yLrDA");
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="onload_body()" style="overflow-x:hidden;">
|
||||
<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">
|
||||
<div class="quickaccesscontrols_PanelSectionRow_26R5w">
|
||||
<div class="quickaccesscontrols_PanelSectionRow_26R5w">
|
||||
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_ExtraPaddingOnChildrenBelow_3nLNL gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable" style="--indent-level:0;">
|
||||
<div class="gamepaddialog_FieldLabelRow_2VcTl">
|
||||
<div class="gamepaddialog_FieldLabel_3jMlJ">
|
||||
Custom Fan Curve
|
||||
</div>
|
||||
<div class="gamepaddialog_FieldChildren_2rhav">
|
||||
<div id="controlToggle" tabindex="0" class="gamepaddialog_Toggle_9Ql-o Focusable" onclick="onclick_controlToggle()">
|
||||
<div class="gamepaddialog_ToggleRail_2bl0i"></div>
|
||||
<div class="gamepaddialog_ToggleSwitch_1PQpp"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gamepaddialog_FieldDescription_1W1to">Overrides SteamOS fan curve</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hiderDiv">
|
||||
<div id="graphDiv" style="height:200px;width:270px;border:1px solid #1a9fff;position:relative;background-color:#1a1f2c;border-radius:4px;" onclick="onclick_graphDiv(event)">
|
||||
Some text to show that something is broken :(
|
||||
</div>
|
||||
<div style="font-size:x-small; text-align:center;">
|
||||
Click to add/remove points on the fan curve.
|
||||
</div>
|
||||
<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">
|
||||
<div class="quickaccesscontrols_PanelSectionRow_26R5w">
|
||||
<div class="quickaccesscontrols_PanelSectionRow_26R5w">
|
||||
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_ExtraPaddingOnChildrenBelow_3nLNL gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable" style="--indent-level:0;">
|
||||
<div class="gamepaddialog_FieldLabelRow_2VcTl">
|
||||
<div class="gamepaddialog_FieldLabel_3jMlJ">
|
||||
Linear Interpolation
|
||||
</div>
|
||||
<div class="gamepaddialog_FieldChildren_2rhav">
|
||||
<div id="interpolToggle" tabindex="0" class="gamepaddialog_Toggle_9Ql-o Focusable" onclick="onclick_interpolToggle()">
|
||||
<div class="gamepaddialog_ToggleRail_2bl0i"></div>
|
||||
<div class="gamepaddialog_ToggleSwitch_1PQpp"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gamepaddialog_FieldDescription_1W1to">Pretends a straight line connects points</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Fan Info -->
|
||||
<div class="quickaccesscontrols_PanelSection_3gY0a" onclick="updateBatteryStats()" style="margin-bottom:0px;">
|
||||
<!--<div class="quickaccesscontrols_PanelSectionTitle_1IigU">
|
||||
<div class="quickaccesscontrols_Text_1cokl">Fan</div>
|
||||
</div>-->
|
||||
<div class="Panel Focusable" tabindex="0">
|
||||
<div class="quickaccesscontrols_PanelSectionRow_3LM_Z">
|
||||
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable" style="--indent-level:0;padding-left:0px;padding-right:0px;">
|
||||
<div class="gamepaddialog_FieldLabelRow_2VcTl">
|
||||
<div class="gamepaddialog_FieldLabel_3jMlJ">Current Fan Speed</div>
|
||||
<div class="gamepaddialog_FieldChildren_2rhav">
|
||||
<div class="gamepaddialog_LabelFieldValue_3pteV" id="fanNow"> (|-_-|) </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel Focusable" tabindex="0">
|
||||
<div class="quickaccesscontrols_PanelSectionRow_3LM_Z">
|
||||
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable" style="--indent-level:0;padding-left:0px;padding-right:0px;">
|
||||
<div class="gamepaddialog_FieldLabelRow_2VcTl">
|
||||
<div class="gamepaddialog_FieldLabel_3jMlJ">Current Temperature</div>
|
||||
<div class="gamepaddialog_FieldChildren_2rhav">
|
||||
<div class="gamepaddialog_LabelFieldValue_3pteV" id="tempNow"> (|-_-|) </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
55
package.json
Normal file
55
package.json
Normal file
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"name": "Fantastic",
|
||||
"version": "0.6.0-alpha1",
|
||||
"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/NGnius/Fantastic.git"
|
||||
},
|
||||
"keywords": [
|
||||
"decky",
|
||||
"plugin",
|
||||
"utility",
|
||||
"fan",
|
||||
"steam-deck",
|
||||
"deck"
|
||||
],
|
||||
"author": "NGnius <ngniusness@gmail.com>",
|
||||
"license": "GPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/NGnius/Fantastic/issues"
|
||||
},
|
||||
"homepage": "https://github.com/NGnius/Fantastic#readme",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^21.1.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.5.0",
|
||||
"@types/react": "16.14.0",
|
||||
"@types/webpack": "^5.28.5",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-import-assets": "^1.1.1",
|
||||
"shx": "^0.3.4",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"decky-frontend-lib": "^3.25.0",
|
||||
"fantastic-wasm": "file:src/rust/pkg",
|
||||
"react-icons": "^4.12.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": [
|
||||
"react",
|
||||
"react-dom"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
{
|
||||
"name": "Fantastic",
|
||||
"author": "NGnius",
|
||||
"main_view_html": "main_view.html",
|
||||
"tile_view_html": "",
|
||||
"flags": ["root", "_debug"],
|
||||
"flags": ["root", "_debug", "global-dfl"],
|
||||
"publish": {
|
||||
"discord_id": "106537989684887552",
|
||||
"description": "Fan controls",
|
||||
"tags": [ "utility", "fan-control" ]
|
||||
"tags": [ "utility", "fan-control", "root" ],
|
||||
"image": "https://raw.githubusercontent.com/NGnius/Fantastic/main/assets/thumbnail.png"
|
||||
}
|
||||
}
|
||||
|
|
953
pnpm-lock.yaml
Normal file
953
pnpm-lock.yaml
Normal file
|
@ -0,0 +1,953 @@
|
|||
lockfileVersion: '6.1'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
decky-frontend-lib:
|
||||
specifier: ^3.25.0
|
||||
version: 3.25.0
|
||||
fantastic-wasm:
|
||||
specifier: file:src/rust/pkg
|
||||
version: file:src/rust/pkg
|
||||
react-icons:
|
||||
specifier: ^4.12.0
|
||||
version: 4.12.0
|
||||
|
||||
devDependencies:
|
||||
'@rollup/plugin-commonjs':
|
||||
specifier: ^21.1.0
|
||||
version: 21.1.0(rollup@2.79.1)
|
||||
'@rollup/plugin-json':
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(rollup@2.79.1)
|
||||
'@rollup/plugin-node-resolve':
|
||||
specifier: ^13.3.0
|
||||
version: 13.3.0(rollup@2.79.1)
|
||||
'@rollup/plugin-replace':
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(rollup@2.79.1)
|
||||
'@rollup/plugin-typescript':
|
||||
specifier: ^8.5.0
|
||||
version: 8.5.0(rollup@2.79.1)(tslib@2.6.2)(typescript@4.9.5)
|
||||
'@types/react':
|
||||
specifier: 16.14.0
|
||||
version: 16.14.0
|
||||
'@types/webpack':
|
||||
specifier: ^5.28.5
|
||||
version: 5.28.5
|
||||
rollup:
|
||||
specifier: ^2.79.1
|
||||
version: 2.79.1
|
||||
rollup-plugin-import-assets:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1(rollup@2.79.1)
|
||||
shx:
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
typescript:
|
||||
specifier: ^4.9.5
|
||||
version: 4.9.5
|
||||
|
||||
packages:
|
||||
|
||||
/@jridgewell/gen-mapping@0.3.5:
|
||||
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
dev: true
|
||||
|
||||
/@jridgewell/resolve-uri@3.1.2:
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/set-array@1.2.1:
|
||||
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/source-map@0.3.5:
|
||||
resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==}
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
dev: true
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.4.15:
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping@0.3.25:
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-commonjs@21.1.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-6ZtHx3VHIp2ReNNDxHjuUml6ur+WcQ28N1yHgCQwsbNkQg2suhxGMDQGJOn/KuDxKtd1xuZP5xSTwBA4GQ8hbA==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^2.38.3
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0(rollup@2.79.1)
|
||||
commondir: 1.0.1
|
||||
estree-walker: 2.0.2
|
||||
glob: 7.2.3
|
||||
is-reference: 1.2.1
|
||||
magic-string: 0.25.9
|
||||
resolve: 1.22.8
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-json@4.1.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0 || ^2.0.0
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0(rollup@2.79.1)
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-node-resolve@13.3.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^2.42.0
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0(rollup@2.79.1)
|
||||
'@types/resolve': 1.17.1
|
||||
deepmerge: 4.3.1
|
||||
is-builtin-module: 3.2.1
|
||||
is-module: 1.0.0
|
||||
resolve: 1.22.8
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-replace@4.0.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0 || ^2.0.0
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0(rollup@2.79.1)
|
||||
magic-string: 0.25.9
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-typescript@8.5.0(rollup@2.79.1)(tslib@2.6.2)(typescript@4.9.5):
|
||||
resolution: {integrity: sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^2.14.0
|
||||
tslib: '*'
|
||||
typescript: '>=3.7.0'
|
||||
peerDependenciesMeta:
|
||||
tslib:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0(rollup@2.79.1)
|
||||
resolve: 1.22.8
|
||||
rollup: 2.79.1
|
||||
tslib: 2.6.2
|
||||
typescript: 4.9.5
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@3.1.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0
|
||||
dependencies:
|
||||
'@types/estree': 0.0.39
|
||||
estree-walker: 1.0.1
|
||||
picomatch: 2.3.1
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@types/eslint-scope@3.7.7:
|
||||
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
||||
dependencies:
|
||||
'@types/eslint': 8.56.5
|
||||
'@types/estree': 1.0.5
|
||||
dev: true
|
||||
|
||||
/@types/eslint@8.56.5:
|
||||
resolution: {integrity: sha512-u5/YPJHo1tvkSF2CE0USEkxon82Z5DBy2xR+qfyYNszpX9qcs4sT6uq2kBbj4BXY1+DBGDPnrhMZV3pKWGNukw==}
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/json-schema': 7.0.15
|
||||
dev: true
|
||||
|
||||
/@types/estree@0.0.39:
|
||||
resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
|
||||
dev: true
|
||||
|
||||
/@types/estree@1.0.5:
|
||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||
dev: true
|
||||
|
||||
/@types/json-schema@7.0.15:
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
dev: true
|
||||
|
||||
/@types/node@20.11.25:
|
||||
resolution: {integrity: sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
dev: true
|
||||
|
||||
/@types/prop-types@15.7.11:
|
||||
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
||||
dev: true
|
||||
|
||||
/@types/react@16.14.0:
|
||||
resolution: {integrity: sha512-jJjHo1uOe+NENRIBvF46tJimUvPnmbQ41Ax0pEm7pRvhPg+wuj8VMOHHiMvaGmZRzRrCtm7KnL5OOE/6kHPK8w==}
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.11
|
||||
csstype: 3.1.3
|
||||
dev: true
|
||||
|
||||
/@types/resolve@1.17.1:
|
||||
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
|
||||
dependencies:
|
||||
'@types/node': 20.11.25
|
||||
dev: true
|
||||
|
||||
/@types/webpack@5.28.5:
|
||||
resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==}
|
||||
dependencies:
|
||||
'@types/node': 20.11.25
|
||||
tapable: 2.2.1
|
||||
webpack: 5.90.3
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
- esbuild
|
||||
- uglify-js
|
||||
- webpack-cli
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/ast@1.11.6:
|
||||
resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==}
|
||||
dependencies:
|
||||
'@webassemblyjs/helper-numbers': 1.11.6
|
||||
'@webassemblyjs/helper-wasm-bytecode': 1.11.6
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/floating-point-hex-parser@1.11.6:
|
||||
resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==}
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/helper-api-error@1.11.6:
|
||||
resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==}
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/helper-buffer@1.11.6:
|
||||
resolution: {integrity: sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==}
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/helper-numbers@1.11.6:
|
||||
resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==}
|
||||
dependencies:
|
||||
'@webassemblyjs/floating-point-hex-parser': 1.11.6
|
||||
'@webassemblyjs/helper-api-error': 1.11.6
|
||||
'@xtuc/long': 4.2.2
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/helper-wasm-bytecode@1.11.6:
|
||||
resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==}
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/helper-wasm-section@1.11.6:
|
||||
resolution: {integrity: sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==}
|
||||
dependencies:
|
||||
'@webassemblyjs/ast': 1.11.6
|
||||
'@webassemblyjs/helper-buffer': 1.11.6
|
||||
'@webassemblyjs/helper-wasm-bytecode': 1.11.6
|
||||
'@webassemblyjs/wasm-gen': 1.11.6
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/ieee754@1.11.6:
|
||||
resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==}
|
||||
dependencies:
|
||||
'@xtuc/ieee754': 1.2.0
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/leb128@1.11.6:
|
||||
resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==}
|
||||
dependencies:
|
||||
'@xtuc/long': 4.2.2
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/utf8@1.11.6:
|
||||
resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==}
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/wasm-edit@1.11.6:
|
||||
resolution: {integrity: sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==}
|
||||
dependencies:
|
||||
'@webassemblyjs/ast': 1.11.6
|
||||
'@webassemblyjs/helper-buffer': 1.11.6
|
||||
'@webassemblyjs/helper-wasm-bytecode': 1.11.6
|
||||
'@webassemblyjs/helper-wasm-section': 1.11.6
|
||||
'@webassemblyjs/wasm-gen': 1.11.6
|
||||
'@webassemblyjs/wasm-opt': 1.11.6
|
||||
'@webassemblyjs/wasm-parser': 1.11.6
|
||||
'@webassemblyjs/wast-printer': 1.11.6
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/wasm-gen@1.11.6:
|
||||
resolution: {integrity: sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==}
|
||||
dependencies:
|
||||
'@webassemblyjs/ast': 1.11.6
|
||||
'@webassemblyjs/helper-wasm-bytecode': 1.11.6
|
||||
'@webassemblyjs/ieee754': 1.11.6
|
||||
'@webassemblyjs/leb128': 1.11.6
|
||||
'@webassemblyjs/utf8': 1.11.6
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/wasm-opt@1.11.6:
|
||||
resolution: {integrity: sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==}
|
||||
dependencies:
|
||||
'@webassemblyjs/ast': 1.11.6
|
||||
'@webassemblyjs/helper-buffer': 1.11.6
|
||||
'@webassemblyjs/wasm-gen': 1.11.6
|
||||
'@webassemblyjs/wasm-parser': 1.11.6
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/wasm-parser@1.11.6:
|
||||
resolution: {integrity: sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==}
|
||||
dependencies:
|
||||
'@webassemblyjs/ast': 1.11.6
|
||||
'@webassemblyjs/helper-api-error': 1.11.6
|
||||
'@webassemblyjs/helper-wasm-bytecode': 1.11.6
|
||||
'@webassemblyjs/ieee754': 1.11.6
|
||||
'@webassemblyjs/leb128': 1.11.6
|
||||
'@webassemblyjs/utf8': 1.11.6
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/wast-printer@1.11.6:
|
||||
resolution: {integrity: sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==}
|
||||
dependencies:
|
||||
'@webassemblyjs/ast': 1.11.6
|
||||
'@xtuc/long': 4.2.2
|
||||
dev: true
|
||||
|
||||
/@xtuc/ieee754@1.2.0:
|
||||
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
||||
dev: true
|
||||
|
||||
/@xtuc/long@4.2.2:
|
||||
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
|
||||
dev: true
|
||||
|
||||
/acorn-import-assertions@1.9.0(acorn@8.11.3):
|
||||
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
|
||||
peerDependencies:
|
||||
acorn: ^8
|
||||
dependencies:
|
||||
acorn: 8.11.3
|
||||
dev: true
|
||||
|
||||
/acorn@8.11.3:
|
||||
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/ajv-keywords@3.5.2(ajv@6.12.6):
|
||||
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
|
||||
peerDependencies:
|
||||
ajv: ^6.9.1
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
dev: true
|
||||
|
||||
/ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
json-schema-traverse: 0.4.1
|
||||
uri-js: 4.4.1
|
||||
dev: true
|
||||
|
||||
/balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: true
|
||||
|
||||
/brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
dev: true
|
||||
|
||||
/browserslist@4.23.0:
|
||||
resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001596
|
||||
electron-to-chromium: 1.4.699
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.13(browserslist@4.23.0)
|
||||
dev: true
|
||||
|
||||
/buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
dev: true
|
||||
|
||||
/builtin-modules@3.3.0:
|
||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001596:
|
||||
resolution: {integrity: sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==}
|
||||
dev: true
|
||||
|
||||
/chrome-trace-event@1.0.3:
|
||||
resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==}
|
||||
engines: {node: '>=6.0'}
|
||||
dev: true
|
||||
|
||||
/commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
dev: true
|
||||
|
||||
/commondir@1.0.1:
|
||||
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||
dev: true
|
||||
|
||||
/concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
||||
/csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
dev: true
|
||||
|
||||
/decky-frontend-lib@3.25.0:
|
||||
resolution: {integrity: sha512-2lBoHS2AIRmuluq/bGdHBz+uyToQE7k3K/vDq1MQbDZ4eC+8CGDuh2T8yZOj3D0yjGP2MdikNNAWPA9Z5l2qDg==}
|
||||
dev: false
|
||||
|
||||
/deepmerge@4.3.1:
|
||||
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium@1.4.699:
|
||||
resolution: {integrity: sha512-I7q3BbQi6e4tJJN5CRcyvxhK0iJb34TV8eJQcgh+fR2fQ8miMgZcEInckCo1U9exDHbfz7DLDnFn8oqH/VcRKw==}
|
||||
dev: true
|
||||
|
||||
/enhanced-resolve@5.15.1:
|
||||
resolution: {integrity: sha512-3d3JRbwsCLJsYgvb6NuWEG44jjPSOMuS73L/6+7BZuoKm3W+qXnSoIYVHi8dG7Qcg4inAY4jbzkZ7MnskePeDg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.2.1
|
||||
dev: true
|
||||
|
||||
/es-module-lexer@1.4.1:
|
||||
resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==}
|
||||
dev: true
|
||||
|
||||
/escalade@3.1.2:
|
||||
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/eslint-scope@5.1.1:
|
||||
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
esrecurse: 4.3.0
|
||||
estraverse: 4.3.0
|
||||
dev: true
|
||||
|
||||
/esrecurse@4.3.0:
|
||||
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
|
||||
engines: {node: '>=4.0'}
|
||||
dependencies:
|
||||
estraverse: 5.3.0
|
||||
dev: true
|
||||
|
||||
/estraverse@4.3.0:
|
||||
resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
|
||||
engines: {node: '>=4.0'}
|
||||
dev: true
|
||||
|
||||
/estraverse@5.3.0:
|
||||
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||
engines: {node: '>=4.0'}
|
||||
dev: true
|
||||
|
||||
/estree-walker@0.6.1:
|
||||
resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
|
||||
dev: true
|
||||
|
||||
/estree-walker@1.0.1:
|
||||
resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
|
||||
dev: true
|
||||
|
||||
/estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
dev: true
|
||||
|
||||
/events@3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
dev: true
|
||||
|
||||
/fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: true
|
||||
|
||||
/fast-json-stable-stringify@2.1.0:
|
||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||
dev: true
|
||||
|
||||
/fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
dev: true
|
||||
|
||||
/fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
dev: true
|
||||
|
||||
/glob-to-regexp@0.4.1:
|
||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||
dev: true
|
||||
|
||||
/glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.1.2
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
dev: true
|
||||
|
||||
/graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
dev: true
|
||||
|
||||
/has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
dev: true
|
||||
|
||||
/inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
dev: true
|
||||
|
||||
/inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
dev: true
|
||||
|
||||
/interpret@1.4.0:
|
||||
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dev: true
|
||||
|
||||
/is-builtin-module@3.2.1:
|
||||
resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
builtin-modules: 3.3.0
|
||||
dev: true
|
||||
|
||||
/is-core-module@2.13.1:
|
||||
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
dev: true
|
||||
|
||||
/is-module@1.0.0:
|
||||
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
|
||||
dev: true
|
||||
|
||||
/is-reference@1.2.1:
|
||||
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
dev: true
|
||||
|
||||
/jest-worker@27.5.1:
|
||||
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
dependencies:
|
||||
'@types/node': 20.11.25
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
dev: true
|
||||
|
||||
/json-parse-even-better-errors@2.3.1:
|
||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||
dev: true
|
||||
|
||||
/json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
dev: true
|
||||
|
||||
/loader-runner@4.3.0:
|
||||
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
|
||||
engines: {node: '>=6.11.5'}
|
||||
dev: true
|
||||
|
||||
/magic-string@0.25.9:
|
||||
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
|
||||
dependencies:
|
||||
sourcemap-codec: 1.4.8
|
||||
dev: true
|
||||
|
||||
/merge-stream@2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
dev: true
|
||||
|
||||
/mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
dev: true
|
||||
|
||||
/minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
dev: true
|
||||
|
||||
/minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
dev: true
|
||||
|
||||
/neo-async@2.6.2:
|
||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||
dev: true
|
||||
|
||||
/node-releases@2.0.14:
|
||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||
dev: true
|
||||
|
||||
/once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
dev: true
|
||||
|
||||
/path-is-absolute@1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
dev: true
|
||||
|
||||
/picocolors@1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
dev: true
|
||||
|
||||
/picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/randombytes@2.1.0:
|
||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: true
|
||||
|
||||
/react-icons@4.12.0:
|
||||
resolution: {integrity: sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/rechoir@0.6.2:
|
||||
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dependencies:
|
||||
resolve: 1.22.8
|
||||
dev: true
|
||||
|
||||
/resolve@1.22.8:
|
||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
is-core-module: 2.13.1
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
dev: true
|
||||
|
||||
/rollup-plugin-import-assets@1.1.1(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-u5zJwOjguTf2N+wETq2weNKGvNkuVc1UX/fPgg215p5xPvGOaI6/BTc024E9brvFjSQTfIYqgvwogQdipknu1g==}
|
||||
peerDependencies:
|
||||
rollup: '>=1.9.0'
|
||||
dependencies:
|
||||
rollup: 2.79.1
|
||||
rollup-pluginutils: 2.8.2
|
||||
url-join: 4.0.1
|
||||
dev: true
|
||||
|
||||
/rollup-pluginutils@2.8.2:
|
||||
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
|
||||
dependencies:
|
||||
estree-walker: 0.6.1
|
||||
dev: true
|
||||
|
||||
/rollup@2.79.1:
|
||||
resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
dev: true
|
||||
|
||||
/schema-utils@3.3.0:
|
||||
resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
ajv: 6.12.6
|
||||
ajv-keywords: 3.5.2(ajv@6.12.6)
|
||||
dev: true
|
||||
|
||||
/serialize-javascript@6.0.2:
|
||||
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
|
||||
dependencies:
|
||||
randombytes: 2.1.0
|
||||
dev: true
|
||||
|
||||
/shelljs@0.8.5:
|
||||
resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
interpret: 1.4.0
|
||||
rechoir: 0.6.2
|
||||
dev: true
|
||||
|
||||
/shx@0.3.4:
|
||||
resolution: {integrity: sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
shelljs: 0.8.5
|
||||
dev: true
|
||||
|
||||
/source-map-support@0.5.21:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/sourcemap-codec@1.4.8:
|
||||
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
||||
deprecated: Please use @jridgewell/sourcemap-codec instead
|
||||
dev: true
|
||||
|
||||
/supports-color@8.1.1:
|
||||
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
dev: true
|
||||
|
||||
/supports-preserve-symlinks-flag@1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/tapable@2.2.1:
|
||||
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/terser-webpack-plugin@5.3.10(webpack@5.90.3):
|
||||
resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
peerDependencies:
|
||||
'@swc/core': '*'
|
||||
esbuild: '*'
|
||||
uglify-js: '*'
|
||||
webpack: ^5.1.0
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
esbuild:
|
||||
optional: true
|
||||
uglify-js:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 3.3.0
|
||||
serialize-javascript: 6.0.2
|
||||
terser: 5.29.1
|
||||
webpack: 5.90.3
|
||||
dev: true
|
||||
|
||||
/terser@5.29.1:
|
||||
resolution: {integrity: sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.5
|
||||
acorn: 8.11.3
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
dev: true
|
||||
|
||||
/tslib@2.6.2:
|
||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||
dev: true
|
||||
|
||||
/typescript@4.9.5:
|
||||
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
dev: true
|
||||
|
||||
/update-browserslist-db@1.0.13(browserslist@4.23.0):
|
||||
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
dependencies:
|
||||
browserslist: 4.23.0
|
||||
escalade: 3.1.2
|
||||
picocolors: 1.0.0
|
||||
dev: true
|
||||
|
||||
/uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
dev: true
|
||||
|
||||
/url-join@4.0.1:
|
||||
resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==}
|
||||
dev: true
|
||||
|
||||
/watchpack@2.4.0:
|
||||
resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
dependencies:
|
||||
glob-to-regexp: 0.4.1
|
||||
graceful-fs: 4.2.11
|
||||
dev: true
|
||||
|
||||
/webpack-sources@3.2.3:
|
||||
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
dev: true
|
||||
|
||||
/webpack@5.90.3:
|
||||
resolution: {integrity: sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
webpack-cli: '*'
|
||||
peerDependenciesMeta:
|
||||
webpack-cli:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/eslint-scope': 3.7.7
|
||||
'@types/estree': 1.0.5
|
||||
'@webassemblyjs/ast': 1.11.6
|
||||
'@webassemblyjs/wasm-edit': 1.11.6
|
||||
'@webassemblyjs/wasm-parser': 1.11.6
|
||||
acorn: 8.11.3
|
||||
acorn-import-assertions: 1.9.0(acorn@8.11.3)
|
||||
browserslist: 4.23.0
|
||||
chrome-trace-event: 1.0.3
|
||||
enhanced-resolve: 5.15.1
|
||||
es-module-lexer: 1.4.1
|
||||
eslint-scope: 5.1.1
|
||||
events: 3.3.0
|
||||
glob-to-regexp: 0.4.1
|
||||
graceful-fs: 4.2.11
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
loader-runner: 4.3.0
|
||||
mime-types: 2.1.35
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 3.3.0
|
||||
tapable: 2.2.1
|
||||
terser-webpack-plugin: 5.3.10(webpack@5.90.3)
|
||||
watchpack: 2.4.0
|
||||
webpack-sources: 3.2.3
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
- esbuild
|
||||
- uglify-js
|
||||
dev: true
|
||||
|
||||
/wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
dev: true
|
||||
|
||||
file:src/rust/pkg:
|
||||
resolution: {directory: src/rust/pkg, type: directory}
|
||||
name: fantastic-wasm
|
||||
version: 0.5.0
|
||||
dev: false
|
38
rollup.config.js
Normal file
38
rollup.config.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
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', 'decky-frontend-lib'],
|
||||
output: {
|
||||
file: 'dist/index.js',
|
||||
globals: {
|
||||
react: 'SP_REACT',
|
||||
'react-dom': 'SP_REACTDOM',
|
||||
'decky-frontend-lib': 'DFL',
|
||||
},
|
||||
format: 'iife',
|
||||
exports: 'default',
|
||||
},
|
||||
});
|
103
src/backend.ts
Normal file
103
src/backend.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
//import {init_usdpl, target_usdpl, init_embedded, call_backend} from "usdpl-front";
|
||||
|
||||
import { init_embedded, target_usdpl } from "fantastic-wasm";
|
||||
import { Fan } from "fantastic-wasm";
|
||||
|
||||
//@ts-ignore
|
||||
//const Fan = {};
|
||||
|
||||
const USDPL_PORT: number = 44444;
|
||||
|
||||
var FAN_CLIENT: Fan | undefined = undefined;
|
||||
|
||||
// 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();
|
||||
FAN_CLIENT = new Fan(USDPL_PORT);
|
||||
console.log("FANTASTIC: USDPL started for framework: " + target_usdpl());
|
||||
//setReady(true);
|
||||
}
|
||||
|
||||
// Back-end functions
|
||||
|
||||
export async function setEnabled(value: boolean): Promise<boolean> {
|
||||
return (await FAN_CLIENT!.set_enable(value))?? value;
|
||||
//return (await call_backend("set_enable", [value]))[0];
|
||||
}
|
||||
|
||||
export async function getEnabled(): Promise<boolean> {
|
||||
return (await FAN_CLIENT!.get_enable(true)) ?? false;
|
||||
}
|
||||
|
||||
export async function setInterpolate(value: boolean): Promise<boolean> {
|
||||
return (await FAN_CLIENT!.set_interpolate(value)) ?? value;
|
||||
//return (await call_backend("set_interpolate", [value]))[0];
|
||||
}
|
||||
|
||||
export async function getInterpolate(): Promise<boolean> {
|
||||
return (await FAN_CLIENT!.get_interpolate(true)) ?? false;
|
||||
//return (await call_backend("get_interpolate", []))[0];
|
||||
}
|
||||
|
||||
export async function getVersion(): Promise<string> {
|
||||
return (await FAN_CLIENT!.version_str(true)) ?? "version";
|
||||
//return (await call_backend("version", []))[0];
|
||||
}
|
||||
|
||||
export async function getName(): Promise<string> {
|
||||
return (await FAN_CLIENT!.name(true))?? "broken";
|
||||
//return (await call_backend("name", []))[0];
|
||||
}
|
||||
|
||||
export async function getCurve(): Promise<{"x": number, "y": number}[]> {
|
||||
var x_s = (await FAN_CLIENT!.get_curve_x(true))?? [];
|
||||
var y_s = (await FAN_CLIENT!.get_curve_y(true))?? [];
|
||||
let result: {"x": number, "y": number}[] = [];
|
||||
for (let i = 0; i < x_s.length && i < y_s.length; i++) {
|
||||
result.push({
|
||||
x: x_s[i],
|
||||
y: y_s[i],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function addCurvePoint(point: {"x": number, "y": number}): Promise<{"x": number, "y": number}[]> {
|
||||
await FAN_CLIENT!.add_curve_point(point.x, point.y);
|
||||
return getCurve();
|
||||
}
|
||||
|
||||
export async function removeCurvePoint(index: number): Promise<{"x": number, "y": number}[]> {
|
||||
await FAN_CLIENT!.remove_curve_point(index);
|
||||
return getCurve();
|
||||
//return (await call_backend("remove_curve_point", [index]))[0];
|
||||
}
|
||||
|
||||
export async function getFanRpm(callback: (rpm: number) => void): Promise<void> {
|
||||
return (await FAN_CLIENT!.get_fan_rpm(true, callback));
|
||||
}
|
||||
|
||||
export async function getTemperature(callback: (temp: number) => void): Promise<void> {
|
||||
return (await FAN_CLIENT!.get_temperature(true, callback));
|
||||
}
|
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;
|
||||
}
|
350
src/index.tsx
Executable file
350
src/index.tsx
Executable file
|
@ -0,0 +1,350 @@
|
|||
import {
|
||||
definePlugin,
|
||||
PanelSection,
|
||||
PanelSectionRow,
|
||||
Field,
|
||||
ServerAPI,
|
||||
ToggleField,
|
||||
staticClasses,
|
||||
Navigation,
|
||||
} from "decky-frontend-lib";
|
||||
import { VFC, useState } from "react";
|
||||
import { FaFan } from "react-icons/fa";
|
||||
import { SiOnlyfans } from "react-icons/si";
|
||||
|
||||
import { version_usdpl } from "fantastic-wasm";
|
||||
|
||||
import * as backend from "./backend";
|
||||
import {Canvas} from "./canvas";
|
||||
|
||||
const POINT_SIZE = 32;
|
||||
|
||||
var periodicHook: any = null;
|
||||
var usdplReady: boolean = false;
|
||||
|
||||
var name: string = "";
|
||||
var version: string = "";
|
||||
|
||||
var curve_backup: {x: number, y: number}[] = [];
|
||||
|
||||
var tempCache: number = -1337;
|
||||
var setTemperature_display = (_: number) => {};
|
||||
|
||||
var fanRpmCache: number = -273;
|
||||
var setFanRpm_display = (_: number) => {};
|
||||
|
||||
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 [curveGlobal, setCurve_internal] = useState<{x: number, y: number}[]>(curve_backup);
|
||||
|
||||
const setCurve = (value: {x: number, y: number}[]) => {
|
||||
setCurve_internal(value);
|
||||
curve_backup = value;
|
||||
}
|
||||
|
||||
const [temperatureGlobal, setTemperature] = useState<number>(tempCache);
|
||||
const [fanRpmGlobal, setFanRpm] = useState<number>(fanRpmCache);
|
||||
setTemperature_display = (x) => {
|
||||
setTemperature(x);
|
||||
tempCache = x;
|
||||
};
|
||||
setFanRpm_display = (x) => {
|
||||
setFanRpm(x);
|
||||
fanRpmCache = x;
|
||||
};
|
||||
|
||||
function setEnable(enable: boolean) {
|
||||
setEnableInternal(enable);
|
||||
}
|
||||
|
||||
function onClickCanvas(e: any) {
|
||||
//console.log("[FANTASTIC] 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("[FANTASTIC] Target dimensions " + target.width.toString() + "x" + target.height.toString());
|
||||
var clickX = realEvent.offsetX;
|
||||
var clickY = realEvent.offsetY;
|
||||
//console.debug("[FANTASTIC] curve click:", clickX, clickY);
|
||||
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");
|
||||
const curvePoint = {x: clickX / target.width, y: 1 - (clickY / target.height)};
|
||||
backend.resolve(backend.addCurvePoint(curvePoint), setCurve);
|
||||
}
|
||||
|
||||
function drawCanvas(ctx: any, frameCount: number): void {
|
||||
if (frameCount % 100 > 1) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
// graph helper lines
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = "#093455";
|
||||
//ctx.fillStyle = "#093455";
|
||||
const totalLines = 7;
|
||||
const lineDistance = 1 / (totalLines + 1);
|
||||
for (let i = 1; i <= totalLines; i++) {
|
||||
ctx.moveTo(lineDistance * i * width, 0);
|
||||
ctx.lineTo(lineDistance * i * width, height);
|
||||
ctx.moveTo(0, lineDistance * i * height);
|
||||
ctx.lineTo(width, lineDistance * i * height);
|
||||
}
|
||||
ctx.stroke();
|
||||
//ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = "#1a9fff";
|
||||
ctx.fillStyle = "#1a9fff";
|
||||
|
||||
// axis labels
|
||||
ctx.textAlign = "center";
|
||||
ctx.rotate(- Math.PI / 2);
|
||||
ctx.fillText("Fan RPM", - height / 2, 12); // Y axis is rotated 90 degrees
|
||||
ctx.rotate(Math.PI / 2);
|
||||
ctx.fillText("Temperature", width / 2, height - 4);
|
||||
// graph data labels
|
||||
ctx.textAlign = "start"; // default
|
||||
ctx.fillText("0", 2, height - 2);
|
||||
ctx.fillText("100%", 2, 9);
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillText("100°C", width - 2, height - 2);
|
||||
|
||||
ctx.moveTo(0, height);
|
||||
if (interpolGlobal) {
|
||||
//ctx.beginPath();
|
||||
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);
|
||||
} else {
|
||||
//ctx.beginPath();
|
||||
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("Rendered fan graph canvas frame", frameCount);
|
||||
//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);
|
||||
backend.resolve(backend.getEnabled(), setEnable);
|
||||
backend.resolve(backend.getInterpolate(), setInterpol);
|
||||
backend.resolve(backend.getCurve(), setCurve);
|
||||
backend.resolve(backend.getTemperature(setTemperature_display), (_: any) => {});
|
||||
backend.resolve(backend.getFanRpm(setFanRpm_display), (_: any) => {});
|
||||
|
||||
if (periodicHook != null) {
|
||||
clearInterval(periodicHook);
|
||||
}
|
||||
|
||||
/*periodicHook = setInterval(function() {
|
||||
backend.resolve(backend.getTemperature(), setTemperature);
|
||||
backend.resolve(backend.getFanRpm(), setFanRpm);
|
||||
}, 1000);*/
|
||||
}
|
||||
|
||||
if (!usdplReady) {
|
||||
return (
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
label="Loading...">
|
||||
If you can read this, something probably went wrong :(
|
||||
</Field>
|
||||
</PanelSectionRow>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO handle clicking on fan curve nodes
|
||||
|
||||
return (
|
||||
<PanelSection>
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
label="Current Fan Speed">
|
||||
{fanRpmGlobal.toFixed(0) + " RPM"}
|
||||
</Field>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
label="Current Temperature">
|
||||
{temperatureGlobal.toFixed(1) + " °C"}
|
||||
</Field>
|
||||
</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>
|
||||
}
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
label={name}
|
||||
onClick={()=> { Navigation.NavigateToExternalWeb("https://git.ngni.us/NG-SD-Plugins/Fantastic/releases"); }}>
|
||||
{"v" + version}
|
||||
</Field>
|
||||
</PanelSectionRow>
|
||||
{ (version?.includes("alpha") || version?.includes("beta")) && <PanelSectionRow>
|
||||
<Field
|
||||
label="USDPL"
|
||||
onClick={()=> { Navigation.NavigateToExternalWeb("https://git.ngni.us/NG-SD-Plugins/usdpl-rs"); }}>
|
||||
v{version_usdpl()}
|
||||
</Field>
|
||||
</PanelSectionRow>}
|
||||
</PanelSection>
|
||||
);
|
||||
};
|
||||
|
||||
(async function(){
|
||||
if (!usdplReady) {
|
||||
await backend.initBackend();
|
||||
usdplReady = true;
|
||||
backend.getEnabled();
|
||||
name = await backend.getName();
|
||||
version = await backend.getVersion();
|
||||
}
|
||||
})();
|
||||
|
||||
export default definePlugin((serverApi: ServerAPI) => {
|
||||
|
||||
let ico = <FaFan />;
|
||||
let now = new Date();
|
||||
if (now.getDate() == 1 && now.getMonth() == 3) {
|
||||
ico = <SiOnlyfans/>;
|
||||
}
|
||||
|
||||
return {
|
||||
title: <div className={staticClasses.Title}>Fantastic</div>,
|
||||
content: <Content serverAPI={serverApi} />,
|
||||
icon: ico,
|
||||
onDismount() {
|
||||
clearInterval(periodicHook!);
|
||||
},
|
||||
};
|
||||
});
|
981
src/rust/Cargo.lock
generated
Normal file
981
src/rust/Cargo.lock
generated
Normal file
|
@ -0,0 +1,981 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "beef"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fantastic-wasm"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"nrpc",
|
||||
"prost",
|
||||
"usdpl-build",
|
||||
"usdpl-front",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-net"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"gloo-utils",
|
||||
"http",
|
||||
"js-sys",
|
||||
"pin-project",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "logos"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1"
|
||||
dependencies = [
|
||||
"logos-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logos-codegen"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68"
|
||||
dependencies = [
|
||||
"beef",
|
||||
"fnv",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex-syntax 0.6.29",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logos-derive"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e"
|
||||
dependencies = [
|
||||
"logos-codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e"
|
||||
|
||||
[[package]]
|
||||
name = "miette"
|
||||
version = "5.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
|
||||
dependencies = [
|
||||
"miette-derive",
|
||||
"once_cell",
|
||||
"thiserror",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miette-derive"
|
||||
version = "5.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
||||
|
||||
[[package]]
|
||||
name = "nrpc"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nrpc-build"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"nrpc",
|
||||
"prettyplease 0.2.12",
|
||||
"proc-macro2",
|
||||
"prost-build",
|
||||
"prost-types",
|
||||
"protox",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-build"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"multimap",
|
||||
"petgraph",
|
||||
"prettyplease 0.1.25",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"regex",
|
||||
"syn 1.0.109",
|
||||
"tempfile",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-reflect"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b823de344848e011658ac981009100818b322421676740546f8b52ed5249428"
|
||||
dependencies = [
|
||||
"logos",
|
||||
"miette",
|
||||
"once_cell",
|
||||
"prost",
|
||||
"prost-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13"
|
||||
dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protox"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24022a7eb88547eaba87a1db7954c9c4cb4a143565c4e8f2b7f3e76eed63db2d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"miette",
|
||||
"prost",
|
||||
"prost-reflect",
|
||||
"prost-types",
|
||||
"protox-parse",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protox-parse"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712a2a651fa4466e67df6c967df5d7fc6cbffac89afc7b834f97ec49846c9c11"
|
||||
dependencies = [
|
||||
"logos",
|
||||
"miette",
|
||||
"prost-types",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "usdpl-build"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"nrpc-build",
|
||||
"prettyplease 0.2.12",
|
||||
"proc-macro2",
|
||||
"prost-build",
|
||||
"prost-types",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usdpl-core"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usdpl-front"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"futures",
|
||||
"futures-channel",
|
||||
"gloo-net",
|
||||
"js-sys",
|
||||
"log",
|
||||
"nrpc",
|
||||
"prost",
|
||||
"usdpl-core",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
|
||||
dependencies = [
|
||||
"either",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
33
src/rust/Cargo.toml
Normal file
33
src/rust/Cargo.toml
Normal file
|
@ -0,0 +1,33 @@
|
|||
[package]
|
||||
name = "fantastic-wasm"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||
description = "Frontend bindings for fan control functionality"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://git.ngni.us/NG-SD-Plugins/Fantastic"
|
||||
keywords = ["utility", "fan-control", "root", "decky"]
|
||||
readme = "../../README.md"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
usdpl-front = { version = "1.0", path = "../../../usdpl-rs/usdpl-front" }
|
||||
nrpc = { version = "1.0", path = "../../../nRPC/nrpc", default-features = false }
|
||||
prost = "0.11"
|
||||
|
||||
[build-dependencies]
|
||||
usdpl-build = { version = "1.0", path = "../../../usdpl-rs/usdpl-build" }
|
||||
|
||||
[features]
|
||||
debug = ["usdpl-front/debug"]
|
||||
decky = ["usdpl-front/decky"]
|
||||
|
||||
[profile.release]
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
debug = false
|
||||
strip = true
|
||||
lto = true
|
||||
codegen-units = 4
|
15
src/rust/build.rs
Normal file
15
src/rust/build.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
fn main() {
|
||||
println!("CWD: {}", std::env::current_dir().unwrap().display());
|
||||
usdpl_build::front::build(
|
||||
[format!(
|
||||
"{}/../../backend-rs/protos/fantastic.proto",
|
||||
std::env::current_dir().unwrap().display()
|
||||
)]
|
||||
.into_iter(),
|
||||
[format!(
|
||||
"{}/../../backend-rs/protos/",
|
||||
std::env::current_dir().unwrap().display()
|
||||
)]
|
||||
.into_iter(),
|
||||
)
|
||||
}
|
19
src/rust/build.sh
Executable file
19
src/rust/build.sh
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
if [ -n "$1" ]; then
|
||||
if [ "$1" == "--help" ]; then
|
||||
echo "Usage:
|
||||
$0 [decky|crankshaft|<nothing>]"
|
||||
exit 0
|
||||
elif [ "$1" == "decky" ]; then
|
||||
echo "Building WASM module for decky framework"
|
||||
RUSTFLAGS="--cfg aes_compact" wasm-pack build --target web --features decky,$2
|
||||
else
|
||||
echo "Unsupported plugin framework \`$1\`"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "WARNING: Building for any plugin framework, which may not work for every framework"
|
||||
RUSTFLAGS="--cfg aes_compact" wasm-pack build --target web --features debug,$2
|
||||
fi
|
||||
|
||||
python3 ./scripts/generate_embedded_wasm.py
|
45
src/rust/scripts/generate_embedded_wasm.py
Normal file
45
src/rust/scripts/generate_embedded_wasm.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
import base64
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Embedding WASM into js")
|
||||
# assumption: current working directory (relative to this script) is ../
|
||||
# assumption: release wasm binary at ./pkg/usdpl_bg.wasm
|
||||
with open("./pkg/fantastic_wasm_bg.wasm", mode="rb") as infile:
|
||||
with open("./pkg/fantastic_wasm.js", mode="ab") as outfile:
|
||||
outfile.write("\n\n// USDPL customization\nconst encoded = \"".encode())
|
||||
encoded = base64.b64encode(infile.read())
|
||||
outfile.write(encoded)
|
||||
outfile.write("\";\n\n".encode())
|
||||
outfile.write(
|
||||
"""function asciiToBinary(str) {
|
||||
if (typeof atob === 'function') {
|
||||
return atob(str)
|
||||
} else {
|
||||
return new Buffer(str, 'base64').toString('binary');
|
||||
}
|
||||
}
|
||||
|
||||
function decode() {
|
||||
var binaryString = asciiToBinary(encoded);
|
||||
var bytes = new Uint8Array(binaryString.length);
|
||||
for (var i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return (async function() {
|
||||
return new Response(bytes.buffer, {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
'Content-Type': 'application/wasm'
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
export function init_embedded() {
|
||||
return __wbg_init(decode())
|
||||
}
|
||||
""".encode())
|
||||
with open("./pkg/fantastic_wasm.d.ts", "a") as outfile:
|
||||
outfile.write("\n\n// USDPL customization\nexport function init_embedded();\n")
|
||||
print("Done: Embedded WASM into js")
|
7
src/rust/src/lib.rs
Normal file
7
src/rust/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
#[allow(missing_docs)]
|
||||
#[allow(dead_code)]
|
||||
pub mod services {
|
||||
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
|
||||
}
|
||||
|
||||
pub use usdpl_front;
|
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;
|
||||
}
|
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