diff --git a/assets/thumbnail.jpg b/assets/thumbnail.jpg index f74149b..e21d64a 100644 Binary files a/assets/thumbnail.jpg and b/assets/thumbnail.jpg differ diff --git a/assets/thumbnail.png b/assets/thumbnail.png index 0560598..c23f683 100644 Binary files a/assets/thumbnail.png and b/assets/thumbnail.png differ diff --git a/assets/thumbnail.xcf b/assets/thumbnail.xcf index 4a9ac7b..b114bb5 100644 Binary files a/assets/thumbnail.xcf and b/assets/thumbnail.xcf differ diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 9174e48..0051bba 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -55,9 +55,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -93,25 +93,73 @@ dependencies = [ ] [[package]] -name = "async-recursion" -version = "1.0.4" +name = "anstream" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-recursion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn", ] [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn", ] [[package]] @@ -143,15 +191,15 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bindgen" -version = "0.64.0" +version = "0.66.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" dependencies = [ "bitflags", "cexpr", @@ -160,20 +208,24 @@ dependencies = [ "lazycell", "log", "peeking_take_while", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn", "which", ] [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -186,9 +238,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.4" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -196,27 +248,27 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytecount" -version = "0.6.3" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" @@ -244,17 +296,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", - "time 0.1.45", + "serde", "wasm-bindgen", - "winapi", + "windows-targets 0.48.5", ] [[package]] @@ -277,6 +329,46 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + [[package]] name = "cmake" version = "0.1.50" @@ -287,16 +379,29 @@ dependencies = [ ] [[package]] -name = "core-foundation-sys" -version = "0.8.4" +name = "colorchoice" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "community_settings_core" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -330,10 +435,19 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.3.8" +name = "data-encoding" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "deranged" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +dependencies = [ + "powerfmt", +] [[package]] name = "digest" @@ -351,6 +465,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encoding" version = "0.2.33" @@ -432,30 +552,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.2.8" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -469,18 +578,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -488,27 +597,27 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", "futures-sink", @@ -530,13 +639,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -551,9 +660,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -563,9 +672,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -573,7 +682,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap", "slab", "tokio", "tokio-util", @@ -582,24 +691,17 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "headers" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.13.1", - "bitflags", + "base64 0.21.5", "bytes", "headers-core", "http", @@ -618,10 +720,16 @@ dependencies = [ ] [[package]] -name = "hermit-abi" -version = "0.3.2" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -630,10 +738,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "http" -version = "0.2.9" +name = "home" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -642,9 +759,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -680,7 +797,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -689,16 +806,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -712,9 +829,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -722,35 +839,25 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -769,9 +876,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libloading" @@ -785,9 +892,9 @@ dependencies = [ [[package]] name = "libryzenadj" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a805571abbf0729e3641b825b734948ce84ccbb54b9f08afae0b860bb12af971" +checksum = "c5bccdf07c3234c06c435648a53d8cb369f76d20e03bb8d2f8c24fb2330efc32" dependencies = [ "errno", "libryzenadj-sys", @@ -797,9 +904,9 @@ dependencies = [ [[package]] name = "libryzenadj-sys" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b72f39d32e246ce0db7b4dd2430ddb5bb4dfa27681bce696a8746ac88892de1" +checksum = "b1de3621be974e892e12d4a07a6a2e32b6a05950759b062d94f5b54f78fabc3a" dependencies = [ "bindgen", "cmake", @@ -808,12 +915,17 @@ dependencies = [ [[package]] name = "limits_core" -version = "2.0.1" +version = "3.0.0" dependencies = [ "serde", - "serde_json", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + [[package]] name = "log" version = "0.4.20" @@ -822,9 +934,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76fc44e2588d5b436dbc3c6cf62aef290f90dab6235744a93dfe1cc18f451e2c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" @@ -859,13 +971,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "wasi", + "windows-sys 0.48.0", ] [[package]] @@ -909,9 +1021,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -928,23 +1040,23 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.11" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.11" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -964,18 +1076,18 @@ checksum = "7b2b2cbbfd8defa51ff24450a61d73b3ff3e158484ddd274a883e886e6fbaa78" [[package]] name = "object" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -1002,9 +1114,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" @@ -1023,7 +1135,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn", ] [[package]] @@ -1050,18 +1162,30 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "powertools" -version = "1.4.0" +version = "2.0.0" dependencies = [ "async-trait", + "chrono", + "clap", + "community_settings_core", + "libc", "libryzenadj", "limits_core", "log", "regex", + "ron", "serde", "serde_json", "simplelog", + "smokepatio", "sysfuss", "tokio", "ureq", @@ -1074,6 +1198,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1086,9 +1220,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -1134,9 +1268,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.4" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -1146,9 +1280,8 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", @@ -1157,9 +1290,35 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.5", + "bitflags", + "serde", + "serde_derive", +] [[package]] name = "rustc-demangle" @@ -1174,19 +1333,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "rustls-pemfile" -version = "1.0.3" +name = "rustix" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "base64 0.21.3", + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.5", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", ] [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "scoped-tls" @@ -1195,30 +1389,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] -name = "serde" -version = "1.0.188" +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1239,9 +1443,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1250,9 +1454,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "simplelog" @@ -1262,7 +1466,7 @@ checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" dependencies = [ "log", "termcolor", - "time 0.3.28", + "time", ] [[package]] @@ -1274,11 +1478,19 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smokepatio" +version = "0.1.0" +dependencies = [ + "embedded-io", + "log", +] + [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -1286,12 +1498,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1300,6 +1512,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.4.1" @@ -1308,20 +1526,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.40" 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" +checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e" dependencies = [ "proc-macro2", "quote", @@ -1345,45 +1552,35 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn", ] [[package]] name = "time" -version = "0.1.45" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", "libc", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -1391,15 +1588,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -1421,9 +1618,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" dependencies = [ "backtrace", "bytes", @@ -1431,8 +1628,8 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.3", - "windows-sys", + "socket2 0.5.5", + "windows-sys 0.48.0", ] [[package]] @@ -1448,9 +1645,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.18.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", @@ -1460,9 +1657,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -1474,17 +1671,17 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap", "toml_datetime", "winnow", ] @@ -1497,11 +1694,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-core", @@ -1509,28 +1705,28 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.18.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "base64 0.13.1", "byteorder", "bytes", + "data-encoding", "http", "httparse", "log", @@ -1543,9 +1739,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" @@ -1558,15 +1754,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -1588,27 +1784,36 @@ dependencies = [ ] [[package]] -name = "ureq" -version = "2.7.1" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" dependencies = [ - "base64 0.21.3", + "base64 0.21.5", "brotli-decompressor", "encoding_rs", "flate2", "log", "once_cell", + "rustls", + "rustls-webpki", "serde", "serde_json", "url", + "webpki-roots", ] [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -1649,6 +1854,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "version_check" version = "0.9.4" @@ -1666,9 +1877,9 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" +checksum = "c1e92e22e03ff1230c03a1a8ee37d2f89cd489e2e541b7550d6afad96faed169" dependencies = [ "bytes", "futures-channel", @@ -1695,12 +1906,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1709,9 +1914,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1719,24 +1924,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1744,32 +1949,39 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -1790,9 +2002,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1804,12 +2016,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1818,7 +2030,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1827,13 +2048,28 @@ 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", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1842,36 +2078,72 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[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_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[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_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[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_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -1879,10 +2151,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "winnow" -version = "0.5.15" +name = "windows_x86_64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winnow" +version = "0.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" dependencies = [ "memchr", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 3b6d7ae..d255bd1 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,21 +1,22 @@ [package] name = "powertools" -version = "1.4.0" +version = "2.0.0" edition = "2021" authors = ["NGnius (Graham) "] description = "Backend (superuser) functionality for PowerTools" license = "GPL-3.0-only" -repository = "https://github.com/NGnius/PowerTools" +repository = "https://git.ngni.us/NG-SD-Plugins/PowerTools" keywords = ["utility", "power-management", "root", "decky"] readme = "../README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -usdpl-back = { version = "0.10.1", features = ["blocking"] }#, path = "../../usdpl-rs/usdpl-back"} +usdpl-back = { version = "0.10.1", features = ["blocking", "decky"] }#, path = "../../usdpl-rs/usdpl-back"} serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -sysfuss = { version = "0.2", features = ["derive"] }#, path = "../../sysfs-nav" } +ron = "0.8" +sysfuss = { version = "0.2", features = ["derive"] }#,path = "../../sysfs-nav"} # async tokio = { version = "*", features = ["time"] } @@ -26,11 +27,25 @@ log = "0.4" simplelog = "0.12" # limits & driver functionality -limits_core = { version = "2", path = "./limits_core" } +limits_core = { version = "3", path = "./limits_core" } regex = "1" -libryzenadj = { version = "0.12" } + +# steam deck libs +smokepatio = { version = "0.1", features = [ "std" ], path = "../../smokepatio" } +libc = "0.2" + +# online settings +community_settings_core = { version = "0.1", path = "./community_settings_core" } +chrono = { version = "0.4", features = [ "serde" ] } + +# hardware enablement +#libryzenadj = { version = "0.14", path = "../../libryzenadj-rs-14" } +libryzenadj = { version = "0.13" } + # ureq's tls feature does not like musl targets -ureq = { version = "2", features = ["json", "gzip", "brotli", "charset"], default-features = false, optional = true } +ureq = { version = "2", features = ["json", "gzip", "brotli", "charset", "tls"], default-features = false, optional = true } + +clap = { version = "4.4", features = [ "derive" ] } [features] default = ["online", "decky"] @@ -38,6 +53,7 @@ decky = ["usdpl-back/decky"] crankshaft = ["usdpl-back/crankshaft"] encrypt = ["usdpl-back/encrypt"] online = ["ureq"] +experimental = [] dev_stuff = [] [profile.release] @@ -45,13 +61,13 @@ debug = false strip = true lto = true codegen-units = 1 +opt-level = 3 [profile.docker] inherits = "release" debug = false strip = true lto = "thin" -codegen-units = 16 -opt-level = 2 +codegen-units = 8 debug-assertions = false overflow-checks = false diff --git a/backend/build.sh b/backend/build.sh index 7e408f6..689ae88 100755 --- a/backend/build.sh +++ b/backend/build.sh @@ -3,10 +3,11 @@ #cargo build --release --target x86_64-unknown-linux-musl #cargo build --target x86_64-unknown-linux-musl #cross build -cargo build +cargo build --release +#cargo build mkdir -p ../bin #cp --preserve=mode ./target/x86_64-unknown-linux-musl/release/powertools ../bin/backend #cp --preserve=mode ./target/x86_64-unknown-linux-musl/debug/powertools ../bin/backend +cp --preserve=mode ./target/release/powertools ../bin/backend #cp --preserve=mode ./target/debug/powertools ../bin/backend -cp --preserve=mode ./target/debug/powertools ../bin/backend diff --git a/backend/community_settings_core/Cargo.lock b/backend/community_settings_core/Cargo.lock new file mode 100644 index 0000000..4873981 --- /dev/null +++ b/backend/community_settings_core/Cargo.lock @@ -0,0 +1,65 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "community_settings_core" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/backend/community_settings_core/Cargo.toml b/backend/community_settings_core/Cargo.toml new file mode 100644 index 0000000..d4f6632 --- /dev/null +++ b/backend/community_settings_core/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "community_settings_core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0", features = ["derive"] } diff --git a/backend/community_settings_core/src/lib.rs b/backend/community_settings_core/src/lib.rs new file mode 100644 index 0000000..a3a6d96 --- /dev/null +++ b/backend/community_settings_core/src/lib.rs @@ -0,0 +1 @@ +pub mod v1; diff --git a/backend/community_settings_core/src/v1/metadata.rs b/backend/community_settings_core/src/v1/metadata.rs new file mode 100644 index 0000000..009baa6 --- /dev/null +++ b/backend/community_settings_core/src/v1/metadata.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone)] +pub struct Metadata { + pub name: String, + pub steam_app_id: u32, + pub steam_user_id: u64, + pub steam_username: String, + pub tags: Vec, + /// Should always be a valid u128, but some parsers do not support that + pub id: String, + pub config: super::Config, +} + +impl Metadata { + pub fn set_id(&mut self, id: u128) { + self.id = id.to_string() + } + + pub fn get_id(&self) -> u128 { + self.id.parse().expect("metadata id must be u128") + } +} diff --git a/backend/community_settings_core/src/v1/mod.rs b/backend/community_settings_core/src/v1/mod.rs new file mode 100644 index 0000000..7519798 --- /dev/null +++ b/backend/community_settings_core/src/v1/mod.rs @@ -0,0 +1,5 @@ +mod metadata; +mod setting; + +pub use metadata::Metadata; +pub use setting::*; diff --git a/backend/community_settings_core/src/v1/setting.rs b/backend/community_settings_core/src/v1/setting.rs new file mode 100644 index 0000000..8d2ea03 --- /dev/null +++ b/backend/community_settings_core/src/v1/setting.rs @@ -0,0 +1,47 @@ +use serde::{Deserialize, Serialize}; + +/// Base setting file containing all information for all components +#[derive(Serialize, Deserialize, Clone)] +pub struct Config { + pub cpus: Vec, + pub gpu: Gpu, + pub battery: Battery, +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +pub struct MinMax { + pub max: Option, + pub min: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct Battery { + pub charge_rate: Option, + pub charge_mode: Option, + #[serde(default)] + pub events: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct BatteryEvent { + pub trigger: String, + pub charge_rate: Option, + pub charge_mode: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct Cpu { + pub online: bool, + pub clock_limits: Option>, + pub governor: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct Gpu { + pub fast_ppt: Option, + pub slow_ppt: Option, + pub tdp: Option, + pub tdp_boost: Option, + pub clock_limits: Option>, + pub memory_clock: Option, +} diff --git a/backend/community_settings_srv/Cargo.lock b/backend/community_settings_srv/Cargo.lock new file mode 100644 index 0000000..1cf7fd3 --- /dev/null +++ b/backend/community_settings_srv/Cargo.lock @@ -0,0 +1,1538 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "actix-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64", + "bitflags 2.4.1", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.41", +] + +[[package]] +name = "actix-router" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "bytestring" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "community_settings_core" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "community_settings_srv" +version = "0.1.0" +dependencies = [ + "actix-web", + "clap", + "community_settings_core", + "log", + "mime", + "ron", + "serde", + "serde_json", + "simplelog", + "tokio", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[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 = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags 2.4.1", + "serde", + "serde_derive", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simplelog" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" +dependencies = [ + "log", + "termcolor", + "time", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "libc", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[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_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[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_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[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_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[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_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zerocopy" +version = "0.7.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "zstd" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/backend/community_settings_srv/Cargo.toml b/backend/community_settings_srv/Cargo.toml new file mode 100644 index 0000000..37cb95c --- /dev/null +++ b/backend/community_settings_srv/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "community_settings_srv" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +community_settings_core = { version = "0.1", path = "../community_settings_core" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +ron = "0.8" + +clap = { version = "4", features = ["derive", "std", "color"], default-features = false } +tokio = { version = "1", features = ["full"] } +actix-web = { version = "4.4" } +mime = { version = "0.3.17" } + +# logging +log = "0.4" +simplelog = "0.12" diff --git a/backend/community_settings_srv/README.md b/backend/community_settings_srv/README.md new file mode 100644 index 0000000..c02999d --- /dev/null +++ b/backend/community_settings_srv/README.md @@ -0,0 +1,7 @@ +# community_settings_srv + +Back-end for browsing community-contributed settings files for games. + +### Technical + +This does not use a database because I'm trying to speedrun the destruction of any semblance of performance for my filesystem. Everything is stored as a file, with symlinks to make it possible to find files multiple ways. diff --git a/backend/community_settings_srv/src/api/get_game.rs b/backend/community_settings_srv/src/api/get_game.rs new file mode 100644 index 0000000..ec19424 --- /dev/null +++ b/backend/community_settings_srv/src/api/get_game.rs @@ -0,0 +1,121 @@ +use actix_web::{get, web, Responder, http::header}; + +use crate::cli::Cli; +use crate::file_util; + +const MAX_RESULTS: usize = 50; + +fn special_settings() -> Vec { + vec![ + community_settings_core::v1::Metadata { + name: "Zeroth the Least".to_owned(), + steam_app_id: 0, + steam_user_id: 76561198116690523, + steam_username: "NGnius".to_owned(), + tags: vec!["0".to_owned(), "gr8".to_owned()], + id: 0.to_string(), + config: community_settings_core::v1::Config { + cpus: vec![ + community_settings_core::v1::Cpu { + online: true, + clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }), + governor: "Michaëlle Jean".to_owned(), + }, + community_settings_core::v1::Cpu { + online: false, + clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }), + governor: "Adrienne Clarkson".to_owned(), + }, + community_settings_core::v1::Cpu { + online: true, + clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }), + governor: "Michael Collins".to_owned(), + } + ], + gpu: community_settings_core::v1::Gpu { + fast_ppt: Some(1), + slow_ppt: Some(1), + tdp: None, + tdp_boost: None, + clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }), + memory_clock: Some(404), + }, + battery: community_settings_core::v1::Battery { + charge_rate: Some(42), + charge_mode: Some("nuclear fusion".to_owned()), + events: vec![ + community_settings_core::v1::BatteryEvent { + trigger: "anything but one on a gun".to_owned(), + charge_rate: Some(42), + charge_mode: Some("neutral".to_owned()), + } + ], + } + } + } + ] +} + +fn get_some_settings_by_app_id(steam_app_id: u32, cli: &'static Cli) -> std::io::Result> { + let app_id_folder = file_util::setting_folder_by_app_id(&cli.folder, steam_app_id); + let mut files: Vec<_> = app_id_folder.read_dir()? + .filter_map(|res| res.ok()) + .filter(|f| f.path().extension().map(|ext| ext == file_util::RON_EXTENSION).unwrap_or(false)) + .filter_map(|f| f.metadata().ok().map(|meta| (f, meta))) + .filter_map(|(f, meta)| meta.created().ok().map(|time| (f, meta, time))) + .collect(); + files.sort_by(|(_, _, a_created), (_, _, b_created)| a_created.cmp(b_created)); + + let mut results = Vec::with_capacity(MAX_RESULTS); + for (_, (f, _, _)) in files.into_iter().enumerate().take_while(|(i, _)| *i < MAX_RESULTS) { + let reader = std::io::BufReader::new(std::fs::File::open(f.path())?); + let setting = match ron::de::from_reader(reader) { + Ok(x) => x, + Err(e) => { + let e_msg = format!("{}", e); + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg)); + } + }; + results.push(setting); + } + Ok(results) +} + +#[get("/api/setting/by_app_id/{id}")] +pub async fn get_setting_by_app_id_handler( + id: web::Path, + accept: web::Header, + cli: web::Data<&'static Cli>, +) -> std::io::Result { + let id: u32 = *id; + println!("Accept: {}", accept.to_string()); + let preferred = accept.preference(); + if super::is_mime_type_ron_capable(&preferred) { + // Send RON + let ron = if id != 0 { + get_some_settings_by_app_id(id, &*cli)? + } else { + special_settings() + }; + // TODO don't dump to string + let result_body = ron::ser::to_string(&ron).unwrap(); + Ok(actix_web::HttpResponse::Ok() + //.insert_header(header::ContentType("application/ron".parse().unwrap())) + .insert_header(header::ContentType(mime::STAR_STAR)) + .body(actix_web::body::BoxBody::new(result_body)) + ) + } else { + // Send JSON (fallback) + let json = if id != 0 { + get_some_settings_by_app_id(id, &*cli)? + } else { + special_settings() + }; + // TODO don't dump to string + let result_body = serde_json::to_string(&json).unwrap(); + Ok(actix_web::HttpResponse::Ok() + .insert_header(header::ContentType::json()) + .body(actix_web::body::BoxBody::new(result_body)) + ) + } +} diff --git a/backend/community_settings_srv/src/api/get_setting.rs b/backend/community_settings_srv/src/api/get_setting.rs new file mode 100644 index 0000000..3121192 --- /dev/null +++ b/backend/community_settings_srv/src/api/get_setting.rs @@ -0,0 +1,121 @@ +use actix_web::{get, web, Responder, http::header}; + +use crate::cli::Cli; +use crate::file_util; + +fn special_settings() -> community_settings_core::v1::Metadata { + community_settings_core::v1::Metadata { + name: "Zeroth the Least".to_owned(), + steam_app_id: 1675200, + steam_user_id: 76561198116690523, + steam_username: "NGnius".to_owned(), + tags: vec!["0".to_owned(), "gr8".to_owned()], + id: 0.to_string(), + config: community_settings_core::v1::Config { + cpus: vec![ + community_settings_core::v1::Cpu { + online: true, + clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }), + governor: "Michaëlle Jean".to_owned(), + }, + community_settings_core::v1::Cpu { + online: false, + clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }), + governor: "Adrienne Clarkson".to_owned(), + }, + community_settings_core::v1::Cpu { + online: true, + clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }), + governor: "Michael Collins".to_owned(), + } + ], + gpu: community_settings_core::v1::Gpu { + fast_ppt: Some(1), + slow_ppt: Some(1), + tdp: None, + tdp_boost: None, + clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }), + memory_clock: Some(404), + }, + battery: community_settings_core::v1::Battery { + charge_rate: Some(42), + charge_mode: Some("nuclear fusion".to_owned()), + events: vec![ + community_settings_core::v1::BatteryEvent { + trigger: "anything but one on a gun".to_owned(), + charge_rate: Some(42), + charge_mode: Some("neutral".to_owned()), + } + ], + } + } + } +} + +#[get("/api/setting/by_id/{id}")] +pub async fn get_setting_handler( + id: web::Path, + accept: web::Header, + cli: web::Data<&'static Cli>, +) -> std::io::Result { + println!("Accept: {}", accept.to_string()); + let id: u128 = match id.parse() { + Ok(x) => x, + Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("invalid setting id `{}` (should be u128): {}", id, e))), + }; + let preferred = accept.preference(); + if super::is_mime_type_ron_capable(&preferred) { + // Send RON + let ron = if id != 0 { + let path = file_util::setting_path_by_id(&cli.folder, id, file_util::RON_EXTENSION); + if !path.exists() { + return Err(std::io::Error::new(std::io::ErrorKind::NotFound, format!("setting id {} does not exist", id))); + } + // TODO? cache this instead of always loading it from file + let reader = std::io::BufReader::new(std::fs::File::open(path)?); + match ron::de::from_reader(reader) { + Ok(x) => x, + Err(e) => { + let e_msg = format!("{}", e); + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg)); + } + } + } else { + special_settings() + }; + // TODO don't dump to string + let result_body = ron::ser::to_string(&ron).unwrap(); + Ok(actix_web::HttpResponse::Ok() + //.insert_header(header::ContentType("application/ron".parse().unwrap())) + .insert_header(header::ContentType(mime::STAR_STAR)) + .body(actix_web::body::BoxBody::new(result_body)) + ) + } else { + // Send JSON (fallback) + let json = if id != 0 { + let path = file_util::setting_path_by_id(&cli.folder, id, file_util::JSON_EXTENSION); + // TODO? cache this instead of always loading it from file + let reader = std::io::BufReader::new(std::fs::File::open(path)?); + match serde_json::from_reader(reader) { + Ok(x) => x, + Err(e) => { + let e_msg = format!("{}", e); + if let Some(io_e) = e.io_error_kind() { + return Err(std::io::Error::new(io_e, e_msg)); + } else { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg)); + } + + } + } + } else { + special_settings() + }; + // TODO don't dump to string + let result_body = serde_json::to_string(&json).unwrap(); + Ok(actix_web::HttpResponse::Ok() + .insert_header(header::ContentType::json()) + .body(actix_web::body::BoxBody::new(result_body)) + ) + } +} diff --git a/backend/community_settings_srv/src/api/mod.rs b/backend/community_settings_srv/src/api/mod.rs new file mode 100644 index 0000000..ca947e3 --- /dev/null +++ b/backend/community_settings_srv/src/api/mod.rs @@ -0,0 +1,12 @@ +mod get_game; +mod get_setting; +mod save_setting; + +pub use get_game::get_setting_by_app_id_handler as get_setting_by_steam_app_id; +pub use get_setting::get_setting_handler as get_setting_by_id; +pub use save_setting::save_setting_handler as save_setting_with_new_id; + +pub(self) fn is_mime_type_ron_capable(mimetype: &mime::Mime) -> bool { + (mimetype.type_() == "application" || mimetype.type_() == mime::STAR) + && (mimetype.subtype() == "ron" || mimetype.subtype() == "cc.replicated.ron" || mimetype.subtype() == "w-ron") +} diff --git a/backend/community_settings_srv/src/api/save_setting.rs b/backend/community_settings_srv/src/api/save_setting.rs new file mode 100644 index 0000000..173364f --- /dev/null +++ b/backend/community_settings_srv/src/api/save_setting.rs @@ -0,0 +1,120 @@ +use actix_web::{post, web, Responder, http::header}; + +use crate::cli::Cli; +use crate::file_util; + +const PAYLOAD_LIMIT: usize = 10_000_000; // 10 Megabyte + +#[post("/api/setting")] +pub async fn save_setting_handler( + data: web::Payload, + content_type: web::Header, + cli: web::Data<&'static Cli>, +) -> std::io::Result { + //println!("Content-Type: {}", content_type.to_string()); + let bytes = match data.to_bytes_limited(PAYLOAD_LIMIT).await { + Ok(Ok(x)) => x, + Ok(Err(e)) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, format!("wut: {}", e))), + Err(_e) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "too many bytes in payload")), + }; + let next_id = file_util::next_setting_id(&cli.folder); + let parsed_data: community_settings_core::v1::Metadata = if super::is_mime_type_ron_capable(&content_type) { + // Parse as RON + match ron::de::from_reader(bytes.as_ref()) { + Ok(x) => x, + Err(e) => { + let e_msg = format!("{}", e); + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg)); + } + } + } else { + // Parse JSON (fallback) + match serde_json::from_reader(bytes.as_ref()) { + Ok(x) => x, + Err(e) => { + let e_msg = format!("{}", e); + if let Some(io_e) = e.io_error_kind() { + return Err(std::io::Error::new(io_e, e_msg)); + } else { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg)); + } + + } + } + }; + // TODO validate user and app id + // Reject blocked users and apps + let path_ron = file_util::setting_path_by_id(&cli.folder, next_id, file_util::RON_EXTENSION); + let writer = std::io::BufWriter::new(std::fs::File::create(&path_ron)?); + if let Err(e) = ron::ser::to_writer(writer, &parsed_data) { + let e_msg = format!("{}", e); + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg)); + } + + let path_json = file_util::setting_path_by_id(&cli.folder, next_id, file_util::JSON_EXTENSION); + let writer = std::io::BufWriter::new(std::fs::File::create(&path_json)?); + if let Err(e) = serde_json::to_writer(writer, &parsed_data) { + let e_msg = format!("{}", e); + if let Some(io_e) = e.io_error_kind() { + return Err(std::io::Error::new(io_e, e_msg)); + } else { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg)); + } + } + + // create symlinks for other ways of looking up these settings files + let filename_ron = file_util::filename(next_id, file_util::RON_EXTENSION); + let filename_json = file_util::filename(next_id, file_util::JSON_EXTENSION); + + // create symlinks to app id folder + let app_id_folder = file_util::setting_folder_by_app_id(&cli.folder, parsed_data.steam_app_id); + if !app_id_folder.exists() { + std::fs::create_dir(&app_id_folder)?; + } + #[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained + { + std::os::windows::fs::symlink_file(&path_ron, app_id_folder.join(&filename_ron))?; + std::os::windows::fs::symlink_file(&path_json, app_id_folder.join(&filename_json))?; + } + #[cfg(target_family = "unix")] + { + std::os::unix::fs::symlink(&path_ron, app_id_folder.join(&filename_ron))?; + std::os::unix::fs::symlink(&path_json, app_id_folder.join(&filename_json))?; + } + + // create symlinks for user id folder + let user_id_folder = file_util::setting_folder_by_user_id(&cli.folder, parsed_data.steam_user_id); + if !user_id_folder.exists() { + std::fs::create_dir(&user_id_folder)?; + } + #[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained + { + std::os::windows::fs::symlink_file(&path_ron, user_id_folder.join(&filename_ron))?; + std::os::windows::fs::symlink_file(&path_json, user_id_folder.join(&filename_json))?; + } + #[cfg(target_family = "unix")] + { + std::os::unix::fs::symlink(&path_ron, user_id_folder.join(&filename_ron))?; + std::os::unix::fs::symlink(&path_json, user_id_folder.join(&filename_json))?; + } + + // create symlinks for each tag + for tag in parsed_data.tags.iter() { + let tag_folder = file_util::setting_folder_by_tag(&cli.folder, tag); + if !tag_folder.exists() { + std::fs::create_dir(&tag_folder)?; + } + #[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained + { + std::os::windows::fs::symlink_file(&path_ron, tag_folder.join(&filename_ron))?; + std::os::windows::fs::symlink_file(&path_json, tag_folder.join(&filename_json))?; + } + #[cfg(target_family = "unix")] + { + std::os::unix::fs::symlink(&path_ron, tag_folder.join(&filename_ron))?; + std::os::unix::fs::symlink(&path_json, tag_folder.join(&filename_json))?; + } + } + + Ok(actix_web::HttpResponse::NoContent()) +} diff --git a/backend/community_settings_srv/src/cli.rs b/backend/community_settings_srv/src/cli.rs new file mode 100644 index 0000000..b787355 --- /dev/null +++ b/backend/community_settings_srv/src/cli.rs @@ -0,0 +1,23 @@ +use clap::Parser; + +#[derive(Parser, Debug, Clone)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + /// Root folder to store contributed setting files + #[arg(short, long, default_value = "./community_settings")] + pub folder: std::path::PathBuf, + + /// Server port + #[arg(short, long, default_value_t = 8080)] + pub port: u16, + + /// Log file location + #[arg(short, long, default_value = "/tmp/powertools_community_settings_srv.log")] + pub log: std::path::PathBuf, +} + +impl Cli { + pub fn get() -> Self { + Self::parse() + } +} diff --git a/backend/community_settings_srv/src/file_util.rs b/backend/community_settings_srv/src/file_util.rs new file mode 100644 index 0000000..670c608 --- /dev/null +++ b/backend/community_settings_srv/src/file_util.rs @@ -0,0 +1,86 @@ +use std::path::{Path, PathBuf}; +use std::sync::Mutex; + +pub const RON_EXTENSION: &'static str = "ron"; +pub const JSON_EXTENSION: &'static str = "json"; + +const SETTING_FOLDER: &'static str = "settings"; +const ID_FOLDER: &'static str = "by_id"; +const APP_ID_FOLDER: &'static str = "by_app_id"; +const USER_ID_FOLDER: &'static str = "by_user_id"; +const TAG_FOLDER: &'static str = "by_tag"; + +static LAST_SETTING_ID: Mutex = Mutex::new(0); + +pub fn build_folder_layout(root: impl AsRef) -> std::io::Result<()> { + std::fs::create_dir_all( + root.as_ref() + .join(SETTING_FOLDER) + .join(ID_FOLDER) + )?; + std::fs::create_dir_all( + root.as_ref() + .join(SETTING_FOLDER) + .join(APP_ID_FOLDER) + )?; + std::fs::create_dir_all( + root.as_ref() + .join(SETTING_FOLDER) + .join(USER_ID_FOLDER) + )?; + std::fs::create_dir_all( + root.as_ref() + .join(SETTING_FOLDER) + .join(TAG_FOLDER) + )?; + Ok(()) +} + +pub fn filename(id: u128, ext: &str) -> String { + format!("{}.{}", id, ext) +} + +pub fn setting_path_by_id(root: impl AsRef, id: u128, ext: &str) -> PathBuf { + root.as_ref() + .join(SETTING_FOLDER) + .join(ID_FOLDER) + .join(filename(id, ext)) +} + +pub fn setting_folder_by_app_id(root: impl AsRef, steam_app_id: u32) -> PathBuf { + root.as_ref() + .join(SETTING_FOLDER) + .join(APP_ID_FOLDER) + .join(steam_app_id.to_string()) +} + +pub fn setting_folder_by_user_id(root: impl AsRef, steam_user_id: u64) -> PathBuf { + root.as_ref() + .join(SETTING_FOLDER) + .join(USER_ID_FOLDER) + .join(steam_user_id.to_string()) +} + +pub fn setting_folder_by_tag(root: impl AsRef, tag: &str) -> PathBuf { + root.as_ref() + .join(SETTING_FOLDER) + .join(TAG_FOLDER) + .join(tag) +} + +pub fn next_setting_id(root: impl AsRef) -> u128 { + let mut lock = LAST_SETTING_ID.lock().unwrap(); + let mut last_id = *lock; + if last_id == 0 { + // needs init + let mut path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION); + while path.exists() { + last_id += 1; + path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION); + } + *lock = last_id; + println!("setting id initialized to {}", last_id); + } + *lock += 1; + *lock +} diff --git a/backend/community_settings_srv/src/main.rs b/backend/community_settings_srv/src/main.rs new file mode 100644 index 0000000..2753bfa --- /dev/null +++ b/backend/community_settings_srv/src/main.rs @@ -0,0 +1,45 @@ +mod api; +mod cli; +mod file_util; + +use actix_web::{web, App, HttpServer}; + +#[tokio::main] +async fn main() -> std::io::Result<()> { + let args = cli::Cli::get(); + println!("cli: {:?}", args); + + simplelog::WriteLogger::init( + #[cfg(debug_assertions)] + { + log::LevelFilter::Debug + }, + #[cfg(not(debug_assertions))] + { + log::LevelFilter::Info + }, + Default::default(), + std::fs::File::create(&args.log).expect("Failed to create log file"), + //std::fs::File::create("/home/deck/powertools-rs.log").unwrap(), + ) + .unwrap(); + log::debug!("Logging to: {}", args.log.display()); + + // setup + log::debug!("Building folder layout (if not exists) at: {}", &args.folder.display()); + file_util::build_folder_layout(&args.folder)?; + + let leaked_args: &'static cli::Cli = Box::leak::<'static>(Box::new(args)); + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(leaked_args)) + //.app_data(web::Data::new(IndexPage::load("dist/index.html").unwrap())) + //.app_data(basic::Config::default().realm("Restricted area")) + .service(api::get_setting_by_id) + .service(api::get_setting_by_steam_app_id) + .service(api::save_setting_with_new_id) + }) + .bind(("0.0.0.0", leaked_args.port))? + .run() + .await +} diff --git a/backend/limits_core/Cargo.lock b/backend/limits_core/Cargo.lock index a3d5955..169eb4d 100644 --- a/backend/limits_core/Cargo.lock +++ b/backend/limits_core/Cargo.lock @@ -2,18 +2,11 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "itoa" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" - [[package]] name = "limits_core" -version = "2.0.1" +version = "3.0.0" dependencies = [ "serde", - "serde_json", ] [[package]] @@ -34,12 +27,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "ryu" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" - [[package]] name = "serde" version = "1.0.166" @@ -60,17 +47,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_json" -version = "1.0.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" -dependencies = [ - "itoa", - "ryu", - "serde", -] - [[package]] name = "syn" version = "2.0.23" diff --git a/backend/limits_core/Cargo.toml b/backend/limits_core/Cargo.toml index 7d2f721..bc36c76 100644 --- a/backend/limits_core/Cargo.toml +++ b/backend/limits_core/Cargo.toml @@ -1,10 +1,9 @@ [package] name = "limits_core" -version = "2.0.1" +version = "3.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" diff --git a/backend/limits_core/src/json/base.rs b/backend/limits_core/src/json/base.rs index cbb77fc..fefab76 100644 --- a/backend/limits_core/src/json/base.rs +++ b/backend/limits_core/src/json/base.rs @@ -1,5 +1,5 @@ -use std::default::Default; use serde::{Deserialize, Serialize}; +use std::default::Default; /// Base JSON limits information #[derive(Serialize, Deserialize, Debug, Clone)] @@ -20,7 +20,7 @@ impl Default for Base { name: "Steam Deck Custom".to_owned(), conditions: super::Conditions { dmi: None, - cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()), + cpuinfo: Some("model name\t: AMD Custom APU (0405)|(0932)\n".to_owned()), os: None, command: None, file_exists: Some("./pt_oc.json".into()), @@ -35,7 +35,7 @@ impl Default for Base { name: "Steam Deck".to_owned(), conditions: super::Conditions { dmi: None, - cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()), + cpuinfo: Some("model name\t: AMD Custom APU (0405)|(0932)\n".to_owned()), os: None, command: None, file_exists: None, @@ -131,7 +131,7 @@ impl Default for Base { name: "AMD R7 6800U".to_owned(), conditions: super::Conditions { dmi: None, - cpuinfo: Some("model name\t+: AMD Ryzen 7 6800U\n".to_owned()), + cpuinfo: Some("model name\t+: AMD Ryzen 7 6800U( with Radeon Graphics)?\n".to_owned()), os: None, command: None, file_exists: None, @@ -174,8 +174,8 @@ impl Default for Base { super::DeveloperMessage { id: 1, title: "Welcome".to_owned(), - body: "Thanks for installing PowerTools! For more information, please check the wiki. For bugs and requests, please create an issue on GitHub.".to_owned(), - url: Some("https://github.com/NGnius/PowerTools/wiki".to_owned()), + body: "Thanks for installing PowerTools! For more information, please check the wiki. For bugs and requests, please create an issue.".to_owned(), + url: Some("https://git.ngni.us/NG-SD-Plugins/PowerTools/wiki".to_owned()), } ], refresh: Some("http://limits.ngni.us:45000/powertools/v1".to_owned()) diff --git a/backend/limits_core/src/json_v2/base.rs b/backend/limits_core/src/json_v2/base.rs new file mode 100644 index 0000000..b47f450 --- /dev/null +++ b/backend/limits_core/src/json_v2/base.rs @@ -0,0 +1,341 @@ +use serde::{Deserialize, Serialize}; +use std::default::Default; + +/// Base JSON limits information +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Base { + /// System-specific configurations + pub configs: Vec, + /// Server messages + pub messages: Vec, + /// Base URL for the config store + pub store: String, + /// URL from which to grab the next update + pub refresh: Option, +} + +impl Default for Base { + fn default() -> Self { + Base { + configs: vec![ + super::Config { + name: "Devs mode best mode".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: None, + os: None, + file_exists: Some("/etc/powertools_dev_mode".into()), + }, + limits: super::Limits { + cpu: super::Limit { + provider: super::CpuLimitType::DevMode, + limits: super::GenericCpusLimit::default_for(super::CpuLimitType::DevMode), + }, + gpu: super::Limit { + provider: super::GpuLimitType::DevMode, + limits: super::GenericGpuLimit::default_for(super::GpuLimitType::DevMode), + }, + battery: super::Limit { + provider: super::BatteryLimitType::DevMode, + limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::DevMode), + }, + } + }, + super::Config { + name: "Steam Deck".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()), + os: None, + file_exists: None, + }, + limits: super::Limits { + cpu: super::Limit { + provider: super::CpuLimitType::SteamDeck, + limits: super::GenericCpusLimit::default_for(super::CpuLimitType::SteamDeck), + }, + gpu: super::Limit { + provider: super::GpuLimitType::SteamDeck, + limits: super::GenericGpuLimit::default_for(super::GpuLimitType::SteamDeck), + }, + battery: super::Limit { + provider: super::BatteryLimitType::SteamDeck, + limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::SteamDeck), + }, + } + }, + super::Config { + name: "Steam Deck OLED".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: Some("model name\t: AMD Custom APU 0932\n".to_owned()), + os: None, + file_exists: None, + }, + limits: super::Limits { + cpu: super::Limit { + provider: super::CpuLimitType::SteamDeckOLED, + limits: super::GenericCpusLimit::default_for(super::CpuLimitType::SteamDeckOLED), + }, + gpu: super::Limit { + provider: super::GpuLimitType::SteamDeckOLED, + limits: super::GenericGpuLimit::default_for(super::GpuLimitType::SteamDeckOLED), + }, + battery: super::Limit { + provider: super::BatteryLimitType::SteamDeckOLED, + limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::SteamDeckOLED), + }, + } + }, + super::Config { + name: "AMD R3 2300U".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: Some("model name\t+: AMD Ryzen 3 2300U\n".to_owned()), + os: None, + file_exists: None, + }, + limits: super::Limits { + cpu: super::CpuLimit { + provider: super::CpuLimitType::GenericAMD, + limits: super::GenericCpusLimit { + cpus: vec![ + super::GenericCpuLimit { + clock_min: Some(super::RangeLimit { min: Some(1000), max: Some(3700) }), + clock_max: Some(super::RangeLimit { min: Some(1000), max: Some(3700) }), + clock_step: Some(100), + skip_resume_reclock: false, + ..Default::default() + }; 4], + global_governors: true, + ..Default::default() + } + }, + gpu: super::GpuLimit { + provider: super::GpuLimitType::GenericAMD, + limits: super::GenericGpuLimit { + fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(25_000) }), + slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(25_000) }), + ppt_step: Some(1_000), + ppt_divisor: Some(1_000), + clock_min: Some(super::RangeLimit { min: Some(400), max: Some(1100) }), + clock_max: Some(super::RangeLimit { min: Some(400), max: Some(1100) }), + clock_step: Some(100), + ..Default::default() + } + }, + battery: super::Limit { + provider: super::BatteryLimitType::Generic, + limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic), + } + }, + }, + super::Config { + name: "AMD R5 5560U".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: Some("model name\t+: AMD Ryzen 5 5560U\n".to_owned()), + os: None, + file_exists: None, + }, + limits: super::Limits { + cpu: super::CpuLimit { + provider: super::CpuLimitType::GenericAMD, + limits: super::GenericCpusLimit { + cpus: vec![ + super::GenericCpuLimit { + clock_min: Some(super::RangeLimit { min: Some(1000), max: Some(4000) }), + clock_max: Some(super::RangeLimit { min: Some(1000), max: Some(4000) }), + clock_step: Some(100), + skip_resume_reclock: false, + ..Default::default() + }; 12], // 6 cores with SMTx2 + global_governors: true, + ..Default::default() + } + }, + gpu: super::GpuLimit { + provider: super::GpuLimitType::GenericAMD, + limits: super::GenericGpuLimit { + fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(25_000) }), + slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(25_000) }), + ppt_step: Some(1_000), + ppt_divisor: Some(1_000), + clock_min: Some(super::RangeLimit { min: Some(400), max: Some(1600) }), + clock_max: Some(super::RangeLimit { min: Some(400), max: Some(1600) }), + clock_step: Some(100), + ..Default::default() + } + }, + battery: super::Limit { + provider: super::BatteryLimitType::Generic, + limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic), + } + } + }, + super::Config { + name: "AMD R7 5825U".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: Some("model name\t+: AMD Ryzen 7 5825U\n".to_owned()), + os: None, + file_exists: None, + }, + limits: super::Limits { + cpu: super::CpuLimit { + provider: super::CpuLimitType::GenericAMD, + limits: super::GenericCpusLimit { + cpus: vec![ + super::GenericCpuLimit { + clock_min: Some(super::RangeLimit { min: Some(1000), max: Some(4500) }), + clock_max: Some(super::RangeLimit { min: Some(1000), max: Some(4500) }), + clock_step: Some(100), + skip_resume_reclock: false, + ..Default::default() + }; 16], // 8 cores with SMTx2 + global_governors: true, + ..Default::default() + } + }, + gpu: super::GpuLimit { + provider: super::GpuLimitType::GenericAMD, + limits: super::GenericGpuLimit { + fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(28_000) }), + slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(28_000) }), + ppt_step: Some(1_000), + ppt_divisor: Some(1_000), + clock_min: Some(super::RangeLimit { min: Some(400), max: Some(2200) }), + clock_max: Some(super::RangeLimit { min: Some(400), max: Some(2200) }), + clock_step: Some(100), + ..Default::default() + } + }, + battery: super::Limit { + provider: super::BatteryLimitType::Generic, + limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic), + } + } + }, + super::Config { + name: "AMD R7 6800U".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: Some("model name\t+: AMD Ryzen 7 6800U( with Radeon Graphics)?\n".to_owned()), + os: None, + file_exists: None, + }, + limits: super::Limits { + cpu: super::CpuLimit { + provider: super::CpuLimitType::GenericAMD, + limits: super::GenericCpusLimit { + cpus: vec![ + super::GenericCpuLimit { + clock_min: Some(super::RangeLimit { min: Some(1000), max: Some(4700) }), + clock_max: Some(super::RangeLimit { min: Some(1000), max: Some(4700) }), + clock_step: Some(100), + skip_resume_reclock: false, + ..Default::default() + }; 16], // 8 cores with SMTx2 + global_governors: true, + ..Default::default() + } + }, + gpu: super::GpuLimit { + provider: super::GpuLimitType::GenericAMD, + limits: super::GenericGpuLimit { + fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(28_000) }), + slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(28_000) }), + ppt_step: Some(1_000), + ppt_divisor: Some(1_000), + clock_min: Some(super::RangeLimit { min: Some(400), max: Some(2200) }), + clock_max: Some(super::RangeLimit { min: Some(400), max: Some(2200) }), + clock_step: Some(100), + ..Default::default() + } + }, + battery: super::Limit { + provider: super::BatteryLimitType::Generic, + limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic), + } + } + }, + super::Config { + name: "AMD R7 7840U".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: Some("model name\\s+: AMD Ryzen 7 7840U( w\\/ Radeon 780M Graphics)?\n".to_owned()), + os: None, + file_exists: None, + }, + limits: super::Limits { + cpu: super::CpuLimit { + provider: super::CpuLimitType::GenericAMD, + limits: super::GenericCpusLimit { + cpus: vec![ + super::GenericCpuLimit { + clock_min: Some(super::RangeLimit { min: Some(400), max: Some(5100) }), + clock_max: Some(super::RangeLimit { min: Some(400), max: Some(5100) }), + clock_step: Some(100), + skip_resume_reclock: false, + ..Default::default() + }; 16], // 8 cores with SMTx2 + global_governors: true, + ..Default::default() + } + }, + gpu: super::GpuLimit { + provider: super::GpuLimitType::GenericAMD, + limits: super::GenericGpuLimit { + fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(53_000) }), + slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(43_000) }), + ppt_step: Some(1_000), + ppt_divisor: Some(1_000), + clock_min: None, + clock_max: None, + clock_step: None, + ..Default::default() + } + }, + battery: super::Limit { + provider: super::BatteryLimitType::Generic, + limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic), + } + } + }, + super::Config { + name: "Fallback".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: None, + os: None, + file_exists: None, + }, + limits: super::Limits { + cpu: super::Limit { + provider: super::CpuLimitType::Unknown, + limits: super::GenericCpusLimit::default_for(super::CpuLimitType::Unknown), + }, + gpu: super::Limit { + provider: super::GpuLimitType::Unknown, + limits: super::GenericGpuLimit::default_for(super::GpuLimitType::Unknown), + }, + battery: super::Limit { + provider: super::BatteryLimitType::Unknown, + limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Unknown), + } + } + } + ], + messages: vec![ + super::DeveloperMessage { + id: 1, + title: "Welcome".to_owned(), + body: "Thanks for installing PowerTools! For more information, please check the wiki. For bugs and requests, please create an issue.".to_owned(), + url: Some("https://git.ngni.us/NG-SD-Plugins/PowerTools/wiki".to_owned()), + } + ], + store: "https://powertools.ngni.us".to_owned(), + refresh: Some("http://limits.ngni.us:45000/powertools/v2".to_owned()) + } + } +} diff --git a/backend/limits_core/src/json_v2/battery_limit.rs b/backend/limits_core/src/json_v2/battery_limit.rs new file mode 100644 index 0000000..3506d4b --- /dev/null +++ b/backend/limits_core/src/json_v2/battery_limit.rs @@ -0,0 +1,99 @@ +use serde::{Deserialize, Serialize}; +use super::RangeLimit; + +#[derive(Serialize, Deserialize, Debug, Clone)] +//#[serde(tag = "target")] +pub enum BatteryLimitType { + #[serde(rename = "GabeBoy", alias = "SteamDeck")] + SteamDeck, + #[serde(rename = "GabeBoySP", alias = "SteamDeckOLED")] + SteamDeckOLED, + Generic, + Unknown, + DevMode, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct GenericBatteryLimit { + pub charge_rate: Option>, + pub charge_modes: Vec, + pub charge_limit: Option>, // battery charge % + pub extra_readouts: bool, + pub extras: super::LimitExtras, +} + +impl GenericBatteryLimit { + pub fn default_for(t: BatteryLimitType) -> Self { + match t { + BatteryLimitType::SteamDeck | BatteryLimitType::SteamDeckOLED => Self::default_steam_deck(), + BatteryLimitType::DevMode => Self::default_dev_mode(), + _t => Self::default(), + } + } + + fn default_steam_deck() -> Self { + Self { + charge_rate: Some(RangeLimit { + min: Some(250), + max: Some(2500), + }), + charge_modes: vec![ + "normal".to_owned(), + "discharge".to_owned(), + "idle".to_owned(), + ], + charge_limit: Some(RangeLimit { + min: Some(10.0), + max: Some(90.0), + }), + extra_readouts: false, + extras: Default::default(), + } + } + + fn default_dev_mode() -> Self { + Self { + charge_rate: Some(RangeLimit { + min: Some(0), + max: Some(1_000), + }), + charge_modes: vec![ + "normal".to_owned(), + "discharge".to_owned(), + "idle".to_owned(), + ], + charge_limit: Some(RangeLimit { + min: Some(1.0), + max: Some(99.0), + }), + extra_readouts: true, + extras: super::LimitExtras { + experiments: true, + quirks: vec!["".to_owned()].into_iter().collect(), + }, + } + } + + pub fn apply_override(&mut self, limit_override: Self) { + if let Some(range) = limit_override.charge_rate { + if range.min.is_none() && range.max.is_none() { + self.charge_rate = None; + } else { + self.charge_rate = Some(range); + } + } + if self.charge_modes.len() != limit_override.charge_modes.len() && !limit_override.charge_modes.is_empty() { + // assume limit_override.cpus wants to override even the cpu count + self.charge_modes = limit_override.charge_modes; + } + if let Some(range) = limit_override.charge_limit { + if range.min.is_none() && range.max.is_none() { + self.charge_limit = None; + } else { + self.charge_limit = Some(range); + } + } + self.extra_readouts = limit_override.extra_readouts; + self.extras = limit_override.extras; + } +} diff --git a/backend/limits_core/src/json_v2/conditions.rs b/backend/limits_core/src/json_v2/conditions.rs new file mode 100644 index 0000000..11607b9 --- /dev/null +++ b/backend/limits_core/src/json_v2/conditions.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +/// Conditions under which a config applies (ANDed together) +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Conditions { + /// Regex pattern for dmidecode output + pub dmi: Option, + /// Regex pattern for /proc/cpuinfo reading + pub cpuinfo: Option, + /// Regex pattern for /etc/os-release reading + pub os: Option, + /// Check if file exists + pub file_exists: Option, +} + +impl Conditions { + pub fn is_empty(&self) -> bool { + self.dmi.is_none() + && self.cpuinfo.is_none() + && self.os.is_none() + && self.file_exists.is_none() + } +} diff --git a/backend/limits_core/src/json_v2/config.rs b/backend/limits_core/src/json_v2/config.rs new file mode 100644 index 0000000..cf1e8e1 --- /dev/null +++ b/backend/limits_core/src/json_v2/config.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Config { + pub name: String, + pub conditions: super::Conditions, + pub limits: super::Limits, +} diff --git a/backend/limits_core/src/json_v2/cpu_limit.rs b/backend/limits_core/src/json_v2/cpu_limit.rs new file mode 100644 index 0000000..4433a66 --- /dev/null +++ b/backend/limits_core/src/json_v2/cpu_limit.rs @@ -0,0 +1,196 @@ +use serde::{Deserialize, Serialize}; + +use super::RangeLimit; + +#[derive(Serialize, Deserialize, Debug, Clone)] +//#[serde(tag = "target")] +pub enum CpuLimitType { + #[serde(rename = "GabeBoy", alias = "SteamDeck")] + SteamDeck, + #[serde(rename = "GabeBoySP", alias = "SteamDeckOLED")] + SteamDeckOLED, + Generic, + GenericAMD, + Unknown, + DevMode, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct GenericCpusLimit { + pub cpus: Vec, + pub global_governors: bool, + pub extras: super::LimitExtras, +} + +impl GenericCpusLimit { + pub fn default_for(t: CpuLimitType) -> Self { + match t { + CpuLimitType::SteamDeck | CpuLimitType::SteamDeckOLED => { + Self { + cpus: [(); 8].iter().enumerate().map(|(i, _)| GenericCpuLimit::default_for(&t, i)).collect(), + global_governors: true, + extras: Default::default(), + } + }, + CpuLimitType::DevMode => { + Self { + cpus: [(); 11].iter().enumerate().map(|(i, _)| GenericCpuLimit::default_for(&t, i)).collect(), + global_governors: true, + extras: super::LimitExtras { + experiments: true, + quirks: vec!["".to_owned()].into_iter().collect(), + }, + } + }, + t => { + let cpu_count = Self::cpu_count().unwrap_or(8); + let mut cpus = Vec::with_capacity(cpu_count); + for i in 0..cpu_count { + cpus.push(GenericCpuLimit::default_for(&t, i)); + } + Self { + cpus, + global_governors: true, + extras: Default::default(), + } + } + } + } + + fn cpu_count() -> Option { + let mut data: String = std::fs::read_to_string("/sys/devices/system/cpu/present") + .unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */); + if let Some(dash_index) = data.find('-') { + let data = data.split_off(dash_index + 1); + if let Ok(max_cpu) = data.parse::() { + return Some(max_cpu + 1); + } + } + None + } + + pub fn apply_override(&mut self, limit_override: Self) { + if self.cpus.len() != limit_override.cpus.len() && !limit_override.cpus.is_empty() { + // assume limit_override.cpus wants to override even the cpu count + self.cpus = limit_override.cpus; + } else { + self.cpus.iter_mut() + .zip(limit_override.cpus.into_iter()) + .for_each(|(cpu, limit_override)| cpu.apply_override(limit_override)); + } + self.global_governors = limit_override.global_governors; + self.extras = limit_override.extras; + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct GenericCpuLimit { + pub clock_min: Option>, + pub clock_max: Option>, + pub clock_step: Option, + pub tdp: Option>, + pub tdp_boost: Option>, + pub tdp_divisor: Option, + pub tdp_step: Option, + pub skip_resume_reclock: bool, + pub extras: super::LimitExtras, +} + +impl GenericCpuLimit { + pub fn default_for(t: &CpuLimitType, _index: usize) -> Self { + match t { + CpuLimitType::SteamDeck => Self::default_steam_deck(), + CpuLimitType::SteamDeckOLED => Self::default_steam_deck_oled(), + CpuLimitType::DevMode => Self { + clock_min: Some(RangeLimit { min: Some(100), max: Some(5000) }), + clock_max: Some(RangeLimit { min: Some(100), max: Some(4800) }), + clock_step: Some(100), + tdp: Some(RangeLimit { min: Some(1_000_000), max: Some(100_000_000) }), + tdp_boost: Some(RangeLimit { min: Some(1_000_000), max: Some(110_000_000) }), + tdp_divisor: Some(1_000_000), + tdp_step: Some(1), + skip_resume_reclock: false, + extras: Default::default(), + }, + _ => Self { + clock_min: None, + clock_max: None, + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: Default::default(), + }, + } + } + + fn default_steam_deck() -> Self { + Self { + clock_min: Some(RangeLimit { + min: Some(1400), + max: Some(3500), + }), + clock_max: Some(RangeLimit { + min: Some(400), + max: Some(3500), + }), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: Default::default(), + } + } + + fn default_steam_deck_oled() -> Self { + Self { + clock_min: Some(RangeLimit { + min: Some(1400), + max: Some(3500), + }), + clock_max: Some(RangeLimit { + min: Some(400), + max: Some(3500), + }), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: super::LimitExtras { + experiments: false, + quirks: vec![ + "clock-autodetect".to_owned(), + ].into_iter().collect() + }, + } + } + + pub fn apply_override(&mut self, limit_override: Self) { + if let Some(range) = limit_override.clock_min { + if range.min.is_none() && range.max.is_none() { + self.clock_min = None; + } else { + self.clock_min = Some(range); + } + } + if let Some(range) = limit_override.clock_max { + if range.min.is_none() && range.max.is_none() { + self.clock_max = None; + } else { + self.clock_max = Some(range); + } + } + if let Some(val) = limit_override.clock_step { + self.clock_step = Some(val); + } + self.clock_step = limit_override.clock_step; + self.skip_resume_reclock = limit_override.skip_resume_reclock; + self.extras = limit_override.extras; + } +} diff --git a/backend/limits_core/src/json_v2/devel_message.rs b/backend/limits_core/src/json_v2/devel_message.rs new file mode 100644 index 0000000..904968d --- /dev/null +++ b/backend/limits_core/src/json_v2/devel_message.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +/// Message from the developers +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DeveloperMessage { + /// Message identifier + pub id: u64, + /// Message title + pub title: String, + /// Message content + pub body: String, + /// Link for further information + pub url: Option, +} diff --git a/backend/limits_core/src/json_v2/gpu_limit.rs b/backend/limits_core/src/json_v2/gpu_limit.rs new file mode 100644 index 0000000..d29ae6e --- /dev/null +++ b/backend/limits_core/src/json_v2/gpu_limit.rs @@ -0,0 +1,204 @@ +use serde::{Deserialize, Serialize}; +use super::RangeLimit; + +#[derive(Serialize, Deserialize, Debug, Clone)] +//#[serde(tag = "target")] +pub enum GpuLimitType { + #[serde(rename = "GabeBoy", alias = "SteamDeck")] + SteamDeck, + #[serde(rename = "GabeBoySP", alias = "SteamDeckOLED")] + SteamDeckOLED, + Generic, + GenericAMD, + Unknown, + DevMode, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct GenericGpuLimit { + pub fast_ppt: Option>, + pub fast_ppt_default: Option, + pub slow_ppt: Option>, + pub slow_ppt_default: Option, + pub ppt_divisor: Option, + pub ppt_step: Option, + pub tdp: Option>, + pub tdp_boost: Option>, + pub tdp_divisor: Option, + pub tdp_step: Option, + pub clock_min: Option>, + pub clock_max: Option>, + pub clock_step: Option, + pub memory_clock: Option>, + pub memory_clock_step: Option, + pub skip_resume_reclock: bool, + pub extras: super::LimitExtras, +} + +impl GenericGpuLimit { + pub fn default_for(t: GpuLimitType) -> Self { + match t { + GpuLimitType::SteamDeck => Self::default_steam_deck(), + GpuLimitType::SteamDeckOLED => Self::default_steam_deck_oled(), + GpuLimitType::DevMode => Self::default_dev_mode(), + _t => Self::default(), + } + } + + fn default_steam_deck() -> Self { + Self { + fast_ppt: Some(RangeLimit { + min: Some(1000000), + max: Some(30_000_000), + }), + fast_ppt_default: Some(15_000_000), + slow_ppt: Some(RangeLimit { + min: Some(1000000), + max: Some(29_000_000), + }), + slow_ppt_default: Some(15_000_000), + ppt_divisor: Some(1_000_000), + ppt_step: Some(1), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + clock_min: Some(RangeLimit { + min: Some(400), + max: Some(1600), + }), + clock_max: Some(RangeLimit { + min: Some(400), + max: Some(1600), + }), + clock_step: Some(100), + // LCD version is a bit broken on sysfs, but it's ok + memory_clock: Some(RangeLimit { + min: Some(400), + max: Some(800), + }), + memory_clock_step: Some(400), + skip_resume_reclock: false, + extras: super::LimitExtras { + experiments: false, + quirks: vec![ + "pp_dpm_fclk-reversed".to_owned(), + "pp_dpm_fclk-not-updated-on-LCD".to_owned(), + //"pp_dpm_fclk-static".to_owned(), + ].into_iter().collect(), + } + } + } + + fn default_steam_deck_oled() -> Self { + let mut sd = Self::default_steam_deck(); + sd.memory_clock_step = Some(200); + sd.extras.quirks.insert("clock-autodetect".to_owned()); + sd + } + + fn default_dev_mode() -> Self { + Self { + fast_ppt: Some(RangeLimit { + min: Some(3_000_000), + max: Some(11_000_000), + }), + fast_ppt_default: Some(10_000_000), + slow_ppt: Some(RangeLimit { + min: Some(7_000_000), + max: Some(11_000_000), + }), + slow_ppt_default: Some(10_000_000), + ppt_divisor: Some(1_000_000), + ppt_step: Some(1), + tdp: Some(RangeLimit { min: Some(1_000_000), max: Some(100_000_000) }), + tdp_boost: Some(RangeLimit { min: Some(1_000_000), max: Some(110_000_000) }), + tdp_divisor: Some(1_000_000), + tdp_step: Some(1), + clock_min: Some(RangeLimit { + min: Some(100), + max: Some(1000), + }), + clock_max: Some(RangeLimit { + min: Some(100), + max: Some(1100), + }), + clock_step: Some(100), + memory_clock: Some(RangeLimit { + min: Some(100), + max: Some(1100), + }), + memory_clock_step: Some(100), + skip_resume_reclock: false, + extras: super::LimitExtras { + experiments: true, + quirks: vec!["dev".to_owned()].into_iter().collect(), + }, + } + } + + pub fn apply_override(&mut self, limit_override: Self) { + if let Some(range) = limit_override.fast_ppt { + if range.min.is_none() && range.max.is_none() { + self.fast_ppt = None; + } else { + self.fast_ppt = Some(range); + } + } + if let Some(def) = limit_override.fast_ppt_default { + self.fast_ppt_default = Some(def); + } + if let Some(range) = limit_override.slow_ppt { + if range.min.is_none() && range.max.is_none() { + self.slow_ppt = None; + } else { + self.slow_ppt = Some(range); + } + } + if let Some(def) = limit_override.slow_ppt_default { + self.slow_ppt_default = Some(def); + } + if let Some(val) = limit_override.ppt_divisor { + self.ppt_divisor = Some(val); + } + if let Some(val) = limit_override.ppt_step { + self.ppt_step = Some(val); + } + if let Some(range) = limit_override.tdp { + if range.min.is_none() && range.max.is_none() { + self.tdp = None; + } else { + self.tdp = Some(range); + } + } + if let Some(range) = limit_override.tdp_boost { + if range.min.is_none() && range.max.is_none() { + self.tdp_boost = None; + } else { + self.tdp_boost = Some(range); + } + } + if let Some(val) = limit_override.tdp_step { + self.tdp_step = Some(val); + } + if let Some(range) = limit_override.clock_min { + if range.min.is_none() && range.max.is_none() { + self.clock_min = None; + } else { + self.clock_min = Some(range); + } + } + if let Some(range) = limit_override.clock_max { + if range.min.is_none() && range.max.is_none() { + self.clock_max = None; + } else { + self.clock_max = Some(range); + } + } + if let Some(val) = limit_override.clock_step { + self.clock_step = Some(val); + } + self.skip_resume_reclock = limit_override.skip_resume_reclock; + self.extras = limit_override.extras; + } +} diff --git a/backend/limits_core/src/json_v2/limits.rs b/backend/limits_core/src/json_v2/limits.rs new file mode 100644 index 0000000..516d31c --- /dev/null +++ b/backend/limits_core/src/json_v2/limits.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Limits { + pub cpu: CpuLimit, + pub gpu: GpuLimit, + pub battery: BatteryLimit, +} + +impl Limits { + pub fn apply_override(&mut self, limit_override: Option) { + if let Some(limit_override) = limit_override { + self.cpu.limits.apply_override(limit_override.cpu.limits); + self.gpu.limits.apply_override(limit_override.gpu.limits); + self.battery.limits.apply_override(limit_override.battery.limits); + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Limit { + pub provider: P, + pub limits: L, +} + +pub type CpuLimit = Limit; +pub type GpuLimit = Limit; +pub type BatteryLimit = Limit; + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct LimitExtras { + pub experiments: bool, + pub quirks: std::collections::HashSet, +} diff --git a/backend/limits_core/src/json_v2/mod.rs b/backend/limits_core/src/json_v2/mod.rs new file mode 100644 index 0000000..d863d7f --- /dev/null +++ b/backend/limits_core/src/json_v2/mod.rs @@ -0,0 +1,21 @@ +mod base; +mod battery_limit; +mod conditions; +mod config; +mod cpu_limit; +mod devel_message; +mod gpu_limit; +mod limits; +mod range; +mod target; + +pub use base::Base; +pub use battery_limit::{BatteryLimitType, GenericBatteryLimit}; +pub use conditions::Conditions; +pub use cpu_limit::{CpuLimitType, GenericCpusLimit, GenericCpuLimit}; +pub use devel_message::DeveloperMessage; +pub use gpu_limit::{GpuLimitType, GenericGpuLimit}; +pub use config::Config; +pub use limits::{Limits, Limit, CpuLimit, GpuLimit, BatteryLimit, LimitExtras}; +pub use range::RangeLimit; +pub use target::Target; diff --git a/backend/limits_core/src/json_v2/range.rs b/backend/limits_core/src/json_v2/range.rs new file mode 100644 index 0000000..62de907 --- /dev/null +++ b/backend/limits_core/src/json_v2/range.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +/// Base JSON limits information +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +pub struct RangeLimit { + pub min: Option, + pub max: Option, +} diff --git a/backend/limits_core/src/json_v2/target.rs b/backend/limits_core/src/json_v2/target.rs new file mode 100644 index 0000000..768df6f --- /dev/null +++ b/backend/limits_core/src/json_v2/target.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum Target { + SteamDeck, + SteamDeckAdvance, + Generic, + Unknown, +} diff --git a/backend/limits_core/src/lib.rs b/backend/limits_core/src/lib.rs index 22fdbb3..11adaf7 100644 --- a/backend/limits_core/src/lib.rs +++ b/backend/limits_core/src/lib.rs @@ -1 +1,2 @@ pub mod json; +pub mod json_v2; diff --git a/backend/limits_srv/Cargo.lock b/backend/limits_srv/Cargo.lock index 1b8b3ee..ca8a790 100644 --- a/backend/limits_srv/Cargo.lock +++ b/backend/limits_srv/Cargo.lock @@ -433,15 +433,14 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "limits_core" -version = "2.0.1" +version = "3.0.0" dependencies = [ "serde", - "serde_json", ] [[package]] name = "limits_srv" -version = "2.0.1" +version = "3.0.0" dependencies = [ "chrono", "limits_core", diff --git a/backend/limits_srv/Cargo.toml b/backend/limits_srv/Cargo.toml index 2d529a8..18f52a6 100644 --- a/backend/limits_srv/Cargo.toml +++ b/backend/limits_srv/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "limits_srv" -version = "2.0.1" +version = "3.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -limits_core = { version = "2.0.1", path = "../limits_core" } +limits_core = { version = "3.0.0", path = "../limits_core" } chrono = { version = "0.4" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/backend/limits_srv/pt_limits.json b/backend/limits_srv/pt_limits.json index 2b34975..2e9264b 100644 --- a/backend/limits_srv/pt_limits.json +++ b/backend/limits_srv/pt_limits.json @@ -4,7 +4,7 @@ "name": "Steam Deck Custom", "conditions": { "dmi": null, - "cpuinfo": "model name\t: AMD Custom APU 0405\n", + "cpuinfo": "model name\t: AMD Custom APU (0405)|(0932)\n", "os": null, "command": null, "file_exists": "./pt_oc.json" @@ -28,7 +28,7 @@ "name": "Steam Deck", "conditions": { "dmi": null, - "cpuinfo": "model name\t: AMD Custom APU 0405\n", + "cpuinfo": "model name\t: AMD Custom APU (0405)|(0932)\n", "os": null, "command": null, "file_exists": null @@ -214,7 +214,7 @@ "name": "AMD R7 6800U", "conditions": { "dmi": null, - "cpuinfo": "model name\t+: AMD Ryzen 7 6800U\n", + "cpuinfo": "model name\t+: AMD Ryzen 7 6800U( with Radeon Graphics)?\n", "os": null, "command": null, "file_exists": null @@ -293,8 +293,8 @@ { "id": 1, "title": "Welcome", - "body": "Thanks for installing PowerTools! For more information, please check the wiki. For bugs and requests, please create an issue on GitHub.", - "url": "https://github.com/NGnius/PowerTools/wiki" + "body": "Thanks for installing PowerTools! For more information, please check the wiki. For bugs and requests, please create an issue.", + "url": "https://git.ngni.us/NG-SD-Plugins/PowerTools/wiki" } ], "refresh": "http://limits.ngni.us:45000/powertools/v1" diff --git a/backend/limits_srv/pt_limits_v2.json b/backend/limits_srv/pt_limits_v2.json new file mode 100644 index 0000000..0fd8033 --- /dev/null +++ b/backend/limits_srv/pt_limits_v2.json @@ -0,0 +1,2725 @@ +{ + "configs": [ + { + "name": "Devs mode best mode", + "conditions": { + "dmi": null, + "cpuinfo": null, + "os": null, + "file_exists": "/etc/powertools_dev_mode" + }, + "limits": { + "cpu": { + "provider": "DevMode", + "limits": { + "cpus": [ + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 100, + "max": 5000 + }, + "clock_max": { + "min": 100, + "max": 4800 + }, + "clock_step": 100, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + ], + "global_governors": true, + "extras": { + "experiments": true, + "quirks": [ + "" + ] + } + } + }, + "gpu": { + "provider": "DevMode", + "limits": { + "fast_ppt": { + "min": 3000000, + "max": 11000000 + }, + "fast_ppt_default": 10000000, + "slow_ppt": { + "min": 7000000, + "max": 11000000 + }, + "slow_ppt_default": 10000000, + "ppt_divisor": 1000000, + "ppt_step": 1, + "tdp": { + "min": 1000000, + "max": 100000000 + }, + "tdp_boost": { + "min": 1000000, + "max": 110000000 + }, + "tdp_divisor": 1000000, + "tdp_step": 1, + "clock_min": { + "min": 100, + "max": 1000 + }, + "clock_max": { + "min": 100, + "max": 1100 + }, + "clock_step": 100, + "memory_clock": { + "min": 100, + "max": 1100 + }, + "memory_clock_step": 100, + "skip_resume_reclock": false, + "extras": { + "experiments": true, + "quirks": [ + "dev" + ] + } + } + }, + "battery": { + "provider": "DevMode", + "limits": { + "charge_rate": { + "min": 0, + "max": 1000 + }, + "charge_modes": [ + "normal", + "discharge", + "idle" + ], + "charge_limit": { + "min": 1.0, + "max": 99.0 + }, + "extra_readouts": true, + "extras": { + "experiments": true, + "quirks": [ + "" + ] + } + } + } + } + }, + { + "name": "Steam Deck", + "conditions": { + "dmi": null, + "cpuinfo": "model name\t: AMD Custom APU 0405\n", + "os": null, + "file_exists": null + }, + "limits": { + "cpu": { + "provider": "GabeBoy", + "limits": { + "cpus": [ + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + ], + "global_governors": true, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "gpu": { + "provider": "GabeBoy", + "limits": { + "fast_ppt": { + "min": 1000000, + "max": 30000000 + }, + "fast_ppt_default": 15000000, + "slow_ppt": { + "min": 1000000, + "max": 29000000 + }, + "slow_ppt_default": 15000000, + "ppt_divisor": 1000000, + "ppt_step": 1, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "clock_min": { + "min": 400, + "max": 1600 + }, + "clock_max": { + "min": 400, + "max": 1600 + }, + "clock_step": 100, + "memory_clock": { + "min": 400, + "max": 800 + }, + "memory_clock_step": 400, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "pp_dpm_fclk-reversed", + "pp_dpm_fclk-not-updated-on-LCD" + ] + } + } + }, + "battery": { + "provider": "GabeBoy", + "limits": { + "charge_rate": { + "min": 250, + "max": 2500 + }, + "charge_modes": [ + "normal", + "discharge", + "idle" + ], + "charge_limit": { + "min": 10.0, + "max": 90.0 + }, + "extra_readouts": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + } + } + }, + { + "name": "Steam Deck OLED", + "conditions": { + "dmi": null, + "cpuinfo": "model name\t: AMD Custom APU 0932\n", + "os": null, + "file_exists": null + }, + "limits": { + "cpu": { + "provider": "GabeBoySP", + "limits": { + "cpus": [ + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "clock-autodetect" + ] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "clock-autodetect" + ] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "clock-autodetect" + ] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "clock-autodetect" + ] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "clock-autodetect" + ] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "clock-autodetect" + ] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "clock-autodetect" + ] + } + }, + { + "clock_min": { + "min": 1400, + "max": 3500 + }, + "clock_max": { + "min": 400, + "max": 3500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "clock-autodetect" + ] + } + } + ], + "global_governors": true, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "gpu": { + "provider": "GabeBoySP", + "limits": { + "fast_ppt": { + "min": 1000000, + "max": 30000000 + }, + "fast_ppt_default": 15000000, + "slow_ppt": { + "min": 1000000, + "max": 29000000 + }, + "slow_ppt_default": 15000000, + "ppt_divisor": 1000000, + "ppt_step": 1, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "clock_min": { + "min": 400, + "max": 1600 + }, + "clock_max": { + "min": 400, + "max": 1600 + }, + "clock_step": 100, + "memory_clock": { + "min": 400, + "max": 800 + }, + "memory_clock_step": 200, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [ + "pp_dpm_fclk-reversed", + "pp_dpm_fclk-not-updated-on-LCD", + "clock-autodetect" + ] + } + } + }, + "battery": { + "provider": "GabeBoySP", + "limits": { + "charge_rate": { + "min": 250, + "max": 2500 + }, + "charge_modes": [ + "normal", + "discharge", + "idle" + ], + "charge_limit": { + "min": 10.0, + "max": 90.0 + }, + "extra_readouts": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + } + } + }, + { + "name": "AMD R3 2300U", + "conditions": { + "dmi": null, + "cpuinfo": "model name\t+: AMD Ryzen 3 2300U\n", + "os": null, + "file_exists": null + }, + "limits": { + "cpu": { + "provider": "GenericAMD", + "limits": { + "cpus": [ + { + "clock_min": { + "min": 1000, + "max": 3700 + }, + "clock_max": { + "min": 1000, + "max": 3700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 3700 + }, + "clock_max": { + "min": 1000, + "max": 3700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 3700 + }, + "clock_max": { + "min": 1000, + "max": 3700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 3700 + }, + "clock_max": { + "min": 1000, + "max": 3700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + ], + "global_governors": true, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "gpu": { + "provider": "GenericAMD", + "limits": { + "fast_ppt": { + "min": 1000, + "max": 25000 + }, + "fast_ppt_default": null, + "slow_ppt": { + "min": 1000, + "max": 25000 + }, + "slow_ppt_default": null, + "ppt_divisor": 1000, + "ppt_step": 1000, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "clock_min": { + "min": 400, + "max": 1100 + }, + "clock_max": { + "min": 400, + "max": 1100 + }, + "clock_step": 100, + "memory_clock": null, + "memory_clock_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "battery": { + "provider": "Generic", + "limits": { + "charge_rate": null, + "charge_modes": [], + "charge_limit": null, + "extra_readouts": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + } + } + }, + { + "name": "AMD R5 5560U", + "conditions": { + "dmi": null, + "cpuinfo": "model name\t+: AMD Ryzen 5 5560U\n", + "os": null, + "file_exists": null + }, + "limits": { + "cpu": { + "provider": "GenericAMD", + "limits": { + "cpus": [ + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4000 + }, + "clock_max": { + "min": 1000, + "max": 4000 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + ], + "global_governors": true, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "gpu": { + "provider": "GenericAMD", + "limits": { + "fast_ppt": { + "min": 1000, + "max": 25000 + }, + "fast_ppt_default": null, + "slow_ppt": { + "min": 1000, + "max": 25000 + }, + "slow_ppt_default": null, + "ppt_divisor": 1000, + "ppt_step": 1000, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "clock_min": { + "min": 400, + "max": 1600 + }, + "clock_max": { + "min": 400, + "max": 1600 + }, + "clock_step": 100, + "memory_clock": null, + "memory_clock_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "battery": { + "provider": "Generic", + "limits": { + "charge_rate": null, + "charge_modes": [], + "charge_limit": null, + "extra_readouts": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + } + } + }, + { + "name": "AMD R7 5825U", + "conditions": { + "dmi": null, + "cpuinfo": "model name\t+: AMD Ryzen 7 5825U\n", + "os": null, + "file_exists": null + }, + "limits": { + "cpu": { + "provider": "GenericAMD", + "limits": { + "cpus": [ + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4500 + }, + "clock_max": { + "min": 1000, + "max": 4500 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + ], + "global_governors": true, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "gpu": { + "provider": "GenericAMD", + "limits": { + "fast_ppt": { + "min": 1000, + "max": 28000 + }, + "fast_ppt_default": null, + "slow_ppt": { + "min": 1000, + "max": 28000 + }, + "slow_ppt_default": null, + "ppt_divisor": 1000, + "ppt_step": 1000, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "clock_min": { + "min": 400, + "max": 2200 + }, + "clock_max": { + "min": 400, + "max": 2200 + }, + "clock_step": 100, + "memory_clock": null, + "memory_clock_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "battery": { + "provider": "Generic", + "limits": { + "charge_rate": null, + "charge_modes": [], + "charge_limit": null, + "extra_readouts": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + } + } + }, + { + "name": "AMD R7 6800U", + "conditions": { + "dmi": null, + "cpuinfo": "model name\t+: AMD Ryzen 7 6800U( with Radeon Graphics)?\n", + "os": null, + "file_exists": null + }, + "limits": { + "cpu": { + "provider": "GenericAMD", + "limits": { + "cpus": [ + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 1000, + "max": 4700 + }, + "clock_max": { + "min": 1000, + "max": 4700 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + ], + "global_governors": true, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "gpu": { + "provider": "GenericAMD", + "limits": { + "fast_ppt": { + "min": 1000, + "max": 28000 + }, + "fast_ppt_default": null, + "slow_ppt": { + "min": 1000, + "max": 28000 + }, + "slow_ppt_default": null, + "ppt_divisor": 1000, + "ppt_step": 1000, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "clock_min": { + "min": 400, + "max": 2200 + }, + "clock_max": { + "min": 400, + "max": 2200 + }, + "clock_step": 100, + "memory_clock": null, + "memory_clock_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "battery": { + "provider": "Generic", + "limits": { + "charge_rate": null, + "charge_modes": [], + "charge_limit": null, + "extra_readouts": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + } + } + }, + { + "name": "AMD R7 7840U", + "conditions": { + "dmi": null, + "cpuinfo": "model name\\s+: AMD Ryzen 7 7840U( w\\/ Radeon 780M Graphics)?\n", + "os": null, + "file_exists": null + }, + "limits": { + "cpu": { + "provider": "GenericAMD", + "limits": { + "cpus": [ + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": { + "min": 400, + "max": 5100 + }, + "clock_max": { + "min": 400, + "max": 5100 + }, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + ], + "global_governors": true, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "gpu": { + "provider": "GenericAMD", + "limits": { + "fast_ppt": { + "min": 1000, + "max": 53000 + }, + "fast_ppt_default": null, + "slow_ppt": { + "min": 1000, + "max": 43000 + }, + "slow_ppt_default": null, + "ppt_divisor": 1000, + "ppt_step": 1000, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "clock_min": null, + "clock_max": null, + "clock_step": null, + "memory_clock": null, + "memory_clock_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "battery": { + "provider": "Generic", + "limits": { + "charge_rate": null, + "charge_modes": [], + "charge_limit": null, + "extra_readouts": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + } + } + }, + { + "name": "Fallback", + "conditions": { + "dmi": null, + "cpuinfo": null, + "os": null, + "file_exists": null + }, + "limits": { + "cpu": { + "provider": "Unknown", + "limits": { + "cpus": [ + { + "clock_min": null, + "clock_max": null, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": null, + "clock_max": null, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": null, + "clock_max": null, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": null, + "clock_max": null, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": null, + "clock_max": null, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": null, + "clock_max": null, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": null, + "clock_max": null, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + }, + { + "clock_min": null, + "clock_max": null, + "clock_step": 100, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + ], + "global_governors": true, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "gpu": { + "provider": "Unknown", + "limits": { + "fast_ppt": null, + "fast_ppt_default": null, + "slow_ppt": null, + "slow_ppt_default": null, + "ppt_divisor": null, + "ppt_step": null, + "tdp": null, + "tdp_boost": null, + "tdp_divisor": null, + "tdp_step": null, + "clock_min": null, + "clock_max": null, + "clock_step": null, + "memory_clock": null, + "memory_clock_step": null, + "skip_resume_reclock": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + }, + "battery": { + "provider": "Unknown", + "limits": { + "charge_rate": null, + "charge_modes": [], + "charge_limit": null, + "extra_readouts": false, + "extras": { + "experiments": false, + "quirks": [] + } + } + } + } + } + ], + "messages": [ + { + "id": 1, + "title": "Welcome", + "body": "Thanks for installing PowerTools! For more information, please check the wiki. For bugs and requests, please create an issue.", + "url": "https://git.ngni.us/NG-SD-Plugins/PowerTools/wiki" + } + ], + "store": "https://powertools.ngni.us", + "refresh": "http://limits.ngni.us:45000/powertools/v2" +} \ No newline at end of file diff --git a/backend/limits_srv/src/main.rs b/backend/limits_srv/src/main.rs index 10a03af..6e33b45 100644 --- a/backend/limits_srv/src/main.rs +++ b/backend/limits_srv/src/main.rs @@ -4,46 +4,61 @@ use std::sync::{RwLock, Arc}; use serde::Serialize; use warp::Filter; -use limits_core::json::Base; - -static VISIT_COUNT: AtomicU64 = AtomicU64::new(0); +static VISIT_V1_COUNT: AtomicU64 = AtomicU64::new(0); +static VISIT_V2_COUNT: AtomicU64 = AtomicU64::new(0); static START_TIME: AtomicI64 = AtomicI64::new(0); -fn get_limits(base: Base) -> impl warp::Reply { - VISIT_COUNT.fetch_add(1, Ordering::AcqRel); +fn get_limits_v1(base: &limits_core::json::Base) -> impl warp::Reply { + VISIT_V1_COUNT.fetch_add(1, Ordering::AcqRel); //println!("Limits got"); - warp::reply::json(&base) + warp::reply::json(base) +} + +fn get_limits_v2(base: &limits_core::json_v2::Base) -> impl warp::Reply { + VISIT_V2_COUNT.fetch_add(1, Ordering::AcqRel); + //println!("Limits got"); + warp::reply::json(base) } #[derive(Serialize)] struct Visits { - visits: u64, + visits_v1: u64, + visits_v2: u64, since: i64, // Unix time (since epoch) } fn get_visits() -> impl warp::Reply { - let count = VISIT_COUNT.load(Ordering::Relaxed); + let count_v1 = VISIT_V1_COUNT.load(Ordering::Relaxed); + let count_v2 = VISIT_V2_COUNT.load(Ordering::Relaxed); let start = START_TIME.load(Ordering::Relaxed); //println!("Count got"); warp::reply::json(&Visits { - visits: count, + visits_v1: count_v1, + visits_v2: count_v2, since: start, }) } #[allow(opaque_hidden_inferred_bound)] -fn routes(base: Arc>) -> impl Filter + Clone { +fn routes(base: Arc>, base2: Arc>) -> impl Filter + Clone { warp::get().and( warp::path!("powertools" / "v1") .map(move || { - let base = base.read().expect("Failed to acquire base limits read lock").clone(); - get_limits(base) + let base = base.read().expect("Failed to acquire base limits read lock"); + get_limits_v1(&base) }) .or( warp::path!("powertools" / "count") .map(get_visits) ) + .or( + warp::path!("powertools" / "v2") + .map(move || { + let base2 = base2.read().expect("Failed to acquire base limits read lock"); + get_limits_v2(&base2) + }) + ) ).recover(recovery) } @@ -59,10 +74,14 @@ pub async fn recovery(reject: warp::Rejection) -> Result>, + pub fast_ppt_default: u64, pub slow_ppt_limits: Option>, + pub slow_ppt_default: u64, pub ppt_step: u64, pub tdp_limits: Option>, pub tdp_boost_limits: Option>, @@ -59,5 +61,13 @@ pub struct GpuLimits { pub clock_min_limits: Option>, pub clock_max_limits: Option>, pub clock_step: u64, - pub memory_control_capable: bool, + pub memory_control: Option>, + pub memory_step: u64, +} + +#[derive(Serialize, Deserialize)] +pub struct VariantInfo { + pub id: String, + pub name: String, + pub id_num: u64, } diff --git a/backend/src/api/general.rs b/backend/src/api/general.rs index 9cda1c2..806347a 100644 --- a/backend/src/api/general.rs +++ b/backend/src/api/general.rs @@ -55,18 +55,44 @@ pub fn load_settings( sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety - let setter = move |path: u64, name: String| { + let setter = move |id: u64, name: String, variant: u64, variant_name: Option| { sender .lock() .unwrap() - .send(ApiMessage::LoadSettings(path, name)) + .send(ApiMessage::LoadSettings( + id, + name, + variant, + variant_name + .unwrap_or_else(|| crate::consts::DEFAULT_SETTINGS_VARIANT_NAME.to_owned()), + )) .expect("load_settings send failed") }; move |params_in: super::ApiParameterType| { if let Some(Primitive::String(id)) = params_in.get(0) { if let Some(Primitive::String(name)) = params_in.get(1) { - setter(id.parse().unwrap_or_default(), name.to_owned()); - vec![true.into()] + if let Some(Primitive::String(variant_id)) = params_in.get(2) { + if let Some(Primitive::String(variant_name)) = params_in.get(3) { + setter( + id.parse().unwrap_or_default(), + name.to_owned(), + variant_id.parse().unwrap_or_default(), + Some(variant_name.to_owned()), + ); + vec![true.into()] + } else { + setter( + id.parse().unwrap_or_default(), + name.to_owned(), + variant_id.parse().unwrap_or_default(), + None, + ); + vec![true.into()] + } + } else { + log::warn!("load_settings missing variant id parameter"); + vec!["load_settings missing variant id parameter".into()] + } } else { log::warn!("load_settings missing name parameter"); vec!["load_settings missing name parameter".into()] @@ -79,6 +105,45 @@ pub fn load_settings( } } +/// Generate load app settings from file web method +pub fn load_variant( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |variant: u64, variant_name: Option| { + log::debug!( + "load_variant(variant: {}, variant_name: {:?})", + variant, + variant_name + ); + sender + .lock() + .unwrap() + .send(ApiMessage::LoadVariant( + variant, + variant_name.unwrap_or_else(|| "".to_owned()), + )) + .expect("load_variant send failed") + }; + move |params_in: super::ApiParameterType| { + if let Some(Primitive::String(variant_id)) = params_in.get(0) { + if let Some(Primitive::String(variant_name)) = params_in.get(1) { + setter( + variant_id.parse().unwrap_or(u64::MAX), + Some(variant_name.to_owned()), + ); + vec![true.into()] + } else { + setter(variant_id.parse().unwrap_or(u64::MAX), None); + vec![true.into()] + } + } else { + log::warn!("load_variant missing variant id parameter"); + vec!["load_variant missing variant id parameter".into()] + } + } +} + /// Generate load default settings from file web method pub fn load_default_settings( sender: Sender, @@ -329,32 +394,60 @@ pub fn get_periodicals(sender: Sender) -> impl AsyncCallable { let sender2 = sender.clone(); move || { let (rx_curr, callback_curr) = build_comms("battery current callback send failed"); - let (rx_charge_now, callback_charge_now) = build_comms("battery charge now callback send failed"); - let (rx_charge_full, callback_charge_full) = build_comms("battery charge full callback send failed"); - let (rx_charge_power, callback_charge_power) = build_comms("battery charge power callback send failed"); + let (rx_charge_now, callback_charge_now) = + build_comms("battery charge now callback send failed"); + let (rx_charge_full, callback_charge_full) = + build_comms("battery charge full callback send failed"); + let (rx_charge_power, callback_charge_power) = + build_comms("battery charge power callback send failed"); let (rx_path, callback_path) = build_comms("general get path (periodical) send failed"); - let sender_locked = sender2 - .lock() - .unwrap(); - let curr = wait_for_response(&*sender_locked, rx_curr, - ApiMessage::Battery(super::handler::BatteryMessage::ReadCurrentNow(callback_curr)), "battery current"); - let charge_now = wait_for_response(&*sender_locked, rx_charge_now, - ApiMessage::Battery(super::handler::BatteryMessage::ReadChargeNow(callback_charge_now)), "battery charge now"); - let charge_full = wait_for_response(&*sender_locked, rx_charge_full, - ApiMessage::Battery(super::handler::BatteryMessage::ReadChargeFull(callback_charge_full)), "battery charge full"); - let charge_power = wait_for_response(&*sender_locked, rx_charge_power, - ApiMessage::Battery(super::handler::BatteryMessage::ReadChargePower(callback_charge_power)), "battery charge power"); + let sender_locked = sender2.lock().unwrap(); + let curr = wait_for_response( + &*sender_locked, + rx_curr, + ApiMessage::Battery(super::handler::BatteryMessage::ReadCurrentNow( + callback_curr, + )), + "battery current", + ); + let charge_now = wait_for_response( + &*sender_locked, + rx_charge_now, + ApiMessage::Battery(super::handler::BatteryMessage::ReadChargeNow( + callback_charge_now, + )), + "battery charge now", + ); + let charge_full = wait_for_response( + &*sender_locked, + rx_charge_full, + ApiMessage::Battery(super::handler::BatteryMessage::ReadChargeFull( + callback_charge_full, + )), + "battery charge full", + ); + let charge_power = wait_for_response( + &*sender_locked, + rx_charge_power, + ApiMessage::Battery(super::handler::BatteryMessage::ReadChargePower( + callback_charge_power, + )), + "battery charge power", + ); - let settings_path = wait_for_response(&*sender_locked, rx_path, - ApiMessage::General(GeneralMessage::GetPath(callback_path)), "general get path"); + let settings_path = wait_for_response( + &*sender_locked, + rx_path, + ApiMessage::General(GeneralMessage::GetPath(callback_path)), + "general get path", + ); vec![ super::utility::map_optional(curr), super::utility::map_optional(charge_now), super::utility::map_optional(charge_full), super::utility::map_optional(charge_power), - super::utility::map_optional(settings_path.to_str()), ] } @@ -365,13 +458,89 @@ pub fn get_periodicals(sender: Sender) -> impl AsyncCallable { } } -fn build_comms<'a, T: Send + 'a>(msg: &'static str) -> (mpsc::Receiver, Box) { +fn build_comms<'a, T: Send + 'a>( + msg: &'static str, +) -> (mpsc::Receiver, Box) { let (tx, rx) = mpsc::channel(); let callback = move |t: T| tx.send(t).expect(msg); (rx, Box::new(callback)) } -fn wait_for_response(sender: &Sender, rx: mpsc::Receiver, api_msg: ApiMessage, op: &str) -> T { +fn wait_for_response( + sender: &Sender, + rx: mpsc::Receiver, + api_msg: ApiMessage, + op: &str, +) -> T { sender.send(api_msg).expect(&format!("{} send failed", op)); rx.recv().expect(&format!("{} callback recv failed", op)) } + +/// Generate get variants +pub fn get_all_variants(sender: Sender) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |variants: Vec| { + tx.send(variants) + .expect("get_all_variants callback send failed") + }; + sender2 + .lock() + .unwrap() + .send(ApiMessage::General(GeneralMessage::GetAllVariants( + Box::new(callback), + ))) + .expect("get_all_variants send failed"); + let mut results = rx.recv().expect("get_all_variants callback recv failed"); + results.sort_by_key(|info| info.id_num); // sort by variant id + results + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + let mut output = Vec::with_capacity(result.len()); + for status in result.iter() { + output.push(Primitive::Json( + serde_json::to_string(status) + .expect("Failed to serialize variant info to JSON"), + )); + } + output + }, + } +} + +/// Generate get current variant +pub fn get_current_variant(sender: Sender) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |variant: super::VariantInfo| { + tx.send(variant) + .expect("get_all_variants callback send failed") + }; + sender2 + .lock() + .unwrap() + .send(ApiMessage::General(GeneralMessage::GetCurrentVariant( + Box::new(callback), + ))) + .expect("get_current_variant send failed"); + rx.recv().expect("get_current_variant callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + vec![Primitive::Json( + serde_json::to_string(&result).expect("Failed to serialize variant info to JSON"), + )] + }, + } +} diff --git a/backend/src/api/gpu.rs b/backend/src/api/gpu.rs index 320a331..f8b178d 100644 --- a/backend/src/api/gpu.rs +++ b/backend/src/api/gpu.rs @@ -160,17 +160,17 @@ pub fn set_slow_memory( sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety - let setter = move |value: bool| { + let setter = move |value: u64| { sender .lock() .unwrap() - .send(ApiMessage::Gpu(GpuMessage::SetSlowMemory(value))) + .send(ApiMessage::Gpu(GpuMessage::SetMemoryClock(Some(value)))) .expect("unset_clock_limits send failed") }; move |params_in: super::ApiParameterType| { - if let Some(&Primitive::Bool(memory_is_slow)) = params_in.get(0) { - setter(memory_is_slow); - vec![memory_is_slow.into()] + if let Some(&Primitive::F64(mem_clock)) = params_in.get(0) { + setter(mem_clock as _); + vec![mem_clock.into()] } else { vec!["set_slow_memory missing parameter 0".into()] } @@ -183,14 +183,14 @@ pub fn get_slow_memory(sender: Sender) -> impl AsyncCallable { let sender2 = sender.clone(); move || { let (tx, rx) = mpsc::channel(); - let callback = move |value: bool| { + let callback = move |value: Option| { tx.send(value) .expect("get_slow_memory callback send failed") }; sender2 .lock() .unwrap() - .send(ApiMessage::Gpu(GpuMessage::GetSlowMemory(Box::new( + .send(ApiMessage::Gpu(GpuMessage::GetMemoryClock(Box::new( callback, )))) .expect("get_slow_memory send failed"); @@ -199,6 +199,23 @@ pub fn get_slow_memory(sender: Sender) -> impl AsyncCallable { }; super::async_utils::AsyncIshGetter { set_get: getter, - trans_getter: |value: bool| vec![value.into()], + trans_getter: |value: Option| vec![super::utility::map_optional(value)], + } +} + +pub fn unset_slow_memory( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move || { + sender + .lock() + .unwrap() + .send(ApiMessage::Gpu(GpuMessage::SetMemoryClock(None))) + .expect("unset_slow_memory send failed") + }; + move |_: super::ApiParameterType| { + setter(); + vec![true.into()] } } diff --git a/backend/src/api/handler.rs b/backend/src/api/handler.rs index 60316c9..5058eab 100644 --- a/backend/src/api/handler.rs +++ b/backend/src/api/handler.rs @@ -3,9 +3,9 @@ use std::sync::mpsc::{self, Receiver, Sender}; use crate::persist::SettingsJson; use crate::settings::{ - MinMax, OnPowerEvent, OnResume, OnSet, PowerMode, Settings, TBattery, TCpus, TGeneral, TGpu, + MinMax, OnLoad, OnPowerEvent, OnResume, OnSet, OnUnload, PowerMode, Settings, TBattery, TCpus, + TGeneral, TGpu, }; -use crate::utility::unwrap_maybe_fatal; type Callback = Box; @@ -23,11 +23,13 @@ pub enum ApiMessage { OnChargeChange(f64), // battery fill amount: 0 = empty, 1 = full PowerVibeCheck, WaitForEmptyQueue(Callback<()>), - LoadSettings(u64, String), // (path, name) + LoadSettings(u64, String, u64, String), // (path, name, variant, variant name) + LoadVariant(u64, String), // (variant, variant name) -- path and name assumed to be for current profile LoadMainSettings, LoadSystemSettings, GetLimits(Callback), GetProvider(String, Callback), + UploadCurrentVariant(String, String), // SteamID, Steam username } pub enum BatteryMessage { @@ -65,7 +67,10 @@ impl BatteryMessage { /// Message instructs the driver to modify settings fn is_modify(&self) -> bool { - matches!(self, Self::SetChargeRate(_) | Self::SetChargeMode(_)) + matches!( + self, + Self::SetChargeRate(_) | Self::SetChargeMode(_) | Self::SetChargeLimit(_) + ) } } @@ -197,8 +202,8 @@ pub enum GpuMessage { GetPpt(Callback<(Option, Option)>), SetClockLimits(Option>), GetClockLimits(Callback>>), - SetSlowMemory(bool), - GetSlowMemory(Callback), + SetMemoryClock(Option), + GetMemoryClock(Callback>), } impl GpuMessage { @@ -209,8 +214,8 @@ impl GpuMessage { Self::GetPpt(cb) => cb(settings.get_ppt()), Self::SetClockLimits(clocks) => settings.clock_limits(clocks), Self::GetClockLimits(cb) => cb(settings.get_clock_limits().map(|x| x.to_owned())), - Self::SetSlowMemory(val) => *settings.slow_memory() = val, - Self::GetSlowMemory(cb) => cb(*settings.slow_memory()), + Self::SetMemoryClock(val) => settings.memory_clock(val), + Self::GetMemoryClock(cb) => cb(settings.get_memory_clock()), } dirty } @@ -218,7 +223,7 @@ impl GpuMessage { fn is_modify(&self) -> bool { matches!( self, - Self::SetPpt(_, _) | Self::SetClockLimits(_) | Self::SetSlowMemory(_) + Self::SetPpt(_, _) | Self::SetClockLimits(_) | Self::SetMemoryClock(_) ) } } @@ -228,6 +233,12 @@ pub enum GeneralMessage { GetPersistent(Callback), GetCurrentProfileName(Callback), GetPath(Callback), + GetCurrentVariant(Callback), + GetAllVariants(Callback>), + AddVariant( + crate::persist::SettingsJson, + Callback>, + ), ApplyNow, } @@ -239,6 +250,18 @@ impl GeneralMessage { Self::GetPersistent(cb) => cb(*settings.persistent()), Self::GetCurrentProfileName(cb) => cb(settings.get_name().to_owned()), Self::GetPath(cb) => cb(settings.get_path().to_owned()), + Self::GetCurrentVariant(cb) => cb(settings.get_variant_info()), + Self::GetAllVariants(cb) => cb(settings.get_variants()), + Self::AddVariant(variant, cb) => match settings.add_variant(variant) { + Ok(variants) => cb(variants), + Err(e) => { + print_errors( + "GeneralMessage::AddVariant => TGeneral::add_variant", + vec![e], + ); + cb(Vec::with_capacity(0)) + } + }, Self::ApplyNow => {} } dirty @@ -264,6 +287,7 @@ fn print_errors(call_name: &str, errors: Vec) { impl ApiMessageHandler { pub fn process_forever(&mut self, settings: &mut Settings) { + crate::utility::ioperm_power_ec(); //let mut dirty_echo = true; // set everything twice, to make sure PowerTools wins on race conditions while let Ok(msg) = self.intake.recv() { let mut dirty = self.process(settings, msg); @@ -286,22 +310,23 @@ impl ApiMessageHandler { // save log::debug!("api_worker is saving..."); let is_persistent = *settings.general.persistent(); - let save_path = - crate::utility::settings_dir().join(settings.general.get_path().clone()); + let save_path = crate::utility::settings_dir().join(settings.general.get_path()); if is_persistent { let settings_clone = settings.json(); let save_json: SettingsJson = settings_clone.into(); - unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings"); - if let Some(event) = &settings.general.on_event().on_save { - if !event.is_empty() { - unwrap_maybe_fatal( - std::process::Command::new("/bin/bash") - .args(&["-c", event]) - .spawn(), - "Failed to start on_save event command", - ); - } + if let Err(e) = crate::persist::FileJson::update_variant_or_create( + &save_path, + settings.general.get_app_id(), + save_json, + settings.general.get_name().to_owned(), + ) { + log::error!( + "Failed to create/update settings file {}: {}", + save_path.display(), + e + ); } + //unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings"); log::debug!("Saved settings to {}", save_path.display()); if let Err(e) = crate::utility::chown_settings_dir() { log::error!("Failed to change config dir permissions: {}", e); @@ -375,27 +400,74 @@ impl ApiMessageHandler { self.on_empty.push(callback); false } - ApiMessage::LoadSettings(id, name) => { - let path = format!("{}.json", id); - match settings.load_file(path.into(), name, false) { + ApiMessage::LoadSettings(id, name, variant_id, variant_name) => { + let path = format!("{}.ron", id); + if let Err(e) = settings.on_unload() { + print_errors("LoadSettings on_unload()", e); + } + match settings.load_file(path.into(), id, name, variant_id, variant_name, false) { Ok(success) => log::info!("Loaded settings file? {}", success), Err(e) => log::warn!("Load file err: {}", e), } + if let Err(e) = settings.on_load() { + print_errors("LoadSettings on_load()", e); + } + true + } + ApiMessage::LoadVariant(variant_id, variant_name) => { + if let Err(e) = settings.on_unload() { + print_errors("LoadVariant on_unload()", e); + } + let path = settings.general.get_path(); + let app_id = settings.general.get_app_id(); + match settings.load_file( + path.into(), + app_id, + settings.general.get_name().to_owned(), + variant_id, + variant_name, + false, + ) { + Ok(success) => log::info!("Loaded variant settings file? {}", success), + Err(e) => log::warn!("Load file err: {}", e), + } + if let Err(e) = settings.on_load() { + print_errors("LoadVariant on_load()", e); + } true } ApiMessage::LoadMainSettings => { + if let Err(e) = settings.on_unload() { + print_errors("LoadMainSettings on_unload()", e); + } match settings.load_file( crate::consts::DEFAULT_SETTINGS_FILE.into(), + 0, crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), + 0, + crate::consts::DEFAULT_SETTINGS_VARIANT_NAME.to_owned(), true, ) { Ok(success) => log::info!("Loaded main settings file? {}", success), Err(e) => log::warn!("Load file err: {}", e), } + if let Err(e) = settings.on_load() { + print_errors("LoadMainSettings on_load()", e); + } true } ApiMessage::LoadSystemSettings => { - settings.load_system_default(settings.general.get_name().to_owned()); + if let Err(e) = settings.on_unload() { + print_errors("LoadSystemSettings on_unload()", e); + } + settings.load_system_default( + settings.general.get_name().to_owned(), + settings.general.get_variant_id(), + settings.general.get_variant_info().name, + ); + if let Err(e) = settings.on_load() { + print_errors("LoadSystemSettings on_load()", e); + } true } ApiMessage::GetLimits(cb) => { @@ -416,6 +488,16 @@ impl ApiMessageHandler { }); false } + ApiMessage::UploadCurrentVariant(steam_id, steam_username) => { + let steam_app_id = settings.general.get_app_id(); + super::web::upload_settings( + steam_app_id, + steam_id, + steam_username, + settings.json(), + ); + false + } } } diff --git a/backend/src/api/message.rs b/backend/src/api/message.rs index 3ac093e..0a7b435 100644 --- a/backend/src/api/message.rs +++ b/backend/src/api/message.rs @@ -1,14 +1,17 @@ -use std::sync::{atomic::{AtomicU64, Ordering}, Arc}; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; use serde::{Deserialize, Serialize}; -use usdpl_back::AsyncCallable; use usdpl_back::core::serdes::Primitive; +use usdpl_back::AsyncCallable; use limits_core::json::DeveloperMessage; -use crate::MESSAGE_SEEN_ID_FILE; use crate::utility::settings_dir; +use crate::MESSAGE_SEEN_ID_FILE; #[derive(Serialize, Deserialize)] pub struct ApiMessage { @@ -34,7 +37,10 @@ impl std::convert::From for ApiMessage { } fn get_dev_messages() -> Vec { - crate::settings::get_dev_messages().drain(..).map(|msg| ApiMessage::from(msg)).collect() + crate::settings::get_dev_messages() + .drain(..) + .map(|msg| ApiMessage::from(msg)) + .collect() } pub struct MessageHandler { @@ -43,8 +49,12 @@ pub struct MessageHandler { impl MessageHandler { pub fn new() -> Self { - let last_seen_id = if let Ok(last_seen_id_bytes) = std::fs::read(settings_dir().join(MESSAGE_SEEN_ID_FILE)) { - if last_seen_id_bytes.len() >= 8 /* bytes in u64 */ { + let last_seen_id = if let Ok(last_seen_id_bytes) = + std::fs::read(settings_dir().join(MESSAGE_SEEN_ID_FILE)) + { + if last_seen_id_bytes.len() >= 8 + /* bytes in u64 */ + { u64::from_le_bytes([ last_seen_id_bytes[0], last_seen_id_bytes[1], @@ -73,7 +83,7 @@ impl MessageHandler { }, AsyncMessageDismisser { seen: self.seen.clone(), - } + }, ) } } @@ -83,8 +93,17 @@ pub struct AsyncMessageGetter { } impl AsyncMessageGetter { - fn remove_before_id(id: u64, messages: impl Iterator) -> impl Iterator { - messages.skip_while(move |msg| if let Some(msg_id) = msg.id { msg_id <= id } else { true }) + fn remove_before_id( + id: u64, + messages: impl Iterator, + ) -> impl Iterator { + messages.skip_while(move |msg| { + if let Some(msg_id) = msg.id { + msg_id <= id + } else { + true + } + }) } } diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 8dc8add..488faf3 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -7,6 +7,7 @@ pub mod gpu; pub mod handler; pub mod message; mod utility; +pub mod web; pub(super) type ApiParameterType = Vec; diff --git a/backend/src/api/web.rs b/backend/src/api/web.rs new file mode 100644 index 0000000..8222fa2 --- /dev/null +++ b/backend/src/api/web.rs @@ -0,0 +1,484 @@ +use std::sync::mpsc::{self, Sender}; +#[cfg(feature = "online")] +use std::sync::RwLock; +use std::sync::{Arc, Mutex}; +use usdpl_back::core::serdes::Primitive; +use usdpl_back::AsyncCallable; + +use super::handler::{ApiMessage, GeneralMessage}; +use crate::utility::CachedData; + +#[cfg(feature = "online")] +const BASE_URL_FALLBACK: &'static str = "https://powertools.ngni.us"; +#[cfg(feature = "online")] +static BASE_URL: RwLock> = RwLock::new(None); + +const MAX_CACHE_DURATION: std::time::Duration = + std::time::Duration::from_secs(60 * 60 * 24 * 7 /* 7 days */); + +type StoreCache = + std::collections::HashMap>>; + +#[cfg(feature = "online")] +pub fn set_base_url(base_url: String) { + *BASE_URL + .write() + .expect("Failed to acquire write lock for store base url") = Some(base_url); +} + +#[cfg(feature = "online")] +fn get_base_url() -> String { + BASE_URL + .read() + .expect("Failed to acquire read lock for store base url") + .clone() + .unwrap_or_else(|| BASE_URL_FALLBACK.to_owned()) +} + +#[cfg(feature = "online")] +fn url_search_by_app_id(steam_app_id: u32) -> String { + format!("{}/api/setting/by_app_id/{}", get_base_url(), steam_app_id) +} + +#[cfg(feature = "online")] +fn url_download_config_by_id(id: u128) -> String { + format!("{}/api/setting/by_id/{}", get_base_url(), id) +} + +#[cfg(feature = "online")] +fn url_upload_config() -> String { + format!("{}/api/setting", get_base_url()) +} + +#[cfg(feature = "online")] +fn cache_path() -> std::path::PathBuf { + crate::utility::settings_dir().join(crate::consts::WEB_SETTINGS_CACHE) +} + +#[cfg(feature = "online")] +fn load_cache() -> StoreCache { + let path = cache_path(); + let file = match std::fs::File::open(&path) { + Ok(f) => f, + Err(e) => { + log::warn!("Failed to open store cache {}: {}", path.display(), e); + return StoreCache::default(); + } + }; + let mut file = std::io::BufReader::new(file); + match ron::de::from_reader(&mut file) { + Ok(cache) => cache, + Err(e) => { + log::error!("Failed to parse store cache {}: {}", path.display(), e); + return StoreCache::default(); + } + } +} + +#[cfg(not(feature = "online"))] +fn load_cache() -> StoreCache { + StoreCache::default() +} + +#[cfg(feature = "online")] +fn save_cache(cache: &StoreCache) { + let path = cache_path(); + let file = match std::fs::File::create(&path) { + Ok(f) => f, + Err(e) => { + log::warn!("Failed to create store cache {}: {}", path.display(), e); + return; + } + }; + let mut file = std::io::BufWriter::new(file); + if let Err(e) = + ron::ser::to_writer_pretty(&mut file, cache, crate::utility::ron_pretty_config()) + { + log::error!("Failed to parse store cache {}: {}", path.display(), e); + } +} + +#[cfg(not(feature = "online"))] +fn save_cache(_cache: &StoreCache) {} + +fn get_maybe_cached(steam_app_id: u32) -> Vec { + let mut cache = load_cache(); + let data = if let Some(cached_result) = cache.get(&steam_app_id) { + if cached_result.needs_update(MAX_CACHE_DURATION) { + // cache needs update + if let Ok(result) = search_by_app_id_online(steam_app_id) { + cache.insert( + steam_app_id, + CachedData { + data: result.clone(), + updated: chrono::offset::Utc::now(), + }, + ); + save_cache(&cache); + result + } else { + // if all else fails, out of date results are better than no results + cached_result.data.to_owned() + } + } else { + // cache is ok, use it + cached_result.data.to_owned() + } + } else { + if let Ok(result) = search_by_app_id_online(steam_app_id) { + cache.insert( + steam_app_id, + CachedData { + data: result.clone(), + updated: chrono::offset::Utc::now(), + }, + ); + save_cache(&cache); + result + } else { + Vec::with_capacity(0) + } + }; + #[cfg(debug_assertions)] + { + data + } + #[cfg(not(debug_assertions))] + { + data.into_iter().filter(|meta| meta.id != "0").collect() + } +} + +#[cfg(feature = "online")] +fn search_by_app_id_online( + steam_app_id: u32, +) -> std::io::Result> { + let req_url = url_search_by_app_id(steam_app_id); + match ureq::get(&req_url).call() { + Ok(response) => { + let json_res: std::io::Result> = + response.into_json(); + match json_res { + Ok(search_results) => Ok(search_results), + Err(e) => { + log::error!("Cannot parse response from `{}`: {}", req_url, e); + Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)) + } + } + } + Err(e) => { + log::warn!("Cannot get search results from `{}`: {}", req_url, e); + Err(std::io::Error::new( + std::io::ErrorKind::ConnectionAborted, + e, + )) + } + } +} + +#[cfg(not(feature = "online"))] +fn search_by_app_id_online( + _steam_app_id: u32, +) -> std::io::Result> { + Ok(Vec::with_capacity(0)) +} + +/// Get search results web method +pub fn search_by_app_id() -> impl AsyncCallable { + let getter = move || { + move |steam_app_id: u32| { + let search_results = get_maybe_cached(steam_app_id); + match serde_json::to_string(&search_results) { + Err(e) => { + log::error!("Cannot convert search results to JSON: {}", e); + "[]".to_owned() + } + Ok(s) => s, + } + } + }; + super::async_utils::AsyncIsh { + trans_setter: |params| { + if let Some(Primitive::String(s)) = params.get(0) { + s.parse::().map_err(|e| format!("search_by_app_id invalid parameter 0: {}", e)) + } else { + Err("search_by_app_id missing/invalid parameter 0".to_owned()) + } + }, + set_get: getter, + trans_getter: |result| vec![Primitive::Json(result)], + } +} + +fn web_config_to_settings_json( + meta: community_settings_core::v1::Metadata, +) -> crate::persist::SettingsJson { + crate::persist::SettingsJson { + version: crate::persist::LATEST_VERSION, + name: meta.name, + variant: u64::MAX, // TODO maybe change this to use the 64 low bits of id (u64::MAX will cause it to generate a new id when added to file variant map + persistent: true, + cpus: meta + .config + .cpus + .into_iter() + .map(|cpu| crate::persist::CpuJson { + online: cpu.online, + clock_limits: cpu.clock_limits.map(|lim| crate::persist::MinMaxJson { + min: lim.min, + max: lim.max, + }), + governor: cpu.governor, + root: None, + }) + .collect(), + gpu: crate::persist::GpuJson { + fast_ppt: meta.config.gpu.fast_ppt, + slow_ppt: meta.config.gpu.slow_ppt, + tdp: meta.config.gpu.tdp, + tdp_boost: meta.config.gpu.tdp_boost, + clock_limits: meta + .config + .gpu + .clock_limits + .map(|lim| crate::persist::MinMaxJson { + min: lim.min, + max: lim.max, + }), + memory_clock: meta.config.gpu.memory_clock, + root: None, + }, + battery: crate::persist::BatteryJson { + charge_rate: meta.config.battery.charge_rate, + charge_mode: meta.config.battery.charge_mode, + events: meta + .config + .battery + .events + .into_iter() + .map(|be| crate::persist::BatteryEventJson { + charge_rate: be.charge_rate, + charge_mode: be.charge_mode, + trigger: be.trigger, + }) + .collect(), + root: None, + }, + provider: Some(crate::persist::DriverJson::AutoDetect), + tags: meta.tags, + } +} + +#[cfg(feature = "online")] +fn download_config(id: u128) -> std::io::Result { + let req_url = url_download_config_by_id(id); + let response = ureq::get(&req_url).call().map_err(|e| { + log::warn!("GET to {} failed: {}", req_url, e); + std::io::Error::new(std::io::ErrorKind::ConnectionAborted, e) + })?; + response.into_json() +} + +#[cfg(not(feature = "online"))] +fn download_config(_id: u128) -> std::io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Online functionality not included in this build", + )) +} + +pub fn upload_settings( + id: u64, + user_id: String, + username: String, + settings: crate::persist::SettingsJson, +) { + log::info!( + "Uploading settings {} by {} ({})", + settings.name, + username, + user_id + ); + let user_id: u64 = match user_id.parse() { + Ok(id) => id, + Err(e) => { + log::error!( + "Failed to parse `{}` as u64: {} (aborted upload_settings very early)", + user_id, + e + ); + return; + } + }; + let meta = settings_to_web_config(id as _, user_id, username, settings); + if let Err(e) = upload_config(meta) { + log::error!("Failed to upload settings: {}", e); + } +} + +fn settings_to_web_config( + app_id: u32, + user_id: u64, + username: String, + settings: crate::persist::SettingsJson, +) -> community_settings_core::v1::Metadata { + #[cfg(any(not(debug_assertions), not(feature = "dev_stuff")))] + let app_id = if app_id == 0 { 1 } else { app_id }; + community_settings_core::v1::Metadata { + name: settings.name, + steam_app_id: app_id, + steam_user_id: user_id, + steam_username: username, + tags: settings.tags, + id: "".to_owned(), + config: community_settings_core::v1::Config { + cpus: settings + .cpus + .into_iter() + .map(|cpu| community_settings_core::v1::Cpu { + online: cpu.online, + clock_limits: cpu + .clock_limits + .map(|lim| community_settings_core::v1::MinMax { + min: lim.min, + max: lim.max, + }), + governor: cpu.governor, + }) + .collect(), + gpu: community_settings_core::v1::Gpu { + fast_ppt: settings.gpu.fast_ppt, + slow_ppt: settings.gpu.slow_ppt, + tdp: settings.gpu.tdp, + tdp_boost: settings.gpu.tdp_boost, + clock_limits: settings.gpu.clock_limits.map(|lim| { + community_settings_core::v1::MinMax { + min: lim.min, + max: lim.max, + } + }), + memory_clock: settings.gpu.memory_clock, + }, + battery: community_settings_core::v1::Battery { + charge_rate: settings.battery.charge_rate, + charge_mode: settings.battery.charge_mode, + events: settings + .battery + .events + .into_iter() + .map(|batt_ev| community_settings_core::v1::BatteryEvent { + trigger: batt_ev.trigger, + charge_rate: batt_ev.charge_rate, + charge_mode: batt_ev.charge_mode, + }) + .collect(), + }, + }, + } +} + +#[cfg(feature = "online")] +fn upload_config(config: community_settings_core::v1::Metadata) -> std::io::Result<()> { + let req_url = url_upload_config(); + ureq::post(&req_url) + .send_json(&config) + .map_err(|e| { + log::warn!("POST to {} failed: {}", req_url, e); + std::io::Error::new(std::io::ErrorKind::ConnectionAborted, e) + }) + .map(|_| ()) +} + +#[cfg(not(feature = "online"))] +fn upload_config(_config: community_settings_core::v1::Metadata) -> std::io::Result<()> { + Ok(()) +} + +/// Download config web method +pub fn download_new_config(sender: Sender) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move |id: u128| { + match download_config(id) { + Ok(meta) => { + let (tx, rx) = mpsc::channel(); + let callback = move |values: Vec| { + tx.send(values) + .expect("download_new_config callback send failed") + }; + sender2 + .lock() + .unwrap() + .send(ApiMessage::General(GeneralMessage::AddVariant( + web_config_to_settings_json(meta), + Box::new(callback), + ))) + .expect("download_new_config send failed"); + return rx.recv().expect("download_new_config callback recv failed"); + } + Err(e) => { + log::error!("Invalid response from download: {}", e); + } + } + vec![] + } + }; + super::async_utils::AsyncIsh { + trans_setter: |params| { + if let Some(Primitive::String(id)) = params.get(0) { + match id.parse::() { + Ok(id) => Ok(id), + Err(e) => Err(format!( + "download_new_config non-u128 string parameter 0: {} (got `{}`)", + e, id + )), + } + } else { + Err("download_new_config missing/invalid parameter 0".to_owned()) + } + }, + set_get: getter, + trans_getter: |result| { + let mut output = Vec::with_capacity(result.len()); + for status in result.iter() { + output.push(Primitive::Json( + serde_json::to_string(status) + .expect("Failed to serialize variant info to JSON"), + )); + } + output + }, + } +} + +/// Upload currently-loaded variant +pub fn upload_current_variant(sender: Sender) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move |(steam_id, steam_username): (String, String)| { + sender2 + .lock() + .unwrap() + .send(ApiMessage::UploadCurrentVariant(steam_id, steam_username)) + .expect("upload_current_variant send failed"); + true + } + }; + super::async_utils::AsyncIsh { + trans_setter: |params| { + if let Some(Primitive::String(steam_id)) = params.get(0) { + if let Some(Primitive::String(steam_username)) = params.get(1) { + Ok((steam_id.to_owned(), steam_username.to_owned())) + } else { + Err("upload_current_variant missing/invalid parameter 1".to_owned()) + } + } else { + Err("upload_current_variant missing/invalid parameter 0".to_owned()) + } + }, + set_get: getter, + trans_getter: |result| vec![result.into()], + } +} diff --git a/backend/src/cli/args.rs b/backend/src/cli/args.rs new file mode 100644 index 0000000..3c10a59 --- /dev/null +++ b/backend/src/cli/args.rs @@ -0,0 +1,39 @@ +use clap::{Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + /// TCP port for front-end client connection + #[arg(long)] + pub port: Option, + + /// Override log file location + #[arg(long)] + pub log: Option, + + /// Force verbose logging + #[arg(short, long)] + pub verbose: bool, + + /// Specail operation to perform + #[command(subcommand)] + pub op: Option, +} + +impl Args { + pub fn load() -> Self { + Self::parse() + } + + pub fn is_default(&self) -> bool { + self.port.is_none() && self.log.is_none() && !self.verbose && self.op.is_none() + } +} + +#[derive(Subcommand, Debug)] +pub enum Operation { + /// Dump useful system information for adding new device support + DumpSys, + /// Remove all files created by PowerTools, not including $HOME/homebrew/plugins/PowerTools/ + Clean, +} diff --git a/backend/src/cli/clean.rs b/backend/src/cli/clean.rs new file mode 100644 index 0000000..590e61a --- /dev/null +++ b/backend/src/cli/clean.rs @@ -0,0 +1,23 @@ +pub fn clean_up() -> Result<(), ()> { + let dirs = vec![ + crate::utility::settings_dir_old(), + crate::utility::settings_dir(), + ]; + + if let Err(e) = clean_up_io(dirs.iter()) { + log::error!("Error removing directories: {}", e); + Err(()) + } else { + Ok(()) + } +} + +fn clean_up_io( + directories: impl Iterator>, +) -> std::io::Result<()> { + let results = directories.map(|dir| std::fs::remove_dir_all(dir)); + for res in results { + res?; + } + Ok(()) +} diff --git a/backend/src/cli/dump_sys.rs b/backend/src/cli/dump_sys.rs new file mode 100644 index 0000000..c626581 --- /dev/null +++ b/backend/src/cli/dump_sys.rs @@ -0,0 +1,74 @@ +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::mpsc::{channel, Sender}; +use std::thread::{self, JoinHandle}; + +pub fn dump_sys_info() -> Result<(), ()> { + let (tx, rx) = channel(); + let mut join_handles = Vec::new(); + let useful_files = vec![ + PathBuf::from("/proc/ioports"), + PathBuf::from("/proc/cpuinfo"), + PathBuf::from("/etc/os-release"), + ]; + for file in useful_files { + join_handles.push(read_file(file, tx.clone())); + } + + let useful_commands = vec!["dmidecode"]; + for cmd in useful_commands.into_iter() { + join_handles.push(execute_command(cmd, tx.clone())); + } + + for join_handle in join_handles.into_iter() { + if let Err(e) = join_handle.join() { + log::error!("Thread failed to complete: {:?}", e); + } + } + + let mut dump_file = + std::fs::File::create("powertools_sys_dump.txt").expect("Failed to create dump file"); + for response in rx.into_iter() { + dump_file + .write( + &format!( + "{} v{} ###### {} ######\n{}\n", + crate::consts::PACKAGE_NAME, + crate::consts::PACKAGE_VERSION, + response.0, + response.1.unwrap_or("[None]".to_owned()) + ) + .into_bytes(), + ) + .expect("Failed to write to dump file"); + } + Ok(()) +} + +fn read_file( + file: impl AsRef + Send + 'static, + tx: Sender<(String, Option)>, +) -> JoinHandle<()> { + thread::spawn(move || { + let file = file.as_ref(); + tx.send(( + file.display().to_string(), + std::fs::read_to_string(file).ok(), + )) + .expect("Failed to send file contents"); + }) +} + +fn execute_command(command: &'static str, tx: Sender<(String, Option)>) -> JoinHandle<()> { + thread::spawn(move || { + tx.send(( + command.to_owned(), + Command::new(command) + .output() + .map(|out| String::from_utf8_lossy(&out.stdout).into_owned()) + .ok(), + )) + .expect("Failed to send command output"); + }) +} diff --git a/backend/src/cli/mod.rs b/backend/src/cli/mod.rs new file mode 100644 index 0000000..720332d --- /dev/null +++ b/backend/src/cli/mod.rs @@ -0,0 +1,12 @@ +mod args; +mod clean; +mod dump_sys; + +pub use args::{Args, Operation}; + +pub fn do_op(op: &Operation) -> Result<(), ()> { + match op { + Operation::DumpSys => dump_sys::dump_sys_info(), + Operation::Clean => clean::clean_up(), + } +} diff --git a/backend/src/consts.rs b/backend/src/consts.rs index e296cbe..246b1bb 100644 --- a/backend/src/consts.rs +++ b/backend/src/consts.rs @@ -3,9 +3,20 @@ pub const PORT: u16 = 44443; pub const PACKAGE_NAME: &'static str = env!("CARGO_PKG_NAME"); pub const PACKAGE_VERSION: &'static str = env!("CARGO_PKG_VERSION"); -pub const DEFAULT_SETTINGS_FILE: &str = "default_settings.json"; +pub const DEFAULT_SETTINGS_FILE: &str = "default_settings.ron"; pub const DEFAULT_SETTINGS_NAME: &str = "Main"; +pub const DEFAULT_SETTINGS_VARIANT_NAME: &str = "Primary"; -pub const LIMITS_FILE: &str = "limits_cache.json"; +pub const LIMITS_FILE: &str = "limits_cache.ron"; +#[cfg(feature = "online")] +pub const LIMITS_REFRESH_PERIOD: std::time::Duration = std::time::Duration::from_secs(60 * 60 * 24); // 1 day +#[cfg(feature = "online")] +pub const LIMITS_STARTUP_WAIT: std::time::Duration = std::time::Duration::from_secs(60); // 1 minute +#[cfg(feature = "online")] +pub const LIMITS_CHECK_PERIOD: std::time::Duration = std::time::Duration::from_secs(5 * 60); // 5 minutes +pub const LIMITS_OVERRIDE_FILE: &str = "limits_override.ron"; + +#[cfg(feature = "online")] +pub const WEB_SETTINGS_CACHE: &str = "store_cache.ron"; pub const MESSAGE_SEEN_ID_FILE: &str = "seen_message.bin"; diff --git a/backend/src/main.rs b/backend/src/main.rs index 6eb6d99..b15fefb 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,4 +1,5 @@ mod api; +mod cli; mod persist; mod settings; mod state; @@ -19,12 +20,18 @@ use usdpl_back::core::serdes::Primitive; use usdpl_back::Instance; fn main() -> Result<(), ()> { + let args = cli::Args::load(); #[cfg(debug_assertions)] let log_filepath = usdpl_back::api::dirs::home() .unwrap_or_else(|| "/tmp/".into()) .join(PACKAGE_NAME.to_owned() + ".log"); #[cfg(not(debug_assertions))] let log_filepath = std::path::Path::new("/tmp").join(format!("{}.log", PACKAGE_NAME)); + let log_filepath = args.log.clone().unwrap_or(log_filepath); + println!("Logging to: {:?}", log_filepath); + if !args.is_default() { + println!("CLI arguments, as parsed: {:?}", &args); + } #[cfg(debug_assertions)] let old_log_filepath = usdpl_back::api::dirs::home() .unwrap_or_else(|| "/tmp/".into()) @@ -43,15 +50,27 @@ fn main() -> Result<(), ()> { }, #[cfg(not(debug_assertions))] { - LevelFilter::Info + if args.verbose { + LevelFilter::Debug + } else { + LevelFilter::Info + } }, Default::default(), - std::fs::File::create(&log_filepath).unwrap(), + std::fs::File::create(&log_filepath).expect("Failed to create log file"), //std::fs::File::create("/home/deck/powertools-rs.log").unwrap(), ) .unwrap(); log::debug!("Logging to: {:?}.", log_filepath); - println!("Logging to: {:?}", log_filepath); + log::info!("CLI arguments, as parsed: {:?}", &args); + + // sepcial operation start-up + if let Some(op) = &args.op { + return cli::do_op(op); + // do not continue with regular startup + } + + // regular start-up log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); log::info!( @@ -75,18 +94,44 @@ fn main() -> Result<(), ()> { let _limits_handle = crate::settings::limits_worker_spawn(); let mut loaded_settings = - persist::SettingsJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE)) - .map(|settings| settings::Settings::from_json(settings, DEFAULT_SETTINGS_FILE.into())) + persist::FileJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE)) + .map(|mut file| { + let mut keys: Vec = file.variants.keys().map(|x| *x).collect(); + keys.sort(); + keys.get(0) + .and_then(|id| file.variants.remove(id)) + .map(|settings| { + settings::Settings::from_json( + DEFAULT_SETTINGS_NAME.into(), + settings, + DEFAULT_SETTINGS_FILE.into(), + 0, + ) + }) + .unwrap_or_else(|| { + settings::Settings::system_default( + DEFAULT_SETTINGS_FILE.into(), + 0, + DEFAULT_SETTINGS_NAME.into(), + 0, + DEFAULT_SETTINGS_VARIANT_NAME.into(), + ) + }) + }) .unwrap_or_else(|_| { settings::Settings::system_default( DEFAULT_SETTINGS_FILE.into(), + 0, DEFAULT_SETTINGS_NAME.into(), + 0, + DEFAULT_SETTINGS_VARIANT_NAME.into(), ) }); log::info!( "Detected device automatically {:?}, using driver: {:?} (This can be overriden)", - crate::settings::auto_detect_provider(), loaded_settings.cpus.provider() + crate::settings::auto_detect_provider(), + loaded_settings.cpus.provider() ); log::debug!("Settings: {:?}", loaded_settings); @@ -99,7 +144,7 @@ fn main() -> Result<(), ()> { let (message_getter, message_dismisser) = api::message::MessageHandler::new().to_callables(); - let instance = Instance::new(PORT) + let instance = Instance::new(args.port.unwrap_or(PORT)) .register("V_INFO", |_: Vec| { #[cfg(debug_assertions)] { @@ -233,6 +278,10 @@ fn main() -> Result<(), ()> { "GPU_get_slow_memory", api::gpu::get_slow_memory(api_sender.clone()), ) + .register( + "GPU_unset_slow_memory", + api::gpu::unset_slow_memory(api_sender.clone()), + ) // general API functions .register( "GENERAL_set_persistent", @@ -246,6 +295,10 @@ fn main() -> Result<(), ()> { "GENERAL_load_settings", api::general::load_settings(api_sender.clone()), ) + .register( + "GENERAL_load_variant", + api::general::load_variant(api_sender.clone()), + ) .register( "GENERAL_load_default_settings", api::general::load_default_settings(api_sender.clone()), @@ -289,10 +342,35 @@ fn main() -> Result<(), ()> { ) .register_async( "GENERAL_get_periodicals", - api::general::get_periodicals(api_sender.clone()) + api::general::get_periodicals(api_sender.clone()), + ) + .register_async( + "GENERAL_get_all_variants", + api::general::get_all_variants(api_sender.clone()), + ) + .register_async( + "GENERAL_get_current_variant", + api::general::get_current_variant(api_sender.clone()), ) .register_async("MESSAGE_get", message_getter) - .register_async("MESSAGE_dismiss", message_dismisser); + .register_async("MESSAGE_dismiss", message_dismisser) + .register_async("WEB_search_by_app", api::web::search_by_app_id()) + .register_async( + "WEB_download_new", + api::web::download_new_config(api_sender.clone()), + ) + .register_async( + "WEB_upload_new", + api::web::upload_current_variant(api_sender.clone()), + ); + + utility::ioperm_power_ec(); + + #[cfg(debug_assertions)] + std::thread::spawn(|| { + utility::ioperm_power_ec(); + settings::steam_deck::flash_led(); + }); if let Err(e) = loaded_settings.on_set() { e.iter() diff --git a/backend/src/persist/driver.rs b/backend/src/persist/driver.rs index 1a774bd..7e18a8b 100644 --- a/backend/src/persist/driver.rs +++ b/backend/src/persist/driver.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; pub enum DriverJson { #[serde(rename = "steam-deck", alias = "gabe-boy")] SteamDeck, - #[serde(rename = "steam-deck-oc", alias = "gabe-boy-advance")] - SteamDeckAdvance, + #[serde(rename = "steam-deck-oled", alias = "gabe-boy-sp")] + SteamDeckOLED, #[serde(rename = "generic")] Generic, #[serde(rename = "generic-amd")] @@ -17,4 +17,6 @@ pub enum DriverJson { #[default] #[serde(rename = "auto")] AutoDetect, + #[serde(rename = "indev")] + DevMode, } diff --git a/backend/src/persist/error.rs b/backend/src/persist/error.rs index 2dcb6fa..502d199 100644 --- a/backend/src/persist/error.rs +++ b/backend/src/persist/error.rs @@ -1,10 +1,10 @@ #[derive(Debug)] -pub enum JsonError { - Serde(serde_json::Error), +pub enum SerdeError { + Serde(RonError), Io(std::io::Error), } -impl std::fmt::Display for JsonError { +impl std::fmt::Display for SerdeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::Serde(e) => (e as &dyn std::fmt::Display).fmt(f), @@ -12,3 +12,32 @@ impl std::fmt::Display for JsonError { } } } + +impl std::error::Error for SerdeError {} + +#[derive(Debug)] +pub enum RonError { + General(ron::error::Error), + Spanned(ron::error::SpannedError), +} + +impl std::fmt::Display for RonError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::General(e) => (e as &dyn std::fmt::Display).fmt(f), + Self::Spanned(e) => (e as &dyn std::fmt::Display).fmt(f), + } + } +} + +impl From for RonError { + fn from(value: ron::error::Error) -> Self { + Self::General(value) + } +} + +impl From for RonError { + fn from(value: ron::error::SpannedError) -> Self { + Self::Spanned(value) + } +} diff --git a/backend/src/persist/file.rs b/backend/src/persist/file.rs new file mode 100644 index 0000000..9720b1e --- /dev/null +++ b/backend/src/persist/file.rs @@ -0,0 +1,115 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use super::SerdeError; +use super::SettingsJson; + +#[derive(Serialize, Deserialize)] +pub struct FileJson { + pub version: u64, + pub name: String, + pub app_id: u64, + pub variants: HashMap, +} + +impl FileJson { + pub fn save>(&self, path: P) -> Result<(), SerdeError> { + let path = path.as_ref(); + + if !self.variants.is_empty() { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).map_err(SerdeError::Io)?; + } + let mut file = std::fs::File::create(path).map_err(SerdeError::Io)?; + ron::ser::to_writer_pretty(&mut file, &self, crate::utility::ron_pretty_config()) + .map_err(|e| SerdeError::Serde(e.into())) + } else { + if path.exists() { + // remove settings file when persistence is turned off, to prevent it from be loaded next time. + std::fs::remove_file(path).map_err(SerdeError::Io) + } else { + Ok(()) + } + } + } + + pub fn open>(path: P) -> Result { + let mut file = std::fs::File::open(path).map_err(SerdeError::Io)?; + ron::de::from_reader(&mut file).map_err(|e| SerdeError::Serde(e.into())) + } + + fn next_available_id(&self) -> u64 { + self.variants.keys().max().map(|k| k + 1).unwrap_or(0) + } + + pub fn update_variant_or_create>( + path: P, + app_id: u64, + mut setting: SettingsJson, + app_name: String, + ) -> Result<(Self, SettingsJson), SerdeError> { + // returns (Self, updated/created variant id) + let path = path.as_ref(); + if !setting.persistent { + let mut file = Self::open(path)?; + + if file.variants.contains_key(&setting.variant) { + file.variants.remove(&setting.variant); + file.save(path)?; + } + return Ok((file, setting)); + } + + let (file, variant_id) = if path.exists() { + let mut file = Self::open(path)?; + // Generate new (available) id if max + if setting.variant == u64::MAX { + setting.variant = file.next_available_id(); + } + // Generate new name if empty + if setting.name.is_empty() { + setting.name = format!("Variant {}", setting.variant); + } + log::debug!( + "Inserting setting variant `{}` ({}) for app `{}` ({})", + setting.name, + setting.variant, + file.name, + app_id + ); + file.variants.insert(setting.variant, setting.clone()); + (file, setting) + } else { + // Generate new id if max + if setting.variant == u64::MAX { + setting.variant = 1; + } + // Generate new name if empty + if setting.name.is_empty() { + setting.name = format!("Variant {}", setting.variant); + } + log::debug!( + "Creating new setting variant `{}` ({}) for app `{}` ({})", + setting.name, + setting.variant, + app_name, + app_id + ); + let mut setting_variants = HashMap::with_capacity(1); + setting_variants.insert(setting.variant, setting.clone()); + ( + Self { + version: 0, + app_id: app_id, + name: app_name, + variants: setting_variants, + }, + setting, + ) + }; + + file.save(path)?; + Ok((file, variant_id)) + } +} diff --git a/backend/src/persist/general.rs b/backend/src/persist/general.rs index 58497ec..8cc93b7 100644 --- a/backend/src/persist/general.rs +++ b/backend/src/persist/general.rs @@ -2,81 +2,37 @@ use std::default::Default; use serde::{Deserialize, Serialize}; -use super::JsonError; use super::{BatteryJson, CpuJson, DriverJson, GpuJson}; -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct OnEventJson { - pub on_save: Option, - pub on_load: Option, - pub on_set: Option, - pub on_resume: Option, -} - -impl Default for OnEventJson { - fn default() -> Self { - Self { - on_save: None, - on_load: None, - on_set: None, - on_resume: None, - } - } -} - -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct SettingsJson { pub version: u64, pub name: String, + pub variant: u64, pub persistent: bool, pub cpus: Vec, pub gpu: GpuJson, pub battery: BatteryJson, pub provider: Option, - pub events: Option, + pub tags: Vec, } impl Default for SettingsJson { fn default() -> Self { Self { version: 0, - name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), + name: crate::consts::DEFAULT_SETTINGS_VARIANT_NAME.to_owned(), + variant: 0, persistent: false, cpus: Vec::with_capacity(8), gpu: GpuJson::default(), battery: BatteryJson::default(), provider: None, - events: None, + tags: Vec::new(), } } } -impl SettingsJson { - pub fn save>(&self, path: P) -> Result<(), JsonError> { - let path = path.as_ref(); - - if self.persistent { - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent).map_err(JsonError::Io)?; - } - let mut file = std::fs::File::create(path).map_err(JsonError::Io)?; - serde_json::to_writer_pretty(&mut file, &self).map_err(JsonError::Serde) - } else { - if path.exists() { - // remove settings file when persistence is turned off, to prevent it from be loaded next time. - std::fs::remove_file(path).map_err(JsonError::Io) - } else { - Ok(()) - } - } - } - - pub fn open>(path: P) -> Result { - let mut file = std::fs::File::open(path).map_err(JsonError::Io)?; - serde_json::from_reader(&mut file).map_err(JsonError::Serde) - } -} - #[derive(Serialize, Deserialize, Clone)] pub struct MinMaxJson { pub max: Option, diff --git a/backend/src/persist/gpu.rs b/backend/src/persist/gpu.rs index 7755847..57df0f2 100644 --- a/backend/src/persist/gpu.rs +++ b/backend/src/persist/gpu.rs @@ -8,8 +8,10 @@ use serde::{Deserialize, Serialize}; pub struct GpuJson { pub fast_ppt: Option, pub slow_ppt: Option, + pub tdp: Option, + pub tdp_boost: Option, pub clock_limits: Option>, - pub slow_memory: bool, + pub memory_clock: Option, #[serde(skip_serializing_if = "Option::is_none")] pub root: Option, } @@ -19,8 +21,10 @@ impl Default for GpuJson { Self { fast_ppt: None, slow_ppt: None, + tdp: None, + tdp_boost: None, clock_limits: None, - slow_memory: false, + memory_clock: None, root: None, } } diff --git a/backend/src/persist/mod.rs b/backend/src/persist/mod.rs index 17dfa35..bfc0b2a 100644 --- a/backend/src/persist/mod.rs +++ b/backend/src/persist/mod.rs @@ -2,13 +2,17 @@ mod battery; mod cpu; mod driver; mod error; +mod file; mod general; mod gpu; pub use battery::{BatteryEventJson, BatteryJson}; pub use cpu::CpuJson; pub use driver::DriverJson; -pub use general::{MinMaxJson, OnEventJson, SettingsJson}; +pub use file::FileJson; +pub use general::{MinMaxJson, SettingsJson}; pub use gpu::GpuJson; -pub use error::JsonError; +pub use error::SerdeError; + +pub const LATEST_VERSION: u64 = 0; diff --git a/backend/src/settings/detect/auto_detect.rs b/backend/src/settings/detect/auto_detect.rs index e66394a..5773f12 100644 --- a/backend/src/settings/detect/auto_detect.rs +++ b/backend/src/settings/detect/auto_detect.rs @@ -2,32 +2,36 @@ use std::fs::File; use regex::RegexBuilder; -use limits_core::json::{BatteryLimit, CpuLimit, GpuLimit, Limits}; +use limits_core::json_v2::{BatteryLimitType, CpuLimitType, GpuLimitType, Limits}; use crate::persist::{DriverJson, SettingsJson}; -use crate::settings::{Driver, General, TBattery, TCpus, TGeneral, TGpu}; +use crate::settings::{Driver, General, ProviderBuilder, TBattery, TCpus, TGeneral, TGpu}; -fn get_limits() -> limits_core::json::Base { - let limits_path = super::utility::limits_path(); - match File::open(&limits_path) { - Ok(f) => match serde_json::from_reader(f) { - Ok(lim) => lim, +fn get_limits() -> limits_core::json_v2::Base { + super::limits_worker::get_limits_cached() +} + +fn get_limits_overrides() -> Option { + let limits_override_path = super::utility::limits_override_path(); + match File::open(&limits_override_path) { + Ok(f) => match ron::de::from_reader(f) { + Ok(lim) => Some(lim), Err(e) => { log::warn!( - "Failed to parse limits file `{}`, cannot use for auto_detect: {}", - limits_path.display(), + "Failed to parse limits override file `{}`, cannot use for auto_detect: {}", + limits_override_path.display(), e ); - limits_core::json::Base::default() + None } }, Err(e) => { - log::warn!( - "Failed to open limits file `{}`: {}", - limits_path.display(), + log::info!( + "Failed to open limits override file `{}`: {}", + limits_override_path.display(), e ); - super::limits_worker::get_limits_cached() + None } } } @@ -37,7 +41,10 @@ pub fn auto_detect_provider() -> DriverJson { let provider = auto_detect0( None, crate::utility::settings_dir().join("autodetect.json"), + 0, "".to_owned(), + 0, + crate::consts::DEFAULT_SETTINGS_VARIANT_NAME.to_owned(), ) .battery .provider(); @@ -47,11 +54,23 @@ pub fn auto_detect_provider() -> DriverJson { /// Device detection logic pub fn auto_detect0( - settings_opt: Option, + settings_opt: Option<&SettingsJson>, json_path: std::path::PathBuf, + app_id: u64, name: String, + variant_id: u64, + variant_name: String, ) -> Driver { - let mut builder = DriverBuilder::new(json_path, name); + let mut general_driver = Box::new(General { + persistent: false, + path: json_path, + app_id, + name, + variant_id, + variant_name, + driver: DriverJson::AutoDetect, + tags: settings_opt.map(|s| s.tags.clone()).unwrap_or_else(|| Vec::new()), + }); let cpu_info: String = usdpl_back::api::files::read_single("/proc/cpuinfo").unwrap_or_default(); log::debug!("Read from /proc/cpuinfo:\n{}", cpu_info); @@ -65,268 +84,272 @@ pub fn auto_detect0( log::debug!("Read dmidecode:\n{}", dmi_info); let limits = get_limits(); + let limits_override = get_limits_overrides(); // build driver based on limits conditions for conf in limits.configs { let conditions = conf.conditions; let mut matches = true; - if conditions.is_empty() { - matches = !builder.is_complete(); - } else { - if let Some(dmi) = &conditions.dmi { - let pattern = RegexBuilder::new(dmi) - .multi_line(true) - .build() - .expect("Invalid DMI regex"); - matches &= pattern.is_match(&dmi_info); - } - if let Some(cpuinfo) = &conditions.cpuinfo { - let pattern = RegexBuilder::new(cpuinfo) - .multi_line(true) - .build() - .expect("Invalid CPU regex"); - matches &= pattern.is_match(&cpu_info); - } - if let Some(os) = &conditions.os { - let pattern = RegexBuilder::new(os) - .multi_line(true) - .build() - .expect("Invalid OS regex"); - matches &= pattern.is_match(&os_info); - } - if let Some(cmd) = &conditions.command { - match std::process::Command::new("bash") - .args(["-c", cmd]) - .status() - { - Ok(status) => matches &= status.code().map(|c| c == 0).unwrap_or(false), - Err(e) => log::warn!("Ignoring bash limits error: {}", e), - } - } - if let Some(file_exists) = &conditions.file_exists { - let exists = std::path::Path::new(file_exists).exists(); - matches &= exists; - } + if let Some(dmi) = &conditions.dmi { + let pattern = RegexBuilder::new(dmi) + .multi_line(true) + .build() + .expect("Invalid DMI regex"); + matches &= pattern.is_match(&dmi_info); } + if let Some(cpuinfo) = &conditions.cpuinfo { + let pattern = RegexBuilder::new(cpuinfo) + .multi_line(true) + .build() + .expect("Invalid CPU regex"); + matches &= pattern.is_match(&cpu_info); + } + if let Some(os) = &conditions.os { + let pattern = RegexBuilder::new(os) + .multi_line(true) + .build() + .expect("Invalid OS regex"); + matches &= pattern.is_match(&os_info); + } + if let Some(file_exists) = &conditions.file_exists { + let exists = std::path::Path::new(file_exists).exists(); + matches &= exists; + } + if matches { + let mut relevant_limits = conf.limits.clone(); + relevant_limits.apply_override(limits_override); if let Some(settings) = &settings_opt { - *builder.general.persistent() = true; - builder.general.name(settings.name.clone()); - for limit in conf.limits { - match limit { - Limits::Cpu(cpus) => { - let cpu_driver: Box = match cpus { - CpuLimit::SteamDeck => { - Box::new(crate::settings::steam_deck::Cpus::from_json( - settings.cpus.clone(), - settings.version, - )) - } - CpuLimit::SteamDeckAdvance => { - Box::new(crate::settings::steam_deck::Cpus::from_json( - settings.cpus.clone(), - settings.version, - )) - } - CpuLimit::Generic(x) => Box::new(crate::settings::generic::Cpus::< - crate::settings::generic::Cpu, - >::from_json_and_limits( - settings.cpus.clone(), - settings.version, - x, - )), - CpuLimit::GenericAMD(x) => Box::new( - crate::settings::generic_amd::Cpus::from_json_and_limits( - settings.cpus.clone(), - settings.version, - x, - ), - ), - CpuLimit::Unknown => { - Box::new(crate::settings::unknown::Cpus::from_json( - settings.cpus.clone(), - settings.version, - )) - } - }; - builder.cpus = Some(cpu_driver); - } - Limits::Gpu(gpu) => { - let driver: Box = match gpu { - GpuLimit::SteamDeck => { - Box::new(crate::settings::steam_deck::Gpu::from_json( - settings.gpu.clone(), - settings.version, - )) - } - GpuLimit::SteamDeckAdvance => { - Box::new(crate::settings::steam_deck::Gpu::from_json( - settings.gpu.clone(), - settings.version, - )) - } - GpuLimit::Generic(x) => { - Box::new(crate::settings::generic::Gpu::from_json_and_limits( - settings.gpu.clone(), - settings.version, - x, - )) - } - GpuLimit::GenericAMD(x) => Box::new( - crate::settings::generic_amd::Gpu::from_json_and_limits( - settings.gpu.clone(), - settings.version, - x, - ), - ), - GpuLimit::Unknown => { - Box::new(crate::settings::unknown::Gpu::from_json( - settings.gpu.clone(), - settings.version, - )) - } - }; - builder.gpu = Some(driver); - } - Limits::Battery(batt) => { - let driver: Box = match batt { - BatteryLimit::SteamDeck => { - Box::new(crate::settings::steam_deck::Battery::from_json( - settings.battery.clone(), - settings.version, - )) - } - BatteryLimit::SteamDeckAdvance => { - Box::new(crate::settings::steam_deck::Battery::from_json( - settings.battery.clone(), - settings.version, - )) - } - BatteryLimit::Generic(x) => Box::new( - crate::settings::generic::Battery::from_json_and_limits( - settings.battery.clone(), - settings.version, - x, - ), - ), - BatteryLimit::Unknown => { - Box::new(crate::settings::unknown::Battery) - } - }; - builder.battery = Some(driver); - } + *general_driver.persistent() = true; + let cpu_driver: Box = match relevant_limits.cpu.provider { + CpuLimitType::SteamDeck => Box::new( + crate::settings::steam_deck::Cpus::from_json_and_limits( + settings.cpus.clone(), + settings.version, + relevant_limits.cpu.limits, + ) + .variant(super::super::steam_deck::Model::LCD), + ), + CpuLimitType::SteamDeckOLED => Box::new( + crate::settings::steam_deck::Cpus::from_json_and_limits( + settings.cpus.clone(), + settings.version, + relevant_limits.cpu.limits, + ) + .variant(super::super::steam_deck::Model::OLED), + ), + CpuLimitType::Generic => Box::new(crate::settings::generic::Cpus::< + crate::settings::generic::Cpu, + >::from_json_and_limits( + settings.cpus.clone(), + settings.version, + relevant_limits.cpu.limits, + )), + CpuLimitType::GenericAMD => { + Box::new(crate::settings::generic_amd::Cpus::from_json_and_limits( + settings.cpus.clone(), + settings.version, + relevant_limits.cpu.limits, + )) } - } + CpuLimitType::Unknown => { + Box::new(crate::settings::unknown::Cpus::from_json_and_limits( + settings.cpus.clone(), + settings.version, + relevant_limits.cpu.limits, + )) + } + CpuLimitType::DevMode => { + Box::new(crate::settings::dev_mode::Cpus::from_json_and_limits( + settings.cpus.clone(), + settings.version, + relevant_limits.cpu.limits, + )) + } + }; + + let gpu_driver: Box = match relevant_limits.gpu.provider { + GpuLimitType::SteamDeck => Box::new( + crate::settings::steam_deck::Gpu::from_json_and_limits( + settings.gpu.clone(), + settings.version, + relevant_limits.gpu.limits, + ) + .variant(super::super::steam_deck::Model::LCD), + ), + GpuLimitType::SteamDeckOLED => Box::new( + crate::settings::steam_deck::Gpu::from_json_and_limits( + settings.gpu.clone(), + settings.version, + relevant_limits.gpu.limits, + ) + .variant(super::super::steam_deck::Model::OLED), + ), + GpuLimitType::Generic => { + Box::new(crate::settings::generic::Gpu::from_json_and_limits( + settings.gpu.clone(), + settings.version, + relevant_limits.gpu.limits, + )) + } + GpuLimitType::GenericAMD => { + Box::new(crate::settings::generic_amd::Gpu::from_json_and_limits( + settings.gpu.clone(), + settings.version, + relevant_limits.gpu.limits, + )) + } + GpuLimitType::Unknown => { + Box::new(crate::settings::unknown::Gpu::from_json_and_limits( + settings.gpu.clone(), + settings.version, + relevant_limits.gpu.limits, + )) + } + GpuLimitType::DevMode => { + Box::new(crate::settings::dev_mode::Gpu::from_json_and_limits( + settings.gpu.clone(), + settings.version, + relevant_limits.gpu.limits, + )) + } + }; + let battery_driver: Box = match relevant_limits.battery.provider { + BatteryLimitType::SteamDeck => Box::new( + crate::settings::steam_deck::Battery::from_json_and_limits( + settings.battery.clone(), + settings.version, + relevant_limits.battery.limits, + ) + .variant(super::super::steam_deck::Model::LCD), + ), + BatteryLimitType::SteamDeckOLED => Box::new( + crate::settings::steam_deck::Battery::from_json_and_limits( + settings.battery.clone(), + settings.version, + relevant_limits.battery.limits, + ) + .variant(super::super::steam_deck::Model::OLED), + ), + BatteryLimitType::Generic => { + Box::new(crate::settings::generic::Battery::from_json_and_limits( + settings.battery.clone(), + settings.version, + relevant_limits.battery.limits, + )) + } + BatteryLimitType::Unknown => { + Box::new(crate::settings::unknown::Battery::from_json_and_limits( + settings.battery.clone(), + settings.version, + relevant_limits.battery.limits, + )) + } + BatteryLimitType::DevMode => { + Box::new(crate::settings::dev_mode::Battery::from_json_and_limits( + settings.battery.clone(), + settings.version, + relevant_limits.battery.limits, + )) + } + }; + + return Driver { + general: general_driver, + cpus: cpu_driver, + gpu: gpu_driver, + battery: battery_driver, + }; } else { - for limit in conf.limits { - match limit { - Limits::Cpu(cpus) => { - let cpu_driver: Box = match cpus { - CpuLimit::SteamDeck => { - Box::new(crate::settings::steam_deck::Cpus::system_default()) - } - CpuLimit::SteamDeckAdvance => { - Box::new(crate::settings::steam_deck::Cpus::system_default()) - } - CpuLimit::Generic(x) => { - Box::new(crate::settings::generic::Cpus::< - crate::settings::generic::Cpu, - >::from_limits(x)) - } - CpuLimit::GenericAMD(x) => { - Box::new(crate::settings::generic_amd::Cpus::from_limits(x)) - } - CpuLimit::Unknown => { - Box::new(crate::settings::unknown::Cpus::system_default()) - } - }; - builder.cpus = Some(cpu_driver); - } - Limits::Gpu(gpu) => { - let driver: Box = match gpu { - GpuLimit::SteamDeck => { - Box::new(crate::settings::steam_deck::Gpu::system_default()) - } - GpuLimit::SteamDeckAdvance => { - Box::new(crate::settings::steam_deck::Gpu::system_default()) - } - GpuLimit::Generic(x) => { - Box::new(crate::settings::generic::Gpu::from_limits(x)) - } - GpuLimit::GenericAMD(x) => { - Box::new(crate::settings::generic_amd::Gpu::from_limits(x)) - } - GpuLimit::Unknown => { - Box::new(crate::settings::unknown::Gpu::system_default()) - } - }; - builder.gpu = Some(driver); - } - Limits::Battery(batt) => { - let driver: Box = match batt { - BatteryLimit::SteamDeck => { - Box::new(crate::settings::steam_deck::Battery::system_default()) - } - BatteryLimit::SteamDeckAdvance => { - Box::new(crate::settings::steam_deck::Battery::system_default()) - } - BatteryLimit::Generic(x) => { - Box::new(crate::settings::generic::Battery::from_limits(x)) - } - BatteryLimit::Unknown => { - Box::new(crate::settings::unknown::Battery) - } - }; - builder.battery = Some(driver); - } + let cpu_driver: Box = match relevant_limits.cpu.provider { + CpuLimitType::SteamDeck => Box::new( + crate::settings::steam_deck::Cpus::from_limits(relevant_limits.cpu.limits) + .variant(super::super::steam_deck::Model::LCD), + ), + CpuLimitType::SteamDeckOLED => Box::new( + crate::settings::steam_deck::Cpus::from_limits(relevant_limits.cpu.limits) + .variant(super::super::steam_deck::Model::OLED), + ), + CpuLimitType::Generic => Box::new(crate::settings::generic::Cpus::< + crate::settings::generic::Cpu, + >::from_limits( + relevant_limits.cpu.limits + )), + CpuLimitType::GenericAMD => Box::new( + crate::settings::generic_amd::Cpus::from_limits(relevant_limits.cpu.limits), + ), + CpuLimitType::Unknown => Box::new(crate::settings::unknown::Cpus::from_limits( + relevant_limits.cpu.limits, + )), + CpuLimitType::DevMode => Box::new( + crate::settings::dev_mode::Cpus::from_limits(relevant_limits.cpu.limits), + ), + }; + let gpu_driver: Box = match relevant_limits.gpu.provider { + GpuLimitType::SteamDeck => Box::new( + crate::settings::steam_deck::Gpu::from_limits(relevant_limits.gpu.limits) + .variant(super::super::steam_deck::Model::LCD), + ), + GpuLimitType::SteamDeckOLED => Box::new( + crate::settings::steam_deck::Gpu::from_limits(relevant_limits.gpu.limits) + .variant(super::super::steam_deck::Model::OLED), + ), + GpuLimitType::Generic => Box::new(crate::settings::generic::Gpu::from_limits( + relevant_limits.gpu.limits, + )), + GpuLimitType::GenericAMD => Box::new( + crate::settings::generic_amd::Gpu::from_limits(relevant_limits.gpu.limits), + ), + GpuLimitType::Unknown => Box::new(crate::settings::unknown::Gpu::from_limits( + relevant_limits.gpu.limits, + )), + GpuLimitType::DevMode => Box::new(crate::settings::dev_mode::Gpu::from_limits( + relevant_limits.gpu.limits, + )), + }; + let battery_driver: Box = match relevant_limits.battery.provider { + BatteryLimitType::SteamDeck => Box::new( + crate::settings::steam_deck::Battery::from_limits( + relevant_limits.battery.limits, + ) + .variant(super::super::steam_deck::Model::LCD), + ), + BatteryLimitType::SteamDeckOLED => Box::new( + crate::settings::steam_deck::Battery::from_limits( + relevant_limits.battery.limits, + ) + .variant(super::super::steam_deck::Model::OLED), + ), + BatteryLimitType::Generic => { + Box::new(crate::settings::generic::Battery::from_limits( + relevant_limits.battery.limits, + )) } - } + BatteryLimitType::Unknown => { + Box::new(crate::settings::unknown::Battery::from_limits( + relevant_limits.battery.limits, + )) + } + BatteryLimitType::DevMode => { + Box::new(crate::settings::dev_mode::Battery::from_limits( + relevant_limits.battery.limits, + )) + } + }; + return Driver { + general: general_driver, + cpus: cpu_driver, + gpu: gpu_driver, + battery: battery_driver, + }; } } } - builder.build() -} - -struct DriverBuilder { - general: Box, - cpus: Option>, - gpu: Option>, - battery: Option>, -} - -impl DriverBuilder { - fn new(json_path: std::path::PathBuf, profile_name: String) -> Self { - Self { - general: Box::new(General { - persistent: false, - path: json_path, - name: profile_name, - driver: DriverJson::AutoDetect, - events: Default::default(), - }), - cpus: None, - gpu: None, - battery: None, - } - } - - fn is_complete(&self) -> bool { - self.cpus.is_some() && self.gpu.is_some() && self.battery.is_some() - } - - fn build(self) -> Driver { - Driver { - general: self.general, - cpus: self - .cpus - .unwrap_or_else(|| Box::new(crate::settings::unknown::Cpus::system_default())), - gpu: self - .gpu - .unwrap_or_else(|| Box::new(crate::settings::unknown::Gpu::system_default())), - battery: self - .battery - .unwrap_or_else(|| Box::new(crate::settings::unknown::Battery)), - } + Driver { + general: general_driver, + cpus: Box::new(crate::settings::unknown::Cpus::system_default()), + gpu: Box::new(crate::settings::unknown::Gpu::system_default()), + battery: Box::new(crate::settings::unknown::Battery), } } diff --git a/backend/src/settings/detect/limits_worker.rs b/backend/src/settings/detect/limits_worker.rs index 8a52b96..9c671fa 100644 --- a/backend/src/settings/detect/limits_worker.rs +++ b/backend/src/settings/detect/limits_worker.rs @@ -1,52 +1,69 @@ use std::thread::{self, JoinHandle}; -#[cfg(feature = "online")] -use std::time::Duration; -use limits_core::json::Base; +use limits_core::json_v2::Base; + +#[inline] +fn expired_updated_time() -> chrono::DateTime { + chrono::offset::Utc::now() - (crate::consts::LIMITS_REFRESH_PERIOD * 2) +} #[cfg(feature = "online")] pub fn spawn() -> JoinHandle<()> { thread::spawn(move || { log::info!("limits_worker starting..."); - let sleep_dur = Duration::from_secs(60 * 60 * 24); // 1 day let limits_path = super::utility::limits_path(); + thread::sleep(crate::consts::LIMITS_STARTUP_WAIT); + log::info!("limits_worker completed startup wait"); loop { if (limits_path.exists() && limits_path.is_file()) || !limits_path.exists() { // try to load limits from file, fallback to built-in default - let base = if limits_path.exists() { + let mut base = if limits_path.exists() { match std::fs::File::open(&limits_path) { - Ok(f) => match serde_json::from_reader(f) { + Ok(f) => match ron::de::from_reader(f) { Ok(b) => b, Err(e) => { log::error!("Cannot parse {}: {}", limits_path.display(), e); - Base::default() + crate::utility::CachedData { + data: Base::default(), + updated: expired_updated_time(), + } } }, Err(e) => { log::error!("Cannot open {}: {}", limits_path.display(), e); - Base::default() + crate::utility::CachedData { + data: Base::default(), + updated: expired_updated_time(), + } } } } else { - let base = Base::default(); - save_base(&base, &limits_path); + let mut base = crate::utility::CachedData { + data: Base::default(), + updated: expired_updated_time(), + }; + save_base(&mut base, &limits_path); base }; - if let Some(refresh) = &base.refresh { - // try to retrieve newer version - match ureq::get(refresh).call() { - Ok(response) => { - let json_res: std::io::Result = response.into_json(); - match json_res { - Ok(new_base) => { - save_base(&new_base, &limits_path); - } - Err(e) => { - log::error!("Cannot parse response from `{}`: {}", refresh, e) + crate::api::web::set_base_url(base.data.store.clone()); + if let Some(refresh) = &base.data.refresh { + if base.needs_update(crate::consts::LIMITS_REFRESH_PERIOD) { + // try to retrieve newer version + match ureq::get(refresh).call() { + Ok(response) => { + let json_res: std::io::Result = response.into_json(); + match json_res { + Ok(new_base) => { + base.data = new_base; + save_base(&mut base, &limits_path); + } + Err(e) => { + log::error!("Cannot parse response from `{}`: {}", refresh, e) + } } } + Err(e) => log::warn!("Cannot download limits from `{}`: {}", refresh, e), } - Err(e) => log::warn!("Cannot download limits from `{}`: {}", refresh, e), } } else { log::info!("limits_worker refresh is empty, terminating..."); @@ -55,7 +72,7 @@ pub fn spawn() -> JoinHandle<()> { } else if !limits_path.is_file() { log::error!("Path for storing limits is not a file!"); } - thread::sleep(sleep_dur); + thread::sleep(crate::consts::LIMITS_CHECK_PERIOD); } log::warn!("limits_worker completed!"); }) @@ -68,39 +85,43 @@ pub fn spawn() -> JoinHandle<()> { }) } -pub fn get_limits_cached() -> Base { +pub fn get_limits_cached() -> Base { let limits_path = super::utility::limits_path(); - if limits_path.is_file() { + let cached: crate::utility::CachedData = if limits_path.is_file() { match std::fs::File::open(&limits_path) { - Ok(f) => match serde_json::from_reader(f) { + Ok(f) => match ron::de::from_reader(f) { Ok(b) => b, Err(e) => { log::error!("Cannot parse {}: {}", limits_path.display(), e); - Base::default() + return Base::default(); } }, Err(e) => { log::error!("Cannot open {}: {}", limits_path.display(), e); - Base::default() + return Base::default(); } } } else { - Base::default() - } + return Base::default(); + }; + cached.data } #[cfg(feature = "online")] -fn save_base(new_base: &Base, path: impl AsRef) { +fn save_base(new_base: &mut crate::utility::CachedData, path: impl AsRef) { let limits_path = path.as_ref(); + new_base.updated = chrono::offset::Utc::now(); match std::fs::File::create(&limits_path) { - Ok(f) => match serde_json::to_writer_pretty(f, &new_base) { - Ok(_) => log::info!("Successfully saved new limits to {}", limits_path.display()), - Err(e) => log::error!( - "Failed to save limits json to file `{}`: {}", - limits_path.display(), - e - ), - }, + Ok(f) => { + match ron::ser::to_writer_pretty(f, new_base, crate::utility::ron_pretty_config()) { + Ok(_) => log::info!("Successfully saved new limits to {}", limits_path.display()), + Err(e) => log::error!( + "Failed to save limits json to file `{}`: {}", + limits_path.display(), + e + ), + } + } Err(e) => log::error!("Cannot create {}: {}", limits_path.display(), e), } } diff --git a/backend/src/settings/detect/utility.rs b/backend/src/settings/detect/utility.rs index c278e66..27d37a8 100644 --- a/backend/src/settings/detect/utility.rs +++ b/backend/src/settings/detect/utility.rs @@ -1,14 +1,18 @@ -use limits_core::json::{DeveloperMessage, Base}; +use limits_core::json::{Base, DeveloperMessage}; pub fn limits_path() -> std::path::PathBuf { crate::utility::settings_dir().join(crate::consts::LIMITS_FILE) } +pub fn limits_override_path() -> std::path::PathBuf { + crate::utility::settings_dir().join(crate::consts::LIMITS_OVERRIDE_FILE) +} + // NOTE: eats errors pub fn get_dev_messages() -> Vec { let limits_path = limits_path(); if let Ok(file) = std::fs::File::open(&limits_path) { - if let Ok(base) = serde_json::from_reader::<_, Base>(file) { + if let Ok(base) = ron::de::from_reader::<_, Base>(file) { base.messages } else { vec![] diff --git a/backend/src/settings/dev_mode/battery.rs b/backend/src/settings/dev_mode/battery.rs new file mode 100644 index 0000000..a0fe208 --- /dev/null +++ b/backend/src/settings/dev_mode/battery.rs @@ -0,0 +1,184 @@ +use std::convert::Into; + +use limits_core::json_v2::GenericBatteryLimit; + +use crate::persist::BatteryJson; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{ProviderBuilder, TBattery}; + +#[derive(Clone)] +pub struct Battery { + persist: BatteryJson, + version: u64, + limits: GenericBatteryLimit, + charge_limit: Option, +} + +impl std::fmt::Debug for Battery { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("dev_mode_Battery") + //.field("persist", &self.persist) + .field("version", &self.version) + .field("limits", &self.limits) + .finish_non_exhaustive() + } +} + +impl Into for Battery { + #[inline] + fn into(self) -> BatteryJson { + self.persist + } +} + +impl ProviderBuilder for Battery { + fn from_json_and_limits( + persist: BatteryJson, + version: u64, + limits: GenericBatteryLimit, + ) -> Self { + Battery { + persist, + version, + limits, + charge_limit: None, + } + } + + fn from_limits(limits: GenericBatteryLimit) -> Self { + Battery { + persist: BatteryJson { + charge_rate: None, + charge_mode: None, + events: vec![], + root: None, + }, + version: 0, + limits, + charge_limit: None, + } + } +} + +impl OnSet for Battery { + fn on_set(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Battery::on_set(self)"); + Ok(()) + } +} + +impl OnResume for Battery { + fn on_resume(&self) -> Result<(), Vec> { + log::debug!("dev_mode_Battery::on_resume(self)"); + Ok(()) + } +} + +impl crate::settings::OnPowerEvent for Battery {} + +impl crate::settings::OnLoad for Battery { + fn on_load(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Battery::on_load(self)"); + Ok(()) + } +} + +impl crate::settings::OnUnload for Battery { + fn on_unload(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Battery::on_unload(self)"); + Ok(()) + } +} + +impl TBattery for Battery { + fn limits(&self) -> crate::api::BatteryLimits { + log::debug!("dev_mode_Battery::limits(self) -> {{...}}"); + crate::api::BatteryLimits { + charge_current: self.limits.charge_rate.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(11), + max: lim.max.unwrap_or(1111), + }), + charge_current_step: 10, + charge_modes: self.limits.charge_modes.clone(), + charge_limit: self.limits.charge_limit.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(2.0), + max: lim.max.unwrap_or(98.0), + }), + charge_limit_step: 1.0, + } + } + + fn json(&self) -> crate::persist::BatteryJson { + log::debug!("dev_mode_Battery::json(self) -> {{...}}"); + self.clone().into() + } + + fn charge_rate(&mut self, rate: Option) { + log::debug!("dev_mode_Battery::charge_rate(self, {:?})", rate); + self.persist.charge_rate = rate; + } + + fn get_charge_rate(&self) -> Option { + log::debug!( + "dev_mode_Battery::get_charge_rate(self) -> {:?}", + self.persist.charge_rate + ); + self.persist.charge_rate + } + + fn charge_mode(&mut self, rate: Option) { + log::debug!("dev_mode_Battery::charge_mode(self, {:?})", rate); + self.persist.charge_mode = rate; + } + + fn get_charge_mode(&self) -> Option { + log::debug!( + "dev_mode_Battery::get_charge_mode(self) -> {:?}", + self.persist.charge_mode + ); + self.persist.charge_mode.clone() + } + + fn read_charge_full(&self) -> Option { + log::debug!("dev_mode_Battery::read_charge_full(self) -> None"); + None + } + + fn read_charge_now(&self) -> Option { + log::debug!("dev_mode_Battery::read_charge_now(self) -> None"); + None + } + + fn read_charge_design(&self) -> Option { + log::debug!("dev_mode_Battery::read_charge_design(self) -> None"); + None + } + + fn read_current_now(&self) -> Option { + log::debug!("dev_mode_Battery::read_current_now(self) -> None"); + None + } + + fn read_charge_power(&self) -> Option { + log::debug!("dev_mode_Battery::read_charge_power(self) -> None"); + None + } + + fn charge_limit(&mut self, limit: Option) { + log::debug!("dev_mode_Battery::charge_limit(self, {:?})", limit); + self.charge_limit = limit; + } + + fn get_charge_limit(&self) -> Option { + log::debug!( + "dev_mode_Battery::get_charge_limit(self) -> {:?}", + self.charge_limit + ); + self.charge_limit + } + + fn provider(&self) -> crate::persist::DriverJson { + log::debug!("dev_mode_Battery::provider(self) -> DevMode"); + crate::persist::DriverJson::DevMode + } +} diff --git a/backend/src/settings/dev_mode/cpu.rs b/backend/src/settings/dev_mode/cpu.rs new file mode 100644 index 0000000..059aaa8 --- /dev/null +++ b/backend/src/settings/dev_mode/cpu.rs @@ -0,0 +1,282 @@ +use std::convert::Into; + +use limits_core::json_v2::{GenericCpuLimit, GenericCpusLimit}; + +use crate::persist::CpuJson; +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{ProviderBuilder, TCpu, TCpus}; + +#[derive(Debug, Clone)] +pub struct Cpus { + cpus: Vec, + #[allow(dead_code)] + version: u64, + smt_enabled: bool, + #[allow(dead_code)] + limits: GenericCpusLimit, +} + +impl OnSet for Cpus { + fn on_set(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Cpus::on_set(self)"); + for cpu in self.cpus.iter_mut() { + cpu.on_set()?; + } + Ok(()) + } +} + +impl OnResume for Cpus { + fn on_resume(&self) -> Result<(), Vec> { + log::debug!("dev_mode_Cpus::on_resume(self)"); + for cpu in self.cpus.iter() { + cpu.on_resume()?; + } + Ok(()) + } +} + +impl crate::settings::OnPowerEvent for Cpus {} + +impl crate::settings::OnLoad for Cpus { + fn on_load(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Cpus::on_load(self)"); + Ok(()) + } +} + +impl crate::settings::OnUnload for Cpus { + fn on_unload(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Cpus::on_unload(self)"); + Ok(()) + } +} + +impl ProviderBuilder, GenericCpusLimit> for Cpus { + fn from_json_and_limits( + persistent: Vec, + version: u64, + limits: GenericCpusLimit, + ) -> Self { + let mut cpus = Vec::with_capacity(persistent.len()); + for (i, cpu) in persistent.iter().enumerate() { + cpus.push(Cpu::from_json_and_limits( + cpu.to_owned(), + version, + i, + limits.cpus.get(i).map(|x| x.to_owned()).unwrap_or_else(|| { + log::warn!("No cpu limit for index {}, using default", i); + Default::default() + }), + )); + } + let smt_guess = crate::settings::util::guess_smt(&persistent); + Self { + cpus, + version, + smt_enabled: smt_guess, + limits, + } + } + + fn from_limits(limits: GenericCpusLimit) -> Self { + let mut cpus = Vec::with_capacity(limits.cpus.len()); + for (i, cpu) in limits.cpus.iter().enumerate() { + cpus.push(Cpu::from_limits(i, cpu.to_owned())); + } + Self { + cpus, + version: 0, + smt_enabled: true, + limits, + } + } +} + +impl TCpus for Cpus { + fn limits(&self) -> crate::api::CpusLimits { + log::debug!("dev_mode_Cpus::limits(self) -> {{...}}"); + crate::api::CpusLimits { + cpus: self.cpus.iter().map(|x| x.limits()).collect(), + count: self.cpus.len(), + smt_capable: true, + governors: vec![ + "this".to_owned(), + "is".to_owned(), + "dev".to_owned(), + "mode".to_owned(), + ], + } + } + + fn json(&self) -> Vec { + log::debug!("dev_mode_Cpus::json(self) -> {{...}}"); + self.cpus.iter().map(|x| x.to_owned().into()).collect() + } + + fn cpus(&mut self) -> Vec<&mut dyn TCpu> { + log::debug!("dev_mode_Cpus::cpus(self) -> {{...}}"); + self.cpus.iter_mut().map(|x| x as &mut dyn TCpu).collect() + } + + fn len(&self) -> usize { + log::debug!("dev_mode_Cpus::len(self) -> {}", self.cpus.len()); + self.cpus.len() + } + + fn smt(&mut self) -> &'_ mut bool { + log::debug!("dev_mode_Cpus::smt(self) -> {}", self.smt_enabled); + &mut self.smt_enabled + } + + fn provider(&self) -> crate::persist::DriverJson { + log::debug!("dev_mode_Cpus::provider(self) -> DevMode"); + crate::persist::DriverJson::DevMode + } +} + +#[derive(Clone)] +pub struct Cpu { + persist: CpuJson, + version: u64, + index: usize, + limits: GenericCpuLimit, + clock_limits: Option>, +} + +impl std::fmt::Debug for Cpu { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("dev_mode_Cpu") + //.field("persist", &self.persist) + .field("version", &self.version) + .field("index", &self.index) + .field("limits", &self.limits) + .finish_non_exhaustive() + } +} + +impl Cpu { + #[inline] + pub fn from_json_and_limits( + other: CpuJson, + version: u64, + i: usize, + limits: GenericCpuLimit, + ) -> Self { + let clock_limits = other.clock_limits.clone().map(|lim| MinMax { + min: lim.min, + max: lim.max, + }); + match version { + 0 => Self { + persist: other, + version, + index: i, + limits, + clock_limits, + }, + _ => Self { + persist: other, + version, + index: i, + limits, + clock_limits, + }, + } + } + + #[inline] + pub fn from_limits(i: usize, limits: GenericCpuLimit) -> Self { + Self { + persist: CpuJson { + online: true, + clock_limits: None, + governor: "".to_owned(), + root: None, + }, + version: 0, + index: i, + limits, + clock_limits: None, + } + } + + fn limits(&self) -> crate::api::CpuLimits { + crate::api::CpuLimits { + clock_min_limits: self.limits.clock_min.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(1100), + max: lim.max.unwrap_or(6900), + }), + clock_max_limits: self.limits.clock_max.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(4200), + max: lim.max.unwrap_or(4300), + }), + clock_step: self.limits.clock_step.unwrap_or(11), + governors: vec![ + "this".to_owned(), + "is".to_owned(), + "dev".to_owned(), + "mode".to_owned(), + ], + } + } +} + +impl Into for Cpu { + #[inline] + fn into(self) -> CpuJson { + self.persist + } +} + +impl OnSet for Cpu { + fn on_set(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Cpu::on_set(self)"); + Ok(()) + } +} + +impl OnResume for Cpu { + fn on_resume(&self) -> Result<(), Vec> { + log::debug!("dev_mode_Cpu::on_resume(self)"); + Ok(()) + } +} + +impl TCpu for Cpu { + fn online(&mut self) -> &mut bool { + log::debug!("dev_mode_Cpu::online(self) -> {}", self.persist.online); + &mut self.persist.online + } + + fn governor(&mut self, governor: String) { + log::debug!("dev_mode_Cpu::governor(self, {})", governor); + self.persist.governor = governor; + } + + fn get_governor(&self) -> &'_ str { + log::debug!("dev_mode_Cpu::governor(self) -> {}", self.persist.governor); + &self.persist.governor + } + + fn clock_limits(&mut self, limits: Option>) { + log::debug!("dev_mode_Cpu::clock_limits(self, {:?})", limits); + self.clock_limits = limits; + self.persist.clock_limits = + self.clock_limits + .clone() + .map(|lim| crate::persist::MinMaxJson { + max: lim.max, + min: lim.min, + }); + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + log::debug!( + "dev_mode_Cpu::get_clock_limits(self) -> {:?}", + self.clock_limits.as_ref() + ); + self.clock_limits.as_ref() + } +} diff --git a/backend/src/settings/dev_mode/gpu.rs b/backend/src/settings/dev_mode/gpu.rs new file mode 100644 index 0000000..42963a7 --- /dev/null +++ b/backend/src/settings/dev_mode/gpu.rs @@ -0,0 +1,216 @@ +use std::convert::Into; + +use limits_core::json_v2::GenericGpuLimit; + +use crate::persist::GpuJson; +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{ProviderBuilder, TGpu}; + +#[derive(Clone)] +pub struct Gpu { + persist: GpuJson, + version: u64, + limits: GenericGpuLimit, + clock_limits: Option>, +} + +impl std::fmt::Debug for Gpu { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("dev_mode_Gpu") + //.field("persist", &self.persist) + .field("version", &self.version) + .field("limits", &self.limits) + .finish_non_exhaustive() + } +} + +impl ProviderBuilder for Gpu { + fn from_json_and_limits(persist: GpuJson, version: u64, limits: GenericGpuLimit) -> Self { + let clock_limits = persist.clock_limits.clone().map(|lim| MinMax { + min: lim.min, + max: lim.max, + }); + Self { + persist, + version, + limits, + clock_limits, + } + } + + fn from_limits(limits: GenericGpuLimit) -> Self { + Self { + persist: GpuJson { + fast_ppt: None, + slow_ppt: None, + tdp: None, + tdp_boost: None, + clock_limits: None, + memory_clock: None, + root: None, + }, + version: 0, + limits, + clock_limits: None, + } + } +} + +impl Into for Gpu { + #[inline] + fn into(self) -> GpuJson { + self.persist + } +} + +impl OnSet for Gpu { + fn on_set(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Gpu::on_set(self)"); + Ok(()) + } +} + +impl OnResume for Gpu { + fn on_resume(&self) -> Result<(), Vec> { + log::debug!("dev_mode_Gpu::on_resume(self)"); + Ok(()) + } +} + +impl crate::settings::OnPowerEvent for Gpu {} + +impl crate::settings::OnLoad for Gpu { + fn on_load(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Gpu::on_load(self)"); + Ok(()) + } +} + +impl crate::settings::OnUnload for Gpu { + fn on_unload(&mut self) -> Result<(), Vec> { + log::debug!("dev_mode_Gpu::on_unload(self)"); + Ok(()) + } +} + +impl TGpu for Gpu { + fn limits(&self) -> crate::api::GpuLimits { + log::debug!("dev_mode_Gpu::limits(self) -> {{...}}"); + let ppt_divisor = self.limits.ppt_divisor.unwrap_or(1_000_000); + let tdp_divisor = self.limits.tdp_divisor.unwrap_or(1_000_000); + let limit_struct = crate::api::GpuLimits { + fast_ppt_limits: self.limits.fast_ppt.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(11_000_000) / ppt_divisor, + max: lim.max.unwrap_or(42_000_000) / ppt_divisor, + }), + fast_ppt_default: self + .limits + .fast_ppt_default + .or_else(|| self.limits.fast_ppt.and_then(|x| x.max)) + .unwrap_or(2_000_000) + / ppt_divisor, + slow_ppt_limits: self.limits.slow_ppt.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(7_000_000) / ppt_divisor, + max: lim.max.unwrap_or(69_000_000) / ppt_divisor, + }), + slow_ppt_default: self + .limits + .slow_ppt_default + .or_else(|| self.limits.slow_ppt.and_then(|x| x.max)) + .unwrap_or(3_000_000) + / ppt_divisor, + ppt_step: self.limits.ppt_step.unwrap_or(1), + tdp_limits: self.limits.tdp.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(11_000_000) / tdp_divisor, + max: lim.max.unwrap_or(69_000_000) / tdp_divisor, + }), + tdp_boost_limits: self.limits.tdp_boost.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(7_000_000) / tdp_divisor, + max: lim.max.unwrap_or(69_000_000) / tdp_divisor, + }), + tdp_step: self.limits.tdp_step.unwrap_or(1), + clock_min_limits: self.limits.clock_min.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(1100), + max: lim.max.unwrap_or(6900), + }), + clock_max_limits: self.limits.clock_max.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(1100), + max: lim.max.unwrap_or(4200), + }), + clock_step: self.limits.clock_step.unwrap_or(100), + memory_control: self.limits.memory_clock.map(|lim| crate::api::RangeLimit { + min: lim.min.unwrap_or(100), + max: lim.max.unwrap_or(1100), + }), + memory_step: self.limits.memory_clock_step.unwrap_or(400), + }; + log::debug!( + "dev_mode_Gpu::limits(self) -> {}", + serde_json::to_string_pretty(&limit_struct).unwrap() + ); + limit_struct + } + + fn json(&self) -> crate::persist::GpuJson { + log::debug!("dev_mode_Gpu::json(self) -> {{...}}"); + self.clone().into() + } + + fn ppt(&mut self, fast: Option, slow: Option) { + log::debug!( + "dev_mode_Gpu::ppt(self, fast: {:?}, slow: {:?})", + fast, + slow + ); + self.persist.fast_ppt = fast; + self.persist.slow_ppt = slow; + } + + fn get_ppt(&self) -> (Option, Option) { + log::debug!( + "dev_mode_Gpu::get_ppt(self) -> (fast: {:?}, slow: {:?})", + self.persist.fast_ppt, + self.persist.slow_ppt + ); + (self.persist.fast_ppt, self.persist.slow_ppt) + } + + fn clock_limits(&mut self, limits: Option>) { + log::debug!("dev_mode_Gpu::clock_limits(self, {:?})", limits); + self.clock_limits = limits; + self.persist.clock_limits = + self.clock_limits + .clone() + .map(|lim| crate::persist::MinMaxJson { + max: lim.max, + min: lim.min, + }); + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + log::debug!( + "dev_mode_Gpu::get_clock_limits(self) -> {:?}", + self.clock_limits.as_ref() + ); + self.clock_limits.as_ref() + } + + fn memory_clock(&mut self, speed: Option) { + log::debug!("dev_mode_Gpu::memory_clock(self, {:?})", speed); + self.persist.memory_clock = speed; + } + + fn get_memory_clock(&self) -> Option { + log::debug!( + "dev_mode_Gpu::memory_clock(self) -> {:?}", + self.persist.memory_clock + ); + self.persist.memory_clock + } + + fn provider(&self) -> crate::persist::DriverJson { + log::debug!("dev_mode_Gpu::provider(self) -> DevMode"); + crate::persist::DriverJson::DevMode + } +} diff --git a/backend/src/settings/dev_mode/mod.rs b/backend/src/settings/dev_mode/mod.rs new file mode 100644 index 0000000..58b6ac1 --- /dev/null +++ b/backend/src/settings/dev_mode/mod.rs @@ -0,0 +1,23 @@ +mod battery; +mod cpu; +mod gpu; + +pub use battery::Battery; +pub use cpu::Cpus; +pub use gpu::Gpu; + +fn _impl_checker() { + fn impl_provider_builder, J, L>() {} + + impl_provider_builder::< + Battery, + crate::persist::BatteryJson, + limits_core::json_v2::GenericBatteryLimit, + >(); + impl_provider_builder::< + Cpus, + Vec, + limits_core::json_v2::GenericCpusLimit, + >(); + impl_provider_builder::(); +} diff --git a/backend/src/settings/driver.rs b/backend/src/settings/driver.rs index 4192f6f..128cf30 100644 --- a/backend/src/settings/driver.rs +++ b/backend/src/settings/driver.rs @@ -1,4 +1,4 @@ -use super::{auto_detect0, General, SettingError, TBattery, TCpus, TGeneral, TGpu}; +use super::{auto_detect0, TBattery, TCpus, TGeneral, TGpu}; use crate::persist::{DriverJson, SettingsJson}; pub struct Driver { @@ -10,102 +10,24 @@ pub struct Driver { impl Driver { pub fn init( - settings: SettingsJson, + name: String, + settings: &SettingsJson, json_path: std::path::PathBuf, - ) -> Result { - Ok(match settings.version { - 0 => Self::version0(settings, json_path)?, - _ => Self { - general: Box::new(General { - persistent: settings.persistent, - path: json_path, - name: settings.name, - driver: DriverJson::SteamDeck, - events: settings.events.unwrap_or_default(), - }), - cpus: Box::new(super::steam_deck::Cpus::from_json( - settings.cpus, - settings.version, - )), - gpu: Box::new(super::steam_deck::Gpu::from_json( - settings.gpu, - settings.version, - )), - battery: Box::new(super::steam_deck::Battery::from_json( - settings.battery, - settings.version, - )), - }, - }) + app_id: u64, + ) -> Self { + let name_bup = settings.name.clone(); + let id_bup = settings.variant; + auto_detect0(Some(settings), json_path, app_id, name, id_bup, name_bup) } - fn version0( - settings: SettingsJson, + pub fn system_default( json_path: std::path::PathBuf, - ) -> Result { - let name = settings.name.clone(); - if let Some(provider) = &settings.provider { - match provider { - DriverJson::SteamDeck => Ok(Self { - general: Box::new(General { - persistent: settings.persistent, - path: json_path, - name: settings.name, - driver: DriverJson::SteamDeck, - events: settings.events.unwrap_or_default(), - }), - cpus: Box::new(super::steam_deck::Cpus::from_json( - settings.cpus, - settings.version, - )), - gpu: Box::new(super::steam_deck::Gpu::from_json( - settings.gpu, - settings.version, - )), - battery: Box::new(super::steam_deck::Battery::from_json( - settings.battery, - settings.version, - )), - }), - // There's nothing special about SteamDeckAdvance, it just appears different - DriverJson::SteamDeckAdvance => Ok(Self { - general: Box::new(General { - persistent: settings.persistent, - path: json_path, - name: settings.name, - driver: DriverJson::SteamDeckAdvance, - events: settings.events.unwrap_or_default(), - }), - cpus: Box::new(super::steam_deck::Cpus::from_json( - settings.cpus, - settings.version, - )), - gpu: Box::new(super::steam_deck::Gpu::from_json( - settings.gpu, - settings.version, - )), - battery: Box::new(super::steam_deck::Battery::from_json( - settings.battery, - settings.version, - )), - }), - DriverJson::Generic | DriverJson::GenericAMD => { - Ok(super::detect::auto_detect0(Some(settings), json_path, name)) - } - DriverJson::Unknown => { - Ok(super::detect::auto_detect0(Some(settings), json_path, name)) - } - DriverJson::AutoDetect => { - Ok(super::detect::auto_detect0(Some(settings), json_path, name)) - } - } - } else { - Ok(super::detect::auto_detect0(Some(settings), json_path, name)) - } - } - - pub fn system_default(json_path: std::path::PathBuf, name: String) -> Self { - auto_detect0(None, json_path, name) + app_id: u64, + name: String, + variant_id: u64, + variant_name: String, + ) -> Self { + auto_detect0(None, json_path, app_id, name, variant_id, variant_name) } } @@ -113,7 +35,7 @@ impl Driver { #[inline] pub fn maybe_do_button() { match super::auto_detect_provider() { - DriverJson::SteamDeck | DriverJson::SteamDeckAdvance => { + DriverJson::SteamDeck | DriverJson::SteamDeckOLED => { crate::settings::steam_deck::flash_led(); } DriverJson::Generic | DriverJson::GenericAMD => { @@ -121,5 +43,6 @@ pub fn maybe_do_button() { } DriverJson::Unknown => log::warn!("Can't do button activities on unknown platform"), DriverJson::AutoDetect => log::warn!("WTF, why is auto_detect detecting AutoDetect???"), + DriverJson::DevMode => log::error!("Hello dev world!"), } } diff --git a/backend/src/settings/general.rs b/backend/src/settings/general.rs index 1f47bc2..ea95661 100644 --- a/backend/src/settings/general.rs +++ b/backend/src/settings/general.rs @@ -2,9 +2,9 @@ use std::path::PathBuf; //use std::sync::{Arc, Mutex}; //use super::{Battery, Cpus, Gpu}; -use super::{OnResume, OnSet, SettingError}; +use super::{OnLoad, OnPowerEvent, OnResume, OnSet, OnUnload, SettingError}; use super::{TBattery, TCpus, TGeneral, TGpu}; -use crate::persist::SettingsJson; +use crate::persist::{FileJson, SettingsJson}; //use crate::utility::unwrap_lock; const LATEST_VERSION: u64 = 0; @@ -32,50 +32,39 @@ impl std::fmt::Display for SettingVariant { pub struct General { pub persistent: bool, pub path: PathBuf, + pub app_id: u64, pub name: String, + pub variant_id: u64, + pub variant_name: String, pub driver: crate::persist::DriverJson, - pub events: crate::persist::OnEventJson, + pub tags: Vec, } impl OnSet for General { fn on_set(&mut self) -> Result<(), Vec> { - if let Some(event) = &self.events.on_set { - if !event.is_empty() { - std::process::Command::new("/bin/bash") - .args(&["-c", event]) - .spawn() - .map_err(|e| { - vec![SettingError { - msg: format!("on_set event command error: {}", e), - setting: SettingVariant::General, - }] - })?; - } - } Ok(()) } } impl OnResume for General { fn on_resume(&self) -> Result<(), Vec> { - if let Some(event) = &self.events.on_resume { - if !event.is_empty() { - std::process::Command::new("/bin/bash") - .args(&["-c", event]) - .spawn() - .map_err(|e| { - vec![SettingError { - msg: format!("on_resume event command error: {}", e), - setting: SettingVariant::General, - }] - })?; - } - } Ok(()) } } -impl crate::settings::OnPowerEvent for General {} +impl OnPowerEvent for General {} + +impl OnLoad for General { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl OnUnload for General { + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} impl TGeneral for General { fn limits(&self) -> crate::api::GeneralLimits { @@ -98,6 +87,14 @@ impl TGeneral for General { self.path = path; } + fn app_id(&mut self) -> &'_ mut u64 { + &mut self.app_id + } + + fn get_app_id(&self) -> u64 { + self.app_id + } + fn get_name(&self) -> &'_ str { &self.name } @@ -106,12 +103,82 @@ impl TGeneral for General { self.name = name; } + fn get_variant_id(&self) -> u64 { + self.variant_id + } + + fn variant_id(&mut self, id: u64) { + self.variant_id = id; + } + + fn variant_name(&mut self, name: String) { + self.variant_name = name; + } + + fn get_variants(&self) -> Vec { + let json_path = crate::utility::settings_dir().join(self.get_path()); + if let Ok(file) = crate::persist::FileJson::open(json_path) { + file.variants + .into_iter() + .map(|(id, conf)| crate::api::VariantInfo { + id: id.to_string(), + name: conf.name, + id_num: id, + }) + .collect() + } else { + vec![self.get_variant_info()] + } + } + + fn add_variant( + &self, + variant: crate::persist::SettingsJson, + ) -> Result, SettingError> { + let variant_name = variant.name.clone(); + let json_path = crate::utility::settings_dir().join(self.get_path()); + crate::persist::FileJson::update_variant_or_create( + json_path, + self.get_app_id(), + variant, + variant_name, + ) + .map_err(|e| SettingError { + msg: format!("failed to add variant: {}", e), + setting: SettingVariant::General, + }) + .map(|file| { + file.0 + .variants + .into_iter() + .map(|(id, conf)| crate::api::VariantInfo { + id: id.to_string(), + name: conf.name, + id_num: id, + }) + .collect() + }) + } + + fn get_variant_info(&self) -> crate::api::VariantInfo { + log::debug!( + "Current variant `{}` ({})", + self.variant_name, + self.variant_id + ); + crate::api::VariantInfo { + id: self.variant_id.to_string(), + name: self.variant_name.clone(), + id_num: self.variant_id, + } + } + fn provider(&self) -> crate::persist::DriverJson { self.driver.clone() } - fn on_event(&self) -> &crate::persist::OnEventJson { - &self.events + fn tags(&self) -> &[String] { + &self.tags } } @@ -155,33 +222,32 @@ impl OnSet for Settings { impl Settings { #[inline] - pub fn from_json(other: SettingsJson, json_path: PathBuf) -> Self { - let name_bup = other.name.clone(); - match super::Driver::init(other, json_path.clone()) { - Ok(x) => { - log::info!( - "Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", - x.general.provider(), - x.cpus.provider(), - x.gpu.provider(), - x.battery.provider() - ); - Self { - general: x.general, - cpus: x.cpus, - gpu: x.gpu, - battery: x.battery, - } - } - Err(e) => { - log::error!("Driver init error: {}", e); - Self::system_default(json_path, name_bup) - } + pub fn from_json(name: String, other: SettingsJson, json_path: PathBuf, app_id: u64) -> Self { + let x = super::Driver::init(name, &other, json_path.clone(), app_id); + log::info!( + "Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", + x.general.provider(), + x.cpus.provider(), + x.gpu.provider(), + x.battery.provider() + ); + Self { + general: x.general, + cpus: x.cpus, + gpu: x.gpu, + battery: x.battery, } } - pub fn system_default(json_path: PathBuf, name: String) -> Self { - let driver = super::Driver::system_default(json_path, name); + pub fn system_default( + json_path: PathBuf, + app_id: u64, + name: String, + variant_id: u64, + variant_name: String, + ) -> Self { + let driver = + super::Driver::system_default(json_path, app_id, name, variant_id, variant_name); Self { general: driver.general, cpus: driver.cpus, @@ -190,75 +256,152 @@ impl Settings { } } - pub fn load_system_default(&mut self, name: String) { - let driver = super::Driver::system_default(self.general.get_path().to_owned(), name); + pub fn load_system_default(&mut self, name: String, variant_id: u64, variant_name: String) { + let driver = super::Driver::system_default( + self.general.get_path().to_owned(), + self.general.get_app_id(), + name, + variant_id, + variant_name, + ); self.cpus = driver.cpus; self.gpu = driver.gpu; self.battery = driver.battery; self.general = driver.general; } + pub fn get_variant<'a>( + settings_file: &'a FileJson, + variant_id: u64, + variant_name: String, + ) -> Result<&'a SettingsJson, SettingError> { + if let Some(variant) = settings_file.variants.get(&variant_id) { + Ok(variant) + } else if variant_id == 0 { + // special case: requesting primary variant for settings with non-persistent primary + let mut valid_ids: Vec<&u64> = settings_file.variants.keys().collect(); + valid_ids.sort(); + if let Some(id) = valid_ids.get(0) { + Ok(settings_file + .variants + .get(id) + .expect("variant id key magically disappeared")) + } else { + Err(SettingError { + msg: format!( + "Cannot get variant `{}` (id:{}) from empty settings file", + variant_name, variant_id + ), + setting: SettingVariant::General, + }) + } + } else { + Err(SettingError { + msg: format!( + "Cannot get non-existent variant `{}` (id:{})", + variant_name, variant_id + ), + setting: SettingVariant::General, + }) + } + } + pub fn load_file( &mut self, filename: PathBuf, + app_id: u64, name: String, + variant: u64, + variant_name: String, system_defaults: bool, ) -> Result { let json_path = crate::utility::settings_dir().join(&filename); if json_path.exists() { - let settings_json = SettingsJson::open(&json_path).map_err(|e| SettingError { - msg: e.to_string(), - setting: SettingVariant::General, - })?; - if !settings_json.persistent { - log::warn!( - "Loaded persistent config `{}` ({}) with persistent=false", - &settings_json.name, + if variant == u64::MAX { + log::debug!( + "Creating new variant `{}` in existing settings file {}", + variant_name, json_path.display() ); - *self.general.persistent() = false; - self.general.name(name); + self.create_and_load_variant(&json_path, app_id, variant_name)?; } else { - match super::Driver::init(settings_json, json_path.clone()) { - Ok(x) => { - log::info!("Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", x.general.provider(), x.cpus.provider(), x.gpu.provider(), x.battery.provider()); - self.general = x.general; - self.cpus = x.cpus; - self.gpu = x.gpu; - self.battery = x.battery; - } - Err(e) => { - log::error!("Driver init error: {}", e); - self.general.name(name); - *self.general.persistent() = false; - self.general.path(json_path); - return Err(e); - } - }; + let file_json = FileJson::open(&json_path).map_err(|e| SettingError { + msg: format!("Failed to open settings {}: {}", json_path.display(), e), + setting: SettingVariant::General, + })?; + let settings_json = Self::get_variant(&file_json, variant, variant_name)?; + if !settings_json.persistent { + log::warn!( + "Loaded persistent config `{}` ({}) with persistent=false", + &settings_json.name, + json_path.display() + ); + *self.general.persistent() = false; + self.general.name(name); + self.general.variant_name(settings_json.name.clone()); + self.general.variant_id(settings_json.variant); + } else { + let x = super::Driver::init(name, settings_json, json_path.clone(), app_id); + log::info!( + "Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", + x.general.provider(), + x.cpus.provider(), + x.gpu.provider(), + x.battery.provider() + ); + self.general = x.general; + self.cpus = x.cpus; + self.gpu = x.gpu; + self.battery = x.battery; + } } } else { if system_defaults { - self.load_system_default(name); + self.load_system_default(name, variant, variant_name.clone()); } else { self.general.name(name); + self.general.variant_name(variant_name.clone()); + self.general.variant_id(variant); } *self.general.persistent() = false; - } - self.general.path(filename); - if let Some(event) = &self.general.on_event().on_load { - if !event.is_empty() { - std::process::Command::new("/bin/bash") - .args(&["-c", event]) - .spawn() - .map_err(|e| SettingError { - msg: format!("on_save event command error: {}", e), - setting: SettingVariant::General, - })?; + if variant == u64::MAX { + log::debug!( + "Creating new variant `{}` in new settings file {}", + variant_name, + json_path.display() + ); + self.create_and_load_variant(&json_path, app_id, variant_name)?; } } + *self.general.app_id() = app_id; + self.general.path(filename); Ok(*self.general.persistent()) } + fn create_and_load_variant( + &mut self, + json_path: &PathBuf, + app_id: u64, + variant_name: String, + ) -> Result<(), SettingError> { + *self.general.persistent() = true; + self.general.variant_id(u64::MAX); + self.general.variant_name(variant_name.clone()); + let (_file_json, new_variant) = FileJson::update_variant_or_create( + json_path, + app_id, + self.json(), + self.general.get_name().to_owned(), + ) + .map_err(|e| SettingError { + msg: format!("Failed to open settings {}: {}", json_path.display(), e), + setting: SettingVariant::General, + })?; + self.general.variant_id(new_variant.variant); + self.general.variant_name(new_variant.name); + Ok(()) + } + /* pub fn load_file(&mut self, filename: PathBuf, name: String, system_defaults: bool) -> Result { let json_path = crate::utility::settings_dir().join(filename); @@ -291,15 +434,17 @@ impl Settings { }*/ pub fn json(&self) -> SettingsJson { + let variant_info = self.general.get_variant_info(); SettingsJson { version: LATEST_VERSION, - name: self.general.get_name().to_owned(), + name: variant_info.name, + variant: self.general.get_variant_id(), persistent: self.general.get_persistent(), cpus: self.cpus.json(), gpu: self.gpu.json(), battery: self.battery.json(), provider: Some(self.general.provider()), - events: Some(self.general.on_event().clone()), + tags: self.general.tags().to_owned(), } } } @@ -334,7 +479,7 @@ impl OnResume for Settings { } } -impl crate::settings::OnPowerEvent for Settings { +impl OnPowerEvent for Settings { fn on_power_event(&mut self, new_mode: super::PowerMode) -> Result<(), Vec> { let mut errors = Vec::new(); @@ -359,6 +504,56 @@ impl crate::settings::OnPowerEvent for Settings { } } +impl OnLoad for Settings { + fn on_load(&mut self) -> Result<(), Vec> { + let mut errors = Vec::new(); + + self.general + .on_load() + .unwrap_or_else(|mut e| errors.append(&mut e)); + self.battery + .on_load() + .unwrap_or_else(|mut e| errors.append(&mut e)); + self.cpus + .on_load() + .unwrap_or_else(|mut e| errors.append(&mut e)); + self.gpu + .on_load() + .unwrap_or_else(|mut e| errors.append(&mut e)); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + +impl OnUnload for Settings { + fn on_unload(&mut self) -> Result<(), Vec> { + let mut errors = Vec::new(); + + self.general + .on_unload() + .unwrap_or_else(|mut e| errors.append(&mut e)); + self.battery + .on_unload() + .unwrap_or_else(|mut e| errors.append(&mut e)); + self.cpus + .on_unload() + .unwrap_or_else(|mut e| errors.append(&mut e)); + self.gpu + .on_unload() + .unwrap_or_else(|mut e| errors.append(&mut e)); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + /*impl Into for Settings { #[inline] fn into(self) -> SettingsJson { diff --git a/backend/src/settings/generic/battery.rs b/backend/src/settings/generic/battery.rs index 81464dd..a046412 100644 --- a/backend/src/settings/generic/battery.rs +++ b/backend/src/settings/generic/battery.rs @@ -1,11 +1,11 @@ use std::convert::Into; -use limits_core::json::GenericBatteryLimit; -use sysfuss::SysEntity; +use limits_core::json_v2::GenericBatteryLimit; +use sysfuss::{SysEntity, SysEntityAttributesExt}; use crate::persist::BatteryJson; -use crate::settings::TBattery; use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{ProviderBuilder, TBattery}; #[derive(Debug, Clone)] pub struct Battery { @@ -21,13 +21,16 @@ impl Into for Battery { charge_rate: None, charge_mode: None, events: Vec::default(), - root: self.sysfs.root().and_then(|p| p.as_ref().to_str().map(|s| s.to_owned())), + root: self + .sysfs + .root() + .and_then(|p| p.as_ref().to_str().map(|s| s.to_owned())), } } } impl Battery { - fn read_f64>(path: P) -> Result { + /*fn read_f64>(path: P) -> Result { let path = path.as_ref(); match usdpl_back::api::files::read_single::<_, f64, _>(path) { Err(e) => Err(SettingError { @@ -38,13 +41,14 @@ impl Battery { // so convert this to mA for consistency Ok(val) => Ok(val / 1000.0), } - } + }*/ fn find_psu_sysfs(root: Option>) -> sysfuss::PowerSupplyPath { let root = crate::settings::util::root_or_default_sysfs(root); match root.power_supply(crate::settings::util::always_satisfied) { - Ok(mut iter) => { - iter.next() + Ok(iter) => { + iter.filter(|x| x.name().is_ok_and(|name| name.starts_with("BAT"))) + .next() .unwrap_or_else(|| { log::error!("Failed to find generic battery power_supply in sysfs (no results), using naive fallback"); root.power_supply_by_name("BAT0") @@ -57,23 +61,57 @@ impl Battery { } } - pub fn from_limits(limits: limits_core::json::GenericBatteryLimit) -> Self { - // TODO - Self { - limits, - sysfs: Self::find_psu_sysfs(None::<&'static str>), + fn get_design_voltage(&self) -> Option { + match self + .sysfs + .attribute::(sysfuss::PowerSupplyAttribute::VoltageMax) + { + Ok(x) => Some(x / 1000000.0), + Err(e) => { + log::debug!("get_design_voltage voltage_max err: {}", e); + match sysfuss::SysEntityRawExt::attribute::<_, f64, _>( + &self.sysfs, + "voltage_min_design".to_owned(), + ) { + // Framework 13 AMD + Ok(x) => Some(x / 1000000.0), + Err(e) => { + log::debug!("get_design_voltage voltage_min_design err: {}", e); + match self + .sysfs + .attribute::(sysfuss::PowerSupplyAttribute::VoltageMin) + { + Ok(x) => Some(x / 1000000.0), + Err(e) => { + log::debug!("get_design_voltage voltage_min err: {}", e); + None + } + } + } + } + } } } +} - pub fn from_json_and_limits( - other: BatteryJson, +impl ProviderBuilder for Battery { + fn from_json_and_limits( + persistent: BatteryJson, _version: u64, - limits: limits_core::json::GenericBatteryLimit, + limits: GenericBatteryLimit, ) -> Self { // TODO Self { limits, - sysfs: Self::find_psu_sysfs(other.root) + sysfs: Self::find_psu_sysfs(persistent.root), + } + } + + fn from_limits(limits: GenericBatteryLimit) -> Self { + // TODO + Self { + limits, + sysfs: Self::find_psu_sysfs(None::<&'static str>), } } } @@ -94,6 +132,18 @@ impl OnResume for Battery { impl crate::settings::OnPowerEvent for Battery {} +impl crate::settings::OnLoad for Battery { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Battery { + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + impl TBattery for Battery { fn limits(&self) -> crate::api::BatteryLimits { crate::api::BatteryLimits { @@ -122,37 +172,67 @@ impl TBattery for Battery { } fn read_charge_full(&self) -> Option { - match Self::read_f64("/sys/class/power_supply/BAT0/energy_full") { - Ok(x) => Some(x), - Err(e) => { - log::warn!("read_charge_full err: {}", e.msg); - None + if let Some(battery_voltage) = self.get_design_voltage() { + match self + .sysfs + .attribute::(sysfuss::PowerSupplyAttribute::ChargeFull) + { + Ok(x) => Some(x / 1000000.0 * battery_voltage), + Err(e) => { + log::warn!("read_charge_full err: {}", e); + None + } } + } else { + None } } fn read_charge_now(&self) -> Option { - match Self::read_f64("/sys/class/power_supply/BAT0/energy_now") { - Ok(x) => Some(x), - Err(e) => { - log::warn!("read_charge_now err: {}", e.msg); - None + if let Some(battery_voltage) = self.get_design_voltage() { + match self + .sysfs + .attribute::(sysfuss::PowerSupplyAttribute::ChargeNow) + { + Ok(x) => Some(x / 1000000.0 * battery_voltage), + Err(e) => { + log::warn!("read_charge_now err: {}", e); + None + } } + } else { + None } } fn read_charge_design(&self) -> Option { - match Self::read_f64("/sys/class/power_supply/BAT0/energy_design") { - Ok(x) => Some(x), - Err(e) => { - log::warn!("read_charge_design err: {}", e.msg); - None + if let Some(battery_voltage) = self.get_design_voltage() { + match self + .sysfs + .attribute::(sysfuss::PowerSupplyAttribute::ChargeFullDesign) + { + Ok(x) => Some(x / 1000000.0 * battery_voltage), + Err(e) => { + log::warn!("read_charge_design err: {}", e); + None + } } + } else { + None } } fn read_current_now(&self) -> Option { - None + match self + .sysfs + .attribute::(sysfuss::PowerSupplyAttribute::CurrentNow) + { + Ok(x) => Some(x / 1000.0), // expects mA, reads uA + Err(e) => { + log::warn!("read_current_now err: {}", e); + None + } + } } fn read_charge_power(&self) -> Option { @@ -162,6 +242,13 @@ impl TBattery for Battery { fn charge_limit(&mut self, _limit: Option) {} fn get_charge_limit(&self) -> Option { + /*match self.sysfs.attribute::(sysfuss::PowerSupplyAttribute::ChargeControlLimit) { + Ok(x) => Some(x/1000.0), + Err(e) => { + log::warn!("read_charge_design err: {}", e); + None + } + }*/ None } diff --git a/backend/src/settings/generic/cpu.rs b/backend/src/settings/generic/cpu.rs index 00f7dbe..6142f62 100644 --- a/backend/src/settings/generic/cpu.rs +++ b/backend/src/settings/generic/cpu.rs @@ -1,13 +1,13 @@ use std::convert::{AsMut, AsRef, Into}; -use limits_core::json::GenericCpuLimit; +use limits_core::json_v2::{GenericCpuLimit, GenericCpusLimit}; use super::FromGenericCpuInfo; use crate::api::RangeLimit; use crate::persist::CpuJson; use crate::settings::{min_max_from_json, MinMax}; use crate::settings::{OnResume, OnSet, SettingError}; -use crate::settings::{TCpu, TCpus}; +use crate::settings::{ProviderBuilder, TCpu, TCpus}; const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control"; @@ -87,31 +87,21 @@ impl + AsRef + TCpu + FromGenericCpuInfo> Cpus { Err(_) => (false, false), } } +} - pub fn from_limits(limits: limits_core::json::GenericCpuLimit) -> Self { - let cpu_count = Self::cpu_count().unwrap_or(8); - let (_, can_smt) = Self::system_smt_capabilities(); - let mut new_cpus = Vec::with_capacity(cpu_count); - for i in 0..cpu_count { - let new_cpu = C::from_limits(i, limits.clone()); - new_cpus.push(new_cpu); - } - Self { - cpus: new_cpus, - smt: true, - smt_capable: can_smt, - } - } - - pub fn from_json_and_limits( +impl + AsRef + TCpu + FromGenericCpuInfo> + ProviderBuilder, GenericCpusLimit> for Cpus +{ + fn from_json_and_limits( mut other: Vec, version: u64, - limits: limits_core::json::GenericCpuLimit, + limits: GenericCpusLimit, ) -> Self { let (_, can_smt) = Self::system_smt_capabilities(); let mut result = Vec::with_capacity(other.len()); let max_cpus = Self::cpu_count(); let smt_guess = crate::settings::util::guess_smt(&other) && can_smt; + let fallback_cpu_limit = GenericCpuLimit::default(); for (i, cpu) in other.drain(..).enumerate() { // prevent having more CPUs than available if let Some(max_cpus) = max_cpus { @@ -119,7 +109,13 @@ impl + AsRef + TCpu + FromGenericCpuInfo> Cpus { break; } } - let new_cpu = C::from_json_and_limits(cpu, version, i, limits.clone()); + let cpu_limit = limits + .cpus + .get(i) + .or_else(|| limits.cpus.get(0)) + .unwrap_or_else(|| &fallback_cpu_limit) + .clone(); + let new_cpu = C::from_json_and_limits(cpu, version, i, cpu_limit); result.push(new_cpu); } if let Some(max_cpus) = max_cpus { @@ -136,6 +132,28 @@ impl + AsRef + TCpu + FromGenericCpuInfo> Cpus { smt_capable: can_smt, } } + + fn from_limits(limits: GenericCpusLimit) -> Self { + let cpu_count = Self::cpu_count().unwrap_or(8); + let (_, can_smt) = Self::system_smt_capabilities(); + let mut new_cpus = Vec::with_capacity(cpu_count); + let fallback_cpu_limit = GenericCpuLimit::default(); + for i in 0..cpu_count { + let cpu_limit = limits + .cpus + .get(i) + .or_else(|| limits.cpus.get(0)) + .unwrap_or_else(|| &fallback_cpu_limit) + .clone(); + let new_cpu = C::from_limits(i, cpu_limit); + new_cpus.push(new_cpu); + } + Self { + cpus: new_cpus, + smt: true, + smt_capable: can_smt, + } + } } impl + AsRef + TCpu + crate::settings::OnPowerEvent> @@ -158,6 +176,22 @@ impl + AsRef + TCpu + crate::settings::OnPowerEvent> } } +impl + AsRef + TCpu + OnResume + OnSet + crate::settings::OnPowerEvent> + crate::settings::OnLoad for Cpus +{ + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl + AsRef + TCpu + OnResume + OnSet + crate::settings::OnPowerEvent> + crate::settings::OnUnload for Cpus +{ + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + impl + AsRef + TCpu + OnResume + OnSet + crate::settings::OnPowerEvent> TCpus for Cpus { @@ -212,6 +246,14 @@ pub struct Cpu { } }*/ +impl Cpu { + #[inline] + fn current_governor(index: usize) -> String { + usdpl_back::api::files::read_single(cpu_governor_path(index)) + .unwrap_or_else(|_| "schedutil".to_owned()) + } +} + impl AsRef for Cpu { #[inline] fn as_ref(&self) -> &Cpu { @@ -231,7 +273,7 @@ impl FromGenericCpuInfo for Cpu { fn from_limits(cpu_index: usize, limits: GenericCpuLimit) -> Self { Self { online: true, - governor: "schedutil".to_owned(), + governor: Self::current_governor(cpu_index), clock_limits: None, limits, index: cpu_index, @@ -345,7 +387,7 @@ impl Cpu { .clock_max .clone() .map(|x| RangeLimit::new(x.min.unwrap_or(0), x.max.unwrap_or(5_000))), - clock_step: self.limits.clock_step, + clock_step: self.limits.clock_step.unwrap_or(100), governors: self.governors(), } } diff --git a/backend/src/settings/generic/gpu.rs b/backend/src/settings/generic/gpu.rs index b8fcb6c..58b70bc 100644 --- a/backend/src/settings/generic/gpu.rs +++ b/backend/src/settings/generic/gpu.rs @@ -1,21 +1,23 @@ use std::convert::Into; -use limits_core::json::GenericGpuLimit; +use limits_core::json_v2::GenericGpuLimit; use sysfuss::{BasicEntityPath, SysEntity}; use crate::api::RangeLimit; use crate::persist::GpuJson; -use crate::settings::TGpu; use crate::settings::{min_max_from_json, MinMax}; use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{ProviderBuilder, TGpu}; #[derive(Debug, Clone)] pub struct Gpu { pub slow_memory: bool, pub fast_ppt: Option, pub slow_ppt: Option, + pub tdp: Option, + pub tdp_boost: Option, pub clock_limits: Option>, - limits: GenericGpuLimit, + pub limits: GenericGpuLimit, sysfs: BasicEntityPath, } @@ -36,56 +38,70 @@ impl Gpu { fn find_card_sysfs(root: Option>) -> BasicEntityPath { let root = crate::settings::util::root_or_default_sysfs(root); match root.class("drm", crate::settings::util::always_satisfied) { - Ok(mut iter) => { - iter.next() - .unwrap_or_else(|| { - log::error!("Failed to find generic gpu drm in sysfs (no results), using naive fallback"); - BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0")) - }) - }, + Ok(mut iter) => iter.next().unwrap_or_else(|| { + log::error!( + "Failed to find generic gpu drm in sysfs (no results), using naive fallback" + ); + BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0")) + }), Err(e) => { - log::error!("Failed to find generic gpu drm in sysfs ({}), using naive fallback", e); + log::error!( + "Failed to find generic gpu drm in sysfs ({}), using naive fallback", + e + ); BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0")) } } } +} - pub fn from_limits(limits: limits_core::json::GenericGpuLimit) -> Self { - Self { - slow_memory: false, - fast_ppt: None, - slow_ppt: None, - clock_limits: None, - limits, - sysfs: Self::find_card_sysfs(None::<&'static str>), - } - } - - pub fn from_json_and_limits( - other: GpuJson, - version: u64, - limits: limits_core::json::GenericGpuLimit, - ) -> Self { +impl ProviderBuilder for Gpu { + fn from_json_and_limits(persistent: GpuJson, version: u64, limits: GenericGpuLimit) -> Self { let clock_lims = if limits.clock_min.is_some() && limits.clock_max.is_some() { - other.clock_limits.map(|x| min_max_from_json(x, version)) + persistent + .clock_limits + .map(|x| min_max_from_json(x, version)) } else { None }; Self { slow_memory: false, fast_ppt: if limits.fast_ppt.is_some() { - other.fast_ppt + persistent.fast_ppt } else { None }, slow_ppt: if limits.slow_ppt.is_some() { - other.slow_ppt + persistent.slow_ppt + } else { + None + }, + tdp: if limits.tdp.is_some() { + persistent.tdp + } else { + None + }, + tdp_boost: if limits.tdp_boost.is_some() { + persistent.tdp_boost } else { None }, clock_limits: clock_lims, limits, - sysfs: Self::find_card_sysfs(other.root) + sysfs: Self::find_card_sysfs(persistent.root), + } + } + + fn from_limits(limits: GenericGpuLimit) -> Self { + Self { + slow_memory: false, + fast_ppt: None, + slow_ppt: None, + tdp: None, + tdp_boost: None, + clock_limits: None, + limits, + sysfs: Self::find_card_sysfs(None::<&'static str>), } } } @@ -96,9 +112,14 @@ impl Into for Gpu { GpuJson { fast_ppt: self.fast_ppt, slow_ppt: self.slow_ppt, + tdp: self.tdp, + tdp_boost: self.tdp_boost, clock_limits: self.clock_limits.map(|x| x.into()), - slow_memory: false, - root: self.sysfs.root().and_then(|p| p.as_ref().to_str().map(|s| s.to_owned())) + memory_clock: None, + root: self + .sysfs + .root() + .and_then(|p| p.as_ref().to_str().map(|s| s.to_owned())), } } } @@ -117,6 +138,18 @@ impl OnResume for Gpu { impl crate::settings::OnPowerEvent for Gpu {} +impl crate::settings::OnLoad for Gpu { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Gpu { + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + impl TGpu for Gpu { fn limits(&self) -> crate::api::GpuLimits { crate::api::GpuLimits { @@ -124,13 +157,55 @@ impl TGpu for Gpu { .limits .fast_ppt .clone() - .map(|x| RangeLimit::new(x.min.unwrap_or(0), x.max.unwrap_or(15_000_000))), + .map(|x| RangeLimit::new(x.min.unwrap_or(0), x.max.unwrap_or(15))) + .map(|mut x| { + if let Some(ppt_divisor) = self.limits.ppt_divisor { + x.min /= ppt_divisor; + x.max /= ppt_divisor; + x + } else { + x + } + }), + fast_ppt_default: { + let def = self + .limits + .fast_ppt_default + .or_else(|| self.limits.fast_ppt.and_then(|x| x.max)) + .unwrap_or(15); + if let Some(ppt_divisor) = self.limits.ppt_divisor { + def / ppt_divisor + } else { + def + } + }, slow_ppt_limits: self .limits .slow_ppt .clone() - .map(|x| RangeLimit::new(x.min.unwrap_or(0), x.max.unwrap_or(15_000_000))), - ppt_step: self.limits.ppt_step.unwrap_or(1_000_000), + .map(|x| RangeLimit::new(x.min.unwrap_or(0), x.max.unwrap_or(15))) + .map(|mut x| { + if let Some(ppt_divisor) = self.limits.ppt_divisor { + x.min /= ppt_divisor; + x.max /= ppt_divisor; + x + } else { + x + } + }), + slow_ppt_default: { + let def = self + .limits + .slow_ppt_default + .or_else(|| self.limits.slow_ppt.and_then(|x| x.max)) + .unwrap_or(15); + if let Some(ppt_divisor) = self.limits.ppt_divisor { + def / ppt_divisor + } else { + def + } + }, + ppt_step: self.limits.ppt_step.unwrap_or(1), tdp_limits: self .limits .tdp @@ -153,7 +228,8 @@ impl TGpu for Gpu { .clone() .map(|x| RangeLimit::new(x.min.unwrap_or(0), x.max.unwrap_or(3_000))), clock_step: self.limits.clock_step.unwrap_or(100), - memory_control_capable: false, + memory_control: None, + memory_step: 100, } } @@ -163,25 +239,56 @@ impl TGpu for Gpu { fn ppt(&mut self, fast: Option, slow: Option) { if let Some(fast_lims) = &self.limits.fast_ppt { - self.fast_ppt = fast.map(|x| { - x.clamp( - fast_lims.min.unwrap_or(0), - fast_lims.max.unwrap_or(u64::MAX), - ) - }); + self.fast_ppt = fast + .map(|x| { + if let Some(ppt_divisor) = self.limits.ppt_divisor { + x * ppt_divisor + } else { + x + } + }) + .map(|x| { + x.clamp( + fast_lims.min.unwrap_or(0), + fast_lims.max.unwrap_or(u64::MAX), + ) + }); } if let Some(slow_lims) = &self.limits.slow_ppt { - self.slow_ppt = slow.map(|x| { - x.clamp( - slow_lims.min.unwrap_or(0), - slow_lims.max.unwrap_or(u64::MAX), - ) - }); + self.slow_ppt = slow + .map(|x| { + if let Some(ppt_divisor) = self.limits.ppt_divisor { + x * ppt_divisor + } else { + x + } + }) + .map(|x| { + x.clamp( + slow_lims.min.unwrap_or(0), + slow_lims.max.unwrap_or(u64::MAX), + ) + }); } } fn get_ppt(&self) -> (Option, Option) { - (self.fast_ppt, self.slow_ppt) + ( + self.fast_ppt.map(|x| { + if let Some(ppt_divisor) = self.limits.ppt_divisor { + x / ppt_divisor + } else { + x + } + }), + self.slow_ppt.map(|x| { + if let Some(ppt_divisor) = self.limits.ppt_divisor { + x / ppt_divisor + } else { + x + } + }), + ) } fn clock_limits(&mut self, limits: Option>) { @@ -200,8 +307,10 @@ impl TGpu for Gpu { self.clock_limits.as_ref() } - fn slow_memory(&mut self) -> &mut bool { - &mut self.slow_memory + fn memory_clock(&mut self, _speed: Option) {} + + fn get_memory_clock(&self) -> Option { + None } fn provider(&self) -> crate::persist::DriverJson { diff --git a/backend/src/settings/generic/mod.rs b/backend/src/settings/generic/mod.rs index 6989a23..1abd444 100644 --- a/backend/src/settings/generic/mod.rs +++ b/backend/src/settings/generic/mod.rs @@ -7,3 +7,19 @@ pub use battery::Battery; pub use cpu::{Cpu, Cpus}; pub use gpu::Gpu; pub use traits::FromGenericCpuInfo; + +fn _impl_checker() { + fn impl_provider_builder, J, L>() {} + + impl_provider_builder::< + Battery, + crate::persist::BatteryJson, + limits_core::json_v2::GenericBatteryLimit, + >(); + impl_provider_builder::< + Cpus, + Vec, + limits_core::json_v2::GenericCpusLimit, + >(); + impl_provider_builder::(); +} diff --git a/backend/src/settings/generic/traits.rs b/backend/src/settings/generic/traits.rs index a8674bb..58d91ad 100644 --- a/backend/src/settings/generic/traits.rs +++ b/backend/src/settings/generic/traits.rs @@ -1,6 +1,7 @@ use crate::persist::CpuJson; -use limits_core::json::GenericCpuLimit; +use limits_core::json_v2::GenericCpuLimit; +// similar to crate::settings::ProviderBuilder pub trait FromGenericCpuInfo { fn from_limits(cpu_index: usize, limits: GenericCpuLimit) -> Self; diff --git a/backend/src/settings/generic_amd/cpu.rs b/backend/src/settings/generic_amd/cpu.rs index 48e119c..0809ee6 100644 --- a/backend/src/settings/generic_amd/cpu.rs +++ b/backend/src/settings/generic_amd/cpu.rs @@ -2,24 +2,24 @@ use crate::persist::CpuJson; use crate::settings::generic::{Cpu as GenericCpu, Cpus as GenericCpus, FromGenericCpuInfo}; use crate::settings::MinMax; use crate::settings::{OnResume, OnSet, SettingError}; -use crate::settings::{TCpu, TCpus}; +use crate::settings::{ProviderBuilder, TCpu, TCpus}; #[derive(Debug)] pub struct Cpus { generic: GenericCpus, } -impl Cpus { - pub fn from_limits(limits: limits_core::json::GenericCpuLimit) -> Self { +impl ProviderBuilder, limits_core::json_v2::GenericCpusLimit> for Cpus { + fn from_limits(limits: limits_core::json_v2::GenericCpusLimit) -> Self { Self { generic: GenericCpus::from_limits(limits), } } - pub fn from_json_and_limits( + fn from_json_and_limits( other: Vec, version: u64, - limits: limits_core::json::GenericCpuLimit, + limits: limits_core::json_v2::GenericCpusLimit, ) -> Self { Self { generic: GenericCpus::from_json_and_limits(other, version, limits), @@ -43,6 +43,18 @@ impl OnSet for Cpus { impl crate::settings::OnPowerEvent for Cpus {} +impl crate::settings::OnLoad for Cpus { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Cpus { + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + impl TCpus for Cpus { fn limits(&self) -> crate::api::CpusLimits { self.generic.limits() @@ -75,7 +87,7 @@ pub struct Cpu { } impl FromGenericCpuInfo for Cpu { - fn from_limits(cpu_index: usize, limits: limits_core::json::GenericCpuLimit) -> Self { + fn from_limits(cpu_index: usize, limits: limits_core::json_v2::GenericCpuLimit) -> Self { let gen = GenericCpu::from_limits(cpu_index, limits.clone()); Self { generic: gen } } @@ -84,7 +96,7 @@ impl FromGenericCpuInfo for Cpu { other: CpuJson, version: u64, cpu_index: usize, - limits: limits_core::json::GenericCpuLimit, + limits: limits_core::json_v2::GenericCpuLimit, ) -> Self { let gen = GenericCpu::from_json_and_limits(other, version, cpu_index, limits); Self { generic: gen } diff --git a/backend/src/settings/generic_amd/gpu.rs b/backend/src/settings/generic_amd/gpu.rs index 9af3bc2..983bd29 100644 --- a/backend/src/settings/generic_amd/gpu.rs +++ b/backend/src/settings/generic_amd/gpu.rs @@ -4,12 +4,69 @@ use std::sync::Mutex; use crate::persist::GpuJson; use crate::settings::generic::Gpu as GenericGpu; use crate::settings::MinMax; -use crate::settings::TGpu; use crate::settings::{OnResume, OnSet, SettingError, SettingVariant}; +use crate::settings::{ProviderBuilder, TGpu}; + +fn msg_or_err( + output: &mut String, + msg: &str, + result: Result, +) { + use std::fmt::Write; + match result { + Ok(val) => writeln!(output, "{}: {}", msg, val).unwrap(), + Err(e) => writeln!(output, "{} failed: {}", msg, e).unwrap(), + } +} + +fn log_capabilities(ryzenadj: &RyzenAdj) { + log::info!( + "RyzenAdj v{}.{}.{}", + libryzenadj::libryzenadj_sys::RYZENADJ_REVISION_VER, + libryzenadj::libryzenadj_sys::RYZENADJ_MAJOR_VER, + libryzenadj::libryzenadj_sys::RYZENADJ_MINIOR_VER + ); + #[cfg(feature = "experimental")] + if let Some(x) = ryzenadj.get_init_table_err() { + log::warn!("RyzenAdj table init error: {}", x); + } + let mut log_msg = String::new(); + msg_or_err(&mut log_msg, "bios version", ryzenadj.get_bios_if_ver()); + msg_or_err( + &mut log_msg, + "refresh", + ryzenadj.refresh().map(|_| "success"), + ); + msg_or_err( + &mut log_msg, + "CPU family", + ryzenadj.get_cpu_family().map(|fam| { + let fam_dbg = format!("{:?}", fam); + format!("{} (#{})", fam_dbg, fam as i32) + }), + ); + msg_or_err( + &mut log_msg, + "get_fast_value (PPT)", + ryzenadj.get_fast_value(), + ); + msg_or_err( + &mut log_msg, + "get_slow_value (PPT)", + ryzenadj.get_slow_value(), + ); + msg_or_err(&mut log_msg, "get_gfx_clk", ryzenadj.get_gfx_clk()); + msg_or_err(&mut log_msg, "get_gfx_volt", ryzenadj.get_gfx_volt()); + + log::info!("RyzenAdj GPU info:\n{}", log_msg); +} fn ryzen_adj_or_log() -> Option> { match RyzenAdj::new() { - Ok(x) => Some(Mutex::new(x)), + Ok(x) => { + log_capabilities(&x); + Some(Mutex::new(x)) + } Err(e) => { log::error!("RyzenAdj init error: {}", e); None @@ -35,8 +92,8 @@ impl std::fmt::Debug for Gpu { } } -impl Gpu { - pub fn from_limits(limits: limits_core::json::GenericGpuLimit) -> Self { +impl ProviderBuilder for Gpu { + fn from_limits(limits: limits_core::json_v2::GenericGpuLimit) -> Self { Self { generic: GenericGpu::from_limits(limits), implementor: ryzen_adj_or_log(), @@ -44,10 +101,10 @@ impl Gpu { } } - pub fn from_json_and_limits( + fn from_json_and_limits( other: GpuJson, version: u64, - limits: limits_core::json::GenericGpuLimit, + limits: limits_core::json_v2::GenericGpuLimit, ) -> Self { Self { generic: GenericGpu::from_json_and_limits(other, version, limits), @@ -55,7 +112,9 @@ impl Gpu { state: Default::default(), } } +} +impl Gpu { fn set_all(&mut self) -> Result<(), Vec> { let mutex = match &self.implementor { Some(x) => x, @@ -253,9 +312,44 @@ impl OnSet for Gpu { impl crate::settings::OnPowerEvent for Gpu {} +impl crate::settings::OnLoad for Gpu { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Gpu { + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +fn bad_gpu_limits() -> crate::api::GpuLimits { + crate::api::GpuLimits { + fast_ppt_limits: None, + fast_ppt_default: 1, + slow_ppt_limits: None, + slow_ppt_default: 1, + ppt_step: 1, + tdp_limits: None, + tdp_boost_limits: None, + tdp_step: 1, + clock_min_limits: None, + clock_max_limits: None, + clock_step: 100, + memory_control: None, + memory_step: 400, + } +} + impl TGpu for Gpu { fn limits(&self) -> crate::api::GpuLimits { - self.generic.limits() + if self.implementor.is_some() { + // NOTE: since set functions may succeed when gets do not, there is no good way to (automatically) check whether things are working + self.generic.limits() + } else { + bad_gpu_limits() + } } fn json(&self) -> crate::persist::GpuJson { @@ -278,8 +372,12 @@ impl TGpu for Gpu { self.generic.get_clock_limits() } - fn slow_memory(&mut self) -> &mut bool { - self.generic.slow_memory() + fn memory_clock(&mut self, speed: Option) { + self.generic.memory_clock(speed) + } + + fn get_memory_clock(&self) -> Option { + self.generic.get_memory_clock() } fn provider(&self) -> crate::persist::DriverJson { diff --git a/backend/src/settings/generic_amd/mod.rs b/backend/src/settings/generic_amd/mod.rs index 6a8e412..c395fc1 100644 --- a/backend/src/settings/generic_amd/mod.rs +++ b/backend/src/settings/generic_amd/mod.rs @@ -1,5 +1,16 @@ mod cpu; mod gpu; -pub use cpu::{Cpu, Cpus}; +pub use cpu::Cpus; pub use gpu::Gpu; + +fn _impl_checker() { + fn impl_provider_builder, J, L>() {} + + impl_provider_builder::< + Cpus, + Vec, + limits_core::json_v2::GenericCpusLimit, + >(); + impl_provider_builder::(); +} diff --git a/backend/src/settings/mod.rs b/backend/src/settings/mod.rs index c3a0044..4e74636 100644 --- a/backend/src/settings/mod.rs +++ b/backend/src/settings/mod.rs @@ -6,24 +6,37 @@ mod min_max; mod traits; mod util; +pub mod dev_mode; pub mod generic; pub mod generic_amd; pub mod steam_deck; pub mod unknown; -pub use detect::{auto_detect0, auto_detect_provider, limits_worker::spawn as limits_worker_spawn, get_dev_messages}; +pub use detect::{ + auto_detect0, auto_detect_provider, get_dev_messages, + limits_worker::spawn as limits_worker_spawn, +}; pub use driver::Driver; pub use general::{General, SettingVariant, Settings}; pub use min_max::{min_max_from_json, MinMax}; pub use error::SettingError; -pub use traits::{OnPowerEvent, OnResume, OnSet, PowerMode, TBattery, TCpu, TCpus, TGeneral, TGpu}; +pub use traits::{ + OnLoad, OnPowerEvent, OnResume, OnSet, OnUnload, PowerMode, ProviderBuilder, TBattery, TCpu, + TCpus, TGeneral, TGpu, +}; #[cfg(test)] mod tests { #[test] fn system_defaults_test() { - let settings = super::Settings::system_default("idc".into(), "Cool name".into()); + let settings = super::Settings::system_default( + "idc".into(), + 0, + "Cool name".into(), + 0, + "Variant 0".into(), + ); println!("Loaded system settings: {:?}", settings); } } diff --git a/backend/src/settings/steam_deck/battery.rs b/backend/src/settings/steam_deck/battery.rs index d03bfd6..d48e2a5 100644 --- a/backend/src/settings/steam_deck/battery.rs +++ b/backend/src/settings/steam_deck/battery.rs @@ -1,26 +1,35 @@ use std::convert::Into; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; -use sysfuss::{PowerSupplyAttribute, PowerSupplyPath, HwMonAttribute, HwMonAttributeItem, HwMonAttributeType, HwMonPath, SysEntity, SysEntityAttributesExt, SysAttribute}; use sysfuss::capability::attributes; +use sysfuss::{ + HwMonAttribute, HwMonAttributeItem, HwMonAttributeType, HwMonPath, PowerSupplyAttribute, + PowerSupplyPath, SysAttribute, SysEntity, SysEntityAttributesExt, +}; + +use limits_core::json_v2::GenericBatteryLimit; -use super::oc_limits::{BatteryLimits, OverclockLimits}; -use super::util::ChargeMode; use crate::api::RangeLimit; use crate::persist::{BatteryEventJson, BatteryJson}; -use crate::settings::TBattery; use crate::settings::{OnPowerEvent, OnResume, OnSet, PowerMode, SettingError}; +use crate::settings::{ProviderBuilder, TBattery}; +use smokepatio::ec::{ + unnamed_power::{ChargeMode, UnnamedPowerEC}, + ControllerSet, +}; #[derive(Debug, Clone)] pub struct Battery { pub charge_rate: Option, pub charge_mode: Option, + pub charge_limit: Option, events: Vec, - limits: BatteryLimits, + limits: GenericBatteryLimit, state: crate::state::steam_deck::Battery, - driver_mode: crate::persist::DriverJson, sysfs_bat: PowerSupplyPath, sysfs_hwmon: Arc, + bat_ec: Arc>, + variant: super::Model, } #[derive(Debug, Clone)] @@ -39,6 +48,7 @@ struct EventInstruction { charge_mode: Option, is_triggered: bool, sysfs_hwmon: Arc, + bat_ec: Arc>, } impl OnPowerEvent for EventInstruction { @@ -91,6 +101,7 @@ impl EventInstruction { match mode { EventTrigger::PluggedIn => "plug-in".to_owned(), EventTrigger::PluggedOut => "plug-out".to_owned(), + // EventInstruction uses 1.0 to represent full, but Strings use 100.0 EventTrigger::BatteryAbove(x) => format!(">{:#0.2}", x * 100.0), EventTrigger::BatteryBelow(x) => format!("<{:#0.2}", x * 100.0), EventTrigger::Ignored => "/shrug".to_owned(), @@ -116,7 +127,12 @@ impl EventInstruction { } } - fn from_json(other: BatteryEventJson, _version: u64, hwmon: Arc) -> Self { + fn from_json( + other: BatteryEventJson, + _version: u64, + hwmon: Arc, + ec: Arc>, + ) -> Self { Self { trigger: Self::str_to_trigger(&other.trigger).unwrap_or(EventTrigger::Ignored), charge_rate: other.charge_rate, @@ -126,17 +142,20 @@ impl EventInstruction { .flatten(), is_triggered: false, sysfs_hwmon: hwmon, + bat_ec: ec, } } fn set_charge_mode(&self) -> Result<(), SettingError> { if let Some(charge_mode) = self.charge_mode { - super::util::set(super::util::Setting::ChargeMode, charge_mode as _) - .map_err(|e| SettingError { - msg: format!("Failed to set charge mode: {}", e), - setting: crate::settings::SettingVariant::Battery, - }) - .map(|_| ()) + let mut lock = self + .bat_ec + .lock() + .expect("failed to lock battery controller"); + lock.set(charge_mode).map_err(|_| SettingError { + msg: format!("Failed to set charge mode"), + setting: crate::settings::SettingVariant::Battery, + }) } else { Ok(()) } @@ -144,17 +163,15 @@ impl EventInstruction { fn set_charge_rate(&self) -> Result<(), SettingError> { if let Some(charge_rate) = self.charge_rate { - let attr = if MAX_BATTERY_CHARGE_RATE_ATTR.exists(&*self.sysfs_hwmon) { - MAX_BATTERY_CHARGE_RATE_ATTR - } else { - MAXIMUM_BATTERY_CHARGE_RATE_ATTR - }; - self.sysfs_hwmon.set(attr, charge_rate).map_err( - |e| SettingError { - msg: format!("Failed to write to `{:?}`: {}", attr, e), + self.sysfs_hwmon + .set(MAX_BATTERY_CHARGE_RATE_ATTR, charge_rate) + .map_err(|e| SettingError { + msg: format!( + "Failed to write to `{:?}`: {}", + MAX_BATTERY_CHARGE_RATE_ATTR, e + ), setting: crate::settings::SettingVariant::Battery, - }, - ) + }) } else { Ok(()) } @@ -194,7 +211,6 @@ const BATTERY_CHARGE_DESIGN_PATH: &str = "/sys/class/power_supply/BAT1/charge_fu const USB_PD_IN_MVOLTAGE_PATH: &str = "/sys/class/hwmon/hwmon5/in0_input"; // read-only const USB_PD_IN_CURRENT_PATH: &str = "/sys/class/hwmon/hwmon5/curr1_input"; // read-only*/ - const BATTERY_NEEDS: &[PowerSupplyAttribute] = &[ PowerSupplyAttribute::Type, PowerSupplyAttribute::CurrentNow, @@ -213,59 +229,15 @@ const HWMON_NEEDS: &[HwMonAttribute] = &[ //HwMonAttribute::custom("maximum_battery_charge_rate"), // NOTE: Cannot filter by custom capabilities ]; -const MAXIMUM_BATTERY_CHARGE_RATE_ATTR: HwMonAttribute = HwMonAttribute::custom("maximum_battery_charge_rate"); -const MAX_BATTERY_CHARGE_RATE_ATTR: HwMonAttribute = HwMonAttribute::custom("maximum_battery_charge_rate"); -const MAX_BATTERY_CHARGE_LEVEL_ATTR: HwMonAttribute = HwMonAttribute::custom("max_battery_charge_level"); +const MAX_BATTERY_CHARGE_RATE_ATTR: HwMonAttribute = + HwMonAttribute::custom("maximum_battery_charge_rate"); +const MAX_BATTERY_CHARGE_LEVEL_ATTR: HwMonAttribute = + HwMonAttribute::custom("max_battery_charge_level"); + +const MAX_CHARGE_RATE: u64 = 2500; +const MIN_CHARGE_RATE: u64 = 250; impl Battery { - #[inline] - pub fn from_json(other: BatteryJson, version: u64) -> Self { - let (oc_limits, is_default) = OverclockLimits::load_or_default(); - let oc_limits = oc_limits.battery; - let driver = if is_default { - crate::persist::DriverJson::SteamDeck - } else { - crate::persist::DriverJson::SteamDeckAdvance - }; - let hwmon_sys = Arc::new(Self::find_hwmon_sysfs(None::<&'static str>)); - match version { - 0 => Self { - charge_rate: other.charge_rate, - charge_mode: other - .charge_mode - .map(|x| Self::str_to_charge_mode(&x)) - .flatten(), - events: other - .events - .into_iter() - .map(|x| EventInstruction::from_json(x, version, hwmon_sys.clone())) - .collect(), - limits: oc_limits, - state: crate::state::steam_deck::Battery::default(), - driver_mode: driver, - sysfs_bat: Self::find_battery_sysfs(None::<&'static str>), - sysfs_hwmon: hwmon_sys, - }, - _ => Self { - charge_rate: other.charge_rate, - charge_mode: other - .charge_mode - .map(|x| Self::str_to_charge_mode(&x)) - .flatten(), - events: other - .events - .into_iter() - .map(|x| EventInstruction::from_json(x, version, hwmon_sys.clone())) - .collect(), - limits: oc_limits, - state: crate::state::steam_deck::Battery::default(), - driver_mode: driver, - sysfs_bat: Self::find_battery_sysfs(None::<&'static str>), - sysfs_hwmon: hwmon_sys, - }, - } - } - fn find_battery_sysfs(root: Option>) -> PowerSupplyPath { let root = crate::settings::util::root_or_default_sysfs(root); match root.power_supply(attributes(BATTERY_NEEDS.into_iter().copied())) { @@ -275,9 +247,12 @@ impl Battery { log::error!("Failed to find SteamDeck battery power_supply in sysfs (no results), using naive fallback"); root.power_supply_by_name("BAT1") }); - log::info!("Found SteamDeck battery power_supply in sysfs: {}", psu.as_ref().display()); + log::info!( + "Found SteamDeck battery power_supply in sysfs: {}", + psu.as_ref().display() + ); psu - }, + } Err(e) => { log::error!("Failed to find SteamDeck battery power_supply in sysfs ({}), using naive fallback", e); root.power_supply_by_name("BAT1") @@ -291,11 +266,15 @@ impl Battery { Ok(hwmon) => { if !hwmon.capable(attributes(HWMON_NEEDS.into_iter().copied())) { log::warn!("Found incapable SteamDeck battery hwmon in sysfs (hwmon by name {} exists but missing attributes), persevering because ignorance is bliss", super::util::JUPITER_HWMON_NAME); - } else { - log::info!("Found SteamDeck battery hwmon {} in sysfs: {}", super::util::JUPITER_HWMON_NAME, hwmon.as_ref().display()); + } else { + log::info!( + "Found SteamDeck battery hwmon {} in sysfs: {}", + super::util::JUPITER_HWMON_NAME, + hwmon.as_ref().display() + ); } hwmon - }, + } Err(e) => { log::warn!("Failed to find SteamDeck battery hwmon {} in sysfs ({}), trying alternate name", super::util::JUPITER_HWMON_NAME, e); @@ -304,10 +283,14 @@ impl Battery { if !hwmon.capable(attributes(HWMON_NEEDS.into_iter().copied())) { log::warn!("Found incapable SteamDeck battery hwmon in sysfs (hwmon by name {} exists but missing attributes), persevering because ignorance is bliss", super::util::STEAMDECK_HWMON_NAME); } else { - log::info!("Found SteamDeck battery hwmon {} in sysfs: {}", super::util::STEAMDECK_HWMON_NAME, hwmon.as_ref().display()); + log::info!( + "Found SteamDeck battery hwmon {} in sysfs: {}", + super::util::STEAMDECK_HWMON_NAME, + hwmon.as_ref().display() + ); } hwmon - }, + } Err(e) => { log::error!("Failed to find SteamDeck battery hwmon {} in sysfs ({}), using naive fallback", super::util::STEAMDECK_HWMON_NAME, e); root.hwmon_by_index(5) @@ -340,32 +323,28 @@ impl Battery { fn set_charge_rate(&mut self) -> Result<(), SettingError> { if let Some(charge_rate) = self.charge_rate { self.state.charge_rate_set = true; - let attr = if MAX_BATTERY_CHARGE_RATE_ATTR.exists(&*self.sysfs_hwmon) { - MAX_BATTERY_CHARGE_RATE_ATTR - } else { - MAXIMUM_BATTERY_CHARGE_RATE_ATTR - }; - let path = attr.path(&*self.sysfs_hwmon); - self.sysfs_hwmon.set(attr, charge_rate).map_err( - |e| SettingError { + let path = MAX_BATTERY_CHARGE_RATE_ATTR.path(&*self.sysfs_hwmon); + self.sysfs_hwmon + .set(MAX_BATTERY_CHARGE_RATE_ATTR, charge_rate) + .map_err(|e| SettingError { msg: format!("Failed to write to `{}`: {}", path.display(), e), setting: crate::settings::SettingVariant::Battery, - }, - ) + }) } else if self.state.charge_rate_set { self.state.charge_rate_set = false; - let attr = if MAX_BATTERY_CHARGE_RATE_ATTR.exists(&*self.sysfs_hwmon) { - MAX_BATTERY_CHARGE_RATE_ATTR - } else { - MAXIMUM_BATTERY_CHARGE_RATE_ATTR - }; - let path = attr.path(&*self.sysfs_hwmon); - self.sysfs_hwmon.set(attr, self.limits.charge_rate.max,).map_err( - |e| SettingError { + let path = MAX_BATTERY_CHARGE_RATE_ATTR.path(&*self.sysfs_hwmon); + self.sysfs_hwmon + .set( + MAX_BATTERY_CHARGE_RATE_ATTR, + self.limits + .charge_rate + .and_then(|lim| lim.max) + .unwrap_or(2500), + ) + .map_err(|e| SettingError { msg: format!("Failed to write to `{}`: {}", path.display(), e), setting: crate::settings::SettingVariant::Battery, - }, - ) + }) } else { Ok(()) } @@ -374,20 +353,60 @@ impl Battery { fn set_charge_mode(&mut self) -> Result<(), SettingError> { if let Some(charge_mode) = self.charge_mode { self.state.charge_mode_set = true; - super::util::set(super::util::Setting::ChargeMode, charge_mode as _) - .map_err(|e| SettingError { - msg: format!("Failed to set charge mode: {}", e), - setting: crate::settings::SettingVariant::Battery, - }) - .map(|_| ()) + let mut lock = self + .bat_ec + .lock() + .expect("Failed to lock battery controller"); + lock.set(charge_mode).map_err(|_| SettingError { + msg: format!("Failed to set charge mode"), + setting: crate::settings::SettingVariant::Battery, + }) } else if self.state.charge_mode_set { self.state.charge_mode_set = false; - super::util::set(super::util::Setting::ChargeMode, ChargeMode::Normal as _) + let mut lock = self + .bat_ec + .lock() + .expect("Failed to lock battery controller"); + lock.set(ChargeMode::Normal).map_err(|_| SettingError { + msg: format!("Failed to set charge mode"), + setting: crate::settings::SettingVariant::Battery, + }) + } else { + Ok(()) + } + } + + fn set_charge_limit(&mut self) -> Result<(), SettingError> { + let attr_exists = MAX_BATTERY_CHARGE_LEVEL_ATTR.exists(&*self.sysfs_hwmon); + log::debug!( + "Does battery limit attribute (max_battery_charge_level) exist? {}", + attr_exists + ); + if let Some(charge_limit) = self.charge_limit { + self.state.charge_limit_set = true; + self.sysfs_hwmon + .set( + MAX_BATTERY_CHARGE_LEVEL_ATTR, + (charge_limit * 100.0).round() as u64, + ) .map_err(|e| SettingError { - msg: format!("Failed to set charge mode: {}", e), + msg: format!( + "Failed to write to {:?}: {}", + MAX_BATTERY_CHARGE_LEVEL_ATTR, e + ), + setting: crate::settings::SettingVariant::Battery, + }) + } else if self.state.charge_limit_set { + self.state.charge_limit_set = false; + self.sysfs_hwmon + .set(MAX_BATTERY_CHARGE_LEVEL_ATTR, 0) + .map_err(|e| SettingError { + msg: format!( + "Failed to reset (write to) {:?}: {}", + MAX_BATTERY_CHARGE_LEVEL_ATTR, e + ), setting: crate::settings::SettingVariant::Battery, }) - .map(|_| ()) } else { Ok(()) } @@ -397,6 +416,7 @@ impl Battery { let mut errors = Vec::new(); self.set_charge_rate().unwrap_or_else(|e| errors.push(e)); self.set_charge_mode().unwrap_or_else(|e| errors.push(e)); + self.set_charge_limit().unwrap_or_else(|e| errors.push(e)); if errors.is_empty() { Ok(()) } else { @@ -406,8 +426,16 @@ impl Battery { fn clamp_all(&mut self) { if let Some(charge_rate) = &mut self.charge_rate { - *charge_rate = - (*charge_rate).clamp(self.limits.charge_rate.min, self.limits.charge_rate.max); + *charge_rate = (*charge_rate).clamp( + self.limits + .charge_rate + .and_then(|lim| lim.min) + .unwrap_or(MIN_CHARGE_RATE), + self.limits + .charge_rate + .and_then(|lim| lim.max) + .unwrap_or(MAX_CHARGE_RATE), + ); } } @@ -489,26 +517,6 @@ impl Battery { } } - pub fn system_default() -> Self { - let (oc_limits, is_default) = OverclockLimits::load_or_default(); - let oc_limits = oc_limits.battery; - let driver = if is_default { - crate::persist::DriverJson::SteamDeck - } else { - crate::persist::DriverJson::SteamDeckAdvance - }; - Self { - charge_rate: None, - charge_mode: None, - events: Vec::new(), - limits: oc_limits, - state: crate::state::steam_deck::Battery::default(), - driver_mode: driver, - sysfs_bat: Self::find_battery_sysfs(None::<&'static str>), - sysfs_hwmon: Arc::new(Self::find_hwmon_sysfs(None::<&'static str>)), - } - } - fn find_limit_event(&self) -> Option { for (i, event) in self.events.iter().enumerate() { match event.trigger { @@ -536,20 +544,159 @@ impl Battery { } None } + + fn remove_charge_limit_instructions(mut self) -> Self { + if let Some(lim_ev) = self.find_limit_event() { + log::debug!("Found limit event @ {}", lim_ev); + if let Some(unlim_ev) = self.find_unlimit_event() { + log::debug!("Found unlimit event @ {}", unlim_ev); + self.charge_limit = match &self.events[lim_ev].trigger { + EventTrigger::BatteryAbove(x) => Some(*x), + _ => panic!("Got limit event with wrong event trigger variant"), + }; + log::debug!("Charge limit detected as {}", self.charge_limit.unwrap()); + if lim_ev > unlim_ev { + self.events.remove(lim_ev); + self.events.remove(unlim_ev); + } else { + self.events.remove(unlim_ev); + self.events.remove(lim_ev); + } + } + } + self + } + + fn with_charge_limit_instructions(&self) -> Vec { + if let Some(limit) = self.charge_limit { + log::debug!("Adding charge limit event instructions for limit {}", limit); + let mut events = self.events.clone(); + // upper limit + log::info!( + "Creating Steam Deck charge limit event instruction of >{}", + limit + ); + events.push(EventInstruction { + trigger: EventTrigger::BatteryAbove(limit), + charge_rate: None, + charge_mode: Some(ChargeMode::Idle), + is_triggered: false, + sysfs_hwmon: self.sysfs_hwmon.clone(), + bat_ec: self.bat_ec.clone(), + }); + // lower limit + let limit = (limit - 0.10).clamp(0.0, 1.0); + log::info!( + "Creating Steam Deck charge limit event instruction of <{}", + limit + ); + events.push(EventInstruction { + trigger: EventTrigger::BatteryBelow(limit), + charge_rate: None, + charge_mode: Some(ChargeMode::Normal), + is_triggered: false, + sysfs_hwmon: self.sysfs_hwmon.clone(), + bat_ec: self.bat_ec.clone(), + }); + events + } else { + log::debug!("No charge limit set, skipping add of event instructions"); + self.events.clone() + } + } + + pub fn variant(mut self, model: super::Model) -> Self { + self.variant = model; + self + } } impl Into for Battery { #[inline] fn into(self) -> BatteryJson { + let events = self.with_charge_limit_instructions(); BatteryJson { charge_rate: self.charge_rate, charge_mode: self.charge_mode.map(Self::charge_mode_to_str), - events: self.events.into_iter().map(|x| x.into()).collect(), - root: self.sysfs_bat.root().or(self.sysfs_hwmon.root()).and_then(|p| p.as_ref().to_str().map(|x| x.to_owned())) + events: events.into_iter().map(|x| x.into()).collect(), + root: self + .sysfs_bat + .root() + .or(self.sysfs_hwmon.root()) + .and_then(|p| p.as_ref().to_str().map(|x| x.to_owned())), } } } +impl ProviderBuilder for Battery { + fn from_json_and_limits( + persistent: BatteryJson, + version: u64, + limits: GenericBatteryLimit, + ) -> Self { + let hwmon_sys = Arc::new(Self::find_hwmon_sysfs(None::<&'static str>)); + let ec = Arc::new(Mutex::new(UnnamedPowerEC::new())); + match version { + 0 => Self { + charge_rate: persistent.charge_rate, + charge_mode: persistent + .charge_mode + .map(|x| Self::str_to_charge_mode(&x)) + .flatten(), + charge_limit: None, + events: persistent + .events + .into_iter() + .map(|x| EventInstruction::from_json(x, version, hwmon_sys.clone(), ec.clone())) + .collect(), + limits: limits, + state: crate::state::steam_deck::Battery::default(), + sysfs_bat: Self::find_battery_sysfs(None::<&'static str>), + sysfs_hwmon: hwmon_sys, + bat_ec: ec, + variant: super::Model::LCD, + } + .remove_charge_limit_instructions(), + _ => Self { + charge_rate: persistent.charge_rate, + charge_mode: persistent + .charge_mode + .map(|x| Self::str_to_charge_mode(&x)) + .flatten(), + charge_limit: None, + events: persistent + .events + .into_iter() + .map(|x| EventInstruction::from_json(x, version, hwmon_sys.clone(), ec.clone())) + .collect(), + limits: limits, + state: crate::state::steam_deck::Battery::default(), + sysfs_bat: Self::find_battery_sysfs(None::<&'static str>), + sysfs_hwmon: hwmon_sys, + bat_ec: ec, + variant: super::Model::LCD, + } + .remove_charge_limit_instructions(), + } + } + + fn from_limits(limits: GenericBatteryLimit) -> Self { + Self { + charge_rate: None, + charge_mode: None, + charge_limit: None, + events: Vec::new(), + limits: limits, + state: crate::state::steam_deck::Battery::default(), + sysfs_bat: Self::find_battery_sysfs(None::<&'static str>), + sysfs_hwmon: Arc::new(Self::find_hwmon_sysfs(None::<&'static str>)), + bat_ec: Arc::new(Mutex::new(UnnamedPowerEC::new())), + variant: super::Model::LCD, + } + .remove_charge_limit_instructions() + } +} + impl OnSet for Battery { fn on_set(&mut self) -> Result<(), Vec> { self.clamp_all(); @@ -584,22 +731,7 @@ impl OnPowerEvent for Battery { PowerMode::BatteryCharge(_) => Ok(()), } .unwrap_or_else(|mut e| errors.append(&mut e)); - let attr_exists = MAX_BATTERY_CHARGE_LEVEL_ATTR.exists(&*self.sysfs_hwmon); - log::info!("Does battery limit attribute (max_battery_charge_level) exist? {}", attr_exists); for ev in &mut self.events { - if attr_exists { - if let EventTrigger::BatteryAbove(level) = ev.trigger { - if let Some(ChargeMode::Idle) = ev.charge_mode { - self.sysfs_hwmon.set(MAX_BATTERY_CHARGE_LEVEL_ATTR, (level * 100.0).round() as u64) - .unwrap_or_else(|e| errors.push( - SettingError { - msg: format!("Failed to write to {:?}: {}", MAX_BATTERY_CHARGE_LEVEL_ATTR, e), - setting: crate::settings::SettingVariant::Battery, - } - )); - } - } - } ev.on_power_event(new_mode) .unwrap_or_else(|mut e| errors.append(&mut e)); } @@ -611,12 +743,32 @@ impl OnPowerEvent for Battery { } } +impl crate::settings::OnLoad for Battery { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Battery { + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + impl TBattery for Battery { fn limits(&self) -> crate::api::BatteryLimits { crate::api::BatteryLimits { charge_current: Some(RangeLimit { - min: self.limits.charge_rate.min, - max: self.limits.charge_rate.max, + min: self + .limits + .charge_rate + .and_then(|lim| lim.min) + .unwrap_or(MIN_CHARGE_RATE), + max: self + .limits + .charge_rate + .and_then(|lim| lim.max) + .unwrap_or(MAX_CHARGE_RATE), }), charge_current_step: 50, charge_modes: vec![ @@ -711,84 +863,11 @@ impl TBattery for Battery { } fn charge_limit(&mut self, limit: Option) { - // upper limit - let index = self.find_limit_event(); - if let Some(index) = index { - if let Some(limit) = limit { - log::info!( - "Updating Steam Deck charge limit event instruction to >{}", - limit - ); - self.events[index] = EventInstruction { - trigger: EventTrigger::BatteryAbove(limit / 100.0), - charge_rate: None, - charge_mode: Some(ChargeMode::Idle), - is_triggered: false, - sysfs_hwmon: self.sysfs_hwmon.clone(), - }; - } else { - self.events.remove(index); - } - } else if let Some(limit) = limit { - log::info!( - "Creating Steam Deck charge limit event instruction of >{}", - limit - ); - self.events.push(EventInstruction { - trigger: EventTrigger::BatteryAbove(limit / 100.0), - charge_rate: None, - charge_mode: Some(ChargeMode::Idle), - is_triggered: false, - sysfs_hwmon: self.sysfs_hwmon.clone(), - }); - } - // lower limit - let index = self.find_unlimit_event(); - if let Some(index) = index { - if let Some(limit) = limit { - let limit = (limit - 10.0).clamp(0.0, 100.0); - log::info!( - "Updating Steam Deck charge limit event instruction to <{}", - limit - ); - self.events[index] = EventInstruction { - trigger: EventTrigger::BatteryBelow(limit / 100.0), - charge_rate: None, - charge_mode: Some(ChargeMode::Normal), - is_triggered: false, - sysfs_hwmon: self.sysfs_hwmon.clone(), - }; - } else { - self.events.remove(index); - } - } else if let Some(limit) = limit { - let limit = (limit - 10.0).clamp(0.0, 100.0); - log::info!( - "Creating Steam Deck charge limit event instruction of <{}", - limit - ); - self.events.push(EventInstruction { - trigger: EventTrigger::BatteryBelow(limit / 100.0), - charge_rate: None, - charge_mode: Some(ChargeMode::Normal), - is_triggered: false, - sysfs_hwmon: self.sysfs_hwmon.clone(), - }); - } + self.charge_limit = limit.map(|lim| lim / 100.0); } fn get_charge_limit(&self) -> Option { - let index = self.find_limit_event(); - if let Some(index) = index { - if let EventTrigger::BatteryAbove(limit) = self.events[index].trigger { - Some(limit * 100.0) - } else { - log::error!("Got index {} for battery charge limit which does not have expected event trigger: {:?}", index, &self.events); - None - } - } else { - None - } + self.charge_limit.map(|lim| lim * 100.0) } fn check_power(&mut self) -> Result, Vec> { @@ -828,6 +907,9 @@ impl TBattery for Battery { } fn provider(&self) -> crate::persist::DriverJson { - self.driver_mode.clone() + match self.variant { + super::Model::LCD => crate::persist::DriverJson::SteamDeck, + super::Model::OLED => crate::persist::DriverJson::SteamDeckOLED, + } } } diff --git a/backend/src/settings/steam_deck/cpu.rs b/backend/src/settings/steam_deck/cpu.rs index a6e4d10..e76df46 100644 --- a/backend/src/settings/steam_deck/cpu.rs +++ b/backend/src/settings/steam_deck/cpu.rs @@ -2,28 +2,33 @@ use std::convert::Into; use sysfuss::{BasicEntityPath, SysEntity, SysEntityAttributesExt}; -use super::oc_limits::{CpuLimits, CpusLimits, OverclockLimits}; +use limits_core::json_v2::{GenericCpuLimit, GenericCpusLimit}; + +use super::util::{range_max_or_fallback, range_min_or_fallback}; use super::POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT; use crate::api::RangeLimit; use crate::persist::CpuJson; use crate::settings::{min_max_from_json, MinMax}; use crate::settings::{OnResume, OnSet, SettingError}; -use crate::settings::{TCpu, TCpus}; +use crate::settings::{ProviderBuilder, TCpu, TCpus}; const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control"; -const CARD_EXTENSIONS: &[&'static str] = &[ - super::DPM_FORCE_LIMITS_ATTRIBUTE -]; +const CARD_EXTENSIONS: &[&'static str] = &[super::DPM_FORCE_LIMITS_ATTRIBUTE]; + +const MAX_CLOCK: u64 = 3500; +const MIN_MAX_CLOCK: u64 = 200; // minimum value allowed for maximum CPU clock, MHz +const MIN_MIN_CLOCK: u64 = 1400; // minimum value allowed for minimum CPU clock, MHz +const CLOCK_STEP: u64 = 100; #[derive(Debug, Clone)] pub struct Cpus { pub cpus: Vec, pub smt: bool, pub smt_capable: bool, - pub(super) limits: CpusLimits, - driver_mode: crate::persist::DriverJson, + pub(super) limits: GenericCpusLimit, + variant: super::Model, } impl OnSet for Cpus { @@ -95,84 +100,49 @@ impl Cpus { } } - pub fn system_default() -> Self { - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.reset(); - let (oc_limits, is_default) = OverclockLimits::load_or_default(); - let oc_limits = oc_limits.cpus; - let driver = if is_default { - crate::persist::DriverJson::SteamDeck - } else { - crate::persist::DriverJson::SteamDeckAdvance - }; - if let Some(max_cpu) = Self::cpu_count() { - let mut sys_cpus = Vec::with_capacity(max_cpu); - for i in 0..max_cpu { - sys_cpus.push(Cpu::system_default( - i, - oc_limits - .cpus - .get(i) - .map(|x| x.to_owned()) - .unwrap_or_default(), - )); - } - let (_, can_smt) = Self::system_smt_capabilities(); - Self { - cpus: sys_cpus, - smt: true, - smt_capable: can_smt, - limits: oc_limits, - driver_mode: driver, - } - } else { - Self { - cpus: vec![], - smt: false, - smt_capable: false, - limits: oc_limits, - driver_mode: driver, - } + pub fn variant(mut self, model: super::Model) -> Self { + self.variant = model; + for cpu in self.cpus.iter_mut() { + cpu.variant(model) } + self } +} - #[inline] - pub fn from_json(mut other: Vec, version: u64) -> Self { +impl ProviderBuilder, GenericCpusLimit> for Cpus { + fn from_json_and_limits( + mut persistent: Vec, + version: u64, + limits: GenericCpusLimit, + ) -> Self { POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.reset(); - let (oc_limits, is_default) = OverclockLimits::load_or_default(); - let oc_limits = oc_limits.cpus; - let driver = if is_default { - crate::persist::DriverJson::SteamDeck - } else { - crate::persist::DriverJson::SteamDeckAdvance - }; let (_, can_smt) = Self::system_smt_capabilities(); - let mut result = Vec::with_capacity(other.len()); + let mut result = Vec::with_capacity(persistent.len()); let max_cpus = Self::cpu_count(); - let smt_guess = crate::settings::util::guess_smt(&other) && can_smt; - for (i, cpu) in other.drain(..).enumerate() { + let smt_guess = crate::settings::util::guess_smt(&persistent) && can_smt; + for (i, cpu) in persistent.drain(..).enumerate() { // prevent having more CPUs than available if let Some(max_cpus) = max_cpus { if i == max_cpus { break; } } - let new_cpu = Cpu::from_json( - cpu, - version, - i, - oc_limits - .cpus - .get(i) - .map(|x| x.to_owned()) - .unwrap_or_default(), - ); + let new_cpu = if let Some(cpu_limit) = limits.cpus.get(i) { + let mut cpu_limit_clone = cpu_limit.to_owned(); + for item in &limits.extras.quirks { + cpu_limit_clone.extras.quirks.insert(item.to_owned()); + } + cpu_limit_clone.extras.experiments |= limits.extras.experiments; + Cpu::from_json_and_limits(cpu, version, i, cpu_limit_clone) + } else { + Cpu::from_json(cpu, version, i) + }; result.push(new_cpu); } if let Some(max_cpus) = max_cpus { if result.len() != max_cpus { - let mut sys_cpus = Cpus::system_default(); - for i in result.len()..sys_cpus.cpus.len() { - result.push(sys_cpus.cpus.remove(i)); + for i in result.len()..max_cpus { + result.push(Cpu::system_default(i)); } } } @@ -180,14 +150,74 @@ impl Cpus { cpus: result, smt: smt_guess, smt_capable: can_smt, - limits: oc_limits, - driver_mode: driver, + limits: limits, + variant: super::Model::LCD, + } + } + + fn from_limits(limits: GenericCpusLimit) -> Self { + POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.reset(); + if let Some(max_cpu) = Self::cpu_count() { + let mut sys_cpus = Vec::with_capacity(max_cpu); + for i in 0..max_cpu { + let new_cpu = if let Some(cpu_limit) = limits.cpus.get(i) { + Cpu::from_limits(i, cpu_limit.to_owned()) + } else { + Cpu::system_default(i) + }; + sys_cpus.push(new_cpu); + } + let (_, can_smt) = Self::system_smt_capabilities(); + Self { + cpus: sys_cpus, + smt: true, + smt_capable: can_smt, + limits: limits, + variant: super::Model::LCD, + } + } else { + Self { + cpus: vec![], + smt: false, + smt_capable: false, + limits: limits, + variant: super::Model::LCD, + } } } } impl crate::settings::OnPowerEvent for Cpus {} +impl crate::settings::OnLoad for Cpus { + fn on_load(&mut self) -> Result<(), Vec> { + let mut errors = Vec::new(); + for cpu in &mut self.cpus { + cpu.on_load().unwrap_or_else(|mut e| errors.append(&mut e)); + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + +impl crate::settings::OnUnload for Cpus { + fn on_unload(&mut self) -> Result<(), Vec> { + let mut errors = Vec::new(); + for cpu in &mut self.cpus { + cpu.on_unload() + .unwrap_or_else(|mut e| errors.append(&mut e)); + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + impl TCpus for Cpus { fn limits(&self) -> crate::api::CpusLimits { crate::api::CpusLimits { @@ -224,7 +254,10 @@ impl TCpus for Cpus { } fn provider(&self) -> crate::persist::DriverJson { - self.driver_mode.clone() + match self.variant { + super::Model::LCD => crate::persist::DriverJson::SteamDeck, + super::Model::OLED => crate::persist::DriverJson::SteamDeckOLED, + } } } @@ -233,10 +266,11 @@ pub struct Cpu { pub online: bool, pub clock_limits: Option>, pub governor: String, - limits: CpuLimits, + limits: GenericCpuLimit, index: usize, state: crate::state::steam_deck::Cpu, sysfs: BasicEntityPath, + variant: super::Model, } //const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; @@ -249,7 +283,12 @@ enum ClockType { impl Cpu { #[inline] - fn from_json(other: CpuJson, version: u64, i: usize, oc_limits: CpuLimits) -> Self { + fn from_json_and_limits( + other: CpuJson, + version: u64, + i: usize, + oc_limits: GenericCpuLimit, + ) -> Self { match version { 0 => Self { online: other.online, @@ -259,6 +298,7 @@ impl Cpu { index: i, state: crate::state::steam_deck::Cpu::default(), sysfs: Self::find_card_sysfs(other.root), + variant: super::Model::LCD, }, _ => Self { online: other.online, @@ -268,13 +308,28 @@ impl Cpu { index: i, state: crate::state::steam_deck::Cpu::default(), sysfs: Self::find_card_sysfs(other.root), + variant: super::Model::LCD, }, } } + #[inline] + fn from_json(other: CpuJson, version: u64, i: usize) -> Self { + let oc_limits = + GenericCpuLimit::default_for(&limits_core::json_v2::CpuLimitType::SteamDeck, i); + Self::from_json_and_limits(other, version, i, oc_limits) + } + fn find_card_sysfs(root: Option>) -> BasicEntityPath { let root = crate::settings::util::root_or_default_sysfs(root); - match root.class("drm", sysfuss::capability::attributes(crate::settings::util::CARD_NEEDS.into_iter().map(|s| s.to_string()))) { + match root.class( + "drm", + sysfuss::capability::attributes( + crate::settings::util::CARD_NEEDS + .into_iter() + .map(|s| s.to_string()), + ), + ) { Ok(iter) => { let card = iter .filter(|ent| if let Ok(name) = ent.name() { name.starts_with("card")} else { false }) @@ -286,25 +341,62 @@ impl Cpu { }); log::info!("Found SteamDeck drm in sysfs: {}", card.as_ref().display()); card - }, + } Err(e) => { - log::error!("Failed to find SteamDeck drm in sysfs ({}), using naive fallback", e); + log::error!( + "Failed to find SteamDeck drm in sysfs ({}), using naive fallback", + e + ); BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0")) } } } - fn set_clock_limit(&self, index: usize, speed: u64, mode: ClockType) -> Result<(), SettingError> { + fn read_max_cpu_clock(&self) -> u64 { + if !(self.limits.extras.experiments + || self.limits.extras.quirks.contains("clock-autodetect")) + { + return MAX_CLOCK; + } + if let super::Model::OLED = self.variant { + if let Ok(freq_khz) = + usdpl_back::api::files::read_single::<_, u64, _>(cpu_max_clock_path(self.index)) + { + log::debug!("Detected CPU max clock of {}KHz", freq_khz); + return freq_khz / 1000; + } + } + MAX_CLOCK + } + + fn set_clock_limit( + &self, + index: usize, + speed: u64, + mode: ClockType, + ) -> Result<(), SettingError> { let payload = format!("p {} {} {}\n", index / 2, mode as u8, speed); - self.sysfs.set(CPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), &payload).map_err(|e| { - SettingError { + self.sysfs + .set(CPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), &payload) + .map_err(|e| SettingError { msg: format!( "Failed to write `{}` to `{}`: {}", &payload, CPU_CLOCK_LIMITS_ATTRIBUTE, e ), setting: crate::settings::SettingVariant::Cpu, - } - }) + }) + } + + fn reset_clock_limits(&self) -> Result<(), SettingError> { + self.sysfs + .set(CPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), "r\n") + .map_err(|e| SettingError { + msg: format!( + "Failed to write `r` to `{}`: {}", + CPU_CLOCK_LIMITS_ATTRIBUTE, e + ), + setting: crate::settings::SettingVariant::Cpu, + }) } fn set_clock_limits(&mut self) -> Result<(), Vec> { @@ -326,11 +418,12 @@ impl Cpu { } // min clock if let Some(min) = clock_limits.min { - let valid_min = if min < self.limits.clock_min.min { - self.limits.clock_min.min - } else { - min - }; + let valid_min = + if min < range_min_or_fallback(&self.limits.clock_min, MIN_MIN_CLOCK) { + range_min_or_fallback(&self.limits.clock_min, MIN_MIN_CLOCK) + } else { + min + }; self.set_clock_limit(self.index, valid_min, ClockType::Min) .unwrap_or_else(|e| errors.push(e)); } @@ -347,22 +440,27 @@ impl Cpu { let mut errors = Vec::new(); self.state.clock_limits_set = false; POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(false, self.index); - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT - .enforce_level(&self.sysfs)?; + POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs)?; if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { // always set clock speeds, since it doesn't reset correctly (kernel/hardware bug) POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs)?; // disable manual clock limits log::debug!("Setting CPU {} to default clockspeed", self.index); // max clock - self.set_clock_limit(self.index, self.limits.clock_max.max, ClockType::Max) - .unwrap_or_else(|e| errors.push(e)); + self.set_clock_limit( + self.index, + range_max_or_fallback(&self.limits.clock_max, MAX_CLOCK), + ClockType::Max, + ) + .unwrap_or_else(|e| errors.push(e)); // min clock - self.set_clock_limit(self.index, self.limits.clock_min.min, ClockType::Min) - .unwrap_or_else(|e| errors.push(e)); + self.set_clock_limit( + self.index, + range_min_or_fallback(&self.limits.clock_min, MIN_MIN_CLOCK), + ClockType::Min, + ) + .unwrap_or_else(|e| errors.push(e)); } - // TODO remove this when it's no longer needed - self.clock_unset_workaround().unwrap_or_else(|mut e| errors.append(&mut e)); if errors.is_empty() { Ok(()) } else { @@ -374,40 +472,50 @@ impl Cpu { } // https://github.com/NGnius/PowerTools/issues/107 - fn clock_unset_workaround(&self) -> Result<(), Vec> { - if !self.state.is_resuming { - let mut errors = Vec::new(); - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(true, self.index); - // always set clock speeds, since it doesn't reset correctly (kernel/hardware bug) - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs)?; - // disable manual clock limits - log::debug!("Setting CPU {} to default clockspeed", self.index); + fn clock_unset(&self) -> Result<(), Vec> { + let mut errors = Vec::new(); + if self.state.clock_limits_set && POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { // max clock - self.set_clock_limit(self.index, self.limits.clock_max.max, ClockType::Max) - .unwrap_or_else(|e| errors.push(e)); + self.set_clock_limit( + self.index, + range_max_or_fallback(&self.limits.clock_max, MAX_CLOCK), + ClockType::Max, + ) + .unwrap_or_else(|e| errors.push(e)); // min clock - self.set_clock_limit(self.index, self.limits.clock_min.min, ClockType::Min) - .unwrap_or_else(|e| errors.push(e)); + self.set_clock_limit( + self.index, + range_min_or_fallback(&self.limits.clock_min, MIN_MIN_CLOCK), + ClockType::Min, + ) + .unwrap_or_else(|e| errors.push(e)); self.set_confirm().unwrap_or_else(|e| errors.push(e)); - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(false, self.index); - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } else { + + self.reset_clock_limits().unwrap_or_else(|e| errors.push(e)); + self.set_confirm().unwrap_or_else(|e| errors.push(e)); + } + POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(false, self.index); + POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT + .enforce_level(&self.sysfs) + .unwrap_or_else(|mut e| errors.append(&mut e)); + if errors.is_empty() { Ok(()) + } else { + Err(errors) } } fn set_confirm(&self) -> Result<(), SettingError> { - self.sysfs.set(CPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), "c\n").map_err(|e| { - SettingError { - msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_ATTRIBUTE, e), + self.sysfs + .set(CPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), "c\n") + .map_err(|e| SettingError { + msg: format!( + "Failed to write `c` to `{}`: {}", + CPU_CLOCK_LIMITS_ATTRIBUTE, e + ), setting: crate::settings::SettingVariant::Cpu, - } - }) + }) } fn set_force_performance_related(&mut self) -> Result<(), Vec> { @@ -476,12 +584,16 @@ impl Cpu { fn clamp_all(&mut self) { if let Some(clock_limits) = &mut self.clock_limits { if let Some(min) = clock_limits.min { - clock_limits.min = - Some(min.clamp(self.limits.clock_min.min, self.limits.clock_min.max)); + clock_limits.min = Some(min.clamp( + range_min_or_fallback(&self.limits.clock_max, MIN_MAX_CLOCK), + range_max_or_fallback(&self.limits.clock_min, MAX_CLOCK), + )); } if let Some(max) = clock_limits.max { - clock_limits.max = - Some(max.clamp(self.limits.clock_max.min, self.limits.clock_max.max)); + clock_limits.max = Some(max.clamp( + range_min_or_fallback(&self.limits.clock_max, MIN_MAX_CLOCK), + range_max_or_fallback(&self.limits.clock_max, MAX_CLOCK), + )); } } } @@ -498,7 +610,7 @@ impl Cpu { } }*/ - fn system_default(cpu_index: usize, oc_limits: CpuLimits) -> Self { + fn from_limits(cpu_index: usize, oc_limits: GenericCpuLimit) -> Self { Self { online: true, clock_limits: None, @@ -506,21 +618,30 @@ impl Cpu { limits: oc_limits, index: cpu_index, state: crate::state::steam_deck::Cpu::default(), - sysfs: Self::find_card_sysfs(None::<&'static str>) + sysfs: Self::find_card_sysfs(None::<&'static str>), + variant: super::Model::LCD, } } + fn system_default(cpu_index: usize) -> Self { + Self::from_limits( + cpu_index, + GenericCpuLimit::default_for(&limits_core::json_v2::CpuLimitType::SteamDeck, cpu_index), + ) + } + fn limits(&self) -> crate::api::CpuLimits { + let max_cpu_clock = self.read_max_cpu_clock(); crate::api::CpuLimits { clock_min_limits: Some(RangeLimit { - min: self.limits.clock_max.min, // allows min to be set by max (it's weird, blame the kernel) - max: self.limits.clock_min.max, + min: range_min_or_fallback(&self.limits.clock_max, MIN_MAX_CLOCK), // allows min to be set by max (it's weird, blame the kernel) + max: range_max_or_fallback(&self.limits.clock_min, max_cpu_clock), }), clock_max_limits: Some(RangeLimit { - min: self.limits.clock_max.min, - max: self.limits.clock_max.max, + min: range_min_or_fallback(&self.limits.clock_max, MIN_MAX_CLOCK), + max: range_max_or_fallback(&self.limits.clock_max, max_cpu_clock), }), - clock_step: self.limits.clock_step, + clock_step: self.limits.clock_step.unwrap_or(CLOCK_STEP), governors: self.governors(), } } @@ -537,6 +658,10 @@ impl Cpu { }; gov_str.split(' ').map(|s| s.to_owned()).collect() } + + pub fn variant(&mut self, model: super::Model) { + self.variant = model; + } } impl Into for Cpu { @@ -546,7 +671,10 @@ impl Into for Cpu { online: self.online, clock_limits: self.clock_limits.map(|x| x.into()), governor: self.governor, - root: self.sysfs.root().and_then(|p| p.as_ref().to_str().map(|r| r.to_owned())) + root: self + .sysfs + .root() + .and_then(|p| p.as_ref().to_str().map(|r| r.to_owned())), } } } @@ -566,6 +694,18 @@ impl OnResume for Cpu { } } +impl crate::settings::OnLoad for Cpu { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Cpu { + fn on_unload(&mut self) -> Result<(), Vec> { + self.clock_unset() + } +} + impl TCpu for Cpu { fn online(&mut self) -> &mut bool { &mut self.online @@ -608,3 +748,11 @@ fn cpu_available_governors_path(index: usize) -> String { index ) } + +#[inline] +fn cpu_max_clock_path(index: usize) -> String { + format!( + "/sys/devices/system/cpu/cpufreq/policy{}/cpuinfo_max_freq", + index + ) +} diff --git a/backend/src/settings/steam_deck/gpu.rs b/backend/src/settings/steam_deck/gpu.rs index f1f428b..2899e1b 100644 --- a/backend/src/settings/steam_deck/gpu.rs +++ b/backend/src/settings/steam_deck/gpu.rs @@ -1,14 +1,18 @@ use std::convert::Into; -use sysfuss::{BasicEntityPath, HwMonPath, SysEntity, capability::attributes, SysEntityAttributesExt, SysAttribute}; +use sysfuss::{ + capability::attributes, BasicEntityPath, HwMonPath, SysAttribute, SysEntity, + SysEntityAttributes, SysEntityAttributesExt, +}; + +use limits_core::json_v2::GenericGpuLimit; -use super::oc_limits::{GpuLimits, OverclockLimits}; use super::POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT; use crate::api::RangeLimit; use crate::persist::GpuJson; -use crate::settings::TGpu; use crate::settings::{min_max_from_json, MinMax}; use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{ProviderBuilder, TGpu}; // usually in /sys/class/hwmon/hwmon4/ const SLOW_PPT_ATTRIBUTE: sysfuss::HwMonAttribute = sysfuss::HwMonAttribute::custom("power1_cap"); @@ -19,12 +23,12 @@ pub struct Gpu { pub fast_ppt: Option, pub slow_ppt: Option, pub clock_limits: Option>, - pub slow_memory: bool, - limits: GpuLimits, + pub memory_clock: Option, + limits: GenericGpuLimit, state: crate::state::steam_deck::Gpu, - driver_mode: crate::persist::DriverJson, sysfs_card: BasicEntityPath, - sysfs_hwmon: HwMonPath + sysfs_hwmon: HwMonPath, + variant: super::Model, } // same as CPU @@ -33,6 +37,7 @@ pub struct Gpu { const GPU_CLOCK_LIMITS_ATTRIBUTE: &str = "device/pp_od_clk_voltage"; const GPU_MEMORY_DOWNCLOCK_ATTRIBUTE: &str = "device/pp_dpm_fclk"; +const GPU_CLOCK_READOUT_ATTRIBUTE: &str = "device/pp_dpm_sclk"; const CARD_EXTENSIONS: &[&'static str] = &[ GPU_CLOCK_LIMITS_ATTRIBUTE, @@ -45,44 +50,28 @@ enum ClockType { Max = 1, } -impl Gpu { - #[inline] - pub fn from_json(other: GpuJson, version: u64) -> Self { - let (oc_limits, is_default) = OverclockLimits::load_or_default(); - let driver = if is_default { - crate::persist::DriverJson::SteamDeck - } else { - crate::persist::DriverJson::SteamDeckAdvance - }; - match version { - 0 => Self { - fast_ppt: other.fast_ppt, - slow_ppt: other.slow_ppt, - clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)), - slow_memory: other.slow_memory, - limits: oc_limits.gpu, - state: crate::state::steam_deck::Gpu::default(), - driver_mode: driver, - sysfs_card: Self::find_card_sysfs(other.root.clone()), - sysfs_hwmon: Self::find_hwmon_sysfs(other.root), - }, - _ => Self { - fast_ppt: other.fast_ppt, - slow_ppt: other.slow_ppt, - clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)), - slow_memory: other.slow_memory, - limits: oc_limits.gpu, - state: crate::state::steam_deck::Gpu::default(), - driver_mode: driver, - sysfs_card: Self::find_card_sysfs(other.root.clone()), - sysfs_hwmon: Self::find_hwmon_sysfs(other.root), - }, - } - } +const MAX_CLOCK: u64 = 1600; +const MIN_CLOCK: u64 = 200; +const MAX_MEMORY_CLOCK: u64 = 800; +const MIN_MEMORY_CLOCK: u64 = 400; +const MAX_FAST_PPT: u64 = 30_000_000; +const MIN_FAST_PPT: u64 = 1_000_000; +const MAX_SLOW_PPT: u64 = 29_000_000; +const MIN_SLOW_PPT: u64 = 1_000_000; +const MIDDLE_PPT: u64 = 15_000_000; +const PPT_DIVISOR: u64 = 1_000; +impl Gpu { fn find_card_sysfs(root: Option>) -> BasicEntityPath { let root = crate::settings::util::root_or_default_sysfs(root); - match root.class("drm", attributes(crate::settings::util::CARD_NEEDS.into_iter().map(|s| s.to_string()))) { + match root.class( + "drm", + attributes( + crate::settings::util::CARD_NEEDS + .into_iter() + .map(|s| s.to_string()), + ), + ) { Ok(iter) => { let card = iter .filter(|ent| if let Ok(name) = ent.name() { name.starts_with("card")} else { false }) @@ -92,11 +81,17 @@ impl Gpu { log::error!("Failed to find SteamDeck gpu drm in sysfs (no results), using naive fallback"); BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0")) }); - log::info!("Found SteamDeck gpu drm in sysfs: {}", card.as_ref().display()); + log::info!( + "Found SteamDeck gpu drm in sysfs: {}", + card.as_ref().display() + ); card - }, + } Err(e) => { - log::error!("Failed to find SteamDeck gpu drm in sysfs ({}), using naive fallback", e); + log::error!( + "Failed to find SteamDeck gpu drm in sysfs ({}), using naive fallback", + e + ); BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0")) } } @@ -104,33 +99,183 @@ impl Gpu { fn find_hwmon_sysfs(root: Option>) -> HwMonPath { let root = crate::settings::util::root_or_default_sysfs(root); - let hwmon = root.hwmon_by_name(super::util::GPU_HWMON_NAME).unwrap_or_else(|e| { - log::error!("Failed to find SteamDeck gpu hwmon in sysfs ({}), using naive fallback", e); - root.hwmon_by_index(4) - }); - log::info!("Found SteamDeck gpu hwmon {} in sysfs: {}", super::util::GPU_HWMON_NAME, hwmon.as_ref().display()); + let hwmon = root + .hwmon_by_name(super::util::GPU_HWMON_NAME) + .unwrap_or_else(|e| { + log::error!( + "Failed to find SteamDeck gpu hwmon in sysfs ({}), using naive fallback", + e + ); + root.hwmon_by_index(4) + }); + log::info!( + "Found SteamDeck gpu hwmon {} in sysfs: {}", + super::util::GPU_HWMON_NAME, + hwmon.as_ref().display() + ); hwmon } fn set_clock_limit(&self, speed: u64, mode: ClockType) -> Result<(), SettingError> { let payload = format!("s {} {}\n", mode as u8, speed); let path = GPU_CLOCK_LIMITS_ATTRIBUTE.path(&self.sysfs_card); - self.sysfs_card.set(GPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), &payload).map_err(|e| { - SettingError { - msg: format!("Failed to write `{}` to `{}`: {}", &payload, path.display(), e), + self.sysfs_card + .set(GPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), &payload) + .map_err(|e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload, + path.display(), + e + ), setting: crate::settings::SettingVariant::Gpu, - } - }) + }) } fn set_confirm(&self) -> Result<(), SettingError> { let path = GPU_CLOCK_LIMITS_ATTRIBUTE.path(&self.sysfs_card); - self.sysfs_card.set(GPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), "c\n").map_err(|e| { - SettingError { + self.sysfs_card + .set(GPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), "c\n") + .map_err(|e| SettingError { msg: format!("Failed to write `c` to `{}`: {}", path.display(), e), setting: crate::settings::SettingVariant::Gpu, + }) + } + + fn read_max_gpu_clock(&self) -> u64 { + if !(self.limits.extras.experiments + || self.limits.extras.quirks.contains("clock-autodetect")) + { + return MAX_CLOCK; + } + if let super::Model::OLED = self.variant { + if let Ok(f) = self + .sysfs_card + .read_value(GPU_CLOCK_READOUT_ATTRIBUTE.to_owned()) + { + let options = parse_pp_dpm_sclk(&String::from_utf8_lossy(&f)); + return options + .get(options.len() - 1) + .map(|x| { + let x = x.1 as u64; + log::debug!("Detected GPU max clock of {}MHz", x); + x + }) + .unwrap_or(MAX_CLOCK); } - }) + } + MAX_CLOCK + } + + fn is_memory_clock_maxed(&self) -> bool { + if let Some(clock) = &self.memory_clock { + if let Some(limit) = &self.limits.memory_clock { + if let Some(limit) = &limit.max { + if let Some(step) = &self.limits.memory_clock_step { + log::debug!( + "chosen_clock: {}, limit_clock: {}, step: {}", + clock, + limit, + step + ); + return clock > &(limit - step); + } else { + log::debug!("chosen_clock: {}, limit_clock: {}", clock, limit); + return clock == limit; + } + } + } + } + true + } + + fn quantize_memory_clock(&self, clock: u64) -> u64 { + if let Ok(f) = self + .sysfs_card + .read_value(GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned()) + { + let options = parse_pp_dpm_fclk(&String::from_utf8_lossy(&f)); + // round (and find) nearest valid clock step + // roughly price is right strategy (clock step will always be lower or equal to chosen) + for i in 0..options.len() { + let (current_val_opt, current_speed_opt) = &options[i]; + let current_speed_opt = *current_speed_opt as u64; + if clock == current_speed_opt { + return *current_val_opt as _; + } else if current_speed_opt > clock { + if i == 0 { + return *current_val_opt as _; + } else { + return options[i - 1].0 as _; + } + } + } + options[options.len() - 1].0 as _ + } else { + self.is_memory_clock_maxed() as u64 + } + } + + fn build_memory_clock_payload(&self, clock: u64) -> String { + let max_val = self.quantize_memory_clock(clock); + let is_oled = matches!(self.variant, super::Model::OLED); + let is_lcd = matches!(self.variant, super::Model::LCD); + let is_lock_feature_enabled = self.limits.extras.quirks.contains("pp_dpm_fclk-static"); + + if (is_oled + && self + .limits + .extras + .quirks + .contains("pp_dpm_fclk-reversed-on-OLED")) + || (is_lcd + && self + .limits + .extras + .quirks + .contains("pp_dpm_fclk-reversed-on-LCD")) + || self.limits.extras.quirks.contains("pp_dpm_fclk-reversed") + { + let options_count = self + .sysfs_card + .read_value(GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned()) + .map(|b| parse_pp_dpm_fclk(&String::from_utf8_lossy(&b)).len()) + .unwrap_or_else(|_| if is_oled { 4 } else { 2 }); + let modifier = (options_count - 1) as u64; + if is_lock_feature_enabled { + format!("{}\n", modifier - max_val) + } else { + if max_val == 0 as u64 { + format!("{}\n", modifier) + } else { + use std::fmt::Write; + let mut payload = format!("{}", modifier - max_val); + for i in (0..max_val).rev(/* rev() isn't necessary but it creates a nicer (ascending) order */) + { + write!(payload, " {}", modifier - i) + .expect("Failed to write to memory payload (should be infallible!?)"); + } + write!(payload, "\n") + .expect("Failed to write to memory payload (should be infallible!?)"); + payload + } + } + } else { + match max_val { + 0 => "0\n".to_owned(), + max_val => { + use std::fmt::Write; + let mut payload = String::from("0"); + for i in 1..max_val { + write!(payload, " {}", i) + .expect("Failed to write to memory payload (should be infallible!?)"); + } + write!(payload, " {}\n", max_val) + .expect("Failed to write to memory payload (should be infallible!?)"); + payload + } + } + } } fn set_clocks(&mut self) -> Result<(), Vec> { @@ -142,11 +287,13 @@ impl Gpu { self.state.clock_limits_set = true; // max clock if let Some(max) = clock_limits.max { - self.set_clock_limit(max, ClockType::Max).unwrap_or_else(|e| errors.push(e)); + self.set_clock_limit(max, ClockType::Max) + .unwrap_or_else(|e| errors.push(e)); } // min clock if let Some(min) = clock_limits.min { - self.set_clock_limit(min, ClockType::Min).unwrap_or_else(|e| errors.push(e)); + self.set_clock_limit(min, ClockType::Min) + .unwrap_or_else(|e| errors.push(e)); } self.set_confirm().unwrap_or_else(|e| errors.push(e)); @@ -155,16 +302,28 @@ impl Gpu { || POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { self.state.clock_limits_set = false; - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(self.slow_memory); + POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(!self.is_memory_clock_maxed()); if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs_card)?; // disable manual clock limits // max clock - self.set_clock_limit(self.limits.clock_max.max, ClockType::Max) - .unwrap_or_else(|e| errors.push(e)); + self.set_clock_limit( + self.limits + .clock_max + .and_then(|lim| lim.max) + .unwrap_or(MAX_CLOCK), + ClockType::Max, + ) + .unwrap_or_else(|e| errors.push(e)); // min clock - self.set_clock_limit(self.limits.clock_min.min, ClockType::Min) - .unwrap_or_else(|e| errors.push(e)); + self.set_clock_limit( + self.limits + .clock_min + .and_then(|lim| lim.min) + .unwrap_or(MIN_CLOCK), + ClockType::Min, + ) + .unwrap_or_else(|e| errors.push(e)); self.set_confirm().unwrap_or_else(|e| errors.push(e)); } else { @@ -180,47 +339,49 @@ impl Gpu { } } - fn set_slow_memory(&self, slow: bool) -> Result<(), SettingError> { - let path = GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.path(&self.sysfs_card); - if slow { - self.sysfs_card.set(GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned(), slow as u8).map_err(|e| { - SettingError { + fn set_memory_speed(&self, clock: u64) -> Result<(), SettingError> { + if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { + let path = GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.path(&self.sysfs_card); + let payload = self.build_memory_clock_payload(clock); + log::debug!( + "Generated payload for gpu fclk (memory): `{}` (is maxed? {})", + payload, + self.is_memory_clock_maxed() + ); + self.sysfs_card + .set(GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned(), payload) + .map_err(|e| SettingError { msg: format!("Failed to write to `{}`: {}", path.display(), e), setting: crate::settings::SettingVariant::Gpu, - } - }) + }) } else { - // NOTE: there is a GPU driver/hardware bug that prevents this from working - self.sysfs_card.set(GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned(), "0 1\n").map_err(|e| { - SettingError { - msg: format!("Failed to write to `{}`: {}", path.display(), e), - setting: crate::settings::SettingVariant::Gpu, - } - }) + Ok(()) } } fn set_force_performance_related(&mut self) -> Result<(), Vec> { let mut errors = Vec::new(); - // enable/disable downclock of GPU memory (to 400Mhz?) - if self.slow_memory { - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(true); - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT - .enforce_level(&self.sysfs_card) - .unwrap_or_else(|mut e| errors.append(&mut e)); - self.set_slow_memory(self.slow_memory).unwrap_or_else(|e| errors.push(e)); - } else if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { - self.set_slow_memory(self.slow_memory).unwrap_or_else(|e| errors.push(e)); - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(self.clock_limits.is_some()); - POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT - .enforce_level(&self.sysfs_card) - .unwrap_or_else(|mut e| errors.append(&mut e)); - } + POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT + .set_gpu(!self.is_memory_clock_maxed() || self.clock_limits.is_some()); + POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT + .enforce_level(&self.sysfs_card) + .unwrap_or_else(|mut e| errors.append(&mut e)); + // enable/disable downclock of GPU memory + self.set_memory_speed( + self.memory_clock + .or_else(|| { + self.limits + .memory_clock + .map(|lim| lim.max.unwrap_or(MAX_MEMORY_CLOCK)) + }) + .unwrap_or(MAX_MEMORY_CLOCK), + ) + .unwrap_or_else(|e| errors.push(e)); self.set_clocks() .unwrap_or_else(|mut e| errors.append(&mut e)); // commit changes (if no errors have already occured) if errors.is_empty() { - if self.slow_memory || self.clock_limits.is_some() { + if !self.is_memory_clock_maxed() || self.clock_limits.is_some() { self.set_confirm().map_err(|e| { errors.push(e); errors @@ -238,7 +399,8 @@ impl Gpu { // set fast PPT if let Some(fast_ppt) = &self.fast_ppt { self.state.fast_ppt_set = true; - self.sysfs_hwmon.set(FAST_PPT_ATTRIBUTE, fast_ppt) + self.sysfs_hwmon + .set(FAST_PPT_ATTRIBUTE, fast_ppt) .map_err(|e| SettingError { msg: format!( "Failed to write `{}` to `{:?}`: {}", @@ -251,8 +413,9 @@ impl Gpu { }); } else if self.state.fast_ppt_set { self.state.fast_ppt_set = false; - let fast_ppt = self.limits.fast_ppt_default; - self.sysfs_hwmon.set(FAST_PPT_ATTRIBUTE, fast_ppt) + let fast_ppt = self.limits.fast_ppt_default.unwrap_or(MIDDLE_PPT); + self.sysfs_hwmon + .set(FAST_PPT_ATTRIBUTE, fast_ppt) .map_err(|e| SettingError { msg: format!( "Failed to write `{}` to `{:?}`: {}", @@ -267,7 +430,8 @@ impl Gpu { // set slow PPT if let Some(slow_ppt) = &self.slow_ppt { self.state.slow_ppt_set = true; - self.sysfs_hwmon.set(SLOW_PPT_ATTRIBUTE, slow_ppt) + self.sysfs_hwmon + .set(SLOW_PPT_ATTRIBUTE, slow_ppt) .map_err(|e| SettingError { msg: format!( "Failed to write `{}` to `{:?}`: {}", @@ -280,8 +444,9 @@ impl Gpu { }); } else if self.state.slow_ppt_set { self.state.slow_ppt_set = false; - let slow_ppt = self.limits.slow_ppt_default; - self.sysfs_hwmon.set(SLOW_PPT_ATTRIBUTE, slow_ppt) + let slow_ppt = self.limits.slow_ppt_default.unwrap_or(MIDDLE_PPT); + self.sysfs_hwmon + .set(SLOW_PPT_ATTRIBUTE, slow_ppt) .map_err(|e| SettingError { msg: format!( "Failed to write `{}` to `{:?}`: {}", @@ -304,40 +469,78 @@ impl Gpu { fn clamp_all(&mut self) { if let Some(fast_ppt) = &mut self.fast_ppt { - *fast_ppt = (*fast_ppt).clamp(self.limits.fast_ppt.min, self.limits.fast_ppt.max); + *fast_ppt = (*fast_ppt).clamp( + self.limits + .fast_ppt + .and_then(|lim| lim.min) + .unwrap_or(MIN_FAST_PPT), + self.limits + .fast_ppt + .and_then(|lim| lim.max) + .unwrap_or(MAX_FAST_PPT), + ); } if let Some(slow_ppt) = &mut self.slow_ppt { - *slow_ppt = (*slow_ppt).clamp(self.limits.slow_ppt.min, self.limits.slow_ppt.max); + *slow_ppt = (*slow_ppt).clamp( + self.limits + .slow_ppt + .and_then(|lim| lim.min) + .unwrap_or(MIN_SLOW_PPT), + self.limits + .slow_ppt + .and_then(|lim| lim.max) + .unwrap_or(MAX_SLOW_PPT), + ); } if let Some(clock_limits) = &mut self.clock_limits { if let Some(min) = clock_limits.min { - clock_limits.min = - Some(min.clamp(self.limits.clock_min.min, self.limits.clock_min.max)); + clock_limits.min = Some( + min.clamp( + self.limits + .clock_min + .and_then(|lim| lim.min) + .unwrap_or(MIN_CLOCK), + self.limits + .clock_min + .and_then(|lim| lim.max) + .unwrap_or(MAX_CLOCK), + ), + ); } if let Some(max) = clock_limits.max { - clock_limits.max = - Some(max.clamp(self.limits.clock_max.min, self.limits.clock_max.max)); + clock_limits.max = Some( + max.clamp( + self.limits + .clock_max + .and_then(|lim| lim.min) + .unwrap_or(MIN_CLOCK), + self.limits + .clock_max + .and_then(|lim| lim.max) + .unwrap_or(MAX_CLOCK), + ), + ); } } + if let Some(mem_clock) = self.memory_clock { + self.memory_clock = Some( + mem_clock.clamp( + self.limits + .memory_clock + .and_then(|lim| lim.min) + .unwrap_or(MIN_MEMORY_CLOCK), + self.limits + .memory_clock + .and_then(|lim| lim.max) + .unwrap_or(MAX_MEMORY_CLOCK), + ), + ); + } } - pub fn system_default() -> Self { - let (oc_limits, is_default) = OverclockLimits::load_or_default(); - Self { - fast_ppt: None, - slow_ppt: None, - clock_limits: None, - slow_memory: false, - limits: oc_limits.gpu, - state: crate::state::steam_deck::Gpu::default(), - driver_mode: if is_default { - crate::persist::DriverJson::SteamDeck - } else { - crate::persist::DriverJson::SteamDeckAdvance - }, - sysfs_card: Self::find_card_sysfs(None::<&'static str>), - sysfs_hwmon: Self::find_hwmon_sysfs(None::<&'static str>), - } + pub fn variant(mut self, model: super::Model) -> Self { + self.variant = model; + self } } @@ -347,9 +550,62 @@ impl Into for Gpu { GpuJson { fast_ppt: self.fast_ppt, slow_ppt: self.slow_ppt, + tdp: None, + tdp_boost: None, clock_limits: self.clock_limits.map(|x| x.into()), - slow_memory: self.slow_memory, - root: self.sysfs_card.root().or(self.sysfs_hwmon.root()).and_then(|p| p.as_ref().to_str().map(|r| r.to_owned())) + memory_clock: self.memory_clock, + root: self + .sysfs_card + .root() + .or(self.sysfs_hwmon.root()) + .and_then(|p| p.as_ref().to_str().map(|r| r.to_owned())), + } + } +} + +impl ProviderBuilder for Gpu { + fn from_json_and_limits(persistent: GpuJson, version: u64, limits: GenericGpuLimit) -> Self { + match version { + 0 => Self { + fast_ppt: persistent.fast_ppt, + slow_ppt: persistent.slow_ppt, + clock_limits: persistent + .clock_limits + .map(|x| min_max_from_json(x, version)), + memory_clock: persistent.memory_clock, + limits: limits, + state: crate::state::steam_deck::Gpu::default(), + sysfs_card: Self::find_card_sysfs(persistent.root.clone()), + sysfs_hwmon: Self::find_hwmon_sysfs(persistent.root), + variant: super::Model::LCD, + }, + _ => Self { + fast_ppt: persistent.fast_ppt, + slow_ppt: persistent.slow_ppt, + clock_limits: persistent + .clock_limits + .map(|x| min_max_from_json(x, version)), + memory_clock: persistent.memory_clock, + limits: limits, + state: crate::state::steam_deck::Gpu::default(), + sysfs_card: Self::find_card_sysfs(persistent.root.clone()), + sysfs_hwmon: Self::find_hwmon_sysfs(persistent.root), + variant: super::Model::LCD, + }, + } + } + + fn from_limits(limits: GenericGpuLimit) -> Self { + Self { + fast_ppt: None, + slow_ppt: None, + clock_limits: None, + memory_clock: None, + limits: limits, + state: crate::state::steam_deck::Gpu::default(), + sysfs_card: Self::find_card_sysfs(None::<&'static str>), + sysfs_hwmon: Self::find_hwmon_sysfs(None::<&'static str>), + variant: super::Model::LCD, } } } @@ -371,31 +627,72 @@ impl OnResume for Gpu { impl crate::settings::OnPowerEvent for Gpu {} +impl crate::settings::OnLoad for Gpu { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Gpu { + fn on_unload(&mut self) -> Result<(), Vec> { + POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(false); + Ok(()) + } +} + impl TGpu for Gpu { fn limits(&self) -> crate::api::GpuLimits { + let max_gpu_clock = self.read_max_gpu_clock(); + let ppt_divisor = self.limits.ppt_divisor.unwrap_or(PPT_DIVISOR); crate::api::GpuLimits { fast_ppt_limits: Some(RangeLimit { - min: self.limits.fast_ppt.min / self.limits.ppt_divisor, - max: self.limits.fast_ppt.max / self.limits.ppt_divisor, + min: super::util::range_min_or_fallback(&self.limits.fast_ppt, MIN_FAST_PPT) + / ppt_divisor, + max: super::util::range_max_or_fallback(&self.limits.fast_ppt, MAX_FAST_PPT) + / ppt_divisor, }), + fast_ppt_default: self + .limits + .fast_ppt_default + .or_else(|| self.limits.fast_ppt.and_then(|x| x.max)) + .unwrap_or(MAX_FAST_PPT) + / ppt_divisor, slow_ppt_limits: Some(RangeLimit { - min: self.limits.slow_ppt.min / self.limits.ppt_divisor, - max: self.limits.slow_ppt.max / self.limits.ppt_divisor, + min: super::util::range_min_or_fallback(&self.limits.slow_ppt, MIN_SLOW_PPT) + / ppt_divisor, + max: super::util::range_max_or_fallback(&self.limits.slow_ppt, MIN_SLOW_PPT) + / ppt_divisor, }), - ppt_step: self.limits.ppt_step, + slow_ppt_default: self + .limits + .slow_ppt_default + .or_else(|| self.limits.slow_ppt.and_then(|x| x.max)) + .unwrap_or(MAX_SLOW_PPT) + / ppt_divisor, + ppt_step: self.limits.ppt_step.unwrap_or(1), tdp_limits: None, tdp_boost_limits: None, tdp_step: 42, clock_min_limits: Some(RangeLimit { - min: self.limits.clock_min.min, - max: self.limits.clock_min.max, + min: super::util::range_min_or_fallback(&self.limits.clock_min, MIN_CLOCK), + max: super::util::range_max_or_fallback(&self.limits.clock_min, max_gpu_clock), }), clock_max_limits: Some(RangeLimit { - min: self.limits.clock_max.min, - max: self.limits.clock_max.max, + min: super::util::range_min_or_fallback(&self.limits.clock_max, MIN_CLOCK), + max: super::util::range_max_or_fallback(&self.limits.clock_max, max_gpu_clock), }), - clock_step: self.limits.clock_step, - memory_control_capable: true, + clock_step: self.limits.clock_step.unwrap_or(100), + memory_control: Some(RangeLimit { + min: super::util::range_min_or_fallback( + &self.limits.memory_clock, + MIN_MEMORY_CLOCK, + ), + max: super::util::range_max_or_fallback( + &self.limits.memory_clock, + MAX_MEMORY_CLOCK, + ), + }), + memory_step: self.limits.memory_clock_step.unwrap_or(400), } } @@ -404,14 +701,16 @@ impl TGpu for Gpu { } fn ppt(&mut self, fast: Option, slow: Option) { - self.fast_ppt = fast.map(|x| x * self.limits.ppt_divisor); - self.slow_ppt = slow.map(|x| x * self.limits.ppt_divisor); + self.fast_ppt = fast.map(|x| x * self.limits.ppt_divisor.unwrap_or(PPT_DIVISOR)); + self.slow_ppt = slow.map(|x| x * self.limits.ppt_divisor.unwrap_or(PPT_DIVISOR)); } fn get_ppt(&self) -> (Option, Option) { ( - self.fast_ppt.map(|x| x / self.limits.ppt_divisor), - self.slow_ppt.map(|x| x / self.limits.ppt_divisor), + self.fast_ppt + .map(|x| x / self.limits.ppt_divisor.unwrap_or(PPT_DIVISOR)), + self.slow_ppt + .map(|x| x / self.limits.ppt_divisor.unwrap_or(PPT_DIVISOR)), ) } @@ -423,11 +722,51 @@ impl TGpu for Gpu { self.clock_limits.as_ref() } - fn slow_memory(&mut self) -> &mut bool { - &mut self.slow_memory + fn memory_clock(&mut self, speed: Option) { + self.memory_clock = speed; + } + + fn get_memory_clock(&self) -> Option { + self.memory_clock } fn provider(&self) -> crate::persist::DriverJson { - self.driver_mode.clone() + match self.variant { + super::Model::LCD => crate::persist::DriverJson::SteamDeck, + super::Model::OLED => crate::persist::DriverJson::SteamDeckOLED, + } } } + +fn parse_sysfs_clk_selector_str(s: &str) -> Vec<(usize, usize)> { + // (value, MHz) + let mut result = Vec::new(); + for line in s.split('\n') { + if !line.is_empty() { + if let Some((val, freq_mess)) = line.split_once(':') { + if let Ok(val) = val.parse::() { + if let Some((freq, _unit)) = + freq_mess.trim().split_once(|c: char| !c.is_digit(10)) + { + if let Ok(freq) = freq.parse::() { + result.push((val, freq)); + } + } + } + } + } else { + break; + } + } + result +} + +#[inline] +fn parse_pp_dpm_fclk(s: &str) -> Vec<(usize, usize)> { + parse_sysfs_clk_selector_str(s) +} + +#[inline] +fn parse_pp_dpm_sclk(s: &str) -> Vec<(usize, usize)> { + parse_sysfs_clk_selector_str(s) +} diff --git a/backend/src/settings/steam_deck/mod.rs b/backend/src/settings/steam_deck/mod.rs index 268fa90..c5a64ce 100644 --- a/backend/src/settings/steam_deck/mod.rs +++ b/backend/src/settings/steam_deck/mod.rs @@ -1,13 +1,36 @@ mod battery; mod cpu; mod gpu; -mod oc_limits; mod power_dpm_force; mod util; pub use battery::Battery; -pub use cpu::{Cpu, Cpus}; +pub use cpu::Cpus; pub use gpu::Gpu; -pub(self) use power_dpm_force::{POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT, DPM_FORCE_LIMITS_ATTRIBUTE}; +pub(self) use power_dpm_force::{ + DPM_FORCE_LIMITS_ATTRIBUTE, POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT, +}; + +#[derive(Debug, Clone, Copy)] +pub enum Model { + LCD, + OLED, +} pub use util::flash_led; + +fn _impl_checker() { + fn impl_provider_builder, J, L>() {} + + impl_provider_builder::< + Battery, + crate::persist::BatteryJson, + limits_core::json_v2::GenericBatteryLimit, + >(); + impl_provider_builder::< + Cpus, + Vec, + limits_core::json_v2::GenericCpusLimit, + >(); + impl_provider_builder::(); +} diff --git a/backend/src/settings/steam_deck/oc_limits.rs b/backend/src/settings/steam_deck/oc_limits.rs deleted file mode 100644 index f229a0c..0000000 --- a/backend/src/settings/steam_deck/oc_limits.rs +++ /dev/null @@ -1,190 +0,0 @@ -use crate::api::RangeLimit as MinMax; -use serde::{Deserialize, Serialize}; - -const OC_LIMITS_FILEPATH: &str = "pt_oc.json"; - -#[derive(Serialize, Deserialize, Debug)] -pub(super) struct OverclockLimits { - pub battery: BatteryLimits, - pub cpus: CpusLimits, - pub gpu: GpuLimits, -} - -impl Default for OverclockLimits { - fn default() -> Self { - Self { - battery: BatteryLimits::default(), - cpus: CpusLimits::default(), - gpu: GpuLimits::default(), - } - } -} - -impl OverclockLimits { - /// (Self, is_default) - pub fn load_or_default() -> (Self, bool) { - let path = oc_limits_filepath(); - if path.exists() { - log::info!("Steam Deck limits file {} found", path.display()); - let mut file = match std::fs::File::open(&path) { - Ok(f) => f, - Err(e) => { - log::warn!( - "Steam Deck limits file {} err: {} (using default fallback)", - path.display(), - e - ); - return (Self::default(), true); - } - }; - match serde_json::from_reader(&mut file) { - Ok(result) => { - log::debug!( - "Steam Deck limits file {} successfully loaded", - path.display() - ); - (result, false) - } - Err(e) => { - log::warn!( - "Steam Deck limits file {} json err: {} (using default fallback)", - path.display(), - e - ); - (Self::default(), true) - } - } - } else { - log::info!( - "Steam Deck limits file {} not found (using default fallback)", - path.display() - ); - (Self::default(), true) - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub(super) struct BatteryLimits { - pub charge_rate: MinMax, - pub extra_readouts: bool, -} - -impl Default for BatteryLimits { - fn default() -> Self { - Self { - charge_rate: MinMax { - min: 250, - max: 2500, - }, - extra_readouts: false, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub(super) struct CpusLimits { - pub cpus: Vec, - pub global_governors: bool, -} - -impl Default for CpusLimits { - fn default() -> Self { - Self { - cpus: [(); 8].iter().map(|_| CpuLimits::default()).collect(), - global_governors: true, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub(super) struct CpuLimits { - pub clock_min: MinMax, - pub clock_max: MinMax, - pub clock_step: u64, - pub skip_resume_reclock: bool, -} - -impl Default for CpuLimits { - fn default() -> Self { - Self { - clock_min: MinMax { - min: 1400, - max: 3500, - }, - clock_max: MinMax { - min: 400, - max: 3500, - }, - clock_step: 100, - skip_resume_reclock: false, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub(super) struct GpuLimits { - pub fast_ppt: MinMax, - pub fast_ppt_default: u64, - pub slow_ppt: MinMax, - pub slow_ppt_default: u64, - pub ppt_divisor: u64, - pub ppt_step: u64, - pub clock_min: MinMax, - pub clock_max: MinMax, - pub clock_step: u64, - pub skip_resume_reclock: bool, -} - -impl Default for GpuLimits { - fn default() -> Self { - Self { - fast_ppt: MinMax { - min: 1000000, - max: 30_000_000, - }, - fast_ppt_default: 15_000_000, - slow_ppt: MinMax { - min: 1000000, - max: 29_000_000, - }, - slow_ppt_default: 15_000_000, - ppt_divisor: 1_000_000, - ppt_step: 1, - clock_min: MinMax { - min: 400, - max: 1600, - }, - clock_max: MinMax { - min: 400, - max: 1600, - }, - clock_step: 100, - skip_resume_reclock: false, - } - } -} - -fn oc_limits_filepath() -> std::path::PathBuf { - crate::utility::settings_dir().join(OC_LIMITS_FILEPATH) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(not(feature = "dev_stuff"))] // this can fail due to reading from incompletely-written file otherwise - #[test] - fn load_pt_oc() { - let mut file = std::fs::File::open("../pt_oc.json").unwrap(); - let settings: OverclockLimits = serde_json::from_reader(&mut file).unwrap(); - assert!(settings.cpus.cpus.len() == 8); - } - - #[cfg(feature = "dev_stuff")] - #[test] - fn emit_default_pt_oc() { - let mut file = std::fs::File::create("../pt_oc.json").unwrap(); - serde_json::to_writer_pretty(&mut file, &OverclockLimits::default()).unwrap(); - } -} diff --git a/backend/src/settings/steam_deck/power_dpm_force.rs b/backend/src/settings/steam_deck/power_dpm_force.rs index 72404cc..039026a 100644 --- a/backend/src/settings/steam_deck/power_dpm_force.rs +++ b/backend/src/settings/steam_deck/power_dpm_force.rs @@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; -use sysfuss::{BasicEntityPath, SysEntityAttributesExt, SysAttribute}; +use sysfuss::{BasicEntityPath, SysAttribute, SysEntityAttributesExt}; use crate::settings::SettingError; @@ -63,7 +63,8 @@ impl PDFPLManager { let needs = self.needs_manual(); let mut errors = Vec::new(); let path = DPM_FORCE_LIMITS_ATTRIBUTE.path(entity); - let mode: String = entity.attribute(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned()) + let mode: String = entity + .attribute(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned()) .map_err(|e| { vec![SettingError { msg: format!("Failed to read `{}`: {}", path.display(), e), @@ -73,7 +74,8 @@ impl PDFPLManager { if mode != "manual" && needs { log::info!("Setting `{}` to manual", path.display()); // set manual control - entity.set(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned(), "manual") + entity + .set(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned(), "manual") .map_err(|e| { errors.push(SettingError { msg: format!("Failed to write `manual` to `{}`: {}", path.display(), e), @@ -84,7 +86,8 @@ impl PDFPLManager { } else if mode != "auto" && !needs { log::info!("Setting `{}` to auto", path.display()); // unset manual control - entity.set(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned(), "auto") + entity + .set(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned(), "auto") .map_err(|e| { errors.push(SettingError { msg: format!("Failed to write `auto` to `{}`: {}", path.display(), e), @@ -93,10 +96,13 @@ impl PDFPLManager { }) .unwrap_or(()); } - if let Ok(mode_now) = - entity.attribute::(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned()) - { - log::debug!("Mode for `{}` is now `{}`", path.display(), mode_now); + if let Ok(mode_now) = entity.attribute::(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned()) { + log::debug!( + "Mode for `{}` is now `{}` ({:#b})", + path.display(), + mode_now, + self.get() + ); } else { log::debug!("Error getting new mode for debugging purposes"); } diff --git a/backend/src/settings/steam_deck/util.rs b/backend/src/settings/steam_deck/util.rs index 52eb3e9..88d940c 100644 --- a/backend/src/settings/steam_deck/util.rs +++ b/backend/src/settings/steam_deck/util.rs @@ -1,79 +1,29 @@ -//! Rough Rust port of some BatCtrl functionality -//! Original: /usr/share/jupiter_controller_fw_updater/RA_bootloader_updater/linux_host_tools/BatCtrl -//! I do not have access to the source code, so this is my own interpretation of what it does. -//! -//! But also Quanta is based in a place with some questionable copyright practices, so... #![allow(dead_code)] -use std::fs::OpenOptions; -use std::io::{Error, Read, Seek, SeekFrom, Write}; - pub const JUPITER_HWMON_NAME: &'static str = "jupiter"; pub const STEAMDECK_HWMON_NAME: &'static str = "steamdeck_hwmon"; pub const GPU_HWMON_NAME: &'static str = "amdgpu"; +pub fn range_min_or_fallback( + range: &Option>, + fallback: I, +) -> I { + range.and_then(|lim| lim.min).unwrap_or(fallback) +} + +pub fn range_max_or_fallback( + range: &Option>, + fallback: I, +) -> I { + range.and_then(|lim| lim.max).unwrap_or(fallback) +} + pub fn card_also_has(card: &dyn sysfuss::SysEntity, extensions: &'static [&'static str]) -> bool { - extensions.iter() + extensions + .iter() .all(|ext| card.as_ref().join(ext).exists()) } -#[inline] -fn write2(p0: u8, p1: u8) -> Result { - write_to(0x6c, 0x81)?; - wait_ready_for_write()?; - let count0 = write_to(0x68, p0)?; - wait_ready_for_write()?; - let count1 = write_to(0x68, p1)?; - Ok(count0 + count1) -} - -fn write_read(p0: u8) -> Result { - write_to(0x6c, 0x81)?; - wait_ready_for_write()?; - write_to(0x68, p0)?; - wait_ready_for_read()?; - read_from(0x68) -} - -fn write_to(location: u64, value: u8) -> Result { - let mut file = OpenOptions::new().write(true).open("/dev/port")?; - file.seek(SeekFrom::Start(location))?; - file.write(&[value]) -} - -fn read_from(location: u64) -> Result { - let mut file = OpenOptions::new().read(true).open("/dev/port")?; - file.seek(SeekFrom::Start(location))?; - let mut buffer = [0]; - file.read(&mut buffer)?; - Ok(buffer[0]) -} - -fn wait_ready_for_write() -> Result<(), Error> { - let mut count = 0; - while count < 0x1ffff && (read_from(0x6c)? & 2) != 0 { - count += 1; - } - Ok(()) -} - -fn wait_ready_for_read() -> Result<(), Error> { - let mut count = 0; - while count < 0x1ffff && (read_from(0x6c)? & 1) == 0 { - count += 1; - } - Ok(()) -} - -pub fn set_led(red_unused: bool, green_aka_white: bool, blue_unused: bool) -> Result { - let payload: u8 = 0x80 - | (red_unused as u8 & 1) - | ((green_aka_white as u8 & 1) << 1) - | ((blue_unused as u8 & 1) << 2); - //log::info!("Payload: {:b}", payload); - write2(Setting::LEDStatus as _, payload) -} - const THINGS: &[u8] = &[ 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, @@ -84,55 +34,22 @@ const THINGS: &[u8] = &[ const TIME_UNIT: std::time::Duration = std::time::Duration::from_millis(200); pub fn flash_led() { - let old_led_state = write_read(Setting::LEDStatus as _) - .map_err(|e| log::error!("Failed to read LED status: {}", e)); + use smokepatio::ec::ControllerSet; + let mut ec = smokepatio::ec::unnamed_power::UnnamedPowerEC::new(); for &code in THINGS { let on = code != 0; - if let Err(e) = set_led(on, on, false) { + let colour = if on { + smokepatio::ec::unnamed_power::StaticColour::Red + } else { + smokepatio::ec::unnamed_power::StaticColour::Off + }; + if let Err(e) = ec.set(colour) { log::error!("Thing err: {}", e); } std::thread::sleep(TIME_UNIT); } - if let Ok(old_led_state) = old_led_state { - log::debug!("Restoring LED state to {:#02b}", old_led_state); - write2(Setting::LEDStatus as _, old_led_state) - .map_err(|e| log::error!("Failed to restore LED status: {}", e)) - .unwrap(); - } -} - -pub fn set(setting: Setting, mode: u8) -> Result { - write2(setting as u8, mode) -} - -#[derive(Copy, Clone)] -#[repr(u8)] -pub enum Setting { - CycleCount = 0x32, - ControlBoard = 0x6C, - Charge = 0xA6, - ChargeMode = 0x76, - LEDStatus = 199, -} - -#[derive(Copy, Clone, Debug)] -#[repr(u8)] -pub enum ControlBoard { - Enable = 0xAA, - Disable = 0xAB, -} - -#[derive(Copy, Clone, Debug)] -#[repr(u8)] -pub enum ChargeMode { - Normal = 0, - Discharge = 0x42, - Idle = 0x45, -} - -#[derive(Copy, Clone)] -#[repr(u8)] -pub enum Charge { - Enable = 0, - Disable = 4, + log::debug!("Restoring LED state"); + ec.set(smokepatio::ec::unnamed_power::StaticColour::Off) + .map_err(|e| log::error!("Failed to restore LED status: {}", e)) + .unwrap(); } diff --git a/backend/src/settings/traits.rs b/backend/src/settings/traits.rs index 4e96882..468f676 100644 --- a/backend/src/settings/traits.rs +++ b/backend/src/settings/traits.rs @@ -10,6 +10,14 @@ pub trait OnResume { fn on_resume(&self) -> Result<(), Vec>; } +pub trait OnLoad { + fn on_load(&mut self) -> Result<(), Vec>; +} + +pub trait OnUnload { + fn on_unload(&mut self) -> Result<(), Vec>; +} + #[repr(u8)] #[derive(Clone, Copy, Debug)] pub enum PowerMode { @@ -40,7 +48,13 @@ pub trait OnPowerEvent { } } -pub trait TGpu: OnSet + OnResume + OnPowerEvent + Debug + Send { +pub trait ProviderBuilder { + fn from_json_and_limits(persistent: J, version: u64, limits: L) -> Self; + + fn from_limits(limits: L) -> Self; +} + +pub trait TGpu: OnSet + OnResume + OnPowerEvent + OnLoad + OnUnload + Debug + Send { fn limits(&self) -> crate::api::GpuLimits; fn json(&self) -> crate::persist::GpuJson; @@ -53,14 +67,16 @@ pub trait TGpu: OnSet + OnResume + OnPowerEvent + Debug + Send { fn get_clock_limits(&self) -> Option<&MinMax>; - fn slow_memory(&mut self) -> &mut bool; + fn memory_clock(&mut self, speed: Option); + + fn get_memory_clock(&self) -> Option; fn provider(&self) -> crate::persist::DriverJson { crate::persist::DriverJson::AutoDetect } } -pub trait TCpus: OnSet + OnResume + OnPowerEvent + Debug + Send { +pub trait TCpus: OnSet + OnResume + OnPowerEvent + OnLoad + OnUnload + Debug + Send { fn limits(&self) -> crate::api::CpusLimits; fn json(&self) -> Vec; @@ -88,7 +104,7 @@ pub trait TCpu: Debug + Send { fn get_clock_limits(&self) -> Option<&MinMax>; } -pub trait TGeneral: OnSet + OnResume + OnPowerEvent + Debug + Send { +pub trait TGeneral: OnSet + OnResume + OnPowerEvent + OnLoad + OnUnload + Debug + Send { fn limits(&self) -> crate::api::GeneralLimits; fn get_persistent(&self) -> bool; @@ -99,16 +115,35 @@ pub trait TGeneral: OnSet + OnResume + OnPowerEvent + Debug + Send { fn path(&mut self, path: std::path::PathBuf); + fn app_id(&mut self) -> &'_ mut u64; + + fn get_app_id(&self) -> u64; + fn get_name(&self) -> &'_ str; fn name(&mut self, name: String); + fn variant_id(&mut self, id: u64); + + fn variant_name(&mut self, name: String); + + fn get_variant_id(&self) -> u64; + + fn get_variants(&self) -> Vec; + + fn get_variant_info(&self) -> crate::api::VariantInfo; + + fn add_variant( + &self, + variant: crate::persist::SettingsJson, + ) -> Result, SettingError>; + fn provider(&self) -> crate::persist::DriverJson; - fn on_event(&self) -> &'_ crate::persist::OnEventJson; + fn tags(&self) -> &'_ [String]; } -pub trait TBattery: OnSet + OnResume + OnPowerEvent + Debug + Send { +pub trait TBattery: OnSet + OnResume + OnPowerEvent + OnLoad + OnUnload + Debug + Send { fn limits(&self) -> crate::api::BatteryLimits; fn json(&self) -> crate::persist::BatteryJson; diff --git a/backend/src/settings/unknown/battery.rs b/backend/src/settings/unknown/battery.rs index ab76959..ed2cdda 100644 --- a/backend/src/settings/unknown/battery.rs +++ b/backend/src/settings/unknown/battery.rs @@ -1,12 +1,21 @@ use std::convert::Into; +use limits_core::json_v2::GenericBatteryLimit; + use crate::persist::BatteryJson; -use crate::settings::TBattery; use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{ProviderBuilder, TBattery}; #[derive(Debug, Clone)] pub struct Battery; +impl Battery { + #[inline] + fn system_default() -> Self { + Battery + } +} + impl Into for Battery { #[inline] fn into(self) -> BatteryJson { @@ -19,6 +28,20 @@ impl Into for Battery { } } +impl ProviderBuilder for Battery { + fn from_json_and_limits( + _persistent: BatteryJson, + _version: u64, + _limits: GenericBatteryLimit, + ) -> Self { + Battery::system_default() + } + + fn from_limits(_limits: GenericBatteryLimit) -> Self { + Battery::system_default() + } +} + impl OnSet for Battery { fn on_set(&mut self) -> Result<(), Vec> { Ok(()) @@ -33,6 +56,18 @@ impl OnResume for Battery { impl crate::settings::OnPowerEvent for Battery {} +impl crate::settings::OnLoad for Battery { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Battery { + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + impl TBattery for Battery { fn limits(&self) -> crate::api::BatteryLimits { crate::api::BatteryLimits { diff --git a/backend/src/settings/unknown/cpu.rs b/backend/src/settings/unknown/cpu.rs index c3985cf..7c1ebd9 100644 --- a/backend/src/settings/unknown/cpu.rs +++ b/backend/src/settings/unknown/cpu.rs @@ -1,9 +1,11 @@ use std::convert::Into; +use limits_core::json_v2::GenericCpusLimit; + use crate::persist::CpuJson; use crate::settings::MinMax; use crate::settings::{OnResume, OnSet, SettingError}; -use crate::settings::{TCpu, TCpus}; +use crate::settings::{ProviderBuilder, TCpu, TCpus}; const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control"; @@ -69,6 +71,18 @@ impl OnResume for Cpus { impl crate::settings::OnPowerEvent for Cpus {} +impl crate::settings::OnLoad for Cpus { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Cpus { + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + impl Cpus { pub fn cpu_count() -> Option { let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH) @@ -111,7 +125,7 @@ impl Cpus { } } - #[inline] + /*#[inline] pub fn from_json(mut other: Vec, version: u64) -> Self { let (_, can_smt) = Self::system_smt_capabilities(); let mut result = Vec::with_capacity(other.len()); @@ -140,6 +154,46 @@ impl Cpus { smt: smt_guess, smt_capable: can_smt, } + }*/ +} + +impl ProviderBuilder, GenericCpusLimit> for Cpus { + fn from_json_and_limits( + mut persistent: Vec, + version: u64, + _limits: GenericCpusLimit, + ) -> Self { + let (_, can_smt) = Self::system_smt_capabilities(); + let mut result = Vec::with_capacity(persistent.len()); + let max_cpus = Self::cpu_count(); + let smt_guess = crate::settings::util::guess_smt(&persistent) && can_smt; + for (i, cpu) in persistent.drain(..).enumerate() { + // prevent having more CPUs than available + if let Some(max_cpus) = max_cpus { + if i == max_cpus { + break; + } + } + let new_cpu = Cpu::from_json(cpu, version, i); + result.push(new_cpu); + } + if let Some(max_cpus) = max_cpus { + if result.len() != max_cpus { + let mut sys_cpus = Cpus::system_default(); + for i in result.len()..sys_cpus.cpus.len() { + result.push(sys_cpus.cpus.remove(i)); + } + } + } + Self { + cpus: result, + smt: smt_guess, + smt_capable: can_smt, + } + } + + fn from_limits(_limits: GenericCpusLimit) -> Self { + Self::system_default() } } @@ -153,7 +207,7 @@ impl TCpus for Cpus { } } - fn json(&self) -> Vec { + fn json(&self) -> Vec { self.cpus.iter().map(|x| x.to_owned().into()).collect() } @@ -246,7 +300,7 @@ impl Cpu { .unwrap_or("schedutil".to_owned()), index: cpu_index, state: crate::state::steam_deck::Cpu::default(), - root: "/".into() + root: "/".into(), } } diff --git a/backend/src/settings/unknown/gpu.rs b/backend/src/settings/unknown/gpu.rs index 0b4b73e..90c1279 100644 --- a/backend/src/settings/unknown/gpu.rs +++ b/backend/src/settings/unknown/gpu.rs @@ -1,23 +1,28 @@ use std::convert::Into; +use limits_core::json_v2::GenericGpuLimit; + use crate::persist::GpuJson; use crate::settings::MinMax; -use crate::settings::TGpu; use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{ProviderBuilder, TGpu}; #[derive(Debug, Clone)] -pub struct Gpu { - slow_memory: bool, // ignored -} +pub struct Gpu {} impl Gpu { - #[inline] - pub fn from_json(_other: GpuJson, _version: u64) -> Self { - Self { slow_memory: false } + pub fn system_default() -> Self { + Self {} + } +} + +impl ProviderBuilder for Gpu { + fn from_json_and_limits(_persistent: GpuJson, _version: u64, _limits: GenericGpuLimit) -> Self { + Self::system_default() } - pub fn system_default() -> Self { - Self { slow_memory: false } + fn from_limits(_limits: GenericGpuLimit) -> Self { + Self::system_default() } } @@ -27,8 +32,10 @@ impl Into for Gpu { GpuJson { fast_ppt: None, slow_ppt: None, + tdp: None, + tdp_boost: None, clock_limits: None, - slow_memory: false, + memory_clock: None, root: None, } } @@ -48,11 +55,25 @@ impl OnResume for Gpu { impl crate::settings::OnPowerEvent for Gpu {} +impl crate::settings::OnLoad for Gpu { + fn on_load(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + +impl crate::settings::OnUnload for Gpu { + fn on_unload(&mut self) -> Result<(), Vec> { + Ok(()) + } +} + impl TGpu for Gpu { fn limits(&self) -> crate::api::GpuLimits { crate::api::GpuLimits { fast_ppt_limits: None, + fast_ppt_default: 1_000_000, slow_ppt_limits: None, + slow_ppt_default: 1_000_000, ppt_step: 1_000_000, tdp_limits: None, tdp_boost_limits: None, @@ -60,7 +81,8 @@ impl TGpu for Gpu { clock_min_limits: None, clock_max_limits: None, clock_step: 100, - memory_control_capable: false, + memory_control: None, + memory_step: 400, } } @@ -80,8 +102,10 @@ impl TGpu for Gpu { None } - fn slow_memory(&mut self) -> &mut bool { - &mut self.slow_memory + fn memory_clock(&mut self, _speed: Option) {} + + fn get_memory_clock(&self) -> Option { + None } fn provider(&self) -> crate::persist::DriverJson { diff --git a/backend/src/settings/unknown/mod.rs b/backend/src/settings/unknown/mod.rs index 2039cae..58b6ac1 100644 --- a/backend/src/settings/unknown/mod.rs +++ b/backend/src/settings/unknown/mod.rs @@ -3,5 +3,21 @@ mod cpu; mod gpu; pub use battery::Battery; -pub use cpu::{Cpu, Cpus}; +pub use cpu::Cpus; pub use gpu::Gpu; + +fn _impl_checker() { + fn impl_provider_builder, J, L>() {} + + impl_provider_builder::< + Battery, + crate::persist::BatteryJson, + limits_core::json_v2::GenericBatteryLimit, + >(); + impl_provider_builder::< + Cpus, + Vec, + limits_core::json_v2::GenericCpusLimit, + >(); + impl_provider_builder::(); +} diff --git a/backend/src/settings/util.rs b/backend/src/settings/util.rs index 47b98c5..8cda62b 100644 --- a/backend/src/settings/util.rs +++ b/backend/src/settings/util.rs @@ -18,10 +18,7 @@ pub fn always_satisfied<'a, X>(_: &'a X) -> bool { true } -pub const CARD_NEEDS: &[&'static str] = &[ - "dev", - "uevent" -]; +pub const CARD_NEEDS: &[&'static str] = &["dev", "uevent"]; #[cfg(test)] mod test { @@ -58,6 +55,7 @@ mod test { fn cpu_with_online(status: bool) -> CpuJson { CpuJson { + root: None, online: status, clock_limits: None, governor: "schedutil".to_owned(), diff --git a/backend/src/state/mod.rs b/backend/src/state/mod.rs index 570de60..cd8d3bc 100644 --- a/backend/src/state/mod.rs +++ b/backend/src/state/mod.rs @@ -1,8 +1,8 @@ -mod error; -mod traits; +//mod error; +//mod traits; pub mod generic; pub mod steam_deck; -pub use error::StateError; -pub use traits::OnPoll; +//pub use error::StateError; +//pub use traits::OnPoll; diff --git a/backend/src/state/steam_deck/battery.rs b/backend/src/state/steam_deck/battery.rs index 3c66e36..19ce82f 100644 --- a/backend/src/state/steam_deck/battery.rs +++ b/backend/src/state/steam_deck/battery.rs @@ -3,6 +3,7 @@ pub struct Battery { pub charge_rate_set: bool, pub charge_mode_set: bool, pub charger_state: ChargeState, + pub charge_limit_set: bool, } impl std::default::Default for Battery { @@ -11,6 +12,7 @@ impl std::default::Default for Battery { charge_rate_set: true, charge_mode_set: true, charger_state: ChargeState::Unknown, + charge_limit_set: true, } } } diff --git a/backend/src/utility.rs b/backend/src/utility.rs index 742cd32..3814211 100644 --- a/backend/src/utility.rs +++ b/backend/src/utility.rs @@ -1,18 +1,10 @@ -use std::fmt::Display; //use std::sync::{LockResult, MutexGuard}; //use std::fs::{Permissions, metadata}; use std::io::{Read, Write}; use std::os::unix::fs::PermissionsExt; -pub fn unwrap_maybe_fatal(result: Result, message: &str) -> T { - match result { - Ok(x) => x, - Err(e) => { - log::error!("{}: {}", message, e); - panic!("{}: {}", message, e); - } - } -} +use serde::{Deserialize, Serialize}; +use chrono::{offset::Utc, DateTime}; /*pub fn unwrap_lock<'a, T: Sized>( result: LockResult>, @@ -27,12 +19,37 @@ pub fn unwrap_maybe_fatal(result: Result, message: & } }*/ -pub fn settings_dir() -> std::path::PathBuf { +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct CachedData { + pub data: T, + pub updated: DateTime, +} + +impl CachedData { + pub fn needs_update(&self, max_age: std::time::Duration) -> bool { + self.updated < (Utc::now() - max_age) + } +} + +pub fn ron_pretty_config() -> ron::ser::PrettyConfig { + ron::ser::PrettyConfig::default() + .struct_names(true) + .compact_arrays(true) +} + +#[allow(dead_code)] +pub fn settings_dir_old() -> std::path::PathBuf { usdpl_back::api::dirs::home() .unwrap_or_else(|| "/tmp/".into()) .join(".config/powertools/") } +pub fn settings_dir() -> std::path::PathBuf { + usdpl_back::api::decky::settings_dir() + .unwrap_or_else(|_| "/tmp/".to_owned()) + .into() +} + pub fn chown_settings_dir() -> std::io::Result<()> { let dir = settings_dir(); #[cfg(feature = "decky")] @@ -101,3 +118,84 @@ pub fn read_version_file() -> String { } } } + +pub fn ioperm_power_ec() { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + unsafe { + let temp_ec = smokepatio::ec::unnamed_power::UnnamedPowerEC::new(); + libc::ioperm(temp_ec.ec().data() as _, 1, 1); + libc::ioperm(temp_ec.ec().cmd() as _, 1, 1); + } +} + +#[cfg(test)] +mod generate { + #[test] + fn generate_default_limits_override() { + let limits = limits_core::json_v2::Limits { + cpu: limits_core::json_v2::Limit { + provider: limits_core::json_v2::CpuLimitType::SteamDeck, + limits: limits_core::json_v2::GenericCpusLimit::default_for( + limits_core::json_v2::CpuLimitType::SteamDeck, + ), + }, + gpu: limits_core::json_v2::Limit { + provider: limits_core::json_v2::GpuLimitType::SteamDeck, + limits: limits_core::json_v2::GenericGpuLimit::default_for( + limits_core::json_v2::GpuLimitType::SteamDeck, + ), + }, + battery: limits_core::json_v2::Limit { + provider: limits_core::json_v2::BatteryLimitType::SteamDeck, + limits: limits_core::json_v2::GenericBatteryLimit::default_for( + limits_core::json_v2::BatteryLimitType::SteamDeck, + ), + }, + }; + let output_file = + std::fs::File::create(format!("../{}", crate::consts::LIMITS_OVERRIDE_FILE)).unwrap(); + ron::ser::to_writer_pretty(output_file, &limits, crate::utility::ron_pretty_config()) + .unwrap(); + } + + #[test] + fn generate_default_minimal_save_file() { + let mut mini_variants = std::collections::HashMap::with_capacity(2); + mini_variants.insert( + 0, + crate::persist::SettingsJson { + version: 0, + name: crate::consts::DEFAULT_SETTINGS_VARIANT_NAME.to_owned(), + variant: 0, + persistent: false, + cpus: vec![crate::persist::CpuJson::default(); 8], + gpu: crate::persist::GpuJson::default(), + battery: crate::persist::BatteryJson::default(), + provider: None, + }, + ); + mini_variants.insert( + 42, + crate::persist::SettingsJson { + version: 0, + name: "FortySecondary".to_owned(), + variant: 42, + persistent: false, + cpus: vec![crate::persist::CpuJson::default(); 8], + gpu: crate::persist::GpuJson::default(), + battery: crate::persist::BatteryJson::default(), + provider: None, + }, + ); + let savefile = crate::persist::FileJson { + version: 0, + app_id: 0, + name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), + variants: mini_variants, + }; + let output_file = + std::fs::File::create(format!("../{}", crate::consts::DEFAULT_SETTINGS_FILE)).unwrap(); + ron::ser::to_writer_pretty(output_file, &savefile, crate::utility::ron_pretty_config()) + .unwrap(); + } +} diff --git a/default_settings.ron b/default_settings.ron new file mode 100644 index 0000000..b0a7583 --- /dev/null +++ b/default_settings.ron @@ -0,0 +1,113 @@ +FileJson( + version: 0, + name: "Main", + app_id: 0, + variants: { + 42: SettingsJson( + version: 0, + name: "FortySecondary", + variant: 42, + persistent: false, + cpus: [CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + )], + gpu: GpuJson( + fast_ppt: None, + slow_ppt: None, + tdp: None, + tdp_boost: None, + clock_limits: None, + memory_clock: None, + ), + battery: BatteryJson( + charge_rate: None, + charge_mode: None, + events: [], +), + provider: None, + ), + 0: SettingsJson( + version: 0, + name: "Primary", + variant: 0, + persistent: false, + cpus: [CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + ), CpuJson( + online: true, + clock_limits: None, + governor: "schedutil", + )], + gpu: GpuJson( + fast_ppt: None, + slow_ppt: None, + tdp: None, + tdp_boost: None, + clock_limits: None, + memory_clock: None, + ), + battery: BatteryJson( + charge_rate: None, + charge_mode: None, + events: [], +), + provider: None, + ), + }, +) \ No newline at end of file diff --git a/limits_override.ron b/limits_override.ron new file mode 100644 index 0000000..c97c3b8 --- /dev/null +++ b/limits_override.ron @@ -0,0 +1,224 @@ +Limits( + cpu: Limit( + provider: GabeBoy, + limits: GenericCpusLimit( + cpus: [GenericCpuLimit( + clock_min: Some(RangeLimit( + min: Some(1400), + max: Some(3500), + )), + clock_max: Some(RangeLimit( + min: Some(400), + max: Some(3500), + )), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: LimitExtras( + experiments: false, + quirks: [], +), + ), GenericCpuLimit( + clock_min: Some(RangeLimit( + min: Some(1400), + max: Some(3500), + )), + clock_max: Some(RangeLimit( + min: Some(400), + max: Some(3500), + )), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: LimitExtras( + experiments: false, + quirks: [], +), + ), GenericCpuLimit( + clock_min: Some(RangeLimit( + min: Some(1400), + max: Some(3500), + )), + clock_max: Some(RangeLimit( + min: Some(400), + max: Some(3500), + )), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: LimitExtras( + experiments: false, + quirks: [], +), + ), GenericCpuLimit( + clock_min: Some(RangeLimit( + min: Some(1400), + max: Some(3500), + )), + clock_max: Some(RangeLimit( + min: Some(400), + max: Some(3500), + )), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: LimitExtras( + experiments: false, + quirks: [], +), + ), GenericCpuLimit( + clock_min: Some(RangeLimit( + min: Some(1400), + max: Some(3500), + )), + clock_max: Some(RangeLimit( + min: Some(400), + max: Some(3500), + )), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: LimitExtras( + experiments: false, + quirks: [], +), + ), GenericCpuLimit( + clock_min: Some(RangeLimit( + min: Some(1400), + max: Some(3500), + )), + clock_max: Some(RangeLimit( + min: Some(400), + max: Some(3500), + )), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: LimitExtras( + experiments: false, + quirks: [], +), + ), GenericCpuLimit( + clock_min: Some(RangeLimit( + min: Some(1400), + max: Some(3500), + )), + clock_max: Some(RangeLimit( + min: Some(400), + max: Some(3500), + )), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: LimitExtras( + experiments: false, + quirks: [], +), + ), GenericCpuLimit( + clock_min: Some(RangeLimit( + min: Some(1400), + max: Some(3500), + )), + clock_max: Some(RangeLimit( + min: Some(400), + max: Some(3500), + )), + clock_step: Some(100), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + skip_resume_reclock: false, + extras: LimitExtras( + experiments: false, + quirks: [], +), + )], + global_governors: true, + extras: LimitExtras( + experiments: false, + quirks: [], +), + ), + ), + gpu: Limit( + provider: GabeBoy, + limits: GenericGpuLimit( + fast_ppt: Some(RangeLimit( + min: Some(1000000), + max: Some(30000000), + )), + fast_ppt_default: Some(15000000), + slow_ppt: Some(RangeLimit( + min: Some(1000000), + max: Some(29000000), + )), + slow_ppt_default: Some(15000000), + ppt_divisor: Some(1000000), + ppt_step: Some(1), + tdp: None, + tdp_boost: None, + tdp_divisor: None, + tdp_step: None, + clock_min: Some(RangeLimit( + min: Some(400), + max: Some(1600), + )), + clock_max: Some(RangeLimit( + min: Some(400), + max: Some(1600), + )), + clock_step: Some(100), + memory_clock: Some(RangeLimit( + min: Some(400), + max: Some(800), + )), + memory_clock_step: Some(400), + skip_resume_reclock: false, + extras: LimitExtras( + experiments: false, + quirks: ["pp_dpm_fclk-reversed", "pp_dpm_fclk-not-updated-on-LCD"], + ), + ), + ), + battery: Limit( + provider: GabeBoy, + limits: GenericBatteryLimit( + charge_rate: Some(RangeLimit( + min: Some(250), + max: Some(2500), + )), + charge_modes: ["normal", "discharge", "idle"], + charge_limit: Some(RangeLimit( + min: Some(10.0), + max: Some(90.0), + )), + extra_readouts: false, + extras: LimitExtras( + experiments: false, + quirks: [], +), + ), + ), +) \ No newline at end of file diff --git a/package.json b/package.json index c49299a..56d46cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "PowerTools", - "version": "1.4.0", + "version": "2.0.0", "description": "Power tweaks for power users", "scripts": { "build": "shx rm -rf dist && rollup -c", @@ -31,16 +31,16 @@ "@rollup/plugin-replace": "^4.0.0", "@rollup/plugin-typescript": "^8.5.0", "@types/react": "16.14.0", - "@types/webpack": "^5.28.1", + "@types/webpack": "^5.28.5", "rollup": "^2.79.1", "rollup-plugin-import-assets": "^1.1.1", "shx": "^0.3.4", - "tslib": "^2.5.3", - "typescript": "^4.9.5" + "tslib": "^2.6.2", + "typescript": "^4.9.5", + "decky-frontend-lib": "~3.24.4" }, "dependencies": { - "decky-frontend-lib": "~3.21.1", - "react-icons": "^4.9.0", + "react-icons": "^5.0.1", "usdpl-front": "file:src/usdpl_front" } } diff --git a/plugin.json b/plugin.json index 97a7714..c854773 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "name": "PowerTools", "author": "NGnius", - "flags": ["root"], + "flags": ["root", "global-dfl"], "publish": { "discord_id": "106537989684887552", "description": "Power tweaks for power users", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c815130..c6e3504 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,12 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: - decky-frontend-lib: - specifier: ~3.21.1 - version: 3.21.1 react-icons: - specifier: ^4.9.0 - version: 4.9.0(react@18.2.0) + specifier: ^5.0.1 + version: 5.0.1(react@18.2.0) usdpl-front: specifier: file:src/usdpl_front version: file:src/usdpl_front @@ -30,13 +27,16 @@ devDependencies: 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.5.3)(typescript@4.9.5) + 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.1 - version: 5.28.1 + specifier: ^5.28.5 + version: 5.28.5 + decky-frontend-lib: + specifier: ~3.24.4 + version: 3.24.4 rollup: specifier: ^2.79.1 version: 2.79.1 @@ -47,8 +47,8 @@ devDependencies: specifier: ^0.3.4 version: 0.3.4 tslib: - specifier: ^2.5.3 - version: 2.5.3 + specifier: ^2.6.2 + version: 2.6.2 typescript: specifier: ^4.9.5 version: 4.9.5 @@ -61,11 +61,11 @@ packages: dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.22 dev: true - /@jridgewell/resolve-uri@3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} dev: true @@ -74,26 +74,22 @@ packages: engines: {node: '>=6.0.0'} dev: true - /@jridgewell/source-map@0.3.3: - resolution: {integrity: sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==} + /@jridgewell/source-map@0.3.5: + resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} dependencies: '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - dev: true - - /@jridgewell/sourcemap-codec@1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + '@jridgewell/trace-mapping': 0.3.22 dev: true /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true - /@jridgewell/trace-mapping@0.3.18: - resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} + /@jridgewell/trace-mapping@0.3.22: + resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 dev: true /@rollup/plugin-commonjs@21.1.0(rollup@2.79.1): @@ -108,7 +104,7 @@ packages: glob: 7.2.3 is-reference: 1.2.1 magic-string: 0.25.9 - resolve: 1.22.2 + resolve: 1.22.8 rollup: 2.79.1 dev: true @@ -132,7 +128,7 @@ packages: deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 - resolve: 1.22.2 + resolve: 1.22.8 rollup: 2.79.1 dev: true @@ -146,7 +142,7 @@ packages: rollup: 2.79.1 dev: true - /@rollup/plugin-typescript@8.5.0(rollup@2.79.1)(tslib@2.5.3)(typescript@4.9.5): + /@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: @@ -158,9 +154,9 @@ packages: optional: true dependencies: '@rollup/pluginutils': 3.1.0(rollup@2.79.1) - resolve: 1.22.2 + resolve: 1.22.8 rollup: 2.79.1 - tslib: 2.5.3 + tslib: 2.6.2 typescript: 4.9.5 dev: true @@ -176,59 +172,61 @@ packages: rollup: 2.79.1 dev: true - /@types/eslint-scope@3.7.4: - resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} + /@types/eslint-scope@3.7.7: + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: - '@types/eslint': 8.40.2 - '@types/estree': 1.0.1 + '@types/eslint': 8.56.2 + '@types/estree': 1.0.5 dev: true - /@types/eslint@8.40.2: - resolution: {integrity: sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==} + /@types/eslint@8.56.2: + resolution: {integrity: sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==} dependencies: - '@types/estree': 1.0.1 - '@types/json-schema': 7.0.12 + '@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.1: - resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true - /@types/json-schema@7.0.12: - resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true - /@types/node@20.3.1: - resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} + /@types/node@20.11.9: + resolution: {integrity: sha512-CQXNuMoS/VcoAMISe5pm4JnEd1Br5jildbQEToEMQvutmv+EaQr90ry9raiudgpyDuqFiV9e4rnjSfLNq12M5w==} + dependencies: + undici-types: 5.26.5 dev: true - /@types/prop-types@15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + /@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.5 - csstype: 3.1.2 + '@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.3.1 + '@types/node': 20.11.9 dev: true - /@types/webpack@5.28.1: - resolution: {integrity: sha512-qw1MqGZclCoBrpiSe/hokSgQM/su8Ocpl3L/YHE0L6moyaypg4+5F7Uzq7NgaPKPxUxUbQ4fLPLpDWdR27bCZw==} + /@types/webpack@5.28.5: + resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==} dependencies: - '@types/node': 20.3.1 + '@types/node': 20.11.9 tapable: 2.2.1 - webpack: 5.87.0 + webpack: 5.90.0 transitivePeerDependencies: - '@swc/core' - esbuild @@ -350,16 +348,16 @@ packages: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} dev: true - /acorn-import-assertions@1.9.0(acorn@8.9.0): + /acorn-import-assertions@1.9.0(acorn@8.11.3): resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} peerDependencies: acorn: ^8 dependencies: - acorn: 8.9.0 + acorn: 8.11.3 dev: true - /acorn@8.9.0: - resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -392,15 +390,15 @@ packages: concat-map: 0.0.1 dev: true - /browserslist@4.21.9: - resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==} + /browserslist@4.22.3: + resolution: {integrity: sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001503 - electron-to-chromium: 1.4.433 - node-releases: 2.0.12 - update-browserslist-db: 1.0.11(browserslist@4.21.9) + caniuse-lite: 1.0.30001581 + electron-to-chromium: 1.4.648 + node-releases: 2.0.14 + update-browserslist-db: 1.0.13(browserslist@4.22.3) dev: true /buffer-from@1.1.2: @@ -412,8 +410,8 @@ packages: engines: {node: '>=6'} dev: true - /caniuse-lite@1.0.30001503: - resolution: {integrity: sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==} + /caniuse-lite@1.0.30001581: + resolution: {integrity: sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==} dev: true /chrome-trace-event@1.0.3: @@ -433,21 +431,21 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /csstype@3.1.2: - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} dev: true - /decky-frontend-lib@3.21.1: - resolution: {integrity: sha512-30605ET9qqZ6St6I9WmMmLGgSrTIdMwo7xy85+lRaF1miUd2icOGEJjwnbVcZDdkal+1fJ3tNEDXlchVfG4TrA==} - dev: false + /decky-frontend-lib@3.24.4: + resolution: {integrity: sha512-aCrzVS74V68PQxi5qFS6rjQwWryVy+yYTQKZztXT/T5v1jlw3AKXpTi47pCQl+6TdAIru4hAAjOF8TSf4iONaw==} + dev: true /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.433: - resolution: {integrity: sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==} + /electron-to-chromium@1.4.648: + resolution: {integrity: sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==} dev: true /enhanced-resolve@5.15.0: @@ -458,8 +456,8 @@ packages: tapable: 2.2.1 dev: true - /es-module-lexer@1.3.0: - resolution: {integrity: sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==} + /es-module-lexer@1.4.1: + resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} dev: true /escalade@3.1.1: @@ -521,16 +519,16 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + /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.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} dev: true /glob-to-regexp@0.4.1: @@ -557,11 +555,11 @@ packages: engines: {node: '>=8'} dev: true - /has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} + /hasown@2.0.0: + resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + engines: {node: '>= 0.4'} dependencies: - function-bind: 1.1.1 + function-bind: 1.1.2 dev: true /inflight@1.0.6: @@ -587,10 +585,10 @@ packages: builtin-modules: 3.3.0 dev: true - /is-core-module@2.12.1: - resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} + /is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: - has: 1.0.3 + hasown: 2.0.0 dev: true /is-module@1.0.0: @@ -600,14 +598,14 @@ packages: /is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} dependencies: - '@types/estree': 1.0.1 + '@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.3.1 + '@types/node': 20.11.9 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -672,8 +670,8 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /node-releases@2.0.12: - resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==} + /node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true /once@1.4.0: @@ -700,8 +698,8 @@ packages: engines: {node: '>=8.6'} dev: true - /punycode@2.3.0: - resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} dev: true @@ -711,8 +709,8 @@ packages: safe-buffer: 5.2.1 dev: true - /react-icons@4.9.0(react@18.2.0): - resolution: {integrity: sha512-ijUnFr//ycebOqujtqtV9PFS7JjhWg0QU6ykURVHuL4cbofvRCf3f6GMn9+fBktEFQOIVZnuAYLZdiyadRQRFg==} + /react-icons@5.0.1(react@18.2.0): + resolution: {integrity: sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==} peerDependencies: react: '*' dependencies: @@ -730,14 +728,14 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} dependencies: - resolve: 1.22.2 + resolve: 1.22.8 dev: true - /resolve@1.22.2: - resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true dependencies: - is-core-module: 2.12.1 + is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true @@ -763,7 +761,7 @@ packages: engines: {node: '>=10.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /safe-buffer@5.2.1: @@ -774,13 +772,13 @@ packages: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/json-schema': 7.0.12 + '@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.1: - resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==} + /serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} dependencies: randombytes: 2.1.0 dev: true @@ -838,8 +836,8 @@ packages: engines: {node: '>=6'} dev: true - /terser-webpack-plugin@5.3.9(webpack@5.87.0): - resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} + /terser-webpack-plugin@5.3.10(webpack@5.90.0): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -854,27 +852,27 @@ packages: uglify-js: optional: true dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.22 jest-worker: 27.5.1 schema-utils: 3.3.0 - serialize-javascript: 6.0.1 - terser: 5.18.0 - webpack: 5.87.0 + serialize-javascript: 6.0.2 + terser: 5.27.0 + webpack: 5.90.0 dev: true - /terser@5.18.0: - resolution: {integrity: sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==} + /terser@5.27.0: + resolution: {integrity: sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==} engines: {node: '>=10'} hasBin: true dependencies: - '@jridgewell/source-map': 0.3.3 - acorn: 8.9.0 + '@jridgewell/source-map': 0.3.5 + acorn: 8.11.3 commander: 2.20.3 source-map-support: 0.5.21 dev: true - /tslib@2.5.3: - resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: true /typescript@4.9.5: @@ -883,13 +881,17 @@ packages: hasBin: true dev: true - /update-browserslist-db@1.0.11(browserslist@4.21.9): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + + /update-browserslist-db@1.0.13(browserslist@4.22.3): + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.9 + browserslist: 4.22.3 escalade: 3.1.1 picocolors: 1.0.0 dev: true @@ -897,7 +899,7 @@ packages: /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.3.0 + punycode: 2.3.1 dev: true /url-join@4.0.1: @@ -917,8 +919,8 @@ packages: engines: {node: '>=10.13.0'} dev: true - /webpack@5.87.0: - resolution: {integrity: sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==} + /webpack@5.90.0: + resolution: {integrity: sha512-bdmyXRCXeeNIePv6R6tGPyy20aUobw4Zy8r0LUS2EWO+U+Ke/gYDgsCh7bl5rB6jPpr4r0SZa6dPxBxLooDT3w==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -927,17 +929,17 @@ packages: webpack-cli: optional: true dependencies: - '@types/eslint-scope': 3.7.4 - '@types/estree': 1.0.1 + '@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.9.0 - acorn-import-assertions: 1.9.0(acorn@8.9.0) - browserslist: 4.21.9 + acorn: 8.11.3 + acorn-import-assertions: 1.9.0(acorn@8.11.3) + browserslist: 4.22.3 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 - es-module-lexer: 1.3.0 + es-module-lexer: 1.4.1 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -948,7 +950,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.87.0) + terser-webpack-plugin: 5.3.10(webpack@5.90.0) watchpack: 2.4.0 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/pt_oc.json b/pt_oc.json deleted file mode 100644 index 4ebd7c1..0000000 --- a/pt_oc.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "battery": { - "charge_rate": { - "min": 250, - "max": 2500 - }, - "extra_readouts": false - }, - "cpus": { - "cpus": [ - { - "clock_min": { - "min": 1400, - "max": 3500 - }, - "clock_max": { - "min": 400, - "max": 3500 - }, - "clock_step": 100, - "skip_resume_reclock": false - }, - { - "clock_min": { - "min": 1400, - "max": 3500 - }, - "clock_max": { - "min": 400, - "max": 3500 - }, - "clock_step": 100, - "skip_resume_reclock": false - }, - { - "clock_min": { - "min": 1400, - "max": 3500 - }, - "clock_max": { - "min": 400, - "max": 3500 - }, - "clock_step": 100, - "skip_resume_reclock": false - }, - { - "clock_min": { - "min": 1400, - "max": 3500 - }, - "clock_max": { - "min": 400, - "max": 3500 - }, - "clock_step": 100, - "skip_resume_reclock": false - }, - { - "clock_min": { - "min": 1400, - "max": 3500 - }, - "clock_max": { - "min": 400, - "max": 3500 - }, - "clock_step": 100, - "skip_resume_reclock": false - }, - { - "clock_min": { - "min": 1400, - "max": 3500 - }, - "clock_max": { - "min": 400, - "max": 3500 - }, - "clock_step": 100, - "skip_resume_reclock": false - }, - { - "clock_min": { - "min": 1400, - "max": 3500 - }, - "clock_max": { - "min": 400, - "max": 3500 - }, - "clock_step": 100, - "skip_resume_reclock": false - }, - { - "clock_min": { - "min": 1400, - "max": 3500 - }, - "clock_max": { - "min": 400, - "max": 3500 - }, - "clock_step": 100, - "skip_resume_reclock": false - } - ], - "global_governors": true - }, - "gpu": { - "fast_ppt": { - "min": 1000000, - "max": 30000000 - }, - "fast_ppt_default": 15000000, - "slow_ppt": { - "min": 1000000, - "max": 29000000 - }, - "slow_ppt_default": 15000000, - "ppt_divisor": 1000000, - "ppt_step": 1, - "clock_min": { - "min": 400, - "max": 1600 - }, - "clock_max": { - "min": 400, - "max": 1600 - }, - "clock_step": 100, - "skip_resume_reclock": false - } -} \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js index 8717908..3b2e789 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -24,12 +24,13 @@ export default defineConfig({ }) ], context: 'window', - external: ['react', 'react-dom'], + external: ['react', 'react-dom', 'decky-frontend-lib'], output: { file: 'dist/index.js', globals: { react: 'SP_REACT', 'react-dom': 'SP_REACTDOM', + 'decky-frontend-lib': 'DFL', }, format: 'iife', exports: 'default', diff --git a/src/backend.ts b/src/backend.ts index 07fe9d8..fa3d276 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -85,12 +85,15 @@ export type GeneralLimits = {}; export type GpuLimits = { fast_ppt_limits: RangeLimit | null; + fast_ppt_default: number; slow_ppt_limits: RangeLimit | null; + slow_ppt_default: number; ppt_step: number; clock_min_limits: RangeLimit | null; clock_max_limits: RangeLimit | null; clock_step: number; - memory_control_capable: boolean; + memory_control: RangeLimit | null, + memory_step: number, }; // API @@ -229,14 +232,18 @@ export async function unsetGpuClockLimits(): Promise { return (await call_backend("GPU_unset_clock_limits", [])); } -export async function setGpuSlowMemory(val: boolean): Promise { - return (await call_backend("GPU_set_slow_memory", [val]))[0]; +export async function setGpuSlowMemory(clock: number): Promise { + return (await call_backend("GPU_set_slow_memory", [clock]))[0]; } -export async function getGpuSlowMemory(): Promise { +export async function getGpuSlowMemory(): Promise { return (await call_backend("GPU_get_slow_memory", []))[0]; } +export async function unsetGpuSlowMemory(): Promise { + return (await call_backend("GPU_unset_slow_memory", [])); +} + // general export async function setGeneralPersistent(val: boolean): Promise { @@ -247,8 +254,22 @@ export async function getGeneralPersistent(): Promise { return (await call_backend("GENERAL_get_persistent", []))[0]; } -export async function loadGeneralSettings(id: string, name: string): Promise { - return (await call_backend("GENERAL_load_settings", [id, name]))[0]; +export async function loadGeneralSettings(id: string, name: string, variant_id: string, variant_name: string | undefined): Promise { + if (variant_name) { + return (await call_backend("GENERAL_load_settings", [id, name, variant_id, variant_name]))[0]; + } else { + return (await call_backend("GENERAL_load_settings", [id, name, variant_id]))[0]; + } + +} + +export async function loadGeneralSettingsVariant(variant_id: string, variant_name: string | undefined): Promise { + console.log("GENERAL_load_variant"); + if (variant_name) { + return (await call_backend("GENERAL_load_variant", [variant_id, variant_name]))[0]; + } else { + return (await call_backend("GENERAL_load_variant", [variant_id]))[0]; + } } export async function loadGeneralDefaultSettings(): Promise { @@ -344,3 +365,41 @@ export async function getPeriodicals(): Promise { settings_path: result[4], }; } + +export type StoreMetadata = { + name: string, + steam_app_id: number, + steam_user_id: number, + steam_username: string, + tags: string[], + id: string, + //config: any, +} + +export async function searchStoreByAppId(id: string): Promise { + //console.log("WEB_search_by_app"); + return (await call_backend("WEB_search_by_app", [id]))[0]; +} + +export type VariantInfo = { + id: string, + name: string, +} + +export async function storeDownloadById(id: string): Promise { + return (await call_backend("WEB_download_new", [id])); +} + +export async function storeUpload(steam_id: string, steam_username: string): Promise { + return (await call_backend("WEB_upload_new", [steam_id, steam_username])); +} + +export async function getAllSettingVariants(): Promise { + //console.log("GENERAL_get_all_variants"); + return (await call_backend("GENERAL_get_all_variants", [])); +} + +export async function getCurrentSettingVariant(): Promise { + //console.log("GENERAL_get_current_variant"); + return (await call_backend("GENERAL_get_current_variant", []))[0]; +} diff --git a/src/components/battery.tsx b/src/components/battery.tsx index 0199e1b..59b476d 100644 --- a/src/components/battery.tsx +++ b/src/components/battery.tsx @@ -1,5 +1,5 @@ import { Fragment } from "react"; -import {Component} from "react"; +import { Component } from "react"; import { ToggleField, SliderField, diff --git a/src/components/debug.tsx b/src/components/debug.tsx index d858253..e41d8ac 100644 --- a/src/components/debug.tsx +++ b/src/components/debug.tsx @@ -22,11 +22,16 @@ let isSpecialDay = now.getDate() == 1 && now.getMonth() == 3; export class Debug extends Component { render() { - return buildDebug(); + const reloadGUI = (x: string) => this.setState((_state) => { + return { + reloadThingy: x, + }; + }); + return buildDebug(reloadGUI); } } -function buildDebug() { +function buildDebug(reloadGUI: (x: string) => void) { return ({/* Version Info */}
{eggCount % 10 == 9 ? "Ha! Nerd" : tr("Debug")} @@ -38,9 +43,10 @@ function buildDebug() { if (eggCount % 10 == 9) { // you know you're bored and/or conceited when you spend time adding an easter egg // that just sends people to your own project's repo - Navigation.NavigateToExternalWeb("https://github.com/NGnius/PowerTools/releases"); + Navigation.NavigateToExternalWeb("https://git.ngni.us/NG-SD-Plugins/PowerTools/releases"); } eggCount++; + reloadGUI("BackendInfo"); }}> {eggCount % 10 == 9 ? "by NGnius" : get_value(BACKEND_INFO)} @@ -48,14 +54,14 @@ function buildDebug() { eggCount++}> + onClick={() => {eggCount++; reloadGUI("FrameworkInfo");}}> {eggCount % 10 == 9 ? "<3 <3 <3" : target_usdpl()} eggCount++}> + onClick={() => {eggCount++; reloadGUI("DriverInfo");}}> {eggCount % 10 == 9 ? "Ryan Gosling" : get_value(DRIVER_INFO)} @@ -66,9 +72,10 @@ function buildDebug() { if (eggCount % 10 == 9) { // you know you're bored and/or conceited when you spend time adding an easter egg // that just sends people to your own project's repo - Navigation.NavigateToExternalWeb("https://github.com/NGnius/usdpl-rs"); + Navigation.NavigateToExternalWeb("https://git.ngni.us/NG-SD-Plugins/usdpl-rs"); } eggCount++; + reloadGUI("USDPLInfo"); }}> v{version_usdpl()} diff --git a/src/components/gpu.tsx b/src/components/gpu.tsx index a8fdd52..fce7670 100644 --- a/src/components/gpu.tsx +++ b/src/components/gpu.tsx @@ -41,11 +41,11 @@ export class Gpu extends Component { onChange={(value: boolean) => { if (value) { if ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits != null) { - set_value(SLOW_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits!.max); + set_value(SLOW_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_default); } if ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits != null) { - set_value(FAST_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits!.max); + set_value(FAST_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_default); } reloadGUI("GPUPPTToggle"); } else { @@ -180,16 +180,42 @@ export class Gpu extends Component { }} />} - {(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.memory_control_capable && + {((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.memory_control) && { - backend.resolve(backend.setGpuSlowMemory(value), (val: boolean) => { - set_value(SLOW_MEMORY_GPU, val); - reloadGUI("GPUSlowMemory"); - }) + if (value) { + set_value(SLOW_MEMORY_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.memory_control!.max); + reloadGUI("GPUMemFreqToggle"); + } else { + set_value(SLOW_MEMORY_GPU, null); + backend.resolve(backend.unsetGpuSlowMemory(), (_: any[]) => { + reloadGUI("GPUUnsetMemFreq"); + }); + } + }} + /> + } + {get_value(SLOW_MEMORY_GPU) != null && + { + backend.log(backend.LogLevel.Debug, "GPU memory clock Max is now " + val.toString()); + backend.resolve( + backend.setGpuSlowMemory(val), + (val: number) => { + set_value(SLOW_MEMORY_GPU, val); + reloadGUI("GPUSetMemFreq"); + } + ); }} /> } diff --git a/src/components/text_field_modal.tsx b/src/components/text_field_modal.tsx new file mode 100644 index 0000000..c5ff5d6 --- /dev/null +++ b/src/components/text_field_modal.tsx @@ -0,0 +1,65 @@ +// Based on https://github.com/isiah-lloyd/radiyo-steam-deck/blob/main/src/TextFieldModal.tsx + +import { ModalRoot, ModalRootProps, Router, TextField, Focusable, DialogButton } from 'decky-frontend-lib'; +import { useEffect, useRef, useState } from 'react'; +import { HiCheck, HiX } from "react-icons/hi"; +type props = ModalRootProps & { + label: string, + placeholder: string, + onClosed: (inputText: string) => void; +} +export const TextFieldModal = ({ closeModal, onClosed, label, placeholder }: props) => { + const [inputText, setInputText] = useState(''); + const handleText = (e: React.ChangeEvent) => { + setInputText(e.target.value); + }; + const textField = useRef(); + useEffect(() => { + Router.CloseSideMenus(); + //This will open up the virtual keyboard + textField.current?.element?.click(); + }, []); + const submit = () => onClosed(inputText); + return ( + +
+ + + { submit() }} + > + + + { if (closeModal) { closeModal() } }} + > + + + + +
+ ); +}; diff --git a/src/consts.ts b/src/consts.ts index 4364cb6..b938fe4 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -30,9 +30,20 @@ export const SLOW_MEMORY_GPU = "GPU_slow_memory"; export const PERSISTENT_GEN = "GENERAL_persistent"; export const NAME_GEN = "GENERAL_name"; export const PATH_GEN = "GENERAL_path"; +export const VARIANTS_GEN = "GENERAL_setting_variants"; +export const CURRENT_VARIANT_GEN = "GENERAL_current_variant"; export const MESSAGE_LIST = "MESSAGE_messages"; +export const INTERNAL_STEAM_ID = "INTERNAL_steam_id"; +export const INTERNAL_STEAM_USERNAME = "INTERNAL_stream_username"; + +export const STORE_RESULTS = "INTERNAL_store_results"; + export const PERIODICAL_BACKEND_PERIOD = 5000; // milliseconds export const AUTOMATIC_REAPPLY_WAIT = 2000; // milliseconds +export const STORE_RESULTS_URI = "/plugins/PowerTools/settings_store"; +export const STORE_MAIN_APP_ID = "1"; +export const STORE_MAIN_APP_ID_DEV = "0"; + diff --git a/src/index.tsx b/src/index.tsx index df1407d..a6e88f3 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,27 +1,26 @@ import { ButtonItem, definePlugin, - //DialogButton, - //Menu, - //MenuItem, + DialogButton, PanelSection, PanelSectionRow, ServerAPI, - //showContextMenu, staticClasses, - //SliderField, ToggleField, - //Dropdown, Field, - //DropdownOption, - //SingleDropdownOption, - //NotchLabel - //gamepadDialogClasses, - //joinClassNames, + Dropdown, + SingleDropdownOption, + Navigation, + Focusable, + Spinner, + showModal, + QuickAccessTab, + ShowModalResult, } from "decky-frontend-lib"; import { VFC, useState } from "react"; -import { GiDrill, GiTimeBomb, GiTimeTrap, GiDynamite } from "react-icons/gi"; -import { HiRefresh, HiTrash } from "react-icons/hi"; +import { GiDrill, GiFireExtinguisher, GiFireBomb, GiMineExplosion } from "react-icons/gi"; +import { HiRefresh, HiTrash, HiPlus, HiUpload } from "react-icons/hi"; +import { TbWorldPlus } from "react-icons/tb"; //import * as python from "./python"; import * as backend from "./backend"; @@ -58,9 +57,18 @@ import { PERSISTENT_GEN, NAME_GEN, PATH_GEN, + VARIANTS_GEN, + CURRENT_VARIANT_GEN, MESSAGE_LIST, + INTERNAL_STEAM_ID, + INTERNAL_STEAM_USERNAME, + + STORE_RESULTS, + STORE_RESULTS_URI, + STORE_MAIN_APP_ID, + PERIODICAL_BACKEND_PERIOD, AUTOMATIC_REAPPLY_WAIT, } from "./consts"; @@ -71,11 +79,19 @@ import { Battery } from "./components/battery"; import { Cpus } from "./components/cpus"; import { DevMessages } from "./components/message"; -var periodicHook: NodeJS.Timer | null = null; +import { TextFieldModal } from "./components/text_field_modal"; + +import { StoreResultsPage } from "./store/page"; + +var periodicHook: NodeJS.Timeout | null = null; var lifetimeHook: any = null; var startHook: any = null; var endHook: any = null; +var userHook: any = null; var usdplReady = false; +var isVariantLoading = false; + +var tryNotifyProfileChange = function() {}; type MinMax = { min: number | null; @@ -114,6 +130,10 @@ const reload = function() { console.debug("POWERTOOLS: got limits ", limits); }); + if (!get_value(STORE_RESULTS)) { + backend.resolve(backend.searchStoreByAppId(STORE_MAIN_APP_ID), (results) => set_value(STORE_RESULTS, results)); + } + backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) }); backend.resolve_nullable(backend.getBatteryChargeRate(), (rate: number | null) => { set_value(CHARGE_RATE_BATT, rate) }); backend.resolve_nullable(backend.getBatteryChargeMode(), (mode: string | null) => { set_value(CHARGE_MODE_BATT, mode) }); @@ -151,11 +171,13 @@ const reload = function() { set_value(CLOCK_MIN_GPU, limits[0]); set_value(CLOCK_MAX_GPU, limits[1]); }); - backend.resolve(backend.getGpuSlowMemory(), (status: boolean) => { set_value(SLOW_MEMORY_GPU, status) }); + backend.resolve_nullable(backend.getGpuSlowMemory(), (status: number | null) => { set_value(SLOW_MEMORY_GPU, status) }); backend.resolve(backend.getGeneralPersistent(), (value: boolean) => { set_value(PERSISTENT_GEN, value) }); backend.resolve(backend.getGeneralSettingsName(), (name: string) => { set_value(NAME_GEN, name) }); backend.resolve(backend.getGeneralSettingsPath(), (path: string) => { set_value(PATH_GEN, path) }); + backend.resolve(backend.getAllSettingVariants(), (variants: backend.VariantInfo[]) => { set_value(VARIANTS_GEN, variants) }); + backend.resolve(backend.getCurrentSettingVariant(), (variant: backend.VariantInfo) => { set_value(CURRENT_VARIANT_GEN, variant) }); backend.resolve(backend.getInfo(), (info: string) => { set_value(BACKEND_INFO, info) }); backend.resolve(backend.getDriverProviderName("gpu"), (driver: string) => { set_value(DRIVER_INFO, driver) }); @@ -163,12 +185,21 @@ const reload = function() { backend.resolve(backend.getMessages(null), (messages: backend.Message[]) => { set_value(MESSAGE_LIST, messages) }); }; -// init USDPL WASM and connection to back-end -(async function(){ - await backend.initBackend(); - usdplReady = true; - reload(); // technically this is only a load +const clearHooks = function() { + clearInterval(periodicHook!); + periodicHook = null; + lifetimeHook?.unregister(); + startHook?.unregister(); + endHook?.unregister(); + userHook?.unregister(); + backend.log(backend.LogLevel.Info, "Unregistered PowerTools callbacks, so long and thanks for all the fish."); +}; + +const registerCallbacks = function(autoclear: boolean) { + if (autoclear) { + clearHooks(); + } // register Steam callbacks //@ts-ignore lifetimeHook = SteamClient.GameSessions.RegisterForAppLifetimeNotifications((update) => { @@ -177,9 +208,20 @@ const reload = function() { //backend.log(backend.LogLevel.Debug, "AppID " + update.unAppID.toString() + " is now running"); } else { //backend.log(backend.LogLevel.Debug, "AppID " + update.unAppID.toString() + " is no longer running"); + backend.resolve(backend.loadGeneralDefaultSettings(), (ok: boolean) => { + backend.log(backend.LogLevel.Debug, "Loading default settings ok? " + ok); + reload(); + backend.resolve(backend.waitForComplete(), (_) => { + backend.log(backend.LogLevel.Debug, "Trying to tell UI to re-render due to game exit"); + tryNotifyProfileChange(); + }); + } + ); backend.resolve( - backend.loadGeneralDefaultSettings(), - (ok: boolean) => {backend.log(backend.LogLevel.Debug, "Loading default settings ok? " + ok)} + backend.searchStoreByAppId(STORE_MAIN_APP_ID), + (results: backend.StoreMetadata[]) => { + set_value(STORE_RESULTS, results); + } ); } }); @@ -187,22 +229,65 @@ const reload = function() { startHook = SteamClient.Apps.RegisterForGameActionStart((actionType, id) => { //@ts-ignore let gameInfo: any = appStore.GetAppOverviewByGameID(id); + let appId: string = gameInfo.appid.toString(); backend.log(backend.LogLevel.Info, "RegisterForGameActionStart callback(" + actionType + ", " + id + ")"); - // don't use gameInfo.appid, haha backend.resolve( - backend.loadGeneralSettings(id.toString(), gameInfo.display_name), - (ok: boolean) => {backend.log(backend.LogLevel.Debug, "Loading settings ok? " + ok)} + backend.loadGeneralSettings(appId, gameInfo.display_name, "0", undefined), + (ok: boolean) => { + backend.log(backend.LogLevel.Debug, "Loading settings ok? " + ok); + reload(); + backend.resolve(backend.waitForComplete(), (_) => { + backend.log(backend.LogLevel.Debug, "Trying to tell UI to re-render due to new game launch"); + tryNotifyProfileChange(); + }); + } + ); + backend.resolve( + backend.searchStoreByAppId(appId), + (results: backend.StoreMetadata[]) => { + set_value(STORE_RESULTS, results); + } ); }); + // this fires immediately, so let's ignore that callback + let hasFiredImmediately = false; //@ts-ignore endHook = SteamClient.Apps.RegisterForGameActionEnd((actionType) => { + if (!hasFiredImmediately) { + hasFiredImmediately = true; + backend.log(backend.LogLevel.Debug, "RegisterForGameActionEnd immediately fired callback(" + actionType + ")"); + return; + } backend.log(backend.LogLevel.Info, "RegisterForGameActionEnd callback(" + actionType + ")"); setTimeout(() => backend.forceApplySettings(), AUTOMATIC_REAPPLY_WAIT); }); + //@ts-ignore + userHook = SteamClient.User.RegisterForCurrentUserChanges((data) => { + const accountName = data.strAccountName; + const steamId = data.strSteamID; + SteamClient.User.GetLoginUsers().then((users: any) => { + users.forEach((user: any) => { + if (user && user.accountName == accountName) { + set_value(INTERNAL_STEAM_ID, steamId); + set_value(INTERNAL_STEAM_USERNAME, user.personaName ? user.personaName : accountName); + } + }); + }); + }); + backend.log(backend.LogLevel.Debug, "Registered PowerTools callbacks, hello!"); +}; + +// init USDPL WASM and connection to back-end +(async function(){ + await backend.initBackend(); + usdplReady = true; + reload(); // technically this is only a load + + registerCallbacks(true); })(); const periodicals = function() { @@ -216,16 +301,13 @@ const periodicals = function() { const oldValue = get_value(PATH_GEN); set_value(PATH_GEN, path); if (path != oldValue) { - backend.log(backend.LogLevel.Info, "Frontend values reload triggered by path change: " + oldValue + " -> " + path); + backend.log(backend.LogLevel.Debug, "Frontend values reload triggered by path change: " + oldValue + " -> " + path); reload(); } }) }; -const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { - - const [idc, reloadGUI] = useState("/shrug"); - +const periodicalsSetup = function(reloadGUI: (s: string) => void) { if (periodicHook != null) { clearInterval(periodicHook); periodicHook = null; @@ -235,6 +317,14 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { periodicals(); reloadGUI("periodic" + (new Date()).getTime().toString()); }, PERIODICAL_BACKEND_PERIOD); +}; + +const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { + + const [idc, reloadGUI] = useState("/shrug"); + tryNotifyProfileChange = function() { reloadGUI("ProfileChangeByNotification") }; + + periodicalsSetup(reloadGUI); if (!usdplReady || !get_value(LIMITS_INFO)) { // Not translated on purpose (to avoid USDPL issues) @@ -246,6 +336,8 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { onClick={(_: MouseEvent) => { console.log("POWERTOOLS: manual reload after startup failure"); reload(); + // try to reload GUI too + backend.resolve(backend.waitForComplete(), (_) => {reloadGUI("LoadSystemDefaults")}); }} > Reload @@ -254,6 +346,33 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { ) } + const variantOptions: SingleDropdownOption[] = (get_value(VARIANTS_GEN) as backend.VariantInfo[]).map((elem) => {return { + data: elem, + label: {elem.name}, + };}); + + var modalResult: ShowModalResult | undefined = undefined; + + const onNewVariantModelClosed = (name: string) => { + if (modalResult) { + modalResult.Close(); + } + console.log("POWERTOOLS: variant name", name); + isVariantLoading = true; + backend.resolve( + backend.loadGeneralSettingsVariant("please give me a new ID k thx bye" /* anything that cannot be parsed as a u64 will be set to u64::MAX, which will cause the back-end to auto-generate an ID */, name), + (ok: boolean) => { + backend.log(backend.LogLevel.Debug, "New settings variant ok? " + ok); + reload(); + backend.resolve(backend.waitForComplete(), (_) => { + backend.log(backend.LogLevel.Debug, "Trying to tell UI to re-render due to new settings variant"); + tryNotifyProfileChange(); + }); + } + ); + Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky); + }; + return ( @@ -289,6 +408,97 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { {get_value(NAME_GEN)}
+ + + {(!isVariantLoading && { + backend.log(backend.LogLevel.Debug, "POWERTOOLS: looking for variant data.id " + (get_value(CURRENT_VARIANT_GEN) as backend.VariantInfo).id.toString()); + return (val.data as backend.VariantInfo).id == (get_value(CURRENT_VARIANT_GEN) as backend.VariantInfo).id; + })} + strDefaultLabel={(get_value(CURRENT_VARIANT_GEN) as backend.VariantInfo | undefined)?.name?? (get_value(VARIANTS_GEN) as backend.VariantInfo[])[0].name} + onChange={(elem: SingleDropdownOption) => { + if (elem.data.id != (get_value(CURRENT_VARIANT_GEN) as backend.VariantInfo).id) { + isVariantLoading = true; + let data = elem.data as backend.VariantInfo; + backend.log(backend.LogLevel.Debug, "Profile variant dropdown selected " + elem.data.id.toString()); + //set_value(CURRENT_VARIANT_GEN, elem.data as backend.VariantInfo); + backend.resolve( + backend.loadGeneralSettingsVariant(data.id, data.name), + (ok: boolean) => { + backend.log(backend.LogLevel.Debug, "Loaded settings variant ok? " + ok); + reload(); + isVariantLoading = false; + backend.resolve(backend.waitForComplete(), (_) => { + backend.log(backend.LogLevel.Debug, "Trying to tell UI to re-render due to newly-selected settings variant"); + tryNotifyProfileChange(); + //reloadGUI("ProfileVariantSelected"); + }); + } + ); + } + }} + />)} + {(isVariantLoading && )} + + + + + { + backend.log(backend.LogLevel.Debug, "Creating new PowerTools settings variant"); + modalResult = showModal( { modalResult?.Close(); Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky)}}/>, window); + }} + > + + + { + const steamId = get_value(INTERNAL_STEAM_ID); + const steamName = get_value(INTERNAL_STEAM_USERNAME); + if (steamId && steamName) { + backend.storeUpload(steamId, steamName); + } else { + backend.log(backend.LogLevel.Warn, "Cannot upload with null steamID (is null: " + !steamId + ") and/or username (is null: " + !steamName + ")"); + } + }} + > + + + { + Navigation.Navigate(STORE_RESULTS_URI); + Navigation.CloseSideMenus(); + }} + > + + + + @@ -332,21 +542,19 @@ export default definePlugin((serverApi: ServerAPI) => { let ico = ; let now = new Date(); if (now.getDate() == 1 && now.getMonth() == 3) { - ico = ; + ico = ; } + //registerCallbacks(false); + serverApi.routerHook.addRoute(STORE_RESULTS_URI, () => ); return { title:
PowerTools
, content: , icon: ico, onDismount() { + tryNotifyProfileChange = function() {}; backend.log(backend.LogLevel.Debug, "PowerTools shutting down"); - clearInterval(periodicHook!); - periodicHook = null; - lifetimeHook?.unregister(); - startHook?.unregister(); - endHook?.unregister(); - //serverApi.routerHook.removeRoute("/decky-plugin-test"); - backend.log(backend.LogLevel.Debug, "Unregistered PowerTools callbacks, so long and thanks for all the fish."); + clearHooks(); + serverApi.routerHook.removeRoute(STORE_RESULTS_URI); }, }; }); diff --git a/src/store/page.tsx b/src/store/page.tsx new file mode 100644 index 0000000..da8f36d --- /dev/null +++ b/src/store/page.tsx @@ -0,0 +1,173 @@ +import { Component } from "react"; +import { HiDownload } from "react-icons/hi"; + +import { PanelSectionRow, Focusable, staticClasses, DialogButton } from "decky-frontend-lib"; + +import * as backend from "../backend"; +import { tr } from "usdpl-front"; +import { get_value, set_value } from "usdpl-front"; + +import { + STORE_RESULTS, + VARIANTS_GEN, +} from "../consts"; + +export class StoreResultsPage extends Component<{onNewVariant: () => void}> { + constructor(props: {onNewVariant: () => void}) { + super(props); + this.state = { + reloadThingy: "/shrug", + }; + } + + render() { + const storeItems = get_value(STORE_RESULTS) as backend.StoreMetadata[] | undefined; + console.log("POWERTOOLS: Rendering store results", storeItems); + if (storeItems) { + if (storeItems.length == 0) { + backend.log(backend.LogLevel.Warn, "No store results; got array with length 0 from cache"); + return ( + { tr("No results") /* TODO translate */ } + ); + } else { + // TODO + return ( + { + storeItems.map((meta: backend.StoreMetadata) => ( + +
+
+ { meta.name } +
+
+ { tr("Created by") /* TODO translate */} { meta.steam_username } +
+
+ { meta.tags.map((tag: string) => ( + {tag} + ) + ) } +
+
+ { + backend.log(backend.LogLevel.Info, "Downloading settings " + meta.name + " (" + meta.id + ")"); + backend.resolve(backend.storeDownloadById(meta.id), + (variants: backend.VariantInfo[]) => { + set_value(VARIANTS_GEN, variants) + this.props.onNewVariant(); + } + ); + }} + > + { /* TODO make this responsive when clicked */} + + +
+
)) + } +
); + } + + } else { + backend.log(backend.LogLevel.Warn, "Store failed to load; got null from cache"); + // store did not pre-load when the game started + return ( + { tr("Store failed to load") /* TODO translate */ } + ); + } + } +} diff --git a/translations/es-ES.mo b/translations/es-ES.mo index d6ba246..19aaa3f 100644 Binary files a/translations/es-ES.mo and b/translations/es-ES.mo differ diff --git a/translations/fr-CA.mo b/translations/fr-CA.mo index f25dd78..b4ecd6e 100644 Binary files a/translations/fr-CA.mo and b/translations/fr-CA.mo differ diff --git a/translations/fr-CA.po b/translations/fr-CA.po index fbb09fb..3be3a46 100644 --- a/translations/fr-CA.po +++ b/translations/fr-CA.po @@ -49,6 +49,16 @@ msgstr "Réappliquer le profil" msgid "Defaults" msgstr "Valeurs par défaut" +#: index.tsx:413 +# (Settings selection dropdown) +msgid "Profile Variant" +msgstr "Variante de profil" + +#: index.tsx:464 +# (Alternate settings profile name) +msgid "Variant name" +msgstr "Nom de la variante" + # -- components/battery.tsx -- # (Battery section title) #: components/battery.tsx:42 diff --git a/translations/fr-FR.mo b/translations/fr-FR.mo index f25dd78..b4ecd6e 100644 Binary files a/translations/fr-FR.mo and b/translations/fr-FR.mo differ diff --git a/translations/pt.pot b/translations/pt.pot index 1c15573..d39e7b3 100644 --- a/translations/pt.pot +++ b/translations/pt.pot @@ -47,6 +47,16 @@ msgstr "" msgid "Defaults" msgstr "" +#: index.tsx:413 +# (Settings selection dropdown) +msgid "Profile Variant" +msgstr "" + +#: index.tsx:464 +# (Alternate settings profile name) +msgid "Variant name" +msgstr "" + # -- components/battery.tsx -- #: components/battery.tsx:42 diff --git a/translations/ru-RU.mo b/translations/ru-RU.mo index 30e36e2..038e12d 100644 Binary files a/translations/ru-RU.mo and b/translations/ru-RU.mo differ diff --git a/translations/uk-UA.mo b/translations/uk-UA.mo index cb725de..63c29b3 100644 Binary files a/translations/uk-UA.mo and b/translations/uk-UA.mo differ diff --git a/translations/zh-CN.mo b/translations/zh-CN.mo index 78bd0ad..334a3d5 100644 Binary files a/translations/zh-CN.mo and b/translations/zh-CN.mo differ diff --git a/translations/zh-HK.mo b/translations/zh-HK.mo index b31cfc2..f832bc6 100644 Binary files a/translations/zh-HK.mo and b/translations/zh-HK.mo differ