diff --git a/.gitignore b/.gitignore index ed38553..5a71389 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,8 @@ yalc.lock /backend/target /bin /backend/out +/**/target + +# packaging +/PowerTools +**.zip diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88237ef --- /dev/null +++ b/Makefile @@ -0,0 +1,87 @@ +# Configuration settings +PLUGIN_NAME ?= $(shell basename $(PWD)) +PLUGIN_VERSION ?= 0.3.0 + +# Source files +TS_FILES := $(shell find src -name *.ts) +TSX_FILES := $(shell find src -name *.tsx) +SRC_FILES := $(TS_FILES) $(TSX_FILES) plugin.json + +TAR_FILES := bin dist main.py package.json plugin.json + +# plugin dir +DATA_PATH ?= homebrew + +# SSH Configuration +SSH_USER ?= gamer +SSH_HOST ?= 192.168.0.246 +SSH_MOUNT_PATH ?= /tmp/remote +SSH_DATA_PATH ?= /home/$(SSH_USER)/$(DATA_PATH) + +# Default target is to build and restart crankshaft +.PHONY: default +default: build restart + +.PHONY: build +build: build ## Builds the project + cd backend && ./build.sh && cd .. + +dist: $(SRC_FILES) node_modules + npm run build + +.PHONY: watch +watch: ## Build and watch for source code changes + npm run build-watch + +package-lock.json: package.json + npm i + +node_modules: node_modules/installed ## Install dependencies +node_modules/installed: package-lock.json + npm ci + touch $@ + +.PHONY: restart +restart: ## Restart crankshaft + ssh $(SSH_USER)@$(SSH_HOST) sudo systemctl restart plugin_loader -S + +.PHONY: debug +debug: ## Show Makefile variables + @echo "Source Files: $(SRC_FILES)" + +.PHONY: cef-debug +cef-debug: ## Open Chrome CEF debugging. Add a network target: localhost:8080 + chromium "chrome://inspect/#devices" + +.PHONY: tunnel +tunnel: ## Create an SSH tunnel to remote Steam Client (accessible on localhost:4040) + ssh $(SSH_USER)@$(SSH_HOST) -N -f -L 4040:localhost:8080 + +$(SSH_MOUNT_PATH)/.mounted: + mkdir -p $(SSH_MOUNT_PATH) + sshfs -o default_permissions $(SSH_USER)@$(SSH_HOST):$(SSH_DATA_PATH) $(SSH_MOUNT_PATH) + touch $(SSH_MOUNT_PATH)/.mounted + $(MAKE) tunnel + +# Cleans and transfers the project +$(SSH_MOUNT_PATH)/plugins/$(PLUGIN_NAME): $(SRC_FILES) + rsync -avh $(PWD)/ $(SSH_MOUNT_PATH)/plugins/$(PLUGIN_NAME) --delete + +.PHONY: remote-restart +remote-restart: ## Restart remote crankshaft + ssh $(SSH_USER)@$(SSH_HOST) sudo systemctl restart plugin_loader + +.PHONY: mount +mount: $(SSH_MOUNT_PATH)/.mounted + +.PHONY: remote-update +remote-update: $(SSH_MOUNT_PATH)/plugins/$(PLUGIN_NAME) + +.PHONY: clean +clean: ## Clean all build artifacts + rm -rf build dist bin + +.PHONY: help +help: ## Show this help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 7aa76c0..541b5f3 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aead" version = "0.4.3" @@ -38,6 +44,52 @@ dependencies = [ "zeroize", ] +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +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 = "async-recursion" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -46,9 +98,31 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] [[package]] name = "bitflags" @@ -58,20 +132,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] [[package]] -name = "block-buffer" -version = "0.10.2" +name = "brotli-decompressor" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" dependencies = [ - "generic-array", + "alloc-no-stdlib", + "alloc-stdlib", ] [[package]] @@ -92,9 +167,18 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -112,14 +196,34 @@ dependencies = [ ] [[package]] -name = "cpufeatures" -version = "0.2.2" +name = "clang-sys" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 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" @@ -141,21 +245,91 @@ dependencies = [ [[package]] name = "digest" -version = "0.9.0" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "generic-array", + "block-buffer", + "crypto-common", ] [[package]] -name = "digest" -version = "0.10.3" +name = "either" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" dependencies = [ - "block-buffer 0.10.2", - "crypto-common", + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", ] [[package]] @@ -167,6 +341,16 @@ dependencies = [ "instant", ] +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -175,19 +359,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -195,27 +378,27 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-core", "futures-sink", @@ -227,9 +410,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -237,9 +420,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -247,10 +430,26 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.13" +name = "gettext-ng" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "c2c86be871deb255ef65fc8395048a2505912c595f1eddc4da03aeb6fda5cf34" +dependencies = [ + "byteorder", + "encoding", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -261,7 +460,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util", "tracing", ] @@ -273,9 +472,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "headers" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64", "bitflags", @@ -284,7 +483,7 @@ dependencies = [ "http", "httpdate", "mime", - "sha-1 0.10.0", + "sha1", ] [[package]] @@ -298,9 +497,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] @@ -335,9 +534,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -347,9 +546,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -371,20 +570,19 @@ dependencies = [ [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -401,15 +599,45 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "limits_core" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] [[package]] name = "log" @@ -420,12 +648,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.5.0" @@ -449,10 +671,25 @@ dependencies = [ ] [[package]] -name = "mio" -version = "0.8.4" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", @@ -479,10 +716,20 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.13.1" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi", "libc", @@ -505,9 +752,9 @@ checksum = "7b2b2cbbfd8defa51ff24450a61d73b3ff3e158484ddd274a883e886e6fbaa78" [[package]] name = "once_cell" -version = "1.13.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "opaque-debug" @@ -516,25 +763,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "percent-encoding" -version = "2.1.0" +name = "peeking_take_while" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -567,26 +820,32 @@ dependencies = [ [[package]] name = "powertools-rs" -version = "1.0.5+1" +version = "1.1.0" dependencies = [ + "async-trait", + "limits_core", "log", + "regex", + "ryzenadj-rs", "serde", "serde_json", "simplelog", + "tokio", + "ureq", "usdpl-back", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.42" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] @@ -599,9 +858,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -629,9 +888,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -645,6 +904,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -655,10 +931,34 @@ dependencies = [ ] [[package]] -name = "ryu" -version = "1.0.10" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "ryzenadj-rs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc77f2824d9c46759ba4907c8d3c3ca7131593a41fc389335bcdfda339a866bd" +dependencies = [ + "bindgen", +] [[package]] name = "safemem" @@ -668,24 +968,24 @@ checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "serde" -version = "1.0.140" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.140" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -694,9 +994,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -717,28 +1017,32 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.8" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ - "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest", ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "simplelog" version = "0.12.0" @@ -761,9 +1065,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -777,9 +1081,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -811,18 +1115,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -831,21 +1135,32 @@ dependencies = [ [[package]] name = "time" -version = "0.3.11" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", "libc", "num_threads", + "serde", + "time-core", "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.4" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] [[package]] name = "tinyvec" @@ -858,15 +1173,15 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.20.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes", @@ -874,17 +1189,16 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", "socket2", - "winapi", + "windows-sys", ] [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", @@ -893,36 +1207,21 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.15.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" dependencies = [ "futures-util", "log", - "pin-project", "tokio", "tungstenite", ] [[package]] name = "tokio-util" -version = "0.6.10" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -940,9 +1239,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -952,24 +1251,24 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", ] [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.14.0" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ "base64", "byteorder", @@ -978,7 +1277,7 @@ dependencies = [ "httparse", "log", "rand", - "sha-1 0.9.8", + "sha-1", "thiserror", "url", "utf-8", @@ -995,9 +1294,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicase" @@ -1010,21 +1309,21 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.2" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -1040,25 +1339,45 @@ dependencies = [ ] [[package]] -name = "url" -version = "2.2.2" +name = "ureq" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" +dependencies = [ + "base64", + "brotli-decompressor", + "encoding_rs", + "flate2", + "log", + "once_cell", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] [[package]] name = "usdpl-back" -version = "0.6.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbbc0781e83ba990f8239142e33173a2d2548701775f3db66702d1af4fd0319a" +checksum = "e2938cb40ba84ebea44658ebb1e4e0045fca54a562873bacab2ae094abab61ff" dependencies = [ + "async-recursion", + "async-trait", "bytes", + "gettext-ng", "hex", + "log", "obfstr", "tokio", "usdpl-core", @@ -1067,9 +1386,9 @@ dependencies = [ [[package]] name = "usdpl-core" -version = "0.6.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862153581fac266458521f49e5906a71c1eee1665cb4c7d71e9586bd34b45394" +checksum = "f3904ca38aca189c68a6bc876cf73de7cc60003476b4e118012ae7eb783c1700" dependencies = [ "aes-gcm-siv", "base64", @@ -1099,9 +1418,9 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" dependencies = [ "bytes", "futures-channel", @@ -1115,6 +1434,7 @@ dependencies = [ "multipart", "percent-encoding", "pin-project", + "rustls-pemfile", "scoped-tls", "serde", "serde_json", @@ -1122,7 +1442,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.6.10", + "tokio-util", "tower-service", "tracing", ] @@ -1133,6 +1453,17 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1166,46 +1497,60 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" +name = "windows_aarch64_gnullvm" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "zeroize" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 7ea2bb0..eadfe67 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,27 +1,55 @@ [package] name = "powertools-rs" -version = "1.0.5+1" +version = "1.1.0" edition = "2021" +authors = ["NGnius (Graham) "] +description = "Backend (superuser) functionality for PowerTools" +license = "GPL-3.0-only" +repository = "https://github.com/NGnius/PowerTools" +keywords = ["utility", "power-management", "root", "decky"] +readme = "../README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -usdpl-back = { version = "0.6.0", features = ["blocking"]} +usdpl-back = { version = "0.9.1", features = ["blocking"] }#, path = "../../usdpl-rs/usdpl-back"} serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +# async +tokio = { version = "*", features = ["time"] } +async-trait = { version = "0.1" } + # logging log = "0.4" simplelog = "0.12" +# limits & driver functionality +limits_core = { version = "0.1.0", path = "./limits_core" } +regex = "1" +ryzenadj-rs = { version = "0.1" } +# ureq's tls feature does not like musl targets +ureq = { version = "2.5", features = ["json", "gzip", "brotli", "charset"], default-features = false, optional = true } + [features] -default = [] +default = ["online"] decky = ["usdpl-back/decky"] crankshaft = ["usdpl-back/crankshaft"] encrypt = ["usdpl-back/encrypt"] +online = ["ureq"] [profile.release] debug = false strip = true lto = true -codegen-units = 4 +codegen-units = 1 + +[profile.docker] +inherits = "release" +debug = false +strip = true +lto = "thin" +codegen-units = 16 +opt-level = 2 +debug-assertions = false +overflow-checks = false diff --git a/backend/Dockerfile b/backend/Dockerfile index f1a0ed3..94a2a88 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,3 +1,5 @@ FROM ghcr.io/steamdeckhomebrew/holo-toolchain-rust:latest -ENTRYPOINT [ "/backend/entrypoint.sh" ] \ No newline at end of file +RUN pacman -S --noconfirm cmake make + +ENTRYPOINT [ "/backend/entrypoint.sh" ] diff --git a/backend/build-docker.sh b/backend/build-docker.sh index c6074a9..58ae2f9 100755 --- a/backend/build-docker.sh +++ b/backend/build-docker.sh @@ -6,7 +6,7 @@ rustc --version cargo --version echo "--- Building plugin backend ---" -cargo build --release +cargo build --profile docker mkdir -p out cp target/release/powertools-rs out/backend diff --git a/backend/limits_core/Cargo.lock b/backend/limits_core/Cargo.lock new file mode 100644 index 0000000..9eb5c72 --- /dev/null +++ b/backend/limits_core/Cargo.lock @@ -0,0 +1,89 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "limits_core" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" diff --git a/backend/limits_core/Cargo.toml b/backend/limits_core/Cargo.toml new file mode 100644 index 0000000..58ceabd --- /dev/null +++ b/backend/limits_core/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "limits_core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/backend/limits_core/src/json/base.rs b/backend/limits_core/src/json/base.rs new file mode 100644 index 0000000..6e77b71 --- /dev/null +++ b/backend/limits_core/src/json/base.rs @@ -0,0 +1,66 @@ +use std::default::Default; +use serde::{Deserialize, Serialize}; + +/// Base JSON limits information +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Base { + /// System-specific configurations + pub configs: Vec, + /// URL from which to grab the next update + pub refresh: Option, +} + +impl Default for Base { + fn default() -> Self { + Base { + configs: vec![ + super::Config { + name: "Steam Deck Custom".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()), + os: None, + command: None, + file_exists: Some("./pt_oc.json".into()), + }, + limits: vec![ + super::Limits::Cpu(super::CpuLimit::SteamDeckAdvance), + super::Limits::Gpu(super::GpuLimit::SteamDeckAdvance), + super::Limits::Battery(super::BatteryLimit::SteamDeckAdvance), + ] + }, + super::Config { + name: "Steam Deck".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()), + os: None, + command: None, + file_exists: None, + }, + limits: vec![ + super::Limits::Cpu(super::CpuLimit::SteamDeck), + super::Limits::Gpu(super::GpuLimit::SteamDeck), + super::Limits::Battery(super::BatteryLimit::SteamDeck), + ] + }, + super::Config { + name: "Fallback".to_owned(), + conditions: super::Conditions { + dmi: None, + cpuinfo: None, + os: None, + command: None, + file_exists: None, + }, + limits: vec![ + super::Limits::Cpu(super::CpuLimit::Unknown), + super::Limits::Gpu(super::GpuLimit::Unknown), + super::Limits::Battery(super::BatteryLimit::Unknown), + ] + } + ], + refresh: Some("http://limits.ngni.us:45000/powertools/v1".to_owned()) + } + } +} diff --git a/backend/limits_core/src/json/battery_limit.rs b/backend/limits_core/src/json/battery_limit.rs new file mode 100644 index 0000000..ca44689 --- /dev/null +++ b/backend/limits_core/src/json/battery_limit.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "target")] +pub enum BatteryLimit { + SteamDeck, + SteamDeckAdvance, + Generic(GenericBatteryLimit), + Unknown, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GenericBatteryLimit { + /* TODO */ +} diff --git a/backend/limits_core/src/json/conditions.rs b/backend/limits_core/src/json/conditions.rs new file mode 100644 index 0000000..be08a1c --- /dev/null +++ b/backend/limits_core/src/json/conditions.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +/// Conditions under which a config applies (ANDed together) +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Conditions { + /// Regex pattern for dmidecode output + pub dmi: Option, + /// Regex pattern for /proc/cpuinfo reading + pub cpuinfo: Option, + /// Regex pattern for /etc/os-release reading + pub os: Option, + /// Custom command to run, where an exit code of 0 means a successful match + pub command: 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.command.is_none() + && self.file_exists.is_none() + } +} diff --git a/backend/limits_core/src/json/config.rs b/backend/limits_core/src/json/config.rs new file mode 100644 index 0000000..57d70ac --- /dev/null +++ b/backend/limits_core/src/json/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: Vec, +} diff --git a/backend/limits_core/src/json/cpu_limit.rs b/backend/limits_core/src/json/cpu_limit.rs new file mode 100644 index 0000000..053ef29 --- /dev/null +++ b/backend/limits_core/src/json/cpu_limit.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +use super::RangeLimit; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "target")] +pub enum CpuLimit { + SteamDeck, + SteamDeckAdvance, + Generic(GenericCpuLimit), + GenericAMD(GenericCpuLimit), + Unknown, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GenericCpuLimit { + pub clock_min: Option>, + pub clock_max: Option>, + pub clock_step: u64, +} diff --git a/backend/limits_core/src/json/gpu_limit.rs b/backend/limits_core/src/json/gpu_limit.rs new file mode 100644 index 0000000..be77676 --- /dev/null +++ b/backend/limits_core/src/json/gpu_limit.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; +use super::RangeLimit; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "target")] +pub enum GpuLimit { + SteamDeck, + SteamDeckAdvance, + Generic(GenericGpuLimit), + GenericAMD(GenericGpuLimit), + Unknown, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GenericGpuLimit { + pub fast_ppt: Option>, + pub slow_ppt: Option>, + pub ppt_step: Option, + pub tdp: Option>, + pub tdp_boost: Option>, + pub tdp_step: Option, + pub clock_min: Option>, + pub clock_max: Option>, + pub clock_step: Option, +} diff --git a/backend/limits_core/src/json/limits.rs b/backend/limits_core/src/json/limits.rs new file mode 100644 index 0000000..10abd4e --- /dev/null +++ b/backend/limits_core/src/json/limits.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "limits")] +pub enum Limits { + Cpu(super::CpuLimit), + Gpu(super::GpuLimit), + Battery(super::BatteryLimit), +} diff --git a/backend/limits_core/src/json/mod.rs b/backend/limits_core/src/json/mod.rs new file mode 100644 index 0000000..73047bc --- /dev/null +++ b/backend/limits_core/src/json/mod.rs @@ -0,0 +1,19 @@ +mod base; +mod battery_limit; +mod conditions; +mod config; +mod cpu_limit; +mod gpu_limit; +mod limits; +mod range; +mod target; + +pub use base::Base; +pub use battery_limit::{BatteryLimit, GenericBatteryLimit}; +pub use conditions::Conditions; +pub use cpu_limit::{CpuLimit, GenericCpuLimit}; +pub use gpu_limit::{GpuLimit, GenericGpuLimit}; +pub use config::Config; +pub use limits::Limits; +pub use range::RangeLimit; +pub use target::Target; diff --git a/backend/limits_core/src/json/range.rs b/backend/limits_core/src/json/range.rs new file mode 100644 index 0000000..2ea3718 --- /dev/null +++ b/backend/limits_core/src/json/range.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +/// Base JSON limits information +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RangeLimit { + pub min: T, + pub max: T, +} diff --git a/backend/limits_core/src/json/target.rs b/backend/limits_core/src/json/target.rs new file mode 100644 index 0000000..768df6f --- /dev/null +++ b/backend/limits_core/src/json/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 new file mode 100644 index 0000000..22fdbb3 --- /dev/null +++ b/backend/limits_core/src/lib.rs @@ -0,0 +1 @@ +pub mod json; diff --git a/backend/limits_srv/Cargo.lock b/backend/limits_srv/Cargo.lock new file mode 100644 index 0000000..0bb3b66 --- /dev/null +++ b/backend/limits_srv/Cargo.lock @@ -0,0 +1,1026 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[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 = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "limits_core" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "limits_srv" +version = "0.1.0" +dependencies = [ + "limits_core", + "serde_json", + "tokio", + "warp", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand", + "safemem", + "tempfile", + "twoway", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[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.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +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.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "serde" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +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 = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[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.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multipart", + "percent-encoding", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + +[[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-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.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/backend/limits_srv/Cargo.toml b/backend/limits_srv/Cargo.toml new file mode 100644 index 0000000..48989d9 --- /dev/null +++ b/backend/limits_srv/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "limits_srv" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +limits_core = { version = "0.1.0", path = "../limits_core" } +serde_json = "1.0" +warp = { version = "0.3" } +tokio = { version = "1.22", features = ["macros", "rt", "rt-multi-thread"] } diff --git a/backend/limits_srv/pt_limits.json b/backend/limits_srv/pt_limits.json new file mode 100644 index 0000000..7a4143a --- /dev/null +++ b/backend/limits_srv/pt_limits.json @@ -0,0 +1,64 @@ +{ + "configs": [ + { + "name": "Steam Deck Custom", + "conditions": { + "cpuinfo": "model name\t: AMD Custom APU 0405\n", + "file_exists": "./pt_oc.json" + }, + "limits": [ + { + "limits": "Cpu", + "target": "SteamDeck" + }, + { + "limits": "Gpu", + "target": "SteamDeck" + }, + { + "limits": "Battery", + "target": "SteamDeck" + } + ] + }, + { + "name": "Steam Deck", + "conditions": { + "cpuinfo": "model name\t: AMD Custom APU 0405\n" + }, + "limits": [ + { + "limits": "Cpu", + "target": "SteamDeck" + }, + { + "limits": "Gpu", + "target": "SteamDeck" + }, + { + "limits": "Battery", + "target": "SteamDeck" + } + ] + }, + { + "name": "Fallback", + "conditions": {}, + "limits": [ + { + "limits": "Cpu", + "target": "Unknown" + }, + { + "limits": "Gpu", + "target": "Unknown" + }, + { + "limits": "Battery", + "target": "Unknown" + } + ] + } + ], + "refresh": "http://limits.ngni.us:45000/powertools/v1" +} diff --git a/backend/limits_srv/src/main.rs b/backend/limits_srv/src/main.rs new file mode 100644 index 0000000..d0054ce --- /dev/null +++ b/backend/limits_srv/src/main.rs @@ -0,0 +1,53 @@ +use std::sync::atomic::{Ordering, AtomicU64}; +use std::sync::{RwLock, Arc}; + +use warp::Filter; + +use limits_core::json::Base; + +static VISIT_COUNT: AtomicU64 = AtomicU64::new(0); + +fn get_limits(base: Base) -> impl warp::Reply { + VISIT_COUNT.fetch_add(1, Ordering::AcqRel); + println!("Limits got"); + warp::reply::json(&base) +} + +fn get_visits() -> impl warp::Reply { + let count = VISIT_COUNT.load(Ordering::Relaxed); + println!("Count got"); + warp::reply::json(&count) +} + +fn routes(base: Arc>) -> 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) + }) + .or( + warp::path!("powertools" / "count") + .map(get_visits) + ) + ).recover(recovery) +} + +pub async fn recovery(reject: warp::Rejection) -> Result { + if reject.is_not_found() { + Ok(warp::hyper::StatusCode::NOT_FOUND) + } else { + Err(reject) + } +} + +#[tokio::main] +async fn main() { + let file = std::fs::File::open("./pt_limits.json").expect("Failed to read limits file"); + let limits: Base = serde_json::from_reader(file).expect("Failed to parse limits file"); + assert!(limits.refresh.is_some(), "`refresh` cannot be null, since it will brick future refreshes"); + + warp::serve(routes(Arc::new(RwLock::new(limits)))) + .run(([0, 0, 0, 0], 8080)) + .await; +} diff --git a/backend/src/api/api_types.rs b/backend/src/api/api_types.rs new file mode 100644 index 0000000..2107852 --- /dev/null +++ b/backend/src/api/api_types.rs @@ -0,0 +1,63 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct RangeLimit { + pub min: T, + pub max: T, +} + +impl From> for RangeLimit { + #[inline] + fn from(other: limits_core::json::RangeLimit) -> Self { + RangeLimit { min: other.min, max: other.max } + } +} + +#[derive(Serialize, Deserialize)] +pub struct SettingsLimits { + pub battery: BatteryLimits, + pub cpu: CpusLimits, + pub gpu: GpuLimits, + pub general: GeneralLimits, +} + +#[derive(Serialize, Deserialize)] +pub struct BatteryLimits { + pub charge_current: Option>, + pub charge_current_step: u64, + pub charge_modes: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct CpusLimits { + pub cpus: Vec, + pub count: usize, + pub smt_capable: bool, + pub governors: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct CpuLimits { + pub clock_min_limits: Option>, + pub clock_max_limits: Option>, + pub clock_step: u64, + pub governors: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct GeneralLimits { +} + +#[derive(Serialize, Deserialize)] +pub struct GpuLimits { + pub fast_ppt_limits: Option>, + pub slow_ppt_limits: Option>, + pub ppt_step: u64, + pub tdp_limits: Option>, + pub tdp_boost_limits: Option>, + pub tdp_step: u64, + pub clock_min_limits: Option>, + pub clock_max_limits: Option>, + pub clock_step: u64, + pub memory_control_capable: bool, +} diff --git a/backend/src/api/async_utils.rs b/backend/src/api/async_utils.rs new file mode 100644 index 0000000..7afdaa6 --- /dev/null +++ b/backend/src/api/async_utils.rs @@ -0,0 +1,70 @@ +//use usdpl_back::core::serdes::Primitive; +use usdpl_back::AsyncCallable; + +pub struct AsyncIsh Result) + Send + Sync, + Gen: (Fn() -> SG) + Send + Sync, + SG: (Fn(In) -> Out) + Send + Sync + 'static, + TG: (Fn(Out) -> super::ApiParameterType) + Send + Sync> { + pub trans_setter: TS, // assumed to be pretty fast + pub set_get: Gen, // probably has locks (i.e. slow) + pub trans_getter: TG, // assumed to be pretty fast +} + +#[async_trait::async_trait] +impl Result) + Send + Sync, + Gen: (Fn() -> SG) + Send + Sync, + SG: (Fn(In) -> Out) + Send + Sync + 'static, + TG: (Fn(Out) -> super::ApiParameterType) + Send + Sync> + AsyncCallable for AsyncIsh { + async fn call(&self, params: super::ApiParameterType) -> super::ApiParameterType { + let t_to_set = match (self.trans_setter)(params) { + Ok(t) => t, + Err(e) => return vec![e.into()] + }; + let setter = (self.set_get)(); + let t_got = match tokio::task::spawn_blocking(move || setter(t_to_set)).await { + Ok(t) => t, + Err(e) => return vec![e.to_string().into()], + }; + (self.trans_getter)(t_got) + } +} + +pub struct AsyncIshGetter G) + Send + Sync, + G: (Fn() -> T) + Send + Sync + 'static, + TG: (Fn(T) -> super::ApiParameterType) + Send + Sync> { + pub set_get: Gen, // probably has locks (i.e. slow) + pub trans_getter: TG, // assumed to be pretty fast +} + +#[async_trait::async_trait] +impl G) + Send + Sync, + G: (Fn() -> T) + Send + Sync + 'static, + TG: (Fn(T) -> super::ApiParameterType) + Send + Sync> + AsyncCallable for AsyncIshGetter { + async fn call(&self, _params: super::ApiParameterType) -> super::ApiParameterType { + let getter = (self.set_get)(); + let t_got = match tokio::task::spawn_blocking(move || getter()).await { + Ok(t) => t, + Err(e) => return vec![e.to_string().into()], + }; + (self.trans_getter)(t_got) + } +} + +pub struct Blocking super::ApiParameterType) + Send + Sync> { + pub func: F, +} + +#[async_trait::async_trait] +impl super::ApiParameterType) + Send + Sync> AsyncCallable for Blocking { + async fn call(&self, params: super::ApiParameterType) -> super::ApiParameterType { + (self.func)(params) + } +} diff --git a/backend/src/api/battery.rs b/backend/src/api/battery.rs index 6140902..e7c4638 100644 --- a/backend/src/api/battery.rs +++ b/backend/src/api/battery.rs @@ -1,47 +1,117 @@ -use std::sync::{mpsc::Sender, Arc, Mutex}; +use std::sync::mpsc::{Sender, self}; +use std::sync::{Arc, Mutex}; use usdpl_back::core::serdes::Primitive; +use usdpl_back::AsyncCallable; -use crate::settings::{Battery, OnSet}; -use crate::utility::{unwrap_lock, unwrap_maybe_fatal}; +use super::handler::{ApiMessage, BatteryMessage}; /// Current current (ha!) web method -pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType { - super::utility::map_result(crate::settings::Battery::read_current_now()) +pub fn current_now( + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |val: Option| tx.send(val).expect("current_now callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadCurrentNow(Box::new(callback)))).expect("current_now send failed"); + rx.recv().expect("current_now callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + super::utility::map_optional_result(Ok(result)) + } + } } +/// Current current (ha!) web method +/*pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType { + super::utility::map_optional_result(crate::settings::driver::read_current_now()) +}*/ + /// Charge now web method -pub fn charge_now(_: super::ApiParameterType) -> super::ApiParameterType { - super::utility::map_result(crate::settings::Battery::read_charge_now()) +pub fn charge_now( + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |val: Option| tx.send(val).expect("charge_now callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadChargeNow(Box::new(callback)))).expect("charge_now send failed"); + rx.recv().expect("charge_now callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + super::utility::map_optional_result(Ok(result)) + } + } } /// Charge full web method -pub fn charge_full(_: super::ApiParameterType) -> super::ApiParameterType { - super::utility::map_result(crate::settings::Battery::read_charge_full()) +pub fn charge_full( + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |val: Option| tx.send(val).expect("charge_full callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadChargeFull(Box::new(callback)))).expect("charge_full send failed"); + rx.recv().expect("charge_full callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + super::utility::map_optional_result(Ok(result)) + } + } } /// Charge design web method -pub fn charge_design(_: super::ApiParameterType) -> super::ApiParameterType { - super::utility::map_result(crate::settings::Battery::read_charge_design()) +pub fn charge_design( + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |val: Option| tx.send(val).expect("charge_design callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadChargeDesign(Box::new(callback)))).expect("charge_design send failed"); + rx.recv().expect("charge_design callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + super::utility::map_optional_result(Ok(result)) + } + } } /// Generate set battery charge rate web method pub fn set_charge_rate( - settings: Arc>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |rate: f64| + sender.lock() + .unwrap() + .send(ApiMessage::Battery(BatteryMessage::SetChargeRate(Some(rate as u64)))) + .expect("set_charge_rate send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::F64(new_val)) = params_in.get(0) { - let mut settings_lock = unwrap_lock(settings.lock(), "battery"); - settings_lock.charge_rate = Some(*new_val as _); - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - super::utility::map_empty_result( - settings_lock.on_set(), - settings_lock.charge_rate.unwrap(), - ) + if let Some(&Primitive::F64(new_val)) = params_in.get(0) { + setter(new_val); + vec![(new_val).into()] } else { vec!["set_charge_rate missing parameter".into()] } @@ -50,30 +120,76 @@ pub fn set_charge_rate( /// Generate get battery charge rate web method pub fn get_charge_rate( - settings: Arc>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let getter = move || { + let (tx, rx) = mpsc::channel(); + let callback = move |rate: Option| tx.send(rate).expect("get_charge_rate callback send failed"); + sender.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::GetChargeRate(Box::new(callback)))).expect("get_charge_rate send failed"); + rx.recv().expect("get_charge_rate callback recv failed") + }; move |_: super::ApiParameterType| { - let settings_lock = unwrap_lock(settings.lock(), "battery"); - vec![settings_lock - .charge_rate - .map(|x| x.into()) - .unwrap_or(Primitive::Empty)] + vec![getter().map(|x| x.into()).unwrap_or(Primitive::Empty)] } } /// Generate unset battery charge rate web method pub fn unset_charge_rate( - settings: Arc>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety - move |_: super::ApiParameterType| { - let mut settings_lock = unwrap_lock(settings.lock(), "battery"); - settings_lock.charge_rate = None; - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - super::utility::map_empty_result(settings_lock.on_set(), true) + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move || sender.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::SetChargeRate(None))).expect("unset_charge_rate send failed"); + move |_params_in: super::ApiParameterType| { + setter(); + vec![true.into()] + } +} + +/// Generate set battery charge mode web method +pub fn set_charge_mode( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |mode: String| + sender.lock() + .unwrap() + .send(ApiMessage::Battery(BatteryMessage::SetChargeMode(Some(mode)))) + .expect("set_charge_mode send failed"); + move |params_in: super::ApiParameterType| { + if let Some(Primitive::String(new_val)) = params_in.get(0) { + setter(new_val.to_owned()); + vec![new_val.to_owned().into()] + } else { + vec!["set_charge_rate missing parameter".into()] + } + } +} + +/// Generate get battery charge mode web method +pub fn get_charge_mode( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let getter = move || { + let (tx, rx) = mpsc::channel(); + let callback = move |mode: Option| tx.send(mode).expect("get_charge_mode callback send failed"); + sender.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::GetChargeMode(Box::new(callback)))).expect("get_charge_mode send failed"); + rx.recv().expect("get_charge_mode callback recv failed") + }; + move |_: super::ApiParameterType| { + vec![getter().map(|x| x.into()).unwrap_or(Primitive::Empty)] + } +} + +/// Generate unset battery charge mode web method +pub fn unset_charge_mode( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move || sender.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::SetChargeMode(None))).expect("unset_charge_mode send failed"); + move |_params_in: super::ApiParameterType| { + setter(); + vec![true.into()] } } diff --git a/backend/src/api/cpu.rs b/backend/src/api/cpu.rs index 47a9243..e1f8aa6 100644 --- a/backend/src/api/cpu.rs +++ b/backend/src/api/cpu.rs @@ -1,13 +1,16 @@ -use std::sync::{mpsc::Sender, Arc, Mutex}; +use std::sync::mpsc::{Sender, self}; +use std::sync::{Arc, Mutex}; use usdpl_back::core::serdes::Primitive; +use usdpl_back::AsyncCallable; -use crate::settings::{Cpu, OnSet, SettingError, SettingVariant, MinMax}; -use crate::utility::{unwrap_lock, unwrap_maybe_fatal}; +use crate::settings::{SettingError, SettingVariant, MinMax}; +//use crate::utility::{unwrap_lock, unwrap_maybe_fatal}; +use super::handler::{ApiMessage, CpuMessage}; /// Available CPUs web method pub fn max_cpus(_: super::ApiParameterType) -> super::ApiParameterType { super::utility::map_result( - Cpu::cpu_count() + crate::settings::steam_deck::Cpus::cpu_count() .map(|x| x as u64) .ok_or_else( || SettingError { @@ -20,29 +23,21 @@ pub fn max_cpus(_: super::ApiParameterType) -> super::ApiParameterType { /// Generate set CPU online web method pub fn set_cpu_online( - settings: Arc>>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |index: usize, value: bool| + sender.lock() + .unwrap() + .send(ApiMessage::Cpu(CpuMessage::SetCpuOnline(index, value))).expect("set_cpu_online send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::F64(index)) = params_in.get(0) { - let mut settings_lock = unwrap_lock(settings.lock(), "cpu"); - if let Some(cpu) = settings_lock.get_mut(*index as usize) { - if let Some(Primitive::Bool(online)) = params_in.get(1) { - cpu.online = *online; - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - super::utility::map_empty_result( - cpu.on_set(), - cpu.online, - ) - } else { - vec!["set_cpu_online missing parameter 1".into()] - } + if let Some(&Primitive::F64(index)) = params_in.get(0) { + //let mut settings_lock = unwrap_lock(settings.lock(), "cpu"); + if let Some(&Primitive::Bool(online)) = params_in.get(1) { + setter(index as usize, online); + vec![online.into()] } else { - vec!["set_cpu_online cpu index out of bounds".into()] + vec!["set_cpu_online missing parameter 1".into()] } } else { vec!["set_cpu_online missing parameter 0".into()] @@ -51,81 +46,157 @@ pub fn set_cpu_online( } pub fn set_cpus_online( - settings: Arc>>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |values: Vec| + sender.lock() + .unwrap() + .send(ApiMessage::Cpu(CpuMessage::SetCpusOnline(values))).expect("set_cpus_online send failed"); move |params_in: super::ApiParameterType| { let mut result = Vec::with_capacity(params_in.len()); - let mut settings_lock = unwrap_lock(settings.lock(), "cpu"); + let mut values = Vec::with_capacity(params_in.len()); for i in 0..params_in.len() { if let Primitive::Bool(online) = params_in[i] { - if let Some(cpu) = settings_lock.get_mut(i) { - cpu.online = online; - match cpu.on_set() { - Ok(_) => result.push(cpu.online.into()), - Err(e) => result.push(e.msg.into()) - } - } + values.push(online); + result.push(online.into()); } else { + values.push(true); result.push(format!("Invalid parameter {}", i).into()) } } - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); + setter(values); result } } -pub fn get_cpus_online( - settings: Arc>>, +/*pub fn get_cpus_online( + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let getter = move || { + let (tx, rx) = mpsc::channel(); + let callback = move |values: Vec| tx.send(values).expect("get_cpus_online callback send failed"); + sender.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetCpusOnline(Box::new(callback)))).expect("get_cpus_online send failed"); + rx.recv().expect("get_cpus_online callback recv failed") + }; move |_: super::ApiParameterType| { - let settings_lock = unwrap_lock(settings.lock(), "cpu"); - let mut output = Vec::with_capacity(settings_lock.len()); - for cpu in settings_lock.as_slice() { - output.push(cpu.online.into()); + let result = getter(); + let mut output = Vec::with_capacity(result.len()); + for &status in result.as_slice() { + output.push(status.into()); } output } +}*/ + +pub fn set_smt( + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move |smt: bool| { + let (tx, rx) = mpsc::channel(); + let callback = move |values: Vec| tx.send(values).expect("set_smt callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::SetSmt(smt, Box::new(callback)))).expect("set_smt send failed"); + rx.recv().expect("set_smt callback recv failed") + } + }; + super::async_utils::AsyncIsh { + trans_setter: |params| { + if let Some(&Primitive::Bool(smt_value)) = params.get(0) { + Ok(smt_value) + } else { + Err("set_smt missing/invalid parameter 0".to_owned()) + } + }, + set_get: getter, + trans_getter: |result| { + let mut output = Vec::with_capacity(result.len()); + for &status in result.as_slice() { + output.push(status.into()); + } + output + } + } +} + +pub fn get_smt( + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |value: bool| tx.send(value).expect("get_smt callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetSmt(Box::new(callback)))).expect("get_smt send failed"); + rx.recv().expect("get_smt callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + vec![result.into()] + } + } +} + +pub fn get_cpus_online( + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |values: Vec| tx.send(values).expect("get_cpus_online callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetCpusOnline(Box::new(callback)))).expect("get_cpus_online send failed"); + rx.recv().expect("get_cpus_online callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + let mut output = Vec::with_capacity(result.len()); + for &status in result.as_slice() { + output.push(status.into()); + } + output + } + } } pub fn set_clock_limits( - settings: Arc>>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |index: usize, value: MinMax| + sender.lock() + .unwrap() + .send(ApiMessage::Cpu(CpuMessage::SetClockLimits(index, Some(value)))).expect("set_clock_limits send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::F64(index)) = params_in.get(0) { - let mut settings_lock = unwrap_lock(settings.lock(), "cpu"); - if let Some(cpu) = settings_lock.get_mut(*index as usize) { - if let Some(Primitive::F64(min)) = params_in.get(1) { - if let Some(Primitive::F64(max)) = params_in.get(2) { - cpu.clock_limits = Some(MinMax { - min: *min as _, - max: *max as _, - }); - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - match cpu.on_set() { - Ok(_) => vec![ - cpu.clock_limits.as_ref().unwrap().min.into(), - cpu.clock_limits.as_ref().unwrap().max.into(), - ], - Err(e) => vec![e.msg.into()] - } + if let Some(&Primitive::F64(index)) = params_in.get(0) { + if let Some(&Primitive::F64(min)) = params_in.get(1) { + if let Some(&Primitive::F64(max)) = params_in.get(2) { + let safe_max = if max < min { + min } else { - vec!["set_clock_limits missing parameter 2".into()] - } + max + }; + let safe_min = if min > max { + max + } else { + min + }; + setter(index as usize, MinMax {min: safe_min as u64, max: safe_max as u64}); + vec![safe_min.into(), safe_max.into()] } else { - vec!["set_clock_limits missing parameter 1".into()] + vec!["set_clock_limits missing parameter 2".into()] } } else { - vec!["set_clock_limits cpu index out of bounds".into()] + vec!["set_clock_limits missing parameter 1".into()] } } else { vec!["set_clock_limits missing parameter 0".into()] @@ -134,19 +205,21 @@ pub fn set_clock_limits( } pub fn get_clock_limits( - settings: Arc>>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let getter = move |index: usize| { + let (tx, rx) = mpsc::channel(); + let callback = move |values: Option>| tx.send(values).expect("get_clock_limits callback send failed"); + sender.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetClockLimits(index, Box::new(callback)))).expect("get_clock_limits send failed"); + rx.recv().expect("get_clock_limits callback recv failed") + }; move |params_in: super::ApiParameterType| { - if let Some(Primitive::F64(index)) = params_in.get(0) { - let mut settings_lock = unwrap_lock(settings.lock(), "cpu"); - if let Some(cpu) = settings_lock.get_mut(*index as usize) { - if let Some(min_max) = &cpu.clock_limits { - vec![min_max.min.into(), min_max.max.into()] - } else { - vec![Primitive::Empty, Primitive::Empty] - } + if let Some(&Primitive::F64(index)) = params_in.get(0) { + if let Some(min_max) = getter(index as usize) { + vec![min_max.min.into(), min_max.max.into()] } else { - vec!["get_clock_limits cpu index out of bounds".into()] + vec![Primitive::Empty, Primitive::Empty] } } else { vec!["get_clock_limits missing parameter 0".into()] @@ -155,23 +228,17 @@ pub fn get_clock_limits( } pub fn unset_clock_limits( - settings: Arc>>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |index: usize| + sender.lock() + .unwrap() + .send(ApiMessage::Cpu(CpuMessage::SetClockLimits(index, None))).expect("unset_clock_limits send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::F64(index)) = params_in.get(0) { - let mut settings_lock = unwrap_lock(settings.lock(), "cpu"); - if let Some(cpu) = settings_lock.get_mut(*index as usize) { - cpu.clock_limits = None; - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - super::utility::map_empty_result(cpu.on_set(), true) - } else { - vec!["get_clock_limits cpu index out of bounds".into()] - } + if let Some(&Primitive::F64(index)) = params_in.get(0) { + setter(index as usize); + vec![true.into()] } else { vec!["get_clock_limits missing parameter 0".into()] } @@ -179,29 +246,20 @@ pub fn unset_clock_limits( } pub fn set_cpu_governor( - settings: Arc>>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |index: usize, governor: String| + sender.lock() + .unwrap() + .send(ApiMessage::Cpu(CpuMessage::SetCpuGovernor(index, governor))).expect("set_cpu_governor send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::F64(index)) = params_in.get(0) { - let mut settings_lock = unwrap_lock(settings.lock(), "cpu"); - if let Some(cpu) = settings_lock.get_mut(*index as usize) { - if let Some(Primitive::String(governor)) = params_in.get(1) { - cpu.governor = governor.to_owned(); - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - super::utility::map_empty_result( - cpu.on_set(), - &cpu.governor as &str, - ) - } else { - vec!["set_cpu_governor missing parameter 1".into()] - } + if let Some(&Primitive::F64(index)) = params_in.get(0) { + if let Some(Primitive::String(governor)) = params_in.get(1) { + setter(index as usize, governor.to_owned()); + vec![(governor as &str).into()] } else { - vec!["set_cpu_governor cpu index out of bounds".into()] + vec!["set_cpu_governor missing parameter 1".into()] } } else { vec!["set_cpu_governor missing parameter 0".into()] @@ -209,14 +267,46 @@ pub fn set_cpu_governor( } } -pub fn get_cpu_governors( - settings: Arc>>, +pub fn set_cpus_governors( + 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 |governors: Vec| + sender.lock() + .unwrap() + .send(ApiMessage::Cpu(CpuMessage::SetCpusGovernor(governors))).expect("set_cpus_governor send failed"); + move |params_in: super::ApiParameterType| { + let mut result = Vec::with_capacity(params_in.len()); + let mut values = Vec::with_capacity(params_in.len()); + for i in 0..params_in.len() { + if let Primitive::String(gov) = ¶ms_in[i] { + values.push(gov.to_owned()); + result.push((gov as &str).into()); + } else { + //values.push(true); + result.push(format!("Invalid parameter {}", i).into()) + } + } + setter(values); + result + } +} + +pub fn get_cpu_governors( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let getter = move || { + let (tx, rx) = mpsc::channel(); + let callback = move |values: Vec| tx.send(values).expect("get_cpu_governors callback send failed"); + sender.lock().unwrap().send(ApiMessage::Cpu(CpuMessage::GetCpusGovernor(Box::new(callback)))).expect("get_cpu_governors send failed"); + rx.recv().expect("get_cpu_governors callback recv failed") + }; move |_: super::ApiParameterType| { - let settings_lock = unwrap_lock(settings.lock(), "cpu"); - let mut output = Vec::with_capacity(settings_lock.len()); - for cpu in settings_lock.as_slice() { - output.push(cpu.governor.clone().into()); + let result = getter(); + let mut output = Vec::with_capacity(result.len()); + for cpu in result.as_slice() { + output.push(cpu.clone().into()); } output } diff --git a/backend/src/api/general.rs b/backend/src/api/general.rs index 4da5021..aae871b 100644 --- a/backend/src/api/general.rs +++ b/backend/src/api/general.rs @@ -1,29 +1,25 @@ -use std::sync::{mpsc::Sender, Arc, Mutex}; +use std::sync::mpsc::{Sender, self}; +use std::sync::{Arc, Mutex}; use usdpl_back::core::serdes::Primitive; +use usdpl_back::AsyncCallable; -use crate::settings::{General, Settings, OnSet}; -use crate::utility::{unwrap_lock, unwrap_maybe_fatal}; +//use crate::utility::{unwrap_lock, unwrap_maybe_fatal}; +use super::handler::{ApiMessage, GeneralMessage}; /// Generate set persistent web method pub fn set_persistent( - settings: Arc>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |pers: bool| + sender.lock() + .unwrap() + .send(ApiMessage::General(GeneralMessage::SetPersistent(pers))).expect("set_persistent send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::Bool(new_val)) = params_in.get(0) { - let mut settings_lock = unwrap_lock(settings.lock(), "general"); - settings_lock.persistent = *new_val; - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - let result = super::utility::map_empty_result( - settings_lock.on_set(), - settings_lock.persistent, - ); - log::debug!("Persistent is now {}", settings_lock.persistent); - result + if let Some(&Primitive::Bool(new_val)) = params_in.get(0) { + setter(new_val); + //log::debug!("Persistent is now {}", settings_lock.persistent); + vec![new_val.into()] } else { vec!["set_persistent missing parameter".into()] } @@ -32,30 +28,34 @@ pub fn set_persistent( /// Generate get persistent save mode web method pub fn get_persistent( - settings: Arc>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let getter = move || { + let (tx, rx) = mpsc::channel(); + let callback = move |value: bool| tx.send(value).expect("get_persistent callback send failed"); + sender.lock().unwrap().send(ApiMessage::General(GeneralMessage::GetPersistent(Box::new(callback)))).expect("get_persistent send failed"); + rx.recv().expect("get_persistent callback recv failed") + }; move |_: super::ApiParameterType| { - let settings_lock = unwrap_lock(settings.lock(), "general"); - vec![settings_lock - .persistent.into()] + vec![getter().into()] } } /// Generate load app settings from file web method pub fn load_settings( - settings: Settings, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |path: String, name: String| + sender.lock() + .unwrap() + .send(ApiMessage::LoadSettings(path, name)).expect("load_settings send failed"); move |params_in: super::ApiParameterType| { if let Some(Primitive::String(path)) = params_in.get(0) { if let Some(Primitive::String(name)) = params_in.get(1) { - match settings.load_file(path.into(), name.to_owned(), false) { - Err(e) => vec![e.msg.into()], - Ok(success) => - super::utility::map_empty_result( - settings.clone().on_set(), - success - ) - } + setter(path.to_owned(), name.to_owned()); + vec![true.into()] } else { vec!["load_settings missing name parameter".into()] } @@ -68,10 +68,17 @@ pub fn load_settings( /// Generate load default settings from file web method pub fn load_default_settings( - settings: Settings, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move || + sender.lock() + .unwrap() + .send(ApiMessage::LoadMainSettings).expect("load_default_settings send failed"); move |_: super::ApiParameterType| { - match settings.load_file( + setter(); + vec![true.into()] + /*match settings.load_file( crate::consts::DEFAULT_SETTINGS_FILE.into(), crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), true @@ -81,32 +88,163 @@ pub fn load_default_settings( settings.clone().on_set(), success ) - } + }*/ + } +} + +/// Generate load system default settings from file web method +pub fn load_system_settings( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move || + sender.lock() + .unwrap() + .send(ApiMessage::LoadSystemSettings).expect("load_default_settings send failed"); + move |_: super::ApiParameterType| { + setter(); + vec![true.into()] + /*match settings.load_file( + crate::consts::DEFAULT_SETTINGS_FILE.into(), + crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), + true + ) { + Err(e) => vec![e.msg.into()], + Ok(success) => super::utility::map_empty_result( + settings.clone().on_set(), + success + ) + }*/ } } /// Generate get current settings name pub fn get_name( - settings: Arc>, -) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - move |_: super::ApiParameterType| { - let settings_lock = unwrap_lock(settings.lock(), "general"); - vec![settings_lock - .name - .clone() - .into()] + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |name: String| tx.send(name).expect("get_name callback send failed"); + sender2.lock().unwrap().send(ApiMessage::General(GeneralMessage::GetCurrentProfileName(Box::new(callback)))).expect("get_name send failed"); + rx.recv().expect("get_name callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + vec![result.into()] + } } } /// Generate wait for all locks to be available web method pub fn lock_unlock_all( - settings: Settings, -) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - move |_: super::ApiParameterType| { - let _lock = unwrap_lock(settings.general.lock(), "general"); - let _lock = unwrap_lock(settings.cpus.lock(), "cpus"); - let _lock = unwrap_lock(settings.gpu.lock(), "gpu"); - let _lock = unwrap_lock(settings.battery.lock(), "battery"); - vec![true.into()] + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |x| tx.send(x).expect("lock_unlock_all callback send failed"); + sender2.lock().unwrap().send(ApiMessage::WaitForEmptyQueue(Box::new(callback))).expect("lock_unlock_all send failed"); + rx.recv().expect("lock_unlock_all callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |_| { + vec![true.into()] + } } } + +/// Generate get limits web method +pub fn get_limits( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let getter = move || { + let (tx, rx) = mpsc::channel(); + let callback = move |value: super::SettingsLimits| tx.send(value).expect("get_limits callback send failed"); + sender.lock().unwrap().send(ApiMessage::GetLimits(Box::new(callback))).expect("get_limits send failed"); + rx.recv().expect("get_limits callback recv failed") + }; + move |_: super::ApiParameterType| { + vec![Primitive::Json(serde_json::to_string(&getter()).unwrap())] + } +} + +/// Generate get current driver name +pub fn get_provider( + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move |provider_name: String| { + let (tx, rx) = mpsc::channel(); + let callback = move |name: crate::persist::DriverJson| tx.send(name).expect("get_provider callback send failed"); + sender2.lock().unwrap().send(ApiMessage::GetProvider(provider_name, Box::new(callback))).expect("get_provider send failed"); + rx.recv().expect("get_provider callback recv failed") + } + }; + super::async_utils::AsyncIsh { + trans_setter: |mut params| { + if let Some(Primitive::String(name)) = params.pop() { + Ok(name.to_owned()) + } else { + Err(format!("Invalid/missing single param in get_provider")) + } + }, + set_get: getter, + trans_getter: |result| { + vec![format!("{:?}", result).into()] + } + } +} + +pub fn gunter(_: super::ApiParameterType) -> super::ApiParameterType { + std::thread::spawn(|| { + log::info!("Zhu Li, do the thing!"); + crate::settings::driver::maybe_do_button(); + log::info!("Thing done.") + }); + vec![true.into()] +} + +/// API web method to send log messages to the back-end log, callable from the front-end +pub fn log_it() -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + move |params| { + if let Some(Primitive::F64(level)) = params.get(0) { + if let Some(Primitive::String(msg)) = params.get(1) { + log_msg_by_level(*level as u8, msg); + vec![true.into()] + } else if let Some(Primitive::Json(msg)) = params.get(1) { + log_msg_by_level(*level as u8, msg); + vec![true.into()] + } else { + log::warn!("Got log_it call with wrong/missing 2nd parameter"); + vec![false.into()] + } + } else { + log::warn!("Got log_it call with wrong/missing 1st parameter"); + vec![false.into()] + } + } +} + +fn log_msg_by_level(level: u8, msg: &str) { + match level { + 1 => log::trace!("FRONT-END: {}", msg), + 2 => log::debug!("FRONT-END: {}", msg), + 3 => log::info!("FRONT-END: {}", msg), + 4 => log::warn!("FRONT-END: {}", msg), + 5 => log::error!("FRONT-END: {}", msg), + _ => log::trace!("FRONT-END: {}", msg), + } +} + diff --git a/backend/src/api/gpu.rs b/backend/src/api/gpu.rs index 5ff5fd3..819eb89 100644 --- a/backend/src/api/gpu.rs +++ b/backend/src/api/gpu.rs @@ -1,31 +1,25 @@ -use std::sync::{mpsc::Sender, Arc, Mutex}; +use std::sync::mpsc::{Sender, self}; +use std::sync::{Mutex, Arc}; use usdpl_back::core::serdes::Primitive; +use usdpl_back::AsyncCallable; -use crate::settings::{Gpu, OnSet, MinMax}; -use crate::utility::{unwrap_lock, unwrap_maybe_fatal}; +use crate::settings::MinMax; +//use crate::utility::{unwrap_lock, unwrap_maybe_fatal}; +use super::handler::{ApiMessage, GpuMessage}; pub fn set_ppt( - settings: Arc>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |fast: u64, slow: u64| + sender.lock() + .unwrap() + .send(ApiMessage::Gpu(GpuMessage::SetPpt(Some(fast), Some(slow)))).expect("set_ppt send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::F64(fast_ppt)) = params_in.get(0) { - if let Some(Primitive::F64(slow_ppt)) = params_in.get(1) { - let mut settings_lock = unwrap_lock(settings.lock(), "gpu"); - settings_lock.fast_ppt = Some(*fast_ppt as u64); - settings_lock.slow_ppt = Some(*slow_ppt as u64); - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - match settings_lock.on_set() { - Ok(_) => vec![ - settings_lock.fast_ppt.unwrap().into(), - settings_lock.slow_ppt.unwrap().into() - ], - Err(e) => vec![e.msg.into()], - } + if let Some(&Primitive::F64(fast_ppt)) = params_in.get(0) { + if let Some(&Primitive::F64(slow_ppt)) = params_in.get(1) { + setter(fast_ppt as u64, slow_ppt as u64); + vec![(fast_ppt as u64).into(), (slow_ppt as u64).into()] } else { vec!["set_ppt missing parameter 1".into()] } @@ -36,60 +30,69 @@ pub fn set_ppt( } pub fn get_ppt( - settings: Arc>, -) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - move |_: super::ApiParameterType| { - let settings_lock = unwrap_lock(settings.lock(), "gpu"); - let fast_ppt = settings_lock.fast_ppt.map(|x| x.into()).unwrap_or(Primitive::Empty); - let slow_ppt = settings_lock.slow_ppt.map(|x| x.into()).unwrap_or(Primitive::Empty); - vec![fast_ppt, slow_ppt] + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |ppt: (Option, Option)| tx.send(ppt).expect("get_ppt callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Gpu(GpuMessage::GetPpt(Box::new(callback)))).expect("get_ppt send failed"); + rx.recv().expect("get_ppt callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |(fast, slow): (Option, Option)| { + vec![ + fast.map(|x| x.into()).unwrap_or(Primitive::Empty), + slow.map(|x| x.into()).unwrap_or(Primitive::Empty), + ] + } } } pub fn unset_ppt( - settings: Arc>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move || + sender.lock() + .unwrap() + .send(ApiMessage::Gpu(GpuMessage::SetPpt(None, None))).expect("set_ppt send failed"); move |_: super::ApiParameterType| { - let mut settings_lock = unwrap_lock(settings.lock(), "gpu"); - settings_lock.fast_ppt = None; - settings_lock.slow_ppt = None; - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - super::utility::map_empty_result( - settings_lock.on_set(), - Primitive::Empty, - ) + setter(); + vec![true.into()] } } pub fn set_clock_limits( - settings: Arc>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |value: MinMax| + sender.lock() + .unwrap() + .send(ApiMessage::Gpu(GpuMessage::SetClockLimits(Some(value)))).expect("set_clock_limits send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::F64(min)) = params_in.get(0) { - if let Some(Primitive::F64(max)) = params_in.get(1) { - let mut settings_lock = unwrap_lock(settings.lock(), "gpu"); - settings_lock.clock_limits = Some(MinMax { - min: *min as _, - max: *max as _, + if let Some(&Primitive::F64(min)) = params_in.get(0) { + if let Some(&Primitive::F64(max)) = params_in.get(1) { + let safe_max = if max < min { + min + } else { + max + }; + let safe_min = if min > max { + max + } else { + min + }; + setter(MinMax { + min: safe_min as _, + max: safe_max as _, }); - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - match settings_lock.on_set() { - Ok(_) => vec![ - settings_lock.clock_limits.as_ref().unwrap().min.into(), - settings_lock.clock_limits.as_ref().unwrap().max.into(), - ], - Err(e) => vec![e.msg.into()] - } + vec![(safe_min as u64).into(), (safe_max as u64).into()] } else { vec!["set_clock_limits missing parameter 1".into()] } @@ -100,51 +103,54 @@ pub fn set_clock_limits( } pub fn get_clock_limits( - settings: Arc>, -) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - move |_: super::ApiParameterType| { - let settings_lock = unwrap_lock(settings.lock(), "gpu"); - if let Some(min_max) = &settings_lock.clock_limits { - vec![min_max.min.into(), min_max.max.into()] - } else { - vec![Primitive::Empty, Primitive::Empty] + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move|| { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |clocks: Option>| tx.send(clocks).expect("get_clock_limits callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Gpu(GpuMessage::GetClockLimits(Box::new(callback)))).expect("get_clock_limits send failed"); + rx.recv().expect("get_clock_limits callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |clocks: Option>| { + clocks.map(|x| vec![ + x.min.into(), x.max.into() + ]).unwrap_or_else(|| vec![Primitive::Empty, Primitive::Empty]) } } } pub fn unset_clock_limits( - settings: Arc>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move || + sender.lock() + .unwrap() + .send(ApiMessage::Gpu(GpuMessage::SetClockLimits(None))).expect("unset_clock_limits send failed"); move |_: super::ApiParameterType| { - let mut settings_lock = unwrap_lock(settings.lock(), "gpu"); - settings_lock.clock_limits = None; - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - super::utility::map_empty_result(settings_lock.on_set(), true) + setter(); + vec![true.into()] } } pub fn set_slow_memory( - settings: Arc>, - saver: Sender<()>, + sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |value: bool| + sender.lock() + .unwrap() + .send(ApiMessage::Gpu(GpuMessage::SetSlowMemory(value))).expect("unset_clock_limits send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::Bool(memory_is_slow)) = params_in.get(0) { - let mut settings_lock = unwrap_lock(settings.lock(), "gpu"); - settings_lock.slow_memory = *memory_is_slow; - unwrap_maybe_fatal( - unwrap_lock(saver.lock(), "save channel").send(()), - "Failed to send on save channel", - ); - super::utility::map_empty_result( - settings_lock.on_set(), - settings_lock.slow_memory, - ) + if let Some(&Primitive::Bool(memory_is_slow)) = params_in.get(0) { + setter(memory_is_slow); + vec![memory_is_slow.into()] } else { vec!["set_slow_memory missing parameter 0".into()] } @@ -152,10 +158,22 @@ pub fn set_slow_memory( } pub fn get_slow_memory( - settings: Arc>, -) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { - move |_: super::ApiParameterType| { - let settings_lock = unwrap_lock(settings.lock(), "cpu"); - vec![settings_lock.slow_memory.into()] + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |value: bool| tx.send(value).expect("get_slow_memory callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Gpu(GpuMessage::GetSlowMemory(Box::new(callback)))).expect("get_slow_memory send failed"); + rx.recv().expect("get_slow_memory callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |value: bool| { + vec![value.into()] + } } } diff --git a/backend/src/api/handler.rs b/backend/src/api/handler.rs new file mode 100644 index 0000000..dc7d1e8 --- /dev/null +++ b/backend/src/api/handler.rs @@ -0,0 +1,330 @@ +use std::sync::mpsc::{self, Receiver, Sender}; + +use crate::settings::{Settings, TCpus, TGpu, TBattery, TGeneral, OnSet, OnResume, MinMax}; +use crate::persist::SettingsJson; +use crate::utility::unwrap_maybe_fatal; + +type Callback = Box; + +pub enum ApiMessage { + Battery(BatteryMessage), + Cpu(CpuMessage), + Gpu(GpuMessage), + General(GeneralMessage), + OnResume, + WaitForEmptyQueue(Callback<()>), + LoadSettings(String, String), // (path, name) + LoadMainSettings, + LoadSystemSettings, + GetLimits(Callback), + GetProvider(String, Callback), +} + +pub enum BatteryMessage { + SetChargeRate(Option), + GetChargeRate(Callback>), + SetChargeMode(Option), + GetChargeMode(Callback>), + ReadChargeFull(Callback>), + ReadChargeNow(Callback>), + ReadChargeDesign(Callback>), + ReadCurrentNow(Callback>), +} + +impl BatteryMessage { + fn process(self, settings: &mut dyn TBattery) -> bool { + let dirty = self.is_modify(); + match self { + Self::SetChargeRate(rate) => settings.charge_rate(rate), + Self::GetChargeRate(cb) => cb(settings.get_charge_rate()), + Self::SetChargeMode(mode) => settings.charge_mode(mode), + Self::GetChargeMode(cb) => cb(settings.get_charge_mode()), + Self::ReadChargeFull(cb) => cb(settings.read_charge_full()), + Self::ReadChargeNow(cb) => cb(settings.read_charge_now()), + Self::ReadChargeDesign(cb) => cb(settings.read_charge_design()), + Self::ReadCurrentNow(cb) => cb(settings.read_current_now()), + } + dirty + } + + /// Message instructs the driver to modify settings + fn is_modify(&self) -> bool { + matches!(self, Self::SetChargeRate(_) | Self::SetChargeMode(_)) + } +} + +pub enum CpuMessage { + SetCpuOnline(usize, bool), + SetCpusOnline(Vec), + SetSmt(bool, Callback>), + GetSmt(Callback), + GetCpusOnline(Callback>), + SetClockLimits(usize, Option>), + GetClockLimits(usize, Callback>>), + SetCpuGovernor(usize, String), + SetCpusGovernor(Vec), + GetCpusGovernor(Callback>), +} + +impl CpuMessage { + fn process(self, settings: &mut dyn TCpus) -> bool { + let dirty = self.is_modify(); + // NOTE: "cpu" refers to the Linux kernel definition of a CPU, which is actually a hardware thread + // not to be confused with a CPU chip, which usually has multiple hardware threads (cpu cores/threads) in the chip + match self { + Self::SetCpuOnline(index, status) => {settings.cpus().get_mut(index).map(|c| *c.online() = status);}, + Self::SetCpusOnline(cpus) => { + for i in 0..cpus.len() { + settings.cpus().get_mut(i).map(|c| *c.online() = cpus[i]); + } + }, + Self::SetSmt(status, cb) => { + if *settings.smt() == status { + // already set, do nothing + } else if status { + // set SMT on + *settings.smt() = true; + let mut should_be_online = false; + let cpu_count = settings.len(); + for i in (0..cpu_count).rev() { + if *settings.cpus()[i].online() && !should_be_online { + should_be_online = true; + // enable the odd-numbered thread right before + // for 1c:2t configs (i.e. anything with SMT2), the highest cpu core is always odd + // (e.g. 4c8t has CPUs 0-7, inclusive) + // this enables the """fake""" (i.e. odd) cpu which is disabled when SMT is set off + if i % 2 == 0 && i+1 != cpu_count { + *(settings.cpus()[i+1].online()) = true; + } + } else { + *settings.cpus()[i].online() = should_be_online; + } + } + } else { + // set SMT off + *settings.smt() = false; + for i in 0..settings.len() { + // this disables the """fake""" (odd) cpu for appearances' sake + // the kernel will automatically disable that same cpu when SMT is changed + *settings.cpus()[i].online() = *settings.cpus()[i].online() && (status || i % 2 == 0); + } + } + let mut result = Vec::with_capacity(settings.len()); + for i in 0..settings.len() { + result.push(*settings.cpus()[i].online()); + } + cb(result); + }, + Self::GetSmt(cb) => { + cb(*settings.smt()); + }, + Self::GetCpusOnline(cb) => { + let mut result = Vec::with_capacity(settings.len()); + for cpu in settings.cpus() { + result.push(*cpu.online()); + } + cb(result); + }, + Self::SetClockLimits(index, clocks) => {settings.cpus().get_mut(index).map(|c| c.clock_limits(clocks));}, + Self::GetClockLimits(index, cb) => {settings.cpus().get(index).map(|c| cb(c.get_clock_limits().map(|x| x.to_owned())));}, + Self::SetCpuGovernor(index, gov) => {settings.cpus().get_mut(index).map(|c| c.governor(gov));}, + Self::SetCpusGovernor(govs) => { + for i in 0..govs.len() { + settings.cpus().get_mut(i).map(|c| c.governor(govs[i].clone())); + } + }, + Self::GetCpusGovernor(cb) => { + let mut result = Vec::with_capacity(settings.len()); + for cpu in settings.cpus() { + result.push(cpu.get_governor().to_owned()); + } + cb(result); + } + } + dirty + } + + /// Message instructs the driver to modify settings + fn is_modify(&self) -> bool { + matches!(self, + Self::SetCpuOnline(_, _) + | Self::SetCpusOnline(_) + | Self::SetSmt(_, _) + | Self::SetClockLimits(_, _) + | Self::SetCpuGovernor(_, _) + | Self::SetCpusGovernor(_) + ) + } +} + +pub enum GpuMessage { + SetPpt(Option, Option), // (fast, slow) + GetPpt(Callback<(Option, Option)>), + SetClockLimits(Option>), + GetClockLimits(Callback>>), + SetSlowMemory(bool), + GetSlowMemory(Callback), +} + +impl GpuMessage { + fn process(self, settings: &mut dyn TGpu) -> bool { + let dirty = self.is_modify(); + match self { + Self::SetPpt(fast, slow) => settings.ppt(fast, slow), + Self::GetPpt(cb) => cb(settings.get_ppt()), + Self::SetClockLimits(clocks) => settings.clock_limits(clocks), + Self::GetClockLimits(cb) => cb(settings.get_clock_limits().map(|x| x.to_owned())), + Self::SetSlowMemory(val) => *settings.slow_memory() = val, + Self::GetSlowMemory(cb) => cb(*settings.slow_memory()), + } + dirty + } + + fn is_modify(&self) -> bool { + matches!(self, + Self::SetPpt(_, _) + | Self::SetClockLimits(_) + | Self::SetSlowMemory(_) + ) + } +} + +pub enum GeneralMessage { + SetPersistent(bool), + GetPersistent(Callback), + GetCurrentProfileName(Callback), +} + +impl GeneralMessage { + fn process(self, settings: &mut dyn TGeneral) -> bool { + let dirty = self.is_modify(); + match self { + Self::SetPersistent(val) => *settings.persistent() = val, + Self::GetPersistent(cb) => cb(*settings.persistent()), + Self::GetCurrentProfileName(cb) => cb(settings.get_name().to_owned()), + } + dirty + } + + fn is_modify(&self) -> bool { + matches!(self, Self::SetPersistent(_)) + } +} + +pub struct ApiMessageHandler { + intake: Receiver, + on_empty: Vec>, +} + +impl ApiMessageHandler { + pub fn process_forever(&mut self, settings: &mut Settings) { + let mut dirty_echo = true; // set everything twice, to make sure PowerTools wins on race conditions + while let Ok(msg) = self.intake.recv() { + let mut dirty = self.process(settings, msg); + while let Ok(msg) = self.intake.try_recv() { + dirty |= self.process(settings, msg); + } + if dirty || dirty_echo { + dirty_echo = dirty; // echo only once + // run on_set + if let Err(e) = settings.on_set() { + log::error!("Settings on_set() err: {}", e); + } + // do callbacks + for func in self.on_empty.drain(..) { + func(()); + } + // save + log::debug!("api_worker is saving..."); + let is_persistent = *settings.general.persistent(); + let save_path = crate::utility::settings_dir() + .join(settings.general.get_path().clone()); + if is_persistent { + let settings_clone = settings.json(); + let save_json: SettingsJson = settings_clone.into(); + unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings"); + log::debug!("Saved settings to {}", save_path.display()); + } else { + if save_path.exists() { + if let Err(e) = std::fs::remove_file(&save_path) { + log::warn!("Failed to delete persistent settings file {}: {}", save_path.display(), e); + } else { + log::debug!("Deleted persistent settings file {}", save_path.display()); + } + } else { + log::debug!("Ignored save request for non-persistent settings"); + } + } + } else { + log::debug!("Skipping callbacks for non-modify handled message(s)"); + } + } + } + + pub fn process(&mut self, settings: &mut Settings, message: ApiMessage) -> bool { + match message { + ApiMessage::Battery(x) => x.process(settings.battery.as_mut()), + ApiMessage::Cpu(x) => x.process(settings.cpus.as_mut()), + ApiMessage::Gpu(x) => x.process(settings.gpu.as_mut()), + ApiMessage::General(x) => x.process(settings.general.as_mut()), + ApiMessage::OnResume => { + if let Err(e) = settings.on_resume() { + log::error!("Settings on_resume() err: {}", e); + } + false + } + ApiMessage::WaitForEmptyQueue(callback) => { + self.on_empty.push(callback); + false + }, + ApiMessage::LoadSettings(path, name) => { + match settings.load_file(path.into(), name, false) { + Ok(success) => log::info!("Loaded settings file? {}", success), + Err(e) => log::warn!("Load file err: {}", e), + } + true + } + ApiMessage::LoadMainSettings => { + match settings.load_file( + crate::consts::DEFAULT_SETTINGS_FILE.into(), + crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), + true + ) { + Ok(success) => log::info!("Loaded main settings file? {}", success), + Err(e) => log::warn!("Load file err: {}", e), + } + true + } + ApiMessage::LoadSystemSettings => { + settings.load_system_default(); + true + }, + ApiMessage::GetLimits(cb) => { + cb(super::SettingsLimits { + battery: settings.battery.limits(), + cpu: settings.cpus.limits(), + gpu: settings.gpu.limits(), + general: settings.general.limits(), + }); + false + }, + ApiMessage::GetProvider(name, cb) => { + cb(match &name as &str { + "battery" => settings.battery.provider(), + "cpu" | "cpus" => settings.cpus.provider(), + "gpu" => settings.gpu.provider(), + _ => settings.general.provider(), + }); + false + } + } + } + + pub fn new() -> (Self, Sender) { + let (tx, rx) = mpsc::channel(); + (Self { + intake: rx, + on_empty: Vec::with_capacity(4), + }, tx) + } +} diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 75e2a6d..174d1bb 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,7 +1,12 @@ +mod api_types; pub mod battery; pub mod cpu; pub mod general; pub mod gpu; +pub mod handler; +mod async_utils; mod utility; pub(super) type ApiParameterType = Vec; + +pub use api_types::*; diff --git a/backend/src/api/utility.rs b/backend/src/api/utility.rs index c5b4bc0..76957ca 100644 --- a/backend/src/api/utility.rs +++ b/backend/src/api/utility.rs @@ -15,6 +15,20 @@ pub fn map_result>(result: Result) -> super: } #[inline] +pub fn map_optional_result>(result: Result, SettingError>) -> super::ApiParameterType { + match result { + Ok(val) => match val { + Some(val) => vec![val.into()], + None => vec![Primitive::Empty], + }, + Err(e) => { + log::debug!("Mapping error to primitive: {}", e); + vec![e.msg.into()] + }, + } +} + +/*#[inline] pub fn map_empty_result>( result: Result<(), SettingError>, success: T, @@ -26,4 +40,4 @@ pub fn map_empty_result>( vec![e.msg.into()] }, } -} +}*/ diff --git a/backend/src/api_worker.rs b/backend/src/api_worker.rs new file mode 100644 index 0000000..2ddff02 --- /dev/null +++ b/backend/src/api_worker.rs @@ -0,0 +1,13 @@ +use std::thread::{self, JoinHandle}; + +use crate::settings::Settings; +//use crate::utility::{unwrap_lock, unwrap_maybe_fatal}; +use crate::api::handler::ApiMessageHandler; + +pub fn spawn(mut settings: Settings, mut handler: ApiMessageHandler) -> JoinHandle<()> { + thread::spawn(move || { + log::info!("api_worker starting..."); + handler.process_forever(&mut settings); + log::warn!("api_worker completed!"); + }) +} diff --git a/backend/src/consts.rs b/backend/src/consts.rs index 43e23b7..842051a 100644 --- a/backend/src/consts.rs +++ b/backend/src/consts.rs @@ -4,4 +4,6 @@ pub const PACKAGE_NAME: &'static str = env!("CARGO_PKG_NAME"); pub const PACKAGE_VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub const DEFAULT_SETTINGS_FILE: &str = "default_settings.json"; -pub const DEFAULT_SETTINGS_NAME: &str = "Default"; +pub const DEFAULT_SETTINGS_NAME: &str = "Main"; + +pub const LIMITS_FILE: &str = "limits_cache.json"; diff --git a/backend/src/main.rs b/backend/src/main.rs index 66ef8e2..f335223 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -6,7 +6,8 @@ mod state; mod consts; use consts::*; mod resume_worker; -mod save_worker; +//mod save_worker; +mod api_worker; mod utility; use settings::OnSet; @@ -17,14 +18,21 @@ use usdpl_back::core::serdes::Primitive; use usdpl_back::Instance; fn main() -> Result<(), ()> { + #[cfg(debug_assertions)] - let log_filepath = format!("/home/deck/{}.log", PACKAGE_NAME); + let log_filepath = usdpl_back::api::dirs::home() + .unwrap_or_else(|| "/tmp/".into()) + .join(PACKAGE_NAME.to_owned()+".log"); #[cfg(not(debug_assertions))] - let log_filepath = format!("/tmp/{}.log", PACKAGE_NAME); + let log_filepath = std::path::Path::new("/tmp").join(format!("{}.log", PACKAGE_NAME)); + #[cfg(debug_assertions)] + let old_log_filepath = usdpl_back::api::dirs::home() + .unwrap_or_else(|| "/tmp/".into()) + .join(PACKAGE_NAME.to_owned()+".log.old"); #[cfg(debug_assertions)] { if std::path::Path::new(&log_filepath).exists() { - std::fs::copy(&log_filepath, format!("/home/deck/{}.log.old", PACKAGE_NAME)).unwrap(); + std::fs::copy(&log_filepath, &old_log_filepath).expect("Unable to increment logs. Do you have write permissions?"); } } WriteLogger::init( @@ -40,8 +48,15 @@ fn main() -> Result<(), ()> { std::fs::File::create(&log_filepath).unwrap(), ) .unwrap(); + log::debug!("Logging to: {:?}.", log_filepath); + println!("Logging to: {:?}", log_filepath); log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); + log::info!("Current dir `{}`", std::env::current_dir().unwrap().display()); + println!("Current dir `{}`", std::env::current_dir().unwrap().display()); + + let _limits_handle = crate::settings::limits_worker_spawn(); + log::info!("Detected device automatically, starting with driver: {:?} (This can be overriden)", crate::settings::auto_detect_provider()); let mut loaded_settings = persist::SettingsJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE)) .map(|settings| settings::Settings::from_json(settings, DEFAULT_SETTINGS_FILE.into())) @@ -49,125 +64,171 @@ fn main() -> Result<(), ()> { log::debug!("Settings: {:?}", loaded_settings); - let (_save_handle, save_sender) = save_worker::spawn(loaded_settings.clone()); - let _resume_handle = resume_worker::spawn(loaded_settings.clone()); + let (api_handler, api_sender) = crate::api::handler::ApiMessageHandler::new(); - if let Err(e) = loaded_settings.on_set() { - log::error!("Startup Settings.on_set() error: {}", e); - } + //let (_save_handle, save_sender) = save_worker::spawn(loaded_settings.clone()); + let _resume_handle = resume_worker::spawn(api_sender.clone()); - Instance::new(PORT) + let instance = Instance::new(PORT) .register("V_INFO", |_: Vec| { vec![format!("{} v{}", PACKAGE_NAME, PACKAGE_VERSION).into()] }) + .register("LOG", api::general::log_it()) // battery API functions - .register("BATTERY_current_now", api::battery::current_now) - .register("BATTERY_charge_now", api::battery::charge_now) - .register("BATTERY_charge_full", api::battery::charge_full) - .register("BATTERY_charge_design", api::battery::charge_design) + .register_async("BATTERY_current_now", api::battery::current_now(api_sender.clone())) + .register_async("BATTERY_charge_now", api::battery::charge_now(api_sender.clone())) + .register_async("BATTERY_charge_full", api::battery::charge_full(api_sender.clone())) + .register_async("BATTERY_charge_design", api::battery::charge_design(api_sender.clone())) .register( "BATTERY_set_charge_rate", - api::battery::set_charge_rate(loaded_settings.battery.clone(), save_sender.clone()), + api::battery::set_charge_rate(api_sender.clone()), ) .register( "BATTERY_get_charge_rate", - api::battery::get_charge_rate(loaded_settings.battery.clone()), + api::battery::get_charge_rate(api_sender.clone()), ) .register( "BATTERY_unset_charge_rate", - api::battery::unset_charge_rate(loaded_settings.battery.clone(), save_sender.clone()), + api::battery::unset_charge_rate(api_sender.clone()), + ) + .register( + "BATTERY_set_charge_mode", + api::battery::set_charge_mode(api_sender.clone()), + ) + .register( + "BATTERY_get_charge_mode", + api::battery::get_charge_mode(api_sender.clone()), + ) + .register( + "BATTERY_unset_charge_mode", + api::battery::unset_charge_mode(api_sender.clone()), ) // cpu API functions .register("CPU_count", api::cpu::max_cpus) .register( "CPU_set_online", - api::cpu::set_cpu_online(loaded_settings.cpus.clone(), save_sender.clone()) + api::cpu::set_cpu_online(api_sender.clone()) ) .register( "CPU_set_onlines", - api::cpu::set_cpus_online(loaded_settings.cpus.clone(), save_sender.clone()) + api::cpu::set_cpus_online(api_sender.clone()) ) - .register( + .register_async( "CPU_get_onlines", - api::cpu::get_cpus_online(loaded_settings.cpus.clone()) + api::cpu::get_cpus_online(api_sender.clone()) + ) + .register_async( + "CPU_set_smt", + api::cpu::set_smt(api_sender.clone()) + ) + .register_async( + "CPU_get_smt", + api::cpu::get_smt(api_sender.clone()) ) .register( "CPU_set_clock_limits", - api::cpu::set_clock_limits(loaded_settings.cpus.clone(), save_sender.clone()) + api::cpu::set_clock_limits(api_sender.clone()) ) .register( "CPU_get_clock_limits", - api::cpu::get_clock_limits(loaded_settings.cpus.clone()) + api::cpu::get_clock_limits(api_sender.clone()) ) .register( "CPU_unset_clock_limits", - api::cpu::unset_clock_limits(loaded_settings.cpus.clone(), save_sender.clone()) + api::cpu::unset_clock_limits(api_sender.clone()) ) .register( "CPU_set_governor", - api::cpu::set_cpu_governor(loaded_settings.cpus.clone(), save_sender.clone()) + api::cpu::set_cpu_governor(api_sender.clone()) + ) + .register( + "CPU_set_governors", + api::cpu::set_cpus_governors(api_sender.clone()) ) .register( "CPU_get_governors", - api::cpu::get_cpu_governors(loaded_settings.cpus.clone()) + api::cpu::get_cpu_governors(api_sender.clone()) ) // gpu API functions .register( "GPU_set_ppt", - api::gpu::set_ppt(loaded_settings.gpu.clone(), save_sender.clone()) + api::gpu::set_ppt(api_sender.clone()) ) - .register( + .register_async( "GPU_get_ppt", - api::gpu::get_ppt(loaded_settings.gpu.clone()) + api::gpu::get_ppt(api_sender.clone()) ) .register( "GPU_unset_ppt", - api::gpu::unset_ppt(loaded_settings.gpu.clone(), save_sender.clone()) + api::gpu::unset_ppt(api_sender.clone()) ) .register( "GPU_set_clock_limits", - api::gpu::set_clock_limits(loaded_settings.gpu.clone(), save_sender.clone()) + api::gpu::set_clock_limits(api_sender.clone()) ) - .register( + .register_async( "GPU_get_clock_limits", - api::gpu::get_clock_limits(loaded_settings.gpu.clone()) + api::gpu::get_clock_limits(api_sender.clone()) ) .register( "GPU_unset_clock_limits", - api::gpu::unset_clock_limits(loaded_settings.gpu.clone(), save_sender.clone()) + api::gpu::unset_clock_limits(api_sender.clone()) ) .register( "GPU_set_slow_memory", - api::gpu::set_slow_memory(loaded_settings.gpu.clone(), save_sender.clone()) + api::gpu::set_slow_memory(api_sender.clone()) ) - .register( + .register_async( "GPU_get_slow_memory", - api::gpu::get_slow_memory(loaded_settings.gpu.clone()) + api::gpu::get_slow_memory(api_sender.clone()) ) // general API functions .register( "GENERAL_set_persistent", - api::general::set_persistent(loaded_settings.general.clone(), save_sender.clone()) + api::general::set_persistent(api_sender.clone()) ) .register( "GENERAL_get_persistent", - api::general::get_persistent(loaded_settings.general.clone()) + api::general::get_persistent(api_sender.clone()) ) .register( "GENERAL_load_settings", - api::general::load_settings(loaded_settings.clone()) + api::general::load_settings(api_sender.clone()) ) .register( "GENERAL_load_default_settings", - api::general::load_default_settings(loaded_settings.clone()) + api::general::load_default_settings(api_sender.clone()) ) .register( + "GENERAL_load_system_settings", + api::general::load_system_settings(api_sender.clone()) + ) + .register_async( "GENERAL_get_name", - api::general::get_name(loaded_settings.general.clone()) + api::general::get_name(api_sender.clone()) ) - .register( + .register_async( "GENERAL_wait_for_unlocks", - api::general::lock_unlock_all(loaded_settings.clone()) + api::general::lock_unlock_all(api_sender.clone()) ) + .register_blocking( + "GENERAL_get_limits", + api::general::get_limits(api_sender.clone()) + ) + .register_async( + "GENERAL_get_provider", + api::general::get_provider(api_sender.clone()) + ) + .register("GENERAL_idk", api::general::gunter); + + if let Err(e) = loaded_settings.on_set() { + log::error!("Startup Settings.on_set() error: {}", e); + } else { + log::info!("Startup Settings.on_set() success"); + } + + api_worker::spawn(loaded_settings, api_handler); + + instance .run_blocking() } diff --git a/backend/src/persist/battery.rs b/backend/src/persist/battery.rs index 259e327..962cfbb 100644 --- a/backend/src/persist/battery.rs +++ b/backend/src/persist/battery.rs @@ -3,13 +3,17 @@ use std::default::Default; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct BatteryJson { pub charge_rate: Option, + pub charge_mode: Option, } impl Default for BatteryJson { fn default() -> Self { - Self { charge_rate: None } + Self { + charge_rate: None, + charge_mode: None, + } } } diff --git a/backend/src/persist/cpu.rs b/backend/src/persist/cpu.rs index 0af2781..442206a 100644 --- a/backend/src/persist/cpu.rs +++ b/backend/src/persist/cpu.rs @@ -7,7 +7,7 @@ use super::MinMaxJson; //const SCALING_FREQUENCIES: &[u64] = &[1700000, 2400000, 2800000]; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct CpuJson { pub online: bool, pub clock_limits: Option>, diff --git a/backend/src/persist/driver.rs b/backend/src/persist/driver.rs new file mode 100644 index 0000000..1a774bd --- /dev/null +++ b/backend/src/persist/driver.rs @@ -0,0 +1,20 @@ +//use std::default::Default; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub enum DriverJson { + #[serde(rename = "steam-deck", alias = "gabe-boy")] + SteamDeck, + #[serde(rename = "steam-deck-oc", alias = "gabe-boy-advance")] + SteamDeckAdvance, + #[serde(rename = "generic")] + Generic, + #[serde(rename = "generic-amd")] + GenericAMD, + #[serde(rename = "unknown")] + Unknown, + #[default] + #[serde(rename = "auto")] + AutoDetect, +} diff --git a/backend/src/persist/general.rs b/backend/src/persist/general.rs index 4ae3c89..22f94da 100644 --- a/backend/src/persist/general.rs +++ b/backend/src/persist/general.rs @@ -3,7 +3,7 @@ use std::default::Default; use serde::{Deserialize, Serialize}; use super::JsonError; -use super::{BatteryJson, CpuJson, GpuJson}; +use super::{BatteryJson, CpuJson, GpuJson, DriverJson}; #[derive(Serialize, Deserialize)] pub struct SettingsJson { @@ -13,6 +13,7 @@ pub struct SettingsJson { pub cpus: Vec, pub gpu: GpuJson, pub battery: BatteryJson, + pub provider: Option, } impl Default for SettingsJson { @@ -24,6 +25,7 @@ impl Default for SettingsJson { cpus: Vec::with_capacity(8), gpu: GpuJson::default(), battery: BatteryJson::default(), + provider: None, } } } @@ -54,7 +56,7 @@ impl SettingsJson { } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct MinMaxJson { pub max: T, pub min: T, diff --git a/backend/src/persist/gpu.rs b/backend/src/persist/gpu.rs index ecb352f..933f2c4 100644 --- a/backend/src/persist/gpu.rs +++ b/backend/src/persist/gpu.rs @@ -4,7 +4,7 @@ use std::default::Default; use super::MinMaxJson; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct GpuJson { pub fast_ppt: Option, pub slow_ppt: Option, diff --git a/backend/src/persist/mod.rs b/backend/src/persist/mod.rs index 5b70ff5..ef9a677 100644 --- a/backend/src/persist/mod.rs +++ b/backend/src/persist/mod.rs @@ -1,11 +1,13 @@ mod battery; mod cpu; +mod driver; mod error; mod general; mod gpu; pub use battery::BatteryJson; pub use cpu::CpuJson; +pub use driver::DriverJson; pub use general::{MinMaxJson, SettingsJson}; pub use gpu::GpuJson; diff --git a/backend/src/resume_worker.rs b/backend/src/resume_worker.rs index 9560d84..2c9279d 100644 --- a/backend/src/resume_worker.rs +++ b/backend/src/resume_worker.rs @@ -1,12 +1,13 @@ use std::thread::{self, JoinHandle}; use std::time::{Duration, Instant}; +use std::sync::mpsc::Sender; -use crate::settings::{OnResume, Settings}; -use crate::utility::unwrap_maybe_fatal; +use crate::api::handler::ApiMessage; +//use crate::utility::unwrap_maybe_fatal; const ALLOWED_ERROR: f64 = 100.0; // period of 10ms with 100x means sleep has to be >= 1s to be detected -pub fn spawn(settings: Settings) -> JoinHandle<()> { +pub fn spawn(sender: Sender) -> JoinHandle<()> { thread::spawn(move || { log::info!("resume_worker starting..."); let duration = Duration::from_millis(10); // very low so it detects before Steam client does @@ -18,7 +19,7 @@ pub fn spawn(settings: Settings) -> JoinHandle<()> { if old_start.as_secs_f64() > duration.as_secs_f64() * (1.0 + ALLOWED_ERROR) { // has just resumed from sleep log::info!("Resume detected"); - unwrap_maybe_fatal(settings.on_resume(), "On resume failure"); + sender.send(ApiMessage::OnResume).expect("resume_worker send failed"); log::debug!( "OnResume completed after sleeping for {}s", old_start.as_secs_f32() diff --git a/backend/src/settings/battery.rs b/backend/src/settings/battery.rs deleted file mode 100644 index ae0dd63..0000000 --- a/backend/src/settings/battery.rs +++ /dev/null @@ -1,188 +0,0 @@ -use std::convert::Into; - -use super::{OnResume, OnSet, SettingError, SettingsRange}; -use crate::persist::BatteryJson; - -#[derive(Debug, Clone)] -pub struct Battery { - pub charge_rate: Option, - state: crate::state::Battery, -} - -const BATTERY_VOLTAGE: f64 = 7.7; - -const BATTERY_CHARGE_RATE_PATH: &str = "/sys/class/hwmon/hwmon5/maximum_battery_charge_rate"; // write-only -const BATTERY_CURRENT_NOW_PATH: &str = "/sys/class/power_supply/BAT1/current_now"; // read-only -const BATTERY_CHARGE_NOW_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_now"; // read-only -const BATTERY_CHARGE_FULL_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full"; // read-only -const BATTERY_CHARGE_DESIGN_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full_design"; // read-only - -impl Battery { - #[inline] - pub fn from_json(other: BatteryJson, version: u64) -> Self { - match version { - 0 => Self { - charge_rate: other.charge_rate, - state: crate::state::Battery::default(), - }, - _ => Self { - charge_rate: other.charge_rate, - state: crate::state::Battery::default(), - }, - } - } - - fn set_all(&mut self) -> Result<(), SettingError> { - if let Some(charge_rate) = self.charge_rate { - self.state.charge_rate_set = true; - usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate).map_err( - |e| SettingError { - msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), - setting: super::SettingVariant::Battery, - }, - ) - } else if self.state.charge_rate_set { - self.state.charge_rate_set = false; - usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, Self::max().charge_rate.unwrap()).map_err( - |e| SettingError { - msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), - setting: super::SettingVariant::Battery, - }, - ) - } else { - Ok(()) - } - } - - fn clamp_all(&mut self) { - let min = Self::min(); - let max = Self::max(); - if let Some(charge_rate) = &mut self.charge_rate { - *charge_rate = (*charge_rate).clamp(min.charge_rate.unwrap(), max.charge_rate.unwrap()); - } - } - - pub fn read_current_now() -> Result { - match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) { - Err((Some(e), None)) => Err(SettingError { - msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), - setting: super::SettingVariant::Battery, - }), - Err((None, Some(e))) => Err(SettingError { - msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), - setting: super::SettingVariant::Battery, - }), - Err(_) => panic!( - "Invalid error while reading from `{}`", - BATTERY_CURRENT_NOW_PATH - ), - // this value is in uA, while it's set in mA - // so convert this to mA for consistency - Ok(val) => Ok(val / 1000), - } - } - - pub fn read_charge_now() -> Result { - match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) { - Err((Some(e), None)) => Err(SettingError { - msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), - setting: super::SettingVariant::Battery, - }), - Err((None, Some(e))) => Err(SettingError { - msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), - setting: super::SettingVariant::Battery, - }), - Err(_) => panic!( - "Invalid error while reading from `{}`", - BATTERY_CHARGE_NOW_PATH - ), - // convert to Wh - Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE), - } - } - - pub fn read_charge_full() -> Result { - match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) { - Err((Some(e), None)) => Err(SettingError { - msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), - setting: super::SettingVariant::Battery, - }), - Err((None, Some(e))) => Err(SettingError { - msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), - setting: super::SettingVariant::Battery, - }), - Err(_) => panic!( - "Invalid error while reading from `{}`", - BATTERY_CHARGE_NOW_PATH - ), - // convert to Wh - Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE), - } - } - - pub fn read_charge_design() -> Result { - match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) { - Err((Some(e), None)) => Err(SettingError { - msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e), - setting: super::SettingVariant::Battery, - }), - Err((None, Some(e))) => Err(SettingError { - msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e), - setting: super::SettingVariant::Battery, - }), - Err(_) => panic!( - "Invalid error while reading from `{}`", - BATTERY_CHARGE_NOW_PATH - ), - // convert to Wh - Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE), - } - } - - pub fn system_default() -> Self { - Self { - charge_rate: None, - state: crate::state::Battery::default(), - } - } -} - -impl Into for Battery { - #[inline] - fn into(self) -> BatteryJson { - BatteryJson { - charge_rate: self.charge_rate, - } - } -} - -impl OnSet for Battery { - fn on_set(&mut self) -> Result<(), SettingError> { - self.clamp_all(); - self.set_all() - } -} - -impl OnResume for Battery { - fn on_resume(&self) -> Result<(), SettingError> { - self.clone().set_all() - } -} - -impl SettingsRange for Battery { - #[inline] - fn max() -> Self { - Self { - charge_rate: Some(2500), - state: crate::state::Battery::default(), - } - } - - #[inline] - fn min() -> Self { - Self { - charge_rate: Some(250), - state: crate::state::Battery::default(), - } - } -} diff --git a/backend/src/settings/cpu.rs b/backend/src/settings/cpu.rs deleted file mode 100644 index dd27ab7..0000000 --- a/backend/src/settings/cpu.rs +++ /dev/null @@ -1,256 +0,0 @@ -use std::convert::Into; - -use super::MinMax; -use super::{OnResume, OnSet, SettingError, SettingsRange}; -use crate::persist::CpuJson; - -#[derive(Debug, Clone)] -pub struct Cpu { - pub online: bool, - pub clock_limits: Option>, - pub governor: String, - index: usize, - state: crate::state::Cpu, -} - -const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; -const CPU_FORCE_LIMITS_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level"; - -const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; - -impl Cpu { - #[inline] - pub fn from_json(other: CpuJson, version: u64, i: usize) -> Self { - match version { - 0 => Self { - online: other.online, - clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), - governor: other.governor, - index: i, - state: crate::state::Cpu::default(), - }, - _ => Self { - online: other.online, - clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), - governor: other.governor, - index: i, - state: crate::state::Cpu::default(), - }, - } - } - - fn set_all(&mut self) -> Result<(), SettingError> { - // set cpu online/offline - if self.index != 0 { // cpu0 cannot be disabled - let online_path = cpu_online_path(self.index); - usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| { - SettingError { - msg: format!("Failed to write to `{}`: {}", &online_path, e), - setting: super::SettingVariant::Cpu, - } - })?; - } - // set clock limits - log::debug!("Setting {} to manual", CPU_FORCE_LIMITS_PATH); - let mode: String = usdpl_back::api::files::read_single(CPU_FORCE_LIMITS_PATH.to_owned()).unwrap(); - if mode != "manual" { - // set manual control - usdpl_back::api::files::write_single(CPU_FORCE_LIMITS_PATH, "manual").map_err(|e| { - SettingError { - msg: format!( - "Failed to write `manual` to `{}`: {}", - CPU_FORCE_LIMITS_PATH, e - ), - setting: super::SettingVariant::Cpu, - } - })?; - } - if let Some(clock_limits) = &self.clock_limits { - log::debug!("Setting CPU {} (min, max) clockspeed to ({}, {})", self.index, clock_limits.min, clock_limits.max); - self.state.clock_limits_set = true; - // max clock - let payload_max = format!("p {} 1 {}\n", self.index / 2, clock_limits.max); - usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err( - |e| SettingError { - msg: format!( - "Failed to write `{}` to `{}`: {}", - &payload_max, CPU_CLOCK_LIMITS_PATH, e - ), - setting: super::SettingVariant::Cpu, - }, - )?; - // min clock - let payload_min = format!("p {} 0 {}\n", self.index / 2, clock_limits.min); - usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err( - |e| SettingError { - msg: format!( - "Failed to write `{}` to `{}`: {}", - &payload_min, CPU_CLOCK_LIMITS_PATH, e - ), - setting: super::SettingVariant::Cpu, - }, - )?; - } else if self.state.clock_limits_set || self.state.is_resuming { - self.state.clock_limits_set = false; - // disable manual clock limits - log::debug!("Setting CPU {} to default clockspeed", self.index); - // max clock - let payload_max = format!("p {} 1 {}\n", self.index / 2, Self::max().clock_limits.unwrap().max); - usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err( - |e| SettingError { - msg: format!( - "Failed to write `{}` to `{}`: {}", - &payload_max, CPU_CLOCK_LIMITS_PATH, e - ), - setting: super::SettingVariant::Cpu, - }, - )?; - // min clock - let payload_min = format!("p {} 0 {}\n", self.index / 2, Self::min().clock_limits.unwrap().min); - usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err( - |e| SettingError { - msg: format!( - "Failed to write `{}` to `{}`: {}", - &payload_min, CPU_CLOCK_LIMITS_PATH, e - ), - setting: super::SettingVariant::Cpu, - }, - )?; - } - // commit changes - usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| { - SettingError { - msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e), - setting: super::SettingVariant::Cpu, - } - })?; - - // set governor - if self.index == 0 || self.online { - let governor_path = cpu_governor_path(self.index); - usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| { - SettingError { - msg: format!( - "Failed to write `{}` to `{}`: {}", - &self.governor, &governor_path, e - ), - setting: super::SettingVariant::Cpu, - } - })?; - } - Ok(()) - } - - fn clamp_all(&mut self) { - let min = Self::min(); - let max = Self::max(); - if let Some(clock_limits) = &mut self.clock_limits { - let max_boost = max.clock_limits.as_ref().unwrap(); - let min_boost = min.clock_limits.as_ref().unwrap(); - clock_limits.min = clock_limits.min.clamp(min_boost.min, max_boost.min); - clock_limits.max = clock_limits.max.clamp(min_boost.max, max_boost.max); - } - } - - fn from_sys(index: usize) -> Self { - Self { - online: usdpl_back::api::files::read_single(cpu_online_path(index)).unwrap_or(1u8) != 0, - clock_limits: None, - governor: usdpl_back::api::files::read_single(cpu_governor_path(index)) - .unwrap_or("schedutil".to_owned()), - index: index, - state: crate::state::Cpu::default(), - } - } - - pub fn cpu_count() -> Option { - let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH) - .unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */); - if let Some(dash_index) = data.find('-') { - let data = data.split_off(dash_index + 1); - if let Ok(max_cpu) = data.parse::() { - return Some(max_cpu + 1); - } - } - log::warn!("Failed to parse CPU info from kernel, is Tux evil?"); - None - } - - pub fn system_default() -> Vec { - if let Some(max_cpu) = Self::cpu_count() { - let mut cpus = Vec::with_capacity(max_cpu); - for i in 0..max_cpu { - cpus.push(Self::from_sys(i)); - } - cpus - } else { - Vec::with_capacity(0) - } - } -} - -impl Into for Cpu { - #[inline] - fn into(self) -> CpuJson { - CpuJson { - online: self.online, - clock_limits: self.clock_limits.map(|x| x.into()), - governor: self.governor, - } - } -} - -impl OnSet for Cpu { - fn on_set(&mut self) -> Result<(), SettingError> { - self.clamp_all(); - self.set_all() - } -} - -impl OnResume for Cpu { - fn on_resume(&self) -> Result<(), SettingError> { - let mut copy = self.clone(); - copy.state.is_resuming = true; - copy.set_all() - } -} - -impl SettingsRange for Cpu { - #[inline] - fn max() -> Self { - Self { - online: true, - clock_limits: Some(MinMax { - max: 3500, - min: 3500, - }), - governor: "schedutil".to_owned(), - index: usize::MAX, - state: crate::state::Cpu::default(), - } - } - - #[inline] - fn min() -> Self { - Self { - online: false, - clock_limits: Some(MinMax { max: 500, min: 1400 }), - governor: "schedutil".to_owned(), - index: usize::MIN, - state: crate::state::Cpu::default(), - } - } -} - -#[inline] -fn cpu_online_path(index: usize) -> String { - format!("/sys/devices/system/cpu/cpu{}/online", index) -} - -#[inline] -fn cpu_governor_path(index: usize) -> String { - format!( - "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor", - index - ) -} diff --git a/backend/src/settings/detect/auto_detect.rs b/backend/src/settings/detect/auto_detect.rs new file mode 100644 index 0000000..d048b8a --- /dev/null +++ b/backend/src/settings/detect/auto_detect.rs @@ -0,0 +1,204 @@ +use std::fs::File; + +use regex::RegexBuilder; + +use limits_core::json::{Limits, BatteryLimit, CpuLimit, GpuLimit}; + +use crate::persist::{DriverJson, SettingsJson}; +use crate::settings::{TGeneral, TCpus, TGpu, TBattery, Driver, General}; + +fn get_limits() -> limits_core::json::Base { + let limits_path = super::utility::limits_path(); + match File::open(&limits_path) { + Ok(f) => { + match serde_json::from_reader(f) { + Ok(lim) => lim, + Err(e) => { + log::warn!("Failed to parse limits file `{}`, cannot use for auto_detect: {}", limits_path.display(), e); + limits_core::json::Base::default() + } + } + }, + Err(e) => { + log::warn!("Failed to open limits file `{}` (trying force refresh...): {}", limits_path.display(), e); + super::limits_worker::get_limits_blocking() + } + } +} + +#[inline] +pub fn auto_detect_provider() -> DriverJson { + let provider = auto_detect0(None, crate::utility::settings_dir().join("autodetect.json")) + .battery + .provider(); + //log::info!("Detected device automatically, compatible driver: {:?}", provider); + provider +} + +/// Device detection logic +pub fn auto_detect0(settings_opt: Option, json_path: std::path::PathBuf) -> Driver { + let mut builder = DriverBuilder::new(json_path); + + let cpu_info: String = usdpl_back::api::files::read_single("/proc/cpuinfo").unwrap_or_default(); + log::debug!("Read from /proc/cpuinfo:\n{}", cpu_info); + let os_info: String = usdpl_back::api::files::read_single("/etc/os-release").unwrap_or_default(); + log::debug!("Read from /etc/os-release:\n{}", os_info); + let dmi_info: String = std::process::Command::new("dmidecode").output().map(|out| String::from_utf8_lossy(&out.stdout).into_owned()).unwrap_or_default(); + log::debug!("Read dmidecode:\n{}", dmi_info); + + let limits = get_limits(); + + // build driver based on limits conditions + for conf in limits.configs { + let conditions = conf.conditions; + let mut matches = true; + if conditions.is_empty() { + matches = !builder.is_complete(); + } else { + if let Some(dmi) = &conditions.dmi { + let pattern = RegexBuilder::new(dmi) + .multi_line(true) + .build() + .expect("Invalid DMI regex"); + matches &=pattern.is_match(&dmi_info); + } + if let Some(cpuinfo) = &conditions.cpuinfo { + let pattern = RegexBuilder::new(cpuinfo) + .multi_line(true) + .build() + .expect("Invalid CPU regex"); + matches &=pattern.is_match(&cpu_info); + } + if let Some(os) = &conditions.os { + let pattern = RegexBuilder::new(os) + .multi_line(true) + .build() + .expect("Invalid OS regex"); + matches &=pattern.is_match(&os_info); + } + if let Some(cmd) = &conditions.command { + match std::process::Command::new("bash") + .args(["-c", cmd]) + .status() { + Ok(status) => matches &= status.code().map(|c| c == 0).unwrap_or(false), + Err(e) => log::warn!("Ignoring bash limits error: {}", e), + } + } + if let Some(file_exists) = &conditions.file_exists { + let exists = std::path::Path::new(file_exists).exists(); + matches &= exists; + } + } + if matches { + if let Some(settings) = &settings_opt { + *builder.general.persistent() = true; + for limit in conf.limits { + match limit { + Limits::Cpu(cpus) => { + let cpu_driver: Box = 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::::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); + } + } + } + } 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::::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); + } + } + } + } + + } + } + + builder.build() +} + +struct DriverBuilder { + general: Box, + cpus: Option>, + gpu: Option>, + battery: Option>, +} + +impl DriverBuilder { + fn new(json_path: std::path::PathBuf) -> Self { + Self { + general: Box::new(General { + persistent: false, + path: json_path, + name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), + driver: DriverJson::AutoDetect, + }), + cpus: None, + gpu: None, + battery: None, + } + } + + fn is_complete(&self) -> bool { + self.cpus.is_some() && self.gpu.is_some() && self.battery.is_some() + } + + fn build(self) -> Driver { + Driver { + general: self.general, + cpus: self.cpus.unwrap_or_else(|| Box::new(crate::settings::unknown::Cpus::system_default())), + gpu: self.gpu.unwrap_or_else(|| Box::new(crate::settings::unknown::Gpu::system_default())), + battery: self.battery.unwrap_or_else(|| Box::new(crate::settings::unknown::Battery)) + } + } +} diff --git a/backend/src/settings/detect/limits_worker.rs b/backend/src/settings/detect/limits_worker.rs new file mode 100644 index 0000000..ef1f0f2 --- /dev/null +++ b/backend/src/settings/detect/limits_worker.rs @@ -0,0 +1,119 @@ +use std::thread::{self, JoinHandle}; +#[cfg(feature = "online")] +use std::time::Duration; + +use limits_core::json::Base; + +#[cfg(feature = "online")] +pub fn spawn() -> JoinHandle<()> { + thread::spawn(move || { + log::info!("limits_worker starting..."); + let sleep_dur = Duration::from_secs(60*60*24); // 1 day + let limits_path = super::utility::limits_path(); + loop { + thread::sleep(sleep_dur); + if (limits_path.exists() && limits_path.is_file()) || !limits_path.exists() { + // try to load limits from file, fallback to built-in default + let base = match std::fs::File::open(&limits_path) { + Ok(f) => { + match serde_json::from_reader(f) { + Ok(b) => b, + Err(e) => { + log::error!("Cannot parse {}: {}", limits_path.display(), e); + Base::default() + } + } + }, + Err(e) => { + log::error!("Cannot open {}: {}", limits_path.display(), e); + Base::default() + } + }; + if let Some(refresh) = &base.refresh { + // try to retrieve newer version + match ureq::get(refresh) + .call() { + Ok(response) => { + let json_res: std::io::Result = response.into_json(); + match json_res { + Ok(new_base) => { + save_base(&new_base, &limits_path); + }, + Err(e) => log::error!("Cannot parse response from `{}`: {}", refresh, e), + } + }, + Err(e) => log::warn!("Cannot download limits from `{}`: {}", refresh, e), + } + } else { + log::info!("limits_worker refresh is empty, terminating..."); + break; + } + } else if !limits_path.is_file() { + log::error!("Path for storing limits is not a file!"); + } + } + log::warn!("limits_worker completed!"); + }) +} + +#[cfg(not(feature = "online"))] +pub fn spawn() -> JoinHandle<()> { + thread::spawn(move || { + log::info!("limits_worker disabled..."); + }) +} + +pub fn get_limits_blocking() -> Base { + let limits_path = super::utility::limits_path(); + if limits_path.is_file() { + match std::fs::File::open(&limits_path) { + Ok(f) => { + match serde_json::from_reader(f) { + Ok(b) => b, + Err(e) => { + log::error!("Cannot parse {}: {}", limits_path.display(), e); + Base::default() + } + } + }, + Err(e) => { + log::error!("Cannot open {}: {}", limits_path.display(), e); + Base::default() + } + } + } else { + #[cfg(feature = "online")] + { + let refresh = Base::default().refresh.unwrap(); + match ureq::get(&refresh) // try to retrieve newer version + .call() { + Ok(response) => { + let json_res: std::io::Result = response.into_json(); + match json_res { + Ok(new_base) => { + save_base(&new_base, &limits_path); + return new_base; + }, + Err(e) => log::error!("Cannot parse response from `{}`: {}", refresh, e) + } + }, + Err(e) => log::warn!("Cannot download limits from `{}`: {}", refresh, e), + } + } + Base::default() + } +} + +#[cfg(feature = "online")] +fn save_base(new_base: &Base, path: impl AsRef) { + let limits_path = path.as_ref(); + match std::fs::File::create(&limits_path) { + Ok(f) => { + match serde_json::to_writer_pretty(f, &new_base) { + Ok(_) => log::info!("Successfully saved new limits to {}", limits_path.display()), + Err(e) => log::error!("Failed to save limits json to file `{}`: {}", limits_path.display(), e), + } + }, + Err(e) => log::error!("Cannot create {}: {}", limits_path.display(), e) + } +} diff --git a/backend/src/settings/detect/mod.rs b/backend/src/settings/detect/mod.rs new file mode 100644 index 0000000..1bb27fc --- /dev/null +++ b/backend/src/settings/detect/mod.rs @@ -0,0 +1,5 @@ +mod auto_detect; +pub mod limits_worker; +mod utility; + +pub use auto_detect::{auto_detect_provider, auto_detect0}; diff --git a/backend/src/settings/detect/utility.rs b/backend/src/settings/detect/utility.rs new file mode 100644 index 0000000..16aec42 --- /dev/null +++ b/backend/src/settings/detect/utility.rs @@ -0,0 +1,3 @@ +pub fn limits_path() -> std::path::PathBuf { + crate::utility::settings_dir().join(crate::consts::LIMITS_FILE) +} diff --git a/backend/src/settings/driver.rs b/backend/src/settings/driver.rs new file mode 100644 index 0000000..59deec3 --- /dev/null +++ b/backend/src/settings/driver.rs @@ -0,0 +1,80 @@ +use crate::persist::{DriverJson, SettingsJson}; +use super::{TGeneral, TCpus, TGpu, TBattery, SettingError, General, auto_detect0}; + +pub struct Driver { + pub general: Box, + pub cpus: Box, + pub gpu: Box, + pub battery: Box, +} + +impl Driver { + pub fn init(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, + }), + cpus: Box::new(super::steam_deck::Cpus::from_json(settings.cpus, settings.version)), + gpu: Box::new(super::steam_deck::Gpu::from_json(settings.gpu, settings.version)), + battery: Box::new(super::steam_deck::Battery::from_json(settings.battery, settings.version)), + }, + }) + } + + fn version0(settings: SettingsJson, json_path: std::path::PathBuf) -> Result { + if let Some(provider) = &settings.provider { + match provider { + DriverJson::SteamDeck => Ok(Self { + general: Box::new(General { + persistent: settings.persistent, + path: json_path, + name: settings.name, + driver: DriverJson::SteamDeck, + }), + cpus: Box::new(super::steam_deck::Cpus::from_json(settings.cpus, settings.version)), + gpu: Box::new(super::steam_deck::Gpu::from_json(settings.gpu, settings.version)), + battery: Box::new(super::steam_deck::Battery::from_json(settings.battery, settings.version)), + }), + // There's nothing special about SteamDeckAdvance, it just appears different + DriverJson::SteamDeckAdvance => Ok(Self { + general: Box::new(General { + persistent: settings.persistent, + path: json_path, + name: settings.name, + driver: DriverJson::SteamDeckAdvance, + }), + cpus: Box::new(super::steam_deck::Cpus::from_json(settings.cpus, settings.version)), + gpu: Box::new(super::steam_deck::Gpu::from_json(settings.gpu, settings.version)), + battery: Box::new(super::steam_deck::Battery::from_json(settings.battery, settings.version)), + }), + DriverJson::Generic | DriverJson::GenericAMD => Ok(super::detect::auto_detect0(Some(settings), json_path)), + DriverJson::Unknown => Ok(super::detect::auto_detect0(Some(settings), json_path)), + DriverJson::AutoDetect => Ok(super::detect::auto_detect0(Some(settings), json_path)), + } + } else { + Ok(super::detect::auto_detect0(Some(settings), json_path)) + } + } + + pub fn system_default(json_path: std::path::PathBuf) -> Self { + auto_detect0(None, json_path) + } +} + +// sshhhh, this function isn't here ;) +#[inline] +pub fn maybe_do_button() { + match super::auto_detect_provider() { + DriverJson::SteamDeck | DriverJson::SteamDeckAdvance => { + crate::settings::steam_deck::flash_led(); + }, + DriverJson::Generic | DriverJson::GenericAMD => log::warn!("You need to come up with something fun on generic"), + DriverJson::Unknown => log::warn!("Can't do button activities on unknown platform"), + DriverJson::AutoDetect => log::warn!("WTF, why is auto_detect detecting AutoDetect???") + } +} diff --git a/backend/src/settings/general.rs b/backend/src/settings/general.rs index 5c40ea4..42eecfa 100644 --- a/backend/src/settings/general.rs +++ b/backend/src/settings/general.rs @@ -1,11 +1,11 @@ -use std::convert::Into; use std::path::PathBuf; -use std::sync::{Arc, Mutex}; +//use std::sync::{Arc, Mutex}; -use super::{Battery, Cpu, Gpu}; +//use super::{Battery, Cpus, Gpu}; use super::{OnResume, OnSet, SettingError}; -use crate::persist::{CpuJson, SettingsJson}; -use crate::utility::unwrap_lock; +use super::{TGeneral, TGpu, TCpus, TBattery}; +use crate::persist::SettingsJson; +//use crate::utility::unwrap_lock; const LATEST_VERSION: u64 = 0; @@ -33,6 +33,7 @@ pub struct General { pub persistent: bool, pub path: PathBuf, pub name: String, + pub driver: crate::persist::DriverJson, } impl OnSet for General { @@ -41,26 +42,60 @@ impl OnSet for General { } } -#[derive(Debug, Clone)] +impl OnResume for General { + fn on_resume(&self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl TGeneral for General { + fn limits(&self) -> crate::api::GeneralLimits { + crate::api::GeneralLimits { } + } + + fn get_persistent(&self) -> bool { + self.persistent + } + + fn persistent(&mut self) -> &'_ mut bool { + &mut self.persistent + } + + fn get_path(&self) -> &'_ std::path::Path { + &self.path + } + + fn path(&mut self, path: std::path::PathBuf) { + self.path = path; + } + + fn get_name(&self) -> &'_ str { + &self.name + } + + fn name(&mut self, name: String) { + self.name = name; + } + + fn provider(&self) -> crate::persist::DriverJson { + self.driver.clone() + } +} + +#[derive(Debug)] pub struct Settings { - pub general: Arc>, - pub cpus: Arc>>, - pub gpu: Arc>, - pub battery: Arc>, + pub general: Box, + pub cpus: Box, + pub gpu: Box, + pub battery: Box, } impl OnSet for Settings { fn on_set(&mut self) -> Result<(), SettingError> { - unwrap_lock(self.battery.lock(), "battery").on_set()?; - { - // cpu lock scope - let mut cpu_lock = unwrap_lock(self.cpus.lock(), "cpu"); - for cpu in cpu_lock.iter_mut() { - cpu.on_set()?; - } - } - unwrap_lock(self.gpu.lock(), "gpu").on_set()?; - unwrap_lock(self.general.lock(), "general").on_set()?; + self.battery.on_set()?; + self.cpus.on_set()?; + self.gpu.on_set()?; + self.general.on_set()?; Ok(()) } } @@ -68,84 +103,42 @@ impl OnSet for Settings { impl Settings { #[inline] pub fn from_json(other: SettingsJson, json_path: PathBuf) -> Self { - match other.version { - 0 => Self { - general: Arc::new(Mutex::new(General { - persistent: other.persistent, - path: json_path, - name: other.name, - })), - cpus: Arc::new(Mutex::new(Self::convert_cpus(other.cpus, other.version))), - gpu: Arc::new(Mutex::new(Gpu::from_json(other.gpu, other.version))), - battery: Arc::new(Mutex::new(Battery::from_json(other.battery, other.version))), - }, - _ => Self { - general: Arc::new(Mutex::new(General { - persistent: other.persistent, - path: json_path, - name: other.name, - })), - cpus: Arc::new(Mutex::new(Self::convert_cpus(other.cpus, other.version))), - gpu: Arc::new(Mutex::new(Gpu::from_json(other.gpu, other.version))), - battery: Arc::new(Mutex::new(Battery::from_json(other.battery, other.version))), - }, - } - } - - fn convert_cpus(mut cpus: Vec, version: u64) -> Vec { - let mut result = Vec::with_capacity(cpus.len()); - let max_cpus = Cpu::cpu_count(); - for (i, cpu) in cpus.drain(..).enumerate() { - // prevent having more CPUs than available - if let Some(max_cpus) = max_cpus { - if i == max_cpus { - break; - } - } - result.push(Cpu::from_json(cpu, version, i)); - } - if let Some(max_cpus) = max_cpus { - if result.len() != max_cpus { - let mut sys_cpus = Cpu::system_default(); - for i in result.len()..sys_cpus.len() { - result.push(sys_cpus.remove(i)); + match super::Driver::init(other, json_path.clone()) { + Ok(x) => { + log::info!("Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", x.general.provider(), x.cpus.provider(), x.gpu.provider(), x.battery.provider()); + Self { + general: x.general, + cpus: x.cpus, + gpu: x.gpu, + battery: x.battery, } + }, + Err(e) => { + log::error!("Driver init error: {}", e); + Self::system_default(json_path) } } - result } pub fn system_default(json_path: PathBuf) -> Self { + let driver = super::Driver::system_default(json_path); Self { - general: Arc::new(Mutex::new(General { - persistent: false, - path: json_path, - name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), - })), - cpus: Arc::new(Mutex::new(Cpu::system_default())), - gpu: Arc::new(Mutex::new(Gpu::system_default())), - battery: Arc::new(Mutex::new(Battery::system_default())), + general: driver.general, + cpus: driver.cpus, + gpu: driver.gpu, + battery: driver.battery, } } - fn load_system_default(&self) { - { - let mut cpu_lock = unwrap_lock(self.cpus.lock(), "cpu"); - *cpu_lock = Cpu::system_default(); - } - { - let mut gpu_lock = unwrap_lock(self.gpu.lock(), "gpu"); - *gpu_lock = Gpu::system_default(); - } - { - let mut battery_lock = unwrap_lock(self.battery.lock(), "battery"); - *battery_lock = Battery::system_default(); - } + pub fn load_system_default(&mut self) { + let driver = super::Driver::system_default(self.general.get_path().to_owned()); + self.cpus = driver.cpus; + self.gpu = driver.gpu; + self.battery = driver.battery; } - - pub fn load_file(&self, filename: PathBuf, name: String, system_defaults: bool) -> Result { + + pub fn load_file(&mut self, filename: PathBuf, name: String, system_defaults: bool) -> Result { let json_path = crate::utility::settings_dir().join(filename); - let mut general_lock = unwrap_lock(self.general.lock(), "general"); if json_path.exists() { let settings_json = SettingsJson::open(&json_path).map_err(|e| SettingError { msg: e.to_string(), @@ -153,80 +146,106 @@ impl Settings { })?; if !settings_json.persistent { log::warn!("Loaded persistent config `{}` ({}) with persistent=false", &settings_json.name, json_path.display()); - general_lock.persistent = false; - general_lock.name = name; + *self.general.persistent() = false; + self.general.name(name); } else { - let new_cpus = Self::convert_cpus(settings_json.cpus, settings_json.version); - let new_gpu = Gpu::from_json(settings_json.gpu, settings_json.version); - let new_battery = Battery::from_json(settings_json.battery, settings_json.version); - { - let mut cpu_lock = unwrap_lock(self.cpus.lock(), "cpu"); - *cpu_lock = new_cpus; - } - { - let mut gpu_lock = unwrap_lock(self.gpu.lock(), "gpu"); - *gpu_lock = new_gpu; - } - { - let mut battery_lock = unwrap_lock(self.battery.lock(), "battery"); - *battery_lock = new_battery; - } - general_lock.persistent = true; - general_lock.name = settings_json.name; + match super::Driver::init(settings_json, json_path.clone()) { + Ok(x) => { + log::info!("Loaded settings with drivers general:{:?},cpus:{:?},gpu:{:?},battery:{:?}", x.general.provider(), x.cpus.provider(), x.gpu.provider(), x.battery.provider()); + self.general = x.general; + self.cpus = x.cpus; + self.gpu = x.gpu; + self.battery = x.battery; + }, + Err(e) => { + log::error!("Driver init error: {}", e); + self.general.name(name); + *self.general.persistent() = false; + self.general.path(json_path); + return Err(e); + } + }; } } else { if system_defaults { self.load_system_default(); } - general_lock.persistent = false; - general_lock.name = name; + *self.general.persistent() = false; + self.general.name(name); + } + self.general.path(json_path); + Ok(*self.general.persistent()) + } + + /* + pub fn load_file(&mut self, filename: PathBuf, name: String, system_defaults: bool) -> Result { + let json_path = crate::utility::settings_dir().join(filename); + //let mut general_lock = unwrap_lock(self.general.lock(), "general"); + if json_path.exists() { + let settings_json = SettingsJson::open(&json_path).map_err(|e| SettingError { + msg: e.to_string(), + setting: SettingVariant::General, + })?; + if !settings_json.persistent { + log::warn!("Loaded persistent config `{}` ({}) with persistent=false", &settings_json.name, json_path.display()); + *self.general.persistent() = false; + self.general.name(name); + } else { + self.cpus = Box::new(super::steam_deck::Cpus::from_json(settings_json.cpus, settings_json.version)); + self.gpu = Box::new(super::steam_deck::Gpu::from_json(settings_json.gpu, settings_json.version)); + self.battery = Box::new(super::steam_deck::Battery::from_json(settings_json.battery, settings_json.version)); + *self.general.persistent() = true; + self.general.name(settings_json.name); + } + } else { + if system_defaults { + self.load_system_default(); + } + *self.general.persistent() = false; + self.general.name(name); + } + self.general.path(json_path); + Ok(*self.general.persistent()) + }*/ + + pub fn json(&self) -> SettingsJson { + SettingsJson { + version: LATEST_VERSION, + name: self.general.get_name().to_owned(), + persistent: self.general.get_persistent(), + cpus: self.cpus.json(), + gpu: self.gpu.json(), + battery: self.battery.json(), + provider: Some(self.general.provider()), } - general_lock.path = json_path; - Ok(general_lock.persistent) } } impl OnResume for Settings { fn on_resume(&self) -> Result<(), SettingError> { - log::debug!("Locking settings for on_resume"); - unwrap_lock(self.battery.lock(), "battery").on_resume()?; - log::debug!("Got battery lock"); - { - let cpu_lock = unwrap_lock(self.cpus.lock(), "cpu"); - log::debug!("Got cpus lock"); - for cpu in cpu_lock.iter() { - cpu.on_resume()?; - } - } - unwrap_lock(self.gpu.lock(), "gpu").on_resume()?; - log::debug!("Got gpu lock"); + log::debug!("Applying settings for on_resume"); + self.battery.on_resume()?; + log::debug!("Resumed battery"); + self.cpus.on_resume()?; + log::debug!("Resumed CPUs"); + self.gpu.on_resume()?; + log::debug!("Resumed GPU"); Ok(()) } } -impl Into for Settings { +/*impl Into for Settings { #[inline] fn into(self) -> SettingsJson { - log::debug!("Locking settings to convert into json"); - let gen_lock = unwrap_lock(self.general.lock(), "general"); - log::debug!("Got general lock"); - let cpu_lock = unwrap_lock(self.cpus.lock(), "cpu"); - log::debug!("Got cpus lock"); - let gpu_lock = unwrap_lock(self.gpu.lock(), "gpu"); - log::debug!("Got gpu lock"); - let batt_lock = unwrap_lock(self.battery.lock(), "battery"); - log::debug!("Got battery lock"); + log::debug!("Converting into json"); SettingsJson { version: LATEST_VERSION, - name: gen_lock.name.clone(), - persistent: gen_lock.persistent, - cpus: cpu_lock - .clone() - .drain(..) - .map(|cpu| cpu.into()) - .collect(), - gpu: gpu_lock.clone().into(), - battery: batt_lock.clone().into(), + name: self.general.get_name().to_owned(), + persistent: self.general.get_persistent(), + cpus: self.cpus.json(), + gpu: self.gpu.json(), + battery: self.battery.json(), + provider: Some(self.general.provider()), } } -} +}*/ diff --git a/backend/src/settings/generic/battery.rs b/backend/src/settings/generic/battery.rs new file mode 100644 index 0000000..1555f4a --- /dev/null +++ b/backend/src/settings/generic/battery.rs @@ -0,0 +1,130 @@ +use std::convert::Into; + +use limits_core::json::GenericBatteryLimit; + +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::TBattery; +use crate::persist::BatteryJson; + +#[derive(Debug, Clone)] +pub struct Battery { + #[allow(dead_code)] + limits: GenericBatteryLimit, +} + +impl Into for Battery { + #[inline] + fn into(self) -> BatteryJson { + BatteryJson { + charge_rate: None, + charge_mode: None, + } + } +} + +impl Battery { + fn read_f64>(path: P) -> Result { + let path = path.as_ref(); + match usdpl_back::api::files::read_single::<_, f64, _>(path) { + Err(e) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", path.display(), e), + setting: crate::settings::SettingVariant::Battery, + }), + // this value is in uA, while it's set in mA + // so convert this to mA for consistency + Ok(val) => Ok(val / 1000.0), + } + } + + pub fn from_limits(limits: limits_core::json::GenericBatteryLimit) -> Self { + // TODO + Self { + limits + } + } + + pub fn from_json_and_limits(_other: BatteryJson, _version: u64, limits: limits_core::json::GenericBatteryLimit) -> Self { + // TODO + Self { + limits + } + } +} + +impl OnSet for Battery { + fn on_set(&mut self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl OnResume for Battery { + fn on_resume(&self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl TBattery for Battery { + fn limits(&self) -> crate::api::BatteryLimits { + crate::api::BatteryLimits { + charge_current: None, + charge_current_step: 50, + charge_modes: vec![], + } + } + + fn json(&self) -> crate::persist::BatteryJson { + self.clone().into() + } + + fn charge_rate(&mut self, _rate: Option) { + } + + fn get_charge_rate(&self) -> Option { + None + } + + fn charge_mode(&mut self, _rate: Option) { + } + + fn get_charge_mode(&self) -> Option { + None + } + + 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 + } + } + } + + 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 + } + } + } + + 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 + } + } + } + + fn read_current_now(&self) -> Option { + None + } + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::Generic + } +} diff --git a/backend/src/settings/generic/cpu.rs b/backend/src/settings/generic/cpu.rs new file mode 100644 index 0000000..2ae1f5c --- /dev/null +++ b/backend/src/settings/generic/cpu.rs @@ -0,0 +1,364 @@ +use std::convert::{Into, AsMut, AsRef}; + +use limits_core::json::GenericCpuLimit; + +use crate::settings::{MinMax, min_max_from_json}; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{TCpus, TCpu}; +use crate::persist::CpuJson; +use super::FromGenericCpuInfo; + +const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; +const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control"; + +#[derive(Debug, Clone)] +pub struct Cpus + AsRef + TCpu> { + pub cpus: Vec, + pub smt: bool, + pub smt_capable: bool, +} + +impl + AsRef + TCpu + OnSet> OnSet for Cpus { + fn on_set(&mut self) -> Result<(), SettingError> { + if self.smt_capable { + // toggle SMT + if self.smt { + usdpl_back::api::files::write_single(CPU_SMT_PATH, "on").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `on` to `{}`: {}", + CPU_SMT_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } else { + usdpl_back::api::files::write_single(CPU_SMT_PATH, "off").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `off` to `{}`: {}", + CPU_SMT_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + } + for (i, cpu) in self.cpus.as_mut_slice().iter_mut().enumerate() { + cpu.as_mut().state.do_set_online = self.smt || i % 2 == 0 || !self.smt_capable; + cpu.on_set()?; + } + Ok(()) + } +} + +impl + AsRef + TCpu + OnResume> OnResume for Cpus { + fn on_resume(&self) -> Result<(), SettingError> { + for cpu in &self.cpus { + cpu.on_resume()?; + } + Ok(()) + } +} + +impl + AsRef + TCpu + FromGenericCpuInfo> Cpus { + pub fn cpu_count() -> Option { + let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH) + .unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */); + if let Some(dash_index) = data.find('-') { + let data = data.split_off(dash_index + 1); + if let Ok(max_cpu) = data.parse::() { + return Some(max_cpu + 1); + } + } + log::warn!("Failed to parse CPU info from kernel, is Tux evil?"); + None + } + + fn system_smt_capabilities() -> (bool, bool) { + match usdpl_back::api::files::read_single::<_, String, _>(CPU_SMT_PATH) { + Ok(val) => (val.trim().to_lowercase() == "on", true), + Err(_) => (false, false) + } + } + + pub fn from_limits(limits: limits_core::json::GenericCpuLimit) -> Self { + let cpu_count = Self::cpu_count().unwrap_or(8); + let (_, can_smt) = Self::system_smt_capabilities(); + let mut new_cpus = Vec::with_capacity(cpu_count); + for i in 0..cpu_count { + let new_cpu = C::from_limits(i, limits.clone()); + new_cpus.push(new_cpu); + } + Self { + cpus: new_cpus, + smt: true, + smt_capable: can_smt, + } + } + + pub fn from_json_and_limits(mut other: Vec, version: u64, limits: limits_core::json::GenericCpuLimit) -> Self { + let (_, can_smt) = Self::system_smt_capabilities(); + let mut result = Vec::with_capacity(other.len()); + let max_cpus = Self::cpu_count(); + let smt_guess = crate::settings::util::guess_smt(&other) && can_smt; + for (i, cpu) in other.drain(..).enumerate() { + // prevent having more CPUs than available + if let Some(max_cpus) = max_cpus { + if i == max_cpus { + break; + } + } + let new_cpu = C::from_json_and_limits(cpu, version, i, limits.clone()); + result.push(new_cpu); + } + if let Some(max_cpus) = max_cpus { + if result.len() != max_cpus { + let mut sys_cpus = Cpus::from_limits(limits.clone()); + for i in result.len()..sys_cpus.cpus.len() { + result.push(sys_cpus.cpus.remove(i)); + } + } + } + Self { + cpus: result, + smt: smt_guess, + smt_capable: can_smt, + } + } +} + +impl + AsRef + TCpu + OnResume + OnSet> TCpus for Cpus { + fn limits(&self) -> crate::api::CpusLimits { + crate::api::CpusLimits { + cpus: self.cpus.iter().map(|x| x.as_ref().limits()).collect(), + count: self.cpus.len(), + smt_capable: self.smt_capable, + governors: Vec::with_capacity(0), + } + } + + fn json(&self) -> Vec { + self.cpus.iter().map(|x| x.as_ref().to_owned().into()).collect() + } + + fn cpus(&mut self) -> Vec<&mut dyn TCpu> { + self.cpus.iter_mut().map(|x| x as &mut dyn TCpu).collect() + } + + fn len(&self) -> usize { + self.cpus.len() + } + + fn smt(&mut self) -> &'_ mut bool { + &mut self.smt + } + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::Generic + } +} + +#[derive(Debug, Clone)] +pub struct Cpu { + pub online: bool, + pub governor: String, + pub clock_limits: Option>, + limits: GenericCpuLimit, + index: usize, + state: crate::state::steam_deck::Cpu, +} + +/*impl Cpu { + #[inline] + pub fn index(&self) -> usize { + self.index + } +}*/ + +impl AsRef for Cpu { + #[inline] + fn as_ref(&self) -> &Cpu { + self + } +} + +impl AsMut for Cpu { + #[inline] + fn as_mut(&mut self) -> &mut Cpu { + self + } +} + +impl FromGenericCpuInfo for Cpu { + #[inline] + fn from_limits(cpu_index: usize, limits: GenericCpuLimit) -> Self { + Self { + online: true, + governor: "schedutil".to_owned(), + clock_limits: None, + limits, + index: cpu_index, + state: crate::state::steam_deck::Cpu::default(), + } + } + + #[inline] + fn from_json_and_limits(other: CpuJson, version: u64, i: usize, limits: GenericCpuLimit) -> Self { + let clock_lims = if limits.clock_min.is_some() && limits.clock_max.is_some() { + other.clock_limits.map(|x| min_max_from_json(x, version)) + } else { + None + }; + match version { + 0 => Self { + online: other.online, + governor: other.governor, + clock_limits: clock_lims, + limits, + index: i, + state: crate::state::steam_deck::Cpu::default(), + }, + _ => Self { + online: other.online, + governor: other.governor, + clock_limits: clock_lims, + limits, + index: i, + state: crate::state::steam_deck::Cpu::default(), + }, + } + } +} + +impl Cpu { + fn set_all(&mut self) -> Result<(), SettingError> { + // set cpu online/offline + if self.index != 0 && self.state.do_set_online { // cpu0 cannot be disabled + let online_path = cpu_online_path(self.index); + usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| { + SettingError { + msg: format!("Failed to write to `{}`: {}", &online_path, e), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + + // set governor + if self.index == 0 || self.online { + let governor_path = cpu_governor_path(self.index); + usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| { + SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &self.governor, &governor_path, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + Ok(()) + } + + /*fn from_sys(cpu_index: usize) -> Self { + Self { + online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0, + governor: usdpl_back::api::files::read_single(cpu_governor_path(cpu_index)) + .unwrap_or("schedutil".to_owned()), + index: cpu_index, + state: crate::state::steam_deck::Cpu::default(), + } + }*/ + + fn governors(&self) -> Vec { + // NOTE: this eats errors + let gov_str: String = match usdpl_back::api::files::read_single(cpu_available_governors_path(self.index)) { + Ok(s) => s, + Err(e) => { + log::warn!("Error getting available CPU governors: {}", e); + return vec![]; + }, + }; + gov_str.split(' ').map(|s| s.to_owned()).collect() + } + + fn limits(&self) -> crate::api::CpuLimits { + crate::api::CpuLimits { + clock_min_limits: self.limits.clock_min.clone().map(|x| x.into()), + clock_max_limits: self.limits.clock_max.clone().map(|x| x.into()), + clock_step: self.limits.clock_step, + governors: self.governors(), + } + } +} + +impl Into for Cpu { + #[inline] + fn into(self) -> CpuJson { + CpuJson { + online: self.online, + clock_limits: self.clock_limits.map(|x| x.into()), + governor: self.governor, + } + } +} + +impl OnSet for Cpu { + fn on_set(&mut self) -> Result<(), SettingError> { + //self.clamp_all(); + self.set_all() + } +} + +impl OnResume for Cpu { + fn on_resume(&self) -> Result<(), SettingError> { + let mut copy = self.clone(); + copy.state.is_resuming = true; + copy.set_all() + } +} + +impl TCpu for Cpu { + fn online(&mut self) -> &mut bool { + &mut self.online + } + + fn governor(&mut self, governor: String) { + self.governor = governor; + } + + fn get_governor(&self) -> &'_ str { + &self.governor + } + + fn clock_limits(&mut self, limits: Option>) { + if self.limits.clock_min.is_some() && self.limits.clock_max.is_some() { + self.clock_limits = limits; + } + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.clock_limits.as_ref() + } +} + +#[inline] +fn cpu_online_path(index: usize) -> String { + format!("/sys/devices/system/cpu/cpu{}/online", index) +} + +#[inline] +fn cpu_governor_path(index: usize) -> String { + format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor", + index + ) +} + +#[inline] +fn cpu_available_governors_path(index: usize) -> String { + format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_available_governors", + index + ) +} diff --git a/backend/src/settings/generic/gpu.rs b/backend/src/settings/generic/gpu.rs new file mode 100644 index 0000000..7e658a4 --- /dev/null +++ b/backend/src/settings/generic/gpu.rs @@ -0,0 +1,139 @@ +use std::convert::Into; + +use limits_core::json::GenericGpuLimit; + +use crate::settings::{MinMax, min_max_from_json}; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::TGpu; +use crate::persist::GpuJson; + +#[derive(Debug, Clone)] +pub struct Gpu { + pub slow_memory: bool, + pub fast_ppt: Option, + pub slow_ppt: Option, + pub clock_limits: Option>, + limits: GenericGpuLimit, +} + +impl Gpu { + /*#[inline] + pub fn from_json(_other: GpuJson, _version: u64) -> Self { + Self { + slow_memory: false, + } + }*/ + + /*pub fn system_default() -> Self { + Self { + slow_memory: false, + } + }*/ + + pub fn from_limits(limits: limits_core::json::GenericGpuLimit) -> Self { + Self { + slow_memory: false, + fast_ppt: None, + slow_ppt: None, + clock_limits: None, + limits, + } + } + + pub fn from_json_and_limits(other: GpuJson, version: u64, limits: limits_core::json::GenericGpuLimit) -> Self { + let clock_lims = if limits.clock_min.is_some() && limits.clock_max.is_some() { + other.clock_limits.map(|x| min_max_from_json(x, version)) + } else { + None + }; + Self { + slow_memory: false, + fast_ppt: if limits.fast_ppt.is_some() {other.fast_ppt} else {None}, + slow_ppt: if limits.slow_ppt.is_some() {other.slow_ppt} else {None}, + clock_limits: clock_lims, + limits, + } + } +} + +impl Into for Gpu { + #[inline] + fn into(self) -> GpuJson { + GpuJson { + fast_ppt: self.fast_ppt, + slow_ppt: self.slow_ppt, + clock_limits: self.clock_limits.map(|x| x.into()), + slow_memory: false, + } + } +} + +impl OnSet for Gpu { + fn on_set(&mut self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl OnResume for Gpu { + fn on_resume(&self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl TGpu for Gpu { + fn limits(&self) -> crate::api::GpuLimits { + crate::api::GpuLimits { + fast_ppt_limits: self.limits.fast_ppt.clone().map(|x| x.into()), + slow_ppt_limits: self.limits.slow_ppt.clone().map(|x| x.into()), + ppt_step: self.limits.ppt_step.unwrap_or(1_000_000), + tdp_limits: self.limits.tdp.clone().map(|x| x.into()), + tdp_boost_limits: self.limits.tdp_boost.clone().map(|x| x.into()), + tdp_step: self.limits.tdp_step.unwrap_or(42), + clock_min_limits: self.limits.clock_min.clone().map(|x| x.into()), + clock_max_limits: self.limits.clock_max.clone().map(|x| x.into()), + clock_step: self.limits.clock_step.unwrap_or(100), + memory_control_capable: false, + } + } + + fn json(&self) -> crate::persist::GpuJson { + self.clone().into() + } + + fn ppt(&mut self, fast: Option, slow: Option) { + if let Some(fast_lims) = &self.limits.fast_ppt { + self.fast_ppt = fast.map(|x| x.clamp(fast_lims.min, fast_lims.max)); + } + if let Some(slow_lims) = &self.limits.slow_ppt { + self.slow_ppt = slow.map(|x| x.clamp(slow_lims.min, slow_lims.max)); + } + } + + fn get_ppt(&self) -> (Option, Option) { + (self.fast_ppt, self.slow_ppt) + } + + fn clock_limits(&mut self, limits: Option>) { + if let Some(clock_min) = &self.limits.clock_min { + if let Some(clock_max) = &self.limits.clock_max { + self.clock_limits = limits.map(|mut x| { + x.min = x.min.clamp(clock_min.min, clock_min.max); + x.max = x.max.clamp(clock_max.max, clock_max.max); + x + }); + } + } + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.clock_limits.as_ref() + } + + fn slow_memory(&mut self) -> &mut bool { + &mut self.slow_memory + } + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::Generic + } +} diff --git a/backend/src/settings/generic/mod.rs b/backend/src/settings/generic/mod.rs new file mode 100644 index 0000000..6989a23 --- /dev/null +++ b/backend/src/settings/generic/mod.rs @@ -0,0 +1,9 @@ +mod battery; +mod cpu; +mod gpu; +mod traits; + +pub use battery::Battery; +pub use cpu::{Cpu, Cpus}; +pub use gpu::Gpu; +pub use traits::FromGenericCpuInfo; diff --git a/backend/src/settings/generic/traits.rs b/backend/src/settings/generic/traits.rs new file mode 100644 index 0000000..bf51e7f --- /dev/null +++ b/backend/src/settings/generic/traits.rs @@ -0,0 +1,8 @@ +use limits_core::json::GenericCpuLimit; +use crate::persist::CpuJson; + +pub trait FromGenericCpuInfo { + fn from_limits(cpu_index: usize, limits: GenericCpuLimit) -> Self; + + fn from_json_and_limits(other: CpuJson, version: u64, cpu_index: usize, limits: GenericCpuLimit) -> Self; +} diff --git a/backend/src/settings/generic_amd/cpu.rs b/backend/src/settings/generic_amd/cpu.rs new file mode 100644 index 0000000..74f5c5b --- /dev/null +++ b/backend/src/settings/generic_amd/cpu.rs @@ -0,0 +1,134 @@ +use crate::persist::CpuJson; +use crate::settings::MinMax; +use crate::settings::generic::{Cpu as GenericCpu, Cpus as GenericCpus, FromGenericCpuInfo}; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{TCpus, TCpu}; + +#[derive(Debug)] +pub struct Cpus { + generic: GenericCpus, +} + +impl Cpus { + pub fn from_limits(limits: limits_core::json::GenericCpuLimit) -> Self { + Self { + generic: GenericCpus::from_limits(limits), + } + } + + pub fn from_json_and_limits(other: Vec, version: u64, limits: limits_core::json::GenericCpuLimit) -> Self { + Self { + generic: GenericCpus::from_json_and_limits(other, version, limits), + } + } +} + +impl OnResume for Cpus { + fn on_resume(&self) -> Result<(), SettingError> { + self.generic.on_resume() + // TODO + } +} + +impl OnSet for Cpus { + fn on_set(&mut self) -> Result<(), SettingError> { + self.generic.on_set() + // TODO + } +} + +impl TCpus for Cpus { + fn limits(&self) -> crate::api::CpusLimits { + self.generic.limits() + } + + fn json(&self) -> Vec { + self.generic.json() // TODO + } + + fn cpus(&mut self) -> Vec<&mut dyn TCpu> { + self.generic.cpus() // TODO + } + + fn len(&self) -> usize { + self.generic.len() // TODO + } + + fn smt(&mut self) -> &'_ mut bool { + self.generic.smt() + } + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::GenericAMD + } +} + +#[derive(Debug)] +pub struct Cpu { + generic: GenericCpu, +} + +impl FromGenericCpuInfo for Cpu { + fn from_limits(cpu_index: usize, limits: limits_core::json::GenericCpuLimit) -> Self { + let gen = GenericCpu::from_limits(cpu_index, limits.clone()); + Self { + generic: gen, + } + } + + fn from_json_and_limits(other: CpuJson, version: u64, cpu_index: usize, limits: limits_core::json::GenericCpuLimit) -> Self { + let gen = GenericCpu::from_json_and_limits(other, version, cpu_index, limits); + Self { + generic: gen, + } + } +} + +impl AsRef for Cpu { + fn as_ref(&self) -> &GenericCpu { + &self.generic + } +} + +impl AsMut for Cpu { + fn as_mut(&mut self) -> &mut GenericCpu { + &mut self.generic + } +} + +impl OnResume for Cpu { + fn on_resume(&self) -> Result<(), SettingError> { + self.generic.on_resume() + // TODO + } +} + +impl OnSet for Cpu { + fn on_set(&mut self) -> Result<(), SettingError> { + self.generic.on_set() + // TODO + } +} + +impl TCpu for Cpu { + fn online(&mut self) -> &mut bool { + self.generic.online() + } + + fn governor(&mut self, governor: String) { + self.generic.governor(governor) + } + + fn get_governor(&self) -> &'_ str { + self.generic.get_governor() + } + + fn clock_limits(&mut self, _limits: Option>) { + //self.generic.clock_limits(limits) + // TODO: support this + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.generic.get_clock_limits() + } +} diff --git a/backend/src/settings/generic_amd/gpu.rs b/backend/src/settings/generic_amd/gpu.rs new file mode 100644 index 0000000..ca4dffe --- /dev/null +++ b/backend/src/settings/generic_amd/gpu.rs @@ -0,0 +1,215 @@ +use std::sync::Mutex; +use ryzenadj_rs::RyzenAccess; + +use crate::persist::GpuJson; +use crate::settings::MinMax; +use crate::settings::generic::Gpu as GenericGpu; +use crate::settings::{OnResume, OnSet, SettingError, SettingVariant}; +use crate::settings::TGpu; + +fn ryzen_adj_or_log() -> Option> { + match RyzenAccess::new() { + Ok(x) => Some(Mutex::new(x)), + Err(e) => { + log::error!("RyzenAdj init error: {}", e); + None + } + } +} + +#[derive(Debug)] +pub struct Gpu { + generic: GenericGpu, + implementor: Option>, + state: crate::state::generic::Gpu, // NOTE this is re-used for simplicity +} + +impl Gpu { + pub fn from_limits(limits: limits_core::json::GenericGpuLimit) -> Self { + Self { + generic: GenericGpu::from_limits(limits), + implementor: ryzen_adj_or_log(), + state: Default::default(), + } + } + + pub fn from_json_and_limits(other: GpuJson, version: u64, limits: limits_core::json::GenericGpuLimit) -> Self { + Self { + generic: GenericGpu::from_json_and_limits(other, version, limits), + implementor: ryzen_adj_or_log(), + state: Default::default(), + } + } + + fn set_all(&mut self) -> Result<(), SettingError> { + let mutex = match &self.implementor { + Some(x) => x, + None => { + return Err(SettingError { + msg: "RyzenAdj unavailable".to_owned(), + setting: SettingVariant::Gpu, + }); + } + }; + let lock = match mutex.lock() { + Ok(x) => x, + Err(e) => { + return Err(SettingError { + msg: format!("RyzenAdj lock acquire failed: {}", e), + setting: SettingVariant::Gpu, + }); + } + }; + if let Some(fast_ppt) = &self.generic.fast_ppt { + if self.state.old_fast_ppt.is_none() { + self.state.old_fast_ppt = Some(lock.get_fast_value() as _); + } + lock.set_fast_limit(*fast_ppt as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_fast_limit({}) err: {}", *fast_ppt, e), + setting: SettingVariant::Gpu, + })?; + } else if let Some(fast_ppt) = &self.state.old_fast_ppt { + lock.set_fast_limit(*fast_ppt as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_fast_limit({}) err: {}", *fast_ppt, e), + setting: SettingVariant::Gpu, + })?; + self.state.old_fast_ppt = None; + } + if let Some(slow_ppt) = &self.generic.slow_ppt { + if self.state.old_slow_ppt.is_none() { + self.state.old_slow_ppt = Some(lock.get_slow_value() as _); + } + lock.set_slow_limit(*slow_ppt as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_slow_limit({}) err: {}", *slow_ppt, e), + setting: SettingVariant::Gpu, + })?; + } else if let Some(slow_ppt) = &self.state.old_slow_ppt { + lock.set_slow_limit(*slow_ppt as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_slow_limit({}) err: {}", *slow_ppt, e), + setting: SettingVariant::Gpu, + })?; + self.state.old_slow_ppt = None; + } + if let Some(clock_limits) = &self.generic.clock_limits { + self.state.clock_limits_set = true; + lock.set_max_gfxclk_freq(clock_limits.max as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_max_gfxclk_freq({}) err: {}", clock_limits.max, e), + setting: SettingVariant::Gpu, + })?; + lock.set_min_gfxclk_freq(clock_limits.min as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_min_gfxclk_freq({}) err: {}", clock_limits.min, e), + setting: SettingVariant::Gpu, + })?; + } else if self.state.clock_limits_set { + self.state.clock_limits_set = false; + let limits = self.generic.limits(); + if let Some(min_limits) = limits.clock_min_limits { + if let Some(max_limits) = limits.clock_max_limits { + lock.set_max_gfxclk_freq(max_limits.max as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_max_gfxclk_freq({}) err: {}", max_limits.max, e), + setting: SettingVariant::Gpu, + })?; + lock.set_min_gfxclk_freq(min_limits.min as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_min_gfxclk_freq({}) err: {}", min_limits.min, e), + setting: SettingVariant::Gpu, + })?; + } + } + } + Ok(()) + } + + fn resume_all(&self) -> Result<(), SettingError> { + // like set_all() but without updating state + // -- assumption: state is already up to date + let mutex = match &self.implementor { + Some(x) => x, + None => { + return Err(SettingError { + msg: "RyzenAdj unavailable".to_owned(), + setting: SettingVariant::Gpu, + }); + } + }; + let lock = match mutex.lock() { + Ok(x) => x, + Err(e) => { + return Err(SettingError { + msg: format!("RyzenAdj lock acquire failed: {}", e), + setting: SettingVariant::Gpu, + }); + } + }; + if let Some(fast_ppt) = &self.generic.fast_ppt { + lock.set_fast_limit(*fast_ppt as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_fast_limit({}) err: {}", *fast_ppt, e), + setting: SettingVariant::Gpu, + })?; + } + if let Some(slow_ppt) = &self.generic.slow_ppt { + lock.set_slow_limit(*slow_ppt as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_slow_limit({}) err: {}", *slow_ppt, e), + setting: SettingVariant::Gpu, + })?; + } + if let Some(clock_limits) = &self.generic.clock_limits { + lock.set_max_gfxclk_freq(clock_limits.max as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_max_gfxclk_freq({}) err: {}", clock_limits.max, e), + setting: SettingVariant::Gpu, + })?; + lock.set_min_gfxclk_freq(clock_limits.min as _).map_err(|e| SettingError { + msg: format!("RyzenAdj set_min_gfxclk_freq({}) err: {}", clock_limits.min, e), + setting: SettingVariant::Gpu, + })?; + } + Ok(()) + } +} + +impl OnResume for Gpu { + fn on_resume(&self) -> Result<(), SettingError> { + self.generic.on_resume()?; + self.resume_all() + } +} + +impl OnSet for Gpu { + fn on_set(&mut self) -> Result<(), SettingError> { + self.generic.on_set()?; + self.set_all() + } +} + +impl TGpu for Gpu { + fn limits(&self) -> crate::api::GpuLimits { + self.generic.limits() + } + + fn json(&self) -> crate::persist::GpuJson { + self.generic.json() + } + + fn ppt(&mut self, fast: Option, slow: Option) { + self.generic.ppt(fast, slow) + } + + fn get_ppt(&self) -> (Option, Option) { + self.generic.get_ppt() + } + + fn clock_limits(&mut self, limits: Option>) { + self.generic.clock_limits(limits) + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.generic.get_clock_limits() + } + + fn slow_memory(&mut self) -> &mut bool { + self.generic.slow_memory() + } + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::GenericAMD + } +} diff --git a/backend/src/settings/generic_amd/mod.rs b/backend/src/settings/generic_amd/mod.rs new file mode 100644 index 0000000..6a8e412 --- /dev/null +++ b/backend/src/settings/generic_amd/mod.rs @@ -0,0 +1,5 @@ +mod cpu; +mod gpu; + +pub use cpu::{Cpu, Cpus}; +pub use gpu::Gpu; diff --git a/backend/src/settings/min_max.rs b/backend/src/settings/min_max.rs index 00ea8fd..c1c9563 100644 --- a/backend/src/settings/min_max.rs +++ b/backend/src/settings/min_max.rs @@ -1,24 +1,19 @@ use std::convert::Into; +use limits_core::json::RangeLimit; + use crate::persist::MinMaxJson; -#[derive(Debug, Clone)] -pub struct MinMax { - pub max: T, - pub min: T, -} +pub type MinMax = RangeLimit; -impl MinMax { - #[inline] - pub fn from_json>(other: MinMaxJson, _version: u64) -> Self { - Self { - max: other.max.into(), - min: other.min.into(), - } +pub fn min_max_from_json>(other: MinMaxJson, _version: u64) -> MinMax { + MinMax { + max: other.max.into(), + min: other.min.into(), } } -impl, Y> Into> for MinMax { +impl, Y> Into> for RangeLimit { #[inline] fn into(self) -> MinMaxJson { MinMaxJson { diff --git a/backend/src/settings/mod.rs b/backend/src/settings/mod.rs index 9f327af..3541526 100644 --- a/backend/src/settings/mod.rs +++ b/backend/src/settings/mod.rs @@ -1,19 +1,23 @@ -mod battery; -mod cpu; +mod detect; +pub mod driver; mod error; mod general; -mod gpu; mod min_max; mod traits; +mod util; -pub use battery::Battery; -pub use cpu::Cpu; +pub mod generic; +pub mod generic_amd; +pub mod steam_deck; +pub mod unknown; + +pub use detect::{auto_detect0, auto_detect_provider, limits_worker::spawn as limits_worker_spawn}; +pub use driver::Driver; pub use general::{SettingVariant, Settings, General}; -pub use gpu::Gpu; -pub use min_max::MinMax; +pub use min_max::{MinMax, min_max_from_json}; pub use error::SettingError; -pub use traits::{OnResume, OnSet, SettingsRange}; +pub use traits::{OnResume, OnSet, SettingsRange, TGeneral, TGpu, TCpus, TBattery, TCpu}; #[cfg(test)] mod tests { diff --git a/backend/src/settings/steam_deck/battery.rs b/backend/src/settings/steam_deck/battery.rs new file mode 100644 index 0000000..5a7f745 --- /dev/null +++ b/backend/src/settings/steam_deck/battery.rs @@ -0,0 +1,271 @@ +use std::convert::Into; + +use crate::api::RangeLimit; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::TBattery; +use crate::persist::BatteryJson; +use super::util::ChargeMode; +use super::oc_limits::{BatteryLimits, OverclockLimits}; + +#[derive(Debug, Clone)] +pub struct Battery { + pub charge_rate: Option, + pub charge_mode: Option, + limits: BatteryLimits, + state: crate::state::steam_deck::Battery, + driver_mode: crate::persist::DriverJson, +} + +const BATTERY_VOLTAGE: f64 = 7.7; + +const BATTERY_CHARGE_RATE_PATH: &str = "/sys/class/hwmon/hwmon5/maximum_battery_charge_rate"; // write-only +const BATTERY_CURRENT_NOW_PATH: &str = "/sys/class/power_supply/BAT1/current_now"; // read-only +const BATTERY_CHARGE_NOW_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_now"; // read-only +const BATTERY_CHARGE_FULL_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full"; // read-only +const BATTERY_CHARGE_DESIGN_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full_design"; // read-only + +impl Battery { + #[inline] + pub fn from_json(other: BatteryJson, version: u64) -> Self { + let (oc_limits, is_default) = OverclockLimits::load_or_default(); + let oc_limits = oc_limits.battery; + let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance }; + match version { + 0 => Self { + charge_rate: other.charge_rate, + charge_mode: other.charge_mode.map(|x| Self::str_to_charge_mode(&x)).flatten(), + limits: oc_limits, + state: crate::state::steam_deck::Battery::default(), + driver_mode: driver, + }, + _ => Self { + charge_rate: other.charge_rate, + charge_mode: other.charge_mode.map(|x| Self::str_to_charge_mode(&x)).flatten(), + limits: oc_limits, + state: crate::state::steam_deck::Battery::default(), + driver_mode: driver, + }, + } + } + + #[inline] + fn charge_mode_to_str(mode: ChargeMode) -> String { + match mode { + ChargeMode::Normal => "normal", + ChargeMode::Idle => "idle", + ChargeMode::Discharge => "discharge", + }.to_owned() + } + + #[inline] + fn str_to_charge_mode(s: &str) -> Option { + match s { + "normal" => Some(ChargeMode::Normal), + "idle" => Some(ChargeMode::Idle), + "discharge" | "disacharge" => Some(ChargeMode::Discharge), + _ => None, + } + } + + fn set_all(&mut self) -> Result<(), SettingError> { + if let Some(charge_rate) = self.charge_rate { + self.state.charge_rate_set = true; + usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate).map_err( + |e| SettingError { + msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }, + )?; + } else if self.state.charge_rate_set { + self.state.charge_rate_set = false; + usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, self.limits.charge_rate.max).map_err( + |e| SettingError { + msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }, + )?; + } + if let Some(charge_mode) = self.charge_mode { + self.state.charge_mode_set = true; + super::util::set(super::util::Setting::ChargeMode, charge_mode as _).map_err( + |e| SettingError { + msg: format!("Failed to set charge mode: {}", e), + setting: crate::settings::SettingVariant::Battery, + }, + )?; + } else if self.state.charge_mode_set { + self.state.charge_mode_set = false; + super::util::set(super::util::Setting::ChargeMode, ChargeMode::Normal as _).map_err( + |e| SettingError { + msg: format!("Failed to set charge mode: {}", e), + setting: crate::settings::SettingVariant::Battery, + }, + )?; + } + Ok(()) + } + + fn clamp_all(&mut self) { + if let Some(charge_rate) = &mut self.charge_rate { + *charge_rate = (*charge_rate).clamp(self.limits.charge_rate.min, self.limits.charge_rate.max); + } + } + + pub fn read_current_now() -> Result { + match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) { + Err(e) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + // this value is in uA, while it's set in mA + // so convert this to mA for consistency + Ok(val) => Ok(val / 1000), + } + } + + pub fn read_charge_now() -> Result { + match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) { + Err(e) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + // convert to Wh + Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE), + } + } + + pub fn read_charge_full() -> Result { + match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) { + Err(e) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + // convert to Wh + Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE), + } + } + + pub fn read_charge_design() -> Result { + match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) { + Err(e) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + // convert to Wh + Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE), + } + } + + pub fn system_default() -> Self { + let (oc_limits, is_default) = OverclockLimits::load_or_default(); + let oc_limits = oc_limits.battery; + let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance }; + Self { + charge_rate: None, + charge_mode: None, + limits: oc_limits, + state: crate::state::steam_deck::Battery::default(), + driver_mode: driver, + } + } +} + +impl Into for Battery { + #[inline] + fn into(self) -> BatteryJson { + BatteryJson { + charge_rate: self.charge_rate, + charge_mode: self.charge_mode.map(Self::charge_mode_to_str), + } + } +} + +impl OnSet for Battery { + fn on_set(&mut self) -> Result<(), SettingError> { + self.clamp_all(); + self.set_all() + } +} + +impl OnResume for Battery { + fn on_resume(&self) -> Result<(), SettingError> { + self.clone().set_all() + } +} + +impl TBattery for Battery { + fn limits(&self) -> crate::api::BatteryLimits { + crate::api::BatteryLimits { + charge_current: Some(RangeLimit{ + min: self.limits.charge_rate.min, + max: self.limits.charge_rate.max + }), + charge_current_step: 50, + charge_modes: vec!["normal".to_owned(), "discharge".to_owned(), "idle".to_owned()], + } + } + + fn json(&self) -> crate::persist::BatteryJson { + self.clone().into() + } + + fn charge_rate(&mut self, rate: Option) { + self.charge_rate = rate; + } + + fn get_charge_rate(&self) -> Option { + self.charge_rate + } + + fn charge_mode(&mut self, mode: Option) { + self.charge_mode = mode.map(|s| Self::str_to_charge_mode(&s)).flatten() + } + + fn get_charge_mode(&self) -> Option { + self.charge_mode.map(Self::charge_mode_to_str) + } + + fn read_charge_full(&self) -> Option { + match Self::read_charge_full() { + Ok(x) => Some(x), + Err(e) => { + log::warn!("read_charge_full err: {}", e.msg); + None + } + } + } + + fn read_charge_now(&self) -> Option { + match Self::read_charge_now() { + Ok(x) => Some(x), + Err(e) => { + log::warn!("read_charge_now err: {}", e.msg); + None + } + } + } + + fn read_charge_design(&self) -> Option { + match Self::read_charge_design() { + Ok(x) => Some(x), + Err(e) => { + log::warn!("read_charge_design err: {}", e.msg); + None + } + } + } + + fn read_current_now(&self) -> Option { + match Self::read_current_now() { + Ok(x) => Some(x as f64), + Err(e) => { + log::warn!("read_current_now err: {}", e.msg); + None + } + } + } + + fn provider(&self) -> crate::persist::DriverJson { + self.driver_mode.clone() + } +} diff --git a/backend/src/settings/steam_deck/cpu.rs b/backend/src/settings/steam_deck/cpu.rs new file mode 100644 index 0000000..2fe0480 --- /dev/null +++ b/backend/src/settings/steam_deck/cpu.rs @@ -0,0 +1,452 @@ +use std::convert::Into; + +use crate::api::RangeLimit; +use crate::settings::{MinMax, min_max_from_json}; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{TCpus, TCpu}; +use crate::persist::CpuJson; +use super::oc_limits::{OverclockLimits, CpusLimits, CpuLimits}; + +const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; +const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control"; + +#[derive(Debug, Clone)] +pub struct Cpus { + pub cpus: Vec, + pub smt: bool, + pub smt_capable: bool, + pub(super) limits: CpusLimits, + driver_mode: crate::persist::DriverJson, +} + +impl OnSet for Cpus { + fn on_set(&mut self) -> Result<(), SettingError> { + if self.smt_capable { + // toggle SMT + if self.smt { + usdpl_back::api::files::write_single(CPU_SMT_PATH, "on").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `on` to `{}`: {}", + CPU_SMT_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } else { + usdpl_back::api::files::write_single(CPU_SMT_PATH, "off").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `off` to `{}`: {}", + CPU_SMT_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + } + for (i, cpu) in self.cpus.as_mut_slice().iter_mut().enumerate() { + cpu.state.do_set_online = self.smt || i % 2 == 0; + cpu.on_set()?; + } + Ok(()) + } +} + +impl OnResume for Cpus { + fn on_resume(&self) -> Result<(), SettingError> { + for cpu in &self.cpus { + cpu.on_resume()?; + } + Ok(()) + } +} + +impl Cpus { + pub fn cpu_count() -> Option { + let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH) + .unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */); + if let Some(dash_index) = data.find('-') { + let data = data.split_off(dash_index + 1); + if let Ok(max_cpu) = data.parse::() { + return Some(max_cpu + 1); + } + } + log::warn!("Failed to parse CPU info from kernel, is Tux evil?"); + None + } + + fn system_smt_capabilities() -> (bool, bool) { + match usdpl_back::api::files::read_single::<_, String, _>(CPU_SMT_PATH) { + Ok(val) => (val.trim().to_lowercase() == "on", true), + Err(_) => (false, false) + } + } + + pub fn system_default() -> Self { + let (oc_limits, is_default) = OverclockLimits::load_or_default(); + let oc_limits = oc_limits.cpus; + let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance }; + if let Some(max_cpu) = Self::cpu_count() { + let mut sys_cpus = Vec::with_capacity(max_cpu); + for i in 0..max_cpu { + sys_cpus.push(Cpu::system_default(i, oc_limits.cpus.get(i).map(|x| x.to_owned()).unwrap_or_default())); + } + let (_, can_smt) = Self::system_smt_capabilities(); + Self { + cpus: sys_cpus, + smt: true, + smt_capable: can_smt, + limits: oc_limits, + driver_mode: driver, + } + } else { + Self { + cpus: vec![], + smt: false, + smt_capable: false, + limits: oc_limits, + driver_mode: driver, + } + } + } + + #[inline] + pub fn from_json(mut other: Vec, version: u64) -> Self { + let (oc_limits, is_default) = OverclockLimits::load_or_default(); + let oc_limits = oc_limits.cpus; + let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance }; + let (_, can_smt) = Self::system_smt_capabilities(); + let mut result = Vec::with_capacity(other.len()); + let max_cpus = Self::cpu_count(); + let smt_guess = crate::settings::util::guess_smt(&other) && can_smt; + for (i, cpu) in other.drain(..).enumerate() { + // prevent having more CPUs than available + if let Some(max_cpus) = max_cpus { + if i == max_cpus { + break; + } + } + let new_cpu = Cpu::from_json(cpu, version, i, oc_limits.cpus.get(i).map(|x| x.to_owned()).unwrap_or_default()); + result.push(new_cpu); + } + if let Some(max_cpus) = max_cpus { + if result.len() != max_cpus { + let mut sys_cpus = Cpus::system_default(); + for i in result.len()..sys_cpus.cpus.len() { + result.push(sys_cpus.cpus.remove(i)); + } + } + } + Self { + cpus: result, + smt: smt_guess, + smt_capable: can_smt, + limits: oc_limits, + driver_mode: driver, + } + } +} + +impl TCpus for Cpus { + fn limits(&self) -> crate::api::CpusLimits { + crate::api::CpusLimits { + cpus: self.cpus.iter().map(|x| x.limits()).collect(), + count: self.cpus.len(), + smt_capable: self.smt_capable, + governors: if self.limits.global_governors { + self.cpus.iter() + .next() + .map(|x| x.governors()) + .unwrap_or_else(|| Vec::with_capacity(0)) + } else { Vec::with_capacity(0) }, + } + } + + fn json(&self) -> Vec { + self.cpus.iter().map(|x| x.to_owned().into()).collect() + } + + fn cpus(&mut self) -> Vec<&mut dyn TCpu> { + self.cpus.iter_mut().map(|x| x as &mut dyn TCpu).collect() + } + + fn len(&self) -> usize { + self.cpus.len() + } + + fn smt(&mut self) -> &'_ mut bool { + &mut self.smt + } + + fn provider(&self) -> crate::persist::DriverJson { + self.driver_mode.clone() + } +} + +#[derive(Debug, Clone)] +pub struct Cpu { + pub online: bool, + pub clock_limits: Option>, + pub governor: String, + limits: CpuLimits, + index: usize, + state: crate::state::steam_deck::Cpu, +} + +const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; +const CPU_FORCE_LIMITS_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level"; + +impl Cpu { + #[inline] + fn from_json(other: CpuJson, version: u64, i: usize, oc_limits: CpuLimits) -> Self { + match version { + 0 => Self { + online: other.online, + clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)), + governor: other.governor, + limits: oc_limits, + index: i, + state: crate::state::steam_deck::Cpu::default(), + }, + _ => Self { + online: other.online, + clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)), + governor: other.governor, + limits: oc_limits, + index: i, + state: crate::state::steam_deck::Cpu::default(), + }, + } + } + + fn set_all(&mut self) -> Result<(), SettingError> { + // set cpu online/offline + if self.index != 0 && self.state.do_set_online { // cpu0 cannot be disabled + let online_path = cpu_online_path(self.index); + usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| { + SettingError { + msg: format!("Failed to write to `{}`: {}", &online_path, e), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + // set clock limits + log::debug!("Setting {} to manual", CPU_FORCE_LIMITS_PATH); + let mode: String = usdpl_back::api::files::read_single(CPU_FORCE_LIMITS_PATH.to_owned()).unwrap(); + if mode != "manual" { + // set manual control + usdpl_back::api::files::write_single(CPU_FORCE_LIMITS_PATH, "manual").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `manual` to `{}`: {}", + CPU_FORCE_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + if let Some(clock_limits) = &self.clock_limits { + log::debug!("Setting CPU {} (min, max) clockspeed to ({}, {})", self.index, clock_limits.min, clock_limits.max); + self.state.clock_limits_set = true; + // max clock + let payload_max = format!("p {} 1 {}\n", self.index / 2, clock_limits.max); + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_max, CPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + }, + )?; + // min clock + let valid_min = if clock_limits.min < self.limits.clock_min.min {self.limits.clock_min.min} else {clock_limits.min}; + let payload_min = format!("p {} 0 {}\n", self.index / 2, valid_min); + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_min, CPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + }, + )?; + } else if self.state.clock_limits_set || self.state.is_resuming { + self.state.clock_limits_set = false; + // disable manual clock limits + log::debug!("Setting CPU {} to default clockspeed", self.index); + // max clock + let payload_max = format!("p {} 1 {}\n", self.index / 2, self.limits.clock_max.max); + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_max, CPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + }, + )?; + // min clock + let payload_min = format!("p {} 0 {}\n", self.index / 2, self.limits.clock_min.min); + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_min, CPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + }, + )?; + } + // commit changes + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| { + SettingError { + msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + + // set governor + if self.index == 0 || self.online { + let governor_path = cpu_governor_path(self.index); + usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| { + SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &self.governor, &governor_path, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + Ok(()) + } + + fn clamp_all(&mut self) { + if let Some(clock_limits) = &mut self.clock_limits { + clock_limits.min = clock_limits.min.clamp(self.limits.clock_min.min, self.limits.clock_min.max); + clock_limits.max = clock_limits.max.clamp(self.limits.clock_max.min, self.limits.clock_max.max); + } + } + + /*fn from_sys(cpu_index: usize, oc_limits: CpuLimits) -> Self { + Self { + online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0, + clock_limits: None, + governor: usdpl_back::api::files::read_single(cpu_governor_path(cpu_index)) + .unwrap_or("schedutil".to_owned()), + limits: oc_limits, + index: cpu_index, + state: crate::state::steam_deck::Cpu::default(), + } + }*/ + + fn system_default(cpu_index: usize, oc_limits: CpuLimits) -> Self { + Self { + online: true, + clock_limits: None, + governor: "schedutil".to_owned(), + limits: oc_limits, + index: cpu_index, + state: crate::state::steam_deck::Cpu::default(), + } + } + + fn limits(&self) -> crate::api::CpuLimits { + crate::api::CpuLimits { + clock_min_limits: Some(RangeLimit { + min: self.limits.clock_max.min, // allows min to be set by max (it's weird, blame the kernel) + max: self.limits.clock_min.max + }), + clock_max_limits: Some(RangeLimit { + min: self.limits.clock_max.min, + max: self.limits.clock_max.max + }), + clock_step: self.limits.clock_step, + governors: self.governors(), + } + } + + fn governors(&self) -> Vec { + // NOTE: this eats errors + let gov_str: String = match usdpl_back::api::files::read_single(cpu_available_governors_path(self.index)) { + Ok(s) => s, + Err(e) => { + log::warn!("Error getting available CPU governors: {}", e); + return vec![]; + } + }; + gov_str.split(' ').map(|s| s.to_owned()).collect() + } +} + +impl Into for Cpu { + #[inline] + fn into(self) -> CpuJson { + CpuJson { + online: self.online, + clock_limits: self.clock_limits.map(|x| x.into()), + governor: self.governor, + } + } +} + +impl OnSet for Cpu { + fn on_set(&mut self) -> Result<(), SettingError> { + self.clamp_all(); + self.set_all() + } +} + +impl OnResume for Cpu { + fn on_resume(&self) -> Result<(), SettingError> { + let mut copy = self.clone(); + copy.state.is_resuming = true; + copy.set_all() + } +} + +impl TCpu for Cpu { + fn online(&mut self) -> &mut bool { + &mut self.online + } + + fn governor(&mut self, governor: String) { + self.governor = governor; + } + + fn get_governor(&self) -> &'_ str { + &self.governor + } + + fn clock_limits(&mut self, limits: Option>) { + self.clock_limits = limits; + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.clock_limits.as_ref() + } +} + +#[inline] +fn cpu_online_path(index: usize) -> String { + format!("/sys/devices/system/cpu/cpu{}/online", index) +} + +#[inline] +fn cpu_governor_path(index: usize) -> String { + format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor", + index + ) +} + + +#[inline] +fn cpu_available_governors_path(index: usize) -> String { + format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_available_governors", + index + ) +} diff --git a/backend/src/settings/gpu.rs b/backend/src/settings/steam_deck/gpu.rs similarity index 59% rename from backend/src/settings/gpu.rs rename to backend/src/settings/steam_deck/gpu.rs index 3a3b15a..3253a8a 100644 --- a/backend/src/settings/gpu.rs +++ b/backend/src/settings/steam_deck/gpu.rs @@ -1,8 +1,11 @@ use std::convert::Into; -use super::MinMax; -use super::{OnResume, OnSet, SettingError, SettingsRange}; +use crate::api::RangeLimit; +use crate::settings::{MinMax, min_max_from_json}; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::TGpu; use crate::persist::GpuJson; +use super::oc_limits::{OverclockLimits, GpuLimits}; const SLOW_PPT: u8 = 1; const FAST_PPT: u8 = 2; @@ -13,7 +16,9 @@ pub struct Gpu { pub slow_ppt: Option, pub clock_limits: Option>, pub slow_memory: bool, - state: crate::state::Gpu, + limits: GpuLimits, + state: crate::state::steam_deck::Gpu, + driver_mode: crate::persist::DriverJson, } // same as CPU @@ -24,20 +29,26 @@ const GPU_MEMORY_DOWNCLOCK_PATH: &str = "/sys/class/drm/card0/device/pp_dpm_fclk impl Gpu { #[inline] pub fn from_json(other: GpuJson, version: u64) -> Self { + let (oc_limits, is_default) = OverclockLimits::load_or_default(); + let driver = if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance }; match version { 0 => Self { fast_ppt: other.fast_ppt, slow_ppt: other.slow_ppt, - clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), + clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)), slow_memory: other.slow_memory, - state: crate::state::Gpu::default(), + limits: oc_limits.gpu, + state: crate::state::steam_deck::Gpu::default(), + driver_mode: driver, }, _ => Self { fast_ppt: other.fast_ppt, slow_ppt: other.slow_ppt, - clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), + clock_limits: other.clock_limits.map(|x| min_max_from_json(x, version)), slow_memory: other.slow_memory, - state: crate::state::Gpu::default(), + limits: oc_limits.gpu, + state: crate::state::steam_deck::Gpu::default(), + driver_mode: driver, }, } } @@ -52,7 +63,7 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", fast_ppt, &fast_ppt_path, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, } })?; } @@ -65,7 +76,7 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", slow_ppt, &slow_ppt_path, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, } })?; } @@ -79,7 +90,7 @@ impl Gpu { "Failed to write `manual` to `{}`: {}", GPU_FORCE_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, } })?; } @@ -87,7 +98,7 @@ impl Gpu { usdpl_back::api::files::write_single(GPU_MEMORY_DOWNCLOCK_PATH, self.slow_memory as u8) .map_err(|e| SettingError { msg: format!("Failed to write to `{}`: {}", GPU_MEMORY_DOWNCLOCK_PATH, e), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, })?; if let Some(clock_limits) = &self.clock_limits { // set clock limits @@ -100,7 +111,7 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", &payload_max, GPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, }, )?; // min clock @@ -111,32 +122,32 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", &payload_min, GPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, }, )?; } else if self.state.clock_limits_set || self.state.is_resuming { self.state.clock_limits_set = false; // disable manual clock limits // max clock - let payload_max = format!("s 1 {}\n", Self::max().clock_limits.unwrap().max); + let payload_max = format!("s 1 {}\n", self.limits.clock_max.max); usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, &payload_max).map_err( |e| SettingError { msg: format!( "Failed to write `{}` to `{}`: {}", &payload_max, GPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, }, )?; // min clock - let payload_min = format!("s 0 {}\n", Self::min().clock_limits.unwrap().min); + let payload_min = format!("s 0 {}\n", self.limits.clock_min.min); usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, &payload_min).map_err( |e| SettingError { msg: format!( "Failed to write `{}` to `{}`: {}", &payload_min, GPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, }, )?; } @@ -144,7 +155,7 @@ impl Gpu { usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| { SettingError { msg: format!("Failed to write `c` to `{}`: {}", GPU_CLOCK_LIMITS_PATH, e), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, } })?; @@ -152,35 +163,34 @@ impl Gpu { } fn clamp_all(&mut self) { - let min = Self::min(); - let max = Self::max(); if let Some(fast_ppt) = &mut self.fast_ppt { *fast_ppt = (*fast_ppt).clamp( - *min.fast_ppt.as_ref().unwrap(), - *max.fast_ppt.as_ref().unwrap(), + self.limits.fast_ppt.min, + self.limits.fast_ppt.max, ); } if let Some(slow_ppt) = &mut self.slow_ppt { *slow_ppt = (*slow_ppt).clamp( - *min.slow_ppt.as_ref().unwrap(), - *max.slow_ppt.as_ref().unwrap(), + self.limits.slow_ppt.min, + self.limits.slow_ppt.max, ); } if let Some(clock_limits) = &mut self.clock_limits { - let max_boost = max.clock_limits.as_ref().unwrap(); - let min_boost = min.clock_limits.as_ref().unwrap(); - clock_limits.min = clock_limits.min.clamp(min_boost.min, max_boost.min); - clock_limits.max = clock_limits.max.clamp(min_boost.max, max_boost.max); + clock_limits.min = clock_limits.min.clamp(self.limits.clock_min.min, self.limits.clock_min.max); + clock_limits.max = clock_limits.max.clamp(self.limits.clock_max.min, self.limits.clock_max.max); } } pub fn system_default() -> Self { + let (oc_limits, is_default) = OverclockLimits::load_or_default(); Self { fast_ppt: None, slow_ppt: None, clock_limits: None, slow_memory: false, - state: crate::state::Gpu::default(), + limits: oc_limits.gpu, + state: crate::state::steam_deck::Gpu::default(), + driver_mode: if is_default { crate::persist::DriverJson::SteamDeck } else { crate::persist::DriverJson::SteamDeckAdvance }, } } } @@ -212,30 +222,61 @@ impl OnResume for Gpu { } } -impl SettingsRange for Gpu { - #[inline] - fn max() -> Self { - Self { - fast_ppt: Some(30000000), - slow_ppt: Some(29000000), - clock_limits: Some(MinMax { - min: 1600, - max: 1600, +impl TGpu for Gpu { + fn limits(&self) -> crate::api::GpuLimits { + crate::api::GpuLimits { + fast_ppt_limits: Some(RangeLimit { + min: self.limits.fast_ppt.min / self.limits.ppt_divisor, + max: self.limits.fast_ppt.max / self.limits.ppt_divisor, }), - slow_memory: false, - state: crate::state::Gpu::default(), + slow_ppt_limits: Some(RangeLimit { + min: self.limits.slow_ppt.min / self.limits.ppt_divisor, + max: self.limits.slow_ppt.max / self.limits.ppt_divisor, + }), + ppt_step: self.limits.ppt_step, + tdp_limits: None, + tdp_boost_limits: None, + tdp_step: 42, + clock_min_limits: Some(RangeLimit { + min: self.limits.clock_min.min, + max: self.limits.clock_min.max, + }), + clock_max_limits: Some(RangeLimit { + min: self.limits.clock_max.min, + max: self.limits.clock_max.max, + }), + clock_step: self.limits.clock_step, + memory_control_capable: true, } } - #[inline] - fn min() -> Self { - Self { - fast_ppt: Some(0), - slow_ppt: Some(1000000), - clock_limits: Some(MinMax { min: 200, max: 200 }), - slow_memory: true, - state: crate::state::Gpu::default(), - } + fn json(&self) -> crate::persist::GpuJson { + self.clone().into() + } + + fn ppt(&mut self, fast: Option, slow: Option) { + self.fast_ppt = fast.map(|x| x * self.limits.ppt_divisor); + self.slow_ppt = slow.map(|x| x * self.limits.ppt_divisor); + } + + fn get_ppt(&self) -> (Option, Option) { + (self.fast_ppt.map(|x| x / self.limits.ppt_divisor), self.slow_ppt.map(|x| x / self.limits.ppt_divisor)) + } + + fn clock_limits(&mut self, limits: Option>) { + self.clock_limits = limits; + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.clock_limits.as_ref() + } + + fn slow_memory(&mut self) -> &mut bool { + &mut self.slow_memory + } + + fn provider(&self) -> crate::persist::DriverJson { + self.driver_mode.clone() } } diff --git a/backend/src/settings/steam_deck/mod.rs b/backend/src/settings/steam_deck/mod.rs new file mode 100644 index 0000000..9c325b6 --- /dev/null +++ b/backend/src/settings/steam_deck/mod.rs @@ -0,0 +1,11 @@ +mod battery; +mod cpu; +mod gpu; +mod oc_limits; +mod util; + +pub use battery::Battery; +pub use cpu::{Cpu, Cpus}; +pub use gpu::Gpu; + +pub use util::flash_led; diff --git a/backend/src/settings/steam_deck/oc_limits.rs b/backend/src/settings/steam_deck/oc_limits.rs new file mode 100644 index 0000000..47e84be --- /dev/null +++ b/backend/src/settings/steam_deck/oc_limits.rs @@ -0,0 +1,125 @@ +use serde::{Deserialize, Serialize}; +use crate::settings::MinMax; + +const OC_LIMITS_FILEPATH: &str = "pt_oc.json"; + +#[derive(Serialize, Deserialize, Debug)] +pub(super) struct OverclockLimits { + pub battery: BatteryLimits, + pub cpus: CpusLimits, + pub gpu: GpuLimits, +} + +impl Default for OverclockLimits { + fn default() -> Self { + Self { + battery: BatteryLimits::default(), + cpus: CpusLimits::default(), + gpu: GpuLimits::default(), + } + } +} + +impl OverclockLimits { + /// (Self, is_default) + pub fn load_or_default() -> (Self, bool) { + let path = oc_limits_filepath(); + if path.exists() { + log::info!("Steam Deck limits file {} found", path.display()); + let mut file = match std::fs::File::open(&path) { + Ok(f) => f, + Err(e) => { + log::warn!("Steam Deck limits file {} err: {} (using default fallback)", path.display(), e); + return (Self::default(), true); + }, + }; + match serde_json::from_reader(&mut file) { + Ok(result) => { + log::debug!("Steam Deck limits file {} successfully loaded", path.display()); + (result, false) + }, + Err(e) => { + log::warn!("Steam Deck limits file {} json err: {} (using default fallback)", path.display(), e); + (Self::default(), true) + } + } + } else { + log::info!("Steam Deck limits file {} not found (using default fallback)", path.display()); + (Self::default(), true) + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub(super) struct BatteryLimits { + pub charge_rate: MinMax, +} + +impl Default for BatteryLimits { + fn default() -> Self { + Self { + charge_rate: MinMax { min: 250, max: 2500 }, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub(super) struct CpusLimits { + pub cpus: Vec, + pub global_governors: bool, +} + +impl Default for CpusLimits { + fn default() -> Self { + Self { + cpus: [(); 8].iter().map(|_| CpuLimits::default()).collect(), + global_governors: false, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub(super) struct CpuLimits { + pub clock_min: MinMax, + pub clock_max: MinMax, + pub clock_step: u64, +} + +impl Default for CpuLimits { + fn default() -> Self { + Self { + clock_min: MinMax { min: 1400, max: 3500 }, + clock_max: MinMax { min: 400, max: 3500 }, + clock_step: 100, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub(super) struct GpuLimits { + pub fast_ppt: MinMax, + pub slow_ppt: MinMax, + pub ppt_divisor: u64, + pub ppt_step: u64, + pub clock_min: MinMax, + pub clock_max: MinMax, + pub clock_step: u64, +} + +impl Default for GpuLimits { + fn default() -> Self { + Self { + fast_ppt: MinMax { min: 1000000, max: 30_000_000 }, + slow_ppt: MinMax { min: 1000000, max: 29_000_000 }, + ppt_divisor: 1_000_000, + ppt_step: 1, + clock_min: MinMax { min: 200, max: 1600 }, + clock_max: MinMax { min: 200, max: 1600 }, + clock_step: 100, + } + } +} + +fn oc_limits_filepath() -> std::path::PathBuf { + crate::utility::settings_dir().join(OC_LIMITS_FILEPATH) +} diff --git a/backend/src/settings/steam_deck/util.rs b/backend/src/settings/steam_deck/util.rs new file mode 100644 index 0000000..def76e9 --- /dev/null +++ b/backend/src/settings/steam_deck/util.rs @@ -0,0 +1,126 @@ +//! Rough Rust port of some BatCtrl functionality +//! Original: /usr/share/jupiter_controller_fw_updater/RA_bootloader_updater/linux_host_tools/BatCtrl +//! I do not have access to the source code, so this is my own interpretation of what it does. +//! +//! But also Quanta is based in a place with some questionable copyright practices, so... +#![allow(dead_code)] + +use std::fs::OpenOptions; +use std::io::{Error, Seek, SeekFrom, Read, Write}; + +#[inline] +fn write2(p0: u8, p1: u8) -> Result { + 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, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, +]; + +const TIME_UNIT: std::time::Duration = std::time::Duration::from_millis(200); + +pub fn flash_led() { + let old_led_state = write_read(Setting::LEDStatus as _).map_err(|e| log::error!("Failed to read LED status: {}", e)); + for &code in THINGS { + let on = code != 0; + if let Err(e) = set_led(on, on, false) { + log::error!("Thing err: {}", e); + } + std::thread::sleep(TIME_UNIT); + } + if let Ok(old_led_state) = old_led_state { + log::debug!("Restoring LED state to {:#02b}", old_led_state); + write2(Setting::LEDStatus as _, old_led_state).map_err(|e| log::error!("Failed to restore LED status: {}", e)).unwrap(); + } +} + +pub fn set(setting: Setting, mode: u8) -> Result { + 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, +} diff --git a/backend/src/settings/traits.rs b/backend/src/settings/traits.rs index 56450df..c2f70bf 100644 --- a/backend/src/settings/traits.rs +++ b/backend/src/settings/traits.rs @@ -1,4 +1,6 @@ +use std::fmt::Debug; use super::SettingError; +use super::MinMax; pub trait OnSet { fn on_set(&mut self) -> Result<(), SettingError>; @@ -12,3 +14,95 @@ pub trait SettingsRange { fn max() -> Self; fn min() -> Self; } + +pub trait TGpu: OnResume + OnSet + Debug + Send { + fn limits(&self) -> crate::api::GpuLimits; + + fn json(&self) -> crate::persist::GpuJson; + + fn ppt(&mut self, fast: Option, slow: Option); + + fn get_ppt(&self) -> (Option, Option); + + fn clock_limits(&mut self, limits: Option>); + + fn get_clock_limits(&self) -> Option<&MinMax>; + + fn slow_memory(&mut self) -> &mut bool; + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::AutoDetect + } +} + +pub trait TCpus: OnResume + OnSet + Debug + Send { + fn limits(&self) -> crate::api::CpusLimits; + + fn json(&self) -> Vec; + + fn cpus(&mut self) -> Vec<&mut dyn TCpu>; + + fn len(&self) -> usize; + + fn smt(&mut self) -> &'_ mut bool; + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::AutoDetect + } +} + +pub trait TCpu: Debug + Send { + fn online(&mut self) -> &mut bool; + + fn governor(&mut self, governor: String); + + fn get_governor(&self) -> &'_ str; + + fn clock_limits(&mut self, limits: Option>); + + fn get_clock_limits(&self) -> Option<&MinMax>; +} + +pub trait TGeneral: OnResume + OnSet + Debug + Send { + fn limits(&self) -> crate::api::GeneralLimits; + + fn get_persistent(&self) -> bool; + + fn persistent(&mut self) -> &'_ mut bool; + + fn get_path(&self) -> &'_ std::path::Path; + + fn path(&mut self, path: std::path::PathBuf); + + fn get_name(&self) -> &'_ str; + + fn name(&mut self, name: String); + + fn provider(&self) -> crate::persist::DriverJson; +} + +pub trait TBattery: OnResume + OnSet + Debug + Send { + fn limits(&self) -> crate::api::BatteryLimits; + + fn json(&self) -> crate::persist::BatteryJson; + + fn charge_rate(&mut self, rate: Option); + + fn get_charge_rate(&self) -> Option; + + fn charge_mode(&mut self, mode: Option); + + fn get_charge_mode(&self) -> Option; + + fn read_charge_full(&self) -> Option; + + fn read_charge_now(&self) -> Option; + + fn read_charge_design(&self) -> Option; + + fn read_current_now(&self) -> Option; + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::AutoDetect + } +} diff --git a/backend/src/settings/unknown/battery.rs b/backend/src/settings/unknown/battery.rs new file mode 100644 index 0000000..dfc57ce --- /dev/null +++ b/backend/src/settings/unknown/battery.rs @@ -0,0 +1,70 @@ +use std::convert::Into; + +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::TBattery; +use crate::persist::BatteryJson; + +#[derive(Debug, Clone)] +pub struct Battery; + +impl Into for Battery { + #[inline] + fn into(self) -> BatteryJson { + BatteryJson { + charge_rate: None, + charge_mode: None, + } + } +} + +impl OnSet for Battery { + fn on_set(&mut self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl OnResume for Battery { + fn on_resume(&self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl TBattery for Battery { + fn limits(&self) -> crate::api::BatteryLimits { + crate::api::BatteryLimits { + charge_current: None, + charge_current_step: 50, + charge_modes: vec![], + } + } + + fn json(&self) -> crate::persist::BatteryJson { + self.clone().into() + } + + fn charge_rate(&mut self, _rate: Option) { + } + + fn get_charge_rate(&self) -> Option { + None + } + + fn charge_mode(&mut self, _rate: Option) { + } + + fn get_charge_mode(&self) -> Option { + None + } + + fn read_charge_full(&self) -> Option { None } + + fn read_charge_now(&self) -> Option { None } + + fn read_charge_design(&self) -> Option { None } + + fn read_current_now(&self) -> Option { None } + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::Unknown + } +} diff --git a/backend/src/settings/unknown/cpu.rs b/backend/src/settings/unknown/cpu.rs new file mode 100644 index 0000000..16e5ba4 --- /dev/null +++ b/backend/src/settings/unknown/cpu.rs @@ -0,0 +1,300 @@ +use std::convert::Into; + +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{TCpus, TCpu}; +use crate::persist::CpuJson; + +const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; +const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control"; + +#[derive(Debug, Clone)] +pub struct Cpus { + pub cpus: Vec, + pub smt: bool, + pub smt_capable: bool, +} + +impl OnSet for Cpus { + fn on_set(&mut self) -> Result<(), SettingError> { + if self.smt_capable { + // toggle SMT + if self.smt { + usdpl_back::api::files::write_single(CPU_SMT_PATH, "on").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `on` to `{}`: {}", + CPU_SMT_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } else { + usdpl_back::api::files::write_single(CPU_SMT_PATH, "off").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `off` to `{}`: {}", + CPU_SMT_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + } + for (i, cpu) in self.cpus.as_mut_slice().iter_mut().enumerate() { + cpu.state.do_set_online = self.smt || i % 2 == 0; + cpu.on_set()?; + } + Ok(()) + } +} + +impl OnResume for Cpus { + fn on_resume(&self) -> Result<(), SettingError> { + for cpu in &self.cpus { + cpu.on_resume()?; + } + Ok(()) + } +} + +impl Cpus { + pub fn cpu_count() -> Option { + let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH) + .unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */); + if let Some(dash_index) = data.find('-') { + let data = data.split_off(dash_index + 1); + if let Ok(max_cpu) = data.parse::() { + return Some(max_cpu + 1); + } + } + log::warn!("Failed to parse CPU info from kernel, is Tux evil?"); + None + } + + fn system_smt_capabilities() -> (bool, bool) { + match usdpl_back::api::files::read_single::<_, String, _>(CPU_SMT_PATH) { + Ok(val) => (val.trim().to_lowercase() == "on", true), + Err(_) => (false, false) + } + } + + pub fn system_default() -> Self { + if let Some(max_cpu) = Self::cpu_count() { + let mut sys_cpus = Vec::with_capacity(max_cpu); + for i in 0..max_cpu { + sys_cpus.push(Cpu::from_sys(i)); + } + let (smt_status, can_smt) = Self::system_smt_capabilities(); + Self { + cpus: sys_cpus, + smt: smt_status, + smt_capable: can_smt, + } + } else { + Self { + cpus: vec![], + smt: false, + smt_capable: false, + } + } + } + + #[inline] + pub fn from_json(mut other: Vec, version: u64) -> Self { + let (_, can_smt) = Self::system_smt_capabilities(); + let mut result = Vec::with_capacity(other.len()); + let max_cpus = Self::cpu_count(); + let smt_guess = crate::settings::util::guess_smt(&other) && can_smt; + for (i, cpu) in other.drain(..).enumerate() { + // prevent having more CPUs than available + if let Some(max_cpus) = max_cpus { + if i == max_cpus { + break; + } + } + let new_cpu = Cpu::from_json(cpu, version, i); + result.push(new_cpu); + } + if let Some(max_cpus) = max_cpus { + if result.len() != max_cpus { + let mut sys_cpus = Cpus::system_default(); + for i in result.len()..sys_cpus.cpus.len() { + result.push(sys_cpus.cpus.remove(i)); + } + } + } + Self { + cpus: result, + smt: smt_guess, + smt_capable: can_smt, + } + } +} + +impl TCpus for Cpus { + fn limits(&self) -> crate::api::CpusLimits { + crate::api::CpusLimits { + cpus: self.cpus.iter().map(|x| x.limits()).collect(), + count: self.cpus.len(), + smt_capable: self.smt_capable, + governors: Vec::with_capacity(0), + } + } + + fn json(&self) -> Vec { + self.cpus.iter().map(|x| x.to_owned().into()).collect() + } + + fn cpus(&mut self) -> Vec<&mut dyn TCpu> { + self.cpus.iter_mut().map(|x| x as &mut dyn TCpu).collect() + } + + fn len(&self) -> usize { + self.cpus.len() + } + + fn smt(&mut self) -> &'_ mut bool { + &mut self.smt + } + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::Unknown + } +} + +#[derive(Debug, Clone)] +pub struct Cpu { + pub online: bool, + pub governor: String, + index: usize, + state: crate::state::steam_deck::Cpu, +} + + +impl Cpu { + #[inline] + pub fn from_json(other: CpuJson, version: u64, i: usize) -> Self { + match version { + 0 => Self { + online: other.online, + governor: other.governor, + index: i, + state: crate::state::steam_deck::Cpu::default(), + }, + _ => Self { + online: other.online, + governor: other.governor, + index: i, + state: crate::state::steam_deck::Cpu::default(), + }, + } + } + + fn set_all(&mut self) -> Result<(), SettingError> { + // set cpu online/offline + if self.index != 0 && self.state.do_set_online { // cpu0 cannot be disabled + let online_path = cpu_online_path(self.index); + usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| { + SettingError { + msg: format!("Failed to write to `{}`: {}", &online_path, e), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + + // set governor + if self.index == 0 || self.online { + let governor_path = cpu_governor_path(self.index); + usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| { + SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &self.governor, &governor_path, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + Ok(()) + } + + fn from_sys(cpu_index: usize) -> Self { + Self { + online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0, + governor: usdpl_back::api::files::read_single(cpu_governor_path(cpu_index)) + .unwrap_or("schedutil".to_owned()), + index: cpu_index, + state: crate::state::steam_deck::Cpu::default(), + } + } + + fn limits(&self) -> crate::api::CpuLimits { + crate::api::CpuLimits { + clock_min_limits: None, + clock_max_limits: None, + clock_step: 100, + governors: vec![], // TODO + } + } +} + +impl Into for Cpu { + #[inline] + fn into(self) -> CpuJson { + CpuJson { + online: self.online, + clock_limits: None, + governor: self.governor, + } + } +} + +impl OnSet for Cpu { + fn on_set(&mut self) -> Result<(), SettingError> { + //self.clamp_all(); + self.set_all() + } +} + +impl OnResume for Cpu { + fn on_resume(&self) -> Result<(), SettingError> { + let mut copy = self.clone(); + copy.state.is_resuming = true; + copy.set_all() + } +} + +impl TCpu for Cpu { + fn online(&mut self) -> &mut bool { + &mut self.online + } + + fn governor(&mut self, governor: String) { + self.governor = governor; + } + + fn get_governor(&self) -> &'_ str { + &self.governor + } + + fn clock_limits(&mut self, _limits: Option>) { + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + None + } +} + +#[inline] +fn cpu_online_path(index: usize) -> String { + format!("/sys/devices/system/cpu/cpu{}/online", index) +} + +#[inline] +fn cpu_governor_path(index: usize) -> String { + format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor", + index + ) +} diff --git a/backend/src/settings/unknown/gpu.rs b/backend/src/settings/unknown/gpu.rs new file mode 100644 index 0000000..fecc4ed --- /dev/null +++ b/backend/src/settings/unknown/gpu.rs @@ -0,0 +1,93 @@ +use std::convert::Into; + +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::TGpu; +use crate::persist::GpuJson; + +#[derive(Debug, Clone)] +pub struct Gpu { + slow_memory: bool, // ignored +} + +impl Gpu { + #[inline] + pub fn from_json(_other: GpuJson, _version: u64) -> Self { + Self { + slow_memory: false, + } + } + + pub fn system_default() -> Self { + Self { + slow_memory: false, + } + } +} + +impl Into for Gpu { + #[inline] + fn into(self) -> GpuJson { + GpuJson { + fast_ppt: None, + slow_ppt: None, + clock_limits: None, + slow_memory: false, + } + } +} + +impl OnSet for Gpu { + fn on_set(&mut self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl OnResume for Gpu { + fn on_resume(&self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl TGpu for Gpu { + fn limits(&self) -> crate::api::GpuLimits { + crate::api::GpuLimits { + fast_ppt_limits: None, + slow_ppt_limits: None, + ppt_step: 1_000_000, + tdp_limits: None, + tdp_boost_limits: None, + tdp_step: 42, + clock_min_limits: None, + clock_max_limits: None, + clock_step: 100, + memory_control_capable: false, + } + } + + fn json(&self) -> crate::persist::GpuJson { + self.clone().into() + } + + fn ppt(&mut self, _fast: Option, _slow: Option) { + } + + fn get_ppt(&self) -> (Option, Option) { + (None, None) + } + + fn clock_limits(&mut self, _limits: Option>) { + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + None + } + + fn slow_memory(&mut self) -> &mut bool { + &mut self.slow_memory + } + + fn provider(&self) -> crate::persist::DriverJson { + crate::persist::DriverJson::Unknown + } +} diff --git a/backend/src/settings/unknown/mod.rs b/backend/src/settings/unknown/mod.rs new file mode 100644 index 0000000..2039cae --- /dev/null +++ b/backend/src/settings/unknown/mod.rs @@ -0,0 +1,7 @@ +mod battery; +mod cpu; +mod gpu; + +pub use battery::Battery; +pub use cpu::{Cpu, Cpus}; +pub use gpu::Gpu; diff --git a/backend/src/settings/util.rs b/backend/src/settings/util.rs new file mode 100644 index 0000000..83f032d --- /dev/null +++ b/backend/src/settings/util.rs @@ -0,0 +1,7 @@ +pub fn guess_smt(cpus: &Vec) -> bool { + let mut guess = true; + for i in (0..cpus.len()).step_by(2) { + guess &= cpus[i].online == cpus[i+1].online; + } + guess +} diff --git a/backend/src/state/generic/gpu.rs b/backend/src/state/generic/gpu.rs new file mode 100644 index 0000000..51e0909 --- /dev/null +++ b/backend/src/state/generic/gpu.rs @@ -0,0 +1,16 @@ +#[derive(Debug, Clone)] +pub struct Gpu { + pub clock_limits_set: bool, + pub old_fast_ppt: Option, + pub old_slow_ppt: Option, +} + +impl std::default::Default for Gpu { + fn default() -> Self { + Self { + clock_limits_set: false, + old_fast_ppt: None, + old_slow_ppt: None, + } + } +} diff --git a/backend/src/state/generic/mod.rs b/backend/src/state/generic/mod.rs new file mode 100644 index 0000000..d91c88c --- /dev/null +++ b/backend/src/state/generic/mod.rs @@ -0,0 +1,3 @@ +mod gpu; + +pub use gpu::Gpu; diff --git a/backend/src/state/mod.rs b/backend/src/state/mod.rs index 1eb1e58..570de60 100644 --- a/backend/src/state/mod.rs +++ b/backend/src/state/mod.rs @@ -1,11 +1,8 @@ -mod battery; -mod cpu; mod error; -mod gpu; mod traits; -pub use battery::Battery; -pub use cpu::Cpu; +pub mod generic; +pub mod steam_deck; + pub use error::StateError; -pub use gpu::Gpu; pub use traits::OnPoll; diff --git a/backend/src/state/battery.rs b/backend/src/state/steam_deck/battery.rs similarity index 76% rename from backend/src/state/battery.rs rename to backend/src/state/steam_deck/battery.rs index ab4c9f0..83826a2 100644 --- a/backend/src/state/battery.rs +++ b/backend/src/state/steam_deck/battery.rs @@ -1,12 +1,14 @@ #[derive(Debug, Clone)] pub struct Battery { pub charge_rate_set: bool, + pub charge_mode_set: bool, } impl std::default::Default for Battery { fn default() -> Self { Self { charge_rate_set: false, + charge_mode_set: false, } } } diff --git a/backend/src/state/cpu.rs b/backend/src/state/steam_deck/cpu.rs similarity index 81% rename from backend/src/state/cpu.rs rename to backend/src/state/steam_deck/cpu.rs index e122115..54b423f 100644 --- a/backend/src/state/cpu.rs +++ b/backend/src/state/steam_deck/cpu.rs @@ -2,6 +2,7 @@ pub struct Cpu { pub clock_limits_set: bool, pub is_resuming: bool, + pub do_set_online: bool, } impl std::default::Default for Cpu { @@ -9,6 +10,7 @@ impl std::default::Default for Cpu { Self { clock_limits_set: false, is_resuming: false, + do_set_online: true, } } } diff --git a/backend/src/state/gpu.rs b/backend/src/state/steam_deck/gpu.rs similarity index 100% rename from backend/src/state/gpu.rs rename to backend/src/state/steam_deck/gpu.rs diff --git a/backend/src/state/steam_deck/mod.rs b/backend/src/state/steam_deck/mod.rs new file mode 100644 index 0000000..c7dca59 --- /dev/null +++ b/backend/src/state/steam_deck/mod.rs @@ -0,0 +1,7 @@ +mod battery; +mod cpu; +mod gpu; + +pub use battery::Battery; +pub use cpu::Cpu; +pub use gpu::Gpu; diff --git a/backend/src/utility.rs b/backend/src/utility.rs index 1369816..aed4673 100644 --- a/backend/src/utility.rs +++ b/backend/src/utility.rs @@ -1,5 +1,5 @@ use std::fmt::Display; -use std::sync::{LockResult, MutexGuard}; +//use std::sync::{LockResult, MutexGuard}; pub fn unwrap_maybe_fatal(result: Result, message: &str) -> T { match result { @@ -11,7 +11,7 @@ pub fn unwrap_maybe_fatal(result: Result, message: & } } -pub fn unwrap_lock<'a, T: Sized>( +/*pub fn unwrap_lock<'a, T: Sized>( result: LockResult>, lock_name: &str, ) -> MutexGuard<'a, T> { @@ -22,10 +22,10 @@ pub fn unwrap_lock<'a, T: Sized>( panic!("Failed to acquire {} lock: {}", lock_name, e); } } -} +}*/ pub fn settings_dir() -> std::path::PathBuf { usdpl_back::api::dirs::home() - .unwrap_or_else(|| "/home/deck".into()) + .unwrap_or_else(|| "/tmp/".into()) .join(".config/powertools/") } diff --git a/package.json b/package.json index b46ed56..60ecd5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "PowerTools", - "version": "1.0.6", + "version": "1.1.0", "description": "Power tweaks for power users", "scripts": { "build": "shx rm -rf dist && rollup -c", @@ -27,20 +27,20 @@ "devDependencies": { "@rollup/plugin-commonjs": "^21.1.0", "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^13.2.1", + "@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-replace": "^4.0.0", - "@rollup/plugin-typescript": "^8.3.2", + "@rollup/plugin-typescript": "^8.5.0", "@types/react": "16.14.0", "@types/webpack": "^5.28.0", - "rollup": "^2.70.2", + "rollup": "^2.79.1", "rollup-plugin-import-assets": "^1.1.1", "shx": "^0.3.4", - "tslib": "^2.4.0", - "typescript": "^4.6.4" + "tslib": "^2.4.1", + "typescript": "^4.9.4" }, "dependencies": { - "decky-frontend-lib": "3.*", - "react-icons": "^4.4.0", - "usdpl-front": "file:./src/usdpl_front" + "decky-frontend-lib": "~3.18.10", + "react-icons": "^4.7.1", + "usdpl-front": "file:src/usdpl_front" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a23363a..39cf072 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,22 +3,22 @@ lockfileVersion: 5.4 specifiers: '@rollup/plugin-commonjs': ^21.1.0 '@rollup/plugin-json': ^4.1.0 - '@rollup/plugin-node-resolve': ^13.2.1 + '@rollup/plugin-node-resolve': ^13.3.0 '@rollup/plugin-replace': ^4.0.0 - '@rollup/plugin-typescript': ^8.3.2 + '@rollup/plugin-typescript': ^8.5.0 '@types/react': 16.14.0 '@types/webpack': ^5.28.0 - decky-frontend-lib: 3.* - react-icons: ^4.4.0 - rollup: ^2.70.2 + decky-frontend-lib: ~3.18.10 + react-icons: ^4.7.1 + rollup: ^2.79.1 rollup-plugin-import-assets: ^1.1.1 shx: ^0.3.4 - tslib: ^2.4.0 - typescript: ^4.6.4 - usdpl-front: file:./src/usdpl_front + tslib: ^2.4.1 + typescript: ^4.9.4 + usdpl-front: file:src/usdpl_front dependencies: - decky-frontend-lib: 3.18.5 + decky-frontend-lib: 3.18.10 react-icons: 4.7.1 usdpl-front: file:src/usdpl_front @@ -27,14 +27,14 @@ devDependencies: '@rollup/plugin-json': 4.1.0_rollup@2.79.1 '@rollup/plugin-node-resolve': 13.3.0_rollup@2.79.1 '@rollup/plugin-replace': 4.0.0_rollup@2.79.1 - '@rollup/plugin-typescript': 8.5.0_sbiskyiysxhldmns7rmnvoiszu + '@rollup/plugin-typescript': 8.5.0_bhcmvni67fkldpaxrtldxbogce '@types/react': 16.14.0 '@types/webpack': 5.28.0 rollup: 2.79.1 rollup-plugin-import-assets: 1.1.1_rollup@2.79.1 shx: 0.3.4 - tslib: 2.4.1 - typescript: 4.9.4 + tslib: 2.5.0 + typescript: 4.9.5 packages: @@ -108,8 +108,8 @@ packages: dependencies: '@rollup/pluginutils': 3.1.0_rollup@2.79.1 '@types/resolve': 1.17.1 - deepmerge: 4.2.2 - is-builtin-module: 3.2.0 + deepmerge: 4.3.0 + is-builtin-module: 3.2.1 is-module: 1.0.0 resolve: 1.22.1 rollup: 2.79.1 @@ -125,7 +125,7 @@ packages: rollup: 2.79.1 dev: true - /@rollup/plugin-typescript/8.5.0_sbiskyiysxhldmns7rmnvoiszu: + /@rollup/plugin-typescript/8.5.0_bhcmvni67fkldpaxrtldxbogce: resolution: {integrity: sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ==} engines: {node: '>=8.0.0'} peerDependencies: @@ -139,8 +139,8 @@ packages: '@rollup/pluginutils': 3.1.0_rollup@2.79.1 resolve: 1.22.1 rollup: 2.79.1 - tslib: 2.4.1 - typescript: 4.9.4 + tslib: 2.5.0 + typescript: 4.9.5 dev: true /@rollup/pluginutils/3.1.0_rollup@2.79.1: @@ -158,12 +158,12 @@ packages: /@types/eslint-scope/3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: - '@types/eslint': 8.4.10 + '@types/eslint': 8.21.0 '@types/estree': 0.0.51 dev: true - /@types/eslint/8.4.10: - resolution: {integrity: sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==} + /@types/eslint/8.21.0: + resolution: {integrity: sha512-35EhHNOXgxnUgh4XCJsGhE7zdlDhYDN/aMG6UbkByCFFNgQ7b3U+uVoqBpicFydR8JEfgdjCF7SJ7MiJfzuiTA==} dependencies: '@types/estree': 0.0.51 '@types/json-schema': 7.0.11 @@ -333,16 +333,16 @@ packages: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} dev: true - /acorn-import-assertions/1.8.0_acorn@8.8.1: + /acorn-import-assertions/1.8.0_acorn@8.8.2: resolution: {integrity: sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==} peerDependencies: acorn: ^8 dependencies: - acorn: 8.8.1 + acorn: 8.8.2 dev: true - /acorn/8.8.1: - resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} + /acorn/8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -375,15 +375,15 @@ packages: concat-map: 0.0.1 dev: true - /browserslist/4.21.4: - resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} + /browserslist/4.21.5: + resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001442 - electron-to-chromium: 1.4.284 - node-releases: 2.0.8 - update-browserslist-db: 1.0.10_browserslist@4.21.4 + caniuse-lite: 1.0.30001450 + electron-to-chromium: 1.4.286 + node-releases: 2.0.9 + update-browserslist-db: 1.0.10_browserslist@4.21.5 dev: true /buffer-from/1.1.2: @@ -395,8 +395,8 @@ packages: engines: {node: '>=6'} dev: true - /caniuse-lite/1.0.30001442: - resolution: {integrity: sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==} + /caniuse-lite/1.0.30001450: + resolution: {integrity: sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew==} dev: true /chrome-trace-event/1.0.3: @@ -420,17 +420,17 @@ packages: resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} dev: true - /decky-frontend-lib/3.18.5: - resolution: {integrity: sha512-CTIJs61La17spws5IzAbLbZ/Bqe+gYgnO6xOrolK1QZh7ZbZeoQ67dtnI0zqRMMC10J8H7jPdqmQnwGN10/bzw==} + /decky-frontend-lib/3.18.10: + resolution: {integrity: sha512-2mgbA3sSkuwQR/FnmhXVrcW6LyTS95IuL6muJAmQCruhBvXapDtjk1TcgxqMZxFZwGD1IPnemPYxHZll6IgnZw==} dev: false - /deepmerge/4.2.2: - resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + /deepmerge/4.3.0: + resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==} engines: {node: '>=0.10.0'} dev: true - /electron-to-chromium/1.4.284: - resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} + /electron-to-chromium/1.4.286: + resolution: {integrity: sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ==} dev: true /enhanced-resolve/5.12.0: @@ -563,8 +563,8 @@ packages: engines: {node: '>= 0.10'} dev: true - /is-builtin-module/3.2.0: - resolution: {integrity: sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==} + /is-builtin-module/3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} engines: {node: '>=6'} dependencies: builtin-modules: 3.3.0 @@ -644,8 +644,8 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /node-releases/2.0.8: - resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==} + /node-releases/2.0.9: + resolution: {integrity: sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA==} dev: true /once/1.4.0: @@ -672,8 +672,8 @@ packages: engines: {node: '>=8.6'} dev: true - /punycode/2.2.0: - resolution: {integrity: sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==} + /punycode/2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} dev: true @@ -742,8 +742,8 @@ packages: ajv-keywords: 3.5.2_ajv@6.12.6 dev: true - /serialize-javascript/6.0.0: - resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + /serialize-javascript/6.0.1: + resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==} dependencies: randombytes: 2.1.0 dev: true @@ -820,39 +820,39 @@ packages: '@jridgewell/trace-mapping': 0.3.17 jest-worker: 27.5.1 schema-utils: 3.1.1 - serialize-javascript: 6.0.0 - terser: 5.16.1 + serialize-javascript: 6.0.1 + terser: 5.16.3 webpack: 5.75.0 dev: true - /terser/5.16.1: - resolution: {integrity: sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==} + /terser/5.16.3: + resolution: {integrity: sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==} engines: {node: '>=10'} hasBin: true dependencies: '@jridgewell/source-map': 0.3.2 - acorn: 8.8.1 + acorn: 8.8.2 commander: 2.20.3 source-map-support: 0.5.21 dev: true - /tslib/2.4.1: - resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} + /tslib/2.5.0: + resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} dev: true - /typescript/4.9.4: - resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} + /typescript/4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} hasBin: true dev: true - /update-browserslist-db/1.0.10_browserslist@4.21.4: + /update-browserslist-db/1.0.10_browserslist@4.21.5: resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.4 + browserslist: 4.21.5 escalade: 3.1.1 picocolors: 1.0.0 dev: true @@ -860,7 +860,7 @@ packages: /uri-js/4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.2.0 + punycode: 2.3.0 dev: true /url-join/4.0.1: @@ -895,9 +895,9 @@ packages: '@webassemblyjs/ast': 1.11.1 '@webassemblyjs/wasm-edit': 1.11.1 '@webassemblyjs/wasm-parser': 1.11.1 - acorn: 8.8.1 - acorn-import-assertions: 1.8.0_acorn@8.8.1 - browserslist: 4.21.4 + acorn: 8.8.2 + acorn-import-assertions: 1.8.0_acorn@8.8.2 + browserslist: 4.21.5 chrome-trace-event: 1.0.3 enhanced-resolve: 5.12.0 es-module-lexer: 0.9.3 @@ -927,5 +927,5 @@ packages: file:src/usdpl_front: resolution: {directory: src/usdpl_front, type: directory} name: usdpl-front - version: 0.6.2 + version: 0.9.1 dev: false diff --git a/pt_oc.json b/pt_oc.json new file mode 100644 index 0000000..3d30b49 --- /dev/null +++ b/pt_oc.json @@ -0,0 +1,59 @@ +{ + "battery": { + "charge_rate": {"min": 250, "max": 2500} + }, + "cpus": { + "cpus": [ + { + "clock_min": {"min": 1400, "max": 3500}, + "clock_max": {"min": 500, "max": 3500}, + "clock_step": 100 + }, + { + "clock_min": {"min": 1400, "max": 3500}, + "clock_max": {"min": 500, "max": 3500}, + "clock_step": 100 + }, + { + "clock_min": {"min": 1400, "max": 3500}, + "clock_max": {"min": 500, "max": 3500}, + "clock_step": 100 + }, + { + "clock_min": {"min": 1400, "max": 3500}, + "clock_max": {"min": 500, "max": 3500}, + "clock_step": 100 + }, + { + "clock_min": {"min": 1400, "max": 3500}, + "clock_max": {"min": 500, "max": 3500}, + "clock_step": 100 + }, + { + "clock_min": {"min": 1400, "max": 3500}, + "clock_max": {"min": 500, "max": 3500}, + "clock_step": 100 + }, + { + "clock_min": {"min": 1400, "max": 3500}, + "clock_max": {"min": 500, "max": 3500}, + "clock_step": 100 + }, + { + "clock_min": {"min": 1400, "max": 3500}, + "clock_max": {"min": 500, "max": 3500}, + "clock_step": 100 + } + ], + "global_governors": false + }, + "gpu": { + "fast_ppt": {"min": 1000000, "max": 30000000}, + "slow_ppt": {"min": 1000000, "max": 29000000}, + "ppt_divisor": 1000000, + "ppt_step": 1, + "clock_min": {"min": 200, "max": 1600}, + "clock_max": {"min": 200, "max": 1600}, + "clock_step": 100 + } +} diff --git a/src/backend.ts b/src/backend.ts index 29d53b8..55d05c8 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -1,29 +1,92 @@ -import {init_usdpl, target_usdpl, init_embedded, call_backend} from "usdpl-front"; +import {init_usdpl, target_usdpl, init_embedded, call_backend, init_tr} from "usdpl-front"; const USDPL_PORT: number = 44443; // Utility -export function resolve(promise: Promise, setter: any) { +export function resolve(promise: Promise, setter: (t: T) => void) { (async function () { let data = await promise; if (data != null) { console.debug("Got resolved", data); setter(data); } else { - console.warn("Resolve failed:", data); + console.warn("Resolve failed:", data, promise); + log(LogLevel.Warn, "A resolve failed"); } })(); } +export function resolve_nullable(promise: Promise, setter: (t: T | null) => void) { + (async function () { + let data = await promise; + console.debug("Got resolved", data); + setter(data); + })(); +} + export async function initBackend() { // init usdpl await init_embedded(); init_usdpl(USDPL_PORT); console.log("USDPL started for framework: " + target_usdpl()); + const user_locale = + navigator.languages && navigator.languages.length + ? navigator.languages[0] + : navigator.language; + console.log("POWERTOOLS: locale", user_locale); + let mo_path = "../plugins/PowerTools/translations/" + user_locale.toString() + ".mo"; + await init_tr(mo_path); + //await init_tr("../plugins/PowerTools/translations/test.mo"); //setReady(true); } +// API limit types + +export type RangeLimit = { + min: number; + max: number; +}; + +export type SettingsLimits = { + battery: BatteryLimits; + cpu: CpusLimits; + gpu: GpuLimits; + general: GeneralLimits; +}; + +export type BatteryLimits = { + charge_current: RangeLimit | null; + charge_current_step: number; + charge_modes: string[]; +}; + +export type CpuLimits = { + clock_min_limits: RangeLimit | null; + clock_max_limits: RangeLimit | null; + clock_step: number; + governors: string[]; +}; + +export type CpusLimits = { + cpus: CpuLimits[]; + count: number; + smt_capable: boolean; + governors: string[]; +}; + +export type GeneralLimits = {}; + +export type GpuLimits = { + fast_ppt_limits: RangeLimit | null; + slow_ppt_limits: RangeLimit | null; + ppt_step: number; + clock_min_limits: RangeLimit | null; + clock_max_limits: RangeLimit | null; + clock_step: number; + memory_control_capable: boolean; +}; + // API export async function getInfo(): Promise { @@ -48,7 +111,7 @@ export async function getBatteryChargeDesign(): Promise { return (await call_backend("BATTERY_charge_design", []))[0]; } -export async function getBatteryChargeRate(): Promise { +export async function getBatteryChargeRate(): Promise { return (await call_backend("BATTERY_get_charge_rate", []))[0]; } @@ -60,12 +123,32 @@ export async function unsetBatteryChargeRate(): Promise { return await call_backend("BATTERY_unset_charge_rate", []); } +export async function getBatteryChargeMode(): Promise { + return (await call_backend("BATTERY_get_charge_mode", []))[0]; +} + +export async function setBatteryChargeMode(val: string): Promise { + return (await call_backend("BATTERY_set_charge_mode", [val]))[0]; +} + +export async function unsetBatteryChargeMode(): Promise { + return await call_backend("BATTERY_unset_charge_mode", []); +} + // CPU -export async function getCpuCount(): Promise { - return (await call_backend("CPU_count", []))[0]; +export async function setCpuSmt(status: boolean): Promise { + return await call_backend("CPU_set_smt", [status]); } +export async function getCpuSmt(): Promise { + return await call_backend("CPU_get_smt", []); +} + +/*export async function getCpuCount(): Promise { + return (await call_backend("CPU_count", []))[0]; +}*/ + export async function setCpuOnline(index: number, online: boolean): Promise { return (await call_backend("CPU_set_online", [index, online]))[0]; } @@ -150,10 +233,38 @@ export async function loadGeneralDefaultSettings(): Promise { return (await call_backend("GENERAL_load_default_settings", []))[0]; } -export async function getGeneralSettingsName(): Promise { +export async function loadGeneralSystemSettings(): Promise { + return (await call_backend("GENERAL_load_system_settings", []))[0]; +} + +export async function getGeneralSettingsName(): Promise { return (await call_backend("GENERAL_get_name", []))[0]; } export async function waitForComplete(): Promise { return (await call_backend("GENERAL_wait_for_unlocks", []))[0]; } + +export async function getLimits(): Promise { + return (await call_backend("GENERAL_get_limits", []))[0]; +} + +export async function getDriverProviderName(name: string): Promise { + return (await call_backend("GENERAL_get_provider", [name]))[0]; +} + +export enum LogLevel { + Trace = 1, + Debug = 2, + Info = 3, + Warn = 4, + Error = 5, +} + +export async function log(level: LogLevel, msg: string): Promise { + return (await call_backend("LOG", [level, msg]))[0]; +} + +export async function idk(): Promise { + return (await call_backend("GENERAL_idk", []))[0]; +} diff --git a/src/components/battery.tsx b/src/components/battery.tsx new file mode 100644 index 0000000..7008156 --- /dev/null +++ b/src/components/battery.tsx @@ -0,0 +1,139 @@ +import { Fragment } from "react"; +import {Component} from "react"; +import { + ToggleField, + SliderField, + Field, + SingleDropdownOption, + Dropdown, + PanelSectionRow, + staticClasses, +} from "decky-frontend-lib"; +import * as backend from "../backend"; +import { tr } from "usdpl-front"; +import { + LIMITS_INFO, + CHARGE_DESIGN_BATT, + CHARGE_FULL_BATT, + CHARGE_NOW_BATT, + CHARGE_RATE_BATT, + CHARGE_MODE_BATT, + CURRENT_BATT, +} from "../consts"; +import { set_value, get_value} from "usdpl-front"; + +export class Battery extends Component<{}> { + constructor(props: {}) { + super(props); + this.state = { + reloadThingy: "/shrug", + }; + } + + render() { + const reloadGUI = (x: string) => this.setState({reloadThingy: x}); + const chargeModeOptions: SingleDropdownOption[] = (get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_modes.map((elem) => {return { + data: elem, + label: {elem}, + };}); + return ( + {/* Battery */} +
+ {tr("Battery")} +
+ {get_value(CHARGE_NOW_BATT) != null && get_value(CHARGE_FULL_BATT) != null && + + {get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%) + + } + {get_value(CHARGE_FULL_BATT) != null && get_value(CHARGE_DESIGN_BATT) != null && + + {get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%) + + } + {(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current != null && + { + if (value) { + set_value(CHARGE_RATE_BATT, 2500); + reloadGUI("BATTChargeRateToggle"); + } else { + set_value(CHARGE_RATE_BATT, null); + backend.resolve(backend.unsetBatteryChargeRate(), (_: any[]) => { + reloadGUI("BATTUnsetChargeRate"); + }); + } + }} + /> + { get_value(CHARGE_RATE_BATT) != null && { + backend.log(backend.LogLevel.Debug, "Charge rate is now " + val.toString()); + const rateNow = get_value(CHARGE_RATE_BATT); + if (val != rateNow) { + backend.resolve(backend.setBatteryChargeRate(val), + (rate: number) => { + set_value(CHARGE_RATE_BATT, rate); + reloadGUI("BATTChargeRate"); + }); + } + }} + />} + } + {chargeModeOptions.length != 0 && + { + if (value) { + set_value(CHARGE_MODE_BATT, chargeModeOptions[0].data as string); + reloadGUI("BATTChargeModeToggle"); + } else { + set_value(CHARGE_MODE_BATT, null); + backend.resolve(backend.unsetBatteryChargeMode(), (_: any[]) => { + reloadGUI("BATTUnsetChargeMode"); + }); + } + }} + /> + {get_value(CHARGE_MODE_BATT) != null && + { + return val.data == get_value(CHARGE_MODE_BATT); + })} + strDefaultLabel={get_value(CHARGE_MODE_BATT)} + onChange={(elem: SingleDropdownOption) => { + backend.log(backend.LogLevel.Debug, "Charge mode dropdown selected " + elem.data.toString()); + backend.resolve(backend.setBatteryChargeMode(elem.data as string), (mode: string) => { + set_value(CHARGE_MODE_BATT, mode); + reloadGUI("BATTChargeMode"); + }); + }} + /> + } + } + + + {get_value(CURRENT_BATT)} mA + + +
); + } +} diff --git a/src/components/cpus.tsx b/src/components/cpus.tsx new file mode 100644 index 0000000..2b9a60d --- /dev/null +++ b/src/components/cpus.tsx @@ -0,0 +1,423 @@ +import { Fragment } from "react"; +import { Component } from "react"; +import { + ToggleField, + SliderField, + Field, + SingleDropdownOption, + Dropdown, + PanelSectionRow, + staticClasses, +} from "decky-frontend-lib"; +import * as backend from "../backend"; +import { tr } from "usdpl-front"; +import { + LIMITS_INFO, + SMT_CPU, + CLOCK_MAX_CPU, + CLOCK_MIN_CPU, + CLOCK_MIN_MAX_CPU, + ONLINE_CPUS, + ONLINE_STATUS_CPUS, + GOVERNOR_CPU, +} from "../consts"; +import { set_value, get_value } from "usdpl-front"; + +interface CpuState { + reloadThingy: string; +} + +let advancedMode = false; +let advancedCpu = 1; + +export class Cpus extends Component<{}, CpuState> { + constructor(props: {}) { + super(props); + this.state = { + reloadThingy: "/shrug", + }; + } + + render() { + const reloadGUI = (x: string) => this.setState((_state) => { + return { + reloadThingy: x, + }; + }); + + const total_cpus = (get_value(LIMITS_INFO) as backend.SettingsLimits | null)?.cpu.count ?? 8; + const advancedCpuIndex = advancedCpu - 1; + const smtAllowed = (get_value(LIMITS_INFO) as backend.SettingsLimits | null)?.cpu.smt_capable ?? true; + + const governorOptions: SingleDropdownOption[] = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].governors.map((elem) => {return { + data: elem, + label: {elem}, + };}); + + const governorGlobalOptions: SingleDropdownOption[] = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.governors.map((elem) => {return { + data: elem, + label: {elem}, + };}); + + return ( + {/* CPU */} +
+ {tr("CPU")} +
+ + { + advancedMode = advanced; + this.setState((state) => { + return { + reloadThingy: state.reloadThingy, + }; + }); + }} + /> + + {/* CPU plebeian mode */} + {!advancedMode && smtAllowed && + { + backend.log(backend.LogLevel.Debug, "SMT is now " + smt.toString()); + //const cpus = get_value(ONLINE_CPUS); + const smtNow = smt && smtAllowed; + backend.resolve(backend.setCpuSmt(smtNow), (statii: boolean[]) => { + set_value(SMT_CPU, smtNow); + set_value(ONLINE_STATUS_CPUS, statii); + const count = countCpus(statii); + set_value(ONLINE_CPUS, count); + reloadGUI("SMT"); + }); + }} + /> + } + {!advancedMode && + { + backend.log(backend.LogLevel.Debug, "CPU slider is now " + cpus.toString()); + const onlines = get_value(ONLINE_CPUS); + if (cpus != onlines) { + set_value(ONLINE_CPUS, cpus); + const smtNow = get_value(SMT_CPU); + let onlines: boolean[] = []; + for (let i = 0; i < total_cpus; i++) { + const online = smtNow? i < cpus : (i % 2 == 0) && (i < cpus * 2); + onlines.push(online); + } + backend.resolve(backend.setCpuOnlines(onlines), (statii: boolean[]) => { + set_value(ONLINE_STATUS_CPUS, statii); + const count = countCpus(statii); + set_value(ONLINE_CPUS, count); + reloadGUI("CPUs"); + }); + reloadGUI("CPUsImmediate"); + } + }} + /> + } + {!advancedMode && + { + if (value) { + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits != null) { + set_value(CLOCK_MIN_CPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits!.min); + } + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits != null) { + set_value(CLOCK_MAX_CPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits!.max); + } + syncPlebClockToAdvanced(); + reloadGUI("CPUFreqToggle"); + } else { + set_value(CLOCK_MIN_CPU, null); + set_value(CLOCK_MAX_CPU, null); + for (let i = 0; i < total_cpus; i++) { + backend.resolve(backend.unsetCpuClockLimits(i), (_idc: any[]) => {}); + } + backend.resolve(backend.waitForComplete(), (_: boolean) => { + reloadGUI("CPUUnsetFreq"); + }); + syncPlebClockToAdvanced(); + } + }} + /> + } + {!advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits != null && + {get_value(CLOCK_MIN_CPU) != null && { + backend.log(backend.LogLevel.Debug, "Min freq slider is now " + freq.toString()); + const freqNow = get_value(CLOCK_MIN_CPU); + const maxNow = get_value(CLOCK_MAX_CPU); + if (freq != freqNow && ((maxNow != null && freq <= maxNow) || maxNow == null)) { + set_value(CLOCK_MIN_CPU, freq); + for (let i = 0; i < total_cpus; i++) { + backend.resolve(backend.setCpuClockLimits(i, freq, get_value(CLOCK_MAX_CPU)), + (limits: number[]) => { + set_value(CLOCK_MIN_CPU, limits[0]); + set_value(CLOCK_MAX_CPU, limits[1]); + syncPlebClockToAdvanced(); + }); + } + backend.resolve(backend.waitForComplete(), (_: boolean) => { + reloadGUI("CPUMinFreq"); + }); + reloadGUI("CPUMinFreqImmediate"); + } + }} + />} + } + {!advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits != null && + {get_value(CLOCK_MAX_CPU) != null && { + backend.log(backend.LogLevel.Debug, "Max freq slider is now " + freq.toString()); + const freqNow = get_value(CLOCK_MAX_CPU); + const minNow = get_value(CLOCK_MIN_CPU); + if (freq != freqNow && ((minNow != null && freq >= minNow) || minNow == null)) { + set_value(CLOCK_MAX_CPU, freq); + for (let i = 0; i < total_cpus; i++) { + backend.resolve(backend.setCpuClockLimits(i, get_value(CLOCK_MIN_CPU), freq), + (limits: number[]) => { + set_value(CLOCK_MIN_CPU, limits[0]); + set_value(CLOCK_MAX_CPU, limits[1]); + syncPlebClockToAdvanced(); + }); + } + backend.resolve(backend.waitForComplete(), (_: boolean) => { + reloadGUI("CPUMaxFreq"); + }); + reloadGUI("CPUMaxFreqImmediate"); + } + }} + />} + } + {!advancedMode && governorGlobalOptions.length != 0 && + + { + backend.log(backend.LogLevel.Debug, "POWERTOOLS: array item " + val.toString()); + backend.log(backend.LogLevel.Debug, "POWERTOOLS: looking for data " + get_value(GOVERNOR_CPU)[0].toString()); + return val.data == get_value(GOVERNOR_CPU)[0]; + })} + strDefaultLabel={get_value(GOVERNOR_CPU)[0]} + onChange={(elem: SingleDropdownOption) => { + backend.log(backend.LogLevel.Debug, "Governor global dropdown selected " + elem.data.toString()); + const governors = get_value(GOVERNOR_CPU); + for (let i = 0; i < total_cpus; i++) { + governors[i] = elem.data as string; + backend.resolve(backend.setCpuGovernor(i, elem.data as string), (_: string) => {}); + } + set_value(GOVERNOR_CPU, governors); + reloadGUI("CPUGlobalGovernor"); + }} + /> + + } + {/* CPU advanced mode */} + {advancedMode && + { + advancedCpu = cpuNum; + this.setState((state) => { + return { + reloadThingy: state.reloadThingy, + }; + }); + }} + /> + } + {advancedMode && + { + backend.log(backend.LogLevel.Debug, "CPU " + advancedCpu.toString() + " is now " + status.toString()); + if (!get_value(SMT_CPU)) { + backend.resolve(backend.setCpuSmt(true), (_newVal: boolean[]) => { + set_value(SMT_CPU, true); + }); + } + backend.resolve(backend.setCpuOnline(advancedCpuIndex, status), (newVal: boolean) => { + const onlines = get_value(ONLINE_STATUS_CPUS); + onlines[advancedCpuIndex] = newVal; + set_value(ONLINE_STATUS_CPUS, onlines); + }); + }} + /> + } + {advancedMode && + { + if (value) { + const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits != null) { + clocks[advancedCpuIndex].min = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits!.min; + } + + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits != null) { + clocks[advancedCpuIndex].max = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits!.max; + } + set_value(CLOCK_MIN_MAX_CPU, clocks); + reloadGUI("CPUFreqToggle"); + } else { + const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; + clocks[advancedCpuIndex].min = null; + clocks[advancedCpuIndex].max = null; + set_value(CLOCK_MIN_MAX_CPU, clocks); + backend.resolve(backend.unsetCpuClockLimits(advancedCpuIndex), (_idc: any[]) => { + reloadGUI("CPUUnsetFreq"); + }); + } + }} + /> + } + {advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits != null && + {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null && { + backend.log(backend.LogLevel.Debug, "Min freq slider for " + advancedCpu.toString() + " is now " + freq.toString()); + const freqNow = get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex] as MinMax; + if (freq != freqNow.min && ((freqNow.max != null && freq <= freqNow.max) || freqNow.max == null)) { + backend.resolve(backend.setCpuClockLimits(advancedCpuIndex, freq, freqNow.max!), + (limits: number[]) => { + const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; + clocks[advancedCpuIndex].min = limits[0]; + clocks[advancedCpuIndex].max = limits[1]; + set_value(CLOCK_MIN_MAX_CPU, clocks); + reloadGUI("CPUMinFreq"); + }); + } + }} + />} + } + {advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits != null && + {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max != null && { + backend.log(backend.LogLevel.Debug, "Max freq slider for " + advancedCpu.toString() + " is now " + freq.toString()); + const freqNow = get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex] as MinMax; + if (freq != freqNow.max && ((freqNow.min != null && freq >= freqNow.min) || freqNow.min == null)) { + backend.resolve(backend.setCpuClockLimits(advancedCpuIndex, freqNow.min!, freq), + (limits: number[]) => { + const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; + clocks[advancedCpuIndex].min = limits[0]; + clocks[advancedCpuIndex].max = limits[1]; + set_value(CLOCK_MIN_MAX_CPU, clocks); + reloadGUI("CPUMaxFreq"); + }); + } + }} + />} + } + {advancedMode && governorOptions.length != 0 && + + { + backend.log(backend.LogLevel.Debug, "POWERTOOLS: array item " + val.toString()); + backend.log(backend.LogLevel.Debug, "POWERTOOLS: looking for data " + get_value(GOVERNOR_CPU)[advancedCpuIndex].toString()); + return val.data == get_value(GOVERNOR_CPU)[advancedCpuIndex]; + })} + strDefaultLabel={get_value(GOVERNOR_CPU)[advancedCpuIndex]} + onChange={(elem: SingleDropdownOption) => { + backend.log(backend.LogLevel.Debug, "Governor dropdown selected " + elem.data.toString()); + backend.resolve(backend.setCpuGovernor(advancedCpuIndex, elem.data as string), (gov: string) => { + const governors = get_value(GOVERNOR_CPU); + governors[advancedCpuIndex] = gov; + set_value(GOVERNOR_CPU, governors); + reloadGUI("CPUGovernor"); + }); + }} + /> + + } +
); + } +} + +function countCpus(statii: boolean[]): number { + let count = 0; + for (let i = 0; i < statii.length; i++) { + if (statii[i]) { + count += 1; + } + } + return count; +} + +type MinMax = { + min: number | null; + max: number | null; +} + +function syncPlebClockToAdvanced() { + const cpuCount = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.count; + const minClock = get_value(CLOCK_MIN_CPU); + const maxClock = get_value(CLOCK_MAX_CPU); + let clockArr = []; + for (let i = 0; i < cpuCount; i++) { + clockArr.push({ + min: minClock, + max: maxClock, + } as MinMax); + } + set_value(CLOCK_MIN_MAX_CPU, clockArr); +} diff --git a/src/components/debug.tsx b/src/components/debug.tsx new file mode 100644 index 0000000..d942200 --- /dev/null +++ b/src/components/debug.tsx @@ -0,0 +1,84 @@ +import { Fragment } from "react"; +import {Component} from "react"; +import { + ButtonItem, + Field, + PanelSectionRow, + staticClasses, + Router, +} from "decky-frontend-lib"; +import * as backend from "../backend"; +import { tr } from "usdpl-front"; +import { + BACKEND_INFO, + DRIVER_INFO, +} from "../consts"; +import { get_value, target_usdpl, version_usdpl} from "usdpl-front"; + +let eggCount = 0; + +export class Debug extends Component<{}> { + render() { + return buildDebug(); + } +} + +function buildDebug() { + return ({/* Version Info */} +
+ {eggCount % 10 == 9 ? "Ha! Nerd" : tr("Debug")} +
+ + { + if (eggCount % 10 == 9) { + // you know you're bored and/or conceited when you spend time adding an easter egg + // that just sends people to your own project's repo + Router.NavigateToExternalWeb("https://github.com/NGnius/PowerTools"); + } + eggCount++; + }}> + {eggCount % 10 == 9 ? "by NGnius" : get_value(BACKEND_INFO)} + + + + eggCount++}> + {eggCount % 10 == 9 ? "<3 <3 <3" : target_usdpl()} + + + + eggCount++}> + {eggCount % 10 == 9 ? "Tracy Chapman" : get_value(DRIVER_INFO)} + + + + { + if (eggCount % 10 == 9) { + // you know you're bored and/or conceited when you spend time adding an easter egg + // that just sends people to your own project's repo + Router.NavigateToExternalWeb("https://github.com/NGnius/usdpl-rs"); + } + eggCount++; + }}> + v{version_usdpl()} + + + {eggCount % 10 == 9 && + { + backend.idk(); + }} + > + ??? + + } +
); +} diff --git a/src/components/gpu.tsx b/src/components/gpu.tsx new file mode 100644 index 0000000..7e432c7 --- /dev/null +++ b/src/components/gpu.tsx @@ -0,0 +1,198 @@ +import { Fragment } from "react"; +import {Component} from "react"; +import { + ToggleField, + SliderField, + PanelSectionRow, + staticClasses, +} from "decky-frontend-lib"; +import * as backend from "../backend"; +import { tr } from "usdpl-front"; +import { + LIMITS_INFO, + SLOW_PPT_GPU, + FAST_PPT_GPU, + CLOCK_MIN_GPU, + CLOCK_MAX_GPU, + SLOW_MEMORY_GPU, +} from "../consts"; +import { set_value, get_value} from "usdpl-front"; + +export class Gpu extends Component<{}> { + constructor(props: {}) { + super(props); + this.state = { + reloadThingy: "/shrug", + }; + } + + render() { + const reloadGUI = (x: string) => this.setState({reloadThingy: x}); + return ( + {/* GPU */} +
+ {tr("GPU")} +
+ { ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits != null ||(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits != null) && + { + if (value) { + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits != null) { + set_value(SLOW_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits!.max); + } + + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits != null) { + set_value(FAST_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits!.max); + } + reloadGUI("GPUPPTToggle"); + } else { + set_value(SLOW_PPT_GPU, null); + set_value(FAST_PPT_GPU, null); + backend.resolve(backend.unsetGpuPpt(), (_: any[]) => { + reloadGUI("GPUUnsetPPT"); + }); + } + }} + /> + } + + { get_value(SLOW_PPT_GPU) != null && { + backend.log(backend.LogLevel.Debug, "SlowPPT is now " + ppt.toString()); + const pptNow = get_value(SLOW_PPT_GPU); + const realPpt = ppt; + if (realPpt != pptNow) { + backend.resolve(backend.setGpuPpt(get_value(FAST_PPT_GPU), realPpt), + (limits: number[]) => { + set_value(FAST_PPT_GPU, limits[0]); + set_value(SLOW_PPT_GPU, limits[1]); + reloadGUI("GPUSlowPPT"); + }); + } + }} + />} + + + {get_value(FAST_PPT_GPU) != null && { + backend.log(backend.LogLevel.Debug, "FastPPT is now " + ppt.toString()); + const pptNow = get_value(FAST_PPT_GPU); + const realPpt = ppt; + if (realPpt != pptNow) { + backend.resolve(backend.setGpuPpt(realPpt, get_value(SLOW_PPT_GPU)), + (limits: number[]) => { + set_value(FAST_PPT_GPU, limits[0]); + set_value(SLOW_PPT_GPU, limits[1]); + reloadGUI("GPUFastPPT"); + }); + } + }} + />} + + {((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits != null || (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits != null) && + { + if (value) { + let clock_min_limits = (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits; + let clock_max_limits = (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits; + if (clock_min_limits != null) { + set_value(CLOCK_MIN_GPU, clock_min_limits.min); + } + if (clock_max_limits != null) { + set_value(CLOCK_MAX_GPU, clock_max_limits.max); + } + reloadGUI("GPUFreqToggle"); + } else { + set_value(CLOCK_MIN_GPU, null); + set_value(CLOCK_MAX_GPU, null); + backend.resolve(backend.unsetGpuClockLimits(), (_: any[]) => { + reloadGUI("GPUUnsetFreq"); + }); + } + }} + /> + } + + { get_value(CLOCK_MIN_GPU) != null && { + backend.log(backend.LogLevel.Debug, "GPU Clock Min is now " + val.toString()); + const valNow = get_value(CLOCK_MIN_GPU); + const maxNow = get_value(CLOCK_MAX_GPU); + if (val != valNow && ((maxNow != null && val <= maxNow) || maxNow == null)) { + backend.resolve(backend.setGpuClockLimits(val, get_value(CLOCK_MAX_GPU)), + (limits: number[]) => { + set_value(CLOCK_MIN_GPU, limits[0]); + set_value(CLOCK_MAX_GPU, limits[1]); + reloadGUI("GPUMinClock"); + }); + } + }} + />} + + + {get_value(CLOCK_MAX_GPU) != null && { + backend.log(backend.LogLevel.Debug, "GPU Clock Max is now " + val.toString()); + const valNow = get_value(CLOCK_MAX_GPU); + const minNow = get_value(CLOCK_MIN_GPU); + if (val != valNow && ((minNow != null && val >= minNow) || minNow == null)) { + backend.resolve(backend.setGpuClockLimits(get_value(CLOCK_MIN_GPU), val), + (limits: number[]) => { + set_value(CLOCK_MIN_GPU, limits[0]); + set_value(CLOCK_MAX_GPU, limits[1]); + reloadGUI("GPUMaxClock"); + }); + } + }} + />} + + {(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.memory_control_capable && + { + backend.resolve(backend.setGpuSlowMemory(value), (val: boolean) => { + set_value(SLOW_MEMORY_GPU, val); + reloadGUI("GPUSlowMemory"); + }) + }} + /> + } +
); + } +} diff --git a/src/consts.ts b/src/consts.ts new file mode 100644 index 0000000..0efe070 --- /dev/null +++ b/src/consts.ts @@ -0,0 +1,29 @@ +export const BACKEND_INFO = "VINFO"; +export const DRIVER_INFO = "GENERAL_provider"; + +export const LIMITS_INFO = "LIMITS_all"; + +export const CURRENT_BATT = "BATTERY_current_now"; +export const CHARGE_RATE_BATT = "BATTERY_charge_rate"; +export const CHARGE_MODE_BATT = "BATTERY_charge_mode"; +export const CHARGE_NOW_BATT = "BATTERY_charge_now"; +export const CHARGE_FULL_BATT = "BATTERY_charge_full"; +export const CHARGE_DESIGN_BATT = "BATTERY_charge_design"; + +//export const TOTAL_CPUS = "CPUs_total"; +export const ONLINE_CPUS = "CPUs_online"; +export const ONLINE_STATUS_CPUS = "CPUs_status_online"; +export const SMT_CPU = "CPUs_SMT"; +export const CLOCK_MIN_CPU = "CPUs_min_clock"; +export const CLOCK_MAX_CPU = "CPUs_max_clock"; +export const CLOCK_MIN_MAX_CPU = "CPUs_minmax_clocks"; +export const GOVERNOR_CPU = "CPUs_governor"; + +export const FAST_PPT_GPU = "GPU_fastPPT"; +export const SLOW_PPT_GPU = "GPU_slowPPT"; +export const CLOCK_MIN_GPU = "GPU_min_clock"; +export const CLOCK_MAX_GPU = "GPU_max_clock"; +export const SLOW_MEMORY_GPU = "GPU_slow_memory"; + +export const PERSISTENT_GEN = "GENERAL_persistent"; +export const NAME_GEN = "GENERAL_name"; diff --git a/src/index.tsx b/src/index.tsx index 91f9547..ea9eee3 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,55 +6,70 @@ import { //MenuItem, PanelSection, PanelSectionRow, - //Router, ServerAPI, //showContextMenu, staticClasses, - SliderField, + //SliderField, ToggleField, + //Dropdown, + Field, + //DropdownOption, + //SingleDropdownOption, //NotchLabel - gamepadDialogClasses, - joinClassNames, + //gamepadDialogClasses, + //joinClassNames, } from "decky-frontend-lib"; import { VFC, useState } from "react"; import { GiDrill } from "react-icons/gi"; //import * as python from "./python"; import * as backend from "./backend"; -import {set_value, get_value, target_usdpl, version_usdpl} from "usdpl-front"; +import { tr } from "usdpl-front"; +import { + BACKEND_INFO, + DRIVER_INFO, + + LIMITS_INFO, + + CURRENT_BATT, + CHARGE_RATE_BATT, + CHARGE_MODE_BATT, + CHARGE_NOW_BATT, + CHARGE_FULL_BATT, + CHARGE_DESIGN_BATT, + + ONLINE_CPUS, + ONLINE_STATUS_CPUS, + SMT_CPU, + CLOCK_MIN_CPU, + CLOCK_MAX_CPU, + CLOCK_MIN_MAX_CPU, + GOVERNOR_CPU, + + FAST_PPT_GPU, + SLOW_PPT_GPU, + CLOCK_MIN_GPU, + CLOCK_MAX_GPU, + SLOW_MEMORY_GPU, + + PERSISTENT_GEN, + NAME_GEN, +} from "./consts"; +import { set_value, get_value } from "usdpl-front"; +import { Debug } from "./components/debug"; +import { Gpu } from "./components/gpu"; +import { Battery } from "./components/battery"; +import { Cpus } from "./components/cpus"; var periodicHook: NodeJS.Timer | null = null; var lifetimeHook: any = null; var startHook: any = null; var usdplReady = false; -var smtAllowed = true; -var smtGlobal = smtAllowed; - -// usdpl persistent store keys - -const BACKEND_INFO = "VINFO"; - -const CURRENT_BATT = "BATTERY_current_now"; -const CHARGE_RATE_BATT = "BATTERY_charge_rate"; -const CHARGE_NOW_BATT = "BATTERY_charge_now"; -const CHARGE_FULL_BATT = "BATTERY_charge_full"; -const CHARGE_DESIGN_BATT = "BATTERY_charge_design" - -const TOTAL_CPUS = "CPUs_total"; -const ONLINE_CPUS = "CPUs_online"; -const CLOCK_MIN_CPU = "CPUs_min_clock"; -const CLOCK_MAX_CPU = "CPUs_max_clock"; -const GOVERNOR_CPU = "CPUs_governor"; - -const FAST_PPT_GPU = "GPU_fastPPT"; -const SLOW_PPT_GPU = "GPU_slowPPT"; -const CLOCK_MIN_GPU = "GPU_min_clock"; -const CLOCK_MAX_GPU = "GPU_max_clock"; -const SLOW_MEMORY_GPU = "GPU_slow_memory"; - -const PERSISTENT_GEN = "GENERAL_persistent"; -const NAME_GEN = "GENERAL_name"; +type MinMax = { + min: number | null; + max: number | null; +} function countCpus(statii: boolean[]): number { let count = 0; @@ -66,29 +81,54 @@ function countCpus(statii: boolean[]): number { return count; } +function syncPlebClockToAdvanced() { + const cpuCount = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.count; + const minClock = get_value(CLOCK_MIN_CPU); + const maxClock = get_value(CLOCK_MAX_CPU); + let clockArr = []; + for (let i = 0; i < cpuCount; i++) { + clockArr.push({ + min: minClock, + max: maxClock, + } as MinMax); + } + set_value(CLOCK_MIN_MAX_CPU, clockArr); +} + const reload = function() { if (!usdplReady) {return;} + backend.resolve(backend.getLimits(), (limits) => { + set_value(LIMITS_INFO, limits); + console.debug("POWERTOOLS: got limits ", limits); + }); + backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) }); - backend.resolve(backend.getBatteryChargeRate(), (rate: number) => { set_value(CHARGE_RATE_BATT, rate) }); + backend.resolve_nullable(backend.getBatteryChargeRate(), (rate: number | null) => { set_value(CHARGE_RATE_BATT, rate) }); + backend.resolve_nullable(backend.getBatteryChargeMode(), (mode: string | null) => { set_value(CHARGE_MODE_BATT, mode) }); backend.resolve(backend.getBatteryChargeNow(), (rate: number) => { set_value(CHARGE_NOW_BATT, rate) }); backend.resolve(backend.getBatteryChargeFull(), (rate: number) => { set_value(CHARGE_FULL_BATT, rate) }); backend.resolve(backend.getBatteryChargeDesign(), (rate: number) => { set_value(CHARGE_DESIGN_BATT, rate) }); - backend.resolve(backend.getCpuCount(), (count: number) => { set_value(TOTAL_CPUS, count)}); + //backend.resolve(backend.getCpuCount(), (count: number) => { set_value(TOTAL_CPUS, count)}); backend.resolve(backend.getCpusOnline(), (statii: boolean[]) => { - // TODO: allow for per-core control of online status + set_value(ONLINE_STATUS_CPUS, statii); const count = countCpus(statii); set_value(ONLINE_CPUS, count); - smtGlobal = statii.length > 3 && statii[0] == statii[1] && statii[2] == statii[3] && smtAllowed; + set_value(SMT_CPU, statii.length > 3 && statii[0] == statii[1] && statii[2] == statii[3]); + }); + backend.resolve(backend.getCpuSmt(), (smt: boolean) => { + set_value(SMT_CPU, smt); }); - // TODO: allow for per-core control of clock limits backend.resolve(backend.getCpuClockLimits(0), (limits: number[]) => { set_value(CLOCK_MIN_CPU, limits[0]); set_value(CLOCK_MAX_CPU, limits[1]); + syncPlebClockToAdvanced(); + }); + backend.resolve(backend.getCpusGovernor(), (governors: string[]) => { + set_value(GOVERNOR_CPU, governors); + backend.log(backend.LogLevel.Info, "POWERTOOLS: Governors from backend " + governors.toString()); }); - // TODO: allow for control of governor - backend.resolve(backend.getCpusGovernor(), (governors: string[]) => { set_value(GOVERNOR_CPU, governors[0]) }); backend.resolve(backend.getGpuPpt(), (ppts: number[]) => { set_value(FAST_PPT_GPU, ppts[0]); @@ -104,6 +144,7 @@ const reload = function() { backend.resolve(backend.getGeneralSettingsName(), (name: string) => { set_value(NAME_GEN, name) }); backend.resolve(backend.getInfo(), (info: string) => { set_value(BACKEND_INFO, info) }); + backend.resolve(backend.getDriverProviderName("gpu"), (driver: string) => { set_value(DRIVER_INFO, driver) }); }; // init USDPL WASM and connection to back-end @@ -117,12 +158,12 @@ const reload = function() { //@ts-ignore lifetimeHook = SteamClient.GameSessions.RegisterForAppLifetimeNotifications((update) => { if (update.bRunning) { - //console.debug("AppID " + update.unAppID.toString() + " is now running"); + //backend.log(backend.LogLevel.Debug, "AppID " + update.unAppID.toString() + " is now running"); } else { - //console.debug("AppID " + update.unAppID.toString() + " is no longer running"); + //backend.log(backend.LogLevel.Debug, "AppID " + update.unAppID.toString() + " is no longer running"); backend.resolve( backend.loadGeneralDefaultSettings(), - (ok: boolean) => {console.debug("Loading default settings ok? " + ok)} + (ok: boolean) => {backend.log(backend.LogLevel.Debug, "Loading default settings ok? " + ok)} ); } }); @@ -133,11 +174,11 @@ const reload = function() { // don't use gameInfo.appid, haha backend.resolve( backend.loadGeneralSettings(id.toString() + ".json", gameInfo.display_name), - (ok: boolean) => {console.debug("Loading settings ok? " + ok)} + (ok: boolean) => {backend.log(backend.LogLevel.Debug, "Loading settings ok? " + ok)} ); }); - console.debug("Registered PowerTools callbacks, hello!"); + backend.log(backend.LogLevel.Debug, "Registered PowerTools callbacks, hello!"); })(); const periodicals = function() { @@ -169,390 +210,26 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { reloadGUI("periodic" + (new Date()).getTime().toString()); }, 1000); - const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard); - - const total_cpus = get_value(TOTAL_CPUS); - return ( - {/* CPU */ /* TODO: set per-core stuff*/} -
- CPU -
- {smtAllowed && - { - console.debug("SMT is now " + smt.toString()); - const cpus = get_value(ONLINE_CPUS); - smtGlobal = smt && smtAllowed; - // TODO: move SMT setting logic back to back-end - let onlines: boolean[] = []; - for (let i = 0; i < total_cpus; i++) { - const online = (smtGlobal? i < cpus : (i % 2 == 0) && (i < cpus * 2)) - || (!smtGlobal && cpus == 4); - onlines.push(online); - } - backend.resolve(backend.setCpuOnlines(onlines), (statii: boolean[]) => { - // TODO: allow for per-core control of online status - const count = countCpus(statii); - set_value(ONLINE_CPUS, count); - reloadGUI("SMT"); - }); - }} - /> - } - - { - console.debug("CPU slider is now " + cpus.toString()); - const onlines = get_value(ONLINE_CPUS); - if (cpus != onlines) { - set_value(ONLINE_CPUS, cpus); - let onlines: boolean[] = []; - for (let i = 0; i < total_cpus; i++) { - const online = smtGlobal? i < cpus : (i % 2 == 0) && (i < cpus * 2); - onlines.push(online); - } - backend.resolve(backend.setCpuOnlines(onlines), (statii: boolean[]) => { - // TODO: allow for per-core control of online status - const count = countCpus(statii); - set_value(ONLINE_CPUS, count); - reloadGUI("CPUs"); - }); - reloadGUI("CPUsImmediate"); - } - }} - /> - - - { - if (value) { - set_value(CLOCK_MIN_CPU, 1400); - set_value(CLOCK_MAX_CPU, 3500); - reloadGUI("CPUFreqToggle"); - } else { - set_value(CLOCK_MIN_CPU, null); - set_value(CLOCK_MAX_CPU, null); - for (let i = 0; i < total_cpus; i++) { - backend.resolve(backend.unsetCpuClockLimits(i), (_idc: any[]) => {}); - } - backend.resolve(backend.waitForComplete(), (_: boolean[]) => { - reloadGUI("CPUUnsetFreq"); - }); - } - }} - /> - - - {get_value(CLOCK_MIN_CPU) != null && { - console.debug("Min freq slider is now " + freq.toString()); - const freqNow = get_value(CLOCK_MIN_CPU); - if (freq != freqNow) { - set_value(CLOCK_MIN_CPU, freq); - for (let i = 0; i < total_cpus; i++) { - backend.resolve(backend.setCpuClockLimits(i, freq, get_value(CLOCK_MAX_CPU)), - (limits: number[]) => { - set_value(CLOCK_MIN_CPU, limits[0]); - set_value(CLOCK_MAX_CPU, limits[1]); - }); - } - backend.resolve(backend.waitForComplete(), (_: boolean[]) => { - reloadGUI("CPUMinFreq"); - }); - reloadGUI("CPUMinFreqImmediate"); - } - }} - />} - - - {get_value(CLOCK_MAX_CPU) != null && { - console.debug("Max freq slider is now " + freq.toString()); - const freqNow = get_value(CLOCK_MAX_CPU); - if (freq != freqNow) { - set_value(CLOCK_MAX_CPU, freq); - for (let i = 0; i < total_cpus; i++) { - backend.resolve(backend.setCpuClockLimits(i, get_value(CLOCK_MIN_CPU), freq), - (limits: number[]) => { - set_value(CLOCK_MIN_CPU, limits[0]); - set_value(CLOCK_MAX_CPU, limits[1]); - }); - } - backend.resolve(backend.waitForComplete(), (_: boolean[]) => { - reloadGUI("CPUMaxFreq"); - }); - reloadGUI("CPUMaxFreqImmediate"); - } - }} - />} - - {/* TODO: CPU governor */} - {/* GPU */} -
- GPU -
- - { - if (value) { - set_value(SLOW_PPT_GPU, 15000000); - set_value(FAST_PPT_GPU, 15000000); - reloadGUI("GPUPPTToggle"); - } else { - set_value(SLOW_PPT_GPU, null); - set_value(FAST_PPT_GPU, null); - backend.resolve(backend.unsetGpuPpt(), (_: any[]) => { - reloadGUI("GPUUnsetPPT"); - }); - } - }} - /> - - - { get_value(SLOW_PPT_GPU) != null && { - console.debug("SlowPPT is now " + ppt.toString()); - const pptNow = get_value(SLOW_PPT_GPU); - if (ppt != pptNow) { - backend.resolve(backend.setGpuPpt(get_value(FAST_PPT_GPU), ppt), - (limits: number[]) => { - set_value(FAST_PPT_GPU, limits[0]); - set_value(SLOW_PPT_GPU, limits[1]); - reloadGUI("GPUSlowPPT"); - }); - } - }} - />} - - - {get_value(FAST_PPT_GPU) != null && { - console.debug("FastPPT is now " + ppt.toString()); - const pptNow = get_value(FAST_PPT_GPU); - if (ppt != pptNow) { - backend.resolve(backend.setGpuPpt(get_value(SLOW_PPT_GPU), ppt), - (limits: number[]) => { - set_value(FAST_PPT_GPU, limits[0]); - set_value(SLOW_PPT_GPU, limits[1]); - reloadGUI("GPUFastPPT"); - }); - } - }} - />} - - - { - if (value) { - set_value(CLOCK_MIN_GPU, 200); - set_value(CLOCK_MAX_GPU, 1600); - reloadGUI("GPUFreqToggle"); - } else { - set_value(CLOCK_MIN_GPU, null); - set_value(CLOCK_MAX_GPU, null); - backend.resolve(backend.unsetGpuClockLimits(), (_: any[]) => { - reloadGUI("GPUUnsetFreq"); - }); - } - }} - /> - - - { get_value(CLOCK_MIN_GPU) != null && { - console.debug("GPU Clock Min is now " + val.toString()); - const valNow = get_value(CLOCK_MIN_GPU); - if (val != valNow) { - backend.resolve(backend.setGpuClockLimits(val, get_value(CLOCK_MAX_GPU)), - (limits: number[]) => { - set_value(CLOCK_MIN_GPU, limits[0]); - set_value(CLOCK_MAX_GPU, limits[1]); - reloadGUI("GPUMinClock"); - }); - } - }} - />} - - - {get_value(CLOCK_MAX_GPU) != null && { - console.debug("GPU Clock Max is now " + val.toString()); - const valNow = get_value(CLOCK_MAX_GPU); - if (val != valNow) { - backend.resolve(backend.setGpuClockLimits(get_value(CLOCK_MIN_GPU), val), - (limits: number[]) => { - set_value(CLOCK_MIN_GPU, limits[0]); - set_value(CLOCK_MAX_GPU, limits[1]); - reloadGUI("GPUMaxClock"); - }); - } - }} - />} - - - { - backend.resolve(backend.setGpuSlowMemory(value), (val: boolean) => { - set_value(SLOW_MEMORY_GPU, val); - reloadGUI("GPUSlowMemory"); - }) - }} - /> - - {/* Battery */} -
- Battery -
- -
-
-
- Now (Charge) -
-
- {get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%) -
-
-
-
- -
-
-
- Max (Design) -
-
- {get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%) -
-
-
-
- - { - if (value) { - set_value(CHARGE_RATE_BATT, 2500); - reloadGUI("BATTChargeRateToggle"); - } else { - set_value(CHARGE_RATE_BATT, null); - backend.resolve(backend.unsetBatteryChargeRate(), (_: any[]) => { - reloadGUI("BATTUnsetChargeRate"); - }); - } - }} - /> - { get_value(CHARGE_RATE_BATT) != null && { - console.debug("Charge rate is now " + val.toString()); - const rateNow = get_value(CHARGE_RATE_BATT); - if (val != rateNow) { - backend.resolve(backend.setBatteryChargeRate(val), - (rate: number) => { - set_value(CHARGE_RATE_BATT, rate); - reloadGUI("BATTChargeRate"); - }); - } - }} - />} - - -
-
-
- Current -
-
- {get_value(CURRENT_BATT)} mA -
-
-
-
+ + + + + + + {/* Persistence */}
- Miscellaneous + {tr("Miscellaneous")}
{ - console.debug("Persist is now " + persist.toString()); + backend.log(backend.LogLevel.Debug, "Persist is now " + persist.toString()); backend.resolve( backend.setGeneralPersistent(persist), (val: boolean) => {set_value(PERSISTENT_GEN, val)} @@ -561,75 +238,32 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { /> -
-
-
- Profile -
-
- {get_value(NAME_GEN)} -
-
-
-
- {/* Version Info */} -
- Debug -
- -
-
-
- Native -
-
- {get_value(BACKEND_INFO)} -
-
-
-
- -
-
-
- Framework -
-
- {target_usdpl()} -
-
-
-
- -
-
-
- USDPL -
-
- v{version_usdpl()} -
-
-
+ + {get_value(NAME_GEN)} +
+ + + { - console.debug("Loading default PowerTools settings"); + backend.log(backend.LogLevel.Debug, "Loading default PowerTools settings"); backend.resolve( backend.setGeneralPersistent(false), (val: boolean) => { set_value(PERSISTENT_GEN, val); - backend.resolve(backend.loadGeneralDefaultSettings(), (_: any[]) => { + backend.resolve(backend.loadGeneralSystemSettings(), (_) => { reload(); - backend.resolve(backend.waitForComplete(), (_: any[]) => {reloadGUI("LoadDefaults")}); + backend.resolve(backend.waitForComplete(), (_) => {reloadGUI("LoadSystemDefaults")}); }); } ); }} > - Defaults + {tr("Defaults")}
@@ -642,13 +276,13 @@ export default definePlugin((serverApi: ServerAPI) => { content: , icon: , onDismount() { - console.debug("PowerTools shutting down"); + backend.log(backend.LogLevel.Debug, "PowerTools shutting down"); clearInterval(periodicHook!); periodicHook = null; lifetimeHook!.unregister(); startHook!.unregister(); serverApi.routerHook.removeRoute("/decky-plugin-test"); - console.debug("Unregistered PowerTools callbacks, goodbye."); + backend.log(backend.LogLevel.Debug, "Unregistered PowerTools callbacks, so long and thanks for all the fish."); }, }; }); diff --git a/src/python.ts b/src/python.ts deleted file mode 100644 index f764856..0000000 --- a/src/python.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { ServerAPI } from "decky-frontend-lib"; - -var server: ServerAPI | undefined = undefined; - -//import { useEffect } from "react"; - -export function resolve(promise: Promise, setter: any) { - (async function () { - let data = await promise; - if (data.success) { - console.debug("Got resolved", data, "promise", promise); - setter(data.result); - } else { - console.warn("Resolve failed:", data, "promise", promise); - } - })(); -} - -export function execute(promise: Promise) { - (async function () { - let data = await promise; - if (data.success) { - console.debug("Got executed", data, "promise", promise); - } else { - console.warn("Execute failed:", data, "promise", promise); - } - - })(); -} - -export function setServer(s: ServerAPI) { - server = s; -} - -// Python functions -export function getVersion(): Promise { - return server!.callPluginMethod("get_version", {}); -} - -export function onViewReady(): Promise { - return server!.callPluginMethod("on_ready", {}); -} - -export function setCPUs(value: number, smt: boolean): Promise { - return server!.callPluginMethod("set_cpus", {"count":value, "smt": smt}); -} - -export function getCPUs(): Promise { - return server!.callPluginMethod("get_cpus", {}); -} - -export function getSMT(): Promise { - return server!.callPluginMethod("get_smt", {}); -} - -export function setCPUBoost(value: boolean): Promise { - return server!.callPluginMethod("set_boost", {"enabled": value}); -} - -export function getCPUBoost(): Promise { - return server!.callPluginMethod("get_boost", {}); -} - -export function setMaxBoost(index: number): Promise { - return server!.callPluginMethod("set_max_boost", {"index": index}); -} - -export function getMaxBoost(): Promise { - return server!.callPluginMethod("get_max_boost", {}); -} - -export function setGPUPower(value: number, index: number): Promise { - return server!.callPluginMethod("set_gpu_power", {"value": value, "power_number": index}); -} - -export function getGPUPower(index: number): Promise { - return server!.callPluginMethod("get_gpu_power", {"power_number": index}); -} - -export function setGPUPowerI(value: number, index: number): Promise { - return server!.callPluginMethod("set_gpu_power_index", {"index": value, "power_number": index}); -} - -export function getGPUPowerI(index: number): Promise { - return server!.callPluginMethod("get_gpu_power_index", {"power_number": index}); -} - -export function setFanTick(tick: number): Promise { - return server!.callPluginMethod("set_fan_tick", {"tick": tick}); -} - -export function getFanTick(): Promise { - return server!.callPluginMethod("get_fan_tick", {}); -} - -export function getFantastic(): Promise { - return server!.callPluginMethod("fantastic_installed", {}); -} - -export function getChargeNow(): Promise { - return server!.callPluginMethod("get_charge_now", {}); -} - -export function getChargeFull(): Promise { - return server!.callPluginMethod("get_charge_full", {}); -} - -export function getChargeDesign(): Promise { - return server!.callPluginMethod("get_charge_design", {}); -} - -export function setPersistent(value: boolean): Promise { - return server!.callPluginMethod("set_persistent", {"enabled": value}); -} - -export function getPersistent(): Promise { - return server!.callPluginMethod("get_persistent", {}); -} - -export function setPerGameProfile(value: boolean): Promise { - return server!.callPluginMethod("set_per_game_profile", {"enabled": value}); -} - -export function getPerGameProfile(): Promise { - return server!.callPluginMethod("get_per_game_profile", {}); -} - -export function getCurrentGame(): Promise { - return server!.callPluginMethod("get_current_game", {}); -} - -export function onGameStart(gameId: number, data: any): Promise { - const data2 = {appid: data.appid, display_name: data.display_name, gameid: gameId}; // Issue #17 - return server!.callPluginMethod("on_game_start", {"game_id": gameId, "data":data2}); -} - -export function onGameStop(gameId: number | null): Promise { - return server!.callPluginMethod("on_game_stop", {"game_id": gameId}); -} diff --git a/src/usdpl_front/package.json b/src/usdpl_front/package.json index fd8c535..1c18e11 100644 --- a/src/usdpl_front/package.json +++ b/src/usdpl_front/package.json @@ -4,7 +4,7 @@ "NGnius (Graham) " ], "description": "Universal Steam Deck Plugin Library front-end designed for WASM", - "version": "0.6.2", + "version": "0.9.1", "license": "GPL-3.0-only", "repository": { "type": "git", diff --git a/src/usdpl_front/usdpl_front.d.ts b/src/usdpl_front/usdpl_front.d.ts index b986bed..9477118 100644 --- a/src/usdpl_front/usdpl_front.d.ts +++ b/src/usdpl_front/usdpl_front.d.ts @@ -36,6 +36,25 @@ export function get_value(key: string): any; * @returns {Promise} */ export function call_backend(name: string, parameters: any[]): Promise; +/** +* Initialize translation strings for the front-end +* @param {string} locale +* @returns {Promise} +*/ +export function init_tr(locale: string): Promise; +/** +* Translate a phrase, equivalent to tr_n(msg_id, 0) +* @param {string} msg_id +* @returns {string} +*/ +export function tr(msg_id: string): string; +/** +* Translate a phrase, retrieving the plural form for `n` items +* @param {string} msg_id +* @param {number} n +* @returns {string} +*/ +export function tr_n(msg_id: string, n: number): string; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; @@ -47,6 +66,9 @@ export interface InitOutput { readonly set_value: (a: number, b: number, c: number) => number; readonly get_value: (a: number, b: number) => number; readonly call_backend: (a: number, b: number, c: number, d: number) => number; + readonly init_tr: (a: number, b: number) => number; + readonly tr: (a: number, b: number, c: number) => void; + readonly tr_n: (a: number, b: number, c: number, d: number) => void; readonly __wbindgen_export_0: (a: number) => number; readonly __wbindgen_export_1: (a: number, b: number, c: number) => number; readonly __wbindgen_export_2: WebAssembly.Table; @@ -57,14 +79,16 @@ export interface InitOutput { readonly __wbindgen_export_6: (a: number, b: number, c: number, d: number) => void; } +export type SyncInitInput = BufferSource | WebAssembly.Module; /** -* Synchronously compiles the given `bytes` and instantiates the WebAssembly module. +* Instantiates the given `module`, which can either be bytes or +* a precompiled `WebAssembly.Module`. * -* @param {BufferSource} bytes +* @param {SyncInitInput} module * * @returns {InitOutput} */ -export function initSync(bytes: BufferSource): InitOutput; +export function initSync(module: SyncInitInput): InitOutput; /** * If `module_or_path` is {RequestInfo} or {URL}, makes a request and diff --git a/src/usdpl_front/usdpl_front.js b/src/usdpl_front/usdpl_front.js index 98ebff0..dc66687 100644 --- a/src/usdpl_front/usdpl_front.js +++ b/src/usdpl_front/usdpl_front.js @@ -21,6 +21,23 @@ function takeObject(idx) { return ret; } +const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = new Uint8Array(); + +function getUint8Memory0() { + if (cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + function addHeapObject(obj) { if (heap_next === heap.length) heap.push(heap.length + 1); const idx = heap_next; @@ -32,14 +49,6 @@ function addHeapObject(obj) { let WASM_VECTOR_LEN = 0; -let cachedUint8Memory0; -function getUint8Memory0() { - if (cachedUint8Memory0.byteLength === 0) { - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8Memory0; -} - const cachedTextEncoder = new TextEncoder('utf-8'); const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' @@ -97,7 +106,8 @@ function isLikeNone(x) { return x === undefined || x === null; } -let cachedInt32Memory0; +let cachedInt32Memory0 = new Int32Array(); + function getInt32Memory0() { if (cachedInt32Memory0.byteLength === 0) { cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); @@ -105,15 +115,8 @@ function getInt32Memory0() { return cachedInt32Memory0; } -const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +let cachedFloat64Memory0 = new Float64Array(); -cachedTextDecoder.decode(); - -function getStringFromWasm0(ptr, len) { - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); -} - -let cachedFloat64Memory0; function getFloat64Memory0() { if (cachedFloat64Memory0.byteLength === 0) { cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); @@ -216,7 +219,8 @@ export function get_value(key) { return takeObject(ret); } -let cachedUint32Memory0; +let cachedUint32Memory0 = new Uint32Array(); + function getUint32Memory0() { if (cachedUint32Memory0.byteLength === 0) { cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer); @@ -249,6 +253,59 @@ export function call_backend(name, parameters) { return takeObject(ret); } +/** +* Initialize translation strings for the front-end +* @param {string} locale +* @returns {Promise} +*/ +export function init_tr(locale) { + const ptr0 = passStringToWasm0(locale, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.init_tr(ptr0, len0); + return takeObject(ret); +} + +/** +* Translate a phrase, equivalent to tr_n(msg_id, 0) +* @param {string} msg_id +* @returns {string} +*/ +export function tr(msg_id) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(msg_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + wasm.tr(retptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_4(r0, r1); + } +} + +/** +* Translate a phrase, retrieving the plural form for `n` items +* @param {string} msg_id +* @param {number} n +* @returns {string} +*/ +export function tr_n(msg_id, n) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(msg_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + wasm.tr_n(retptr, ptr0, len0, n); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_4(r0, r1); + } +} + function handleError(f, args) { try { return f.apply(this, args); @@ -256,7 +313,7 @@ function handleError(f, args) { wasm.__wbindgen_export_5(addHeapObject(e)); } } -function __wbg_adapter_57(arg0, arg1, arg2, arg3) { +function __wbg_adapter_58(arg0, arg1, arg2, arg3) { wasm.__wbindgen_export_6(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); } @@ -297,8 +354,8 @@ function getImports() { imports.wbg.__wbindgen_object_drop_ref = function(arg0) { takeObject(arg0); }; - imports.wbg.__wbindgen_object_clone_ref = function(arg0) { - const ret = getObject(arg0); + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); return addHeapObject(ret); }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { @@ -309,8 +366,8 @@ function getImports() { getInt32Memory0()[arg0 / 4 + 1] = len0; getInt32Memory0()[arg0 / 4 + 0] = ptr0; }; - imports.wbg.__wbindgen_string_new = function(arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1); + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); return addHeapObject(ret); }; imports.wbg.__wbindgen_number_new = function(arg0) { @@ -336,30 +393,35 @@ function getImports() { const ret = getObject(arg0) === undefined; return ret; }; - imports.wbg.__wbg_instanceof_Window_a2a08d3918d7d4d0 = function(arg0) { - const ret = getObject(arg0) instanceof Window; + imports.wbg.__wbg_instanceof_Window_acc97ff9f5d2c7b4 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Window; + } catch { + result = false; + } + const ret = result; return ret; }; - imports.wbg.__wbg_fetch_23507368eed8d838 = function(arg0, arg1) { + imports.wbg.__wbg_fetch_0fe04905cccfc2aa = function(arg0, arg1) { const ret = getObject(arg0).fetch(getObject(arg1)); return addHeapObject(ret); }; - imports.wbg.__wbg_instanceof_Response_e928c54c1025470c = function(arg0) { - const ret = getObject(arg0) instanceof Response; + imports.wbg.__wbg_instanceof_Response_eaa426220848a39e = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Response; + } catch { + result = false; + } + const ret = result; return ret; }; - imports.wbg.__wbg_url_0f82030e7245954c = function(arg0, arg1) { - const ret = getObject(arg1).url; - const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); - const len0 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len0; - getInt32Memory0()[arg0 / 4 + 0] = ptr0; - }; - imports.wbg.__wbg_text_5cb78830c1a11c5b = function() { return handleError(function (arg0) { + imports.wbg.__wbg_text_1169d752cc697903 = function() { return handleError(function (arg0) { const ret = getObject(arg0).text(); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_newwithstrandinit_41c86e821f771b24 = function() { return handleError(function (arg0, arg1, arg2) { + imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) { const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2)); return addHeapObject(ret); }, arguments) }; @@ -372,15 +434,15 @@ function getImports() { const ret = false; return ret; }; - imports.wbg.__wbg_newnoargs_fc5356289219b93b = function(arg0, arg1) { + imports.wbg.__wbg_newnoargs_b5b063fc6c2f0376 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return addHeapObject(ret); }; - imports.wbg.__wbg_call_4573f605ca4b5f10 = function() { return handleError(function (arg0, arg1) { + imports.wbg.__wbg_call_97ae9d8645dc388b = function() { return handleError(function (arg0, arg1) { const ret = getObject(arg0).call(getObject(arg1)); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_new_306ce8d57919e6ae = function() { + imports.wbg.__wbg_new_0b9bfdd97583284e = function() { const ret = new Object(); return addHeapObject(ret); }; @@ -388,41 +450,41 @@ function getImports() { const ret = typeof(getObject(arg0)) === 'string'; return ret; }; - imports.wbg.__wbg_self_ba1ddafe9ea7a3a2 = function() { return handleError(function () { + imports.wbg.__wbg_self_6d479506f72c6a71 = function() { return handleError(function () { const ret = self.self; return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_window_be3cc430364fd32c = function() { return handleError(function () { + imports.wbg.__wbg_window_f2557cc78490aceb = function() { return handleError(function () { const ret = window.window; return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_globalThis_56d9c9f814daeeee = function() { return handleError(function () { + imports.wbg.__wbg_globalThis_7f206bda628d5286 = function() { return handleError(function () { const ret = globalThis.globalThis; return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_global_8c35aeee4ac77f2b = function() { return handleError(function () { + imports.wbg.__wbg_global_ba75c50d1cf384f4 = function() { return handleError(function () { const ret = global.global; return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_newwithlength_51bd08aed34ec6a3 = function(arg0) { + imports.wbg.__wbg_newwithlength_7c42f7e738a9d5d3 = function(arg0) { const ret = new Array(arg0 >>> 0); return addHeapObject(ret); }; - imports.wbg.__wbg_set_c1d04f8b45a036e7 = function(arg0, arg1, arg2) { + imports.wbg.__wbg_set_a68214f35c417fa9 = function(arg0, arg1, arg2) { getObject(arg0)[arg1 >>> 0] = takeObject(arg2); }; - imports.wbg.__wbg_call_9855a4612eb496cb = function() { return handleError(function (arg0, arg1, arg2) { + imports.wbg.__wbg_call_168da88779e35f61 = function() { return handleError(function (arg0, arg1, arg2) { const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_new_78403b138428b684 = function(arg0, arg1) { + imports.wbg.__wbg_new_9962f939219f1820 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_57(a, state0.b, arg0, arg1); + return __wbg_adapter_58(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -433,35 +495,35 @@ function getImports() { state0.a = state0.b = 0; } }; - imports.wbg.__wbg_resolve_f269ce174f88b294 = function(arg0) { + imports.wbg.__wbg_resolve_99fe17964f31ffc0 = function(arg0) { const ret = Promise.resolve(getObject(arg0)); return addHeapObject(ret); }; - imports.wbg.__wbg_then_1c698eedca15eed6 = function(arg0, arg1) { + imports.wbg.__wbg_then_11f7a54d67b4bfad = function(arg0, arg1) { const ret = getObject(arg0).then(getObject(arg1)); return addHeapObject(ret); }; - imports.wbg.__wbg_then_4debc41d4fc92ce5 = function(arg0, arg1, arg2) { + imports.wbg.__wbg_then_cedad20fbbd9418a = function(arg0, arg1, arg2) { const ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); return addHeapObject(ret); }; - imports.wbg.__wbg_parse_5b823b8686817eb8 = function() { return handleError(function (arg0, arg1) { + imports.wbg.__wbg_parse_e23be3fecd886e2a = function() { return handleError(function (arg0, arg1) { const ret = JSON.parse(getStringFromWasm0(arg0, arg1)); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_stringify_cf20dc96bee34a66 = function() { return handleError(function (arg0) { + imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) { const ret = JSON.stringify(getObject(arg0)); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_set_b12cd0ab82903c2f = function() { return handleError(function (arg0, arg1, arg2) { + imports.wbg.__wbg_set_bf3f89b92d5a34bf = function() { return handleError(function (arg0, arg1, arg2) { const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); return ret; }, arguments) }; imports.wbg.__wbindgen_throw = function(arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); }; - imports.wbg.__wbindgen_closure_wrapper336 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 66, __wbg_adapter_26); + imports.wbg.__wbindgen_closure_wrapper385 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 70, __wbg_adapter_26); return addHeapObject(ret); }; @@ -475,21 +537,24 @@ function initMemory(imports, maybe_memory) { function finalizeInit(instance, module) { wasm = instance.exports; init.__wbindgen_wasm_module = module; - cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); - cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); - cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer); - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + cachedFloat64Memory0 = new Float64Array(); + cachedInt32Memory0 = new Int32Array(); + cachedUint32Memory0 = new Uint32Array(); + cachedUint8Memory0 = new Uint8Array(); return wasm; } -function initSync(bytes) { +function initSync(module) { const imports = getImports(); initMemory(imports); - const module = new WebAssembly.Module(bytes); + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + const instance = new WebAssembly.Instance(module, imports); return finalizeInit(instance, module); @@ -517,7 +582,7 @@ export default init; // USDPL customization -const encoded = "AGFzbQEAAAABmwEXYAJ/fwBgAn9/AX9gAX8AYAN/f38Bf2AEf39/fwBgA39/fwBgAX8Bf2AAAX9gBH9/f38Bf2AFf39/f38AYAAAYAF/AX5gBX9/f39/AX9gBn9/f39/fwF/YAZ/f39/f38AYAV/f31/fwBgBX9/fH9/AGAEf31/fwBgBH98f38AYAJ+fwBgB39/f39/f38Bf2ADfn9/AX9gAXwBfwLLCSQDd2JnGl9fd2JpbmRnZW5fb2JqZWN0X2Ryb3BfcmVmAAIDd2JnG19fd2JpbmRnZW5fb2JqZWN0X2Nsb25lX3JlZgAGA3diZxVfX3diaW5kZ2VuX3N0cmluZ19nZXQAAAN3YmcVX193YmluZGdlbl9zdHJpbmdfbmV3AAEDd2JnFV9fd2JpbmRnZW5fbnVtYmVyX25ldwAWA3diZxZfX3diaW5kZ2VuX2Jvb2xlYW5fZ2V0AAYDd2JnFV9fd2JpbmRnZW5fbnVtYmVyX2dldAAAA3diZxJfX3diaW5kZ2VuX2lzX251bGwABgN3YmcXX193YmluZGdlbl9pc191bmRlZmluZWQABgN3YmcoX193YmdfaW5zdGFuY2VvZl9XaW5kb3dfYTJhMDhkMzkxOGQ3ZDRkMAAGA3diZxxfX3diZ19mZXRjaF8yMzUwNzM2OGVlZDhkODM4AAEDd2JnKl9fd2JnX2luc3RhbmNlb2ZfUmVzcG9uc2VfZTkyOGM1NGMxMDI1NDcwYwAGA3diZxpfX3diZ191cmxfMGY4MjAzMGU3MjQ1OTU0YwAAA3diZxtfX3diZ190ZXh0XzVjYjc4ODMwYzFhMTFjNWIABgN3YmcoX193YmdfbmV3d2l0aHN0cmFuZGluaXRfNDFjODZlODIxZjc3MWIyNAADA3diZxJfX3diaW5kZ2VuX2NiX2Ryb3AABgN3YmcgX193YmdfbmV3bm9hcmdzX2ZjNTM1NjI4OTIxOWI5M2IAAQN3YmcbX193YmdfY2FsbF80NTczZjYwNWNhNGI1ZjEwAAEDd2JnGl9fd2JnX25ld18zMDZjZThkNTc5MTllNmFlAAcDd2JnFF9fd2JpbmRnZW5faXNfc3RyaW5nAAYDd2JnG19fd2JnX3NlbGZfYmExZGRhZmU5ZWE3YTNhMgAHA3diZx1fX3diZ193aW5kb3dfYmUzY2M0MzAzNjRm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wABBkO/AADYCAEGQ78AAQYjvwAA2AgBBpO/AAEGY78AANgIAQZjvwABBkO/AADYCAEGs78AAQaDvwAA2AgBBoO/AAEGY78AANgIAQbTvwABBqO/AADYCAEGo78AAQaDvwAA2AgBBvO/AAEGw78AANgIAQbDvwABBqO/AADYCAEHE78AAQbjvwAA2AgBBuO/AAEGw78AANgIAQczvwABBwO/AADYCAEHA78AAQbjvwAA2AgBB1O/AAEHI78AANgIAQcjvwABBwO/AADYCAEHc78AAQdDvwAA2AgBB0O/AAEHI78AANgIAQeTvwABB2O/AADYCAEHY78AAQdDvwAA2AgBB7O/AAEHg78AANgIAQeDvwABB2O/AADYCAEH078AAQejvwAA2AgBB6O/AAEHg78AANgIAQfzvwABB8O/AADYCAEHw78AAQejvwAA2AgBB+O/AAEHw78AANgIAQQhBCBDMAiEBQRRBCBDMAiECQRBBCBDMAiEGQYzxwAAgAyADEIoDIgBBCBDMAiAAayIDEIgDIgA2AgBBhPHAACAFQQhqIAYgASACamogA2prIgE2AgAgACABQQFyNgIEQQhBCBDMAiEDQRRBCBDMAiECQRBBCBDMAiEFIAAgARCIA0Go8cAAQYCAgAE2AgAgBSACIANBCGtqajYCBAtBACECQYTxwAAoAgAiACAETQ0AQYTxwAAgACAEayIBNgIAQYzxwABBjPHAACgCACIAIAQQiAMiAzYCACADIAFBAXI2AgQgACAEEOYCIAAQigMhAgsgCUEQaiQAIAILxBECD38CfiMAQaAIayIHJAAgB0EIakEAQYAIEIQDGiAALAAEQQJ0QcitwABqKAIAIQUgACgCACEOIABBBWotAAAhDwJAAkADQCAEIAJJIhAEQAJAAkACQAJAIAIgBGsiACAOIA4gAEsbIgggBGoiCyAITwRAIAsgAksNAUEAIQYgASAEaiEMIAhBG0kEQEEAIQAMBQtBACAIQWZqIgAgACAISxshDUEAIQADQCAGQRpqIAhLDQMgAEHhB08NBCAHQYgIaiAGIAxqIgkQqgIgBy0AiAgNCSAHQQhqIABqIgQgBSAHKQCJCCITQjiGIhRCOoinai0AADoAACAEQQFqIAUgFCATQiiGQoCAgICAgMD/AIOEIhRCNIinQT9xai0AADoAACAEQQJqIAUg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wABBI0GwnMAAEIQCAAtBISEGIARBJEkNASAEEAAMAQsgA0EQahCoASADKAIMIQYLIAFBgAFqELwCIAFBAToApAFBAQshASAAIAY2AgQgACABRTYCACADQTBqJAAL3QUBCX8CQCACBEAgACgCBCEIIAAoAgAhCSAAKAIIIQcDQAJAIActAABFDQAgCUH40MAAQQQgCCgCDBEDAEUNAEEBDwtBACEGIAIhAwJAA0ACQCABIAZqIQQCQAJAAkACQCADQQhPBEAgBEEDakF8cSAEayIARQRAIANBeGohCkEAIQUMAwsgAyAAIAAgA0sbIQVBACEAA0AgACAEai0AAEEKRg0FIABBAWoiACAFRw0ACwwBCyADRQ0EQQAhACAELQAAQQpGDQMgA0EBRg0EQQEhACAELQABQQpGDQMgA0ECRg0EQQIhACAELQACQQpGDQMgA0EDRg0EQQMhACAELQADQQpGDQMgA0EERg0EQQQhACAELQAEQQpGDQMgA0EFRg0EQQUhACAELQAFQQpGDQMgA0EGRg0EQQYhACAELQAGQQpHDQQMAwsgBSADQXhqIgpLDQELA0ACQCAEIAVqIgsoAgAiAEF/cyAAQYqUqNAAc0H//ft3anFBgIGChHhxDQAgC0EEaigCACIAQX9zIABBipSo0ABzQf/9+3dqcUGAgYKEeHENACAFQQhqIgUgCk0NAQsLIAUgA00NACAFIAMQ/AIACyADIAVGDQEgBSADayEDIAQgBWohBEEAIQADQCAAIARqLQAAQQpHBEAgAyAAQQFqIgBqDQEMAwsLIAAgBWohAAsCQCAAIAZqIgBBAWoiBiAASSACIAZJcg0AIAAgAWotAABBCkcNACAHQQE6AAACQCACIAZNBEAgBiACIgBGDQUMAQsgASAGaiwAAEG/f0wNACAGIQAMBAsgASACQQAgBhChAgALIAIgBmshAyACIAZPDQELCyAHQQA6AAAgAiEACyAJIAEgACAIKAIMEQMABEBBAQ8LAkAgAiAATQRAIAAgAkYNAQwECyAAIAFqLAAAQb9/TA0DCyAAIAFqIQEgAiAAayICDQALC0EADwsgASACIAAgAhChAgALnAYCBH8BfiMAQTBrIgQkACAEIAEtAAAiBkEBajoACCAEQSBqIAIgBEEIakEBIAMoAgwiBREEAAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAEKAIgRQRAIAQoAiQhByAGQQFrDgkCAwQFBgcICQoBCyAEKQIkIQggAEEBNgIAIAAgCDcCBAwLCyAEQgA3AwgMCQsgBEEIaiABQQRqIAIgAxCaAQwICyAEIAFBBGooAgA2AhggBEEgaiACIARBGGpBBCAFEQQAIAQCfyAEKAIgRQRAIAQgBCgCJDYCDEEADAELIAQgBCkCJDcCDEEBCzYCCAwHCyAEIAFBCGorAwA5AxggBEEgaiACIARBGGpBCCAFEQQAIAQCfyAEKAIgRQRAIAQgBCgCJDYCDEEADAELIAQgBCkCJDcCDEEBCzYCCAwGCyAEIAFBBGooAgA2AhggBEEgaiACIARBGGpBBCAFEQQAIAQCfyAEKAIgRQRAIAQgBCgCJDYCDEEADAELIAQgBCkCJDcCDEEBCzYCCAwFCyAEIAFBCGopAwA3AxggBEEgaiACIARBGGpBCCAFEQQAIAQCfyAEKAIgRQRAIAQgBCgCJDYCDEEADAELIAQgBCkCJDcCDEEBCzYCCAwECyAEIAFBBGooAgA2AhggBEEgaiACIARBGGpBBCAFEQQAIAQCfyAEKAIgRQRAIAQgBCgCJDYCDEEADAELIAQgBCkCJDcCDEEBCzYCCAwDCyAEIAFBCGopAwA3AxggBEEgaiACIARBGGpBCCAFEQQAIAQCfyAEKAIgRQRAIAQgBCgCJDYCDEEADAELIAQgBCkCJDcCDEEBCzYCCAwCCyAEIAEtAAE6ABhBASEBIARBIGogAiAEQRhqQQEgBREEAAJAIAQoAiBFBEAgBCAEKAIkNgIMQQAhAQwBCyAEIAQpAiQ3AgwLIAQgATYCCAwBCyAEQQhqIAFBBGogAiADEJoBCyAEKAIIRQRAIAQoAgwhASAAQQA2AgAgACABIAdqNgIEDAELIAQpAgwhCCAAQQE2AgAgACAINwIECyAEQTBqJAALkgUBB38CQAJAAn8CQCAAIAFrIAJJBEAgASACaiEFIAAgAmohAyACQQ9LDQEgAAwCCyACQQ9NBEAgACEDDAMLIABBACAAa0EDcSIFaiEEIAUEQCAAIQMgASEAA0AgAyAALQAAOgAAIABBAWohACADQQFqIgMgBEkNAAsLIAQgAiAFayICQXxxIgZqIQMCQCABIAVqIgVBA3EiAARAIAZBAUgNASAFQXxxIgdBBGohAUEAIABBA3QiCGtBGHEhCSAHKAIAIQADQCAEIAAgCHYgASgCACIAIAl0cjYCACABQQRqIQEgBEEEaiIEIANJDQALDAELIAZBAUgNACAFIQEDQCAEIAEoAgA2AgAgAUEEaiEBIARBBGoiBCADSQ0ACwsgAkEDcSECIAUgBmohAQwCCyADQXxxIQBBACADQQNxIgZrIQcgBgRAIAEgAmpBf2ohBANAIANBf2oiAyAELQAAOgAAIARBf2ohBCAAIANJDQALCyAAIAIgBmsiBkF8cSICayEDQQAgAmshAgJAIAUgB2oiBUEDcSIEBEAgAkF/Sg0BIAVBfHEiB0F8aiEBQQAgBEEDdCIIa0EYcSEJIAcoAgAhBANAIABBfGoiACAEIAl0IAEoAgAiBCAIdnI2AgAgAUF8aiEBIAAgA0sNAAsMAQsgAkF/Sg0AIAEgBmpBfGohAQNAIABBfGoiACABKAIANgIAIAFBfGohASAAIANLDQALCyAGQQNxIgBFDQIgAiAFaiEFIAMgAGsLIQAgBUF/aiEBA0AgA0F/aiIDIAEtAAA6AAAgAUF/aiEBIAAgA0kNAAsMAQsgAkUNACACIANqIQADQCADIAEtAAA6AAAgAUEBaiEBIANBAWoiAyAASQ0ACwsL6AQCBH8GfiAAIAAoAjggAmo2AjgCQCAAAn8CQAJAAkAgACgCPCIFRQRADAELAn5BCCAFayIEIAIgBCACSRsiBkEDTQRAQgAMAQtBBCEDIAE1AAALIQcgACAAKQMwIANBAXIgBkkEQCABIANqMwAAIANBA3SthiAHhCEHIANBAnIhAwsgAyAGSQR+IAEgA2oxAAAgA0EDdK2GIAeEBSAHCyAFQQN0QThxrYaEIgc3AzAgBCACSw0BIABBIGoiAyAAQShqIgUpAwAgB4UiCCAAQRhqIgYpAwB8IgogAykDACIJQg2JIAkgACkDEHwiCYUiC3wiDCALQhGJhTcDACAGIAxCIIk3AwAgBSAKIAhCEImFIghCFYkgCCAJQiCJfCIIhTcDACAAIAcgCIU3AxALIAIgBGsiAkEHcSEDIAQgAkF4cSICSQRAIABBGGopAwAhCCAAQSBqKQMAIQcgAEEoaikDACEKIAApAxAhCQNAIAggASAEaikAACILIAqFIgh8IgogByAJfCIJIAdCDYmFIgd8IgwgB0IRiYUhByAIQhCJIAqFIghCFYkgCCAJQiCJfCIJhSEKIAxCIIkhCCAJIAuFIQkgBEEIaiIEIAJJDQALIAAgBzcDICAAIAk3AxAgACAKNwMoIAAgCDcDGAsgA0EDSw0BQgAhB0EADAILIAIgBWohAwwCCyABIARqNQAAIQdBBAsiAkEBciADSQRAIAEgAiAEamozAAAgAkEDdK2GIAeEIQcgAkECciECCyACIANJBH4gASACIARqajEAACACQQN0rYYgB4QFIAcLNwMwCyAAIAM2AjwLngUCBn8BfiMAQdAAayIDJAAgA0H/AToAByADQSBqIAEgA0EHakEBIAIoAiARBAACQAJAAkACQAJAAkACQAJAIAMtACBBBEcEQCADKQMgIgmnQf8BcUEGRw0BC0ECIQQCQAJAAkACQAJAAkACQAJAIAMtAAdBf2oOCAcGDgAFAQIEAwtBAyEEDAwLQQUhBAwLCyAAQQU6AAQMCQsgAEEFOgAEDAgLIANBIGogASACEEcgAykCJCEJIAMoAiAEQCAAIAk3AgQMCAsgCUIgiKchBiADQTBqKAIAIQUgA0EsaigCACEHIAmnIQhBByEEDAkLIANBIGogASACEEQgAykCJCEJIAMoAiBFDQUgACAJNwIEDAYLIANBIGogASACEHwgAygCIEUNAyAAIAMpAiQ3AgQMBQsgA0EgaiABIAIQViADKAIgRQ0BIAAgAykCJDcCBAwECyAAQQE2AgAgACAJNwIEDAYLIANBEGogA0E4aikDADcDACADQRhqIANBQGspAwA3AwAgAyADQTBqKQMANwMIIANBLGooAgAhByADQShqKAIAIQYgA0HIAGooAgAhBUEAIQQMBAsgA0EQaiADQThqKQMANwMAIAMgA0EwaikDADcDCCADQSxqKAIAIQcgA0EoaigCACEGIANBQGsoAgAhBUEBIQQMAwsgCUIgiKchBiADQTBqKAIAIQUgA0EsaigCACEHIAmnIQhBBCEEDAILIABBATYCAAwCCwsgAEEANgIAIABBGGogAykDCDcCACAAQTBqIAVBAWo2AgAgAEEUaiAHNgIAIABBEGogBjYCACAAQQxqIAg2AgAgAEEIaiAENgIAIABBKGogA0EYaikDADcCACAAQSBqIANBEGopAwA3AgALIANB0ABqJAALgAUBCn8jAEEwayIDJAAgA0EkaiABNgIAIANBAzoAKCADQoCAgICABDcDCCADIAA2AiAgA0EANgIYIANBADYCEAJAAkACQCACKAIIIgpFBEAgAkEUaigCACIERQ0BIAIoAgAhASACKAIQIQAgBEF/akH/////AXFBAWoiByEEA0AgAUEEaigCACIFBEAgAygCICABKAIAIAUgAygCJCgCDBEDAA0ECyAAKAIAIANBCGogAEEEaigCABEBAA0DIABBCGohACABQQhqIQEgBEF/aiIEDQALDAELIAJBDGooAgAiAEUNACAAQQV0IQsgAEF/akH///8/cUEBaiEHIAIoAgAhAQNAIAFBBGooAgAiAARAIAMoAiAgASgCACAAIAMoAiQoAgwRAwANAwsgAyAEIApqIgVBHGotAAA6ACggAyAFQQRqKQIAQiCJNwMIIAVBGGooAgAhBiACKAIQIQhBACEJQQAhAAJAAkACQCAFQRRqKAIAQQFrDgIAAgELIAZBA3QgCGoiDCgCBEH6AEcNASAMKAIAKAIAIQYLQQEhAAsgAyAGNgIUIAMgADYCECAFQRBqKAIAIQACQAJAAkAgBUEMaigCAEEBaw4CAAIBCyAAQQN0IAhqIgYoAgRB+gBHDQEgBigCACgCACEAC0EBIQkLIAMgADYCHCADIAk2AhggCCAFKAIAQQN0aiIAKAIAIANBCGogACgCBBEBAA0CIAFBCGohASALIARBIGoiBEcNAAsLQQAhACAHIAIoAgRJIgFFDQEgAygCICACKAIAIAdBA3RqQQAgARsiASgCACABKAIEIAMoAiQoAgwRAwBFDQELQQEhAAsgA0EwaiQAIAAL2wQBBn8jAEFAaiIDJAAgA0EIaiABIAIQ3AIgAyADKAIIIAMoAgwQ3AIgAyADKQMANwMQIANBMGogA0EQahA7AkACQAJAAkACQCADKAIwIgYEQCADKAI0IQEgA0E8aigCAA0CIAAgBjYCBAwBCyAAQejNwAA2AgRBACEBCyAAQQA2AgAgAEEIaiABNgIADAELAkAgAkUEQEEBIQUMAQsgAkF/TA0DIAJBARDaAiIFRQ0CCyADQQA2AiAgAyAFNgIYIAMgAjYCHCABIAJLBEAgA0EYakEAIAEQdSADKAIYIQUgAygCICEECyAEIAVqIAYgARCDAxogAyABIARqIgI2AiAgAygCHCACa0ECTQRAIANBGGogAkEDEHUg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BCEGA0ACQCADQTBqIAEgAhAvIAMoAjANACADQShqIAdBCGooAgAiBDYCACADIAcpAgAiCzcDICADKAI4IQggAygCSCEJIANBOGoiCiAENgIAIAMgCzcDMCADKAIYIgQgAygCFEYEQCADQRBqIAQQ6gEgAygCGCEECyAGIAlqIQYgAygCECAEQQR0aiIEIAMpAzA3AgQgBCAINgIAIARBDGogCigCADYCACADIAMoAhhBAWo2AhggBUF/aiIFDQEMAwsLIAMpAjQhCyAAQQE2AgAgACALNwIEIAMoAhgiAARAIAMoAhAhBCAAQQR0IQUDQCAELQAAIgBBfmpBB0kgAEVyRQRAIARBBGoQiQILIARBEGohBCAFQXBqIgUNAAsLIANBEGoQkAIMAgsgAEEBNgIAIAAgCzcCBAwBCyAAIAMpAxA3AgQgAEEANgIAIABBEGogBjYCACAAQQxqIANBGGooAgA2AgALIANB0ABqJAAL3AMDA38BfgF8IwBB4ABrIgIkACACIAE2AiwCQAJAQQFBAiABEAUiA0EBRhtBACADGyIDQQJHBEAgAEEIOgAAIAAgAzoAAQwBCyACQRhqIAEQBiACKAIYBEAgAisDICEGIABBAzoAACAAQQhqIAY5AwAMAQsgAkEQaiABEAICQCACKAIQIgNFBEAgAkEANgIwDAELIAIgAigCFCIENgJYIAIgBDYCVCACIAM2AlAgAkEIaiACQdAAahCaAiACQTBqIAIoAgggAigCDBDLAiACKAIwRQ0AIABBAToAACAAQQRqIAIpAzA3AgAgAEEMaiACQThqKAIANgIADAELAkACQAJAAkAgARAHQQFGDQAgARAIQQFGDQAgAiACQSxqEP4BIAIoAgQhASACKAIARQ0BIABBADoAACABQSRPDQIMAwsgAEEAOgAADAILIAIgATYCPCACQdAAaiACQTxqELwBIAIoAlBFDQMgAkHIAGogAkHYAGooAgAiAzYCACACIAIpA1AiBTcDQCAAQQxqIAM2AgAgAEEEaiAFNwIAIABBCToAACABQSRJDQELIAEQAAsgAigCLCEBCyABQSRPBEAgARAACyACQeAAaiQADwtBlJXAAEErQYicwAAQhAIAC6sEAgV/AX5BASEDAkAgASgCGCIEQScgAUEcaigCACgCECIFEQEADQBBAiEBQTAhAgJAAn4C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IGooAgA2AgALIANBgAFqJAALtAMBBH8jAEEgayICJAACQAJAAkACQAJAAkAgAC0AuAVBAWsOAwMAAgELAAsgAEHYAmogAEHYAhCDAxoLIAJBEGogAEHYAmoiAyABEJkBQQMhAQJAIAIoAhAiBEECRiIFDQAgAigCFCEBAkACQAJAIAAtAKgFDgQBAgIAAgsgAEGABGohAwsgAxCGAgsCQCAERQRAIAIgATYCGCACQSA2AhwgAiAAQbAFaiACQRxqIAJBGGoQ5QEgAigCAA0EIAIoAgQiAUEkTwRAIAEQAAsgAigCHCIBQSRPBEAgARAACyACKAIYIgFBJEkNASABEAAMAQsgAiABNgIYIAJBIDYCHCACQQhqIABBtAVqIAJBHGogAkEYahDlASACKAIIDQQgAigCDCIBQSRPBEAgARAACyACKAIcIgFBJE8EQCABEAALIAIoAhgiAUEkSQ0AIAEQAAsgACgCsAUiAUEkTwRAIAEQAAtBASEBIAAoArQFIgNBJEkNACADEAALIAAgAToAuAUgAkEgaiQAIAUPC0Ggj8AAQSNBiI/AABCEAgALQYCAwABBFRD2AgALQYCAwABBFRD2AgALsQMBAX8jAEFAaiICJAACQAJAAkACQAJAAkAgAC0AAEEBaw4DAQIDAAsgAiAAQQRqKAIANgIEQRRBARDaAiIARQ0EIABBEGpByMjAACgAADYAACAAQQhqQcDIwAApAAA3AAAgAEG4yMAAKQAANwAAIAJClICAgMACNwIMIAIgADYCCCACQTxqQQI2AgAgAkEkakHiADYCACACQgM3AiwgAkHAxsAANgIoIAJB4wA2AhwgAiACQRhqNgI4IAIgAkEEajYCICACIAJBCGo2AhggASACQShqELsBIQAgAigCDEEBSA0DIAIoAggQKwwDCyAALQABIQAgAkE8akEBNgIAIAJCATcCLCACQbzAwAA2AiggAkHkADYCDCACIABBIHNBP3FBAnQiAEHMyMAAaigCADYCHCACIABBzMrAAGooAgA2AhggAiACQQhqNgI4IAIgAkEYajYCCCABIAJBKGoQuwEhAAwCCyAAQQRqKAIAIgAoAgAgACgCBCABEP8CIQAMAQsgAEEEaigCACIAKAIAIAEgACgCBCgCEBEBACEACyACQUBrJAAgAA8LQRRBARD7AgAL+gIBBX8gAEEL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CSACQQRqQejlwAAQTBoCfyACLQAMIgEgAi0ADUUNABogAUH/AXEhAEEBIAANABogAigCCCIALQAAQQRxRQRAIAAoAhhBh9HAAEECIABBHGooAgAoAgwRAwAMAQsgACgCGEGG0cAAQQEgAEEcaigCACgCDBEDAAsgAkEQaiQAQf8BcUEARwvrAQECfyMAQTBrIgIkAAJ/AkACQAJAIAAtAABBfGoiA0ECIANB/wFxQQJJG0H/AXFBAWsOAgECAAsgAkEkakEANgIAIAJB0JzAADYCICACQgE3AhQgAkGsncAANgIQIAEgAkEQahC7AQwCCyACQSRqQQA2AgAgAkHQnMAANgIgIAJCATcCFCACQYidwAA2AhAgASACQRBqELsBDAELIAIgADYCDCACQSRqQQE2AgAgAkICNwIUIAJB4JzAADYCECACQTk2AiwgAiACQShqNgIgIAIgAkEMajYCKCABIAJBEGoQuwELIAJBMGokAAvrAQECfyMAQTBrIgIkAAJ/AkACQAJAIAAtAABBfGoiA0ECIANB/wFxQQJJG0H/AXFBAWsOAgECAAsgAkEkakEANgIAIAJB0JzAADYCICACQgE3AhQgAkGQnsAANgIQIAEgAkEQahC7AQwCCyACQSRqQQA2AgAgAkHQnMAANgIgIAJCATcCFCACQeydwAA2AhAgASACQRBqELsBDAELIAIgADYCDCACQSRqQQE2AgAgAkICNwIUIAJBxJ3AADYCECACQTk2AiwgAiACQShqNgIgIAIgAkEMajYCKCABIAJBEGoQuwELIAJBMGokAAvfAQICfwF+IwBBIGsiAyQAIAAoAgBFBEAgAEF/NgIAIANBGGogAEEkaikCADcDACADQRBqIABBHGopAgA3AwAgAEEUaikCACEFIABBGGpBADYCACADIAU3AwggA0EIahC9AgJAIAAoAgRBAkYNACAAQQRqKAIEIgRBJEkNACAEEAALIAAgAjYCCCAAIAE2AgQgACgCECEBIABBADYCECAAIAAoAgBBAWo2AgAgAQRAIAAoAgwgASgCBBECAAsgA0EgaiQADwtBzLnAAEEQIANBCGpB3LnAAEGku8AAEK4BAAu7AQECfyMAQSBrIgQkACAAAn9BACACIANqIgMgAkkNABogASgCBCICQQF0IgUgAyAFIANLGyIDQQggA0EISxsiBUF/c0EfdiEDIAQgAgR/IAQgASgCADYCECAEIAI2AhQgAkF/c0EfdgVBAAs2AhggBCAFIAMgBEEQahCQASAEKAIEIQMgBCgCAARAIARBCGooAgAMAQsgASAFNgIEIAEgAzYCAEGBgICAeAs2AgQgACADNgIAIARBIGokAAu8AQIBfwJ+IwBBIGsiAyQAIANCfzcDGCADIAEgA0EYakEIIAIoAiARBAAgAAJ/AkAgAy0AAEEERwRAIAMpAwAiBKdB/wFxQQZHDQELIAMpAxghBSADIAEgAhBAIAMpAgQhBCADKAIADQAgA0EMaigCACEBIABBIGogA0EQaigCAEEIajYCACAAQRhqIAE2AgAgAEEQaiAENwMAIABBCGogBTcDAEEADAELIAAgBDcCBEEBCzYCACADQSBqJAALtAEBAX8jAEFAaiIDJAAgA0EIakGACBDMASADQQA2AhggAyADKQMINwMQIANBIGogASACIANBEGoQJAJAIAMtACBBA0YEQCADQTBqIANBGGooAgA2AgAgAyADKQMQNwMoIANCADcDICAAIANBIGpB5I3AABA3IANBKGoQvAIMAQsgA0IFNwMgIANBOGogA0EgahDzAiAAIAMpAzg3AgQgAEEBNgIAIANBEGoQvAILIANBQGskAAvTAQECfyMAQTBrIgMkACADIAE2AgggAyABKAIIIgQ2AgwgA0EQaiACIAEQPSABKAIIIgIgBE8EQCADQSBqIAEoAgAgBGogAiAEaxAxAkAgAygCIEUEQCAAIAMpAxA3AgAgAEEIaiADQRhqKAIANgIAIAMgASgCCDYCDAwBCwJAIAMoAhBFBEAgAEECNgIEIABBCGpBnIvAADYCAAwBCyAAIAMpAhQ3AgQLIABBATYCAAsgA0EIaiIAKAIAIAAoAgQ2AgggA0EwaiQADwsgBCACEPwCAAu1AQECfyMAQTBrIgMkACADQYAIEMwBIANBADYCECADIAMpAwA3AwggA0EYaiABIANBCGpB4IzAABBhAkACQCADKAIYRQRAIAMoAhAiBCADKAIcIgFJDQIgAygCCCABIAIQ3AEgAEEANgIAIAAgATYCBAwBCyADIAMpAhw3AxggA0EoaiADQRhqEPMCIAAgAykDKDcCBCAAQQE2AgALIANBCGoQvAIgA0EwaiQADwsgASAEEP0CAAvVAQEFfyMAQRBr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IAJBGGogAEEIaigCADYCACACIAApAgA3AxAgAkEIaiACQRBqIAEQTSACKAIIIQAgAigCDCACQSBqJABBISAAGwtyAQF/IwBBMGsiAiQAIAIgATYCBCACIAA2AgAgAkEcakECNgIAIAJBLGpB5QA2AgAgAkICNwIMIAJBvNPAADYCCCACQeUANgIkIAIgAkEgajYCGCACIAJBBGo2AiggAiACNgIgIAJBCGpB7NPAABCPAgALcgEBfyMAQTBrIgIkACACIAE2AgQgAiAANgIAIAJBHGpBAjYCACACQSxqQeUANgIAIAJCAjcCDCACQdDUwAA2AgggAkHlADYCJCACIAJBIGo2AhggAiACQQRqNgIoIAIgAjYCICACQQhqQeDUwAAQjwIAC3IBAX8jAEEwayICJAAgAiABNgIEIAIgADYCACACQRxqQQI2AgAgAkEsakHlADYCACACQgI3AgwgAkGM1MAANgIIIAJB5QA2AiQgAiACQSBqNgIYIAIgAkEEajYCKCACIAI2AiAgAkEIakGc1MAAEI8CAAtvAQF/IwBBMGsiAyQAIAMgATYCBCADIAA2AgAgA0EcakECNgIAIANBLGpB5QA2AgAgA0ICNwIMIANBpNDAADYCCCADQeUANgIkIAMgA0EgajYCGCADIAM2AiggAyADQQRqNgIgIANBCGogAhCPAgALVgECfyMAQSBrIgIkACAAQRxqKAIAIQMgACgCGCACQRhqIAFBEGopAgA3AwAgAkEQaiABQQhqKQIANwMAIAIgASkCADcDCCADIAJBCGoQOCACQSBqJAALaQECfyMAQSBrIgIkACACQQhqIAEoAgAQAgJAIAIoAggiAQRAIAIgAigCDCIDNgIYIAIgAzYCFCACIAE2AhAgAiACQRBqEJoCIAAgAigCACACKAIEEMsCDAELIABBADYCAAsgAkEgaiQAC2cBAX8jAEEgayIDJAAgACgCACADQQhqIAEgAhAxIAMoAggEQCADIAMpAgw3AxhB4K7AAEErIANBGGpBjK/AAEGAsMAAEK4BAAsgAygCDCIAIAAgA0EQaigCAGoQ+QEgA0EgaiQAQQALbQEEfyMAQRBrIgEkAAJAQQBByLzAACgCABEGACICBEAgACgCACgCACIAIAAoAgAiA0EBaiIENgIAIAQgA0kNASACIAAQoQEgAUEQaiQADwtBlLjAAEHGACABQQhqQdy4wABBvLnAABCuAQALAAtwAQF/AkACQAJAAkAgAC0AuAUOBAECAgACCyAAQdgCahC3AiAAKAKwBSIBQSRPBEAgARAACyAAKAK0BSIAQSNNDQEMAgsgABC3AiAAKAKwBSIBQSRPBEAgARAACyAAKAK0BSIAQSNLDQELDwsgABAAC1kBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBrIvAACACQQhqEDggAkEgaiQAC1kBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpB/JTAACACQQhqEDggAkEgaiQAC2EBA38gACABELACIAAoAgAiBCAAKAIIIgJqIQMCQAJAIAFBAk8EQCADQQAgAUF/aiIBEIQDGiAEIAEgAmoiAmohAwwBCyABRQ0BCyADQQA6AAAgAkEBaiECCyAAIAI2AggLWQEBfyMAQSBrIgIkACACIAAoAgA2AgQgAkEYaiABQRBqKQIANwMAIAJBEGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakHsv8AAIAJBCGoQOCACQSBqJAALaQAjAEEwayIBJABBgO3AAC0AAARAIAFBHGpBATYCACABQgI3AgwgAUH8xsAANgIIIAFB5QA2AiQgASAANgIsIAEgAUEgajYCGCABIAFBLGo2AiAgAUEIakGkx8AAEI8CAAsgAUEwaiQAC1kBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpB0M3AACACQQhqEDggAkEgaiQAC2gBAn8gASgCACEDAkACQAJAIAFBCGooAgAiAUUEQEEBIQIMAQsgAUF/TA0BIAFBARDaAiICRQ0CCyACIAMgARCDAyECIAAgATYCCCAAIAE2AgQgACACNgIADwsQjgIACyABQQEQ+wIAC1kBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpB7NLAACACQQhqEDggAkEgaiQAC44BAgF/An4jAEEQayIBJABB2OzAACAAOwEAQdDswABCATcDACABENQBIAEpAwghAiABKQMAIQNB9OzAACgCAARAQfDswAAoAgAEQBCFARDRAQsLQfjswABCADcDAEH07MAAQYiawAA2AgBB4OzAACADNwMAQejswAAgAjcDAEHw7MAAQQA2AgAgAUEQaiQAC1YBAX8jAEEgayICJAAgAiAANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBrIvAACACQQhqEDggAkEgaiQAC1YBAX8jAEEgayICJAAgAiAANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpB/JTAACACQQhqEDggAkEgaiQAC2AAAkACQAJAAkACQCAAKAIADgcBAgMDBAMDAAsgAEEEaiIAEKcBIAAQkQIPCyAAQRBqELwCIABBHGoiABDdASAAEJACDwsgAEEQaiIAEN0BIAAQkAILDwsgAEEEahC8AgtIAQJ/AkAgAUUEQEEBIQIMAQsgAUEATgRAIAEgAUF/c0EfdiIDENoCIgINASABIAMQ+wIACxCOAgALIAAgATYCBCAAIAI2AgALXQECfyAAQQxqKAIAIgIgAkF/aiIDIAAoAgQiAiAAKAIAa3FrQQFGBEAgABCMASAAKAIMQX9qIQMgACgCBCECCyAAIAJBAWogA3E2AgQgACgCCCACQQJ0aiABNgIAC1YBAX8jAEEgayICJAAgAiAANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpB7NLAACACQQhqEDggAkEgaiQAC2IBAX8jAEFAaiIBJAAgAUEBOgAPIABBADYCCCAAQgE3AgAgAUEQaiAAQYCEwAAQogIgAUEPaiABQRBqEI8BBEBBmITAAEE3IAFBOGpB0ITAAEGshcAAEK4BAAsgAUFAayQAC10BAX8jAEEQayIEJAAgASgCACACKAIAIAMoAgAQISECIARBCGoQpwIgAAJ/IAQoAghFBEAgACACQQBHOgABQQAMAQsgAEEEaiAEKAIMNgIAQQELOgAAIARBEGokAAtVAQN/AkACQAJAQfDswAAoAgAiAUEBaiIAQf////8AcSAARgRA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/AIACyAAIAIgA2s2AgQgACABKAIIIANqNgIAC0ABAX8jAEEgayIAJAAgAEEcakEANgIAIABBzMzAADYCGCAAQgE3AgwgAEHozMAANgIIIABBCGpBwM3AABCPAgALQwEDfwJAIAJFDQADQCAALQAAIgQgAS0AACIFRgRAIABBAWohACABQQFqIQEgAkF/aiICDQEMAgsLIAQgBWshAwsgAwtIAQJ/IAAtAABBA0YEQCAAQQRqKAIAIgEoAgAgASgCBCgCABECACABKAIEIgIoAgQEQCACKAIIGiABKAIAECsLIAAoAgQQKwsLSAEBfyMAQRBrIgIkACACQQhqIAAgARBrAkAgAigCDCIAQYGAgIB4RwRAIABFDQEgAigCCCAAEPsCAAsgAkEQaiQADwsQjgIAC0gBAX8jAEEQayICJAAgAkEIaiAAIAEQagJAIAIoAgwiAEGBgICAeEcEQCAARQ0BIAIoAgggABD7AgALIAJBEGokAA8LEI4CAAtKAQF/IwBBEGsiAiQAIAJBCGogACABQQEQewJAIAIoAgwiAEGBgICAeEcEQCAARQ0BIAIoAgggABD7AgALIAJBEGokAA8LEI4CAAtKAQF/IwBBEGsiAyQAIANBCGogACABIAIQewJAIAMoAgwiAEGBgICAeEcEQCAARQ0BIAMoAgggABD7AgALIANBEGokAA8LEI4CAAtQAQF/QSBBBBDaAiIBRQRAQSBBBBD7AgALIAFCgYCAgBA3AgAgASAAKQIANwIIIAFBEGogAEEIaikCADcCACABQRhqIABBEGopAgA3AgAgAQtLAQF/IAAoAgAiAQRAIAEgACgCBCgCABECACAAKAIEIgEoAgQEQCABKAIIGiAAKAIAECsLIABBCGooAgAgAEEMaigCACgCDBECAAsLTQECfyMAQRBrIgIkACAAKAIAIQMgAEEANgIAIANFBEBBlLrAAEEcEPYCAAsgAiADNgIMIANBCGpBASABEHogAkEMahCXASACQRBqJAALTQECfyMAQRBrIgIkACAAKAIAIQMgAEEANgIAIANFBEBBlLrAAEEcEPYCAAsgAiADNgIMIANBCGpBACABEHogAkEMahCXASACQRBqJAALSgEBfyAAKAIAIgBBBGooAgAgACgCCCIDayACSQRAIAAgAyACEHMgACgCCCEDCyAAKAIAIANqIAEg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MgEBfyAAAn9BACABKAIEIgJFDQAaIAAgAjYCBCAAIAEoAgA2AgAgAkF/c0Efdgs2AggLQAEBfyMAQSBrIgAkACAAQRxqQQA2AgAgAEHozcAANgIYIABCATcCDCAAQZjOwAA2AgggAEEIakGgzsAAEI8CAAs/AQF/IwBBIGsiAiQAIAJBAToAGCACIAE2AhQgAiAANgIQIAJBvNDAADYCDCACQaDPwAA2AgggAkEIahDfAQALKQEBfwJAIAAoAgQiAUUNACAAKAIAIAFBBHRFIAFB////P0tyDQAQKwsLKQEBfwJAIAAoAgQiAUUNACAAKAIAIAFBKGxFIAFBs+bMGUtyDQAQKwsLKgEBfwJAIAAoAgQiAUUNACAAKAIAIAFBAnRFIAFB/////wFLcg0AECsLCzMAAkAgAEH8////B0sNACAARQRAQQQPCyAAIABB/f///wdJQQJ0ENoCIgBFDQAgAA8LAAs0AQJ/IAFBeGoiAiACKAIAIgJBAWoiAzYCACADIAJJBEAACyAAQcy8wAA2AgQgACABNgIACzMBAX9BNEEEENoCIgFFBEBBNEEEEPsCAAsgAUKBgICAEDcCACABQQhqIABBLBCDAxogAQsvAQF/IwBBEGsiAiQAIAIgACgCADYCDCACQQxqIAEQYiACQQxqEPoBIAJBEGokAAsiAQF/AkAgACgCBCIBRQ0AIABBCGooAgBBAUgNACABECsLCyQAIwBBEGsiACQAIABBCGogARCjAiAAQQhqELABIABBEGokAAsyAQF/IAAgASgCBCABKAIIIgJLBH8gASACEPUBIAEoAggFIAILNgIEIAAgASgCADYCAAsyAQF/IAAgASgCBCABKAIIIgJLBH8gASACEPYBIAEoAggFIAILNgIEIAAgASgCADYCAAsjACAAKAIAIgBBcGoQvAIgAEF8aigCACIAQSRPBEAgABAACwsjAQF/An9BASABEAlFDQAaQQALIQIgACABNgIEIAAgAjYCAAsxAQF/QQRBBBDaAiICRQRAQQRBBBD7AgALIAIgATYCACAAQey5wAA2AgQgACACNgIACzEBAX9BBEEEENoCIgJFBEBBBEEEEPsCAAsgAiABNgIAIABBgLrAADYCBCAAIAI2AgALKgEBfyAAKAIABEAgABD6ASAAKAIEIgFBJE8EQCABEAALIABBCGoQxAIL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YTg4OWEvbGlicmFyeS9zdGQvc3JjL2lvL2ltcGxzLnJzAOgAEABLAAAA8gAAAA0AAAAvcnVzdGMvMjBmZmVhNjkzOGI1ODM5YzM5MDI1MmUwNzk0MGI5OWUzYjZhODg5YS9saWJyYXJ5L3N0ZC9zcmMvaW8vY3Vyc29yLnJzRAEQAEwAAADrAAAACgAAAC9ydXN0Yy8yMGZmZWE2OTM4YjU4MzljMzkwMjUyZTA3OTQwYjk5ZTNiNmE4ODlhL2xpYnJhcnkvc3RkL3NyYy9pby9yZWFkYnVmLnJzAAAAoAEQAE0AAADkAAAAKwAAAAcAAAAMAAAABAAAAAgAAAAJAAAACgAAAGEgRGlzcGxheSBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvciB1bmV4cGVjdGVkbHkAAwAAAAAAAAABAAAACwAAAC9ydXN0Yy8yMGZmZWE2OTM4YjU4MzljMzkwMjUyZTA3OTQwYjk5ZTNiNmE4ODlhL2xpYnJhcnkvYWxsb2Mvc3JjL3N0cmluZy5ycwBgAhAASwAAALkJAAAJAAAAY2FsbGVkIGBPcHRpb246OnVud3JhcCgpYCBvbiBhIGBOb25lYCB2YWx1ZWNhbGxlZCBgUmVzdWx0Ojp1bndyYXAoKWAgb24gYW4gYEVycmAgdmFsdWUAAAMAAAAAAAAAAQAAAAwAAAAvaG9tZS9uZ25pdXMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTMuMC9zcmMvY2h1bmtlZF9lbmNvZGVyLnJzAAAAJAMQAGEAAAAtAAAAGgAAACQDEABhAAAANwAAAEQAAAAkAxAAYQAAADoAAAAnAAAAL2hvbWUvbmduaXVzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2Jhc2U2NC0wLjEzLjAvc3JjL2VuY29kZS5yc7gDEABYAAAAtgAAACAAAAC4AxAAWAAAALcAAAAlAAAAuAMQAFgAAAD8AAAAHAAAALgDEABYAAAA/QAAACEAAAC4AxAAWAAAABMBAAAuAAAAuAMQAFgAAAATAQAACQAAALgDEABYAAAAFAEAAAkAAAC4AxAAWAAAAAsBAAAuAAAAuAMQAFgAAAALAQAACQAAALgDEABYAAAADQEAAA8AAAC4AxAAWAAAAAwBAAAJAAAAuAMQAFgAAAAPAQAACQAAALgDEABYAAAAoAAAACoAAAAwLjYuMnVzZHBsLWZyb250L3NyYy9saWIucnMA5QQQABYAAABgAAAAGAAAAOUEEAAWAAAAaAAAABgAAAAvcnVzdGMvMjBmZmVhNjkzOGI1ODM5YzM5MDI1MmUwNzk0MGI5OWUzYjZhODg5YS9saWJyYXJ5L3N0ZC9zcmMvaW8vbW9kLnJzAAAAHAUQAEkAAABQAQAAGAAAAHN0cmVhbSBkaWQgbm90IGNvbnRhaW4gdmFsaWQgVVRGLTgAAHgFEAAiAAAAFQAAACgpAAANAAAABAAAAAQAAAAOAAAADwAAABAAAAANAAAAAAAAAAEAAAARAAAAV3JpdGluZyB0byBhIFN0cmluZyBzaG91bGRuJ3QgZmFpbC9ob21lL25nbml1cy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9iYXNlNjQtMC4xMy4wL3NyYy9lbmNvZGUucnMAAPYFEABYAAAAUwAAAA4AAAAVAAAADAAAAAQAAAAWAAAAFwAAABgAAAAZAAAAGgAAABsAAAAcAAAAL2hvbWUvbmduaXVzL0RvY3VtZW50cy9naXQtcmVwb3MvdXNkcGwtcnMvdXNkcGwtY29yZS9zcmMvc2VyZGVzL3RyYWl0cy5ycwAAAIgGEABJAAAAdgAAABwAAAAdAAAAGAAAAAgAAAAeAAAAHwAAACAAAAAhAAAAIgAAACMAAAAkAAAAJQAAAGB1bndyYXBfdGhyb3dgIGZhaWxlZC9ob21lL25nbml1cy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy93YXNtLWJpbmRnZW4tZnV0dXJlcy0wLjQuMzEvc3JjL2xpYi5ycyUHEABjAAAA2gAAACAAQaCPwAAL0EZgYXN5bmMgZm5gIHJlc3VtZWQgYWZ0ZXIgY29tcGxldGlvbmZhaWxlZCB0byB3cml0ZSB3aG9sZSBidWZmZXIAwwcQABwAAAAXAAAAJgAAAAwAAAAEAAAAJwAAACgAAAApAAAAZm9ybWF0dGVyIGVycm9yAAQIEAAPAAAAKAAAAC9ydXN0Yy8yMGZmZWE2OTM4YjU4MzljMzkwMjUyZTA3OTQwYjk5ZTNiNmE4ODlhL2xpYnJhcnkvc3RkL3NyYy9pby9tb2QucnMAAAAgCBAASQAAABoFAAAWAAAAIAgQAEkAAAAeBQAADQAAAGFkdmFuY2luZyBpbyBzbGljZXMgYmV5b25kIHRoZWlyIGxlbmd0aACMCBAAJwAAACAIEABJAAAAHAUAAA0AAAAqAAAADAAAAAQAAAArAAAAKgAAAAwAAAAEAAAALAAAACsAAADMCBAALQAAAC4AAAAvAAAAMAAAADEAAABjYW5ub3QgYWNjZXNzIGEgVGhyZWFkIExvY2FsIFN0b3JhZ2UgdmFsdWUgZHVyaW5nIG9yIGFmdGVyIGRlc3RydWN0aW9uAAAyAAAAAAAAAAEAAAAzAAAAL3J1c3RjLzIwZmZlYTY5MzhiNTgzOWMzOTAyNTJlMDc5NDBiOTllM2I2YTg4OWEvbGlicmFyeS9zdGQvc3JjL3RocmVhZC9sb2NhbC5ycwBgCRAATwAAAKUBAAAJAAAAKgAAAAwAAAAEAAAANAAAADUAAAAKAAAAYSBEaXNwbGF5IGltcGxlbWVudGF0aW9uIHJldHVybmVkIGFuIGVycm9yIHVuZXhwZWN0ZWRseQAyAAAAAAAAAAEAAAALAAAAL3J1c3RjLzIwZmZlYTY5MzhiNTgzOWMzOTAyNTJlMDc5NDBiOTllM2I2YTg4OWEvbGlicmFyeS9hbGxvYy9zcmMvc3RyaW5nLnJzACAKEABLAAAAuQkAAAkAAAAyAAAABAAAAAQAAAA2AAAANwAAADgAAABjYWxsZWQgYE9wdGlvbjo6dW53cmFwKClgIG9uIGEgYE5vbmVgIHZhbHVlaW50ZXJuYWwgZXJyb3I6IGVudGVyZWQgdW5yZWFjaGFibGUgY29kZTogAAAAvwoQACoAAAAvaG9tZS9uZ25pdXMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTMuMC9zcmMvZGVjb2RlLnJz9AoQAFgAAADSAQAAHwAAAPQKEABYAAAA2AEAAB8AAAD0ChAAWAAAAOEBAAAfAAAA9AoQAFgAAADqAQAAHwAAAPQKEABYAAAA8wEAAB8AAAD0ChAAWAAAAPwBAAAfAAAA9AoQAFgAAAAFAgAAHwAAAPQKEABYAAAADgIAAB8AAAD0ChAAWAAAAAMBAAAkAAAA9AoQAFgAAAAEAQAAKQAAAPQKEABYAAAAKgEAABYAAAD0ChAAWAAAAC0BAAAaAAAA9AoQAFgAAABBAQAADgAAAPQKEABYAAAARAEAABIAAAD0ChAAWAAAAFgBAAATAAAASW1wb3NzaWJsZTogbXVzdCBvbmx5IGhhdmUgMCB0byA4IGlucHV0IGJ5dGVzIGluIGxhc3QgY2h1bmssIHdpdGggbm8gaW52YWxpZCBsZW5ndGhzPAwQAFQAAAD0ChAAWAAAAJ0BAAAOAAAA9AoQAFgAAACxAQAACQAAAE92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgb3V0cHV0IGJ1ZmZlciBsZW5ndGgAAPQKEABYAAAAlgAAAAoAAAD0ChAAWAAAAJsAAAAhAAAA//////////91c2RwbC1mcm9udC9zcmMvY29ubmVjdGlvbi5ycwAAABANEAAdAAAAGAAAACYAAABQT1NUaHR0cDovLzovdXNkcGwvY2FsbABEDRAABwAAAEsNEAABAAAATA0QAAsAAABsb2NhbGhvc3QAAABwDRAACQAAABANEAAdAAAAKwAAACQAAAAQDRAAHQAAADIAAAAnAAAARXhwZWN0ZWQgY2FsbCByZXNwb25zZSBtZXNzYWdlIGZyb20gLCBnb3Qgc29tZXRoaW5nIGVsc2WkDRAAJAAAAMgNEAAUAAAAdXNkcGwtZnJvbnQvc3JjL2NvbnZlcnQucnMAAOwNEAAaAAAAIAAAACcAAAB1c2RwbC1mcm9udC9zcmMvbGliLnJzAAAYDhAAFgAAAG8AAABOAAAAGA4QABYAAABuAAAAAQAAAExvYWRFcnJvcjogSW8oKQBQDhAADgAAAF4OEAABAAAATG9hZEVycm9yOiBJbnZhbGlkRGF0YQAAcA4QABYAAABMb2FkRXJyb3I6IFRvb1NtYWxsQnVmZmVyAAAAkA4QABkAAABEdW1wRXJyb3I6IElvKAAAtA4QAA4AAABeDhAAAQAAAER1bXBFcnJvcjogVW5zdXBwb3J0ZWQAANQOEAAWAAAARHVtcEVycm9yOiBUb29TbWFsbEJ1ZmZlcgAAAPQOEAAZAAAAY3JhbmtzaGFmdAAAGA8QAAoAAABkZWNreQAAACwPEAAFAAAAYW55ADwPEAADAAAAISIjJCUmJygpKissLTAxMjM0NTY3ODlAQUJDREVGR0hJSktMTU5QUVJTVFVWWFlaW2BhYmNkZWhpamtsbXBxckFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5KywuL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Li8wMTIzNDU2Nzg5QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5LV9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MDEyMzQ1Njc4OSsv////////////////////////////////////////////AAECAwQFBgcICQoLDP//DQ4PEBESExQVFv///////xcYGRobHB0eHyAhIiMkJf8mJygpKiss/y0uLzD/////MTIzNDU2//83ODk6Ozz//z0+P/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8+P////zQ1Njc4OTo7PD3/////////AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBn///////8aGxwdHh8gISIjJCUmJygpKissLS4vMDEyM///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAE2Nzg5Ojs8PT4//////////wIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob////////HB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDX//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wABAgMEBQYHCAkKC/////////8MDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJf///////yYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////z7//zQ1Njc4OTo7PD3/////////AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBn/////P/8aGxwdHh8gISIjJCUmJygpKissLS4vMDEyM///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Pv///z80NTY3ODk6Ozw9/////////wABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ////////GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjP/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////iBAQAEgQEAAIEBAAyA8QAIgPEABIDxAAyBUQAMgUEADIExAAyBIQAMgREADIEBAAL2hvbWUvbmduaXVzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2Jhc2U2NC0wLjEzLjAvc3JjL2VuY29kZS5yc/gWEABYAAAAOwEAAAkAAABjYWxsZWQgYFJlc3VsdDo6dW53cmFwKClgIG9uIGFuIGBFcnJgIHZhbHVlADoAAAAIAAAABAAAADsAAAAvaG9tZS9uZ25pdXMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTMuMC9zcmMvY2h1bmtlZF9lbmNvZGVyLnJzAAAAnBcQAGEAAABoAAAAMAAAAE92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgbnVtYmVyIG9mIGNodW5rcyBpbiBpbnB1dC9ob21lL25nbml1cy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9iYXNlNjQtMC4xMy4wL3NyYy9kZWNvZGUucnMAQxgQAFgAAAC8AAAACgAAADwAAAAIAAAABAAAAD0AAAA+AAAAPAAAAAgAAAAEAAAAPwAAAGJvZHltZXRob2Rtb2Rlc2FtZS1vcmlnaW5uby1jb3JzY29yc25hdmlnYXRlYXR0ZW1wdGVkIHRvIGNvbnZlcnQgaW52YWxpZCBSZXF1ZXN0TW9kZSBpbnRvIEpTVmFsdWUvaG9tZS9uZ25pdXMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvd2ViLXN5cy0wLjMuNTgvc3JjL2ZlYXR1cmVzL2dlbl9SZXF1ZXN0TW9kZS5yczEZEABrAAAAAwAAAAEAAABhbHJlYWR5IGJvcnJvd2VkQwAAAAAAAAABAAAARAAAAGFscmVhZHkgbXV0YWJseSBib3Jyb3dlZEMAAAAAAAAAAQAAAEUAAABhc3NlcnRpb24gZmFpbGVkOiBtaWQgPD0gc2VsZi5sZW4oKS9ydXN0Yy8yMGZmZWE2OTM4YjU4MzljMzkwMjUyZTA3OTQwYjk5ZTNiNmE4ODlhL2xpYnJhcnkvYWxsb2Mvc3JjL2NvbGxlY3Rpb25zL3ZlY19kZXF1ZS9tb2QucnNhc3NlcnRpb24gZmFpbGVkOiBzZWxmLmNhcCgpID09IG9sZF9jYXAgKiAyFxoQAF4AAADdCAAACQAAAC9ydXN0Yy8yMGZmZWE2OTM4YjU4MzljMzkwMjUyZTA3OTQwYjk5ZTNiNmE4ODlhL2xpYnJhcnkvYWxsb2Mvc3JjL2NvbGxlY3Rpb25zL3ZlY19kZXF1ZS9yaW5nX3NsaWNlcy5ycwAAsBoQAGYAAAAgAAAADgAAALAaEABmAAAAIwAAABEAAAAvaG9tZS9uZ25pdXMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvd2FzbS1iaW5kZ2VuLWZ1dHVyZXMtMC40LjMxL3NyYy9xdWV1ZS5ycwAAADgbEABlAAAAGgAAAC4AAAA4GxAAZQAAAB0AAAApAAAAOBsQAGUAAAAyAAAAGgAAAEYAAAAEAAAABAAAAEcAAABIAAAAY2xvc3VyZSBpbnZva2VkIHJlY3Vyc2l2ZWx5IG9yIGRlc3Ryb3llZCBhbHJlYWR5Y2Fubm90IGFjY2VzcyBhIFRocmVhZCBMb2NhbCBTdG9yYWdlIHZhbHVlIGR1cmluZyBvciBhZnRlciBkZXN0cnVjdGlvbgAASgAAAAAAAAABAAAAMwAAAC9ydXN0Yy8yMGZmZWE2OTM4YjU4MzljMzkwMjUyZTA3OTQwYjk5ZTNiNmE4ODlhL2xpYnJhcnkvc3RkL3NyYy90aHJlYWQvbG9jYWwucnMAbBwQAE8AAAClAQAACQAAAGFscmVhZHkgYm9ycm93ZWRKAAAAAAAAAAEAAABEAAAASwAAAAQAAAAEAAAATAAAAE0AAABLAAAABAAAAAQAAABOAAAATwAAAEZuT25jZSBjYWxsZWQgbW9yZSB0aGFuIG9uY2UvaG9tZS9uZ25pdXMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvd2FzbS1iaW5kZ2VuLWZ1dHVyZXMtMC40LjMxL3NyYy9saWIucnMAMB0QAGMAAAClAAAADwAAADAdEABjAAAAhQAAACcAAAAwHRAAYwAAAK8AAAAkAAAAL2hvbWUvbmduaXVzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3dhc20tYmluZGdlbi1mdXR1cmVzLTAuNC4zMS9zcmMvdGFzay9zaW5nbGV0aHJlYWQucnMAAADEHRAAcQAAACEAAAAVAAAAUAAAAFEAAABSAAAAUwAAAFQAAADEHRAAcQAAAFUAAAAlAAAAVQAAAHJldHVybiB0aGlzY2xvc3VyZSBpbnZva2VkIHJlY3Vyc2l2ZWx5IG9yIGRlc3Ryb3llZCBhbHJlYWR5Y2Fubm90IGFjY2VzcyBhIFRocmVhZCBMb2NhbCBTdG9yYWdlIHZhbHVlIGR1cmluZyBvciBhZnRlciBkZXN0cnVjdGlvbgAAAGEAAAAAAAAAAQAAADMAAAAvcnVzdGMvMjBmZmVhNjkzOGI1ODM5YzM5MDI1MmUwNzk0MGI5OWUzYjZhODg5YS9saWJyYXJ5L3N0ZC9zcmMvdGhyZWFkL2xvY2FsLnJzAAQfEABPAAAApQEAAAkAAABUcmllZCB0byBzaHJpbmsgdG8gYSBsYXJnZXIgY2FwYWNpdHlkHxAAJAAAAC9ydXN0Yy8yMGZmZWE2OTM4YjU4MzljMzkwMjUyZTA3OTQwYjk5ZTNiNmE4ODlhL2xpYnJhcnkvYWxsb2Mvc3JjL3Jhd192ZWMucnOQHxAATAAAAKoBAAAJAAAAZwAAAAQAAAAEAAAAaAAAAGkAAABqAAAAY2FsbGVkIGBPcHRpb246OnVud3JhcCgpYCBvbiBhIGBOb25lYCB2YWx1ZUFjY2Vzc0Vycm9yAAAEIBAAAAAAAHVuY2F0ZWdvcml6ZWQgZXJyb3JvdGhlciBlcnJvcm91dCBvZiBtZW1vcnl1bmV4cGVjdGVkIGVuZCBvZiBmaWxldW5zdXBwb3J0ZWRvcGVyYXRpb24gaW50ZXJydXB0ZWRhcmd1bWVudCBsaXN0IHRvbyBsb25naW52YWxpZCBmaWxlbmFtZXRvbyBtYW55IGxpbmtzY3Jvc3MtZGV2aWNlIGxpbmsgb3IgcmVuYW1lZGVhZGxvY2tleGVjdXRhYmxlIGZpbGUgYnVzeXJlc291cmNlIGJ1c3lmaWxlIHRvbyBsYXJnZWZpbGVzeXN0ZW0gcXVvdGEgZXhjZWVkZWRzZWVrIG9uIHVuc2Vla2FibGUgZmlsZW5vIHN0b3JhZ2Ugc3BhY2V3cml0ZSB6ZXJvdGltZWQgb3V0aW52YWxpZCBkYXRhaW52YWxpZCBpbnB1dCBwYXJhbWV0ZXJzdGFsZSBuZXR3b3JrIGZpbGUgaGFuZGxlZmlsZXN5c3RlbSBsb29wIG9yIGluZGlyZWN0aW9uIGxpbWl0IChlLmcuIHN5bWxpbmsgbG9vcClyZWFkLW9ubHkgZmlsZXN5c3RlbSBvciBzdG9yYWdlIG1lZGl1bWRpcmVjdG9yeSBub3QgZW1wdHlpcyBhIGRpcmVjdG9yeW5vdCBhIGRpcmVjdG9yeW9wZXJhdGlvbiB3b3VsZCBibG9ja2VudGl0eSBhbHJlYWR5IGV4aXN0c2Jyb2tlbiBwaXBlbmV0d29yayBkb3duYWRkcmVzcyBub3QgYXZhaWxhYmxlYWRkcmVzcyBpbiB1c2Vub3QgY29ubmVjdGVkY29ubmVjdGlvbiBhYm9ydGVkbmV0d29yayB1bnJlYWNoYWJsZWhvc3QgdW5yZWFjaGFibGVjb25uZWN0aW9uIHJlc2V0Y29ubmVjdGlvbiByZWZ1c2VkcGVybWlzc2lvbiBkZW5pZWRlbnRpdHkgbm90IGZvdW5kIChvcyBlcnJvciApAAAABCAQAAAAAAAxIxAACwAAADwjEAABAAAAbWVtb3J5IGFsbG9jYXRpb24gb2YgIGJ5dGVzIGZhaWxlZAoAWCMQABUAAABtIxAADgAAAGxpYnJhcnkvc3RkL3NyYy9hbGxvYy5yc4wjEAAYAAAAUgEAAAkAAABsaWJyYXJ5L3N0ZC9zcmMvcGFuaWNraW5nLnJztCMQABwAAABHAgAADwAAALQjEAAcAAAARgIAAA8AAABrAAAADAAAAAQAAABsAAAAZwAAAAgAAAAEAAAAbQAAAG4AAAAQAAAABAAAAG8AAABwAAAAZwAAAAgAAAAEAAAAcQAAAHIAAABvcGVy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"; +const encoded = "AGFzbQEAAAABtwEbYAJ/fwBgAn9/AX9gAX8AYAN/f38AYAR/f39/AGADf39/AX9gAX8Bf2AAAGAAAX9gBX9/f39/AGAEf39/fwF/YAV/f39/fwF/YAF/AX5gA39/fwF+YAR/fn9/AGAGf39/f39/AX9gBn9/f39/fwBgBX9/fn9/AGAFf399f38AYAV/f3x/fwBgBH99f38AYAR/fH9/AGACfn8AYAd/f39/f39/AX9gA35/fwF/YAF8AX9gAn9/AX4CqgkjA3diZxpfX3diaW5kZ2VuX29iamVjdF9kcm9wX3JlZgACA3diZxVfX3diaW5kZ2VuX3N0cmluZ19uZXcAAQN3YmcVX193YmluZGdlbl9zdHJpbmdfZ2V0AAADd2JnG19fd2JpbmRnZW5fb2JqZWN0X2Nsb25lX3JlZgAGA3diZxVfX3diaW5kZ2VuX251bWJlcl9uZXcAGQN3YmcWX193YmluZGdlbl9ib29sZWFuX2dldAAGA3diZxVfX3diaW5kZ2VuX251bWJlcl9nZXQAAAN3YmcSX193YmluZGdlbl9pc19udWxsAAYDd2JnF19fd2JpbmRnZW5faXNfdW5kZWZpbmVkAAYDd2JnKF9fd2JnX2luc3RhbmNlb2ZfV2luZG93X2FjYzk3ZmY5ZjVkMmM3YjQABgN3YmccX193YmdfZmV0Y2hfMGZlMDQ5MDVjY2NmYzJhYQABA3diZypfX3diZ19pbnN0YW5jZW9mX1Jlc3BvbnNlX2VhYTQyNjIyMDg0OGEzOWUABgN3YmcbX193YmdfdGV4dF8xMTY5ZDc1MmNjNjk3OTAzAAYDd2JnKF9fd2JnX25ld3dpdGhzdHJhbmRpbml0XzA1ZDcxODA3ODg0MjBjNDAABQN3YmcSX193YmluZGdlbl9jYl9kcm9wAAYDd2JnIF9fd2JnX25ld25vYXJnc19iNWIwNjNmYzZjMmYwMzc2AAEDd2JnG19fd2JnX2NhbGxfOTdhZTlkODY0NWRjMzg4YgABA3diZxpfX3diZ19uZXdfMGI5YmZkZDk3NTgzMjg0ZQAIA3diZxRfX3diaW5kZ2VuX2lzX3N0cmluZwAGA3diZxtfX3diZ19zZWxmXzZkNDc5NTA2ZjcyYzZhNzEACAN3YmcdX193Ymdfd2luZG93X2YyNTU3Y2M3ODQ5MGFjZWIA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hsAAEMMBAAsgBiAIQfCGwAAQwwEACyAHQQhqIARqIAUgBmotAAA6AAAgACAIaiEACwJAIA9FIAsgAklyDQAgAEGBCEkEQCACIAdBCGogAGpBgAggAGsQ4QEgAGohAAwBCyAAQYAIQeiEwAAQhgMACyAAQYEITw0CIAshBCADIAdBCGogABDfAUUNAQsLIAdBoAhqJAAgEA8LIABBgAhB+ITAABCHAwALQbiDwABBKyAHQZgIakHkg8AAQaCHwAAQtAEAC80NAgd/AX4jAEHgAmsiAiQAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAALQCYBEEBaw4DCQIBAAsgACAAQYgCakGIAhCiAxoLAkACQAJAAkAgAC0AgAJBAWsOAw0EAAELIABB7AFqIQYgAC0A7AFBAWsOAwcDAgELIAAoAvABIQMgAiAAQfQBaigCACIENgJAIAIgAzYCPCACIAQ2AjggAkEgaiACQThqELICIAJBKGogAigCICACKAIkEOUCIAAoAvgBIQMgAiAAQfwBaigCACIENgJAIAIgAzYCPCACIAQ2AjggAkEYaiACQThqELECIAIoAhghBSACKAIcIQQgAkGUAmogAkEwaigCADYCACACIAIpAyg3AowCIAAgAkE4akHgARCiAyIDIAQ2AugBIAMgBTYC5AEgAyAENgLgASADQQA6AOwBIANB7AFqIQYLIAAgACkC1AE3ArABIAAgACkC4AE3ArwBIABBuAFqIABB3AFqKAIANgIAIABBxAFqIgMgAEHoAWooAgAiBDYCAEGw68AAQbDrwAApAwAiCUIBfDcDACACQRBqIAQQngEgAigCECEEIABB0AFqQQA2AgAgAEHMAWogAigCFDYCACAAIAQ2AsgBIAMoAgAhBSAAKAK8ASEEIAIgAEHAAWooAgAiAzYCpAIgAiADIAVBAnRqNgKgAiACIAM2ApwCIAIgBDYCmAIgAEHIAWohBCAFBEADQCACIANBBGo2ApwCIAJBOGogAygCABBCIAAoAtABIgMgACgCyAFGBEAgBCADEPwBIAAoAtABIQMLIAAoAswBIANBBHRqIgMgAikDODcDACADQQhqIAJBQGspAwA3AwAgACAAKALQAUEBajYC0AEgAigCnAIiAyACKAKgAkcNAAsLIAJBmAJqEMcBQbjrwAAvAQAhAyACQThqIABBsAFqENEBIABBmAFqIAJBQGsoAgA2AgAgACACKQM4NwOQASAAIAQpAgA3ApwBIABBpAFqIARBCGooAgA2AgAgAEEAOgCqASAAIAM7AagBIAAgCTcDiAEgAEEANgKAASAAIAk3AwAgAEGqAWohBAwDCyAAQaoBaiEEIAAtAKoBQQFrDgMIAAMBCwALIAAvAagBIQMgACkDACEJCyACQUBrIABBgAFqQSgQogMaIABBEGogAkE4akEwEKIDGiAAQegAakEAOgAAIABB5ABqIAM7AQAgACAJNwMICyACQThqIABBCGoiBSABECcgAigCOCIBQQtGDQIgAkHIAmogAkHIAGopAwA3AwAgAkHQAmoiAyACQdAAaikDADcDACACQdgCaiIGIAJB2ABqKQMANwMAIAIgAikDQDcDwAIgAigCPCEEIAUQrgEgAUEKRwRAIAJBuAJqIAYpAwA3AwAgAkGwAmogAykDADcDACACQagCaiACQcgCaikDADcDACACIAIpA8ACNwOgAiACIAQ2ApwCIAIgATYCmAIgAUEBRg0CQeyawABBMhABIQQgAkGYAmoQugELIABBAToAqgEgABDHAgwHC0GAkMAAQSNBhJzAABD3AQALIAJBsAJqKAIAIQEgAkGsAmooAgAhAyACQagCaigCACEEIABBAToAqgEgABDHAiADRQ0FIAIgARAXNgLAAiACIAM2AqQCIAIgAyABQQR0IgVqNgKgAiACIAM2ApwCIAIgBDYCmAIgAUUNBEEAIQQgAkE4akEBciIGQQdqIQcDQCACIANBEGoiATYCnAIgAy0AACIIQQpGDQUgBiADKQABNwAAIAcgA0EIaikAADcAACACIAg6ADggAkE4ahBeIQMgAkHAAmooAgAgBCADEBggBEEBaiEEIAEhAyAFQXBqIgUNAAsMBAsgBkEDOgAAIARBAzoAAEEBDAYLQYCQwABBI0Hkj8AAEPcBAAtBgJDAAEEjQdyawAAQ9wEAC0GAkMAAQSNBlJzAABD3AQALIAJBmAJqELIBIAIoAsACIQUMAQtBISEFIARBJEkNACAEEAALIABBsAFqEJoCIABBAToA7AFBAAsiAQRAQQMhBCAAQQM6AIACDAELIAAQlgIgAEEBOgCAAiACIAU2AiggAkEgNgI4IAJBCGogAEGQBGoiAyACQThqIAJBKGoQhAIgAigCCEUEQCACKAIMIgRBI0sEQCAEEAALIAIoAjgiBEEkTwRAIAQQAAsgAigCKCIEQSRPBEAgBBAACyADKAIAIgNBJE8EQCADEAALQQEhBCAAKAKUBCIDQSRJDQEgAxAADAELQbiAwABBFRCWAwALIAAgBDoAmAQgAkHgAmokACABQQBHC5IMAQV/IwBBkAJrIgMkACABAn8CQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCABLQBgQQFrDgQHAAIDAQsACyABIAEpAwA3AwggA0HIAGogAUEQakEoEKIDGiABIAEvAVw7AV4gARARNgI4IAFBOGoiBBDAASAEEMIBIANBpAFqQRk2AgAgA0GcAWpBGjYCACADIAFB3gBqNgKgASADQbSawAA2ApgBIANBGzYClAEgAyABQQhqNgKQASADQQM2AswBIANBBDYCxAEgA0GImsAANgLAASADQQA2ArgBIAMgA0GQAWo2AsgBIANBgAFqIANBuAFqEEEgAUHEAGogA0GIAWooAgA2AgAgASADKQOAATcCPCADQbgBaiADQcgAakEoEKIDGiADQZABaiADQbgBahCBASADKAKQASEFIAMoApQBIgZFDQsgAUHQAGogAygCmAEiBzYCACABQcwAaiAGNgIAIAEgBTYCSCADQZABaiAGIAcQNyADQcABaiIFIANBmAFqKQMANwMAIAMgAykDkAE3A7gBIANB8ABqIANBuAFqEL0BIAUgA0H4AGooAgA2AgAgAyADKQNwNwO4ASADIANBuAFqEIwCNgKQASAEIANBkAFqELABIAMoApABIgVBJE8EQCAFEAALIANBQGsgAUFAaygCACABQcQAaigCACAEEPMBIAMoAkQhBSADKAJADQogASAFNgJUIANBOGoQ7QEgAygCOEUNBiABIAMoAjw2AlggASABQdgAaigCACABQdQAaigCABAKEEg2AmgLIANBMGogAUHoAGoiBCACEI0BIAMoAjAiBkECRg0GIAMoAjQhBSAEEK8BIAYNCCADQShqIAUQ8gEgAygCLCEFIAMoAigNCCABIAU2AmggA0EgaiAEEPYBIAMoAiAhBCABQfQAaiADKAIkIgU2AgAgASAENgJwIAQNASABIAUQSDYCbAsgA0EYaiABQewAaiIEIAIQjQEgAygCGCICQQJGDQEgAygCHCEFIAQQrwEgAg0AIANBEGogBRDxASADKAIUIQQgAygCEEUNAiAEIQULIAEoAmgiAkEkSQ0GIAIQAAwGCyAAQQs2AgBBBAwKCyADQQhqIAQQAiADKAIIIgVFBEAgA0EANgK8AQwECyADKAIMIQIgAyAFNgK8ASADIAI2AsABIAMgAjYCuAEgAyADQbgBahCyAiADQbgBaiADKAIAIAMoAgQQ5QIgAygCvAFFDQMgA0GIAWogA0HAAWoiBigCACICNgIAIAMgAykDuAE3A4ABIANBuAFqIAMoAoQBIAIQmgEgAygCuAEiAkEKRwRAIAMoArwBIQUgA0GQAWogBkEoEKIDGiADQfABaiADQZgBaikDADcDACADQfgBaiADQaABaikDADcDACADQYACaiADQagBaikDADcDACADIAMpA5ABNwPoASADQYABahCaAiAEQSRPBEAgBBAACyABKAJoIgRBJE8EQCAEEAALIAEoAlgiBEEkTwRAIAQQAAsgASgCVCIEQSRPBEAgBBAACyABQcgAahCaAiABQTxqEJoCIAEoAjgiAUEkSQ0JIAEQAAwJCyADIAMpArwBNwOIAiADQYgCahCpASEFIANBgAFqEJoCIARBJE8EQCAEEAALIAEoAmgiAkEkTwRAIAIQAAsgASgCWCICQSRPBEAgAhAACyABKAJUIgJBJE8EQCACEAALIAFByABqEJoCIAFBPGoQmgIgASgCOCIBQSRJDQcgARAADAcLQYCQwABBI0HYmcAAEPcBAAtBoJPAAEErQbyawAAQ9wEACyAAQQs2AgBBAwwGC0Ggk8AAQStBzJrAABD3AQALIAEoAlgiAkEkTwRAIAIQAAsgASgCVCICQSRJDQAgAhAACyABQcgAahCaAgsgAUE8ahCaAiABKAI4IgFBJEkNACABEAALQQohAgsgACAFNgIEIAAgAjYCACAAIAMpA+gBNwMIIABBEGogA0HwAWopAwA3AwAgAEEYaiADQfgBaikDADcDACAAQSBqIANBgAJqKQMANwMAQQELOgBgIANBkAJqJAAL4gsCCn8CfiMAQeACayICJAACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAC0A2AJBAWsOAwcCAQALIAAgAEGoAWpBqAEQogMaCwJAIAAtAKABQQFrDgMFAQACCyAAQQhqIQUgAEGYAWotAABBAWsOAwcAAwILAAsgACgCACEDIAIgACgCBCIENgJQIAIgAzYCTCACIAQ2AkggAkEwaiACQcgAahCyAiACQThqIAIoAjAgAigCNBDlAiACQdQBaiACQUBrKAIANgIAIAIgAikDODcCzAEgAEEIaiIFIAJByABqQZABEKIDGiAAQZgBakEAOgAACyAAQYgBaiAAQZQBaigCADYCACAAQYABaiIDIABBjAFqKQIANwIAQbDrwABBsOvAACkDACIMQgF8NwMAIAJBhAJqIAMQ0QEgAEEYakEJNgIAIAAgDDcDCCAAQegAakEAOgAAIABBHGogAikChAI3AgAgAEEkaiACQYwCaigCADYCACAAQeQAakG468AALwEAOwEACyACQcgAaiAFIAEQJyACKAJIQQtGDQIgAkHYAWogAkHIAGpBKBCiAxogBRCuAQJAAkAgAigC2AEiB0F4ag4DAQYABgsgAigC3AEhAUH868AAKAIABEAQ4wILQfzrwABBADYCACABQSRJDQggARAADAgLIAJB4AFqKAIAIQEgAkHkAWooAgAhAyACKALcASEGIAJBIGoQ5wEgAikDICEMIAIpAyghDSACQcgAaiADEGogAkGoAmoiCCACQdAAaiIJKQMANwMAIAIgAikDSDcDoAIgAiANNwOYAiACIAw3A5ACIAIgATYCVCACIAEgA0EYbCIEajYCUCACIAE2AkwgAiAGNgJIIANFDQUgAkHYAmohBgNAIAIgAUEYaiIDNgJMIAEoAgQiCkUNBiABKAIAIQsgAiABKAIINgLIAiACIAo2AsQCIAIgCzYCwAIgBiABQRRqKAIANgIAIAIgASkCDDcD0AIgAkGwAmogAkGQAmogAkHAAmogAkHQAmoQSyACKAK0AgRAIAJBsAJqEKYCIAJBsAJqEMoCCyADIQEgBEFoaiIEDQALDAULQYCQwABBI0G0nMAAEPcBAAtBgJDAAEEjQeSPwAAQ9wEAC0EDIQQgAEEDOgCgASAAQQM6AJgBQQEhAQwFC0GAkMAAQSNBpJzAABD3AQALQfzrwAAoAgAEQBDjAgtB/OvAAEEANgIAQQEhAQwBCyACQcgAahC8ASACQeAAaiAIKQMANwMAIAJB2ABqIgMgAkGgAmopAwA3AwAgCSACQZgCaikDADcDACACIAIpA5ACNwNIQQAhAUH8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f0wNAiACQQEQ9wIiAUUNAQsgA0EANgJAIAMgATYCPCADIAI2AjggBCACSwR/IANBOGpBACAEEIUBIAMoAkAhBSADKAI8BSABCyAFaiAGIAQQogMaIAMgBCAFaiICNgJAIAMoAjggAmtBAk0EQCADQThqIAJBAxCFASADKAJAIQILIAMoAjwiASACaiIEQazNwAAvAAAiBTsAACAEQQJqQa7NwAAtAAAiBjoAACADIAJBA2oiAjYCQCADIAMpAzA3A0ggA0HQAGogA0HIAGoQPiADKAJQBEADQCADQegAaiADQdgAaikDADcDACADIAMpA1A3A2AgA0EQaiADQeAAahCdAyADKAIQIQcgAygCOCACayADKAIUIgRJBEAgA0E4aiACIAQQhQEgAygCQCECIAMoAjwhAQsgASACaiAHIAQQogMaIAMgAiAEaiICNgJAIANBCGogA0HgAGoQngMgAygCDARAIAMoAjggAmtBAk0EQCADQThqIAJBAxCFASADKAJAIQILIAMoAjwiASACaiIEIAU7AAAgBEECaiAGOgAAIAMgAkEDaiICNgJACyADQdAAaiADQcgAahA+IAMoAlANAAsLIAAgAykDODcCBCAAQQE2AgAgAEEMaiADQUBrKAIANgIADAMLIAJBARCbAwALEJgCAAsgAEH4y8AANgIEIABBADYCACAAQQhqQQA2AgALIANB8ABqJAAL0AQCBH8GfiAAIAAoAjggAmo2AjgCQCAAAn8CQAJAAkAgACgCPCIFRQRADAELAn4gAkEIIAVrIgQgAiAESRsiBkEDTQRAQgAMAQtBBCEDIAE1AAALIQcgACAAKQMwIANBAXIgBkkEQCABIANqMwAAIANBA3SthiAHhCEHIANBAnIhAwsgAyAGSQR+IAEgA2oxAAAgA0EDdK2GIAeEBSAHCyAFQQN0QThxrYaEIgc3AzAgBCACSw0BIAAgACkDGCAHhSIIIAApAwh8IgkgACkDECIKQg2JIAogACkDAHwiCoUiC3wiDCALQhGJhTcDECAAIAxCIIk3AwggACAJIAhCEImFIghCFYkgCCAKQiCJfCIIhTcDGCAAIAcgCIU3AwALIAIgBGsiAkEHcSEDIAQgAkF4cSICSQRAIAApAwghCCAAKQMQIQcgACkDACEJIAApAxghCgNAIAggASAEaikAACILIAqFIgh8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CGpBADYCACAAQQE2AgAL3AEBAX8jAEEgayICJAACfwJAAkACQCAALQAAQQFrDgIBAgALIAJBFGpBATYCACACQRxqQQA2AgAgAkGMncAANgIQIAJB5JzAADYCGCACQQA2AgggASACQQhqENgBDAILIAJBFGpBATYCACACQRxqQQA2AgAgAkGAncAANgIQIAJB5JzAADYCGCACQQA2AgggASACQQhqENgBDAELIAJBFGpBATYCACACQRxqQQA2AgAgAkHwnMAANgIQIAJB5JzAADYCGCACQQA2AgggASACQQhqENgBCyACQSBqJAALugECAn8BfiMAQRBrIgQkACAEQQhqIAEgAiADEIYBAkACQAJ/IAQtAAhBBkYEQCAEKAIMDAELIAQpAwgiBkL/AYNCBlINASAGQiCIpwshBSAEQQhqIAFBDGogAiADEHcCQAJ/IAQtAAhBBkYEQCAEKAIMDAELIAQpAwgiBkL/AYNCBlINASAGQiCIpwshAyAAQQY6AAAgACADIAVqNgIEDAILIAAgBjcCAAwBCyAAIAY3AgALIARBEGokAAusAQEEfwJAIAAoAgwiAkUNACAAKAIEIQMgACgCACIBIAAoAggiAEEAIAEgACABSRtrIgAgAmogAiABIABrIgFLGyAARwRAIAMgAEECdGohACACIAEgAiABSRtBAnQhBANAIAAQtgEgAEEEaiEAIARBfGoiBA0ACwsgAiABTQ0AIAJBAnQgAiABIAIgAUkbQQJ0ayEAA0AgAxC2ASADQQRqIQMgAEF8aiIADQALCwurAQEDfwJAIAJBD00EQCAAIQMMAQsgAEEAIABrQQNxIgRqIQUgBARAIAAhAwNAIAMgAToAACADQQFqIgMgBUkNAAsLIAUgAiAEayICQXxxIgRqIQMgBEEBTgRAIAFB/wFxQYGChAhsIQQDQCAFIAQ2AgAgBUEEaiIFIANJDQALCyACQQNxIQILIAIEQCACIANqIQIDQCADIAE6AAAgA0EBaiIDIAJJDQALCyAAC64BAQF/IAACfwJAAn8CQCACBEACQAJAAkAgAUEATgRAIAMoAghFDQIgAygCBCIEDQEgAQ0DDAULIABBCGpBADYCAAwGCyADKAIAIAQgAiABEOgCDAQLIAFFDQILIAEgAhD3AgwCCyAAIAE2AgQgAEEIakEANgIADAILIAILIgME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CyAAQQRqIgEQpwEgARDKAgwBCyAAQQRqIgEQmwIgARDKAgsgAEEoaiEAIAJBWGoiAg0ACwsLmwEBA38jAEEQayIFJAAgAygCCCECIAVBCGogARDwASADKAIEIgQgAkkEQCACIARB4IzAABCGAwALIAMoAgAgAmogBSgCCCAEIAJrIgQgBSgCDCIGIAQgBkkbIgQQogMaIAMgAiAEaiICNgIIIABBBDoAACABIAEpAwAgBK18NwMAIAMgAygCDCIAIAIgACACSxs2AgwgBUEQaiQAC5gBAQN/IwBBQGoiASQAIAFBADYCCCABQoCAgIAQNwMAIAFBEGogARC+AiAAIAFBEGoQeEUEQCABQRhqIAFBCGooAgA2AgAgASABKQMANwMQIAFBEGoQ2wIgAC0AACIDQQRPQQAgA0EGcUEERhtFBEAgABCHAgsgAUFAayQADwtB/JHAAEE3IAFBOGpBtJLAAEGQk8AAELQBAAuYAQEDfyMAQUBqIgEkACABQQA2AgggAUKAgICAEDcDACABQRBqIAEQvgIgACABQRBqEHlFBEAgAUEYaiABQQhqKAIANgIAIAEgASkDADcDECABQRBqENsCIAAtAAAiA0EET0EAIANBBnFBBEYbRQRAIAAQhwILIAFBQGskAA8LQfyRwABBNyABQThqQbSSwABBkJPAABC0AQALigEBBX8gACAAKAIAIgEQgwIgACgCCCIFIAEgACgCDCICa0sEQCABIAVrIgMgAiADayICS0EAIAAoAgAiBCABayACTxtFBEAgACgCBCIBIAQgA2siBEECdGogASAFQQJ0aiADQQJ0EKEDIAAgBDYCCA8LIAAoAgQiACABQQJ0aiAAIAJBAnQQogMaCwu1AQEDfyMAQRBrIgEkACAAKAIAIgJBFGooAgAhAwJAAn8CQAJAIAJBDGooAgAOAgABAwsgAw0CQQAhAkGEvsAADAELIAMNASACKAIIIgMoAgQhAiADKAIACyEDIAEgAjYCBCABIAM2AgAgAUGkxsAAIAAoAgQiASgCCCAAKAIIIAEtABAQiwEACyABQQA2AgQgASACNgIMIAFBkMbAACAAKAIEIgEoAgggACgCCCABLQAQEIsBAAuXAQECfyMAQRBrIgEkACAAKAIARQRAIABBfzYCACAAAn9BACAAKAIEIgJFDQAaIABBADoAFCABIABB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AEHY7MAAKQMAUARAQejswAACfgJAIABFDQAgACkDACAAQgA3AwBCAVINACAAKQMIIQIgACkDEAwBCyABQgI3AwggAUIBNwMAIAEpAwAhAiABKQMICzcDAEHg7MAAIAI3AwBB2OzAAEIBNwMACyABQRBqJABB4OzAAAt+AQF/IwBBEGsiASQAIAFB7rHAAEEEEAE2AgggAUGEssAAQQQQATYCDCABIAAgAUEIaiABQQxqEOIBIAEoAgwiAEEkTwRAIAAQAAsgASgCCCIAQSRPBEAgABAACwJAIAEtAABFDQAgASgCBCIAQSRJDQAgABAACyABQRBqJAALeQEBfyMAQTBrIgMkACADIAE2AgQgAyAANgIAIANBFGpBAjYCACADQRxqQQI2AgAgA0EsakHrADYCACADQbTOwAA2AhAgA0EANgIIIANB6wA2AiQgAyADQSBqNgIYIAMgAzYCKCADIANBBGo2AiAgA0EIaiACEKgCAAt5AQF/IwBBMGsiAyQAIAMgATYCBCADIAA2AgAgA0EUakECNgIAIANBHGpBAjYCACADQSxqQesANgIAIANB5NLAADYCECADQQA2AgggA0HrADYCJCADIANBIGo2AhggAyADQQRqNgIoIAMgAzYCICADQQhqIAIQqAIAC3kBAX8jAEEwayIDJAAgAyABNgIEIAMgADYCACADQRRqQQI2AgAgA0EcakECNgIAIANBLGpB6wA2AgAgA0GE08AANgIQIANBADYCCCADQesANgIkIAMgA0EgajYCGCADIANBBGo2AiggAyADNgIgIANBCGogAhCoAgALeQEBfyMAQTBrIgMkACADIAE2AgQgAyAANgIAIANBFGpBAjYCACADQRxqQQI2AgAgA0EsakHrADYCACADQbjTwAA2AhAgA0EANgIIIANB6wA2AiQgAyADQSBqNgIYIAMgA0EEajYCKCADIAM2AiAgA0EIaiACEKgCAAtzAQR/IwBBEGsiASQAIAAoAggiAiAAKAIEIgNHBEAgAiADa0ECdkECdCECA0AgAygCACIEQSRPBEAgBBAACyADQQRqIQMgAkF8aiICDQALCyABIAAoAgw2AgwgASAAKAIANgIIIAFBCGoQmQIgAUEQaiQAC2wBAX8jAEEwayIDJAAgAyABNgIoIAMgADYCJCADIAE2AiAgA0EIaiADQSBqELICIANBEGogAygCCCADKAIMEOUCIANBKGogA0EYaigCADYCACADIAMpAxA3AyAgA0EgaiACEM8BIANBMGokAAtpAQF/IwBBMGsiAiQAIAIgATYCKCACIAA2AiQgAiABNgIgIAJBCGogAkEgahCyAiACQRBqIAIoAgggAigCDBDlAiACQShqIAJBGGooAgA2AgAgAiACKQMQNwMgIAJBIGoQZyACQTBqJAALcgEDfyMAQSBrIgIkAAJ/QQEgACABEFwNABogASgCBCEDIAEoAgAhBCACQQA2AhwgAkGwzcAANgIYIAJBATYCFCACQeDNwAA2AhAgAkEANgIIQQEgBCADIAJBCGoQOg0AGiAAQQRqIAEQXAsgAkEgaiQAC3cBAX8CQAJAIABFDQAgACgCACEBIABBADYCACAAKAIEIQACQCABDgIBAgALIABBJEkNACAAEAALEFYhAAtBnOzAACgCACEBQZzswAAgADYCAEGY7MAAKAIAQZjswABBATYCAEUgAUEkSXJFBEAgARAAC0Gc7MAAC3wBA38gACAAEKoDIgBBCBDmAiAAayICEKgDIQBBlPDAACABIAJrIgE2AgBBnPDAACAANgIAIAAgAUEBcjYCBEEIQQgQ5gIhAkEUQQgQ5gIhA0EQQQgQ5gIhBCAAIAEQqAMgBCADIAJBCGtqajYCBEGo8MAAQYCAgAE2AgALcwAjAEEwayIBJABBgOzAAC0AAARAIAFBFGpBAjYCACABQRxqQQE2AgAgAUH8xMAANgIQIAFBADYCCCABQesANgIkIAEgADYCLCABIAFBIGo2AhggASABQSxqNgIgIAFBCGpBpMXAABCoAgALIAFBMGokAAt2AQF/IAAtAAQhASAALQAFBEAgAUH/AXEhASAAAn9BASABDQAaIAAoAgAiAS0AGEEEcUUEQCABKAIAQZfPwABBAiABKAIEKAIMEQUADAELIAEoAgBBls/AAEEBIAEoAgQoAgwRBQALIgE6AAQLIAFB/wFxQQBHC2kBAX8jAEEgayICJABB3OvAACgCAEUEQEGgk8AAQStB5JvAABD3AQALIAJBGGogAEEIaigCADYCACACIAApAgA3AxAgAkEIaiACQRBqIAEQVyACKAIIIQAgAigCDCACQSBqJABBISAAGwtrAQJ/IwBBIGsiAiQAIAJBCGogASgCABACAkAgAigCCCIDBEAgAigCDCEBIAIgAzYCFCACIAE2AhggAiABNgIQIAIgAkEQahCyAiAAIAIoAgAgAigCBBDlAgwBCyAAQQA2AgQLIAJBIGokAAtrAQJ/IAFBBGooAgAhAwJAAkACQCABQQhqKAIAIgFFBEBBASECDAELIAFBf0wNASABQQEQ9wIiAkUNAgsgAiADIAEQogMhAiAAIAE2AgggACACNgIEIAAgATYCAA8LEJgCAAsgAUEBEJsDAAtZAQF/IwBBIGsiAiQAIAIgACgCADYCBCACQRhqIAFBEGopAgA3AwAgAkEQaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQaCDwAAgAkEIahA6IAJBIGokAAtZAQF/IwBBIGsiAiQAIAIgACgCADYCBCACQRhqIAFBEGopAgA3AwAgAkEQaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQYiDwAAgAkEIahA6IAJBIGokAAthAQN/IAAgARDLAiAAKAIEIgQgACgCCCICaiEDAkACQCABQQJPBEAgA0EAIAFBf2oiARCjAxogBCABIAJqIgJqIQMMAQsgAUUNAQsgA0EAOgAAIAJBAWohAgsgACACNgIIC1kBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpB7L3AACACQQhqEDogAkEgaiQAC1kBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpB4MvAACACQQhqEDogAkEgaiQAC1kBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBqNHAACACQQhqEDogAkEgaiQAC1MBAn8jAEEgayICJAAgACgCBCEDIAAoAgAgAkEYaiABQRBqKQIANwMAIAJBEGogAUEIaikCADcDACACIAEpAgA3AwggAyACQQhqEDogAkEgaiQAC1YBAX8jAEEgayICJAAgAiAANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBiIPAACACQQhqEDogAkEgaiQAC1YBAX8jAEEgayICJAAgAiAANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBoIPAACACQQhqEDogAkEgaiQAC44BAgF/An4jAEEQayIBJABBuOvAACAAOwEAQbDrwABCADcDACABEOcBIAEpAwghAiABKQMAIQNB3OvAACgCAARAQdDrwAAoAgAEQBCbARCrAgsLQdzrwABBsJnAADYCAEHQ68AAQgA3AwBBwOvAACADNwMAQcjrwAAgAjcDAEHY68AAQQA2AgAgAUEQaiQAC1YBAX8jAEEgayICJAAgAiAANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBqNHAACACQQhqEDogAkEgaiQAC2EBAX8jAEFAaiIBJAAgAUEBOgAPIABBADYCCCAAQoCAgIAQNwIAIAFBEGogABC+AiABQQ9qIAFBEGoQkwEEQEH8kcAAQTcgAUE4akG0ksAAQZCTwAAQtAEACyABQUBrJAALSAECfwJAIAFFBEBBASECDAELIAFBAE4EQCABIAFBf3NBH3YiAxD3AiICDQEgASADEJsDAAsQmAIACyAAIAI2AgQgACABNgIAC2IBAX8jAEEgayIDJAAgACgCACADQQhqIAEgAhAwIAMoAggEQCADIAMpAgw3AxhB9K7AAEErIANBGGpBoK/AAEGUsMAAELQBAAsgAygCDCADQRBqKAIAEJcCIANBIGokAEEAC2kBA38jAEEQayIBJAACQEEAQeC4wAAoAgARBgAiAgRAIAAoAgAoAgAiACAAKAIAQQFqIgM2AgAgA0UNASACIAAQtwEgAUEQaiQADwtBtLnAAEHGACABQQhqQfy5wABB3LrAABC0AQALAAtWAQJ/AkAgAEEDcEEDc0EDcCIERQRADAELQQAhAANAIAAgAkcEQCAAIAFqQT06AAAgAEEBaiEDQQEhACADIARJDQEMAgsLIAIgAkG0n8AAEMMBAAsgAwtaAQF/IwBBEGsiBCQAIAEoAgAgAigCACADKAIAECAhASAEQQhqEL0CIAACfyAEKAIIRQRAIAAgAUEARzoAAUEADAELIAAgBCgCDDYCBEEBCzoAACAEQRBqJAALVAEBfyMAQTBrIgEkACABQRBqQcibwABBBRCFAiABQShqIAFBGGooAgA2AgAgASABKQMQNwMgIAFBCGogAUEgahCyAiAAIAEpAwg3AwAgAUEwaiQAC1kBAn8gACgCDCICIAAoAgAiA0YEQCAAEKsBIAAoAgwhAiAAKAIAIQMLIAAoAgQgACgCCCACaiICQQAgAyACIANJG2tBAnRqIAE2AgAgACAAKAIMQQFqNgIMC0cBAn8CQCAAKAIAIgJFDQAgAiAAQRRqKAIAIgEgADUCECACQQFqrX6nakF/akEAIAFrcSIBakEJakUNACAAKAIMIAFrECsLC2ABAn8jAEEQayIBJAAgAUGwh8AAQRUQhQJBDEEEEPcCIgJFBEBBDEEEEJsDAAsgAiABKQMANwIAIAJBCGogAUEIaigCADYCACAAQeCAwAA2AgQgACACNgIAIAFBEGokAAtkAgJ/AX4jAEEQayICJABBAEGkkMAAKAIAEQYAIgEEQCABIAEpAwAiA0IBfDcDACAAIAEpAwg3AwggACADNwMAIAJBEGokAA8LQaiQwABBxgAgAkEIakHwkMAAQdCRwAAQtAEAC0oBAX8jAEEgayIAJAAgAEEUakEBNgIAIABBHGpBADYCACAAQfjKwAA2AhAgAEHcysAANgIYIABBADYCCCAAQQhqQdDLwAAQqAIAC00BAX8jAEEwayIBJAAgAUEQahDdASABQShqIAFBGGooAgA2AgAgASABKQMQNwMgIAFBCGogAUEgahCyAiAAIAEpAwg3AwAgAUEwaiQAC2cBAX8jAEEgayIDJAAgAyACNgIMIANBEGoiAkGAAjsBBCACQQZqQQA6AAAgAkH9BTYCACADQRBqIAAgASADQQxqECUEQEGkmMAAQSIgA0EYakHMk8AAQaCZwAAQtAEACyADQSBqJAALTAECfyAAKAIIIgEEQCAAKAIEIQAgAUEEdCEBA0AgAC0AACICQX5qQQdJIAJFckUEQCAAQQRqEJoCCyAAQRBqIQAgAUFwaiIBDQALCwtWAQJ/IAEoAgAhAiABQQA2AgACQCACBEAgASgCBCEDQQhBBBD3AiIBRQ0BIAEgAzYCBCABIAI2AgAgAEHUscAANgIEIAAgATYCAA8LAAtBCEEEEJsDAAtRAQN/IwBBEGsiASQAIAFBCGoQjwIQtAIgASgCDCECAkAgASgCCEUEQEEBIQMMAQsgAkEkSQ0AIAIQAAsgACACNgIEIAAgAzYCACABQRBqJAALSwECfyMAQRBrIgEkACABIABBeGo2AgggAC0AFCAAQQE6ABQgASABQQhqNgIMQQFxRQRAIAFBDGoQ4AELIAFBCGoQtgEgAUEQaiQAC18BA38jAEEQayIBJAACQCAAKAIMIgIEQCAAKAIIIgNFDQEgASACNgIIIAEgADYCBCABIAM2AgAgARC2AgALQYS+wABBK0HgxcAAEPcBAAtBhL7AAEErQdDFwAAQ9wEAC1ACAn8CfiABQRBqKAIAIgIgASkDACIEIAKtIgUgBCAFVBunIgNJBEAgAyACQfSJwAAQhgMACyAAIAIgA2s2AgQgACABQQxqKAIAIANqNgIAC0gBA38jAEEQayICJAAgAiABNgIMQQEhASACQQxqKAIAEBJBAUYgAigCDCEDBEBBACEBCyAAIAM2AgQgACABNgIAIAJBEGokAAtIAQN/IwBBEGsiAiQAIAIgATYCDEEBIQEgAkEMaigCABALQQBHIAIoAgwhAwRAQQAhAQsgACADNgIEIAAgATYCACACQRBqJAALSgEBfyMAQRBrIgQkACABIAIgAygCABANIQEgBEEIahC9AiAAAn8gASAEKAIIIgFFDQAaIAQoAgwLNgIEIAAgATYCACAEQRBqJAALWAECfyMAQRBrIgEkACABIAA2AgRBAEHguMAAKAIAEQYAIgIEQCACIAAQtwEgAUEQaiQADwsgAUEEahC2AUG0ucAAQcYAIAFBCGpB/LnAAEHcusAAELQBAAtYAQF/IwBBIGsiACQAIABBDGpBATYCACAAQRRqQQE2AgAgAEGIlMAANgIIIABBADYCACAAQRo2AhwgAEGsl8AANgIYIAAgAEEYajYCECAAQbSXwAAQqAIAC0YBAX8jAEEQayICJAAgASgCABAMIQEgAkEIahC9AiAAAn8gASACKAIIIgFFDQAaIAIoAgwLNgIEIAAgATYCACACQRBqJAALUgEBfyMAQSBrIgMkACADQQxqQQE2AgAgA0EUakEANgIAIANBsM3AADYCECADQQA2AgAgAyABNgIcIAMgADYCGCADIANBGGo2AgggAyACEKgCAAtTAQF/IwBBIGsiAiQAIAJBDGpBATYCACACQRRqQQE2AgAgAkHUzsAANgIIIAJBADYCACACQf8ANgIcIAIgADYCGCACIAJBGGo2AhAgAiABEKgCAAtDAQN/AkAgAkUNAANAIAAtAAAiBCABLQAAIgVGBEAgAEEBaiEAIAFBAWohASACQX9qIgINAQwCCwsgBCAFayEDCyADC0gBAX8jAEEQayICJAAgAkEIaiAAIAEQfwJAIAIoAgwiAEGBgICAeEcEQCAARQ0BIAIoAgggABCbAwALIAJBEGokAA8LEJgCAAtIAQF/IwBBEGsiAiQAIAJBCGogACABEHwCQCACKAIMIgBBgYCAgHhHBEAgAEUNASACKAIIIAAQmwMACyACQRBqJAAPCxCYAgALSAEBfyMAQRBrIgIkACACQQhqIAAgARB+AkAgAigCDCIAQYGAgIB4RwRAIABFDQEgAigCCCAAEJsDAAsgAkEQaiQADwsQmAIAC0sBAX8jAEEQayICJAAgAkEIaiAAIAFBARCMAQJAIAIoAgwiAEGBgICAeEcEQCAARQ0BIAIoAgggABCbAwALIAJBEGokAA8LEJgCAAtIAQF/IwBBEGsiAiQAIAJBCGogACABEH0CQCACKAIMIgBBgYCAgHhHBEAgAEUNASACKAIIIAAQmwMACyACQRBqJAAPCxCYAgALSwEBfyMAQRBrIgMkACADQQhqIAAgASACEIwBAkAgAygCDCIAQYGAgIB4RwRAIABFDQEgAygCCCAAEJsDAAsgA0EQaiQADwsQmAIAC00BAn8jAEEQayICJAAgACgCACEDIABBADYCACADRQRAQay2wABBHBCWAwALIAIgAzYCDCADQQhqQQEgARB6IAJBDGoQrwEgAkEQaiQAC00BAn8jAEEQayICJAAgACgCACEDIABBADYCACADRQRAQay2wABBHBCWAwALIAIgAzYCDCADQQhqQQAgARB6IAJBDGoQrwEgAkEQaiQAC0MBAn8jAEEQayIBJAAgASAAQXhqNgIIIAAtABQgAEEBOgAUIAEgAUEIajYCDEEBcUUEQCABQQxqEOABCyABQRBqJAALSQEBfyMAQRBrIgIkACACQQhqIAAgARCAAQJAIAIoAgwiAEGBgICAeEcEQCAARQ0BIAIoAgggABCbAwALIAJBEGokAA8LEJgCAAtOAQF/IwBBEGsiBCQAIAEoAgAgAigCACADKAIAEBkhASAEQQhqEL0CIAQoAgwhAiAAIAQoAggiAzYCACAAIAIgASADGzYCBCAEQRBqJAALSQEDfyMAQRBrIgMkACADQQhqIAIQ3gEgAygCCCEEIAAgAygCDCIFNgIEIAAgBDYCACAFIAEgAhCiAxogACACNgIIIANBEGokAAtQAQF/QSBBBBD3AiIBRQRAQSBBBBCbAwALIAFCgYCAgBA3AgAgASAAKQIANwIIIAFBEGogAEEIaikCADcCACABQRhqIABBEGopAgA3AgAgAQtFAQJ/IAAtAABBA0YEQCAAKAIEIgEoAgAgASgCBCgCABECACABKAIEIgIoAgQEQCACKAIIGiABKAIAECsLIAAoAgQQKwsLSgEBfyMAQbABayIBJAAgASAAQagBEKIDIgAgADYCrAEgAEGsAWpB2I7AABCnAyAALQCgAUEDRgRAIABBCGoQrAILIABBsAFqJAALSAEBfyAAKAIAIgEEQCABIAAoAgQoAgARAgAgACgCBCIBKAIEBEAgASgCCBogACgCABArCyAAKAIIIABBDGooAgAoAgwRAgALC0gBAX8gACgCACIAKAIAIAAoAggiA2sgAkkEQCAAIAMgAhCEASAAKAIIIQMLIAAoAgQgA2ogASACEKIDGiAAIAIgA2o2AghBAAtIAQF/IAAoAgAiACgCACAAKAIIIgNrIAJJBEAgACADIAIQhQEgACgCCCEDCyAAKAIEIANqIAEgAhCiAxogACACIANqNgIIQQALPgECfyMAQRBrIgEkACABQQhqIABBCGooAgAiAjYCACABIAApAgA3AwAgASgCBCACEAEgARCaAiABQRBqJAALQAECfyAAKAIIIgEEQCAAKAIEIQAgAUECdCEBA0AgACgCACICQSRPBEAgAhAACyAAQQRqIQAgAUF8aiIBDQALCwtPAQF/IAAoAgAiACAAKAIAQX9qIgE2AgACQCABDQAgAEEMaiIBEJUBIAEoAgAEQCABKAIEECsLIAAgACgCBEF/aiIBNgIEIAENACAAECsLC0kBAn8jAEEQayIBJABBAEHsusAAKAIAEQYAIgAEQCAAKAIAEAMgAUEQaiQADwtBq7vAAEHGACABQQhqQfS7wABB1LzAABC0AQALRwEBfyMAQZACayIBJAAgASAAQYgCEKIDIgAgADYCjAIgAEGMAmpB7I7AABCnAyAALQCAAkEDRgRAIAAQlgILIABBkAJqJAALRgEBfyMAQZACayIEJAAgBEEAOgCIAiAE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NEEEEPcCIgFFBEBBNEEEEJsDAAsgAUKBgICAEDcCACABQQhqIABBLBCiAxogAQsyAQF/IAAgASgCACABKAIIIgJLBH8gASACEJ4CIAEoAggFIAILNgIEIAAgASgCBDYCAAsyAQF/IAAgASgCACABKAIIIgJLBH8gASACEJ8CIAEoAggFIAILNgIEIAAgASgCBDYCAAsuAQF/IAEoAgAiAgRAIABBATYCCCAAIAI2AgQgACABKAIENgIADwsgAEEANgIICyMBAX8Cf0EBIAEQCUUNABpBAAshAiAAIAE2AgQgACACNgIACzABAX8gAUF4aiICIAIoAgBBAWoiAjYCACACRQRAAAsgAEHkuMAANgIEIAAgATYCAAstAQF/IwBBEGsiASQAIAFBCGogAEEIaigCADYCACABIAApAgA3AwAgARCsAQALIwAgACgCAEFwaiIAEJoCIABBDGooAgAiAEEkTwRAIAAQAAsLMQEBf0EEQQQQ9wIiAkUEQEEEQQQQmwMACyACIAE2AgAgAEGEtsAANgIEIAAgAjYCAAsxAQF/QQRBBBD3AiICRQRAQQRBBBCbAwALIAIgATYCACAAQZi2wAA2AgQgACACNgIACycAIAAgACgCBEEBcSABckECcjYCBCAAIAFqIgAgACgCBEEBcjYCBAsmAQF/IwBBEGsiASQAIAEgAEF4ajYCDCABQQxqELYBIAFBEGokAAsmAAJAIABFDQAgACABKAIAEQIAIAEoAgRFDQAgASgCCBogABArCws6AQJ/QbjswAAtAAAhAUG47MAAQQA6AABBvOzAACgCACECQbzswABBADYCACAAIAI2AgQgACABNgIACzQAIABBAzoAICAAQoCAgICABDcCGCAAQQA2AhAgAEEANgIIIABB5JHAADYCBCAAIAE2AgALMgEBfyABKAIAQa++wABBCyABKAIEKAIMEQUAIQIgAEEAOgAFIAAgAjoABCAAIAE2AgALIAEBfwJAIABBBGooAgAiAUUNACAAKAIARQ0AIAEQKwsLJgEBfyMAQRBrIgMkACADIAE2AgwgAyAANgIIIANBCGogAhD4AQALEwAgACABKQAANwABIABBADoAAAsmAQF/IABBB2oiASAASQRAQaSwwABBM0GwscAAEIoDAAsgAUEDdgsjAAJAIAFB/P///wdNBEAgACABQQQgAhDoAiIADQELAAsgAAsjACACIAIoAgRBfnE2AgQgACABQQFyNgIEIAAgAWogATYCAAseACAAKAIAIgCtQgAgAKx9IABBf0oiABsgACABEFoLKgACQAJAAkAgAC0AqgEOBAACAgECCyAAQYABahC6AQ8LIABBCGoQrgELCyUAIABFBEBB+7rAAEEwEJYDAAsgACACIAMgBCAFIAEoAhARCwALHQAgACgCACgCACABKAIMQQAgAmtBGGxqQWhqEEMLEQAgACgCAARAIAAoAgQQKwsLIAEBfyAAKAIAIAAoAggiAmsgAUkEQCAAIAIgARD/AQsLIwAgAEUEQEH7usAAQTAQlgMACyAAIAIgAyAEIAEoAhARCgALIwAgAEUEQEH7usAAQTAQlgMACyAAIAIgAyAEIAEoAhARBAALIwAgAEUEQEH7usAAQTAQlgMACyAAIAIgAyAEIAEoAhARFQALIwAgAEUEQEH7usAAQTAQlgMACyAAIAIgAyAEIAEoAhARDgALIwAgAEUEQEH7usAAQTAQlgMACyAAIAIgAyAEIAEoAhARFAALFwAgASADRgR/IAAgAiABEPkBRQVBAAsLHgAgACABQQNyNgIEIAAgAWoiACAAKAIEQQFyNgIECyEAIABFBEBB+7rAAEEwEJYDAAsgACACIAMgASgCEBEDAAsUACAAKAIABEAgAEEEaigCABArCwsaACAAKAIAKAIAIAEoAgwgAkEEdGtBcGoQQwsdACABKAIARQRAAAsgAEHUscAANgIEIAAgATYCAAsfACAARQRAQcCzwABBMBCWAwALIAAgAiABKAIQEQAACx8AIABFBEBBhLnAAEEwEJYDAAsgACACIAEoAhARAAALHwAgAEUEQEH7usAAQTAQlgMACyAAIAIgASgCEBEBAAsXACAAKAIABEAgABCpAiAAQQxqEKkCCwsfAQF/IAAoAgQgACgCCBABIAAoAgAEQCAAKAIEECsLCx4BAX9BnOzAACEBQZjswAAoAgAEfyABBSAAEMsBCwsZAQF/IAAoAhAiAQR/IAEFIABBFGooAgALCxkAIAEgAiADEJcCIABBBDoAACAAIAM2AgQLFwAgAEEEaigCACAAQQhqKAIAIAEQnAMLEgBBAEEZIABBAXZrIABBH0YbCxYAIAAgAUEBcjYCBCAAIAFqIAE2AgALFgAgAEEEaigCACAAQQhqKAIAIAEQLwsTAEHw68AAKAIABEAQmQEQpAILCxcAQYjswAAoAgBFBEAgABBvC0GE7MAACxcAIAAgAjYCCCAAIAE2AgQgACACNgIACxAAIAAgAWpBf2pBACABa3ELEgAgAC0AAEEERwRAIAAQhwILCwwAIAAgASACIAMQLgsLACABBEAgABArCwsPACAAQQF0IgBBACAAa3ILGQAgASgCAEHozcAAQQsgASgCBCgCDBEFAAsZACABKAIAQfPNwABBDiABKAIEKAIMEQUACxkAIAEoAgBB/OPAAEEFIAEoAgQoAgwRBQALEwAgACgCACgCCCABIAIQlwJBAAsSACABIAIgAxCXAiAAQQQ6AAALCgAgAEEIahCaAgsPACAAKAIABEAgABCvAQsLFAAgACgCACABIAAoAgQoAgwRAQALDwAgACABIAIgAyAEECkACw4AIAAoAgAgARBwGkEACxAAIAAoAgAgASACEJcCQQALEAAgACgCCCABIAIQlwJBAAsIACAAIAEQSQsRACAAKAIAIAAoAgQgARCcAwsQACAAIAI2AgQgACABNgIACxYAQbzswAAgADYCAEG47MAAQQE6AAALEwAgAEGAxsAANgIEIAAgATYCAAsNACAALQAEQQJxQQF2Cw8AIAAgAUEEaikCADcDAAsQACABIAAoAgAgACgCBBAsCw0AIAAgASACEJcCQQALCgBBACAAayAAcQsLACAALQAEQQNxRQsMACAAIAFBA3I2AgQLDQAgACgCACAAKAIEagsNACAAKAIAIAEQWEEACw4AIAAoAgAaA0AMAAsACwwAIAAgASACEMQBAAsMACAAIAEgAhDFAQALDAAgACABIAIQxgEACw0AIAA1AgBBASABEFoLDAAgACABIAIQwQIACw0AIAAoAgAgASACEEcLDQAgACkDAEEBIAEQWgsNACAAMwEAQQEgARBaCw4AIAAoAgAtAAAgARBdCwsAIAAjAGokACMACwcAIAAQmgILCgAgACABIAIQOwsLACAAIAIgARChAQsNACABQeCRwABBAhAsCwsAIAAoAgAgARBKCwsAIAAgAUHYABAiCwkAIAAgARAhAAsKACAAKAIEQXhxCwoAIAAoAgRBAXELCgAgACgCDEEBcQsKACAAKAIMQQF2CxoAIAAgAUHA7MAAKAIAIgBB7AAgABsRAAAACwoAIAIgACABECwLDAAgACABKQIANwMACwwAIAAgASkCCDcDAAsNACABQfzRwABBAhAsCwsAIAAoAgAgARBcCwoAIAAgASACEDULCgAgACABIAIQWQsLACAAIAEgAhCWAQsHACAAEMEBCwkAIABBBDoAAAsJACAAQQA2AgALCAAgACABEBoLBwAgACABagsHACAAIAFrCwcAIABBCGoLBwAgAEF4agsHACAAENsBCwcAIAAQjgILBABBAQsNAEKRuN6i/Jvrr/sACw0AQsi14M/KhtvTiX8LDQBC54bFsIye5t3IAAsMAELJ4Nbe5qrapDkLAwABCwMAAQsL/GoFAEGAgMAAC/EPAQAAAAQAAAAEAAAAAgAAAAMAAAADAAAAAQAAAAQAAAAEAAAABAAAAAUAAAAFAAAA//////////9gdW53cmFwX3Rocm93YCBmYWlsZWQAAAAGAAAADAAAAAQAAAAHAAAABgAAAAwAAAAEAAAACAAAAAcAAABQABAACQAAAAoAAAALAAAACQAAAAwAAAAvcnVzdGMvMDQ0MmZiYWJlMjRlYzQzNjM2YTgwYWQxZjQwYTBhZDkyYTJlMzhkZi9saWJyYXJ5L3N0ZC9zcmMvaW8vbW9kLnJzAAAAjAAQAEkAAABTAQAAGAAAAHN0cmVhbSBkaWQgbm90IGNvbnRhaW4gdmFsaWQgVVRGLTgAAOgAEAAiAAAAFQAAAC9ydXN0Yy8wNDQyZmJhYmUyNGVjNDM2MzZhODBhZDFmNDBhMGFkOTJhMmUzOGRmL2xpYnJhcnkvc3RkL3NyYy9pby9yZWFkYnVmLnJzAAAAGAEQAE0AAABjAAAANgAAABgBEABNAAAAywAAADYAAAANAAAABAAAAAQAAAAOAAAADwAAABAAAAANAAAABAAAAAQAAAARAAAAEgAAABMAAABjYWxsZWQgYFJlc3VsdDo6dW53cmFwKClgIG9uIGFuIGBFcnJgIHZhbHVlAA0AAAAAAAAAAQAAABQAAAAvaG9tZS9uZ25pdXMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTMuMC9zcmMvY2h1bmtlZF9lbmNvZGVyLnJzAAAA9AEQAGEAAAAtAAAAGgAAAPQBEABhAAAANwAAAEQAAAD0ARAAYQAAADoAAAAnAAAAL2hvbWUvbmduaXVzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2Jhc2U2NC0wLjEzLjAvc3JjL2VuY29kZS5yc4gCEABYAAAAtgAAACAAAACIAhAAWAAAALcAAAAlAAAAiAIQAFgAAAD8AAAAHAAAAIgCEABYAAAA/QAAACEAAACIAhAAWAAAABMBAAAuAAAAiAIQAFgAAAATAQAACQAAAIgCEABYAAAAFAEAAAkAAACIAhAAWAAAAAsBAAAuAAAAiAIQAFgAAAALAQAACQAAAIgCEABYAAAADQEAAA8AAACIAhAAWAAAAAwBAAAJAAAAiAIQAFgAAAAPAQAACQAAAIgCEABYAAAAoAAAACoAAABmYWlsZWQgdG8gZmlsbCBidWZmZXJmYWlsZWQgdG8gd3JpdGUgd2hvbGUgYnVmZmVyAAAAxQMQABwAAAAXAAAAFQAAAAwAAAAEAAAAFgAAABcAAAAYAAAAZm9ybWF0dGVyIGVycm9yAAgEEAAPAAAAKAAAAGZhaWxlZCB0byBmaWxsIHdob2xlIGJ1ZmZlcgAkBBAAGwAAACUAAAAvcnVzdGMvMDQ0MmZiYWJlMjRlYzQzNjM2YTgwYWQxZjQwYTBhZDkyYTJlMzhkZi9saWJyYXJ5L3N0ZC9zcmMvaW8vaW1wbHMucnMATAQQAEsAAADyAAAADQAAAC9ydXN0Yy8wNDQyZmJhYmUyNGVjNDM2MzZhODBhZDFmNDBhMGFkOTJhMmUzOGRmL2xpYnJhcnkvc3RkL3NyYy9pby9jdXJzb3IucnOoBBAATAAAAOsAAAAKAAAAL3J1c3RjLzA0NDJmYmFiZTI0ZWM0MzYzNmE4MGFkMWY0MGEwYWQ5MmEyZTM4ZGYvbGlicmFyeS9zdGQvc3JjL2lvL21vZC5ycwAAAAQFEABJAAAAJAUAABYAAAAvcnVzdGMvMDQ0MmZiYWJlMjRlYzQzNjM2YTgwYWQxZjQwYTBhZDkyYTJlMzhkZi9saWJyYXJ5L3N0ZC9zcmMvc3lzL3dhc20vLi4vdW5zdXBwb3J0ZWQvaW8ucnMAAABgBRAAXQAAAA4AAAATAAAAYWR2YW5jaW5nIGlvIHNsaWNlcyBiZXlvbmQgdGhlaXIgbGVuZ3RoANAFEAAnAAAABAUQAEkAAAAmBQAADQAAAC9ydXN0Yy8wNDQyZmJhYmUyNGVjNDM2MzZhODBhZDFmNDBhMGFkOTJhMmUzOGRmL2xpYnJhcnkvc3RkL3NyYy9pby9yZWFkYnVmLnJzAAAAEAYQAE0AAADmAAAADgAAABwAAAAMAAAABAAAAB0AAAAeAAAAHwAAACAAAAAhAAAAIgAAACMAAAAvaG9tZS9uZ25pdXMvRG9jdW1lbnRzL2dpdC1yZXBvcy91c2RwbC1ycy91c2RwbC1jb3JlL3NyYy9zZXJkZXMvdHJhaXRzLnJzAAAAmAYQAEkAAAB2AAAAHAAAACQAAAAYAAAACAAAACUAAAAmAAAAJwAAACgAAAApAAAAKgAAACsAAAAsAAAAYHVud3JhcF90aHJvd2AgZmFpbGVkAAAALQAAAGABAAAIAAAALgAAAC8AAAAgAgAACAAAADAAAAAxAAAABAAAAAQAAAAyAAAAMwAAADEAAAAEAAAABAAAADQAAAA1AAAAL2hvbWUvbmduaXVzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3dhc20tYmluZGdlbi1mdXR1cmVzLTAuNC4zMy9zcmMvbGliLnJzAIAHEABjAAAA2gAAABUAQYCQwAALyERgYXN5bmMgZm5gIHJlc3VtZWQgYWZ0ZXIgY29tcGxldGlvbgA2AAAAY2Fubm90IGFjY2VzcyBhIFRocmVhZCBMb2NhbCBTdG9yYWdlIHZhbHVlIGR1cmluZyBvciBhZnRlciBkZXN0cnVjdGlvbgAAMQAAAAAAAAABAAAANwAAAC9ydXN0Yy8wNDQyZmJhYmUyNGVjNDM2MzZhODBhZDFmNDBhMGFkOTJhMmUzOGRmL2xpYnJhcnkvc3RkL3NyYy90aHJlYWQvbG9jYWwucnMAgAgQAE8AAACmAQAAGgAAACgpAAAcAAAADAAAAAQAAAA4AAAAOQAAADoAAABhIERpc3BsYXkgaW1wbGVtZW50YXRpb24gcmV0dXJuZWQgYW4gZXJyb3IgdW5leHBlY3RlZGx5ADEAAAAAAAAAAQAAADsAAAAvcnVzdGMvMDQ0MmZiYWJlMjRlYzQzNjM2YTgwYWQxZjQwYTBhZDkyYTJlMzhkZi9saWJyYXJ5L2FsbG9jL3NyYy9zdHJpbmcucnMARAkQAEsAAADpCQAADgAAAGNhbGxlZCBgT3B0aW9uOjp1bndyYXAoKWAgb24gYSBgTm9uZWAgdmFsdWUAMQAAAAAAAAABAAAAPAAAAGludGVybmFsIGVycm9yOiBlbnRlcmVkIHVucmVhY2hhYmxlIGNvZGU6IAAA3AkQACoAAAAvaG9tZS9uZ25pdXMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTMuMC9zcmMvZGVjb2RlLnJzEAoQAFgAAADSAQAAHwAAABAKEABYAAAA2AEAAB8AAAAQChAAWAAAAOEBAAAfAAAAEAoQAFgAAADqAQAAHwAAABAKEABYAAAA8wEAAB8AAAAQChAAWAAAAPwBAAAfAAAAEAoQAFgAAAAFAgAAHwAAABAKEABYAAAADgIAAB8AAAAQChAAWAAAAAMBAAAkAAAAEAoQAFgAAAAEAQAAKQAAABAKEABYAAAAKgEAABYAAAAQChAAWAAAAC0BAAAaAAAAEAoQAFgAAABBAQAADgAAABAKEABYAAAARAEAABIAAAAQChAAWAAAAFgBAAATAAAASW1wb3NzaWJsZTogbXVzdCBvbmx5IGhhdmUgMCB0byA4IGlucHV0IGJ5dGVzIGluIGxhc3QgY2h1bmssIHdpdGggbm8gaW52YWxpZCBsZW5ndGhzWAsQAFQAAAAQChAAWAAAAJ0BAAAOAAAAEAoQAFgAAACxAQAACQAAAE92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgb3V0cHV0IGJ1ZmZlciBsZW5ndGgAABAKEABYAAAAlgAAAAoAAAAQChAAWAAAAJsAAAAhAAAAV3JpdGluZyB0byBhIFN0cmluZyBzaG91bGRuJ3QgZmFpbC9ob21lL25nbml1cy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9iYXNlNjQtMC4xMy4wL3NyYy9lbmNvZGUucnMAAEYMEABYAAAAUwAAAA4AAAD//////////3VzZHBsLWZyb250L3NyYy9jb25uZWN0aW9uLnJzAAAAuAwQAB0AAAAZAAAAJgAAAFBPU1RodHRwOi8vdXNkcGwuOi91c2RwbC9jYWxsAAAA7AwQAAwAAAD4DBAAAQAAAPkMEAABAAAA+gwQAAsAAABsb2NhbGhvc3QAAAAoDRAACQAAALgMEAAdAAAALQAAACQAAAC4DBAAHQAAADQAAAAnAAAAuAwQAB0AAABJAAAAJgAAAEV4cGVjdGVkIGNhbGwgcmVzcG9uc2UgbWVzc2FnZSwgZ290IHNvbWV0aGluZyBlbHNldXNkcGwtZnJvbnQvc3JjL2NvbnZlcnQucnOeDRAAGgAAACAAAAAnAAAAMC45LjF1c2RwbC1mcm9udC9zcmMvbGliLnJzAM0NEAAWAAAAYQAAABgAAADNDRAAFgAAAGkAAAAYAAAAzQ0QABYAAABwAAAATgAAAM0NEAAWAAAAbwAAAAEAAADNDRAAFgAAAJ8AAAAmAAAAzQ0QABYAAACeAAAAAQAAAM0NEAAWAAAAwwAAAEAAAADNDRAAFgAAANEAAABAAAAAY3JhbmtzaGFmdAAAZA4QAAoAAABkZWNreQAAAHgOEAAFAAAAYW55AIgOEAADAAAATG9hZEVycm9yOiBJbygpAJQOEAAOAAAAog4QAAEAAABMb2FkRXJyb3I6IEludmFsaWREYXRhAAC0DhAAFgAAAExvYWRFcnJvcjogVG9vU21hbGxCdWZmZXIAAADUDhAAGQAAAER1bXBFcnJvcjogSW8oAAD4DhAADgAAAKIOEAABAAAARHVtcEVycm9yOiBVbnN1cHBvcnRlZAAAGA8QABYAAABEdW1wRXJyb3I6IFRvb1NtYWxsQnVmZmVyAAAAOA8QABkAAAAvaG9tZS9uZ25pdXMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTMuMC9zcmMvZW5jb2RlLnJzXA8QAFgAAAA7AQAACQAAACEiIyQlJicoKSorLC0wMTIzNDU2Nzg5QEFCQ0RFRkdISUpLTE1OUFFSU1RVVlhZWltgYWJjZGVoaWprbG1wcXJBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MDEyMzQ1Njc4OSssLi9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MDEyMzQ1Njc4OS4vMDEyMzQ1Njc4OUFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MDEyMzQ1Njc4OS1fQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrL////////////////////////////////////////////wABAgMEBQYHCAkKCwz//w0ODxAREhMUFRb///////8XGBkaGxwdHh8gISIjJCX/JicoKSorLP8tLi8w/////zEyMzQ1Nv//Nzg5Ojs8//89Pj//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Pj////80NTY3ODk6Ozw9/////////wABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ////////GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wABNjc4OTo7PD0+P/////////8CAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaG////////xwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAQIDBAUGBwgJCgv/////////DA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCX///////8mJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8+//80NTY3ODk6Ozw9/////////wABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ/////z//GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////z7///8/NDU2Nzg5Ojs8Pf////////8AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGf///////xobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wQREADEEBAAhBAQAEQQEAAEEBAAxA8QAEQWEABEFRAARBQQAEQTEABEEhAARBEQAGNhbGxlZCBgUmVzdWx0Ojp1bndyYXAoKWAgb24gYW4gYEVycmAgdmFsdWUAPgAAAAgAAAAEAAAAPwAAAC9ob21lL25nbml1cy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9iYXNlNjQtMC4xMy4wL3NyYy9jaHVua2VkX2VuY29kZXIucnMAAACwFxAAYQAAAGgAAAAwAAAAT3ZlcmZsb3cgd2hlbiBjYWxjdWxhdGluZyBudW1iZXIgb2YgY2h1bmtzIGluIGlucHV0L2hvbWUvbmduaXVzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2Jhc2U2NC0wLjEzLjAvc3JjL2RlY29kZS5ycwBXGBAAWAAAALwAAAAKAAAAQAAAAAgAAAAEAAAAQQAAAEIAAABAAAAACAAAAAQAAABDAAAAYm9keW1ldGhvZG1vZGVzYW1lLW9yaWdpbm5vLWNvcnNjb3JzbmF2aWdhdGVhdHRlbXB0ZWQgdG8gY29udmVydCBpbnZhbGlkIFJlcXVlc3RNb2RlIGludG8gSlNWYWx1ZS9ob21lL25nbml1cy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy93ZWItc3lzLTAuMy42MC9zcmMvZmVhdHVyZXMvZ2VuX1JlcXVlc3RNb2RlLnJzRRkQAGsAAAADAAAAAQAAAGNsb3N1cmUgaW52b2tlZCByZWN1cnNpdmVseSBvciBkZXN0cm95ZWQgYWxyZWFkeUcAAAAEAAAABAAAAEgAAABJAAAAYWxyZWFkeSBib3Jyb3dlZEoAAAAAAAAAAQAAAEsAAABhbHJlYWR5IG11dGFibHkgYm9ycm93ZWRKAAAAAAAAAAEAAABMAAAAL2hvbWUvbmduaXVzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3dhc20tYmluZGdlbi1mdXR1cmVzLTAuNC4zMy9zcmMvcXVldWUucnMAAABMGhAAZQAAABoAAAAuAAAATBoQAGUAAAAdAAAAKQAAAEwaEABlAAAAMgAAABoAAABhbHJlYWR5IGJvcnJvd2VkTQAAAAAAAAABAAAASwAAAE4AAAAEAAAABAAAAE8AAABQAAAATgAAAAQAAAAEAAAAUQAAAFIAAABGbk9uY2UgY2FsbGVkIG1vcmUgdGhhbiBvbmNlL2hvbWUvbmduaXVzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3dhc20tYmluZGdlbi1mdXR1cmVzLTAuNC4zMy9zcmMvbGliLnJzAEgbEABjAAAApQAAAA8AAABIGxAAYwAAAIUAAAAnAAAASBsQAGMAAACvAAAAJAAAAC9ob21lL25nbml1cy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy93YXNtLWJpbmRnZW4tZnV0dXJlcy0wLjQuMzMvc3JjL3Rhc2svc2luZ2xldGhyZWFkLnJzAAAA3BsQAHEAAAAhAAAAFQAAAFMAAABUAAAAVQAAAFYAAABXAAAA3BsQAHEAAABVAAAAJQAAAGNsb3N1cmUgaW52b2tlZCByZWN1cnNpdmVseSBvciBkZXN0cm95ZWQgYWxyZWFkeWNhbm5vdCBhY2Nlc3MgYSBUaHJlYWQgTG9jYWwgU3RvcmFnZSB2YWx1ZSBkdXJpbmcgb3IgYWZ0ZXIgZGVzdHJ1Y3Rpb24AAFkAAAAAAAAAAQAAADcAAAAvcnVzdGMvMDQ0MmZiYWJlMjRlYzQzNjM2YTgwYWQxZjQwYTBhZDkyYTJlMzhkZi9saWJyYXJ5L3N0ZC9zcmMvdGhyZWFkL2xvY2FsLnJzAAwdEABPAAAApgEAABoAAABaAAAAcmV0dXJuIHRoaXNjbG9zdXJlIGludm9rZWQgcmVjdXJzaXZlbHkgb3IgZGVzdHJveWVkIGFscmVhZHljYW5ub3QgYWNjZXNzIGEgVGhyZWFkIExvY2FsIFN0b3JhZ2UgdmFsdWUgZHVyaW5nIG9yIGFmdGVyIGRlc3RydWN0aW9uAAAAZwAAAAAAAAABAAAANwAAAC9ydXN0Yy8wNDQyZmJhYmUyNGVjNDM2MzZhODBhZDFmNDBhMGFkOTJhMmUzOGRmL2xpYnJhcnkvc3RkL3NyYy90aHJlYWQvbG9jYWwucnMABB4QAE8AAACmAQAAGgAAAFRyaWVkIHRvIHNocmluayB0byBhIGxhcmdlciBjYXBhY2l0eWQeEAAkAAAAL3J1c3RjLzA0NDJmYmFiZTI0ZWM0MzYzNmE4MGFkMWY0MGEwYWQ5MmEyZTM4ZGYvbGlicmFyeS9hbGxvYy9zcmMvcmF3X3ZlYy5yc5AeEABMAAAAqgEAAAkAAABtAAAABAAAAAQAAABuAAAAbwAAAHAAAABjYWxsZWQgYE9wdGlvbjo6dW53cmFwKClgIG9uIGEgYE5vbmVgIHZhbHVlQWNjZXNzRXJyb3IAAAQfEAAAAAAAdW5jYXRlZ29yaXplZCBlcnJvcm90aGVyIGVycm9yb3V0IG9mIG1lbW9yeXVuZXhwZWN0ZWQgZW5kIG9mIGZpbGV1bnN1cHBvcnRlZG9wZXJhdGlvbiBpbnRlcnJ1cHRlZGFyZ3VtZW50IGxpc3QgdG9vIGxvbmdpbnZhbGlkIGZpbGVuYW1ldG9vIG1hbnkgbGlua3Njcm9zcy1kZXZpY2UgbGluayBvciByZW5hbWVkZWFkbG9ja2V4ZWN1dGFibGUgZmlsZSBidXN5cmVzb3VyY2UgYnVzeWZpbGUgdG9vIGxhcmdlZmlsZXN5c3RlbSBxdW90YSBleGNlZWRlZHNlZWsgb24gdW5zZWVrYWJsZSBmaWxlbm8gc3RvcmFnZSBzcGFjZXdyaXRlIHplcm90aW1lZCBvdXRpbnZhbGlkIGRhdGFpbnZhbGlkIGlucHV0IHBhcmFtZXRlcnN0YWxlIG5ldHdvcmsgZmlsZSBoYW5kbGVmaWxlc3lzdGVtIGxvb3Agb3IgaW5kaXJlY3Rpb24gbGltaXQgKGUuZy4gc3ltbGluayBsb29wKXJlYWQtb25seSBmaWxlc3lzdGVtIG9yIHN0b3JhZ2UgbWVkaXVtZGlyZWN0b3J5IG5vdCBlbXB0eWlzIGEgZGlyZWN0b3J5bm90IGEgZGlyZWN0b3J5b3BlcmF0aW9uIHdvdWxkIGJsb2NrZW50aXR5IGFscmVhZHkgZXhpc3RzYnJva2VuIHBpcGVuZXR3b3JrIGRvd25hZGRyZXNzIG5vdCBhdmFpbGFibGVhZGRyZXNzIGluIHVzZW5vdCBjb25uZWN0ZWRjb25uZWN0aW9uIGFib3J0ZWRuZXR3b3JrIHVucmVhY2hhYmxlaG9zdCB1bnJlYWNoYWJsZWNvbm5lY3Rpb24gcmVzZXRjb25uZWN0aW9uIHJlZnVzZWRwZXJtaXNzaW9uIGRlbmllZGVudGl0eSBub3QgZm91bmQgKG9zIGVycm9yICkAAAAEHxAAAAAAADEiEAALAAAAPCIQAAEAAABtZW1vcnkgYWxsb2NhdGlvbiBvZiAgYnl0ZXMgZmFpbGVkAABYIhAAFQAAAG0iEAANAAAAbGlicmFyeS9zdGQvc3JjL2FsbG9jLnJzjCIQABgAAABVAQAACQAAAGxpYnJhcnkvc3RkL3NyYy9wYW5pY2tpbmcucnO0IhAAHAAAAD4CAAAeAAAAtCIQABwAAAA9AgAAHwAAAHEAAAAMAAAABAAAAHIAAABtAAAACAAAAAQAAABzAAAAdAAAABAAAAAEAAAAdQAAAHYAAABtAAAACAAAAAQAAAB3AAAAeAAAAG0AAAAAAAAAAQAAAHkAAABvcGVyYXRpb24gc3VjY2Vzc2Z1bA4AAAAQAAAAFgAAABUAAAALAAAAFgAAAA0AAAALAAAAEwAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABEAAAASAAAAEAAAABAAAAATAAAAEgAAAA0AAAAOAAAAFQAAAAwAAAALAAAAFQAAABUAAAAPAAAADgAAABMAAAAmAAAAOAAAABkAAAAXAAAADAAAAAkAAAAKAAAAEAAAABcAAAAZAAAADgAAAA0AAAAUAAAACAAAABsAAADLHxAAux8QAKUfEACQHxAAhR8QAG8fEABiHxAAVx8QAEQfEAAhIhAAISIQACEiEAAhIhAAISIQACEiEAAhIhAAISIQACEiEAAhIhAAISIQACEiEAAhIhAAISIQACEiEAAhIhAAISIQACEiEAAhIhAAISIQACEiEAAhIhAAISIQACEiEAAQIhAA/iEQAO4hEADeIRAAyyEQALkhEACsIRAAniEQAIkhEAB9IRAAciEQAF0hEABIIRAAOSEQACshEAAYIRAA8iAQALogEAChIBAAiiAQAH4gEAB1IBAAayAQAFsgEABEIBAAKyAQAB0gEAAQIBAA/B8QAPQfEADZHxAASGFzaCB0YWJsZSBjYXBhY2l0eSBvdmVyZmxvd1wlEAAcAAAAL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvaGFzaGJyb3duLTAuMTIuMy9zcmMvcmF3L21vZC5ycwCAJRAATwAAAFoAAAAoAAAAegAAAAQAAAAEAAAAewAAAHwAAAB9AAAAbGlicmFyeS9hbGxvYy9zcmMvcmF3X3ZlYy5yc2NhcGFjaXR5IG92ZXJmbG93AAAAFCYQABEAAAD4JRAAHAAAAAYCAAAFAAAAYSBmb3JtYXR0aW5nIHRyYWl0IGltcGxlbWVudGF0aW9uIHJldHVybmVkIGFuIGVycm9yAHoAAAAAAAAAAQAAADsAAABsaWJyYXJ5L2FsbG9jL3NyYy9mbXQucnOEJhAAGAAAAGQCAAAgAAAA77+9AGNhbGxlZCBgT3B0aW9uOjp1bndyYXAoKWAgb24gYSBgTm9uZWAgdmFsdWUpLi4AANwmEAACAAAAQm9ycm93RXJyb3JCb3Jyb3dNdXRFcnJvcgBpbmRleCBvdXQgb2YgYm91bmRzOiB0aGUgbGVuIGlzICBidXQgdGhlIGluZGV4IGlzIAInEAAgAAAAIicQABIAAACDAAAAAAAAAAEAAACEAAAAsCYQAAAAAABgOiAAsCYQAAAAAABdJxAAAgAAAIMAAAAMAAAABAAAAIUAAACGAAAAhwAAACAgICAgewosCiwgIHsgfSB9KAooLAAAAIMAAAAEAAAABAAAAIgAAABsaWJyYXJ5L2NvcmUvc3JjL2ZtdC9udW0ucnMAsCcQABsAAABlAAAAFAAAADB4MDAwMTAyMDMwNDA1MDYwNzA4MDkxMDExMTIxMzE0MTUxNjE3MTgxOTIwMjEyMjIzMjQyNTI2MjcyODI5MzAzMTMyMzMzNDM1MzYzNzM4Mzk0MDQxNDI0MzQ0NDU0NjQ3NDg0OTUwNTE1MjUzNTQ1NTU2NTc1ODU5NjA2MTYyNjM2NDY1NjY2NzY4Njk3MDcxNzI3Mzc0NzU3Njc3Nzg3OTgwODE4MjgzODQ4NTg2ODc4ODg5OTA5MTkyOTM5NDk1OTY5Nzk4OTkAAIMAAAAEAAAABAAAAIkAAACKAAAAiwAAAGxpYnJhcnkvY29yZS9zcmMvZm10L21vZC5ycwDAKBAAGwAAAHoJAAAeAAAAwCgQABsAAACBCQAAFgAAACgpbGlicmFyeS9jb3JlL3NyYy9zbGljZS9tZW1jaHIucnMAAP4oEAAgAAAAaAAAACcAAABy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AQEBAggGCgIBMB8xBDAHAQEFASgJDAIgBAICAQM4AQECAwEBAzoIAgKYAwENAQcEAQYBAwLGQAABwyEAA40BYCAABmkCAAQBCiACUAIAAQMBBAEZAgUBlwIaEg0BJggZCy4DMAECBAICJwFDBgICAgIMAQgBLwEzAQEDAgIFAgEBKgIIAe4BAgEEAQABABAQEAACAAHiAZUFAAMBAgUEKAMEAaUCAAQAAlADRgsxBHsBNg8pAQICCgMxBAICBwE9AyQFAQg+AQwCNAkKBAIBXwMCAQECBgECAZ0BAwgVAjkCAQEBARYBDgcDBcMIAgMBARcBUQECBgEBAgEBAgEC6wECBAYCAQIbAlUIAgEBAmoBAQECBgEBZQMCBAEFAAkBAvUBCgIBAQQBkAQCAgQBIAooBgIECAEJBgIDLg0BAgAHAQYBAVIWAgcBAgECegYDAQECAQcBAUgCAwEBAQACCwI0BQUBAQEAAQYPAAU7BwABPwRRAQACAC4CFwABAQMEBQgIAgceBJQDADcEMggBDgEWBQEPAAcBEQIHAQIBBWQBoAcAAT0EAAQAB20HAGCA8ABBuOvAAAsCaXoAOwlwcm9kdWNlcnMBDHByb2Nlc3NlZC1ieQIGd2FscnVzBjAuMTkuMAx3YXNtLWJpbmRnZW4GMC4yLjgz"; function asciiToBinary(str) { if (typeof atob === 'function') { diff --git a/src/usdpl_front/usdpl_front_bg.wasm b/src/usdpl_front/usdpl_front_bg.wasm index d63284b..52c1584 100644 Binary files a/src/usdpl_front/usdpl_front_bg.wasm and b/src/usdpl_front/usdpl_front_bg.wasm differ diff --git a/src/usdpl_front/usdpl_front_bg.wasm.d.ts b/src/usdpl_front/usdpl_front_bg.wasm.d.ts index 1e96bc2..959d805 100644 --- a/src/usdpl_front/usdpl_front_bg.wasm.d.ts +++ b/src/usdpl_front/usdpl_front_bg.wasm.d.ts @@ -7,6 +7,9 @@ export function version_usdpl(a: number): void; export function set_value(a: number, b: number, c: number): number; export function get_value(a: number, b: number): number; export function call_backend(a: number, b: number, c: number, d: number): number; +export function init_tr(a: number, b: number): number; +export function tr(a: number, b: number, c: number): void; +export function tr_n(a: number, b: number, c: number, d: number): void; export function __wbindgen_export_0(a: number): number; export function __wbindgen_export_1(a: number, b: number, c: number): number; export const __wbindgen_export_2: WebAssembly.Table; diff --git a/translations/build.py b/translations/build.py new file mode 100755 index 0000000..524bd54 --- /dev/null +++ b/translations/build.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import os +import subprocess + +if __name__ == "__main__": + for item in os.listdir("."): + if item[-3:] == ".po": + print("Generating binary translation file from", item) + subprocess.run(["msgfmt", "-c", "-o", item[:-2]+"mo", item]) + else: + print("Ignoring", item) + diff --git a/translations/es-ES.mo b/translations/es-ES.mo new file mode 100644 index 0000000..d6ba246 Binary files /dev/null and b/translations/es-ES.mo differ diff --git a/translations/es-ES.po b/translations/es-ES.po new file mode 100644 index 0000000..bf85030 --- /dev/null +++ b/translations/es-ES.po @@ -0,0 +1,234 @@ +# TEMPLATE TITLE. +# Copyright (C) 2023 NGnius +# This file is distributed under the same license as the PowerTools package. +# NGnius (Graham) , 2023. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: v1.1\n" +"Report-Msgid-Bugs-To: https://github.com/NGnius/PowerTools/issues\n" +"POT-Creation-Date: 2023-01-09 19:52-0500\n" +"PO-Revision-Date: 2023-01-18 19:52-0500\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es-ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# -- index.tsx -- + +#: index.tsx:226 +# (Section title) +msgid "Miscellaneous" +msgstr "Misceláneo" + +#: index.tsx:226 +# (Profile persistence toggle) +msgid "Persistent Profile" +msgstr "Persistente" + +#: index.tsx:227 +# (Profile persistence toggle description) +msgid "Save profile and load it next time" +msgstr "Guardar perfil y cargarlo la próxima vez" + +#: index.tsx:239 +# (Profile display) +msgid "Profile" +msgstr "Perfil" + +#: index.tsx:266 +# (Button to reset everything to system defaults) +msgid "Defaults" +msgstr "Valores predeterminados" + +# -- components/battery.tsx -- + +#: components/battery.tsx:42 +# (Battery section title) +msgid "Battery" +msgstr "Batería" + +#: components/battery.tsx:46 +# (Charge of battery at this moment, with percentage of expected full charge in brackets) +msgid "Now (Charge)" +msgstr "Ahora (Charge)" + +#: components/battery.tsx:52 +# (Maximum capacity of battery, with percentage of design capacity in brackets) +msgid "Max (Design)" +msgstr "Max (Design)" + +#: components/battery.tsx:59 +# (Charge current limit override toggle) +msgid "Charge Current Limits" +msgstr "Limite de carga" + +#: components/battery.tsx:60 +# (Charge current limit override toggle description) +msgid "Control battery charge rate when awake" +msgstr "Control de carga de la batería mientras está encendido" + +#: components/battery.tsx:74 +# (Battery maximum input current with unit) +msgid "Maximum (mA)" +msgstr "Máximo (mA)" + +#: components/battery.tsx:97,115 +# (Battery charge mode override toggle) +msgid "Charge Mode" +msgstr "Modo carga" + +#: components/battery.tsx:98 +# (Battery charge mode override toggle description) +msgid "Force battery charge mode" +msgstr "Forzar modo carga de la batería" + +#: components/battery.tsx:112 +# (Battery charge mode dropdown) +msgid "Mode" +msgstr "Modo" + +#: components/battery.tsx:133 +# (Battery current display) +msgid "Current" +msgstr "Corriente" + +# -- components/cpus.tsx -- + +#: components/cpus.tsx:64 +# (CPU section title) +msgid "CPU" +msgstr "CPU" + +#: components/cpus.tsx:70 +# (CPU advanced mode toggle) +msgid "Advanced" +msgstr "Avanzado" + +#: components/cpus.tsx:71 +# (CPU advanced mode toggle description) +msgid "Enables per-thread configuration" +msgstr "Habilita la configuración por subprocesos" + +#: components/cpus.tsx:88 +# (CPU Simultaneous MultiThreading toggle) +msgid "SMT" +msgstr "SMT" + +#: components/cpus.tsx:89 +# (CPU SMT toggle description) +msgid "Enables odd-numbered CPUs" +msgstr "Habilita CPUs impares" + +#: components/cpus.tsx:106 +# (CPU thread count slider) +msgid "Threads" +msgstr "Subprocesos" + +#: components/cpus.tsx:137 +#: components/gpu.tsx:112 +# (Clock speed override toggle) +msgid "Frequency Limits" +msgstr "Límites de frecuencia" + +#: components/cpus.tsx:138 +#: components/gpu.tsx:113 +# (Clock speed override toggle description) +msgid "Set bounds on clock speed" +msgstr "Establecer límites en la velocidad de reloj" + +#: components/cpus.tsx:165 +#: components/gpu.tsx:137 +# (Minimum clock speed with unit) +msgid "Minimum (MHz)" +msgstr "Mínimo (MHz)" + +#: components/cpus.tsx:195 +#: components/gpu.tsx:160 +# (Maximum clock speed with unit) +msgid "Maximum (MHz)" +msgstr "Máximo (MHz)" + +# advanced mode + +#: components/cpus.tsx:226 +# (CPU selection slider) +msgid "Selected CPU" +msgstr "CPU seleccionada" + +#: components/cpus.tsx:246 +# (CPU Online toggle) +msgid "Online" +msgstr "Online" + +#: components/cpus.tsx:247 +# (CPU Online description) +msgid "Allow the CPU thread to do work" +msgstr "Permite que el subproceso de la CPU funcione" + +#: components/cpus.tsx:342 +# (CPU scheduling governor dropdown -- governor names are not translated) +msgid "Governor" +msgstr "Gobernador" + +# -- components/debug.tsx -- + +#: components/debug.tsx:29 +# (Debug section title) +msgid "Debug" +msgstr "Depurar" + +#: components/debug.tsx:33 +# (Version display for native back-end of PowerTools) +msgid "Native" +msgstr "Nativo" + +#: components/debug.tsx:47 +# (Mode display for framework of USDPL API) +msgid "Framework" +msgstr "Framework" + +#: components/debug.tsx:54 +# (Display for software implementation in PowerTools which applies settings) +msgid "Driver" +msgstr "Controlador" + +# -- components/gpu.tsx -- + +#: components/gpu.tsx:34 +# (GPU section title) +msgid "GPU" +msgstr "GPU" + +#: components/gpu.tsx:39 +# (PPT Limits override toggle) +msgid "PowerPlay Limits" +msgstr "Límites de PowerPlay" + +#: components/gpu.tsx:40 +# (PPT Limits override toggle description) +msgid "Override APU TDP settings" +msgstr "Anular la configuración del APU TDP" + +#: components/gpu.tsx:63 +# (SlowPPT slider with unit) +msgid "SlowPPT (W)" +msgstr "SlowPPT (W)" + +#: components/gpu.tsx:87 +# (FastPPT slider with unit) +msgid "FastPPT (W)" +msgstr "FastPPT (W)" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle) +msgid "Downclock Memory" +msgstr "Memoria descendente" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle description) +msgid "Force RAM into low-power mode" +msgstr "Forzar RAM a modo ahorro de energía" diff --git a/translations/fr-CA.mo b/translations/fr-CA.mo new file mode 100644 index 0000000..d0cc852 Binary files /dev/null and b/translations/fr-CA.mo differ diff --git a/translations/fr-CA.po b/translations/fr-CA.po new file mode 100644 index 0000000..bc987c9 --- /dev/null +++ b/translations/fr-CA.po @@ -0,0 +1,225 @@ +# TEMPLATE TITLE. +# Copyright (C) 2023 NGnius +# This file is distributed under the same license as the PowerTools package. +# NGnius (Graham) , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: v1.1\n" +"Report-Msgid-Bugs-To: https://github.com/NGnius/PowerTools/issues\n" +"POT-Creation-Date: 2023-01-09 19:52-0500\n" +"PO-Revision-Date: 2023-01-11 08:49-0500\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr_CA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 3.2.2\n" + +# -- index.tsx -- +# (Section title) +#: index.tsx:226 +msgid "Miscellaneous" +msgstr "Divers" + +# (Profile persistence toggle) +#: index.tsx:226 +msgid "Persistent Profile" +msgstr "Persistant" + +# (Profile persistence toggle description) +#: index.tsx:227 +msgid "Save profile and load it next time" +msgstr "Sauvegarder le profile et le charger la prochaine fois" + +# (Profile display) +#: index.tsx:239 +msgid "Profile" +msgstr "Profile" + +# (Button to reset everything to system defaults) +#: index.tsx:266 +msgid "Defaults" +msgstr "Valeurs par défaut" + +# -- components/battery.tsx -- +# (Battery section title) +#: components/battery.tsx:42 +msgid "Battery" +msgstr "Batterie" + +# (Charge of battery at this moment, with percentage of expected full charge in brackets) +#: components/battery.tsx:46 +msgid "Now (Charge)" +msgstr "Présentement (Charge)" + +# (Maximum capacity of battery, with percentage of design capacity in brackets) +#: components/battery.tsx:52 +msgid "Max (Design)" +msgstr "Max (Conçue)" + +# (Charge current limit override toggle) +#: components/battery.tsx:59 +msgid "Charge Current Limits" +msgstr "Limites de courant de charge" + +# (Charge current limit override toggle description) +#: components/battery.tsx:60 +msgid "Control battery charge rate when awake" +msgstr "Contrôler le taux de charge quand actif" + +# (Battery maximum input current with unit) +#: components/battery.tsx:74 +msgid "Maximum (mA)" +msgstr "Maximum (mA)" + +# (Battery charge mode override toggle) +#: components/battery.tsx:97,115 +msgid "Charge Mode" +msgstr "Mode de Charge" + +# (Battery charge mode override toggle description) +#: components/battery.tsx:98 +msgid "Force battery charge mode" +msgstr "Forcer le mode de charge de la batterie" + +# (Battery charge mode dropdown) +#: components/battery.tsx:112 +msgid "Mode" +msgstr "Mode" + +# (Battery current display) +#: components/battery.tsx:133 +msgid "Current" +msgstr "Courant" + +# -- components/cpus.tsx -- +# (CPU section title) +#: components/cpus.tsx:64 +msgid "CPU" +msgstr "CPU" + +# (CPU advanced mode toggle) +#: components/cpus.tsx:70 +msgid "Advanced" +msgstr "Avancé" + +# (CPU advanced mode toggle description) +#: components/cpus.tsx:71 +msgid "Enables per-thread configuration" +msgstr "Permet la configuration par fil d'exécution" + +# (CPU Simultaneous MultiThreading toggle) +#: components/cpus.tsx:88 +msgid "SMT" +msgstr "Multifil Simultané" + +# (CPU SMT toggle description) +#: components/cpus.tsx:89 +msgid "Enables odd-numbered CPUs" +msgstr "Active un nombre impair de fils d'exécution" + +# (CPU thread count slider) +#: components/cpus.tsx:106 +msgid "Threads" +msgstr "Fils" + +# (Clock speed override toggle) +#: components/cpus.tsx:137 components/gpu.tsx:112 +msgid "Frequency Limits" +msgstr "Limites de fréquence" + +# (Clock speed override toggle description) +#: components/cpus.tsx:138 components/gpu.tsx:113 +msgid "Set bounds on clock speed" +msgstr "Fixer les limites sur la fréquence d'horloge" + +# (Minimum clock speed with unit) +#: components/cpus.tsx:165 components/gpu.tsx:137 +msgid "Minimum (MHz)" +msgstr "Minimum (MHz)" + +# (Maximum clock speed with unit) +#: components/cpus.tsx:195 components/gpu.tsx:160 +msgid "Maximum (MHz)" +msgstr "Maximum (MHz)" + +# advanced mode +# (CPU selection slider) +#: components/cpus.tsx:226 +msgid "Selected CPU" +msgstr "CPU Sélectionnée" + +# (CPU Online toggle) +#: components/cpus.tsx:246 +msgid "Online" +msgstr "En Ligne" + +# (CPU Online description) +#: components/cpus.tsx:247 +msgid "Allow the CPU thread to do work" +msgstr "Permettre le fil d'exécution à travailler" + +# (CPU scheduling governor dropdown -- governor names are not translated) +#: components/cpus.tsx:342 +msgid "Governor" +msgstr "Régulateur" + +# -- components/debug.tsx -- +# (Debug section title) +#: components/debug.tsx:29 +msgid "Debug" +msgstr "Déboguer" + +# (Version display for native back-end of PowerTools) +#: components/debug.tsx:33 +msgid "Native" +msgstr "Natif" + +# (Mode display for framework of USDPL API) +#: components/debug.tsx:47 +msgid "Framework" +msgstr "Cadre d'application" + +# (Display for software implementation in PowerTools which applies settings) +#: components/debug.tsx:54 +msgid "Driver" +msgstr "Pilote" + +# -- components/gpu.tsx -- +# (GPU section title) +#: components/gpu.tsx:34 +msgid "GPU" +msgstr "GPU" + +# (PPT Limits override toggle) +#: components/gpu.tsx:39 +msgid "PowerPlay Limits" +msgstr "Limites du PowerPlay" + +# (PPT Limits override toggle description) +#: components/gpu.tsx:40 +msgid "Override APU TDP settings" +msgstr "Remplacer les paramètres TDP du processeur auxiliaire" + +# (SlowPPT slider with unit) +#: components/gpu.tsx:63 +msgid "SlowPPT (W)" +msgstr "SlowPPT (W)" + +# (FastPPT slider with unit) +#: components/gpu.tsx:87 +msgid "FastPPT (W)" +msgstr "FastPPT (W)" + +# (Reduce memory clock speed toggle) +#: components/gpu.tsx:112 +msgid "Downclock Memory" +msgstr "Souscadencer la mémoire" + +# (Reduce memory clock speed toggle description) +#: components/gpu.tsx:112 +msgid "Force RAM into low-power mode" +msgstr "Forcer la mémoire vive en mode basse consommation" diff --git a/translations/fr-FR.mo b/translations/fr-FR.mo new file mode 120000 index 0000000..303685b --- /dev/null +++ b/translations/fr-FR.mo @@ -0,0 +1 @@ +fr-CA.mo \ No newline at end of file diff --git a/translations/pt.pot b/translations/pt.pot new file mode 100644 index 0000000..9dc33cf --- /dev/null +++ b/translations/pt.pot @@ -0,0 +1,232 @@ +# TEMPLATE TITLE. +# Copyright (C) 2023 NGnius +# This file is distributed under the same license as the PowerTools package. +# NGnius (Graham) , 2023. +msgid "" +msgstr "" +"Project-Id-Version: v1.1\n" +"Report-Msgid-Bugs-To: https://github.com/NGnius/PowerTools/issues\n" +"POT-Creation-Date: 2023-01-09 19:52-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# -- index.tsx -- + +#: index.tsx:226 +# (Section title) +msgid "Miscellaneous" +msgstr "" + +#: index.tsx:226 +# (Profile persistence toggle, a bit like SteamOS's "Use per-game profile") +msgid "Persistent Profile" +msgstr "" + +#: index.tsx:227 +# (Profile persistence toggle description) +msgid "Save profile and load it next time" +msgstr "" + +#: index.tsx:239 +# (Profile display) +msgid "Profile" +msgstr "" + +#: index.tsx:266 +# (Button to reset everything to system defaults) +msgid "Defaults" +msgstr "" + +# -- components/battery.tsx -- + +#: components/battery.tsx:42 +# (Battery section title) +msgid "Battery" +msgstr "" + +#: components/battery.tsx:46 +# (Charge of battery at this moment, with percentage of expected full charge in brackets) +msgid "Now (Charge)" +msgstr "" + +#: components/battery.tsx:52 +# (Maximum capacity of battery, with percentage of design capacity in brackets) +msgid "Max (Design)" +msgstr "" + +#: components/battery.tsx:59 +# (Charge current limit override toggle) +msgid "Charge Current Limits" +msgstr "" + +#: components/battery.tsx:60 +# (Charge current limit override toggle description) +msgid "Control battery charge rate when awake" +msgstr "" + +#: components/battery.tsx:74 +# (Battery maximum input current with unit) +msgid "Maximum (mA)" +msgstr "" + +#: components/battery.tsx:97,115 +# (Battery charge mode override toggle) +msgid "Charge Mode" +msgstr "" + +#: components/battery.tsx:98 +# (Battery charge mode override toggle description) +msgid "Force battery charge mode" +msgstr "" + +#: components/battery.tsx:112 +# (Battery charge mode dropdown) +msgid "Mode" +msgstr "" + +#: components/battery.tsx:133 +# (Battery electrical current display) +msgid "Current" +msgstr "" + +# -- components/cpus.tsx -- + +#: components/cpus.tsx:64 +# (CPU section title) +msgid "CPU" +msgstr "" + +#: components/cpus.tsx:70 +# (CPU advanced mode toggle) +msgid "Advanced" +msgstr "" + +#: components/cpus.tsx:71 +# (CPU advanced mode toggle description) +msgid "Enables per-thread configuration" +msgstr "" + +#: components/cpus.tsx:88 +# (CPU Simultaneous MultiThreading toggle) +msgid "SMT" +msgstr "" + +#: components/cpus.tsx:89 +# (CPU SMT toggle description) +msgid "Enables odd-numbered CPUs" +msgstr "" + +#: components/cpus.tsx:106 +# (CPU thread count slider) +msgid "Threads" +msgstr "" + +#: components/cpus.tsx:137 +#: components/gpu.tsx:112 +# (Clock speed override toggle) +msgid "Frequency Limits" +msgstr "" + +#: components/cpus.tsx:138 +#: components/gpu.tsx:113 +# (Clock speed override toggle description) +msgid "Set bounds on clock speed" +msgstr "" + +#: components/cpus.tsx:165 +#: components/gpu.tsx:137 +# (Minimum clock speed with unit) +msgid "Minimum (MHz)" +msgstr "" + +#: components/cpus.tsx:195 +#: components/gpu.tsx:160 +# (Maximum clock speed with unit) +msgid "Maximum (MHz)" +msgstr "" + +# advanced mode + +#: components/cpus.tsx:226 +# (CPU selection slider) +msgid "Selected CPU" +msgstr "" + +#: components/cpus.tsx:246 +# (CPU Online toggle) +msgid "Online" +msgstr "" + +#: components/cpus.tsx:247 +# (CPU Online description) +msgid "Allow the CPU thread to do work" +msgstr "" + +#: components/cpus.tsx:342 +# (CPU scheduling governor dropdown -- governor names are not translated) +msgid "Governor" +msgstr "" + +# -- components/debug.tsx -- + +#: components/debug.tsx:29 +# (Debug section title) +msgid "Debug" +msgstr "" + +#: components/debug.tsx:33 +# (Version display for native back-end of PowerTools) +msgid "Native" +msgstr "" + +#: components/debug.tsx:47 +# (Mode display for framework of USDPL API) +msgid "Framework" +msgstr "" + +#: components/debug.tsx:54 +# (Display for software implementation in PowerTools which applies settings) +msgid "Driver" +msgstr "" + +# -- components/gpu.tsx -- + +#: components/gpu.tsx:34 +# (GPU section title) +msgid "GPU" +msgstr "" + +#: components/gpu.tsx:39 +# (PPT Limits override toggle) +msgid "PowerPlay Limits" +msgstr "" + +#: components/gpu.tsx:40 +# (PPT Limits override toggle description) +msgid "Override APU TDP settings" +msgstr "" + +#: components/gpu.tsx:63 +# (SlowPPT slider with unit) +msgid "SlowPPT (W)" +msgstr "" + +#: components/gpu.tsx:87 +# (FastPPT slider with unit) +msgid "FastPPT (W)" +msgstr "" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle) +msgid "Downclock Memory" +msgstr "" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle description) +msgid "Force RAM into low-power mode" +msgstr "" diff --git a/translations/test.mo b/translations/test.mo new file mode 100644 index 0000000..4def730 Binary files /dev/null and b/translations/test.mo differ diff --git a/translations/test.po b/translations/test.po new file mode 100644 index 0000000..8ba4fcb --- /dev/null +++ b/translations/test.po @@ -0,0 +1,227 @@ +# TEMPLATE TITLE. +# Copyright (C) 2023 NGnius +# This file is distributed under the same license as the PowerTools package. +# NGnius (Graham) , 2023. +msgid "" +msgstr "" +"Project-Id-Version: v1.1\n" +"Report-Msgid-Bugs-To: https://github.com/NGnius/PowerTools/issues\n" +"POT-Creation-Date: 2023-01-10 20:06-0500\n" +"PO-Revision-Date: 2023-01-10 20:06-0500\n" +"Last-Translator: NGnius \n" +"Language-Team: NGnius \n" +"Language: conlang\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# -- index.tsx -- + +#: index.tsx:226 +# (Section title) +msgid "Miscellaneous" +msgstr "test123" + +#: index.tsx:226 +# (Profile persistence toggle) +msgid "Persistent" +msgstr "test123" + +#: index.tsx:227 +# (Profile persistence toggle description) +msgid "Save profile and load it next time" +msgstr "test123" + +#: index.tsx:239 +# (Profile display) +msgid "Profile" +msgstr "test123" + +# -- components/battery.tsx -- + +#: components/battery.tsx:42 +# (Battery section title) +msgid "Battery" +msgstr "test123" + +#: components/battery.tsx:46 +# (Charge of battery at this moment, with percentage of expected full charge in brackets) +msgid "Now (Charge)" +msgstr "test123" + +#: components/battery.tsx:52 +# (Maximum capacity of battery, with percentage of design capacity in brackets) +msgid "Max (Design)" +msgstr "test123" + +#: components/battery.tsx:59 +# (Charge current limit override toggle) +msgid "Charge Current Limits" +msgstr "test123" + +#: components/battery.tsx:60 +# (Charge current limit override toggle description) +msgid "Control battery charge rate when awake" +msgstr "test123" + +#: components/battery.tsx:74 +# (Battery maximum input current with unit) +msgid "Maximum (mA)" +msgstr "test123" + +#: components/battery.tsx:97,115 +# (Battery charge mode override toggle) +msgid "Charge Mode" +msgstr "test123" + +#: components/battery.tsx:98 +# (Battery charge mode override toggle description) +msgid "Force battery charge mode" +msgstr "test123" + +#: components/battery.tsx:112 +# (Battery charge mode dropdown) +msgid "Mode" +msgstr "test123" + +#: components/battery.tsx:133 +# (Battery current display) +msgid "Current" +msgstr "test123" + +# -- components/cpus.tsx -- + +#: components/cpus.tsx:64 +# (CPU section title) +msgid "CPU" +msgstr "test123" + +#: components/cpus.tsx:70 +# (CPU advanced mode toggle) +msgid "Advanced" +msgstr "test123" + +#: components/cpus.tsx:71 +# (CPU advanced mode toggle description) +msgid "Enables per-thread configuration" +msgstr "test123" + +#: components/cpus.tsx:88 +# (CPU Simultaneous MultiThreading toggle) +msgid "SMT" +msgstr "test123" + +#: components/cpus.tsx:89 +# (CPU SMT toggle description) +msgid "Enables odd-numbered CPUs" +msgstr "test123" + +#: components/cpus.tsx:106 +# (CPU thread count slider) +msgid "Threads" +msgstr "test123" + +#: components/cpus.tsx:137 +#: components/gpu.tsx:112 +# (Clock speed override toggle) +msgid "Frequency Limits" +msgstr "test123" + +#: components/cpus.tsx:138 +#: components/gpu.tsx:113 +# (Clock speed override toggle description) +msgid "Set bounds on clock speed" +msgstr "test123" + +#: components/cpus.tsx:165 +#: components/gpu.tsx:137 +# (Minimum clock speed with unit) +msgid "Minimum (MHz)" +msgstr "test123" + +#: components/cpus.tsx:195 +#: components/gpu.tsx:160 +# (Maximum clock speed with unit) +msgid "Maximum (MHz)" +msgstr "test123" + +# advanced mode + +#: components/cpus.tsx:226 +# (CPU selection slider) +msgid "Selected CPU" +msgstr "test123" + +#: components/cpus.tsx:246 +# (CPU Online toggle) +msgid "Online" +msgstr "test123" + +#: components/cpus.tsx:247 +# (CPU Online description) +msgid "Allow the CPU thread to do work" +msgstr "test123" + +#: components/cpus.tsx:342 +# (CPU scheduling governor dropdown -- governor names are not translated) +msgid "Governor" +msgstr "test123" + +# -- components/debug.tsx -- + +#: components/debug.tsx:29 +# (Debug section title) +msgid "Debug" +msgstr "test123" + +#: components/debug.tsx:33 +# (Version display for native back-end of PowerTools) +msgid "Native" +msgstr "test123" + +#: components/debug.tsx:47 +# (Mode display for framework of USDPL API) +msgid "Framework" +msgstr "test123" + +#: components/debug.tsx:54 +# (Display for software implementation in PowerTools which applies settings) +msgid "Driver" +msgstr "test123" + +# -- components/gpu.tsx -- + +#: components/gpu.tsx:34 +# (GPU section title) +msgid "GPU" +msgstr "test123" + +#: components/gpu.tsx:39 +# (PPT Limits override toggle) +msgid "PowerPlay Limits" +msgstr "test123" + +#: components/gpu.tsx:40 +# (PPT Limits override toggle description) +msgid "Override APU TDP settings" +msgstr "test123" + +#: components/gpu.tsx:63 +# (SlowPPT slider with unit) +msgid "SlowPPT (W)" +msgstr "test123" + +#: components/gpu.tsx:87 +# (FastPPT slider with unit) +msgid "FastPPT (W)" +msgstr "test123" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle) +msgid "Downclock Memory" +msgstr "test123" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle description) +msgid "Force RAM into low-power mode" +msgstr "test123" diff --git a/translations/zh-CN.mo b/translations/zh-CN.mo new file mode 100644 index 0000000..78bd0ad Binary files /dev/null and b/translations/zh-CN.mo differ diff --git a/translations/zh-CN.po b/translations/zh-CN.po new file mode 100644 index 0000000..7678172 --- /dev/null +++ b/translations/zh-CN.po @@ -0,0 +1,228 @@ +# TEMPLATE TITLE. +# Copyright (C) 2023 NGnius +# This file is distributed under the same license as the PowerTools package. +# NGnius (Graham) , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: v1.1\n" +"Report-Msgid-Bugs-To: https://github.com/NGnius/PowerTools/issues\n" +"POT-Creation-Date: 2023-01-12 \n" +"PO-Revision-Date: 2023-01-12 \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# -- index.tsx -- + +#: index.tsx:226 +# (Section title) +msgid "Miscellaneou" +msgstr "其他选项" + +#: index.tsx:226 +# (Profile persistence toggle) +msgid "Persistent Profile" +msgstr "持久" + +#: index.tsx:227 +# (Profile persistence toggle description) +msgid "Save profile and load it next time" +msgstr "保存配置并自动加载" + +#: index.tsx:239 +# (Profile display) +msgid "Profile" +msgstr "配置文件" + +# -- components/battery.tsx -- + +#: components/battery.tsx:42 +# (Battery section title) +msgid "Battery" +msgstr "电池" + +#: components/battery.tsx:46 +# (Charge of battery at this moment, with percentage of expected full charge in brackets) +msgid "Now (Charge)" +msgstr "充电中 (Charge)" + +#: components/battery.tsx:52 +# (Maximum capacity of battery, with percentage of design capacity in brackets) +msgid "Max (Design)" +msgstr "最大电量 (Design)" + +#: components/battery.tsx:59 +# (Charge current limit override toggle) +msgid "Charge Current Limits" +msgstr "充电电流限制" + +#: components/battery.tsx:60 +# (Charge current limit override toggle description) +msgid "Control battery charge rate when awake" +msgstr "控制电池充电效率" + +#: components/battery.tsx:74 +# (Battery maximum input current with unit) +msgid "Maximum (mA)" +msgstr "最大 (mA)" + +#: components/battery.tsx:97,115 +# (Battery charge mode override toggle) +msgid "Charge Mode" +msgstr "充电模式" + +#: components/battery.tsx:98 +# (Battery charge mode override toggle description) +msgid "Force battery charge mode" +msgstr "强制电池充电模式" + +#: components/battery.tsx:112 +# (Battery charge mode dropdown) +msgid "Mode" +msgstr "模式" + +#: components/battery.tsx:133 +# (Battery current display) +msgid "Current" +msgstr "当前" + +# -- components/cpus.tsx -- + +#: components/cpus.tsx:64 +# (CPU section title) +msgid "CPU" +msgstr "CPU" + +#: components/cpus.tsx:70 +# (CPU advanced mode toggle) +msgid "Advanced" +msgstr "高级" + +#: components/cpus.tsx:71 +# (CPU advanced mode toggle description) +msgid "Enables per-thread configuration" +msgstr "线程设置" + +#: components/cpus.tsx:88 +# (CPU Simultaneous MultiThreading toggle) +msgid "SMT" +msgstr "超线程" + +#: components/cpus.tsx:89 +# (CPU SMT toggle description) +msgid "Enables odd-numbered CPUs" +msgstr "启用超线程" + +#: components/cpus.tsx:106 +# (CPU thread count slider) +msgid "Threads" +msgstr "线程数" + +#: components/cpus.tsx:137 +#: components/gpu.tsx:112 +# (Clock speed override toggle) +msgid "Frequency Limits" +msgstr "频率限制" + +#: components/cpus.tsx:138 +#: components/gpu.tsx:113 +# (Clock speed override toggle description) +msgid "Set bounds on clock speed" +msgstr "设置频率" + +#: components/cpus.tsx:165 +#: components/gpu.tsx:137 +# (Minimum clock speed with unit) +msgid "Minimum (MHz)" +msgstr "最小 (MHz)" + +#: components/cpus.tsx:195 +#: components/gpu.tsx:160 +# (Maximum clock speed with unit) +msgid "Maximum (MHz)" +msgstr "最大 (MHz)" + +# advanced mode + +#: components/cpus.tsx:226 +# (CPU selection slider) +msgid "Selected CPU" +msgstr "选择 CPU" + +#: components/cpus.tsx:246 +# (CPU Online toggle) +msgid "Online" +msgstr "在线" + +#: components/cpus.tsx:247 +# (CPU Online description) +msgid "Allow the CPU thread to do work" +msgstr "允许 CPU 工作线程" + +#: components/cpus.tsx:342 +# (CPU scheduling governor dropdown -- governor names are not translated) +msgid "Governor" +msgstr "稳压" + +# -- components/debug.tsx -- + +#: components/debug.tsx:29 +# (Debug section title) +msgid "Debug" +msgstr "Debug" + +#: components/debug.tsx:33 +# (Version display for native back-end of PowerTools) +msgid "Native" +msgstr "本地版本" + +#: components/debug.tsx:47 +# (Mode display for framework of USDPL API) +msgid "Framework" +msgstr "API 框架" + +#: components/debug.tsx:54 +# (Display for software implementation in PowerTools which applies settings) +msgid "Driver" +msgstr "驱动程序" + +# -- components/gpu.tsx -- + +#: components/gpu.tsx:34 +# (GPU section title) +msgid "GPU" +msgstr "GPU" + +#: components/gpu.tsx:39 +# (PPT Limits override toggle) +msgid "PowerPlay Limits" +msgstr "PowerPlay 限制" + +#: components/gpu.tsx:40 +# (PPT Limits override toggle description) +msgid "Override APU TDP settings" +msgstr "覆盖 APU TDP 设置" + +#: components/gpu.tsx:63 +# (SlowPPT slider with unit) +msgid "SlowPPT (W)" +msgstr "SlowPPT (W)" + +#: components/gpu.tsx:87 +# (FastPPT slider with unit) +msgid "FastPPT (W)" +msgstr "FastPPT (W)" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle) +msgid "Downclock Memory" +msgstr "降低内存频率" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle description) +msgid "Force RAM into low-power mode" +msgstr "强制内存进入低功耗模式" diff --git a/translations/zh-HK.mo b/translations/zh-HK.mo new file mode 100644 index 0000000..b31cfc2 Binary files /dev/null and b/translations/zh-HK.mo differ diff --git a/translations/zh-HK.po b/translations/zh-HK.po new file mode 100644 index 0000000..1840a4b --- /dev/null +++ b/translations/zh-HK.po @@ -0,0 +1,229 @@ +# TEMPLATE TITLE. +# Copyright (C) 2023 NGnius +# This file is distributed under the same license as the PowerTools package. +# NGnius (Graham) , 2023. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: v1.1\n" +"Report-Msgid-Bugs-To: https://github.com/NGnius/PowerTools/issues\n" +"POT-Creation-Date: 2023-01-12 \n" +"PO-Revision-Date: 2023-01-12 \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: zh_HK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# -- index.tsx -- + +#: index.tsx:226 +# (Section title) +msgid "Miscellaneou" +msgstr "其他選項" + +#: index.tsx:226 +# (Profile persistence toggle) +msgid "Persistent Profile" +msgstr "持續" + +#: index.tsx:227 +# (Profile persistence toggle description) +msgid "Save profile and load it next time" +msgstr "保存配置並自動載入" + +#: index.tsx:239 +# (Profile display) +msgid "Profile" +msgstr "配置文件" + +# -- components/battery.tsx -- + +#: components/battery.tsx:42 +# (Battery section title) +msgid "Battery" +msgstr "電池" + +#: components/battery.tsx:46 +# (Charge of battery at this moment, with percentage of expected full charge in brackets) +msgid "Now (Charge)" +msgstr "此刻 (正在充電中)" + +#: components/battery.tsx:52 +# (Maximum capacity of battery, with percentage of design capacity in brackets) +msgid "Max (Design)" +msgstr "最大電量 (設計)" + +#: components/battery.tsx:59 +# (Charge current limit override toggle) +msgid "Charge Current Limits" +msgstr "充電電流限制" + +#: components/battery.tsx:60 +# (Charge current limit override toggle description) +msgid "Control battery charge rate when awake" +msgstr "控制電池充電效率" + +#: components/battery.tsx:74 +# (Battery maximum input current with unit) +msgid "Maximum (mA)" +msgstr "最大 (mA)" + +#: components/battery.tsx:97,115 +# (Battery charge mode override toggle) +msgid "Charge Mode" +msgstr "充電模式" + +#: components/battery.tsx:98 +# (Battery charge mode override toggle description) +msgid "Force battery charge mode" +msgstr "強制電池充電模式" + +#: components/battery.tsx:112 +# (Battery charge mode dropdown) +msgid "Mode" +msgstr "模式" + +#: components/battery.tsx:133 +# (Battery current display) +msgid "Current" +msgstr "當前" + +# -- components/cpus.tsx -- + +#: components/cpus.tsx:64 +# (CPU section title) +msgid "CPU" +msgstr "CPU" + +#: components/cpus.tsx:70 +# (CPU advanced mode toggle) +msgid "Advanced" +msgstr "進階" + +#: components/cpus.tsx:71 +# (CPU advanced mode toggle description) +msgid "Enables per-thread configuration" +msgstr "線程設定" + +#: components/cpus.tsx:88 +# (CPU Simultaneous MultiThreading toggle) +msgid "SMT" +msgstr "同步多執行緒" + +#: components/cpus.tsx:89 +# (CPU SMT toggle description) +msgid "Enables odd-numbered CPUs" +msgstr "啟用多執行緒CPU" + +#: components/cpus.tsx:106 +# (CPU thread count slider) +msgid "Threads" +msgstr "線程數" + +#: components/cpus.tsx:137 +#: components/gpu.tsx:112 +# (Clock speed override toggle) +msgid "Frequency Limits" +msgstr "頻率限制" + +#: components/cpus.tsx:138 +#: components/gpu.tsx:113 +# (Clock speed override toggle description) +msgid "Set bounds on clock speed" +msgstr "設定頻率" + +#: components/cpus.tsx:165 +#: components/gpu.tsx:137 +# (Minimum clock speed with unit) +msgid "Minimum (MHz)" +msgstr "最小 (MHz)" + +#: components/cpus.tsx:195 +#: components/gpu.tsx:160 +# (Maximum clock speed with unit) +msgid "Maximum (MHz)" +msgstr "最大 (MHz)" + +# advanced mode + +#: components/cpus.tsx:226 +# (CPU selection slider) +msgid "Selected CPU" +msgstr "選擇 CPU" + +#: components/cpus.tsx:246 +# (CPU Online toggle) +msgid "Online" +msgstr "線上" + +#: components/cpus.tsx:247 +# (CPU Online description) +msgid "Allow the CPU thread to do work" +msgstr "允许CPU工作線程" + +#: components/cpus.tsx:342 +# (CPU scheduling governor dropdown -- governor names are not translated) +msgid "Governor" +msgstr "穩壓" + +# -- components/debug.tsx -- + +#: components/debug.tsx:29 +# (Debug section title) +msgid "Debug" +msgstr "除錯" + +#: components/debug.tsx:33 +# (Version display for native back-end of PowerTools) +msgid "Native" +msgstr "原生版本" + +#: components/debug.tsx:47 +# (Mode display for framework of USDPL API) +msgid "Framework" +msgstr "API 架構" + +#: components/debug.tsx:54 +# (Display for software implementation in PowerTools which applies settings) +msgid "Driver" +msgstr "驅動程式" + +# -- components/gpu.tsx -- + +#: components/gpu.tsx:34 +# (GPU section title) +msgid "GPU" +msgstr "GPU" + +#: components/gpu.tsx:39 +# (PPT Limits override toggle) +msgid "PowerPlay Limits" +msgstr "PowerPlay 限制" + +#: components/gpu.tsx:40 +# (PPT Limits override toggle description) +msgid "Override APU TDP settings" +msgstr "覆蓋 APU TDP 設定" + +#: components/gpu.tsx:63 +# (SlowPPT slider with unit) +msgid "SlowPPT (W)" +msgstr "SlowPPT (W)" + +#: components/gpu.tsx:87 +# (FastPPT slider with unit) +msgid "FastPPT (W)" +msgstr "FastPPT (W)" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle) +msgid "Downclock Memory" +msgstr "降低記憶體頻率" + +#: components/gpu.tsx:112 +# (Reduce memory clock speed toggle description) +msgid "Force RAM into low-power mode" +msgstr "強制記憶體進入低功率消耗模式"