Compare commits

..

25 commits
v0.3.3 ... main

Author SHA1 Message Date
924f1aaa51 Use correct fan max range for Steam Deck OLED; fixes #19 2024-09-29 17:16:22 -04:00
364d9650f2 Add Asus ROG Ally fan support (without interpolation) 2024-09-14 13:00:16 -04:00
dd0c3998f0 Refactor fan and sensor into traits 2024-08-20 21:25:43 -04:00
a0f565895b Fix compile with USDPL & nRPC v1.0 2024-04-02 20:53:55 -04:00
32d09219da Update DFL, move back to dependencies (not devDeps) 2024-03-10 22:01:53 -04:00
b637f4f5f9 Fix canvas clicking for #20 2024-03-10 21:48:30 -04:00
8c9a7b7c68 Switch to global DFL 2024-01-28 11:24:34 -05:00
f620dc1854 Add extra debug information for system, add root flag 2023-12-02 13:33:55 -05:00
6c8b335417 Make periodic reads protobuf streams to reuse open websockets 2023-10-09 18:21:31 -04:00
dd6672f9ba Don't init twice 2023-09-03 18:21:23 -04:00
f4a3f06aae Remove unused functions, improve startup checks 2023-09-03 17:38:20 -04:00
255958f96b Add extra version info in pre-release versions 2023-09-03 11:41:59 -04:00
2bec0087a0 Use sysfuss for kernel interactions, usability fixes for alpha1 2023-09-03 11:23:57 -04:00
9972ac7854 Make all code compile with latest indev crates 2023-08-29 19:51:33 -04:00
438d91f639 Get experimental USDPL working 2023-06-28 22:22:48 -04:00
1c6a889006
Create FUNDING.yml 2023-06-04 20:46:37 +00:00
3d744f31aa Upgrade frontend to experimental USDPL next 2023-06-04 16:31:29 -04:00
c88402e580 Upgrade backend & wasm to USDPL next 2023-06-04 14:50:58 -04:00
b52441f1ad Add const to set hwmon index 2023-02-23 21:43:00 -05:00
95dab80171 Update libs and frameworks 2023-02-23 21:15:33 -05:00
dc68a1f1fb Update USDPL to v0.10 2023-02-20 22:56:08 -05:00
b6e70c2c79 Improve UI/UX with fields and graph 2022-12-22 18:03:59 -05:00
b3de3fcd7e Directly disable jupiter-fan-control service to fix #10 2022-11-25 20:56:35 -05:00
2286e0f43a Remove some unused files 2022-09-18 17:22:45 -04:00
2fc52c246f Update some dependencies and remove package-lock 2022-09-18 17:20:14 -04:00
45 changed files with 4472 additions and 4603 deletions

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
github: NGnius
liberapay: NGnius

3
.gitignore vendored
View file

@ -22,6 +22,7 @@ coverage
# Dependency directory # Dependency directory
node_modules node_modules
bower_components bower_components
package-lock.json
# Editors # Editors
.idea .idea
@ -43,7 +44,9 @@ yalc.lock
/backend-rs/target /backend-rs/target
backend backend
/bin/backend /bin/backend
/src/rust/target
# packaged teasers # packaged teasers
*.zip *.zip
*.tar.gz *.tar.gz
Fantastic/

1707
backend-rs/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,34 @@
[package] [package]
name = "fantastic-rs" name = "fantastic-rs"
version = "0.3.3" version = "0.6.0-alpha1"
edition = "2021" 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] [dependencies]
usdpl-back = { version = "0.5.3", features = ["blocking"]}#, path = "../usdpl/usdpl-back"} usdpl-back = { version = "1.0", features = ["blocking"], path = "../../usdpl-rs/usdpl-back"}
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
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 # logging
log = "0.4" log = "0.4"
simplelog = "0.12" simplelog = "0.12"
[build-dependencies]
usdpl-build = { version = "1.0", path = "../../usdpl-rs/usdpl-build" }
[profile.release] [profile.release]
debug = false debug = false
strip = true strip = true

9
backend-rs/build.rs Normal file
View 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())
}

View file

@ -1,5 +1,13 @@
#!/bin/bash #!/bin/bash
cross build --release #cargo build --release --target x86_64-unknown-linux-musl
mkdir ../bin #cargo build --target x86_64-unknown-linux-musl
cp ./target/release/fantastic-rs ../bin/backend #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

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

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

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

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

View file

@ -0,0 +1,2 @@
mod adapter;
pub use adapter::RogAllyFan;

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

View file

@ -0,0 +1,2 @@
mod adapter;
pub use adapter::SteamDeckFan;

View file

@ -0,0 +1,5 @@
mod traits;
pub use traits::{FanAdapter, SensorAdapter, SensorReading, SensorType};
pub mod fans;
pub mod sensors;

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

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

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

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

View file

@ -1,259 +1,238 @@
use usdpl_back::core::serdes::Primitive; use crate::services::fantastic::*;
use usdpl_back::nrpc::_helpers::futures::{StreamExt, FutureExt};
use super::control::ControlRuntime; use super::control::ControlRuntime;
use super::json::GraphPointJson;
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
pub const NAME: &'static str = env!("CARGO_PKG_NAME"); pub const NAME: &'static str = env!("CARGO_PKG_NAME");
pub fn hello(params: Vec<Primitive>) -> Vec<Primitive> { const FAN_READ_PERIOD: std::time::Duration = std::time::Duration::from_millis(1000);
if let Some(Primitive::String(name)) = params.get(0) { const TEMPERATURE_READ_PERIOD: std::time::Duration = std::time::Duration::from_millis(2000);
vec![Primitive::String(format!("Hello {}", name))]
} else { pub struct FanService {
vec![] ctrl: ControlRuntime,
}
impl FanService {
pub fn new(runtime: ControlRuntime) -> Self {
runtime.run();
Self {
ctrl: runtime,
}
} }
} }
pub fn echo(params: Vec<Primitive>) -> Vec<Primitive> { fn once_true() -> impl std::iter::Iterator<Item = bool> {
params // iters over [true, false, false, ...]
std::iter::once(true).chain(std::iter::repeat(false))
} }
pub fn version(_: Vec<Primitive>) -> Vec<Primitive> { #[::nrpc::_helpers::async_trait::async_trait]
vec![VERSION.into()] impl<'a> IFan<'a> for FanService {
} async fn echo(
&mut self,
pub fn name(_: Vec<Primitive>) -> Vec<Primitive> { input: EchoMessage,
vec![NAME.into()] ) -> Result<EchoMessage, Box<dyn std::error::Error + Send>> {
} Ok(input)
}
pub fn get_fan_rpm(_: Vec<Primitive>) -> Vec<Primitive> { async fn hello(
if let Some(rpm) = crate::sys::read_fan() { &mut self,
log::debug!("get_fan_rpm() success: {}", rpm); input: NameMessage,
vec![rpm.into()] ) -> Result<HelloResponse, Box<dyn std::error::Error + Send>> {
} else { Ok(HelloResponse {
log::error!("get_fan_rpm failed to read fan speed"); phrase: format!("Hello {}", input.name)
Vec::new() })
}
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()))
}
} }
}
pub fn get_temperature(_: Vec<Primitive>) -> Vec<Primitive> { async fn get_temperature<'b: 'a>(
if let Some(temperature) = crate::sys::read_thermal_zone(0) { &mut self,
let real_temp = temperature as f64 / 1000.0; _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); log::debug!("get_temperature() success: {}", real_temp);
vec![real_temp.into()] Ok(TemperatureMessage { temperature: real_temp })
} else { },
log::error!("get_fan_rpm failed to read fan speed"); Err(e) => {
Vec::new() 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))
} }
}
pub fn set_enable_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> { async fn set_enable(
let runtime_settings = runtime.settings_clone(); &mut self,
let runtime_state = runtime.state_clone(); input: EnablementMessage,
move |params| { ) -> Result<EnablementMessage, Box<dyn std::error::Error + Send>>{
if let Some(Primitive::Bool(enabled)) = params.get(0) { let mut settings = self.ctrl.settings().write().await;
let mut settings = match runtime_settings.write() { if settings.enable != input.is_enabled {
Ok(x) => x, let mut state = self.ctrl.state().write().await;
Err(e) => { settings.enable = input.is_enabled;
log::error!("set_enable failed to acquire settings write lock: {}", e);
return vec![];
}
};
if settings.enable != *enabled {
settings.enable = *enabled;
let mut state = match runtime_state.write() {
Ok(x) => x,
Err(e) => {
log::error!("set_enable failed to acquire state write lock: {}", e);
return vec![];
}
};
state.dirty = true; state.dirty = true;
log::debug!("set_enable({}) success", enabled);
} }
vec![(*enabled).into()] log::debug!("set_enable({}) success", input.is_enabled);
} else { Ok(input)
Vec::new()
} }
} async fn get_enable(
} &mut self,
_input: Empty,
pub fn get_enable_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> { ) -> Result<EnablementMessage, Box<dyn std::error::Error + Send>>{
let runtime_settings = runtime.settings_clone(); let is_enabled = self.ctrl.settings().read().await.enable;
move |_| {
let lock = match runtime_settings.read() {
Ok(x) => x,
Err(e) => {
log::error!("get_enable failed to acquire settings read lock: {}", e);
return vec![];
}
};
log::debug!("get_enable() success"); log::debug!("get_enable() success");
vec![lock.enable.into()] Ok(EnablementMessage { is_enabled })
} }
} async fn set_interpolate(
&mut self,
pub fn set_interpolate_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> { input: EnablementMessage,
let runtime_settings = runtime.settings_clone(); ) -> Result<EnablementMessage, Box<dyn std::error::Error + Send>>{
let runtime_state = runtime.state_clone(); let mut settings = self.ctrl.settings().write().await;
move |params| { if settings.interpolate != input.is_enabled {
if let Some(Primitive::Bool(enabled)) = params.get(0) { let mut state = self.ctrl.state().write().await;
let mut settings = match runtime_settings.write() { settings.interpolate = input.is_enabled;
Ok(x) => x,
Err(e) => {
log::error!("set_enable failed to acquire settings write lock: {}", e);
return vec![];
}
};
if settings.interpolate != *enabled {
settings.interpolate = *enabled;
let mut state = match runtime_state.write() {
Ok(x) => x,
Err(e) => {
log::error!("set_interpolate failed to acquire state write lock: {}", e);
return vec![];
}
};
state.dirty = true; state.dirty = true;
log::debug!("set_interpolate({}) success", enabled);
} }
vec![(*enabled).into()] log::debug!("set_interpolate({}) success", input.is_enabled);
} else { Ok(input)
Vec::new()
} }
} async fn get_interpolate(
} &mut self,
_input: Empty,
pub fn get_interpolate_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> { ) -> Result<EnablementMessage, Box<dyn std::error::Error + Send>>{
let runtime_settings = runtime.settings_clone(); let is_enabled = self.ctrl.settings().read().await.interpolate;
move |_| {
let lock = match runtime_settings.read() {
Ok(x) => x,
Err(e) => {
log::error!("get_interpolate failed to acquire settings read lock: {}", e);
return vec![];
}
};
log::debug!("get_interpolate() success"); log::debug!("get_interpolate() success");
vec![lock.interpolate.into()] Ok(EnablementMessage { is_enabled })
} }
} async fn get_curve_x(
&mut self,
fn curve_to_json(curve: &Vec<super::datastructs::GraphPoint>) -> serde_json::Result<String> { _input: Empty,
let mut curve_points = Vec::<GraphPointJson>::with_capacity(curve.len()); ) -> Result<CurveMessageX, Box<dyn std::error::Error + Send>>{
for point in curve.iter() { let settings = self.ctrl.settings().read().await;
curve_points.push(point.clone().into()); let x = settings.curve.iter().map(|p| p.x).collect();
log::debug!("get_curve_x() success");
Ok(CurveMessageX { x })
} }
serde_json::to_string(&curve_points) async fn get_curve_y(
} &mut self,
_input: Empty,
pub fn get_curve_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> { ) -> Result<CurveMessageY, Box<dyn std::error::Error + Send>>{
let runtime_settings = runtime.settings_clone(); let settings = self.ctrl.settings().read().await;
move |_| { let y = settings.curve.iter().map(|p| p.y).collect();
let lock = match runtime_settings.read() { log::debug!("get_curve_x() success");
Ok(x) => x, Ok(CurveMessageY { y })
Err(e) => {
log::error!("get_curve failed to acquire settings read lock: {}", e);
return vec![];
} }
}; async fn add_curve_point(
let json_str = match curve_to_json(&lock.curve) { &mut self,
Ok(x) => x, point: GraphPoint,
Err(e) => { ) -> Result<Empty, Box<dyn std::error::Error + Send>>{
log::error!("get_curve failed to serialize points: {}", e); let mut settings = self.ctrl.settings().write().await;
return vec![]; settings.curve.push(super::datastructs::GraphPoint {
} x: point.x,
}; y: point.y
log::debug!("get_curve() success"); });
vec![Primitive::Json(json_str)]
}
}
pub fn add_curve_point_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> {
let runtime_settings = runtime.settings_clone();
let runtime_state = runtime.state_clone();
move |params| {
if let Some(Primitive::Json(json_str)) = params.get(0) {
let mut settings = match runtime_settings.write() {
Ok(x) => x,
Err(e) => {
log::error!("add_curve_point failed to acquire settings write lock: {}", e);
return vec![];
}
};
let new_point: GraphPointJson = match serde_json::from_str(&json_str) {
Ok(x) => x,
Err(e) => {
log::error!("add_curve_point failed deserialize point json: {}", e);
return vec![];
}
};
let version = settings.version;
settings.curve.push(super::datastructs::GraphPoint::from_json(new_point, version));
settings.sort_curve(); settings.sort_curve();
let mut state = match runtime_state.write() { let mut state = self.ctrl.state().write().await;
Ok(x) => x,
Err(e) => {
log::error!("add_curve_point failed to acquire state write lock: {}", e);
return vec![];
}
};
state.dirty = true; state.dirty = true;
let json_str = match curve_to_json(&settings.curve) { log::debug!("add_curve_point(point: {:?}) success", point);
Ok(x) => x, Ok(Empty { ok: true })
Err(e) => {
log::error!("add_curve_point failed to serialize points: {}", e);
return vec![];
} }
}; async fn remove_curve_point(
log::debug!("add_curve_point({}) success", json_str); &mut self,
vec![Primitive::Json(json_str)] input: IndexMessage,
} else { ) -> Result<Empty, Box<dyn std::error::Error + Send>>{
Vec::new() let mut settings = self.ctrl.settings().write().await;
} let i = input.index as usize;
} if settings.curve.len() > i {
} settings.curve.swap_remove(i);
pub fn remove_curve_point_gen(runtime: &ControlRuntime) -> impl Fn(Vec<Primitive>) -> Vec<Primitive> {
let runtime_settings = runtime.settings_clone();
let runtime_state = runtime.state_clone();
move |params| {
if let Some(Primitive::F64(index)) = params.get(0) {
let mut settings = match runtime_settings.write() {
Ok(x) => x,
Err(e) => {
log::error!("remove_curve_point failed to acquire settings write lock: {}", e);
return vec![];
}
};
let rounded = index.round();
if rounded >= 0.0 && rounded < settings.curve.len() as _ {
let index = rounded as usize;
settings.curve.swap_remove(index);
settings.sort_curve(); settings.sort_curve();
let mut state = match runtime_state.write() { let mut state = self.ctrl.state().write().await;
Ok(x) => x,
Err(e) => {
log::error!("remove_curve_point failed to acquire state write lock: {}", e);
return vec![];
}
};
state.dirty = true; state.dirty = true;
let json_str = match curve_to_json(&settings.curve) { log::debug!("remove_curve_point(point: {}) success", input.index);
Ok(x) => x, Ok(Empty { ok: true })
Err(e) => {
log::error!("remove_curve_point failed to serialize points: {}", e);
return vec![];
}
};
log::debug!("remove_curve_point({}) success", json_str);
vec![Primitive::Json(json_str)]
} else { } else {
log::error!("remove_curve_point received index out of bounds: {} indexing array of length {}", index, settings.curve.len()); log::debug!("remove_curve_point(index: {}) failed", input.index);
return vec![]; Ok(Empty { ok: false })
}
} else {
Vec::new()
} }
} }
} }

View file

@ -1,25 +1,36 @@
//! Fan control //! Fan control
use std::sync::{RwLock, Arc}; use std::sync::Arc;
//use std::collections::HashMap;
use tokio::sync::RwLock;
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use super::datastructs::{Settings, State, GraphPoint}; use super::datastructs::{Settings, State};
use super::json::SettingsJson; use super::json::SettingsJson;
pub struct ControlRuntime { pub struct ControlRuntime {
settings: Arc<RwLock<Settings>>, settings: Arc<RwLock<Settings>>,
state: Arc<RwLock<State>>, state: Arc<RwLock<State>>,
fan_adapter: Arc<Box<dyn crate::adapters::FanAdapter + 'static>>,
sensor_adapter: Arc<Box<dyn crate::adapters::SensorAdapter + 'static>>,
} }
impl ControlRuntime { impl ControlRuntime {
pub fn new() -> Self { #[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 new_state = State::new();
let settings_p = settings_path(&new_state.home); let settings_p = settings_path(&new_state.home);
Self { Self {
settings: Arc::new(RwLock::new(super::json::SettingsJson::open(settings_p).unwrap_or_default().into())), settings: Arc::new(RwLock::new(super::json::SettingsJson::open(settings_p).unwrap_or_default().into())),
state: Arc::new(RwLock::new(new_state)), state: Arc::new(RwLock::new(new_state)),
fan_adapter: Arc::new(fan),
sensor_adapter: Arc::new(sensor),
} }
} }
@ -31,9 +42,27 @@ impl ControlRuntime {
self.state.clone() 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<()> { pub fn run(&self) -> thread::JoinHandle<()> {
let runtime_settings = self.settings_clone(); let runtime_settings = self.settings_clone();
let runtime_state = self.state_clone(); let runtime_state = self.state_clone();
let runtime_fan = self.fan_adapter.clone();
let runtime_sensor = self.sensor_adapter.clone();
thread::spawn(move || { thread::spawn(move || {
let sleep_duration = Duration::from_millis(1000); let sleep_duration = Duration::from_millis(1000);
let mut start_time = Instant::now(); let mut start_time = Instant::now();
@ -42,186 +71,43 @@ impl ControlRuntime {
// resumed from sleep; do fan re-init // resumed from sleep; do fan re-init
log::debug!("Detected resume from sleep, overriding fan again"); log::debug!("Detected resume from sleep, overriding fan again");
{ {
let state = match runtime_state.read() { let settings = runtime_settings.blocking_read();
Ok(x) => x,
Err(e) => {
log::error!("runtime failed to acquire state read lock: {}", e);
continue;
}
};
let settings = match runtime_settings.read() {
Ok(x) => x,
Err(e) => {
log::error!("runtime failed to acquire settings read lock: {}", e);
continue;
}
};
if settings.enable { if settings.enable {
Self::on_set_enable(&settings, &state); runtime_fan.on_enable_toggled(&settings);
} }
} }
} }
start_time = Instant::now(); start_time = Instant::now();
{ // save to file { // save to file
let state = match runtime_state.read() { let state = runtime_state.blocking_read();
Ok(x) => x,
Err(e) => {
log::error!("runtime failed to acquire state read lock: {}", e);
continue;
}
};
if state.dirty { if state.dirty {
// save settings to file // save settings to file
let settings = match runtime_settings.read() { let settings = runtime_settings.blocking_read();
Ok(x) => x,
Err(e) => {
log::error!("runtime failed to acquire settings read lock: {}", e);
continue;
}
};
let settings_json: SettingsJson = settings.clone().into(); let settings_json: SettingsJson = settings.clone().into();
if let Err(e) = settings_json.save(settings_path(&state.home)) { if let Err(e) = settings_json.save(settings_path(&state.home)) {
log::error!("SettingsJson.save({}) error: {}", settings_path(&state.home).display(), e); log::error!("SettingsJson.save({}) error: {}", settings_path(&state.home).display(), e);
} }
Self::on_set_enable(&settings, &state); runtime_fan.on_enable_toggled(&settings);
drop(state); drop(state);
let mut state = match runtime_state.write() { let mut state = runtime_state.blocking_write();
Ok(x) => x,
Err(e) => {
log::error!("runtime failed to acquire state write lock: {}", e);
continue;
}
};
state.dirty = false; state.dirty = false;
} }
} }
{ // fan control { // fan control
let settings = match runtime_settings.read() { let settings = runtime_settings.blocking_read();
Ok(x) => x,
Err(e) => {
log::error!("runtime failed to acquire settings read lock: {}", e);
continue;
}
};
if settings.enable { if settings.enable {
Self::do_fan_control(&settings); 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); thread::sleep(sleep_duration);
} }
}) })
} }
fn on_set_enable(settings: &Settings, _state: &State) {
// TODO stop/start jupiter fan control (or maybe let the UI handle that?)
if let Err(e) = crate::sys::write_fan_recalc(settings.enable) {
log::error!("runtime failed to write to fan recalculate file: {}", e);
}
}
fn do_fan_control(settings: &Settings) {
/*
curve = self.settings["curve"]
fan_ratio = 0 # unnecessary in Python, but stupid without
if len(curve) == 0:
fan_ratio = 1
else:
index = -1
temperature_ratio = (thermal_zone(0) - TEMPERATURE_MINIMUM) / (TEMPERATURE_MAXIMUM - TEMPERATURE_MINIMUM)
for i in range(len(curve)-1, -1, -1):
if curve[i]["x"] < temperature_ratio:
index = i
break
if self.settings["interpolate"]:
fan_ratio = self.interpolate_fan(self, index, temperature_ratio)
else:
fan_ratio = self.step_fan(self, index, temperature_ratio)
set_fan_target(int((fan_ratio * FAN_MAXIMUM) + FAN_MINIMUM))
*/
let fan_ratio: f64 = if let Some(thermal_zone) = crate::sys::read_thermal_zone(0) {
let temperature_ratio = (((thermal_zone as f64)/1000.0) - settings.temperature_bounds.min)
/ (settings.temperature_bounds.max - settings.temperature_bounds.min);
let mut index = None;
for i in (0..settings.curve.len()).rev() {
if settings.curve[i].x < temperature_ratio {
index = Some(i);
break;
}
}
if settings.interpolate {
Self::interpolate_fan(settings, index, temperature_ratio)
} else {
Self::step_fan(settings, index, temperature_ratio)
}
} else {
1.0
};
let fan_speed: u64 = ((fan_ratio * (settings.fan_bounds.max - settings.fan_bounds.min)) + settings.fan_bounds.min) as _;
if let Err(e) = crate::sys::write_fan_target(fan_speed) {
log::error!("runtime failed to write to fan target file: {}", e);
}
}
fn interpolate_fan(settings: &Settings, index: Option<usize>, t_ratio: f64) -> f64 {
/*
curve = self.settings["curve"]
upper_point = {"x": 1.0, "y": 0.0}
lower_point = {"x": 0.0, "y": 1.0}
if index != -1: # guaranteed to not be empty
lower_point = curve[index]
if index != len(curve) - 1:
upper_point = curve[index+1]
#logging.debug(f"lower_point: {lower_point}, upper_point: {upper_point}")
upper_y = 1-upper_point["y"]
lower_y = 1-lower_point["y"]
slope_m = (upper_y - lower_y) / (upper_point["x"] - lower_point["x"])
y_intercept_b = lower_y - (slope_m * lower_point["x"])
logging.debug(f"interpolation: y = {slope_m}x + {y_intercept_b}")
return (slope_m * temperature_ratio) + y_intercept_b
*/
let (upper, lower) = if let Some(i) = index {
(if i != settings.curve.len() - 1 {
settings.curve[i+1].clone()
} else {
GraphPoint{x: 1.0, y: 1.0}
},
settings.curve[i].clone())
} else {
(if settings.curve.is_empty() {
GraphPoint{x: 1.0, y: 1.0}
} else {
settings.curve[0].clone()
},
GraphPoint{x: 0.0, y: 0.0})
};
let slope_m = (upper.y - lower.y) / (upper.x - lower.x);
let y_intercept_b = lower.y - (slope_m * lower.x);
log::debug!("interpolation: y = {}x + {} (between {:?} and {:?})", slope_m, y_intercept_b, upper, lower);
(slope_m * t_ratio) + y_intercept_b
}
fn step_fan(settings: &Settings, index: Option<usize>, _t_ratio: f64) -> f64 {
/*
curve = self.settings["curve"]
if index != -1:
return 1 - curve[index]["y"]
else:
if len(curve) == 0:
return 1
else:
return 0.5
*/
// 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
}
}
}
} }
fn settings_path<P: AsRef<std::path::Path>>(home: P) -> std::path::PathBuf { fn settings_path<P: AsRef<std::path::Path>>(home: P) -> std::path::PathBuf {

View file

@ -2,7 +2,7 @@ use std::default::Default;
use std::convert::{Into, From}; use std::convert::{Into, From};
use std::path::PathBuf; use std::path::PathBuf;
use super::json::{SettingsJson, GraphPointJson, BoundsJson}; use super::json::{SettingsJson, GraphPointJson};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Settings { pub struct Settings {
@ -10,8 +10,6 @@ pub struct Settings {
pub enable: bool, pub enable: bool,
pub interpolate: bool, pub interpolate: bool,
pub curve: Vec<GraphPoint>, pub curve: Vec<GraphPoint>,
pub fan_bounds: Bounds<f64>,
pub temperature_bounds: Bounds<f64>,
} }
impl Settings { impl Settings {
@ -28,42 +26,18 @@ impl From<SettingsJson> for Settings {
enable: other.enable, enable: other.enable,
interpolate: other.interpolate, interpolate: other.interpolate,
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(), curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
fan_bounds: Bounds {
min: 1.0,
max: 7000.0,
},
temperature_bounds: Bounds {
min: 0.0,
max: 100.0,
},
}, },
1 => Self { 1 => Self {
version: 1, version: 1,
enable: other.enable, enable: other.enable,
interpolate: other.interpolate, interpolate: other.interpolate,
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(), curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
fan_bounds: other.fan_bounds.map(|x| Bounds::<f64>::from_json(x, other.version)).unwrap_or(Bounds {
min: 1.0,
max: 7000.0,
}),
temperature_bounds: other.temperature_bounds.map(|x| Bounds::<f64>::from_json(x, other.version)).unwrap_or(Bounds {
min: 0.0,
max: 100.0,
}),
}, },
_ => Self { _ => Self {
version: 1, version: 1,
enable: other.enable, enable: other.enable,
interpolate: other.interpolate, interpolate: other.interpolate,
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(), curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
fan_bounds: Bounds {
min: 1.0,
max: 7000.0,
},
temperature_bounds: Bounds {
min: 0.0,
max: 100.0,
},
} }
}; };
result.sort_curve(); result.sort_curve();
@ -79,8 +53,6 @@ impl Into<SettingsJson> for Settings {
enable: self.enable, enable: self.enable,
interpolate: self.interpolate, interpolate: self.interpolate,
curve: self.curve.drain(..).map(|x| x.into()).collect(), curve: self.curve.drain(..).map(|x| x.into()).collect(),
fan_bounds: Some(self.fan_bounds.into()),
temperature_bounds: Some(self.temperature_bounds.into()),
} }
} }
} }
@ -121,72 +93,6 @@ impl Into<GraphPointJson> for GraphPoint {
} }
} }
#[derive(Debug, Clone)]
pub struct Bounds<T: core::fmt::Debug + Clone> {
pub min: T,
pub max: T,
}
/*impl Bounds<usize> {
#[inline]
pub fn from_json(other: BoundsJson, version: u64) -> Self {
match version {
0 => Self {
min: other.min as _,
max: other.max as _,
},
1 => Self {
min: other.min as _,
max: other.max as _,
},
_ => Self {
min: other.min as _,
max: other.max as _,
}
}
}
}*/
impl Bounds<f64> {
#[inline]
pub fn from_json(other: BoundsJson, version: u64) -> Self {
match version {
0 => Self {
min: other.min,
max: other.max,
},
1 => Self {
min: other.min,
max: other.max,
},
_ => Self {
min: other.min,
max: other.max,
}
}
}
}
/*impl Into<BoundsJson> for Bounds<usize> {
#[inline]
fn into(self) -> BoundsJson {
BoundsJson {
min: self.min as _,
max: self.max as _,
}
}
}*/
impl Into<BoundsJson> for Bounds<f64> {
#[inline]
fn into(self) -> BoundsJson {
BoundsJson {
min: self.min,
max: self.max,
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct State { pub struct State {
pub home: PathBuf, pub home: PathBuf,

View file

@ -11,8 +11,6 @@ pub struct SettingsJson {
pub enable: bool, pub enable: bool,
pub interpolate: bool, pub interpolate: bool,
pub curve: Vec<GraphPointJson>, pub curve: Vec<GraphPointJson>,
pub fan_bounds: Option<BoundsJson>,
pub temperature_bounds: Option<BoundsJson>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -21,12 +19,6 @@ pub struct GraphPointJson {
pub y: f64, pub y: f64,
} }
#[derive(Serialize, Deserialize)]
pub struct BoundsJson {
pub min: f64,
pub max: f64,
}
impl Default for SettingsJson { impl Default for SettingsJson {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -34,14 +26,6 @@ impl Default for SettingsJson {
enable: false, enable: false,
interpolate: true, interpolate: true,
curve: Vec::new(), curve: Vec::new(),
fan_bounds: Some(BoundsJson {
min: 0.0,
max: 7000.0,
}),
temperature_bounds: Some(BoundsJson {
min: 0.0,
max: 100.0,
})
} }
} }
} }

View file

@ -1,4 +1,5 @@
mod api; mod api;
mod adapters;
mod control; mod control;
mod datastructs; mod datastructs;
mod json; mod json;
@ -6,7 +7,13 @@ mod sys;
use simplelog::{WriteLogger, LevelFilter}; use simplelog::{WriteLogger, LevelFilter};
use usdpl_back::Instance; #[allow(missing_docs)]
#[allow(dead_code)]
pub mod services {
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}
use services::fantastic::FanServer;
const PORT: u16 = 44444; const PORT: u16 = 44444;
@ -20,9 +27,29 @@ fn main() -> Result<(), ()> {
log::info!("Starting back-end ({} v{})", api::NAME, api::VERSION); log::info!("Starting back-end ({} v{})", api::NAME, api::VERSION);
println!("Starting back-end ({} v{})", api::NAME, api::VERSION); println!("Starting back-end ({} v{})", api::NAME, api::VERSION);
let runtime = control::ControlRuntime::new(); usdpl_back::Server::new(PORT)
runtime.run(); .register(FanServer::new(
Instance::new(PORT) 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("echo", api::echo)
.register("hello", api::hello) .register("hello", api::hello)
.register("version", api::version) .register("version", api::version)
@ -36,7 +63,7 @@ fn main() -> Result<(), ()> {
.register("get_curve", api::get_curve_gen(&runtime)) .register("get_curve", api::get_curve_gen(&runtime))
.register("add_curve_point", api::add_curve_point_gen(&runtime)) .register("add_curve_point", api::add_curve_point_gen(&runtime))
.register("remove_curve_point", api::remove_curve_point_gen(&runtime)) .register("remove_curve_point", api::remove_curve_point_gen(&runtime))
.run_blocking() .run_blocking()*/
//Ok(()) //Ok(())
//println!("Hello, world!"); //println!("Hello, world!");
} }

View file

@ -1,17 +1 @@
use usdpl_back::api::files::*; pub const SYSFS_ROOT: &str = "/";
pub fn read_fan() -> Option<u64> {
read_single("/sys/class/hwmon/hwmon5/fan1_input").ok()
}
pub fn read_thermal_zone(index: u8) -> Option<u64> {
read_single(format!("/sys/class/thermal/thermal_zone{}/temp", index)).ok()
}
pub fn write_fan_recalc(enabled: bool) -> Result<(), std::io::Error> {
write_single("/sys/class/hwmon/hwmon5/recalculate", enabled as u8)
}
pub fn write_fan_target(rpm: u64) -> Result<(), std::io::Error> {
write_single("/sys/class/hwmon/hwmon5/fan1_target", rpm)
}

14
main.py
View file

@ -25,6 +25,18 @@ class Plugin:
# Asyncio-compatible long-running code, executed in a task when the plugin is loaded # Asyncio-compatible long-running code, executed in a task when the plugin is loaded
async def _main(self): async def _main(self):
# startup # startup
self.backend_proc = subprocess.Popen([PARENT_DIR + "/bin/backend"]) self.backend_proc = subprocess.Popen(
[PARENT_DIR + "/bin/backend"],
env = dict(os.environ))
while True: while True:
await asyncio.sleep(1) await asyncio.sleep(1)
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

View file

@ -1,292 +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) {
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 = "";
graphDiv.innerHTML = newStr;
console.log("Fan graph redrawn");
}
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";
}
}
const TOGGLE_ON_CLASS = "gamepaddialog_On_3ld7T"
function setToggleState(toggle, state) {
if (state && !toggle.classList.contains(TOGGLE_ON_CLASS)) {
toggle.classList.add(TOGGLE_ON_CLASS);
}
if (!state && toggle.classList.contains(TOGGLE_ON_CLASS)) {
toggle.classList.remove(TOGGLE_ON_CLASS);
}
}
function getToggleState(toggle) {
return toggle.classList.contains(TOGGLE_ON_CLASS);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
</script>
</head>
<body onload="onload_body()" style="overflow-x:hidden;margin:0px;">
<!-- Spacer (moves top out of shadow above it) -->
<div class="quickaccessmenu_TabGroupPanel_1QO7b">
<div class="quickaccesscontrols_PanelSection_2C0g0" style="margin-bottom:6px;">
<!--<div class="quickaccesscontrols_PanelSectionRow_2VQ88">
</div>-->
</div>
</div>
<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">
<div class="quickaccesscontrols_PanelSection_2C0g0" style="padding:0px 4px;">
<div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_WithDescription_3bMIS gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;">
<div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">
Custom Fan Curve
</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div id="controlToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable" onclick="onclick_controlToggle()">
<div class="gamepaddialog_ToggleRail_2JtC3"></div>
<div class="gamepaddialog_ToggleSwitch_3__OD"></div>
</div>
</div>
</div>
<div class="gamepaddialog_FieldDescription_2OJfk">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;margin:auto;" 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_PanelSection_2C0g0" style="padding:0px 4px;">
<div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_WithDescription_3bMIS gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_ExtraPaddingOnChildrenBelow_5UO-_ gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;">
<div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">
Linear Interpolation
</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div id="interpolToggle" tabindex="0" class="gamepaddialog_Toggle_24G4g Focusable" onclick="onclick_interpolToggle()">
<div class="gamepaddialog_ToggleRail_2JtC3"></div>
<div class="gamepaddialog_ToggleSwitch_3__OD"></div>
</div>
</div>
</div>
<div class="gamepaddialog_FieldDescription_2OJfk">Pretends a straight line connects points</div>
</div>
</div>
</div>
</div>
</div>
<!-- Fan Info -->
<div class="quickaccesscontrols_PanelSection_2C0g0" 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_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;padding-left:0px;padding-right:0px;">
<div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">Current Fan Speed</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div class="gamepaddialog_LabelFieldValue_5Mylh" id="fanNow"> (|-_-|) </div>
</div>
</div>
</div>
</div>
</div>
<div class="Panel Focusable" tabindex="0">
<div class="quickaccesscontrols_PanelSectionRow_2VQ88">
<div class="gamepaddialog_Field_S-_La gamepaddialog_WithFirstRow_qFXi6 gamepaddialog_VerticalAlignCenter_3XNvA gamepaddialog_InlineWrapShiftsChildrenBelow_pHUb6 gamepaddialog_WithBottomSeparatorStandard_3s1Rk gamepaddialog_StandardPadding_XRBFu gamepaddialog_HighlightOnFocus_wE4V6 Panel Focusable" style="--indent-level:0;padding-left:0px;padding-right:0px;">
<div class="gamepaddialog_FieldLabelRow_H9WOq">
<div class="gamepaddialog_FieldLabel_3b0U-">Current Temperature</div>
<div class="gamepaddialog_FieldChildren_14_HB">
<div class="gamepaddialog_LabelFieldValue_5Mylh" id="tempNow"> (|-_-|) </div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

2588
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "Fantastic", "name": "Fantastic",
"version": "0.3.3", "version": "0.6.0-alpha1",
"description": "A template to quickly create decky plugins from scratch, based on TypeScript and webpack", "description": "A template to quickly create decky plugins from scratch, based on TypeScript and webpack",
"scripts": { "scripts": {
"build": "shx rm -rf dist && rollup -c", "build": "shx rm -rf dist && rollup -c",
@ -28,21 +28,21 @@
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^21.1.0", "@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.2.1", "@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0", "@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-typescript": "^8.3.2", "@rollup/plugin-typescript": "^8.5.0",
"@types/react": "16.14.0", "@types/react": "16.14.0",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.5",
"rollup": "^2.70.2", "rollup": "^2.79.1",
"rollup-plugin-import-assets": "^1.1.1", "rollup-plugin-import-assets": "^1.1.1",
"shx": "^0.3.4", "shx": "^0.3.4",
"tslib": "^2.4.0", "tslib": "^2.6.2",
"typescript": "^4.6.4" "typescript": "^4.9.5"
}, },
"dependencies": { "dependencies": {
"decky-frontend-lib": "^1.0.1", "decky-frontend-lib": "^3.25.0",
"react-icons": "^4.3.1", "fantastic-wasm": "file:src/rust/pkg",
"usdpl-front": "file:./src/usdpl" "react-icons": "^4.12.0"
}, },
"pnpm": { "pnpm": {
"peerDependencyRules": { "peerDependencyRules": {

View file

@ -1,11 +1,11 @@
{ {
"name": "Fantastic", "name": "Fantastic",
"author": "NGnius", "author": "NGnius",
"flags": ["root", "debug"], "flags": ["root", "_debug", "global-dfl"],
"publish": { "publish": {
"discord_id": "106537989684887552", "discord_id": "106537989684887552",
"description": "Fan controls", "description": "Fan controls",
"tags": [ "utility", "fan-control" ], "tags": [ "utility", "fan-control", "root" ],
"image": "https://raw.githubusercontent.com/NGnius/Fantastic/main/assets/thumbnail.png" "image": "https://raw.githubusercontent.com/NGnius/Fantastic/main/assets/thumbnail.png"
} }
} }

953
pnpm-lock.yaml Normal file
View 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

View file

@ -24,12 +24,13 @@ export default defineConfig({
}) })
], ],
context: 'window', context: 'window',
external: ['react', 'react-dom'], external: ['react', 'react-dom', 'decky-frontend-lib'],
output: { output: {
file: 'dist/index.js', file: 'dist/index.js',
globals: { globals: {
react: 'SP_REACT', react: 'SP_REACT',
'react-dom': 'SP_REACTDOM', 'react-dom': 'SP_REACTDOM',
'decky-frontend-lib': 'DFL',
}, },
format: 'iife', format: 'iife',
exports: 'default', exports: 'default',

View file

@ -1,7 +1,15 @@
import {init_usdpl, target, init_embedded, call_backend} from "usdpl-front"; //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; const USDPL_PORT: number = 44444;
var FAN_CLIENT: Fan | undefined = undefined;
// Utility // Utility
export function resolve(promise: Promise<any>, setter: any) { export function resolve(promise: Promise<any>, setter: any) {
@ -26,49 +34,70 @@ export function execute(promise: Promise<any[]>) {
export async function initBackend() { export async function initBackend() {
// init usdpl // init usdpl
await init_embedded(); await init_embedded();
init_usdpl(USDPL_PORT); FAN_CLIENT = new Fan(USDPL_PORT);
console.log("USDPL started for framework: " + target()); console.log("FANTASTIC: USDPL started for framework: " + target_usdpl());
//setReady(true); //setReady(true);
} }
// Back-end functions // Back-end functions
export async function setEnabled(value: boolean): Promise<boolean> { export async function setEnabled(value: boolean): Promise<boolean> {
return (await call_backend("set_enable", [value]))[0]; return (await FAN_CLIENT!.set_enable(value))?? value;
//return (await call_backend("set_enable", [value]))[0];
} }
export async function getEnabled(): Promise<boolean> { export async function getEnabled(): Promise<boolean> {
return (await call_backend("get_enable", []))[0]; return (await FAN_CLIENT!.get_enable(true)) ?? false;
} }
export async function setInterpolate(value: boolean): Promise<boolean> { export async function setInterpolate(value: boolean): Promise<boolean> {
return (await call_backend("set_interpolate", [value]))[0]; return (await FAN_CLIENT!.set_interpolate(value)) ?? value;
//return (await call_backend("set_interpolate", [value]))[0];
} }
export async function getInterpolate(): Promise<boolean> { export async function getInterpolate(): Promise<boolean> {
return (await call_backend("get_interpolate", []))[0]; return (await FAN_CLIENT!.get_interpolate(true)) ?? false;
//return (await call_backend("get_interpolate", []))[0];
} }
export async function getVersion(): Promise<string> { export async function getVersion(): Promise<string> {
return (await call_backend("version", []))[0]; 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}[]> { export async function getCurve(): Promise<{"x": number, "y": number}[]> {
return (await call_backend("get_curve", []))[0]; 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}[]> { export async function addCurvePoint(point: {"x": number, "y": number}): Promise<{"x": number, "y": number}[]> {
return (await call_backend("add_curve_point", [point]))[0]; await FAN_CLIENT!.add_curve_point(point.x, point.y);
return getCurve();
} }
export async function removeCurvePoint(index: number): Promise<{"x": number, "y": number}[]> { export async function removeCurvePoint(index: number): Promise<{"x": number, "y": number}[]> {
return (await call_backend("remove_curve_point", [index]))[0]; await FAN_CLIENT!.remove_curve_point(index);
return getCurve();
//return (await call_backend("remove_curve_point", [index]))[0];
} }
export async function getFanRpm(): Promise<number> { export async function getFanRpm(callback: (rpm: number) => void): Promise<void> {
return (await call_backend("get_fan_rpm", []))[0]; return (await FAN_CLIENT!.get_fan_rpm(true, callback));
} }
export async function getTemperature(): Promise<number> { export async function getTemperature(callback: (temp: number) => void): Promise<void> {
return (await call_backend("get_temperature", []))[0]; return (await FAN_CLIENT!.get_temperature(true, callback));
} }

View file

@ -1,18 +1,19 @@
import { import {
definePlugin, definePlugin,
DialogButton,
PanelSection, PanelSection,
PanelSectionRow, PanelSectionRow,
Field,
ServerAPI, ServerAPI,
ToggleField, ToggleField,
staticClasses, staticClasses,
gamepadDialogClasses, Navigation,
joinClassNames,
} from "decky-frontend-lib"; } from "decky-frontend-lib";
import { VFC, useState } from "react"; import { VFC, useState } from "react";
import { FaFan } from "react-icons/fa"; import { FaFan } from "react-icons/fa";
import { SiOnlyfans } from "react-icons/si"; import { SiOnlyfans } from "react-icons/si";
import { version_usdpl } from "fantastic-wasm";
import * as backend from "./backend"; import * as backend from "./backend";
import {Canvas} from "./canvas"; import {Canvas} from "./canvas";
@ -21,8 +22,17 @@ const POINT_SIZE = 32;
var periodicHook: any = null; var periodicHook: any = null;
var usdplReady: boolean = false; var usdplReady: boolean = false;
var name: string = "";
var version: string = "";
var curve_backup: {x: number, y: number}[] = []; 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 Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
// const [result, setResult] = useState<number | undefined>(); // const [result, setResult] = useState<number | undefined>();
@ -50,23 +60,30 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
curve_backup = value; curve_backup = value;
} }
const [temperatureGlobal, setTemperature] = useState<number>(-273.15); const [temperatureGlobal, setTemperature] = useState<number>(tempCache);
const [fanRpmGlobal, setFanRpm] = useState<number>(-1337); const [fanRpmGlobal, setFanRpm] = useState<number>(fanRpmCache);
setTemperature_display = (x) => {
setTemperature(x);
tempCache = x;
};
setFanRpm_display = (x) => {
setFanRpm(x);
fanRpmCache = x;
};
function setEnable(enable: boolean) { function setEnable(enable: boolean) {
setEnableInternal(enable); setEnableInternal(enable);
//@ts-ignore
SteamClient.System.SetBetaFanControl(!enable);
} }
function onClickCanvas(e: any) { function onClickCanvas(e: any) {
//console.log("canvas click", e); //console.log("[FANTASTIC] canvas click", e);
const realEvent: any = e.nativeEvent; const realEvent: any = e.nativeEvent;
//console.log("Canvas click @ (" + realEvent.layerX.toString() + ", " + realEvent.layerY.toString() + ")"); //console.log("Canvas click @ (" + realEvent.layerX.toString() + ", " + realEvent.layerY.toString() + ")");
const target: any = e.currentTarget; const target: any = e.currentTarget;
//console.log("Target dimensions " + target.width.toString() + "x" + target.height.toString()); //console.log("[FANTASTIC] Target dimensions " + target.width.toString() + "x" + target.height.toString());
const clickX = realEvent.layerX; var clickX = realEvent.offsetX;
const clickY = realEvent.layerY; var clickY = realEvent.offsetY;
//console.debug("[FANTASTIC] curve click:", clickX, clickY);
for (let i = 0; i < curveGlobal.length; i++) { for (let i = 0; i < curveGlobal.length; i++) {
const curvePoint = curveGlobal[i]; const curvePoint = curveGlobal[i];
const pointX = curvePoint.x * target.width; const pointX = curvePoint.x * target.width;
@ -83,7 +100,8 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
} }
} }
//console.log("Adding new point"); //console.log("Adding new point");
backend.resolve(backend.addCurvePoint({x: clickX / target.width, y: 1 - (clickY / target.height)}), setCurve); const curvePoint = {x: clickX / target.width, y: 1 - (clickY / target.height)};
backend.resolve(backend.addCurvePoint(curvePoint), setCurve);
} }
function drawCanvas(ctx: any, frameCount: number): void { function drawCanvas(ctx: any, frameCount: number): void {
@ -92,7 +110,6 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
} }
const width: number = ctx.canvas.width; const width: number = ctx.canvas.width;
const height: number = ctx.canvas.height; const height: number = ctx.canvas.height;
ctx.strokeStyle = "#1a9fff"; ctx.strokeStyle = "#1a9fff";
ctx.fillStyle = "#1a9fff"; ctx.fillStyle = "#1a9fff";
ctx.lineWidth = 2; ctx.lineWidth = 2;
@ -108,9 +125,42 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // Right eye*/ ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // Right eye*/
//ctx.beginPath(); //ctx.beginPath();
//ctx.moveTo(0, height); //ctx.moveTo(0, height);
if (interpolGlobal) {
// graph helper lines
ctx.beginPath(); 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); ctx.moveTo(0, height);
if (interpolGlobal) {
//ctx.beginPath();
for (let i = 0; i < curveGlobal.length; i++) { for (let i = 0; i < curveGlobal.length; i++) {
const canvasHeight = (1 - curveGlobal[i].y) * height; const canvasHeight = (1 - curveGlobal[i].y) * height;
const canvasWidth = curveGlobal[i].x * width; const canvasWidth = curveGlobal[i].x * width;
@ -121,11 +171,8 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
} }
ctx.lineTo(width, 0); ctx.lineTo(width, 0);
//ctx.moveTo(width, 0); //ctx.moveTo(width, 0);
ctx.stroke();
ctx.fill();
} else { } else {
ctx.beginPath(); //ctx.beginPath();
ctx.moveTo(0, height);
for (let i = 0; i < curveGlobal.length - 1; i++) { for (let i = 0; i < curveGlobal.length - 1; i++) {
const canvasHeight = (1 - curveGlobal[i].y) * height; const canvasHeight = (1 - curveGlobal[i].y) * height;
const canvasWidth = curveGlobal[i].x * width; const canvasWidth = curveGlobal[i].x * width;
@ -155,11 +202,10 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
ctx.moveTo(canvasWidth2, canvasHeight2); ctx.moveTo(canvasWidth2, canvasHeight2);
ctx.lineTo(canvasWidth2, height); ctx.lineTo(canvasWidth2, height);
} }
//ctx.moveTo(width, 0); //ctx.moveTo(width, 0);
}
ctx.stroke(); ctx.stroke();
ctx.fill(); ctx.fill();
}
console.debug("Rendered fan graph canvas frame", frameCount); console.debug("Rendered fan graph canvas frame", frameCount);
//console.debug("Drew canvas with " + curveGlobal.length.toString() + " points; " + width.toString() + "x" + height.toString()); //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.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
@ -176,55 +222,45 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
backend.resolve(backend.getEnabled(), setEnable); backend.resolve(backend.getEnabled(), setEnable);
backend.resolve(backend.getInterpolate(), setInterpol); backend.resolve(backend.getInterpolate(), setInterpol);
backend.resolve(backend.getCurve(), setCurve); backend.resolve(backend.getCurve(), setCurve);
backend.resolve(backend.getTemperature(), setTemperature); backend.resolve(backend.getTemperature(setTemperature_display), (_: any) => {});
backend.resolve(backend.getFanRpm(), setFanRpm); backend.resolve(backend.getFanRpm(setFanRpm_display), (_: any) => {});
if (periodicHook != null) { if (periodicHook != null) {
clearInterval(periodicHook); clearInterval(periodicHook);
} }
periodicHook = setInterval(function() { /*periodicHook = setInterval(function() {
backend.resolve(backend.getTemperature(), setTemperature); backend.resolve(backend.getTemperature(), setTemperature);
backend.resolve(backend.getFanRpm(), setFanRpm); backend.resolve(backend.getFanRpm(), setFanRpm);
}, 1000); }, 1000);*/
} }
if (!usdplReady) { if (!usdplReady) {
return ( return (
<PanelSection> <PanelSectionRow>
</PanelSection> <Field
label="Loading...">
If you can read this, something probably went wrong :(
</Field>
</PanelSectionRow>
); );
} }
const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard);
// TODO handle clicking on fan curve nodes // TODO handle clicking on fan curve nodes
return ( return (
<PanelSection> <PanelSection>
<PanelSectionRow> <PanelSectionRow>
<div className={FieldWithSeparator}> <Field
<div className={gamepadDialogClasses.FieldLabelRow}> label="Current Fan Speed">
<div className={gamepadDialogClasses.FieldLabel}>
Current Fan Speed
</div>
<div className={gamepadDialogClasses.FieldChildren}>
{fanRpmGlobal.toFixed(0) + " RPM"} {fanRpmGlobal.toFixed(0) + " RPM"}
</div> </Field>
</div>
</div>
</PanelSectionRow> </PanelSectionRow>
<PanelSectionRow> <PanelSectionRow>
<div className={FieldWithSeparator}> <Field
<div className={gamepadDialogClasses.FieldLabelRow}> label="Current Temperature">
<div className={gamepadDialogClasses.FieldLabel}>
Current Temperature
</div>
<div className={gamepadDialogClasses.FieldChildren}>
{temperatureGlobal.toFixed(1) + " °C"} {temperatureGlobal.toFixed(1) + " °C"}
</div> </Field>
</div>
</div>
</PanelSectionRow> </PanelSectionRow>
<PanelSectionRow> <PanelSectionRow>
<ToggleField <ToggleField
@ -267,34 +303,35 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({serverAPI}) => {
/> />
</PanelSectionRow> </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> </PanelSection>
); );
}; };
const DeckyPluginRouterTest: VFC = () => { (async function(){
return ( if (!usdplReady) {
<div style={{ marginTop: "50px", color: "white" }}>
Hello World!
<DialogButton onClick={() => {}}>
Go to Store
</DialogButton>
</div>
);
};
export default definePlugin((serverApi: ServerAPI) => {
serverApi.routerHook.addRoute("/decky-plugin-test", DeckyPluginRouterTest, {
exact: true,
});
(async function(){
await backend.initBackend(); await backend.initBackend();
usdplReady = true; usdplReady = true;
backend.getEnabled().then((enabled: boolean) => { backend.getEnabled();
//@ts-ignore name = await backend.getName();
SteamClient.System.SetBetaFanControl(!enabled); version = await backend.getVersion();
}); }
})(); })();
export default definePlugin((serverApi: ServerAPI) => {
let ico = <FaFan />; let ico = <FaFan />;
let now = new Date(); let now = new Date();
@ -308,7 +345,6 @@ export default definePlugin((serverApi: ServerAPI) => {
icon: ico, icon: ico,
onDismount() { onDismount() {
clearInterval(periodicHook!); clearInterval(periodicHook!);
serverApi.routerHook.removeRoute("/decky-plugin-test");
}, },
}; };
}); });

981
src/rust/Cargo.lock generated Normal file
View 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
View 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
View 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
View 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

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

View file

@ -1,21 +0,0 @@
{
"name": "usdpl-front",
"collaborators": [
"NGnius (Graham) <ngniusness@gmail.com>"
],
"description": "Universal Steam Deck Plugin Library front-end designed for WASM",
"version": "0.4.0",
"license": "GPL-3.0-only",
"repository": {
"type": "git",
"url": "https://github.com/NGnius/usdpl-rs"
},
"files": [
"usdpl_front_bg.wasm",
"usdpl_front.js",
"usdpl_front.d.ts"
],
"module": "usdpl_front.js",
"types": "usdpl_front.d.ts",
"sideEffects": false
}

View file

@ -1,51 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Initialize the front-end library
* @param {number} port
*/
export function init_usdpl(port: number): void;
/**
* Get the targeted plugin framework, or "any" if unknown
* @returns {string}
*/
export function target(): string;
/**
* Call a function on the back-end.
* Returns null (None) if this fails for any reason.
* @param {string} name
* @param {any[]} parameters
* @returns {Promise<any>}
*/
export function call_backend(name: string, parameters: any[]): Promise<any>;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly init_usdpl: (a: number) => void;
readonly target: (a: number) => void;
readonly call_backend: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2bd47cee569ae4c6: (a: number, b: number, c: number) => void;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_free: (a: number, b: number) => void;
readonly __wbindgen_exn_store: (a: number) => void;
readonly wasm_bindgen__convert__closures__invoke2_mut__hfe1195d34914cc54: (a: number, b: number, c: number, d: number) => void;
}
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;
// USDPL customization
export function init_embedded();

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -1,14 +0,0 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export function init_usdpl(a: number): void;
export function target(a: number): void;
export function call_backend(a: number, b: number, c: number, d: number): number;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export const __wbindgen_export_2: WebAssembly.Table;
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2bd47cee569ae4c6(a: number, b: number, c: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_free(a: number, b: number): void;
export function __wbindgen_exn_store(a: number): void;
export function wasm_bindgen__convert__closures__invoke2_mut__hfe1195d34914cc54(a: number, b: number, c: number, d: number): void;

View file